From 2e26a6c5f149b45b1507d6e2272e58679994203c Mon Sep 17 00:00:00 2001 From: Ravjot Singh Date: Sun, 8 Dec 2024 14:10:28 +0530 Subject: [PATCH 1/6] Added Undo and Redo buttons to the main toolbar in index.html Signed-off-by: Ravjot Singh --- css/activity.css | 8 ++++++++ icons/redo-button.svg | 16 ++++++++++++++++ icons/undo-button.svg | 16 ++++++++++++++++ index.html | 2 ++ 4 files changed, 42 insertions(+) create mode 100644 icons/redo-button.svg create mode 100644 icons/undo-button.svg diff --git a/css/activity.css b/css/activity.css index 5cecaf7..eed41a5 100644 --- a/css/activity.css +++ b/css/activity.css @@ -8,4 +8,12 @@ #main-toolbar #clear-button { background-image: url(../icons/clear-button.svg); +} + +#main-toolbar #undo-button { + background-image: url(../icons/undo-button.svg); +} + +#main-toolbar #redo-button { + background-image: url(../icons/redo-button.svg); } \ No newline at end of file diff --git a/icons/redo-button.svg b/icons/redo-button.svg new file mode 100644 index 0000000..cadb202 --- /dev/null +++ b/icons/redo-button.svg @@ -0,0 +1,16 @@ + + + +]> + + + + + \ No newline at end of file diff --git a/icons/undo-button.svg b/icons/undo-button.svg new file mode 100644 index 0000000..3a869b5 --- /dev/null +++ b/icons/undo-button.svg @@ -0,0 +1,16 @@ + + + +]> + + + + + \ No newline at end of file diff --git a/index.html b/index.html index 3c47fa2..8dca9e7 100644 --- a/index.html +++ b/index.html @@ -16,6 +16,8 @@ + + From 7ffa6a14e95f1dde5e80b57828709d7435f17596 Mon Sep 17 00:00:00 2001 From: Ravjot Singh Date: Sun, 8 Dec 2024 14:15:34 +0530 Subject: [PATCH 2/6] Implemented a stoke history data structure Signed-off-by: Ravjot Singh --- js/activity.js | 172 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 125 insertions(+), 47 deletions(-) diff --git a/js/activity.js b/js/activity.js index 995d47a..77b2e2f 100644 --- a/js/activity.js +++ b/js/activity.js @@ -4,29 +4,34 @@ define(function (require) { require("easel"); require("handlebars"); var shapes = require("activity/shapes"); - + + // Manipulate the DOM only when it is ready. require(["domReady!"], function (doc) { // Initialize the activity. activity.setup(); - + + // Colorize the activity icon. var activityButton = document.getElementById("activity-button"); activity.getXOColor(function (error, colors) { icon.colorize(activityButton, colors); }); - + + // Initialize the main applicaton var app = new DrawingApp(); app.init(); }); - + + function DrawingApp() { // Stage and canvass this.canvas = null; this.stage = null; this.drawingCanvas = null; - + + // Drawing variables this.oldPt = null; this.midPt = null; @@ -43,67 +48,87 @@ define(function (require) { ]; this.index = 0; this.update = true; - + + // Assets and bitmaps this.imagepos = []; this.myimages = []; this.bitmaps = []; this.bitmapLabels = []; this.pen_bitmap = null; - + + // Other variables this.nlabels = []; this.shape = 0; - + + + // Undo/Redo variables + this.strokes = []; + this.undoneStrokes = []; + this.currentStroke = null; + + // Image sources this.Star = "images/star.svg"; this.Dot = "images/dot.svg"; this.Pen = "images/pen.svg"; } - + + DrawingApp.prototype.init = function () { var self = this; - + + if (window.top != window) { document.getElementById("header").style.display = "none"; } document.getElementById("loader").className = "loader"; - + + // Set up the canvas and stage this.canvas = document.getElementById("myCanvas"); this.stage = new createjs.Stage(this.canvas); - + + // Enable touch and mouse interactions createjs.Touch.enable(this.stage); this.stage.mouseMoveOutside = true; this.stage.enableMouseOver(10); - + + // Initialize drawing points this.oldPt = new createjs.Point(400, 300); this.midPt = this.oldPt; this.oldMidPt = this.oldPt; - + + // Initialize image positions and labels for (var i = 0; i < 21; i++) { this.imagepos[i] = [-100, -100]; this.nlabels[i] = document.getElementById("n" + i.toString()); } - + + // Load images this.loadImages(); - + + // Create a drawing canvas for the activity this.drawingCanvas = new createjs.Shape(); this.stage.addChild(this.drawingCanvas); this.stage.update(); - + + // Set up event listeners this.setupEventListeners(); }; - + + DrawingApp.prototype.loadImages = function () { var self = this; - + + // Load the dot and star images for (var i = 0; i < this.nlabels.length; i++) { var image = new Image(); @@ -114,7 +139,8 @@ define(function (require) { }; this.myimages.push(image); } - + + // Load the pen image var penImage = new Image(); penImage.src = this.Pen; @@ -122,81 +148,97 @@ define(function (require) { self.handlePenLoad(event); }; }; - + + DrawingApp.prototype.handleImageLoad = function (event) { var image = event.target; var imgW = image.width; var imgH = image.height; var i = image.dataId; - + + var container = new createjs.Container(); this.stage.addChild(container); - + + // Create bitmap and text label var bitmap = new createjs.Bitmap(image); var bitText = new createjs.Text(i.toString(), "bold 20px Arial", "#000"); this.bitmaps[i] = bitmap; this.bitmapLabels[i] = bitText; - + + container.addChild(bitmap); container.addChild(bitText); - + + // Set initial positions bitmap.x = this.imagepos[i][0]; bitmap.y = this.imagepos[i][1]; bitText.x = bitmap.x; bitText.y = bitmap.y; - + + // Set registration point and scale bitmap.regX = imgW / 2; bitmap.regY = imgH / 2; bitmap.scaleX = bitmap.scaleY = bitmap.scale = i === 0 ? 0.5 : 1.5; - + + // Set hit area var hitArea = new createjs.Shape(); hitArea.graphics.beginFill("#FFF").drawEllipse(-11, -14, 24, 18); hitArea.x = imgW / 2; hitArea.y = imgH / 2; bitmap.hitArea = hitArea; - + + // Set cursor bitmap.cursor = "pointer"; - + + document.getElementById("loader").className = ""; createjs.Ticker.addEventListener("tick", this.tick.bind(this)); }; - + + DrawingApp.prototype.handlePenLoad = function (event) { var self = this; var image = event.target; var imgW = image.width; var imgH = image.height; - + + var container = new createjs.Container(); this.stage.addChild(container); - + + // Create pen bitmap var bitmap = new createjs.Bitmap(image); this.pen_bitmap = bitmap; container.addChild(bitmap); - + + // Set initial position and registration point bitmap.x = this.imagepos[0][0]; bitmap.y = this.imagepos[0][1]; bitmap.regX = imgW / 2; bitmap.regY = imgH / 2; bitmap.scaleX = bitmap.scaleY = bitmap.scale = 1; - + + // Set hit area var hitArea = new createjs.Shape(); hitArea.graphics.beginFill("#FFF").drawEllipse(-22, -28, 48, 36); hitArea.x = imgW / 2; hitArea.y = imgH / 2; bitmap.hitArea = hitArea; - + + // Set cursor bitmap.cursor = "pointer"; - + + // Event handlers (function (target) { target.onPress = function (evt) { @@ -206,18 +248,22 @@ define(function (require) { x: target.x - evt.stageX, y: target.y - evt.stageY, }; - - // Initialize drawing variables - self.color = self.colors[self.index++ % self.colors.length]; - self.stroke = (Math.random() * 30 + 10) | 0; - self.oldPt = new createjs.Point(self.stage.mouseX, self.stage.mouseY); - self.oldMidPt = self.oldPt; - + + + // Initialize a new stroke + self.currentStroke = { + color: self.color, + strokeSize: self.stroke, + segments: [], + }; + + evt.onMouseMove = function (ev) { target.x = ev.stageX + offset.x; target.y = ev.stageY + offset.y; self.update = true; - + + var midPt = new createjs.Point( (self.oldPt.x + self.stage.mouseX) >> 1, (self.oldPt.y + self.stage.mouseY) >> 1 @@ -232,18 +278,50 @@ define(function (require) { self.oldMidPt.x, self.oldMidPt.y ); - + + + // Record this segment of the stroke + self.currentStroke.segments.push({ + start: { x: self.oldMidPt.x, y: self.oldMidPt.y }, + control: { x: self.oldPt.x, y: self.oldPt.y }, + end: { x: midPt.x, y: midPt.y }, + }); + + self.oldPt.x = self.stage.mouseX; self.oldPt.y = self.stage.mouseY; self.oldMidPt.x = midPt.x; self.oldMidPt.y = midPt.y; }; + + + evt.onMouseUp = function () { + // Finalize the current stroke + if ( + self.currentStroke && + self.currentStroke.segments.length > 0 + ) { + self.strokes.push(self.currentStroke); + self.currentStroke = null; + // Clear the redo stack since a new stroke is drawn + self.undoneStrokes = []; + } + }; }; - + + target.onMouseOver = function () { target.scaleX = target.scaleY = target.scale * 1.2; self.update = true; + self.color = self.colors[(self.index++) % self.colors.length]; + self.stroke = (Math.random() * 30 + 10) | 0; + self.oldPt = new createjs.Point( + self.stage.mouseX, + self.stage.mouseY + ); + self.oldMidPt = self.oldPt; }; + target.onMouseOut = function () { target.scaleX = target.scaleY = target.scale; @@ -323,4 +401,4 @@ define(function (require) { // Export the DrawingApp class return DrawingApp; -}); +}); \ No newline at end of file From e694807118ef96b2d21a2ee1cef3f57ca0a86e45 Mon Sep 17 00:00:00 2001 From: Ravjot Singh Date: Sun, 8 Dec 2024 14:16:57 +0530 Subject: [PATCH 3/6] Implemented undo and redo Methods and integrated it with UI Signed-off-by: Ravjot Singh --- js/activity.js | 203 +++++++++++++++++++++++++------------------------ 1 file changed, 103 insertions(+), 100 deletions(-) diff --git a/js/activity.js b/js/activity.js index 77b2e2f..de183cc 100644 --- a/js/activity.js +++ b/js/activity.js @@ -4,34 +4,29 @@ define(function (require) { require("easel"); require("handlebars"); var shapes = require("activity/shapes"); - - + // Manipulate the DOM only when it is ready. require(["domReady!"], function (doc) { // Initialize the activity. activity.setup(); - - + // Colorize the activity icon. var activityButton = document.getElementById("activity-button"); activity.getXOColor(function (error, colors) { icon.colorize(activityButton, colors); }); - - + // Initialize the main applicaton var app = new DrawingApp(); app.init(); }); - - + function DrawingApp() { // Stage and canvass this.canvas = null; this.stage = null; this.drawingCanvas = null; - - + // Drawing variables this.oldPt = null; this.midPt = null; @@ -48,87 +43,72 @@ define(function (require) { ]; this.index = 0; this.update = true; - - + // Assets and bitmaps this.imagepos = []; this.myimages = []; this.bitmaps = []; this.bitmapLabels = []; this.pen_bitmap = null; - - + // Other variables this.nlabels = []; this.shape = 0; - - + // Undo/Redo variables this.strokes = []; this.undoneStrokes = []; this.currentStroke = null; - - + // Image sources this.Star = "images/star.svg"; this.Dot = "images/dot.svg"; this.Pen = "images/pen.svg"; } - - + DrawingApp.prototype.init = function () { var self = this; - - + if (window.top != window) { document.getElementById("header").style.display = "none"; } document.getElementById("loader").className = "loader"; - - + // Set up the canvas and stage this.canvas = document.getElementById("myCanvas"); this.stage = new createjs.Stage(this.canvas); - - + // Enable touch and mouse interactions createjs.Touch.enable(this.stage); this.stage.mouseMoveOutside = true; this.stage.enableMouseOver(10); - - + // Initialize drawing points this.oldPt = new createjs.Point(400, 300); this.midPt = this.oldPt; this.oldMidPt = this.oldPt; - - + // Initialize image positions and labels for (var i = 0; i < 21; i++) { this.imagepos[i] = [-100, -100]; this.nlabels[i] = document.getElementById("n" + i.toString()); } - - + // Load images this.loadImages(); - - + // Create a drawing canvas for the activity this.drawingCanvas = new createjs.Shape(); this.stage.addChild(this.drawingCanvas); this.stage.update(); - - + // Set up event listeners this.setupEventListeners(); }; - - + DrawingApp.prototype.loadImages = function () { var self = this; - - + // Load the dot and star images for (var i = 0; i < this.nlabels.length; i++) { var image = new Image(); @@ -139,8 +119,7 @@ define(function (require) { }; this.myimages.push(image); } - - + // Load the pen image var penImage = new Image(); penImage.src = this.Pen; @@ -148,97 +127,81 @@ define(function (require) { self.handlePenLoad(event); }; }; - - + DrawingApp.prototype.handleImageLoad = function (event) { var image = event.target; var imgW = image.width; var imgH = image.height; var i = image.dataId; - - + var container = new createjs.Container(); this.stage.addChild(container); - - + // Create bitmap and text label var bitmap = new createjs.Bitmap(image); var bitText = new createjs.Text(i.toString(), "bold 20px Arial", "#000"); this.bitmaps[i] = bitmap; this.bitmapLabels[i] = bitText; - - + container.addChild(bitmap); container.addChild(bitText); - - + // Set initial positions bitmap.x = this.imagepos[i][0]; bitmap.y = this.imagepos[i][1]; bitText.x = bitmap.x; bitText.y = bitmap.y; - - + // Set registration point and scale bitmap.regX = imgW / 2; bitmap.regY = imgH / 2; bitmap.scaleX = bitmap.scaleY = bitmap.scale = i === 0 ? 0.5 : 1.5; - - + // Set hit area var hitArea = new createjs.Shape(); hitArea.graphics.beginFill("#FFF").drawEllipse(-11, -14, 24, 18); hitArea.x = imgW / 2; hitArea.y = imgH / 2; bitmap.hitArea = hitArea; - - + // Set cursor bitmap.cursor = "pointer"; - - + document.getElementById("loader").className = ""; createjs.Ticker.addEventListener("tick", this.tick.bind(this)); }; - - + DrawingApp.prototype.handlePenLoad = function (event) { var self = this; var image = event.target; var imgW = image.width; var imgH = image.height; - - + var container = new createjs.Container(); this.stage.addChild(container); - - + // Create pen bitmap var bitmap = new createjs.Bitmap(image); this.pen_bitmap = bitmap; container.addChild(bitmap); - - + // Set initial position and registration point bitmap.x = this.imagepos[0][0]; bitmap.y = this.imagepos[0][1]; bitmap.regX = imgW / 2; bitmap.regY = imgH / 2; bitmap.scaleX = bitmap.scaleY = bitmap.scale = 1; - - + // Set hit area var hitArea = new createjs.Shape(); hitArea.graphics.beginFill("#FFF").drawEllipse(-22, -28, 48, 36); hitArea.x = imgW / 2; hitArea.y = imgH / 2; bitmap.hitArea = hitArea; - - + // Set cursor bitmap.cursor = "pointer"; - - + // Event handlers (function (target) { target.onPress = function (evt) { @@ -248,22 +211,19 @@ define(function (require) { x: target.x - evt.stageX, y: target.y - evt.stageY, }; - - + // Initialize a new stroke self.currentStroke = { color: self.color, strokeSize: self.stroke, segments: [], }; - - + evt.onMouseMove = function (ev) { target.x = ev.stageX + offset.x; target.y = ev.stageY + offset.y; self.update = true; - - + var midPt = new createjs.Point( (self.oldPt.x + self.stage.mouseX) >> 1, (self.oldPt.y + self.stage.mouseY) >> 1 @@ -278,29 +238,23 @@ define(function (require) { self.oldMidPt.x, self.oldMidPt.y ); - - + // Record this segment of the stroke self.currentStroke.segments.push({ start: { x: self.oldMidPt.x, y: self.oldMidPt.y }, control: { x: self.oldPt.x, y: self.oldPt.y }, end: { x: midPt.x, y: midPt.y }, }); - - + self.oldPt.x = self.stage.mouseX; self.oldPt.y = self.stage.mouseY; self.oldMidPt.x = midPt.x; self.oldMidPt.y = midPt.y; }; - - + evt.onMouseUp = function () { // Finalize the current stroke - if ( - self.currentStroke && - self.currentStroke.segments.length > 0 - ) { + if (self.currentStroke && self.currentStroke.segments.length > 0) { self.strokes.push(self.currentStroke); self.currentStroke = null; // Clear the redo stack since a new stroke is drawn @@ -308,20 +262,15 @@ define(function (require) { } }; }; - - + target.onMouseOver = function () { target.scaleX = target.scaleY = target.scale * 1.2; self.update = true; - self.color = self.colors[(self.index++) % self.colors.length]; + self.color = self.colors[self.index++ % self.colors.length]; self.stroke = (Math.random() * 30 + 10) | 0; - self.oldPt = new createjs.Point( - self.stage.mouseX, - self.stage.mouseY - ); + self.oldPt = new createjs.Point(self.stage.mouseX, self.stage.mouseY); self.oldMidPt = self.oldPt; }; - target.onMouseOut = function () { target.scaleX = target.scaleY = target.scale; @@ -375,6 +324,10 @@ define(function (require) { this.midPt = this.oldPt; this.oldMidPt = this.oldPt; this.update = true; + + // Clear the stroke history + this.strokes = []; + this.undoneStrokes = []; }; DrawingApp.prototype.setupEventListeners = function () { @@ -397,8 +350,58 @@ define(function (require) { clearButton.addEventListener("click", function () { self.clearCanvas(); }); + + // Undo button + var undoButton = document.getElementById("undo-button"); + undoButton.addEventListener("click", function () { + self.undo(); + }); + + // Redo button + var redoButton = document.getElementById("redo-button"); + redoButton.addEventListener("click", function () { + self.redo(); + }); + }; + + // Undo the last stroke + DrawingApp.prototype.undo = function () { + if (this.strokes.length > 0) { + var lastStroke = this.strokes.pop(); + this.undoneStrokes.push(lastStroke); + this.redrawStrokes(); + } + }; + + // Redo the last undone stroke + DrawingApp.prototype.redo = function () { + if (this.undoneStrokes.length > 0) { + var stroke = this.undoneStrokes.pop(); + this.strokes.push(stroke); + this.redrawStrokes(); + } }; - // Export the DrawingApp class - return DrawingApp; + // Redraw all strokes from the history + DrawingApp.prototype.redrawStrokes = function () { + // Clear the drawingCanvas + this.drawingCanvas.graphics.clear(); + + // Redraw each stroke + for (var i = 0; i < this.strokes.length; i++) { + var s = this.strokes[i]; + this.drawingCanvas.graphics + .setStrokeStyle(s.strokeSize, "round", "round") + .beginStroke(s.color); + + for (var j = 0; j < s.segments.length; j++) { + var seg = s.segments[j]; + this.drawingCanvas.graphics + .moveTo(seg.start.x, seg.start.y) + .curveTo(seg.control.x, seg.control.y, seg.end.x, seg.end.y); + } + } + + this.update = true; + }; }); \ No newline at end of file From da38f00168d9f71dd4a50b1c977912393cb08244 Mon Sep 17 00:00:00 2001 From: Ravjot Singh Date: Tue, 10 Dec 2024 13:46:33 +0530 Subject: [PATCH 4/6] Update: Undo and Redo Button svgs to match sugar style Signed-off-by: Ravjot Singh --- icons/redo-button.svg | 18 +++++++----------- icons/undo-button.svg | 18 +++++++----------- 2 files changed, 14 insertions(+), 22 deletions(-) diff --git a/icons/redo-button.svg b/icons/redo-button.svg index cadb202..8674f6c 100644 --- a/icons/redo-button.svg +++ b/icons/redo-button.svg @@ -1,16 +1,12 @@ - + + ]> - - - + + + + \ No newline at end of file diff --git a/icons/undo-button.svg b/icons/undo-button.svg index 3a869b5..9a7c1da 100644 --- a/icons/undo-button.svg +++ b/icons/undo-button.svg @@ -1,16 +1,12 @@ - + + ]> - - - + + + + \ No newline at end of file From 572581405905df4849d379efe553afd8dc87fb0a Mon Sep 17 00:00:00 2001 From: Ravjot Singh Date: Mon, 16 Dec 2024 04:58:53 +0530 Subject: [PATCH 5/6] Update: undo and redo svgs Signed-off-by: Ravjot Singh --- icons/redo-button.svg | 26 +++++++++++++++----------- icons/undo-button.svg | 25 ++++++++++++++----------- 2 files changed, 29 insertions(+), 22 deletions(-) diff --git a/icons/redo-button.svg b/icons/redo-button.svg index 8674f6c..6d976bb 100644 --- a/icons/redo-button.svg +++ b/icons/redo-button.svg @@ -1,12 +1,16 @@ - - - -]> - - - - - + + + + + + + + + + + + \ No newline at end of file diff --git a/icons/undo-button.svg b/icons/undo-button.svg index 9a7c1da..c6d0785 100644 --- a/icons/undo-button.svg +++ b/icons/undo-button.svg @@ -1,12 +1,15 @@ - - - -]> - - - - - + + + + + + + + + + + \ No newline at end of file From 1ebc7c8ddaa6a31dd89a0303d6757e0d4728ea26 Mon Sep 17 00:00:00 2001 From: Ravjot Singh Date: Tue, 17 Dec 2024 21:51:55 +0530 Subject: [PATCH 6/6] Fix: Resolved minor whitespace and newline issues caused by linting tools Signed-off-by: Ravjot Singh --- css/activity.css | 2 +- js/activity.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/css/activity.css b/css/activity.css index eed41a5..dd533c1 100644 --- a/css/activity.css +++ b/css/activity.css @@ -16,4 +16,4 @@ #main-toolbar #redo-button { background-image: url(../icons/redo-button.svg); -} \ No newline at end of file +} diff --git a/js/activity.js b/js/activity.js index de183cc..083923c 100644 --- a/js/activity.js +++ b/js/activity.js @@ -404,4 +404,4 @@ define(function (require) { this.update = true; }; -}); \ No newline at end of file +});