Split Nenya into proper Maven submodules (same treatment Narya got).

This commit is contained in:
Michael Bayne
2012-02-27 11:46:22 -08:00
parent 90146d517d
commit 5e2380eb24
645 changed files with 358 additions and 283 deletions
@@ -0,0 +1,79 @@
//
// $Id$
//
// Nenya library - tools for developing networked games
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/nenya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.flex {
import mx.controls.List;
import mx.core.EdgeMetrics;
/**
* A list that can be configured with the vertical scrollbar on the left or right side.
*/
public class AmbidextrousList extends List
{
/**
* Get whether the scrollbar is on the left side.
*/
public function get scrollBarOnLeft () :Boolean
{
return _scrollLeft;
}
/**
* Sets whether to place the scrollbar on the left or right side.
*/
public function set scrollBarOnLeft (scrollLeft :Boolean) :void
{
_scrollLeft = scrollLeft;
scrollAreaChanged = true;
invalidateDisplayList();
}
/** @inheritDoc */
override public function get viewMetrics () :EdgeMetrics
{
var em :EdgeMetrics = super.viewMetrics;
if (_scrollLeft && verticalScrollBar && verticalScrollBar.visible) {
em.right -= verticalScrollBar.minWidth;
em.left += verticalScrollBar.minWidth;
}
return em;
}
/** @inheritDoc */
override protected function updateDisplayList (uw :Number, uh :Number) :void
{
super.updateDisplayList(uw, uh);
if (_scrollLeft) {
var vm :EdgeMetrics = viewMetrics;
verticalScrollBar.move(vm.left - verticalScrollBar.minWidth, vm.top);
}
}
/** Do we want to have the scrollbar on the left? */
protected var _scrollLeft :Boolean;
}
}
@@ -0,0 +1,237 @@
//
// $Id$
//
// Nenya library - tools for developing networked games
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/nenya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.flex {
import flash.display.DisplayObjectContainer;
import flash.events.Event;
import flash.events.FocusEvent;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.ui.Keyboard;
import mx.containers.HBox;
import mx.core.Application;
import mx.controls.TextInput;
import mx.events.FlexEvent;
import com.threerings.util.Arrays;
import com.threerings.util.DelayUtil;
import com.threerings.util.StringUtil;
import com.threerings.flex.CommandButton;
import com.threerings.crowd.chat.client.ChatDirector;
import com.threerings.crowd.chat.data.ChatCodes;
import com.threerings.crowd.util.CrowdContext;
/**
* The chat control widget.
*/
public class ChatControl extends HBox
{
/**
* Request focus for the oldest ChatControl.
*/
public static function grabFocus () :void
{
if (_controls.length > 0) {
(_controls[0] as ChatControl).setFocus();
}
}
public function ChatControl (
ctx :CrowdContext, sendButtonLabel :String = "send",
height :Number = NaN, controlHeight :Number = NaN)
{
_ctx = ctx;
_chatDtr = _ctx.getChatDirector();
this.height = height;
styleName = "chatControl";
addChild(_txt = new ChatInput());
_txt.addEventListener(FlexEvent.ENTER, sendChat, false, 0, true);
_txt.addEventListener(KeyboardEvent.KEY_UP, handleKeyUp);
_but = new CommandButton(sendButtonLabel, sendChat);
addChild(_but);
if (!isNaN(controlHeight)) {
_txt.height = controlHeight;
_but.height = controlHeight;
}
addEventListener(Event.ADDED_TO_STAGE, handleAddRemove);
addEventListener(Event.REMOVED_FROM_STAGE, handleAddRemove);
}
/**
* Provides access to the text field we use to accept chat.
*/
public function get chatInput () :ChatInput
{
return _txt;
}
/**
* Provides access to the send button.
*/
public function get sendButton () :CommandButton
{
return _but;
}
override public function set enabled (en :Boolean) :void
{
super.enabled = en;
if (_txt != null) {
_txt.enabled = en;
_but.enabled = en;
}
}
/**
* Request focus to this chat control.
*/
override public function setFocus () :void
{
_txt.setFocus();
}
/**
* Configures the chat director to which we should send our chat. Pass null to restore our
* default chat director.
*/
public function setChatDirector (chatDtr :ChatDirector) :void
{
_chatDtr = (chatDtr == null) ? _ctx.getChatDirector() : chatDtr;
}
/**
* Configures the background color of the text entry area.
*/
public function setChatColor (color :uint) :void
{
_txt.setStyle("backgroundColor", color);
}
/**
* Handles FlexEvent.ENTER and the action from the send button.
*/
protected function sendChat (... ignored) :void
{
var message :String = StringUtil.trim(_txt.text);
if ("" == message) {
return;
}
var result :String = _chatDtr.requestChat(null, message, true);
if (result != ChatCodes.SUCCESS) {
_chatDtr.displayFeedback(null, result);
return;
}
// If there was no error, clear the entry area in prep for the next entry event
DelayUtil.delayFrame(function () : void {
// WORKAROUND
// Originally we cleared the text immediately, but with flex 3.2 this broke
// for *some* people. Weird! We're called from the event dispatcher for the
// enter key, so it's possible that the default action is booching it?
// In any case, this could possibly be removed in the future by the ambitious.
// Note also: Flex's built-in callLater() doesn't work, but DelayUtil does. WTF?!?
_txt.text = "";
});
_histidx = -1;
}
protected function scrollHistory (next :Boolean) :void
{
var size :int = _chatDtr.getCommandHistorySize();
if ((_histidx == -1) || (_histidx == size)) {
_curLine = _txt.text;
_histidx = size;
}
_histidx = (next) ? Math.min(_histidx + 1, size)
: Math.max(_histidx - 1, 0);
var text :String = (_histidx == size) ? _curLine : _chatDtr.getCommandHistory(_histidx);
_txt.text = text;
_txt.setSelection(text.length, text.length);
}
/**
* Handles Event.ADDED_TO_STAGE and Event.REMOVED_FROM_STAGE.
*/
protected function handleAddRemove (event :Event) :void
{
if (event.type == Event.ADDED_TO_STAGE) {
// set up any already-configured text
_txt.text = _curLine;
_histidx = -1;
// request focus
callLater(_txt.setFocus);
_controls.push(this);
} else {
_curLine = _txt.text;
Arrays.removeAll(_controls, this);
}
}
protected function handleKeyUp (event :KeyboardEvent) :void
{
switch (event.keyCode) {
case Keyboard.UP: scrollHistory(false); break;
case Keyboard.DOWN: scrollHistory(true); break;
}
}
/** Our client-side context. */
protected var _ctx :CrowdContext;
/** The director to which we are sending chat requests. */
protected var _chatDtr :ChatDirector;
/** The place where the user may enter chat. */
protected var _txt :ChatInput;
/** The current index in the chat command history. */
protected var _histidx :int = -1;
/** The button for sending chat. */
protected var _but :CommandButton;
/** An array of the currently shown-controls. */
protected static var _controls :Array = [];
/** The preserved current line of text when traversing history or carried between instances of
* ChatControl. */
protected static var _curLine :String;
}
}
@@ -0,0 +1,103 @@
//
// $Id$
//
// Nenya library - tools for developing networked games
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/nenya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
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;
addEventListener(Event.ADDED_TO_STAGE, handleAddRemove);
addEventListener(Event.REMOVED_FROM_STAGE, handleAddRemove);
}
// documentation inherited from interface ChatDisplay
public function clear () :void
{
this.htmlText = "";
}
// documentation inherited from interface ChatDisplay
public function displayMessage (msg :ChatMessage) :void
{
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;
}
// handle us being added or removed from the stage
protected function handleAddRemove (event :Event) :void
{
var chatdir :ChatDirector = _ctx.getChatDirector();
if (event.type == Event.ADDED_TO_STAGE) {
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,107 @@
//
// $Id$
//
// Nenya library - tools for developing networked games
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/nenya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.flex {
import flash.events.FocusEvent;
import flash.text.TextField;
import mx.controls.TextInput;
import com.threerings.text.TextFieldUtil;
/**
* The class name of an image to use as the input prompt.
*/
[Style(name="prompt", type="Class")]
/**
* A special TextInput for entering Chat. One of these is used in ChatControl.
*
* A standard TextInput has the stupid behavior of selecting all the text when it receives
* focus. Disable that so that we can receive focus and people can type and we don't blow away
* whatever they had before.
*/
public class ChatInput extends TextInput
{
public function ChatInput ()
{
width = 147;
showPrompt(true);
}
override public function stylesInitialized () :void
{
super.stylesInitialized();
checkShowPrompt();
}
override public function styleChanged (styleProp :String) :void
{
super.styleChanged(styleProp);
if (styleProp == "prompt") {
checkShowPrompt();
}
}
protected function checkShowPrompt () :void
{
showPrompt(focusManager == null || focusManager.getFocus() != this);
}
protected function showPrompt (show :Boolean) :void
{
setStyle("backgroundImage", (show && ("" == text)) ? getStyle("prompt") : undefined);
}
override protected function createChildren () :void
{
super.createChildren();
// For some reason, in embeds, during certain circumstances, it's really hard to focus
// chat. This makes that work better. The problem was *not* testable locally, only
// on embeds in production. (I don't know why.)
TextFieldUtil.setFocusable(TextField(textField));
}
override protected function focusInHandler (event :FocusEvent) :void
{
var oldValue :Boolean = textField.selectable;
textField.selectable = false;
try {
super.focusInHandler(event);
} finally {
textField.selectable = oldValue;
}
showPrompt(false);
}
override protected function focusOutHandler (event :FocusEvent) :void
{
super.focusOutHandler(event);
showPrompt(true);
}
}
}
@@ -0,0 +1,127 @@
//
// $Id$
//
// Nenya library - tools for developing networked games
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/nenya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.flex {
import flash.events.MouseEvent;
import mx.controls.Button;
import com.threerings.util.CommandEvent;
/**
* 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.
*
* @param label the label text for the button.
* @param cmdOrFn either a String, which will be the CommandEvent command to dispatch,
* or a function, which will be called when clicked.
* @param arg the argument for the CommentEvent or the function. If the arg is an Array
* then those parameters are used for calling the function.
*/
public function CommandButton (label :String = null, cmdOrFn :* = null, arg :Object = null)
{
validateCmd(cmdOrFn);
this.label = label;
_cmdOrFn = cmdOrFn;
_arg = arg;
buttonMode = true;
}
/**
* Set the command and argument to be issued when this button is pressed.
*/
public function setCommand (cmd :String, arg :Object = null) :void
{
_cmdOrFn = cmd;
_arg = arg;
}
/**
* Set a callback function to call when the button is pressed.
*/
public function setCallback (fn :Function, arg :Object = null) :void
{
_cmdOrFn = fn;
_arg = arg;
}
/**
* Get the argument (or arguments, in an Array).
*/
public function getArg () :Object
{
return _arg;
}
/**
* Emulate a user click, dispatching all relevant events.
*/
public function activate () :void
{
dispatchEvent(new MouseEvent(MouseEvent.CLICK));
}
override public function set enabled (val :Boolean) :void
{
super.enabled = val;
buttonMode = val;
}
override protected function clickHandler (event :MouseEvent) :void
{
super.clickHandler(event);
processCommandClick(this, event, _cmdOrFn, getArg());
}
internal static function validateCmd (cmdOrFn :Object) :void
{
if (cmdOrFn != null && !(cmdOrFn is String) && !(cmdOrFn is Function)) {
// runtime errors suck, but this is actionscript
throw new Error("cmdOrFn must be a String or Function.");
}
}
internal static function processCommandClick (
button :Button, event :MouseEvent, cmdOrFn :Object, arg :Object) :void
{
if (button.enabled) {
event.stopImmediatePropagation();
if (arg == null && button.toggle) {
arg = button.selected;
}
CommandEvent.dispatch(button, cmdOrFn, arg);
}
}
/** The command (String) to submit, or the function (Function) to call
* when clicked, */
protected var _cmdOrFn :Object;
/** The argument that accompanies our command. */
protected var _arg :Object;
}
}
@@ -0,0 +1,85 @@
//
// $Id$
//
// Nenya library - tools for developing networked games
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/nenya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.flex {
import flash.events.MouseEvent;
import mx.controls.CheckBox;
import com.threerings.util.CommandEvent;
/**
* A checkbox that dispatches a command (or calls a function) when it is toggled.
*/
public class CommandCheckBox extends CheckBox
{
/**
* Create a command checkbox.
*
* @param label the label text for the checkbox.
* @param cmdOrFn either a String, which will be the CommandEvent command to dispatch,
* or a function, which will be called when clicked.
* @param arg the argument for the CommentEvent or the function. If the arg is an Array
* then those parameters are used for calling the function.
* Note that if arg is null, the actual argument passed will be the 'selected' state of the
* checkbox. If you really want to call a method with no args, specify arg as an emtpy Array.
*/
public function CommandCheckBox (label :String = null, cmdOrFn :* = null, arg :Object = null)
{
CommandButton.validateCmd(cmdOrFn);
this.label = label;
_cmdOrFn = cmdOrFn;
_arg = arg;
}
/**
* Set the command and argument to be issued when this button is pressed.
*/
public function setCommand (cmd :String, arg :Object = null) :void
{
_cmdOrFn = cmd;
_arg = arg;
}
/**
* Set a callback function to call when the button is pressed.
*/
public function setCallback (fn :Function, arg :Object = null) :void
{
_cmdOrFn = fn;
_arg = arg;
}
override protected function clickHandler (event :MouseEvent) :void
{
super.clickHandler(event);
CommandButton.processCommandClick(this, event, _cmdOrFn, _arg);
}
/** The command (String) to submit, or the function (Function) to call
* when clicked, */
protected var _cmdOrFn :Object;
/** The argument that accompanies our command. */
protected var _arg :Object;
}
}
@@ -0,0 +1,126 @@
//
// $Id$
//
// Nenya library - tools for developing networked games
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/nenya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.flex {
import flash.events.Event;
import mx.controls.ComboBox;
import mx.events.ListEvent;
import com.threerings.util.CommandEvent;
import com.threerings.util.Util;
/**
* A combo box that dispatches a command (or calls a callback) when an item is selected.
* The argument will be the 'data' value of the selected item.
* NOTE: Unlike the other Command* controls, CommandComboBox allows a null cmd/callback
* to be specified.
*/
public class CommandComboBox extends ComboBox
{
/** The field of the selectedItem object used as the 'data'. If this property is null,
* then the item is the data. */
public var dataField :String = "data";
/**
* Create a command combobox.
*
* @param cmdOrFn either a String, which will be the CommandEvent command to dispatch,
* or a function, which will be called when changed.
*/
public function CommandComboBox (cmdOrFn :* = null)
{
CommandButton.validateCmd(cmdOrFn);
_cmdOrFn = cmdOrFn;
addEventListener(ListEvent.CHANGE, handleChange, false, int.MIN_VALUE);
}
/**
* Set the command and argument to be issued when this button is pressed.
*/
public function setCommand (cmd :String) :void
{
_cmdOrFn = cmd;
}
/**
* Set a callback function to call when the button is pressed.
*/
public function setCallback (fn :Function) :void
{
_cmdOrFn = fn;
}
/**
* Set the selectedItem based on the data field.
*/
public function set selectedData (data :Object) :void
{
for (var ii :int = 0; ii < dataProvider.length; ++ii) {
if (Util.equals(data, itemToData(dataProvider[ii]))) {
this.selectedIndex = ii;
return;
}
}
// not found, clear the selection
this.selectedIndex = -1;
}
/**
* The value that will be passed to the command or function based on dataField and the
* current selected item.
*/
public function get selectedData () :Object
{
return itemToData(this.selectedItem);
}
/**
* Extract the data from the specified item. This can be expanded in the future
* if we provide a 'dataFunction'.
*/
protected function itemToData (item :Object) :Object
{
if (item != null && dataField != null) {
try {
return item[dataField];
} catch (re :ReferenceError) {
// fallback to just returning the item
}
}
return item;
}
protected function handleChange (event :ListEvent) :void
{
if (_cmdOrFn != null && this.selectedIndex != -1) {
CommandEvent.dispatch(this, _cmdOrFn, selectedData);
}
}
/** The command (String) to submit, or the function (Function) to call
* when changed, */
protected var _cmdOrFn :Object;
}
}
@@ -0,0 +1,90 @@
//
// $Id$
//
// Nenya library - tools for developing networked games
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/nenya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.flex {
import flash.events.MouseEvent;
import mx.controls.LinkButton;
import com.threerings.util.CommandEvent;
/**
* A CommandLinkButton looks like a link but dispatches a Controller command
* when clicked.
*/
public class CommandLinkButton extends LinkButton
{
/**
* Create a command link button.
*
* @param label the label text for the button.
* @param cmdOrFn either a String, which will be the CommandEvent command to dispatch,
* or a function, which will be called when clicked.
* @param arg the argument for the CommentEvent or the function. If the arg is an Array
* then those parameters are used for calling the function.
*/
public function CommandLinkButton (label :String = null, cmdOrFn :* = null, arg :Object = null)
{
CommandButton.validateCmd(cmdOrFn);
this.label = label;
_cmdOrFn = cmdOrFn;
_arg = arg;
}
override public function set enabled (enable :Boolean) :void
{
super.enabled = enable;
buttonMode = enable;
}
/**
* Set the command and argument to be issued when this button is pressed.
*/
public function setCommand (cmd :String, arg :Object = null) :void
{
_cmdOrFn = cmd;
_arg = arg;
}
/**
* Set a callback function to call when the button is pressed.
*/
public function setCallback (fn :Function, arg :Object = null) :void
{
_cmdOrFn = fn;
_arg = arg;
}
override protected function clickHandler (event :MouseEvent) :void
{
super.clickHandler(event);
CommandButton.processCommandClick(this, event, _cmdOrFn, _arg);
}
/** The command (String) to submit, or the function (Function) to call
* when clicked, */
protected var _cmdOrFn :Object;
/** Any argument that accompanies our command. */
protected var _arg :Object;
}
}
@@ -0,0 +1,537 @@
//
// $Id$
//
// Nenya library - tools for developing networked games
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/nenya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.flex {
import flash.display.DisplayObject;
import flash.display.DisplayObjectContainer;
import flash.events.Event;
import flash.events.IEventDispatcher;
import flash.events.MouseEvent;
import flash.geom.Point;
import flash.geom.Rectangle;
import mx.controls.Button;
import mx.controls.Menu;
import mx.controls.listClasses.BaseListData;
import mx.controls.listClasses.IListItemRenderer;
import mx.controls.menuClasses.IMenuItemRenderer;
import mx.controls.menuClasses.MenuListData;
import mx.core.mx_internal;
import mx.core.Application;
import mx.core.ClassFactory;
import mx.core.IFlexDisplayObject;
import mx.core.ScrollPolicy;
import mx.events.MenuEvent;
import mx.managers.PopUpManager;
import com.threerings.util.CommandEvent;
import com.threerings.flex.PopUpUtil;
import com.threerings.flex.menuClasses.CommandMenuItemRenderer;
import com.threerings.flex.menuClasses.CommandListData;
use namespace mx_internal;
/**
* 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.
*
* Another property now supported is "iconObject", which is an already-instantiated
* IFlexDisplayObject to use as the icon. This will only be applied if the "icon" property
* (which specifies a Class) is not used.
*
* Another 'type' now supported is "title", which gives the label for that item the style
* '.menuTitle' and disables it. Your ".menuTitle" style should use the disabledColor.
*
* 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.
*
* CommandMenu is scrollable. This can be forced by setting verticalScrollPolicy to
* ScrollPolicy.AUTO or ScrollPolicy.ON and setting the maxHeight. Also, if the scrolling isn't
* forced on, but the menu does not fit within the vertical bounds given (either the stage size by
* default, or the height of the rectangle given in setBounds()), scrolling will be turned on
* so that none of the content is lost.
*
* Note: we don't extend flexlib's ScrollableMenu (or its sub-class ScrollableArrowMenu) because it
* does some weird stuff in measure() that forces some of our menus down to a very small height...
*/
public class CommandMenu extends Menu
{
/**
* Factory method to create a command menu.
*
* @param items an array of menu items.
* @param dispatcher an override event dispatcher to use for command events, rather than
* our parent.
*/
public static function createMenu (
items :Array, dispatcher :IEventDispatcher = null) :CommandMenu
{
var menu :CommandMenu = new CommandMenu();
menu.owner = DisplayObjectContainer(Application.application);
menu.tabEnabled = false;
menu.showRoot = true;
menu.setDispatcher(dispatcher);
Menu.popUpMenu(menu, null, items); // does not actually pop up, but needed.
return menu;
}
/**
* Add a title to the TOP of the specified menu.
*
* @param icon may be a Class or IFlexDisplayObject.
*/
public static function addTitle (
menuItems :Array, title :String, icon :* = null, separatorAfter :Boolean = true) :void
{
var o :Object = { label: title, type: "title" };
if (icon is Class) {
o["icon"] = icon;
} else if (icon is IFlexDisplayObject) {
o["iconObject"] = icon;
}
if (separatorAfter) {
menuItems.unshift({ type: "separator" });
}
menuItems.unshift(o);
}
/**
* Add a separator to the specified menu, unless it would not make sense to do so, because
* the menu is empty or already ends with a separator.
*/
public static function addSeparator (menuItems :Array) :void
{
const len :int = menuItems.length;
if (len > 0 && menuItems[len - 1].type != "separator") {
menuItems.push({ type: "separator" });
}
}
/**
* The mx.controls.Menu class overrides setting and getting the verticalScrollPolicy
* basically setting the verticalScrollPolicy did nothing, and getting it always returned
* ScrollPolicy.OFF. So that's not going to work if we want the menu to scroll. Here we
* reinstate the verticalScrollPolicy setter, and keep a local copy of the value in a
* protected variable _verticalScrollPolicy.
*
* This setter is basically a copy of what ScrollControlBase and ListBase do.
*/
override public function set verticalScrollPolicy (value :String) :void
{
var newPolicy :String = value.toLowerCase();
itemsSizeChanged = true;
if (_verticalScrollPolicy != newPolicy) {
_verticalScrollPolicy = newPolicy;
dispatchEvent(new Event("verticalScrollPolicyChanged"));
}
invalidateDisplayList();
}
override public function get verticalScrollPolicy () :String
{
return _verticalScrollPolicy;
}
public function CommandMenu ()
{
super();
itemRenderer = new ClassFactory(getItemRenderer());
verticalScrollPolicy = ScrollPolicy.OFF;
variableRowHeight = true;
addEventListener(MenuEvent.ITEM_CLICK, itemClicked);
addEventListener(MenuEvent.MENU_HIDE, menuHidden);
}
/**
* Called in the CommandMenu constructor, this should return the item rendering class for this
* CommandMenu.
*/
protected function getItemRenderer () :Class
{
return CommandMenuItemRenderer;
}
/**
* Configure the button that popped-up this menu, which enables two features:
* 1) If the button is clicked again while the menu is up, it won't re-trigger, the menu will
* just close.
* 2) However the menu closes, if the button is a toggle, it will be de-selected.
*/
public function setTriggerButton (trigger :Button) :void
{
_trigger = trigger;
}
/**
* Configures the event dispatcher to be used when dispatching this menu's command events.
* Normally they will be dispatched on our parent (usually the SystemManager or something).
*/
public function setDispatcher (dispatcher :IEventDispatcher) :void
{
_dispatcher = dispatcher;
}
/**
* Sets a Rectangle (in stage coords) that the menu will attempt to keep submenus positioned
* within. If a submenu is too large to fit, it will position it in the lower right corner
* of this Rectangle (or top if popping upwards, or left if popping leftwards). This
* value defaults to the stage bounds.
*/
public function setBounds (fitting :Rectangle) :void
{
_fitting = fitting;
}
/**
* Actually pop up the menu. This can be used instead of show().
*/
public function popUp (
trigger :DisplayObject, popUpwards :Boolean = false, popLeftwards :Boolean = false) :void
{
_upping = popUpwards;
_lefting = popLeftwards;
var r :Rectangle = trigger.getBounds(trigger.stage);
show(_lefting ? r.left : r.right, _upping ? r.top : r.bottom);
}
/**
* Shows the menu at the specified mouse coordinates.
*/
public function popUpAt (
mx :int, my :int, popUpwards :Boolean = false, popLeftwards :Boolean = false) :void
{
_upping = popUpwards;
_lefting = popLeftwards;
show(mx, my);
}
/**
* Shows the menu at the current mouse location.
*/
public function popUpAtMouse (popUpwards :Boolean = false, popLeftwards :Boolean = false) :void
{
_upping = popUpwards;
_lefting = popLeftwards;
show(); // our show, with no args, pops at the mouse
}
/**
* Just like our superclass's show(), except that when invoked with no args, causes the menu to
* show at the current mouse location instead of the top-left corner of the application.
* Also, we ensure that the resulting menu is in-bounds.
*/
override public function show (xShow :Object = null, yShow :Object = null) :void
{
if (xShow == null) {
xShow = DisplayObject(Application.application).mouseX;
}
if (yShow == null) {
yShow = DisplayObject(Application.application).mouseY;
}
super.show(xShow, yShow);
// reposition now that we know our size
if (_lefting) {
y = x - getExplicitOrMeasuredWidth();
}
if (_upping) {
y = y - getExplicitOrMeasuredHeight();
}
var fitting :Rectangle = _fitting || screen;
PopUpUtil.fitInRect(this, fitting);
// if after fitting as best we can, the menu is outside of the declared bounds, we force
// scrolling and set the max height
if (y < fitting.y) {
verticalScrollPolicy = ScrollPolicy.AUTO;
maxHeight = fitting.height;
y = fitting.y;
}
// set up some stuff to clean up and behave
systemManager.topLevelSystemManager.getSandboxRoot().addEventListener(
MouseEvent.MOUSE_DOWN, handleMouseDownOutside, false, 1, true);
}
override public function hide () :void
{
super.hide();
systemManager.topLevelSystemManager.getSandboxRoot().removeEventListener(
MouseEvent.MOUSE_DOWN, handleMouseDownOutside);
}
/**
* The Menu class overrode configureScrollBars() and made the function do nothing. That means
* the scrollbars don't know how to draw themselves, so here we reinstate configureScrollBars.
* This is basically a copy of the same method from the mx.controls.List class.
*/
override protected function configureScrollBars () :void
{
var rowCount :int = listItems.length;
if (rowCount == 0) {
return;
}
// if there is more than one row and it is a partial row we don't count it
if (rowCount > 1 &&
rowInfo[rowCount - 1].y + rowInfo[rowCount - 1].height > listContent.height) {
rowCount--;
}
// offset, when added to rowCount, is hte index of the dataProvider item for that row.
// IOW, row 10 in listItems is showing dataProvider item 10 + verticalScrollPosition -
// lockedRowCount - 1
var offset :int = verticalScrollPosition - lockedRowCount - 1;
// don't count filler rows at the bottom either.
var fillerRows :int = 0;
while (rowCount > 0 && listItems[rowCount - 1].length == 0)
{
if (collection && rowCount + offset >= collection.length) {
rowCount--;
fillerRows++;
} else {
break;
}
}
var colCount :int = listItems[0].length;
var oldHorizontalScrollBar :Object = horizontalScrollBar;
var oldVerticalScrollBar :Object = verticalScrollBar;
var roundedWidth :int = Math.round(unscaledWidth);
var length :int = collection ? collection.length - lockedRowCount : 0;
var numRows :int = rowCount - lockedRowCount;
setScrollBarProperties(Math.round(listContent.width), roundedWidth, length, numRows);
maxVerticalScrollPosition = Math.max(length - numRows, 0);
}
/**
* Callback for MenuEvent.ITEM_CLICK.
*/
protected function itemClicked (event :MenuEvent) :void
{
var arg :Object = getItemProp(event.item, "arg");
var cmdOrFn :Object = getItemProp(event.item, "command");
if (cmdOrFn == null) {
cmdOrFn = getItemProp(event.item, "callback");
}
if (cmdOrFn != null) {
event.stopImmediatePropagation();
CommandEvent.dispatch(_dispatcher == null ? mx_internal::parentDisplayObject :
_dispatcher, cmdOrFn, arg);
}
// else: no warning. There may be non-command menu items mixed in.
}
/**
* Handles MenuEvent.MENU_HIDE.
*/
protected function menuHidden (event :MenuEvent) :void
{
// don't react to submenu hides
if ((event.menu == this) && (_trigger != null) && _trigger.toggle) {
_trigger.selected = false;
}
}
/**
* Handle a down-popping click outside the menu.
*/
protected function handleMouseDownOutside (event :MouseEvent) :void
{
if (event.target == _trigger) {
_trigger.addEventListener(
MouseEvent.CLICK, handleTriggerClick, false, int.MAX_VALUE, true);
}
}
/**
* Suppress the click that wants to land on the trigger button.
*/
protected function handleTriggerClick (event :MouseEvent) :void
{
event.stopImmediatePropagation();
_trigger.removeEventListener(MouseEvent.CLICK, handleTriggerClick);
}
// from ScrollableArrowMenu..
override mx_internal function openSubMenu (row :IListItemRenderer) :void
{
supposedToLoseFocus = true;
var r :Menu = getRootMenu();
var menu :CommandMenu;
// check to see if the menu exists, if not create it
if (!IMenuItemRenderer(row).menu) {
// the only differences between this method and the original method in mx.controls.Menu
// are these few lines.
menu = new CommandMenu();
menu.maxHeight = this.maxHeight;
menu.verticalScrollPolicy = this.verticalScrollPolicy;
menu.variableRowHeight = this.variableRowHeight;
menu.parentMenu = this;
menu.owner = this;
menu.showRoot = showRoot;
menu.dataDescriptor = r.dataDescriptor;
menu.styleName = r;
menu.labelField = r.labelField;
menu.labelFunction = r.labelFunction;
menu.iconField = r.iconField;
menu.iconFunction = r.iconFunction;
menu.itemRenderer = r.itemRenderer;
menu.rowHeight = r.rowHeight;
menu.scaleY = r.scaleY;
menu.scaleX = r.scaleX;
// if there's data and it has children then add the items
if (row.data && _dataDescriptor.isBranch(row.data) &&
_dataDescriptor.hasChildren(row.data)) {
menu.dataProvider = _dataDescriptor.getChildren(row.data);
}
menu.sourceMenuBar = sourceMenuBar;
menu.sourceMenuBarItem = sourceMenuBarItem;
IMenuItemRenderer(row).menu = menu;
PopUpManager.addPopUp(menu, r, false);
}
super.openSubMenu(row);
// if we're lefting, upping or fitting make sure our submenu does so as well
var submenu :Menu = IMenuItemRenderer(row).menu;
if (_lefting) {
submenu.x -= submenu.getExplicitOrMeasuredWidth();
}
if (_upping) {
var rowLoc :Point = row.localToGlobal(new Point());
submenu.y = rowLoc.y - submenu.getExplicitOrMeasuredHeight() + row.height;
}
var fitting :Rectangle = _fitting || screen;
PopUpUtil.fitInRect(submenu, fitting);
// if after fitting as best we can, the menu is outside of the declared bounds, we force
// scrolling and set the max height
if (submenu.y < fitting.y) {
submenu.verticalScrollPolicy = ScrollPolicy.AUTO;
submenu.maxHeight = fitting.height;
submenu.y = fitting.y;
}
}
override protected function measure () :void
{
super.measure();
if (measuredHeight > this.maxHeight) {
measuredHeight = this.maxHeight;
}
if (verticalScrollPolicy == ScrollPolicy.ON || verticalScrollPolicy == ScrollPolicy.AUTO) {
if (verticalScrollBar) {
measuredMinWidth = measuredWidth = measuredWidth + verticalScrollBar.minWidth;
}
}
commitProperties();
}
// from List
override protected function makeListData (data :Object, uid :String, rowNum :int) :BaseListData
{
// Oh, FFS.
// We need to set up these "maxMeasuredIconWidth" fields on the MenuListData, but our
// superclass has made those variables private.
// We can get the values out of another MenuListData, so we just always call super()
// to create one of those, and if we need to make a CommandListData, we construct one
// from the fields in the MenuListData.
var menuListData :MenuListData = super.makeListData(data, uid, rowNum) as MenuListData;
var iconObject :IFlexDisplayObject = getItemProp(data, "iconObject") as IFlexDisplayObject;
if (iconObject != null) {
var cmdListData :CommandListData = new CommandListData(menuListData.label,
menuListData.icon, iconObject, labelField, uid, this, rowNum);
cmdListData.maxMeasuredIconWidth = menuListData.maxMeasuredIconWidth;
cmdListData.maxMeasuredTypeIconWidth = menuListData.maxMeasuredTypeIconWidth;
cmdListData.maxMeasuredBranchIconWidth = menuListData.maxMeasuredBranchIconWidth;
cmdListData.useTwoColumns = menuListData.useTwoColumns;
return cmdListData;
}
return menuListData;
}
/**
* Get the specified property for the specified item, if any. Somewhat similar to bits in the
* DefaultDataDescriptor.
*/
protected function getItemProp (item :Object, prop :String) :Object
{
try {
if (item is XML) {
return String((item as XML).attribute(prop));
} else if (prop in item) {
return item[prop];
}
} catch (e :Error) {
// alas; fall through
}
return null;
}
protected var _dispatcher :IEventDispatcher;
protected var _trigger :Button;
protected var _lefting :Boolean = false;
protected var _upping :Boolean = false;
protected var _fitting :Rectangle = null;
protected var _verticalScrollPolicy :String;
}
}
@@ -0,0 +1,323 @@
//
// $Id$
//
// Nenya library - tools for developing networked games
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/nenya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
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.Map;
import com.threerings.util.Maps;
/**
* 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 :Map = Maps.newMapOf(int);
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,205 @@
//
// $Id$
//
// Nenya library - tools for developing networked games
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/nenya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.flex {
import mx.core.IFactory;
import mx.core.ScrollPolicy;
import mx.collections.ArrayCollection;
import mx.collections.IList;
import mx.collections.Sort;
import mx.controls.List;
import com.threerings.util.Util;
import com.threerings.presents.dobj.AttributeChangedEvent;
import com.threerings.presents.dobj.DEvent;
import com.threerings.presents.dobj.DObject;
import com.threerings.presents.dobj.DSet;
import com.threerings.presents.dobj.DSet_Entry;
import com.threerings.presents.dobj.EntryAddedEvent;
import com.threerings.presents.dobj.EntryUpdatedEvent;
import com.threerings.presents.dobj.EntryRemovedEvent;
import com.threerings.presents.dobj.EventListener;
import com.threerings.presents.dobj.NamedEvent;
/**
* A list that renders the contents of a DSet.
*/
public class DSetList extends List
implements EventListener
{
public function DSetList (
renderer :IFactory, sortFn :Function = null, filterFn :Function = null)
{
horizontalScrollPolicy = ScrollPolicy.OFF;
verticalScrollPolicy = ScrollPolicy.ON;
selectable = false;
itemRenderer = renderer;
_data = new ArrayCollection();
var sort :Sort = new Sort();
sort.compareFunction = sortFn;
_data.sort = sort;
_data.filterFunction = filterFn;
_data.refresh();
dataProvider = _data;
}
/**
* Set the data to be displayed by this List, if not being attached to a DObject.
*/
public function setData (array :Array /* of DSet_Entry */) :void
{
_data.source = (array == null) ? [] : array;
refresh();
}
/**
* Refresh the data, which can be called if something external has changed
* the sort order or the rendering.
*/
public function refresh () :void
{
_data.refresh();
}
/**
* Scroll so that the entry with the specified key is visible.
*/
public function scrollToKey (key :Object) :Boolean
{
var scrollIdx :int = findKeyIndex(key, false);
return (scrollIdx == -1) ? false : scrollToIndex(scrollIdx);
}
/**
* Initialize listening on the specified object.
*
* @param object the DObject on which to listen.
* @param field the name of the DSet field that we're displaying.
* @param refreshFields additional fields that cause refresh() to be called should
* AttributeChangedEvents arrive on them.
*/
public function init (object :DObject, field :String, ... refreshFields) :void
{
shutdown();
_object = object;
_field = field;
_refreshFields = refreshFields;
_object.addListener(this);
setEntries(_object[_field] as DSet);
}
/**
* Shut down listening.
*/
public function shutdown () :void
{
if (_object != null) {
_object.removeListener(this);
_object = null;
_field = null;
_refreshFields = null;
setData(null);
}
}
/**
* Safely set the entries, even if null.
*/
protected function setEntries (entries :DSet) :void
{
setData((entries == null) ? null : entries.toArray());
}
/**
* From com.threerings.presents.dobj.EventListener. Not actually part of the public API.
*/
public function eventReceived (event :DEvent) :void
{
if (!(event is NamedEvent)) {
return;
}
var name :String = NamedEvent(event).getName();
if (name == _field) {
if (event is EntryAddedEvent) {
_data.list.addItem(EntryAddedEvent(event).getEntry());
} else if (event is EntryUpdatedEvent) {
var entry :DSet_Entry = EntryUpdatedEvent(event).getEntry();
var upIdx :int = findKeyIndex(entry.getKey());
if (upIdx != -1) {
_data.list.setItemAt(entry, upIdx);
}
} else if (event is EntryRemovedEvent) {
var remIdx :int = findKeyIndex(EntryRemovedEvent(event).getKey());
if (remIdx != -1) {
_data.list.removeItemAt(remIdx);
}
} else if (event is AttributeChangedEvent) {
setEntries(AttributeChangedEvent(event).getValue() as DSet);
return; // skip the full refresh
}
} else if (-1 == _refreshFields.indexOf(name)) {
return; // this name is not our concern: do nothing
}
// we reach this line if the event was for a _refreshField, or a Set event on _field
refresh();
}
/**
* Find the index of the specified entry key from the raw or filtered list.
*/
protected function findKeyIndex (key :Object, raw :Boolean = true) :int
{
var list :IList = raw ? _data.list : _data;
for (var ii :int = 0; ii < list.length; ii++) {
if (Util.equals(key, DSet_Entry(list.getItemAt(ii)).getKey())) {
return ii;
}
}
return -1;
}
/** The collection for the List, This is the filtered/sorted view. For the
* the raw list, we access _data.list. */
protected var _data :ArrayCollection;
/** The object on which we're listening. */
protected var _object :DObject;
/** The fieldname of the DSet in the _object. */
protected var _field :String;
/** Fields on which to watch for updates and call refresh(). */
protected var _refreshFields :Array;
}
}
@@ -0,0 +1,166 @@
//
// $Id$
//
// Nenya library - tools for developing networked games
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/nenya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.flex {
import flash.display.DisplayObject;
import mx.controls.Label;
import mx.controls.Spacer;
import mx.controls.Text;
import mx.core.Container;
import mx.core.UIComponent;
import mx.containers.HBox;
import mx.containers.VBox;
/**
* Flex-related utility methods.
*/
public class FlexUtil
{
/**
* Creates a label with the supplied text, and optionally applies a style class and tooltip
* to it.
*/
public static function createLabel (
text :String, style :String = null, toolTip :String = null) :Label
{
var label :Label = new Label();
label.text = text;
label.styleName = style;
label.toolTip = toolTip;
return label;
}
/**
* Create an uneditable/unselectable multiline Text widget with the specified text and width.
*/
public static function createText (text :String, width :int, style :String = null) :Text
{
var t :Text = new Text();
t.styleName = style;
t.width = width;
t.selectable = false;
t.text = text;
return t;
}
/**
* Create an uneditable/unselectable multiline Text widget with 100% width.
*/
public static function createWideText (text :String, style :String = null) :Text
{
var t :Text = new Text();
t.styleName = style;
t.percentWidth = 100;
t.selectable = false;
t.text = text;
return t;
}
/**
* How hard would it have been for them make Spacer accept two optional arguments?
*/
public static function createSpacer (width :int = 0, height :int = 0) :Spacer
{
var spacer :Spacer = new Spacer();
spacer.width = width;
spacer.height = height;
return spacer;
}
/**
* Return a count of how many visible (includeInLayout=true) children
* are in the specified container.
*/
public static function countLayoutChildren (container :Container) :int
{
var layoutChildren :int = container.numChildren;
for each (var child :Object in container.getChildren()) {
if ((child is UIComponent) && !UIComponent(child).includeInLayout) {
layoutChildren--;
}
}
return layoutChildren;
}
/**
* In flex the 'visible' property controls visibility separate from whether
* the component takes up space in the layout, which is controlled by 'includeInLayout'.
* We usually want to set them together, so this does that for us.
* @return the new visible setting.
*/
public static function setVisible (component :UIComponent, visible :Boolean) :Boolean
{
component.visible = visible;
component.includeInLayout = visible;
return visible;
}
/**
* Creates a new container of the given class and adds the given children to it.
*/
public static function createContainer (clazz :Class, ...children) :Container
{
var container :Container = new clazz() as Container;
for each (var comp :UIComponent in children) {
container.addChild(comp);
}
return container;
}
/**
* Creates a new HBox container and adds the given children to it.
*/
public static function createHBox (...children) :HBox
{
children.unshift(HBox);
return createContainer.apply(null, children) as HBox;
}
/**
* Creates a new VBox container and adds the given children to it.
*/
public static function createVBox (...children) :VBox
{
children.unshift(VBox);
return createContainer.apply(null, children) as VBox;
}
/**
* Creates a simple UIComponent that wraps a display object.
*/
public static function wrap (obj :DisplayObject) :FlexWrapper
{
return new FlexWrapper(obj);
}
/**
* Creates a simple UIComponent that wraps a display object and inherits its size.
*/
public static function wrapSized (obj :DisplayObject) :FlexWrapper
{
return new FlexWrapper(obj, true);
}
}
}
@@ -0,0 +1,44 @@
//
// $Id$
//
// Nenya library - tools for developing networked games
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/nenya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.flex {
import flash.display.DisplayObject;
import mx.core.UIComponent;
/**
* Wraps a non-Flex component for use in Flex.
*/
public class FlexWrapper extends UIComponent
{
public function FlexWrapper (object :DisplayObject, inheritSize :Boolean = false)
{
// don't capture mouse events in this wrapper
mouseEnabled = false;
addChild(object);
if (inheritSize) {
width = object.width;
height = object.height;
}
}
}
}
@@ -0,0 +1,52 @@
//
// $Id$
//
// Nenya library - tools for developing networked games
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/nenya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
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,50 @@
//
// $Id$
//
// Nenya library - tools for developing networked games
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/nenya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
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();
}
}
}
@@ -0,0 +1,117 @@
//
// $Id$
//
// Nenya library - tools for developing networked games
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/nenya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.flex {
import mx.containers.Grid;
import mx.containers.GridItem;
import mx.containers.GridRow;
import mx.controls.Label;
import mx.core.UIComponent;
/**
* Convenience methods for adding children to Grids.
*/
public class GridUtil
{
/**
* Add a new row to the grid, containing the specified
* components.
*
* @param specs a list of components or Strings, or you can follow
* any component with a two-dimensional array that specifies
* grid width/height.
*
* Example: addRow(grid, "labeltxt", _entryField, _bigThing, [2, 2], _smallThing);
*
* All will be put in the same row, but bigThing will have
* colspan=2 rowspan=2
*/
public static function addRow (grid :Grid, ... specs) :GridRow
{
var row :GridRow = new GridRow();
var lastItem :GridItem;
for each (var o :Object in specs) {
if (o is String) {
var lbl :Label = new Label();
lbl.text = String(o);
o = lbl;
}
if (o is UIComponent) {
lastItem = addToRow(row, UIComponent(o));
} else if (o is Array) {
var arr :Array = (o as Array);
lastItem.colSpan = int(arr[0]);
lastItem.rowSpan = int(arr[1]);
} else {
throw new ArgumentError();
}
}
grid.addChild(row);
return row;
}
/**
* Add a child to the specified grid row, returning the
* GridItem created for containment of the child.
*/
public static function addToRow (row :GridRow, comp :UIComponent) :GridItem
{
var item :GridItem = new GridItem();
item.addChild(comp);
row.addChild(item);
return item;
}
/**
* Get the number of cells in a grid.
*/
public static function getCellCount (grid :Grid) :int
{
var count :int = 0;
for (var ii :int = 0; ii < grid.numChildren; ii++) {
count += (grid.getChildAt(ii) as GridRow).numChildren;
}
return count;
}
/**
* Return the contents of the specified cell. Not super fast, as it
* checks each row in order.
*/
public static function getCellAt (grid :Grid, index :int) :UIComponent
{
for (var ii :int = 0; ii < grid.numChildren; ii++) {
var row :GridRow = grid.getChildAt(ii) as GridRow;
if (index < row.numChildren) {
return (row.getChildAt(index) as GridItem).getChildAt(0) as UIComponent;
} else {
index -= row.numChildren; // and then on to the next row
}
}
return null; // never found it. throw an out-of-range error?
}
}
}
@@ -0,0 +1,83 @@
//
// $Id$
//
// Nenya library - tools for developing networked games
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/nenya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.flex {
import flash.display.Stage;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.ui.Keyboard;
import flash.utils.Dictionary;
import mx.core.Application;
import mx.controls.Button;
public class KeyboardManager
{
public static function setShortcut (trigger :*, keyCode :uint, ... modifiers) :void
{
_triggers[trigger] = [ keyCode, modifiers ];
ensureListening();
}
public static function clearShortcut (trigger :*) :void
{
delete _triggers[trigger];
}
protected static function ensureListening () :void
{
var stage :Stage = Application(Application.application).stage;
stage.addEventListener(KeyboardEvent.KEY_DOWN, handleKeyDown);
}
protected static function handleKeyDown (event :KeyboardEvent) :void
{
for (var trigger :* in _triggers) {
var info :Array = _triggers[trigger] as Array;
var keyCode :uint = info[0];
var modifiers :Array = info[1] as Array;
if (event.keyCode == keyCode &&
// (event.altKey == (-1 != modifierCodes.indexOf(Keyboard.ALTERNATE))) &&
(event.ctrlKey == (-1 != modifiers.indexOf(Keyboard.CONTROL))) &&
(event.shiftKey == (-1 != modifiers.indexOf(Keyboard.SHIFT)))) {
wasTriggered(trigger);
}
}
}
protected static function wasTriggered (trigger :*) :void
{
if (trigger is Button) {
var button :Button = Button(trigger);
if (button.stage != null && button.enabled) {
button.dispatchEvent(new MouseEvent(MouseEvent.CLICK));
}
}
}
protected static const _triggers :Dictionary = new Dictionary(true);
}
}
@@ -0,0 +1,75 @@
//
// $Id$
//
// Nenya library - tools for developing networked games
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/nenya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.flex {
import mx.core.ScrollPolicy;
import mx.containers.HBox;
import mx.controls.Label;
import mx.controls.sliderClasses.Slider;
import mx.events.SliderEvent;
/**
* A simple component that displays a label to the left of a slider.
*/
public class LabeledSlider extends HBox
{
/** The actual slider. */
public var slider :Slider;
/**
* Create a LabeledSlider holding the specified slider.
*/
public function LabeledSlider (slider :Slider, labelWidth :int = 17)
{
_label = new Label();
_label.text = String(slider.value);
_label.width = labelWidth;
horizontalScrollPolicy = ScrollPolicy.OFF;
setStyle("horizontalGap", 2);
addChild(_label);
this.slider = slider;
slider.showDataTip = false; // because we do it...
addChild(slider);
slider.addEventListener(SliderEvent.CHANGE, handleSliderChange, false, 0, true);
}
override public function set width (width :Number) :void
{
super.width = width;
slider.width = width - _label.width - 2;
}
protected function handleSliderChange (event :SliderEvent) :void
{
_label.text = String(event.value);
}
protected var _label :Label;
}
}
@@ -0,0 +1,58 @@
//
// $Id$
//
// Nenya library - tools for developing networked games
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/nenya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.flex {
import flash.display.DisplayObjectContainer;
import mx.core.ContainerCreationPolicy;
import mx.core.UIComponent;
import mx.containers.VBox;
public class LazyContainer extends VBox
{
/**
* Create a lazy container that will not create its children
* until needed.
*
* @param creation a function that takes no args and returns
* a UIComponent to be added to the container.
*/
public function LazyContainer (creation :Function)
{
_creation = creation;
creationPolicy = ContainerCreationPolicy.NONE;
}
override public function createComponentsFromDescriptors (
recurse :Boolean = true) :void
{
super.createComponentsFromDescriptors(recurse);
if (_creation != null) {
addChild(_creation() as UIComponent);
_creation = null; // assist gc
}
}
protected var _creation :Function;
}
}
@@ -0,0 +1,63 @@
//
// $Id$
//
// Nenya library - tools for developing networked games
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/nenya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.flex {
import mx.core.ContainerCreationPolicy;
import mx.core.UIComponent;
import mx.containers.TabNavigator;
import mx.containers.VBox;
/**
* A tab navigator that is specially set up for lazy-creating the
* content in each tab.
*/
public class LazyTabNavigator extends TabNavigator
{
public function LazyTabNavigator ()
{
super();
}
/**
* Add a tab to the container. The creation function takes no args and
* returns a UIComponent.
*/
public function addTab (label :String, creation :Function) :void
{
addTabAt(label, creation, numChildren);
}
/**
* Add a tab to the container at the specified index.
* The creation function takes no args and returns a UIComponent.
*/
public function addTabAt (
label :String, creation :Function, index :int) :void
{
var box :LazyContainer = new LazyContainer(creation);
box.label = label;
addChildAt(box, index);
}
}
}
@@ -0,0 +1,134 @@
//
// $Id$
//
// Nenya library - tools for developing networked games
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/nenya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.flex {
import flash.display.Loader;
import flash.display.Sprite;
import mx.core.IFlexAsset;
import mx.core.IFlexDisplayObject;
import mx.core.IInvalidating;
import com.threerings.util.MultiLoader;
/**
* An asset that can be used in Flex, that's loaded via a Loader.
*/
public class LoadedAsset extends Sprite
implements IFlexAsset, IFlexDisplayObject
{
/**
* @param source a String (url) or URLRequest, or Class or a ByteArray.
*/
public function LoadedAsset (source :Object) :void
{
MultiLoader.getLoaders(source, handleLoaded);
}
override public function get height () :Number
{
return (_loader == null) ? _measHeight : super.height;
}
override public function set height (value :Number) :void
{
if (_loader == null) {
_reqHeight = value;
} else {
_loader.height = value;
}
}
override public function get width () :Number
{
return (_loader == null) ? _measWidth : super.width;
}
override public function set width (value :Number) :void
{
if (_loader == null) {
_reqWidth = value;
} else {
_loader.width = value;
}
}
// from IFlexDisplayObject
public function get measuredHeight () :Number
{
return _measHeight;
}
// from IFlexDisplayObject
public function get measuredWidth () :Number
{
return _measWidth;
}
// from IFlexDisplayObject
public function move (x :Number, y :Number) :void
{
this.x = x;
this.y = y;
}
// from IFlexDisplayObject
public function setActualSize (newWidth :Number, newHeight :Number) :void
{
width = newWidth;
height = newHeight;
}
protected function handleLoaded (loader :Loader) :void
{
_loader = loader;
addChild(_loader);
// Note our measured size
_measWidth = loader.width;
_measHeight = loader.height;
// set any size that was set while we were being loaded
if (!isNaN(_reqWidth)) {
loader.width = _reqWidth;
}
if (!isNaN(_reqHeight)) {
loader.height = _reqHeight;
}
// if we have a parent that knows about sizes, tell it we now know our size
if (parent is IInvalidating) {
IInvalidating(parent).invalidateSize();
}
}
protected var _loader :Loader;
// initial / base sizes
protected var _measWidth :Number = 0;
protected var _measHeight :Number = 0;
// requested sizes, during loading
protected var _reqWidth :Number;
protected var _reqHeight :Number;
}
}
@@ -0,0 +1,122 @@
//
// $Id$
//
// Nenya library - tools for developing networked games
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/nenya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.flex {
import flash.geom.Point;
import flash.geom.Rectangle;
import mx.core.UIComponent;
import com.threerings.display.DisplayUtil;
/**
* Flex popup utilities.
*/
public class PopUpUtil
{
/**
* Center the popup inside the stage, to which it should already be added.
*/
public static function center (popup :UIComponent) :void
{
centerInRect(popup, createStageRect(popup));
}
/**
* Center the popup within the specified rectangle.
*/
public static function centerInRect (popup :UIComponent, rect :Rectangle) :void
{
var p :Point = DisplayUtil.centerRectInRect(new Rectangle(0, 0,
popup.getExplicitOrMeasuredWidth(), popup.getExplicitOrMeasuredHeight()), rect);
popup.x = p.x;
popup.y = p.y;
}
/**
* Fit the popup inside the stage.
*/
public static function fit (popup :UIComponent) :void
{
fitInRect(popup, createStageRect(popup));
}
/**
* Fit the popup inside the specified rectangle.
*/
public static function fitInRect (popup :UIComponent, rect :Rectangle) :void
{
var p :Point = DisplayUtil.fitRectInRect(createPopupRect(popup), rect);
popup.x = p.x;
popup.y = p.y;
}
/**
* Try to fit inside the specified rectangle, also avoiding other popups.
*
* @param popup the popup we'll try to move.
* @param bounds the boundary within which to place the popup.
* @param padding extra padding to place between popups (but not around the inside of
* the bounds, do that yourself if you want it).
*/
public static function avoidOtherPopups (
popup :UIComponent, bounds :Rectangle, padding :int = 0) :void
{
var avoid :Array = [];
var r :Rectangle;
// find the other popups we need to avoid
for (var ii :int = popup.parent.numChildren - 1; ii >= 0; ii--) {
var comp :UIComponent = popup.parent.getChildAt(ii) as UIComponent;
if (comp != null && comp.isPopUp && comp.visible && comp != popup) {
r = createPopupRect(comp);
r.inflate(padding, padding);
avoid.push(r);
}
}
r = createPopupRect(popup);
// put the rectangle in-bounds, so that even if avoiding the others fails, we're
// at least in-bounds when fitRectInRect resets to the original position.
var p :Point = DisplayUtil.fitRectInRect(r, bounds);
r.x = p.x;
r.y = p.y; // assigning to topLeft adjusts width/height too, so don't do it!
DisplayUtil.positionRect(r, bounds, avoid);
popup.x = r.x;
popup.y = r.y;
}
/**
* Wee utility method.
*/
protected static function createStageRect (popup :UIComponent) :Rectangle
{
return new Rectangle(0, 0, popup.stage.stageWidth, popup.stage.stageHeight);
}
protected static function createPopupRect (popup :UIComponent) :Rectangle
{
return new Rectangle(popup.x, popup.y,
popup.getExplicitOrMeasuredWidth(), popup.getExplicitOrMeasuredHeight());
}
}
}
@@ -0,0 +1,181 @@
//
// $Id$
//
// Nenya library - tools for developing networked games
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/nenya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.flex {
import flash.display.DisplayObject;
import flash.display.Graphics;
import flash.display.Shape;
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;
_mask = new Shape();
rawChildren.addChild(_mask);
this.mask = _mask;
_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, 0x000000, 1.0);
graphics.drawRect(0, 0, width, height);
var g :Graphics = _mask.graphics;
g.clear();
g.beginFill(0xffffff, 0.0);
g.drawRect(0, 0, width, height);
g.endFill();
}
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(0xffffff, 0.3);
g.lineStyle(1, 0x000000, 0.3);
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 _mask :Shape;
protected var _scale :Number;
protected var _bounds :Rectangle;
protected var _scroller :Rectangle;
}
}
@@ -0,0 +1,42 @@
//
// $Id$
//
// Nenya library - tools for developing networked games
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/nenya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.flex.menuClasses {
import mx.core.IFlexDisplayObject;
import mx.core.IUIComponent;
import mx.controls.menuClasses.MenuListData;
public class CommandListData extends MenuListData
{
/** An already-instantiated icon, to use if the icon property is null. */
public var iconObject :IFlexDisplayObject;
public function CommandListData (text :String, icon :Class, iconObject :IFlexDisplayObject,
labelField :String, uid :String, owner :IUIComponent,
rowIndex :int = 0, columnIndex :int = 0)
{
super(text, icon, labelField, uid, owner, rowIndex, columnIndex);
this.iconObject = iconObject;
}
}
}
@@ -0,0 +1,61 @@
//
// $Id$
//
// Nenya library - tools for developing networked games
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/nenya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.flex.menuClasses {
import flash.display.DisplayObject;
import mx.controls.Menu;
import mx.controls.menuClasses.IMenuDataDescriptor;
import mx.controls.menuClasses.MenuItemRenderer;
public class CommandMenuItemRenderer extends MenuItemRenderer
{
public function CommandMenuItemRenderer ()
{
super();
}
override protected function commitProperties () :void
{
super.commitProperties();
// Note: a menu with no items will have a null listData
if ((icon == null) && (listData is CommandListData)) {
icon = CommandListData(listData).iconObject;
if (icon != null) {
addChild(DisplayObject(icon));
}
}
if (label.visible && (listData != null)) {
var dataDescriptor :IMenuDataDescriptor = Menu(listData.owner).dataDescriptor;
var typeVal :String = dataDescriptor.getType(data);
if (typeVal == "title") {
dataDescriptor.setEnabled(data, false);
label.enabled = false;
label.styleName = "menuTitle";
invalidateDisplayList();
}
}
}
}
}