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 offsetX = UINumber.Compute( control, UIStyleNumberProperty.Left, 0 ) - UINumber.Compute( control, UIStyleNumberProperty.Right, 0 ); var offsetY = UINumber.Compute( control, UIStyleNumberProperty.Top, 0 ) - UINumber.Compute( control, UIStyleNumberProperty.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; var maxVerticalPlacementOffset = 0f; var maxLineY = lines.Count > 0 ? lines[ lines.Count - 1 ].maxY : 0; region.contentSize.X = 0f; region.contentSize.Y = maxLineY; lines.ForEach( l => { region.contentSize.X = Mathf.Max( region.contentSize.X, l.maxX ); } ); if ( UINumber.IsNullOrNone( region, UIStyleNumberProperty.Width ) ) { maxWidth = region.contentSize.X; } else { maxWidth = UINumber.Compute( region, UIStyleNumberProperty.Width ); } if ( ! UINumber.IsNullOrNone( UIStyle.GetUINumberProperty( region, UIStyleNumberProperty.Height ) ) ) { maxHeight = UINumber.Compute( region, UIStyleNumberProperty.Height ); maxVerticalPlacementOffset = maxHeight - maxLineY; } else if ( lines.Count > 0 ) { maxHeight = maxLineY; } var margin = UINumber.Compute( region, UIStyleNumberProperty.Margin, 0 ); var marginLeft = margin + UINumber.Compute( region, UIStyleNumberProperty.MarginLeft, 0 ); var marginTop = margin + UINumber.Compute( region, UIStyleNumberProperty.MarginTop, 0 ); var marginRight = margin + UINumber.Compute( region, UIStyleNumberProperty.MarginRight, 0 ); var marginBottom = margin + UINumber.Compute( region, UIStyleNumberProperty.MarginBottom, 0 ); var verticalPlacementOffset = UINumber.Compute( region, UIStyleNumberProperty.VerticalPlacement, 0, maxVerticalPlacementOffset ); // if ( region.Name == "Audio" ) // { // RJLog.Log( "Audio Placements", // "maxHeight:", maxHeight, // "maxLineY:", maxLineY, // "maxVerticalPlacementOffset", maxVerticalPlacementOffset, // "verticalPlacementOffset", verticalPlacementOffset // ); // } var marginOffset = new Vector2( marginLeft, marginTop + verticalPlacementOffset ); region.contentOffset = marginOffset; PlaceControls( region, lines, marginOffset ); var alignmentWidth = maxWidth + marginLeft + marginRight; var horizontalAlignment = UINumber.Compute( region, UIStyleNumberProperty.HorizontalAlignment, 0 ); region.contentOffset.X = Mathf.Lerp( 0, alignmentWidth - region.contentSize.X, horizontalAlignment / 100f ); 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, UIStyleNumberProperty.Width, 100000000f ); var lines = new List(); var currentLine = new Line(); var elementSpacing = UINumber.Compute( region, UIStyleNumberProperty.ElementSpacing, 0f ); var lineSpacing = UINumber.Compute( region, UIStyleNumberProperty.LineSpacing, 0f ); Nodes.ForEachDirectChild( region, c => UILayouting.UpdateChild( c ) ); Nodes.ForEachDirectChild( region, child => { if ( ! child.Visible ) { return; } 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; var lineWrap = UIStyle.LineWrap( styleContainer ); lineWrap = lineWrap == UILineWrap.___ ? UILineWrap.Wrap_On_Overflow : lineWrap; if ( UILineWrap.Wrap_Never != lineWrap && ( UILineWrap.Wrap_Always == lineWrap || nextEndX > width ) ) { lines.Add( currentLine ); currentLine = new Line(); x = 0; currentLine.y = lines[ lines.Count - 1 ].maxY + lineSpacing; 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 + lineSpacing; } lines.Add( currentLine ); return lines; } static void AdjustLines( UIRegion region, List lines ) { var verticalAlignment = UINumber.Compute( region, UIStyleNumberProperty.VerticalAlignment, 0.5f, 1 ); lines.ForEach( line => AdjustVerticalAlignment( region, line, verticalAlignment ) ); if ( UINumber.IsNullOrNone( region, UIStyleNumberProperty.Width ) ) { return; } var horizontalAlignment = UINumber.Compute( region, UIStyleNumberProperty.HorizontalAlignment, 0.5f, 1 ); var maxWidth = UINumber.Compute( region, UIStyleNumberProperty.Width, 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 ) ); } ); } } }