From c9885ad5b10f34ca14247eb4d0315ae99f6448d3 Mon Sep 17 00:00:00 2001 From: Bernat Romagosa Date: Tue, 11 Apr 2017 17:51:06 +0200 Subject: [PATCH 01/13] webcam dialogs working --- blocks.js | 40 +++++++++++++++++- gui.js | 100 +++++++++++++++++++++++++++++++++++++++++-- objects.js | 121 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 256 insertions(+), 5 deletions(-) diff --git a/blocks.js b/blocks.js index ee497ddf..17378d30 100755 --- a/blocks.js +++ b/blocks.js @@ -9515,7 +9515,8 @@ SymbolMorph.prototype.names = [ 'arrowRight', 'arrowRightOutline', 'robot', - 'magnifiyingGlass' + 'magnifiyingGlass', + 'camera' ]; // SymbolMorph instance creation: @@ -9687,6 +9688,8 @@ SymbolMorph.prototype.symbolCanvasColored = function (aColor) { return this.drawSymbolRobot(canvas, aColor); case 'magnifiyingGlass': return this.drawSymbolMagnifyingGlass(canvas, aColor); + case 'camera': + return this.drawSymbolCamera(canvas, aColor); default: return canvas; } @@ -10865,6 +10868,41 @@ SymbolMorph.prototype.drawSymbolMagnifyingGlass = function (canvas, color) { return canvas; }; +SymbolMorph.prototype.drawSymbolCamera = function (canvas, color) { + // answer a canvas showing a camera + var ctx = canvas.getContext('2d'), + w = canvas.width, + h = canvas.width, + r = w * 0.16, + l = Math.max(w / 20, 0.5); + + ctx.lineWidth = l * 2; + + // camera body + ctx.fillStyle = color.toString(); + ctx.beginPath(); + ctx.moveTo(l, h * 5 / 6); + ctx.lineTo(w - l, h * 5 / 6); + ctx.lineTo(w - l, h / 4); + ctx.lineTo(w * 3 / 4 , h / 4); + ctx.lineTo(w * 5 / 8 , l); + ctx.lineTo(w * 3 / 8 , l); + ctx.lineTo(w / 4 , h / 4); + ctx.lineTo(l , h / 4); + ctx.closePath(); + ctx.fill(); + + // camera lens + ctx.save(); + ctx.globalCompositeOperation = 'destination-out'; + ctx.beginPath(); + ctx.arc(w / 2, h / 2, r, radians(0), radians(360), false); + ctx.fill(); + ctx.restore(); + + return canvas; +}; + // ColorSlotMorph ////////////////////////////////////////////////////// /* diff --git a/gui.js b/gui.js index 11ab5422..a90c3ff2 100644 --- a/gui.js +++ b/gui.js @@ -1416,6 +1416,7 @@ IDE_Morph.prototype.createCorralBar = function () { var padding = 5, newbutton, paintbutton, + cambutton, colors = [ this.groupColor, this.frameColor.darker(50), @@ -1477,6 +1478,31 @@ IDE_Morph.prototype.createCorralBar = function () { this.corralBar.left() + padding + newbutton.width() + padding ); this.corralBar.add(paintbutton); + + cambutton = new PushButtonMorph( + this, + "newCamSprite", + new SymbolMorph("camera", 15) + ); + cambutton.corner = 12; + cambutton.color = colors[0]; + cambutton.highlightColor = colors[1]; + cambutton.pressColor = colors[2]; + cambutton.labelMinExtent = new Point(36, 18); + cambutton.padding = 0; + cambutton.labelShadowOffset = new Point(-1, -1); + cambutton.labelShadowColor = colors[1]; + cambutton.labelColor = this.buttonLabelColor; + cambutton.contrast = this.buttonContrast; + cambutton.drawNew(); + cambutton.hint = "take a camera snapshot and\nimport it as a new sprite"; + cambutton.fixLayout(); + cambutton.setCenter(this.corralBar.center()); + cambutton.setLeft( + this.corralBar.left() + padding + newbutton.width() + padding + paintbutton.width() + padding + ); + this.corralBar.add(cambutton); + }; IDE_Morph.prototype.createCorral = function () { @@ -2137,6 +2163,30 @@ IDE_Morph.prototype.paintNewSprite = function () { ); }; +IDE_Morph.prototype.newCamSprite = function () { + var sprite = new SpriteMorph(this.globalVariables), + camDialog, + myself = this; + + sprite.name = this.newSpriteName(sprite.name); + sprite.setCenter(this.stage.center()); + this.stage.add(sprite); + this.sprites.add(sprite); + this.corral.addSprite(sprite); + this.selectSprite(sprite); + + camDialog = new CamSnapshotDialogMorph( + this, + sprite, + function () { myself.removeSprite(sprite); }, + function (costume) { + sprite.addCostume(costume); + sprite.wearCostume(costume); + }); + + camDialog.popUp(this.world()); +}; + IDE_Morph.prototype.duplicateSprite = function (sprite) { var duplicate = sprite.fullCopy(); @@ -7168,7 +7218,7 @@ CostumeIconMorph.prototype.removeCostume = function () { var wardrobe = this.parentThatIsA(WardrobeMorph), idx = this.parent.children.indexOf(this), ide = this.parentThatIsA(IDE_Morph); - wardrobe.removeCostumeAt(idx - 2); + wardrobe.removeCostumeAt(idx - 3); // ignore the paintbrush and camera buttons if (ide.currentSprite.costume === this.object) { ide.currentSprite.wearCostume(null); } @@ -7429,7 +7479,8 @@ WardrobeMorph.prototype.updateList = function () { icon, template, txt, - paintbutton; + paintbutton, + cambutton; this.changed(); oldFlag = Morph.prototype.trackChanges; @@ -7470,9 +7521,32 @@ WardrobeMorph.prototype.updateList = function () { paintbutton.setCenter(icon.center()); paintbutton.setLeft(icon.right() + padding * 4); - this.addContents(paintbutton); + cambutton = new PushButtonMorph( + this, + "newFromCam", + new SymbolMorph("camera", 15) + ); + cambutton.padding = 0; + cambutton.corner = 12; + cambutton.color = IDE_Morph.prototype.groupColor; + cambutton.highlightColor = IDE_Morph.prototype.frameColor.darker(50); + cambutton.pressColor = paintbutton.highlightColor; + cambutton.labelMinExtent = new Point(36, 18); + cambutton.labelShadowOffset = new Point(-1, -1); + cambutton.labelShadowColor = paintbutton.highlightColor; + cambutton.labelColor = TurtleIconMorph.prototype.labelColor; + cambutton.contrast = this.buttonContrast; + cambutton.drawNew(); + cambutton.hint = "Import a new costume from your webcam"; + cambutton.setPosition(new Point(x, y)); + cambutton.fixLayout(); + cambutton.setCenter(paintbutton.center()); + cambutton.setLeft(paintbutton.right() + padding * 4); + + this.addContents(cambutton); + txt = new TextMorph(localize( "costumes tab help" // look up long string in translator )); @@ -7483,7 +7557,6 @@ WardrobeMorph.prototype.updateList = function () { this.addContents(txt); y = txt.bottom() + padding; - this.sprite.costumes.asArray().forEach(function (costume) { template = icon = new CostumeIconMorph(costume, template); icon.setPosition(new Point(x, y)); @@ -7541,6 +7614,25 @@ WardrobeMorph.prototype.paintNew = function () { }); }; +WardrobeMorph.prototype.newFromCam = function () { + var camDialog, + ide = this.parentThatIsA(IDE_Morph), + myself = this, + sprite = this.sprite; + + camDialog = new CamSnapshotDialogMorph( + ide, + sprite, + nop, + function (costume) { + sprite.addCostume(costume); + sprite.wearCostume(costume); + myself.updateList(); + }); + + camDialog.popUp(this.world()); +}; + // Wardrobe drag & drop WardrobeMorph.prototype.wantsDropOf = function (morph) { diff --git a/objects.js b/objects.js index 5c887d85..4f147b6f 100755 --- a/objects.js +++ b/objects.js @@ -97,6 +97,7 @@ var WatcherMorph; var StagePrompterMorph; var Note; var SpriteHighlightMorph; +var CamSnapshotDialogMorph; function isSnapObject(thing) { return thing instanceof SpriteMorph || (thing instanceof StageMorph); @@ -8661,4 +8662,124 @@ StagePrompterMorph.prototype.mouseClickLeft = function () { StagePrompterMorph.prototype.accept = function () { this.isDone = true; +} + +// CamSnapshotDialogMorph ///////////////////////////////////////////////////////// + +/* + I am a dialog morph that lets users take a snapshot using their webcam + and use it as a costume for their sprites or a background for the Stage. +*/ + +// CamSnapshotDialogMorph inherits from DialogBoxMorph: + + +CamSnapshotDialogMorph.prototype = new DialogBoxMorph(); +CamSnapshotDialogMorph.prototype.constructor = CamSnapshotDialogMorph; +CamSnapshotDialogMorph.uber = DialogBoxMorph.prototype; + +// CamSnapshotDialogMorph instance creation + +function CamSnapshotDialogMorph(ide, sprite, onCancel, onAccept) { + this.init(ide, sprite, onCancel, onAccept); +}; + +CamSnapshotDialogMorph.prototype.init = function (ide, sprite, onCancel, onAccept) { + this.ide = ide; + this.sprite = sprite; + this.padding = 10; + + this.oncancel = onCancel; + this.accept = onAccept; + + this.videoElement = null; // an HTML5 video element + this.videoView = new Morph(); // a morph where we'll copy the video contents + + CamSnapshotDialogMorph.uber.init.call(this); + + this.labelString = 'Camera'; + this.createLabel(); + + this.buildContents(); +}; + +CamSnapshotDialogMorph.prototype.buildContents = function () { + var myself = this, + stage = this.ide.stage; + + this.videoElement = document.createElement('video'); + this.videoElement.hidden = true; + document.body.appendChild(this.videoElement); + + if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { + navigator.mediaDevices.getUserMedia({ video: true }) + .then(function (stream) { + myself.videoElement.src = window.URL.createObjectURL(stream); + myself.videoElement.play(); + }); + } + + this.videoView.setExtent(this.ide.stage.dimensions); + this.videoView.image = newCanvas(this.videoView.extent()); + this.videoView.drawOn = function (aCanvas) { + var context = aCanvas.getContext('2d'), + w = this.width(), + h = this.height(); + + context.save(); + + // Flip the image so it looks like a mirror + context.translate(w, 0); + context.scale(-1, 1); + + context.drawImage( + myself.videoElement, + this.left() * -1, + this.top(), + w, + h + ); + + context.restore(); + }; + + this.videoView.step = function () { + this.changed(); + }; + + this.addBody(new AlignmentMorph('column', this.padding / 2)); + this.body.add(this.videoView); + this.body.fixLayout(); + + this.addButton('ok', 'Save'); + this.addButton('cancel', 'Cancel'); + + this.fixLayout(); + this.drawNew(); +}; + +CamSnapshotDialogMorph.prototype.ok = function () { + var stage = this.ide.stage, + canvas = newCanvas(stage.dimensions); + + context = canvas.getContext('2d'); + context.translate(stage.dimensions.x, 0); + context.scale(-1, 1); + + context.drawImage( + this.videoElement, + 0, + 0, + stage.dimensions.x, + stage.dimensions.y + ); + + this.accept(new Costume(canvas), this.sprite.newCostumeName('camera')); + CamSnapshotDialogMorph.uber.destroy.call(this); +}; + +CamSnapshotDialogMorph.prototype.destroy = function () { + this.videoElement.remove(); + this.oncancel.call(this); + CamSnapshotDialogMorph.uber.destroy.call(this); }; From 075d9c2d3a62fa1b5290cff838ad1583c8f81cdc Mon Sep 17 00:00:00 2001 From: Bernat Romagosa Date: Wed, 12 Apr 2017 10:31:44 +0200 Subject: [PATCH 02/13] Added guard against non-SSL access --- objects.js | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/objects.js b/objects.js index 4f147b6f..b5ccb715 100755 --- a/objects.js +++ b/objects.js @@ -8758,6 +8758,20 @@ CamSnapshotDialogMorph.prototype.buildContents = function () { this.drawNew(); }; +CamSnapshotDialogMorph.prototype.popUp = function (world) { + CamSnapshotDialogMorph.uber.popUp.call(this, world); + + if (location.protocol === 'http:') { + this.ide.inform( + 'SSL Error', + 'Due to browser security policies, you need to\n' + + 'access Snap! through HTTPS to use the camera.\n\n' + + 'Plase replace the "http://" part of the address\n' + + 'in your browser by "https://" and try again.'); + this.destroy(); + } +}; + CamSnapshotDialogMorph.prototype.ok = function () { var stage = this.ide.stage, canvas = newCanvas(stage.dimensions); @@ -8775,11 +8789,17 @@ CamSnapshotDialogMorph.prototype.ok = function () { ); this.accept(new Costume(canvas), this.sprite.newCostumeName('camera')); - CamSnapshotDialogMorph.uber.destroy.call(this); + this.close(); }; CamSnapshotDialogMorph.prototype.destroy = function () { - this.videoElement.remove(); this.oncancel.call(this); + this.close(); +}; + +CamSnapshotDialogMorph.prototype.close = function () { + if (this.videoElement) { + this.videoElement.remove(); + } CamSnapshotDialogMorph.uber.destroy.call(this); }; From a213a1d213cc7d68bad1e9868086c72dd9964c91 Mon Sep 17 00:00:00 2001 From: Bernat Romagosa Date: Mon, 12 Jun 2017 12:36:26 +0200 Subject: [PATCH 03/13] stop video stream when webcam dialog is closed --- objects.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/objects.js b/objects.js index b5ccb715..f5baeed0 100755 --- a/objects.js +++ b/objects.js @@ -8716,6 +8716,7 @@ CamSnapshotDialogMorph.prototype.buildContents = function () { .then(function (stream) { myself.videoElement.src = window.URL.createObjectURL(stream); myself.videoElement.play(); + myself.videoElement.stream = stream; }); } @@ -8799,6 +8800,7 @@ CamSnapshotDialogMorph.prototype.destroy = function () { CamSnapshotDialogMorph.prototype.close = function () { if (this.videoElement) { + this.videoElement.stream.getTracks()[0].stop(); this.videoElement.remove(); } CamSnapshotDialogMorph.uber.destroy.call(this); From bdc6c9e1c408b7b320659f40121d7b82377340a9 Mon Sep 17 00:00:00 2001 From: Michael Ball Date: Wed, 28 Jun 2017 00:23:52 -0700 Subject: [PATCH 04/13] Fix some file naming bugs The additional text wasnt correctly being appened to project name. Also, this makes "untitled" lowercase, consistent with other usage. --- blocks.js | 8 ++++---- byob.js | 4 ++-- gui.js | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/blocks.js b/blocks.js index ee497ddf..dcb113b4 100755 --- a/blocks.js +++ b/blocks.js @@ -1985,7 +1985,7 @@ SyntaxElementMorph.prototype.exportPictureWithResult = function (aBubble) { // request to open pic in new window. ide.saveCanvasAs( pic, - ide.projetName || localize('Untitled') + ' ' + localize('script pic'), + (ide.projetName || localize('untitled')) + ' ' + localize('script pic'), true ); }; @@ -2520,7 +2520,7 @@ BlockMorph.prototype.userMenu = function () { ); ide.saveCanvasAs( myself.topBlock().scriptPic(), - ide.projetName || localize('Untitled') + ' ' + + (ide.projetName || localize('untitled')) + ' ' + localize('script pic'), true // request new window ); @@ -6118,7 +6118,7 @@ ScriptsMorph.prototype.exportScriptsPicture = function () { if (pic) { ide.saveCanvasAs( pic, - ide.projetName || localize('Untitled') + ' ' + + (ide.projetName || localize('untitled')) + ' ' + localize('script pic'), true // request new window ); @@ -12633,7 +12633,7 @@ CommentMorph.prototype.userMenu = function () { var ide = myself.parentThatIsA(IDE_Morph); ide.saveCanvasAs( myself.fullImageClassic(), - ide.projetName || localize('Untitled') + ' ' + + (ide.projetName || localize('untitled')) + ' ' + localize('comment pic'), true // request new window ); diff --git a/byob.js b/byob.js index 8652e53e..a850e1e8 100755 --- a/byob.js +++ b/byob.js @@ -870,7 +870,7 @@ CustomCommandBlockMorph.prototype.userMenu = function () { var ide = this.world().children[0]; ide.saveCanvasAs( this.topBlock().scriptPic(), - ide.projectName || localize('Untitled') + ' ' + + (ide.projectName || localize('untitled')) + ' ' + localize('script pic'), true // request opening a new window ); @@ -3540,7 +3540,7 @@ BlockExportDialogMorph.prototype.exportBlocks = function () { + ''; ide.saveXMLAs( str, - ide.projectName || localize('Untitled') + ' ' + localize('blocks') + (ide.projectName || localize('untitled')) + ' ' + localize('blocks') ); } else { new DialogBoxMorph().inform( diff --git a/gui.js b/gui.js index 4c5ab9d2..3d0b27c3 100644 --- a/gui.js +++ b/gui.js @@ -3612,7 +3612,7 @@ IDE_Morph.prototype.exportScriptsPicture = function () { y += padding; y += each.height; }); - this.saveCanvasAs(pic, this.projectName || localize('Untitled'), true); + this.saveCanvasAs(pic, this.projectName || localize('untitled'), true); }; IDE_Morph.prototype.exportProjectSummary = function (useDropShadows) { From edb384dd23104ebe6228c6c08ce4b0790b3bd581 Mon Sep 17 00:00:00 2001 From: Bernat Romagosa Date: Mon, 4 Sep 2017 14:01:24 +0200 Subject: [PATCH 05/13] Disable cam button if not using HTTPS --- gui.js | 18 ++++++++++++++++++ objects.js | 14 -------------- widgets.js | 25 ++++++++++++++++++++++--- 3 files changed, 40 insertions(+), 17 deletions(-) diff --git a/gui.js b/gui.js index 5ad06cc9..51b9347a 100644 --- a/gui.js +++ b/gui.js @@ -1487,6 +1487,7 @@ IDE_Morph.prototype.createCorralBar = function () { "newCamSprite", new SymbolMorph("camera", 15) ); + cambutton.corner = 12; cambutton.color = colors[0]; cambutton.highlightColor = colors[1]; @@ -1504,6 +1505,15 @@ IDE_Morph.prototype.createCorralBar = function () { cambutton.setLeft( this.corralBar.left() + padding + newbutton.width() + padding + paintbutton.width() + padding ); + + if (location.protocol === 'http:') { + cambutton.hint = 'Due to browser security policies, you need to\n' + + 'access Snap! through HTTPS to use the camera.\n\n' + + 'Plase replace the "http://" part of the address\n' + + 'in your browser by "https://" and try again.'; + cambutton.disable(); + } + this.corralBar.add(cambutton); }; @@ -7616,6 +7626,14 @@ WardrobeMorph.prototype.updateList = function () { cambutton.setCenter(paintbutton.center()); cambutton.setLeft(paintbutton.right() + padding * 4); + if (location.protocol === 'http:') { + cambutton.hint = 'Due to browser security policies, you need to\n' + + 'access Snap! through HTTPS to use the camera.\n\n' + + 'Plase replace the "http://" part of the address\n' + + 'in your browser by "https://" and try again.'; + cambutton.disable(); + } + this.addContents(cambutton); txt = new TextMorph(localize( diff --git a/objects.js b/objects.js index e74254c7..3b31e35a 100644 --- a/objects.js +++ b/objects.js @@ -9622,20 +9622,6 @@ CamSnapshotDialogMorph.prototype.buildContents = function () { this.drawNew(); }; -CamSnapshotDialogMorph.prototype.popUp = function (world) { - CamSnapshotDialogMorph.uber.popUp.call(this, world); - - if (location.protocol === 'http:') { - this.ide.inform( - 'SSL Error', - 'Due to browser security policies, you need to\n' + - 'access Snap! through HTTPS to use the camera.\n\n' + - 'Plase replace the "http://" part of the address\n' + - 'in your browser by "https://" and try again.'); - this.destroy(); - } -}; - CamSnapshotDialogMorph.prototype.ok = function () { var stage = this.ide.stage, canvas = newCanvas(stage.dimensions); diff --git a/widgets.js b/widgets.js index 1eb784dd..e04e4e62 100644 --- a/widgets.js +++ b/widgets.js @@ -168,6 +168,7 @@ PushButtonMorph.prototype.init = function ( this.hint = hint || null; this.template = template || null; // for pre-computed backbrounds // if a template is specified, its background images are used as cache + this.enabled = true; // initialize inherited properties: TriggerMorph.uber.init.call(this); @@ -204,9 +205,11 @@ PushButtonMorph.prototype.mouseDownLeft = function () { }; PushButtonMorph.prototype.mouseClickLeft = function () { - PushButtonMorph.uber.mouseClickLeft.call(this); - if (this.label) { - this.label.setCenter(this.center()); + if (this.enabled) { + PushButtonMorph.uber.mouseClickLeft.call(this); + if (this.label) { + this.label.setCenter(this.center()); + } } }; @@ -479,6 +482,22 @@ PushButtonMorph.prototype.createLabel = function () { this.add(this.label); }; +// PushButtonMorph states + +PushButtonMorph.prototype.disable = function () { + this.enabled = false; + this.forAllChildren(function (child) { + child.alpha = 0.3; + }); +}; + +PushButtonMorph.prototype.enable = function () { + this.enabled = true; + this.forAllChildren(function (child) { + child.alpha = 1; + }); +}; + // ToggleButtonMorph /////////////////////////////////////////////////////// /* From 8f44dbcae704d369a77eff178430520128e13c53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20M=C3=B6nig?= Date: Mon, 4 Sep 2017 16:27:48 +0200 Subject: [PATCH 06/13] fixed #1339 --- blocks.js | 6 +++--- history.txt | 5 +++++ objects.js | 3 ++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/blocks.js b/blocks.js index 944f3bc0..9f09928b 100644 --- a/blocks.js +++ b/blocks.js @@ -148,7 +148,7 @@ CustomCommandBlockMorph, SymbolMorph*/ // Global stuff //////////////////////////////////////////////////////// -modules.blocks = '2017-September-01'; +modules.blocks = '2017-September-04'; var SyntaxElementMorph; var BlockMorph; @@ -8196,7 +8196,7 @@ InputSlotMorph.prototype.distancesMenu = function () { allNames = []; stage.children.forEach(function (morph) { - if (morph instanceof SpriteMorph) { + if (morph instanceof SpriteMorph && !morph.isTemporary) { if (morph.name !== rcvr.name) { allNames = allNames.concat(morph.name); } @@ -8242,7 +8242,7 @@ InputSlotMorph.prototype.objectsMenu = function () { dict[stage.name] = stage.name; stage.children.forEach(function (morph) { - if (morph instanceof SpriteMorph) { + if (morph instanceof SpriteMorph && !morph.isTemporary) { allNames.push(morph.name); } }); diff --git a/history.txt b/history.txt index 71f34906..eb9157ab 100755 --- a/history.txt +++ b/history.txt @@ -3601,6 +3601,10 @@ Fixes: * Morphic, Objects: fixed #1843 * Croation translation update, thanks, Zeljko Hrvoj! +170904 +------ +* Blocks, Objects: fixed #1339 + v4.1 Features: * polymorphic sprite-local custom blocks @@ -3635,4 +3639,5 @@ Fixes: * fixed rotation-bug when flipping costumes in "only turn left/right" mode" * fixed variable renaming (“refactoring”) bugs, thanks, Bernat! * fixed “fill” block crash when applying the same color twice +* fixed occasional empty drop-down menu items named “close” * fixed some typos diff --git a/objects.js b/objects.js index 4d00b389..a676a6dd 100644 --- a/objects.js +++ b/objects.js @@ -82,7 +82,7 @@ SpeechBubbleMorph, RingMorph, isNil, FileReader, TableDialogMorph, BlockEditorMorph, BlockDialogMorph, PrototypeHatBlockMorph, localize, TableMorph, TableFrameMorph, normalizeCanvas, BooleanSlotMorph, HandleMorph*/ -modules.objects = '2017-September-01'; +modules.objects = '2017-September-04'; var SpriteMorph; var StageMorph; @@ -5291,6 +5291,7 @@ SpriteMorph.prototype.chooseExemplar = function () { myself = this, other = stage.children.filter(function (m) { return m instanceof SpriteMorph && + !m.isTemporary && (!contains(m.allExemplars(), myself)); }), menu; From 01c22843f922b48a958f104632c35e354b1d6b5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20M=C3=B6nig?= Date: Tue, 5 Sep 2017 08:34:46 +0200 Subject: [PATCH 07/13] German translation update --- blocks.js | 4 +-- gui.js | 4 +-- history.txt | 4 +++ lang-de.js | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++--- locale.js | 4 +-- objects.js | 2 +- 6 files changed, 85 insertions(+), 11 deletions(-) diff --git a/blocks.js b/blocks.js index 9f09928b..8e5f1250 100644 --- a/blocks.js +++ b/blocks.js @@ -148,7 +148,7 @@ CustomCommandBlockMorph, SymbolMorph*/ // Global stuff //////////////////////////////////////////////////////// -modules.blocks = '2017-September-04'; +modules.blocks = '2017-September-05'; var SyntaxElementMorph; var BlockMorph; @@ -6232,7 +6232,7 @@ ScriptsMorph.prototype.userMenu = function () { ); if (ide) { menu.addLine(); - if (obj.exemplar) { + if (!blockEditor && obj.exemplar) { addOption( 'inherited', function () { diff --git a/gui.js b/gui.js index d2ebc972..63eafe39 100644 --- a/gui.js +++ b/gui.js @@ -74,7 +74,7 @@ isRetinaSupported, SliderMorph, Animation*/ // Global stuff //////////////////////////////////////////////////////// -modules.gui = '2017-September-01'; +modules.gui = '2017-September-05'; // Declarations @@ -6656,7 +6656,7 @@ SpriteIconMorph.prototype.init = function (aSprite, aTemplate) { hover = function () { if (!aSprite.exemplar) {return null; } - return (localize('parent' + ':\n' + aSprite.exemplar.name)); + return (localize('parent') + ':\n' + aSprite.exemplar.name); }; // additional properties: diff --git a/history.txt b/history.txt index eb9157ab..3a6f7151 100755 --- a/history.txt +++ b/history.txt @@ -3605,6 +3605,10 @@ Fixes: ------ * Blocks, Objects: fixed #1339 +170905 +------ +* German translation update + v4.1 Features: * polymorphic sprite-local custom blocks diff --git a/lang-de.js b/lang-de.js index 00286bfd..99bbeb46 100644 --- a/lang-de.js +++ b/lang-de.js @@ -185,7 +185,7 @@ SnapTranslator.dict.de = { 'translator_e-mail': 'jens@moenig.org', // optional 'last_changed': - '2017-06-24', // this, too, will appear in the Translators tab + '2017-09-05', // this, too, will appear in the Translators tab // GUI // control bar: @@ -381,8 +381,10 @@ SnapTranslator.dict.de = { 'stoppe alle Kl\u00e4nge', 'rest for %n beats': 'spiele Pause f\u00fcr %n Schl\u00e4ge', - 'play note %n for %n beats': - 'spiele Note %n f\u00fcr %n Schl\u00e4ge', + 'play note %note for %n beats': + 'spiele Note %note f\u00fcr %n Schl\u00e4ge', + 'set instrument to %inst': + 'setze Instrument auf %inst', 'change tempo by %n': '\u00e4ndere Tempo um %n', 'set tempo to %n bpm': @@ -390,6 +392,16 @@ SnapTranslator.dict.de = { 'tempo': 'Tempo', + // "instruments", i.e. wave forms + '(1) sine': + '(1) Sinus', + '(2) square': + '(2) Quadrat', + '(3) sawtooth': + '(3) Sägeblatt', + '(4) triangle': + '(4) Dreieck', + // pen: 'clear': 'wische', @@ -495,10 +507,16 @@ SnapTranslator.dict.de = { 'Wenn ich geklont werde', 'create a clone of %cln': 'klone %cln', + 'a new clone of %cln': + 'neuer Klon von %cln', 'myself': - 'mich', + 'selbst', 'delete this clone': 'entferne diesen Klon', + 'tell %spr to %cl': + 'lasse %spr tun %cl', + 'ask %spr for %repRing': + 'frage %spr nach %repRing', // sensing: 'touching %col ?': @@ -537,6 +555,24 @@ SnapTranslator.dict.de = { 'Turbomodus?', 'set turbo mode to %b': 'setze Turbomodus auf %b', + 'current %dates': + 'Kalender %dates', + 'year': + 'Jahr', + 'month': + 'Monat', + 'date': + 'Datum', + 'day of week': + 'Wochentag', + 'hour': + 'Stunde', + 'minute': + 'Minute', + 'second': + 'Sekunde', + 'time in milliseconds': + 'Zeit in Millisekunden', 'filtered for %clr': 'nach %clr gefiltert', @@ -584,6 +620,8 @@ SnapTranslator.dict.de = { 'ist %s ein(e) %typ ?', 'is %s identical to %s ?': 'ist %s identisch mit %s ?', + 'JavaScript function ( %mult%s ) { %code }': + 'JavaScript Funktion ( %mult%s ) { %code }', 'type of %s': 'Typ von %s', @@ -595,6 +633,8 @@ SnapTranslator.dict.de = { 'Variablenname', 'Script variable name': 'Skriptvariablenname', + 'inherit %shd': + 'erbe %shd', 'Delete a variable': 'Variable l\u00f6schen', @@ -797,6 +837,10 @@ SnapTranslator.dict.de = { 'ausschalten um Schieber\nin Eingabefeldern zu verhindern', 'check to enable\ninput sliders for\nentry fields': 'einschalten um Schieber\nin Eingabefeldern zu aktivieren', + 'Retina display support': + 'Retina Bildschirmauflösung', + 'Codification support': + 'Kodifikation', 'Clicking sound': 'Akustisches Klicken', 'uncheck to turn\nblock clicking\nsound off': @@ -909,6 +953,8 @@ SnapTranslator.dict.de = { // sprites: 'edit': 'Bearbeiten', + 'clone': + 'Klonen', 'move': 'Verschieben', 'pivot': @@ -921,6 +967,14 @@ SnapTranslator.dict.de = { 'Alle Teile abtrennen', 'export...': 'Exportieren...', + 'parent...': + 'Vorfahr...', + 'current parent': + 'aktueller Vorfahr', + 'release': + 'Entlassen', + 'make temporary and\nhide in the sprite corral': + 'temporär machen\nund Icon verstecken', // stage: 'show all': @@ -1099,6 +1153,8 @@ SnapTranslator.dict.de = { // block editor 'Block Editor': 'Blockeditor', + 'Method': + 'Methode', 'Apply': 'Anwenden', @@ -1437,4 +1493,18 @@ SnapTranslator.dict.de = { 'Name', 'stage': 'B\u00fchne', + 'costumes': + 'Kostüme', + 'sounds': + 'Klänge', + 'scripts': + 'Skripte', + + // inheritance + 'inherited': + 'geerbt', + 'check to inherit\nfrom': + 'einschalten, um zu erben\nvon', + 'uncheck to\ndisinherit': + 'ausschalten, um \nnicht mehr zu erben' }; diff --git a/locale.js b/locale.js index 71e9f883..7f82e061 100644 --- a/locale.js +++ b/locale.js @@ -42,7 +42,7 @@ /*global modules, contains*/ -modules.locale = '2017-September-01'; +modules.locale = '2017-September-05'; // Global stuff @@ -160,7 +160,7 @@ SnapTranslator.dict.de = { 'translator_e-mail': 'jens@moenig.org', 'last_changed': - '2017-06-24' + '2017-09-05' }; SnapTranslator.dict.it = { diff --git a/objects.js b/objects.js index a676a6dd..1658f258 100644 --- a/objects.js +++ b/objects.js @@ -82,7 +82,7 @@ SpeechBubbleMorph, RingMorph, isNil, FileReader, TableDialogMorph, BlockEditorMorph, BlockDialogMorph, PrototypeHatBlockMorph, localize, TableMorph, TableFrameMorph, normalizeCanvas, BooleanSlotMorph, HandleMorph*/ -modules.objects = '2017-September-04'; +modules.objects = '2017-September-05'; var SpriteMorph; var StageMorph; From 37dbcad27f03fe9edd8c43875305c3d2c29b59f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20M=C3=B6nig?= Date: Tue, 5 Sep 2017 11:24:57 +0200 Subject: [PATCH 08/13] =?UTF-8?q?Renamed=20=E2=80=9Chttp=E2=80=9D=20block?= =?UTF-8?q?=20to=20=E2=80=9Curl=E2=80=9D,=20use=20location.protocol=20(sup?= =?UTF-8?q?port=20https)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- history.txt | 2 ++ objects.js | 2 +- threads.js | 15 ++++++++++++--- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/history.txt b/history.txt index 3a6f7151..32a4a550 100755 --- a/history.txt +++ b/history.txt @@ -3608,6 +3608,7 @@ Fixes: 170905 ------ * German translation update +* Threads, Objects: Renamed “http” block to “url”, use location.protocol (support https) v4.1 Features: @@ -3633,6 +3634,7 @@ v4.1 Features: * two-item lists as x-y coordinate arguments for "point towards”, "go to" and “distance to” primitives * Piano keyboard as drop-down menu for entering musical notes, Thanks, Michael! * Basic “instruments” support: sine, square, sawtooth and triangle waves +* Support https in “url” reporter Fixes: * changed keyboard shortcut indicator for “find blocks” to “^” diff --git a/objects.js b/objects.js index 1658f258..3234a9ee 100644 --- a/objects.js +++ b/objects.js @@ -899,7 +899,7 @@ SpriteMorph.prototype.initBlocks = function () { reportURL: { type: 'reporter', category: 'sensing', - spec: 'http:// %s', + spec: 'url %s', defaults: ['snap.berkeley.edu'] }, reportIsFastTracking: { diff --git a/threads.js b/threads.js index 14a32229..1929a4fd 100644 --- a/threads.js +++ b/threads.js @@ -61,7 +61,7 @@ StageMorph, SpriteMorph, StagePrompterMorph, Note, modules, isString, copy, isNil, WatcherMorph, List, ListWatcherMorph, alert, console, TableMorph, TableFrameMorph, ColorSlotMorph, isSnapObject*/ -modules.threads = '2017-September-01'; +modules.threads = '2017-September-05'; var ThreadManager; var Process; @@ -2265,10 +2265,19 @@ Process.prototype.reportLastAnswer = function () { // Process URI retrieval (interpolated) Process.prototype.reportURL = function (url) { - var response; + var idx, protocol, hostname, response; if (!this.httpRequest) { + // use the location protocol unless the user specifies otherwise + idx = url.indexOf('://'); + if (idx < 0) { + protocol = location.protocol + '//'; + hostname = url; + } else { + protocol = url.slice(0, idx) + '://'; + hostname = url.slice(idx + 3, url.length); + } this.httpRequest = new XMLHttpRequest(); - this.httpRequest.open("GET", 'http://' + url, true); + this.httpRequest.open("GET", protocol + hostname, true); this.httpRequest.send(null); } else if (this.httpRequest.readyState === 4) { response = this.httpRequest.responseText; From 9f17f34e7c778612b5d7d7ff4cb37a325911057a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20M=C3=B6nig?= Date: Wed, 6 Sep 2017 07:26:28 +0200 Subject: [PATCH 09/13] =?UTF-8?q?added=20=E2=80=9Ccsv=E2=80=9D=20option=20?= =?UTF-8?q?to=20the=20SPLIT=20primitive?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- blocks.js | 5 +++-- history.txt | 5 +++++ threads.js | 40 +++++++++++++++++++++++++++++++++++++++- 3 files changed, 47 insertions(+), 3 deletions(-) diff --git a/blocks.js b/blocks.js index 8e5f1250..b1cc83e5 100644 --- a/blocks.js +++ b/blocks.js @@ -148,7 +148,7 @@ CustomCommandBlockMorph, SymbolMorph*/ // Global stuff //////////////////////////////////////////////////////// -modules.blocks = '2017-September-05'; +modules.blocks = '2017-September-06'; var SyntaxElementMorph; var BlockMorph; @@ -1025,7 +1025,8 @@ SyntaxElementMorph.prototype.labelPart = function (spec) { 'whitespace' : ['whitespace'], 'line' : ['line'], 'tab' : ['tab'], - 'cr' : ['cr'] + 'cr' : ['cr'], + 'csv' : ['csv'] }, false // read-only ); diff --git a/history.txt b/history.txt index 32a4a550..c7083f7e 100755 --- a/history.txt +++ b/history.txt @@ -3610,6 +3610,10 @@ Fixes: * German translation update * Threads, Objects: Renamed “http” block to “url”, use location.protocol (support https) +170906 +------ +* Blocks, Threads: added “csv” option to the SPLIT primitive + v4.1 Features: * polymorphic sprite-local custom blocks @@ -3635,6 +3639,7 @@ v4.1 Features: * Piano keyboard as drop-down menu for entering musical notes, Thanks, Michael! * Basic “instruments” support: sine, square, sawtooth and triangle waves * Support https in “url” reporter +* splitting csv-text Fixes: * changed keyboard shortcut indicator for “find blocks” to “^” diff --git a/threads.js b/threads.js index 1929a4fd..3aa56fd1 100644 --- a/threads.js +++ b/threads.js @@ -61,7 +61,7 @@ StageMorph, SpriteMorph, StagePrompterMorph, Note, modules, isString, copy, isNil, WatcherMorph, List, ListWatcherMorph, alert, console, TableMorph, TableFrameMorph, ColorSlotMorph, isSnapObject*/ -modules.threads = '2017-September-05'; +modules.threads = '2017-September-06'; var ThreadManager; var Process; @@ -2727,12 +2727,50 @@ Process.prototype.reportTextSplit = function (string, delimiter) { case 'letter': del = ''; break; + case 'csv': + return this.parseCSV(string); default: del = isNil(delimiter) ? '' : delimiter.toString(); } return new List(str.split(del)); }; +Process.prototype.parseCSV = function (string) { + // parse a single row of CSV data into a one-dimensional list + // this assumes that the whole csv data has already been split + // by lines. + // taken from: + // https://stackoverflow.com/questions/8493195/how-can-i-parse-a-csv-string-with-javascript-which-contains-comma-in-data + + var re_valid = /^\s*(?:'[^'\\]*(?:\\[\S\s][^'\\]*)*'|"[^"\\]*(?:\\[\S\s][^"\\]*)*"|[^,'"\s\\]*(?:\s+[^,'"\s\\]+)*)\s*(?:,\s*(?:'[^'\\]*(?:\\[\S\s][^'\\]*)*'|"[^"\\]*(?:\\[\S\s][^"\\]*)*"|[^,'"\s\\]*(?:\s+[^,'"\s\\]+)*)\s*)*$/, + re_value = /(?!\s*$)\s*(?:'([^'\\]*(?:\\[\S\s][^'\\]*)*)'|"([^"\\]*(?:\\[\S\s][^"\\]*)*)"|([^,'"\s\\]*(?:\s+[^,'"\s\\]+)*))\s*(?:,|$)/g, + a = []; + + if (!re_valid.test(string)) { + return new List(); + } + string.replace( + re_value, + function(m0, m1, m2, m3) { + if (m1 !== undefined) { + // remove backslash from \' in single quoted values. + a.push(m1.replace(/\\'/g, "'")); + } else if (m2 !== undefined) { + // remove backslash from \" in double quoted values. + a.push(m2.replace(/\\"/g, '"')); + } else if (m3 !== undefined) { + a.push(m3); + } + return ''; + } + ); + // special case: empty last value. + if (/,\s*$/.test(string)) { + a.push(''); + } + return new List(a); +}; + // Process debugging Process.prototype.alert = function (data) { From 4b169000b5fd07bf90606863137e7bebc1e5c9f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20M=C3=B6nig?= Date: Wed, 6 Sep 2017 07:55:30 +0200 Subject: [PATCH 10/13] allow https query from locally loaded sources thanks, Michael, for the hint! --- history.txt | 1 + threads.js | 18 +++++++++--------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/history.txt b/history.txt index c7083f7e..8da84367 100755 --- a/history.txt +++ b/history.txt @@ -3613,6 +3613,7 @@ Fixes: 170906 ------ * Blocks, Threads: added “csv” option to the SPLIT primitive +* Threads: allow https query from locally loaded sources (thanks, Michael, for the hint!) v4.1 Features: diff --git a/threads.js b/threads.js index 3aa56fd1..f9dd896b 100644 --- a/threads.js +++ b/threads.js @@ -2265,19 +2265,19 @@ Process.prototype.reportLastAnswer = function () { // Process URI retrieval (interpolated) Process.prototype.reportURL = function (url) { - var idx, protocol, hostname, response; + var response; if (!this.httpRequest) { // use the location protocol unless the user specifies otherwise - idx = url.indexOf('://'); - if (idx < 0) { - protocol = location.protocol + '//'; - hostname = url; - } else { - protocol = url.slice(0, idx) + '://'; - hostname = url.slice(idx + 3, url.length); + if (url.indexOf('//') < 0) { + if (location.protocol === 'file:') { + // allow requests from locally loaded sources + url = 'https://' + url; + } else { + url = location.protocol + '//' + url; + } } this.httpRequest = new XMLHttpRequest(); - this.httpRequest.open("GET", protocol + hostname, true); + this.httpRequest.open("GET", url, true); this.httpRequest.send(null); } else if (this.httpRequest.readyState === 4) { response = this.httpRequest.responseText; From 47264f10b03f3d7f2a393902f7551a65e5e81222 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20M=C3=B6nig?= Date: Fri, 8 Sep 2017 19:03:28 +0200 Subject: [PATCH 11/13] little tweaks to camera snapshot code mostly to satisfy JSHint ;-) --- gui.js | 11 ++++++++--- history.txt | 5 +++++ objects.js | 16 ++++++++-------- symbols.js | 2 +- widgets.js | 2 +- 5 files changed, 23 insertions(+), 13 deletions(-) diff --git a/gui.js b/gui.js index d6a4a375..a074d83f 100644 --- a/gui.js +++ b/gui.js @@ -70,11 +70,11 @@ fontHeight, hex_sha512, sb, CommentMorph, CommandBlockMorph, BlockLabelPlaceHolderMorph, Audio, SpeechBubbleMorph, ScriptFocusMorph, XML_Element, WatcherMorph, BlockRemovalDialogMorph, saveAs, TableMorph, isSnapObject, isRetinaEnabled, disableRetinaSupport, enableRetinaSupport, -isRetinaSupported, SliderMorph, Animation*/ +isRetinaSupported, SliderMorph, Animation, CamSnapshotDialogMorph*/ // Global stuff //////////////////////////////////////////////////////// -modules.gui = '2017-September-05'; +modules.gui = '2017-September-08'; // Declarations @@ -1503,7 +1503,12 @@ IDE_Morph.prototype.createCorralBar = function () { cambutton.fixLayout(); cambutton.setCenter(this.corralBar.center()); cambutton.setLeft( - this.corralBar.left() + padding + newbutton.width() + padding + paintbutton.width() + padding + this.corralBar.left() + + padding + + newbutton.width() + + padding + + paintbutton.width() + + padding ); if (location.protocol === 'http:') { diff --git a/history.txt b/history.txt index 8da84367..d31da3f3 100755 --- a/history.txt +++ b/history.txt @@ -3615,12 +3615,17 @@ Fixes: * Blocks, Threads: added “csv” option to the SPLIT primitive * Threads: allow https query from locally loaded sources (thanks, Michael, for the hint!) +170908 +------ +* GUI, Objects, Widgets, Symbols: Camera Snapshot Dialog. Thank you, Bernat!! + v4.1 Features: * polymorphic sprite-local custom blocks * inheritance of sprite-local custom blocks * inheritance of sprite attributes (x, y, direction, size, costumes, costume #, sounds, scripts) * first-class costumes and sounds +* camera snapshots for costumes and new sprites * localization support when typing expressions * support for user-forced line-breaks in custom block labels * ternary Boolean slot setting: support to limit Boolean input slots to “true/false” outside of rings and in palette diff --git a/objects.js b/objects.js index 7858a2c0..e89af7e0 100644 --- a/objects.js +++ b/objects.js @@ -80,9 +80,10 @@ document, isNaN, isString, newCanvas, nop, parseFloat, radians, window, modules, IDE_Morph, VariableDialogMorph, HTMLCanvasElement, Context, List, SpeechBubbleMorph, RingMorph, isNil, FileReader, TableDialogMorph, BlockEditorMorph, BlockDialogMorph, PrototypeHatBlockMorph, localize, -TableMorph, TableFrameMorph, normalizeCanvas, BooleanSlotMorph, HandleMorph*/ +TableMorph, TableFrameMorph, normalizeCanvas, BooleanSlotMorph, HandleMorph, +AlignmentMorph*/ -modules.objects = '2017-September-05'; +modules.objects = '2017-September-08'; var SpriteMorph; var StageMorph; @@ -9526,7 +9527,7 @@ StagePrompterMorph.prototype.mouseClickLeft = function () { StagePrompterMorph.prototype.accept = function () { this.isDone = true; -} +}; // CamSnapshotDialogMorph ///////////////////////////////////////////////////////// @@ -9546,7 +9547,7 @@ CamSnapshotDialogMorph.uber = DialogBoxMorph.prototype; function CamSnapshotDialogMorph(ide, sprite, onCancel, onAccept) { this.init(ide, sprite, onCancel, onAccept); -}; +} CamSnapshotDialogMorph.prototype.init = function (ide, sprite, onCancel, onAccept) { this.ide = ide; @@ -9568,8 +9569,7 @@ CamSnapshotDialogMorph.prototype.init = function (ide, sprite, onCancel, onAccep }; CamSnapshotDialogMorph.prototype.buildContents = function () { - var myself = this, - stage = this.ide.stage; + var myself = this; this.videoElement = document.createElement('video'); this.videoElement.hidden = true; @@ -9625,9 +9625,9 @@ CamSnapshotDialogMorph.prototype.buildContents = function () { CamSnapshotDialogMorph.prototype.ok = function () { var stage = this.ide.stage, - canvas = newCanvas(stage.dimensions); + canvas = newCanvas(stage.dimensions), + context = canvas.getContext('2d'); - context = canvas.getContext('2d'); context.translate(stage.dimensions.x, 0); context.scale(-1, 1); diff --git a/symbols.js b/symbols.js index 5607cb4b..b7d20bf2 100644 --- a/symbols.js +++ b/symbols.js @@ -41,7 +41,7 @@ // Global stuff //////////////////////////////////////////////////////// -modules.symbols = '2017-September-01'; +modules.symbols = '2017-September-08'; var SymbolMorph; diff --git a/widgets.js b/widgets.js index e04e4e62..08c3927b 100644 --- a/widgets.js +++ b/widgets.js @@ -85,7 +85,7 @@ HTMLCanvasElement, fontHeight, SymbolMorph, localize, SpeechBubbleMorph, ArrowMorph, MenuMorph, isString, isNil, SliderMorph, MorphicPreferences, ScrollFrameMorph, MenuItemMorph, Note*/ -modules.widgets = '2017-September-01'; +modules.widgets = '2017-September-08'; var PushButtonMorph; var ToggleButtonMorph; From d995f173e70331362d668bc7ee647d79c0b69d95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20M=C3=B6nig?= Date: Fri, 8 Sep 2017 19:39:10 +0200 Subject: [PATCH 12/13] move CamSnapshotDialogMorph code from objects to gui --- gui.js | 138 +++++++++++++++++++++++++++++++++++++++++++++++++++-- objects.js | 131 +------------------------------------------------- 2 files changed, 136 insertions(+), 133 deletions(-) diff --git a/gui.js b/gui.js index a074d83f..87bb3c26 100644 --- a/gui.js +++ b/gui.js @@ -43,8 +43,9 @@ TurtleIconMorph CostumeIconMorph WardrobeMorph - StageHandleMorph; - PaletteHandleMorph; + StageHandleMorph + PaletteHandleMorph + CamSnapshotDialogMorph credits @@ -70,7 +71,7 @@ fontHeight, hex_sha512, sb, CommentMorph, CommandBlockMorph, BlockLabelPlaceHolderMorph, Audio, SpeechBubbleMorph, ScriptFocusMorph, XML_Element, WatcherMorph, BlockRemovalDialogMorph, saveAs, TableMorph, isSnapObject, isRetinaEnabled, disableRetinaSupport, enableRetinaSupport, -isRetinaSupported, SliderMorph, Animation, CamSnapshotDialogMorph*/ +isRetinaSupported, SliderMorph, Animation*/ // Global stuff //////////////////////////////////////////////////////// @@ -89,6 +90,7 @@ var SoundIconMorph; var JukeboxMorph; var StageHandleMorph; var PaletteHandleMorph; +var CamSnapshotDialogMorph; // IDE_Morph /////////////////////////////////////////////////////////// @@ -8333,3 +8335,133 @@ PaletteHandleMorph.prototype.mouseDoubleClick = function () { this.target.parentThatIsA(IDE_Morph).setPaletteWidth(200); }; +// CamSnapshotDialogMorph //////////////////////////////////////////////////// + +/* + I am a dialog morph that lets users take a snapshot using their webcam + and use it as a costume for their sprites or a background for the Stage. +*/ + +// CamSnapshotDialogMorph inherits from DialogBoxMorph: + + +CamSnapshotDialogMorph.prototype = new DialogBoxMorph(); +CamSnapshotDialogMorph.prototype.constructor = CamSnapshotDialogMorph; +CamSnapshotDialogMorph.uber = DialogBoxMorph.prototype; + +// CamSnapshotDialogMorph instance creation + +function CamSnapshotDialogMorph(ide, sprite, onCancel, onAccept) { + this.init(ide, sprite, onCancel, onAccept); +} + +CamSnapshotDialogMorph.prototype.init = function ( + ide, + sprite, + onCancel, +onAccept) { + this.ide = ide; + this.sprite = sprite; + this.padding = 10; + + this.oncancel = onCancel; + this.accept = onAccept; + + this.videoElement = null; // an HTML5 video element + this.videoView = new Morph(); // a morph where we'll copy the video contents + + CamSnapshotDialogMorph.uber.init.call(this); + + this.labelString = 'Camera'; + this.createLabel(); + + this.buildContents(); +}; + +CamSnapshotDialogMorph.prototype.buildContents = function () { + var myself = this; + + this.videoElement = document.createElement('video'); + this.videoElement.hidden = true; + document.body.appendChild(this.videoElement); + + if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { + navigator.mediaDevices.getUserMedia({ video: true }) + .then(function (stream) { + myself.videoElement.src = window.URL.createObjectURL(stream); + myself.videoElement.play(); + myself.videoElement.stream = stream; + }); + } + + this.videoView.setExtent(this.ide.stage.dimensions); + this.videoView.image = newCanvas(this.videoView.extent()); + this.videoView.drawOn = function (aCanvas) { + var context = aCanvas.getContext('2d'), + w = this.width(), + h = this.height(); + + context.save(); + + // Flip the image so it looks like a mirror + context.translate(w, 0); + context.scale(-1, 1); + + context.drawImage( + myself.videoElement, + this.left() * -1, + this.top(), + w, + h + ); + + context.restore(); + }; + + this.videoView.step = function () { + this.changed(); + }; + + this.addBody(new AlignmentMorph('column', this.padding / 2)); + this.body.add(this.videoView); + this.body.fixLayout(); + + this.addButton('ok', 'Save'); + this.addButton('cancel', 'Cancel'); + + this.fixLayout(); + this.drawNew(); +}; + +CamSnapshotDialogMorph.prototype.ok = function () { + var stage = this.ide.stage, + canvas = newCanvas(stage.dimensions), + context = canvas.getContext('2d'); + + context.translate(stage.dimensions.x, 0); + context.scale(-1, 1); + + context.drawImage( + this.videoElement, + 0, + 0, + stage.dimensions.x, + stage.dimensions.y + ); + + this.accept(new Costume(canvas), this.sprite.newCostumeName('camera')); + this.close(); +}; + +CamSnapshotDialogMorph.prototype.destroy = function () { + this.oncancel.call(this); + this.close(); +}; + +CamSnapshotDialogMorph.prototype.close = function () { + if (this.videoElement) { + this.videoElement.stream.getTracks()[0].stop(); + this.videoElement.remove(); + } + CamSnapshotDialogMorph.uber.destroy.call(this); +}; diff --git a/objects.js b/objects.js index e89af7e0..b7444997 100644 --- a/objects.js +++ b/objects.js @@ -80,8 +80,7 @@ document, isNaN, isString, newCanvas, nop, parseFloat, radians, window, modules, IDE_Morph, VariableDialogMorph, HTMLCanvasElement, Context, List, SpeechBubbleMorph, RingMorph, isNil, FileReader, TableDialogMorph, BlockEditorMorph, BlockDialogMorph, PrototypeHatBlockMorph, localize, -TableMorph, TableFrameMorph, normalizeCanvas, BooleanSlotMorph, HandleMorph, -AlignmentMorph*/ +TableMorph, TableFrameMorph, normalizeCanvas, BooleanSlotMorph, HandleMorph*/ modules.objects = '2017-September-08'; @@ -98,7 +97,6 @@ var WatcherMorph; var StagePrompterMorph; var Note; var SpriteHighlightMorph; -var CamSnapshotDialogMorph; function isSnapObject(thing) { return thing instanceof SpriteMorph || (thing instanceof StageMorph); @@ -9528,130 +9526,3 @@ StagePrompterMorph.prototype.mouseClickLeft = function () { StagePrompterMorph.prototype.accept = function () { this.isDone = true; }; - -// CamSnapshotDialogMorph ///////////////////////////////////////////////////////// - -/* - I am a dialog morph that lets users take a snapshot using their webcam - and use it as a costume for their sprites or a background for the Stage. -*/ - -// CamSnapshotDialogMorph inherits from DialogBoxMorph: - - -CamSnapshotDialogMorph.prototype = new DialogBoxMorph(); -CamSnapshotDialogMorph.prototype.constructor = CamSnapshotDialogMorph; -CamSnapshotDialogMorph.uber = DialogBoxMorph.prototype; - -// CamSnapshotDialogMorph instance creation - -function CamSnapshotDialogMorph(ide, sprite, onCancel, onAccept) { - this.init(ide, sprite, onCancel, onAccept); -} - -CamSnapshotDialogMorph.prototype.init = function (ide, sprite, onCancel, onAccept) { - this.ide = ide; - this.sprite = sprite; - this.padding = 10; - - this.oncancel = onCancel; - this.accept = onAccept; - - this.videoElement = null; // an HTML5 video element - this.videoView = new Morph(); // a morph where we'll copy the video contents - - CamSnapshotDialogMorph.uber.init.call(this); - - this.labelString = 'Camera'; - this.createLabel(); - - this.buildContents(); -}; - -CamSnapshotDialogMorph.prototype.buildContents = function () { - var myself = this; - - this.videoElement = document.createElement('video'); - this.videoElement.hidden = true; - document.body.appendChild(this.videoElement); - - if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { - navigator.mediaDevices.getUserMedia({ video: true }) - .then(function (stream) { - myself.videoElement.src = window.URL.createObjectURL(stream); - myself.videoElement.play(); - myself.videoElement.stream = stream; - }); - } - - this.videoView.setExtent(this.ide.stage.dimensions); - this.videoView.image = newCanvas(this.videoView.extent()); - this.videoView.drawOn = function (aCanvas) { - var context = aCanvas.getContext('2d'), - w = this.width(), - h = this.height(); - - context.save(); - - // Flip the image so it looks like a mirror - context.translate(w, 0); - context.scale(-1, 1); - - context.drawImage( - myself.videoElement, - this.left() * -1, - this.top(), - w, - h - ); - - context.restore(); - }; - - this.videoView.step = function () { - this.changed(); - }; - - this.addBody(new AlignmentMorph('column', this.padding / 2)); - this.body.add(this.videoView); - this.body.fixLayout(); - - this.addButton('ok', 'Save'); - this.addButton('cancel', 'Cancel'); - - this.fixLayout(); - this.drawNew(); -}; - -CamSnapshotDialogMorph.prototype.ok = function () { - var stage = this.ide.stage, - canvas = newCanvas(stage.dimensions), - context = canvas.getContext('2d'); - - context.translate(stage.dimensions.x, 0); - context.scale(-1, 1); - - context.drawImage( - this.videoElement, - 0, - 0, - stage.dimensions.x, - stage.dimensions.y - ); - - this.accept(new Costume(canvas), this.sprite.newCostumeName('camera')); - this.close(); -}; - -CamSnapshotDialogMorph.prototype.destroy = function () { - this.oncancel.call(this); - this.close(); -}; - -CamSnapshotDialogMorph.prototype.close = function () { - if (this.videoElement) { - this.videoElement.stream.getTracks()[0].stop(); - this.videoElement.remove(); - } - CamSnapshotDialogMorph.uber.destroy.call(this); -}; From ba9a7bd6f6c3278945a829be694ab42bf1555353 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20M=C3=B6nig?= Date: Fri, 8 Sep 2017 20:49:27 +0200 Subject: [PATCH 13/13] slightly tweak camera button layout in the wardrobe --- gui.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gui.js b/gui.js index 87bb3c26..3480342f 100644 --- a/gui.js +++ b/gui.js @@ -7562,6 +7562,7 @@ WardrobeMorph.prototype.updateList = function () { x = this.left() + 5, y = this.top() + 5, padding = 4, + toolsPadding = 5, oldFlag = Morph.prototype.trackChanges, oldPos = this.contents.position(), icon, @@ -7631,7 +7632,7 @@ WardrobeMorph.prototype.updateList = function () { cambutton.setPosition(new Point(x, y)); cambutton.fixLayout(); cambutton.setCenter(paintbutton.center()); - cambutton.setLeft(paintbutton.right() + padding * 4); + cambutton.setLeft(paintbutton.right() + toolsPadding); if (location.protocol === 'http:') { cambutton.hint = 'Due to browser security policies, you need to\n' +