diff options
| -rw-r--r-- | blocks.js | 5 | ||||
| -rw-r--r-- | gui.js | 73 | ||||
| -rwxr-xr-x | history.txt | 10 | ||||
| -rw-r--r-- | lang-cs.js | 8 | ||||
| -rw-r--r-- | lang-de.js | 8 | ||||
| -rw-r--r-- | lang-eo.js | 8 | ||||
| -rw-r--r-- | lang-es.js | 8 | ||||
| -rw-r--r-- | lang-fr.js | 6 | ||||
| -rw-r--r-- | lang-it.js | 8 | ||||
| -rwxr-xr-x | lang-ja.js | 8 | ||||
| -rwxr-xr-x | lang-ja_HIRA.js | 8 | ||||
| -rw-r--r-- | lang-ko.js | 4 | ||||
| -rwxr-xr-x | lang-pt.js | 8 | ||||
| -rw-r--r-- | lang-ru.js | 8 | ||||
| -rw-r--r-- | lang-si.js | 4 | ||||
| -rw-r--r-- | lang-zh.js | 8 | ||||
| -rw-r--r-- | lists.js | 13 | ||||
| -rw-r--r-- | morphic.js | 125 | ||||
| -rwxr-xr-x | morphic.txt | 1507 | ||||
| -rw-r--r-- | objects.js | 43 |
20 files changed, 1011 insertions, 859 deletions
@@ -3443,16 +3443,19 @@ ReporterBlockMorph.prototype.getSlotSpec = function () { // ReporterBlockMorph events ReporterBlockMorph.prototype.mouseClickLeft = function (pos) { + var isRing; if (this.parent instanceof BlockInputFragmentMorph) { return this.parent.mouseClickLeft(); } if (this.parent instanceof TemplateSlotMorph) { + isRing = this.parent.parent && this.parent.parent.parent && + this.parent.parent.parent instanceof RingMorph; new DialogBoxMorph( this, this.setSpec, this ).prompt( - "Input name", + isRing ? "Input name" : "Script variable name", this.blockSpec, this.world() ); @@ -68,7 +68,7 @@ sb, CommentMorph, CommandBlockMorph*/ // Global stuff //////////////////////////////////////////////////////// -modules.gui = '2013-April-19'; +modules.gui = '2013-April-22'; // Declarations @@ -1989,7 +1989,7 @@ IDE_Morph.prototype.projectMenu = function () { menu.addItem( shiftClicked ? - 'Export project as plain text ...' : 'Export project...', + 'Export project as plain text...' : 'Export project...', function () { if (myself.projectName) { myself.exportProject(myself.projectName, shiftClicked); @@ -2004,14 +2004,14 @@ IDE_Morph.prototype.projectMenu = function () { ); menu.addItem( - 'Export blocks ...', + 'Export blocks...', function () {myself.exportGlobalBlocks(); }, 'show global custom block definitions as XML\nin a new browser window' ); menu.addLine(); menu.addItem( - 'Import tools...', + 'Import tools', function () { var url = 'http://snap.berkeley.edu/snapsource/tools.xml', @@ -2901,6 +2901,7 @@ IDE_Morph.prototype.userSetBlocksScale = function () { */ sample = new FrameMorph(); + sample.acceptsDrops = false; sample.texture = 'scriptsPaneTexture.gif'; sample.setExtent(new Point(250, 180)); scrpt.setPosition(sample.position().add(10)); @@ -3723,7 +3724,7 @@ ProjectDialogMorph.prototype.setSource = function (source) { myself.ide.cloudError().call(null, err, lbl); } ); - break; + return; case 'local': this.projectList = this.getLocalProjectList(); break; @@ -3731,14 +3732,16 @@ ProjectDialogMorph.prototype.setSource = function (source) { this.projectList = []; break; } - this.listField.destroy(); + this.listField.destroy(); this.listField = new ListMorph( this.projectList, this.projectList.length > 0 ? function (element) { return element.name; - } : null + } : null, + null, + function () {myself.ok(); } ); this.fixListFieldItemColors(); @@ -3754,6 +3757,7 @@ ProjectDialogMorph.prototype.setSource = function (source) { this.listField.action = function (item) { var src, xml; + if (item === undefined) {return; } if (myself.nameField) { myself.nameField.setContents(item.name || ''); } @@ -3775,6 +3779,7 @@ ProjectDialogMorph.prototype.setSource = function (source) { }; } else { // 'examples', 'cloud' is initialized elsewhere this.listField.action = function (item) { + if (item === undefined) {return; } if (myself.nameField) { myself.nameField.setContents(item.name || ''); } @@ -3791,21 +3796,14 @@ ProjectDialogMorph.prototype.setSource = function (source) { } this.body.add(this.listField); - if (this.source === 'cloud') { - this.shareButton.show(); - this.unshareButton.hide(); + this.shareButton.hide(); + this.unshareButton.hide(); + if (this.source === 'local') { this.deleteButton.show(); - } else { - this.shareButton.hide(); - this.unshareButton.hide(); - if (this.source === 'local') { - this.deleteButton.show(); - } else { // examples - this.deleteButton.hide(); - } + } else { // examples + this.deleteButton.hide(); } this.buttons.fixLayout(); - this.fixLayout(); if (this.task === 'open') { this.clearDetails(); @@ -3852,9 +3850,9 @@ ProjectDialogMorph.prototype.installCloudProjectList = function (pl) { 'bold', function (proj) {return proj.Public === 'true'; } ] - ] + ], + function () {myself.ok(); } ); - this.fixListFieldItemColors(); this.listField.fixLayout = nop; this.listField.edge = InputFieldMorph.prototype.edge; @@ -3865,6 +3863,7 @@ ProjectDialogMorph.prototype.installCloudProjectList = function (pl) { this.listField.drawRectBorder = InputFieldMorph.prototype.drawRectBorder; this.listField.action = function (item) { + if (item === undefined) {return; } if (myself.nameField) { myself.nameField.setContents(item.ProjectName || ''); } @@ -3888,6 +3887,10 @@ ProjectDialogMorph.prototype.installCloudProjectList = function (pl) { myself.edit(); }; this.body.add(this.listField); + this.shareButton.show(); + this.unshareButton.hide(); + this.deleteButton.show(); + this.buttons.fixLayout(); this.fixLayout(); if (this.task === 'open') { this.clearDetails(); @@ -3904,36 +3907,32 @@ ProjectDialogMorph.prototype.clearDetails = function () { }; ProjectDialogMorph.prototype.openProject = function () { - var myself = this; - + var myself = this, + proj = this.listField.selected; + if (!proj) {return; } if (this.source === 'cloud') { - if (this.listField.selected) { - this.openSelectedCloudProject(); - } + this.openCloudProject(proj); } else { // 'local, examples' - if (this.listField.selected) { - myself.ide.source = 'local'; - this.ide.openProject(this.listField.selected.name); - this.destroy(); - } + myself.ide.source = 'local'; + this.ide.openProject(proj.name); + this.destroy(); } }; -ProjectDialogMorph.prototype.openSelectedCloudProject = function () { +ProjectDialogMorph.prototype.openCloudProject = function (project) { var myself = this; - this.nextSteps([ + myself.ide.nextSteps([ function () { myself.ide.showMessage('Fetching project\nfrom the cloud...'); }, function () { - myself.rawOpenSelectedCloudProject(); + myself.rawOpenCloudProject(project); } ]); }; -ProjectDialogMorph.prototype.rawOpenSelectedCloudProject = function () { - var myself = this, - proj = this.listField.selected; +ProjectDialogMorph.prototype.rawOpenCloudProject = function (proj) { + var myself = this; SnapCloud.reconnect( function () { SnapCloud.callService( diff --git a/history.txt b/history.txt index f8cb536..76dbc16 100755 --- a/history.txt +++ b/history.txt @@ -1647,3 +1647,13 @@ ______ 130421 ------ * using the percent character in variable names is now safe (fixes Github issue #65) +* Morphic: added Doubleclick support, example: inspectors +* GUI: Double clicking a project in the project dialog performs the dialog's action on it (open / save) + +130422 +------ +* GUI: Double clicking support for cloud side of project dialog + +130423 +------ +* Lists, Objects: Circularity no longer breaks watchers @@ -633,18 +633,18 @@ SnapTranslator.dict.cs = { 'file menu import hint': 'Načíst exportovaný projekt, ' + 'knihovnu bloků, kostýmy nebo zvuky', - 'Export project as plain text ...': + 'Export project as plain text...': 'Exportovat projekt jako prostý text...', 'Export project...': 'Exportovat projekt...', 'show project data as XML\nin a new browser window': 'zobrazit data projektu jako xml XML\n v novém okně prohlížeče', - 'Export blocks ...': + 'Export blocks...': 'Exportovat bloky...', 'show global custom block definitions as XML\nin a new browser window': 'Zobrazit definici vlastních bloků jako\nXML v novém okně prohlížeče', - 'Import tools...': - 'Importovat nástroje...', + 'Import tools': + 'Importovat nástroje', 'load the official library of\npowerful blocks': 'nahraje oficialní knihovnu\npokročilých bloků', @@ -637,18 +637,18 @@ SnapTranslator.dict.de = { 'l\u00e4dt ein exportiertes Projekt,\neine Bibliothek mit ' + 'Bl\u00f6cken\n' + 'ein Kost\u00fcm oder einen Klang', - 'Export project as plain text ...': + 'Export project as plain text...': 'Projekt als normalen Text exportieren...', 'Export project...': 'Projekt exportieren...', 'show project data as XML\nin a new browser window': 'zeigt das Projekt als XML\nin einem neuen Browserfenster an', - 'Export blocks ...': + 'Export blocks...': 'Bl\u00f6cke exportieren...', 'show global custom block definitions as XML\nin a new browser window': 'zeigt globale Benutzerblockdefinitionen\nals XML im Browser an', - 'Import tools...': - 'Tools laden...', + 'Import tools': + 'Tools laden', 'load the official library of\npowerful blocks': 'das offizielle Modul mit\nm\u00e4chtigen Bl\u00f6cken laden', @@ -606,14 +606,14 @@ SnapTranslator.dict.eo = { 'Importi...',
'file menu import hint':
'elŝutu dosieron kun blokoj sonoj aŭ kostumoj',
- 'Export project as plain text ...':
- 'Eksporti projekton en plata teksta formo ...',
+ 'Export project as plain text...':
+ 'Eksporti projekton en plata teksta formo...',
'Export project...':
'Eksporti projekton...',
'show project data as XML\nin a new browser window':
'prezenti projekton kiel XML\nen nova fenestro de retumilo',
- 'Export blocks ...':
- 'Eksporti blokojn ...',
+ 'Export blocks...':
+ 'Eksporti blokojn...',
'show global custom block definitions as XML\nin a new browser window':
'prezenti mallokajn difinojn de propraj blokoj kiel XML\nen nova fenestro de retumilo',
'Delete Project':
@@ -619,18 +619,18 @@ SnapTranslator.dict.es = { 'Importar...', 'file menu import hint': 'men\u00FA de archivo de importaci\u00F3n indirecta', - 'Export project as plain text ...': + 'Export project as plain text...': 'Exportar proyecto como texto...', 'Export project...': 'Exportar proyecto...', 'show project data as XML\nin a new browser window': 'mostrar informaci\u00F3n de proyecto como XML\nen una nueva ventana', - 'Export blocks ...': + 'Export blocks...': 'Exportar bloques...', 'show global custom block definitions as XML\nin a new browser window': 'mostrar definiciones de bloques globales personalizados como XML\nen una nueva ventana', - 'Import tools...': - 'Herramientas de importaci\u00F3n...', + 'Import tools': + 'Herramientas de importaci\u00F3n', 'load the official library of\npowerful blocks': 'cargar la biblioteca oficial de\nbloques poderosos', @@ -587,7 +587,7 @@ SnapTranslator.dict.fr = { // menus // snap menu 'About...': - 'A propos de Snap! ...', + 'A propos de Snap!...', 'Snap! website': 'Snap! le site web', 'Download source': @@ -618,13 +618,13 @@ SnapTranslator.dict.fr = { 'importer un projet export\u00E9,\nune biblioth\u00E8que de ' + 'blocs\n' + 'un costume ou un son', - 'Export project as plain text ...': + 'Export project as plain text...': 'Exporter le projet comme texte...', 'Export project...': 'Exporter le projet...', 'show project data as XML\nin a new browser window': 'ouvrir le projet au format XML\ndans une nouvelle fen\u00EAtre de votre navigateur', - 'Export blocks ...': + 'Export blocks...': 'Exporter les blocs ', 'show global custom block definitions as XML\nin a new browser window': 'montrer les d\u00E9finitions de bloc global personnalis\u00E9 au format XML \ndans une nouvelle fen\u00EAtre de navigateur', @@ -634,18 +634,18 @@ SnapTranslator.dict.it = { 'carica un file di progetto,\nuna libreria di blocchi,' + '\nun costume o un suono esportati' + '\n\nNon supportato da tutti i browser', - 'Export project as plain text ...': + 'Export project as plain text...': 'Esporta il progetto come un file di testo...', 'Export project...': 'Esporta il progetto...', 'show project data as XML\nin a new browser window': 'mostra i dati del progetto in formato XML\nin una nuova finestra del browser', - 'Export blocks ...': + 'Export blocks...': 'Esporta blocchi...', 'show global custom block definitions as XML\nin a new browser window': 'mostra in formato XML le definizione dei nuovi blocchi\nin una nuova finestra del browser', - 'Import tools...': - 'Importa tools...', + 'Import tools': + 'Importa tools', 'load the official library of\npowerful blocks': 'carica la libreria ufficiale di\nblocchi Snap', @@ -632,18 +632,18 @@ SnapTranslator.dict.ja = { 'チェックするとレポーターをドラッグ&ドロップするとき\n' + '空のレポーターにフォーカスします\n\n' + 'いくつかのブラウザーではサポートされません', - 'Export project as plain text ...': + 'Export project as plain text...': 'テキストファイルとしてプロジェクトを書き出す...', 'Export project...': 'プロジェクトを書き出す...', 'show project data as XML\nin a new browser window': 'プロジェクトのデータをXMLとして\nブラウザの新しいウインドウに表示する', - 'Export blocks ...': + 'Export blocks...': 'ブロックを書き出す...', 'show global custom block definitions as XML\nin a new browser window': 'グローバルカスタムブロックの定義をXMLとして\nブラウザの新しいウインドウに表示する', - 'Import tools...': - 'ツールを読み込む...', + 'Import tools': + 'ツールを読み込む', 'load the official library of\npowerful blocks': '強力なブロックの公式\nライブラリを読み込む', diff --git a/lang-ja_HIRA.js b/lang-ja_HIRA.js index d42ecfb..f017f93 100755 --- a/lang-ja_HIRA.js +++ b/lang-ja_HIRA.js @@ -632,18 +632,18 @@ SnapTranslator.dict.ja_HIRA = { 'チェックするとレポーターをドラッグ&ドロップするとき\n'
+ 'そらのレポーターにフォーカスします\n\n'
+ 'いくつかのブラウザーではサポートされません',
- 'Export project as plain text ...':
+ 'Export project as plain text...':
'テキストファイルとしてプロジェクトをかきだす...',
'Export project...':
'プロジェクトをかきだす...',
'show project data as XML\nin a new browser window':
'プロジェクトのデータをXMLとして\nブラウザのあたらしいウインドウにひょうじする',
- 'Export blocks ...':
+ 'Export blocks...':
'ブロックをかきだす...',
'show global custom block definitions as XML\nin a new browser window':
'グローバルカスタムブロックのていぎをXMLとして\nブラウザのあたらしいウインドウにひょうじする',
- 'Import tools...':
- 'ツールをよみこむ...',
+ 'Import tools':
+ 'ツールをよみこむ',
'load the official library of\npowerful blocks':
'きょうりょくなブロックのこうしき\nライブラリをよみこむ',
@@ -618,13 +618,13 @@ SnapTranslator.dict.ko = { '내보낸 프로젝트 파일, 블록 라이브러리\n' + '스프라이트 모양 또는 소리를 가져옵니다.\n\n' + '일부 웹브라우저에서는 지원되지 않습니다.', - 'Export project as plain text ...': + 'Export project as plain text...': '프로젝트를 텍스트 파일로 내보내기...', 'Export project...': '프로젝트 내보내기...', 'show project data as XML\nin a new browser window': '프로젝트 데이터를\n새로운 윈도우에 XML 형태로 보여주기', - 'Export blocks ...': + 'Export blocks...': '블록 내보내기...', 'show global custom block definitions as XML\nin a new browser window': '새롭게 정의한 전역 블록 데이터를\n새로운 윈도우에 XML 형태로 보여주기', @@ -639,18 +639,18 @@ SnapTranslator.dict.pt = { 'Importar para este projecto\num projecto exportado,\n' + 'uma biblioteca de blocos,\n' + 'um traje ou um som.', - 'Export project as plain text ...': + 'Export project as plain text...': 'Exportar este projecto como texto simples…', 'Export project...': 'Exportar este projecto…', 'show project data as XML\nin a new browser window': 'Mostrar os dados do projecto no\nformato XML numa nova janela do navegador.', - 'Export blocks ...': + 'Export blocks...': 'Exportar blocos deste projecto…', 'show global custom block definitions as XML\nin a new browser window': 'Mostrar as definições de blocos\npersonalizados globais no formato\nXML numa nova janela do navegador.', - 'Import tools...': - 'Importar as ferramentas oficiais para este projecto…', + 'Import tools': + 'Importar as ferramentas oficiais para este projecto', 'load the official library of\npowerful blocks': 'Importar para este projecto\na biblioteca oficial de blocos.', @@ -613,18 +613,18 @@ SnapTranslator.dict.ru = { 'Импорт...',
'file menu import hint':
'загрузить экспортированный проект\nили библиотеку блоков, маску или звук',
- 'Export project as plain text ...':
+ 'Export project as plain text...':
'Экспорт проект как текстовый файл...',
'Export project...':
'Экспорт проект...',
'show project data as XML\nin a new browser window':
'представить проектные данные как XML\nв новом окне браузера',
- 'Export blocks ...':
+ 'Export blocks...':
'Экспорт блоки...',
'show global custom block definitions as XML\nin a new browser window':
'представить определения глобальных пользовательских блоков как XML\nв новом окне браузера',
- 'Import tools...':
- 'Импорт сервисные ср-ва...',
+ 'Import tools':
+ 'Импорт сервисные ср-ва',
'load the official library of\npowerful blocks':
'загрузить служебную библиотеку блоков',
@@ -610,13 +610,13 @@ SnapTranslator.dict.si = { 'Nalaganje izvo\u017Eenega projekta,\nknji\u017Enice z ' + 'bloki\n' + 'obleko ali zvokom', - 'Export project as plain text ...': + 'Export project as plain text...': 'Izvozi projekt kot navadno besedilo...', 'Export project...': 'Izvozi projekt...', 'show project data as XML\nin a new browser window': 'Prikaz projekta kot XML\nv novem oknu brkljalnika', - 'Export blocks ...': + 'Export blocks...': 'Izvozi bloke', 'show global custom block definitions as XML\nin a new browser window': 'Prikaz definicij globalnih lastnih blokov kot XML\nv novem oknu brkljalnika', @@ -636,18 +636,18 @@ SnapTranslator.dict.zh = { '当你拖动到系统,注意查看检查报告\n'
+ '要注意检查报告为空\n\n'
+ '有一些浏览器不支持这一功能',
- 'Export project as plain text ...':
+ 'Export project as plain text...':
'纯文本格式导出项目...',
'Export project...':
'导出项目...',
'show project data as XML\nin a new browser window':
'新浏览窗以XML格式显示项目',
- 'Export blocks ...':
+ 'Export blocks...':
'导出程序块...',
'show global custom block definitions as XML\nin a new browser window':
'新浏览窗以XML格式显示全局自定义程序块',
- 'Import tools...':
- '导入工具包...',
+ 'Import tools':
+ '导入工具包',
'load the official library of\npowerful blocks':
'载入官方库和强大的程序块',
@@ -61,7 +61,7 @@ PushButtonMorph, SyntaxElementMorph, Color, Point, WatcherMorph, StringMorph, SpriteMorph, ScrollFrameMorph, CellMorph, ArrowMorph, MenuMorph, snapEquals, Morph, isNil, localize*/ -modules.lists = '2013-April-12'; +modules.lists = '2013-April-23'; var List; var ListWatcherMorph; @@ -345,11 +345,11 @@ ListWatcherMorph.prototype.cellColor = // ListWatcherMorph instance creation: -function ListWatcherMorph(list) { - this.init(list); +function ListWatcherMorph(list, parentCell) { + this.init(list, parentCell); } -ListWatcherMorph.prototype.init = function (list) { +ListWatcherMorph.prototype.init = function (list, parentCell) { var myself = this; this.list = list || new List(); @@ -357,6 +357,7 @@ ListWatcherMorph.prototype.init = function (list) { this.range = 100; this.lastUpdated = Date.now(); this.lastCell = null; + this.parentCell = parentCell || null; // for circularity detection // elements declarations this.label = new StringMorph( @@ -434,6 +435,7 @@ ListWatcherMorph.prototype.update = function (anyway) { starttime, maxtime = 1000; this.frame.contents.children.forEach(function (m) { + if (m instanceof CellMorph && m.contentsMorph instanceof ListWatcherMorph) { m.contentsMorph.update(); @@ -529,7 +531,8 @@ ListWatcherMorph.prototype.update = function (anyway) { cell = new CellMorph( this.list.at(idx), this.cellColor, - idx + idx, + this.parentCell ); button = new PushButtonMorph( this.list.remove, @@ -147,6 +147,7 @@ III. yet to implement --------------------- - keyboard support for scroll frames and lists + - virtual keyboard support for Android and IE IV. open issues @@ -416,7 +417,7 @@ * add(submorph) - attaches submorph ontop * addBack(submorph) - attaches submorph underneath - * fullCopy() - duplication + * fullCopy() - duplication * destroy() - deletion @@ -450,6 +451,7 @@ mouseDownRight mouseClickLeft mouseClickRight + mouseDoubleClick mouseEnter mouseLeave mouseEnterDragging @@ -1033,7 +1035,7 @@ /*global window, HTMLCanvasElement, getMinimumFontHeight, FileReader, Audio, FileList, getBlurredShadowSupport*/ -var morphicVersion = '2013-April-19'; +var morphicVersion = '2013-April-21'; var modules = {}; // keep track of additional loaded modules var useBlurredShadows = getBlurredShadowSupport(); // check for Chrome-bug @@ -6161,7 +6163,7 @@ InspectorMorph.prototype.setTarget = function (target) { }; InspectorMorph.prototype.buildPanes = function () { - var attribs = [], property, myself = this, ctrl, ev; + var attribs = [], property, myself = this, ctrl, ev, doubleClickAction; // remove existing panes this.children.forEach(function (m) { @@ -6194,6 +6196,20 @@ InspectorMorph.prototype.buildPanes = function () { return typeof myself.target[prop] === 'function'; }); } // otherwise show all properties + + doubleClickAction = function () { + var world, inspector; + if (!isObject(myself.currentProperty)) {return; } + world = myself.world(); + inspector = new InspectorMorph( + myself.currentProperty + ); + inspector.setPosition(world.hand.position()); + inspector.keepWithin(world); + world.add(inspector); + inspector.changed(); + }; + this.list = new ListMorph( this.target instanceof Array ? attribs : attribs.sort(), null, // label getter @@ -6209,10 +6225,13 @@ InspectorMorph.prototype.buildPanes = function () { } ] ] - : null + : null, + doubleClickAction ); + this.list.action = function (selected) { var val, txt, cnts; + if (selected === undefined) {return; } val = myself.target[selected]; myself.currentProperty = val; if (val === null) { @@ -6228,6 +6247,7 @@ InspectorMorph.prototype.buildPanes = function () { cnts.setReceiver(myself.target); myself.detail.setContents(cnts); }; + this.list.hBar.alpha = 0.6; this.list.vBar.alpha = 0.6; this.list.contents.step = null; @@ -6609,7 +6629,8 @@ MenuMorph.prototype.addItem = function ( hint, color, bold, // bool - italic // bool + italic, // bool + doubleClickAction // optional, when used as list contents ) { /* labelString is normally a single-line string. But it can also be one @@ -6625,7 +6646,8 @@ MenuMorph.prototype.addItem = function ( hint, color, bold || false, - italic || false]); + italic || false, + doubleClickAction]); }; MenuMorph.prototype.addLine = function (width) { @@ -6713,7 +6735,8 @@ MenuMorph.prototype.drawNew = function () { tuple[2], // bubble help hint tuple[3], // color tuple[4], // bold - tuple[5] // italic + tuple[5], // italic + tuple[6] // doubleclick action ); } if (isLine) { @@ -7910,7 +7933,8 @@ function TriggerMorph( hint, labelColor, labelBold, - labelItalic + labelItalic, + doubleClickAction ) { this.init( target, @@ -7922,7 +7946,8 @@ function TriggerMorph( hint, labelColor, labelBold, - labelItalic + labelItalic, + doubleClickAction ); } @@ -7936,11 +7961,13 @@ TriggerMorph.prototype.init = function ( hint, labelColor, labelBold, - labelItalic + labelItalic, + doubleClickAction ) { // additional properties: this.target = target || null; this.action = action || null; + this.doubleClickAction = doubleClickAction || null; this.environment = environment || null; this.labelString = labelString || null; this.label = null; @@ -8067,6 +8094,29 @@ TriggerMorph.prototype.trigger = function () { } }; +TriggerMorph.prototype.triggerDoubleClick = function () { + // same as trigger() but use doubleClickAction instead of action property + // note that specifying a doubleClickAction is optional + if (!this.doubleClickAction) {return; } + if (typeof this.target === 'function') { + if (typeof this.doubleClickAction === 'function') { + this.target.call( + this.environment, + this.doubleClickAction.call(), + this + ); + } else { + this.target.call(this.environment, this.doubleClickAction, this); + } + } else { + if (typeof this.doubleClickAction === 'function') { + this.doubleClickAction.call(this.target); + } else { // assume it's a String + this.target[this.doubleClickAction](); + } + } +}; + // TriggerMorph events: TriggerMorph.prototype.mouseEnter = function () { @@ -8096,6 +8146,10 @@ TriggerMorph.prototype.mouseClickLeft = function () { this.trigger(); }; +TriggerMorph.prototype.mouseDoubleClick = function () { + this.triggerDoubleClick(); +}; + TriggerMorph.prototype.rootForGrab = function () { return null; }; @@ -8147,7 +8201,8 @@ function MenuItemMorph( hint, color, bold, - italic + italic, + doubleClickAction // optional when used as list morph item ) { this.init( target, @@ -8159,7 +8214,8 @@ function MenuItemMorph( hint, color, bold, - italic + italic, + doubleClickAction ); } @@ -8859,7 +8915,7 @@ ListMorph.prototype = new ScrollFrameMorph(); ListMorph.prototype.constructor = ListMorph; ListMorph.uber = ScrollFrameMorph.prototype; -function ListMorph(elements, labelGetter, format) { +function ListMorph(elements, labelGetter, format, doubleClickAction) { /* passing a format is optional. If the format parameter is specified it has to be of the following pattern: @@ -8891,11 +8947,17 @@ function ListMorph(elements, labelGetter, format) { } return element.toString(); }, - format || [] + format || [], + doubleClickAction // optional callback ); } -ListMorph.prototype.init = function (elements, labelGetter, format) { +ListMorph.prototype.init = function ( + elements, + labelGetter, + format, + doubleClickAction +) { ListMorph.uber.init.call(this); this.contents.acceptsDrops = false; @@ -8909,6 +8971,7 @@ ListMorph.prototype.init = function (elements, labelGetter, format) { this.selected = null; // actual element currently selected this.active = null; // menu item representing the selected element this.action = null; + this.doubleClickAction = doubleClickAction || null; this.acceptsDrops = false; this.buildListContents(); }; @@ -8948,7 +9011,8 @@ ListMorph.prototype.buildListContents = function () { null, // hint color, bold, - italic + italic, + myself.doubleClickAction ); }); this.listContents.setPosition(this.contents.position()); @@ -9340,6 +9404,7 @@ HandMorph.prototype.drop = function () { mouseDownRight mouseClickLeft mouseClickRight + mouseDoubleClick mouseEnter mouseLeave mouseEnterDragging @@ -9472,6 +9537,23 @@ HandMorph.prototype.processMouseUp = function () { this.mouseButton = null; }; +HandMorph.prototype.processDoubleClick = function () { + var morph = this.morphAtPointer(); + + this.destroyTemporaries(); + if (this.children.length !== 0) { + this.drop(); + } else { + while (morph && !morph.mouseDoubleClick) { + morph = morph.parent; + } + if (morph) { + morph.mouseDoubleClick(this.bounds.origin); + } + } + this.mouseButton = null; +}; + HandMorph.prototype.processMouseMove = function (event) { var pos, posInDocument = getDocumentPositionOf(this.world.worldCanvas), @@ -9525,7 +9607,7 @@ HandMorph.prototype.processMouseMove = function (event) { /* original, more cautious code for grabbing Morphs, - retained in case of needing to fall back: + retained in case of needing to fall back: if (morph === this.morphToGrab) { if (morph.isDraggable) { @@ -10040,6 +10122,15 @@ WorldMorph.prototype.initEventListeners = function () { ); canvas.addEventListener( + "dblclick", + function (event) { + event.preventDefault(); + myself.hand.processDoubleClick(event); + }, + false + ); + + canvas.addEventListener( "touchend", function (event) { myself.hand.processTouchEnd(event); diff --git a/morphic.txt b/morphic.txt index afa6096..042b7e5 100755 --- a/morphic.txt +++ b/morphic.txt @@ -9,7 +9,7 @@ Copyright (C) 2012 by Jens Mönig - this documentation last changed: November 07, 2012 + this documentation last changed: April 07, 2013 This file is part of Snap!. @@ -27,986 +27,1005 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. - documentation contents - ---------------------- - I. inheritance hierarchy - II. object definition toc - III. yet to implement - IV. open issues - V. browser compatibility - VI. the big picture - VII. programming guide - (1) setting up a web page - (a) single world - (b) multiple worlds - (c) an application - (2) manipulating morphs - (3) events - (a) mouse events - (b) context menu - (c) dragging - (d) dropping - (e) keyboard events - (f) resize event - (g) combined mouse-keyboard events - (h) text editing events - (4) stepping - (5) creating new kinds of morphs - (6) development and user modes - (7) turtle graphics - (8) damage list housekeeping - (9) minifying morphic.js - VIII. acknowledgements - IX. contributors + documentation contents + ---------------------- + I. inheritance hierarchy + II. object definition toc + III. yet to implement + IV. open issues + V. browser compatibility + VI. the big picture + VII. programming guide + (1) setting up a web page + (a) single world + (b) multiple worlds + (c) an application + (2) manipulating morphs + (3) events + (a) mouse events + (b) context menu + (c) dragging + (d) dropping + (e) keyboard events + (f) resize event + (g) combined mouse-keyboard events + (h) text editing events + (4) stepping + (5) creating new kinds of morphs + (6) development and user modes + (7) turtle graphics + (8) damage list housekeeping + (9) minifying morphic.js + VIII. acknowledgements + IX. contributors - I. hierarchy - ------------- - the following tree lists all constructors hierarchically, - indentation indicating inheritance. Refer to this list to get a - contextual overview: + I. hierarchy + ------------- + the following tree lists all constructors hierarchically, + indentation indicating inheritance. Refer to this list to get a + contextual overview: - Color - Node - Morph - BlinkerMorph - CursorMorph - BouncerMorph* - BoxMorph - InspectorMorph - MenuMorph - MouseSensorMorph* - SpeechBubbleMorph - CircleBoxMorph - SliderButtonMorph - SliderMorph - ColorPaletteMorph - GrayPaletteMorph - ColorPickerMorph - FrameMorph - ScrollFrameMorph - ListMorph - StringFieldMorph - WorldMorph - HandleMorph - HandMorph - PenMorph - ShadowMorph - StringMorph - TextMorph - TriggerMorph - MenuItemMorph - Point - Rectangle + Color + Node + Morph + BlinkerMorph + CursorMorph + BouncerMorph* + BoxMorph + InspectorMorph + MenuMorph + MouseSensorMorph* + SpeechBubbleMorph + CircleBoxMorph + SliderButtonMorph + SliderMorph + ColorPaletteMorph + GrayPaletteMorph + ColorPickerMorph + FrameMorph + ScrollFrameMorph + ListMorph + StringFieldMorph + WorldMorph + HandleMorph + HandMorph + PenMorph + ShadowMorph + StringMorph + TextMorph + TriggerMorph + MenuItemMorph + Point + Rectangle - II. toc - ------- - the following list shows the order in which all constructors are - defined. Use this list to locate code in this document: + II. toc + ------- + the following list shows the order in which all constructors are + defined. Use this list to locate code in this document: - Global settings - Global functions + Global settings + Global functions - Color - Point - Rectangle - Node - Morph - ShadowMorph - HandleMorph - PenMorph - ColorPaletteMorph - GrayPaletteMorph - ColorPickerMorph - BlinkerMorph - CursorMorph - BoxMorph - SpeechBubbleMorph - CircleBoxMorph - SliderButtonMorph - SliderMorph - MouseSensorMorph* - InspectorMorph - MenuMorph - StringMorph - TextMorph - TriggerMorph - MenuItemMorph - FrameMorph - ScrollFrameMorph - ListMorph - StringFieldMorph - BouncerMorph* - HandMorph - WorldMorph + Color + Point + Rectangle + Node + Morph + ShadowMorph + HandleMorph + PenMorph + ColorPaletteMorph + GrayPaletteMorph + ColorPickerMorph + BlinkerMorph + CursorMorph + BoxMorph + SpeechBubbleMorph + CircleBoxMorph + SliderButtonMorph + SliderMorph + MouseSensorMorph* + InspectorMorph + MenuMorph + StringMorph + TextMorph + TriggerMorph + MenuItemMorph + FrameMorph + ScrollFrameMorph + ListMorph + StringFieldMorph + BouncerMorph* + HandMorph + WorldMorph - * included only for demo purposes + * included only for demo purposes - III. yet to implement - --------------------- - - keyboard support for scroll frames and lists + III. yet to implement + --------------------- + - keyboard support for scroll frames and lists + - virtual keyboard support for Android and IE - IV. open issues - ---------------- - - blurry shadows don't work well in Chrome + IV. open issues + ---------------- + - blurry shadows don't work well in Chrome - V. browser compatibility - ------------------------ - I have taken great care and considerable effort to make morphic.js - runnable and appearing exactly the same on all current browsers - available to me: + V. browser compatibility + ------------------------ + I have taken great care and considerable effort to make morphic.js + runnable and appearing exactly the same on all current browsers + available to me: - - Firefox for Windows - - Firefox for Mac - - Chrome for Windows (blurry shadows have some issues) - - Chrome for Mac - - Safari for Windows - - safari for Mac - - Safari for iOS (mobile) - - IE for Windows - - Opera for Windows - - Opera for Mac + - Firefox for Windows + - Firefox for Mac + - Chrome for Windows (blurry shadows have some issues) + - Chrome for Mac + - Safari for Windows + - safari for Mac + - Safari for iOS (mobile) + - IE for Windows + - Opera for Windows + - Opera for Mac - VI. the big picture - ------------------- - Morphic.js is completely based on Canvas and JavaScript, it is just - Morphic, nothing else. Morphic.js is very basic and covers only the - bare essentials: + VI. the big picture + ------------------- + Morphic.js is completely based on Canvas and JavaScript, it is just + Morphic, nothing else. Morphic.js is very basic and covers only the + bare essentials: - * a stepping mechanism (a time-sharing multiplexer for lively - user interaction ontop of a single OS/browser thread) - * progressive display updates (only dirty rectangles are - redrawn in each display cycle) - * a tree structure - * a single World per Canvas element (although you can have - multiple worlds in multiple Canvas elements on the same web - page) - * a single Hand per World (but you can support multi-touch - events) - * a single text entry focus per World + * a stepping mechanism (a time-sharing multiplexer for lively + user interaction ontop of a single OS/browser thread) + * progressive display updates (only dirty rectangles are + redrawn in each display cycle) + * a tree structure + * a single World per Canvas element (although you can have + multiple worlds in multiple Canvas elements on the same web + page) + * a single Hand per World (but you can support multi-touch + events) + * a single text entry focus per World - In its current state morphic.js doesn't support Transforms (you - cannot rotate Morphs), but with PenMorph there already is a simple - LOGO-like turtle that you can use to draw onto any Morph it is - attached to. I'm planning to add special Morphs that support these - operations later on, but not for every Morph in the system. - Therefore these additions ("sprites" etc.) are likely to be part of - other libraries ("microworld.js") in separate files. + In its current state morphic.js doesn't support Transforms (you + cannot rotate Morphs), but with PenMorph there already is a simple + LOGO-like turtle that you can use to draw onto any Morph it is + attached to. I'm planning to add special Morphs that support these + operations later on, but not for every Morph in the system. + Therefore these additions ("sprites" etc.) are likely to be part of + other libraries ("microworld.js") in separate files. - the purpose of morphic.js is to provide a malleable framework that - will let me experiment with lively GUIs for my hobby horse, which - is drag-and-drop, blocks based programming languages. Those things - (BYOB4 - http://byob.berkeley.edu) will be written using morphic.js - as a library. + the purpose of morphic.js is to provide a malleable framework that + will let me experiment with lively GUIs for my hobby horse, which + is drag-and-drop, blocks based programming languages. Those things + (BYOB4 - http://byob.berkeley.edu) will be written using morphic.js + as a library. - VII. programming guide - ---------------------- - Morphic.js provides a library for lively GUIs inside single HTML - Canvas elements. Each such canvas element functions as a "world" in - which other visible shapes ("morphs") can be positioned and - manipulated, often directly and interactively by the user. Morphs - are tree nodes and may contain any number of submorphs ("children"). + VII. programming guide + ---------------------- + Morphic.js provides a library for lively GUIs inside single HTML + Canvas elements. Each such canvas element functions as a "world" in + which other visible shapes ("morphs") can be positioned and + manipulated, often directly and interactively by the user. Morphs + are tree nodes and may contain any number of submorphs ("children"). - All things visible in a morphic World are morphs themselves, i.e. - all text rendering, blinking cursors, entry fields, menus, buttons, - sliders, windows and dialog boxes etc. are created with morphic.js - rather than using HTML DOM elements, and as a consequence can be - changed and adjusted by the programmer regardless of proprietary - browser behavior. + All things visible in a morphic World are morphs themselves, i.e. + all text rendering, blinking cursors, entry fields, menus, buttons, + sliders, windows and dialog boxes etc. are created with morphic.js + rather than using HTML DOM elements, and as a consequence can be + changed and adjusted by the programmer regardless of proprietary + browser behavior. - Each World has an - invisible - "Hand" resembling the mouse cursor - (or the user's finger on touch screens) which handles mouse events, - and may also have a keyboardReceiver to handle key events. + Each World has an - invisible - "Hand" resembling the mouse cursor + (or the user's finger on touch screens) which handles mouse events, + and may also have a keyboardReceiver to handle key events. - The basic idea of Morphic is to continuously run display cycles and - to incrementally update the screen by only redrawing those World - regions which have been "dirtied" since the last redraw. Before - each shape is processed for redisplay it gets the chance to perform - a "step" procedure, thus allowing for an illusion of concurrency. + The basic idea of Morphic is to continuously run display cycles and + to incrementally update the screen by only redrawing those World + regions which have been "dirtied" since the last redraw. Before + each shape is processed for redisplay it gets the chance to perform + a "step" procedure, thus allowing for an illusion of concurrency. - (1) setting up a web page - ------------------------- - Setting up a web page for Morphic always involves three steps: - adding one or more Canvas elements, defining one or more worlds, - initializing and starting the main loop. + (1) setting up a web page + ------------------------- + Setting up a web page for Morphic always involves three steps: + adding one or more Canvas elements, defining one or more worlds, + initializing and starting the main loop. - (a) single world - ----------------- - Most commonly you will want your World to fill the browsers's whole - client area. This default situation is easiest and most straight - forward. + (a) single world + ----------------- + Most commonly you will want your World to fill the browsers's whole + client area. This default situation is easiest and most straight + forward. - example html file: + example html file: - <!DOCTYPE html> - <html> - <head> - <title>Morphic!</title> - <script type="text/javascript" src="morphic.js"></script> - <script type="text/javascript"> - var world; + <!DOCTYPE html> + <html> + <head> + <title>Morphic!</title> + <script type="text/javascript" src="morphic.js"></script> + <script type="text/javascript"> + var world; - window.onload = function () { - world = new WorldMorph( - document.getElementById('world')); - setInterval(loop, 50); - }; + window.onload = function () { + world = new WorldMorph( + document.getElementById('world')); + setInterval(loop, 50); + }; - function loop() { - world.doOneCycle(); - } - </script> - </head> - <body> - <canvas id="world" tabindex="1" width="800" height="600"> - <p>Your browser doesn't support canvas.</p> - </canvas> - </body> - </html> + function loop() { + world.doOneCycle(); + } + </script> + </head> + <body> + <canvas id="world" tabindex="1" width="800" height="600"> + <p>Your browser doesn't support canvas.</p> + </canvas> + </body> + </html> - if you use ScrollFrames or otherwise plan to support mouse wheel - scrolling events, you might also add the following inline-CSS - attribute to the Canvas element: + if you use ScrollFrames or otherwise plan to support mouse wheel + scrolling events, you might also add the following inline-CSS + attribute to the Canvas element: - style="position: absolute;" + style="position: absolute;" - which will prevent the World to be scrolled around instead of the - elements inside of it in some browsers. + which will prevent the World to be scrolled around instead of the + elements inside of it in some browsers. - (b) multiple worlds - ------------------- - If you wish to create a web page with more than one world, make - sure to prevent each world from auto-filling the whole page and - include it in the main loop. It's also a good idea to give each - world its own tabindex: + (b) multiple worlds + ------------------- + If you wish to create a web page with more than one world, make + sure to prevent each world from auto-filling the whole page and + include it in the main loop. It's also a good idea to give each + world its own tabindex: - example html file: + example html file: - <!DOCTYPE html> - <html> - <head> - <title>Morphic!</title> - <script type="text/javascript" src="morphic.js"></script> - <script type="text/javascript"> - var world1, world2; + <!DOCTYPE html> + <html> + <head> + <title>Morphic!</title> + <script type="text/javascript" src="morphic.js"></script> + <script type="text/javascript"> + var world1, world2; - window.onload = function () { - world1 = new WorldMorph( - document.getElementById('world1'), false); - world2 = new WorldMorph( - document.getElementById('world2'), false); - setInterval(loop, 50); - }; + window.onload = function () { + world1 = new WorldMorph( + document.getElementById('world1'), false); + world2 = new WorldMorph( + document.getElementById('world2'), false); + setInterval(loop, 50); + }; - function loop() { - world1.doOneCycle(); - world2.doOneCycle(); - } - </script> - </head> - <body> - <p>first world:</p> - <canvas id="world1" tabindex="1" width="600" height="400"> - <p>Your browser doesn't support canvas.</p> - </canvas> - <p>second world:</p> - <canvas id="world2" tabindex="2" width="400" height="600"> - <p>Your browser doesn't support canvas.</p> - </canvas> - </body> - </html> + function loop() { + world1.doOneCycle(); + world2.doOneCycle(); + } + </script> + </head> + <body> + <p>first world:</p> + <canvas id="world1" tabindex="1" width="600" height="400"> + <p>Your browser doesn't support canvas.</p> + </canvas> + <p>second world:</p> + <canvas id="world2" tabindex="2" width="400" height="600"> + <p>Your browser doesn't support canvas.</p> + </canvas> + </body> + </html> - (c) an application - ------------------- - Of course, most of the time you don't want to just plain use the - standard Morhic World "as is" out of the box, but write your own - application (something like Scratch!) in it. For such an - application you'll create your own morph prototypes, perhaps - assemble your own "window frame" and bring it all to life in a - customized World state. the following example creates a simple - snake-like mouse drawing game. + (c) an application + ------------------- + Of course, most of the time you don't want to just plain use the + standard Morhic World "as is" out of the box, but write your own + application (something like Scratch!) in it. For such an + application you'll create your own morph prototypes, perhaps + assemble your own "window frame" and bring it all to life in a + customized World state. the following example creates a simple + snake-like mouse drawing game. - example html file: + example html file: - <!DOCTYPE html> - <html> - <head> - <title>touch me!</title> - <script type="text/javascript" src="morphic.js"></script> - <script type="text/javascript"> - var worldCanvas, sensor; + <!DOCTYPE html> + <html> + <head> + <title>touch me!</title> + <script type="text/javascript" src="morphic.js"></script> + <script type="text/javascript"> + var worldCanvas, sensor; - window.onload = function () { - var x, y, w, h; + window.onload = function () { + var x, y, w, h; - worldCanvas = document.getElementById('world'); - world = new WorldMorph(worldCanvas); - world.isDevMode = false; - world.color = new Color(); + worldCanvas = document.getElementById('world'); + world = new WorldMorph(worldCanvas); + world.isDevMode = false; + world.color = new Color(); - w = 100; - h = 100; + w = 100; + h = 100; - x = 0; - y = 0; + x = 0; + y = 0; - while ((y * h) < world.height()) { - while ((x * w) < world.width()) { - sensor = new MouseSensorMorph(); - sensor.setPosition(new Point(x * w, y * h)); - sensor.alpha = 0; - sensor.setExtent(new Point(w, h)); - world.add(sensor); - x += 1; - } - x = 0; - y += 1; - } - setInterval(loop, 50); - }; + while ((y * h) < world.height()) { + while ((x * w) < world.width()) { + sensor = new MouseSensorMorph(); + sensor.setPosition(new Point(x * w, y * h)); + sensor.alpha = 0; + sensor.setExtent(new Point(w, h)); + world.add(sensor); + x += 1; + } + x = 0; + y += 1; + } + setInterval(loop, 50); + }; - function loop() { - world.doOneCycle(); - } - </script> - </head> - <body bgcolor='black'> - <canvas id="world" width="800" height="600"> - <p>Your browser doesn't support canvas.</p> - </canvas> - </body> - </html> + function loop() { + world.doOneCycle(); + } + </script> + </head> + <body bgcolor='black'> + <canvas id="world" width="800" height="600"> + <p>Your browser doesn't support canvas.</p> + </canvas> + </body> + </html> - To get an idea how you can craft your own custom morph prototypes - I've included two examples which should give you an idea how to add - properties, override inherited methods and use the stepping - mechanism for "livelyness": + To get an idea how you can craft your own custom morph prototypes + I've included two examples which should give you an idea how to add + properties, override inherited methods and use the stepping + mechanism for "livelyness": - BouncerMorph - MouseSensorMorph + BouncerMorph + MouseSensorMorph - For the sake of sharing a single file I've included those examples - in morphic.js itself. Usually you'll define your additions in a - separate file and keep morphic.js untouched. + For the sake of sharing a single file I've included those examples + in morphic.js itself. Usually you'll define your additions in a + separate file and keep morphic.js untouched. - (2) manipulating morphs - ----------------------- - There are many methods to programmatically manipulate morphs. Among - the most important and common ones among all morphs are the - following nine: + (2) manipulating morphs + ----------------------- + There are many methods to programmatically manipulate morphs. Among + the most important and common ones among all morphs are the + following nine: - * hide() - * show() + * hide() + * show() - * setPosition(aPoint) - * setExtent(aPoint) - * setColor(aColor) + * setPosition(aPoint) + * setExtent(aPoint) + * setColor(aColor) - * add(submorph) - attaches submorph ontop - * addBack(submorph) - attaches submorph underneath + * add(submorph) - attaches submorph ontop + * addBack(submorph) - attaches submorph underneath - * fullCopy() - duplication - * destroy() - deletion + * fullCopy() - duplication + * destroy() - deletion - (3) events - ---------- - All user (and system) interaction is triggered by events, which are - passed on from the root element - the World - to its submorphs. The - World contains a list of system (browser) events it reacts to in its + (3) events + ---------- + All user (and system) interaction is triggered by events, which are + passed on from the root element - the World - to its submorphs. The + World contains a list of system (browser) events it reacts to in its - initEventListeners() + initEventListeners() - method. Currently there are + method. Currently there are - - mouse - - drop - - keyboard - - (window) resize + - mouse + - drop + - keyboard + - (window) resize - events. + events. - These system events are dispatched within the morphic World by the - World's Hand and its keyboardReceiver (usually the active text - cursor). + These system events are dispatched within the morphic World by the + World's Hand and its keyboardReceiver (usually the active text + cursor). - (a) mouse events: - ----------------- - The Hand dispatches the following mouse events to relevant morphs: + (a) mouse events: + ----------------- + The Hand dispatches the following mouse events to relevant morphs: - mouseDownLeft - mouseDownRight - mouseClickLeft - mouseClickRight - mouseEnter - mouseLeave - mouseEnterDragging - mouseLeaveDragging - mouseMove - mouseScroll + mouseDownLeft + mouseDownRight + mouseClickLeft + mouseClickRight + mouseDoubleClick + mouseEnter + mouseLeave + mouseEnterDragging + mouseLeaveDragging + mouseMove + mouseScroll - If you wish your morph to react to any such event, simply add a - method of the same name as the event, e.g: + If you wish your morph to react to any such event, simply add a + method of the same name as the event, e.g: - MyMorph.prototype.mouseMove = function(pos) {}; + MyMorph.prototype.mouseMove = function(pos) {}; - The only optional parameter of such a method is a Point object - indicating the current position of the Hand inside the World's - coordinate system. + The only optional parameter of such a method is a Point object + indicating the current position of the Hand inside the World's + coordinate system. - Events may be "bubbled" up a morph's owner chain by calling + Events may be "bubbled" up a morph's owner chain by calling - this.escalateEvent(functionName, arg) + this.escalateEvent(functionName, arg) - in the event handler method's code. + in the event handler method's code. - Likewise, removing the event handler method will render your morph - passive to the event in question. + Likewise, removing the event handler method will render your morph + passive to the event in question. - (b) context menu: - ----------------- - By default right-clicking (or single-finger tap-and-hold) on a morph - also invokes its context menu (in addition to firing the - mouseClickRight event). A morph's context menu can be customized by - assigning a Menu instance to its + (b) context menu: + ----------------- + By default right-clicking (or single-finger tap-and-hold) on a morph + also invokes its context menu (in addition to firing the + mouseClickRight event). A morph's context menu can be customized by + assigning a Menu instance to its - customContextMenu + customContextMenu - property, or altogether suppressed by overriding its inherited + property, or altogether suppressed by overriding its inherited - contextMenu() + contextMenu() - method. + method. - (c) dragging: - ------------- - Dragging a morph is initiated when the left mouse button is pressed, - held and the mouse is moved. + (c) dragging: + ------------- + Dragging a morph is initiated when the left mouse button is pressed, + held and the mouse is moved. - You can control whether a morph is draggable by setting its + You can control whether a morph is draggable by setting its - isDraggable + isDraggable - property either to false or true. If a morph isn't draggable itself - it will pass the pick-up request up its owner chain. This lets you - create draggable composite morphs like Windows, DialogBoxes, - Sliders etc. + property either to false or true. If a morph isn't draggable itself + it will pass the pick-up request up its owner chain. This lets you + create draggable composite morphs like Windows, DialogBoxes, + Sliders etc. - Sometimes it is desireable to make "template" shapes which cannot be - moved themselves, but from which instead duplicates can be peeled - off. This is especially useful for building blocks in construction - kits, e.g. the MIT-Scratch palette. Morphic.js lets you control this - functionality by setting the + Sometimes it is desireable to make "template" shapes which cannot be + moved themselves, but from which instead duplicates can be peeled + off. This is especially useful for building blocks in construction + kits, e.g. the MIT-Scratch palette. Morphic.js lets you control this + functionality by setting the - isTemplate + isTemplate - property flag to true for any morph whose "isDraggable" property is - turned off. When dragging such a Morph the hand will instead grab - a duplicate of the template whose "isDraggable" flag is true and - whose "isTemplate" flag is false, in other words: a non-template. + property flag to true for any morph whose "isDraggable" property is + turned off. When dragging such a Morph the hand will instead grab + a duplicate of the template whose "isDraggable" flag is true and + whose "isTemplate" flag is false, in other words: a non-template. - 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), - e.g. when "dragging" a morph handle to resize its owner, or when - "dragging" a slider button. + 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), + e.g. when "dragging" a morph handle to resize its owner, or when + "dragging" a slider button. - Right before a morph is picked up its + Right before a morph is picked up its - prepareToBeGrabbed(handMorph) + prepareToBeGrabbed(handMorph) - method is invoked, if it is present. Immediately after the pick-up - the former parent's + method is invoked, if it is present. Immediately after the pick-up + the former parent's - reactToGrabOf(grabbedMorph) + reactToGrabOf(grabbedMorph) - method is called, again only if it exists. + method is called, again only if it exists. - Similar to events, these methods are optional and don't exist by - default. For a simple example of how they can be used to adjust - scroll bars in a scroll frame please have a look at their - implementation in FrameMorph. + Similar to events, these methods are optional and don't exist by + default. For a simple example of how they can be used to adjust + scroll bars in a scroll frame please have a look at their + implementation in FrameMorph. - (d) dropping: - ------------- - Dropping is triggered when the left mouse button is either pressed - or released while the Hand is dragging a morph. + (d) dropping: + ------------- + Dropping is triggered when the left mouse button is either pressed + or released while the Hand is dragging a morph. - Dropping a morph causes it to become embedded in a new owner morph. - You can control this embedding behavior by setting the prospective - drop target's + Dropping a morph causes it to become embedded in a new owner morph. + You can control this embedding behavior by setting the prospective + drop target's - acceptsDrops + acceptsDrops - property to either true or false, or by overriding its inherited + property to either true or false, or by overriding its inherited - wantsDropOf(aMorph) + wantsDropOf(aMorph) - method. + method. - Right after a morph has been dropped its + Right after a morph has been dropped its - justDropped(handMorph) + justDropped(handMorph) - method is called, and its new parent's + method is called, and its new parent's - reactToDropOf(droppedMorph, handMorph) + reactToDropOf(droppedMorph, handMorph) - method is invoked, again only if each method exists. + method is invoked, again only if each method exists. - Similar to events, these methods are optional and by default are - not present in morphs by default (watch out for inheritance, - though!). For a simple example of how they can be used to adjust - scroll bars in a scroll frame please have a look at their - implementation in FrameMorph. + Similar to events, these methods are optional and by default are + not present in morphs by default (watch out for inheritance, + though!). For a simple example of how they can be used to adjust + scroll bars in a scroll frame please have a look at their + implementation in FrameMorph. - Drops of image elements from outside the world canvas are dispatched as + Drops of image elements from outside the world canvas are dispatched as - droppedImage(aCanvas, name) + droppedImage(aCanvas, name) + droppedSVG(anImage, name) - events to interested Morphs at the mouse pointer. If you want you Morph - to e.g. import outside images you can add the droppedImage() method to - it. The parameter passed to the event handles is a new offscreen - canvas element representing a copy of the original image element which - can be directly used, e.g. by assigning it to another Morph's image - property. + events to interested Morphs at the mouse pointer. If you want you Morph + to e.g. import outside images you can add the droppedImage() and / or the + droppedSVG() methods to it. The parameter passed to the event handles is + a new offscreen canvas element representing a copy of the original image + element which can be directly used, e.g. by assigning it to another + Morph's image property. In the case of a dropped SVG it is an image + element (not a canvas), which has to be rasterized onto a canvas before + it can be used. The benefit of handling SVGs as image elements is that + rasterization can be deferred until the destination scale is known, taking + advantage of SVG's ability for smooth scaling. If instead SVGs are to be + rasterized right away, you can set the - The same applies to drops of audio or text files from outside the world - canvas. + MorphicPreferences.rasterizeSVGs - Those are dispatched as + preference to <true>. In this case dropped SVGs also trigger the + droppedImage() event with a canvas containing a rasterized version of the + SVG. - droppedAudio(anAudio, name) - droppedText(aString, name) + The same applies to drops of audio or text files from outside the world + canvas. - events to interested Morphs at the mouse pointer. + Those are dispatched as + droppedAudio(anAudio, name) + droppedText(aString, name) - (e) keyboard events - ------------------- - The World dispatches the following key events to its active - keyboardReceiver: + events to interested Morphs at the mouse pointer. - keypress - keydown + if none of the above content types can be determined, the file contents + is dispatched as an ArrayBuffer to interested Morphs: + + droppedBinary(anArrayBuffer, name) + + + (e) keyboard events + ------------------- + The World dispatches the following key events to its active + keyboardReceiver: + + keypress + keydown keyup - Currently the only morph which acts as keyboard receiver is - CursorMorph, the basic text editing widget. If you wish to add - keyboard support to your morph you need to add event handling - methods for + Currently the only morph which acts as keyboard receiver is + CursorMorph, the basic text editing widget. If you wish to add + keyboard support to your morph you need to add event handling + methods for - processKeyPress(event) - processKeyDown(event) - processKeyUp(event) + processKeyPress(event) + processKeyDown(event) + processKeyUp(event) - and activate them by assigning your morph to the World's + and activate them by assigning your morph to the World's - keyboardReceiver + keyboardReceiver - property. + property. - Note that processKeyUp() is optional and doesn't have to be present - if your morph doesn't require it. + Note that processKeyUp() is optional and doesn't have to be present + if your morph doesn't require it. - (f) resize event - ---------------- - The Window resize event is handled by the World and allows the - World's extent to be adjusted so that it always completely fills - the browser's visible page. You can turn off this default behavior - by setting the World's + (f) resize event + ---------------- + The Window resize event is handled by the World and allows the + World's extent to be adjusted so that it always completely fills + the browser's visible page. You can turn off this default behavior + by setting the World's - useFillPage + useFillPage - property to false. + property to false. - Alternatively you can also initialize the World with the - useFillPage switch turned off from the beginning by passing the - false value as second parameter to the World's constructor: + Alternatively you can also initialize the World with the + useFillPage switch turned off from the beginning by passing the + false value as second parameter to the World's constructor: - world = new World(aCanvas, false); + world = new World(aCanvas, false); - Use this when creating a web page with multiple Worlds. + Use this when creating a web page with multiple Worlds. - if "useFillPage" is turned on the World dispatches an + if "useFillPage" is turned on the World dispatches an - reactToWorldResize(newBounds) + reactToWorldResize(newBounds) - events to all of its children (toplevel only), allowing each to - adjust to the new World bounds by implementing a corresponding - method, the passed argument being the World's new dimensions after - completing the resize. By default, the "reactToWorldResize" Method - does not exist. + events to all of its children (toplevel only), allowing each to + adjust to the new World bounds by implementing a corresponding + method, the passed argument being the World's new dimensions after + completing the resize. By default, the "reactToWorldResize" Method + does not exist. - Example: + Example: - Add the following method to your Morph to let it automatically - fill the whole World, but leave a 10 pixel border uncovered: + Add the following method to your Morph to let it automatically + fill the whole World, but leave a 10 pixel border uncovered: - MyMorph.prototype.reactToWorldResize = function (rect) { - this.changed(); - this.bounds = rect.insetBy(10); - this.drawNew(); - this.changed(); - }; + MyMorph.prototype.reactToWorldResize = function (rect) { + this.changed(); + this.bounds = rect.insetBy(10); + this.drawNew(); + this.changed(); + }; - (g) combined mouse-keyboard events - ---------------------------------- - Occasionally you'll want an object to react differently to a mouse - click or to some other mouse event while the user holds down a key - on the keyboard. Such "shift-click", "ctl-click", or "alt-click" - events can be implemented by querying the World's + (g) combined mouse-keyboard events + ---------------------------------- + Occasionally you'll want an object to react differently to a mouse + click or to some other mouse event while the user holds down a key + on the keyboard. Such "shift-click", "ctl-click", or "alt-click" + events can be implemented by querying the World's - currentKey + currentKey - property inside the function that reacts to the mouse event. This - property stores the keyCode of the key that's currently pressed. - Once the key is released by the user it reverts to null. + property inside the function that reacts to the mouse event. This + property stores the keyCode of the key that's currently pressed. + Once the key is released by the user it reverts to null. - (h) text editing events - ----------------------- - Much of Morphic's "liveliness" comes out of allowing text elements - (instances of either single-lined StringMorph or multi-lined TextMorph) - to be directly manipulated and edited by users. This requires other - objects which may have an interest in the text element's state to react - appropriately. Therefore text elements and their manipulators emit - a stream of events, mostly by "bubbling" them up the text element's - owner chain. Text elements' parents are notified about the following - events: + (h) text editing events + ----------------------- + Much of Morphic's "liveliness" comes out of allowing text elements + (instances of either single-lined StringMorph or multi-lined TextMorph) + to be directly manipulated and edited by users. This requires other + objects which may have an interest in the text element's state to react + appropriately. Therefore text elements and their manipulators emit + a stream of events, mostly by "bubbling" them up the text element's + owner chain. Text elements' parents are notified about the following + events: - Whenever the user presses a key on the keyboard while a text element - is being edited, a + Whenever the user presses a key on the keyboard while a text element + is being edited, a - reactToKeystroke(event) + reactToKeystroke(event) - is escalated up its parent chain, the "event" parameter being the - original one received by the World. + is escalated up its parent chain, the "event" parameter being the + original one received by the World. - Once the user has completed the edit, the following events are - dispatched: + Once the user has completed the edit, the following events are + dispatched: - accept() - <enter> was pressed on a single line of text - cancel() - <esc> was pressed on any text element + accept() - <enter> was pressed on a single line of text + cancel() - <esc> was pressed on any text element - Note that "accept" only gets triggered by single-line texte elements, - as the <enter> key is used to insert line breaks in multi-line - elements. Therefore, whenever a text edit is terminated by the user - (accepted, cancelled or otherwise), + Note that "accept" only gets triggered by single-line texte elements, + as the <enter> key is used to insert line breaks in multi-line + elements. Therefore, whenever a text edit is terminated by the user + (accepted, cancelled or otherwise), - reactToEdit(StringOrTextMorph) + reactToEdit(StringOrTextMorph) - is triggered. + is triggered. - If the MorphicPreference's + If the MorphicPreference's - useSliderForInput + useSliderForInput - setting is turned on, a slider is popped up underneath the currently - edited text element letting the user insert numbers out of the given - slider range. Whenever this happens, i.e. whenever the slider is moved - or while the slider button is pressed, a stream of + setting is turned on, a slider is popped up underneath the currently + edited text element letting the user insert numbers out of the given + slider range. Whenever this happens, i.e. whenever the slider is moved + or while the slider button is pressed, a stream of - reactToSliderEdit(StringOrTextMorph) + reactToSliderEdit(StringOrTextMorph) - events is dispatched, allowing for "Bret-Victor" style "live coding" - applications. + events is dispatched, allowing for "Bret-Victor" style "live coding" + applications. - In addition to user-initiated events text elements also emit - change notifications to their direct parents whenever their drawNew() - method is invoked. That way complex Morphs containing text elements - get a chance to react if something about the embedded text has been - modified programmatically. These events are: + In addition to user-initiated events text elements also emit + change notifications to their direct parents whenever their drawNew() + method is invoked. That way complex Morphs containing text elements + get a chance to react if something about the embedded text has been + modified programmatically. These events are: - layoutChanged() - sent from instances of TextMorph - fixLayout() - sent from instances of StringMorph + layoutChanged() - sent from instances of TextMorph + fixLayout() - sent from instances of StringMorph - they are different so that Morphs which contain both multi-line and - single-line text elements can hold them apart. + they are different so that Morphs which contain both multi-line and + single-line text elements can hold them apart. - (4) stepping - ------------ - Stepping is what makes Morphic "magical". Two properties control - a morph's stepping behavior: the fps attribute and the step() - method. + (4) stepping + ------------ + Stepping is what makes Morphic "magical". Two properties control + a morph's stepping behavior: the fps attribute and the step() + method. - By default the + By default the - step() + step() - method does nothing. As you can see in the examples of BouncerMorph - and MouseSensorMorph you can easily override this inherited method - to suit your needs. + method does nothing. As you can see in the examples of BouncerMorph + and MouseSensorMorph you can easily override this inherited method + to suit your needs. - By default the step() method is called once per display cycle. - Depending on the number of actively stepping morphs and the - complexity of your step() methods this can cause quite a strain on - your CPU, and also result in your application behaving differently - on slower computers than on fast ones. + By default the step() method is called once per display cycle. + Depending on the number of actively stepping morphs and the + complexity of your step() methods this can cause quite a strain on + your CPU, and also result in your application behaving differently + on slower computers than on fast ones. - setting + setting - myMorph.fps + myMorph.fps - to a number lower than the interval for the main loop lets you free - system resources (albeit at the cost of a less responsive or slower - behavior for this particular morph). + to a number lower than the interval for the main loop lets you free + system resources (albeit at the cost of a less responsive or slower + behavior for this particular morph). - (5) creating new kinds of morphs - -------------------------------- - The real fun begins when you start to create new kinds of morphs - with customized shapes. Imagine, e.g. jigsaw puzzle pieces or - musical notes. For this you have to override the default + (5) creating new kinds of morphs + -------------------------------- + The real fun begins when you start to create new kinds of morphs + with customized shapes. Imagine, e.g. jigsaw puzzle pieces or + musical notes. For this you have to override the default - drawNew() + drawNew() - method. + method. - This method creates a new offscreen Canvas and stores it in - the morph's + This method creates a new offscreen Canvas and stores it in + the morph's - image + image - property. + property. - Use the following template for a start: + Use the following template for a start: - MyMorph.prototype.drawNew = function() { - var context; - this.image = newCanvas(this.extent()); - context = this.image.getContext('2d'); - // use context to paint stuff here - }; + MyMorph.prototype.drawNew = function() { + var context; + this.image = newCanvas(this.extent()); + context = this.image.getContext('2d'); + // 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 - default + If your new morph stores or references other morphs outside of the + submorph tree in other properties, be sure to also override the + default - copyRecordingReferences() + copyRecordingReferences() - method accordingly if you want it to support duplication. + method accordingly if you want it to support duplication. - (6) development and user modes - ------------------------------ - When working with Squeak on Scratch or BYOB among the features I - like the best and use the most is inspecting what's going on in - the World while it is up and running. That's what development mode - is for (you could also call it debug mode). In essence development - mode controls which context menu shows up. In user mode right - clicking (or double finger tapping) a morph invokes its + (6) development and user modes + ------------------------------ + When working with Squeak on Scratch or BYOB among the features I + like the best and use the most is inspecting what's going on in + the World while it is up and running. That's what development mode + is for (you could also call it debug mode). In essence development + mode controls which context menu shows up. In user mode right + clicking (or double finger tapping) a morph invokes its - customContextMenu + customContextMenu - property, whereas in development mode only the general + property, whereas in development mode only the general - developersMenu() + developersMenu() - method is called and the resulting menu invoked. The developers' - menu features Gui-Builder-wise functionality to directly inspect, - take apart, reassamble and otherwise manipulate morphs and their - contents. + method is called and the resulting menu invoked. The developers' + menu features Gui-Builder-wise functionality to directly inspect, + take apart, reassamble and otherwise manipulate morphs and their + contents. - Instead of using the "customContextMenu" property you can also - assign a more dynamic contextMenu by overriding the general + Instead of using the "customContextMenu" property you can also + assign a more dynamic contextMenu by overriding the general - userMenu() + userMenu() - method with a customized menu constructor. The difference between - the customContextMenu property and the userMenu() method is that - the former is also present in development mode and overrides the - developersMenu() result. For an example of how to use the - customContextMenu property have a look at TextMorph's evaluation - menu, which is used for the Inspector's evaluation pane. + method with a customized menu constructor. The difference between + the customContextMenu property and the userMenu() method is that + the former is also present in development mode and overrides the + developersMenu() result. For an example of how to use the + customContextMenu property have a look at TextMorph's evaluation + menu, which is used for the Inspector's evaluation pane. - When in development mode you can inspect every Morph's properties - with the inspector, including all of its methods. The inspector - also lets you add, remove and rename properties, and even edit - their values at runtime. Like in a Smalltalk environment the inspect - features an evaluation pane into which you can type in arbitrary - JavaScript code and evaluate it in the context of the inspectee. + When in development mode you can inspect every Morph's properties + with the inspector, including all of its methods. The inspector + also lets you add, remove and rename properties, and even edit + their values at runtime. Like in a Smalltalk environment the inspect + features an evaluation pane into which you can type in arbitrary + JavaScript code and evaluate it in the context of the inspectee. - Use switching between user and development modes while you are - developing an application and disable switching to development once - you're done and deploying, because generally you don't want to - confuse end-users with inspectors and meta-level stuff. + Use switching between user and development modes while you are + developing an application and disable switching to development once + you're done and deploying, because generally you don't want to + confuse end-users with inspectors and meta-level stuff. - (7) turtle graphics - ------------------- + (7) turtle graphics + ------------------- - The basic Morphic kernel features a simple LOGO turtle constructor - called + The basic Morphic kernel features a simple LOGO turtle constructor + called - PenMorph + PenMorph - which you can use to draw onto its parent Morph. By default every - Morph in the system (including the World) is able to act as turtle - canvas and can display pen trails. Pen trails will be lost whenever - the trails morph (the pen's parent) performs a "drawNew()" - operation. If you want to create your own pen trails canvas, you - may wish to modify its + which you can use to draw onto its parent Morph. By default every + Morph in the system (including the World) is able to act as turtle + canvas and can display pen trails. Pen trails will be lost whenever + the trails morph (the pen's parent) performs a "drawNew()" + operation. If you want to create your own pen trails canvas, you + may wish to modify its - penTrails() + penTrails() - property, so that it keeps a separate offscreen canvas for pen - trails (and doesn't loose these on redraw). + property, so that it keeps a separate offscreen canvas for pen + trails (and doesn't loose these on redraw). - the following properties of PenMorph are relevant for turtle - graphics: + the following properties of PenMorph are relevant for turtle + graphics: - color - a Color - size - line width of pen trails - heading - degrees - isDown - drawing state + color - a Color + size - line width of pen trails + heading - degrees + isDown - drawing state - the following commands can be used to actually draw something: + the following commands can be used to actually draw something: - up() - lift the pen up, further movements leave no trails - down() - set down, further movements leave trails - clear() - remove all trails from the current parent - forward(n) - move n steps in the current direction (heading) - turn(n) - turn right n degrees + up() - lift the pen up, further movements leave no trails + down() - set down, further movements leave trails + clear() - remove all trails from the current parent + forward(n) - move n steps in the current direction (heading) + turn(n) - turn right n degrees - Turtle graphics can best be explored interactively by creating a - new PenMorph object and by manipulating it with the inspector - widget. + Turtle graphics can best be explored interactively by creating a + new PenMorph object and by manipulating it with the inspector + widget. - NOTE: PenMorph has a special optimization for recursive operations - called + NOTE: PenMorph has a special optimization for recursive operations + called - warp(function) + warp(function) - You can significantly speed up recursive ops and increase the depth - of recursion that's displayable by wrapping WARP around your - recursive function call: + You can significantly speed up recursive ops and increase the depth + of recursion that's displayable by wrapping WARP around your + recursive function call: - example: + example: - myPen.warp(function () { - myPen.tree(12, 120, 20); - }) + myPen.warp(function () { + myPen.tree(12, 120, 20); + }) - will be much faster than just invoking the tree function, because it - prevents the parent's parent from keeping track of every single line - segment and instead redraws the outcome in a single pass. + will be much faster than just invoking the tree function, because it + prevents the parent's parent from keeping track of every single line + segment and instead redraws the outcome in a single pass. - (8) damage list housekeeping - ---------------------------- - Morphic's progressive display update comes at the cost of having to - cycle through a list of "broken rectangles" every display cycle. If - this list gets very long working this damage list can lead to a - seemingly dramatic slow-down of the Morphic system. Typically this - occurs when updating the layout of complex Morphs with very many - submorphs, e.g. when resizing an inspector window. + (8) damage list housekeeping + ---------------------------- + Morphic's progressive display update comes at the cost of having to + cycle through a list of "broken rectangles" every display cycle. If + this list gets very long working this damage list can lead to a + seemingly dramatic slow-down of the Morphic system. Typically this + occurs when updating the layout of complex Morphs with very many + submorphs, e.g. when resizing an inspector window. - An effective strategy to cope with this is to use the inherited + An effective strategy to cope with this is to use the inherited - trackChanges + trackChanges - property of the Morph prototype for damage list housekeeping. + property of the Morph prototype for damage list housekeeping. - The trackChanges property of the Morph prototype is a Boolean switch - that determines whether the World's damage list ('broken' rectangles) - tracks changes. By default the switch is always on. If set to false - changes are not stored. This can be very useful for housekeeping of - the damage list in situations where a large number of (sub-) morphs - are changed more or less at once. Instead of keeping track of every - single submorph's changes tremendous performance improvements can be - achieved by setting the trackChanges flag to false before propagating - the layout changes, setting it to true again and then storing the full - bounds of the surrounding morph. An an example refer to the + The trackChanges property of the Morph prototype is a Boolean switch + that determines whether the World's damage list ('broken' rectangles) + tracks changes. By default the switch is always on. If set to false + changes are not stored. This can be very useful for housekeeping of + the damage list in situations where a large number of (sub-) morphs + are changed more or less at once. Instead of keeping track of every + single submorph's changes tremendous performance improvements can be + achieved by setting the trackChanges flag to false before propagating + the layout changes, setting it to true again and then storing the full + bounds of the surrounding morph. An an example refer to the - moveBy() + moveBy() - method of HandMorph, and to the + method of HandMorph, and to the - fixLayout() + fixLayout() - method of InspectorMorph, or the + method of InspectorMorph, or the - startLayout() - endLayout() + startLayout() + endLayout() - methods of SyntaxElementMorph in the Snap application. + methods of SyntaxElementMorph in the Snap application. - (9) minifying morphic.js - ------------------------ - Coming from Smalltalk and being a Squeaker at heart I am a huge fan - of browsing the code itself to make sense of it. Therefore I have - included this documentation and (too little) inline comments so all - you need to get going is this very file. + (9) minifying morphic.js + ------------------------ + Coming from Smalltalk and being a Squeaker at heart I am a huge fan + of browsing the code itself to make sense of it. Therefore I have + included this documentation and (too little) inline comments so all + you need to get going is this very file. - Nowadays with live streaming HD video even on mobile phones 250 KB - shouldn't be a big strain on bandwith, still minifying and even - compressing morphic.js down do about 100 KB may sometimes improve - performance in production use. + Nowadays with live streaming HD video even on mobile phones 250 KB + shouldn't be a big strain on bandwith, still minifying and even + compressing morphic.js down do about 100 KB may sometimes improve + performance in production use. - Being an attorney-at-law myself you programmer folk keep harassing - me with rabulistic nitpickings about free software licenses. I'm - releasing morphic.js under an AGPL license. Therefore please make - sure to adhere to that license in any minified or compressed version. + Being an attorney-at-law myself you programmer folk keep harassing + me with rabulistic nitpickings about free software licenses. I'm + releasing morphic.js under an AGPL license. Therefore please make + sure to adhere to that license in any minified or compressed version. - VIII. acknowledgements - ---------------------- - The original Morphic was designed and written by Randy Smith and - John Maloney for the SELF programming language, and later ported to - Squeak (Smalltalk) by John Maloney and Dan Ingalls, who has also - ported it to JavaScript (the Lively Kernel), once again setting - a "Gold Standard" for self sustaining systems which morphic.js - cannot and does not aspire to meet. + VIII. acknowledgements + ---------------------- + The original Morphic was designed and written by Randy Smith and + John Maloney for the SELF programming language, and later ported to + Squeak (Smalltalk) by John Maloney and Dan Ingalls, who has also + ported it to JavaScript (the Lively Kernel), once again setting + a "Gold Standard" for self sustaining systems which morphic.js + cannot and does not aspire to meet. - This Morphic implementation for JavaScript is not a direct port of - Squeak's Morphic, but still many individual functions have been - ported almost literally from Squeak, sometimes even including their - comments, e.g. the morph duplication mechanism fullCopy(). Squeak - has been a treasure trove, and if morphic.js looks, feels and - smells a lot like Squeak, I'll take it as a compliment. + This Morphic implementation for JavaScript is not a direct port of + Squeak's Morphic, but still many individual functions have been + ported almost literally from Squeak, sometimes even including their + comments, e.g. the morph duplication mechanism fullCopy(). Squeak + has been a treasure trove, and if morphic.js looks, feels and + smells a lot like Squeak, I'll take it as a compliment. - Evelyn Eastmond has inspired and encouraged me with her wonderful - implementation of DesignBlocksJS. Thanks for sharing code, ideas - and enthusiasm for programming. + Evelyn Eastmond has inspired and encouraged me with her wonderful + implementation of DesignBlocksJS. Thanks for sharing code, ideas + and enthusiasm for programming. - John Maloney has been my mentor and my source of inspiration for - these Morphic experiments. Thanks for the critique, the suggestions - and explanations for all things Morphic and for being my all time - programming hero. + John Maloney has been my mentor and my source of inspiration for + these Morphic experiments. Thanks for the critique, the suggestions + and explanations for all things Morphic and for being my all time + 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. + 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. - 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. - Ian Reynolds contributed backspace key handling for Chrome. - Davide Della Casa contributed performance optimizations for Firefox. + 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. + Ian Reynolds contributed backspace key handling for Chrome. + Davide Della Casa contributed performance optimizations for Firefox. - - Jens Mönig + - Jens Mönig @@ -120,7 +120,7 @@ PrototypeHatBlockMorph*/ // Global stuff //////////////////////////////////////////////////////// -modules.objects = '2013-April-19'; +modules.objects = '2013-April-23'; var SpriteMorph; var StageMorph; @@ -1675,7 +1675,7 @@ SpriteMorph.prototype.blockTemplates = function (category) { new VariableDialogMorph( null, function (pair) { - if (pair) { + if (pair && !myself.variables.silentFind(pair[0])) { myself.addVariable(pair[0], pair[1]); myself.toggleVariableWatcher(pair[0], pair[1]); myself.blocksCache[cat] = null; @@ -3796,7 +3796,7 @@ StageMorph.prototype.blockTemplates = function (category) { new VariableDialogMorph( null, function (pair) { - if (pair) { + if (pair && !myself.variables.silentFind(pair[0])) { myself.addVariable(pair[0], pair[1]); myself.toggleVariableWatcher(pair[0], pair[1]); myself.blocksCache[cat] = null; @@ -4805,16 +4805,17 @@ CellMorph.uber = BoxMorph.prototype; // CellMorph instance creation: -function CellMorph(contents, color, idx) { - this.init(contents, color, idx); +function CellMorph(contents, color, idx, parentCell) { + this.init(contents, color, idx, parentCell); } -CellMorph.prototype.init = function (contents, color, idx) { +CellMorph.prototype.init = function (contents, color, idx, parentCell) { this.contents = (contents === 0 ? 0 : contents === false ? false : contents || ''); this.isEditable = isNil(idx) ? false : true; this.idx = idx || null; // for list watchers + this.parentCell = parentCell || null; // for list circularity detection CellMorph.uber.init.call( this, SyntaxElementMorph.prototype.corner, @@ -4842,6 +4843,17 @@ CellMorph.prototype.normal = function () { this.changed(); }; +// CellMorph circularity testing: + + +CellMorph.prototype.isCircular = function (list) { + if (!this.parentCell) {return false; } + if (list instanceof List) { + return this.contents === list || this.parentCell.isCircular(list); + } + return this.parentCell.isCircular(this.contents); +}; + // CellMorph layout: CellMorph.prototype.fixLayout = function () { @@ -4914,8 +4926,23 @@ CellMorph.prototype.drawNew = function () { this.contentsMorph.silentSetHeight(img.height); this.contentsMorph.image = img; } else if (this.contents instanceof List) { - this.contentsMorph = new ListWatcherMorph(this.contents); - this.contentsMorph.isDraggable = false; + if (this.isCircular()) { + this.contentsMorph = new TextMorph( + '(...)', + fontSize, + null, + false, // bold + true, // italic + 'center' + ); + this.contentsMorph.setColor(new Color(255, 255, 255)); + } else { + this.contentsMorph = new ListWatcherMorph( + this.contents, + this + ); + this.contentsMorph.isDraggable = false; + } } else { this.contentsMorph = new TextMorph( !isNil(this.contents) ? this.contents.toString() : '', |
