using Godot; using System.Collections.Generic; namespace Rokojori { public class UIFlowLayout { public class ControlPlacement { public Control control; public float offset; public float width; public float height; public float yOffset = 0; public float maxX => offset + width; public void AlignVertically( float maxHeight, float alignment ) { var bottomAlignement = maxHeight - height; yOffset = Mathf.Lerp( 0, bottomAlignement, alignment ); } public void Apply( Vector2 parentOffset ) { if ( UIStyling.CanPosition( control ) ) { var container = (UIStylePropertyContainer) control; var left = UIStyle.Left( container ); var right = UIStyle.Right( container ); var top = UIStyle.Top( container ); var bottom = UIStyle.Bottom( container ); var offsetX = UINumber.Compute( control, left, 0 ) - UINumber.Compute( control, right, 0 ); var offsetY = UINumber.Compute( control, top, 0 ) - UINumber.Compute( control, bottom, 0 ); UILayouting.SetPosition( control, parentOffset + new Vector2( offset + offsetX, yOffset + offsetY ) ); } else { control.Position = parentOffset + new Vector2( offset, yOffset ); } } } public class Line { public float y; public float size; public float xOffset; public List placements = new List(); public float maxY => y + size; public float maxX => placements.Count == 0 ? 0 : placements[ placements.Count -1 ].maxX ; } public static void Apply( UIRegion region ) { var lines = CreateLines( region ); AdjustLines( region, lines ); var maxWidth = 0f; var maxHeight = 0f; if ( UINumber.IsNullOrNone( UIStyle.Width( region ) ) ) { lines.ForEach( l => { maxWidth = Mathf.Max( maxWidth, l.maxX ); } ); } else { maxWidth = UINumber.Compute( region, UIStyle.Width( region ) ); } if ( lines.Count > 0 ) { maxHeight = lines[ lines.Count - 1 ].maxY; } var margin = UINumber.Compute( region, UIStyle.Margin( region ), 0 ); var marginLeft = margin + UINumber.Compute( region, UIStyle.MarginLeft( region ), 0 ); var marginTop = margin + UINumber.Compute( region, UIStyle.MarginTop( region ), 0 ); var marginRight = margin + UINumber.Compute( region, UIStyle.MarginRight( region ), 0 ); var marginBottom = margin + UINumber.Compute( region, UIStyle.MarginBottom( region ), 0 ); var marginOffset = new Vector2( marginLeft, marginTop ); PlaceControls( region, lines, marginOffset ); var verticalMargins = marginTop + marginBottom; var horizontalMargins = marginLeft + marginRight; region.Size = new Vector2( maxWidth + horizontalMargins, maxHeight + verticalMargins ); if ( UIStyle.Position( region ) == UIPosition.Parent_Anchor ) { UILayouting.SetPositionInParentAnchor( region ); } Nodes.ForEachDirectChild( region, child => { var styleContainer = child as UIStylePropertyContainer; if ( styleContainer == null || UIStyle.Position( styleContainer ) != UIPosition.Parent_Anchor ) { return; } UILayouting.UpdateChild( child ); UILayouting.SetPositionInParentAnchor( styleContainer ); } ); } static List CreateLines( UIRegion region ) { var x = 0f; var width = UINumber.Compute( region, UIStyle.Width( region ), 100000000f ); var lines = new List(); var currentLine = new Line(); var elementSpacing = UINumber.Compute( region, UIStyle.ElementSpacing( region ), 0f ); Nodes.ForEachDirectChild( region, c => UILayouting.UpdateChild( c ) ); Nodes.ForEachDirectChild( region, child => { var styleContainer = child as UIStylePropertyContainer; if ( styleContainer != null && UIStyle.Position( styleContainer ) == UIPosition.Parent_Anchor ) { return; } var cWidth = UILayouting.GetWidth( child ); var cHeight = UILayouting.GetHeight( child ); var nextEndX = x + cWidth + elementSpacing; if ( nextEndX > width ) { lines.Add( currentLine ); currentLine = new Line(); x = 0; currentLine.y = lines[ lines.Count - 1 ].maxY; nextEndX = cWidth + elementSpacing; } var placement = new ControlPlacement(); placement.offset = x; placement.width = cWidth; placement.height = cHeight; placement.control = child; currentLine.placements.Add( placement ); currentLine.size = Mathf.Max( currentLine.size, cHeight ); x = nextEndX; } ); if ( lines.Count > 0 ) { currentLine.y = lines[ lines.Count - 1 ].maxY; } lines.Add( currentLine ); return lines; } static void AdjustLines( UIRegion region, List lines ) { var verticalAlignment = UINumber.Compute( region, region.verticalAlignment, 0.5f, 1 ); lines.ForEach( line => AdjustVerticalAlignment( region, line, verticalAlignment ) ); if ( UINumber.IsNullOrNone( UIStyle.Width( region ) ) ) { return; } var horizontalAlignment = UINumber.Compute( region, region.horizontalAlignment, 0.5f, 1 ); var maxWidth = UINumber.Compute( region, UIStyle.Width( region ), 0 ); lines.ForEach( line => AdjustHorizontalAlignment( region, line, horizontalAlignment, maxWidth ) ); } static void AdjustVerticalAlignment( UIRegion region, Line line, float verticalAlignment ) { line.placements.ForEach( p => p.AlignVertically( line.size, verticalAlignment ) ); } static void AdjustHorizontalAlignment( UIRegion region, Line line, float horizontalAlignment, float maxWidth ) { var lineWidth = line.maxX; var maxOffset = maxWidth - lineWidth; line.xOffset = horizontalAlignment * maxOffset; } static void PlaceControls( UIRegion region, List lines, Vector2 marginOffset ) { lines.ForEach( line => { var offset = new Vector2( line.xOffset, line.y ) + marginOffset; line.placements.ForEach( p => p.Apply( offset ) ); } ); } } }