diff options
| -rwxr-xr-x | history.txt | 1 | ||||
| -rw-r--r-- | paint.js | 1430 |
2 files changed, 718 insertions, 713 deletions
diff --git a/history.txt b/history.txt index 37a90a6..37d1d6b 100755 --- a/history.txt +++ b/history.txt @@ -2049,3 +2049,4 @@ ______ ------ * Threads, Blocks, Objects: The FOR reporter’s first input now also accepts blocks and scripts („rings“), and reports a copy that is bound to the sprite indicated by the second input. This lets you „zombify“ (or remote-control) sprites (and create custom TELL and ASK blocks) * Blocks: initial support for „sensing“ sprite-only custom block definitions, commented out for now +* Paint: Add mouseLeaveDragging() event behavior, thanks, Kartik, for this fix! @@ -1,14 +1,14 @@ -/*
+/* paint.js -
- a paint editor for Snap!
- inspired by the Scratch paint editor.
-
+ + a paint editor for Snap! + inspired by the Scratch paint editor. + written by Kartik Chandra - Copyright (C) 2013 by Kartik Chandra
-
- This file is part of Snap!.
-
+ Copyright (C) 2013 by Kartik Chandra + + This file is part of Snap!. + Snap! is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of @@ -50,186 +50,187 @@ May 16 - flat design adjustments (Jens) July 12 - pipette tool, code formatting adjustments (Jens) September 16 - flood fill freeze fix (Kartik) + Jan 08 - mouse leave dragging fix (Kartik) + + */ - */
-
-/*global Point, Rectangle, DialogBoxMorph, fontHeight, AlignmentMorph,
- FrameMorph, PushButtonMorph, Color, SymbolMorph, newCanvas, Morph, TextMorph,
- CostumeIconMorph, IDE_Morph, Costume, SpriteMorph, nop, Image, WardrobeMorph,
- TurtleIconMorph, localize, MenuMorph, InputFieldMorph, SliderMorph,
+/*global Point, Rectangle, DialogBoxMorph, fontHeight, AlignmentMorph, + FrameMorph, PushButtonMorph, Color, SymbolMorph, newCanvas, Morph, TextMorph, + CostumeIconMorph, IDE_Morph, Costume, SpriteMorph, nop, Image, WardrobeMorph, + TurtleIconMorph, localize, MenuMorph, InputFieldMorph, SliderMorph, ToggleMorph, ToggleButtonMorph, BoxMorph, modules, radians, - MorphicPreferences, getDocumentPositionOf
- */
-
+ MorphicPreferences, getDocumentPositionOf + */ + // Global stuff //////////////////////////////////////////////////////// -modules.paint = '2013-September-16'; +modules.paint = '2014-January-08'; // Declarations -var PaintEditorMorph;
-var PaintCanvasMorph;
-var PaintColorPickerMorph;
-
-// PaintEditorMorph //////////////////////////
+var PaintEditorMorph; +var PaintCanvasMorph; +var PaintColorPickerMorph; + +// PaintEditorMorph ////////////////////////// + +// A complete paint editor -// A complete paint editor
-
-PaintEditorMorph.prototype = new DialogBoxMorph();
-PaintEditorMorph.prototype.constructor = PaintEditorMorph;
-PaintEditorMorph.uber = DialogBoxMorph.prototype;
-
-PaintEditorMorph.prototype.padding = 10;
-
-function PaintEditorMorph() {
- this.init();
-}
-
-PaintEditorMorph.prototype.init = function () {
- // additional properties:
+PaintEditorMorph.prototype = new DialogBoxMorph(); +PaintEditorMorph.prototype.constructor = PaintEditorMorph; +PaintEditorMorph.uber = DialogBoxMorph.prototype; + +PaintEditorMorph.prototype.padding = 10; + +function PaintEditorMorph() { + this.init(); +} + +PaintEditorMorph.prototype.init = function () { + // additional properties: this.paper = null; // paint canvas - this.oncancel = null;
-
- // initialize inherited properties:
- PaintEditorMorph.uber.init.call(this);
-
- // override inherited properties:
- this.labelString = "Paint Editor";
- this.createLabel();
-
- // build contents:
- this.buildContents();
-};
-
-PaintEditorMorph.prototype.buildContents = function () {
- var myself = this;
-
+ this.oncancel = null; + + // initialize inherited properties: + PaintEditorMorph.uber.init.call(this); + + // override inherited properties: + this.labelString = "Paint Editor"; + this.createLabel(); + + // build contents: + this.buildContents(); +}; + +PaintEditorMorph.prototype.buildContents = function () { + var myself = this; + this.paper = new PaintCanvasMorph(function () {return myself.shift; }); - this.paper.setExtent(new Point(480, 360));
-
- this.addBody(new AlignmentMorph('row', this.padding));
- this.controls = new AlignmentMorph('column', this.padding);
- this.controls.alignment = 'left';
-
- this.edits = new AlignmentMorph('row', this.padding);
- this.buildEdits();
- this.controls.add(this.edits);
-
- this.body.color = this.color;
-
- this.body.add(this.controls);
- this.body.add(this.paper);
-
- this.toolbox = new BoxMorph();
- this.toolbox.color = SpriteMorph.prototype.paletteColor.lighter(8);
+ this.paper.setExtent(new Point(480, 360)); + + this.addBody(new AlignmentMorph('row', this.padding)); + this.controls = new AlignmentMorph('column', this.padding); + this.controls.alignment = 'left'; + + this.edits = new AlignmentMorph('row', this.padding); + this.buildEdits(); + this.controls.add(this.edits); + + this.body.color = this.color; + + this.body.add(this.controls); + this.body.add(this.paper); + + this.toolbox = new BoxMorph(); + this.toolbox.color = SpriteMorph.prototype.paletteColor.lighter(8); this.toolbox.borderColor = this.toolbox.color.lighter(40); if (MorphicPreferences.isFlat) { this.toolbox.edge = 0; - }
-
- this.buildToolbox();
- this.controls.add(this.toolbox);
-
- this.propertiesControls = {
- colorpicker: null,
- penSizeSlider: null,
- penSizeField: null,
- primaryColorButton: null,
- primaryColorViewer: null,
- constrain: null
- };
- this.populatePropertiesMenu();
-
- this.addButton("ok", "OK");
- this.addButton("cancel", "Cancel");
-
- this.refreshToolButtons();
- this.fixLayout();
- this.drawNew();
-};
-
-PaintEditorMorph.prototype.buildToolbox = function () {
- var tools = {
- brush:
- "Paintbrush tool\n(free draw)",
- rectangle:
- "Stroked Rectangle\n(shift: square)",
- circle:
- "Stroked Ellipse\n(shift: circle)",
- eraser:
- "Eraser tool",
- crosshairs:
- "Set the rotation center",
-
- line:
- "Line tool\n(shift: vertical/horizontal)",
- rectangleSolid:
- "Filled Rectangle\n(shift: square)",
- circleSolid:
- "Filled Ellipse\n(shift: circle)",
- paintbucket:
- "Fill a region",
+ } + + this.buildToolbox(); + this.controls.add(this.toolbox); + + this.propertiesControls = { + colorpicker: null, + penSizeSlider: null, + penSizeField: null, + primaryColorButton: null, + primaryColorViewer: null, + constrain: null + }; + this.populatePropertiesMenu(); + + this.addButton("ok", "OK"); + this.addButton("cancel", "Cancel"); + + this.refreshToolButtons(); + this.fixLayout(); + this.drawNew(); +}; + +PaintEditorMorph.prototype.buildToolbox = function () { + var tools = { + brush: + "Paintbrush tool\n(free draw)", + rectangle: + "Stroked Rectangle\n(shift: square)", + circle: + "Stroked Ellipse\n(shift: circle)", + eraser: + "Eraser tool", + crosshairs: + "Set the rotation center", + + line: + "Line tool\n(shift: vertical/horizontal)", + rectangleSolid: + "Filled Rectangle\n(shift: square)", + circleSolid: + "Filled Ellipse\n(shift: circle)", + paintbucket: + "Fill a region", pipette: "Pipette tool\n(pick a color anywhere)" - },
- myself = this,
- left = this.toolbox.left(),
- top = this.toolbox.top(),
- padding = 2,
- inset = 5,
- x = 0,
- y = 0;
-
- Object.keys(tools).forEach(function (tool) {
- var btn = myself.toolButton(tool, tools[tool]);
- btn.setPosition(new Point(
- left + x,
- top + y
- ));
- x += btn.width() + padding;
- if (tool === "crosshairs") {
- x = 0;
- y += btn.height() + padding;
- myself.paper.drawcrosshair();
- }
- myself.toolbox[tool] = btn;
- myself.toolbox.add(btn);
- });
-
- this.toolbox.bounds = this.toolbox.fullBounds().expandBy(inset * 2);
- this.toolbox.drawNew();
-};
-
-PaintEditorMorph.prototype.buildEdits = function () {
- var paper = this.paper;
-
- this.edits.add(this.pushButton(
- "undo",
- function () {paper.undo(); }
- ));
-
- this.edits.add(this.pushButton(
- "clear",
- function () {paper.clearCanvas(); }
- ));
- this.edits.fixLayout();
-};
-
-PaintEditorMorph.prototype.openIn = function (world, oldim, oldrc, callback) {
+ }, + myself = this, + left = this.toolbox.left(), + top = this.toolbox.top(), + padding = 2, + inset = 5, + x = 0, + y = 0; + + Object.keys(tools).forEach(function (tool) { + var btn = myself.toolButton(tool, tools[tool]); + btn.setPosition(new Point( + left + x, + top + y + )); + x += btn.width() + padding; + if (tool === "crosshairs") { + x = 0; + y += btn.height() + padding; + myself.paper.drawcrosshair(); + } + myself.toolbox[tool] = btn; + myself.toolbox.add(btn); + }); + + this.toolbox.bounds = this.toolbox.fullBounds().expandBy(inset * 2); + this.toolbox.drawNew(); +}; + +PaintEditorMorph.prototype.buildEdits = function () { + var paper = this.paper; + + this.edits.add(this.pushButton( + "undo", + function () {paper.undo(); } + )); + + this.edits.add(this.pushButton( + "clear", + function () {paper.clearCanvas(); } + )); + this.edits.fixLayout(); +}; + +PaintEditorMorph.prototype.openIn = function (world, oldim, oldrc, callback) { // Open the editor in a world with an optional image to edit - this.oldim = oldim;
- this.oldrc = oldrc.copy();
- this.callback = callback || nop;
+ this.oldim = oldim; + this.oldrc = oldrc.copy(); + this.callback = callback || nop; - this.processKeyUp = function () {
- this.shift = false;
- this.propertiesControls.constrain.refresh();
- };
+ this.processKeyUp = function () { + this.shift = false; + this.propertiesControls.constrain.refresh(); + }; - this.processKeyDown = function () {
- this.shift = this.world().currentKey === 16;
- this.propertiesControls.constrain.refresh();
- };
+ this.processKeyDown = function () { + this.shift = this.world().currentKey === 16; + this.propertiesControls.constrain.refresh(); + }; //merge oldim: if (this.oldim) { @@ -244,170 +245,170 @@ PaintEditorMorph.prototype.openIn = function (world, oldim, oldrc, callback) { this.paper.drawNew(); } - this.key = 'paint';
- this.popUp(world);
-};
-
+ this.key = 'paint'; + this.popUp(world); +}; + PaintEditorMorph.prototype.fixLayout = function () { var oldFlag = Morph.prototype.trackChanges; -
+ this.changed(); oldFlag = Morph.prototype.trackChanges; Morph.prototype.trackChanges = false; if (this.paper) { this.paper.buildContents(); - this.paper.drawNew();
- }
- if (this.controls) {this.controls.fixLayout(); }
- if (this.body) {this.body.fixLayout(); }
- PaintEditorMorph.uber.fixLayout.call(this);
+ this.paper.drawNew(); + } + if (this.controls) {this.controls.fixLayout(); } + if (this.body) {this.body.fixLayout(); } + PaintEditorMorph.uber.fixLayout.call(this); Morph.prototype.trackChanges = oldFlag; this.changed(); -};
-
-PaintEditorMorph.prototype.refreshToolButtons = function () {
- this.toolbox.children.forEach(function (toggle) {
- toggle.refresh();
- });
-};
-
-PaintEditorMorph.prototype.ok = function () {
- this.callback(
- this.paper.paper,
- this.paper.rotationCenter
- );
- this.destroy();
-};
+}; + +PaintEditorMorph.prototype.refreshToolButtons = function () { + this.toolbox.children.forEach(function (toggle) { + toggle.refresh(); + }); +}; + +PaintEditorMorph.prototype.ok = function () { + this.callback( + this.paper.paper, + this.paper.rotationCenter + ); + this.destroy(); +}; PaintEditorMorph.prototype.cancel = function () { if (this.oncancel) {this.oncancel(); } this.destroy(); }; -
-PaintEditorMorph.prototype.populatePropertiesMenu = function () {
- var c = this.controls,
- myself = this,
- pc = this.propertiesControls,
- alpen = new AlignmentMorph("row", this.padding);
-
- pc.primaryColorViewer = new Morph();
- pc.primaryColorViewer.setExtent(new Point(180, 50));
- pc.primaryColorViewer.color = new Color(0, 0, 0);
- pc.colorpicker = new PaintColorPickerMorph(
- new Point(180, 100),
- function (color) {
- var ni = newCanvas(pc.primaryColorViewer.extent()),
- ctx = ni.getContext("2d"),
- i,
- j;
- myself.paper.settings.primarycolor = color;
- if (color === "transparent") {
- for (i = 0; i < 180; i += 5) {
- for (j = 0; j < 15; j += 5) {
- ctx.fillStyle =
- ((j + i) / 5) % 2 === 0 ?
- "rgba(0, 0, 0, 0.2)" :
- "rgba(0, 0, 0, 0.5)";
- ctx.fillRect(i, j, 5, 5);
-
- }
- }
- } else {
- ctx.fillStyle = color.toString();
- ctx.fillRect(0, 0, 180, 15);
- }
- ctx.strokeStyle = "black";
- ctx.lineWidth = Math.min(myself.paper.settings.linewidth, 20);
- ctx.beginPath();
- ctx.lineCap = "round";
- ctx.moveTo(20, 30);
- ctx.lineTo(160, 30);
- ctx.stroke();
- pc.primaryColorViewer.image = ni;
- pc.primaryColorViewer.changed();
- }
- );
- pc.colorpicker.action(new Color(0, 0, 0));
-
- pc.penSizeSlider = new SliderMorph(0, 20, 5, 5);
- pc.penSizeSlider.orientation = "horizontal";
- pc.penSizeSlider.setHeight(15);
- pc.penSizeSlider.setWidth(150);
- pc.penSizeSlider.action = function (num) {
- if (pc.penSizeField) {
- pc.penSizeField.setContents(num);
- }
- myself.paper.settings.linewidth = num;
- pc.colorpicker.action(myself.paper.settings.primarycolor);
- };
- pc.penSizeField = new InputFieldMorph("5", true, null, false);
- pc.penSizeField.contents().minWidth = 20;
- pc.penSizeField.setWidth(25);
- pc.penSizeField.accept = function () {
- var val = parseFloat(pc.penSizeField.getValue());
- pc.penSizeSlider.value = val;
- pc.penSizeSlider.drawNew();
- pc.penSizeSlider.updateValue();
- this.setContents(val);
- myself.paper.settings.linewidth = val;
- this.world().keyboardReceiver = myself;
- pc.colorpicker.action(myself.paper.settings.primarycolor);
- };
- alpen.add(pc.penSizeSlider);
- alpen.add(pc.penSizeField);
- alpen.color = myself.color;
- alpen.fixLayout();
- pc.penSizeField.drawNew();
- pc.constrain = new ToggleMorph(
- "checkbox",
- this,
- function () {myself.shift = !myself.shift; },
- "Constrain proportions of shapes?\n(you can also hold shift)",
- function () {return myself.shift; }
- );
- c.add(pc.colorpicker);
- //c.add(pc.primaryColorButton);
- c.add(pc.primaryColorViewer);
- c.add(new TextMorph("Brush size"));
- c.add(alpen);
- c.add(pc.constrain);
-};
-
-PaintEditorMorph.prototype.toolButton = function (icon, hint) {
- var button, myself = this;
-
- button = new ToggleButtonMorph(
- null,
+ +PaintEditorMorph.prototype.populatePropertiesMenu = function () { + var c = this.controls, + myself = this, + pc = this.propertiesControls, + alpen = new AlignmentMorph("row", this.padding); + + pc.primaryColorViewer = new Morph(); + pc.primaryColorViewer.setExtent(new Point(180, 50)); + pc.primaryColorViewer.color = new Color(0, 0, 0); + pc.colorpicker = new PaintColorPickerMorph( + new Point(180, 100), + function (color) { + var ni = newCanvas(pc.primaryColorViewer.extent()), + ctx = ni.getContext("2d"), + i, + j; + myself.paper.settings.primarycolor = color; + if (color === "transparent") { + for (i = 0; i < 180; i += 5) { + for (j = 0; j < 15; j += 5) { + ctx.fillStyle = + ((j + i) / 5) % 2 === 0 ? + "rgba(0, 0, 0, 0.2)" : + "rgba(0, 0, 0, 0.5)"; + ctx.fillRect(i, j, 5, 5); + + } + } + } else { + ctx.fillStyle = color.toString(); + ctx.fillRect(0, 0, 180, 15); + } + ctx.strokeStyle = "black"; + ctx.lineWidth = Math.min(myself.paper.settings.linewidth, 20); + ctx.beginPath(); + ctx.lineCap = "round"; + ctx.moveTo(20, 30); + ctx.lineTo(160, 30); + ctx.stroke(); + pc.primaryColorViewer.image = ni; + pc.primaryColorViewer.changed(); + } + ); + pc.colorpicker.action(new Color(0, 0, 0)); + + pc.penSizeSlider = new SliderMorph(0, 20, 5, 5); + pc.penSizeSlider.orientation = "horizontal"; + pc.penSizeSlider.setHeight(15); + pc.penSizeSlider.setWidth(150); + pc.penSizeSlider.action = function (num) { + if (pc.penSizeField) { + pc.penSizeField.setContents(num); + } + myself.paper.settings.linewidth = num; + pc.colorpicker.action(myself.paper.settings.primarycolor); + }; + pc.penSizeField = new InputFieldMorph("5", true, null, false); + pc.penSizeField.contents().minWidth = 20; + pc.penSizeField.setWidth(25); + pc.penSizeField.accept = function () { + var val = parseFloat(pc.penSizeField.getValue()); + pc.penSizeSlider.value = val; + pc.penSizeSlider.drawNew(); + pc.penSizeSlider.updateValue(); + this.setContents(val); + myself.paper.settings.linewidth = val; + this.world().keyboardReceiver = myself; + pc.colorpicker.action(myself.paper.settings.primarycolor); + }; + alpen.add(pc.penSizeSlider); + alpen.add(pc.penSizeField); + alpen.color = myself.color; + alpen.fixLayout(); + pc.penSizeField.drawNew(); + pc.constrain = new ToggleMorph( + "checkbox", + this, + function () {myself.shift = !myself.shift; }, + "Constrain proportions of shapes?\n(you can also hold shift)", + function () {return myself.shift; } + ); + c.add(pc.colorpicker); + //c.add(pc.primaryColorButton); + c.add(pc.primaryColorViewer); + c.add(new TextMorph("Brush size")); + c.add(alpen); + c.add(pc.constrain); +}; + +PaintEditorMorph.prototype.toolButton = function (icon, hint) { + var button, myself = this; + + button = new ToggleButtonMorph( + null, this, function () { // action - myself.paper.currentTool = icon;
- myself.paper.toolChanged(icon);
+ myself.paper.currentTool = icon; + myself.paper.toolChanged(icon); myself.refreshToolButtons(); if (icon === 'pipette') { myself.getUserColor(); - }
- },
- new SymbolMorph(icon, 18),
- function () {return myself.paper.currentTool === icon; }
- );
-
- button.hint = hint;
- button.drawNew();
- button.fixLayout();
- return button;
-};
-
-PaintEditorMorph.prototype.pushButton = function (title, action, hint) {
- return new PushButtonMorph(
- this,
- action,
- title,
- null,
- hint
- );
-};
+ } + }, + new SymbolMorph(icon, 18), + function () {return myself.paper.currentTool === icon; } + ); + + button.hint = hint; + button.drawNew(); + button.fixLayout(); + return button; +}; + +PaintEditorMorph.prototype.pushButton = function (title, action, hint) { + return new PushButtonMorph( + this, + action, + title, + null, + hint + ); +}; PaintEditorMorph.prototype.getUserColor = function () { var myself = this, @@ -440,167 +441,167 @@ PaintEditorMorph.prototype.getUserColor = function () { hand.processMouseUp = mouseUpBak; }; }; -
-// AdvancedColorPickerMorph //////////////////
-// A large hsl color picker
-
-PaintColorPickerMorph.prototype = new Morph();
-PaintColorPickerMorph.prototype.constructor = PaintColorPickerMorph;
-PaintColorPickerMorph.uber = Morph.prototype;
-
-function PaintColorPickerMorph(extent, action) {
- this.init(extent, action);
-}
-
-PaintColorPickerMorph.prototype.init = function (extent, action) {
- this.setExtent(extent || new Point(200, 100));
- this.action = action || nop;
- this.drawNew();
-};
-
-PaintColorPickerMorph.prototype.drawNew = function () {
- var x = 0,
- y = 0,
- can = newCanvas(this.extent()),
- ctx = can.getContext("2d"),
- colorselection,
- r;
- for (x = 0; x < this.width(); x += 1) {
- for (y = 0; y < this.height() - 20; y += 1) {
- ctx.fillStyle = "hsl(" +
- (360 * x / this.width()) +
- "," +
- "100%," +
- (y * 100 / (this.height() - 20)) +
- "%)";
- ctx.fillRect(x, y, 1, 1);
- }
- }
- for (x = 0; x < this.width(); x += 1) {
- r = Math.floor(255 * x / this.width());
- ctx.fillStyle = "rgb(" + r + ", " + r + ", " + r + ")";
- ctx.fillRect(x, this.height() - 20, 1, 10);
- }
- colorselection = ["black", "white", "gray"];
- for (x = 0; x < colorselection.length; x += 1) {
- ctx.fillStyle = colorselection[x];
- ctx.fillRect(
- x * this.width() / colorselection.length,
- this.height() - 10,
- this.width() / colorselection.length,
- 10
- );
- }
- for (x = this.width() * 2 / 3; x < this.width(); x += 2) {
- for (y = this.height() - 10; y < this.height(); y += 2) {
- if ((x + y) / 2 % 2 === 0) {
- ctx.fillStyle = "#DDD";
- ctx.fillRect(x, y, 2, 2);
- }
- }
- }
- this.image = can;
-};
-
-PaintColorPickerMorph.prototype.mouseDownLeft = function (pos) {
- if ((pos.subtract(this.position()).x > this.width() * 2 / 3) &&
- (pos.subtract(this.position()).y > this.height() - 10)) {
- this.action("transparent");
- } else {
- this.action(this.getPixelColor(pos));
- }
-};
-
-PaintColorPickerMorph.prototype.mouseMove =
+// AdvancedColorPickerMorph ////////////////// + +// A large hsl color picker + +PaintColorPickerMorph.prototype = new Morph(); +PaintColorPickerMorph.prototype.constructor = PaintColorPickerMorph; +PaintColorPickerMorph.uber = Morph.prototype; + +function PaintColorPickerMorph(extent, action) { + this.init(extent, action); +} + +PaintColorPickerMorph.prototype.init = function (extent, action) { + this.setExtent(extent || new Point(200, 100)); + this.action = action || nop; + this.drawNew(); +}; + +PaintColorPickerMorph.prototype.drawNew = function () { + var x = 0, + y = 0, + can = newCanvas(this.extent()), + ctx = can.getContext("2d"), + colorselection, + r; + for (x = 0; x < this.width(); x += 1) { + for (y = 0; y < this.height() - 20; y += 1) { + ctx.fillStyle = "hsl(" + + (360 * x / this.width()) + + "," + + "100%," + + (y * 100 / (this.height() - 20)) + + "%)"; + ctx.fillRect(x, y, 1, 1); + } + } + for (x = 0; x < this.width(); x += 1) { + r = Math.floor(255 * x / this.width()); + ctx.fillStyle = "rgb(" + r + ", " + r + ", " + r + ")"; + ctx.fillRect(x, this.height() - 20, 1, 10); + } + colorselection = ["black", "white", "gray"]; + for (x = 0; x < colorselection.length; x += 1) { + ctx.fillStyle = colorselection[x]; + ctx.fillRect( + x * this.width() / colorselection.length, + this.height() - 10, + this.width() / colorselection.length, + 10 + ); + } + for (x = this.width() * 2 / 3; x < this.width(); x += 2) { + for (y = this.height() - 10; y < this.height(); y += 2) { + if ((x + y) / 2 % 2 === 0) { + ctx.fillStyle = "#DDD"; + ctx.fillRect(x, y, 2, 2); + } + } + } + this.image = can; +}; + +PaintColorPickerMorph.prototype.mouseDownLeft = function (pos) { + if ((pos.subtract(this.position()).x > this.width() * 2 / 3) && + (pos.subtract(this.position()).y > this.height() - 10)) { + this.action("transparent"); + } else { + this.action(this.getPixelColor(pos)); + } +}; + +PaintColorPickerMorph.prototype.mouseMove = PaintColorPickerMorph.prototype.mouseDownLeft; -
+ // PaintCanvasMorph /////////////////////////// /* - A canvas which reacts to drag events to
+ A canvas which reacts to drag events to modify its image, based on a 'tool' property. */ -
-PaintCanvasMorph.prototype = new Morph();
-PaintCanvasMorph.prototype.constructor = PaintCanvasMorph;
-PaintCanvasMorph.uber = Morph.prototype;
-
-function PaintCanvasMorph(shift) {
- this.init(shift);
-}
-
-PaintCanvasMorph.prototype.init = function (shift) {
- this.rotationCenter = new Point(240, 180);
- this.dragRect = null;
- this.previousDragPoint = null;
- this.currentTool = "brush";
- this.dragRect = new Rectangle();
- // rectangle with origin being the starting drag position and
+ +PaintCanvasMorph.prototype = new Morph(); +PaintCanvasMorph.prototype.constructor = PaintCanvasMorph; +PaintCanvasMorph.uber = Morph.prototype; + +function PaintCanvasMorph(shift) { + this.init(shift); +} + +PaintCanvasMorph.prototype.init = function (shift) { + this.rotationCenter = new Point(240, 180); + this.dragRect = null; + this.previousDragPoint = null; + this.currentTool = "brush"; + this.dragRect = new Rectangle(); + // rectangle with origin being the starting drag position and // corner being the current drag position - this.mask = newCanvas(this.extent()); // Temporary canvas
- this.paper = newCanvas(this.extent()); // Actual canvas
- this.erasermask = newCanvas(this.extent()); // eraser memory
- this.background = newCanvas(this.extent()); // checkers
- this.settings = {
- "primarycolor": new Color(0, 0, 0, 255), // usually fill color
- "secondarycolor": new Color(0, 0, 0, 255), // (unused)
- "linewidth": 3 // stroke width
- };
- this.brushBuffer = [];
- this.undoBuffer = [];
- this.isShiftPressed = shift || function () {
- var key = this.world().currentKey;
- return (key === 16);
+ this.mask = newCanvas(this.extent()); // Temporary canvas + this.paper = newCanvas(this.extent()); // Actual canvas + this.erasermask = newCanvas(this.extent()); // eraser memory + this.background = newCanvas(this.extent()); // checkers + this.settings = { + "primarycolor": new Color(0, 0, 0, 255), // usually fill color + "secondarycolor": new Color(0, 0, 0, 255), // (unused) + "linewidth": 3 // stroke width + }; + this.brushBuffer = []; + this.undoBuffer = []; + this.isShiftPressed = shift || function () { + var key = this.world().currentKey; + return (key === 16); }; - this.buildContents();
-};
-
-PaintCanvasMorph.prototype.cacheUndo = function () {
- var cachecan = newCanvas(this.extent());
- this.merge(this.paper, cachecan);
- this.undoBuffer.push(cachecan);
-};
-
-PaintCanvasMorph.prototype.undo = function () {
- if (this.undoBuffer.length > 0) {
- this.paper = newCanvas(this.extent());
- this.mask.width = this.mask.width + 1 - 1;
- this.merge(this.undoBuffer.pop(), this.paper);
- this.drawNew();
- this.changed();
- }
-};
-
-PaintCanvasMorph.prototype.merge = function (a, b) {
- b.getContext("2d").drawImage(a, 0, 0);
+ this.buildContents(); }; -
-PaintCanvasMorph.prototype.centermerge = function (a, b) {
- b.getContext("2d").drawImage(
- a,
- (b.width - a.width) / 2,
- (b.height - a.height) / 2
- );
-};
-
+ +PaintCanvasMorph.prototype.cacheUndo = function () { + var cachecan = newCanvas(this.extent()); + this.merge(this.paper, cachecan); + this.undoBuffer.push(cachecan); +}; + +PaintCanvasMorph.prototype.undo = function () { + if (this.undoBuffer.length > 0) { + this.paper = newCanvas(this.extent()); + this.mask.width = this.mask.width + 1 - 1; + this.merge(this.undoBuffer.pop(), this.paper); + this.drawNew(); + this.changed(); + } +}; + +PaintCanvasMorph.prototype.merge = function (a, b) { + b.getContext("2d").drawImage(a, 0, 0); +}; + +PaintCanvasMorph.prototype.centermerge = function (a, b) { + b.getContext("2d").drawImage( + a, + (b.width - a.width) / 2, + (b.height - a.height) / 2 + ); +}; + PaintCanvasMorph.prototype.clearCanvas = function () { - this.buildContents();
- this.drawNew();
- this.changed();
-};
-
-PaintCanvasMorph.prototype.toolChanged = function (tool) {
- this.mask = newCanvas(this.extent());
- if (tool === "crosshairs") {
- this.drawcrosshair();
- }
- this.drawNew();
- this.changed();
-};
-
-PaintCanvasMorph.prototype.drawcrosshair = function (context) {
- var ctx = context || this.mask.getContext("2d"),
- rp = this.rotationCenter;
+ this.buildContents(); + this.drawNew(); + this.changed(); +}; + +PaintCanvasMorph.prototype.toolChanged = function (tool) { + this.mask = newCanvas(this.extent()); + if (tool === "crosshairs") { + this.drawcrosshair(); + } + this.drawNew(); + this.changed(); +}; + +PaintCanvasMorph.prototype.drawcrosshair = function (context) { + var ctx = context || this.mask.getContext("2d"), + rp = this.rotationCenter; ctx.lineWidth = 1; ctx.strokeStyle = 'black'; @@ -646,32 +647,32 @@ PaintCanvasMorph.prototype.drawcrosshair = function (context) { ctx.moveTo(rp.x, 0); ctx.lineTo(rp.x, this.mask.height); ctx.stroke(); -
- this.drawNew();
- this.changed();
-};
-
+ + this.drawNew(); + this.changed(); +}; + PaintCanvasMorph.prototype.floodfill = function (sourcepoint) { - var width = this.paper.width,
- height = this.paper.height,
- ctx = this.paper.getContext("2d"),
- img = ctx.getImageData(0, 0, width, height),
- data = img.data,
- stack = [Math.round(sourcepoint.y) * width + sourcepoint.x],
- currentpoint,
- read,
- sourcecolor,
- checkpoint;
- read = function (p) {
- var d = p * 4;
- return [data[d], data[d + 1], data[d + 2], data[d + 3]];
- };
- sourcecolor = read(stack[0]);
- checkpoint = function (p) {
- return p[0] === sourcecolor[0] &&
- p[1] === sourcecolor[1] &&
- p[2] === sourcecolor[2] &&
- p[3] === sourcecolor[3];
+ var width = this.paper.width, + height = this.paper.height, + ctx = this.paper.getContext("2d"), + img = ctx.getImageData(0, 0, width, height), + data = img.data, + stack = [Math.round(sourcepoint.y) * width + sourcepoint.x], + currentpoint, + read, + sourcecolor, + checkpoint; + read = function (p) { + var d = p * 4; + return [data[d], data[d + 1], data[d + 2], data[d + 3]]; + }; + sourcecolor = read(stack[0]); + checkpoint = function (p) { + return p[0] === sourcecolor[0] && + p[1] === sourcecolor[1] && + p[2] === sourcecolor[2] && + p[3] === sourcecolor[3]; }; // if already filled, abort @@ -690,201 +691,204 @@ PaintCanvasMorph.prototype.floodfill = function (sourcepoint) { } while (stack.length > 0) { - currentpoint = stack.pop();
- if (checkpoint(read(currentpoint))) {
- if (currentpoint % 480 > 1) {
- stack.push(currentpoint + 1);
- stack.push(currentpoint - 1);
- }
- if (currentpoint > 0 && currentpoint < 360 * 480) {
- stack.push(currentpoint + width);
- stack.push(currentpoint - width);
- }
- }
- if (this.settings.primarycolor === "transparent") {
- data[currentpoint * 4 + 3] = 0;
- } else {
- data[currentpoint * 4] = this.settings.primarycolor.r;
- data[currentpoint * 4 + 1] = this.settings.primarycolor.g;
- data[currentpoint * 4 + 2] = this.settings.primarycolor.b;
- data[currentpoint * 4 + 3] = this.settings.primarycolor.a;
- }
- }
+ currentpoint = stack.pop(); + if (checkpoint(read(currentpoint))) { + if (currentpoint % 480 > 1) { + stack.push(currentpoint + 1); + stack.push(currentpoint - 1); + } + if (currentpoint > 0 && currentpoint < 360 * 480) { + stack.push(currentpoint + width); + stack.push(currentpoint - width); + } + } + if (this.settings.primarycolor === "transparent") { + data[currentpoint * 4 + 3] = 0; + } else { + data[currentpoint * 4] = this.settings.primarycolor.r; + data[currentpoint * 4 + 1] = this.settings.primarycolor.g; + data[currentpoint * 4 + 2] = this.settings.primarycolor.b; + data[currentpoint * 4 + 3] = this.settings.primarycolor.a; + } + } ctx.putImageData(img, 0, 0); this.drawNew(); this.changed(); -};
-
-PaintCanvasMorph.prototype.mouseDownLeft = function (pos) {
- this.cacheUndo();
- this.dragRect.origin = pos.subtract(this.bounds.origin);
- this.dragRect.corner = pos.subtract(this.bounds.origin);
- this.previousDragPoint = this.dragRect.corner.copy();
+}; + +PaintCanvasMorph.prototype.mouseDownLeft = function (pos) { + this.cacheUndo(); + this.dragRect.origin = pos.subtract(this.bounds.origin); + this.dragRect.corner = pos.subtract(this.bounds.origin); + this.previousDragPoint = this.dragRect.corner.copy(); if (this.currentTool === 'crosshairs') { this.rotationCenter = pos.subtract(this.bounds.origin); this.drawcrosshair(); return; } - if (this.currentTool === "paintbucket") {
- return this.floodfill(pos.subtract(this.bounds.origin));
- }
- if (this.settings.primarycolor === "transparent" &&
- this.currentTool !== "crosshairs") {
- this.erasermask = newCanvas(this.extent());
- this.merge(this.paper, this.erasermask);
- }
-};
-
+ if (this.currentTool === "paintbucket") { + return this.floodfill(pos.subtract(this.bounds.origin)); + } + if (this.settings.primarycolor === "transparent" && + this.currentTool !== "crosshairs") { + this.erasermask = newCanvas(this.extent()); + this.merge(this.paper, this.erasermask); + } +}; + PaintCanvasMorph.prototype.mouseMove = function (pos) { if (this.currentTool === "paintbucket") { return; } -
- var relpos = pos.subtract(this.bounds.origin),
- mctx = this.mask.getContext("2d"),
- pctx = this.paper.getContext("2d"),
- x = this.dragRect.origin.x, // original drag X
- y = this.dragRect.origin.y, // original drag y
- p = relpos.x, // current drag x
- q = relpos.y, // current drag y
- w = (p - x) / 2, // half the rect width
- h = (q - y) / 2, // half the rect height
- i; // iterator number
- mctx.save();
- function newW() {
- return Math.max(Math.abs(w), Math.abs(h)) * (w / Math.abs(w));
- }
- function newH() {
- return Math.max(Math.abs(w), Math.abs(h)) * (h / Math.abs(h));
- }
- this.brushBuffer.push([p, q]);
- mctx.lineWidth = this.settings.linewidth;
- mctx.clearRect(0, 0, this.bounds.width(), this.bounds.height()); // mask
-
- this.dragRect.corner = relpos.subtract(this.dragRect.origin); // reset crn
-
- if (this.settings.primarycolor === "transparent" &&
- this.currentTool !== "crosshairs") {
- this.merge(this.erasermask, this.mask);
- pctx.clearRect(0, 0, this.bounds.width(), this.bounds.height());
- mctx.globalCompositeOperation = "destination-out";
- } else {
- mctx.fillStyle = this.settings.primarycolor.toString();
- mctx.strokeStyle = this.settings.primarycolor.toString();
- }
- switch (this.currentTool) {
- case "rectangle":
- if (this.isShiftPressed()) {
- mctx.strokeRect(x, y, newW() * 2, newH() * 2);
- } else {
- mctx.strokeRect(x, y, w * 2, h * 2);
- }
- break;
- case "rectangleSolid":
- if (this.isShiftPressed()) {
- mctx.fillRect(x, y, newW() * 2, newH() * 2);
- } else {
- mctx.fillRect(x, y, w * 2, h * 2);
- }
- break;
- case "brush":
- mctx.lineCap = "round";
- mctx.lineJoin = "round";
- mctx.beginPath();
- mctx.moveTo(this.brushBuffer[0][0], this.brushBuffer[0][1]);
- for (i = 0; i < this.brushBuffer.length; i += 1) {
- mctx.lineTo(this.brushBuffer[i][0], this.brushBuffer[i][1]);
- }
- mctx.stroke();
- break;
- case "line":
- mctx.beginPath();
- mctx.moveTo(x, y);
- if (this.isShiftPressed()) {
- if (Math.abs(h) > Math.abs(w)) {
- mctx.lineTo(x, q);
- } else {
- mctx.lineTo(p, y);
- }
- } else {
- mctx.lineTo(p, q);
- }
- mctx.stroke();
- break;
- case "circle":
- case "circleSolid":
- mctx.beginPath();
- if (this.isShiftPressed()) {
- mctx.arc(
- x,
- y,
- new Point(x, y).distanceTo(new Point(p, q)),
- 0,
- Math.PI * 2,
- false
- );
- } else {
- for (i = 0; i < 480; i += 1) {
- mctx.lineTo(
- i,
- (2 * h) * Math.sqrt(2 - Math.pow(
- (i - x) / (2 * w),
- 2
- )) + y
- );
- }
- for (i = 480; i > 0; i -= 1) {
- mctx.lineTo(
- i,
- -1 * (2 * h) * Math.sqrt(2 - Math.pow(
- (i - x) / (2 * w),
- 2
- )) + y
- );
- }
- }
- mctx.closePath();
- if (this.currentTool === "circleSolid") {
- mctx.fill();
- } else {
- if (this.currentTool === "circle") {
- mctx.stroke();
- }
- }
- break;
+ + var relpos = pos.subtract(this.bounds.origin), + mctx = this.mask.getContext("2d"), + pctx = this.paper.getContext("2d"), + x = this.dragRect.origin.x, // original drag X + y = this.dragRect.origin.y, // original drag y + p = relpos.x, // current drag x + q = relpos.y, // current drag y + w = (p - x) / 2, // half the rect width + h = (q - y) / 2, // half the rect height + i; // iterator number + mctx.save(); + function newW() { + return Math.max(Math.abs(w), Math.abs(h)) * (w / Math.abs(w)); + } + function newH() { + return Math.max(Math.abs(w), Math.abs(h)) * (h / Math.abs(h)); + } + this.brushBuffer.push([p, q]); + mctx.lineWidth = this.settings.linewidth; + mctx.clearRect(0, 0, this.bounds.width(), this.bounds.height()); // mask + + this.dragRect.corner = relpos.subtract(this.dragRect.origin); // reset crn + + if (this.settings.primarycolor === "transparent" && + this.currentTool !== "crosshairs") { + this.merge(this.erasermask, this.mask); + pctx.clearRect(0, 0, this.bounds.width(), this.bounds.height()); + mctx.globalCompositeOperation = "destination-out"; + } else { + mctx.fillStyle = this.settings.primarycolor.toString(); + mctx.strokeStyle = this.settings.primarycolor.toString(); + } + switch (this.currentTool) { + case "rectangle": + if (this.isShiftPressed()) { + mctx.strokeRect(x, y, newW() * 2, newH() * 2); + } else { + mctx.strokeRect(x, y, w * 2, h * 2); + } + break; + case "rectangleSolid": + if (this.isShiftPressed()) { + mctx.fillRect(x, y, newW() * 2, newH() * 2); + } else { + mctx.fillRect(x, y, w * 2, h * 2); + } + break; + case "brush": + mctx.lineCap = "round"; + mctx.lineJoin = "round"; + mctx.beginPath(); + mctx.moveTo(this.brushBuffer[0][0], this.brushBuffer[0][1]); + for (i = 0; i < this.brushBuffer.length; i += 1) { + mctx.lineTo(this.brushBuffer[i][0], this.brushBuffer[i][1]); + } + mctx.stroke(); + break; + case "line": + mctx.beginPath(); + mctx.moveTo(x, y); + if (this.isShiftPressed()) { + if (Math.abs(h) > Math.abs(w)) { + mctx.lineTo(x, q); + } else { + mctx.lineTo(p, y); + } + } else { + mctx.lineTo(p, q); + } + mctx.stroke(); + break; + case "circle": + case "circleSolid": + mctx.beginPath(); + if (this.isShiftPressed()) { + mctx.arc( + x, + y, + new Point(x, y).distanceTo(new Point(p, q)), + 0, + Math.PI * 2, + false + ); + } else { + for (i = 0; i < 480; i += 1) { + mctx.lineTo( + i, + (2 * h) * Math.sqrt(2 - Math.pow( + (i - x) / (2 * w), + 2 + )) + y + ); + } + for (i = 480; i > 0; i -= 1) { + mctx.lineTo( + i, + -1 * (2 * h) * Math.sqrt(2 - Math.pow( + (i - x) / (2 * w), + 2 + )) + y + ); + } + } + mctx.closePath(); + if (this.currentTool === "circleSolid") { + mctx.fill(); + } else { + if (this.currentTool === "circle") { + mctx.stroke(); + } + } + break; case "crosshairs": this.rotationCenter = relpos.copy(); this.drawcrosshair(mctx); - break;
- case "eraser":
- this.merge(this.paper, this.mask);
- mctx.save();
- mctx.globalCompositeOperation = "destination-out";
- mctx.beginPath();
- mctx.moveTo(this.brushBuffer[0][0], this.brushBuffer[0][1]);
- for (i = 0; i < this.brushBuffer.length; i += 1) {
- mctx.lineTo(this.brushBuffer[i][0], this.brushBuffer[i][1]);
- }
- mctx.stroke();
- mctx.restore();
- this.paper = newCanvas(this.extent());
- this.merge(this.mask, this.paper);
- break;
- default:
- nop();
- }
- this.previousDragPoint = relpos;
- this.drawNew();
- this.changed();
- mctx.restore();
-};
-
+ break; + case "eraser": + this.merge(this.paper, this.mask); + mctx.save(); + mctx.globalCompositeOperation = "destination-out"; + mctx.beginPath(); + mctx.moveTo(this.brushBuffer[0][0], this.brushBuffer[0][1]); + for (i = 0; i < this.brushBuffer.length; i += 1) { + mctx.lineTo(this.brushBuffer[i][0], this.brushBuffer[i][1]); + } + mctx.stroke(); + mctx.restore(); + this.paper = newCanvas(this.extent()); + this.merge(this.mask, this.paper); + break; + default: + nop(); + } + this.previousDragPoint = relpos; + this.drawNew(); + this.changed(); + mctx.restore(); +}; + PaintCanvasMorph.prototype.mouseClickLeft = function () { - if (this.currentTool !== "crosshairs") {
- this.merge(this.mask, this.paper);
- }
- this.brushBuffer = [];
-};
+ if (this.currentTool !== "crosshairs") { + this.merge(this.mask, this.paper); + } + this.brushBuffer = []; +}; + +PaintCanvasMorph.prototype.mouseLeaveDragging + = PaintCanvasMorph.prototype.mouseClickLeft; PaintCanvasMorph.prototype.buildContents = function () { this.background = newCanvas(this.extent()); @@ -903,47 +907,47 @@ PaintCanvasMorph.prototype.buildContents = function () { } } }; -
-PaintCanvasMorph.prototype.drawNew = function () {
- var can = newCanvas(this.extent());
- this.merge(this.background, can);
- this.merge(this.paper, can);
- this.merge(this.mask, can);
- this.image = can;
- this.drawFrame();
-};
-
-PaintCanvasMorph.prototype.drawFrame = function () {
- var context, borderColor;
-
- context = this.image.getContext('2d');
- if (this.parent) {
- this.color = this.parent.color.lighter(this.contrast * 0.75);
- borderColor = this.parent.color;
- } else {
- borderColor = new Color(120, 120, 120);
- }
- context.fillStyle = this.color.toString();
-
- // cache my border colors
- this.cachedClr = borderColor.toString();
- this.cachedClrBright = borderColor.lighter(this.contrast)
- .toString();
- this.cachedClrDark = borderColor.darker(this.contrast).toString();
- this.drawRectBorder(context);
-};
-
-PaintCanvasMorph.prototype.drawRectBorder
- = InputFieldMorph.prototype.drawRectBorder;
-
-PaintCanvasMorph.prototype.edge
- = InputFieldMorph.prototype.edge;
-
-PaintCanvasMorph.prototype.fontSize
- = InputFieldMorph.prototype.fontSize;
-
-PaintCanvasMorph.prototype.typeInPadding
- = InputFieldMorph.prototype.typeInPadding;
-
-PaintCanvasMorph.prototype.contrast
- = InputFieldMorph.prototype.contrast;
+ +PaintCanvasMorph.prototype.drawNew = function () { + var can = newCanvas(this.extent()); + this.merge(this.background, can); + this.merge(this.paper, can); + this.merge(this.mask, can); + this.image = can; + this.drawFrame(); +}; + +PaintCanvasMorph.prototype.drawFrame = function () { + var context, borderColor; + + context = this.image.getContext('2d'); + if (this.parent) { + this.color = this.parent.color.lighter(this.contrast * 0.75); + borderColor = this.parent.color; + } else { + borderColor = new Color(120, 120, 120); + } + context.fillStyle = this.color.toString(); + + // cache my border colors + this.cachedClr = borderColor.toString(); + this.cachedClrBright = borderColor.lighter(this.contrast) + .toString(); + this.cachedClrDark = borderColor.darker(this.contrast).toString(); + this.drawRectBorder(context); +}; + +PaintCanvasMorph.prototype.drawRectBorder + = InputFieldMorph.prototype.drawRectBorder; + +PaintCanvasMorph.prototype.edge + = InputFieldMorph.prototype.edge; + +PaintCanvasMorph.prototype.fontSize + = InputFieldMorph.prototype.fontSize; + +PaintCanvasMorph.prototype.typeInPadding + = InputFieldMorph.prototype.typeInPadding; + +PaintCanvasMorph.prototype.contrast + = InputFieldMorph.prototype.contrast; |
