mirror of
https://github.com/Facepunch/sbox-public.git
synced 2026-08-01 08:18:20 -04:00
ADDED css min(), max() and clamp() for lengths css-wide keywords inherit, initial, unset, revert (incl. shorthands) added currentColor keyword to css colors can parse oklch(), lab() and hwb() added css text-align: justify css white-space: pre-wrap and break-spaces added css word-break: break-word parses css image-rendering: crisp-edges (point sampling) added css inset shorthand now parses 1–4 values in the css border-width shorthand added css transition-duration, -delay, -property and -timing-function longhands css :has() now supports descendant selectors added css opacity percentages added css overflow: auto (maps to scroll) added css gap: normal added css filter: none css transform: scale() now takes comma-separated args css animations now accept ms units added more css justify-content, align-* and text-align keywords (start, end, …) parses css dvh/svh/lvh/dvw viewport units (treated as vh/vw) parses css object-fit: scale-down (treated as contain) added css margin-block / margin-inline (+ the logical margin sides) added css padding-block / padding-inline (+ the logical padding sides) added css inset-block / inset-inline (+ the logical inset sides) added the css flex-flow shorthand added the css font shorthand added css font-size keywords (xx-small … xxx-large) css letter-spacing and word-spacing now accept normal added css font-smooth: none css aspect-ratio now accepts the auto form css font-family now maps generic families (serif, sans-serif, monospace) added css flex-flow added css font FIXED fixed css !important dropping the whole declaration css line-height without units is now a multiplier fixed css calc() division and full-expression parsing fixed the css flex shorthand for single-number and three-value forms fixed the css transition shorthand when the property is omitted css font-family now uses the first family in a comma stack colors now parse fully in the css background shorthand an unknown css @-rule no longer drops the whole stylesheet css variables no longer match on partial tokens injected css variables are kept across stylesheet hotloads fixed css selector specificity overflowing when comparing rules css background: none now resets the background (image and colour) css filter: none now overrides a filter set by a base class css transform: none now overrides a transform set by a base class IMPROVED css rule matching is ~3–5× faster on large stylesheets (rules indexed by class) ~2× fewer allocations in css transitions (snapshot styles once, not per property) finished css animations no longer re-layout every frame less GC in css sibling/child selector queries (no list copies or wrappers) less GC diffing active css rules (no LINQ or hashsets) faster, lower-GC Yoga layout wrapper css stylesheet hotload now watches newly imported files
713 lines
18 KiB
C#
713 lines
18 KiB
C#
using Sandbox.Audio;
|
|
|
|
namespace Sandbox.UI;
|
|
|
|
public partial class Panel
|
|
{
|
|
internal YogaWrapper YogaNode;
|
|
|
|
/// <summary>
|
|
/// Access to various bounding boxes of this panel.
|
|
/// </summary>
|
|
[Hide]
|
|
public Box Box { get; init; } = new Box();
|
|
|
|
/// <summary>
|
|
/// If true, calls <see cref="DrawContent(PanelRenderer, ref RenderState)"/>.
|
|
/// </summary>
|
|
[Hide, Obsolete( "Use Draw" )]
|
|
public virtual bool HasContent => false;
|
|
|
|
/// <summary>
|
|
/// The velocity of the current scroll
|
|
/// </summary>
|
|
[Hide]
|
|
public Vector2 ScrollVelocity;
|
|
|
|
/// <summary>
|
|
/// Offset of the panel's children position for scrolling purposes.
|
|
/// </summary>
|
|
[Hide]
|
|
public Vector2 ScrollOffset { get; set; }
|
|
|
|
/// <summary>
|
|
/// Scale of the panel on the screen.
|
|
/// </summary>
|
|
[Hide]
|
|
public float ScaleToScreen { get; internal set; } = 1.0f;
|
|
|
|
/// <summary>
|
|
/// Inverse scale of <see cref="ScaleToScreen"/>.
|
|
/// </summary>
|
|
[Hide]
|
|
public float ScaleFromScreen => 1.0f / ScaleToScreen;
|
|
|
|
int LayoutCount = 0;
|
|
|
|
|
|
/// <summary>
|
|
/// If this panel has transforms, they'll be reflected here
|
|
/// </summary>
|
|
[Hide]
|
|
public Matrix? LocalMatrix { get; internal set; }
|
|
|
|
/// <summary>
|
|
/// If this panel or its parents have transforms, they'll be compounded here.
|
|
/// </summary>
|
|
[Hide]
|
|
public Matrix? GlobalMatrix { get; internal set; }
|
|
|
|
/// <summary>
|
|
/// The matrix that is applied as a result of transform: styles
|
|
/// </summary>
|
|
[Hide]
|
|
internal Matrix TransformMatrix { get; set; }
|
|
|
|
/// <summary>
|
|
/// The computed style has a non-default backdrop filter property
|
|
/// </summary>
|
|
[Hide]
|
|
internal bool HasBackdropFilter { get; private set; }
|
|
|
|
[Hide]
|
|
internal bool HasFilter { get; private set; }
|
|
|
|
[Hide]
|
|
internal bool HasCustomDraw => CachedDescriptors?.CustomEntries.Count > 0;
|
|
|
|
/// <summary>
|
|
/// The computed style has a renderable background
|
|
/// </summary>
|
|
[Hide]
|
|
internal bool HasBackground { get; private set; }
|
|
|
|
internal void UpdateVisibility()
|
|
{
|
|
bool old = IsVisible;
|
|
|
|
IsVisibleSelf = ComputedStyle?.CalcVisible() ?? false;
|
|
IsVisibleSelf = IsVisibleSelf || HasActiveTransitions;
|
|
IsVisible = IsVisibleSelf && (Parent?.IsVisible ?? true);
|
|
|
|
if ( old == IsVisible )
|
|
return;
|
|
|
|
if ( Parent != null )
|
|
{
|
|
Parent.IndexesDirty = true;
|
|
}
|
|
|
|
var c = _children?.Count ?? 0;
|
|
|
|
for ( int i = 0; i < c; i++ )
|
|
{
|
|
_children[i].UpdateVisibility();
|
|
}
|
|
|
|
try
|
|
{
|
|
OnVisibilityChanged();
|
|
}
|
|
catch ( System.Exception e )
|
|
{
|
|
Log.Warning( e );
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called when the visibility of the current panel changes. This could be because our own style changed, or a parent style.
|
|
/// You can check visibility using <see cref="IsVisible"/> and <see cref="IsVisibleSelf"/>.
|
|
/// </summary>
|
|
protected virtual void OnVisibilityChanged()
|
|
{
|
|
|
|
}
|
|
|
|
bool needsPreLayout = true;
|
|
bool needsFinalLayout = true;
|
|
|
|
internal void SetNeedsPreLayout()
|
|
{
|
|
if ( needsPreLayout ) return;
|
|
|
|
needsPreLayout = true;
|
|
needsFinalLayout = true;
|
|
|
|
Parent?.SetNeedsPreLayout();
|
|
}
|
|
|
|
internal virtual void PreLayout( LayoutCascade cascade )
|
|
{
|
|
if ( YogaNode == null )
|
|
return;
|
|
|
|
if ( !needsPreLayout && !cascade.SelectorChanged && !cascade.ParentChanged )
|
|
return;
|
|
|
|
needsPreLayout = false;
|
|
|
|
if ( IndexesDirty )
|
|
{
|
|
UpdateChildrenIndexes();
|
|
}
|
|
|
|
|
|
ComputedStyle = Style.BuildFinal( ref cascade, out bool changed );
|
|
cascade.ParentStyles = ComputedStyle;
|
|
|
|
PushLengthValues();
|
|
|
|
ScaleToScreen = cascade.Scale;
|
|
var previousOpacity = Opacity;
|
|
Opacity = ComputedStyle.Opacity.Value * (Parent?.Opacity ?? 1.0f);
|
|
UpdateVisibility();
|
|
|
|
if ( changed || !YogaNode.Initialized )
|
|
{
|
|
UpdateYoga();
|
|
}
|
|
|
|
if ( Opacity != previousOpacity )
|
|
{
|
|
IsRenderDirty = true;
|
|
}
|
|
|
|
if ( changed )
|
|
{
|
|
IsRenderDirty = true;
|
|
|
|
if ( Parent is not null )
|
|
{
|
|
Parent._renderChildrenDirty = true;
|
|
}
|
|
|
|
HasBackdropFilter = !ComputedStyle.IsDefault( "backdrop-filter-blur" )
|
|
|| !ComputedStyle.IsDefault( "backdrop-filter-contrast" )
|
|
|| !ComputedStyle.IsDefault( "backdrop-filter-saturate" )
|
|
|| !ComputedStyle.IsDefault( "backdrop-filter-sepia" )
|
|
|| !ComputedStyle.IsDefault( "backdrop-filter-invert" )
|
|
|| !ComputedStyle.IsDefault( "backdrop-filter-hue-rotate" )
|
|
|| !ComputedStyle.IsDefault( "backdrop-filter-brightness" );
|
|
|
|
HasFilter = !ComputedStyle.IsDefault( "filter-saturate" )
|
|
|| !ComputedStyle.IsDefault( "filter-brightness" )
|
|
|| !ComputedStyle.IsDefault( "filter-contrast" )
|
|
|| !ComputedStyle.IsDefault( "filter-blur" )
|
|
|| !ComputedStyle.IsDefault( "filter-sepia" )
|
|
|| !ComputedStyle.IsDefault( "filter-hue-rotate" )
|
|
|| !ComputedStyle.IsDefault( "filter-invert" )
|
|
|| !ComputedStyle.IsDefault( "filter-tint" )
|
|
|| !ComputedStyle.IsDefault( "filter-border-width" );
|
|
|
|
HasBackground = ComputedStyle.BackgroundColor.Value.a > 0f
|
|
|| ComputedStyle.BorderImageSource is not null
|
|
|| (ComputedStyle.BackgroundImage is not null && ComputedStyle.BackgroundImage != Texture.Invalid)
|
|
|| (ComputedStyle.BorderLeftColor.Value.a > 0f && ComputedStyle.BorderLeftWidth.Value.GetPixels( 1.0f ) > 0f)
|
|
|| (ComputedStyle.BorderTopColor.Value.a > 0f && ComputedStyle.BorderTopWidth.Value.GetPixels( 1.0f ) > 0f)
|
|
|| (ComputedStyle.BorderRightColor.Value.a > 0f && ComputedStyle.BorderRightWidth.Value.GetPixels( 1.0f ) > 0f)
|
|
|| (ComputedStyle.BorderBottomColor.Value.a > 0f && ComputedStyle.BorderBottomWidth.Value.GetPixels( 1.0f ) > 0f);
|
|
|
|
UpdateLayer( ComputedStyle );
|
|
}
|
|
|
|
UpdateOrder();
|
|
|
|
if ( LayoutCount > 0 && !IsVisibleSelf )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( _children == null || _children.Count == 0 )
|
|
return;
|
|
|
|
// We need to tell the children to force an update if any of the parent's
|
|
// cascading styles have changed.
|
|
cascade.ParentChanged = cascade.ParentChanged || changed;
|
|
|
|
for ( int i = 0; i < _children.Count; i++ )
|
|
{
|
|
_children[i].PreLayout( cascade );
|
|
}
|
|
|
|
//
|
|
// Our children's 'order' properties might have changed
|
|
// if so, tell yoga about the new order
|
|
//
|
|
SortChildrenOrder();
|
|
}
|
|
|
|
internal void UpdateYoga()
|
|
{
|
|
if ( ComputedStyle == null )
|
|
return;
|
|
|
|
YogaNode.Width = ComputedStyle.Width;
|
|
YogaNode.Height = ComputedStyle.Height;
|
|
YogaNode.MaxWidth = ComputedStyle.MaxWidth;
|
|
YogaNode.MaxHeight = ComputedStyle.MaxHeight;
|
|
YogaNode.MinWidth = ComputedStyle.MinWidth;
|
|
YogaNode.MinHeight = ComputedStyle.MinHeight;
|
|
YogaNode.Display = ComputedStyle.Display;
|
|
|
|
YogaNode.Left = ComputedStyle.Left;
|
|
YogaNode.Right = ComputedStyle.Right;
|
|
YogaNode.Top = ComputedStyle.Top;
|
|
YogaNode.Bottom = ComputedStyle.Bottom;
|
|
|
|
YogaNode.MarginLeft = ComputedStyle.MarginLeft;
|
|
YogaNode.MarginRight = ComputedStyle.MarginRight;
|
|
YogaNode.MarginTop = ComputedStyle.MarginTop;
|
|
YogaNode.MarginBottom = ComputedStyle.MarginBottom;
|
|
|
|
YogaNode.PaddingLeft = ComputedStyle.PaddingLeft;
|
|
YogaNode.PaddingRight = ComputedStyle.PaddingRight;
|
|
YogaNode.PaddingTop = ComputedStyle.PaddingTop;
|
|
YogaNode.PaddingBottom = ComputedStyle.PaddingBottom;
|
|
|
|
YogaNode.BorderLeftWidth = ComputedStyle.BorderLeftWidth;
|
|
YogaNode.BorderTopWidth = ComputedStyle.BorderTopWidth;
|
|
YogaNode.BorderRightWidth = ComputedStyle.BorderRightWidth;
|
|
YogaNode.BorderBottomWidth = ComputedStyle.BorderBottomWidth;
|
|
|
|
YogaNode.PositionType = ComputedStyle.Position;
|
|
YogaNode.AspectRatio = ComputedStyle.AspectRatio;
|
|
YogaNode.FlexGrow = ComputedStyle.FlexGrow;
|
|
YogaNode.FlexShrink = ComputedStyle.FlexShrink;
|
|
YogaNode.FlexBasis = ComputedStyle.FlexBasis;
|
|
YogaNode.Wrap = ComputedStyle.FlexWrap;
|
|
|
|
YogaNode.AlignContent = ComputedStyle.AlignContent;
|
|
YogaNode.AlignItems = ComputedStyle.AlignItems;
|
|
YogaNode.AlignSelf = ComputedStyle.AlignSelf;
|
|
YogaNode.FlexDirection = ComputedStyle.FlexDirection;
|
|
YogaNode.JustifyContent = ComputedStyle.JustifyContent;
|
|
YogaNode.Overflow = ComputedStyle.Overflow;
|
|
|
|
YogaNode.RowGap = ComputedStyle.RowGap;
|
|
YogaNode.ColumnGap = ComputedStyle.ColumnGap;
|
|
|
|
YogaNode.Initialized = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The currently calculated opacity.
|
|
/// This is set by multiplying our current style opacity with our parent's opacity.
|
|
/// </summary>
|
|
[Hide]
|
|
public float Opacity { get; private set; } = 1.0f;
|
|
|
|
/// <summary>
|
|
/// This panel has just been laid out. You can modify its position now and it will affect its children.
|
|
/// This is a useful place to restrict shit to the screen etc.
|
|
/// </summary>
|
|
public virtual void OnLayout( ref Rect layoutRect )
|
|
{
|
|
|
|
}
|
|
|
|
int layoutHash;
|
|
|
|
/// <summary>
|
|
/// Takes a <see cref="LayoutCascade"/> and returns an outer rect
|
|
/// </summary>
|
|
public virtual void FinalLayout( Vector2 offset )
|
|
{
|
|
if ( ComputedStyle is null )
|
|
return;
|
|
|
|
if ( YogaNode is null )
|
|
return;
|
|
|
|
PushLengthValues();
|
|
|
|
var hash = HashCode.Combine( offset, ScrollOffset, ScrollVelocity, ComputedStyle?.Transform, Opacity, ComputedStyle.Display );
|
|
if ( layoutHash == hash && !YogaNode.HasNewLayout && !needsFinalLayout ) return;
|
|
|
|
needsFinalLayout = false;
|
|
layoutHash = hash;
|
|
|
|
//if ( YogaNode.HasNewLayout || parentPos != offset )
|
|
{
|
|
var previousRect = Box.Rect;
|
|
|
|
Box.Rect = YogaNode.YogaRect;
|
|
|
|
Box.Rect.Position += offset;
|
|
|
|
OnLayout( ref Box.Rect );
|
|
|
|
Box.Padding = YogaNode.Padding;
|
|
Box.Margin = YogaNode.Margin;
|
|
Box.Border = YogaNode.Border;
|
|
|
|
Box.RectOuter = Box.Rect.Grow( YogaNode.Margin.Left, YogaNode.Margin.Top, YogaNode.Margin.Right, YogaNode.Margin.Bottom );
|
|
Box.RectInner = Box.Rect.Shrink( YogaNode.Padding.Left, YogaNode.Padding.Top, YogaNode.Padding.Right, YogaNode.Padding.Bottom );
|
|
Box.ClipRect = Box.Rect.Shrink( YogaNode.Border.Left, YogaNode.Border.Top, YogaNode.Border.Right, YogaNode.Border.Bottom );
|
|
|
|
UpdateLayer( ComputedStyle );
|
|
|
|
Box.Rect = Box.Rect.Floor();
|
|
Box.RectOuter = Box.RectOuter.Floor();
|
|
Box.RectInner = Box.RectInner.Floor();
|
|
Box.ClipRect = Box.ClipRect.Floor();
|
|
|
|
// Build the matrix that is generated from "transform" etc. We do this here after we have the size of the
|
|
// panel - which should be super duper fine.
|
|
TransformMatrix = ComputedStyle.BuildTransformMatrix( Box.Rect.Size );
|
|
|
|
if ( previousRect != Box.Rect )
|
|
{
|
|
IsRenderDirty = true;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If we have an intro flag, we need to turn it off
|
|
// because by now it's been on for one frame
|
|
//
|
|
if ( HasIntro )
|
|
{
|
|
// A nice optimization here would be to not dirty the
|
|
// style selector if none of our styles have a :intro flag
|
|
Switch( PseudoClass.Intro, false );
|
|
}
|
|
|
|
if ( ComputedStyle.Display == DisplayMode.None ) return;
|
|
if ( LayoutCount > 0 && Opacity <= 0.0f ) return;
|
|
|
|
// The initial state should be true for these panels
|
|
// So there is no need to manually scroll to the bottom for scroll to be pinned there by default
|
|
if ( LayoutCount == 0 && PreferScrollToBottom )
|
|
{
|
|
IsScrollAtBottom = true;
|
|
}
|
|
|
|
bool wasScrollatBottom = IsScrollAtBottom;
|
|
|
|
offset = Box.Rect.Position - ScrollOffset.SnapToGrid( 1.0f );
|
|
FinalLayoutChildren( offset );
|
|
|
|
if ( wasScrollatBottom )
|
|
{
|
|
UpdateScrollPin();
|
|
}
|
|
|
|
LayoutCount++;
|
|
}
|
|
|
|
private void PushLengthValues()
|
|
{
|
|
Length.CurrentFontSize = ComputedStyle.FontSize ?? Length.Pixels( 13 ).Value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// If true, we'll try to stay scrolled to the bottom when the panel changes size
|
|
/// </summary>
|
|
[Hide]
|
|
public bool PreferScrollToBottom { get; set; }
|
|
|
|
/// <summary>
|
|
/// Whether the scrolling is currently pinned to the bottom of the panel as dictated by <see cref="PreferScrollToBottom"/>.
|
|
/// </summary>
|
|
[Hide]
|
|
public bool IsScrollAtBottom { get; private set; }
|
|
|
|
/// <summary>
|
|
/// The size of the scrollable area within this panel.
|
|
/// </summary>
|
|
[Hide]
|
|
public Vector2 ScrollSize { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Is this panel currently being scrolled by dragging?
|
|
/// </summary>
|
|
[Hide]
|
|
public bool IsDragScrolling { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Layout the children of this panel.
|
|
/// </summary>
|
|
/// <param name="offset">The parent's position.</param>
|
|
protected virtual void FinalLayoutChildren( Vector2 offset )
|
|
{
|
|
if ( !HasChildren )
|
|
return;
|
|
|
|
for ( int i = 0; i < _children.Count; i++ )
|
|
{
|
|
try
|
|
{
|
|
_children[i].FinalLayout( offset );
|
|
}
|
|
catch ( System.Exception e )
|
|
{
|
|
Log.Warning( e );
|
|
}
|
|
}
|
|
|
|
if ( ComputedStyle.Overflow.Value == OverflowMode.Scroll )
|
|
{
|
|
var rect = Box.Rect;
|
|
rect.Position -= ScrollOffset;
|
|
|
|
for ( int i = 0; i < _children.Count; i++ )
|
|
{
|
|
var child = _children[i];
|
|
|
|
if ( child.IsVisible )
|
|
{
|
|
rect.Add( child.GetLayoutRect() );
|
|
}
|
|
}
|
|
|
|
rect.Height += Box.Padding.Bottom;
|
|
rect.Right += Box.Padding.Right;
|
|
|
|
ConstrainScrolling( rect.Size );
|
|
}
|
|
else
|
|
{
|
|
ScrollOffset = 0;
|
|
}
|
|
|
|
}
|
|
|
|
Rect GetLayoutRect()
|
|
{
|
|
if ( HasChildren && ComputedStyle.Display == DisplayMode.Contents )
|
|
{
|
|
Rect rect = default;
|
|
for ( int i = 0; i < _children.Count; i++ )
|
|
{
|
|
var child = _children[i];
|
|
|
|
if ( child.IsVisible )
|
|
{
|
|
if ( i == 0 ) rect = child.GetLayoutRect();
|
|
else rect.Add( child.GetLayoutRect() );
|
|
}
|
|
}
|
|
|
|
return rect;
|
|
}
|
|
|
|
return Box.RectOuter;
|
|
}
|
|
|
|
private void UpdateScrollPin()
|
|
{
|
|
if ( !PreferScrollToBottom )
|
|
return;
|
|
|
|
if ( IsScrollAtBottom )
|
|
return;
|
|
|
|
if ( !ScrollVelocity.y.AlmostEqual( 0, 0.1f ) )
|
|
return;
|
|
|
|
ScrollOffset = new Vector2( ScrollOffset.x, ScrollSize.y );
|
|
IsScrollAtBottom = true;
|
|
ScrollVelocity.y = 0;
|
|
|
|
}
|
|
|
|
bool isScrolling;
|
|
Vector2 scrollVelocityVelocity;
|
|
|
|
protected virtual void AddScrollVelocity()
|
|
{
|
|
if ( ScrollVelocity.IsNearZeroLength )
|
|
{
|
|
ScrollVelocity = 0;
|
|
return;
|
|
}
|
|
|
|
ScrollVelocity = Vector2.SmoothDamp( ScrollVelocity, 0, ref scrollVelocityVelocity, 0.5f, RealTime.SmoothDelta );
|
|
|
|
// Bring it to a stop
|
|
if ( ScrollVelocity.y.AlmostEqual( 0, 0.01f ) ) ScrollVelocity.y = 0;
|
|
if ( ScrollVelocity.x.AlmostEqual( 0, 0.01f ) ) ScrollVelocity.x = 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constrain <see cref="ScrollOffset">scrolling</see> to the given size.
|
|
/// </summary>
|
|
protected virtual void ConstrainScrolling( Vector2 size )
|
|
{
|
|
if ( IsDragScrolling )
|
|
return;
|
|
|
|
isScrolling = false;
|
|
|
|
size -= Box.Rect.Size;
|
|
|
|
var heightChange = size.y - ScrollSize.y;
|
|
|
|
ScrollSize = size;
|
|
ScrollSize = ScrollSize.SnapToGrid( 1.0f );
|
|
|
|
var overflow = ComputedStyle.Overflow;
|
|
|
|
if ( overflow == OverflowMode.Visible || overflow == OverflowMode.Hidden )
|
|
{
|
|
ScrollOffset = 0;
|
|
return;
|
|
}
|
|
|
|
var so = ScrollOffset;
|
|
|
|
// add velocity
|
|
so += ScrollVelocity * RealTime.SmoothDelta * 60.0f;
|
|
|
|
// Reverse the axis if flex-direction: *-reverse or justify-content: flex-end;
|
|
var axisReversed = ComputedStyle.JustifyContent == Justify.FlexEnd || ComputedStyle.FlexDirection == FlexDirection.RowReverse || ComputedStyle.FlexDirection == FlexDirection.ColumnReverse;
|
|
|
|
IsScrollAtBottom = so.y + ScrollVelocity.y >= size.y;
|
|
if ( ScrollVelocity.y > 0 && IsScrollAtBottom ) so.y += heightChange;
|
|
|
|
//
|
|
// TODO - a style to let them turn springy mode off ?
|
|
//
|
|
|
|
var constrainSpeed = RealTime.SmoothDelta * 100.0f;
|
|
|
|
if ( axisReversed )
|
|
{
|
|
if ( so.y > 0 ) so.y = so.y.LerpTo( 0, constrainSpeed );
|
|
if ( so.x > 0 ) so.x = so.x.LerpTo( 0, constrainSpeed );
|
|
if ( so.y < -ScrollSize.y ) so.y = so.y.LerpTo( -ScrollSize.y, constrainSpeed );
|
|
if ( so.x < -ScrollSize.x ) so.x = so.x.LerpTo( -ScrollSize.x, constrainSpeed );
|
|
}
|
|
else
|
|
{
|
|
if ( so.y < 0 ) so.y = so.y.LerpTo( 0, constrainSpeed );
|
|
if ( so.x < 0 ) so.x = so.x.LerpTo( 0, constrainSpeed );
|
|
if ( so.y > ScrollSize.y ) so.y = so.y.LerpTo( ScrollSize.y, constrainSpeed );
|
|
if ( so.x > ScrollSize.x ) so.x = so.x.LerpTo( ScrollSize.x, constrainSpeed );
|
|
}
|
|
|
|
if ( ScrollOffset == so )
|
|
return;
|
|
|
|
ScrollOffset = so;
|
|
isScrolling = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Play a sound from this panel.
|
|
/// </summary>
|
|
public void PlaySound( string sound )
|
|
{
|
|
if ( string.IsNullOrEmpty( sound ) )
|
|
return;
|
|
|
|
var h = Sound.Play( sound );
|
|
if ( !h.IsValid() )
|
|
return;
|
|
|
|
if ( FindRootPanel() is WorldPanel worldPanel )
|
|
{
|
|
// Calculate world position of the element, not just the root WorldPanel
|
|
var worldPosition = worldPanel.Position;
|
|
var panelPosition = Box.Rect.Position;
|
|
var worldRotation = worldPanel.Rotation * new Angles( 0, 90, 0 );
|
|
var worldOffset = new Vector3( panelPosition.x, panelPosition.y, 0 );
|
|
worldOffset = worldRotation * (worldOffset * ScenePanelObject.ScreenToWorldScale);
|
|
h.TargetMixer = Mixer.FindMixerByName( "Game" );
|
|
h.Position = worldPosition + worldOffset;
|
|
}
|
|
else
|
|
{
|
|
var normalizedScreenPosition = Box.Rect.Center / Screen.Size;
|
|
normalizedScreenPosition -= 0.5f;
|
|
h.TargetMixer = Mixer.FindMixerByName( "UI" );
|
|
h.Position = new Vector3( 64.0f, normalizedScreenPosition.x.Clamp( -1, 1 ) * -256.0f, -normalizedScreenPosition.y.Clamp( -1, 1 ) * 64.0f );
|
|
h.ListenLocal = true;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Represents position and size of a <see cref="Panel"/> on the screen.
|
|
/// </summary>
|
|
[SkipHotload]
|
|
public class Box
|
|
{
|
|
/// <summary>
|
|
/// Position and size of the element on the screen, <b>including both - its padding AND margin</b>.
|
|
/// </summary>
|
|
public Rect RectOuter;
|
|
|
|
/// <summary>
|
|
/// Position and size of only the element's inner content on the screen, <i>without padding OR margin</i>.
|
|
/// </summary>
|
|
public Rect RectInner;
|
|
|
|
/// <summary>
|
|
/// The size of padding.
|
|
/// </summary>
|
|
public Margin Padding;
|
|
|
|
/// <summary>
|
|
/// The size of border.
|
|
/// </summary>
|
|
public Margin Border;
|
|
|
|
/// <summary>
|
|
/// The size of border.
|
|
/// </summary>
|
|
public Margin Margin;
|
|
|
|
/// <summary>
|
|
/// Position and size of the element on the screen, <b>including its padding</b>, <i>but not margin</i>.
|
|
/// </summary>
|
|
public Rect Rect;
|
|
|
|
/// <summary>
|
|
/// <see cref="Rect"/> minus the border sizes.
|
|
/// Used internally to "clip" (hide) everything outside of these bounds, if the panels <see cref="OverflowMode"/> is not set to <see cref="OverflowMode.Visible"/>.
|
|
/// </summary>
|
|
public Rect ClipRect;
|
|
|
|
/// <summary>
|
|
/// Position of the left edge in screen coordinates.
|
|
/// </summary>
|
|
public float Left => Rect.Left;
|
|
|
|
/// <summary>
|
|
/// Position of the right edge in screen coordinates.
|
|
/// </summary>
|
|
public float Right => Rect.Right;
|
|
|
|
/// <summary>
|
|
/// Position of the top edge in screen coordinates.
|
|
/// </summary>
|
|
public float Top => Rect.Top;
|
|
|
|
/// <summary>
|
|
/// Position of the bottom edge in screen coordinates.
|
|
/// </summary>
|
|
public float Bottom => Rect.Bottom;
|
|
}
|
|
|
|
internal static class YogaEx
|
|
{
|
|
public static float ToFloat( this Length? self, Length? dimension )
|
|
{
|
|
if ( self == null ) return 0;
|
|
|
|
if ( self.Value.Unit == LengthUnit.Expression )
|
|
return self.Value.GetPixels( dimension?.Value ?? 0f );
|
|
|
|
if ( self.Value.Unit == LengthUnit.Pixels )
|
|
return self.Value.Value;
|
|
|
|
if ( self.Value.Unit == LengthUnit.RootEm || self.Value.Unit == LengthUnit.Em )
|
|
return self.Value.GetPixels( dimension?.Value ?? 0f );
|
|
|
|
// TODO
|
|
return self.Value.Value;
|
|
}
|
|
}
|