Flash components we've made, display-related utils.

git-svn-id: svn+ssh://src.earth.threerings.net/nenya/trunk@161 ed5b42cb-e716-0410-a449-f6a68f950b19
This commit is contained in:
Ray Greenwell
2007-02-28 22:50:05 +00:00
parent 6e7b1741ff
commit 8e791eb994
7 changed files with 1246 additions and 0 deletions
+183
View File
@@ -0,0 +1,183 @@
//
// $Id$
//
// Nenya library - tools for developing networked games
// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/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.flash {
import flash.display.DisplayObject;
import flash.display.DisplayObjectContainer;
import flash.geom.Point;
import flash.geom.Rectangle;
import mx.core.IRawChildrenContainer;
public class DisplayUtil
{
/**
* Call the specified function for the display object and all descendants.
*
* This is nearly exactly like mx.utils.DisplayUtil.walkDisplayObjects,
* except this method copes with security errors when examining a child.
*/
public static function applyToHierarchy (
disp :DisplayObject, callbackFunction :Function) :void
{
callbackFunction(disp);
if (disp is DisplayObjectContainer) {
// a little type-unsafety so that we don't have to write two blocks
var o :Object = (disp is IRawChildrenContainer) ?
IRawChildrenContainer(disp).rawChildren : disp;
var nn :int = int(o.numChildren);
for (var ii :int = 0; ii < nn; ii++) {
try {
disp = DisplayObject(o.getChildAt(ii));
} catch (err :SecurityError) {
continue;
}
// and then we apply outside of the try/catch block so that
// we don't hide errors thrown by the callbackFunction.
applyToHierarchy(disp, callbackFunction);
}
}
}
/**
* Returns the most reasonable position for the specified rectangle to
* be placed at so as to maximize its containment by the specified
* bounding rectangle while still placing it as near its original
* coordinates as possible.
*
* @param rect the rectangle to be positioned.
* @param bounds the containing rectangle.
*/
public static function fitRectInRect (
rect :Rectangle , bounds :Rectangle) :Point
{
// Guarantee that the right and bottom edges will be contained
// and do our best for the top and left edges.
var br :Point = bounds.bottomRight;
return new Point(
Math.min(br.x - rect.width, Math.max(rect.x, bounds.x)),
Math.min(br.y - rect.height, Math.max(rect.y, bounds.y)));
}
/**
* Position the specified rectangle within the bounds, avoiding
* any of the Rectangles in the avoid array, which may be destructively
* modified.
*
* @return true if the rectangle was successfully placed, given the
* constraints, or false if the positioning failed (the rectangle will
* be left at its original location.
*/
public static function positionRect (
r :Rectangle, bounds :Rectangle, avoid :Array) :Boolean
{
var origPos :Point = r.topLeft;
var pointSorter :Function = createPointSorter(origPos);
var possibles :Array = new Array();
// start things off with the passed-in point (adjusted to
// be inside the bounds, if needed)
possibles.push(fitRectInRect(r, bounds));
// keep track of area that doesn't generate new possibles
var dead :Array = new Array();
// Note: labeled breaks and continues are supposed to be legal,
// but they throw wacky runtime exceptions for me. So instead
// I'm throwing a boolean and using that to continue the while
/* CHECKPOSSIBLES: */ while (possibles.length > 0) {
try {
var p :Point = (possibles.shift() as Point);
r.x = p.x;
r.y = p.y;
// make sure the rectangle is in the view
if (!bounds.containsRect(r)) {
continue;
}
// and not over a dead area
for each (var deadRect :Rectangle in dead) {
if (deadRect.intersects(r)) {
//continue CHECKPOSSIBLES;
throw true; // continue outer loop
}
}
// see if it hits any rects we're trying to avoid
for (var ii :int = 0; ii < avoid.length; ii++) {
var avoidRect :Rectangle = (avoid[ii] as Rectangle);
if (avoidRect.intersects(r)) {
// remove it from the avoid list
avoid.splice(ii, 1);
// but add it to the dead list
dead.push(avoidRect);
// add 4 new possible points, each pushed in
// one direction
possibles.push(
new Point(avoidRect.x - r.width, r.y),
new Point(r.x, avoidRect.y - r.height),
new Point(avoidRect.x + avoidRect.width, r.y),
new Point(r.x, avoidRect.y + avoidRect.height));
// re-sort the list
possibles.sort(pointSorter);
//continue CHECKPOSSIBLES;
throw true; // continue outer loop
}
}
// hey! if we got here, then it worked!
return true;
} catch (continueWhile :Boolean) {
// simply catch the boolean and use it to continue inner loops
}
}
// we never found a match, move the rectangle back
r.x = origPos.x;
r.y = origPos.y;
return false;
}
/**
* Create a sort Function that can be used to compare Points in an
* Array according to their distance from the specified Point.
*
* Note: The function will always sort according to distance from the
* passed-in point, even if that point's coordinates change after
* the function is created.
*/
public static function createPointSorter (origin :Point) :Function
{
return function (p1 :Point, p2 :Point) :Number {
var dist1 :Number = Point.distance(origin, p1);
var dist2 :Number = Point.distance(origin, p2);
return (dist1 > dist2) ? 1 : ((dist1 < dist2) ? -1 : 0); // signum
};
}
}
}