/*
gui.js
a programming environment
based on morphic.js, blocks.js, threads.js and objects.js
inspired by Scratch
written by Jens Mönig
jens@moenig.org
Copyright (C) 2014 by Jens Mönig
This file is part of Snap!.
Snap! is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of
the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
prerequisites:
--------------
needs blocks.js, threads.js, objects.js and morphic.js
toc
---
the following list shows the order in which all constructors are
defined. Use this list to locate code in this document:
IDE_Morph
credits
-------
Nathan Dinsmore contributed saving and loading of projects,
ypr-Snap! project conversion and countless bugfixes
Ian Reynolds contributed handling and visualization of sounds
*/
/*global modules, Morph, SpriteMorph, Color, newCanvas,
VariableFrame, Point, MenuMorph,
morphicVersion, DialogBoxMorph, ToggleButtonMorph, contains,
StageMorph, PushButtonMorph, Process, SnapSerializer, MorphicPreferences,
SymbolMorph,
BlockImportDialogMorph, SnapTranslator, localize, List */
// Global stuff ////////////////////////////////////////////////////////
modules.gui = '2014-July-30';
// Declarations
var IDE_Morph;
// IDE_Morph ///////////////////////////////////////////////////////////
// I am SNAP's top-level frame, the Editor window
// IDE_Morph inherits from Morph:
IDE_Morph.prototype = new Morph();
IDE_Morph.prototype.constructor = IDE_Morph;
IDE_Morph.uber = Morph.prototype;
// IDE_Morph preferences settings and skins
IDE_Morph.prototype.setDefaultDesign = function () {
MorphicPreferences.isFlat = false;
SpriteMorph.prototype.paletteColor = new Color(55, 55, 55);
SpriteMorph.prototype.paletteTextColor = new Color(230, 230, 230);
StageMorph.prototype.paletteTextColor
= SpriteMorph.prototype.paletteTextColor;
StageMorph.prototype.paletteColor = SpriteMorph.prototype.paletteColor;
SpriteMorph.prototype.sliderColor
= SpriteMorph.prototype.paletteColor.lighter(30);
IDE_Morph.prototype.buttonContrast = 30;
IDE_Morph.prototype.backgroundColor = new Color(40, 40, 40);
IDE_Morph.prototype.frameColor = SpriteMorph.prototype.paletteColor;
IDE_Morph.prototype.groupColor
= SpriteMorph.prototype.paletteColor.lighter(8);
IDE_Morph.prototype.sliderColor = SpriteMorph.prototype.sliderColor;
IDE_Morph.prototype.buttonLabelColor = new Color(255, 255, 255);
IDE_Morph.prototype.tabColors = [
IDE_Morph.prototype.groupColor.darker(40),
IDE_Morph.prototype.groupColor.darker(60),
IDE_Morph.prototype.groupColor
];
IDE_Morph.prototype.rotationStyleColors = IDE_Morph.prototype.tabColors;
IDE_Morph.prototype.appModeColor = new Color();
IDE_Morph.prototype.scriptsPaneTexture = 'scriptsPaneTexture.gif';
IDE_Morph.prototype.padding = 5;
};
IDE_Morph.prototype.setFlatDesign = function () {
MorphicPreferences.isFlat = true;
SpriteMorph.prototype.paletteColor = new Color(255, 255, 255);
SpriteMorph.prototype.paletteTextColor = new Color(70, 70, 70);
StageMorph.prototype.paletteTextColor
= SpriteMorph.prototype.paletteTextColor;
StageMorph.prototype.paletteColor = SpriteMorph.prototype.paletteColor;
SpriteMorph.prototype.sliderColor = SpriteMorph.prototype.paletteColor;
IDE_Morph.prototype.buttonContrast = 30;
IDE_Morph.prototype.backgroundColor = new Color(200, 200, 200);
IDE_Morph.prototype.frameColor = new Color(255, 255, 255);
IDE_Morph.prototype.groupColor = new Color(230, 230, 230);
IDE_Morph.prototype.sliderColor = SpriteMorph.prototype.sliderColor;
IDE_Morph.prototype.buttonLabelColor = new Color(70, 70, 70);
IDE_Morph.prototype.tabColors = [
IDE_Morph.prototype.groupColor.lighter(60),
IDE_Morph.prototype.groupColor.darker(10),
IDE_Morph.prototype.groupColor
];
IDE_Morph.prototype.rotationStyleColors = [
IDE_Morph.prototype.groupColor,
IDE_Morph.prototype.groupColor.darker(10),
IDE_Morph.prototype.groupColor.darker(30)
];
IDE_Morph.prototype.appModeColor = IDE_Morph.prototype.frameColor;
IDE_Morph.prototype.scriptsPaneTexture = null;
IDE_Morph.prototype.padding = 1;
};
IDE_Morph.prototype.setDefaultDesign();
// IDE_Morph instance creation:
function IDE_Morph(isAutoFill) {
this.init(isAutoFill);
}
IDE_Morph.prototype.init = function (isAutoFill) {
// global font setting
MorphicPreferences.globalFontFamily = 'Helvetica, Arial';
// restore saved user preferences
this.userLanguage = null;
// additional properties:
this.source = 'local';
this.serializer = new SnapSerializer();
this.globalVariables = new VariableFrame();
this.currentSprite = new SpriteMorph(this.globalVariables);
this.sprites = new List([this.currentSprite]);
this.currentCategory = 'motion';
this.currentTab = 'scripts';
this.projectName = '';
this.projectNotes = '';
this.logo = null;
this.controlBar = null;
this.stage = null;
this.isAutoFill = isAutoFill || true;
this.filePicker = null;
this.hasChangedMedia = false;
this.isAnimating = true;
this.stageRatio = 1; // for IDE animations, e.g. when zooming
this.loadNewProject = false; // flag when starting up translated
this.shield = null;
// initialize inherited properties:
IDE_Morph.uber.init.call(this);
// override inherited properites:
this.color = this.appModeColor;
};
IDE_Morph.prototype.openIn = function (world) {
var hash;
this.buildPanes();
world.add(this);
this.reactToWorldResize(world.bounds);
function getURL(url) {
try {
var request = new XMLHttpRequest();
request.open('GET', url, false);
request.send();
if (request.status === 200) {
return request.responseText;
}
throw new Error('unable to retrieve ' + url);
} catch (err) {
return;
}
}
function interpretUrlAnchors() {
if (location.hash.substr(0, 6) === '#open:') {
hash = location.hash.substr(6);
if (hash.charAt(0) === '%'
|| hash.search(/\%(?:[0-9a-f]{2})/i) > -1) {
hash = decodeURIComponent(hash);
}
if (contains(
['project', 'blocks', 'sprites', 'snapdata'].map(
function (each) {
return hash.substr(0, 8).indexOf(each);
}
),
1
)) {
this.droppedText(hash);
} else {
this.droppedText(getURL(hash));
}
}
}
if (this.userLanguage) {
this.setLanguage(this.userLanguage, interpretUrlAnchors);
} else {
interpretUrlAnchors.call(this);
}
};
// IDE_Morph construction
IDE_Morph.prototype.buildPanes = function () {
this.createLogo();
this.createControlBar();
this.createStage();
};
IDE_Morph.prototype.createLogo = function () {
var myself = this;
if (this.logo) {
this.logo.destroy();
}
this.logo = new Morph();
this.logo.texture = 'snap_logo_sm.png';
this.logo.drawNew = function () {
this.image = newCanvas(this.extent());
var context = this.image.getContext('2d');
context.fillStyle = this.color;
context.fillRect(0, 0, this.width(), this.height());
if (this.texture) {
this.drawTexture(this.texture);
}
};
this.logo.drawCachedTexture = function () {
var context = this.image.getContext('2d');
context.drawImage(
this.cachedTexture,
5,
Math.round((this.height() - this.cachedTexture.height) / 2)
);
this.changed();
};
this.logo.mouseClickLeft = function () {
myself.snapMenu();
};
this.logo.color = new Color();
this.logo.setExtent(new Point(200, 28)); // dimensions are fixed
this.add(this.logo);
};
IDE_Morph.prototype.createControlBar = function () {
// assumes the logo has already been created
var padding = 5,
button,
stopButton,
pauseButton,
startButton,
x,
colors = [
this.groupColor,
this.frameColor.darker(50),
this.frameColor.darker(50)
],
myself = this;
if (this.controlBar) {
this.controlBar.destroy();
}
this.controlBar = new Morph();
this.controlBar.color = this.appModeColor;
this.controlBar.setHeight(this.logo.height()); // height is fixed
this.controlBar.mouseClickLeft = function () {
this.world().fillPage();
};
this.add(this.controlBar);
// stopButton
button = new PushButtonMorph(
this,
'stopAllScripts',
new SymbolMorph('octagon', 14)
);
button.corner = 12;
button.color = colors[0];
button.highlightColor = colors[1];
button.pressColor = colors[2];
button.labelMinExtent = new Point(36, 18);
button.padding = 0;
button.labelShadowOffset = new Point(-1, -1);
button.labelShadowColor = colors[1];
button.labelColor = new Color(200, 0, 0);
button.contrast = this.buttonContrast;
button.drawNew();
// button.hint = 'stop\nevery-\nthing';
button.fixLayout();
stopButton = button;
this.controlBar.add(stopButton);
//pauseButton
button = new ToggleButtonMorph(
null, //colors,
myself, // the IDE is the target
'togglePauseResume',
[
new SymbolMorph('pause', 12),
new SymbolMorph('pointRight', 14)
],
function () { // query
return myself.isPaused();
}
);
button.corner = 12;
button.color = colors[0];
button.highlightColor = colors[1];
button.pressColor = colors[2];
button.labelMinExtent = new Point(36, 18);
button.padding = 0;
button.labelShadowOffset = new Point(-1, -1);
button.labelShadowColor = colors[1];
button.labelColor = new Color(255, 220, 0);
button.contrast = this.buttonContrast;
button.drawNew();
// button.hint = 'pause/resume\nall scripts';
button.fixLayout();
button.refresh();
pauseButton = button;
this.controlBar.add(pauseButton);
this.controlBar.pauseButton = pauseButton; // for refreshing
// startButton
button = new PushButtonMorph(
this,
'pressStart',
new SymbolMorph('flag', 14)
);
button.corner = 12;
button.color = colors[0];
button.highlightColor = colors[1];
button.pressColor = colors[2];
button.labelMinExtent = new Point(36, 18);
button.padding = 0;
button.labelShadowOffset = new Point(-1, -1);
button.labelShadowColor = colors[1];
button.labelColor = new Color(0, 200, 0);
button.contrast = this.buttonContrast;
button.drawNew();
// button.hint = 'start green\nflag scripts';
button.fixLayout();
startButton = button;
this.controlBar.add(startButton);
this.controlBar.startButton = startButton;
this.controlBar.fixLayout = function () {
x = this.right() - padding;
[stopButton, pauseButton, startButton].forEach(
function (button) {
button.setCenter(myself.controlBar.center());
button.setRight(x);
x -= button.width();
x -= padding;
}
);
x = myself.right() - StageMorph.prototype.dimensions.x;
this.updateLabel();
};
this.controlBar.updateLabel = function () {
if (this.label) {
this.label.destroy();
}
};
};
IDE_Morph.prototype.createStage = function () {
// assumes that the logo pane has already been created
if (this.stage) {
this.stage.destroy();
}
StageMorph.prototype.frameRate = 0;
this.stage = new StageMorph(this.globalVariables);
this.stage.setExtent(this.stage.dimensions); // dimensions are fixed
if (this.currentSprite instanceof SpriteMorph) {
this.currentSprite.setPosition(
this.stage.center().subtract(
this.currentSprite.extent().divideBy(2)
)
);
this.stage.add(this.currentSprite);
}
this.add(this.stage);
};
IDE_Morph.prototype.createCorral = function () {
};
// IDE_Morph resizing
IDE_Morph.prototype.setExtent = function (point) {
var minExt,
ext;
// determine the minimum dimensions making sense for the current mode
minExt = StageMorph.prototype.dimensions.add(
this.controlBar.height() + 10
);
ext = point.max(minExt);
IDE_Morph.uber.setExtent.call(this, ext);
this.fixLayout();
};
// IDE_Morph events
IDE_Morph.prototype.reactToWorldResize = function (rect) {
if (this.isAutoFill) {
this.setPosition(rect.origin);
this.setExtent(rect.extent());
}
if (this.filePicker) {
document.body.removeChild(this.filePicker);
this.filePicker = null;
}
};
// IDE_Morph layout
IDE_Morph.prototype.fixLayout = function () {
var padding = this.padding;
Morph.prototype.trackChanges = false;
// controlBar
this.controlBar.setPosition(this.logo.topRight());
this.controlBar.setWidth(this.right() - this.controlBar.left());
this.controlBar.fixLayout();
// stage
this.stage.setScale(Math.floor(Math.min(
(this.width() - padding * 2) / this.stage.dimensions.x,
(this.height() - this.controlBar.height() * 2 - padding * 2)
/ this.stage.dimensions.y
) * 10) / 10);
this.stage.setCenter(this.center());
Morph.prototype.trackChanges = true;
this.changed();
};
IDE_Morph.prototype.droppedText = function (aString, name) {
var lbl = name ? name.split('.')[0] : '';
if (aString.indexOf('