From cd36a57a1acbe16a1cf116c8d36574c0186b872e Mon Sep 17 00:00:00 2001 From: Julian Descottes Date: Sat, 2 Jul 2016 12:45:48 +0200 Subject: [PATCH] export controller: add synchronized rows input, remove poweroftwo checkbox --- src/css/forms.css | 4 + src/css/settings-export.css | 17 +-- .../exportimage/PngExportController.js | 101 ++++++++++-------- src/js/utils/UserSettings.js | 2 - src/js/widgets/SizeInput.js | 33 ++---- src/js/widgets/SynchronizedInputs.js | 40 +++++++ src/piskel-script-list.js | 1 + src/templates/settings/export/gif.html | 6 +- src/templates/settings/export/misc.html | 4 +- src/templates/settings/export/png.html | 20 ++-- src/templates/settings/export/zip.html | 2 +- 11 files changed, 139 insertions(+), 91 deletions(-) create mode 100644 src/js/widgets/SynchronizedInputs.js diff --git a/src/css/forms.css b/src/css/forms.css index e7663793..47778c72 100644 --- a/src/css/forms.css +++ b/src/css/forms.css @@ -14,6 +14,10 @@ height: 23px; } +.textfield[readonly="true"] { + background: transparent; +} + .textfield[disabled=disabled] { background : #3a3a3a; } diff --git a/src/css/settings-export.css b/src/css/settings-export.css index 593584b1..182aa534 100644 --- a/src/css/settings-export.css +++ b/src/css/settings-export.css @@ -137,11 +137,14 @@ border-width: 1px; } -.export-info { +.export-panel-header { padding: 10px 5px 0px; - font-weight: normal; - text-shadow: none; +} + +.export-info { font-style: italic; + text-shadow: none; + font-weight: normal; } .export-panel-section { @@ -167,13 +170,13 @@ width: 50px; } -.png-export-dimension-info { - font-style: italic; - font-weight: normal; +.png-export-dimension-info, +.png-export-datauri-info { margin-left: 5px; } -#png-export-columns { +#png-export-columns, +#png-export-rows { /* Override default textfield padding-right to keep the number spinners aligned to the right. */ padding-right: 0; diff --git a/src/js/controller/settings/exportimage/PngExportController.js b/src/js/controller/settings/exportimage/PngExportController.js index 838426be..158a9cda 100644 --- a/src/js/controller/settings/exportimage/PngExportController.js +++ b/src/js/controller/settings/exportimage/PngExportController.js @@ -1,12 +1,13 @@ (function () { var ns = $.namespace('pskl.controller.settings.exportimage'); - var dimensionInfoPattern = '{{width}} x {{height}} px, {{frames}}
{{rows}}, {{columns}}.'; + var dimensionInfoPattern = '{{width}} x {{height}} px, {{frames}}
{{columns}}, {{rows}}.'; // Shortcut to pskl.utils.Template.replace var replace = pskl.utils.Template.replace; - // Helper to return "X items" or "1 item" if X is 1. + // Helper to return "X items" or "1 item" if X is 1. Can be cnsidered as an overkill, + // but the one-liner equivalent is hard to read. var pluralize = function (word, count) { if (count === 1) { return '1 ' + word; @@ -14,11 +15,6 @@ return count + ' ' + word + 's'; }; - // Compute the nearest power of two for the provided number. - var getNearestPowerOfTwo = function (number) { - return Math.pow(2, Math.ceil(Math.log(number) / Math.log(2))); - }; - ns.PngExportController = function (piskelController, exportController) { this.piskelController = piskelController; this.exportController = exportController; @@ -30,16 +26,19 @@ ns.PngExportController.prototype.init = function () { this.layoutContainer = document.querySelector('.png-export-layout-section'); this.dimensionInfo = document.querySelector('.png-export-dimension-info'); + + this.rowsInput = document.querySelector('#png-export-rows'); this.columnsInput = document.querySelector('#png-export-columns'); - this.powerTwo = document.querySelector('#png-export-power-two'); + var downloadButton = document.querySelector('.png-download-button'); + var dataUriButton = document.querySelector('.datauri-open-button'); this.initLayoutSection_(); this.updateDimensionLabel_(); + this.addEventListener(this.columnsInput, 'input', this.onColumnsInput_); this.addEventListener(downloadButton, 'click', this.onDownloadClick_); - this.addEventListener(this.columnsInput, 'input', this.onColumnsChanged_); - this.addEventListener(this.powerTwo, 'change', this.onPowerTwoChanged_); + this.addEventListener(dataUriButton, 'click', this.onDataUriClick_); $.subscribe(Events.EXPORT_SCALE_CHANGED, this.onScaleChanged_); }; @@ -57,8 +56,9 @@ // Hide the layout section if only one frame is defined. this.layoutContainer.style.display = 'none'; } else { + this.columnsInput.setAttribute('max', frames); this.columnsInput.value = this.getBestFit_(); - this.powerTwo.checked = pskl.UserSettings.get('EXPORT_PNG_POWER_TWO'); + this.onColumnsInput_(); } }; @@ -69,15 +69,10 @@ var height = this.piskelController.getHeight() * zoom; var columns = this.getColumns_(); - var rows = Math.ceil(frames / columns); + var rows = this.getRows_(); width = columns * width; height = rows * height; - if (this.isPowerTwoEnabled_()) { - width = getNearestPowerOfTwo(width); - height = getNearestPowerOfTwo(height); - } - this.dimensionInfo.innerHTML = replace(dimensionInfoPattern, { width: width, height: height, @@ -91,42 +86,51 @@ return parseInt(this.columnsInput.value || 1, 10); }; + ns.PngExportController.prototype.getRows_ = function () { + return parseInt(this.rowsInput.value || 1, 10); + }; + ns.PngExportController.prototype.getBestFit_ = function () { var ratio = this.piskelController.getWidth() / this.piskelController.getHeight(); var frameCount = this.piskelController.getFrameCount(); var bestFit = Math.round(Math.sqrt(frameCount / ratio)); - return Math.max(1, Math.min(bestFit, frameCount)); - }; - - ns.PngExportController.prototype.isPowerTwoEnabled_ = function () { - return pskl.UserSettings.get('EXPORT_PNG_POWER_TWO'); + return pskl.utils.Math.minmax(bestFit, 1, frameCount); }; ns.PngExportController.prototype.onScaleChanged_ = function () { this.updateDimensionLabel_(); }; - ns.PngExportController.prototype.onColumnsChanged_ = function () { - if (this.getColumns_() > this.piskelController.getFrameCount()) { - this.columnsInput.value = this.piskelController.getFrameCount(); - } else if (this.getColumns_() < 1) { - this.columnsInput.value = 1; + /** + * Synchronise column and row inputs, called everytime a user input updates one of the + * two inputs by the SynchronizedInputs widget. + */ + ns.PngExportController.prototype.onColumnsInput_ = function () { + var value = this.columnsInput.value; + if (value === '') { + // Skip the synchronization if the input is empty. + return; } + + value = parseInt(value, 10); + if (isNaN(value)) { + value = 1; + } + + // Force the value to be in bounds, in the user tried to update it by directly typing + // a value. + value = pskl.utils.Math.minmax(value, 1, this.piskelController.getFrameCount()); + this.columnsInput.value = value; + + // Update readonly rowsInput + this.rowsInput.value = Math.ceil(this.piskelController.getFrameCount() / value); this.updateDimensionLabel_(); }; - ns.PngExportController.prototype.onPowerTwoChanged_ = function () { - pskl.UserSettings.set('EXPORT_PNG_POWER_TWO', this.powerTwo.checked); - this.updateDimensionLabel_(); - }; - - ns.PngExportController.prototype.onDownloadClick_ = function (evt) { - var name = this.piskelController.getPiskel().getDescriptor().name; - var fileName = name + '.png'; - + ns.PngExportController.prototype.createPngSpritesheet_ = function () { var renderer = new pskl.rendering.PiskelRenderer(this.piskelController); - var outputCanvas = renderer.renderAsCanvas(this.getColumns_()); + var outputCanvas = renderer.renderAsCanvas(this.getColumns_(), this.getRows_()); var width = outputCanvas.width; var height = outputCanvas.height; @@ -135,15 +139,24 @@ outputCanvas = pskl.utils.ImageResizer.resize(outputCanvas, width * zoom, height * zoom, false); } - if (this.isPowerTwoEnabled_()) { - var paddingCanvas = pskl.utils.CanvasUtils.createCanvas( - getNearestPowerOfTwo(width * zoom), getNearestPowerOfTwo(height * zoom)); - paddingCanvas.getContext('2d').drawImage(outputCanvas, 0, 0); - outputCanvas = paddingCanvas; - } + return outputCanvas; + }; - pskl.utils.BlobUtils.canvasToBlob(outputCanvas, function(blob) { + ns.PngExportController.prototype.onDownloadClick_ = function (evt) { + // Create PNG export. + var canvas = this.createPngSpritesheet_(); + + // Generate file name + var name = this.piskelController.getPiskel().getDescriptor().name; + var fileName = name + '.png'; + + // Transform to blob and start download. + pskl.utils.BlobUtils.canvasToBlob(canvas, function(blob) { pskl.utils.FileUtils.downloadAsFile(blob, fileName); }); }; + + ns.PngExportController.prototype.onDataUriClick_ = function (evt) { + window.open(this.createPngSpritesheet_().toDataURL('image/png')); + }; })(); diff --git a/src/js/utils/UserSettings.js b/src/js/utils/UserSettings.js index a72a9faf..fcb9fe1b 100644 --- a/src/js/utils/UserSettings.js +++ b/src/js/utils/UserSettings.js @@ -12,7 +12,6 @@ ONION_SKIN : 'ONION_SKIN', LAYER_PREVIEW : 'LAYER_PREVIEW', LAYER_OPACITY : 'LAYER_OPACITY', - EXPORT_PNG_POWER_TWO: 'EXPORT_PNG_POWER_TWO', EXPORT_SCALE: 'EXPORT_SCALE', EXPORT_TAB: 'EXPORT_TAB', PEN_SIZE : 'PEN_SIZE', @@ -31,7 +30,6 @@ 'ONION_SKIN' : false, 'LAYER_OPACITY' : 0.2, 'LAYER_PREVIEW' : true, - 'EXPORT_PNG_POWER_TWO' : false, 'EXPORT_SCALE' : 1, 'EXPORT_TAB' : 'gif', 'PEN_SIZE' : 1, diff --git a/src/js/widgets/SizeInput.js b/src/js/widgets/SizeInput.js index 80a7109b..46d4a0b5 100644 --- a/src/js/widgets/SizeInput.js +++ b/src/js/widgets/SizeInput.js @@ -19,31 +19,24 @@ this.initHeight = options.initHeight; this.onChange = options.onChange; - this.syncEnabled = true; - this.lastInput = this.widthInput; + this.synchronizedInputs = new ns.SynchronizedInputs({ + leftInput: this.widthInput, + rightInput: this.heightInput, + synchronize: this.synchronize_.bind(this) + }); + + this.disableSync = this.synchronizedInputs.disableSync.bind(this.synchronizedInputs); + this.enableSync = this.synchronizedInputs.enableSync.bind(this.synchronizedInputs); this.widthInput.value = this.initWidth; this.heightInput.value = this.initHeight; - - pskl.utils.Event.addEventListener(this.widthInput, 'keyup', this.onSizeInputKeyUp_, this); - pskl.utils.Event.addEventListener(this.heightInput, 'keyup', this.onSizeInputKeyUp_, this); }; ns.SizeInput.prototype.destroy = function () { - pskl.utils.Event.removeAllEventListeners(this); + this.synchronizedInputs.destroy(); this.widthInput = null; this.heightInput = null; - this.lastInput = null; - }; - - ns.SizeInput.prototype.enableSync = function () { - this.syncEnabled = true; - this.synchronize_(this.lastInput); - }; - - ns.SizeInput.prototype.disableSync = function () { - this.syncEnabled = false; }; ns.SizeInput.prototype.setWidth = function (width) { @@ -56,14 +49,6 @@ this.synchronize_(this.heightInput); }; - ns.SizeInput.prototype.onSizeInputKeyUp_ = function (evt) { - var target = evt.target; - if (this.syncEnabled) { - this.synchronize_(target); - } - this.lastInput = target; - }; - /** * Based on the value of the provided sizeInput (considered as emitter) * update the value of the other sizeInput to match the current width/height ratio diff --git a/src/js/widgets/SynchronizedInputs.js b/src/js/widgets/SynchronizedInputs.js new file mode 100644 index 00000000..479515f2 --- /dev/null +++ b/src/js/widgets/SynchronizedInputs.js @@ -0,0 +1,40 @@ +(function () { + var ns = $.namespace('pskl.widgets'); + + ns.SynchronizedInputs = function (options) { + this.leftInput = options.leftInput; + this.rightInput = options.rightInput; + this.synchronize = options.synchronize; + + this.syncEnabled = true; + this.lastInput = this.leftInput; + + pskl.utils.Event.addEventListener(this.leftInput, 'input', this.onInput_, this); + pskl.utils.Event.addEventListener(this.rightInput, 'input', this.onInput_, this); + }; + + ns.SynchronizedInputs.prototype.destroy = function () { + pskl.utils.Event.removeAllEventListeners(this); + + this.leftInput = null; + this.rightInput = null; + this.lastInput = null; + }; + + ns.SynchronizedInputs.prototype.enableSync = function () { + this.syncEnabled = true; + this.synchronize(this.lastInput); + }; + + ns.SynchronizedInputs.prototype.disableSync = function () { + this.syncEnabled = false; + }; + + ns.SynchronizedInputs.prototype.onInput_ = function (evt) { + var target = evt.target; + if (this.syncEnabled) { + this.synchronize(target); + } + this.lastInput = target; + }; +})(); diff --git a/src/piskel-script-list.js b/src/piskel-script-list.js index f9f2fafd..364c14e0 100644 --- a/src/piskel-script-list.js +++ b/src/piskel-script-list.js @@ -142,6 +142,7 @@ "js/widgets/ColorsList.js", "js/widgets/HslRgbColorPicker.js", "js/widgets/SizeInput.js", + "js/widgets/SynchronizedInputs.js", // Services "js/service/storage/StorageService.js", diff --git a/src/templates/settings/export/gif.html b/src/templates/settings/export/gif.html index 73d7d566..8446ab90 100644 --- a/src/templates/settings/export/gif.html +++ b/src/templates/settings/export/gif.html @@ -1,15 +1,15 @@ \ No newline at end of file diff --git a/src/templates/settings/export/zip.html b/src/templates/settings/export/zip.html index 362d0fa7..1562a8bf 100644 --- a/src/templates/settings/export/zip.html +++ b/src/templates/settings/export/zip.html @@ -1,6 +1,6 @@