Skip to content

Commit 56d3fab

Browse files
committed
improved user interaction scenarios for buttons, handles and sliders
1 parent 4655256 commit 56d3fab

File tree

5 files changed

+233
-189
lines changed

5 files changed

+233
-189
lines changed

HISTORY.md

+4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## in development:
44

5+
### 2025-03-19
6+
* morphic: added lockMouseFocus() mechanism and the concept of a clickTarget, improved user interaction scenarios for buttons, handles and sliders
7+
* gui: improved user interaction scenarios for buttons, handles and sliders
8+
59
### 2025-03-17
610
* new dev version
711
* morphic: simplified BoxMorph & CircleBoxMorph outlinePath rendering with new Canvas roundRect() primitive

src/morphic.txt renamed to docs/morphic.txt

+49-5
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77
written by Jens Mönig
88
99

10-
Copyright (C) 2010-2022 by Jens Mönig
11-
12-
This documentation last changed: August 03, 2022
10+
Copyright (C) 2010-2025 by Jens Mönig
1311

12+
This documentation last changed: March 19, 2025
13+
1414
This file is part of Snap!.
1515

1616
Snap! is free software: you can redistribute it and/or modify
@@ -50,6 +50,7 @@
5050
(f) resize event
5151
(g) combined mouse-keyboard events
5252
(h) text editing events
53+
(i) indicating unsaved changes
5354
(4) stepping
5455
(5) creating new kinds of morphs
5556
(a) drawing the shape
@@ -318,7 +319,6 @@
318319
var world1, world2;
319320

