summaryrefslogtreecommitdiff
path: root/morphic.js
diff options
context:
space:
mode:
authorGubolin <gubolin@fantasymail.de>2015-07-23 14:28:31 +0200
committerGubolin <gubolin@fantasymail.de>2015-07-23 14:28:31 +0200
commit29b5dba84e8f3aa95a3b9296d5a01a2609f4590d (patch)
tree4ec090b68a695c6ba6d7bc26b6362519b485a0ba /morphic.js
parent526a595c5e00f171eb814f063e58a6c18c5784d4 (diff)
parent5aec52889d79a8257971095719195fabcbc33559 (diff)
downloadsnap-gh-pages.tar.gz
snap-gh-pages.zip
merge developmentgh-pages
Diffstat (limited to 'morphic.js')
-rw-r--r--morphic.js410
1 files changed, 150 insertions, 260 deletions
diff --git a/morphic.js b/morphic.js
index 4a16597..f359bb3 100644
--- a/morphic.js
+++ b/morphic.js
@@ -8,7 +8,7 @@
written by Jens Mönig
jens@moenig.org
- Copyright (C) 2014 by Jens Mönig
+ Copyright (C) 2015 by Jens Mönig
This file is part of Snap!.
@@ -527,6 +527,12 @@
a duplicate of the template whose "isDraggable" flag is true and
whose "isTemplate" flag is false, in other words: a non-template.
+ When creating a copy from a template, the copy's
+
+ reactToTemplateCopy
+
+ is invoked, if it is present.
+
Dragging is indicated by adding a drop shadow to the morph in hand.
If a morph follows the hand without displaying a drop shadow it is
merely being moved about without changing its parent (owner morph),
@@ -822,13 +828,13 @@
// use context to paint stuff here
};
- If your new morph stores or references other morphs outside of the
- submorph tree in other properties, be sure to also override the
+ If your new morph stores or references to other morphs outside of
+ the submorph tree in other properties, be sure to also override the
default
- copyRecordingReferences()
+ updateReferences()
- method accordingly if you want it to support duplication.
+ method if you want it to support duplication.
(6) development and user modes
@@ -1020,16 +1026,17 @@
programming hero.
I have originally written morphic.js in Florian Balmer's Notepad2
- editor for Windows and later switched to Apple's Dashcode. I've also
- come to depend on both Douglas Crockford's JSLint, Mozilla's Firebug
- and Google's Chrome to get it right.
+ editor for Windows, later switched to Apple's Dashcode and later
+ still to Apple's Xcode. I've also come to depend on both Douglas
+ Crockford's JSLint, Mozilla's Firebug and Google's Chrome to get
+ it right.
IX. contributors
----------------------
Joe Otto found and fixed many early bugs and taught me some tricks.
Nathan Dinsmore contributed mouse wheel scrolling, cached
- background texture handling and countless bug fixes.
+ background texture handling, countless bug fixes and optimizations.
Ian Reynolds contributed backspace key handling for Chrome.
Davide Della Casa contributed performance optimizations for Firefox.
@@ -1041,7 +1048,7 @@
/*global window, HTMLCanvasElement, getMinimumFontHeight, FileReader, Audio,
FileList, getBlurredShadowSupport*/
-var morphicVersion = '2014-December-05';
+var morphicVersion = '2015-June-26';
var modules = {}; // keep track of additional loaded modules
var useBlurredShadows = getBlurredShadowSupport(); // check for Chrome-bug
@@ -1236,19 +1243,9 @@ function getDocumentPositionOf(aDOMelement) {
return pos;
}
-function clone(target) {
- // answer a new instance of target's type
- if (typeof target === 'object') {
- var Clone = function () {nop(); };
- Clone.prototype = target;
- return new Clone();
- }
- return target;
-}
-
function copy(target) {
// answer a shallow copy of target
- var value, c, property;
+ var value, c, property, keys, l, i;
if (typeof target !== 'object') {
return target;
@@ -1259,18 +1256,16 @@ function copy(target) {
}
if (target instanceof target.constructor &&
target.constructor !== Object) {
- c = clone(target.constructor.prototype);
- for (property in target) {
- if (Object.prototype.hasOwnProperty.call(target, property)) {
- c[property] = target[property];
- }
+ c = Object.create(target.constructor.prototype);
+ keys = Object.keys(target);
+ for (l = keys.length, i = 0; i < l; i += 1) {
+ property = keys[i];
+ c[property] = target[property];
}
} else {
c = {};
for (property in target) {
- if (!c[property]) {
- c[property] = target[property];
- }
+ c[property] = target[property];
}
}
return c;
@@ -2205,7 +2200,7 @@ function Morph() {
// Morph initialization:
-Morph.prototype.init = function () {
+Morph.prototype.init = function (noDraw) {
Morph.uber.init.call(this);
this.isMorph = true;
this.bounds = new Rectangle(0, 0, 50, 40);
@@ -2218,7 +2213,7 @@ Morph.prototype.init = function () {
this.isTemplate = false;
this.acceptsDrops = false;
this.noticesTransparentClick = false;
- this.drawNew();
+ if (!noDraw) {this.drawNew(); }
this.fps = 0;
this.customContextMenu = null;
this.lastTime = Date.now();
@@ -2404,19 +2399,19 @@ Morph.prototype.visibleBounds = function () {
// Morph accessing - simple changes:
Morph.prototype.moveBy = function (delta) {
- this.changed();
- this.bounds = this.bounds.translateBy(delta);
- this.children.forEach(function (child) {
- child.moveBy(delta);
- });
- this.changed();
+ this.fullChanged();
+ this.silentMoveBy(delta);
+ this.fullChanged();
};
Morph.prototype.silentMoveBy = function (delta) {
+ var children = this.children,
+ i = children.length;
this.bounds = this.bounds.translateBy(delta);
- this.children.forEach(function (child) {
- child.silentMoveBy(delta);
- });
+ // ugly optimization avoiding forEach()
+ for (i; i > 0; i -= 1) {
+ children[i - 1].silentMoveBy(delta);
+ }
};
Morph.prototype.setPosition = function (aPoint) {
@@ -2892,7 +2887,7 @@ Morph.prototype.fullChanged = function () {
};
Morph.prototype.childChanged = function () {
- // react to a change in one of my children,
+ // react to a change in one of my children,
// default is to just pass this message on upwards
// override this method for Morphs that need to adjust accordingly
if (this.parent) {
@@ -2929,6 +2924,18 @@ Morph.prototype.addBack = function (aMorph) {
this.addChildFirst(aMorph);
};
+Morph.prototype.topMorphAt = function (point) {
+ var i, result;
+ if (!this.isVisible) {return null; }
+ for (i = this.children.length - 1; i >= 0; i -= 1) {
+ result = this.children[i].topMorphAt(point);
+ if (result) {return result; }
+ }
+ return this.bounds.containsPoint(point) &&
+ (this.noticesTransparentClick || !this.isTransparentAt(point)) ? this
+ : null;
+};
+
Morph.prototype.topMorphSuchThat = function (predicate) {
var next;
if (predicate.call(null, this)) {
@@ -2944,30 +2951,6 @@ Morph.prototype.topMorphSuchThat = function (predicate) {
return null;
};
-Morph.prototype.morphAt = function (aPoint) {
- var morphs = this.allChildren().slice(0).reverse(),
- result = null;
- morphs.forEach(function (m) {
- if (m.fullBounds().containsPoint(aPoint) &&
- (result === null)) {
- result = m;
- }
- });
- return result;
-};
-
-/*
- alternative - more elegant and possibly more
- performant - solution for morphAt.
- Has some issues, commented out for now
-
-Morph.prototype.morphAt = function (aPoint) {
- return this.topMorphSuchThat(function (m) {
- return m.fullBounds().containsPoint(aPoint);
- });
-};
-*/
-
Morph.prototype.overlappedMorphs = function () {
//exclude the World
var world = this.world(),
@@ -3039,45 +3022,54 @@ Morph.prototype.fullCopy = function () {
Other properties are also *shallow* copied, so you must override
to deep copy Arrays and (complex) Objects
*/
- var dict = {}, c;
- c = this.copyRecordingReferences(dict);
+ var map = new Map(), c;
+ c = this.copyRecordingReferences(map);
c.forAllChildren(function (m) {
- m.updateReferences(dict);
+ m.updateReferences(map);
});
return c;
};
-Morph.prototype.copyRecordingReferences = function (dict) {
+Morph.prototype.copyRecordingReferences = function (map) {
/*
Recursively copy this entire composite morph, recording the
correspondence between old and new morphs in the given dictionary.
This dictionary will be used to update intra-composite references
in the copy. See updateReferences().
- Note: This default implementation copies ONLY morphs in the
- submorph hierarchy. If a morph stores morphs in other properties
- that it wants to copy, then it should override this method to do so.
- The same goes for morphs that contain other complex data that
- should be copied when the morph is duplicated.
+
+ Note: This default implementation copies ONLY morphs. If a morph
+ stores morphs in other properties that it wants to copy, then it
+ should override this method to do so. The same goes for morphs that
+ contain other complex data that should be copied when the morph is
+ duplicated.
*/
var c = this.copy();
- dict[this] = c;
+ map.set(this, c);
this.children.forEach(function (m) {
- c.add(m.copyRecordingReferences(dict));
+ c.add(m.copyRecordingReferences(map));
});
return c;
};
-Morph.prototype.updateReferences = function (dict) {
+Morph.prototype.updateReferences = function (map) {
/*
Update intra-morph references within a composite morph that has
been copied. For example, if a button refers to morph X in the
orginal composite then the copy of that button in the new composite
should refer to the copy of X in new composite, not the original X.
*/
- var property;
- for (property in this) {
- if (this[property] && this[property].isMorph && dict[property]) {
- this[property] = dict[property];
+ var properties = Object.keys(this),
+ l = properties.length,
+ property,
+ value,
+ reference,
+ i;
+ for (i = 0; i < l; i += 1) {
+ property = properties[i];
+ value = this[property];
+ if (value && value.isMorph) {
+ reference = map.get(value);
+ if (reference) { this[property] = reference; }
}
}
};
@@ -3148,9 +3140,7 @@ Morph.prototype.slideBackTo = function (situation, inSteps) {
this.fps = 0;
this.step = function () {
- myself.fullChanged();
- myself.silentMoveBy(new Point(xStep, yStep));
- myself.fullChanged();
+ myself.moveBy(new Point(xStep, yStep));
stepCount += 1;
if (stepCount === steps) {
situation.origin.add(myself);
@@ -3699,6 +3689,10 @@ function ShadowMorph() {
this.init();
}
+ShadowMorph.prototype.topMorphAt = function () {
+ return null;
+};
+
// HandleMorph ////////////////////////////////////////////////////////
// I am a resize / move handle that can be attached to any Morph
@@ -3916,20 +3910,6 @@ HandleMorph.prototype.mouseLeave = function () {
this.changed();
};
-// HandleMorph duplicating:
-
-HandleMorph.prototype.copyRecordingReferences = function (dict) {
- // inherited, see comment in Morph
- var c = HandleMorph.uber.copyRecordingReferences.call(
- this,
- dict
- );
- if (c.target && dict[this.target]) {
- c.target = (dict[this.target]);
- }
- return c;
-};
-
// HandleMorph menu:
HandleMorph.prototype.attach = function () {
@@ -4253,20 +4233,6 @@ ColorPaletteMorph.prototype.updateTarget = function () {
}
};
-// ColorPaletteMorph duplicating:
-
-ColorPaletteMorph.prototype.copyRecordingReferences = function (dict) {
- // inherited, see comment in Morph
- var c = ColorPaletteMorph.uber.copyRecordingReferences.call(
- this,
- dict
- );
- if (c.target && dict[this.target]) {
- c.target = (dict[this.target]);
- }
- return c;
-};
-
// ColorPaletteMorph menu:
ColorPaletteMorph.prototype.developersMenu = function () {
@@ -5833,23 +5799,6 @@ SliderMorph.prototype.updateTarget = function () {
}
};
-// SliderMorph duplicating:
-
-SliderMorph.prototype.copyRecordingReferences = function (dict) {
- // inherited, see comment in Morph
- var c = SliderMorph.uber.copyRecordingReferences.call(
- this,
- dict
- );
- if (c.target && dict[this.target]) {
- c.target = (dict[this.target]);
- }
- if (c.button && dict[this.button]) {
- c.button = (dict[this.button]);
- }
- return c;
-};
-
// SliderMorph menu:
SliderMorph.prototype.developersMenu = function () {
@@ -5926,7 +5875,7 @@ SliderMorph.prototype.userSetStart = function (num) {
this.start = Math.max(num, this.stop);
};
-SliderMorph.prototype.setStart = function (num) {
+SliderMorph.prototype.setStart = function (num, noUpdate) {
// for context menu demo purposes
var newStart;
if (typeof num === 'number') {
@@ -5944,12 +5893,12 @@ SliderMorph.prototype.setStart = function (num) {
}
}
this.value = Math.max(this.value, this.start);
- this.updateTarget();
+ if (!noUpdate) {this.updateTarget(); }
this.drawNew();
this.changed();
};
-SliderMorph.prototype.setStop = function (num) {
+SliderMorph.prototype.setStop = function (num, noUpdate) {
// for context menu demo purposes
var newStop;
if (typeof num === 'number') {
@@ -5961,12 +5910,12 @@ SliderMorph.prototype.setStop = function (num) {
}
}
this.value = Math.min(this.value, this.stop);
- this.updateTarget();
+ if (!noUpdate) {this.updateTarget(); }
this.drawNew();
this.changed();
};
-SliderMorph.prototype.setSize = function (num) {
+SliderMorph.prototype.setSize = function (num, noUpdate) {
// for context menu demo purposes
var newSize;
if (typeof num === 'number') {
@@ -5984,7 +5933,7 @@ SliderMorph.prototype.setSize = function (num) {
}
}
this.value = Math.min(this.value, this.stop - this.size);
- this.updateTarget();
+ if (!noUpdate) {this.updateTarget(); }
this.drawNew();
this.changed();
};
@@ -6556,7 +6505,7 @@ InspectorMorph.prototype.setExtent = function (aPoint) {
this.fixLayout();
};
-//InspectorMorph editing ops:
+// InspectorMorph editing ops:
InspectorMorph.prototype.save = function () {
var txt = this.detail.contents.children[0].text.toString(),
@@ -6646,6 +6595,15 @@ InspectorMorph.prototype.step = function () {
this.fixLayout();
};
+// InspectorMorph duplicating:
+
+InspectorMorph.prototype.updateReferences = function (map) {
+ var active = this.list.activeIndex();
+ InspectorMorph.uber.updateReferences.call(this, map);
+ this.buildPanes();
+ this.list.activateIndex(active);
+};
+
// MenuMorph ///////////////////////////////////////////////////////////
// MenuMorph: referenced constructors
@@ -7017,7 +6975,7 @@ StringMorph.prototype.init = function (
this.markedBackgoundColor = new Color(60, 60, 120);
// initialize inherited properties:
- StringMorph.uber.init.call(this);
+ StringMorph.uber.init.call(this, true);
// override inherited properites:
this.color = color || new Color(0, 0, 0);
@@ -7395,6 +7353,11 @@ StringMorph.prototype.selectionStartSlot = function () {
};
StringMorph.prototype.clearSelection = function () {
+ if (!this.currentlySelecting &&
+ this.startMark === 0 &&
+ this.endMark === 0) {
+ return;
+ }
this.currentlySelecting = false;
this.startMark = 0;
this.endMark = 0;
@@ -7447,6 +7410,8 @@ StringMorph.prototype.mouseClickLeft = function (pos) {
StringMorph.prototype.enableSelecting = function () {
this.mouseDownLeft = function (pos) {
+ var crs = this.root().cursor,
+ already = crs ? crs.target === this : false;
this.clearSelection();
if (this.isEditable && (!this.isDraggable)) {
this.edit();
@@ -7454,6 +7419,7 @@ StringMorph.prototype.enableSelecting = function () {
this.startMark = this.slotAt(pos);
this.endMark = this.startMark;
this.currentlySelecting = true;
+ if (!already) {this.escalateEvent('mouseDownLeft', pos); }
}
};
this.mouseMove = function (pos) {
@@ -8060,7 +8026,7 @@ TriggerMorph.prototype.init = function (
this.environment = environment || null;
this.labelString = labelString || null;
this.label = null;
- this.hint = hint || null;
+ this.hint = hint || null; // null, String, or Function
this.fontSize = fontSize || MorphicPreferences.menuFontSize;
this.fontStyle = fontStyle || 'sans-serif';
this.highlightColor = new Color(192, 192, 192);
@@ -8131,20 +8097,6 @@ TriggerMorph.prototype.createLabel = function () {
this.add(this.label);
};
-// TriggerMorph duplicating:
-
-TriggerMorph.prototype.copyRecordingReferences = function (dict) {
- // inherited, see comment in Morph
- var c = TriggerMorph.uber.copyRecordingReferences.call(
- this,
- dict
- );
- if (c.label && dict[this.label]) {
- c.label = (dict[this.label]);
- }
- return c;
-};
-
// TriggerMorph action:
TriggerMorph.prototype.trigger = function () {
@@ -8209,10 +8161,11 @@ TriggerMorph.prototype.triggerDoubleClick = function () {
// TriggerMorph events:
TriggerMorph.prototype.mouseEnter = function () {
+ var contents = this.hint instanceof Function ? this.hint() : this.hint;
this.image = this.highlightImage;
this.changed();
- if (this.hint) {
- this.bubbleHelp(this.hint);
+ if (contents) {
+ this.bubbleHelp(contents);
}
};
@@ -8489,15 +8442,19 @@ FrameMorph.prototype.fullDrawOn = function (aCanvas, aRect) {
});
};
-// FrameMorph scrolling optimization:
+// FrameMorph navigation:
-FrameMorph.prototype.moveBy = function (delta) {
- this.changed();
- this.bounds = this.bounds.translateBy(delta);
- this.children.forEach(function (child) {
- child.silentMoveBy(delta);
- });
- this.changed();
+FrameMorph.prototype.topMorphAt = function (point) {
+ var i, result;
+ if (!(this.isVisible && this.bounds.containsPoint(point))) {
+ return null;
+ }
+ for (i = this.children.length - 1; i >= 0; i -= 1) {
+ result = this.children[i].topMorphAt(point);
+ if (result) {return result; }
+ }
+ return this.noticesTransparentClick ||
+ !this.isTransparentAt(point) ? this : null;
};
// FrameMorph scrolling support:
@@ -8589,20 +8546,6 @@ FrameMorph.prototype.reactToGrabOf = function () {
this.adjustBounds();
};
-// FrameMorph duplicating:
-
-FrameMorph.prototype.copyRecordingReferences = function (dict) {
- // inherited, see comment in Morph
- var c = FrameMorph.uber.copyRecordingReferences.call(
- this,
- dict
- );
- if (c.frame && dict[this.scrollFrame]) {
- c.frame = (dict[this.scrollFrame]);
- }
- return c;
-};
-
// FrameMorph menus:
FrameMorph.prototype.developersMenu = function () {
@@ -8947,32 +8890,23 @@ ScrollFrameMorph.prototype.mouseScroll = function (y, x) {
// ScrollFrameMorph duplicating:
-ScrollFrameMorph.prototype.copyRecordingReferences = function (dict) {
- // inherited, see comment in Morph
- var c = ScrollFrameMorph.uber.copyRecordingReferences.call(
- this,
- dict
- );
- if (c.contents && dict[this.contents]) {
- c.contents = (dict[this.contents]);
- }
- if (c.hBar && dict[this.hBar]) {
- c.hBar = (dict[this.hBar]);
- c.hBar.action = function (num) {
- c.contents.setPosition(
- new Point(c.left() - num, c.contents.position().y)
+ScrollFrameMorph.prototype.updateReferences = function (map) {
+ var myself = this;
+ ScrollFrameMorph.uber.updateReferences.call(this, map);
+ if (this.hBar) {
+ this.hBar.action = function (num) {
+ myself.contents.setPosition(
+ new Point(myself.left() - num, myself.contents.position().y)
);
};
}
- if (c.vBar && dict[this.vBar]) {
- c.vBar = (dict[this.vBar]);
- c.vBar.action = function (num) {
- c.contents.setPosition(
- new Point(c.contents.position().x, c.top() - num)
+ if (this.vBar) {
+ this.vBar.action = function (num) {
+ myself.contents.setPosition(
+ new Point(myself.contents.position().x, myself.top() - num)
);
};
}
- return c;
};
// ScrollFrameMorph menu:
@@ -9136,6 +9070,18 @@ ListMorph.prototype.setExtent = function (aPoint) {
ListMorph.uber.setExtent.call(this, aPoint);
};
+ListMorph.prototype.activeIndex = function () {
+ return this.listContents.children.indexOf(this.active);
+};
+
+ListMorph.prototype.activateIndex = function (idx) {
+ var item = this.listContents.children[idx];
+ if (!item) {return; }
+ item.image = item.pressImage;
+ item.changed();
+ item.trigger();
+};
+
// StringFieldMorph ////////////////////////////////////////////////////
// StringFieldMorph inherit from FrameMorph:
@@ -9232,20 +9178,6 @@ StringFieldMorph.prototype.mouseClickLeft = function (pos) {
}
};
-// StringFieldMorph duplicating:
-
-StringFieldMorph.prototype.copyRecordingReferences = function (dict) {
- // inherited, see comment in Morph
- var c = StringFieldMorph.uber.copyRecordingReferences.call(
- this,
- dict
- );
- if (c.text && dict[this.text]) {
- c.text = (dict[this.text]);
- }
- return c;
-};
-
// BouncerMorph ////////////////////////////////////////////////////////
// I am a Demo of a stepping custom Morph
@@ -9373,54 +9305,18 @@ HandMorph.prototype.changed = function () {
if (this.world !== null) {
b = this.fullBounds();
if (!b.extent().eq(new Point())) {
- this.world.broken.push(this.fullBounds().spread());
+ this.world.broken.push(b.spread());
}
}
-
};
-// HandMorph navigation:
-
-HandMorph.prototype.morphAtPointer = function () {
- var morphs = this.world.allChildren().slice(0).reverse(),
- myself = this,
- result = null;
-
- morphs.forEach(function (m) {
- if (m.visibleBounds().containsPoint(myself.bounds.origin) &&
- result === null &&
- m.isVisible &&
- (m.noticesTransparentClick ||
- (!m.isTransparentAt(myself.bounds.origin))) &&
- (!(m instanceof ShadowMorph)) &&
- m.allParents().every(function (each) {
- return each.isVisible;
- })) {
- result = m;
- }
- });
- if (result !== null) {
- return result;
- }
- return this.world;
-};
+HandMorph.prototype.fullChanged = HandMorph.prototype.changed;
-/*
- alternative - more elegant and possibly more
- performant - solution for morphAtPointer.
- Has some issues, commented out for now
+// HandMorph navigation:
HandMorph.prototype.morphAtPointer = function () {
- var myself = this;
- return this.world.topMorphSuchThat(function (m) {
- return m.visibleBounds().containsPoint(myself.bounds.origin) &&
- m.isVisible &&
- (m.noticesTransparentClick ||
- (! m.isTransparentAt(myself.bounds.origin))) &&
- (! (m instanceof ShadowMorph));
- });
+ return this.world.topMorphAt(this.bounds.origin) || this.world;
};
-*/
HandMorph.prototype.allMorphsAtPointer = function () {
var morphs = this.world.allChildren(),
@@ -9683,6 +9579,9 @@ HandMorph.prototype.processMouseMove = function (event) {
morph = this.morphToGrab.fullCopy();
morph.isTemplate = false;
morph.isDraggable = true;
+ if (morph.reactToTemplateCopy) {
+ morph.reactToTemplateCopy();
+ }
this.grab(morph);
this.grabOrigin = this.morphToGrab.situation();
}
@@ -9957,16 +9856,6 @@ HandMorph.prototype.destroyTemporaries = function () {
});
};
-// HandMorph dragging optimization
-
-HandMorph.prototype.moveBy = function (delta) {
- Morph.prototype.trackChanges = false;
- HandMorph.uber.moveBy.call(this, delta);
- Morph.prototype.trackChanges = true;
- this.fullChanged();
-};
-
-
// WorldMorph //////////////////////////////////////////////////////////
// I represent the <canvas> element
@@ -9995,6 +9884,7 @@ WorldMorph.prototype.init = function (aCanvas, fillPage) {
this.isDraggable = false;
this.currentKey = null; // currently pressed key code
this.worldCanvas = aCanvas;
+ this.noticesTransparentClick = true;
// additional properties:
this.stamp = Date.now(); // reference in multi-world setups