Moved here.

git-svn-id: svn+ssh://src.earth.threerings.net/nenya/trunk@126 ed5b42cb-e716-0410-a449-f6a68f950b19
This commit is contained in:
Ray Greenwell
2007-01-31 02:59:09 +00:00
parent 12f8cabd90
commit 76074998f4
8 changed files with 908 additions and 0 deletions
@@ -0,0 +1,82 @@
package com.threerings.flex {
import flash.display.DisplayObjectContainer;
import flash.events.Event;
import mx.controls.TextArea;
import com.threerings.crowd.chat.client.ChatDirector;
import com.threerings.crowd.chat.client.ChatDisplay;
import com.threerings.crowd.chat.data.ChatMessage;
import com.threerings.crowd.chat.data.UserMessage;
import com.threerings.crowd.util.CrowdContext;
/**
* A very simple chat display.
*/
public class ChatDisplayBox extends TextArea
implements ChatDisplay
{
public function ChatDisplayBox (ctx :CrowdContext)
{
_ctx = ctx;
this.editable = false;
// TODO
width = 400;
height = 150;
}
// documentation inherited from interface ChatDisplay
public function clear () :void
{
this.htmlText = "";
}
// documentation inherited from interface ChatDisplay
public function displayMessage (
msg :ChatMessage, alreadyDisplayed :Boolean) :Boolean
{
if (!_scrollBot) {
_scrollBot = (verticalScrollPosition == maxVerticalScrollPosition);
}
// display the message
if (msg is UserMessage) {
this.htmlText += "<font color=\"red\">&lt;" +
(msg as UserMessage).speaker + "&gt;</font> ";
}
this.htmlText += msg.message;
return true;
}
override public function parentChanged (p :DisplayObjectContainer) :void
{
super.parentChanged(p);
var chatdir :ChatDirector = _ctx.getChatDirector();
if (p != null) {
chatdir.addChatDisplay(this);
} else {
chatdir.removeChatDisplay(this);
}
}
// documentation inherited
override protected function updateDisplayList (uw :Number, uh :Number) :void
{
super.updateDisplayList(uw, uh);
if (_scrollBot) {
verticalScrollPosition = maxVerticalScrollPosition;
_scrollBot = false;
}
}
/** The giver of life. */
protected var _ctx :CrowdContext;
protected var _scrollBot :Boolean;
}
}
@@ -0,0 +1,54 @@
package com.threerings.flex {
import flash.events.MouseEvent;
import mx.controls.Button;
/**
* A command button simply dispatches a Controller command (with an optional
* argument) when it is clicked.
*/
public class CommandButton extends Button
{
/**
* Create a command button.
*/
public function CommandButton (cmd :String = null, arg :Object = null)
{
setCommand(cmd, arg);
}
/**
* Set the command and argument to be issued when this button is pressed.
*/
public function setCommand (cmd :String, arg :Object = null) :void
{
_cmd = cmd;
_arg = arg;
}
override protected function clickHandler (event :MouseEvent) :void
{
super.clickHandler(event);
if (enabled) {
// stop the click event
event.stopImmediatePropagation();
// dispatch the command event
// if the arg is null and we're a toggle button, dispatch our state
var arg :Object = _arg;
if (arg == null && toggle) {
arg = selected;
}
CommandEvent.dispatch(this, _cmd, arg);
}
}
/** The command to submit when clicked. */
protected var _cmd :String;
/** The argument that accompanies our command. */
protected var _arg :Object;
}
}
@@ -0,0 +1,88 @@
package com.threerings.flex {
import flash.errors.IllegalOperationError;
import flash.events.Event;
import flash.events.IEventDispatcher;
public class CommandEvent extends Event
{
/** The event type for all controller events. */
public static const TYPE :String = "commandEvt";
/**
* Use this method to dispatch CommandEvents.
*/
public static function dispatch (
disp :IEventDispatcher, cmd :String, arg :Object = null) :void
{
// Create the event
var event :CommandEvent = create(cmd, arg);
// Dispatch it. A return value of true means that the event was
// never cancelled, so we complain.
if (disp == null || disp.dispatchEvent(event)) {
Log.getLog(CommandEvent).warning("Unhandled controller command " +
"[cmd=" + cmd + ", arg=" + arg + "].");
}
}
/** The command. */
public var command :String;
/** An optional argument. */
public var arg :Object;
/**
* Command events may not be directly constructed, use the dispatch
* method to do your work.
*/
public function CommandEvent (command :String, arg :Object)
{
super(TYPE, true, true);
if (_blockConstructor) {
throw new IllegalOperationError();
}
this.command = command;
this.arg = arg;
}
/**
* Mark this command as handled, stopping its propagation up the
* hierarchy.
*/
public function markAsHandled () :void
{
preventDefault();
stopImmediatePropagation();
}
override public function clone () :Event
{
return create(command, arg);
}
override public function toString () :String
{
return "CommandEvent[" + command + " (" + arg + ")]";
}
/**
* A factory method for privately creating command events.
*/
protected static function create (cmd :String, arg :Object) :CommandEvent
{
var event :CommandEvent;
_blockConstructor = false;
try {
event = new CommandEvent(cmd, arg);
} finally {
_blockConstructor = true;
}
return event;
}
/** Used to prevent unauthorized construction. */
protected static var _blockConstructor :Boolean = true;
}
}
+176
View File
@@ -0,0 +1,176 @@
package com.threerings.flex {
import flash.display.DisplayObject;
import flash.display.DisplayObjectContainer;
import flash.geom.Rectangle;
import mx.controls.Menu;
import mx.core.mx_internal;
import mx.core.ScrollPolicy;
import mx.events.MenuEvent;
/**
* A pretty standard menu that can submit CommandEvents if menu items
* have "command" and possibly "arg" properties. Commands are submitted to
* controllers for processing. Alternatively, you may specify
* "callback" properties that specify a function closure to call, with the
* "arg" property containing either a single arg or an array of args.
*
* Example dataProvider array:
* [ { label: "Go home", icon: homeIconClass,
* command: Controller.GO_HOME, arg: homeId },
* { type: "separator"},
{ label: "Crazytown", callback: setCrazy, arg: [ true, false ] },
* { label: "Other places", children: subMenuArray }
* ];
*
* See "Defining menu structure and data" in the Flex manual for the
* full list.
*/
public class CommandMenu extends Menu
{
public function CommandMenu ()
{
super();
addEventListener(MenuEvent.ITEM_CLICK, itemClicked);
// addEventListener(MenuEvent.MENU_SHOW, handleMenuShown);
}
/**
* Factory method to create a command menu.
*
* @param parent The parent of this menu.
* @param items an array of menu items.
*/
public static function createMenu (
parent :DisplayObjectContainer, items :Array) :CommandMenu
{
var menu :CommandMenu = new CommandMenu();
menu.tabEnabled = false;
menu.showRoot = true;
Menu.popUpMenu(menu, parent, items);
return menu;
}
/**
* Actually pop up the menu. This can be used instead of show().
*/
public function popUp (
trigger :DisplayObject, popUpwards :Boolean = false) :void
{
var r :Rectangle = trigger.getBounds(trigger.stage);
if (popUpwards) {
show(r.x, 0);
// then, reposition the y once we know our size
y = r.y - getExplicitOrMeasuredHeight();
} else {
// simply position it below the trigger
show(r.x, r.y + r.height);
}
}
/**
* Callback for MenuEvent.ITEM_CLICK.
*/
protected function itemClicked (event :MenuEvent) :void
{
var arg :Object = getItemArgument(event.item);
var cmd :String = getItemCommand(event.item);
var fn :Function;
if (cmd == null) {
fn = getItemCallback(event.item);
}
if (cmd != null || fn != null) {
event.stopImmediatePropagation();
if (cmd != null) {
CommandEvent.dispatch(mx_internal::parentDisplayObject, cmd, arg);
} else {
try {
var args :Array = (arg as Array);
if (args == null && arg != null) {
args = [ arg ];
}
fn.apply(null, args);
} catch (err :Error) {
Log.getLog(this).warning("Unable to call menu callback: " +
event.item);
}
}
}
}
// /**
// * Callback for MenuEvent.MENU_SHOW.
// */
// protected function handleMenuShown (event :MenuEvent) :void
// {
// // just ensure that every menu can scroll
// event.menu.verticalScrollPolicy = ScrollPolicy.ON;
// }
/**
* Get the command for the specified item, if any.
* Somewhat similar to bits in the DefaultDataDescriptor.
*/
protected function getItemCommand (item :Object) :String
{
try {
if (item is XML) {
return (item.@command as String);
} else if (item is Object) {
return (item.command as String);
}
} catch (e :Error) {
// fall through
}
return null;
}
/**
* Get the callback function for the specified item, if any.
*/
protected function getItemCallback (item :Object) :Function
{
try {
if (item is XML) {
return (item.@callback as Function);
} else if (item is Object) {
return (item.callback as Function);
}
} catch (e :Error) {
// fall through
}
return null;
}
/**
* Get the command for the specified item, if any.
* Somewhat similar to bits in the DefaultDataDescriptor.
*/
protected function getItemArgument (item :Object) :Object
{
try {
if (item is XML) {
return item.@arg;
} else if (item is Object) {
return item.arg;
}
} catch (e :Error) {
// fall through
}
return null;
}
}
}
+301
View File
@@ -0,0 +1,301 @@
package com.threerings.flex {
import flash.display.DisplayObject;
import flash.display.InteractiveObject;
import flash.display.Sprite;
import flash.events.MouseEvent;
import flash.text.TextField;
import flash.text.TextFieldType;
import flash.ui.Mouse;
import mx.core.Application;
import mx.managers.SystemManager;
import mx.styles.CSSStyleDeclaration;
import mx.styles.StyleManager;
import com.threerings.util.HashMap;
/**
* A better CursorManager.
*/
public class CursorManager
{
public static const SYSTEM_CURSOR :int = 0;
public static const BUSY_CURSOR :int = 1;
//--------------------------------------------------------------------------
//
// Class methods
//
//--------------------------------------------------------------------------
/**
* Makes the cursor visible.
* Cursor visibility is not reference-counted.
* A single call to the <code>showCursor()</code> method
* always shows the cursor regardless of how many calls
* to the <code>hideCursor()</code> method were made.
*/
public static function showCursor () :void
{
if (_currentCursorId == SYSTEM_CURSOR) {
Mouse.show();
} else {
_cursorHolder.visible = true;
}
}
/**
* Makes the cursor invisible.
* Cursor visibility is not reference-counted.
* A single call to the <code>hideCursor()</code> method
* always hides the cursor regardless of how many calls
* to the <code>showCursor()</code> method were made.
*/
public static function hideCursor () :void
{
_cursorHolder.visible = false;
Mouse.hide();
}
public static function getCurrentCursorId () :int
{
return _currentCursorId;
}
/**
* Creates a new cursor.
*
* @param cursorClass Class of the cursor to display.
*
* @param xOffset Number that specifies the x offset
* of the cursor, in pixels, relative to the mouse pointer.
* The default value is 0.
*
* @param yOffset Number that specifies the y offset
* of the cursor, in pixels, relative to the mouse pointer.
* The default value is 0.
*
* @return The ID of the cursor.
*/
public static function addCursor (
cursorClass :Class, xOffset :int = 0, yOffset :int = 0) :int
{
var cursorId :int = _nextCursorId++;
var rec :CursorRecord = new CursorRecord(cursorClass);
rec.x = xOffset;
rec.y = yOffset;
_cursors.put(cursorId, rec);
return cursorId;
}
/**
* Set the current cursor.
*/
public static function setCursor (id :int) :void
{
if (id == _currentCursorId) {
return;
}
if (!_initialized) {
// oh, let's set it up
_systemManager = Application.application.systemManager;
// set up the busy cursor
var cursorManagerStyleDeclaration :CSSStyleDeclaration =
StyleManager.getStyleDeclaration("CursorManager");
var busyCursorClass :Class =
cursorManagerStyleDeclaration.getStyle("busyCursor");
_busyCursor = new CursorRecord(busyCursorClass);
// The first time a cursor is requested of the CursorManager,
// create a Sprite to hold the cursor symbol
_cursorHolder = new Sprite();
_cursorHolder.mouseEnabled = false;
_systemManager.cursorChildren.addChild(_cursorHolder);
_initialized = true;
}
// figure out what the new cursor will be like
var rec :CursorRecord;
if (id == BUSY_CURSOR) {
rec = _busyCursor;
} else if (id != SYSTEM_CURSOR) {
rec = (_cursors.get(id) as CursorRecord);
if (rec == null) {
// a bogus id was specified
// TODO: throw an error?
return;
}
}
if (rec != null && (rec.cursor is Class)) {
// go ahead and instantiate the class
try {
var disp :DisplayObject = new (rec.cursor as Class);
rec.cursor = disp;
if (disp is InteractiveObject) {
(disp as InteractiveObject).mouseEnabled = false;
}
} catch (err :Error) {
// this cursor is not usable, bail
return;
}
}
// always remove any custom cursors from the hierarchy
if (_cursorHolder.numChildren > 0) {
_cursorHolder.removeChildAt(0);
}
if (id != SYSTEM_CURSOR) {
Mouse.hide();
var currentCursor :DisplayObject = (rec.cursor as DisplayObject);
_cursorHolder.addChild(currentCursor);
_cursorHolder.x = _systemManager.mouseX + rec.x;
_cursorHolder.y = _systemManager.mouseY + rec.y;
_currentXOffset = rec.x;
_currentYOffset = rec.y;
_systemManager.stage.addEventListener(
MouseEvent.MOUSE_MOVE, mouseMoveHandler);
} else {
_currentXOffset = 0;
_currentYOffset = 0;
_systemManager.stage.removeEventListener(
MouseEvent.MOUSE_MOVE, mouseMoveHandler);
Mouse.show();
}
_currentCursorId = id;
}
/**
* Removes a cursor from the cursor list.
* If the cursor being removed is the currently displayed cursor,
* the CursorManager displays the next cursor in the list, if one exists.
* If the list becomes empty, the CursorManager displays
* the default system cursor.
*
* @param cursorID ID of cursor to remove.
*/
public static function removeCursor (id :int) :void
{
var rec :CursorRecord = (_cursors.remove(id) as CursorRecord);
if (rec != null && id == _currentCursorId) {
setCursor(SYSTEM_CURSOR);
}
}
/**
* Removes all of the cursors from the cursor list
* and restores the system cursor.
*/
public static function removeAllCursors () :void
{
_cursors.clear();
if (_currentCursorId != BUSY_CURSOR) {
setCursor(SYSTEM_CURSOR);
}
}
//--------------------------------------------------------------------------
//
// Class event handlers
//
//--------------------------------------------------------------------------
/**
* @private
*/
private static function mouseMoveHandler (event :MouseEvent) :void
{
_cursorHolder.x = _systemManager.mouseX + _currentXOffset;
_cursorHolder.y = _systemManager.mouseY + _currentYOffset;
var target :Object = event.target;
// Do target test.
if (!_overTextField &&
target is TextField && target.type == TextFieldType.INPUT) {
_overTextField = true;
_showSystemCursor = true;
} else if (_overTextField &&
!(target is TextField && target.type == TextFieldType.INPUT)) {
_overTextField = false;
_showCustomCursor = true;
}
// Handle switching between system and custom cursor.
if (_showSystemCursor) {
_showSystemCursor = false;
_cursorHolder.visible = false;
Mouse.show();
}
if (_showCustomCursor) {
_showCustomCursor = false;
_cursorHolder.visible = true;
Mouse.hide();
}
}
/** A mapping of all assigned cursor ids. */
private static const _cursors :HashMap = new HashMap();
private static var _currentCursorId :int = 0; // SYSTEM_CURSOR
private static var _nextCursorId :int = 2; // skip BUSY
private static var _initialized :Boolean = false;
private static var _cursorHolder :Sprite;
private static var _currentXOffset :int = 0;
private static var _currentYOffset :int = 0;
/** A record for the busy cursor (where it can't be removed). */
private static var _busyCursor :CursorRecord;
private static var _overTextField :Boolean = false;
private static var _overLink :Boolean = false;
private static var _showSystemCursor :Boolean = false;
private static var _showCustomCursor :Boolean = false;
private static var _systemManager :SystemManager = null;
}
}
/**
*/
class CursorRecord extends Object
{
public function CursorRecord (clazz :Class)
{
cursor = clazz;
}
/** The class, or instantiated cursor. */
public var cursor :Object
/** The x/y offset for the hotspot. */
public var x :Number;
public var y :Number;
}
@@ -0,0 +1,31 @@
package com.threerings.flex {
import mx.effects.IEffectInstance;
import mx.effects.Effect;
public class FunctionEffect extends Effect
{
/** The function to call. */
public var func :Function;
/** The arguments to pass to the function. */
public var args :Array;
public function FunctionEffect (target :Object = null)
{
super(target);
instanceClass = FunctionEffectInstance;
}
// documentation inherited
override protected function initInstance (instance :IEffectInstance) :void
{
super.initInstance(instance);
var fe :FunctionEffectInstance = (instance as FunctionEffectInstance);
fe.func = func;
fe.args = args;
}
}
}
@@ -0,0 +1,29 @@
package com.threerings.flex {
import mx.effects.effectClasses.ActionEffectInstance;
public class FunctionEffectInstance extends ActionEffectInstance
{
public function FunctionEffectInstance (target :Object)
{
super(target);
}
/** The function to call. */
public var func :Function;
/** The args to pass. */
public var args :Array;
override public function play () :void
{
super.play();
// call the function!
func.apply(null, args);
// and we're done
finishRepeat();
}
}
}
+147
View File
@@ -0,0 +1,147 @@
package com.threerings.flex {
import flash.display.DisplayObject;
import flash.display.Graphics;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.geom.Matrix;
import flash.geom.Rectangle;
import mx.containers.Canvas;
/**
* A control for scrolling some component separately.
*/
public class ScrollBox extends Canvas
{
public function ScrollBox (
target :DisplayObject, maxWidth :int, maxHeight :int)
{
_target = target;
_maxWidth = maxWidth;
_maxHeight = maxHeight;
opaqueBackground = 0xFFFFFF;
_box = new Sprite();
rawChildren.addChild(_box);
recheckBounds();
// TODO: only when something changes
addEventListener(Event.ENTER_FRAME, enterFrame);
_box.addEventListener(MouseEvent.MOUSE_DOWN, spritePressed);
}
override public function setActualSize (w :Number, h :Number) :void
{
super.setActualSize(w, h);
recheckBounds();
graphics.clear();
graphics.lineStyle(1, 0);
graphics.drawRect(0, 0, width, height);
}
protected function enterFrame (vent :Event) :void
{
if (!_dragging) {
recheckBounds();
}
}
/**
* Get the bounds of the scrollable area. Broken-out for easy overridding.
*/
protected function getScrollBounds () :Rectangle
{
var bounds :Rectangle = _target.transform.pixelBounds;
// var m :Matrix = _target.transform.concatenatedMatrix;
// bounds.topLeft = m.transformPoint(bounds.topLeft);
// bounds.bottomRight = m.transformPoint(bounds.bottomRight);
return bounds;
}
protected function recheckBounds () :void
{
var bounds :Rectangle = getScrollBounds();
// see what's visible, what's not..
var scroller :Rectangle = _target.scrollRect;
if (scroller == null) {
scroller = bounds.clone();
}
if ((_bounds != null) && _bounds.equals(bounds) &&
(_scroller != null) && _scroller.equals(scroller)) {
return;
}
_bounds = bounds.clone();
_scroller = scroller;
_scale = Math.min(1,
Math.min(_maxWidth / _bounds.width, _maxHeight / _bounds.height));
width = _scale * _bounds.width;
height = _scale * _bounds.height;
drawBox(_box, _scroller.width * _scale, _scroller.height * _scale);
_box.x = (_scroller.x - _bounds.x) * _scale;
_box.y = (_scroller.y - _bounds.y) * _scale;
}
protected function drawBox (box :Sprite, ww :Number, hh :Number) :void
{
var g :Graphics = _box.graphics;
g.clear();
g.beginFill(0x0000FF);
g.drawRect(0, 0, ww, hh);
g.endFill();
}
protected function spritePressed (evt :MouseEvent) :void
{
_box.addEventListener(Event.ENTER_FRAME, boxUpdate);
_box.startDrag(true,
new Rectangle(0, 0, width - _box.width, height - _box.height));
stage.addEventListener(MouseEvent.MOUSE_UP, spriteReleased);
_dragging = true;
}
protected function spriteReleased (evt :MouseEvent) :void
{
stage.removeEventListener(MouseEvent.MOUSE_UP, spriteReleased);
_box.stopDrag();
_box.removeEventListener(Event.ENTER_FRAME, boxUpdate);
_dragging = false;
}
protected function boxUpdate (evt :Event) :void
{
var r :Rectangle = _target.scrollRect;
r.x = (_box.x / _scale) + _bounds.x;
r.y = (_box.y / _scale) + _bounds.y;
_target.scrollRect = r;
}
protected var _dragging :Boolean;
protected var _target :DisplayObject;
protected var _maxWidth :int;
protected var _maxHeight :int;
protected var _box :Sprite;
protected var _scale :Number;
protected var _bounds :Rectangle;
protected var _scroller :Rectangle;
}
}