634 |
635 | This program is free software: you can redistribute it and/or modify
636 | it under the terms of the GNU Affero General Public License as published by
637 | the Free Software Foundation, either version 3 of the License, or
638 | (at your option) any later version.
639 |
640 | This program is distributed in the hope that it will be useful,
641 | but WITHOUT ANY WARRANTY; without even the implied warranty of
642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
643 | GNU Affero General Public License for more details.
644 |
645 | You should have received a copy of the GNU Affero General Public License
646 | along with this program. If not, see .
647 |
648 | Also add information on how to contact you by electronic and paper mail.
649 |
650 | If your software can interact with users remotely through a computer
651 | network, you should also make sure that it provides a way for users to
652 | get its source. For example, if your program is a web application, its
653 | interface could display a "Source" link that leads users to an archive
654 | of the code. There are many ways you could offer source, and different
655 | solutions will be better for different programs; see section 13 for the
656 | specific requirements.
657 |
658 | You should also get your employer (if you work as a programmer) or school,
659 | if any, to sign a "copyright disclaimer" for the program, if necessary.
660 | For more information on this, and how to apply and follow the GNU AGPL, see
661 | .
662 |
--------------------------------------------------------------------------------
/minimal.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Morphic!
5 |
6 |
20 |
21 |
22 |
25 |
26 |
--------------------------------------------------------------------------------
/morphic.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Morphic!
5 |
6 |
44 |
45 |
46 |
47 |
50 |
51 |
--------------------------------------------------------------------------------
/morphic.txt:
--------------------------------------------------------------------------------
1 |
2 | morphic.js
3 |
4 | a lively Web-GUI
5 | inspired by Squeak
6 |
7 | written by Jens Mönig
8 | jens@moenig.org
9 |
10 | Copyright (C) 2016 by Jens Mönig
11 |
12 | this documentation last changed: July 14, 2016
13 |
14 | This file is part of Snap!.
15 |
16 | Snap! is free software: you can redistribute it and/or modify
17 | it under the terms of the GNU Affero General Public License as
18 | published by the Free Software Foundation, either version 3 of
19 | the License, or (at your option) any later version.
20 |
21 | This program is distributed in the hope that it will be useful,
22 | but WITHOUT ANY WARRANTY; without even the implied warranty of
23 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 | GNU Affero General Public License for more details.
25 |
26 | You should have received a copy of the GNU Affero General Public License
27 | along with this program. If not, see .
28 |
29 |
30 | documentation contents
31 | ----------------------
32 | I. inheritance hierarchy
33 | II. object definition toc
34 | III. yet to implement
35 | IV. open issues
36 | V. browser compatibility
37 | VI. the big picture
38 | VII. programming guide
39 | (1) setting up a web page
40 | (a) single world
41 | (b) multiple worlds
42 | (c) an application
43 | (2) manipulating morphs
44 | (3) events
45 | (a) mouse events
46 | (b) context menu
47 | (c) dragging
48 | (d) dropping
49 | (e) keyboard events
50 | (f) resize event
51 | (g) combined mouse-keyboard events
52 | (h) text editing events
53 | (4) stepping
54 | (5) creating new kinds of morphs
55 | (6) development and user modes
56 | (7) turtle graphics
57 | (8) damage list housekeeping
58 | (9) supporting high-resolution "retina" screens
59 | (10) minifying morphic.js
60 | VIII. acknowledgements
61 | IX. contributors
62 |
63 |
64 | I. hierarchy
65 | -------------
66 | the following tree lists all constructors hierarchically,
67 | indentation indicating inheritance. Refer to this list to get a
68 | contextual overview:
69 |
70 | Color
71 | Node
72 | Morph
73 | BlinkerMorph
74 | CursorMorph
75 | BouncerMorph*
76 | BoxMorph
77 | InspectorMorph
78 | MenuMorph
79 | MouseSensorMorph*
80 | SpeechBubbleMorph
81 | CircleBoxMorph
82 | SliderButtonMorph
83 | SliderMorph
84 | ColorPaletteMorph
85 | GrayPaletteMorph
86 | ColorPickerMorph
87 | FrameMorph
88 | ScrollFrameMorph
89 | ListMorph
90 | StringFieldMorph
91 | WorldMorph
92 | HandleMorph
93 | HandMorph
94 | PenMorph
95 | ShadowMorph
96 | StringMorph
97 | TextMorph
98 | TriggerMorph
99 | MenuItemMorph
100 | Point
101 | Rectangle
102 |
103 |
104 | II. toc
105 | -------
106 | the following list shows the order in which all constructors are
107 | defined. Use this list to locate code in this document:
108 |
109 | Global settings
110 | Global functions
111 |
112 | Color
113 | Point
114 | Rectangle
115 | Node
116 | Morph
117 | ShadowMorph
118 | HandleMorph
119 | PenMorph
120 | ColorPaletteMorph
121 | GrayPaletteMorph
122 | ColorPickerMorph
123 | BlinkerMorph
124 | CursorMorph
125 | BoxMorph
126 | SpeechBubbleMorph
127 | CircleBoxMorph
128 | SliderButtonMorph
129 | SliderMorph
130 | MouseSensorMorph*
131 | InspectorMorph
132 | MenuMorph
133 | StringMorph
134 | TextMorph
135 | TriggerMorph
136 | MenuItemMorph
137 | FrameMorph
138 | ScrollFrameMorph
139 | ListMorph
140 | StringFieldMorph
141 | BouncerMorph*
142 | HandMorph
143 | WorldMorph
144 |
145 | * included only for demo purposes
146 |
147 |
148 | III. yet to implement
149 | ---------------------
150 | - keyboard support for scroll frames and lists
151 | - full keyboard support for menus (partial support exists)
152 | - virtual keyboard support for Android and IE
153 |
154 |
155 | IV. open issues
156 | ----------------
157 | - clipboard support (copy & paste) for non-textual data
158 |
159 |
160 | V. browser compatibility
161 | ------------------------
162 | I have taken great care and considerable effort to make morphic.js
163 | runnable and appearing exactly the same on all current browsers
164 | available to me:
165 |
166 | - Firefox for Windows
167 | - Firefox for Mac
168 | - Firefox for Android
169 | - Chrome for Windows
170 | - Chrome for Mac
171 | - Chrome for Android
172 | - Safari for Windows (deprecated)
173 | - safari for Mac
174 | - Safari for iOS (mobile)
175 | - IE for Windows
176 | - Edge for Windows
177 | - Opera for Windows
178 | - Opera for Mac
179 |
180 |
181 | VI. the big picture
182 | -------------------
183 | Morphic.js is completely based on Canvas and JavaScript, it is just
184 | Morphic, nothing else. Morphic.js is very basic and covers only the
185 | bare essentials:
186 |
187 | * a stepping mechanism (a time-sharing multiplexer for lively
188 | user interaction ontop of a single OS/browser thread)
189 | * progressive display updates (only dirty rectangles are
190 | redrawn in each display cycle)
191 | * a tree structure
192 | * a single World per Canvas element (although you can have
193 | multiple worlds in multiple Canvas elements on the same web
194 | page)
195 | * a single Hand per World (but you can support multi-touch
196 | events)
197 | * a single text entry focus per World
198 |
199 | In its current state morphic.js doesn't support Transforms (you
200 | cannot rotate Morphs), but with PenMorph there already is a simple
201 | LOGO-like turtle that you can use to draw onto any Morph it is
202 | attached to. I'm planning to add special Morphs that support these
203 | operations later on, but not for every Morph in the system.
204 | Therefore these additions ("sprites" etc.) are likely to be part of
205 | other libraries ("microworld.js") in separate files.
206 |
207 | the purpose of morphic.js is to provide a malleable framework that
208 | will let me experiment with lively GUIs for my hobby horse, which
209 | is drag-and-drop, blocks based programming languages. Those things
210 | (BYOB4 - http://byob.berkeley.edu) will be written using morphic.js
211 | as a library.
212 |
213 |
214 | VII. programming guide
215 | ----------------------
216 | Morphic.js provides a library for lively GUIs inside single HTML
217 | Canvas elements. Each such canvas element functions as a "world" in
218 | which other visible shapes ("morphs") can be positioned and
219 | manipulated, often directly and interactively by the user. Morphs
220 | are tree nodes and may contain any number of submorphs ("children").
221 |
222 | All things visible in a morphic World are morphs themselves, i.e.
223 | all text rendering, blinking cursors, entry fields, menus, buttons,
224 | sliders, windows and dialog boxes etc. are created with morphic.js
225 | rather than using HTML DOM elements, and as a consequence can be
226 | changed and adjusted by the programmer regardless of proprietary
227 | browser behavior.
228 |
229 | Each World has an - invisible - "Hand" resembling the mouse cursor
230 | (or the user's finger on touch screens) which handles mouse events,
231 | and may also have a keyboardReceiver to handle key events.
232 |
233 | The basic idea of Morphic is to continuously run display cycles and
234 | to incrementally update the screen by only redrawing those World
235 | regions which have been "dirtied" since the last redraw. Before
236 | each shape is processed for redisplay it gets the chance to perform
237 | a "step" procedure, thus allowing for an illusion of concurrency.
238 |
239 |
240 | (1) setting up a web page
241 | -------------------------
242 | Setting up a web page for Morphic always involves three steps:
243 | adding one or more Canvas elements, defining one or more worlds,
244 | initializing and starting the main loop.
245 |
246 |
247 | (a) single world
248 | -----------------
249 | Most commonly you will want your World to fill the browsers's whole
250 | client area. This default situation is easiest and most straight
251 | forward.
252 |
253 | example html file:
254 |
255 |
256 |
257 |
258 | Morphic!
259 |
260 |
274 |
275 |
276 |
279 |
280 |
281 |
282 | if you use ScrollFrames or otherwise plan to support mouse wheel
283 | scrolling events, you might also add the following inline-CSS
284 | attribute to the Canvas element:
285 |
286 | style="position: absolute;"
287 |
288 | which will prevent the World to be scrolled around instead of the
289 | elements inside of it in some browsers.
290 |
291 |
292 | (b) multiple worlds
293 | -------------------
294 | If you wish to create a web page with more than one world, make
295 | sure to prevent each world from auto-filling the whole page and
296 | include it in the main loop. It's also a good idea to give each
297 | world its own tabindex:
298 |
299 | example html file:
300 |
301 |
302 |
303 |
304 | Morphic!
305 |
306 |
323 |
324 |
325 | first world:
326 |
329 | second world:
330 |
333 |
334 |
335 |
336 |
337 | (c) an application
338 | -------------------
339 | Of course, most of the time you don't want to just plain use the
340 | standard Morphic World "as is" out of the box, but write your own
341 | application (something like Scratch!) in it. For such an
342 | application you'll create your own morph prototypes, perhaps
343 | assemble your own "window frame" and bring it all to life in a
344 | customized World state. the following example creates a simple
345 | snake-like mouse drawing game.
346 |
347 | example html file:
348 |
349 |
350 |
351 |
352 | touch me!
353 |
354 |
391 |
392 |
393 |
396 |
397 |
398 |
399 | To get an idea how you can craft your own custom morph prototypes
400 | I've included two examples which should give you an idea how to add
401 | properties, override inherited methods and use the stepping
402 | mechanism for "livelyness":
403 |
404 | BouncerMorph
405 | MouseSensorMorph
406 |
407 | For the sake of sharing a single file I've included those examples
408 | in morphic.js itself. Usually you'll define your additions in a
409 | separate file and keep morphic.js untouched.
410 |
411 |
412 | (2) manipulating morphs
413 | -----------------------
414 | There are many methods to programmatically manipulate morphs. Among
415 | the most important and common ones among all morphs are the
416 | following nine:
417 |
418 | * hide()
419 | * show()
420 |
421 | * setPosition(aPoint)
422 | * setExtent(aPoint)
423 | * setColor(aColor)
424 |
425 | * add(submorph) - attaches submorph ontop
426 | * addBack(submorph) - attaches submorph underneath
427 |
428 | * fullCopy() - duplication
429 | * destroy() - deletion
430 |
431 |
432 | (3) events
433 | ----------
434 | All user (and system) interaction is triggered by events, which are
435 | passed on from the root element - the World - to its submorphs. The
436 | World contains a list of system (browser) events it reacts to in its
437 |
438 | initEventListeners()
439 |
440 | method. Currently there are
441 |
442 | - mouse
443 | - drop
444 | - keyboard
445 | - (window) resize
446 |
447 | events.
448 |
449 | These system events are dispatched within the morphic World by the
450 | World's Hand and its keyboardReceiver (usually the active text
451 | cursor).
452 |
453 |
454 | (a) mouse events:
455 | -----------------
456 | The Hand dispatches the following mouse events to relevant morphs:
457 |
458 | mouseDownLeft
459 | mouseDownRight
460 | mouseClickLeft
461 | mouseClickRight
462 | mouseDoubleClick
463 | mouseEnter
464 | mouseLeave
465 | mouseEnterDragging
466 | mouseLeaveDragging
467 | mouseMove
468 | mouseScroll
469 |
470 | If you wish your morph to react to any such event, simply add a
471 | method of the same name as the event, e.g:
472 |
473 | MyMorph.prototype.mouseMove = function(pos) {};
474 |
475 | All of these methods have as optional parameter a Point object
476 | indicating the current position of the Hand inside the World's
477 | coordinate system. The
478 |
479 | mouseMove(pos, button)
480 |
481 | event method has an additional optional parameter indicating the
482 | currently pressed mouse button, which is either 'left' or 'right'.
483 | You can use this to let users interact with 3D environments.
484 |
485 | Events may be "bubbled" up a morph's owner chain by calling
486 |
487 | this.escalateEvent(functionName, arg)
488 |
489 | in the event handler method's code.
490 |
491 | Likewise, removing the event handler method will render your morph
492 | passive to the event in question.
493 |
494 |
495 | (b) context menu:
496 | -----------------
497 | By default right-clicking (or single-finger tap-and-hold) on a morph
498 | also invokes its context menu (in addition to firing the
499 | mouseClickRight event). A morph's context menu can be customized by
500 | assigning a Menu instance to its
501 |
502 | customContextMenu
503 |
504 | property, or altogether suppressed by overriding its inherited
505 |
506 | contextMenu()
507 |
508 | method.
509 |
510 |
511 | (c) dragging:
512 | -------------
513 | Dragging a morph is initiated when the left mouse button is pressed,
514 | held and the mouse is moved.
515 |
516 | You can control whether a morph is draggable by setting its
517 |
518 | isDraggable
519 |
520 | property either to false or true. If a morph isn't draggable itself
521 | it will pass the pick-up request up its owner chain. This lets you
522 | create draggable composite morphs like Windows, DialogBoxes,
523 | Sliders etc.
524 |
525 | Sometimes it is desireable to make "template" shapes which cannot be
526 | moved themselves, but from which instead duplicates can be peeled
527 | off. This is especially useful for building blocks in construction
528 | kits, e.g. the MIT-Scratch palette. Morphic.js lets you control this
529 | functionality by setting the
530 |
531 | isTemplate
532 |
533 | property flag to true for any morph whose "isDraggable" property is
534 | turned off. When dragging such a Morph the hand will instead grab
535 | a duplicate of the template whose "isDraggable" flag is true and
536 | whose "isTemplate" flag is false, in other words: a non-template.
537 |
538 | When creating a copy from a template, the copy's
539 |
540 | reactToTemplateCopy
541 |
542 | is invoked, if it is present.
543 |
544 | Dragging is indicated by adding a drop shadow to the morph in hand.
545 | If a morph follows the hand without displaying a drop shadow it is
546 | merely being moved about without changing its parent (owner morph),
547 | e.g. when "dragging" a morph handle to resize its owner, or when
548 | "dragging" a slider button.
549 |
550 | Right before a morph is picked up its
551 |
552 | prepareToBeGrabbed(handMorph)
553 |
554 | method is invoked, if it is present. Immediately after the pick-up
555 | the former parent's
556 |
557 | reactToGrabOf(grabbedMorph)
558 |
559 | method is called, again only if it exists.
560 |
561 | Similar to events, these methods are optional and don't exist by
562 | default. For a simple example of how they can be used to adjust
563 | scroll bars in a scroll frame please have a look at their
564 | implementation in FrameMorph.
565 |
566 |
567 | (d) dropping:
568 | -------------
569 | Dropping is triggered when the left mouse button is either pressed
570 | or released while the Hand is dragging a morph.
571 |
572 | Dropping a morph causes it to become embedded in a new owner morph.
573 | You can control this embedding behavior by setting the prospective
574 | drop target's
575 |
576 | acceptsDrops
577 |
578 | property to either true or false, or by overriding its inherited
579 |
580 | wantsDropOf(aMorph)
581 |
582 | method.
583 |
584 | Right after a morph has been dropped its
585 |
586 | justDropped(handMorph)
587 |
588 | method is called, and its new parent's
589 |
590 | reactToDropOf(droppedMorph, handMorph)
591 |
592 | method is invoked, again only if each method exists.
593 |
594 | Similar to events, these methods are optional and by default are
595 | not present in morphs by default (watch out for inheritance,
596 | though!). For a simple example of how they can be used to adjust
597 | scroll bars in a scroll frame please have a look at their
598 | implementation in FrameMorph.
599 |
600 | Drops of image elements from outside the world canvas are dispatched as
601 |
602 | droppedImage(aCanvas, name)
603 | droppedSVG(anImage, name)
604 |
605 | events to interested Morphs at the mouse pointer. If you want you Morph
606 | to e.g. import outside images you can add the droppedImage() and / or the
607 | droppedSVG() methods to it. The parameter passed to the event handles is
608 | a new offscreen canvas element representing a copy of the original image
609 | element which can be directly used, e.g. by assigning it to another
610 | Morph's image property. In the case of a dropped SVG it is an image
611 | element (not a canvas), which has to be rasterized onto a canvas before
612 | it can be used. The benefit of handling SVGs as image elements is that
613 | rasterization can be deferred until the destination scale is known, taking
614 | advantage of SVG's ability for smooth scaling. If instead SVGs are to be
615 | rasterized right away, you can set the
616 |
617 | MorphicPreferences.rasterizeSVGs
618 |
619 | preference to . In this case dropped SVGs also trigger the
620 | droppedImage() event with a canvas containing a rasterized version of the
621 | SVG.
622 |
623 | The same applies to drops of audio or text files from outside the world
624 | canvas.
625 |
626 | Those are dispatched as
627 |
628 | droppedAudio(anAudio, name)
629 | droppedText(aString, name)
630 |
631 | events to interested Morphs at the mouse pointer.
632 |
633 | if none of the above content types can be determined, the file contents
634 | is dispatched as an ArrayBuffer to interested Morphs:
635 |
636 | droppedBinary(anArrayBuffer, name)
637 |
638 |
639 | (e) keyboard events
640 | -------------------
641 | The World dispatches the following key events to its active
642 | keyboardReceiver:
643 |
644 | keypress
645 | keydown
646 | keyup
647 |
648 | Currently the only morph which acts as keyboard receiver is
649 | CursorMorph, the basic text editing widget. If you wish to add
650 | keyboard support to your morph you need to add event handling
651 | methods for
652 |
653 | processKeyPress(event)
654 | processKeyDown(event)
655 | processKeyUp(event)
656 |
657 | and activate them by assigning your morph to the World's
658 |
659 | keyboardReceiver
660 |
661 | property.
662 |
663 | Note that processKeyUp() is optional and doesn't have to be present
664 | if your morph doesn't require it.
665 |
666 |
667 | (f) resize event
668 | ----------------
669 | The Window resize event is handled by the World and allows the
670 | World's extent to be adjusted so that it always completely fills
671 | the browser's visible page. You can turn off this default behavior
672 | by setting the World's
673 |
674 | useFillPage
675 |
676 | property to false.
677 |
678 | Alternatively you can also initialize the World with the
679 | useFillPage switch turned off from the beginning by passing the
680 | false value as second parameter to the World's constructor:
681 |
682 | world = new World(aCanvas, false);
683 |
684 | Use this when creating a web page with multiple Worlds.
685 |
686 | if "useFillPage" is turned on the World dispatches an
687 |
688 | reactToWorldResize(newBounds)
689 |
690 | events to all of its children (toplevel only), allowing each to
691 | adjust to the new World bounds by implementing a corresponding
692 | method, the passed argument being the World's new dimensions after
693 | completing the resize. By default, the "reactToWorldResize" Method
694 | does not exist.
695 |
696 | Example:
697 |
698 | Add the following method to your Morph to let it automatically
699 | fill the whole World, but leave a 10 pixel border uncovered:
700 |
701 | MyMorph.prototype.reactToWorldResize = function (rect) {
702 | this.changed();
703 | this.bounds = rect.insetBy(10);
704 | this.drawNew();
705 | this.changed();
706 | };
707 |
708 |
709 | (g) combined mouse-keyboard events
710 | ----------------------------------
711 | Occasionally you'll want an object to react differently to a mouse
712 | click or to some other mouse event while the user holds down a key
713 | on the keyboard. Such "shift-click", "ctl-click", or "alt-click"
714 | events can be implemented by querying the World's
715 |
716 | currentKey
717 |
718 | property inside the function that reacts to the mouse event. This
719 | property stores the keyCode of the key that's currently pressed.
720 | Once the key is released by the user it reverts to null.
721 |
722 |
723 | (h) text editing events
724 | -----------------------
725 | Much of Morphic's "liveliness" comes out of allowing text elements
726 | (instances of either single-lined StringMorph or multi-lined TextMorph)
727 | to be directly manipulated and edited by users. This requires other
728 | objects which may have an interest in the text element's state to react
729 | appropriately. Therefore text elements and their manipulators emit
730 | a stream of events, mostly by "bubbling" them up the text element's
731 | owner chain. Text elements' parents are notified about the following
732 | events:
733 |
734 | Whenever the user presses a key on the keyboard while a text element
735 | is being edited, a
736 |
737 | reactToKeystroke(event)
738 |
739 | is escalated up its parent chain, the "event" parameter being the
740 | original one received by the World.
741 |
742 | Once the user has completed the edit, the following events are
743 | dispatched:
744 |
745 | accept() - was pressed on a single line of text
746 | cancel() - was pressed on any text element
747 |
748 | Note that "accept" only gets triggered by single-line texte elements,
749 | as the key is used to insert line breaks in multi-line
750 | elements. Therefore, whenever a text edit is terminated by the user
751 | (accepted, cancelled or otherwise),
752 |
753 | reactToEdit(StringOrTextMorph)
754 |
755 | is triggered.
756 |
757 | If the MorphicPreference's
758 |
759 | useSliderForInput
760 |
761 | setting is turned on, a slider is popped up underneath the currently
762 | edited text element letting the user insert numbers out of the given
763 | slider range. Whenever this happens, i.e. whenever the slider is moved
764 | or while the slider button is pressed, a stream of
765 |
766 | reactToSliderEdit(StringOrTextMorph)
767 |
768 | events is dispatched, allowing for "Bret-Victor" style "live coding"
769 | applications.
770 |
771 | In addition to user-initiated events text elements also emit
772 | change notifications to their direct parents whenever their drawNew()
773 | method is invoked. That way complex Morphs containing text elements
774 | get a chance to react if something about the embedded text has been
775 | modified programmatically. These events are:
776 |
777 | layoutChanged() - sent from instances of TextMorph
778 | fixLayout() - sent from instances of StringMorph
779 |
780 | they are different so that Morphs which contain both multi-line and
781 | single-line text elements can hold them apart.
782 |
783 |
784 | (4) stepping
785 | ------------
786 | Stepping is what makes Morphic "magical". Two properties control
787 | a morph's stepping behavior: the fps attribute and the step()
788 | method.
789 |
790 | By default the
791 |
792 | step()
793 |
794 | method does nothing. As you can see in the examples of BouncerMorph
795 | and MouseSensorMorph you can easily override this inherited method
796 | to suit your needs.
797 |
798 | By default the step() method is called once per display cycle.
799 | Depending on the number of actively stepping morphs and the
800 | complexity of your step() methods this can cause quite a strain on
801 | your CPU, and also result in your application behaving differently
802 | on slower computers than on fast ones.
803 |
804 | setting
805 |
806 | myMorph.fps
807 |
808 | to a number lower than the interval for the main loop lets you free
809 | system resources (albeit at the cost of a less responsive or slower
810 | behavior for this particular morph).
811 |
812 |
813 | (5) creating new kinds of morphs
814 | --------------------------------
815 | The real fun begins when you start to create new kinds of morphs
816 | with customized shapes. Imagine, e.g. jigsaw puzzle pieces or
817 | musical notes. For this you have to override the default
818 |
819 | drawNew()
820 |
821 | method.
822 |
823 | This method creates a new offscreen Canvas and stores it in
824 | the morph's
825 |
826 | image
827 |
828 | property.
829 |
830 | Use the following template for a start:
831 |
832 | MyMorph.prototype.drawNew = function() {
833 | var context;
834 | this.image = newCanvas(this.extent());
835 | context = this.image.getContext('2d');
836 | // use context to paint stuff here
837 | };
838 |
839 | If your new morph stores or references to other morphs outside of
840 | the submorph tree in other properties, be sure to also override the
841 | default
842 |
843 | updateReferences()
844 |
845 | method if you want it to support duplication.
846 |
847 |
848 | (6) development and user modes
849 | ------------------------------
850 | When working with Squeak on Scratch or BYOB among the features I
851 | like the best and use the most is inspecting what's going on in
852 | the World while it is up and running. That's what development mode
853 | is for (you could also call it debug mode). In essence development
854 | mode controls which context menu shows up. In user mode right
855 | clicking (or double finger tapping) a morph invokes its
856 |
857 | customContextMenu
858 |
859 | property, whereas in development mode only the general
860 |
861 | developersMenu()
862 |
863 | method is called and the resulting menu invoked. The developers'
864 | menu features Gui-Builder-wise functionality to directly inspect,
865 | take apart, reassamble and otherwise manipulate morphs and their
866 | contents.
867 |
868 | Instead of using the "customContextMenu" property you can also
869 | assign a more dynamic contextMenu by overriding the general
870 |
871 | userMenu()
872 |
873 | method with a customized menu constructor. The difference between
874 | the customContextMenu property and the userMenu() method is that
875 | the former is also present in development mode and overrides the
876 | developersMenu() result. For an example of how to use the
877 | customContextMenu property have a look at TextMorph's evaluation
878 | menu, which is used for the Inspector's evaluation pane.
879 |
880 | When in development mode you can inspect every Morph's properties
881 | with the inspector, including all of its methods. The inspector
882 | also lets you add, remove and rename properties, and even edit
883 | their values at runtime. Like in a Smalltalk environment the inspect
884 | features an evaluation pane into which you can type in arbitrary
885 | JavaScript code and evaluate it in the context of the inspectee.
886 |
887 | Use switching between user and development modes while you are
888 | developing an application and disable switching to development once
889 | you're done and deploying, because generally you don't want to
890 | confuse end-users with inspectors and meta-level stuff.
891 |
892 |
893 | (7) turtle graphics
894 | -------------------
895 |
896 | The basic Morphic kernel features a simple LOGO turtle constructor
897 | called
898 |
899 | PenMorph
900 |
901 | which you can use to draw onto its parent Morph. By default every
902 | Morph in the system (including the World) is able to act as turtle
903 | canvas and can display pen trails. Pen trails will be lost whenever
904 | the trails morph (the pen's parent) performs a "drawNew()"
905 | operation. If you want to create your own pen trails canvas, you
906 | may wish to modify its
907 |
908 | penTrails()
909 |
910 | property, so that it keeps a separate offscreen canvas for pen
911 | trails (and doesn't loose these on redraw).
912 |
913 | the following properties of PenMorph are relevant for turtle
914 | graphics:
915 |
916 | color - a Color
917 | size - line width of pen trails
918 | heading - degrees
919 | isDown - drawing state
920 |
921 | the following commands can be used to actually draw something:
922 |
923 | up() - lift the pen up, further movements leave no trails
924 | down() - set down, further movements leave trails
925 | clear() - remove all trails from the current parent
926 | forward(n) - move n steps in the current direction (heading)
927 | turn(n) - turn right n degrees
928 |
929 | Turtle graphics can best be explored interactively by creating a
930 | new PenMorph object and by manipulating it with the inspector
931 | widget.
932 |
933 | NOTE: PenMorph has a special optimization for recursive operations
934 | called
935 |
936 | warp(function)
937 |
938 | You can significantly speed up recursive ops and increase the depth
939 | of recursion that's displayable by wrapping WARP around your
940 | recursive function call:
941 |
942 | example:
943 |
944 | myPen.warp(function () {
945 | myPen.tree(12, 120, 20);
946 | })
947 |
948 | will be much faster than just invoking the tree function, because it
949 | prevents the parent's parent from keeping track of every single line
950 | segment and instead redraws the outcome in a single pass.
951 |
952 |
953 | (8) damage list housekeeping
954 | ----------------------------
955 | Morphic's progressive display update comes at the cost of having to
956 | cycle through a list of "broken rectangles" every display cycle. If
957 | this list gets very long working this damage list can lead to a
958 | seemingly dramatic slow-down of the Morphic system. Typically this
959 | occurs when updating the layout of complex Morphs with very many
960 | submorphs, e.g. when resizing an inspector window.
961 |
962 | An effective strategy to cope with this is to use the inherited
963 |
964 | trackChanges
965 |
966 | property of the Morph prototype for damage list housekeeping.
967 |
968 | The trackChanges property of the Morph prototype is a Boolean switch
969 | that determines whether the World's damage list ('broken' rectangles)
970 | tracks changes. By default the switch is always on. If set to false
971 | changes are not stored. This can be very useful for housekeeping of
972 | the damage list in situations where a large number of (sub-) morphs
973 | are changed more or less at once. Instead of keeping track of every
974 | single submorph's changes tremendous performance improvements can be
975 | achieved by setting the trackChanges flag to false before propagating
976 | the layout changes, setting it to true again and then storing the full
977 | bounds of the surrounding morph. As an example refer to the
978 |
979 | moveBy()
980 |
981 | method of HandMorph, and to the
982 |
983 | fixLayout()
984 |
985 | method of InspectorMorph, or the
986 |
987 | startLayout()
988 | endLayout()
989 |
990 | methods of SyntaxElementMorph in the Snap application.
991 |
992 |
993 | (9) supporting high-resolution "retina" screens
994 | -----------------------------------------------
995 | By default retina support gets installed when Morphic.js loads. There
996 | are two global functions that let you test for retina availability:
997 |
998 | isRetinaSupported() - Bool, answers if retina support is available
999 | isRetinaEnabled() - Bool, answers if currently in retina mode
1000 |
1001 | and two more functions that let you control retina support if it is
1002 | available:
1003 |
1004 | enableRetinaSupport()
1005 | disableRetinaSupport()
1006 |
1007 | Both of these internally test whether retina is available, so they are
1008 | safe to call directly. For an example how to make retina support
1009 | user-specifiable refer to
1010 |
1011 | Snap! >> guis.js >> toggleRetina()
1012 |
1013 | Even when in retina mode it often makes sense to use normal-resolution
1014 | canvasses for simple shapes in order to save system resources and
1015 | optimize performance. Examples are costumes and backgrounds in Snap.
1016 | In Morphic you can create new canvas elements using
1017 |
1018 | newCanvas(extentPoint [, nonRetinaFlag])
1019 |
1020 | If retina support is enabled such new canvasses will automatically be
1021 | high-resolution canvasses, unless the newCanvas() function is given an
1022 | otherwise optional second Boolean argument that explicitly makes
1023 | it a non-retina canvas.
1024 |
1025 | Not the whole canvas API is supported by Morphic's retina utilities.
1026 | Especially if your code uses putImageData() you will want to "downgrade"
1027 | a target high-resolution canvas to a normal-resolution ("non-retina")
1028 | one before using
1029 |
1030 | normalizeCanvas(aCanvas [, copyFlag])
1031 |
1032 | This will change the target canvas' resolution in place (!). If you
1033 | pass in the optional second Boolean flag the function returns
1034 | a non-retina copy and leaves the target canvas unchanged. An example
1035 | of this normalize mechanism is converting the penTrails layer of Snap's
1036 | stage (high-resolution) into a sprite-costume (normal resolution).
1037 |
1038 |
1039 | (10) minifying morphic.js
1040 | -------------------------
1041 | Coming from Smalltalk and being a Squeaker at heart I am a huge fan
1042 | of browsing the code itself to make sense of it. Therefore I have
1043 | included this documentation and (too little) inline comments so all
1044 | you need to get going is this very file.
1045 |
1046 | Nowadays with live streaming HD video even on mobile phones 250 KB
1047 | shouldn't be a big strain on bandwith, still minifying and even
1048 | compressing morphic.js down do about 100 KB may sometimes improve
1049 | performance in production use.
1050 |
1051 | Being an attorney-at-law myself you programmer folk keep harassing
1052 | me with rabulistic nitpickings about free software licenses. I'm
1053 | releasing morphic.js under an AGPL license. Therefore please make
1054 | sure to adhere to that license in any minified or compressed version.
1055 |
1056 |
1057 | VIII. acknowledgements
1058 | ----------------------
1059 | The original Morphic was designed and written by Randy Smith and
1060 | John Maloney for the SELF programming language, and later ported to
1061 | Squeak (Smalltalk) by John Maloney and Dan Ingalls, who has also
1062 | ported it to JavaScript (the Lively Kernel), once again setting
1063 | a "Gold Standard" for self sustaining systems which morphic.js
1064 | cannot and does not aspire to meet.
1065 |
1066 | This Morphic implementation for JavaScript is not a direct port of
1067 | Squeak's Morphic, but still many individual functions have been
1068 | ported almost literally from Squeak, sometimes even including their
1069 | comments, e.g. the morph duplication mechanism fullCopy(). Squeak
1070 | has been a treasure trove, and if morphic.js looks, feels and
1071 | smells a lot like Squeak, I'll take it as a compliment.
1072 |
1073 | Evelyn Eastmond has inspired and encouraged me with her wonderful
1074 | implementation of DesignBlocksJS. Thanks for sharing code, ideas
1075 | and enthusiasm for programming.
1076 |
1077 | John Maloney has been my mentor and my source of inspiration for
1078 | these Morphic experiments. Thanks for the critique, the suggestions
1079 | and explanations for all things Morphic and for being my all time
1080 | programming hero.
1081 |
1082 | I have originally written morphic.js in Florian Balmer's Notepad2
1083 | editor for Windows, later switched to Apple's Dashcode and later
1084 | still to Apple's Xcode. I've also come to depend on both Douglas
1085 | Crockford's JSLint and later the JSHint project, as well as on
1086 | Mozilla's Firebug and Google's Chrome to get
1087 | it right.
1088 |
1089 |
1090 | IX. contributors
1091 | ----------------------
1092 | Joe Otto found and fixed many early bugs and taught me some tricks.
1093 | Nathan Dinsmore contributed mouse wheel scrolling, cached
1094 | background texture handling, countless bug fixes and optimizations.
1095 | Ian Reynolds contributed backspace key handling for Chrome.
1096 | Davide Della Casa contributed performance optimizations for Firefox.
1097 | Jason N (@cyderize) contributed native copy & paste for text editing.
1098 | Bartosz Leper contributed retina display support.
1099 |
1100 | - Jens Mönig
1101 |
--------------------------------------------------------------------------------
/multiple.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Morphic!
5 |
6 |
23 |
24 |
25 | first world:
26 |
29 | second world:
30 |
33 |
34 |
--------------------------------------------------------------------------------