summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--blocks.js5
-rw-r--r--gui.js73
-rwxr-xr-xhistory.txt10
-rw-r--r--lang-cs.js8
-rw-r--r--lang-de.js8
-rw-r--r--lang-eo.js8
-rw-r--r--lang-es.js8
-rw-r--r--lang-fr.js6
-rw-r--r--lang-it.js8
-rwxr-xr-xlang-ja.js8
-rwxr-xr-xlang-ja_HIRA.js8
-rw-r--r--lang-ko.js4
-rwxr-xr-xlang-pt.js8
-rw-r--r--lang-ru.js8
-rw-r--r--lang-si.js4
-rw-r--r--lang-zh.js8
-rw-r--r--lists.js13
-rw-r--r--morphic.js125
-rwxr-xr-xmorphic.txt1507
-rw-r--r--objects.js43
20 files changed, 1011 insertions, 859 deletions
diff --git a/blocks.js b/blocks.js
index 681a9de..588b4a6 100644
--- a/blocks.js
+++ b/blocks.js
@@ -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()
);
diff --git a/gui.js b/gui.js
index f2ac1ac..8e0b5d3 100644
--- a/gui.js
+++ b/gui.js
@@ -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
diff --git a/lang-cs.js b/lang-cs.js
index 6bd9697..727fd68 100644
--- a/lang-cs.js
+++ b/lang-cs.js
@@ -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ů',
diff --git a/lang-de.js b/lang-de.js
index 09bce41..85dacc8 100644
--- a/lang-de.js
+++ b/lang-de.js
@@ -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',
diff --git a/lang-eo.js b/lang-eo.js
index c8d07d1..36eb72a 100644
--- a/lang-eo.js
+++ b/lang-eo.js
@@ -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':
diff --git a/lang-es.js b/lang-es.js
index fec4cfe..120d519 100644
--- a/lang-es.js
+++ b/lang-es.js
@@ -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',
diff --git a/lang-fr.js b/lang-fr.js
index d554965..b545185 100644
--- a/lang-fr.js
+++ b/lang-fr.js
@@ -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',
diff --git a/lang-it.js b/lang-it.js
index c86a7cc..44111aa 100644
--- a/lang-it.js
+++ b/lang-it.js
@@ -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',
diff --git a/lang-ja.js b/lang-ja.js
index 56e1f3b..ad80e91 100755
--- a/lang-ja.js
+++ b/lang-ja.js
@@ -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ライブラリをよみこむ',
diff --git a/lang-ko.js b/lang-ko.js
index 955d30b..a1e50eb 100644
--- a/lang-ko.js
+++ b/lang-ko.js
@@ -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 형태로 보여주기',
diff --git a/lang-pt.js b/lang-pt.js
index b964a7c..679a0bf 100755
--- a/lang-pt.js
+++ b/lang-pt.js
@@ -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.',
diff --git a/lang-ru.js b/lang-ru.js
index fc334b6..45a0870 100644
--- a/lang-ru.js
+++ b/lang-ru.js
@@ -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':
'загрузить служебную библиотеку блоков',
diff --git a/lang-si.js b/lang-si.js
index d5fa36f..8985286 100644
--- a/lang-si.js
+++ b/lang-si.js
@@ -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',
diff --git a/lang-zh.js b/lang-zh.js
index b4d75b8..71bff59 100644
--- a/lang-zh.js
+++ b/lang-zh.js
@@ -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':
'载入官方库和强大的程序块',
diff --git a/lists.js b/lists.js
index 7382840..ac85b25 100644
--- a/lists.js
+++ b/lists.js
@@ -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,
diff --git a/morphic.js b/morphic.js
index 0faa43c..590337a 100644
--- a/morphic.js
+++ b/morphic.js
@@ -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
diff --git a/objects.js b/objects.js
index dc47375..1b7027f 100644
--- a/objects.js
+++ b/objects.js
@@ -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() : '',