support for retina displays, integrates #1063 - IN PROGRESS

This has bee designed and written by the amazing Bartosz Leper
(@bl-nero). Since I totally suck at Git and find myself incompetent to
resolve merge conflicts, I’ve decided to manually copy Bartosz’s
changes mostly verbatim over, tweaking them only ever so slightly in
ways that don’t alter their functionality, except for introducing a
tiny test so current Safari still loads Snap, albeit not in retina
mode. THIS IT NOT READY FOR RELEASE YET, IT’S WORK IN PROGRESS. Get it?
Thanks!
dev
Jens Mönig 2016-05-10 20:33:44 +02:00
rodzic 483e55a70f
commit 179d93f103
8 zmienionych plików z 274 dodań i 56 usunięć

Wyświetl plik

@ -149,7 +149,7 @@ isSnapObject, copy*/
// Global stuff ////////////////////////////////////////////////////////
modules.blocks = '2016-May-04';
modules.blocks = '2016-May-10';
var SyntaxElementMorph;
var BlockMorph;
@ -2664,7 +2664,7 @@ BlockMorph.prototype.showHelp = function () {
}
pic.onload = function () {
help = newCanvas(new Point(pic.width, pic.height));
help = newCanvas(new Point(pic.width, pic.height), true); // nonRetina
ctx = help.getContext('2d');
ctx.drawImage(pic, 0, 0);
new DialogBoxMorph().inform(

9
gui.js
Wyświetl plik

@ -70,7 +70,7 @@ isSnapObject*/
// Global stuff ////////////////////////////////////////////////////////
modules.gui = '2016-May-09';
modules.gui = '2016-May-10';
// Declarations
@ -2903,7 +2903,7 @@ IDE_Morph.prototype.importMedia = function (mediaType) {
};
frame.addContents(icon);
img.onload = function () {
var canvas = newCanvas(new Point(img.width, img.height));
var canvas = newCanvas(new Point(img.width, img.height), true);
canvas.getContext('2d').drawImage(img, 0, 0);
icon.object = new Costume(canvas, item.name);
icon.refresh();
@ -2911,7 +2911,7 @@ IDE_Morph.prototype.importMedia = function (mediaType) {
img.src = url;
});
dialog.popUp(world);
dialog.setExtent(new Point(390, 300));
dialog.setExtent(new Point(400, 300));
dialog.setCenter(world.center());
dialog.drawNew();
@ -2931,7 +2931,7 @@ IDE_Morph.prototype.aboutSnap = function () {
module, btn1, btn2, btn3, btn4, licenseBtn, translatorsBtn,
world = this.world();
aboutTxt = 'Snap! 4.0.7.2\nBuild Your Own Blocks\n\n'
aboutTxt = 'Snap! 4.0.8 - dev -\nBuild Your Own Blocks\n\n'
+ 'Copyright \u24B8 2016 Jens M\u00F6nig and '
+ 'Brian Harvey\n'
+ 'jens@moenig.org, bh@cs.berkeley.edu\n\n'
@ -2968,6 +2968,7 @@ IDE_Morph.prototype.aboutSnap = function () {
+ '\ncountless bugfixes and optimizations'
+ '\nKartik Chandra: Paint Editor'
+ '\nMichael Ball: Time/Date UI, many bugfixes'
+ '\nBartosz Leper: Retina Display Support'
+ '\n"Ava" Yuan Yuan: Graphic Effects'
+ '\nKyle Hotchkiss: Block search design'
+ '\nIan Reynolds: UI Design, Event Bindings, '

Wyświetl plik

@ -2928,3 +2928,5 @@ http://snap.berkeley.edu/run#cloud:Username=jens&ProjectName=rotation
* Media import dialog with thumbnail, thanks to @ubertao!
== v4.0.7.2 ====
* in progress: Retina Display Support, thanks, Bartosz Leper!!

Wyświetl plik

@ -42,7 +42,7 @@
/*global modules, contains*/
modules.locale = '2016-May-09';
modules.locale = '2016-May-10';
// Global stuff
@ -167,11 +167,11 @@ SnapTranslator.dict.it = {
'language_name':
'Italiano',
'language_translator':
'Stefano Federici, Alberto Firpo',
'Stefano Federici, Alberto Firpo, Massimo Ghisalberti',
'translator_e-mail':
's_federici@yahoo.com, albertofirpo12@gmail.com',
's_federici@yahoo.com, albertofirpo12@gmail.com, zairik@gmail.com',
'last_changed':
'2015-01-12'
'2016-05-10'
};
SnapTranslator.dict.ja = {

Wyświetl plik

@ -1036,7 +1036,8 @@
I have originally written morphic.js in Florian Balmer's Notepad2
editor for Windows, later switched to Apple's Dashcode and later
still to Apple's Xcode. I've also come to depend on both Douglas
Crockford's JSLint, Mozilla's Firebug and Google's Chrome to get
Crockford's JSLint and later the JSHint project, as well as on
Mozilla's Firebug and Google's Chrome to get
it right.
@ -1048,6 +1049,7 @@
Ian Reynolds contributed backspace key handling for Chrome.
Davide Della Casa contributed performance optimizations for Firefox.
Jason N (@cyderize) contributed native copy & paste for text editing.
Bartosz Leper contributed retina display support.
- Jens Mönig
*/
@ -1056,7 +1058,7 @@
/*global window, HTMLCanvasElement, FileReader, Audio, FileList*/
var morphicVersion = '2016-May-04';
var morphicVersion = '2016-May-10';
var modules = {}; // keep track of additional loaded modules
var useBlurredShadows = getBlurredShadowSupport(); // check for Chrome-bug
@ -1102,6 +1104,8 @@ var touchScreenSettings = {
var MorphicPreferences = standardSettings;
enableRetinaSupport();
// Global Functions ////////////////////////////////////////////////////
function nop() {
@ -1170,13 +1174,18 @@ function fontHeight(height) {
return minHeight * 1.2; // assuming 1/5 font size for ascenders
}
function newCanvas(extentPoint) {
function newCanvas(extentPoint, nonRetina) {
// answer a new empty instance of Canvas, don't display anywhere
// nonRetina - optional Boolean "false"
// by default retina support is automatic
var canvas, ext;
ext = extentPoint || {x: 0, y: 0};
canvas = document.createElement('canvas');
canvas.width = ext.x;
canvas.height = ext.y;
if (nonRetina && canvas.isRetinaEnabled) {
canvas.isRetinaEnabled = false;
}
return canvas;
}
@ -1281,6 +1290,216 @@ function copy(target) {
return c;
}
// Retina Display Support //////////////////////////////////////////////
function enableRetinaSupport() {
/*
=== contributed by Bartosz Leper ===
This installs a series of utilities that allow using Canvas the same way
on retina and non-retina displays. If the display is a retina one, the
underlying dimensions of the Canvas elements are doubled, but this will
be transparent to the code that uses Canvas. All dimensions read or
written to the Canvas element will be scaled appropriately.
NOTE: This implementation is not exhaustive; it only implements what is
needed by the Snap! UI.
*/
// Get the window's pixel ratio for canvas elements.
// See: http://www.html5rocks.com/en/tutorials/canvas/hidpi/
var ctx = document.createElement("canvas").getContext("2d"),
backingStorePixelRatio = ctx.webkitBackingStorePixelRatio ||
ctx.mozBackingStorePixelRatio ||
ctx.msBackingStorePixelRatio ||
ctx.oBackingStorePixelRatio ||
ctx.backingStorePixelRatio || 1,
// Unfortunately, it's really hard to make this work well when changing
// zoom level, so let's leave it like this right now, and stick to
// whatever the ratio was in the beginning.
originalDevicePixelRatio = window.devicePixelRatio,
canvasProto = HTMLCanvasElement.prototype,
contextProto = CanvasRenderingContext2D.prototype,
uber = {
drawImage: contextProto.drawImage,
getImageData: contextProto.getImageData,
width: Object.getOwnPropertyDescriptor(
canvasProto,
'width'
),
height: Object.getOwnPropertyDescriptor(
canvasProto,
'height'
),
shadowOffsetX: Object.getOwnPropertyDescriptor(
contextProto,
'shadowOffsetX'
),
shadowOffsetY: Object.getOwnPropertyDescriptor(
contextProto,
'shadowOffsetY'
),
shadowBlur: Object.getOwnPropertyDescriptor(
contextProto,
'shadowBlur'
)
};
// check whether properties can be overridden, needed for Safari
if (Object.keys(uber).some(function (any) {
var prop = uber[any];
return prop.hasOwnProperty('configurable') && (!prop.configurable);
})) {return; }
function getPixelRatio(imageSource) {
return imageSource.isRetinaEnabled ?
(originalDevicePixelRatio || 1) / backingStorePixelRatio : 1;
}
canvasProto._isRetinaEnabled = true;
Object.defineProperty(canvasProto, 'isRetinaEnabled', {
get: function() {
return this._isRetinaEnabled;
},
set: function(enabled) {
var prevPixelRatio = getPixelRatio(this);
var prevWidth = this.width;
var prevHeight = this.height;
this._isRetinaEnabled = enabled;
if (getPixelRatio(this) != prevPixelRatio) {
this.width = prevWidth;
this.height = prevHeight;
}
}
});
Object.defineProperty(canvasProto, 'width', {
get: function() {
return uber.width.get.call(this) / getPixelRatio(this);
},
set: function(width) {
var pixelRatio = getPixelRatio(this);
uber.width.set.call(this, width * pixelRatio);
var context = this.getContext('2d');
context.restore();
context.save();
context.scale(pixelRatio, pixelRatio);
}
});
Object.defineProperty(canvasProto, 'height', {
get: function() {
return uber.height.get.call(this) / getPixelRatio(this);
},
set: function(height) {
var pixelRatio = getPixelRatio(this);
uber.height.set.call(this, height * pixelRatio);
var context = this.getContext('2d');
context.restore();
context.save();
context.scale(pixelRatio, pixelRatio);
}
});
contextProto.drawImage = function(image) {
var pixelRatio = getPixelRatio(image),
sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight;
// Different signatures of drawImage() method have different
// parameter assignments.
switch (arguments.length) {
case 9:
sx = arguments[1];
sy = arguments[2];
sWidth = arguments[3];
sHeight = arguments[4];
dx = arguments[5];
dy = arguments[6];
dWidth = arguments[7];
dHeight = arguments[8];
break;
case 5:
sx = 0;
sy = 0;
sWidth = image.width;
sHeight = image.height;
dx = arguments[1];
dy = arguments[2];
dWidth = arguments[3];
dHeight = arguments[4];
break;
case 3:
sx = 0;
sy = 0;
sWidth = image.width;
sHeight = image.height;
dx = arguments[1];
dy = arguments[2];
dWidth = image.width;
dHeight = image.height;
break;
default:
throw Error('Called drawImage() with ' + arguments.length +
' arguments');
}
uber.drawImage.call(
this, image,
sx * pixelRatio, sy * pixelRatio,
sWidth * pixelRatio, sHeight * pixelRatio,
dx, dy,
dWidth, dHeight);
};
contextProto.getImageData = function(sx, sy, sw, sh) {
var pixelRatio = getPixelRatio(this.canvas);
return uber.getImageData.call(
this,
sx * pixelRatio, sy * pixelRatio,
sw * pixelRatio, sh * pixelRatio);
};
Object.defineProperty(contextProto, 'shadowOffsetX', {
get: function() {
return uber.shadowOffsetX.get.call(this) /
getPixelRatio(this.canvas);
},
set: function(offset) {
var pixelRatio = getPixelRatio(this.canvas);
uber.shadowOffsetX.set.call(this, offset * pixelRatio);
}
});
Object.defineProperty(contextProto, 'shadowOffsetY', {
get: function() {
return uber.shadowOffsetY.get.call(this) /
getPixelRatio(this.canvas);
},
set: function(offset) {
var pixelRatio = getPixelRatio(this.canvas);
uber.shadowOffsetY.set.call(this, offset * pixelRatio);
}
});
Object.defineProperty(contextProto, 'shadowBlur', {
get: function() {
return uber.shadowBlur.get.call(this) /
getPixelRatio(this.canvas);
},
set: function(blur) {
var pixelRatio = getPixelRatio(this.canvas);
uber.shadowBlur.set.call(this, blur * pixelRatio);
}
});
}
// Colors //////////////////////////////////////////////////////////////
// Color instance creation:
@ -9947,7 +10166,7 @@ HandMorph.prototype.processDrop = function (event) {
target = target.parent;
}
pic.onload = function () {
canvas = newCanvas(new Point(pic.width, pic.height));
canvas = newCanvas(new Point(pic.width, pic.height), true);
canvas.getContext('2d').drawImage(pic, 0, 0);
target.droppedImage(canvas, aFile.name);
};
@ -10038,7 +10257,7 @@ HandMorph.prototype.processDrop = function (event) {
}
img = new Image();
img.onload = function () {
canvas = newCanvas(new Point(img.width, img.height));
canvas = newCanvas(new Point(img.width, img.height), true);
canvas.getContext('2d').drawImage(img, 0, 0);
target.droppedImage(canvas);
};
@ -10050,7 +10269,7 @@ HandMorph.prototype.processDrop = function (event) {
}
img = new Image();
img.onload = function () {
canvas = newCanvas(new Point(img.width, img.height));
canvas = newCanvas(new Point(img.width, img.height), true);
canvas.getContext('2d').drawImage(img, 0, 0);
target.droppedImage(canvas);
};
@ -10190,22 +10409,16 @@ WorldMorph.prototype.doOneCycle = function () {
};
WorldMorph.prototype.fillPage = function () {
var pos = getDocumentPositionOf(this.worldCanvas),
clientHeight = window.innerHeight,
var clientHeight = window.innerHeight,
clientWidth = window.innerWidth,
myself = this;
this.worldCanvas.style.position = "absolute";
this.worldCanvas.style.left = "0px";
this.worldCanvas.style.right = "0px";
this.worldCanvas.style.width = "100%";
this.worldCanvas.style.height = "100%";
if (pos.x > 0) {
this.worldCanvas.style.position = "absolute";
this.worldCanvas.style.left = "0px";
pos.x = 0;
}
if (pos.y > 0) {
this.worldCanvas.style.position = "absolute";
this.worldCanvas.style.top = "0px";
pos.y = 0;
}
if (document.documentElement.scrollTop) {
// scrolled down b/c of viewport scaling
clientHeight = document.documentElement.clientHeight;

Wyświetl plik

@ -82,7 +82,7 @@ SpeechBubbleMorph, RingMorph, isNil, FileReader, TableDialogMorph,
BlockEditorMorph, BlockDialogMorph, PrototypeHatBlockMorph, localize,
TableMorph, TableFrameMorph*/
modules.objects = '2016-May-04';
modules.objects = '2016-May-10';
var SpriteMorph;
var StageMorph;
@ -1498,7 +1498,7 @@ SpriteMorph.prototype.drawNew = function () {
// create a new, adequately dimensioned canvas
// and draw the costume on it
this.image = newCanvas(costumeExtent);
this.image = newCanvas(costumeExtent, true);
this.silentSetExtent(costumeExtent);
ctx = this.image.getContext('2d');
ctx.scale(this.scale * stageScale, this.scale * stageScale);
@ -1527,7 +1527,7 @@ SpriteMorph.prototype.drawNew = function () {
1000
);
this.silentSetExtent(new Point(newX, newX));
this.image = newCanvas(this.extent());
this.image = newCanvas(this.extent(), true);
this.setCenter(currentCenter, true); // just me
SpriteMorph.uber.drawNew.call(this, facing);
this.rotationOffset = this.extent().divideBy(2);
@ -1576,7 +1576,7 @@ SpriteMorph.prototype.colorFiltered = function (aColor) {
dta;
src = this.image.getContext('2d').getImageData(0, 0, ext.x, ext.y);
morph.image = newCanvas(ext);
morph.image = newCanvas(ext, true);
morph.bounds = this.bounds.copy();
ctx = morph.image.getContext('2d');
dta = ctx.createImageData(ext.x, ext.y);
@ -3005,11 +3005,11 @@ SpriteMorph.prototype.goBack = function (layers) {
SpriteMorph.prototype.overlappingImage = function (otherSprite) {
// overrides method from Morph because Sprites aren't nested Morphs
var oRect = this.bounds.intersect(otherSprite.bounds),
oImg = newCanvas(oRect.extent()),
oImg = newCanvas(oRect.extent(), true),
ctx = oImg.getContext('2d');
if (oRect.width() < 1 || oRect.height() < 1) {
return newCanvas(new Point(1, 1));
return newCanvas(new Point(1, 1), true);
}
ctx.drawImage(
this.image,
@ -6522,7 +6522,7 @@ SpriteBubbleMorph.prototype.fixLayout = function () {
// Costume instance creation
function Costume(canvas, name, rotationCenter) {
this.contents = canvas || newCanvas();
this.contents = canvas || newCanvas(null, true);
this.shrinkToFit(this.maxExtent());
this.name = name || null;
this.rotationCenter = rotationCenter || this.center();
@ -6566,7 +6566,7 @@ Costume.prototype.shrinkWrap = function () {
// adjust my contents' bounds to my visible bounding box
var bb = this.boundingBox(),
ext = bb.extent(),
pic = newCanvas(ext),
pic = newCanvas(ext, true),
ctx = pic.getContext('2d');
ctx.drawImage(
@ -6652,7 +6652,7 @@ Costume.prototype.boundingBox = function () {
// Costume duplication
Costume.prototype.copy = function () {
var canvas = newCanvas(this.extent()),
var canvas = newCanvas(this.extent(), true),
cpy,
ctx;
ctx = canvas.getContext('2d');
@ -6670,7 +6670,7 @@ Costume.prototype.flipped = function () {
(mirrored along a vertical axis), used for
SpriteMorph's rotation style type 2
*/
var canvas = newCanvas(this.extent()),
var canvas = newCanvas(this.extent(), true),
ctx = canvas.getContext('2d'),
flipped;
@ -6696,7 +6696,7 @@ Costume.prototype.edit = function (aWorld, anIDE, isnew, oncancel, onsubmit) {
editor.openIn(
aWorld,
isnew ?
newCanvas(StageMorph.prototype.dimensions) :
newCanvas(StageMorph.prototype.dimensions, true) :
this.contents,
isnew ?
null :

Wyświetl plik

@ -62,6 +62,7 @@
Dec 15 - center rotation point on costume creating (Craxic)
Jan 18 - avoid pixel collision detection in PaintCanvas (Jens)
Mar 22 - fixed automatic rotation center point mechanism (Jens)
May 10 - retina display support adjustments (Jens)
*/
/*global Point, Rectangle, DialogBoxMorph, AlignmentMorph, PushButtonMorph,
@ -72,7 +73,7 @@ StageMorph, isNil*/
// Global stuff ////////////////////////////////////////////////////////
modules.paint = '2016-May-02';
modules.paint = '2016-May-10';
// Declarations
@ -572,9 +573,9 @@ PaintCanvasMorph.prototype.init = function (shift) {
this.dragRect = new Rectangle();
// rectangle with origin being the starting drag position and
// corner being the current drag position
this.mask = newCanvas(this.extent()); // Temporary canvas
this.paper = newCanvas(this.extent()); // Actual canvas
this.erasermask = newCanvas(this.extent()); // eraser memory
this.mask = newCanvas(this.extent(), true); // Temporary canvas
this.paper = newCanvas(this.extent(), true); // Actual canvas
this.erasermask = newCanvas(this.extent(), true); // eraser memory
this.background = newCanvas(this.extent()); // checkers
this.settings = {
"primarycolor": new Color(0, 0, 0, 255), // usually fill color
@ -617,8 +618,8 @@ PaintCanvasMorph.prototype.updateAutomaticCenter = function () {
PaintCanvasMorph.prototype.scale = function (x, y) {
this.updateAutomaticCenter();
this.mask = newCanvas(this.extent());
var c = newCanvas(this.extent());
this.mask = newCanvas(this.extent(), true);
var c = newCanvas(this.extent(), true);
c.getContext("2d").save();
c.getContext("2d").translate(
this.rotationCenter.x,
@ -637,14 +638,14 @@ PaintCanvasMorph.prototype.scale = function (x, y) {
};
PaintCanvasMorph.prototype.cacheUndo = function () {
var cachecan = newCanvas(this.extent());
var cachecan = newCanvas(this.extent(), true);
this.merge(this.paper, cachecan);
this.undoBuffer.push(cachecan);
};
PaintCanvasMorph.prototype.undo = function () {
if (this.undoBuffer.length > 0) {
this.paper = newCanvas(this.extent());
this.paper = newCanvas(this.extent(), true);
this.mask.width = this.mask.width + 1 - 1;
this.merge(this.undoBuffer.pop(), this.paper);
this.drawNew();
@ -671,7 +672,7 @@ PaintCanvasMorph.prototype.clearCanvas = function () {
};
PaintCanvasMorph.prototype.toolChanged = function (tool) {
this.mask = newCanvas(this.extent());
this.mask = newCanvas(this.extent(), true);
if (tool === "crosshairs") {
this.updateAutomaticCenter();
this.drawcrosshair();
@ -812,7 +813,7 @@ PaintCanvasMorph.prototype.mouseDownLeft = function (pos) {
}
if (this.settings.primarycolor === "transparent" &&
this.currentTool !== "crosshairs") {
this.erasermask = newCanvas(this.extent());
this.erasermask = newCanvas(this.extent(), true);
this.merge(this.paper, this.erasermask);
}
};
@ -953,7 +954,7 @@ PaintCanvasMorph.prototype.mouseMove = function (pos) {
}
mctx.stroke();
mctx.restore();
this.paper = newCanvas(this.extent());
this.paper = newCanvas(this.extent(), true);
this.merge(this.mask, this.paper);
break;
default:
@ -977,9 +978,9 @@ PaintCanvasMorph.prototype.mouseLeaveDragging
PaintCanvasMorph.prototype.buildContents = function () {
this.background = newCanvas(this.extent());
this.paper = newCanvas(this.extent());
this.mask = newCanvas(this.extent());
this.erasermask = newCanvas(this.extent());
this.paper = newCanvas(this.extent(), true);
this.mask = newCanvas(this.extent(), true);
this.erasermask = newCanvas(this.extent(), true);
var i, j, bkctx = this.background.getContext("2d");
for (i = 0; i < this.background.width; i += 5) {
for (j = 0; j < this.background.height; j += 5) {
@ -994,7 +995,7 @@ PaintCanvasMorph.prototype.buildContents = function () {
};
PaintCanvasMorph.prototype.drawNew = function () {
var can = newCanvas(this.extent());
var can = newCanvas(this.extent(), true);
this.merge(this.background, can);
this.merge(this.paper, can);
this.merge(this.mask, can);

Wyświetl plik

@ -60,7 +60,7 @@ SyntaxElementMorph, Variable, isSnapObject, console*/
// Global stuff ////////////////////////////////////////////////////////
modules.store = '2016-May-02';
modules.store = '2016-May-10';
// XML_Serializer ///////////////////////////////////////////////////////
@ -1321,7 +1321,8 @@ SnapSerializer.prototype.loadValue = function (model) {
v = new Costume(null, name, center);
image.onload = function () {
var canvas = newCanvas(
new Point(image.width, image.height)
new Point(image.width, image.height),
true // nonRetina
),
context = canvas.getContext('2d');
context.drawImage(image, 0, 0);