diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..7f2ef5de --- /dev/null +++ b/.travis.yml @@ -0,0 +1,3 @@ +language: node_js +node_js: + - 0.6 \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..fd40ada0 --- /dev/null +++ b/Makefile @@ -0,0 +1,2 @@ +test: + jshint js/*.js \ No newline at end of file diff --git a/js/Constants.js b/js/Constants.js index 8ef4e4da..1f676f0e 100644 --- a/js/Constants.js +++ b/js/Constants.js @@ -1,19 +1,19 @@ var Constants = { - DEFAULT_PEN_COLOR : '#000000', - TRANSPARENT_COLOR : 'TRANSPARENT', - - /* - * Fake semi-transparent color used to highlight transparent - * strokes and rectangles: - */ - SELECTION_TRANSPARENT_COLOR: 'rgba(255, 255, 255, 0.6)', - - /* - * Default entry point for piskel web service: - */ - PISKEL_SERVICE_URL: 'http://2.piskel-app.appspot.com', + DEFAULT_PEN_COLOR : '#000000', + TRANSPARENT_COLOR : 'TRANSPARENT', + + /* + * Fake semi-transparent color used to highlight transparent + * strokes and rectangles: + */ + SELECTION_TRANSPARENT_COLOR: 'rgba(255, 255, 255, 0.6)', + + /* + * Default entry point for piskel web service: + */ + PISKEL_SERVICE_URL: 'http://2.piskel-app.appspot.com', - GRID_STROKE_WIDTH: 1, - GRID_STROKE_COLOR: "lightgray" + GRID_STROKE_WIDTH: 1, + GRID_STROKE_COLOR: "lightgray" }; \ No newline at end of file diff --git a/js/Events.js b/js/Events.js index ce13ed7a..bb8ea4e5 100644 --- a/js/Events.js +++ b/js/Events.js @@ -1,42 +1,40 @@ Events = { - - TOOL_SELECTED : "TOOL_SELECTED", - TOOL_RELEASED : "TOOL_RELEASED", - COLOR_SELECTED: "COLOR_SELECTED", - COLOR_USED: "COLOR_USED", + + TOOL_SELECTED : "TOOL_SELECTED", + TOOL_RELEASED : "TOOL_RELEASED", + COLOR_SELECTED: "COLOR_SELECTED", - /** - * When this event is emitted, a request is sent to the localstorage - * Service to save the current framesheet. The storage service - * may not immediately store data (internal throttling of requests). - */ - LOCALSTORAGE_REQUEST: "LOCALSTORAGE_REQUEST", + /** + * When this event is emitted, a request is sent to the localstorage + * Service to save the current framesheet. The storage service + * may not immediately store data (internal throttling of requests). + */ + LOCALSTORAGE_REQUEST: "LOCALSTORAGE_REQUEST", - CANVAS_RIGHT_CLICKED: "CANVAS_RIGHT_CLICKED", - CANVAS_RIGHT_CLICK_RELEASED: "CANVAS_RIGHT_CLICK_RELEASED", + CANVAS_RIGHT_CLICKED: "CANVAS_RIGHT_CLICKED", - /** - * Event to request a refresh of the display. - * A bit overkill but, it's just workaround in our current drawing system. - * TODO: Remove or rework when redraw system is refactored. - */ - REFRESH: "REFRESH", + /** + * Event to request a refresh of the display. + * A bit overkill but, it's just workaround in our current drawing system. + * TODO: Remove or rework when redraw system is refactored. + */ + REFRESH: "REFRESH", - /** - * Temporary event to bind the redraw of right preview film to the canvas. - * This redraw should be driven by model updates. - * TODO(vincz): Remove. - */ - REDRAW_PREVIEWFILM: "REDRAW_PREVIEWFILM", + /** + * Temporary event to bind the redraw of right preview film to the canvas. + * This redraw should be driven by model updates. + * TODO(vincz): Remove. + */ + REDRAW_PREVIEWFILM: "REDRAW_PREVIEWFILM", - GRID_DISPLAY_STATE_CHANGED: "GRID_DISPLAY_STATE_CHANGED", + GRID_DISPLAY_STATE_CHANGED: "GRID_DISPLAY_STATE_CHANGED", - /** - * The framesheet was reseted and is now probably drastically different. - * Number of frames, content of frames, color used for the palette may have changed. - */ - FRAMESHEET_RESET: "FRAMESHEET_RESET", - - SHOW_NOTIFICATION: "SHOW_NOTIFICATION", - HIDE_NOTIFICATION: "HIDE_NOTIFICATION" + /** + * The framesheet was reseted and is now probably drastically different. + * Number of frames, content of frames, color used for the palette may have changed. + */ + FRAMESHEET_RESET: "FRAMESHEET_RESET", + + SHOW_NOTIFICATION: "SHOW_NOTIFICATION", + HIDE_NOTIFICATION: "HIDE_NOTIFICATION" }; \ No newline at end of file diff --git a/js/HistoryManager.js b/js/HistoryManager.js index 4a3c5c4b..d6a92208 100644 --- a/js/HistoryManager.js +++ b/js/HistoryManager.js @@ -1,40 +1,40 @@ (function () { var ns = $.namespace("pskl"); - ns.HistoryManager = function () {}; + ns.HistoryManager = function (framesheet) { + this.framesheet = framesheet; + }; ns.HistoryManager.prototype.init = function () { - document.body.addEventListener('keyup', this.onKeyup.bind(this)); - $.subscribe(Events.TOOL_RELEASED, this.saveState.bind(this)); + document.body.addEventListener('keyup', this.onKeyup.bind(this)); + $.subscribe(Events.TOOL_RELEASED, this.saveState.bind(this)); }; - ns.HistoryManager.prototype.saveState = function () { - piskel.getCurrentFrame().saveState(); - }; + ns.HistoryManager.prototype.saveState = function () { + this.framesheet.getCurrentFrame().saveState(); + }; ns.HistoryManager.prototype.onKeyup = function (evt) { - if (evt.ctrlKey && evt.keyCode == 90) { // CTRL + Z - this.undo(); - } + if (evt.ctrlKey && evt.keyCode == 90) { // CTRL + Z + this.undo(); + } - if (evt.ctrlKey && evt.keyCode == 89) { // CTRL+ Y - this.redo(); - } - }; + if (evt.ctrlKey && evt.keyCode == 89) { // CTRL+ Y + this.redo(); + } + }; - ns.HistoryManager.prototype.undo = function () { - piskel.getCurrentFrame().loadPreviousState(); - this.redraw(); - }; + ns.HistoryManager.prototype.undo = function () { + this.framesheet.getCurrentFrame().loadPreviousState(); + this.redraw(); + }; - ns.HistoryManager.prototype.redo = function () { - piskel.getCurrentFrame().loadNextState(); - this.redraw(); - }; + ns.HistoryManager.prototype.redo = function () { + this.framesheet.getCurrentFrame().loadNextState(); + this.redraw(); + }; - ns.HistoryManager.prototype.redraw = function () { - piskel.drawingController.renderFrame(); - piskel.previewsController.createPreviews(); - }; - - ns.HistoryManager = new ns.HistoryManager(); + ns.HistoryManager.prototype.redraw = function () { + this.framesheet.drawingController.renderFrame(); + this.framesheet.previewsController.createPreviews(); + }; })(); \ No newline at end of file diff --git a/js/LocalStorageService.js b/js/LocalStorageService.js index d697adf7..63cc3b59 100644 --- a/js/LocalStorageService.js +++ b/js/LocalStorageService.js @@ -8,20 +8,20 @@ $.namespace("pskl"); pskl.LocalStorageService = (function() { - var frameSheet_; + var frameSheet_; - /** - * @private - */ - var localStorageThrottler_ = null; - - /** - * @private - */ - var persistToLocalStorageRequest_ = function() { + /** + * @private + */ + var localStorageThrottler_ = null; + + /** + * @private + */ + var persistToLocalStorageRequest_ = function() { // Persist to localStorage when drawing. We throttle localStorage accesses // for high frequency drawing (eg mousemove). - if(localStorageThrottler_ != null) { + if(localStorageThrottler_ !== null) { window.clearTimeout(localStorageThrottler_); } localStorageThrottler_ = window.setTimeout(function() { @@ -31,65 +31,63 @@ pskl.LocalStorageService = (function() { }; /** - * @private - */ - var persistToLocalStorage_ = function() { - console.log('[LocalStorage service]: Snapshot stored') - window.localStorage['snapShot'] = frameSheet_.serialize(); + * @private + */ + var persistToLocalStorage_ = function() { + console.log('[LocalStorage service]: Snapshot stored'); + window.localStorage.snapShot = frameSheet_.serialize(); }; /** - * @private - */ - var restoreFromLocalStorage_ = function() { - frameSheet_.deserialize(window.localStorage['snapShot']); - // Model updated, redraw everything: - $.publish(Events.REFRESH); + * @private + */ + var restoreFromLocalStorage_ = function() { + frameSheet_.deserialize(window.localStorage.snapShot); }; /** - * @private - */ - var cleanLocalStorage_ = function() { - console.log('[LocalStorage service]: Snapshot removed') - delete window.localStorage['snapShot']; + * @private + */ + var cleanLocalStorage_ = function() { + console.log('[LocalStorage service]: Snapshot removed'); + delete window.localStorage.snapShot; }; - return { - init: function(frameSheet) { + return { + init: function(frameSheet) { - if(frameSheet == undefined) { - throw "Bad LocalStorageService initialization: " - } - frameSheet_ = frameSheet; + if(frameSheet === undefined) { + throw "Bad LocalStorageService initialization: "; + } + frameSheet_ = frameSheet; - $.subscribe(Events.LOCALSTORAGE_REQUEST, persistToLocalStorageRequest_); - }, + $.subscribe(Events.LOCALSTORAGE_REQUEST, persistToLocalStorageRequest_); + }, - // TODO(vincz): Find a good place to put this UI rendering, a service should not render UI. - displayRestoreNotification: function() { - if(window.localStorage && window.localStorage['snapShot']) { - var reloadLink = "reload"; - var discardLink = "discard"; - var content = "Non saved version found. " + reloadLink + " or " + discardLink; + // TODO(vincz): Find a good place to put this UI rendering, a service should not render UI. + displayRestoreNotification: function() { + if(window.localStorage && window.localStorage.snapShot) { + var reloadLink = "reload"; + var discardLink = "discard"; + var content = "Non saved version found. " + reloadLink + " or " + discardLink; - $.publish(Events.SHOW_NOTIFICATION, [{ - "content": content, - "behavior": function(rootNode) { - rootNode = $(rootNode); - rootNode.click(function(evt) { - var target = $(evt.target); - if(target.hasClass("localstorage-restore")) { - restoreFromLocalStorage_(); - } - else if (target.hasClass("localstorage-discard")) { - cleanLocalStorage_(); - } - $.publish(Events.HIDE_NOTIFICATION); - }); - } - }]); - } - } - }; + $.publish(Events.SHOW_NOTIFICATION, [{ + "content": content, + "behavior": function(rootNode) { + rootNode = $(rootNode); + rootNode.click(function(evt) { + var target = $(evt.target); + if(target.hasClass("localstorage-restore")) { + restoreFromLocalStorage_(); + } + else if (target.hasClass("localstorage-discard")) { + cleanLocalStorage_(); + } + $.publish(Events.HIDE_NOTIFICATION); + }); + } + }]); + } + } + }; })(); \ No newline at end of file diff --git a/js/Palette.js b/js/Palette.js index 7f1ffb6c..8460e398 100644 --- a/js/Palette.js +++ b/js/Palette.js @@ -7,37 +7,37 @@ $.namespace("pskl"); pskl.Palette = (function() { - - var paletteRoot, - paletteColors = []; + + var paletteRoot, + paletteColors = []; - /** - * @private - */ - var onPickerChange_ = function(evt, isPrimary) { + /** + * @private + */ + var onPickerChange_ = function(evt, isPrimary) { var inputPicker = $(evt.target); $.publish(Events.COLOR_SELECTED, [inputPicker.val(), evt.data.isPrimary]); - }; + }; - /** - * @private - */ - var createPalette_ = function (colors) { - // Always adding transparent color - paletteRoot.html(''); - for(var color in colors) { - if(color != Constants.TRANSPARENT_COLOR) { - addColorToPalette_(color); - } - } - }; + /** + * @private + */ + var createPalette_ = function (colors) { + // Always adding transparent color + paletteRoot.html(''); + for(var color in colors) { + if(color != Constants.TRANSPARENT_COLOR) { + addColorToPalette_(color); + } + } + }; - /** - * @private - */ - var addColorToPalette_ = function (color) { + /** + * @private + */ + var addColorToPalette_ = function (color) { if (paletteColors.indexOf(color) == -1 && color != Constants.TRANSPARENT_COLOR) { - var colorEl = document.createElement("li"); + var colorEl = document.createElement("li"); colorEl.className = "palette-color"; colorEl.setAttribute("data-color", color); colorEl.setAttribute("title", color); @@ -77,37 +77,37 @@ pskl.Palette = (function() { } else { colorPicker[0].color.fromString(color); } - } + }; - return { - init: function(framesheet) { - - paletteRoot = $("#palette"); + return { + init: function(framesheet) { + + paletteRoot = $("#palette"); - // Initialize palette: - createPalette_(framesheet.getUsedColors()); + // Initialize palette: + createPalette_(framesheet.getUsedColors()); - $.subscribe(Events.FRAMESHEET_RESET, function(evt) { - createPalette_(framesheet.getUsedColors()); - }); + $.subscribe(Events.FRAMESHEET_RESET, function(evt) { + createPalette_(framesheet.getUsedColors()); + }); - paletteRoot.mouseup(onPaletteColorClick_); - $.subscribe(Events.COLOR_SELECTED, function(evt, color) { - addColorToPalette_(color); - }); + paletteRoot.mouseup(onPaletteColorClick_); + $.subscribe(Events.COLOR_SELECTED, function(evt, color) { + addColorToPalette_(color); + }); - // Initialize colorpicker: - var colorPicker = $('#color-picker'); - colorPicker.val(Constants.DEFAULT_PEN_COLOR); - colorPicker.change({isPrimary : true}, onPickerChange_); + // Initialize colorpicker: + var colorPicker = $('#color-picker'); + colorPicker.val(Constants.DEFAULT_PEN_COLOR); + colorPicker.change({isPrimary : true}, onPickerChange_); var secondaryColorPicker = $('#secondary-color-picker'); secondaryColorPicker.val(Constants.TRANSPARENT_COLOR); secondaryColorPicker.change({isPrimary : false}, onPickerChange_); - } - }; + } + }; })(); diff --git a/js/ToolSelector.js b/js/ToolSelector.js index 21122552..78d4f36d 100644 --- a/js/ToolSelector.js +++ b/js/ToolSelector.js @@ -9,93 +9,83 @@ $.namespace("pskl"); pskl.ToolSelector = (function() { - var toolInstances = { - "simplePen" : new pskl.drawingtools.SimplePen(), - "eraser" : new pskl.drawingtools.Eraser(), - "paintBucket" : new pskl.drawingtools.PaintBucket(), - "stroke" : new pskl.drawingtools.Stroke(), - "rectangle" : new pskl.drawingtools.Rectangle(), - "move" : new pskl.drawingtools.Move(), - "select" : new pskl.drawingtools.Select() - }; - var currentSelectedTool = toolInstances.simplePen; - var previousSelectedTool = toolInstances.simplePen; + var toolInstances = { + "simplePen" : new pskl.drawingtools.SimplePen(), + "eraser" : new pskl.drawingtools.Eraser(), + "paintBucket" : new pskl.drawingtools.PaintBucket(), + "stroke" : new pskl.drawingtools.Stroke(), + "rectangle" : new pskl.drawingtools.Rectangle(), + "move" : new pskl.drawingtools.Move(), + "select" : new pskl.drawingtools.Select() + }; + var currentSelectedTool = toolInstances.simplePen; + var previousSelectedTool = toolInstances.simplePen; - var selectTool_ = function(tool) { - var maincontainer = $("body"); - var previousSelectedToolClass = maincontainer.data("selected-tool-class"); - if(previousSelectedToolClass) { - maincontainer.removeClass(previousSelectedToolClass); - } - maincontainer.addClass(toolBehavior.toolId); - $("#drawing-canvas-container").data("selected-tool-class", toolBehavior.toolId); - }; - - var activateToolOnStage_ = function(tool) { - var stage = $("body"); + var activateToolOnStage_ = function(tool) { + var stage = $("body"); var previousSelectedToolClass = stage.data("selected-tool-class"); if(previousSelectedToolClass) { stage.removeClass(previousSelectedToolClass); } stage.addClass(tool.toolId); stage.data("selected-tool-class", tool.toolId); - }; + }; - var selectTool_ = function(tool) { - console.log("Selecting Tool:" , currentSelectedTool); - currentSelectedTool = tool; - activateToolOnStage_(currentSelectedTool); - $.publish(Events.TOOL_SELECTED, [tool]); - }; + var selectTool_ = function(tool) { + console.log("Selecting Tool:" , currentSelectedTool); + currentSelectedTool = tool; + activateToolOnStage_(currentSelectedTool); + $.publish(Events.TOOL_SELECTED, [tool]); + }; - /** - * @private - */ - var onToolIconClicked_ = function(evt) { - var target = $(evt.target); - var clickedTool = target.closest(".tool-icon"); + /** + * @private + */ + var onToolIconClicked_ = function(evt) { + var target = $(evt.target); + var clickedTool = target.closest(".tool-icon"); - if(clickedTool.length) { - for(var tool in toolInstances) { - if (toolInstances[tool].toolId == clickedTool.data()["toolId"]) { - selectTool_(toolInstances[tool]); + if(clickedTool.length) { + for(var tool in toolInstances) { + if (toolInstances[tool].toolId == clickedTool.data().toolId) { + selectTool_(toolInstances[tool]); - // Show tool as selected: - $("#tools-container .tool-icon.selected").removeClass("selected"); - clickedTool.addClass("selected"); - } - } - } - }; + // Show tool as selected: + $("#tools-container .tool-icon.selected").removeClass("selected"); + clickedTool.addClass("selected"); + } + } + } + }; - /** - * Get state for the checkbox that control the display of the grid - * on the drawing canvas. - * @private - */ - var isShowGridChecked_ = function() { - var showGridCheckbox = $('#show-grid'); - var isChecked = showGridCheckbox.is(':checked'); - return isChecked; - }; + /** + * Get state for the checkbox that control the display of the grid + * on the drawing canvas. + * @private + */ + var isShowGridChecked_ = function() { + var showGridCheckbox = $('#show-grid'); + var isChecked = showGridCheckbox.is(':checked'); + return isChecked; + }; - return { - init: function() { - - // Initialize tool: - // Set SimplePen as default selected tool: - selectTool_(toolInstances.simplePen); - // Activate listener on tool panel: - $("#tools-container").click(onToolIconClicked_); + return { + init: function() { + + // Initialize tool: + // Set SimplePen as default selected tool: + selectTool_(toolInstances.simplePen); + // Activate listener on tool panel: + $("#tools-container").click(onToolIconClicked_); - // Show/hide the grid on drawing canvas: - $.publish(Events.GRID_DISPLAY_STATE_CHANGED, [isShowGridChecked_()]) - $('#show-grid').change(function(evt) { - var checked = isShowGridChecked_(); - $.publish(Events.GRID_DISPLAY_STATE_CHANGED, [checked]) - }); - } - }; + // Show/hide the grid on drawing canvas: + $.publish(Events.GRID_DISPLAY_STATE_CHANGED, [isShowGridChecked_()]); + $('#show-grid').change(function(evt) { + var checked = isShowGridChecked_(); + $.publish(Events.GRID_DISPLAY_STATE_CHANGED, [checked]); + }); + } + }; })(); diff --git a/js/controller/DrawingController.js b/js/controller/DrawingController.js index 145d83d8..e77e4e85 100644 --- a/js/controller/DrawingController.js +++ b/js/controller/DrawingController.js @@ -1,8 +1,6 @@ (function () { var ns = $.namespace("pskl.controller"); - ns.DrawingController = function (frame, container, dpi) { - this.dpi = dpi; - + ns.DrawingController = function (framesheet, container, dpi) { // TODO(vincz): Store user prefs in a localstorage string ? var renderingOptions = { "dpi": dpi, @@ -12,29 +10,22 @@ /** * @public */ - this.frame = frame; + this.framesheet = framesheet; /** * @public */ - this.overlayFrame = pskl.model.Frame.createEmptyFromFrame(frame); + this.overlayFrame = pskl.model.Frame.createEmptyFromFrame(framesheet.getCurrentFrame()); /** * @private */ this.container = container; - this.renderer = new pskl.rendering.FrameRenderer( - this.container, - renderingOptions, - "drawing-canvas"); - - this.overlayRenderer = new pskl.rendering.FrameRenderer( - this.container, - renderingOptions, - "canvas-overlay"); + this.renderer = new pskl.rendering.FrameRenderer(this.container, renderingOptions, "drawing-canvas"); + this.overlayRenderer = new pskl.rendering.FrameRenderer(this.container, renderingOptions, "canvas-overlay"); - this.renderer.init(this.frame); + this.renderer.init(framesheet.getCurrentFrame()); this.overlayRenderer.init(this.overlayFrame); // State of drawing controller: @@ -48,19 +39,18 @@ this.initMouseBehavior(); $.subscribe(Events.TOOL_SELECTED, $.proxy(function(evt, toolBehavior) { + console.log("Tool selected: ", toolBehavior); + this.currentToolBehavior = toolBehavior; + }, this)); - console.log("Tool selected: ", toolBehavior); - this.currentToolBehavior = toolBehavior; - }, this)); - - $.subscribe(Events.COLOR_SELECTED, $.proxy(function(evt, color, isPrimary) { - console.log("Color selected: ", color); - if (isPrimary) { - this.primaryColor = color; - } else { - this.secondaryColor = color; - } - }, this)); + $.subscribe(Events.COLOR_SELECTED, $.proxy(function(evt, color, isPrimary) { + console.log("Color selected: ", color); + if (isPrimary) { + this.primaryColor = color; + } else { + this.secondaryColor = color; + } + }, this)); }; ns.DrawingController.prototype.initMouseBehavior = function() { @@ -84,14 +74,13 @@ $.publish(Events.CANVAS_RIGHT_CLICKED); } - var spriteCoordinate = this.getSpriteCoordinate(event); - //console.log("mousedown: col: " + spriteCoordinate.col + " - row: " + spriteCoordinate.row); + var coords = this.getSpriteCoordinates(event); this.currentToolBehavior.applyToolAt( - spriteCoordinate.col, - spriteCoordinate.row, + coords.col, coords.row, this.getCurrentColor_(), - this + this.framesheet.getCurrentFrame(), + this.overlayFrame ); $.publish(Events.LOCALSTORAGE_REQUEST); @@ -104,27 +93,20 @@ var currentTime = new Date().getTime(); // Throttling of the mousemove event: if ((currentTime - this.previousMousemoveTime) > 40 ) { - var spriteCoordinate = this.getSpriteCoordinate(event); + var coords = this.getSpriteCoordinates(event); if (this.isClicked) { this.currentToolBehavior.moveToolAt( - spriteCoordinate.col, - spriteCoordinate.row, + coords.col, coords.row, this.getCurrentColor_(), - this + this.framesheet.getCurrentFrame(), + this.overlayFrame ); - //console.log("mousemove: col: " + spriteCoordinate.col + " - row: " + spriteCoordinate.row); - // TODO(vincz): Find a way to move that to the model instead of being at the interaction level. // Eg when drawing, it may make sense to have it here. However for a non drawing tool, // you don't need to draw anything when mousemoving and you request useless localStorage. $.publish(Events.LOCALSTORAGE_REQUEST); - } else { - // debug mode to see the selected pixel - // this.clearOverlay(); - // this.overlayFrame.setPixel( spriteCoordinate.col,spriteCoordinate.row, "#ff0000"); - // this.renderOverlay(); } this.previousMousemoveTime = currentTime; } @@ -139,26 +121,20 @@ // the user was probably drawing on the canvas. // Note: The mousemove movement (and the mouseup) may end up outside // of the drawing canvas. - if(this.isRightClicked) { - $.publish(Events.CANVAS_RIGHT_CLICK_RELEASED); - } - this.isClicked = false; this.isRightClicked = false; - var spriteCoordinate = this.getSpriteCoordinate(event); + + var coords = this.getSpriteCoordinates(event); //console.log("mousemove: col: " + spriteCoordinate.col + " - row: " + spriteCoordinate.row); this.currentToolBehavior.releaseToolAt( - spriteCoordinate.col, - spriteCoordinate.row, + coords.col, coords.row, this.getCurrentColor_(), - this + this.framesheet.getCurrentFrame(), + this.overlayFrame ); $.publish(Events.TOOL_RELEASED); - - // TODO: Remove that when we have the centralized redraw loop - $.publish(Events.REDRAW_PREVIEWFILM); } }, @@ -176,7 +152,7 @@ /** * @private */ - ns.DrawingController.prototype.getSpriteCoordinate = function(event) { + ns.DrawingController.prototype.getSpriteCoordinates = function(event) { var coords = this.getRelativeCoordinates(event.clientX, event.clientY); return this.renderer.convertPixelCoordinatesIntoSpriteCoordinate(coords); }; @@ -219,17 +195,14 @@ }; ns.DrawingController.prototype.renderFrame = function () { - var serializedFrame = this.frame.serialize(); + var frame = this.framesheet.getCurrentFrame(); + var serializedFrame = frame.serialize(); if (this.serializedFrame != serializedFrame) { this.serializedFrame = serializedFrame - this.renderer.render(this.frame); + this.renderer.render(frame); } }; - ns.DrawingController.prototype.renderFramePixel = function (col, row) { - this.renderer.drawPixel(col, row, this.frame); - }; - ns.DrawingController.prototype.renderOverlay = function () { var serializedOverlay = this.overlayFrame.serialize(); if (this.serializedOverlay != serializedOverlay) { @@ -237,8 +210,4 @@ this.overlayRenderer.render(this.overlayFrame); } }; - - ns.DrawingController.prototype.clearOverlay = function () { - this.overlayFrame = pskl.model.Frame.createEmptyFromFrame(this.frame); - }; })(); \ No newline at end of file diff --git a/js/controller/PreviewFilmController.js b/js/controller/PreviewFilmController.js index 5ae04e03..6385c1dd 100644 --- a/js/controller/PreviewFilmController.js +++ b/js/controller/PreviewFilmController.js @@ -6,11 +6,10 @@ this.framesheet = framesheet; this.container = container; - this.dirty = false; + this.redrawFlag = true; - $.subscribe(Events.REDRAW_PREVIEWFILM, $.proxy(function(evt) { - this.dirty = true; - }, this)); + $.subscribe(Events.TOOL_RELEASED, this.flagForRedraw_.bind(this)); + $.subscribe(Events.FRAMESHEET_RESET, this.flagForRedraw_.bind(this)); }; ns.PreviewFilmController.prototype.init = function() { @@ -20,19 +19,29 @@ ns.PreviewFilmController.prototype.addFrame = function () { this.framesheet.addEmptyFrame(); - piskel.setActiveFrame(this.framesheet.getFrameCount() - 1); + this.framesheet.setCurrentFrameIndex(this.framesheet.getFrameCount() - 1); + }; + + ns.PreviewFilmController.prototype.flagForRedraw_ = function () { + this.redrawFlag = true; }; ns.PreviewFilmController.prototype.render = function () { - if (!this.dirty) return - // TODO(vincz): Full redraw on any drawing modification, optimize. + if (this.redrawFlag) { + // TODO(vincz): Full redraw on any drawing modification, optimize. + this.createPreviews_(); + this.redrawFlag = false; + } + }; + + ns.PreviewFilmController.prototype.createPreviews_ = function () { this.container.html(""); var frameCount = this.framesheet.getFrameCount(); for (var i = 0, l = frameCount; i < l ; i++) { - this.container.append(this.createInterstitialTile_(i)); - this.container.append(this.createPreviewTile_(i, this.framesheet)); + this.container.append(this.createInterstitialTile_(i)); + this.container.append(this.createPreviewTile_(i)); } this.container.append(this.createInterstitialTile_(frameCount)); @@ -40,19 +49,18 @@ if(needDragndropBehavior) { this.initDragndropBehavior_(); } - this.dirty = false; }; /** * @private */ ns.PreviewFilmController.prototype.createInterstitialTile_ = function (tileNumber) { - var initerstitialTile = document.createElement("div"); - initerstitialTile.className = "interstitial-tile" - initerstitialTile.setAttribute("data-tile-type", "interstitial"); - initerstitialTile.setAttribute("data-inject-drop-tile-at", tileNumber); + var interstitialTile = document.createElement("div"); + interstitialTile.className = "interstitial-tile" + interstitialTile.setAttribute("data-tile-type", "interstitial"); + interstitialTile.setAttribute("data-inject-drop-tile-at", tileNumber); - return initerstitialTile; + return interstitialTile; }; /** @@ -101,8 +109,8 @@ // inside the drag target. We normalize that by taking the correct ancestor: var originTile = $(event.srcElement).closest(".preview-tile"); var originFrameId = parseInt(originTile.data("tile-number"), 10); + var dropTarget = $(event.target); - if(dropTarget.data("tile-type") == "interstitial") { var targetInsertionId = parseInt(dropTarget.data("inject-drop-tile-at"), 10); // In case we drop outside of the tile container @@ -120,8 +128,7 @@ if(activeFrame > (this.framesheet.getFrameCount() - 1)) { activeFrame = targetInsertionId - 1; } - } - else { + } else { var targetSwapId = parseInt(dropTarget.data("tile-number"), 10); // In case we drop outside of the tile container if(isNaN(originFrameId) || isNaN(targetSwapId)) { @@ -135,26 +142,24 @@ $('#preview-list').removeClass("show-interstitial-tiles"); - // TODO(vincz): deprecate. - piskel.setActiveFrame(activeFrame); + this.framesheet.setCurrentFrameIndex(activeFrame); // TODO(vincz): move localstorage request to the model layer? $.publish(Events.LOCALSTORAGE_REQUEST); - }; /** * @private * TODO(vincz): clean this giant rendering function & remove listeners. */ - ns.PreviewFilmController.prototype.createPreviewTile_ = function(tileNumber, framesheet) { + ns.PreviewFilmController.prototype.createPreviewTile_ = function(tileNumber) { var currentFrame = this.framesheet.getFrameByIndex(tileNumber); var previewTileRoot = document.createElement("li"); var classname = "preview-tile"; previewTileRoot.setAttribute("data-tile-number", tileNumber); - if (piskel.getActiveFrameIndex() == tileNumber) { + if (this.framesheet.getCurrentFrame() == currentFrame) { classname += " selected"; } previewTileRoot.className = classname; @@ -166,25 +171,14 @@ canvasBackground.className = "canvas-background"; canvasContainer.appendChild(canvasBackground); - previewTileRoot.addEventListener('click', function(evt) { - // has not class tile-action: - if(!evt.target.classList.contains('tile-action')) { - piskel.setActiveFrame(tileNumber); - } - }); + previewTileRoot.addEventListener('click', this.onPreviewClick_.bind(this, tileNumber)); var canvasPreviewDuplicateAction = document.createElement("button"); canvasPreviewDuplicateAction.className = "tile-action" canvasPreviewDuplicateAction.innerHTML = "dup" - canvasPreviewDuplicateAction.addEventListener('click', function(evt) { - framesheet.duplicateFrameByIndex(tileNumber); - $.publish(Events.LOCALSTORAGE_REQUEST); // Should come from model - $.publish('SET_ACTIVE_FRAME', [tileNumber + 1]); - }); + canvasPreviewDuplicateAction.addEventListener('click', this.onAddButtonClick_.bind(this, tileNumber)); - //this.renderer.render(this.framesheet.getFrameByIndex(tileNumber), canvasPreview); - // TODO(vincz): Eventually optimize this part by not recreating a FrameRenderer. Note that the real optim // is to make this update function (#createPreviewTile) less aggressive. var renderingOptions = {"dpi": this.dpi }; @@ -198,14 +192,29 @@ var canvasPreviewDeleteAction = document.createElement("button"); canvasPreviewDeleteAction.className = "tile-action" canvasPreviewDeleteAction.innerHTML = "del" - canvasPreviewDeleteAction.addEventListener('click', function(evt) { - framesheet.removeFrameByIndex(tileNumber); - $.publish(Events.FRAMESHEET_RESET); - $.publish(Events.LOCALSTORAGE_REQUEST); // Should come from model - }); + canvasPreviewDeleteAction.addEventListener('click', this.onDeleteButtonClick_.bind(this, tileNumber)); previewTileRoot.appendChild(canvasPreviewDeleteAction); } return previewTileRoot; }; + + ns.PreviewFilmController.prototype.onPreviewClick_ = function (index, evt) { + // has not class tile-action: + if(!evt.target.classList.contains('tile-action')) { + this.framesheet.setCurrentFrameIndex(index); + } + }; + + ns.PreviewFilmController.prototype.onDeleteButtonClick_ = function (index, evt) { + this.framesheet.removeFrameByIndex(index); + $.publish(Events.FRAMESHEET_RESET); + $.publish(Events.LOCALSTORAGE_REQUEST); // Should come from model + }; + + ns.PreviewFilmController.prototype.onAddButtonClick_ = function (index, evt) { + this.framesheet.duplicateFrameByIndex(index); + $.publish(Events.LOCALSTORAGE_REQUEST); // Should come from model + this.framesheet.setCurrentFrameIndex(index + 1); + }; })(); \ No newline at end of file diff --git a/js/drawingtools/BaseTool.js b/js/drawingtools/BaseTool.js index 905aa162..84f96824 100644 --- a/js/drawingtools/BaseTool.js +++ b/js/drawingtools/BaseTool.js @@ -8,11 +8,11 @@ ns.BaseTool = function() {}; - ns.BaseTool.prototype.applyToolAt = function(col, row, frame) {}; + ns.BaseTool.prototype.applyToolAt = function(col, row, color, frame, overlay) {}; - ns.BaseTool.prototype.moveToolAt = function(col, row, frame) {}; + ns.BaseTool.prototype.moveToolAt = function(col, row, color, frame, overlay) {}; - ns.BaseTool.prototype.releaseToolAt = function(col, row, frame) {}; + ns.BaseTool.prototype.releaseToolAt = function(col, row, color, frame, overlay) {}; /** * Bresenham line algorihtm: Get an array of pixels from diff --git a/js/drawingtools/Eraser.js b/js/drawingtools/Eraser.js index e83cf617..064adf85 100644 --- a/js/drawingtools/Eraser.js +++ b/js/drawingtools/Eraser.js @@ -16,7 +16,7 @@ /** * @override */ - ns.Eraser.prototype.applyToolAt = function(col, row, color, drawer) { - this.superclass.applyToolAt.call(this, col, row, Constants.TRANSPARENT_COLOR, drawer); + ns.Eraser.prototype.applyToolAt = function(col, row, color, frame, overlay) { + this.superclass.applyToolAt.call(this, col, row, Constants.TRANSPARENT_COLOR, frame, overlay); }; })(); \ No newline at end of file diff --git a/js/drawingtools/Move.js b/js/drawingtools/Move.js index 23b0f58f..ec120287 100644 --- a/js/drawingtools/Move.js +++ b/js/drawingtools/Move.js @@ -19,16 +19,16 @@ /** * @override */ - ns.Move.prototype.applyToolAt = function(col, row, color, drawer) { + ns.Move.prototype.applyToolAt = function(col, row, color, frame, overlay) { this.startCol = col; this.startRow = row; - this.frameClone = drawer.frame.clone(); + this.frameClone = frame.clone(); }; - ns.Move.prototype.moveToolAt = function(col, row, color, drawer) { + ns.Move.prototype.moveToolAt = function(col, row, color, frame, overlay) { var colDiff = col - this.startCol, rowDiff = row - this.startRow; if (colDiff != 0 || rowDiff != 0) { - this.shiftFrame(colDiff, rowDiff, drawer.frame, this.frameClone); + this.shiftFrame(colDiff, rowDiff, frame, this.frameClone); } }; @@ -49,7 +49,7 @@ /** * @override */ - ns.Move.prototype.releaseToolAt = function(col, row, color, drawer) { - this.moveToolAt(col, row, color, drawer); + ns.Move.prototype.releaseToolAt = function(col, row, color, frame, overlay) { + this.moveToolAt(col, row, color, frame, overlay); }; })(); diff --git a/js/drawingtools/PaintBucket.js b/js/drawingtools/PaintBucket.js index f3c3ccfe..b3176324 100644 --- a/js/drawingtools/PaintBucket.js +++ b/js/drawingtools/PaintBucket.js @@ -15,12 +15,11 @@ /** * @override */ - ns.PaintBucket.prototype.applyToolAt = function(col, row, color, drawer) { + ns.PaintBucket.prototype.applyToolAt = function(col, row, color, frame, overlay) { // Change model: - var targetColor = drawer.frame.getPixel(col, row); - //this.recursiveFloodFill_(frame, col, row, targetColor, color); - this.queueLinearFloodFill_(drawer.frame, col, row, targetColor, color); + var targetColor = frame.getPixel(col, row); + this.queueLinearFloodFill_(frame, col, row, targetColor, color); }; /** diff --git a/js/drawingtools/Rectangle.js b/js/drawingtools/Rectangle.js index 5f2fa7c7..f5fd906d 100644 --- a/js/drawingtools/Rectangle.js +++ b/js/drawingtools/Rectangle.js @@ -19,17 +19,16 @@ /** * @override */ - ns.Rectangle.prototype.applyToolAt = function(col, row, color, drawer) { + ns.Rectangle.prototype.applyToolAt = function(col, row, color, frame, overlay) { this.startCol = col; this.startRow = row; // Drawing the first point of the rectangle in the fake overlay canvas: - drawer.overlayFrame.setPixel(col, row, color); + overlay.setPixel(col, row, color); }; - ns.Rectangle.prototype.moveToolAt = function(col, row, color, drawer) { - // Clean overlay canvas: - drawer.clearOverlay(); + ns.Rectangle.prototype.moveToolAt = function(col, row, color, frame, overlay) { + overlay.clear(); // When the user moussemove (before releasing), we dynamically compute the // pixel to draw the line and draw this line in the overlay : @@ -41,21 +40,21 @@ if(color == Constants.TRANSPARENT_COLOR) { color = Constants.SELECTION_TRANSPARENT_COLOR; } - drawer.overlayFrame.setPixel(strokePoints[i].col, strokePoints[i].row, color); + overlay.setPixel(strokePoints[i].col, strokePoints[i].row, color); } }; /** * @override */ - ns.Rectangle.prototype.releaseToolAt = function(col, row, color, drawer) { - drawer.clearOverlay(); + ns.Rectangle.prototype.releaseToolAt = function(col, row, color, frame, overlay) { + overlay.clear(); // If the stroke tool is released outside of the canvas, we cancel the stroke: - if(drawer.frame.containsPixel(col, row)) { + if(frame.containsPixel(col, row)) { var strokePoints = this.getRectanglePixels_(this.startCol, col, this.startRow, row); for(var i = 0; i< strokePoints.length; i++) { // Change model: - drawer.frame.setPixel(strokePoints[i].col, strokePoints[i].row, color); + frame.setPixel(strokePoints[i].col, strokePoints[i].row, color); } // The user released the tool to draw a line. We will compute the pixel coordinate, impact // the model and draw them in the drawing canvas (not the fake overlay anymore) diff --git a/js/drawingtools/SelectTool.js b/js/drawingtools/SelectTool.js new file mode 100644 index 00000000..851e9842 --- /dev/null +++ b/js/drawingtools/SelectTool.js @@ -0,0 +1,130 @@ +/* + * @provide pskl.drawingtools.Select + * + * @require pskl.utils + */ +(function() { + var ns = $.namespace("pskl.drawingtools"); + + ns.Select = function() { + this.toolId = "tool-select" + + // Select's first point coordinates (set in applyToolAt) + this.startCol = null; + this.startRow = null; + }; + + pskl.utils.inherit(ns.Select, ns.BaseTool); + + /** + * @override + */ + ns.Select.prototype.applyToolAt = function(col, row, color, drawer) { + this.startCol = col; + this.startRow = row; + + // Drawing the first point of the rectangle in the fake overlay canvas: + drawer.overlayFrame.setPixel(col, row, color); + }; + + ns.Select.prototype.moveToolAt = function(col, row, color, drawer) { + // Clean overlay canvas: + drawer.clearOverlay(); + + // When the user moussemove (before releasing), we dynamically compute the + // pixel to draw the line and draw this line in the overlay : + var strokePoints = this.getRectanglePixels_(this.startCol, col, this.startRow, row); + + color = Constants.SELECTION_TRANSPARENT_COLOR; + // Drawing current stroke: + for(var i = 0; i< strokePoints.length; i++) { + + drawer.overlayFrame.setPixel(strokePoints[i].col, strokePoints[i].row, color); + } + }; + + /** + * @override + */ + ns.Select.prototype.releaseToolAt = function(col, row, color, drawer) { + drawer.clearOverlay(); + // If the stroke tool is released outside of the canvas, we cancel the stroke: + if(drawer.frame.containsPixel(col, row)) { + + + // Creating horizontal sides of the rectangle: + var pixels = this.getRectangleSelection(this.startCol, col, this.startRow, row); + + //var strokePoints = this.getRectanglePixels_(this.startCol, col, this.startRow, row); + + for(var i = 0; i< pixels.length; i++) { + // Change model: + drawer.frame.setPixel(pixels[i].col, pixels[i].row, color); + } + // The user released the tool to draw a line. We will compute the pixel coordinate, impact + // the model and draw them in the drawing canvas (not the fake overlay anymore) + } + }; + + ns.Select.prototype.getRectangleSelection = function(x0, x1, y0, y1) { + + var pixels = []; + var swap; + + if(x0 > x1) { + swap = x0; + x0 = x1; + x1 = swap; + } + if(y0 > y1) { + swap = y0; + y0 = y1; + y1 = swap; + } + + for(var x = x0; x <= x1; x++) { + for(var y = y0; y <= y1; y++) { + pixels.push({"col": x, "row": y}); + } + } + + return pixels; + }; + + /** + * Get an array of pixels representing the rectangle. + * + * @private + */ + ns.Select.prototype.getRectanglePixels_ = function(x0, x1, y0, y1) { + + var pixels = []; + var swap; + + if(x0 > x1) { + swap = x0; + x0 = x1; + x1 = swap; + } + if(y0 > y1) { + swap = y0; + y0 = y1; + y1 = swap; + } + + // Creating horizontal sides of the rectangle: + for(var x = x0; x <= x1; x++) { + pixels.push({"col": x, "row": y0}); + pixels.push({"col": x, "row": y1}); + } + + // Creating vertical sides of the rectangle: + for(var y = y0; y <= y1; y++) { + pixels.push({"col": x0, "row": y}); + pixels.push({"col": x1, "row": y}); + } + + return pixels; + }; + +})(); diff --git a/js/drawingtools/SimplePen.js b/js/drawingtools/SimplePen.js index 86065d63..ffc0047f 100644 --- a/js/drawingtools/SimplePen.js +++ b/js/drawingtools/SimplePen.js @@ -18,27 +18,27 @@ /** * @override */ - ns.SimplePen.prototype.applyToolAt = function(col, row, color, drawer) { - if (drawer.frame.containsPixel(col, row)) { + ns.SimplePen.prototype.applyToolAt = function(col, row, color, frame, overlay) { + if (frame.containsPixel(col, row)) { this.previousCol = col; this.previousRow = row; - drawer.frame.setPixel(col, row, color); + frame.setPixel(col, row, color); } }; - ns.SimplePen.prototype.moveToolAt = function(col, row, color, drawer) { - + ns.SimplePen.prototype.moveToolAt = function(col, row, color, frame, overlay) { if((Math.abs(col - this.previousCol) > 1) || (Math.abs(row - this.previousRow) > 1)) { // The pen movement is too fast for the mousemove frequency, there is a gap between the // current point and the previously drawn one. // We fill the gap by calculating missing dots (simple linear interpolation) and draw them. var interpolatedPixels = this.getLinePixels_(col, this.previousCol, row, this.previousRow); for(var i=0, l=interpolatedPixels.length; i