replace morphic version due to new chrome bugs

master
Michael 2023-11-24 14:21:52 +01:00
rodzic 2b6160e80b
commit d6fe6e23f9
5 zmienionych plików z 14272 dodań i 43 usunięć

Wyświetl plik

@ -5,7 +5,7 @@
<title>TurtleStitch</title>
<link rel="shortcut icon" href="stitchcode/favicon-32x32.png" type="image/png" />
<script src="src/morphic.js?version=2022-01-28"></script>
<script src="src/morphic.js?version=2023-11-24"></script>
<script src="src/symbols.js?version=2021-03-03"></script>
<script src="src/widgets.js?version=2021-17-09"></script>
<script src="src/blocks.js?version=2022-01-30"></script>

12780
src/morphic.dist.js 100644

Plik diff jest za duży Load Diff

1265
src/morphic.dist.txt 100755

Plik diff jest za duży Load Diff

Wyświetl plik

@ -8,7 +8,7 @@
written by Jens Mönig
jens@moenig.org
Copyright (C) 2010-2022 by Jens Mönig
Copyright (C) 2010-2023 by Jens Mönig
This file is part of Snap!.
@ -317,7 +317,6 @@
var world1, world2;
window.onload = function () {
disableRetinaSupport();
world1 = new WorldMorph(
document.getElementById('world1'), false);
world2 = new WorldMorph(
@ -326,7 +325,7 @@
};
function loop() {
requestAnimationFrame(loop);
requestAnimationFrame(loop);
world1.doOneCycle();
world2.doOneCycle();
}
@ -642,7 +641,7 @@
Drops of image elements from outside the world canvas are dispatched as
droppedImage(aCanvas, name)
droppedImage(aCanvas, name, embeddedData)
droppedSVG(anImage, name)
events to interested Morphs at the mouse pointer. If you want your Morph
@ -663,8 +662,22 @@
droppedImage() event with a canvas containing a rasterized version of the
SVG.
The same applies to drops of audio or text files from outside the world
canvas.
Note that PNG images provide for embedded text comments, which can be used
to include code or arbitrary data such as a CSV, JSON or XML file inside
the image. Such a payload has to be identified by an agreed-upon marker.
The default tag is stored in MorphicPreferences and can be overriden by
apps wishing to make use of this feature. If such an embedded text-payload
is found inside a PNG it is passed as the optional third "embeddedData"
parameter to the "droppedImage()" event. embedded text only applies to PNGs.
You can embed a string into the PNG metadata of a PNG by calling
embedMetadataPNG(aCanvas, aString)
with a raster image represented by a canvas and a string that is to be
embedded into the PNG's metadata.
The same event mechanism applies to drops of audio or text files from
outside the world canvas.
Those are dispatched as
@ -1276,6 +1289,8 @@
Jason N (@cyderize) contributed native copy & paste for text editing.
Bartosz Leper contributed retina display support.
Zhenlei Jia and Dariusz Dorożalski pioneered IME text editing.
Dariusz Dorożalski and Jesus Villalobos contributed embedding blocks
into image metadata.
Bernat Romagosa contributed to text editing and to the core design.
Michael Ball found and fixed a longstanding scrolling bug.
Brian Harvey contributed to the design and implementation of submenus.
@ -1289,9 +1304,9 @@
/*global window, HTMLCanvasElement, FileReader, Audio, FileList, Map*/
/*jshint esversion: 6*/
/*jshint esversion: 11, bitwise: false*/
var morphicVersion = '2022-January-28';
var morphicVersion = '2023-November-07';
var modules = {}; // keep track of additional loaded modules
var useBlurredShadows = true;
@ -1303,6 +1318,7 @@ const CLEAR = new Color(0, 0, 0, 0);
Object.freeze(ZERO);
Object.freeze(BLACK);
Object.freeze(WHITE);
Object.freeze(CLEAR);
var standardSettings = {
minimumFontHeight: getMinimumFontHeight(), // browser settings
@ -1318,6 +1334,7 @@ var standardSettings = {
mouseScrollAmount: 40,
useSliderForInput: false,
isTouchDevice: false, // turned on by touch events, don't set
pngPayloadMarker: 'Data\tPayload\tEmbedded',
rasterizeSVGs: false,
isFlat: false,
grabThreshold: 5,
@ -1338,6 +1355,7 @@ var touchScreenSettings = {
mouseScrollAmount: 40,
useSliderForInput: false,
isTouchDevice: true,
pngPayloadMarker: 'Data\tPayload\tEmbedded',
rasterizeSVGs: false,
isFlat: false,
grabThreshold: 5,
@ -1569,6 +1587,45 @@ function copy(target) {
return c;
}
function embedMetadataPNG(aCanvas, aString) {
var embedTag = MorphicPreferences.pngPayloadMarker,
crc32 = (str, crc) => {
let table = [...Array(256).keys()].map(it =>
[...Array(8)].reduce((cc) =>
(cc & 1) ? (0xedb88320 ^ (cc >>> 1)) : (cc >>> 1), it)
);
crc = [...str].reduce(
(crc, ch) => (crc >>> 8) ^ table[(crc ^ ch.charCodeAt(0)) & 0xff],
(crc ? crc = 0 : crc) ^ (-1) // (crc ||= 0) ^ (-1)
);
return ( crc ^ (-1) ) >>> 0;
},
arr2Str = (arr) =>
arr.reduce((res, byte) => res + String.fromCharCode(byte), ''),
int2BStr = (val) =>
arr2Str(Array.from(new Uint8Array(new Uint32Array( [val] ).buffer)).reverse()),
buildChunk = (data) => {
let res = "iTXt" + data;
return int2BStr(data.length) + res + int2BStr(crc32(res));
},
parts = aCanvas.toDataURL("image/png").split(","),
bPart = atob(parts[1]).split(""),
newChunk = buildChunk(
"Snap!_SRC\0\0\0\0\0" +
embedTag +
encodeURIComponent(aString) +
embedTag
);
try {
bPart.splice(-12, 0, ...newChunk);
parts[1] = btoa(bPart.join(""));
} catch (err) {
console.log(err);
}
return parts.join(',');
}
// Retina Display Support //////////////////////////////////////////////
/*
@ -1721,7 +1778,7 @@ function enableRetinaSupport() {
this.height = prevHeight;
}
},
configurable: true // [Jens]: allow to be deleted an reconfigured
configurable: true // [Jens]: allow to be deleted and reconfigured
});
Object.defineProperty(canvasProto, 'width', {
@ -1739,7 +1796,7 @@ function enableRetinaSupport() {
context.restore();
context.save();
*/
context.scale(pixelRatio, pixelRatio);
context?.scale(pixelRatio, pixelRatio);
} catch (err) {
console.log('Retina Display Support Problem', err);
uber.width.set.call(this, width);
@ -1760,7 +1817,7 @@ function enableRetinaSupport() {
context.restore();
context.save();
*/
context.scale(pixelRatio, pixelRatio);
context?.scale(pixelRatio, pixelRatio);
}
});
@ -2708,7 +2765,7 @@ Rectangle.prototype.boundingBox = function () {
Rectangle.prototype.center = function () {
return this.origin.add(
this.corner.subtract(this.origin).floorDivideBy(2)
this.corner.subtract(this.origin).divideBy(2)
);
};
@ -3407,7 +3464,7 @@ Morph.prototype.setBottom = function (y) {
Morph.prototype.setCenter = function (aPoint) {
this.setPosition(
aPoint.subtract(
this.extent().floorDivideBy(2)
this.extent().divideBy(2)
)
);
};
@ -3415,7 +3472,7 @@ Morph.prototype.setCenter = function (aPoint) {
Morph.prototype.setFullCenter = function (aPoint) {
this.setPosition(
aPoint.subtract(
this.fullBounds().extent().floorDivideBy(2)
this.fullBounds().extent().divideBy(2)
)
);
};
@ -4456,7 +4513,28 @@ Morph.prototype.developersMenu = function () {
);
menu.addItem(
"pic...",
() => window.open(this.fullImage().toDataURL()),
() => {
var imgURL = this.fullImage().toDataURL(),
doc, body, tag, str;
try {
doc = window.open('', '_blank', 'popup').document;
body = doc.getElementsByTagName('body')[0];
str = '' + this;
doc.title = str;
tag = doc.createElement('h1');
tag.textContent = str;
body.appendChild(tag);
tag = doc.createElement('img');
tag.alt = str;
tag.src = imgURL;
body.appendChild(tag);
} catch (error) {
console.warn(
'failed to popup pic, morph:%O, error:%O, image URL:%O',
this, error, [imgURL]
);
}
},
'open a new window\nwith a picture of this morph'
);
menu.addLine();
@ -8183,7 +8261,7 @@ MenuMorph.prototype.adjustWidths = function () {
if (item === this.label) {
item.text.setPosition(
item.center().subtract(
item.text.extent().floorDivideBy(2)
item.text.extent().divideBy(2)
)
);
}
@ -9289,7 +9367,25 @@ TextMorph.prototype.parse = function () {
this.maxLineWidth,
context.measureText(oldline).width
);
oldline = word + ' ';
w = context.measureText(word).width;
if (w > this.maxWidth) {
oldline = '';
word.split('').forEach((letter, idx) => {
w = context.measureText(oldline + letter).width;
if (w > this.maxWidth && oldline.length) {
this.lines.push(oldline);
this.lineSlots.push(slot + idx);
this.maxLineWidth = Math.max(
this.maxLineWidth,
context.measureText(oldline).width
);
oldline = '';
}
oldline += letter;
});
} else {
oldline = word + ' ';
}
} else {
oldline = newline;
}
@ -9823,7 +9919,7 @@ TriggerMorph.prototype.createLabel = function () {
TriggerMorph.prototype.fixLayout = function () {
this.label.setPosition(
this.center().subtract(
this.label.extent().floorDivideBy(2)
this.label.extent().divideBy(2)
)
);
};
@ -11163,6 +11259,7 @@ HandMorph.prototype.init = function (aWorld) {
this.temporaries = [];
this.touchHoldTimeout = null;
this.contextMenuEnabled = false;
this.touchStartPosition = null;
// properties for caching dragged objects:
this.cachedFullImage = null;
@ -11284,12 +11381,12 @@ HandMorph.prototype.grab = function (aMorph) {
if (!aMorph.noDropShadow) {
aMorph.addShadow();
}
this.add(aMorph);
// cache the dragged object's display resources
this.cachedFullImage = aMorph.fullImage();
this.cachedFullBounds = aMorph.fullBounds();
this.add(aMorph);
this.changed();
if (oldParent && oldParent.reactToGrabOf) {
oldParent.reactToGrabOf(aMorph);
@ -11409,6 +11506,10 @@ HandMorph.prototype.processTouchStart = function (event) {
MorphicPreferences.isTouchDevice = true;
clearInterval(this.touchHoldTimeout);
if (event.touches.length === 1) {
this.touchStartPosition = new Point(
event.touches[0].pageX,
event.touches[0].pageY
);
this.touchHoldTimeout = setInterval( // simulate mouseRightClick
() => {
this.processMouseDown({button: 2});
@ -11425,7 +11526,12 @@ HandMorph.prototype.processTouchStart = function (event) {
};
HandMorph.prototype.processTouchMove = function (event) {
var pos = new Point(event.touches[0].pageX, event.touches[0].pageY);
MorphicPreferences.isTouchDevice = true;
if (this.touchStartPosition.distanceTo(pos) <
MorphicPreferences.grabThreshold) {
return;
}
if (event.touches.length === 1) {
var touch = event.touches[0];
this.processMouseMove(touch);
@ -11643,7 +11749,7 @@ HandMorph.prototype.processDrop = function (event) {
onto the world canvas, turn it into an offscreen canvas or audio
element and dispatch the
droppedImage(canvas, name)
droppedImage(canvas, name, embeddedData)
droppedSVG(image, name)
droppedAudio(audio, name)
droppedText(text, name, type)
@ -11692,16 +11798,38 @@ HandMorph.prototype.processDrop = function (event) {
function readImage(aFile) {
var pic = new Image(),
frd = new FileReader(),
trg = target;
trg = target,
embedTag = MorphicPreferences.pngPayloadMarker;
while (!trg.droppedImage) {
trg = trg.parent;
}
pic.onload = () => {
canvas = newCanvas(new Point(pic.width, pic.height), true);
canvas.getContext('2d').drawImage(pic, 0, 0);
trg.droppedImage(canvas, aFile.name);
bulkDrop();
(async () => {
// extract embedded data (e.g. blocks)
// from the image's metadata if present
var buff = new Uint8Array(await aFile?.arrayBuffer()),
strBuff = buff.reduce((acc, b) =>
acc + String.fromCharCode(b), ""),
embedded;
if (strBuff.includes(embedTag)) {
try {
embedded = decodeURIComponent(
(strBuff)?.split(embedTag)[1]
);
} catch (err) {
console.log(err);
}
}
canvas = newCanvas(new Point(pic.width, pic.height), true);
canvas.getContext('2d').drawImage(pic, 0, 0);
trg.droppedImage(canvas, aFile.name, embedded);
bulkDrop();
})();
};
frd = new FileReader();
frd.onloadend = (e) => pic.src = e.target.result;
frd.readAsDataURL(aFile);
@ -11953,6 +12081,10 @@ WorldMorph.prototype.init = function (aCanvas, fillPage) {
this.activeMenu = null;
this.activeHandle = null;
if (!fillPage && aCanvas.isRetinaEnabled) {
this.initRetina();
}
this.initKeyboardHandler();
this.resetKeyboardHandler();
this.initEventListeners();
@ -12081,16 +12213,29 @@ WorldMorph.prototype.fillPage = function () {
});
};
WorldMorph.prototype.initRetina = function () {
var canvasHeight = this.worldCanvas.getBoundingClientRect().height,
canvasWidth = this.worldCanvas.getBoundingClientRect().width;
this.worldCanvas.style.width = canvasWidth + 'px';
this.worldCanvas.width = canvasWidth;
this.setWidth(canvasWidth);
this.worldCanvas.style.height = canvasHeight + 'px';
this.worldCanvas.height = canvasHeight;
this.setHeight(canvasHeight);
};
// WorldMorph global pixel access:
WorldMorph.prototype.getGlobalPixelColor = function (point) {
// answer the color at the given point.
var dta = this.worldCanvas.getContext('2d').getImageData(
point.x,
point.y,
1,
1
).data;
// first, create a new temporary canvas representing the fullImage
// and sample that one instead of the actual world canvas
// this slows things down but keeps Chrome from crashing
// in v119 in the Fall of 2023
var dta = Morph.prototype.fullImage.call(this)
.getContext('2d')
.getImageData(point.x, point.y, 1, 1)
.data;
return new Color(dta[0], dta[1], dta[2]);
};
@ -12113,6 +12258,8 @@ WorldMorph.prototype.initKeyboardHandler = function () {
kbd.world = this;
kbd.style.zIndex = -1;
kbd.autofocus = true;
kbd.style.width = '0px';
kbd.style.height = '0px';
document.body.appendChild(kbd);
this.keyboardHandler = kbd;
@ -12305,6 +12452,7 @@ WorldMorph.prototype.initEventListeners = function () {
window.addEventListener(
"drop",
event => {
this.hand.processMouseMove(event);
this.hand.processDrop(event);
event.preventDefault();
},

Wyświetl plik

@ -7,10 +7,10 @@
written by Jens Mönig
jens@moenig.org
Copyright (C) 2010-2020 by Jens Mönig
This documentation last changed: June 9, 2020
Copyright (C) 2010-2022 by Jens Mönig
This documentation last changed: November 22
This file is part of Snap!.
Snap! is free software: you can redistribute it and/or modify
@ -303,7 +303,7 @@
-------------------
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
include it in the main loop. It's also a good idea to give each
world its own tabindex:
example html file:
@ -318,7 +318,6 @@
var world1, world2;
window.onload = function () {
disableRetinaSupport();
world1 = new WorldMorph(
document.getElementById('world1'), false);
world2 = new WorldMorph(
@ -327,7 +326,7 @@
};
function loop() {
requestAnimationFrame(loop);
requestAnimationFrame(loop);
world1.doOneCycle();
world2.doOneCycle();
}
@ -472,6 +471,8 @@
mouseLeave
mouseEnterDragging
mouseLeaveDragging
mouseEnterBounds
mouseLeaveBounds
mouseMove
mouseScroll
@ -480,7 +481,7 @@
MyMorph.prototype.mouseMove = function(pos) {};
All of these methods have as optional parameter a Point object
Most of these methods have as optional parameter a Point object
indicating the current position of the Hand inside the World's
coordinate system. The
@ -490,6 +491,16 @@
currently pressed mouse button, which is either 'left' or 'right'.
You can use this to let users interact with 3D environments.
The
mouseEnterDragging(morph)
mouseLeaveDragging(morph)
mouseEnterBounds(morph)
mouseLeaveBounds(morph)
event methods have as optional parameter the morph currently dragged by
the Hand, if any.
Events may be "bubbled" up a morph's owner chain by calling
this.escalateEvent(functionName, arg)
@ -631,7 +642,7 @@
Drops of image elements from outside the world canvas are dispatched as
droppedImage(aCanvas, name)
droppedImage(aCanvas, name, embeddedData)
droppedSVG(anImage, name)
events to interested Morphs at the mouse pointer. If you want your Morph
@ -652,8 +663,22 @@
droppedImage() event with a canvas containing a rasterized version of the
SVG.
The same applies to drops of audio or text files from outside the world
canvas.
Note that PNG images provide for embedded text comments, which can be used
to include code or arbitrary data such as a CSV, JSON or XML file inside
the image. Such a payload has to be identified by an agreed-upon marker.
The default tag is stored in MorphicPreferences and can be overriden by
apps wishing to make use of this feature. If such an embedded text-payload
is found inside a PNG it is passed as the optional third "embeddedData"
parameter to the "droppedImage()" event. embedded text only applies to PNGs.
You can embed a string into the PNG metadata of a PNG by calling
embedMetadataPNG(aCanvas, aString)
with a raster image represented by a canvas and a string that is to be
embedded into the PNG's metadata.
The same event mechanism applies to drops of audio or text files from
outside the world canvas.
Those are dispatched as
@ -667,6 +692,15 @@
droppedBinary(anArrayBuffer, name)
In case multiple files are dropped simulateneously the events
beginBulkDrop()
endBulkDrop()
are dispatched to to Morphs interested in bracketing the bulk operation,
and the endBulkDrop() event is only signalled after the contents last file
has been asynchronously made available.
(e) keyboard events
-------------------
@ -1256,10 +1290,12 @@
Jason N (@cyderize) contributed native copy & paste for text editing.
Bartosz Leper contributed retina display support.
Zhenlei Jia and Dariusz Dorożalski pioneered IME text editing.
Dariusz Dorożalski and Jesus Villalobos contributed embedding blocks
into image metadata.
Bernat Romagosa contributed to text editing and to the core design.
Michael Ball found and fixed a longstanding scrolling bug.
Brian Harvey contributed to the design and implementation of submenus.
Ken Kahn contributed to Chinese keboard entry and Android support.
Brian Broll contributed clickable URLs in text elements.
Brian Broll contributed clickable URLs in text elements and many bugfixes.
- Jens Mönig