Merge pull request #215 from nathan/bubble

Added speech bubble with value of reporter when clicked
This commit is contained in:
Shane M. Clements 2014-05-28 13:33:07 -06:00
commit d3c17cb714
5 changed files with 109 additions and 28 deletions

View file

@ -132,6 +132,7 @@ public class Scratch extends Sprite {
stage.addEventListener(MouseEvent.MOUSE_DOWN, gh.mouseDown);
stage.addEventListener(MouseEvent.MOUSE_MOVE, gh.mouseMove);
stage.addEventListener(MouseEvent.MOUSE_UP, gh.mouseUp);
stage.addEventListener(MouseEvent.MOUSE_WHEEL, gh.mouseWheel);
stage.addEventListener('rightClick', gh.rightMouseClick);
stage.addEventListener(KeyboardEvent.KEY_DOWN, runtime.keyDown);
stage.addEventListener(KeyboardEvent.KEY_UP, runtime.keyUp);
@ -817,6 +818,12 @@ public class Scratch extends Sprite {
public function handleTool(tool:String, evt:MouseEvent):void { }
public function showBubble(text:String, x:* = null, y:* = null, width:Number = 0):void {
if (x == null) x = stage.mouseX;
if (y == null) y = stage.mouseY;
gh.showBubble(text, Number(x), Number(y), width);
}
// -----------------------------
// Project Management and Sign in
//------------------------------

View file

@ -58,6 +58,7 @@
package interpreter {
import flash.utils.Dictionary;
import flash.utils.getTimer;
import flash.geom.Point;
import blocks.*;
import primitives.*;
import scratch.*;
@ -118,7 +119,8 @@ public class Interpreter {
currentMSecs = getTimer();
var oldThread:Thread = activeThread;
activeThread = new Thread(b, targetObj);
app.log(evalCmd(b));
var p:Point = b.localToGlobal(new Point(0, 0));
app.showBubble(String(evalCmd(b)), p.x, p.y, b.width);
activeThread = oldThread;
return;
}

View file

@ -569,7 +569,7 @@ public class ScratchSprite extends ScratchObj {
}
if (!(s is String)) s = s.toString();
if (s.length == 0) return;
bubble = new TalkBubble(s, type, isAsk);
bubble = new TalkBubble(s, type, isAsk ? 'ask' : 'say');
parent.addChild(bubble);
updateBubble();
}

View file

@ -26,16 +26,39 @@ public class TalkBubble extends Sprite {
public var pointsLeft:Boolean;
private var type:String; // 'say' or 'think'
private var style:String; // 'say' or 'ask' or 'result'
private var shape:Shape;
private var text:TextField;
private static var textFormat:TextFormat = new TextFormat(CSS.font, 14, 0, true, null, null, null, null, TextFormatAlign.CENTER);
private static var resultFormat:TextFormat = new TextFormat(CSS.font, 12, CSS.textColor, null, null, null, null, null, TextFormatAlign.CENTER);
private var outlineColor:int = 0xA0A0A0;
private var radius:int = 8; // corner radius
private var padding:int = 5;
private var minWidth:int = 55;
private var lastXY:Array;
private var pInset1:int = 16
private var pInset2:int = 50
private var pDrop:int = 17;
private var pDropX:int = 8;
private var lineWidth:Number = 3;
public function TalkBubble(s:String, type:String, isAsk:Boolean) {
public function TalkBubble(s:String, type:String, style:String) {
this.type = type;
this.style = style;
if (style == 'ask') {
outlineColor = 0x4AADDE;
} else if (style == 'result') {
outlineColor = 0x888888;
minWidth = 16;
padding = 3;
radius = 5;
pInset1 = 8;
pInset2 = 16;
pDrop = 5;
pDropX = 4;
lineWidth = 0.5;
}
pointsLeft = true;
if (isAsk) outlineColor = 0x4AADDE;
shape = new Shape();
addChild(shape);
text = makeText();
@ -49,7 +72,7 @@ public class TalkBubble extends Sprite {
var newValue:Boolean = (dir == 'left');
if (pointsLeft == newValue) return;
pointsLeft = newValue;
setWidthHeight(text.width + 10, text.height + 10);
setWidthHeight(text.width + padding * 2, text.height + padding * 2);
}
public function getText():String { return text.text }
@ -58,30 +81,28 @@ public class TalkBubble extends Sprite {
var desiredWidth:int = 135;
text.width = desiredWidth + 100; // wider than desiredWidth
text.text = s;
text.width = Math.max(55, Math.min(text.textWidth + 8, desiredWidth)); // fix word wrap
setWidthHeight(text.width + 10, text.height + 10);
text.width = Math.max(minWidth, Math.min(text.textWidth + 8, desiredWidth)); // fix word wrap
setWidthHeight(text.width + padding * 2, text.height + padding * 2);
}
private function setWidthHeight(w:int, h:int):void {
var g:Graphics = shape.graphics;
g.clear();
g.beginFill(0xFFFFFF);
g.lineStyle(3, outlineColor);
g.lineStyle(lineWidth, outlineColor);
if (type == 'think') drawThink(w, h);
else drawTalk(w, h);
}
private function makeText():TextField {
var format:TextFormat = new TextFormat(CSS.font, 14, 0, true);
format.align = TextFormatAlign.CENTER;
var result:TextField = new TextField();
result.autoSize = TextFieldAutoSize.LEFT;
result.defaultTextFormat = format;
result.defaultTextFormat = style == 'result' ? resultFormat : textFormat;
result.selectable = false; // not selectable
result.type = 'dynamic'; // not editable
result.wordWrap = true;
result.x = 5;
result.y = 5;
result.x = padding;
result.y = padding;
return result;
}
@ -89,7 +110,6 @@ public class TalkBubble extends Sprite {
var insetW:int = w - radius;
var insetH:int = h - radius;
// pointer geometry:
var pInset1:int = 16, pInset2:int = 50, pDrop:int = 17;
startAt(radius, 0);
line(insetW, 0);
arc(w, radius);
@ -97,11 +117,11 @@ public class TalkBubble extends Sprite {
arc(insetW, h);
if (pointsLeft) {
line(pInset2, h);
line(8, h + pDrop);
line(pDropX, h + pDrop);
line(pInset1, h);
} else {
line(w - pInset1, h);
line(w - 8, h + pDrop);
line(w - pDropX, h + pDrop);
line(w - pInset2, h);
}
line(radius, h);

View file

@ -49,8 +49,8 @@
package util {
import flash.display.*;
import flash.events.MouseEvent;
import flash.external.ExternalInterface;
import flash.filters.*;
import flash.external.ExternalInterface;
import flash.filters.*;
import flash.geom.*;
import flash.text.*;
import flash.utils.getTimer;
@ -73,6 +73,7 @@ public class GestureHandler {
private var originalScale:Number;
private var app:Scratch;
private var stage:Stage;
private var dragClient:DragClient;
private var mouseDownTime:uint;
private var gesture:String = "idle";
@ -81,13 +82,20 @@ public class GestureHandler {
private var mouseDownEvent:MouseEvent;
private var inIE:Boolean;
private var bubble:TalkBubble;
private var bubbleStartX:Number;
private var bubbleStartY:Number;
private static var bubbleRange:Number = 25;
private static var bubbleMargin:Number = 5;
public function GestureHandler(app:Scratch, inIE:Boolean) {
this.app = app;
this.stage = app.stage;
this.inIE = inIE;
}
public function setDragClient(newClient:DragClient, evt:MouseEvent):void {
Menu.removeMenusFrom(app.stage);
Menu.removeMenusFrom(stage);
if (dragClient != null) dragClient.dragEnd(evt);
dragClient = newClient as DragClient;
dragClient.dragBegin(evt);
@ -124,12 +132,12 @@ public class GestureHandler {
public function rightMouseDown(x:int, y:int, isChrome:Boolean):void {
// To avoid getting the Adobe menu on right-click, JavaScript captures
// right-button mouseDown events and calls this method.'
Menu.removeMenusFrom(app.stage);
Menu.removeMenusFrom(stage);
var menuTarget:* = findTargetFor('menu', app, x, y);
if (!menuTarget) return;
try { var menu:Menu = menuTarget.menu(new MouseEvent('right click')) } catch (e:Error) {}
if (menu) menu.showOnStage(app.stage, x, y);
if (!isChrome) Menu.removeMenusFrom(app.stage); // hack: clear menuJustCreated because there's no rightMouseUp
if (menu) menu.showOnStage(stage, x, y);
if (!isChrome) Menu.removeMenusFrom(stage); // hack: clear menuJustCreated because there's no rightMouseUp
}
private function findTargetFor(property:String, obj:*, x:int, y:int):DisplayObject {
@ -150,6 +158,7 @@ public class GestureHandler {
ExternalInterface.call('tip_bar_api.fixIE');
evt.updateAfterEvent(); // needed to avoid losing display updates with later version of Flash 11
hideBubble();
mouseIsDown = true;
if (gesture == 'clickOrDoubleClick') {
handleDoubleClick(mouseDownEvent);
@ -218,6 +227,13 @@ public class GestureHandler {
spr.scratchY = 180 - stageP.y;
spr.updateBubble();
}
if (bubble) {
var dx:Number = bubbleStartX - stage.mouseX;
var dy:Number = bubbleStartY - stage.mouseY;
if (dx * dx + dy * dy > bubbleRange * bubbleRange) {
hideBubble();
}
}
}
public function mouseUp(evt:MouseEvent):void {
@ -230,7 +246,7 @@ public class GestureHandler {
return;
}
drop(evt);
Menu.removeMenusFrom(app.stage);
Menu.removeMenusFrom(stage);
if (gesture == "unknown") {
if (mouseTarget && ('doubleClick' in mouseTarget)) gesture = "clickOrDoubleClick";
else {
@ -251,6 +267,10 @@ public class GestureHandler {
}
}
public function mouseWheel(evt:MouseEvent):void {
hideBubble();
}
private function findMouseTarget(evt:MouseEvent, target:*):DisplayObject {
// Find the mouse target for the given event. Return null if no target found.
@ -266,7 +286,7 @@ public class GestureHandler {
}
o = o.parent;
}
var rect:Rectangle = app.stageObj().getRect(app.stage);
var rect:Rectangle = app.stageObj().getRect(stage);
if(!mouseTarget && rect.contains(evt.stageX, evt.stageY)) return findMouseTargetOnStage(evt.stageX / app.scaleX, evt.stageY / app.scaleY);
if (o == null) return null;
if ((o is Block) && Block(o).isEmbeddedInProcHat()) return o.parent;
@ -315,7 +335,7 @@ public class GestureHandler {
private function handleDrag(evt:MouseEvent):void {
// Note: Called with a null event if gesture is click and hold.
Menu.removeMenusFrom(app.stage);
Menu.removeMenusFrom(stage);
if (!('objToGrab' in mouseTarget)) return;
if (!app.editMode) {
if ((mouseTarget is ScratchSprite) && !ScratchSprite(mouseTarget).isDraggable) return; // don't drag locked sprites in presentation mode
@ -345,7 +365,7 @@ public class GestureHandler {
if (mouseTarget == null) return;
var menu:Menu;
try { menu = mouseTarget.menu(evt) } catch (e:Error) {}
if (menu) menu.showOnStage(app.stage, evt.stageX / app.scaleX, evt.stageY / app.scaleY);
if (menu) menu.showOnStage(stage, evt.stageX / app.scaleX, evt.stageY / app.scaleY);
}
private var lastGrowShrinkSprite:Sprite;
@ -409,7 +429,7 @@ public class GestureHandler {
}
if (app.editMode) addDropShadowTo(obj);
app.stage.addChild(obj);
stage.addChild(obj);
obj.x = globalP.x;
obj.y = globalP.y;
if (evt && mouseDownEvent) {
@ -425,7 +445,7 @@ public class GestureHandler {
// Search for an object to handle this drop and return true one is found.
// Note: Search from front to back, so the front-most object catches the dropped object.
if(app.isIn3D) app.stagePane.visible = true;
var possibleTargets:Array = app.stage.getObjectsUnderPoint(new Point(evt.stageX / app.scaleX, evt.stageY / app.scaleY));
var possibleTargets:Array = stage.getObjectsUnderPoint(new Point(evt.stageX / app.scaleX, evt.stageY / app.scaleY));
if(app.isIn3D) {
app.stagePane.visible = false;
if(possibleTargets.length == 0 && app.stagePane.scrollRect.contains(app.stagePane.mouseX, app.stagePane.mouseY))
@ -484,6 +504,38 @@ public class GestureHandler {
o.filters = newFilters;
}
public function showBubble(text:String, x:Number, y:Number, width:Number = 0):void {
hideBubble();
bubble = new TalkBubble(text || ' ', 'say', 'result');
bubbleStartX = stage.mouseX;
bubbleStartY = stage.mouseY;
var bx:Number = x + width;
var by:Number = y - bubble.height;
if (bx + bubble.width > stage.stageWidth - bubbleMargin && x - bubble.width > bubbleMargin) {
bx = x - bubble.width;
bubble.setDirection('right');
} else {
bubble.setDirection('left');
}
bubble.x = Math.max(bubbleMargin, Math.min(stage.stageWidth - bubbleMargin, bx));
bubble.y = Math.max(bubbleMargin, Math.min(stage.stageHeight - bubbleMargin, by));
var f:DropShadowFilter = new DropShadowFilter();
f.distance = 4;
f.blurX = f.blurY = 8;
f.alpha = 0.2;
bubble.filters = bubble.filters.concat(f);
stage.addChild(bubble);
}
public function hideBubble():void {
if (bubble) {
stage.removeChild(bubble);
bubble = null;
}
}
/* Debugging */
private var debugSelection:DisplayObject;