package engine.ui; // __ __ _____ ______ // | | | | / ___ \ | ____| // | |____| | | / _\_| | |____ // | ____ | | | |_ \ | ____| // | | | | | \__/ | | |____ // |__| |__| \______/ |______| /* / file: UIPane.hx / author: and.schaafsma@gmail.com / purpose: Class for resizable and scalable UI Panels / My hope is that this code is so awful I'm never allowed to write UI code again. */ import game.ui.console.Console; import openfl.display.Sprite; import openfl.display.BitmapData; import openfl.display.DisplayObject; import openfl.text.TextField; import engine.ConVar; import engine.geom.Geom.P2d; @:enum abstract PaneLayout(Int) { var HORIZONTAL = 0; var VERTICAL = 1; } enum PaneAnchor { LEFT; TOPLEFT; TOP; TOPRIGHT; RIGHT; BOTTOMLEFT; BOTTOM; BOTTOMRIGHT; } enum ExpandBehavior { // ABSOLUTE; FACTOR(f:Float); // CONTENT; STRETCH; // FILL; // FIT; NONE; } enum PaneAlign { START; END; } typedef PaneDimensions = { var width:Float; var height:Float; @:optional var expandBehavior:ExpandBehavior; @:optional var minWidth:Float; @:optional var minHeight:Float; @:optional var maxWidth:Float; @:optional var maxHeight:Float; } typedef PaneProps = { dimensions:PaneDimensions, layout:PaneLayout } class UIPane { public var name:String; public var sprite:Sprite; public var x(get, set):Float; public var y(get, set):Float; function get_x() { return sprite.x; } function set_x(x) { return sprite.x = x; } function get_y() { return sprite.y; } function set_y(y) { return sprite.y = y; } public var dimensions:PaneDimensions; public var width(get, set):Float; function get_width() { return dimensions.width; } function set_width(width) { dimensions.width = width; onResize(); return width; } public var height(get, set):Float; function get_height() { return dimensions.height; } function set_height(height) { dimensions.height = height; onResize(); return height; } public var layout:PaneLayout; public var autoArrange:Bool = true; public var autoArrangeChildren = true; public var expand:Bool; public var children:Array = []; public var parent:UIPane = null; public var align:PaneAlign = START; public var maskSprite:Sprite; public static var panelist:Array = []; public function new(_name:String, _dimensions:PaneDimensions) { // Set name name = _name; // Set dimensions if (_dimensions != null) { dimensions = _dimensions; } else { dimensions = { width: 0, height: 0, minWidth: 0, minHeight: 0, maxWidth: 0, maxHeight: 0 }; } if (name == null || name == "") { Console.devMsg("UI Pane name init error"); } // Initialize Sprite initSprite(); // Draw debug pane for visualizing drawDebugPane(); panelist.push(this); } public static var ccmd_ui_redraw = ConVar.registerCCmd("ui_redraw", (args:Array) -> { redrawUIPanes(); }); public static function redrawUIPanes() { for (pane in panelist) { pane.redraw(); } } public function getAnchorOffset(anchor:PaneAnchor):P2d { switch (anchor) { case LEFT: return {x: 0, y: (height / 2)}; case TOPLEFT: return {x: 0, y: 0}; case TOP: return {x: width / 2, y: 0}; case TOPRIGHT: return {x: width, y: 0}; case RIGHT: return {x: width, y: height / 2} case BOTTOMRIGHT: return {x: width, y: height}; case BOTTOM: return {x: width / 2, y: height}; case BOTTOMLEFT: return {x: 0, y: height}; } } public function initSprite():Void { // Construct Sprite object sprite = new Sprite(); // Draw mask maskSprite = new Sprite(); drawMask(); sprite.addChild(maskSprite); sprite.mask = maskSprite; } public function drawMask():Void { maskSprite.graphics.clear(); maskSprite.graphics.beginFill(0xffffff); maskSprite.graphics.drawRect(0, 0, width, height); } public function drawDebugPane() { // Clear graphics sprite.graphics.clear(); // Create objects we need to draw. var label:TextField = new TextField(); var bmp = new BitmapData(Std.int(width), Std.int(height), true, 0x00000000); // Set textfield text to the panel name label.text = name; // Draw textfield to bitmap bmp.draw(label); // Draw graphics sprite.graphics.beginFill(Std.int(Math.random() * 0xffffff), 0.5); sprite.graphics.drawRect(0, 0, dimensions.width, dimensions.height); sprite.graphics.beginBitmapFill(bmp, null); sprite.graphics.drawRect(0, 0, dimensions.width, dimensions.height); // Visualize Anchor Points if (false) { sprite.graphics.beginFill(Std.int(Math.random() * 0xffffff), 0.5); sprite.graphics.drawCircle(getAnchorOffset(TOPLEFT).x, getAnchorOffset(TOPLEFT).y, 5); sprite.graphics.drawCircle(getAnchorOffset(TOP).x, getAnchorOffset(TOP).y, 5); sprite.graphics.drawCircle(getAnchorOffset(TOPRIGHT).x, getAnchorOffset(TOPRIGHT).y, 5); sprite.graphics.drawCircle(getAnchorOffset(RIGHT).x, getAnchorOffset(RIGHT).y, 5); sprite.graphics.drawCircle(getAnchorOffset(BOTTOMRIGHT).x, getAnchorOffset(BOTTOMRIGHT).y, 5); sprite.graphics.drawCircle(getAnchorOffset(BOTTOM).x, getAnchorOffset(BOTTOM).y, 5); sprite.graphics.drawCircle(getAnchorOffset(BOTTOMLEFT).x, getAnchorOffset(BOTTOMLEFT).y, 5); sprite.graphics.drawCircle(getAnchorOffset(LEFT).x, getAnchorOffset(LEFT).y, 5); } } public var endOffset:Float = 0; public var startOffset:Float = 0; public function addChild(child:UIPane) { if (child.parent != null) throw "Attempting to attach child UIPane that already has a parent"; children.push(child); sprite.addChild(child.sprite); child.parent = this; if (child.autoArrange && autoArrangeChildren) { arrangeChild(child); } return child; } public function redraw():Void { drawDebugPane(); drawMask(); } public function onResize() { redraw(); if (autoArrangeChildren) { arrangeChildren(); } } private function arrangeChild(child:UIPane) { // We do not arrange the if (!child.autoArrange) { return; } if (endOffset == 0) { switch (layout) { case HORIZONTAL: endOffset = width; case VERTICAL: endOffset = height; } } var offsetDiff:Float = endOffset - startOffset; // Set child dimensions switch (child.dimensions.expandBehavior) { case STRETCH: switch (layout) { case HORIZONTAL: child.width = offsetDiff; child.height = height; case VERTICAL: child.width = width; child.height = offsetDiff; } case FACTOR(f): switch (layout) { case HORIZONTAL: child.width = width * f; child.height = height; case VERTICAL: child.width = width; child.height = height * f; } default: switch (layout) { case HORIZONTAL: // do some shit child.height = height; case VERTICAL: // do some other shit child.width = width; default: child.height = child.height; } } // Set child position switch (child.align) { case START: switch (layout) { case HORIZONTAL: child.x = startOffset; startOffset += child.width; case VERTICAL: child.y = startOffset; startOffset += child.height; } case END: switch (layout) { case HORIZONTAL: child.x = endOffset -= child.width; case VERTICAL: child.y = endOffset -= child.height; } } } public function arrangeChildren() { // reset offsets startOffset = 0; endOffset = 0; // arrance each child for (child in children) { arrangeChild(child); } } }