├── LICENSE ├── README.md ├── com └── player03 │ └── layout │ ├── Direction.hx │ ├── Layout.hx │ ├── LayoutCreator.hx │ ├── Resizable.hx │ ├── Scale.hx │ ├── area │ ├── Area.hx │ └── StageArea.hx │ └── item │ ├── CustomCallback.hx │ ├── Edge.hx │ ├── LayoutItem.hx │ ├── Position.hx │ ├── Size.hx │ └── TextSize.hx ├── haxelib.json └── layout ├── Layout.hx ├── LayoutCreator.hx └── LayoutPreserver.hx /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-2016 Joseph Cloutier 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Advanced Layout 2 | 3 | An easy way to create fluid layouts in OpenFL. There are two main ways to use it: 4 | 5 | 1. [Define your layout using convenience functions](#creating-layouts). 6 | 2. [Take a pre-built layout, and make it scale](#preserving-layouts). 7 | 8 | For a slower introduction to this library and the philosophy behind it, check out [my blog post](https://player03.com/openfl/advanced-layout/). 9 | 10 | Installation 11 | ============ 12 | 13 | ```text 14 | haxelib install advanced-layout 15 | ``` 16 | 17 | Setup 18 | ----- 19 | 20 | When enabling layouts for your project, you need to specify what type of objects you're working with. Add one or more of these lines after your import statements: 21 | 22 | ```haxe 23 | using layout.LayoutCreator.ForOpenFL; //compatible with DisplayObjects 24 | using layout.LayoutCreator.ForRectangles; //compatible with OpenFL's Rectangles 25 | using layout.LayoutCreator.ForFlixel; //compatible with FlxSprites 26 | using layout.LayoutCreator.ForHaxePunk; //compatible with HaxePunk's entities 27 | ``` 28 | 29 | Creating layouts 30 | ================ 31 | 32 | These functions will move your objects into place and keep them there. If you've already arranged everything, consider using the [alternate paradigm](#preserving-layouts). 33 | 34 | Scaling 35 | ------- 36 | 37 | ```haxe 38 | //Sample objects. 39 | var mySprite:Sprite = new Sprite(); 40 | mySprite.graphics.beginFill(0x000000); 41 | mySprite.graphics.drawCircle(40, 40, 40); 42 | addChild(mySprite); 43 | var myBitmap:Bitmap = new Bitmap(Assets.getBitmapData("MyBitmap.png")); 44 | addChild(myBitmap); 45 | 46 | //The easiest way to handle scale. The sprite will now scale with 47 | //the stage. 48 | mySprite.simpleScale(); 49 | 50 | //Make the sprite fill half the stage. Caution: this only changes 51 | //the size; it won't center it. 52 | mySprite.fillPercentWidth(0.5); 53 | mySprite.fillPercentHeight(0.5); 54 | 55 | //Make the bitmap match the width of the sprite, meaning it will 56 | //fill half the stage horizontally. The bitmap's height won't 57 | //scale at all... yet. 58 | myBitmap.matchWidth(sprite); 59 | 60 | //Scale the bitmap's height so that it isn't distorted. 61 | myBitmap.maintainAspectRatio(); 62 | ``` 63 | 64 | Positioning 65 | ----------- 66 | 67 | Always set the position after setting the scale. Most of these functions take the object's current width and height into account, so you want to make sure those values are up to date. 68 | 69 | ```haxe 70 | //Place the bitmap on the right edge of the stage. 71 | myBitmap.alignRight(); 72 | 73 | //Place the sprite in the top-left corner, leaving a small margin. 74 | mySprite.alignTopLeft(5); 75 | 76 | //Place the bitmap 75% of the way down the stage. 77 | myBitmap.verticalPercent(0.75); 78 | 79 | //Place the bitmap a medium distance from the left. 80 | myBitmap.alignLeft(50); 81 | ``` 82 | 83 | Sometimes the best way to create a layout is to specify that one object should be above, below, left of, or right of another object. 84 | 85 | ```haxe 86 | //Place the bitmap below the sprite. (Warning: this only affects the 87 | //y coordinate; the x coordinate will be unchanged.) 88 | myBitmap.below(mySprite); 89 | 90 | //Place the bitmap to the right of the sprite. (This only affects the 91 | //x coordinate, so now it'll be diagonally below.) 92 | myBitmap.rightOf(mySprite); 93 | 94 | //Center the bitmap directly below the sprite. (This affects both the 95 | //x and y coordinates.) 96 | myBitmap.belowCenter(mySprite); 97 | 98 | //Place the bitmap directly right of the sprite, a long distance away. 99 | myBitmap.rightOfCenter(mySprite, 300); 100 | 101 | //Place the sprite in the center of the stage. Warning: now the 102 | //instructions are out of order. The bitmap will be placed to the 103 | //right of wherever the sprite was last time. 104 | mySprite.center(); 105 | 106 | //Correct the ordering. 107 | myBitmap.rightOfCenter(mySprite, 300); 108 | ``` 109 | 110 | Conflict resolution 111 | ------------------- 112 | 113 | Advanced Layout uses an intelligent system to determine whether a given instruction conflicts with a previous one. When a conflict occurs, the old instruction will be removed from the list. 114 | 115 | ```haxe 116 | //The first instruction never conflicts with anything. 117 | myBitmap.simpleWidth(); 118 | 119 | //This does not conflict with simpleWidth(), so both will be used. 120 | myBitmap.fillHeight(); 121 | 122 | //This conflicts with fillHeight(), so fillHeight() will be replaced. 123 | myBitmap.simpleHeight(); 124 | 125 | //This does not conflict with either size command; all three will be used. 126 | myBitmap.alignLeft(); 127 | 128 | //This does not conflict with alignLeft() or the size commands, so 129 | //all of them will be used. 130 | myBitmap.alignBottom(); 131 | 132 | //This conflicts with both alignBottom() and alignLeft(), so both 133 | //will be replaced. 134 | myBitmap.alignTopCenter(); 135 | 136 | //This conflicts with only the "top" part of alignTopCenter(), so 137 | //only that part will be replaced. 138 | myBitmap.above(mySprite); 139 | ``` 140 | 141 | It's obviously more efficient not to call extra functions in the first place, but if for some reason you have to, it won't cause a memory leak. 142 | 143 | Preserving layouts 144 | ================== 145 | 146 | Another approach is to arrange everything by hand inside a pre-defined "stage", and use that as a template. (Adobe Animate provides - or provided? - a WYSIWYG editor for this.) Advanced Layout has a few ways to take your pre-arranged layout and make it scale smoothly. 147 | 148 | Guessing 149 | -------- 150 | 151 | The `preserve()` function will look at an object's size and position, and guess how it should scale. 152 | 153 | If you have a lot of objects and are using OpenFL, `preserveChildren()` will recursively call `preserve()` on each one. This allows you to dramatically simplify OpenFL's "SimpleSWFLayout" sample project: 154 | 155 | ```haxe 156 | var clip:MovieClip = Assets.getMovieClip("layout:Layout"); 157 | addChild(clip); 158 | 159 | clip.preserveChildren(); 160 | ``` 161 | 162 | Not guessing 163 | ------------ 164 | 165 | If guessing isn't reliable enough, you can choose how to preserve an object's location: 166 | 167 | ```haxe 168 | //Make the object follow the right edge. 169 | myObject.stickToRight(); 170 | 171 | //Make the object follow the left edge. If it's on the right, this 172 | //may push it offscreen, or pull it towards the center. (Not pretty!) 173 | myObject.stickToLeft(); 174 | 175 | //Tug of war! The object stretches horizontally so that its left edge 176 | //follows the screen's left, and its right edge follows the screen's right. 177 | myObject.stickToLeftAndRight(); 178 | ``` 179 | 180 | These `stickTo()` functions always preserve whatever margin currently exists. If an object is five pixels from the right, and you call `stickToRight()`, the object will keep a five-pixel margin (except that the margin will scale slightly as the stage gets wider and narrower). 181 | 182 | This is how objects get pushed offscreen. If you call `stickToLeft()` for an object that's on the right, the margin will be enormous (say, 600 pixels on an 800 pixel stage). If the stage gets narrower, the margin will stay at ~600, which could at that point be more than the entire width. 183 | 184 | You usually want to stick an object to the side it's closest to (which `preserve()` is good at), but there are exceptions. 185 | 186 | - If the object is part of a group, it's usually more important to stick it to the same place as the rest of the group. If you call the same `stickTo()` function for the whole group, all the pieces will move as if they're a single unit. `preserve()` is terrible at catching this. 187 | - If the object takes up most of the screen, you may want to stretch it to fill, or you might just want to center it. Depends on the type of object, and which ends up looking better. `preserve()` will err on the side of "stretch it." 188 | 189 | Some guessing 190 | ------------- 191 | 192 | A middle road is also possible. You can use `preserve()` to make some quick guesses, then override what it gets wrong. For example: 193 | 194 | ```haxe 195 | //Assume we have a `ui` object with a pre-defined layout. 196 | 197 | //Start by taking guesses as to how this layout should 198 | //scale, even though some guesses will be wrong. 199 | ui.preserveChildren(); 200 | 201 | //Fortunately, the guesses are wrong in the same way 202 | //every time. Here are the mistakes: 203 | 204 | //The popup window gets stretched horizontally, when 205 | //it should be centered. The vertical axis is fine. 206 | ui.popupWindow.stickToCenterX(); 207 | 208 | //The popup window's x button sticks to the top right 209 | //corner of the stage instead of the popup window. 210 | ui.xButton.stickToCenter(); 211 | ``` 212 | 213 | Supporting other libraries 214 | ========================== 215 | Even though it focuses on the OpenFL ecosystem, Advanced Layout's code is library-agnostic. It's possible to add support for any 2D display library, as long as that library lets you get and set `x`, `y`, `width`, and `height`. (Or the equivalents thereof.) 216 | 217 | To add support for a new library, you need: 218 | 219 | 1. To be able to implicitly cast that library's objects to [`Resizable`](https://github.com/player-03/advanced-layout/blob/master/com/player03/layout/Resizable.hx#L29). 220 | - This will require a subclass of [`ResizableImpl`](https://github.com/player-03/advanced-layout/blob/master/com/player03/layout/Resizable.hx#L112). 221 | 2. A subclass of `LayoutCreator`. 222 | 223 | There are a two main routes you can take. For both examples, assume you're trying to add support for a type called `Sprite2D`. 224 | 225 | 1. If you're using a well-known library, make changes directly to Advanced Layout, and submit them as a pull request. 226 | 1. Modify [Resizable.hx](https://github.com/player-03/advanced-layout/blob/master/com/player03/layout/Resizable.hx) directly, adding a `fromSprite2D` function and a `Sprite2DResizable` class. Follow the examples given and it should work. 227 | 2. Add a `ForSprite2D` class to LayoutCreator.hx, again following the examples given. 228 | 3. Type `using com.player03.LayoutCreator.ForSprite2D;` and you're good to go! 229 | 2. If you don't plan to submit your changes, it's best not to modify Advanced Layout at all, or you might lose your work after an update. Instead, you want to create 230 | 1. Create a `Sprite2DResizable` class, again following the examples given in Resizable.hx. 231 | 2. Create a subclass of `TypedLayoutCreator`, and include a function to convert to `Resizable`. 232 | 233 | ```haxe 234 | import com.player03.layout.LayoutCreator; 235 | import com.player03.layout.Resizable; 236 | 237 | class Sprite2DLayoutCreator extends TypedLayoutCreator { 238 | private static inline function toResizable(sprite:Sprite2D):Resizable { 239 | return new Sprite2DResizable(sprite); 240 | } 241 | } 242 | ``` 243 | 244 | 3. Type `using your.package.Sprite2DLayoutCreator;` and you're good to go! 245 | 246 | Cleaning up 247 | =========== 248 | 249 | If you use the same UI for the entire lifespan of your app, you don't have to worry about cleaning anything up. However, if you want an object to be garbage collected, you'll need to remove Advanced Layout's references to it. 250 | 251 | You can remove objects one at a time, if you like: 252 | 253 | ```haxe 254 | Layout.currentLayout.remove(myObject); 255 | ``` 256 | 257 | This may get tedious if you're trying to dispose of a large number at once. In that case, you'll want to plan ahead. When you first set up these objects, make sure to add them to their own `Layout` object: 258 | 259 | ```haxe 260 | myLayout = new Layout(); 261 | Layout.currentLayout = myLayout; 262 | 263 | //Now when you lay out your objects, they'll be associated with myLayout. 264 | 265 | //... 266 | 267 | //Once you're done laying out objects, you'll want to un-set currentLayout. 268 | //Otherwise, a different class may add items to this class's layout. 269 | Layout.currentLayout = null; 270 | ``` 271 | 272 | Now you have a set of objects all associated with `myLayout`. Once you're done with them, simply call `myLayout.dispose()` to clean up Advanced Layout's references. 273 | -------------------------------------------------------------------------------- /com/player03/layout/Direction.hx: -------------------------------------------------------------------------------- 1 | package com.player03.layout; 2 | 3 | /** 4 | * The cardinal directions. 5 | * @author Joseph Cloutier 6 | */ 7 | enum abstract Direction(Int) { 8 | var LEFT = 0; 9 | var RIGHT = 1; 10 | var TOP = 2; 11 | var BOTTOM = 3; 12 | 13 | /** 14 | * @return True if `LEFT` or `RIGHT`, false if `TOP` or `BOTTOM`. 15 | */ 16 | public inline function isHorizontal():Bool { 17 | return (this & 2) == 0; 18 | } 19 | 20 | /** 21 | * @return True if `TOP` or `BOTTOM`, false if `LEFT` or `RIGHT`. 22 | */ 23 | public inline function isVertical():Bool { 24 | return (this & 2) == 2; 25 | } 26 | 27 | /** 28 | * @return True if `TOP` or `LEFT`, false if `BOTTOM` or `RIGHT`. 29 | */ 30 | public inline function isTopLeft():Bool { 31 | return (this & 1) == 0; 32 | } 33 | 34 | /** 35 | * @return True if `BOTTOM` or `RIGHT`, false if `TOP` or `LEFT`. 36 | */ 37 | public inline function isBottomRight():Bool { 38 | return (this & 1) == 1; 39 | } 40 | } 41 | 42 | class DirectionUtils { 43 | } 44 | -------------------------------------------------------------------------------- /com/player03/layout/Layout.hx: -------------------------------------------------------------------------------- 1 | package com.player03.layout; 2 | 3 | import flash.events.Event; 4 | import flash.Vector; 5 | import com.player03.layout.area.Area; 6 | import com.player03.layout.area.StageArea; 7 | import com.player03.layout.item.CustomCallback; 8 | import com.player03.layout.item.LayoutItem; 9 | import com.player03.layout.Resizable; 10 | 11 | /** 12 | * 13 | * @author Joseph Cloutier 14 | */ 15 | class Layout { 16 | public static var stageLayout(get, null):Layout; 17 | private static function get_stageLayout():Layout { 18 | if(stageLayout == null) { 19 | stageLayout = new Layout(new Scale()); 20 | stageScale = stageLayout.scale; 21 | } 22 | return stageLayout; 23 | } 24 | 25 | private static var stageScale:Scale; 26 | public static function setStageBaseDimensions(width:Int, height:Int):Void { 27 | if(stageScale == null) { 28 | get_stageLayout(); 29 | } 30 | 31 | stageScale.baseWidth = width; 32 | stageScale.baseHeight = height; 33 | stageScale.onResize(); 34 | StageArea.instance.onStageResize(); 35 | } 36 | 37 | /** 38 | * The Layout object that will be used (by LayoutCreator and LayoutPreserver) 39 | * if you don't specify one. You may update this at any time. Setting 40 | * this to null will make it default to stageLayout. 41 | */ 42 | @:isVar public static var currentLayout(get, set):Layout; 43 | private static function get_currentLayout():Layout { 44 | if(currentLayout == null) { 45 | currentLayout = Layout.stageLayout; 46 | } 47 | return currentLayout; 48 | } 49 | private static inline function set_currentLayout(value:Layout):Layout { 50 | //I'd prefer to use (get, default), but that's not allowed. 51 | return currentLayout = value; 52 | } 53 | 54 | public var scale(default, null):Scale; 55 | public var bounds(default, null):Area; 56 | 57 | private var items:Vector; 58 | 59 | public function new(?scale:Scale, ?bounds:Area) { 60 | if(bounds == null) { 61 | this.bounds = StageArea.instance; 62 | } else { 63 | this.bounds = bounds; 64 | } 65 | 66 | if(scale == null) { 67 | if(bounds != null) { 68 | this.scale = new Scale(Std.int(bounds.width), Std.int(bounds.height), bounds); 69 | } else { 70 | this.scale = stageLayout.scale; 71 | } 72 | } else { 73 | this.scale = scale; 74 | } 75 | 76 | this.bounds.addEventListener(Event.CHANGE, onBoundsChanged); 77 | 78 | items = new Vector(); 79 | } 80 | 81 | private function onBoundsChanged(e:Event):Void { 82 | apply(); 83 | } 84 | 85 | /** 86 | * Applies this layout, updating the position, size, etc. of each 87 | * tracked object. Updates are done in the order they were added, so 88 | * be sure to set size before setting position. 89 | */ 90 | public function apply():Void { 91 | for(instruction in items) { 92 | instruction.item.apply(instruction.target, instruction.area, scale); 93 | } 94 | } 95 | 96 | /** 97 | * Applies this layout to the given item only. 98 | */ 99 | public function applyTo(target:Resizable):Void { 100 | for(instruction in items) { 101 | if(instruction.target != null && instruction.target.equals(target)) { 102 | instruction.item.apply(instruction.target, instruction.area, scale); 103 | } 104 | } 105 | } 106 | 107 | public function dispose():Void { 108 | bounds.removeEventListener(Event.CHANGE, onBoundsChanged); 109 | items = null; 110 | if(this == currentLayout) { 111 | currentLayout = null; 112 | } 113 | } 114 | 115 | /** 116 | * Adds a layout item, to be applied when the stage is resized and 117 | * when apply() is called. If no "base" object is specified, the 118 | * entire layout area will be used as the base. 119 | * 120 | * This clears any conflicting items. 121 | */ 122 | public function add(target:Resizable, 123 | item:LayoutItem, 124 | ?base:Resizable):Void { 125 | //If the target was added before, use the original Resizable object. 126 | //(It would be more efficient not to create multiple Resizable objects 127 | //at all, but that isn't feasible.) 128 | var wasAddedBefore:Bool = false; 129 | for(item in items) { 130 | if(item.target != null && item.target.equals(target)) { 131 | target = item.target; 132 | wasAddedBefore = true; 133 | break; 134 | } 135 | } 136 | 137 | if(wasAddedBefore) { 138 | var i:Int = items.length - 1; 139 | while(i >= 0) { 140 | if(items[i].target == target 141 | && LayoutMask.hasConflict( 142 | items[i].item.mask, 143 | item.mask)) { 144 | items.splice(i, 1); 145 | } 146 | i--; 147 | } 148 | } 149 | 150 | var boundItem:BoundItem = 151 | new BoundItem(target, 152 | base != null ? base : bounds, 153 | item); 154 | items.push(boundItem); 155 | boundItem.item.apply(boundItem.target, boundItem.area, scale); 156 | } 157 | 158 | /** 159 | * Adds a callback to be called at the current point in the layout process. 160 | * If items have already been added using add(), those will be applied 161 | * before calling the callback. If more items are added later, those will be 162 | * applied afterwards. 163 | */ 164 | public inline function addCallback(callback:Void -> Void, callImmediately:Bool):Void { 165 | items.push(new BoundItem(null, null, new CustomCallback(callback))); 166 | 167 | if(callImmediately) { 168 | callback(); 169 | } 170 | } 171 | 172 | public inline function addMultiple(target:Resizable, 173 | items:Array, 174 | ?base:Resizable):Void { 175 | for(item in items) { 176 | add(target, item, base); 177 | } 178 | } 179 | 180 | /** 181 | * Removes all references to the given object. 182 | */ 183 | public function remove(target:Resizable):Void { 184 | var i:Int = items.length - 1; 185 | while(i >= 0) { 186 | if(items[i].target == null) { 187 | //Skip callback items. 188 | } else if(items[i].target.equals(target) || items[i].area.equals(target)) { 189 | items.splice(i, 1); 190 | } 191 | 192 | i--; 193 | } 194 | } 195 | 196 | /** 197 | * Removes any references to the given object 198 | * that match the given mask. 199 | */ 200 | public function removeSpecific(target:Resizable, mask:LayoutMask):Void { 201 | var i:Int = items.length - 1; 202 | while(i >= 0) { 203 | if(items[i].target != null 204 | && (items[i].target.equals(target) || items[i].area.equals(target)) 205 | && LayoutMask.hasConflict( 206 | items[i].item.mask, 207 | mask)) { 208 | items.splice(i, 1); 209 | } 210 | 211 | i--; 212 | } 213 | } 214 | 215 | /** 216 | * Removes the given callback function. 217 | */ 218 | public inline function removeCallback(callback:Void -> Void):Void { 219 | var i:Int = items.length - 1; 220 | while(i >= 0) { 221 | if(Std.isOfType(items[i].item, CustomCallback) && (cast items[i].item:CustomCallback).callback == callback) { 222 | items.splice(i, 1); 223 | } 224 | 225 | i--; 226 | } 227 | } 228 | 229 | /** 230 | * @return Whether this target/item pair would overwrite an existing pair. 231 | */ 232 | public function conflictExists(target:Resizable, item:LayoutItem):Bool { 233 | for(i in items) { 234 | if(i.target != null && i.target.equals(target) 235 | && LayoutMask.hasConflict( 236 | i.item.mask, 237 | item.mask)) { 238 | return true; 239 | } 240 | } 241 | return false; 242 | } 243 | 244 | /** 245 | * Finds the InstructionMask values currently associated with the 246 | * given object. 247 | */ 248 | public function getMask(target:Resizable):Int { 249 | var mask:Int = 0; 250 | for(item in items) { 251 | if(item.target != null && item.target.equals(target)) { 252 | mask |= item.item.mask; 253 | } 254 | } 255 | return mask; 256 | } 257 | } 258 | 259 | /** 260 | * It's like bind(), except for a class. 261 | */ 262 | private class BoundItem { 263 | public var target:Resizable; 264 | public var area:Resizable; 265 | public var item:LayoutItem; 266 | 267 | public function new(target:Resizable, area:Resizable, item:LayoutItem) { 268 | this.target = target; 269 | this.area = area; 270 | this.item = item; 271 | } 272 | 273 | public static function sortOrder(a:BoundItem, b:BoundItem):Int { 274 | //Conveniently, the mask bits are already in order. (Hopefully 275 | //they'll stay that way...) 276 | return a.item.mask - b.item.mask; 277 | } 278 | } 279 | -------------------------------------------------------------------------------- /com/player03/layout/LayoutCreator.hx: -------------------------------------------------------------------------------- 1 | package com.player03.layout; 2 | 3 | #if macro 4 | import haxe.macro.Context; 5 | import haxe.macro.Expr; 6 | import haxe.macro.Type; 7 | 8 | using haxe.macro.ComplexTypeTools; 9 | #else 10 | import flash.text.TextField; 11 | import flash.display.DisplayObjectContainer; 12 | import com.player03.layout.item.Edge; 13 | import com.player03.layout.item.LayoutItem.LayoutMask; 14 | import com.player03.layout.item.Position; 15 | import com.player03.layout.item.Size; 16 | import com.player03.layout.item.TextSize; 17 | import com.player03.layout.Resizable; 18 | 19 | /** 20 | * To use this as a static extension, you need to specify what you're using it for: 21 | * 22 | * using layout.LayoutCreator.ForOpenFL; //compatible with DisplayObjects, but not Rectangles 23 | * using layout.LayoutCreator.ForRectangles; //compatible with OpenFL's Rectangles 24 | * using layout.LayoutCreator.ForFlixel; //compatible with FlxSprites 25 | * using layout.LayoutCreator.ForHaxePunk; //compatible with HaxePunk's entities 26 | * 27 | * Once you've picked one or more of the above, without any further setup, you can 28 | * call layout functions as if they were instance methods: 29 | * 30 | * object0.simpleWidth(30); 31 | * object0.simpleHeight(40); 32 | * object0.alignBottomRight(); 33 | * 34 | * object1.simpleScale(); 35 | * object1.center(); 36 | * 37 | * object2.fillWidth(); 38 | * object2.simpleHeight(); 39 | * object2.below(object1); 40 | * 41 | * Remember: set width and height before setting an object's position. Your 42 | * instructions will be run in order, and position often depends on dimensions. 43 | * 44 | * If you've already positioned your objects, try using the stickTo() functions 45 | * to make them stay there and scale appropriately. Alternatively, try using 46 | * preserve() if you want the class to guess which edge(s) each object should 47 | * stick to. 48 | * 49 | * @author Joseph Cloutier 50 | */ 51 | @:build(com.player03.layout.LayoutCreator.LayoutCreatorBuilder.recordFields()) 52 | class LayoutCreator { 53 | private static inline function check(layout:Layout):Layout { 54 | if(layout == null) { 55 | return Layout.currentLayout; 56 | } else { 57 | return layout; 58 | } 59 | } 60 | 61 | //Place objects relative to one another 62 | //===================================== 63 | 64 | /** 65 | * Places the object left of the target, separated by the given margin. 66 | */ 67 | public static inline function leftOf(objectToPlace:Resizable, target:Resizable, ?margin:Float = 0, ?layout:Layout):Void { 68 | check(layout).add(objectToPlace, Position.adjacent(margin, LEFT), target); 69 | } 70 | /** 71 | * Places the object right of the target, separated by the given margin. 72 | */ 73 | public static inline function rightOf(objectToPlace:Resizable, target:Resizable, ?margin:Float = 0, ?layout:Layout):Void { 74 | check(layout).add(objectToPlace, Position.adjacent(margin, RIGHT), target); 75 | } 76 | /** 77 | * Places the object above the target, separated by the given margin. 78 | */ 79 | public static inline function above(objectToPlace:Resizable, target:Resizable, ?margin:Float = 0, ?layout:Layout):Void { 80 | check(layout).add(objectToPlace, Position.adjacent(margin, TOP), target); 81 | } 82 | /** 83 | * Places the object below the target, separated by the given margin. 84 | */ 85 | public static inline function below(objectToPlace:Resizable, target:Resizable, ?margin:Float = 0, ?layout:Layout):Void { 86 | check(layout).add(objectToPlace, Position.adjacent(margin, BOTTOM), target); 87 | } 88 | 89 | /** 90 | * Places the object left of the target, centered vertically, and 91 | * separated by the given margin. 92 | */ 93 | public static inline function leftOfCenter(objectToPlace:Resizable, target:Resizable, ?margin:Float = 0, ?layout:Layout):Void { 94 | layout = check(layout); 95 | layout.add(objectToPlace, Position.adjacent(margin, LEFT), target); 96 | layout.add(objectToPlace, Position.centerY(), target); 97 | } 98 | /** 99 | * Places the object right of the target, centered vertically, and 100 | * separated by the given margin. 101 | */ 102 | public static inline function rightOfCenter(objectToPlace:Resizable, target:Resizable, ?margin:Float = 0, ?layout:Layout):Void { 103 | layout = check(layout); 104 | layout.add(objectToPlace, Position.adjacent(margin, RIGHT), target); 105 | layout.add(objectToPlace, Position.centerY(), target); 106 | } 107 | /** 108 | * Places the object above the target, centered horizontally, and 109 | * separated by the given margin. 110 | */ 111 | public static inline function aboveCenter(objectToPlace:Resizable, target:Resizable, ?margin:Float = 0, ?layout:Layout):Void { 112 | layout = check(layout); 113 | layout.add(objectToPlace, Position.adjacent(margin, TOP), target); 114 | layout.add(objectToPlace, Position.centerX(), target); 115 | } 116 | /** 117 | * Places the object below the target, centered horizontally, and 118 | * separated by the given margin. 119 | */ 120 | public static inline function belowCenter(objectToPlace:Resizable, target:Resizable, ?margin:Float = 0, ?layout:Layout):Void { 121 | layout = check(layout); 122 | layout.add(objectToPlace, Position.adjacent(margin, BOTTOM), target); 123 | layout.add(objectToPlace, Position.centerX(), target); 124 | } 125 | 126 | /** 127 | * Aligns the object with the target along the given edge. For 128 | * instance, if you specify BOTTOM, the object's bottom edge will be 129 | * aligned with the target's bottom edge. 130 | */ 131 | public static inline function alignWith(objectToPlace:Resizable, target:Resizable, direction:Direction, ?layout:Layout):Void { 132 | check(layout).add(objectToPlace, Position.edge(direction), target); 133 | } 134 | /** 135 | * Centers the object horizontally on the target. 136 | */ 137 | public static inline function centerXOn(objectToPlace:Resizable, target:Resizable, ?layout:Layout):Void { 138 | check(layout).add(objectToPlace, Position.centerX(), target); 139 | } 140 | /** 141 | * Centers the object vertically on the target. 142 | */ 143 | public static inline function centerYOn(objectToPlace:Resizable, target:Resizable, ?layout:Layout):Void { 144 | check(layout).add(objectToPlace, Position.centerY(), target); 145 | } 146 | 147 | //Set objects' edges relative to other objects 148 | //============================================ 149 | 150 | /** 151 | * Places the object left of the target, separated by the given 152 | * margin. Compatible with fillAreaRightOf(). 153 | */ 154 | public static function fillAreaLeftOf(objectToPlace:Resizable, target:Resizable, ?margin:Float = 0, ?layout:Layout):Void { 155 | layout = check(layout); 156 | layout.add(objectToPlace, Edge.matchOppositeEdges(RIGHT, margin), target); 157 | 158 | var fill:Edge = Edge.matchSameEdges(LEFT, margin); 159 | if(!layout.conflictExists(objectToPlace, fill)) { 160 | layout.add(objectToPlace, fill); 161 | } 162 | } 163 | 164 | /** 165 | * Places the object right of the target, separated by the given 166 | * margin. Compatible with fillAreaLeftOf(). 167 | */ 168 | public static function fillAreaRightOf(objectToPlace:Resizable, target:Resizable, ?margin:Float = 0, ?layout:Layout):Void { 169 | layout = check(layout); 170 | var fill:Edge = Edge.matchSameEdges(RIGHT, margin); 171 | if(!layout.conflictExists(objectToPlace, fill)) { 172 | layout.add(objectToPlace, fill); 173 | } 174 | 175 | layout.add(objectToPlace, Edge.matchOppositeEdges(LEFT, margin), target); 176 | } 177 | 178 | /** 179 | * Places the object above the target, separated by the given margin. 180 | * Compatible with fillAreaBelow(). 181 | */ 182 | public static function fillAreaAbove(objectToPlace:Resizable, target:Resizable, ?margin:Float = 0, ?layout:Layout):Void { 183 | layout = check(layout); 184 | layout.add(objectToPlace, Edge.matchOppositeEdges(BOTTOM, margin), target); 185 | 186 | var fill:Edge = Edge.matchSameEdges(TOP, margin); 187 | if(!layout.conflictExists(objectToPlace, fill)) { 188 | layout.add(objectToPlace, fill); 189 | } 190 | } 191 | 192 | /** 193 | * Places the object below the target, separated by the given margin. 194 | * Compatible with fillAreaAbove(). 195 | */ 196 | public static function fillAreaBelow(objectToPlace:Resizable, target:Resizable, ?margin:Float = 0, ?layout:Layout):Void { 197 | layout = check(layout); 198 | var fill:Edge = Edge.matchSameEdges(BOTTOM, margin); 199 | if(!layout.conflictExists(objectToPlace, fill)) { 200 | layout.add(objectToPlace, fill); 201 | } 202 | 203 | layout.add(objectToPlace, Edge.matchOppositeEdges(TOP, margin), target); 204 | } 205 | 206 | //Place objects onstage 207 | //===================== 208 | 209 | /** 210 | * Sets the object's x coordinate to this value times Scale.scaleX. 211 | */ 212 | public static inline function simpleX(objectToPlace:Resizable, x:Float, ?layout:Layout):Void { 213 | check(layout).add(objectToPlace, Position.inside(x, LEFT)); 214 | } 215 | /** 216 | * Sets the object's y coordinate to this value times Scale.scaleY. 217 | */ 218 | public static inline function simpleY(objectToPlace:Resizable, y:Float, ?layout:Layout):Void { 219 | check(layout).add(objectToPlace, Position.inside(y, TOP)); 220 | } 221 | 222 | /** 223 | * Centers the object horizontally onscreen. 224 | */ 225 | public static inline function centerX(objectToPlace:Resizable, ?layout:Layout):Void { 226 | check(layout).add(objectToPlace, Position.centerX()); 227 | } 228 | /** 229 | * Centers the object vertically onscreen. 230 | */ 231 | public static inline function centerY(objectToPlace:Resizable, ?layout:Layout):Void { 232 | check(layout).add(objectToPlace, Position.centerY()); 233 | } 234 | /** 235 | * Centers the object onscreen. 236 | */ 237 | public static inline function center(objectToPlace:Resizable, ?layout:Layout):Void { 238 | centerX(objectToPlace, layout); 239 | centerY(objectToPlace, layout); 240 | } 241 | 242 | /** 243 | * Aligns the object to the left edge of the screen. 244 | */ 245 | public static inline function alignLeft(objectToPlace:Resizable, ?margin:Float, ?layout:Layout):Void { 246 | check(layout).add(objectToPlace, margin == null ? Position.edge(LEFT) : Position.inside(margin, LEFT)); 247 | } 248 | /** 249 | * Aligns the object to the right edge of the screen. 250 | */ 251 | public static inline function alignRight(objectToPlace:Resizable, ?margin:Float, ?layout:Layout):Void { 252 | check(layout).add(objectToPlace, margin == null ? Position.edge(RIGHT) : Position.inside(margin, RIGHT)); 253 | } 254 | /** 255 | * Aligns the object to the top edge of the screen. 256 | */ 257 | public static inline function alignTop(objectToPlace:Resizable, ?margin:Float, ?layout:Layout):Void { 258 | check(layout).add(objectToPlace, margin == null ? Position.edge(TOP) : Position.inside(margin, TOP)); 259 | } 260 | /** 261 | * Aligns the object to the bottom edge of the screen. 262 | */ 263 | public static inline function alignBottom(objectToPlace:Resizable, ?margin:Float, ?layout:Layout):Void { 264 | check(layout).add(objectToPlace, margin == null ? Position.edge(BOTTOM) : Position.inside(margin, BOTTOM)); 265 | } 266 | 267 | /** 268 | * Aligns the object to the top-left corner of the screen. 269 | */ 270 | public static inline function alignTopLeft(objectToPlace:Resizable, ?margin:Float, ?layout:Layout):Void { 271 | alignLeft(objectToPlace, margin, layout); 272 | alignTop(objectToPlace, margin, layout); 273 | } 274 | /** 275 | * Aligns the object to the top-right corner of the screen. 276 | */ 277 | public static inline function alignTopRight(objectToPlace:Resizable, ?margin:Float, ?layout:Layout):Void { 278 | alignRight(objectToPlace, margin, layout); 279 | alignTop(objectToPlace, margin, layout); 280 | } 281 | /** 282 | * Aligns the object to the bottom-left corner of the screen. 283 | */ 284 | public static inline function alignBottomLeft(objectToPlace:Resizable, ?margin:Float, ?layout:Layout):Void { 285 | alignLeft(objectToPlace, margin, layout); 286 | alignBottom(objectToPlace, margin, layout); 287 | } 288 | /** 289 | * Aligns the object to the bottom-right corner of the screen. 290 | */ 291 | public static inline function alignBottomRight(objectToPlace:Resizable, ?margin:Float, ?layout:Layout):Void { 292 | alignRight(objectToPlace, margin, layout); 293 | alignBottom(objectToPlace, margin, layout); 294 | } 295 | 296 | /** 297 | * Centers the object along the left edge of the screen. 298 | */ 299 | public static inline function alignLeftCenter(objectToPlace:Resizable, ?margin:Float, ?layout:Layout):Void { 300 | alignLeft(objectToPlace, margin, layout); 301 | centerY(objectToPlace, layout); 302 | } 303 | /** 304 | * Centers the object along the right edge of the screen. 305 | */ 306 | public static inline function alignRightCenter(objectToPlace:Resizable, ?margin:Float, ?layout:Layout):Void { 307 | alignRight(objectToPlace, margin, layout); 308 | centerY(objectToPlace, layout); 309 | } 310 | /** 311 | * Centers the object along the top of the screen. 312 | */ 313 | public static inline function alignTopCenter(objectToPlace:Resizable, ?margin:Float, ?layout:Layout):Void { 314 | centerX(objectToPlace, layout); 315 | alignTop(objectToPlace, margin, layout); 316 | } 317 | /** 318 | * Centers the object along the bottom of the screen. 319 | */ 320 | public static inline function alignBottomCenter(objectToPlace:Resizable, ?margin:Float, ?layout:Layout):Void { 321 | centerX(objectToPlace, layout); 322 | alignBottom(objectToPlace, margin, layout); 323 | } 324 | 325 | 326 | /** 327 | * Sets the object's x coordinate so that it's the given percent of 328 | * the way across the stage. 0 is equivalent to alignLeft(), 0.5 is 329 | * equivalent to centerX(), and 1 is equivalent to alignRight(). 330 | * Values below 0 or above 1 may produce unintuitive results. 331 | */ 332 | public static inline function horizontalPercent(objectToPlace:Resizable, percent:Float, ?layout:Layout):Void { 333 | check(layout).add(objectToPlace, Position.horizontalPercent(percent)); 334 | } 335 | /** 336 | * Sets the object's y coordinate so that it's the given percent of 337 | * the way down the stage. 0 is equivalent to alignTop(), 0.5 is 338 | * equivalent to centerY(), and 1 is equivalent to alignBottom(). 339 | * Values below 0 or above 1 may produce unintuitive results. 340 | */ 341 | public static inline function verticalPercent(objectToPlace:Resizable, percent:Float, ?layout:Layout):Void { 342 | check(layout).add(objectToPlace, Position.verticalPercent(percent)); 343 | } 344 | 345 | //Simple scaling options 346 | //====================== 347 | 348 | /** 349 | * The object will be scaled based on the current scale values. This 350 | * uses the object's initial dimensions, not the object's dimensions at the 351 | * time of calling. 352 | */ 353 | public static inline function simpleScale(objectToScale:Resizable, ?layout:Layout):Void { 354 | simpleWidth(objectToScale, layout); 355 | simpleHeight(objectToScale, layout); 356 | } 357 | /** 358 | * Sets the object's width to this value times Scale.scaleX. If the 359 | * width isn't specified, the object's initial width will be used. 360 | */ 361 | public static inline function simpleWidth(objectToScale:Resizable, ?width:Float, ?layout:Layout):Void { 362 | check(layout).add(objectToScale, Size.simpleWidth(width)); 363 | } 364 | /** 365 | * Sets the object's height to this value times Scale.scaleY. If the 366 | * height isn't specified, the object's initial height will be used. 367 | */ 368 | public static inline function simpleHeight(objectToScale:Resizable, ?height:Float, ?layout:Layout):Void { 369 | check(layout).add(objectToScale, Size.simpleHeight(height)); 370 | } 371 | 372 | /** 373 | * Like simpleScale(), but the object won't go below its initial 374 | * width and height. 375 | */ 376 | public static inline function rigidSimpleScale(objectToScale:Resizable, ?layout:Layout):Void { 377 | rigidSimpleWidth(objectToScale, layout); 378 | rigidSimpleHeight(objectToScale, layout); 379 | } 380 | /** 381 | * Like simpleWidth(), but the object won't go below its initial 382 | * width. 383 | */ 384 | public static inline function rigidSimpleWidth(objectToScale:Resizable, ?width:Float, ?layout:Layout):Void { 385 | if(width == null) { 386 | width = objectToScale.baseWidth; 387 | } 388 | check(layout).add(objectToScale, Size.clampedSimpleWidth(width, width)); 389 | } 390 | /** 391 | * Like simpleHeight(), but the object won't go below its initial 392 | * height. 393 | */ 394 | public static inline function rigidSimpleHeight(objectToScale:Resizable, ?height:Float, ?layout:Layout):Void { 395 | if(height == null) { 396 | height = objectToScale.baseHeight; 397 | } 398 | check(layout).add(objectToScale, Size.clampedSimpleHeight(height, height)); 399 | } 400 | 401 | //Scale objects relative to one another 402 | //===================================== 403 | 404 | /** 405 | * Sets the object's width to match the target's width, minus the 406 | * given margin times two. Call centerXOn() after this to ensure that 407 | * the same margin appears on both sides of the object. 408 | */ 409 | public static inline function matchWidth(objectToScale:Resizable, target:Resizable, ?margin:Float = 0, ?layout:Layout):Void { 410 | check(layout).add(objectToScale, Size.widthMinus(margin * 2), target); 411 | } 412 | /** 413 | * Sets the object's height to match the target's height, minus the 414 | * given margin times two. Call centerYOn() after this to ensure that 415 | * the same margin appears both above and below the object. 416 | */ 417 | public static inline function matchHeight(objectToScale:Resizable, target:Resizable, ?margin:Float = 0, ?layout:Layout):Void { 418 | check(layout).add(objectToScale, Size.heightMinus(margin * 2), target); 419 | } 420 | 421 | //Scale objects relative to the stage 422 | //=================================== 423 | 424 | /** 425 | * Sets the object's width to fill the stage horizontally, minus the 426 | * given margin times two. Call centerX() after this to ensure that 427 | * the same margin appears on both sides of the object. 428 | */ 429 | public static inline function fillWidth(objectToScale:Resizable, ?margin:Float = 0, ?layout:Layout):Void { 430 | check(layout).add(objectToScale, Size.widthMinus(margin * 2)); 431 | } 432 | /** 433 | * Sets the object's height to fill the stage vertically, minus the 434 | * given margin times two. Call centerY() after this to ensure that 435 | * the same margin appears both above and below the object. 436 | */ 437 | public static inline function fillHeight(objectToScale:Resizable, ?margin:Float = 0, ?layout:Layout):Void { 438 | check(layout).add(objectToScale, Size.heightMinus(margin * 2)); 439 | } 440 | 441 | /** 442 | * Scales the object to take up this much of the stage horizontally. 443 | * Caution: despite the name, "percent" should be a value between 0 444 | * and 1. 445 | */ 446 | public static inline function fillPercentWidth(objectToScale:Resizable, percent:Float, ?layout:Layout):Void { 447 | check(layout).add(objectToScale, Size.relativeWidth(percent)); 448 | } 449 | /** 450 | * Scales the object to take up this much of the stage vertically. 451 | * Caution: despite the name, "percent" should be a value between 0 452 | * and 1. 453 | */ 454 | public static inline function fillPercentHeight(objectToScale:Resizable, percent:Float, ?layout:Layout):Void { 455 | check(layout).add(objectToScale, Size.relativeHeight(percent)); 456 | } 457 | 458 | /** 459 | * Like fillPercentWidth(), but the object won't go below its 460 | * initial height. 461 | */ 462 | public static inline function rigidFillPercentWidth(objectToScale:Resizable, percent:Float, ?layout:Layout):Void { 463 | check(layout).add(objectToScale, Size.clampedRelativeWidth(percent, objectToScale.baseWidth)); 464 | } 465 | /** 466 | * Like fillPercentHeight(), but the object won't go below its 467 | * initial height. 468 | */ 469 | public static inline function rigidFillPercentHeight(objectToScale:Resizable, percent:Float, ?layout:Layout):Void { 470 | check(layout).add(objectToScale, Size.clampedRelativeHeight(percent, objectToScale.baseHeight)); 471 | } 472 | 473 | //Miscellaneous 474 | //============= 475 | 476 | /** 477 | * If one dimension is being scaled and the other isn't, this will 478 | * scale the other one to maintain the aspect ratio. If both 479 | * dimensions are already defined, this function will do nothing. 480 | * 481 | * If neither are defined, this will add an item for both, and you 482 | * can replace one of the items later. 483 | */ 484 | public static function maintainAspectRatio(objectToScale:Resizable, ?layout:Layout):Void { 485 | layout = check(layout); 486 | 487 | var mask:Int = layout.getMask(objectToScale); 488 | var width:Bool = (mask & LayoutMask.AFFECTS_WIDTH) != 0; 489 | var height:Bool = (mask & LayoutMask.AFFECTS_HEIGHT) != 0; 490 | 491 | if(!width) { 492 | layout.add(objectToScale, Size.maintainAspectRatio(true)); 493 | } 494 | if(!height) { 495 | layout.add(objectToScale, Size.maintainAspectRatio(false)); 496 | } 497 | } 498 | 499 | //Position objects near an edge, and scale them normally 500 | //====================================================== 501 | 502 | public static inline function stickToLeft(object:Resizable, ?rigid:Bool = false, ?layout:Layout):Void { 503 | layout = check(layout); 504 | if(rigid) { 505 | layout.add(object, Size.rigidSimpleWidth(object.baseWidth)); 506 | layout.add(object, Position.rigidInside(object.x / layout.scale.x, LEFT)); 507 | } else { 508 | layout.add(object, Size.simpleWidth()); 509 | layout.add(object, Position.inside(object.x / layout.scale.x, LEFT)); 510 | } 511 | } 512 | public static inline function stickToRight(object:Resizable, ?rigid:Bool = false, ?layout:Layout):Void { 513 | layout = check(layout); 514 | if(rigid) { 515 | layout.add(object, Size.rigidSimpleWidth(object.baseWidth)); 516 | layout.add(object, Position.rigidInside((layout.bounds.height - object.right) / layout.scale.x, RIGHT)); 517 | } else { 518 | layout.add(object, Size.simpleWidth()); 519 | layout.add(object, Position.inside((layout.bounds.width - object.right) / layout.scale.x, RIGHT)); 520 | } 521 | } 522 | public static inline function stickToTop(object:Resizable, ?rigid:Bool = false, ?layout:Layout):Void { 523 | layout = check(layout); 524 | if(rigid) { 525 | layout.add(object, Size.rigidSimpleHeight(object.baseHeight)); 526 | layout.add(object, Position.rigidInside(object.y / layout.scale.y, TOP)); 527 | } else { 528 | layout.add(object, Size.simpleHeight()); 529 | layout.add(object, Position.inside(object.y / layout.scale.y, TOP)); 530 | } 531 | } 532 | public static inline function stickToBottom(object:Resizable, ?rigid:Bool = false, ?layout:Layout):Void { 533 | layout = check(layout); 534 | if(rigid) { 535 | layout.add(object, Size.rigidSimpleHeight(object.baseHeight)); 536 | layout.add(object, Position.rigidInside((layout.bounds.height - object.bottom) / layout.scale.y, BOTTOM)); 537 | } else { 538 | layout.add(object, Size.simpleHeight()); 539 | layout.add(object, Position.inside((layout.bounds.height - object.bottom) / layout.scale.y, BOTTOM)); 540 | } 541 | } 542 | public static inline function stickToCenterX(object:Resizable, ?rigid:Bool = false, ?layout:Layout):Void { 543 | layout = check(layout); 544 | if(rigid) { 545 | layout.add(object, Size.rigidSimpleWidth(object.baseWidth)); 546 | } else { 547 | layout.add(object, Size.simpleWidth()); 548 | } 549 | layout.add(object, Position.offsetFromCenterX((object.x + object.width / 2 - layout.bounds.width / 2) / layout.scale.x)); 550 | } 551 | public static inline function stickToCenterY(object:Resizable, ?rigid:Bool = false, ?layout:Layout):Void { 552 | layout = check(layout); 553 | if(rigid) { 554 | layout.add(object, Size.rigidSimpleHeight(object.baseHeight)); 555 | } else { 556 | layout.add(object, Size.simpleHeight()); 557 | } 558 | layout.add(object, Position.offsetFromCenterY((object.y + object.height / 2 - layout.bounds.height / 2) / layout.scale.y)); 559 | } 560 | 561 | public static inline function stickToTopLeft(object:Resizable, ?rigid:Bool = false, ?layout:Layout):Void { 562 | stickToLeft(object, rigid, layout); 563 | stickToTop(object, rigid, layout); 564 | } 565 | public static inline function stickToTopRight(object:Resizable, ?rigid:Bool = false, ?layout:Layout):Void { 566 | stickToRight(object, rigid, layout); 567 | stickToTop(object, rigid, layout); 568 | } 569 | public static inline function stickToBottomLeft(object:Resizable, ?rigid:Bool = false, ?layout:Layout):Void { 570 | stickToLeft(object, rigid, layout); 571 | stickToBottom(object, rigid, layout); 572 | } 573 | public static inline function stickToBottomRight(object:Resizable, ?rigid:Bool = false, ?layout:Layout):Void { 574 | stickToRight(object, rigid, layout); 575 | stickToBottom(object, rigid, layout); 576 | } 577 | public static inline function stickToLeftCenter(object:Resizable, ?rigid:Bool = false, ?layout:Layout):Void { 578 | stickToLeft(object, rigid, layout); 579 | stickToCenterY(object, rigid, layout); 580 | } 581 | public static inline function stickToRightCenter(object:Resizable, ?rigid:Bool = false, ?layout:Layout):Void { 582 | stickToRight(object, rigid, layout); 583 | stickToCenterY(object, rigid, layout); 584 | } 585 | public static inline function stickToTopCenter(object:Resizable, ?rigid:Bool = false, ?layout:Layout):Void { 586 | stickToCenterX(object, rigid, layout); 587 | stickToTop(object, rigid, layout); 588 | } 589 | public static inline function stickToBottomCenter(object:Resizable, ?rigid:Bool = false, ?layout:Layout):Void { 590 | stickToCenterX(object, rigid, layout); 591 | stickToBottom(object, rigid, layout); 592 | } 593 | public static inline function stickToCenter(object:Resizable, ?rigid:Bool = false, ?layout:Layout):Void { 594 | stickToCenterX(object, rigid, layout); 595 | stickToCenterY(object, rigid, layout); 596 | } 597 | 598 | //Stretch objects to fill the stage (minus any margins) 599 | //===================================================== 600 | 601 | public static function stickToLeftAndRight(object:Resizable, ?rigid:Bool = false, ?layout:Layout):Void { 602 | layout = check(layout); 603 | if(rigid) { 604 | layout.add(object, Size.clampedWidthMinus(layout.scale.baseWidth - object.width, object.baseWidth)); 605 | layout.add(object, Position.rigidInside(object.x, LEFT)); 606 | } else { 607 | layout.add(object, Size.widthMinus(layout.scale.baseWidth - object.width)); 608 | layout.add(object, Position.inside(object.x, LEFT)); 609 | } 610 | } 611 | public static inline function stickToTopAndBottom(object:Resizable, ?rigid:Bool = false, ?layout:Layout):Void { 612 | layout = check(layout); 613 | if(rigid) { 614 | layout.add(object, Size.clampedHeightMinus(layout.scale.baseHeight - object.height, object.baseHeight)); 615 | layout.add(object, Position.rigidInside(object.y, TOP)); 616 | } else { 617 | layout.add(object, Size.heightMinus(layout.scale.baseHeight - object.height)); 618 | layout.add(object, Position.inside(object.y, TOP)); 619 | } 620 | } 621 | public static inline function stickToAllSides(object:Resizable, ?rigid:Bool = false, ?layout:Layout):Void { 622 | stickToLeftAndRight(object, rigid, layout); 623 | stickToTopAndBottom(object, rigid, layout); 624 | } 625 | 626 | //Intelligent (?) positioning 627 | //=========================== 628 | 629 | /** 630 | * Guess which edges the object should stick to, or if it should stick to 631 | * the center, or if it should stretch to fill the stage. 632 | */ 633 | public static function preserve(object:Resizable, ?rigid:Bool = false, ?layout:Layout):Void { 634 | layout = check(layout); 635 | 636 | if(object.baseWidth > layout.scale.baseWidth / 2) { 637 | stickToLeftAndRight(object, rigid, layout); 638 | } else { 639 | var leftMargin:Float = object.left; 640 | var rightMargin:Float = layout.scale.baseWidth - object.right; 641 | var centerDifferenceX:Float = Math.abs(object.centerX - layout.scale.baseWidth / 2); 642 | 643 | if(centerDifferenceX < leftMargin && centerDifferenceX < rightMargin) { 644 | stickToCenterX(object, rigid, layout); 645 | } else if(leftMargin < rightMargin) { 646 | stickToLeft(object, rigid, layout); 647 | } else { 648 | stickToRight(object, rigid, layout); 649 | } 650 | } 651 | 652 | if(object.baseHeight > layout.scale.baseHeight / 2) { 653 | stickToTopAndBottom(object, rigid, layout); 654 | } else { 655 | var topMargin:Float = object.top; 656 | var bottomMargin:Float = layout.scale.baseHeight - object.bottom; 657 | var centerDifferenceY:Float = Math.abs(object.centerY - layout.scale.baseHeight / 2); 658 | 659 | if(centerDifferenceY < topMargin && centerDifferenceY < bottomMargin) { 660 | stickToCenterY(object, rigid, layout); 661 | } else if(topMargin < bottomMargin) { 662 | stickToTop(object, rigid, layout); 663 | } else { 664 | stickToBottom(object, rigid, layout); 665 | } 666 | } 667 | } 668 | } 669 | 670 | //This macro copies the functions from `LayoutCreator` into direct 671 | //subclasses of `TypedLayoutCreator`, modifying the functions to 672 | //accept the given type. 673 | @:autoBuild(com.player03.layout.LayoutCreator.LayoutCreatorBuilder.build()) 674 | @:noCompletion class TypedLayoutCreator extends LayoutCreator { 675 | } 676 | 677 | class ForAreas extends TypedLayoutCreator { 678 | } 679 | 680 | class ForRectangles extends TypedLayoutCreator { 681 | } 682 | 683 | class ForOpenFL extends TypedLayoutCreator { 684 | /** 685 | * Scales the text in the given text field. If no base size is 686 | * specified, the default text size will be used. If a minimum text 687 | * size is specified, the given value will be used. 688 | */ 689 | public static inline function simpleTextSize(textField:TextField, ?baseTextSize:Int, ?minimumTextSize:Int, ?layout:Layout):Void { 690 | if(baseTextSize == null) { 691 | baseTextSize = Std.int(textField.defaultTextFormat.size); 692 | } 693 | if(minimumTextSize == null) { 694 | LayoutCreator.check(layout).add(textField, TextSize.simpleTextSize(baseTextSize)); 695 | } else { 696 | LayoutCreator.check(layout).add(textField, TextSize.textSizeWithMinimum(baseTextSize, minimumTextSize)); 697 | } 698 | } 699 | 700 | /** 701 | * Guess how to scale all children of the given object. 702 | */ 703 | public static inline function preserveChildren(parent:DisplayObjectContainer, ?rigid:Bool = false, ?layout:Layout):Void { 704 | for(i in 0...parent.numChildren) { 705 | LayoutCreator.preserve(parent.getChildAt(i), rigid, layout); 706 | } 707 | } 708 | } 709 | 710 | #if haxeui 711 | class ForHaxeUI extends TypedLayoutCreator { 712 | } 713 | #end 714 | #if flixel 715 | class ForFlixel extends TypedLayoutCreator { 716 | } 717 | #end 718 | #if haxepunk 719 | class ForHaxePunk extends TypedLayoutCreator { 720 | } 721 | #end 722 | #end 723 | 724 | @:noCompletion class LayoutCreatorBuilder { 725 | #if macro 726 | private static var layoutCreatorFields:Array; 727 | #end 728 | 729 | @:allow(com.player03.layout.LayoutCreator) 730 | private static macro function recordFields():Array { 731 | layoutCreatorFields = Context.getBuildFields(); 732 | return layoutCreatorFields; 733 | } 734 | 735 | public static macro function build():Array { 736 | var fields:Array = Context.getBuildFields(); 737 | 738 | var localClass:ClassType = Context.getLocalClass().get(); 739 | if(localClass.superClass.t.get().name != "TypedLayoutCreator") { 740 | return fields; 741 | } 742 | 743 | var params:Array = localClass.superClass.params; 744 | if(params.length != 1) { 745 | Context.error("Subclasses of TypedLayoutCreator must specify exactly one type parameter.", localClass.pos); 746 | } 747 | 748 | var inputType:ComplexType = Context.toComplexType(Context.follow(params[0])); 749 | 750 | //Hopefully the superclass will always be processed first, but if not, it's best not to 751 | //throw an error. That way, the other macros can finish and the next attempt will work. 752 | if(layoutCreatorFields == null) { 753 | Context.warning("Macros got executed in the wrong order. Please try again without restarting the language server.", Context.currentPos()); 754 | return fields; 755 | } 756 | 757 | var hasToResizableFunction:Bool = false; 758 | for(field in fields) { 759 | if(field.name == "toResizable") { 760 | switch(field.kind) { 761 | case FFun({ args: [arg], ret: TPath({ name: "Resizable" }) }): 762 | arg.type = inputType; 763 | hasToResizableFunction = true; 764 | default: 765 | Context.warning("toResizable() should take one argument and return Resizable.", field.pos); 766 | } 767 | 768 | if(field.access == null || field.access.indexOf(AStatic) < 0) { 769 | if(field.access == null) { 770 | field.access = []; 771 | } 772 | 773 | field.access.push(AStatic); 774 | 775 | var staticMessage:String = "toResizable() must be static."; 776 | if(field.access.indexOf(AInline) < 0) { 777 | Context.info(staticMessage + " Consider inlining it as well, for performance.", field.pos); 778 | } else { 779 | Context.info(staticMessage, field.pos); 780 | } 781 | } 782 | 783 | break; 784 | } 785 | } 786 | if(!hasToResizableFunction && !Context.unify(inputType.toType(), (macro:com.player03.layout.Resizable).toType())) { 787 | Context.error("Can't unify " + (new haxe.macro.Printer().printComplexType(inputType)) + " with Resizable. To get around this, define a toResizable() function that explicitly converts.", localClass.pos); 788 | } 789 | 790 | for(field in layoutCreatorFields) { 791 | if(field.access.indexOf(APublic) < 0 || field.access.indexOf(AStatic) < 0) { 792 | continue; 793 | } 794 | 795 | switch(field.kind) { 796 | case FFun(f): 797 | if(f.args.length < 2) { 798 | continue; 799 | } 800 | 801 | var args:Array = f.args.copy(); 802 | args[0] = { 803 | name: args[0].name, 804 | type: inputType 805 | }; 806 | 807 | var callName:String = field.name; 808 | var callArgs:Array = [for(arg in args) @:pos(field.pos) macro $i{arg.name}]; 809 | if(hasToResizableFunction) { 810 | callArgs[0] = @:pos(field.pos) macro toResizable(${callArgs[0]}); 811 | } 812 | 813 | var fun:Function = { 814 | args: args, 815 | expr: @:pos(field.pos) macro com.player03.layout.LayoutCreator.$callName($a{callArgs}), 816 | ret: f.ret 817 | }; 818 | 819 | fields.push({ 820 | name: field.name, 821 | kind: FFun(fun), 822 | access: [APublic, AStatic, AInline], 823 | pos: field.pos 824 | }); 825 | default: 826 | } 827 | } 828 | 829 | return fields; 830 | } 831 | } 832 | -------------------------------------------------------------------------------- /com/player03/layout/Resizable.hx: -------------------------------------------------------------------------------- 1 | package com.player03.layout; 2 | 3 | import flash.display.DisplayObject; 4 | import flash.geom.Rectangle; 5 | import com.player03.layout.area.Area; 6 | 7 | #if haxeui 8 | import haxe.ui.toolkit.core.interfaces.IDisplayObject; 9 | #end 10 | #if flixel 11 | import flixel.FlxSprite; 12 | #end 13 | #if haxepunk 14 | import haxepunk.Entity; 15 | #end 16 | 17 | /** 18 | * Represents an object with position, width, and height. The following 19 | * are supported, and will be converted automatically: 20 | * Area (from this library) 21 | * DisplayObject (Flash/OpenFL) 22 | * Rectangle (Flash/OpenFL) 23 | * DisplayObject (HaxeUI) 24 | * FlxSprite (Flixel) 25 | * Entity (HaxePunk) 26 | * Custom classes that extend ResizableImpl 27 | */ 28 | @:forward(x, y, width, height, baseWidth, baseHeight, left, right, top, bottom) 29 | abstract Resizable(ResizableImpl) from ResizableImpl { 30 | public var scaleX(get, set):Float; 31 | public var scaleY(get, set):Float; 32 | 33 | public var centerX(get, never):Float; 34 | public var centerY(get, never):Float; 35 | 36 | /** 37 | * Useful for when you need something invisible to mark a position. 38 | */ 39 | public function new() { 40 | this = cast new RectangleResizable(new Rectangle(0, 0, 1, 1)); 41 | } 42 | 43 | @:from private static inline function fromDisplayObject(displayObject:DisplayObject):Resizable { 44 | return cast new DisplayObjectResizable(displayObject); 45 | } 46 | #if haxeui 47 | @:from private static inline function fromHaxeUIObject(haxeUIObject:IDisplayObject):Resizable { 48 | return cast new HaxeUIObjectResizable(haxeUIObject); 49 | } 50 | #end 51 | #if flixel 52 | @:from private static inline function fromFlxSprite(flxSprite:FlxSprite):Resizable { 53 | return cast new FlxSpriteResizable(flxSprite); 54 | } 55 | #end 56 | #if haxepunk 57 | @:from private static inline function fromHaxePunkEntity(entity:Entity):Resizable { 58 | return cast new HaxePunkEntityResizable(entity); 59 | } 60 | #end 61 | @:from private static inline function fromArea(area:Area):Resizable { 62 | return cast new AreaResizable(area); 63 | } 64 | @:from private static inline function fromRectangle(rectangle:Rectangle):Resizable { 65 | return cast new RectangleResizable(rectangle); 66 | } 67 | 68 | private inline function get_scaleX():Float { 69 | return this.width / this.baseWidth; 70 | } 71 | private inline function set_scaleX(value:Float):Float { 72 | this.width = value * this.baseWidth; 73 | return value; 74 | } 75 | private inline function get_scaleY():Float { 76 | return this.height / this.baseHeight; 77 | } 78 | private inline function set_scaleY(value:Float):Float { 79 | this.height = value * this.baseHeight; 80 | return value; 81 | } 82 | 83 | private inline function get_centerX():Float { 84 | return this.x + this.width / 2; 85 | } 86 | private inline function get_centerY():Float { 87 | return this.y + this.height / 2; 88 | } 89 | 90 | public inline function castDisplayObject(type:Class):T { 91 | if(Std.isOfType(this, DisplayObjectResizable)) { 92 | var displayResizable:DisplayObjectResizable = cast this; 93 | if(Std.isOfType(displayResizable.displayObject, type)) { 94 | return cast displayResizable.displayObject; 95 | } else { 96 | throw type + " required!"; 97 | } 98 | } else { 99 | throw type + " required!"; 100 | } 101 | } 102 | 103 | public inline function equals(other:Resizable):Bool { 104 | return this.sourceObject == other.asImpl().sourceObject; 105 | } 106 | 107 | private inline function asImpl():ResizableImpl { 108 | return this; 109 | } 110 | } 111 | 112 | class ResizableImpl { 113 | public var x(get, set):Float; 114 | public var y(get, set):Float; 115 | public var width(get, set):Float; 116 | public var height(get, set):Float; 117 | public var baseWidth:Float; 118 | public var baseHeight:Float; 119 | 120 | public var sourceObject(get, never):Dynamic; 121 | 122 | /** 123 | * The setter adjusts the x coordinate and width so that the left 124 | * edge moves and the right edge stays the same. 125 | */ 126 | public var left(get, set):Float; 127 | /** 128 | * The setter adjusts the width so that the right edge moves and the 129 | * left edge stays the same. 130 | */ 131 | public var right(get, set):Float; 132 | /** 133 | * The setter adjusts the y coordinate and height so that the top 134 | * moves and the bottom stays the same. 135 | */ 136 | public var top(get, set):Float; 137 | /** 138 | * The setter adjusts the height so that the bottom moves and the top 139 | * stays the same. 140 | */ 141 | public var bottom(get, set):Float; 142 | 143 | private function new() { 144 | baseWidth = width; 145 | baseHeight = height; 146 | } 147 | 148 | private function get_x():Float { return 0; } 149 | private function set_x(value:Float):Float { return 0; } 150 | private function get_y():Float { return 0; } 151 | private function set_y(value:Float):Float { return 0; } 152 | private function get_width():Float { return 0; } 153 | private function set_width(value:Float):Float { return 0; } 154 | private function get_height():Float { return 0; } 155 | private function set_height(value:Float):Float { return 0; } 156 | 157 | private inline function get_left():Float { 158 | return x; 159 | } 160 | private function set_left(value:Float):Float { 161 | width -= value - x; 162 | return x = value; 163 | } 164 | private inline function get_right():Float { 165 | return x + width; 166 | } 167 | private function set_right(value:Float):Float { 168 | width = value - x; 169 | return value; 170 | } 171 | private inline function get_top():Float { 172 | return y; 173 | } 174 | private function set_top(value:Float):Float { 175 | height -= value - y; 176 | return y = value; 177 | } 178 | private inline function get_bottom():Float { return y + height; } 179 | private function set_bottom(value:Float):Float { 180 | height = value - y; 181 | return value; 182 | } 183 | 184 | private function get_sourceObject():Dynamic { 185 | throw "get_sourceObject() must be overridden!"; 186 | return null; 187 | } 188 | } 189 | 190 | private class DisplayObjectResizable extends ResizableImpl { 191 | public var displayObject:DisplayObject; 192 | 193 | public function new(displayObject:DisplayObject) { 194 | this.displayObject = displayObject; 195 | super(); 196 | 197 | baseWidth = displayObject.width / displayObject.scaleX; 198 | baseHeight = displayObject.height / displayObject.scaleY; 199 | } 200 | 201 | private override function get_x():Float { 202 | return displayObject.x; 203 | } 204 | private override function set_x(value:Float):Float { 205 | return displayObject.x = value; 206 | } 207 | 208 | private override function get_y():Float { 209 | return displayObject.y; 210 | } 211 | private override function set_y(value:Float):Float { 212 | return displayObject.y = value; 213 | } 214 | 215 | private override function get_width():Float { 216 | return displayObject.width; 217 | } 218 | private override function set_width(value:Float):Float { 219 | return displayObject.width = value; 220 | } 221 | 222 | private override function get_height():Float { 223 | return displayObject.height; 224 | } 225 | private override function set_height(value:Float):Float { 226 | return displayObject.height = value; 227 | } 228 | 229 | private override function get_sourceObject():Dynamic { 230 | return displayObject; 231 | } 232 | } 233 | 234 | private class AreaResizable extends ResizableImpl { 235 | private var area:Area; 236 | 237 | public function new(area:Area) { 238 | this.area = area; 239 | super(); 240 | } 241 | 242 | private override function get_x():Float { 243 | return area.x; 244 | } 245 | private override function set_x(value:Float):Float { 246 | return area.x = value; 247 | } 248 | 249 | private override function get_y():Float { 250 | return area.y; 251 | } 252 | private override function set_y(value:Float):Float { 253 | return area.y = value; 254 | } 255 | 256 | private override function get_width():Float { 257 | return area.width; 258 | } 259 | private override function set_width(value:Float):Float { 260 | return area.width = value; 261 | } 262 | 263 | private override function get_height():Float { 264 | return area.height; 265 | } 266 | private override function set_height(value:Float):Float { 267 | return area.height = value; 268 | } 269 | 270 | private override function set_left(value:Float):Float { 271 | return area.left = value; 272 | } 273 | private override function set_right(value:Float):Float { 274 | return area.right = value; 275 | } 276 | private override function set_top(value:Float):Float { 277 | return area.top = value; 278 | } 279 | private override function set_bottom(value:Float):Float { 280 | return area.bottom = value; 281 | } 282 | 283 | private override function get_sourceObject():Dynamic { 284 | return area; 285 | } 286 | } 287 | 288 | private class RectangleResizable extends ResizableImpl { 289 | private var rectangle:Rectangle; 290 | 291 | public function new(rectangle:Rectangle) { 292 | this.rectangle = rectangle; 293 | super(); 294 | } 295 | 296 | private override function get_x():Float { 297 | return rectangle.x; 298 | } 299 | private override function set_x(value:Float):Float { 300 | return rectangle.x = value; 301 | } 302 | 303 | private override function get_y():Float { 304 | return rectangle.y; 305 | } 306 | private override function set_y(value:Float):Float { 307 | return rectangle.y = value; 308 | } 309 | 310 | private override function get_width():Float { 311 | return rectangle.width; 312 | } 313 | private override function set_width(value:Float):Float { 314 | return rectangle.width = value; 315 | } 316 | 317 | private override function get_height():Float { 318 | return rectangle.height; 319 | } 320 | private override function set_height(value:Float):Float { 321 | return rectangle.height = value; 322 | } 323 | 324 | private override function get_sourceObject():Dynamic { 325 | return rectangle; 326 | } 327 | } 328 | 329 | #if haxeui 330 | private class HaxeUIObjectResizable extends ResizableImpl { 331 | public var displayObject:IDisplayObject; 332 | 333 | public function new(displayObject:IDisplayObject) { 334 | this.displayObject = displayObject; 335 | super(); 336 | } 337 | 338 | private override function get_x():Float { 339 | return displayObject.x; 340 | } 341 | private override function set_x(value:Float):Float { 342 | return displayObject.x = value; 343 | } 344 | 345 | private override function get_y():Float { 346 | return displayObject.y; 347 | } 348 | private override function set_y(value:Float):Float { 349 | return displayObject.y = value; 350 | } 351 | 352 | private override function get_width():Float { 353 | return displayObject.width; 354 | } 355 | private override function set_width(value:Float):Float { 356 | return displayObject.width = value; 357 | } 358 | 359 | private override function get_height():Float { 360 | return displayObject.height; 361 | } 362 | private override function set_height(value:Float):Float { 363 | return displayObject.height = value; 364 | } 365 | 366 | private override function get_sourceObject():Dynamic { 367 | return displayObject; 368 | } 369 | } 370 | #end 371 | 372 | #if flixel 373 | private class FlxSpriteResizable extends ResizableImpl { 374 | public var flxSprite:FlxSprite; 375 | 376 | public function new(flxSprite:FlxSprite) { 377 | this.flxSprite = flxSprite; 378 | super(); 379 | } 380 | 381 | private override function get_x():Float { 382 | return flxSprite.x; 383 | } 384 | private override function set_x(value:Float):Float { 385 | return flxSprite.x = value; 386 | } 387 | 388 | private override function get_y():Float { 389 | return flxSprite.y; 390 | } 391 | private override function set_y(value:Float):Float { 392 | return flxSprite.y = value; 393 | } 394 | 395 | private override function get_width():Float { 396 | return flxSprite.width; 397 | } 398 | private override function set_width(value:Float):Float { 399 | //setGraphicSize() 400 | flxSprite.scale.x = value / flxSprite.frameWidth; 401 | 402 | //updateHitbox() 403 | flxSprite.width = value; 404 | flxSprite.offset.x = (value - flxSprite.frameWidth) * -0.5; 405 | flxSprite.origin.x = flxSprite.frameWidth * 0.5; 406 | 407 | return value; 408 | } 409 | 410 | private override function get_height():Float { 411 | return flxSprite.height; 412 | } 413 | private override function set_height(value:Float):Float { 414 | //setGraphicSize() 415 | flxSprite.scale.y = value / flxSprite.frameHeight; 416 | 417 | //updateHitbox() 418 | flxSprite.height = value; 419 | flxSprite.offset.y = (value - flxSprite.frameHeight) * -0.5; 420 | flxSprite.origin.y = flxSprite.frameHeight * 0.5; 421 | 422 | return value; 423 | } 424 | 425 | private override function get_sourceObject():Dynamic { 426 | return flxSprite; 427 | } 428 | } 429 | #end 430 | 431 | #if haxepunk 432 | private class HaxePunkEntityResizable extends ResizableImpl { 433 | public var entity:Entity; 434 | 435 | public function new(entity:Entity) { 436 | this.entity = entity; 437 | super(); 438 | } 439 | 440 | private override function get_x():Float { 441 | return entity.localX; 442 | } 443 | private override function set_x(value:Float):Float { 444 | return entity.localX = value; 445 | } 446 | 447 | private override function get_y():Float { 448 | return entity.localY; 449 | } 450 | private override function set_y(value:Float):Float { 451 | return entity.localY = value; 452 | } 453 | 454 | private override function get_width():Float { 455 | return entity.width; 456 | } 457 | private override function set_width(value:Float):Float { 458 | return entity.width = Math.round(value); 459 | } 460 | 461 | private override function get_height():Float { 462 | return entity.height; 463 | } 464 | private override function set_height(value:Float):Float { 465 | return entity.height = Math.round(value); 466 | } 467 | 468 | private override function get_sourceObject():Dynamic { 469 | return entity; 470 | } 471 | } 472 | #end 473 | -------------------------------------------------------------------------------- /com/player03/layout/Scale.hx: -------------------------------------------------------------------------------- 1 | package com.player03.layout; 2 | 3 | import com.player03.layout.area.Area; 4 | import com.player03.layout.area.StageArea; 5 | import com.player03.layout.Direction; 6 | import flash.events.Event; 7 | import flash.Lib; 8 | 9 | #if openfl 10 | import openfl.system.Capabilities; 11 | import lime.app.Application; 12 | #end 13 | 14 | /** 15 | * When you set a "simple" size for an object (as opposed to a relative 16 | * size), that value will be multiplied by either Scale.x or Scale.y. For 17 | * instance, setting simpleWidth(128) when Scale.x is 2 will result in a 18 | * width of 256. All margins are also scaled by these values. 19 | * 20 | * Each Layout can have its own Scale, but by default a Scale is shared 21 | * between nested layouts. 22 | * 23 | * For instance, you might want an object to be 100x100 pixels on an 24 | * 800x600 stage. But on a 5120x2880 stage, that wouldn't be enough, so 25 | * this class will tell you how much to scale it up. 26 | * 27 | * One possibility is to scale the width and height independently: 28 | * multiply width by 6.4, and multiply height by 4.8. However, this will 29 | * cause images to be distorted, which often won't look good. Call 30 | * Scale.stretch() if you want to enable this. 31 | * 32 | * This class offers options to preserve the aspect ratio, but none of 33 | * them are perfect. A 5120x2880 stage is proportionately wider than the 34 | * 800x600 "base" stage, so there's no way to get an exact fit while 35 | * keeping the aspect ratio. 36 | * 37 | * Choosing a scale of 4.8 would make the height fit exactly 38 | * (600 * 4.8 == 2880), but the width would fall short: 800 * 4.8 is only 39 | * 3840. You'd end up with blank space horizontally. This is done by 40 | * default. 41 | * 42 | * If you went with a scale of 6.4, the width would now match, but the 43 | * converted height would be more than the stage height, and some objects 44 | * might get cut off. Call Scale.aspectRatioWithCropping() to enable this. 45 | * @author Joseph Cloutier 46 | */ 47 | @:allow(com.player03.layout.ScaleBehavior) 48 | @:allow(com.player03.layout.Layout) 49 | class Scale { 50 | public static var stageScale(get, never):Scale; 51 | private static inline function get_stageScale():Scale { 52 | return Layout.stageLayout.scale; 53 | } 54 | 55 | public var x(default, null):Float = 1; 56 | public var y(default, null):Float = 1; 57 | 58 | /** 59 | * The average of x and y. 60 | */ 61 | public var average(get, never):Float; 62 | 63 | public var baseWidth:Int; 64 | public var baseHeight:Int; 65 | 66 | /** 67 | * @param baseWidth The initial width of the layout. Scaling will be 68 | * determined by dividing the current width by baseWidth. 69 | * @param baseHeight The initial height of the layout. Scaling will 70 | * be determined by dividing the current height by baseHeight. 71 | * @param area If you want to use something besides the stage 72 | * boundaries to calculate scale, specify it here. In most cases this 73 | * won't be necessary. 74 | */ 75 | public function new(?baseWidth:Int = 800, ?baseHeight:Int = 600, 76 | ?area:Area) { 77 | this.baseWidth = baseWidth; 78 | this.baseHeight = baseHeight; 79 | this.area = area != null ? area : StageArea.instance; 80 | 81 | aspectRatio(); 82 | } 83 | 84 | /** 85 | * The behavior updates Scale.x and Scale.y whenever the stage size 86 | * changes. 87 | */ 88 | private var behavior(null, set):ScaleBehavior; 89 | private function set_behavior(value:ScaleBehavior):ScaleBehavior { 90 | if(behavior != null) { 91 | x = 1; 92 | y = 1; 93 | } 94 | 95 | behavior = value; 96 | 97 | area.dispatchEvent(new Event(Event.CHANGE)); 98 | 99 | return behavior; 100 | } 101 | private function onResize(?e:Event):Void { 102 | if(behavior != null) { 103 | behavior.onResize(Std.int(area.width), Std.int(area.height), this); 104 | } 105 | } 106 | 107 | private inline function get_average():Float { 108 | return (x + y) / 2; 109 | } 110 | 111 | /** 112 | * Do not scale anything. 113 | */ 114 | public inline function noScale():Void { 115 | behavior = null; 116 | } 117 | /** 118 | * Fills the stage. Does not maintain aspect ratio. 119 | */ 120 | public inline function stretch():Void { 121 | behavior = new ExactFitScale(); 122 | } 123 | /** 124 | * Makes everything as large as possible while maintaining aspect 125 | * ratio and fitting onstage. Some space may be left empty. This is 126 | * the default behavior. 127 | */ 128 | public inline function aspectRatio():Void { 129 | behavior = new ShowAllScale(); 130 | } 131 | /** 132 | * Maintains aspect ratio and fills the stage. Some items may extend 133 | * offscreen, and there is additional danger of overlap. 134 | */ 135 | public inline function aspectRatioWithCropping():Void { 136 | behavior = new NoBorderScale(); 137 | } 138 | 139 | #if (openfl && !flash && !html5) 140 | /** 141 | * Scale based on the screen's DPI. This requires OpenFL and 142 | * doesn't work in Flash or HTML5. Use something else in that 143 | * case. 144 | * @param baseDPI The pixels per inch of the device you're 145 | * testing on. The default is 160 because that's what Android 146 | * considers to be "medium" density. 147 | * @param smart Whether to adjust the scale based on how 148 | * large users may expect things to appear onscreen. For 149 | * instance, desktop users with higher resolution screens may 150 | * expect things to look smaller. Defaults to true. 151 | */ 152 | public function screenDPI(?baseDPI:Int = 160, ?smart:Bool = true):Void { 153 | behavior = new DPIScale(baseDPI, smart); 154 | area.dispatchEvent(new Event(Event.CHANGE)); 155 | } 156 | #end 157 | 158 | public var area(default, set):Area; 159 | private function set_area(value:Area):Area { 160 | if(area != null) { 161 | area.removeEventListener(Event.CHANGE, onResize); 162 | } 163 | 164 | if(value == null) { 165 | area = StageArea.instance; 166 | } else { 167 | area = value; 168 | } 169 | 170 | area.addEventListener(Event.CHANGE, onResize, false, 1); 171 | 172 | if(behavior != null) { 173 | onResize(null); 174 | } 175 | 176 | return area; 177 | } 178 | } 179 | 180 | private class ScaleBehavior { 181 | public function new() { 182 | } 183 | 184 | public function onResize(stageWidth:Int, stageHeight:Int, scale:Scale):Void { 185 | scale.x = 1; 186 | scale.y = 1; 187 | } 188 | } 189 | 190 | /** 191 | * Scale based only on the stage width, maintaining aspect ratio. 192 | */ 193 | private class WidthScale extends ScaleBehavior { 194 | public function new() { 195 | super(); 196 | } 197 | 198 | public override function onResize(stageWidth:Int, stageHeight:Int, scale:Scale):Void { 199 | scale.x = stageWidth / scale.baseWidth; 200 | scale.y = scale.x; 201 | } 202 | } 203 | 204 | /** 205 | * Scale based only on the stage height, maintaining aspect ratio. 206 | */ 207 | class HeightScale extends ScaleBehavior { 208 | public function new() { 209 | super(); 210 | } 211 | 212 | public override function onResize(stageWidth:Int, stageHeight:Int, scale:Scale):Void { 213 | scale.x = stageHeight / scale.baseHeight; 214 | scale.y = scale.x; 215 | } 216 | } 217 | 218 | //The following are all named after Flash's StageScaleMode constants. 219 | class ExactFitScale extends ScaleBehavior { 220 | public function new() { 221 | super(); 222 | } 223 | 224 | public override function onResize(stageWidth:Int, stageHeight:Int, scale:Scale):Void { 225 | scale.x = stageWidth / scale.baseWidth; 226 | scale.y = stageHeight / scale.baseHeight; 227 | } 228 | } 229 | 230 | class ShowAllScale extends ScaleBehavior { 231 | public function new() { 232 | super(); 233 | } 234 | 235 | public override function onResize(stageWidth:Int, stageHeight:Int, scale:Scale):Void { 236 | scale.x = Math.min(stageWidth / scale.baseWidth, 237 | stageHeight / scale.baseHeight); 238 | scale.y = scale.x; 239 | } 240 | } 241 | 242 | class NoBorderScale extends ScaleBehavior { 243 | public function new() { 244 | super(); 245 | } 246 | 247 | public override function onResize(stageWidth:Int, stageHeight:Int, scale:Scale):Void { 248 | scale.x = Math.max(stageWidth / scale.baseWidth, 249 | stageHeight / scale.baseHeight); 250 | scale.y = scale.x; 251 | } 252 | } 253 | 254 | #if (openfl && !flash) 255 | class DPIScale extends ScaleBehavior { 256 | private var baseDPI:Float; 257 | private var smart:Bool; 258 | 259 | public function new(baseDPI:Float, smart:Bool) { 260 | super(); 261 | 262 | this.baseDPI = baseDPI; 263 | this.smart = smart; 264 | } 265 | 266 | public override function onResize(stageWidth:Int, stageHeight:Int, scale:Scale):Void { 267 | scale.x = (smart ? Capabilities.screenDPI : Application.current.window.display.dpi) / baseDPI; 268 | scale.y = scale.x; 269 | } 270 | } 271 | #end 272 | -------------------------------------------------------------------------------- /com/player03/layout/area/Area.hx: -------------------------------------------------------------------------------- 1 | package com.player03.layout.area; 2 | 3 | import flash.events.Event; 4 | import flash.events.EventDispatcher; 5 | 6 | /** 7 | * A rectangular region, to place objects within. Note: area boundaries 8 | * will not always be strictly enforced. 9 | * 10 | * After the area changes for any reason, it will dispatch a CHANGE event. 11 | * @author Joseph Cloutier 12 | */ 13 | @:allow(com.player03.layout.Scale) 14 | class Area extends EventDispatcher { 15 | public var x(default, set):Float; 16 | public var y(default, set):Float; 17 | public var width(default, set):Float; 18 | public var height(default, set):Float; 19 | 20 | public var centerX(get, never):Float; 21 | public var centerY(get, never):Float; 22 | public var left(get, set):Float; 23 | public var right(get, set):Float; 24 | public var top(get, set):Float; 25 | public var bottom(get, set):Float; 26 | 27 | public function new(?x:Float = 0, ?y:Float = 0, ?width:Float = 0, ?height:Float = 0) { 28 | super(); 29 | 30 | setTo(x, y, width, height); 31 | } 32 | 33 | /** 34 | * Sets this rectangle to the given values, only dispatching a single 35 | * CHANGE event in the process. 36 | */ 37 | public function setTo(x:Float, y:Float, width:Float, height:Float, ?suppressEvent:Bool = false):Void { 38 | Reflect.setField(this, "x", x); 39 | Reflect.setField(this, "y", y); 40 | Reflect.setField(this, "width", width); 41 | Reflect.setField(this, "height", height); 42 | 43 | if(!suppressEvent) { 44 | queueChangeEvent(); 45 | } 46 | } 47 | 48 | private function set_x(value:Float):Float { 49 | x = value; 50 | queueChangeEvent(); 51 | return x; 52 | } 53 | private function set_y(value:Float):Float { 54 | y = value; 55 | queueChangeEvent(); 56 | return y; 57 | } 58 | private function set_width(value:Float):Float { 59 | width = value; 60 | queueChangeEvent(); 61 | return width; 62 | } 63 | private function set_height(value:Float):Float { 64 | height = value; 65 | queueChangeEvent(); 66 | return height; 67 | } 68 | 69 | private inline function get_left():Float { 70 | return x; 71 | } 72 | private inline function set_left(value:Float):Float { 73 | setTo(value, y, width - (value - x), height); 74 | return value; 75 | } 76 | private inline function get_right():Float { 77 | return x + width; 78 | } 79 | private inline function set_right(value:Float):Float { 80 | width = value - x; 81 | return value; 82 | } 83 | private inline function get_top():Float { 84 | return y; 85 | } 86 | private inline function set_top(value:Float):Float { 87 | setTo(x, value, width, height - (value - y)); 88 | return value; 89 | } 90 | private inline function get_bottom():Float { 91 | return y + height; 92 | } 93 | private inline function set_bottom(value:Float):Float { 94 | height = value - y; 95 | return value; 96 | } 97 | 98 | private inline function get_centerX():Float { 99 | return x + width / 2; 100 | } 101 | private inline function get_centerY():Float { 102 | return y + height / 2; 103 | } 104 | 105 | public inline function clone():Area { 106 | return new Area(x, y, width, height); 107 | } 108 | 109 | private static var currentArea:Area; 110 | private static var queue:Array = []; 111 | 112 | /** 113 | * Dispatches a CHANGE event for this Area, but only after all ongoing 114 | * CHANGE events are resolved. 115 | */ 116 | public function queueChangeEvent():Void { 117 | if(this == currentArea) { 118 | return; 119 | } 120 | 121 | //Queue this Area unless it's already queued. 122 | if(queue.indexOf(this) < 0) { 123 | queue.push(this); 124 | } 125 | 126 | //If the queue isn't currently being handled, dispatch events for each 127 | //Area in the queue. 128 | while(currentArea == null && queue.length > 0) { 129 | currentArea = queue[0]; 130 | queue.splice(0, 1); 131 | 132 | //Dispatch the event. This may extend the queue. 133 | currentArea.dispatchEvent(new Event(Event.CHANGE)); 134 | 135 | currentArea = null; 136 | } 137 | } 138 | 139 | public override function toString():String { 140 | return '(x=$x, y=$y, width=$width, height=$height)'; 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /com/player03/layout/area/StageArea.hx: -------------------------------------------------------------------------------- 1 | package com.player03.layout.area; 2 | 3 | import flash.events.Event; 4 | import flash.events.EventDispatcher; 5 | import flash.display.Stage; 6 | import flash.Lib; 7 | 8 | #if flixel 9 | import flixel.FlxG; 10 | import flixel.system.scaleModes.StageSizeScaleMode; 11 | #end 12 | 13 | /** 14 | * The full area of the stage. Use StageArea.instance rather than 15 | * instantiating it yourself. 16 | * @author Joseph Cloutier 17 | */ 18 | @:allow(com.player03.layout.Layout) 19 | class StageArea extends Area { 20 | public static var instance(get, null):StageArea; 21 | private static function get_instance():StageArea { 22 | if(instance == null) { 23 | instance = new StageArea(); 24 | } 25 | return instance; 26 | } 27 | 28 | private function new() { 29 | super(); 30 | 31 | #if flixel 32 | FlxG.signals.gameResized.add(setStageDimensions); 33 | FlxG.scaleMode = new StageSizeScaleMode(); 34 | #else 35 | Lib.current.stage.addEventListener(Event.RESIZE, onStageResize, false, 1); 36 | onStageResize(null); 37 | #end 38 | } 39 | 40 | #if flixel 41 | private function setStageDimensions(width:Int, height:Int):Void { 42 | super.setTo(0, 0, width, height); 43 | } 44 | #end 45 | 46 | private function onStageResize(?e:Event):Void { 47 | var stage:Stage = Lib.current.stage; 48 | 49 | if(stage.stageWidth != width || stage.stageHeight != height) { 50 | super.setTo(0, 0, stage.stageWidth, stage.stageHeight); 51 | } 52 | } 53 | 54 | //The boundaries are set automatically and can't otherwise be modified. 55 | public override function setTo(x:Float, y:Float, width:Float, height:Float, ?suppressEvent:Bool = false):Void { 56 | } 57 | private override function set_x(value:Float):Float { 58 | return x; 59 | } 60 | private override function set_y(value:Float):Float { 61 | return y; 62 | } 63 | private override function set_width(value:Float):Float { 64 | return width; 65 | } 66 | private override function set_height(value:Float):Float { 67 | return height; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /com/player03/layout/item/CustomCallback.hx: -------------------------------------------------------------------------------- 1 | package com.player03.layout.item; 2 | 3 | import com.player03.layout.item.LayoutItem.LayoutMask; 4 | 5 | class CustomCallback implements LayoutItem { 6 | public var callback:Void -> Void; 7 | public var mask:LayoutMask = 0; 8 | 9 | public function new(callback:Void -> Void) { 10 | this.callback = callback; 11 | } 12 | 13 | public function apply(target:Resizable, area:Resizable, scale:Scale):Void { 14 | callback(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /com/player03/layout/item/Edge.hx: -------------------------------------------------------------------------------- 1 | package com.player03.layout.item; 2 | 3 | import com.player03.layout.Direction; 4 | import com.player03.layout.item.LayoutItem.LayoutMask; 5 | import com.player03.layout.Resizable; 6 | import com.player03.layout.Scale; 7 | import flash.display.DisplayObject; 8 | 9 | /** 10 | * The Edge class moves one of an object's edges at a time. This causes 11 | * it to modify an object's position AND size. 12 | */ 13 | class Edge implements LayoutItem { 14 | /** 15 | * Makes the given edge of the target match the opposite edge of the 16 | * area. For instance, if you pass LEFT, then the left edge of the 17 | * target will match the right edge of the area. 18 | */ 19 | public static function matchOppositeEdges(edge:Direction, ?margin:Float = 0):Edge { 20 | //The class names have to be the opposite of the edge parameter, 21 | //because the parameter refers to the target, and the class names 22 | //refer to the area. 23 | if(edge.isTopLeft()) { 24 | return new OutsideRightOrBottom(edge.isHorizontal(), margin); 25 | } else { 26 | return new OutsideLeftOrTop(edge.isHorizontal(), margin); 27 | } 28 | } 29 | 30 | /** 31 | * Makes the given edge of the target match the same edge of the area. 32 | * For instance, if you pass LEFT, then the left edge of the target 33 | * will match the left edge of the area. 34 | * 35 | * Calling this for all four directions would make the target exactly 36 | * match the area. (Of course, there are more efficient ways to do 37 | * that...) 38 | */ 39 | public static function matchSameEdges(edge:Direction, ?margin:Float):Edge { 40 | if(edge.isTopLeft()) { 41 | return new InsideLeftOrTop(edge.isHorizontal(), margin); 42 | } else { 43 | return new InsideRightOrBottom(edge.isHorizontal(), margin); 44 | } 45 | } 46 | 47 | private var direction:Direction; 48 | private var horizontal:Bool; 49 | public var mask:LayoutMask; 50 | 51 | public function new(direction:Direction) { 52 | this.direction = direction; 53 | horizontal = direction.isHorizontal(); 54 | switch(direction) { 55 | case LEFT: 56 | mask = LayoutMask.SETS_LEFT_EDGE; 57 | case RIGHT: 58 | mask = LayoutMask.SETS_RIGHT_EDGE; 59 | case TOP: 60 | mask = LayoutMask.SETS_TOP_EDGE; 61 | case BOTTOM: 62 | mask = LayoutMask.SETS_BOTTOM_EDGE; 63 | } 64 | } 65 | 66 | public function apply(target:Resizable, area:Resizable, scale:Scale):Void { 67 | var targetEdge:Float = switch(direction) { 68 | case LEFT: 69 | target.left; 70 | case RIGHT: 71 | target.right; 72 | case TOP: 73 | target.top; 74 | case BOTTOM: 75 | target.bottom; 76 | }; 77 | var adjustment:Float = getEdge( 78 | horizontal ? area.x : area.y, 79 | horizontal ? area.width : area.height, 80 | targetEdge, 81 | horizontal ? scale.x : scale.y) 82 | - targetEdge; 83 | 84 | if(adjustment != 0) { 85 | switch(direction) { 86 | case LEFT: 87 | target.left += adjustment; 88 | case RIGHT: 89 | target.right += adjustment; 90 | case TOP: 91 | target.top += adjustment; 92 | case BOTTOM: 93 | target.bottom += adjustment; 94 | } 95 | } 96 | } 97 | 98 | /** 99 | * @return The new value for targetEdge. 100 | */ 101 | private function getEdge(areaMin:Float, areaSize:Float, targetEdge:Float, scale:Float):Float { 102 | return targetEdge; 103 | } 104 | } 105 | 106 | /** 107 | * Sets the right or bottom edge of the target based on the left or top 108 | * edge of the area. 109 | */ 110 | private class OutsideLeftOrTop extends Edge { 111 | private var margin:Float; 112 | 113 | public function new(horizontal:Bool, ?margin:Float = 0) { 114 | super(horizontal ? RIGHT : BOTTOM); 115 | 116 | this.margin = margin; 117 | } 118 | 119 | private override function getEdge(areaMin:Float, areaSize:Float, targetEdge:Float, scale:Float):Float { 120 | return areaMin - margin * scale; 121 | } 122 | } 123 | /** 124 | * Sets the left or top edge of the target based on the right or bottom 125 | * edge of the area. 126 | */ 127 | private class OutsideRightOrBottom extends Edge { 128 | private var margin:Float; 129 | 130 | public function new(horizontal:Bool, ?margin:Float = 0) { 131 | super(horizontal ? LEFT : TOP); 132 | 133 | this.margin = margin; 134 | } 135 | 136 | private override function getEdge(areaMin:Float, areaSize:Float, targetEdge:Float, scale:Float):Float { 137 | return areaMin + areaSize + margin * scale; 138 | } 139 | } 140 | 141 | /** 142 | * Sets the left or top edge of the target based on the same edge of the 143 | * area. 144 | */ 145 | private class InsideLeftOrTop extends Edge { 146 | private var margin:Float; 147 | 148 | public function new(horizontal:Bool, ?margin:Float = 0) { 149 | super(horizontal ? LEFT : TOP); 150 | 151 | this.margin = margin; 152 | } 153 | 154 | private override function getEdge(areaMin:Float, areaSize:Float, targetEdge:Float, scale:Float):Float { 155 | return areaMin + margin * scale; 156 | } 157 | } 158 | /** 159 | * Sets the right or bottom edge of the target based on the same edge of 160 | * the area. 161 | */ 162 | private class InsideRightOrBottom extends Edge { 163 | private var margin:Float; 164 | 165 | public function new(horizontal:Bool, ?margin:Float = 0) { 166 | super(horizontal ? RIGHT : BOTTOM); 167 | 168 | this.margin = margin; 169 | } 170 | 171 | private override function getEdge(areaMin:Float, areaSize:Float, targetEdge:Float, scale:Float):Float { 172 | return areaMin + areaSize - margin * scale; 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /com/player03/layout/item/LayoutItem.hx: -------------------------------------------------------------------------------- 1 | package com.player03.layout.item; 2 | 3 | import com.player03.layout.Scale; 4 | import com.player03.layout.Resizable; 5 | 6 | /** 7 | * @author Joseph Cloutier 8 | */ 9 | interface LayoutItem { 10 | var mask:LayoutMask; 11 | function apply(target:Resizable, area:Resizable, scale:Scale):Void; 12 | } 13 | 14 | abstract LayoutMask(Int) from Int to Int { 15 | /** 16 | * The bits associated with the default method of masking. 17 | */ 18 | private static inline var DEFAULT_BITS:LayoutMask = AFFECTS_X | AFFECTS_Y | AFFECTS_WIDTH | AFFECTS_HEIGHT; 19 | /** 20 | * This layout item conflicts with any other item that updates x. 21 | */ 22 | public static inline var AFFECTS_X:LayoutMask = 0x1; 23 | /** 24 | * This layout item conflicts with any other item that updates y. 25 | */ 26 | public static inline var AFFECTS_Y:LayoutMask = 0x2; 27 | /** 28 | * This layout item conflicts with any other item that updates width. 29 | */ 30 | public static inline var AFFECTS_WIDTH:LayoutMask = 0x4; 31 | /** 32 | * This layout item conflicts with any other item that updates height. 33 | */ 34 | public static inline var AFFECTS_HEIGHT:LayoutMask = 0x8; 35 | 36 | /** 37 | * The bits associated with the "edge" method of masking. If both 38 | * masks have any of these bits, the default bits should be ignored. 39 | * The Edge class is meant to conflict with both the Position and 40 | * Size classes, but not with itself. 41 | */ 42 | private static inline var EDGE_BITS:LayoutMask = 0x10 | 0x20 | 0x40 | 0x80; 43 | /** 44 | * This layout item conflicts with any other item that updates x or 45 | * width, unless the other item specifically updates the right edge. 46 | */ 47 | public static inline var SETS_LEFT_EDGE:LayoutMask = 0x10 | AFFECTS_X | AFFECTS_WIDTH; 48 | /** 49 | * This layout item conflicts with any other item that updates x or 50 | * width, unless the other item specifically updates the left edge. 51 | */ 52 | public static inline var SETS_RIGHT_EDGE:LayoutMask = 0x20 | AFFECTS_X | AFFECTS_WIDTH; 53 | /** 54 | * This layout item conflicts with any other item that updates y or 55 | * height, unless the other item specifically updates the bottom edge. 56 | */ 57 | public static inline var SETS_TOP_EDGE:LayoutMask = 0x40 | AFFECTS_Y | AFFECTS_HEIGHT; 58 | /** 59 | * This layout item conflicts with any other item that updates y or 60 | * height, unless the other item specifically updates the top edge. 61 | */ 62 | public static inline var SETS_BOTTOM_EDGE:LayoutMask = 0x80 | AFFECTS_Y | AFFECTS_HEIGHT; 63 | 64 | /** 65 | * This layout item changes the text size used by a text field. 66 | */ 67 | public static inline var AFFECTS_TEXT_SIZE:LayoutMask = 0x100; 68 | 69 | public static inline function hasConflict(maskA:Int, maskB:Int):Bool { 70 | if(setsEdge(maskA) && setsEdge(maskB)) { 71 | maskA = maskA & EDGE_BITS; 72 | maskB = maskB & EDGE_BITS; 73 | } 74 | return (maskA & maskB) != 0; 75 | } 76 | 77 | public static inline function setsEdge(mask:Int):Bool { 78 | return (mask & EDGE_BITS) != 0; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /com/player03/layout/item/Position.hx: -------------------------------------------------------------------------------- 1 | package com.player03.layout.item; 2 | 3 | import com.player03.layout.Direction; 4 | import com.player03.layout.item.LayoutItem.LayoutMask; 5 | import com.player03.layout.Resizable; 6 | import com.player03.layout.Scale; 7 | import flash.display.DisplayObject; 8 | 9 | /** 10 | * Each Position value represents position along only one axis. You will 11 | * have to use two Position values if you want to fully specify an object's 12 | * position. It is strongly recommended that you apply size before position. 13 | */ 14 | class Position implements LayoutItem { 15 | public static function edge(edge:Direction):Position { 16 | switch(edge) { 17 | case LEFT: 18 | return new Position(true); 19 | case RIGHT: 20 | return new Percent(true, 1); 21 | case TOP: 22 | return new Position(false); 23 | case BOTTOM: 24 | return new Percent(false, 1); 25 | } 26 | } 27 | 28 | public static inline function centerX():Position { 29 | return new Percent(true, 0.5); 30 | } 31 | public static inline function centerY():Position { 32 | return new Percent(false, 0.5); 33 | } 34 | public static inline function horizontalPercent(percent:Float):Position { 35 | return new Percent(true, percent); 36 | } 37 | public static inline function verticalPercent(percent:Float):Position { 38 | return new Percent(false, percent); 39 | } 40 | 41 | /** 42 | * @param offset In unscaled pixels. 43 | */ 44 | public static inline function offsetFromCenterX(offset:Float):Position { 45 | return new PercentWithOffset(true, 0.5, offset); 46 | } 47 | /** 48 | * @param offset In unscaled pixels. 49 | */ 50 | public static inline function offsetFromCenterY(offset:Float):Position { 51 | return new PercentWithOffset(false, 0.5, offset); 52 | } 53 | 54 | /** 55 | * Places the target next to the area, in the given direction. 56 | * Affects only one axis. 57 | */ 58 | public static inline function adjacent(margin:Float, direction:Direction):Position { 59 | return new Outside(margin, direction); 60 | } 61 | 62 | /** 63 | * Places the target inside the area, next to the given edge. 64 | */ 65 | public static inline function inside(margin:Float, direction:Direction):Position { 66 | return new Inside(margin, direction); 67 | } 68 | 69 | /** 70 | * Places the target inside the area, next to the given edge. 71 | * The margin won't go below its initial value. 72 | */ 73 | public static inline function rigidInside(margin:Float, direction:Direction):Position { 74 | return new RigidInside(margin, direction); 75 | } 76 | 77 | private var horizontal:Bool; 78 | public var mask:LayoutMask; 79 | 80 | public function new(horizontal:Bool) { 81 | this.horizontal = horizontal; 82 | mask = horizontal ? LayoutMask.AFFECTS_X : LayoutMask.AFFECTS_Y; 83 | } 84 | 85 | public function apply(target:Resizable, area:Resizable, scale:Scale):Void { 86 | if(horizontal) { 87 | var x:Float = getCoordinate(area.x, area.width, target.width, scale.x); 88 | if(x != target.x) { 89 | target.x = x; 90 | } 91 | } else { 92 | var y:Float = getCoordinate(area.y, area.height, target.height, scale.y); 93 | if(y != target.y) { 94 | target.y = y; 95 | } 96 | } 97 | } 98 | 99 | private function getCoordinate(areaMin:Float, areaSize:Float, targetSize:Float, scale:Float):Float { 100 | return areaMin; 101 | } 102 | } 103 | 104 | /** 105 | * Define a location within the area as a percent of the distance from 106 | * the top/left to the bottom/right. For instance, new Percent(0.25) 107 | * will place the display object towards the top or the left (depending 108 | * on which access this is used for). 109 | * 110 | * It's possible to define percentages beyond [0, 1], but this is 111 | * discouraged, as the object may not be placed where you might expect. 112 | */ 113 | private class Percent extends Position { 114 | private var percent:Float; 115 | 116 | public function new(horizontal:Bool, percent:Float) { 117 | super(horizontal); 118 | this.percent = percent; 119 | } 120 | 121 | private override function getCoordinate(areaMin:Float, areaSize:Float, targetSize:Float, scale:Float):Float { 122 | return percent * (areaSize - targetSize) + areaMin; 123 | } 124 | } 125 | 126 | /** 127 | * Like Percent, but adds the given offset afterwards. The offset is 128 | * multiplied by the current scale. 129 | */ 130 | private class PercentWithOffset extends Position { 131 | private var percent:Float; 132 | private var offset:Float; 133 | 134 | public function new(horizontal:Bool, percent:Float, offset:Float) { 135 | super(horizontal); 136 | this.percent = percent; 137 | this.offset = offset; 138 | } 139 | 140 | private override function getCoordinate(areaMin:Float, areaSize:Float, targetSize:Float, scale:Float):Float { 141 | return percent * (areaSize - targetSize) + offset * scale + areaMin; 142 | } 143 | } 144 | 145 | /** 146 | * Places the target within the area, a short distance from the given 147 | * edge. The distance is multiplied by the current scale. 148 | */ 149 | private class Inside extends Position { 150 | private var margin:Float; 151 | private var direction:Direction; 152 | 153 | public function new(margin:Float, direction:Direction) { 154 | super(direction.isHorizontal()); 155 | this.margin = margin; 156 | this.direction = direction; 157 | } 158 | 159 | private override function getCoordinate(areaMin:Float, areaSize:Float, targetSize:Float, scale:Float):Float { 160 | if(direction.isTopLeft()) { 161 | return areaMin + margin * scale; 162 | } else { 163 | return areaMin + areaSize - margin * scale - targetSize; 164 | } 165 | } 166 | } 167 | 168 | /** 169 | * Places the target next to the area, a short distance from the given 170 | * edge. The distance is multiplied by the current scale. 171 | */ 172 | private class Outside extends Position { 173 | private var margin:Float; 174 | private var direction:Direction; 175 | 176 | public function new(margin:Float, direction:Direction) { 177 | super(direction.isHorizontal()); 178 | this.margin = margin; 179 | this.direction = direction; 180 | } 181 | 182 | private override function getCoordinate(areaMin:Float, areaSize:Float, targetSize:Float, scale:Float):Float { 183 | if(direction.isTopLeft()) { 184 | return areaMin - margin * scale - targetSize; 185 | } else { 186 | return areaMin + areaSize + margin * scale; 187 | } 188 | } 189 | } 190 | 191 | /** 192 | * Places the target within the area, a short distance from the given 193 | * edge. The distance is multiplied by the current scale, unless the 194 | * scale is less than 1, in which case it will be treated as 1. 195 | */ 196 | private class RigidInside extends Inside { 197 | public function new(margin:Float, direction:Direction) { 198 | super(margin, direction); 199 | } 200 | 201 | private override function getCoordinate(areaMin:Float, areaSize:Float, targetSize:Float, scale:Float):Float { 202 | return super.getCoordinate(areaMin, areaSize, targetSize, scale >= 1 ? scale : 1); 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /com/player03/layout/item/Size.hx: -------------------------------------------------------------------------------- 1 | package com.player03.layout.item; 2 | 3 | import com.player03.layout.item.LayoutItem.LayoutMask; 4 | import com.player03.layout.Resizable; 5 | 6 | /** 7 | * Each Size value represents size along only one axis. You will have to use 8 | * two Size values if you want to fully specify an object's size. It is 9 | * strongly recommended that you apply size before position. 10 | */ 11 | class Size implements LayoutItem { 12 | /** 13 | * Set the target's width ignoring everything except the overall 14 | * Scale. If you specify width, this will behave as if the target 15 | * started at that width. 16 | */ 17 | public static inline function simpleWidth(?width:Float):Size { 18 | if(width != null) { 19 | return new SimpleSize(true, width); 20 | } else { 21 | return new Size(true); 22 | } 23 | } 24 | /** 25 | * Set the target's height ignoring everything except the overall 26 | * Scale. If you specify height, this will behave as if the target 27 | * started at that height. 28 | */ 29 | public static inline function simpleHeight(?height:Float):Size { 30 | if(height != null) { 31 | return new SimpleSize(false, height); 32 | } else { 33 | return new Size(false); 34 | } 35 | } 36 | 37 | /** 38 | * Scale the target relative to the base's width. For instance, 39 | * relativeWidth(0.95) plus Align.HORIZONTAL_CENTER will leave a 40 | * small margin on both sides. 41 | */ 42 | public static inline function relativeWidth(percent:Float):Size { 43 | return new RelativeSize(true, percent); 44 | } 45 | /** 46 | * Scale the target relative to the base's height. For instance, 47 | * relativeHeight(0.5) will make the target half as tall as the base. 48 | */ 49 | public static inline function relativeHeight(percent:Float):Size { 50 | return new RelativeSize(false, percent); 51 | } 52 | 53 | /** 54 | * Sets the target's width to the area's width, minus the given amount. 55 | */ 56 | public static inline function widthMinus(amount:Float):Size { 57 | return new MarginSize(true, amount); 58 | } 59 | /** 60 | * Sets the target's height to the area's height, minus the given 61 | * amount. 62 | */ 63 | public static inline function heightMinus(amount:Float):Size { 64 | return new MarginSize(false, amount); 65 | } 66 | 67 | /** 68 | * Maintains the original aspect ratio by adjusting one dimension 69 | * to match the other. 70 | * @param adjustWidth If true, the width will be adjusted based on 71 | * the height. If false, the height will be adjusted based on the 72 | * width. 73 | */ 74 | public static inline function maintainAspectRatio(adjustWidth:Bool):Size { 75 | return new AspectRatio(adjustWidth); 76 | } 77 | 78 | /** 79 | * Like simpleWidth(), but you can set a minimum and/or maximum width. 80 | */ 81 | public static inline function clampedSimpleWidth(?width:Float, ?minimum:Float, ?maximum:Float):Size { 82 | if(width != null) { 83 | return new ClampedSimpleSize(true, width, minimum, maximum); 84 | } else { 85 | return new ClampedSize(true, minimum, maximum); 86 | } 87 | } 88 | /** 89 | * Like simpleHeight(), but you can set a minimum and/or maximum height. 90 | */ 91 | public static inline function clampedSimpleHeight(?height:Float, ?minimum:Float, ?maximum:Float):Size { 92 | if(height != null) { 93 | return new ClampedSimpleSize(false, height, minimum, maximum); 94 | } else { 95 | return new ClampedSize(false, minimum, maximum); 96 | } 97 | } 98 | 99 | /** 100 | * Like relativeWidth(), but you can set a minimum and/or maximum width. 101 | */ 102 | public static inline function clampedRelativeWidth(percent:Float, ?minimum:Float, ?maximum:Float):Size { 103 | return new ClampedRelativeSize(true, percent, minimum, maximum); 104 | } 105 | /** 106 | * Like relativeHeight(), but you can set a minimum and/or maximum height. 107 | */ 108 | public static inline function clampedRelativeHeight(percent:Float, ?minimum:Float, ?maximum:Float):Size { 109 | return new ClampedRelativeSize(false, percent, minimum, maximum); 110 | } 111 | 112 | /** 113 | * Like widthMinus(), but you can set a minimum and/or maximum width. 114 | */ 115 | public static inline function clampedWidthMinus(amount:Float, ?minimum:Float, ?maximum:Float):Size { 116 | return new ClampedMarginSize(true, amount, minimum, maximum); 117 | } 118 | /** 119 | * Like heightMinus(), but you can set a minimum and/or maximum height. 120 | */ 121 | public static inline function clampedHeightMinus(amount:Float, ?minimum:Float, ?maximum:Float):Size { 122 | return new ClampedMarginSize(false, amount, minimum, maximum); 123 | } 124 | 125 | /** 126 | * Like simpleWidth(), but width will not go below its original value. 127 | */ 128 | public static inline function rigidSimpleWidth(width:Float):Size { 129 | return clampedSimpleWidth(width, width); 130 | } 131 | /** 132 | * Like simpleHeight(), but height will not go below its original value. 133 | */ 134 | public static inline function rigidSimpleHeight(height:Float):Size { 135 | return clampedSimpleHeight(height, height); 136 | } 137 | 138 | private var horizontal:Bool; 139 | public var mask:LayoutMask; 140 | 141 | public function new(horizontal:Bool) { 142 | this.horizontal = horizontal; 143 | mask = horizontal ? LayoutMask.AFFECTS_WIDTH : LayoutMask.AFFECTS_HEIGHT; 144 | } 145 | 146 | public function apply(target:Resizable, area:Resizable, scale:Scale):Void { 147 | if(horizontal) { 148 | var width:Float = getSize(target.baseWidth, area.width, scale.x); 149 | if(width != target.width) { 150 | target.width = width; 151 | } 152 | } else { 153 | var height:Float = getSize(target.baseHeight, area.height, scale.y); 154 | if(height != target.height) { 155 | target.height = height; 156 | } 157 | } 158 | } 159 | 160 | private function getSize(targetSize:Float, areaSize:Float, scale:Float):Float { 161 | return targetSize * scale; 162 | } 163 | } 164 | 165 | private class SimpleSize extends Size { 166 | private var size:Float; 167 | 168 | public function new(horizontal:Bool, size:Float) { 169 | super(horizontal); 170 | this.size = size; 171 | } 172 | 173 | private override function getSize(targetSize:Float, areaSize:Float, scale:Float):Float { 174 | return size * scale; 175 | } 176 | } 177 | 178 | private class RelativeSize extends Size { 179 | private var percent:Float; 180 | 181 | public function new(horizontal:Bool, percent:Float) { 182 | super(horizontal); 183 | this.percent = percent; 184 | } 185 | 186 | private override function getSize(targetSize:Float, areaSize:Float, scale:Float):Float { 187 | return areaSize * percent; 188 | } 189 | } 190 | 191 | /** 192 | * Warning: this name may be a little misleading; the class only allows 193 | * for one margin of the given size. If you want a margin on both sides, 194 | * multiply by 2. 195 | */ 196 | private class MarginSize extends Size { 197 | /** 198 | * This is only a margin on one side. Normally a class like this 199 | * would subtract (2 * margin), but this one only subtracts margin. 200 | */ 201 | private var margin:Float; 202 | 203 | public function new(horizontal:Bool, margin:Float) { 204 | super(horizontal); 205 | this.margin = margin; 206 | } 207 | 208 | private override function getSize(targetSize:Float, areaSize:Float, scale:Float):Float { 209 | return areaSize - margin * scale; 210 | } 211 | } 212 | 213 | /** 214 | * Like MarginSize, but it ignores scale. 215 | * 216 | * Warning: this name may be a little misleading; the class only allows 217 | * for one margin of the given size. If you want a margin on both sides, 218 | * multiply by 2. 219 | */ 220 | private class ExactMarginSize extends Size { 221 | /** 222 | * This is only a margin on one side. Normally a class like this 223 | * would subtract (2 * margin), but this one only subtracts margin. 224 | */ 225 | private var margin:Float; 226 | 227 | public function new(horizontal:Bool, margin:Float) { 228 | super(horizontal); 229 | this.margin = margin; 230 | } 231 | 232 | private override function getSize(targetSize:Float, areaSize:Float, scale:Float):Float { 233 | return areaSize - margin; 234 | } 235 | } 236 | 237 | private class AspectRatio extends Size { 238 | public function new(horizontal:Bool) { 239 | super(horizontal); 240 | } 241 | 242 | public override function apply(target:Resizable, area:Resizable, scale:Scale):Void { 243 | if(horizontal) { 244 | target.scaleX = target.scaleY; 245 | } else { 246 | target.scaleY = target.scaleX; 247 | } 248 | } 249 | } 250 | 251 | /** 252 | * Enforces a minimum and/or maximum size. The minimum defaults to 253 | * NEGATIVE_INFINITY, and the maximum defaults to POSITIVE_INFINITY. 254 | */ 255 | private class ClampedSize extends Size { 256 | private var minimum:Float; 257 | private var maximum:Float; 258 | 259 | public function new(horizontal:Bool, ?minimum:Float, ?maximum:Float) { 260 | super(horizontal); 261 | 262 | if(minimum != null) { 263 | this.minimum = minimum; 264 | } else { 265 | this.minimum = Math.NEGATIVE_INFINITY; 266 | } 267 | 268 | if(maximum != null) { 269 | this.maximum = maximum; 270 | } else { 271 | this.maximum = Math.POSITIVE_INFINITY; 272 | } 273 | } 274 | 275 | private override function getSize(targetSize:Float, areaSize:Float, scale:Float):Float { 276 | return clamp(targetSize * scale); 277 | } 278 | 279 | private function clamp(size:Float):Float { 280 | if(size < minimum) { 281 | return minimum; 282 | } else if(size > maximum) { 283 | return maximum; 284 | } else { 285 | return size; 286 | } 287 | } 288 | } 289 | 290 | private class ClampedSimpleSize extends ClampedSize { 291 | private var size:Float; 292 | 293 | public function new(horizontal:Bool, size:Float, ?minimum:Float, ?maximum:Float) { 294 | super(horizontal, minimum, maximum); 295 | this.size = size; 296 | } 297 | 298 | private override function getSize(targetSize:Float, areaSize:Float, scale:Float):Float { 299 | return clamp(size * scale); 300 | } 301 | } 302 | 303 | private class ClampedRelativeSize extends ClampedSize { 304 | private var percent:Float; 305 | 306 | public function new(horizontal:Bool, percent:Float, ?minimum:Float, ?maximum:Float) { 307 | super(horizontal, minimum, maximum); 308 | this.percent = percent; 309 | } 310 | 311 | private override function getSize(targetSize:Float, areaSize:Float, scale:Float):Float { 312 | return clamp(areaSize * percent); 313 | } 314 | } 315 | 316 | private class ClampedMarginSize extends ClampedSize { 317 | /** 318 | * This is only a margin on one side. Normally a class like this 319 | * would subtract (2 * margin), but this one only subtracts margin. 320 | */ 321 | private var margin:Float; 322 | 323 | public function new(horizontal:Bool, margin:Float, ?minimum:Float, ?maximum:Float) { 324 | super(horizontal, minimum, maximum); 325 | this.margin = margin; 326 | } 327 | 328 | private override function getSize(targetSize:Float, areaSize:Float, scale:Float):Float { 329 | return clamp(areaSize - margin * scale); 330 | } 331 | } 332 | -------------------------------------------------------------------------------- /com/player03/layout/item/TextSize.hx: -------------------------------------------------------------------------------- 1 | package com.player03.layout.item; 2 | 3 | import com.player03.layout.item.LayoutItem.LayoutMask; 4 | import com.player03.layout.Resizable; 5 | import flash.text.TextField; 6 | import flash.text.TextFormat; 7 | 8 | /** 9 | * Sets the text size of a text field based on the current scale. To 10 | * resize the text field based on the text size, use TextField.autoSize. 11 | * 12 | * If you pass anything except a TextField object, an error will be thrown. 13 | */ 14 | class TextSize implements LayoutItem { 15 | public static inline function simpleTextSize(baseSize:Int):TextSize { 16 | return new TextSize(baseSize); 17 | } 18 | 19 | public static inline function textSizeWithMinimum(baseSize:Int, minimum:Int):TextSize { 20 | return new TextSizeWithMinimum(baseSize, minimum); 21 | } 22 | 23 | /** 24 | * In most cases this won't matter much, but if you want to use 25 | * Scale.x rather than Scale.y, set this to true. 26 | */ 27 | public var horizontal:Bool = false; 28 | public var mask:LayoutMask = LayoutMask.AFFECTS_TEXT_SIZE; 29 | private var baseSize:Int; 30 | 31 | private function new(baseSize:Int) { 32 | this.baseSize = baseSize; 33 | } 34 | 35 | public function apply(target:Resizable, area:Resizable, scale:Scale):Void { 36 | var textField:TextField = target.castDisplayObject(TextField); 37 | 38 | var format:TextFormat = textField.defaultTextFormat; 39 | format.size = getTextSize(scale); 40 | 41 | textField.defaultTextFormat = format; 42 | 43 | //When calling setTextFormat(), don't change anything except the size. 44 | textField.setTextFormat(new TextFormat(null, format.size)); 45 | } 46 | 47 | private function getTextSize(scale:Scale):Int { 48 | return Math.round(baseSize * (horizontal ? scale.x : scale.y)); 49 | } 50 | } 51 | 52 | private class TextSizeWithMinimum extends TextSize { 53 | private var minimum:Int; 54 | 55 | public function new(baseSize:Int, minimum:Int) { 56 | super(baseSize); 57 | 58 | this.minimum = minimum; 59 | } 60 | 61 | private override function getTextSize(scale:Scale):Int { 62 | var result:Int = super.getTextSize(scale); 63 | if(result < minimum) { 64 | return minimum; 65 | } else { 66 | return result; 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /haxelib.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "advanced-layout", 3 | "url": "http://www.github.com/player-03/advanced-layout", 4 | "license": "MIT", 5 | "tags": [], 6 | "description": "An easy way to create fluid layouts.", 7 | "version": "0.11.0", 8 | "releasenote": "Improved documentation and extensibility.", 9 | "contributors": [ "player_03" ], 10 | "dependencies": {} 11 | } 12 | 13 | -------------------------------------------------------------------------------- /layout/Layout.hx: -------------------------------------------------------------------------------- 1 | package layout; 2 | 3 | typedef Layout = com.player03.layout.Layout; -------------------------------------------------------------------------------- /layout/LayoutCreator.hx: -------------------------------------------------------------------------------- 1 | package layout; 2 | 3 | /** 4 | * To use this as a static extension, you need to specify what you're using it for: 5 | * 6 | * using layout.LayoutCreator.ForOpenFL; //compatible with DisplayObjects, but not Rectangles 7 | * using layout.LayoutCreator.ForRectangles; //compatible with OpenFL's Rectangles 8 | * using layout.LayoutCreator.ForFlixel; //compatible with FlxSprites 9 | * using layout.LayoutCreator.ForHaxePunk; //compatible with HaxePunk's entities 10 | * 11 | * Once you've picked one or more of the above, without any further setup, you can 12 | * call layout functions as if they were instance methods: 13 | * 14 | * object0.simpleWidth(30); 15 | * object0.simpleHeight(40); 16 | * object0.alignBottomRight(); 17 | * 18 | * object1.simpleScale(); 19 | * object1.center(); 20 | * 21 | * object2.fillWidth(); 22 | * object2.simpleHeight(); 23 | * object2.below(object1); 24 | * 25 | * Remember: set width and height before setting an object's position. Your 26 | * instructions will be run in order, and position often depends on dimensions. 27 | * 28 | * @author Joseph Cloutier 29 | */ 30 | typedef LayoutCreator = com.player03.layout.LayoutCreator; 31 | 32 | typedef ForAreas = com.player03.layout.LayoutCreator.ForAreas; 33 | typedef ForRectangles = com.player03.layout.LayoutCreator.ForRectangles; 34 | typedef ForOpenFL = com.player03.layout.LayoutCreator.ForOpenFL; 35 | 36 | #if haxeui 37 | typedef ForHaxeUI = com.player03.layout.LayoutCreator.ForHaxeUI; 38 | #end 39 | #if flixel 40 | typedef ForFlixel = com.player03.layout.LayoutCreator.ForFlixel; 41 | #end 42 | #if haxepunk 43 | typedef ForHaxePunk = com.player03.layout.LayoutCreator.ForHaxePunk; 44 | #end 45 | -------------------------------------------------------------------------------- /layout/LayoutPreserver.hx: -------------------------------------------------------------------------------- 1 | package layout; 2 | 3 | /** 4 | * Use this class if you positioned your objects beforehand, and all you need is 5 | * to make everything scale. If you still need to position your objects, 6 | * consider using LayoutCreator to save a step. 7 | * 8 | * Use this class as a static extension. 9 | * 10 | * Example: consider an object that you've placed at the bottom of the stage. 11 | * When the stage scales, you want to update the object's y coordinate so that 12 | * it stays at the bottom. To do this, call stickToBottom(). 13 | * 14 | * All "stickTo" functions remember how far the object was from the edge (or 15 | * center). So if the sample object starts five pixels above the bottom, it will 16 | * remain about five pixels above the bottom as the stage scales. This 17 | * five-pixel margin will increase as the stage gets bigger, and it will 18 | * decrease as the stage gets smaller. 19 | * 20 | * If you want this class to guess how an object should scale, use the 21 | * preserve() function. 22 | * 23 | * @author Joseph Cloutier 24 | */ 25 | typedef LayoutPreserver = com.player03.layout.LayoutCreator; --------------------------------------------------------------------------------