mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2024-12-24 06:52:40 -05:00
Merge pull request #1204 from paulkaplan/reorder-apis
Add methods for reordering costumes and sounds
This commit is contained in:
commit
db3fca17bc
4 changed files with 236 additions and 2 deletions
|
@ -488,7 +488,7 @@ class RenderedTarget extends Target {
|
||||||
* @param {?int} index Index at which to add costume
|
* @param {?int} index Index at which to add costume
|
||||||
*/
|
*/
|
||||||
addCostume (costumeObject, index) {
|
addCostume (costumeObject, index) {
|
||||||
if (index) {
|
if (typeof index === 'number' && !isNaN(index)) {
|
||||||
this.sprite.addCostumeAt(costumeObject, index);
|
this.sprite.addCostumeAt(costumeObject, index);
|
||||||
} else {
|
} else {
|
||||||
this.sprite.addCostumeAt(costumeObject, this.sprite.costumes.length);
|
this.sprite.addCostumeAt(costumeObject, this.sprite.costumes.length);
|
||||||
|
@ -551,7 +551,7 @@ class RenderedTarget extends Target {
|
||||||
addSound (soundObject, index) {
|
addSound (soundObject, index) {
|
||||||
const usedNames = this.sprite.sounds.map(sound => sound.name);
|
const usedNames = this.sprite.sounds.map(sound => sound.name);
|
||||||
soundObject.name = StringUtil.unusedName(soundObject.name, usedNames);
|
soundObject.name = StringUtil.unusedName(soundObject.name, usedNames);
|
||||||
if (index) {
|
if (typeof index === 'number' && !isNaN(index)) {
|
||||||
this.sprite.sounds.splice(index, 0, soundObject);
|
this.sprite.sounds.splice(index, 0, soundObject);
|
||||||
} else {
|
} else {
|
||||||
this.sprite.sounds.push(soundObject);
|
this.sprite.sounds.push(soundObject);
|
||||||
|
@ -640,6 +640,47 @@ class RenderedTarget extends Target {
|
||||||
return this.sprite.costumes;
|
return this.sprite.costumes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reorder costume list by moving costume at costumeIndex to newIndex.
|
||||||
|
* @param {!number} costumeIndex Index of the costume to move.
|
||||||
|
* @param {!number} newIndex New index for that costume.
|
||||||
|
* @returns {boolean} If a change occurred (i.e. if the indices do not match)
|
||||||
|
*/
|
||||||
|
reorderCostume (costumeIndex, newIndex) {
|
||||||
|
newIndex = MathUtil.clamp(newIndex, 0, this.sprite.costumes.length - 1);
|
||||||
|
costumeIndex = MathUtil.clamp(costumeIndex, 0, this.sprite.costumes.length - 1);
|
||||||
|
|
||||||
|
if (newIndex === costumeIndex) return false;
|
||||||
|
|
||||||
|
const currentCostume = this.getCurrentCostume();
|
||||||
|
const costume = this.sprite.costumes[costumeIndex];
|
||||||
|
|
||||||
|
// Use the sprite method for deleting costumes because setCostume is handled manually
|
||||||
|
this.sprite.deleteCostumeAt(costumeIndex);
|
||||||
|
|
||||||
|
this.addCostume(costume, newIndex);
|
||||||
|
this.currentCostume = this.getCostumeIndexByName(currentCostume.name);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reorder sound list by moving sound at soundIndex to newIndex.
|
||||||
|
* @param {!number} soundIndex Index of the sound to move.
|
||||||
|
* @param {!number} newIndex New index for that sound.
|
||||||
|
* @returns {boolean} If a change occurred (i.e. if the indices do not match)
|
||||||
|
*/
|
||||||
|
reorderSound (soundIndex, newIndex) {
|
||||||
|
newIndex = MathUtil.clamp(newIndex, 0, this.sprite.sounds.length - 1);
|
||||||
|
soundIndex = MathUtil.clamp(soundIndex, 0, this.sprite.sounds.length - 1);
|
||||||
|
|
||||||
|
if (newIndex === soundIndex) return false;
|
||||||
|
|
||||||
|
const sound = this.sprite.sounds[soundIndex];
|
||||||
|
this.deleteSound(soundIndex);
|
||||||
|
this.addSound(sound, newIndex);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get full sound list
|
* Get full sound list
|
||||||
* @return {object[]} list of sounds
|
* @return {object[]} list of sounds
|
||||||
|
|
|
@ -1033,6 +1033,36 @@ class VirtualMachine extends EventEmitter {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reorder the costumes of a target if it exists. Return whether it succeeded.
|
||||||
|
* @param {!string} targetId ID of the target which owns the costumes.
|
||||||
|
* @param {!number} costumeIndex index of the costume to move.
|
||||||
|
* @param {!number} newIndex index that the costume should be moved to.
|
||||||
|
* @returns {boolean} Whether a costume was reordered.
|
||||||
|
*/
|
||||||
|
reorderCostume (targetId, costumeIndex, newIndex) {
|
||||||
|
const target = this.runtime.getTargetById(targetId);
|
||||||
|
if (target) {
|
||||||
|
return target.reorderCostume(costumeIndex, newIndex);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reorder the sounds of a target if it exists. Return whether it occured.
|
||||||
|
* @param {!string} targetId ID of the target which owns the sounds.
|
||||||
|
* @param {!number} soundIndex index of the sound to move.
|
||||||
|
* @param {!number} newIndex index that the sound should be moved to.
|
||||||
|
* @returns {boolean} Whether a sound was reordered.
|
||||||
|
*/
|
||||||
|
reorderSound (targetId, soundIndex, newIndex) {
|
||||||
|
const target = this.runtime.getTargetById(targetId);
|
||||||
|
if (target) {
|
||||||
|
return target.reorderSound(soundIndex, newIndex);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Put a target into a "drag" state, during which its X/Y positions will be unaffected
|
* Put a target into a "drag" state, during which its X/Y positions will be unaffected
|
||||||
* by blocks.
|
* by blocks.
|
||||||
|
|
|
@ -427,3 +427,104 @@ test('#renameCostume does not duplicate names', t => {
|
||||||
t.equal(a.sprite.costumes[1].name, 'first2');
|
t.equal(a.sprite.costumes[1].name, 'first2');
|
||||||
t.end();
|
t.end();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('#reorderCostume', t => {
|
||||||
|
const o1 = {id: 0};
|
||||||
|
const o2 = {id: 1};
|
||||||
|
const o3 = {id: 2};
|
||||||
|
const o4 = {id: 3};
|
||||||
|
const o5 = {id: 4};
|
||||||
|
const s = new Sprite();
|
||||||
|
const r = new Runtime();
|
||||||
|
s.costumes = [o1, o2, o3, o4, o5];
|
||||||
|
const a = new RenderedTarget(s, r);
|
||||||
|
const renderer = new FakeRenderer();
|
||||||
|
a.renderer = renderer;
|
||||||
|
|
||||||
|
const resetCostumes = () => {
|
||||||
|
a.setCostume(0);
|
||||||
|
s.costumes = [o1, o2, o3, o4, o5];
|
||||||
|
};
|
||||||
|
const costumeIds = () => a.sprite.costumes.map(c => c.id);
|
||||||
|
|
||||||
|
resetCostumes();
|
||||||
|
t.deepEquals(costumeIds(), [0, 1, 2, 3, 4]);
|
||||||
|
t.equals(a.currentCostume, 0);
|
||||||
|
|
||||||
|
// Returns false if the costumes are the same and no change occurred
|
||||||
|
t.equal(a.reorderCostume(3, 3), false);
|
||||||
|
t.equal(a.reorderCostume(999, 5000), false); // Clamped to the same values.
|
||||||
|
t.equal(a.reorderCostume(-999, -5000), false);
|
||||||
|
|
||||||
|
// Make sure reordering up and down works and current costume follows
|
||||||
|
resetCostumes();
|
||||||
|
t.equal(a.reorderCostume(0, 3), true);
|
||||||
|
t.deepEquals(costumeIds(), [1, 2, 3, 0, 4]);
|
||||||
|
t.equals(a.currentCostume, 3); // Index of id=0
|
||||||
|
|
||||||
|
resetCostumes();
|
||||||
|
a.setCostume(1);
|
||||||
|
t.equal(a.reorderCostume(3, 1), true);
|
||||||
|
t.deepEquals(costumeIds(), [0, 3, 1, 2, 4]);
|
||||||
|
t.equals(a.currentCostume, 2); // Index of id=1
|
||||||
|
|
||||||
|
// Out of bounds indices get clamped
|
||||||
|
resetCostumes();
|
||||||
|
t.equal(a.reorderCostume(10, 0), true);
|
||||||
|
t.deepEquals(costumeIds(), [4, 0, 1, 2, 3]);
|
||||||
|
t.equals(a.currentCostume, 1); // Index of id=0
|
||||||
|
|
||||||
|
resetCostumes();
|
||||||
|
t.equal(a.reorderCostume(2, -1000), true);
|
||||||
|
t.deepEquals(costumeIds(), [2, 0, 1, 3, 4]);
|
||||||
|
t.equals(a.currentCostume, 1); // Index of id=0
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('#reorderSound', t => {
|
||||||
|
const o1 = {id: 0, name: 'name0'};
|
||||||
|
const o2 = {id: 1, name: 'name1'};
|
||||||
|
const o3 = {id: 2, name: 'name2'};
|
||||||
|
const o4 = {id: 3, name: 'name3'};
|
||||||
|
const o5 = {id: 4, name: 'name4'};
|
||||||
|
const s = new Sprite();
|
||||||
|
const r = new Runtime();
|
||||||
|
s.sounds = [o1, o2, o3, o4, o5];
|
||||||
|
const a = new RenderedTarget(s, r);
|
||||||
|
const renderer = new FakeRenderer();
|
||||||
|
a.renderer = renderer;
|
||||||
|
|
||||||
|
const resetSounds = () => {
|
||||||
|
s.sounds = [o1, o2, o3, o4, o5];
|
||||||
|
};
|
||||||
|
const soundIds = () => a.sprite.sounds.map(c => c.id);
|
||||||
|
|
||||||
|
resetSounds();
|
||||||
|
t.deepEquals(soundIds(), [0, 1, 2, 3, 4]);
|
||||||
|
|
||||||
|
// Return false if indices are the same and no change occurred.
|
||||||
|
t.equal(a.reorderSound(3, 3), false);
|
||||||
|
t.equal(a.reorderSound(100000, 99999), false); // Clamped to the same values
|
||||||
|
t.equal(a.reorderSound(-100000, -99999), false);
|
||||||
|
|
||||||
|
// Make sure reordering up and down works and current sound follows
|
||||||
|
resetSounds();
|
||||||
|
t.equal(a.reorderSound(0, 3), true);
|
||||||
|
t.deepEquals(soundIds(), [1, 2, 3, 0, 4]);
|
||||||
|
|
||||||
|
resetSounds();
|
||||||
|
t.equal(a.reorderSound(3, 1), true);
|
||||||
|
t.deepEquals(soundIds(), [0, 3, 1, 2, 4]);
|
||||||
|
|
||||||
|
// Out of bounds indices get clamped
|
||||||
|
resetSounds();
|
||||||
|
t.equal(a.reorderSound(10, 0), true);
|
||||||
|
t.deepEquals(soundIds(), [4, 0, 1, 2, 3]);
|
||||||
|
|
||||||
|
resetSounds();
|
||||||
|
t.equal(a.reorderSound(2, -1000), true);
|
||||||
|
t.deepEquals(soundIds(), [2, 0, 1, 3, 4]);
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
|
@ -306,6 +306,68 @@ test('duplicateSprite assigns duplicated sprite a fresh name', t => {
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('reorderCostume', t => {
|
||||||
|
const vm = new VirtualMachine();
|
||||||
|
vm.emitTargetsUpdate = () => {};
|
||||||
|
|
||||||
|
const spr = new Sprite(null, vm.runtime);
|
||||||
|
spr.name = 'foo';
|
||||||
|
const target = spr.createClone();
|
||||||
|
|
||||||
|
// Stub out reorder on target, tested in rendered-target tests.
|
||||||
|
// Just want to know if it is called with the right params.
|
||||||
|
let costumeIndex = null;
|
||||||
|
let newIndex = null;
|
||||||
|
target.reorderCostume = (_costumeIndex, _newIndex) => {
|
||||||
|
costumeIndex = _costumeIndex;
|
||||||
|
newIndex = _newIndex;
|
||||||
|
return true; // Do not need all the logic about if a reorder occurred.
|
||||||
|
};
|
||||||
|
|
||||||
|
vm.runtime.targets = [target];
|
||||||
|
|
||||||
|
t.equal(vm.reorderCostume('not-a-target', 0, 3), false);
|
||||||
|
t.equal(costumeIndex, null);
|
||||||
|
t.equal(newIndex, null);
|
||||||
|
|
||||||
|
t.equal(vm.reorderCostume(target.id, 0, 3), true);
|
||||||
|
t.equal(costumeIndex, 0);
|
||||||
|
t.equal(newIndex, 3);
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('reorderSound', t => {
|
||||||
|
const vm = new VirtualMachine();
|
||||||
|
vm.emitTargetsUpdate = () => {};
|
||||||
|
|
||||||
|
const spr = new Sprite(null, vm.runtime);
|
||||||
|
spr.name = 'foo';
|
||||||
|
const target = spr.createClone();
|
||||||
|
|
||||||
|
// Stub out reorder on target, tested in rendered-target tests.
|
||||||
|
// Just want to know if it is called with the right params.
|
||||||
|
let soundIndex = null;
|
||||||
|
let newIndex = null;
|
||||||
|
target.reorderSound = (_soundIndex, _newIndex) => {
|
||||||
|
soundIndex = _soundIndex;
|
||||||
|
newIndex = _newIndex;
|
||||||
|
return true; // Do not need all the logic about if a reorder occurred.
|
||||||
|
};
|
||||||
|
|
||||||
|
vm.runtime.targets = [target];
|
||||||
|
|
||||||
|
t.equal(vm.reorderSound('not-a-target', 0, 3), false);
|
||||||
|
t.equal(soundIndex, null); // Make sure reorder function was not called somehow.
|
||||||
|
t.equal(newIndex, null);
|
||||||
|
|
||||||
|
t.equal(vm.reorderSound(target.id, 0, 3), true);
|
||||||
|
t.equal(soundIndex, 0); // Make sure reorder function was called correctly.
|
||||||
|
t.equal(newIndex, 3);
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
test('emitWorkspaceUpdate', t => {
|
test('emitWorkspaceUpdate', t => {
|
||||||
const vm = new VirtualMachine();
|
const vm = new VirtualMachine();
|
||||||
const blocksToXML = comments => {
|
const blocksToXML = comments => {
|
||||||
|
|
Loading…
Reference in a new issue