Skip to content
This repository has been archived by the owner on Sep 3, 2019. It is now read-only.

Adds block and sprite (in sprite pane) highlighting capability. #861

Closed
8 changes: 8 additions & 0 deletions src/blocks/Block.as
Expand Up @@ -341,6 +341,14 @@ public class Block extends Sprite {
return [f];
}

public function showBlockHighlight():void {
base.showHighlightFilters()
}

public function hideBlockHighlight():void {
base.hideHighlightFilters()
}

public function saveOriginalState():void {
originalParent = parent;
if (parent) {
Expand Down
34 changes: 34 additions & 0 deletions src/blocks/BlockShape.as
Expand Up @@ -178,6 +178,40 @@ public class BlockShape extends Shape {
return [f];
}

public function showHighlightFilters():void {
if (filters && filters.length > 0) {
for each (var f:* in filters) {
if (f is GlowFilter) return;
}
}
filters = highlightFilters().concat(filters || []);
}

public function hideHighlightFilters():void {
if (filters && filters.length > 0) {
var newFilters:Array = [];
for each (var f:* in filters) {
if (!(f is GlowFilter)) newFilters.push(f);
}
filters = newFilters;
}
}

private function highlightFilters():Array {
// use inner and outer - looks a bit better, than just either one, I think...?
var f:GlowFilter = new GlowFilter(0xaeff00);
f.strength = 3;
f.blurX = f.blurY = 4;
f.quality = 3;
f.inner = true;
var g:GlowFilter = new GlowFilter(0xaeff00);
g.strength = 3;
g.blurX = g.blurY = 4;
g.quality = 3;
g.inner = false;
return [f,g];
}

private function setShape(shape:int):void {
this.shape = shape;
switch(shape) {
Expand Down
247 changes: 227 additions & 20 deletions src/scratch/BlockMenus.as
Expand Up @@ -492,11 +492,11 @@ public class BlockMenus implements DragClient {
private function genericBlockMenu(evt:MouseEvent):void {
if (!block || block.isEmbeddedParameter()) return;
var m:Menu = new Menu(null, 'genericBlock');
addGenericBlockItems(m);
addGenericBlockItems(m,true);
showMenu(m);
}

private function addGenericBlockItems(m:Menu):void {
private function addGenericBlockItems(m:Menu,highlightItems:Boolean=false):void {
if (!block) return;
m.addLine();
if (!isInPalette(block)) {
Expand All @@ -509,6 +509,11 @@ public class BlockMenus implements DragClient {
}
m.addItem('help', block.showHelp);
m.addLine();
if (highlightItems) {
m.addItem('highlight this block',genericHighlightSameBlocks); // "highlight same blocks", "highlight all like this"...?

This comment was marked as abuse.

m.addItem('clear highlights',clearHighlights);
m.addLine();
}
}

private function duplicateStack():void {
Expand All @@ -522,20 +527,147 @@ public class BlockMenus implements DragClient {
}
if (!block) return;
var m:Menu = new Menu(opMenu, 'changeOp');
addGenericBlockItems(m);
addGenericBlockItems(m,true);
if (!isInPalette(block)) for each (var op:String in opList) m.addItem(op);
showMenu(m);
}

private function clearHighlights():void {
app.runtime.clearBlockHighlights();
app.highlightSprites([]);
}

private function showHighlights(sprites:Array,blocklist:Array):void {
if (blocklist.length>0 && sprites.length>0) {
app.runtime.showBlockHighlights(blocklist);
app.highlightSprites(sprites);
} else {
app.runtime.clearBlockHighlights();
app.highlightSprites([]);
}
}

private function highlightSameSpec(matchName:String):void {
var vo:ScratchObj = app.viewedObj();
if (!vo) return;
var blocklist:Array = [];
for each (var stack:Block in vo.scripts) {
// for each block in stack
stack.allBlocksDo(function (b:Block):void {
if (b.spec == matchName) blocklist.push(b);
});
}
showHighlights([vo],blocklist);
}

private function highlightSameBlocks(matchOp:String,bargnum:int,bargval:String): void {
var sprites:Array = [];
var blocklist:Array = [];
for each (var o:ScratchObj in app.stagePane.allObjects()) {
if (!o.isClone) {
var blks:Array = [];
for each (var stack:Block in o.scripts) {
// for each block in stack
stack.allBlocksDo(function (b:Block):void {
if (b.op == matchOp) {
if (bargnum<0 || (b.args[bargnum] is BlockArg && b.args[bargnum].argValue is String && b.args[bargnum].argValue==bargval)) {
blks.push(b);
}
}
});
}
if (blks.length>0) {
sprites.push(o);
blocklist = blks.concat(blocklist);
}
}
}
showHighlights(sprites,blocklist);
}


/* The way below works is that certain blocks will only show a match if a certain
* one of their args is also the same. For example, "set var to _" will only match
* other "set" blocks that have the same var.
* Not sure if all of these are really how we want, but they are there to show
* the general idea and possibilities...
* Such blocks are as follows...
* - all variable blocks (but getter has custom highlight menu items)
* - all list blocks (but getter has custom highlight menu items)
* - maths operator block "[function v] of ( )" (must have same function)
* - stop block (must have same "all"/"other scripts in sprite"/"this script")
* - "attribute of sprite/stage" (must have same attribute - what about sprite??)
* - play sound & play sound until done (only match if also the same sound)
* - switch costume/backdrop (must have same costume/backdrop)
* - set & change effect (must have same effect)
* - when backdrop switches event (must have same backdrop)
* - create clone block (must have same sprite)
* - touching something (must have same sprite or edge or mouse-pointer)
* - distance to sprite (must have same sprite)
* - when timer/volume greater than events (must be same sensor)
* It's also perhaps not so great having these spec strings mentioned in here?
* - could declare these arrays static in Specs.as, perhaps...?
*/
private function genericHighlightSameBlocks(): void {
// These only match within the viewed object, and if spec matches
const specArr:Array = [Specs.GET_PARAM,"whenCloned","deleteClone"];
// These only match if first arg also matches
const barg0Arr:Array = [Specs.SET_VAR,Specs.CHANGE_VAR,"touching:","distanceTo:",
"getAttribute:of:","lineCountOfList:","list:contains:","showList:","hideList:",
"showVariable:","hideVariable:","computeFunction:of:","whenSceneStarts",
"createCloneOf","whenSensorGreaterThan","playsound:","doPlaySoundAndWait",
"changeGraphicEffect:by:","setGraphicEffect:to:","lookLike:","startScene",
"startSceneAndWait","stopScripts"]
// These only match if second arg also matches (i.e. list name)
const barg1Arr:Array = ["append:toList:","deleteLine:ofList:",
"setLine:ofList:to:","getLine:ofList:"];
// These only match if third arg (i.e. the list name) also matches
const barg2Arr:Array = ["insert:at:ofList:"];
if (specArr.indexOf(block.op)>-1) {
highlightSameSpec( block.spec );
} else if (barg0Arr.indexOf(block.op)>-1 && block.args[0] is BlockArg) {
highlightSameBlocks( block.op, 0, block.args[0].argValue as String);
} else if (barg1Arr.indexOf(block.op)>-1 && block.args[1] is BlockArg) {
highlightSameBlocks( block.op, 1, block.args[1].argValue as String);
} else if (barg2Arr.indexOf(block.op)>-1 && block.args[2] is BlockArg) {
highlightSameBlocks( block.op, 2, block.args[2].argValue as String);
} else {
highlightSameBlocks( block.op, -1, null);
}
}

// ***** Procedure menu (for procedure definition hats and call blocks) *****

private function procMenu(evt:MouseEvent):void {
var m:Menu = new Menu(null, 'proc');
addGenericBlockItems(m);
m.addItem('edit', editProcSpec);
m.addLine();
if (block.op == Specs.CALL) m.addItem('highlight define', highlightProcDef);
m.addItem('highlight callers', highlightCallers);
m.addItem('clear highlights', clearHighlights);
showMenu(m);
}

private function highlightProcDef():void {
if(!app.editMode) return;
if (block.op != Specs.CALL) return;
var def:Block = app.viewedObj().lookupProcedure(block.spec);
if (!def || !app.viewedObj()) { // should never happen...?
clearHighlights();
return;
}
showHighlights([app.viewedObj()],[def]);
var pane:ScriptsPane = def.parent as ScriptsPane;
if (!pane) return;
if (pane.parent is ScrollFrame) {
pane.x = 5 - def.x*pane.scaleX;
pane.y = 5 - def.y*pane.scaleX;
(pane.parent as ScrollFrame).constrainScroll();
(pane.parent as ScrollFrame).updateScrollbars();
}
}

private function editProcSpec():void {
if (block.op == Specs.CALL) {
var def:Block = app.viewedObj().lookupProcedure(block.spec);
Expand Down Expand Up @@ -578,6 +710,18 @@ public class BlockMenus implements DragClient {
app.updatePalette();
}

private function highlightCallers():void {
var o:ScratchObj = app.viewedObj();
if (!o) return; // can this ever happen?
if (block.op == Specs.CALL) {
var def:Block = o.lookupProcedure(block.spec);
if (!def) return;
block = def;
}
var blks:Array = app.runtime.allCallsOf(block.spec, o);
showHighlights([o],blks);
}

// ***** Variable and List menus *****

private function listMenu(evt:MouseEvent):void {
Expand All @@ -586,18 +730,23 @@ public class BlockMenus implements DragClient {
if (isGetter) {
if (isInPalette(block)) m.addItem('delete list', deleteVarOrList); // list reporter in palette
addGenericBlockItems(m);
m.addLine();
m.addItem('highlight list', highlightList);
m.addItem('clear highlights', clearHighlights);
m.addLine()
}
var myName:String = isGetter ? blockVarOrListName() : null;
var listName:String;
for each (listName in app.stageObj().listNames()) {
if (listName != myName) m.addItem(listName);
}
if (!app.viewedObj().isStage) {
m.addLine();
for each (listName in app.viewedObj().listNames()) {
if (!(isInPalette(block) && isGetter)) {
var myName:String = isGetter ? blockVarOrListName() : null;
var listName:String;
for each (listName in app.stageObj().listNames()) {
if (listName != myName) m.addItem(listName);
}
if (!app.viewedObj().isStage) {
m.addLine();
for each (listName in app.viewedObj().listNames()) {
if (listName != myName) m.addItem(listName);
}
}
}
showMenu(m);
}
Expand All @@ -609,8 +758,16 @@ public class BlockMenus implements DragClient {
m.addItem('rename variable', renameVar);
m.addItem('delete variable', deleteVarOrList);
addGenericBlockItems(m);
m.addItem('highlight variable', highlightVar);
m.addItem('clear highlights', clearHighlights);
m.addLine();
} else {
if (isGetter) addGenericBlockItems(m);
if (isGetter) {
addGenericBlockItems(m);
m.addItem('highlight variable', highlightVar);
m.addItem('clear highlights', clearHighlights);
m.addLine();
}
var myName:String = blockVarOrListName();
var vName:String;
for each (vName in app.stageObj().varNames()) {
Expand Down Expand Up @@ -689,6 +846,52 @@ public class BlockMenus implements DragClient {
Scratch.app.setSaveNeeded();
}

private function highlightList():void {
var myName:String = blockVarOrListName();
var vo:ScratchObj = app.viewedObj();
if (!vo) return;
var sprites:Array = [];
var blocklist:Array = [];
if (vo.isStage || !vo.ownsVar(myName)) {
for each (var o:ScratchObj in app.stagePane.allObjects()) {
if (!o.isClone) {
var blks:Array = app.runtime.allUsesOfList(myName,o,false);
if (blks.length>0) {
sprites.push(o);
blocklist = blks.concat(blocklist);
}
}
}
} else {
sprites = [vo];
blocklist = app.runtime.allUsesOfList(myName,vo);
}
showHighlights(sprites,blocklist);
}

private function highlightVar():void {
var myName:String = blockVarOrListName();
var vo:ScratchObj = app.viewedObj();
if (!vo) return;
var sprites:Array = [];
var blocklist:Array = [];
if (vo.isStage || !vo.ownsVar(myName)) {
for each (var o:ScratchObj in app.stagePane.allObjects()) {
if (!o.isClone) {
var blks:Array = app.runtime.allUsesOfVariable(myName,o,false);
if (blks.length>0) {
sprites.push(o);
blocklist = blks.concat(blocklist);
}
}
}
} else {
sprites = [vo];
blocklist = app.runtime.allUsesOfVariable(myName,vo);
}
showHighlights(sprites,blocklist);
}

// ***** Color picker support *****

public function dragBegin(evt:MouseEvent):void { }
Expand Down Expand Up @@ -773,18 +976,22 @@ public class BlockMenus implements DragClient {
function showBroadcasts(selection:*):void {
if (selection is Function) { selection(); return; }
var msg:String = block.args[0].argValue;
var sprites:Array = [];
if (selection == 'show senders') sprites = app.runtime.allSendersOfBroadcast(msg);
if (selection == 'show receivers') sprites = app.runtime.allReceiversOfBroadcast(msg);
if (selection == 'clear senders/receivers') sprites = [];
app.highlightSprites(sprites);
var result:Array = [];
if (selection == 'highlight senders') {
result = app.runtime.allSendersOfBroadcast(msg);
} else if (selection == 'highlight receivers') {
result = app.runtime.allReceiversOfBroadcast(msg);
} else if (selection == 'clear highlights') {
result = [[],[]];
}
showHighlights(result[0],result[1]);
}
var m:Menu = new Menu(showBroadcasts, 'broadcastInfo');
addGenericBlockItems(m);
if (!isInPalette(block)) {
m.addItem('show senders');
m.addItem('show receivers');
m.addItem('clear senders/receivers');
m.addItem('highlight senders');
m.addItem('highlight receivers');
m.addItem('clear highlights');
}
showMenu(m);
}
Expand Down