Spaces:
Running
Running
| ; | |
| /* | |
| * Copyright (c) 2013-2025 Vanessa Freudenberg | |
| * | |
| * Permission is hereby granted, free of charge, to any person obtaining a copy | |
| * of this software and associated documentation files (the "Software"), to deal | |
| * in the Software without restriction, including without limitation the rights | |
| * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
| * copies of the Software, and to permit persons to whom the Software is | |
| * furnished to do so, subject to the following conditions: | |
| * | |
| * The above copyright notice and this permission notice shall be included in | |
| * all copies or substantial portions of the Software. | |
| * | |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
| * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
| * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
| * THE SOFTWARE. | |
| */ | |
| Object.extend(Squeak.Primitives.prototype, | |
| 'display', { | |
| initDisplay: function(display) { | |
| this.display = display; | |
| this.display.vm = this.vm; | |
| this.indexedColors = [ | |
| 0xFFFFFFFF, 0xFF000001, 0xFFFFFFFF, 0xFF808080, 0xFFFF0000, 0xFF00FF00, 0xFF0000FF, 0xFF00FFFF, | |
| 0xFFFFFF00, 0xFFFF00FF, 0xFF202020, 0xFF404040, 0xFF606060, 0xFF9F9F9F, 0xFFBFBFBF, 0xFFDFDFDF, | |
| 0xFF080808, 0xFF101010, 0xFF181818, 0xFF282828, 0xFF303030, 0xFF383838, 0xFF484848, 0xFF505050, | |
| 0xFF585858, 0xFF686868, 0xFF707070, 0xFF787878, 0xFF878787, 0xFF8F8F8F, 0xFF979797, 0xFFA7A7A7, | |
| 0xFFAFAFAF, 0xFFB7B7B7, 0xFFC7C7C7, 0xFFCFCFCF, 0xFFD7D7D7, 0xFFE7E7E7, 0xFFEFEFEF, 0xFFF7F7F7, | |
| 0xFF000001, 0xFF003300, 0xFF006600, 0xFF009900, 0xFF00CC00, 0xFF00FF00, 0xFF000033, 0xFF003333, | |
| 0xFF006633, 0xFF009933, 0xFF00CC33, 0xFF00FF33, 0xFF000066, 0xFF003366, 0xFF006666, 0xFF009966, | |
| 0xFF00CC66, 0xFF00FF66, 0xFF000099, 0xFF003399, 0xFF006699, 0xFF009999, 0xFF00CC99, 0xFF00FF99, | |
| 0xFF0000CC, 0xFF0033CC, 0xFF0066CC, 0xFF0099CC, 0xFF00CCCC, 0xFF00FFCC, 0xFF0000FF, 0xFF0033FF, | |
| 0xFF0066FF, 0xFF0099FF, 0xFF00CCFF, 0xFF00FFFF, 0xFF330000, 0xFF333300, 0xFF336600, 0xFF339900, | |
| 0xFF33CC00, 0xFF33FF00, 0xFF330033, 0xFF333333, 0xFF336633, 0xFF339933, 0xFF33CC33, 0xFF33FF33, | |
| 0xFF330066, 0xFF333366, 0xFF336666, 0xFF339966, 0xFF33CC66, 0xFF33FF66, 0xFF330099, 0xFF333399, | |
| 0xFF336699, 0xFF339999, 0xFF33CC99, 0xFF33FF99, 0xFF3300CC, 0xFF3333CC, 0xFF3366CC, 0xFF3399CC, | |
| 0xFF33CCCC, 0xFF33FFCC, 0xFF3300FF, 0xFF3333FF, 0xFF3366FF, 0xFF3399FF, 0xFF33CCFF, 0xFF33FFFF, | |
| 0xFF660000, 0xFF663300, 0xFF666600, 0xFF669900, 0xFF66CC00, 0xFF66FF00, 0xFF660033, 0xFF663333, | |
| 0xFF666633, 0xFF669933, 0xFF66CC33, 0xFF66FF33, 0xFF660066, 0xFF663366, 0xFF666666, 0xFF669966, | |
| 0xFF66CC66, 0xFF66FF66, 0xFF660099, 0xFF663399, 0xFF666699, 0xFF669999, 0xFF66CC99, 0xFF66FF99, | |
| 0xFF6600CC, 0xFF6633CC, 0xFF6666CC, 0xFF6699CC, 0xFF66CCCC, 0xFF66FFCC, 0xFF6600FF, 0xFF6633FF, | |
| 0xFF6666FF, 0xFF6699FF, 0xFF66CCFF, 0xFF66FFFF, 0xFF990000, 0xFF993300, 0xFF996600, 0xFF999900, | |
| 0xFF99CC00, 0xFF99FF00, 0xFF990033, 0xFF993333, 0xFF996633, 0xFF999933, 0xFF99CC33, 0xFF99FF33, | |
| 0xFF990066, 0xFF993366, 0xFF996666, 0xFF999966, 0xFF99CC66, 0xFF99FF66, 0xFF990099, 0xFF993399, | |
| 0xFF996699, 0xFF999999, 0xFF99CC99, 0xFF99FF99, 0xFF9900CC, 0xFF9933CC, 0xFF9966CC, 0xFF9999CC, | |
| 0xFF99CCCC, 0xFF99FFCC, 0xFF9900FF, 0xFF9933FF, 0xFF9966FF, 0xFF9999FF, 0xFF99CCFF, 0xFF99FFFF, | |
| 0xFFCC0000, 0xFFCC3300, 0xFFCC6600, 0xFFCC9900, 0xFFCCCC00, 0xFFCCFF00, 0xFFCC0033, 0xFFCC3333, | |
| 0xFFCC6633, 0xFFCC9933, 0xFFCCCC33, 0xFFCCFF33, 0xFFCC0066, 0xFFCC3366, 0xFFCC6666, 0xFFCC9966, | |
| 0xFFCCCC66, 0xFFCCFF66, 0xFFCC0099, 0xFFCC3399, 0xFFCC6699, 0xFFCC9999, 0xFFCCCC99, 0xFFCCFF99, | |
| 0xFFCC00CC, 0xFFCC33CC, 0xFFCC66CC, 0xFFCC99CC, 0xFFCCCCCC, 0xFFCCFFCC, 0xFFCC00FF, 0xFFCC33FF, | |
| 0xFFCC66FF, 0xFFCC99FF, 0xFFCCCCFF, 0xFFCCFFFF, 0xFFFF0000, 0xFFFF3300, 0xFFFF6600, 0xFFFF9900, | |
| 0xFFFFCC00, 0xFFFFFF00, 0xFFFF0033, 0xFFFF3333, 0xFFFF6633, 0xFFFF9933, 0xFFFFCC33, 0xFFFFFF33, | |
| 0xFFFF0066, 0xFFFF3366, 0xFFFF6666, 0xFFFF9966, 0xFFFFCC66, 0xFFFFFF66, 0xFFFF0099, 0xFFFF3399, | |
| 0xFFFF6699, 0xFFFF9999, 0xFFFFCC99, 0xFFFFFF99, 0xFFFF00CC, 0xFFFF33CC, 0xFFFF66CC, 0xFFFF99CC, | |
| 0xFFFFCCCC, 0xFFFFFFCC, 0xFFFF00FF, 0xFFFF33FF, 0xFFFF66FF, 0xFFFF99FF, 0xFFFFCCFF, 0xFFFFFFFF]; | |
| }, | |
| primitiveBeCursor: function(argCount) { | |
| if (this.display.cursorCanvas) { | |
| var cursorForm = this.loadForm(this.stackNonInteger(argCount), true), | |
| maskForm = argCount === 1 ? this.loadForm(this.stackNonInteger(0)) : null; | |
| if (!this.success || !cursorForm) return false; | |
| var cursorCanvas = this.display.cursorCanvas, | |
| context = cursorCanvas.getContext("2d"), | |
| bounds = {left: 0, top: 0, right: cursorForm.width, bottom: cursorForm.height}; | |
| cursorCanvas.width = cursorForm.width; | |
| cursorCanvas.height = cursorForm.height; | |
| if (cursorForm.depth === 1) { | |
| if (maskForm) { | |
| cursorForm = this.cursorMergeMask(cursorForm, maskForm); | |
| this.showForm(context, cursorForm, bounds, [0x00000000, 0xFF0000FF, 0xFFFFFFFF, 0xFF000000]); | |
| } else { | |
| this.showForm(context, cursorForm, bounds, [0x00000000, 0xFF000000]); | |
| } | |
| } else { | |
| this.showForm(context, cursorForm, bounds, true); | |
| } | |
| var scale = this.display.scale || 1.0; | |
| if (cursorForm.width <= 16 && cursorForm.height <= 16) { | |
| scale = 1.0; | |
| } | |
| cursorCanvas.style.width = Math.round(cursorCanvas.width * scale) + "px"; | |
| cursorCanvas.style.height = Math.round(cursorCanvas.height * scale) + "px"; | |
| this.display.cursorOffsetX = cursorForm.offsetX * scale|0; | |
| this.display.cursorOffsetY = cursorForm.offsetY * scale|0; | |
| } | |
| this.vm.popN(argCount); | |
| return true; | |
| }, | |
| cursorMergeMask: function(cursor, mask) { | |
| // make 2-bit form from cursor and mask 1-bit forms | |
| var bits = new Uint32Array(16); | |
| for (var y = 0; y < 16; y++) { | |
| var c = cursor.bits[y], | |
| m = mask.bits[y], | |
| bit = 0x80000000, | |
| merged = 0; | |
| for (var x = 0; x < 16; x++) { | |
| merged = merged | ((m & bit) >> x) | ((c & bit) >> (x + 1)); | |
| bit = bit >>> 1; | |
| } | |
| bits[y] = merged; | |
| } | |
| return { | |
| obj: cursor.obj, bits: bits, | |
| depth: 2, width: 16, height: 16, | |
| offsetX: cursor.offsetX, offsetY: cursor.offsetY, | |
| msb: true, pixPerWord: 16, pitch: 1, | |
| } | |
| }, | |
| primitiveBeDisplay: function(argCount) { | |
| var displayObj = this.vm.stackValue(0); | |
| this.vm.specialObjects[Squeak.splOb_TheDisplay] = displayObj; | |
| this.vm.popN(argCount); // return self | |
| return true; | |
| }, | |
| primitiveReverseDisplay: function(argCount) { | |
| this.reverseDisplay = !this.reverseDisplay; | |
| this.redrawDisplay(); | |
| if (this.display.cursorCanvas) { | |
| var canvas = this.display.cursorCanvas, | |
| context = canvas.getContext("2d"), | |
| image = context.getImageData(0, 0, canvas.width, canvas.height), | |
| data = new Uint32Array(image.data.buffer); | |
| for (var i = 0; i < data.length; i++) | |
| data[i] = data[i] ^ 0x00FFFFFF; | |
| context.putImageData(image, 0, 0); | |
| } | |
| return true; | |
| }, | |
| primitiveShowDisplayRect: function(argCount) { | |
| // Force the given rectangular section of the Display to be copied to the screen. | |
| var rect = { | |
| left: this.stackInteger(3), | |
| top: this.stackInteger(1), | |
| right: this.stackInteger(2), | |
| bottom: this.stackInteger(0), | |
| }; | |
| if (!this.success) return false; | |
| this.redrawDisplay(rect); | |
| this.vm.popN(argCount); | |
| return true; | |
| }, | |
| redrawDisplay: function(rect) { | |
| var theDisplay = this.theDisplay(), | |
| bounds = {left: 0, top: 0, right: theDisplay.width, bottom: theDisplay.height}; | |
| if (rect) { | |
| if (rect.left > bounds.left) bounds.left = rect.left; | |
| if (rect.right < bounds.right) bounds.right = rect.right; | |
| if (rect.top > bounds.top) bounds.top = rect.top; | |
| if (rect.bottom < bounds.bottom) bounds.bottom = rect.bottom; | |
| } | |
| if (bounds.left < bounds.right && bounds.top < bounds.bottom) | |
| this.displayUpdate(theDisplay, bounds); | |
| }, | |
| showForm: function(ctx, form, rect, cursorColors) { | |
| if (!rect) return; | |
| var srcX = rect.left, | |
| srcY = rect.top, | |
| srcW = rect.right - srcX, | |
| srcH = rect.bottom - srcY, | |
| pixels = ctx.createImageData(srcW, srcH), | |
| pixelData = pixels.data; | |
| if (!pixelData.buffer) { // mobile IE uses a different data-structure | |
| pixelData = new Uint8Array(srcW * srcH * 4); | |
| } | |
| var dest = new Uint32Array(pixelData.buffer); | |
| switch (form.depth) { | |
| case 1: | |
| case 2: | |
| case 4: | |
| case 8: | |
| var colors = cursorColors || this.swappedColors; | |
| if (!colors) { | |
| colors = []; | |
| for (var i = 0; i < 256; i++) { | |
| var argb = this.indexedColors[i], | |
| abgr = (argb & 0xFF00FF00) // green and alpha | |
| + ((argb & 0x00FF0000) >> 16) // shift red down | |
| + ((argb & 0x000000FF) << 16); // shift blue up | |
| colors[i] = abgr; | |
| } | |
| this.swappedColors = colors; | |
| } | |
| if (this.reverseDisplay) { | |
| if (cursorColors) { | |
| colors = cursorColors.map(function(c){return c ^ 0x00FFFFFF}); | |
| } else { | |
| if (!this.reversedColors) | |
| this.reversedColors = colors.map(function(c){return c ^ 0x00FFFFFF}); | |
| colors = this.reversedColors; | |
| } | |
| } | |
| var mask = (1 << form.depth) - 1; | |
| var leftSrcShift = 32 - (srcX % form.pixPerWord + 1) * form.depth; | |
| for (var y = 0; y < srcH; y++) { | |
| var srcIndex = form.pitch * srcY + (srcX / form.pixPerWord | 0); | |
| var srcShift = leftSrcShift; | |
| var src = form.bits[srcIndex]; | |
| var dstIndex = pixels.width * y; | |
| for (var x = 0; x < srcW; x++) { | |
| dest[dstIndex++] = colors[(src >>> srcShift) & mask]; | |
| if ((srcShift -= form.depth) < 0) { | |
| srcShift = 32 - form.depth; | |
| src = form.bits[++srcIndex]; | |
| } | |
| } | |
| srcY++; | |
| }; | |
| break; | |
| case 16: | |
| var leftSrcShift = srcX % 2 ? 0 : 16; | |
| for (var y = 0; y < srcH; y++) { | |
| var srcIndex = form.pitch * srcY + (srcX / 2 | 0); | |
| var srcShift = leftSrcShift; | |
| var src = form.bits[srcIndex]; | |
| var dstIndex = pixels.width * y; | |
| for (var x = 0; x < srcW; x++) { | |
| var rgb = src >>> srcShift; | |
| dest[dstIndex++] = | |
| ((rgb & 0x7C00) >> 7) // shift red down 2*5, up 0*8 + 3 | |
| + ((rgb & 0x03E0) << 6) // shift green down 1*5, up 1*8 + 3 | |
| + ((rgb & 0x001F) << 19) // shift blue down 0*5, up 2*8 + 3 | |
| + 0xFF000000; // set alpha to opaque | |
| if ((srcShift -= 16) < 0) { | |
| srcShift = 16; | |
| src = form.bits[++srcIndex]; | |
| } | |
| } | |
| srcY++; | |
| }; | |
| break; | |
| case 32: | |
| var opaque = cursorColors ? 0 : 0xFF000000; // keep alpha for cursors | |
| for (var y = 0; y < srcH; y++) { | |
| var srcIndex = form.pitch * srcY + srcX; | |
| var dstIndex = pixels.width * y; | |
| for (var x = 0; x < srcW; x++) { | |
| var argb = form.bits[srcIndex++]; // convert ARGB -> ABGR | |
| var abgr = (argb & 0xFF00FF00) // green and alpha is okay | |
| | ((argb & 0x00FF0000) >> 16) // shift red down | |
| | ((argb & 0x000000FF) << 16) // shift blue up | |
| | opaque; // set alpha to opaque | |
| dest[dstIndex++] = abgr; | |
| } | |
| srcY++; | |
| }; | |
| break; | |
| default: throw Error("depth not implemented"); | |
| }; | |
| if (pixels.data !== pixelData) { | |
| pixels.data.set(pixelData); | |
| } | |
| ctx.putImageData(pixels, rect.left, rect.top); | |
| }, | |
| primitiveDeferDisplayUpdates: function(argCount) { | |
| var flag = this.stackBoolean(0); | |
| if (!this.success) return false; | |
| this.deferDisplayUpdates = flag; | |
| this.vm.popN(argCount); | |
| return true; | |
| }, | |
| primitiveForceDisplayUpdate: function(argCount) { | |
| this.vm.breakOut(); // show on screen | |
| this.vm.popN(argCount); | |
| return true; | |
| }, | |
| primitiveScanCharacters: function(argCount) { | |
| if (argCount !== 6) return false; | |
| // Load the arguments | |
| var kernDelta = this.stackInteger(0); | |
| var stops = this.stackNonInteger(1); | |
| var scanRightX = this.stackInteger(2); | |
| var sourceString = this.stackNonInteger(3); | |
| var scanStopIndex = this.stackInteger(4); | |
| var scanStartIndex = this.stackInteger(5); | |
| if (!this.success) return false; | |
| if (stops.pointersSize() < 258 || !sourceString.isBytes()) return false; | |
| if (!(scanStartIndex > 0 && scanStopIndex > 0 && scanStopIndex <= sourceString.bytesSize())) return false; | |
| // Load receiver and required instVars | |
| var rcvr = this.stackNonInteger(6); | |
| if (!this.success || rcvr.pointersSize() < 4) return false; | |
| var scanDestX = this.checkSmallInt(rcvr.pointers[0]); | |
| var scanLastIndex = this.checkSmallInt(rcvr.pointers[1]); | |
| var scanXTable = this.checkNonInteger(rcvr.pointers[2]); | |
| var scanMap = this.checkNonInteger(rcvr.pointers[3]); | |
| if (!this.success || scanMap.pointersSize() !== 256) return false; | |
| var maxGlyph = scanXTable.pointersSize() - 2; | |
| // Okay, here we go. We have eliminated nearly all failure | |
| // conditions, to optimize the inner fetches. | |
| var EndOfRun = 257; | |
| var CrossedX = 258; | |
| var scanLastIndex = scanStartIndex; | |
| while (scanLastIndex <= scanStopIndex) { | |
| // Known to be okay since scanStartIndex > 0 and scanStopIndex <= sourceString size | |
| var ascii = sourceString.bytes[scanLastIndex - 1]; | |
| // Known to be okay since stops size >= 258 | |
| var stopReason = stops.pointers[ascii]; | |
| if (!stopReason.isNil) { | |
| // Store everything back and get out of here since some stop conditionn needs to be checked" | |
| this.ensureSmallInt(scanDestX); if (!this.success) return false; | |
| rcvr.pointers[0] = scanDestX; | |
| rcvr.pointers[1] = scanLastIndex; | |
| return this.popNandPushIfOK(7, stopReason); | |
| } | |
| // Known to be okay since scanMap size = 256 | |
| var glyphIndex = this.checkSmallInt(scanMap.pointers[ascii]); | |
| // fail if the glyphIndex is out of range | |
| if (!this.success || glyphIndex < 0 || glyphIndex > maxGlyph) return false; | |
| var sourceX = this.checkSmallInt(scanXTable.pointers[glyphIndex]); | |
| var sourceX2 = this.checkSmallInt(scanXTable.pointers[glyphIndex + 1]); | |
| // Above may fail if non-integer entries in scanXTable | |
| if (!this.success) return false; | |
| var nextDestX = scanDestX + sourceX2 - sourceX; | |
| if (nextDestX > scanRightX) { | |
| // Store everything back and get out of here since we got to the right edge | |
| this.ensureSmallInt(scanDestX); if (!this.success) return false; | |
| rcvr.pointers[0] = scanDestX; | |
| rcvr.pointers[1] = scanLastIndex; | |
| stopReason = stops.pointers[CrossedX - 1]; | |
| return this.popNandPushIfOK(7, stopReason); | |
| } | |
| scanDestX = nextDestX + kernDelta; | |
| scanLastIndex = scanLastIndex + 1; | |
| } | |
| this.ensureSmallInt(scanDestX); if (!this.success) return false; | |
| rcvr.pointers[0] = scanDestX; | |
| rcvr.pointers[1] = scanStopIndex; | |
| stopReason = stops.pointers[EndOfRun - 1]; | |
| return this.popNandPushIfOK(7, stopReason); | |
| }, | |
| primitiveScreenSize: function(argCount) { | |
| var display = this.display, | |
| w = display.width || display.context.canvas.width, | |
| h = display.height || display.context.canvas.height; | |
| return this.popNandPushIfOK(argCount+1, this.makePointWithXandY(w, h)); | |
| }, | |
| primitiveScreenScaleFactor: function(argCount) { | |
| var scaleFactor = (this.display.highdpi ? window.devicePixelRatio : 1) || 1; | |
| return this.popNandPushIfOK(argCount+1, scaleFactor); | |
| }, | |
| primitiveSetFullScreen: function(argCount) { | |
| var flag = this.stackBoolean(0); | |
| if (!this.success) return false; | |
| if (this.display.fullscreen != flag) { | |
| if (this.display.fullscreenRequest) { | |
| // freeze until we get the right display size | |
| var unfreeze = this.vm.freeze(); | |
| this.display.fullscreenRequest(flag, function thenDo() { | |
| unfreeze(); | |
| }); | |
| } else { | |
| this.display.fullscreen = flag; | |
| this.vm.breakOut(); // let VM go into fullscreen mode | |
| } | |
| } | |
| this.vm.popN(argCount); | |
| return true; | |
| }, | |
| primitiveTestDisplayDepth: function(argCount) { | |
| var supportedDepths = [1, 2, 4, 8, 16, 32]; // match showForm | |
| return this.popNandPushBoolIfOK(argCount+1, supportedDepths.indexOf(this.stackInteger(0)) >= 0); | |
| }, | |
| loadForm: function(formObj, withOffset) { | |
| if (formObj.isNil) return null; | |
| var form = { | |
| obj: formObj, | |
| bits: formObj.pointers[Squeak.Form_bits].wordsOrBytes(), | |
| depth: formObj.pointers[Squeak.Form_depth], | |
| width: formObj.pointers[Squeak.Form_width], | |
| height: formObj.pointers[Squeak.Form_height], | |
| } | |
| if (withOffset) { | |
| var offset = formObj.pointers[Squeak.Form_offset]; | |
| form.offsetX = offset.pointers ? offset.pointers[Squeak.Point_x] : 0; | |
| form.offsetY = offset.pointers ? offset.pointers[Squeak.Point_y] : 0; | |
| } | |
| if (form.width === 0 || form.height === 0) return form; | |
| if (!(form.width > 0 && form.height > 0)) return null; | |
| form.msb = form.depth > 0; | |
| if (!form.msb) form.depth = -form.depth; | |
| if (!(form.depth > 0)) return null; // happens if not int | |
| form.pixPerWord = 32 / form.depth; | |
| form.pitch = (form.width + (form.pixPerWord - 1)) / form.pixPerWord | 0; | |
| if (form.bits.length !== (form.pitch * form.height)) { | |
| if (form.bits.length > (form.pitch * form.height)) { | |
| this.vm.warnOnce("loadForm(): " + form.bits.length + " !== " + form.pitch + "*" + form.height + "=" + (form.pitch*form.height)); | |
| } else { | |
| return null; | |
| } | |
| } | |
| return form; | |
| }, | |
| theDisplay: function() { | |
| return this.loadForm(this.vm.specialObjects[Squeak.splOb_TheDisplay]); | |
| }, | |
| displayDirty: function(form, rect) { | |
| if (!this.deferDisplayUpdates | |
| && form == this.vm.specialObjects[Squeak.splOb_TheDisplay]) | |
| this.displayUpdate(this.theDisplay(), rect); | |
| }, | |
| displayUpdate: function(form, rect) { | |
| this.showForm(this.display.context, form, rect); | |
| this.display.lastTick = this.vm.lastTick; | |
| this.display.idle = 0; | |
| }, | |
| primitiveBeep: function(argCount) { | |
| var ctx = Squeak.startAudioOut(); | |
| if (ctx) { | |
| var beep = ctx.createOscillator(); | |
| beep.connect(ctx.destination); | |
| beep.type = 'square'; | |
| beep.frequency.value = 880; | |
| beep.start(); | |
| beep.stop(ctx.currentTime + 0.05); | |
| } else { | |
| this.vm.warnOnce("could not initialize audio"); | |
| } | |
| return this.popNIfOK(argCount); | |
| }, | |
| hostWindow_primitiveHostWindowTitle: function(argCount) { | |
| if (this.display.setTitle) { | |
| var utf8 = this.stackNonInteger(0).bytes; | |
| var title = (new TextDecoder()).decode(utf8); | |
| this.display.setTitle(title); | |
| } | |
| return this.popNIfOK(argCount); | |
| } | |
| }); | |