diff --git a/css/forms.css b/css/forms.css new file mode 100644 index 00000000..8cc1dd53 --- /dev/null +++ b/css/forms.css @@ -0,0 +1,69 @@ +.textfield { + background : black; + border : 1px solid #888; + border-radius : 2px; + padding : 3px 10px; + color : white; +} + +.textfield[disabled=disabled] { + background : #3a3a3a; +} + +.textfield-small { + width : 50px; +} + +.button { + height: 24px; + box-sizing: border-box; + + background-color: #3f3f3f; + border: 1px solid #333; + border-top-color: #666; + border-bottom-color: #222; + + cursor: pointer; + text-decoration: none; + + color: white; + text-shadow: 0px -1px 0 black; + font-weight: bold; + font-size: 1rem; + text-align: center; + + transition: background-color 0.2s linear; +} + +.button:hover { + text-decoration: none; + background-color: #484848; + color: gold; +} + +.button-primary { + background-color: rgb(255,215,0); /* gold */ + + border-color: rgb(179, 164, 0); + border-top-color: white; + border-bottom-color: rgb(151, 133, 0); + + color: black; + text-shadow: 0px 1px 0 #fff; +} + +.button-primary:hover { + background-color: rgb(255,235,20); + color: #333; +} + +.button[disabled=disabled], +.button[disabled=disabled]:hover { + cursor:default; + background-color: #aaa; + color: #777; + text-shadow: 0px 1px 0 #bbb; + border-color: #666; + border-top-color: #999; + border-bottom-color: #555; +} \ No newline at end of file diff --git a/css/settings.css b/css/settings.css index d872702a..5b58f9bd 100644 --- a/css/settings.css +++ b/css/settings.css @@ -31,14 +31,14 @@ } .right-sticky-section.expanded .tool-icon { - margin-right: 1px; + padding-right: 1px; } .right-sticky-section .tool-icon.has-expanded-drawer { position: relative; background-color: #444; margin-right: 0; - padding-right: 1px; + padding-right: 2px; } .settings-section { @@ -81,7 +81,7 @@ top: -2px; right: -2px; bottom: -2px; - left: -2px; + left: -2px; } .background-picker:hover:after { @@ -93,11 +93,18 @@ } /* Gif Export Setting panel*/ -.export-gif-upload-button { +.gif-upload-button, +.gif-render-button { + /*float : right;*/ margin-top : 10px; + margin-right : 10px; } -.export-gif-preview { +.gif-export-radio-group { + margin:10px 0; +} + +.gif-export-preview { margin-top:20px; max-width:240px; position:relative; @@ -115,4 +122,46 @@ -moz-box-sizing:border-box; background: rgba(0,0,0,0.5); color: white; +} + +/* Import panel */ +.import-section { + margin: 15px 0; +} + +.import-section-disabled { + color : #888; +} + +.import-section-preview { + display : inline-block; + height : 60px; + width: 60px; + border : 1px dashed #999; + border-radius: 3px; + margin-left : 10px; +} + +.import-resize-field { + width: 30px; + margin: 0px 8px; + text-align: right; +} + +.file-input-button { + margin: 0px 8px; + border-radius: 2px; +} + +.file-input-status { + display: inline-block; + width: 130px; + overflow: hidden; + + height: 1.5rem; + word-break : break-all; + vertical-align: middle; + font-style: italic; + font-weight: normal; + text-shadow: none; } \ No newline at end of file diff --git a/css/style.css b/css/style.css index 1b810419..841f5325 100644 --- a/css/style.css +++ b/css/style.css @@ -1,5 +1,3 @@ - - body { background: radial-gradient(circle, #000, #373737); /* 16/06/2013 : -webkit still needed for @@ -8,6 +6,11 @@ body { background: -webkit-radial-gradient(circle, #000, #373737); } +/* Browser fixes */ +::-ms-clear { + display: none; +} + /** * Application layout */ @@ -246,35 +249,19 @@ body { } .layers-button { - height: 24px; margin: 0; width: 25%; float : left; - border: none; - box-sizing: border-box; - border-top: 1px solid #666; - border-right: 1px solid #333; - border-bottom: 1px solid #333; - background-color: #3f3f3f; - cursor: pointer; - color: white; - font-size: 0.7em; - font-weight: bold; - text-align: center; - text-decoration: none; - text-shadow: 0px -1px 0 #000; - transition: background-color 0.2s linear; +} + +/* @override */ +.button.layers-button { + border-left-width: 0; } .layers-button:last-child { border-right-width: 0; } - -.layers-button:hover { - text-decoration: none; - background-color: #484848; - color: gold; -} /** * User messages */ diff --git a/css/tools.css b/css/tools.css index 3ee94ddf..664221a5 100644 --- a/css/tools.css +++ b/css/tools.css @@ -25,7 +25,7 @@ .tool-icon:hover { background-color: #444; -} +} /* * Tool icons: @@ -209,6 +209,13 @@ position: relative; } +.tool-icon.import-icon { + background-image: url(../img/import-icon.png); + background-position: 10px 5px; + background-size: 26px; + position: relative; +} + .upload-cloud-icon .label { position: absolute; left: 0; diff --git a/img/import-icon.png b/img/import-icon.png new file mode 100644 index 00000000..ed310656 Binary files /dev/null and b/img/import-icon.png differ diff --git a/index.html b/index.html index 50c1ddd6..309d86f3 100644 --- a/index.html +++ b/index.html @@ -9,6 +9,7 @@ + @@ -45,8 +46,9 @@
- - + + +
diff --git a/js/Events.js b/js/Events.js index 64985864..e7e5e7ea 100644 --- a/js/Events.js +++ b/js/Events.js @@ -39,6 +39,9 @@ var Events = { */ USER_SETTINGS_CHANGED: "USER_SETTINGS_CHANGED", + /* Listened to by SettingsController */ + CLOSE_SETTINGS_DRAWER : "CLOSE_SETTINGS_DRAWER", + /** * The framesheet was reseted and is now probably drastically different. * Number of frames, content of frames, color used for the palette may have changed. diff --git a/js/app.js b/js/app.js index def736df..3262db09 100644 --- a/js/app.js +++ b/js/app.js @@ -33,7 +33,7 @@ this.layersListController = new pskl.controller.LayersListController(this.piskelController); this.layersListController.init(); - this.settingsController = new pskl.controller.SettingsController(this.piskelController); + this.settingsController = new pskl.controller.settings.SettingsController(this.piskelController); this.settingsController.init(); this.selectionManager = new pskl.selection.SelectionManager(this.piskelController); @@ -94,7 +94,7 @@ finishInitAppEngine_ : function () { if (pskl.framesheetData_ && pskl.framesheetData_.content) { var piskel = pskl.utils.Serializer.createPiskel(pskl.framesheetData_.content); - piskel.app.PiskelController.setPiskel(piskel); + pskl.app.piskelController.setPiskel(piskel); pskl.app.animationController.setFPS(pskl.framesheetData_.fps); } }, diff --git a/js/controller/settings/GifExportController.js b/js/controller/settings/GifExportController.js index 58ed6ca6..42e57d3d 100644 --- a/js/controller/settings/GifExportController.js +++ b/js/controller/settings/GifExportController.js @@ -24,9 +24,9 @@ ]; ns.GifExportController.prototype.init = function () { - this.radioTemplate_ = pskl.utils.Template.get("export-gif-radio-template"); + this.radioTemplate_ = pskl.utils.Template.get("gif-export-radio-template"); - this.previewContainerEl = document.querySelectorAll(".export-gif-preview div")[0]; + this.previewContainerEl = document.querySelectorAll(".gif-export-preview")[0]; this.radioGroupEl = document.querySelectorAll(".gif-export-radio-group")[0]; this.uploadForm = $("[name=gif-export-upload-form]"); @@ -56,7 +56,7 @@ }; ns.GifExportController.prototype.updatePreview_ = function (src) { - this.previewContainerEl.innerHTML = ""; + this.previewContainerEl.innerHTML = "
"; }; ns.GifExportController.prototype.getSelectedDpi_ = function () { diff --git a/js/controller/settings/ImportController.js b/js/controller/settings/ImportController.js new file mode 100644 index 00000000..f28166f9 --- /dev/null +++ b/js/controller/settings/ImportController.js @@ -0,0 +1,168 @@ +(function () { + var ns = $.namespace('pskl.controller.settings'); + var DEFAULT_FILE_STATUS = 'No file selected ...'; + var PREVIEW_HEIGHT = 60; + + ns.ImportController = function (piskelController) { + this.piskelController = piskelController; + this.importedImage_ = null; + }; + + ns.ImportController.prototype.init = function () { + this.importForm = $("[name=import-form]"); + this.hiddenFileInput = $("[name=file-upload-input]"); + this.fileInputButton = $(".file-input-button"); + this.fileInputStatus = $(".file-input-status"); + this.fileInputStatus.html(DEFAULT_FILE_STATUS); + + this.importPreview = $(".import-section-preview"); + + this.resizeWidth = $("[name=resize-width]"); + this.resizeHeight = $("[name=resize-height]"); + this.smoothResize = $("[name=smooth-resize-checkbox]"); + this.submitButton = $("[name=import-submit]"); + + this.importForm.submit(this.onImportFormSubmit_.bind(this)); + this.hiddenFileInput.change(this.onFileUploadChange_.bind(this)); + this.fileInputButton.click(this.onFileInputClick_.bind(this)); + + this.resizeWidth.keyup(this.onResizeInputKeyUp_.bind(this, 'width')); + this.resizeHeight.keyup(this.onResizeInputKeyUp_.bind(this, 'height')); + }; + + ns.ImportController.prototype.reset_ = function () { + this.importForm.get(0).reset(); + this.fileInputStatus.html(DEFAULT_FILE_STATUS); + $.publish(Events.CLOSE_SETTINGS_DRAWER); + }; + + ns.ImportController.prototype.onResizeInputKeyUp_ = function (from, evt) { + if (this.importedImage_) { + this.synchronizeResizeFields_(evt.target.value, from); + } + }; + + ns.ImportController.prototype.synchronizeResizeFields_ = function (value, from) { + value = parseInt(value, 10); + if (isNaN(value)) { + value = 0; + } + var height = this.importedImage_.height, width = this.importedImage_.width; + if (from === 'width') { + this.resizeHeight.val(Math.round(value * height / width)); + } else { + this.resizeWidth.val(Math.round(value * width / height)); + } + }; + + ns.ImportController.prototype.onImportFormSubmit_ = function (evt) { + evt.originalEvent.preventDefault(); + this.importImageToPiskel_(); + }; + + ns.ImportController.prototype.onFileUploadChange_ = function (evt) { + this.importFromFile_(); + }; + + ns.ImportController.prototype.onFileInputClick_ = function (evt) { + this.hiddenFileInput.click(); + }; + + ns.ImportController.prototype.importFromFile_ = function () { + var files = this.hiddenFileInput.get(0).files; + if (files.length == 1) { + var file = files[0]; + if (this.isImage_(file)) { + this.readImageFile_(file); + this.enableDisabledSections_(); + } else { + this.reset_(); + throw "File is not an image : " + file.type; + } + } + }; + + ns.ImportController.prototype.enableDisabledSections_ = function () { + this.resizeWidth.removeAttr('disabled'); + this.resizeHeight.removeAttr('disabled'); + this.smoothResize.removeAttr('disabled'); + this.submitButton.removeAttr('disabled'); + + this.fileInputButton.removeClass('button-primary'); + this.fileInputButton.blur(); + + $('.import-section-disabled').removeClass('import-section-disabled'); + }; + + ns.ImportController.prototype.readImageFile_ = function (imageFile) { + pskl.utils.FileUtils.readFile(imageFile, this.processImageSource_.bind(this)); + }; + + /** + * Create an image from the given source (url or data-url), and onload forward to onImageLoaded + * TODO : should be a generic utility method, should take a callback + * @param {String} imageSource url or data-url, will be used as src for the image + */ + ns.ImportController.prototype.processImageSource_ = function (imageSource) { + this.importedImage_ = new Image(); + this.importedImage_.onload = this.onImageLoaded_.bind(this); + this.importedImage_.src = imageSource; + }; + + ns.ImportController.prototype.onImageLoaded_ = function (evt) { + var w = this.importedImage_.width, + h = this.importedImage_.height; + var filePath = this.hiddenFileInput.val(); + var fileName = this.extractFileNameFromPath_(filePath); + this.fileInputStatus.html(fileName); + + this.resizeWidth.val(w); + this.resizeHeight.val(h); + + this.importPreview.width("auto"); + this.importPreview.append(this.createImagePreview_()); + }; + + ns.ImportController.prototype.createImagePreview_ = function () { + var image = document.createElement('IMG'); + image.src = this.importedImage_.src; + image.setAttribute('height', PREVIEW_HEIGHT); + return image; + }; + + ns.ImportController.prototype.extractFileNameFromPath_ = function (path) { + var parts = []; + if (path.indexOf('/') !== -1) { + parts = path.split('/'); + } else if (path.indexOf('\\') !== -1) { + parts = path.split('\\'); + } else { + parts = [path]; + } + return parts[parts.length-1]; + }; + + ns.ImportController.prototype.importImageToPiskel_ = function () { + if (this.importedImage_) { + if (window.confirm("You are about to create a new Piskel, unsaved changes will be lost.")) { + var w = this.resizeWidth.val(), + h = this.resizeHeight.val(), + smoothing = !!this.smoothResize.prop('checked'); + + var image = pskl.utils.ImageResizer.resize(this.importedImage_, w, h, smoothing); + var frame = pskl.utils.FrameUtils.createFromImage(image); + + var piskel = pskl.utils.Serializer.createPiskel([frame]); + pskl.app.piskelController.setPiskel(piskel); + pskl.app.animationController.setFPS(Constants.DEFAULT.FPS); + + this.reset_(); + } + } + }; + + ns.ImportController.prototype.isImage_ = function (file) { + return file.type.indexOf('image') === 0; + }; + +})(); \ No newline at end of file diff --git a/js/controller/SettingsController.js b/js/controller/settings/SettingsController.js similarity index 79% rename from js/controller/SettingsController.js rename to js/controller/settings/SettingsController.js index 611eebf2..708b3fa1 100644 --- a/js/controller/SettingsController.js +++ b/js/controller/settings/SettingsController.js @@ -1,14 +1,18 @@ (function () { - var ns = $.namespace("pskl.controller"); - + var ns = $.namespace("pskl.controller.settings"); + var settings = { - user : { - template : 'templates/settings-application.html', - controller : ns.settings.ApplicationSettingsController + 'user' : { + template : 'templates/settings/application.html', + controller : ns.ApplicationSettingsController }, - gif : { - template : 'templates/settings-export-gif.html', - controller : ns.settings.GifExportController + 'gif' : { + template : 'templates/settings/export-gif.html', + controller : ns.GifExportController + }, + 'import' : { + template : 'templates/settings/import.html', + controller : ns.ImportController } }; @@ -44,14 +48,16 @@ this.closeDrawer(); } }.bind(this)); + + $.subscribe(Events.CLOSE_SETTINGS_DRAWER, this.closeDrawer.bind(this)); }; ns.SettingsController.prototype.loadSetting = function (setting) { this.drawerContainer.innerHTML = pskl.utils.Template.get(settings[setting].template); (new settings[setting].controller(this.piskelController)).init(); - + this.settingsContainer.addClass(EXP_DRAWER_CLS); - + $('.' + SEL_SETTING_CLS).removeClass(SEL_SETTING_CLS); $('[data-setting='+setting+']').addClass(SEL_SETTING_CLS); @@ -66,5 +72,5 @@ this.expanded = false; this.currentSetting = null; }; - + })(); \ No newline at end of file diff --git a/js/utils/FileUtils.js b/js/utils/FileUtils.js new file mode 100644 index 00000000..74f25892 --- /dev/null +++ b/js/utils/FileUtils.js @@ -0,0 +1,13 @@ +(function () { + var ns = $.namespace('pskl.utils'); + + ns.FileUtils = { + readFile : function (file, callback) { + var reader = new FileReader(); + reader.onload = function(event){ + callback(event.target.result); + }; + reader.readAsDataURL(file); + } + }; +})(); \ No newline at end of file diff --git a/js/utils/FrameUtils.js b/js/utils/FrameUtils.js index cb615527..ef14f935 100644 --- a/js/utils/FrameUtils.js +++ b/js/utils/FrameUtils.js @@ -20,6 +20,62 @@ frameA.setPixel(col, row, p); } }); + }, + + /** + * Create a pskl.model.Frame from an Image object. + * Transparent pixels will either be converted to completely opaque or completely transparent pixels. + * @param {Image} image source image + * @return {pskl.model.Frame} corresponding frame + */ + createFromImage : function (image) { + var w = image.width, + h = image.height; + var canvas = pskl.CanvasUtils.createCanvas(w, h); + var context = canvas.getContext('2d'); + + context.drawImage(image, 0,0,w,h,0,0,w,h); + var imgData = context.getImageData(0,0,w,h).data; + // Draw the zoomed-up pixels to a different canvas context + var frame = []; + for (var x=0;x + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/templates/layers-list.html b/templates/layers-list.html index 12636bfe..f9ccfec1 100644 --- a/templates/layers-list.html +++ b/templates/layers-list.html @@ -1,10 +1,10 @@

Layers

- - - - + + + +
- + + -
+
\ No newline at end of file diff --git a/templates/settings/import.html b/templates/settings/import.html new file mode 100644 index 00000000..405cd092 --- /dev/null +++ b/templates/settings/import.html @@ -0,0 +1,31 @@ +
+
+ Import Picture +
+
+
+
+ File : + + + +
+
+ Preview : +
+
+
+ Size : + x + +
+
+ Smooth resize : + +
+ +
+
+
\ No newline at end of file