From 314f53bdfcee4ca920a144541629cd2fb8e6409b Mon Sep 17 00:00:00 2001 From: "Alex A. Naanou" Date: Sat, 30 Nov 2013 05:43:08 +0400 Subject: [PATCH] added the editor to the main ui, data still not saved (experimental mode)... Signed-off-by: Alex A. Naanou --- ui/css/editor.css | 270 +++++++++++++++++++++++++++++ ui/experiments/editor.html | 11 ++ ui/experiments/editor.js | 13 +- ui/ext-lib/jquery-ui.js | 7 + ui/index.html | 3 + ui/keybindings.js | 26 ++- ui/lib/editor.js | 344 +++++++++++++++++++++++++++++++++++++ 7 files changed, 661 insertions(+), 13 deletions(-) create mode 100755 ui/css/editor.css create mode 100755 ui/ext-lib/jquery-ui.js create mode 100755 ui/lib/editor.js diff --git a/ui/css/editor.css b/ui/css/editor.css new file mode 100755 index 00000000..0604d140 --- /dev/null +++ b/ui/css/editor.css @@ -0,0 +1,270 @@ +.panel { + display: inline-block; + min-width: 200px; + max-width: 450px; + + font-size: 12px; + + border: solid 2px silver; + border-radius: 4px; + + background: white; + box-shadow: 5px 5px 30px -5px rgba(0, 0, 0, 0.5); + opacity: 0.95; + + overflow: visible; +} +.panel summary { + padding-left: 3px; + background: silver +} +.panel summary::-webkit-details-marker { + color: gray; +} +.panel .close-button { + display: inline-block; + position: absolute; + right: 5px; + cursor: hand; +} +.panel .close-button:hover { + font-weight: bold; + color: red; + text-shadow: 0px 0px 2px rgba(0, 0, 0, 0.5); +} +.panel button, +.panel details, +.panel .state { + margin: 1px; + font-size: 11px; + border: solid 1px #aaa; + border-radius: 4px; + /* needed for dragging */ + background: white; +} +.panel details { + margin: 3px; + border: solid 1px silver; + box-shadow: none; +} +.panel details summary { + background: #ddd; + /* + background: white; + box-shadow: 0px 0px 50px -5px rgba(0, 0, 0, 0.4); + */ +} +.panel .sub-panel-content { + margin: 10px; +} + +.panel button:active, +.panel .state:active { + background: silver; +} + + +/* main controls */ +.panel .control { + white-space:nowrap; +} +.panel .control .title { + display: inline-block; + width: 60px; + cursor: move; +} +.panel .control .slider { + -webkit-appearance: none !important; + width: 150px; + height: 3px; + border: solid 1px #ccc; + border-radius: 2px; + background: white; +} +.panel .control.at-default .slider { +} +.panel .control .slider::-webkit-slider-thumb { + -webkit-appearance: none !important; + height: 13px; + width: 13px; + /*border: solid 1px gray;*/ + border: solid 2px #aaa; + border-radius: 50%; + background: white; + box-shadow: 1px 1px 10px 0px rgba(0, 0, 0, 0.3); +} +.panel .control.at-default .slider::-webkit-slider-thumb { + opacity: 0.5; +} +.panel .control .value { + -webkit-appearance: none !important; + display: inline-block; + width: 25px; + text-align: right; + font-size: 11px; + margin-left: 5px; + margin-right: 5px; + border: none; + border-radius: 2px; + background: transparent; +} +.panel .control input::-webkit-outer-spin-button, +.panel .control input::-webkit-inner-spin-button { + -webkit-appearance: none !important; +} +.panel .control .reset { + visibility: hidden; + border: solid 1px transparent; +} +.panel .control:hover button.reset { + visibility: visible; +} +.panel .control .reset:hover { + border: solid 1px silver; +} + + +/* Snapshots */ +.panel .state { + display: inline-block; + margin: 1px; + padding-left: 5px; + padding-right: 5px; +} +.panel .state.ui-draggable-dragging { + box-shadow: 2px 2px 10px -2px rgba(0, 0, 0, 0.4); +} +.panel .states { + min-height: 30px; +} + + +/* misc */ +.panel hr { + border: none; + border-top: solid 1px silver; +} + + + +/* dark theme */ +.dark .panel { + border: solid 2px #333; + background: black; + color: silver; + box-shadow: 3px 3px 30px 0px rgba(0, 0, 0, 0.5); +} +.dark .panel summary { + background: #333; +} +.dark .panel summary::-webkit-details-marker { + color: #555; +} +.dark .panel button, +.dark .panel .state, +.dark .panel details { + border: solid 1px #333; + /* needed for dragging */ + background: #080808; + color: #888; +} +.dark .panel details { + border: solid 1px #333; +} +.dark .panel details summary { + background: #333; + color: silver; +} +.dark .panel .state:active, +.dark .panel button:active { + background: #222; +} +.dark .panel .control .slider { + border: solid 1px #555; + background: black; +} +.dark .panel .control.at-default .slider { +} +.dark .panel .control .slider::-webkit-slider-thumb { + border: solid 2px #aaa; + background: black; + box-shadow: 1px 1px 10px 0px rgba(0, 0, 0, 0.5); +} +.dark .panel .control.at-default .slider::-webkit-slider-thumb { + border: solid 1px gray; + opacity: 0.5; +} +.dark .panel .control .value { + border: none; + background: transparent; + color: gray; +} +.dark .panel .control .reset:hover { + border: solid 1px #333; +} +.dark .panel hr { + border: none; + border-top: solid 1px #333; +} + + +/* gray theme */ + +.gray .panel { + border: solid 2px #444; + background: #333; + color: silver; + box-shadow: 3px 3px 30px 0px rgba(0, 0, 0, 0.5); +} +.gray .panel summary { + background: #444; +} +.gray .panel summary::-webkit-details-marker { + color: #555; +} +.gray .panel button, +.gray .panel .state, +.gray .panel details { + border: solid 1px #444; + /* needed for dragging */ + background: #333; + color: #888; +} +.gray .panel details { + border: solid 1px #454545; +} +.gray .panel details summary { + background: #444; + color: silver; +} +.gray .panel .state:active, +.gray .panel button:active { + background: #444; +} +.gray .panel .control .slider { + border: solid 1px #555; + background: #222; +} +.gray .panel .control.at-default .slider { +} +.gray .panel .control .slider::-webkit-slider-thumb { + border: solid 2px #aaa; + background: #333; +} +.gray .panel .control.at-default .slider::-webkit-slider-thumb { + border: solid 1px gray; + opacity: 0.5; +} +.gray .panel .control .value { + border: none; + background: transparent; + color: gray; +} +.gray .panel .control .reset:hover { + border: solid 1px #444; +} +.gray .panel hr { + border: none; + border-top: solid 1px #444; +} + diff --git a/ui/experiments/editor.html b/ui/experiments/editor.html index 7b5b68a8..df274c2e 100755 --- a/ui/experiments/editor.html +++ b/ui/experiments/editor.html @@ -21,6 +21,17 @@ body.gray { + + diff --git a/ui/keybindings.js b/ui/keybindings.js index 6b59b8c6..6c81c1c7 100755 --- a/ui/keybindings.js +++ b/ui/keybindings.js @@ -700,7 +700,31 @@ var KEYBOARD_CONFIG = { }), */ - E: doc('Open image in external software', openImage), + E: { + default: doc('Open image in external software', openImage), + ctrl: doc('Open preview editor panel (Experimental)', + function(){ + if($('.panel').length == 0){ + $('.viewer') + .append(makeControls('.current.image') + .addClass('noScroll')) + .on('focusingImage', function(){ + if($('.panel').css('display') != 'none'){ + reloadControls('.current.image') + } + }) + reloadControls('.current.image') + } else { + var ed = $('.panel') + if(ed.css('display') == 'none'){ + reloadControls('.current.image') + ed.show() + } else { + ed.hide() + } + } + }), + }, // XXX make F4 a default editor and E a default viewer... F4: 'E', diff --git a/ui/lib/editor.js b/ui/lib/editor.js new file mode 100755 index 00000000..ba1db64c --- /dev/null +++ b/ui/lib/editor.js @@ -0,0 +1,344 @@ +/********************************************************************** +* +* +**********************************************************************/ + +var DEFAULT_FILTER_ORDER = [ + 'brightness', + 'contrast', + 'saturate', + 'hue-rotate', + 'grayscale', + 'invert', + 'sepia' +] + +var SLIDER_SCALE = 43.47 + + + + +/*********************************************************************/ + +function r2v(r){ + return Math.pow(Math.E, r/SLIDER_SCALE) +} + +function v2r(v){ + return Math.log(v)*SLIDER_SCALE +} + + + +/*********************************************************************/ + +// Update filter in target image... +// +function updateFilter(e, f, v, order){ + e = $(e) + var state = e + .css('-webkit-filter') + state = state == 'none' ? '' : state + ' ' + // update existing filter... + if(RegExp(f).test(state)){ + state = state.replace(RegExp(f+'\\s*\\([^\\)]*\\)'), f+'('+v+')') + // add new filter... + } else { + state += f+'('+v+')' + state = sortFilterStr(state, order) + } + e.css({ + '-webkit-filter': state, + }) + return v +} +function resetFilter(e, f){ + e = $(e) + var state = e + .css('-webkit-filter') + state = state == 'none' ? '' : state + ' ' + state = state.replace(RegExp(f+'\\s*\\([^\\)]*\\)'), '').trim() + e.css({ + '-webkit-filter': state, + }) + return e +} + + +function getSliderOrder(){ + return $('.filter-list').sortable('toArray') +} +// NOTE: this will return only the set filters... +function getFilterOrder(target){ + return $(target) + .css('-webkit-filter') + .split(/\s*\([^\)]*\)\s*/g) + .slice(0, -1) +} + + +function sortFilterStr(state, order){ + order = order == null ? getSliderOrder() : order + state = state.split(/\s+/) + state.sort(function(a, b){ + a = order.indexOf(a.replace(/\(.*/, '')) + b = order.indexOf(b.replace(/\(.*/, '')) + return a - b + }) + return state.join(' ') +} +function sortFilterSliders(order){ + return $('.filter-list').sortChildren(function(a, b){ + a = order.indexOf(a.id) + b = order.indexOf(b.id) + return a - b + }) +} + + +// Load state of sliders from target... +// +function loadSliderState(target){ + var res = $(target) + .css('-webkit-filter') + var state = res + .split(/\s*\(\s*|\s*\)\s*/g) + .reverse() + .slice(1) + // reset sliders to defaults... + $('input[type=range]').each(function(i, e){ + e = $(e) + e.val(e.attr('default')).change() + }) + // set the saved values... + while(state.length > 0){ + // XXX avoid using ids... + var e = $('[filter='+state.pop()+']') + if(e.prop('normalize')){ + e.val(v2r(parseFloat(state.pop()))).change() + } else { + e.val(parseFloat(state.pop())).change() + } + } + return res +} + + +function saveSnapshot(target){ + var l = $('.state').last().text() + l = l == '' ? 0 : parseInt(l)+1 + var state = $(target).css('-webkit-filter') + $('
') + .text(l) + .addClass('state') + .attr({ + state: state, + sliders: getSliderOrder().join(' ') + }) + // load state... + .click(function(){ + loadSliderState($(target).css('-webkit-filter', state)) + sortFilterSliders($(this).attr('sliders').split(' ')) + }) + .appendTo($('.states')) + .draggable({ + revert: 'invalid', + revertDuration: 200, + }) +} +function clearSnapshots(){ + $('.state').remove() +} + + +// Re-read filters form target image and reset the controls... +// +function reloadControls(target){ + clearSnapshots() + var state = loadSliderState(target) + + // nothing set -- default sort... + if(state == 'none'){ + sortFilterSliders(DEFAULT_FILTER_ORDER) + + // load existing sort state... + } else { + sortFilterSliders(getFilterOrder(target).concat(DEFAULT_FILTER_ORDER)) + } + // make a snapshot... + saveSnapshot(target) +} + + + +/********************************************************************** +* Element constructors... +*/ + +function makeAbsRange(text, filter, target, min, max, dfl, step, translate, normalize){ + min = min == null ? 0 : min + max = max == null ? 1 : max + dfl = dfl == null ? min : dfl + step = step == null ? 0.01 : step + translate = translate == null ? function(v){return v} : translate + normalize = normalize == null ? false : true + + var elem = $('
') + .attr({ + id: filter, + }) + $('') + .html(text) + .appendTo(elem) + var range = $('') + .attr({ + filter: filter, + min: min, + max: max, + step: step, + default: dfl, + }) + .prop('normalize', normalize) + .val(dfl) + .change(function(){ + var val = this.valueAsNumber + value.val(val) + updateFilter(target, filter, translate(val)) + if(parseFloat(val) == dfl){ + elem.addClass('at-default') + } else { + elem.removeClass('at-default') + } + }) + .appendTo(elem) + var value = $('') + .attr({ + min: min, + max: max, + step: step, + }) + .val(dfl) + .change(function(){ + range.val($(this).val()).change() + }) + .appendTo(elem) + $('') + .click(function(){ + range.val(dfl).change() + resetFilter(target, filter) + }) + .appendTo(elem) + return elem +} +function makeLogRange(text, filter, target){ + return makeAbsRange(text, filter, target, -100, 100, 0, 0.1, r2v, true) +} + + +function makeControls(target){ + // tool panel... + var panel = $('
') + .addClass('panel') + .css({ + position: 'absolute', + top: '100px', + left: '100px', + }) + .append($('Edit') + .append($('') + .addClass('close-button') + .click(function(){ + $(this).parents('.panel').hide() + return false + }) + .html('×'))) + .draggable({ + containment: 'parent', + scroll: false, + }) + + // wrapper for sub-panels... + var content = $('') + .sortable({ + forcePlaceholderSize: true, + start: function(e, ui){ + ui.placeholder.height(ui.helper.outerHeight()); + ui.placeholder.width(ui.helper.outerWidth()); + }, + opacity: 0.7, + }) + .appendTo(panel) + + // filters... + $('
') + .append($('Filters')) + .append($('
') + .append($('
') + .append(makeLogRange('Brightness:', 'brightness', target)) + .append(makeLogRange('Contrast:', 'contrast', target)) + .append(makeLogRange('Saturation:', 'saturate', target)) + .append(makeAbsRange('Hue:', 'hue-rotate', target, + -180, 180, 0, 0.5, function(v){ return v+'deg' })) + .append(makeAbsRange('Grayscale:', 'grayscale', target)) + .append(makeAbsRange('Invert:', 'invert', target)) + .append(makeAbsRange('Sepia:', 'sepia', target)) + .sortable({ + axis: 'y', + }) + .on('sortstop', function(){ + // update image filter order... + var img = $(target) + img.css('-webkit-filter', sortFilterStr(img.css('-webkit-filter'))) + })) + .append($('
')) + .append('Reset: ') + .append($('') + .click(function(){ + $('.reset').click() + })) + .append($('') + .click(function(){ + sortFilterSliders(DEFAULT_FILTER_ORDER) + })) + .append($('') + .click(function(){ + $('.reset').click() + sortFilterSliders(DEFAULT_FILTER_ORDER) + }))) + .appendTo(content) + + // snapshots... + $('
') + .append($('Snapshots')) + .append($('
') + .append($('
')) + .append($('
')) + .append($('