rj-action-library/Runtime/UI/Layouts/UIFlowLayout.cs

235 lines
6.5 KiB
C#

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<ControlPlacement> placements = new List<ControlPlacement>();
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( region.width ) )
{
lines.ForEach( l => { maxWidth = Mathf.Max( maxWidth, l.maxX ); } );
}
else
{
maxWidth = UINumber.Compute( region, region.width );
}
if ( lines.Count > 0 )
{
maxHeight = lines[ lines.Count - 1 ].maxY;
}
var margin = UINumber.Compute( region, region.margin, 0 );
var marginLeft = margin + UINumber.Compute( region, region.marginLeft, 0 );
var marginTop = margin + UINumber.Compute( region, region.marginTop, 0 );
var marginRight = margin + UINumber.Compute( region, region.marginRight, 0 );
var marginBottom = margin + UINumber.Compute( region, region.marginBottom, 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 ( region.position == UIPosition.Parent_Anchor )
{
var p = NodesWalker.Get().Parent( region ) as Control;
if ( p != null )
{
var pWidth = UILayouting.GetWidth( p );
var pHeight = UILayouting.GetHeight( p );
var x = p.Position.X;
var y = p.Position.Y;
if ( ! UINumber.IsNullOrNone( region.left ))
{
var left = UINumber.Compute( region, region.left, 0 );
x = left;
}
else if ( ! UINumber.IsNullOrNone( region.right ) )
{
var right = UINumber.Compute( region, region.right, 0 );
x = ( pWidth - UILayouting.GetWidth( region ) ) - right;
}
UILayouting.SetPosition( region, new Vector2( x, y ) );
}
}
}
static List<Line> CreateLines( UIRegion region )
{
var x = 0f;
var width = UINumber.Compute( region, region.width, 100000000f );
var lines = new List<Line>();
var currentLine = new Line();
var elementSpacing = UINumber.Compute( region, region.elementSpacing, 0f );
Nodes.ForEachDirectChild<Control>( region, c => UILayouting.UpdateChild( c ) );
Nodes.ForEachDirectChild<Control>( region,
child =>
{
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<Line> lines )
{
var verticalAlignment = UINumber.Compute( region, region.verticalAlignment, 0.5f, 1 );
lines.ForEach( line => AdjustVerticalAlignment( region, line, verticalAlignment ) );
if ( UINumber.IsNullOrNone( region.width ) )
{
return;
}
var horizontalAlignment = UINumber.Compute( region, region.horizontalAlignment, 0.5f, 1 );
var maxWidth = UINumber.Compute( region, region.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<Line> lines, Vector2 marginOffset )
{
lines.ForEach(
line =>
{
var offset = new Vector2( line.xOffset, line.y ) + marginOffset;
line.placements.ForEach( p => p.Apply( offset ) );
}
);
}
}
}