A UI node component used by the UiPro system. It stores layout and style, computes layout relative to a parent, provides render attributes for the renderer, converts screen coordinates to NDC, and tests whether a point lies inside the (rounded) node and its clip rect.
using System;
namespace Sandbox.UiPro;
public abstract class NodeComponent : Component
{
public static readonly Material Material = Material.FromShader( "shaders/node.shader" );
[Property, InlineEditor, Group( "Layout Settings" ), Order( -999 )] public virtual NodeStyle Style { get; set; }
[Property] public bool ReceivePointerInput { get; set; } = false;
public NodeLayout Layout { get; private set; } = new NodeLayout();
public void Update( NodeLayout parentLayout, float scaleFactor )
{
UpdateStyle( scaleFactor );
Layout.Compute( parentLayout, Style );
}
protected virtual void UpdateStyle(float scaleFactor) { }
public RenderAttributes GetRenderAttributes( Vector2 viewportSize )
{
RenderAttributes renderAttributes = new RenderAttributes();
Vector2 ndcPos = ScreenPositionToNdc( Layout.Outer.Position, viewportSize );
Vector2 ndcSize = ScreenSizeToNdc( Layout.Outer.Size, viewportSize );
renderAttributes.Set( "NodeRect", new Vector4( ndcPos.x, ndcPos.y, ndcSize.x, ndcSize.y ) );
renderAttributes.Set( "NodeScreenRect", new Vector4( Layout.Outer.Position.x, Layout.Outer.Position.y, Layout.Outer.Size.x, Layout.Outer.Size.y ) );
renderAttributes.Set( "Texture", Style.Texture );
renderAttributes.Set( "Tint", Style.Tint );
renderAttributes.Set( "UvScale", Style.UvScale );
renderAttributes.Set( "UvOffset", Style.UvOffset );
renderAttributes.Set( "BorderColor", Style.BorderColor );
renderAttributes.Set( "NodeSize", Layout.Outer.Size );
renderAttributes.Set( "CornerRadius", Style.CornerRadius );
renderAttributes.Set( "BorderWidth", Style.BorderWidth );
renderAttributes.Set( "ClipRect", new Vector4( Layout.ClipRect.Position.x, Layout.ClipRect.Position.y, Layout.ClipRect.Size.x, Layout.ClipRect.Size.y ) );
renderAttributes.Set( "ClipRadius", Layout.ClipRadius );
return renderAttributes;
}
private static Vector2 ScreenPositionToNdc( Vector2 position, Vector2 viewport )
{
float x = position.x / viewport.x * 2.0f - 1.0f;
float y = 1.0f - position.y / viewport.y * 2.0f;
return new Vector2( x, y );
}
private static Vector2 ScreenSizeToNdc( Vector2 size, Vector2 viewport )
{
float x = size.x / viewport.x * 2.0f;
float y = size.y / viewport.y * 2.0f;
return new Vector2( x, y );
}
public bool ContainsPoint( Vector2 point )
{
if ( !IsInsideRoundedRect( Layout.Outer, Style.CornerRadius, point ) )
return false;
if ( !IsInsideRoundedRect( Layout.ClipRect, Layout.ClipRadius, point ) )
return false;
return true;
}
protected static bool IsInsideRoundedRect( Rect rect, float radius, Vector2 point )
{
if ( !rect.IsInside( point ) )
return false;
if ( radius <= 0f )
return true;
float r = Math.Min( radius, Math.Min( rect.Width, rect.Height ) * 0.5f );
float cx = Math.Clamp( point.x, rect.Left + r, rect.Right - r );
float cy = Math.Clamp( point.y, rect.Top + r, rect.Bottom - r );
float dx = point.x - cx;
float dy = point.y - cy;
return dx * dx + dy * dy <= r * r;
}
}