320321
window.onload = function () {
321-
disableRetinaSupport();
322322
world1 = new WorldMorph(
323323
document.getElementById('world1'), false);
324324
world2 = new WorldMorph(
@@ -327,7 +327,7 @@
327327
};
328328

329329
function loop() {
330-
requestAnimationFrame(loop);
330+
requestAnimationFrame(loop);
331331
world1.doOneCycle();
332332
world2.doOneCycle();
333333
}
@@ -492,6 +492,19 @@
492492
currently pressed mouse button, which is either 'left' or 'right'.
493493
You can use this to let users interact with 3D environments.
494494

495+
Mouse move and click events are only triggered when and as long as the
496+
mouse pointer is within a morph's bounds. If you wish to confine and
497+
retain mouse interaction to a particular morph while a mouse button is
498+
pressed you can call the morph's
499+
500+
lockMouseFocus()
501+
502+
method, preferrably in one of its mouseDown methods. Afterwards the
503+
ensuing mouse events will be routed to the locked morph even after the
504+
mouse pointer has left its bounds, and the mouseLeave event will not
505+
occur until the mouse button is released again. This is useful for
506+
creating sliders and handle widgets for resizing, moving, rotating etc.
507+
495508
The
496509

497510
mouseEnterDragging(morph)
@@ -858,6 +871,19 @@
858871
single-line text elements can hold them apart.
859872

860873

874+
(i) indicating unsaved changes
875+
------------------------------
876+
Before closing a browser tab with a Morphic world any top level morph
877+
can signal unsaved changes by implementing a
878+
879+
hasUnsavedChanges()
880+
881+
method, which returns a Boolean value indicating whether it is safe to
882+
destroy. If any top level morph indicates unsaved changes the browser
883+
pops up a dialog box warning about unsaved changes and prompting for user
884+
confirmation to close it.
885+
886+
861887
(4) stepping
862888
------------
863889
Stepping is what makes Morphic "magical". Two properties control
@@ -1230,6 +1256,24 @@
12301256

12311257
are implemented.
12321258

1259+
Animations can also be used to schedule a function execution just once
1260+
in lockstep with the Morphic scheduler, avoiding the setTimeout() pattern.
1261+
A syntactic shortcut for single-time scheduling exists in the WorldMorph's
1262+
1263+
schedule()
1264+
1265+
method.
1266+
1267+
For usage examples look at how TriggerMorph implements
1268+
1269+
bubbleHelp()
1270+
1271+
or at MenuItemMorph's
1272+
1273+
delaySubmenu()
1274+
1275+
method.
1276+
12331277

12341278
(10) minifying morphic.js
12351279
-------------------------

snap.html

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@
1313
<meta name="mobile-web-app-title" content="Snap!">
1414
<meta name="msapplication-TileImage" content="img/snap-icon-144.png">
1515
<meta name="msapplication-TileColor" content="#FFFFFF">
16-
<script src="src/morphic.js?version=2025-03-17"></script>
16+
<script src="src/morphic.js?version=2025-03-19"></script>
1717
<script src="src/symbols.js?version=2024-11-24"></script>
1818
<script src="src/widgets.js?version=2025-03-17"></script>
1919
<script src="src/blocks.js?version=2025-01-25"></script>
2020
<script src="src/threads.js?version=2025-02-26"></script>
2121
<script src="src/objects.js?version=2025-03-17"></script>
2222
<script src="src/scenes.js?version=2024-05-28"></script>
23-
<script src="src/gui.js?version=2025-03-17"></script>
23+
<script src="src/gui.js?version=2025-03-19"></script>
2424
<script src="src/paint.js?version=2023-05-24"></script>
2525
<script src="src/lists.js?version=2025-02-27"></script>
2626
<script src="src/byob.js?version=2024-12-18"></script>

src/gui.js

+44-66
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ HatBlockMorph*/
8787

8888
// Global stuff ////////////////////////////////////////////////////////
8989

90-
modules.gui = '2025-March-17';
90+
modules.gui = '2025-March-19';
9191

9292
// Declarations
9393

@@ -12819,6 +12819,7 @@ function StageHandleMorph(target) {
1281912819

1282012820
StageHandleMorph.prototype.init = function (target) {
1282112821
this.target = target || null;
12822+
this.offset = null;
1282212823
this.userState = 'normal'; // or 'highlight'
1282312824
HandleMorph.uber.init.call(this);
1282412825
this.color = IDE_Morph.prototype.isBright ?
@@ -12897,37 +12898,6 @@ StageHandleMorph.prototype.fixLayout = function () {
1289712898
if (ide) {ide.add(this); } // come to front
1289812899
};
1289912900

12900-
// StageHandleMorph stepping:
12901-
12902-
StageHandleMorph.prototype.step = null;
12903-
12904-
StageHandleMorph.prototype.mouseDownLeft = function (pos) {
12905-
var world = this.world(),
12906-
offset = this.right() - pos.x,
12907-
ide = this.target.parentThatIsA(IDE_Morph);
12908-
12909-
if (!this.target) {
12910-
return null;
12911-
}
12912-
ide.isSmallStage = true;
12913-
ide.controlBar.stageSizeButton.refresh();
12914-
12915-
this.step = function () {
12916-
var newPos, newWidth;
12917-
if (world.hand.mouseButton) {
12918-
newPos = world.hand.bounds.origin.x + offset;
12919-
newWidth = this.target.right() - newPos;
12920-
ide.stageRatio = newWidth / this.target.dimensions.x;
12921-
ide.setExtent(world.extent());
12922-
12923-
} else {
12924-
this.step = null;
12925-
ide.isSmallStage = (ide.stageRatio !== 1);
12926-
ide.controlBar.stageSizeButton.refresh();
12927-
}
12928-
};
12929-
};
12930-
1293112901
// StageHandleMorph events:
1293212902

1293312903
StageHandleMorph.prototype.mouseEnter = function () {
@@ -12940,6 +12910,26 @@ StageHandleMorph.prototype.mouseLeave = function () {
1294012910
this.rerender();
1294112911
};
1294212912

12913+
StageHandleMorph.prototype.mouseDownLeft = function (pos) {
12914+
var ide = this.target.parentThatIsA(IDE_Morph);
12915+
this.offset = this.right() - pos.x;
12916+
ide.isSmallStage = true;
12917+
ide.controlBar.stageSizeButton.refresh();
12918+
this.lockMouseFocus();
12919+
};
12920+
12921+
StageHandleMorph.prototype.mouseMove = function (pos) {
12922+
var ide = this.target.parentThatIsA(IDE_Morph),
12923+
newPos = pos.x + this.offset,
12924+
newWidth = this.target.right() - newPos;
12925+
ide.stageRatio = newWidth / this.target.dimensions.x;
12926+
if (ide.isSmallStage !== (ide.stageRatio !== 1)) {
12927+
ide.isSmallStage = (ide.stageRatio !== 1);
12928+
ide.controlBar.stageSizeButton.refresh();
12929+
}
12930+
ide.setExtent(ide.world().extent());
12931+
};
12932+
1294312933
StageHandleMorph.prototype.mouseDoubleClick = function () {
1294412934
this.target.parentThatIsA(IDE_Morph).toggleStageSize(true, 1);
1294512935
};
@@ -12963,6 +12953,7 @@ function PaletteHandleMorph(target) {
1296312953

1296412954
PaletteHandleMorph.prototype.init = function (target) {
1296512955
this.target = target || null;
12956+
this.offset = null;
1296612957
this.userState = 'normal';
1296712958
HandleMorph.uber.init.call(this);
1296812959
this.color = IDE_Morph.prototype.isBright ?
@@ -12989,40 +12980,6 @@ PaletteHandleMorph.prototype.fixLayout = function () {
1298912980
if (ide) {ide.add(this); } // come to front
1299012981
};
1299112982

12992-
// PaletteHandleMorph stepping:
12993-
12994-
PaletteHandleMorph.prototype.step = null;
12995-
12996-
PaletteHandleMorph.prototype.mouseDownLeft = function (pos) {
12997-
var world = this.world(),
12998-
offset = this.right() - pos.x,
12999-
ide = this.target.parentThatIsA(IDE_Morph),
13000-
cnf = ide.config,
13001-
border = cnf.border || 0;
13002-
13003-
if (!this.target) {
13004-
return null;
13005-
}
13006-
this.step = function () {
13007-
var newPos;
13008-
if (world.hand.mouseButton) {
13009-
newPos = world.hand.bounds.origin.x + offset;
13010-
ide.paletteWidth = Math.min(
13011-
Math.max(
13012-
200, newPos - ide.left() - border * 2),
13013-
cnf.noSprites ?
13014-
ide.width() - border * 2
13015-
: ide.stageHandle.left() -
13016-
ide.spriteBar.tabBar.width()
13017-
);
13018-
ide.setExtent(world.extent());
13019-
13020-
} else {
13021-
this.step = null;
13022-
}
13023-
};
13024-
};
13025-
1302612983
// PaletteHandleMorph events:
1302712984

1302812985
PaletteHandleMorph.prototype.mouseEnter
@@ -13031,6 +12988,27 @@ PaletteHandleMorph.prototype.mouseEnter
1303112988
PaletteHandleMorph.prototype.mouseLeave
1303212989
= StageHandleMorph.prototype.mouseLeave;
1303312990

12991+
PaletteHandleMorph.prototype.mouseDownLeft = function (pos) {
12992+
this.offset = this.right() - pos.x;
12993+
this.lockMouseFocus();
12994+
};
12995+
12996+
PaletteHandleMorph.prototype.mouseMove = function (pos) {
12997+
var ide = this.target.parentThatIsA(IDE_Morph),
12998+
cnf = ide.config,
12999+
border = cnf.border || 0,
13000+
newPos = pos.x + this.offset;
13001+
ide.paletteWidth = Math.min(
13002+
Math.max(
13003+
200, newPos - ide.left() - border * 2),
13004+
cnf.noSprites ?
13005+
ide.width() - border * 2
13006+
: ide.stageHandle.left() -
13007+
ide.spriteBar.tabBar.width()
13008+
);
13009+
ide.setExtent(ide.world().extent());
13010+
};
13011+
1303413012
PaletteHandleMorph.prototype.mouseDoubleClick = function () {
1303513013
this.target.parentThatIsA(IDE_Morph).setPaletteWidth(200);
1303613014
};

0 commit comments

Comments
 (0)