/* 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('