Pārlūkot izejas kodu

added Flow to ease UI alignments

ncannasse 9 gadi atpakaļ
vecāks
revīzija
4083c4ed4a
1 mainītis faili ar 467 papildinājumiem un 0 dzēšanām
  1. 467 0
      h2d/Flow.hx

+ 467 - 0
h2d/Flow.hx

@@ -0,0 +1,467 @@
+package h2d;
+
+enum FlowAlign {
+	Top;
+	Left;
+	Right;
+	Middle;
+	Bottom;
+	Absolute;
+}
+
+class FlowProperties {
+
+	public var paddingLeft = 0;
+	public var paddingTop = 0;
+	public var paddingRight = 0;
+	public var paddingBottom = 0;
+
+	public var align : Null<FlowAlign>;
+
+	public var offsetX = 0;
+	public var offsetY = 0;
+
+	public var minWidth : Null<Int>;
+	public var minHeight : Null<Int>;
+
+	public var calculatedWidth : Float = 0.;
+	public var calculatedHeight : Float = 0.;
+
+	public function new() {
+	}
+
+}
+
+class Flow extends Sprite {
+
+	static var tmpBounds = new h2d.col.Bounds();
+
+	/**
+		If some sub element gets resized, you need to set reflow to true in order to force
+		the reflow of elements. You can also directly call reflow() which will immediately
+		update all elements positions.
+
+		If a reflow is needed, reflow() will be called before rendering the flow.
+		Each change in one of the flow properties or addition/removal of elements will set needReflow to true.
+	**/
+	public var needReflow : Bool = true;
+	public var align(default,set) : FlowAlign = Bottom;
+
+	public var minWidth(default, set) : Null<Int>;
+	public var minHeight(default, set) : Null<Int>;
+	public var maxWidth(default, set) : Null<Int>;
+	public var maxHeight(default, set) : Null<Int>;
+
+	/**
+		The calculated width based on the flow elements.
+	**/
+	public var calculatedWidth(get, null) = 0.;
+
+	/**
+		The calculated height based on the flow elements.
+	**/
+	public var calculatedHeight(get, null) = 0.;
+
+	/**
+		Will set all padding values at the same time.
+	**/
+	public var padding(never, set) : Int;
+	public var paddingLeft(default, set) : Int = 0;
+	public var paddingRight(default, set) : Int = 0;
+	public var paddingTop(default, set) : Int = 0;
+	public var paddingBottom(default, set) : Int = 0;
+
+	/**
+		The horizontal space between two flowed elements.
+	**/
+	public var horitontalSpacing(default, set) : Int = 0;
+
+	/**
+		The vertical space between two flowed elements.
+	**/
+	public var verticalSpacing(default, set) : Int = 0;
+
+	/**
+		Set enableInteractive to true to create an h2d.Interactive accessible through
+		the interactive field which will automatically cover the whole Flow area.
+	**/
+	public var enableInteractive(default, set) : Bool;
+
+	/**
+		See enableInteractive.
+	**/
+	public var interactive(default, null) : h2d.Interactive;
+
+	/**
+		Setting a background tile will create a ScaleGrid background which uses the borderWidth/Height values for its borders.
+		It will automatically resize when the reflow is done to cover the whole Flow area.
+	**/
+	public var backgroundTile(default, set) : h2d.Tile;
+	public var borderWidth(default, set) : Int = 0;
+	public var borderHeight(default, set) : Int = 0;
+
+	/**
+		By default, elements will be flowed horizontaly, then in several lines if maxWidth is reached.
+		You can instead flow them vertically, then to next column is maxHeight is reached by setting the isVertical flag to true.
+	**/
+	public var isVertical(default, set) : Bool;
+
+	var background : h2d.ScaleGrid;
+	var properties : Array<FlowProperties> = [];
+
+	public function new(?parent) {
+		super(parent);
+	}
+
+	override public function getBounds(?relativeTo:Sprite, ?out:h2d.col.Bounds):h2d.col.Bounds {
+		reflow();
+		return super.getBounds(relativeTo, out);
+	}
+
+	/**
+		Get the per-element properties. Returns null if the element is not currently part of the flow.
+	**/
+	public function getProperties( e : h2d.Sprite ) {
+		needReflow = true; // properties might be changed
+		return properties[getChildIndex(e)];
+	}
+
+	function set_isVertical(v) {
+		if( isVertical == v )
+			return v;
+		needReflow = true;
+		return isVertical = v;
+	}
+
+	function set_align(v) {
+		if( align == v )
+			return v;
+		needReflow = true;
+		return align = v;
+	}
+
+	function set_padding(v) {
+		paddingLeft = v;
+		paddingTop = v;
+		paddingRight = v;
+		paddingBottom = v;
+		return v;
+	}
+
+	function get_calculatedWidth() {
+		if( needReflow ) reflow();
+		return calculatedWidth;
+	}
+
+	function get_calculatedHeight() {
+		if( needReflow ) reflow();
+		return calculatedHeight;
+	}
+
+	function set_paddingLeft(v) {
+		if( paddingLeft == v ) return v;
+		needReflow = true;
+		if( background != null ) background.borderWidth = paddingLeft;
+		return paddingLeft = v;
+	}
+
+	function set_paddingRight(v) {
+		if( paddingRight == v ) return v;
+		needReflow = true;
+		return paddingRight = v;
+	}
+
+	function set_paddingTop(v) {
+		if( paddingTop == v ) return v;
+		needReflow = true;
+		if( background != null ) background.borderHeight = paddingTop;
+		return paddingTop = v;
+	}
+
+	function set_paddingBottom(v) {
+		if( paddingBottom == v ) return v;
+		needReflow = true;
+		return paddingBottom = v;
+	}
+
+	override function getBoundsRec( relativeTo, out ) {
+		super.getBoundsRec(relativeTo, out);
+		if( needReflow ) reflow();
+		var cw = calculatedWidth;
+		var ch = calculatedHeight;
+		if( cw != 0 || ch != 0 )
+			addBounds(relativeTo, out, 0, 0, cw, ch);
+	}
+
+	override function addChildAt( s, pos ) {
+		if( background != null ) pos++;
+		super.addChildAt(s, pos);
+		properties.insert(pos, new FlowProperties());
+		needReflow = true;
+	}
+
+	override public function removeChild(s:Sprite) {
+		var index = getChildIndex(s);
+		super.removeChild(s);
+		if( index > 0 ) {
+			needReflow = true;
+			properties.splice(index, 1);
+		}
+	}
+
+	override function sync(ctx:RenderContext) {
+		if( needReflow ) reflow();
+		super.sync(ctx);
+	}
+
+	function set_maxWidth(w) {
+		if( maxWidth == w )
+			return w;
+		needReflow = true;
+		return maxWidth = w;
+	}
+
+	function set_maxHeight(h) {
+		if( maxHeight == h )
+			return h;
+		needReflow = true;
+		return maxHeight = h;
+	}
+
+	function set_minWidth(w) {
+		if( minWidth == w )
+			return w;
+		needReflow = true;
+		return minWidth = w;
+	}
+
+	function set_minHeight(h) {
+		if( minHeight == h )
+			return h;
+		needReflow = true;
+		return minHeight = h;
+	}
+
+	function set_horitontalSpacing(s) {
+		if( horitontalSpacing == s )
+			return s;
+		needReflow = true;
+		return horitontalSpacing = s;
+	}
+
+	function set_verticalSpacing(s) {
+		if( verticalSpacing == s )
+			return s;
+		needReflow = true;
+		return verticalSpacing = s;
+	}
+
+	function set_enableInteractive(b) {
+		if( enableInteractive == b )
+			return b;
+		if( b ) {
+			if( interactive == null ) {
+				interactive = new h2d.Interactive(0, 0, this);
+				properties[properties.length - 1].align = Absolute;
+				if( !needReflow ) {
+					interactive.width = calculatedWidth;
+					interactive.height = calculatedHeight;
+				}
+			}
+		} else {
+			if( interactive != null ) {
+				interactive.remove();
+				interactive = null;
+			}
+		}
+		return enableInteractive = b;
+	}
+
+	function set_backgroundTile(t) {
+		if( backgroundTile == t )
+			return t;
+		if( t != null ) {
+			if( background == null ) {
+				var background = new h2d.ScaleGrid(t, borderWidth, borderHeight);
+				addChildAt(background, 0);
+				properties[0].align = Absolute;
+				this.background = background;
+				if( !needReflow ) {
+					background.width = Math.ceil(calculatedWidth);
+					background.height = Math.ceil(calculatedHeight);
+				}
+			}
+			background.tile = t;
+		} else {
+			if( background != null ) {
+				background.remove();
+				background = null;
+			}
+		}
+		return t;
+	}
+
+	function set_borderWidth(v) {
+		if( borderWidth == v )
+			return v;
+		if( background != null ) background.borderWidth = v;
+		needReflow = true;
+		return borderWidth = v;
+	}
+
+	function set_borderHeight(v) {
+		if( borderHeight == v )
+			return v;
+		if( background != null ) background.borderHeight = v;
+		needReflow = true;
+		return borderHeight = v;
+	}
+
+	/**
+		Call to force all flowed elements position to be updated.
+		See needReflow for more informations.
+	**/
+	public function reflow() {
+
+		var cw, ch;
+		if( !isVertical ) {
+
+			var startX = paddingLeft + borderWidth;
+			var x : Float = startX;
+			var y : Float = paddingTop + borderHeight;
+			cw = x;
+			var maxLineHeight = 0.;
+			var tmpBounds = tmpBounds;
+			var maxWidth = maxWidth == null ? 100000000 : maxWidth - (paddingLeft + paddingRight + borderWidth * 2);
+			var lastIndex = 0;
+
+			inline function alignLine( maxIndex ) {
+				for( i in lastIndex...maxIndex ) {
+					var c = childs[i];
+					var p = properties[i];
+					var a = p.align != null ? p.align : align;
+					switch( a ) {
+					case Bottom:
+						c.y += maxLineHeight - p.calculatedHeight;
+					case Middle:
+						c.y += Std.int((maxLineHeight - p.calculatedHeight) * 0.5);
+					default:
+					}
+				}
+				lastIndex = maxIndex;
+			}
+
+			for( i in 0...childs.length ) {
+				var c = childs[i];
+				var p = properties[i];
+
+				if( p.align == Absolute ) continue;
+
+				c.x = 0;
+				c.y = 0;
+				var b = c.getBounds(this, tmpBounds);
+
+				p.calculatedWidth = b.xMax + p.paddingLeft + p.paddingRight;
+				p.calculatedHeight = b.yMax + p.paddingTop + p.paddingBottom;
+				if( p.minWidth != null && p.calculatedWidth < p.minWidth ) p.calculatedWidth = p.minWidth;
+				if( p.minHeight != null && p.calculatedHeight < p.minHeight ) p.calculatedHeight = p.minHeight;
+
+				if( x + p.calculatedWidth > maxWidth && x > startX ) {
+					alignLine(i);
+					y += maxLineHeight + verticalSpacing;
+					maxLineHeight = 0;
+					x = startX;
+				}
+
+				c.x = x + p.offsetX + p.paddingLeft;
+				c.y = y + p.offsetY + p.paddingTop;
+
+				x += p.calculatedWidth;
+				if( x > cw ) cw = x;
+				x += horitontalSpacing;
+				if( p.calculatedHeight > maxLineHeight ) maxLineHeight = p.calculatedHeight;
+			}
+			alignLine(childs.length);
+			cw += paddingRight + borderWidth;
+			ch = y + maxLineHeight + paddingBottom + borderHeight;
+		} else {
+
+			var startY = paddingTop + borderHeight;
+			var y : Float = startY;
+			var x : Float = paddingLeft + borderWidth;
+			ch = y;
+			var maxColWidth = 0.;
+			var tmpBounds = tmpBounds;
+			var maxHeight = maxHeight == null ? 100000000 : maxHeight - (paddingTop + paddingBottom + borderHeight * 2);
+			var lastIndex = 0;
+
+			inline function alignLine( maxIndex ) {
+				for( i in lastIndex...maxIndex ) {
+					var c = childs[i];
+					var p = properties[i];
+					var a = p.align != null ? p.align : align;
+					switch( a ) {
+					case Right:
+						c.x += maxColWidth - p.calculatedWidth;
+					case Middle:
+						c.x += Std.int((maxColWidth - p.calculatedWidth) * 0.5);
+					default:
+					}
+				}
+				lastIndex = maxIndex;
+			}
+
+			for( i in 0...childs.length ) {
+				var c = childs[i];
+				var p = properties[i];
+
+				if( p.align == Absolute ) continue;
+
+				c.x = 0;
+				c.y = 0;
+				var b = c.getBounds(this, tmpBounds);
+
+				p.calculatedWidth = b.xMax + p.paddingLeft + p.paddingRight;
+				p.calculatedHeight = b.yMax + p.paddingTop + p.paddingBottom;
+				if( p.minWidth != null && p.calculatedWidth < p.minWidth ) p.calculatedWidth = p.minWidth;
+				if( p.minHeight != null && p.calculatedHeight < p.minHeight ) p.calculatedHeight = p.minHeight;
+
+				if( y + p.calculatedHeight > maxHeight && y > startY ) {
+					alignLine(i);
+					x += maxColWidth + horitontalSpacing;
+					maxColWidth = 0;
+					y = startY;
+				}
+
+				c.x = x + p.offsetX + p.paddingLeft;
+				c.y = y + p.offsetY + p.paddingTop;
+
+				y += p.calculatedHeight;
+				if( y > ch ) ch = y;
+				y += verticalSpacing;
+				if( p.calculatedWidth > maxColWidth ) maxColWidth = p.calculatedWidth;
+			}
+			alignLine(childs.length);
+			ch += paddingTop + borderHeight;
+			cw = x + maxColWidth + paddingRight + borderWidth;
+		}
+
+		if( minWidth != null && cw < minWidth ) cw = minWidth;
+		if( minHeight != null && ch < minHeight ) ch = minHeight;
+
+		if( interactive != null ) {
+			interactive.width = cw;
+			interactive.height = ch;
+		}
+
+		if( background != null ) {
+			background.width = Math.ceil(cw);
+			background.height = Math.ceil(ch);
+		}
+
+		calculatedWidth = cw;
+		calculatedHeight = ch;
+		needReflow = false;
+	}
+
+}