mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2025-01-11 10:39:56 -05:00
Layer group ordering.
This commit is contained in:
parent
1d98a7b5fe
commit
67b114b9c9
9 changed files with 102 additions and 36 deletions
|
@ -2,6 +2,7 @@ const Cast = require('../util/cast');
|
||||||
const Clone = require('../util/clone');
|
const Clone = require('../util/clone');
|
||||||
const RenderedTarget = require('../sprites/rendered-target');
|
const RenderedTarget = require('../sprites/rendered-target');
|
||||||
const uid = require('../util/uid');
|
const uid = require('../util/uid');
|
||||||
|
const StageLayering = require('../engine/stage-layering');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {object} BubbleState - the bubble state associated with a particular target.
|
* @typedef {object} BubbleState - the bubble state associated with a particular target.
|
||||||
|
@ -91,7 +92,7 @@ class Scratch3LooksBlocks {
|
||||||
_onTargetWillExit (target) {
|
_onTargetWillExit (target) {
|
||||||
const bubbleState = this._getBubbleState(target);
|
const bubbleState = this._getBubbleState(target);
|
||||||
if (bubbleState.drawableId && bubbleState.skinId) {
|
if (bubbleState.drawableId && bubbleState.skinId) {
|
||||||
this.runtime.renderer.destroyDrawable(bubbleState.drawableId);
|
this.runtime.renderer.destroyDrawable(bubbleState.drawableId, StageLayering.BUBBLE_LAYER);
|
||||||
this.runtime.renderer.destroySkin(bubbleState.skinId);
|
this.runtime.renderer.destroySkin(bubbleState.skinId);
|
||||||
bubbleState.drawableId = null;
|
bubbleState.drawableId = null;
|
||||||
bubbleState.skinId = null;
|
bubbleState.skinId = null;
|
||||||
|
@ -195,10 +196,9 @@ class Scratch3LooksBlocks {
|
||||||
bubbleState.onSpriteRight = false;
|
bubbleState.onSpriteRight = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bubbleState.drawableId = this.runtime.renderer.createDrawable();
|
bubbleState.drawableId = this.runtime.renderer.createDrawable(StageLayering.BUBBLE_LAYER);
|
||||||
bubbleState.skinId = this.runtime.renderer.createTextSkin(type, text, bubbleState.onSpriteRight, [0, 0]);
|
bubbleState.skinId = this.runtime.renderer.createTextSkin(type, text, bubbleState.onSpriteRight, [0, 0]);
|
||||||
|
|
||||||
this.runtime.renderer.setDrawableOrder(bubbleState.drawableId, Infinity);
|
|
||||||
this.runtime.renderer.updateDrawableProperties(bubbleState.drawableId, {
|
this.runtime.renderer.updateDrawableProperties(bubbleState.drawableId, {
|
||||||
skinId: bubbleState.skinId
|
skinId: bubbleState.skinId
|
||||||
});
|
});
|
||||||
|
|
|
@ -12,6 +12,7 @@ const TargetType = require('../extension-support/target-type');
|
||||||
const Thread = require('./thread');
|
const Thread = require('./thread');
|
||||||
const log = require('../util/log');
|
const log = require('../util/log');
|
||||||
const maybeFormatMessage = require('../util/maybe-format-message');
|
const maybeFormatMessage = require('../util/maybe-format-message');
|
||||||
|
const StageLayering = require('./stage-layering');
|
||||||
|
|
||||||
// Virtual I/O devices.
|
// Virtual I/O devices.
|
||||||
const Clock = require('../io/clock');
|
const Clock = require('../io/clock');
|
||||||
|
@ -915,6 +916,7 @@ class Runtime extends EventEmitter {
|
||||||
*/
|
*/
|
||||||
attachRenderer (renderer) {
|
attachRenderer (renderer) {
|
||||||
this.renderer = renderer;
|
this.renderer = renderer;
|
||||||
|
this.renderer.setLayerGroupOrdering(StageLayering.LAYER_GROUPS);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
63
src/engine/stage-layering.js
Normal file
63
src/engine/stage-layering.js
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
class StageLayering {
|
||||||
|
static get BACKGROUND_LAYER () {
|
||||||
|
return 'background';
|
||||||
|
}
|
||||||
|
|
||||||
|
static get EXTENSION_LAYER () {
|
||||||
|
return 'extensions';
|
||||||
|
}
|
||||||
|
|
||||||
|
static get SPRITE_LAYER () {
|
||||||
|
return 'sprite';
|
||||||
|
}
|
||||||
|
|
||||||
|
static get BUBBLE_LAYER () {
|
||||||
|
return 'bubble';
|
||||||
|
}
|
||||||
|
|
||||||
|
static get BACKGROUND_ORDER () {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Video should be in the back of the extension group
|
||||||
|
static get VIDEO_ORDER () {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get PEN_ORDER () {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Order of layer groups relative to each other,
|
||||||
|
// and ordering style of each
|
||||||
|
// Currently extensions are the only layer group
|
||||||
|
// that have an explicit ordering (e.g. video must be behind pen).
|
||||||
|
// All other groups here are ordered based on when they get added
|
||||||
|
static get LAYER_GROUPS () {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
group: StageLayering.BACKGROUND_LAYER,
|
||||||
|
// This is a weird use case for a layer group ordering style,
|
||||||
|
// because in the main Scratch use case, this group has only one item,
|
||||||
|
// so ordering of the items doesn't really matter.
|
||||||
|
explicitOrdering: false
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
group: StageLayering.EXTENSION_LAYER,
|
||||||
|
explicitOrdering: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
group: StageLayering.SPRITE_LAYER,
|
||||||
|
explicitOrdering: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
group: StageLayering.BUBBLE_LAYER,
|
||||||
|
explicitOrdering: false
|
||||||
|
}
|
||||||
|
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = StageLayering;
|
|
@ -7,6 +7,7 @@ const formatMessage = require('format-message');
|
||||||
const MathUtil = require('../../util/math-util');
|
const MathUtil = require('../../util/math-util');
|
||||||
const RenderedTarget = require('../../sprites/rendered-target');
|
const RenderedTarget = require('../../sprites/rendered-target');
|
||||||
const log = require('../../util/log');
|
const log = require('../../util/log');
|
||||||
|
const StageLayering = require('../../engine/stage-layering');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Icon svg to be displayed at the left edge of each extension block, encoded as a data URI.
|
* Icon svg to be displayed at the left edge of each extension block, encoded as a data URI.
|
||||||
|
@ -87,15 +88,6 @@ class Scratch3PenBlocks {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Place the pen layer in front of the backdrop but behind everything else.
|
|
||||||
* We should probably handle this somewhere else... somewhere central that knows about pen, backdrop, video, etc.
|
|
||||||
* Maybe it should be in the GUI?
|
|
||||||
* @type {int}
|
|
||||||
*/
|
|
||||||
static get PEN_ORDER () {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The minimum and maximum allowed pen size.
|
* The minimum and maximum allowed pen size.
|
||||||
|
@ -136,8 +128,8 @@ class Scratch3PenBlocks {
|
||||||
_getPenLayerID () {
|
_getPenLayerID () {
|
||||||
if (this._penSkinId < 0 && this.runtime.renderer) {
|
if (this._penSkinId < 0 && this.runtime.renderer) {
|
||||||
this._penSkinId = this.runtime.renderer.createPenSkin();
|
this._penSkinId = this.runtime.renderer.createPenSkin();
|
||||||
this._penDrawableId = this.runtime.renderer.createDrawable();
|
this._penDrawableId = this.runtime.renderer.createDrawable(
|
||||||
this.runtime.renderer.setDrawableOrder(this._penDrawableId, Scratch3PenBlocks.PEN_ORDER);
|
StageLayering.EXTENSION_LAYER, StageLayering.PEN_ORDER);
|
||||||
this.runtime.renderer.updateDrawableProperties(this._penDrawableId, {skinId: this._penSkinId});
|
this.runtime.renderer.updateDrawableProperties(this._penDrawableId, {skinId: this._penSkinId});
|
||||||
}
|
}
|
||||||
return this._penSkinId;
|
return this._penSkinId;
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
const StageLayering = require('../engine/stage-layering');
|
||||||
|
|
||||||
class Video {
|
class Video {
|
||||||
constructor (runtime) {
|
constructor (runtime) {
|
||||||
this.runtime = runtime;
|
this.runtime = runtime;
|
||||||
|
@ -144,11 +146,7 @@ class Video {
|
||||||
if (this._skinId === -1 && this._skin === null && this._drawable === -1) {
|
if (this._skinId === -1 && this._skin === null && this._drawable === -1) {
|
||||||
this._skinId = renderer.createPenSkin();
|
this._skinId = renderer.createPenSkin();
|
||||||
this._skin = renderer._allSkins[this._skinId];
|
this._skin = renderer._allSkins[this._skinId];
|
||||||
this._drawable = renderer.createDrawable();
|
this._drawable = renderer.createDrawable(StageLayering.EXTENSION_LAYER, StageLayering.VIDEO_ORDER);
|
||||||
renderer.setDrawableOrder(
|
|
||||||
this._drawable,
|
|
||||||
Video.ORDER
|
|
||||||
);
|
|
||||||
renderer.updateDrawableProperties(this._drawable, {
|
renderer.updateDrawableProperties(this._drawable, {
|
||||||
skinId: this._skinId
|
skinId: this._skinId
|
||||||
});
|
});
|
||||||
|
|
|
@ -327,7 +327,7 @@ const parseScratchObject = function (object, runtime, extensions, topLevel, zip)
|
||||||
// Blocks container for this object.
|
// Blocks container for this object.
|
||||||
const blocks = new Blocks();
|
const blocks = new Blocks();
|
||||||
// @todo: For now, load all Scratch objects (stage/sprites) as a Sprite.
|
// @todo: For now, load all Scratch objects (stage/sprites) as a Sprite.
|
||||||
const sprite = new Sprite(blocks, runtime);
|
const sprite = new Sprite(blocks, runtime, topLevel /* whether this sprite is a stge or not */);
|
||||||
// Sprite/stage name from JSON.
|
// Sprite/stage name from JSON.
|
||||||
if (object.hasOwnProperty('objName')) {
|
if (object.hasOwnProperty('objName')) {
|
||||||
sprite.name = topLevel ? 'Stage' : object.objName;
|
sprite.name = topLevel ? 'Stage' : object.objName;
|
||||||
|
|
|
@ -684,7 +684,7 @@ const parseScratchObject = function (object, runtime, extensions, zip) {
|
||||||
const blocks = new Blocks();
|
const blocks = new Blocks();
|
||||||
|
|
||||||
// @todo: For now, load all Scratch objects (stage/sprites) as a Sprite.
|
// @todo: For now, load all Scratch objects (stage/sprites) as a Sprite.
|
||||||
const sprite = new Sprite(blocks, runtime);
|
const sprite = new Sprite(blocks, runtime, object.isStage);
|
||||||
|
|
||||||
// Sprite/stage name from JSON.
|
// Sprite/stage name from JSON.
|
||||||
if (object.hasOwnProperty('name')) {
|
if (object.hasOwnProperty('name')) {
|
||||||
|
|
|
@ -2,6 +2,7 @@ const log = require('../util/log');
|
||||||
const MathUtil = require('../util/math-util');
|
const MathUtil = require('../util/math-util');
|
||||||
const StringUtil = require('../util/string-util');
|
const StringUtil = require('../util/string-util');
|
||||||
const Target = require('../engine/target');
|
const Target = require('../engine/target');
|
||||||
|
const StageLayering = require('../engine/stage-layering');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rendered target: instance of a sprite (clone), or the stage.
|
* Rendered target: instance of a sprite (clone), or the stage.
|
||||||
|
@ -156,9 +157,10 @@ class RenderedTarget extends Target {
|
||||||
/**
|
/**
|
||||||
* Create a drawable with the this.renderer.
|
* Create a drawable with the this.renderer.
|
||||||
*/
|
*/
|
||||||
initDrawable () {
|
initDrawable (isStage) {
|
||||||
if (this.renderer) {
|
if (this.renderer) {
|
||||||
this.drawableID = this.renderer.createDrawable();
|
this.drawableID = this.renderer.createDrawable(isStage ?
|
||||||
|
StageLayering.BACKGROUND_LAYER : StageLayering.SPRITE_LAYER);
|
||||||
}
|
}
|
||||||
// If we're a clone, start the hats.
|
// If we're a clone, start the hats.
|
||||||
if (!this.isOriginal) {
|
if (!this.isOriginal) {
|
||||||
|
@ -800,18 +802,22 @@ class RenderedTarget extends Target {
|
||||||
/**
|
/**
|
||||||
* Move to the front layer.
|
* Move to the front layer.
|
||||||
*/
|
*/
|
||||||
goToFront () {
|
goToFront () { // This should only ever be used for sprites
|
||||||
if (this.renderer) {
|
if (this.renderer) {
|
||||||
this.renderer.setDrawableOrder(this.drawableID, Infinity);
|
// Let the renderer re-order the sprite based on its knowledge
|
||||||
|
// of what layers are present
|
||||||
|
this.renderer.setDrawableOrder(this.drawableID, Infinity, StageLayering.SPRITE_LAYER);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Move to the back layer.
|
* Move to the back layer.
|
||||||
*/
|
*/
|
||||||
goToBack () {
|
goToBack () { // This should only ever be used for sprites
|
||||||
if (this.renderer) {
|
if (this.renderer) {
|
||||||
this.renderer.setDrawableOrder(this.drawableID, -Infinity, false, 1);
|
// Let the renderer re-order the sprite based on its knowledge
|
||||||
|
// of what layers are present
|
||||||
|
this.renderer.setDrawableOrder(this.drawableID, -Infinity, StageLayering.SPRITE_LAYER, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -821,7 +827,7 @@ class RenderedTarget extends Target {
|
||||||
*/
|
*/
|
||||||
goForwardLayers (nLayers) {
|
goForwardLayers (nLayers) {
|
||||||
if (this.renderer) {
|
if (this.renderer) {
|
||||||
this.renderer.setDrawableOrder(this.drawableID, nLayers, true, 1);
|
this.renderer.setDrawableOrder(this.drawableID, nLayers, StageLayering.SPRITE_LAYER, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -831,7 +837,7 @@ class RenderedTarget extends Target {
|
||||||
*/
|
*/
|
||||||
goBackwardLayers (nLayers) {
|
goBackwardLayers (nLayers) {
|
||||||
if (this.renderer) {
|
if (this.renderer) {
|
||||||
this.renderer.setDrawableOrder(this.drawableID, -nLayers, true, 1);
|
this.renderer.setDrawableOrder(this.drawableID, -nLayers, StageLayering.SPRITE_LAYER, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -842,8 +848,8 @@ class RenderedTarget extends Target {
|
||||||
goBehindOther (other) {
|
goBehindOther (other) {
|
||||||
if (this.renderer) {
|
if (this.renderer) {
|
||||||
const otherLayer = this.renderer.setDrawableOrder(
|
const otherLayer = this.renderer.setDrawableOrder(
|
||||||
other.drawableID, 0, true);
|
other.drawableID, 0, StageLayering.SPRITE_LAYER, true);
|
||||||
this.renderer.setDrawableOrder(this.drawableID, otherLayer);
|
this.renderer.setDrawableOrder(this.drawableID, otherLayer, StageLayering.SPRITE_LAYER);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -912,7 +918,7 @@ class RenderedTarget extends Target {
|
||||||
newClone.effects = JSON.parse(JSON.stringify(this.effects));
|
newClone.effects = JSON.parse(JSON.stringify(this.effects));
|
||||||
newClone.variables = JSON.parse(JSON.stringify(this.variables));
|
newClone.variables = JSON.parse(JSON.stringify(this.variables));
|
||||||
newClone.lists = JSON.parse(JSON.stringify(this.lists));
|
newClone.lists = JSON.parse(JSON.stringify(this.lists));
|
||||||
newClone.initDrawable();
|
newClone.initDrawable(false); // this,sprite is not a stage if we're calling makeClone on it
|
||||||
newClone.updateAllDrawableProperties();
|
newClone.updateAllDrawableProperties();
|
||||||
// Place behind the current target.
|
// Place behind the current target.
|
||||||
newClone.goBehindOther(this);
|
newClone.goBehindOther(this);
|
||||||
|
@ -1049,7 +1055,7 @@ class RenderedTarget extends Target {
|
||||||
this.runtime.stopForTarget(this);
|
this.runtime.stopForTarget(this);
|
||||||
this.sprite.removeClone(this);
|
this.sprite.removeClone(this);
|
||||||
if (this.renderer && this.drawableID !== null) {
|
if (this.renderer && this.drawableID !== null) {
|
||||||
this.renderer.destroyDrawable(this.drawableID);
|
this.renderer.destroyDrawable(this.drawableID, StageLayering.SPRITE_LAYER);
|
||||||
if (this.visible) {
|
if (this.visible) {
|
||||||
this.emit(RenderedTarget.EVENT_TARGET_VISUAL_CHANGE, this);
|
this.emit(RenderedTarget.EVENT_TARGET_VISUAL_CHANGE, this);
|
||||||
this.runtime.requestRedraw();
|
this.runtime.requestRedraw();
|
||||||
|
|
|
@ -10,9 +10,10 @@ class Sprite {
|
||||||
* All clones of a sprite have shared blocks, shared costumes, shared variables.
|
* All clones of a sprite have shared blocks, shared costumes, shared variables.
|
||||||
* @param {?Blocks} blocks Shared blocks object for all clones of sprite.
|
* @param {?Blocks} blocks Shared blocks object for all clones of sprite.
|
||||||
* @param {Runtime} runtime Reference to the runtime.
|
* @param {Runtime} runtime Reference to the runtime.
|
||||||
|
* @param {boolean=} isStage Whether or not this sprite is a stage
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
constructor (blocks, runtime) {
|
constructor (blocks, runtime, isStage) {
|
||||||
this.runtime = runtime;
|
this.runtime = runtime;
|
||||||
if (!blocks) {
|
if (!blocks) {
|
||||||
// Shared set of blocks for all clones.
|
// Shared set of blocks for all clones.
|
||||||
|
@ -46,8 +47,12 @@ class Sprite {
|
||||||
* @type {Array.<!RenderedTarget>}
|
* @type {Array.<!RenderedTarget>}
|
||||||
*/
|
*/
|
||||||
this.clones = [];
|
this.clones = [];
|
||||||
|
|
||||||
|
// Needed for figuring out whether the associated drawable should
|
||||||
|
// go in the background layer or sprite layer
|
||||||
|
this.isStage = isStage || false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add an array of costumes, taking care to avoid duplicate names.
|
* Add an array of costumes, taking care to avoid duplicate names.
|
||||||
* @param {!Array<object>} costumes Array of objects representing costumes.
|
* @param {!Array<object>} costumes Array of objects representing costumes.
|
||||||
|
@ -103,7 +108,7 @@ class Sprite {
|
||||||
this.clones.push(newClone);
|
this.clones.push(newClone);
|
||||||
newClone.initAudio();
|
newClone.initAudio();
|
||||||
if (newClone.isOriginal) {
|
if (newClone.isOriginal) {
|
||||||
newClone.initDrawable();
|
newClone.initDrawable(this.isStage);
|
||||||
this.runtime.fireTargetWasCreated(newClone);
|
this.runtime.fireTargetWasCreated(newClone);
|
||||||
} else {
|
} else {
|
||||||
this.runtime.fireTargetWasCreated(newClone, this.clones[0]);
|
this.runtime.fireTargetWasCreated(newClone, this.clones[0]);
|
||||||
|
|
Loading…
Reference in a new issue