mirror of
https://github.com/Facepunch/sbox-public.git
synced 2026-01-10 23:38:34 -05:00
This commit imports the C# engine code and game files, excluding C++ source code. [Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
322 lines
7.0 KiB
C#
322 lines
7.0 KiB
C#
using Sandbox.Engine;
|
|
|
|
namespace Sandbox.UI;
|
|
|
|
/// <summary>
|
|
/// A root panel. Serves as a container for other panels, handles things such as rendering.
|
|
/// </summary>
|
|
public partial class RootPanel : Panel
|
|
{
|
|
/// <summary>
|
|
/// Bounds of the panel, i.e. its size and position on the screen.
|
|
/// </summary>
|
|
public Rect PanelBounds { get; set; } = new Rect( 0, 0, 512, 512 );
|
|
|
|
/// <summary>
|
|
/// If any of our panels are visible and want mouse input (pointer-events != none) then
|
|
/// this will be set to true.
|
|
/// </summary>
|
|
internal bool ChildrenWantMouseInput { get; set; }
|
|
|
|
/// <summary>
|
|
/// The scale of this panel and its children.
|
|
/// </summary>
|
|
public float Scale { get; protected set; } = 1.0f;
|
|
|
|
/// <summary>
|
|
/// If set to true this panel won't be rendered to the screen like a normal panel.
|
|
/// This is true when the panel is drawn via other means (like as a world panel).
|
|
/// </summary>
|
|
public bool RenderedManually { get; set; }
|
|
|
|
/// <summary>
|
|
/// True if this is a world panel, so should be skipped when determining cursor visibility etc
|
|
/// </summary>
|
|
public virtual bool IsWorldPanel { get; set; }
|
|
|
|
/// <summary>
|
|
/// If this panel belongs to a VR overlay
|
|
/// </summary>
|
|
public bool IsVR { get; internal set; }
|
|
|
|
/// <summary>
|
|
/// If this panel should be rendered with ~4K resolution.
|
|
/// </summary>
|
|
public bool IsHighQualityVR { get; internal set; }
|
|
|
|
/// <summary>
|
|
/// Current global mouse position, projected onto plane for world panels.
|
|
/// </summary>
|
|
internal Vector2 MousePos;
|
|
|
|
public RootPanel()
|
|
{
|
|
Style.Width = Length.Percent( 100 );
|
|
Style.Height = Length.Percent( 100 );
|
|
|
|
GlobalContext.Current.UISystem.AddRoot( this );
|
|
AddToLists();
|
|
|
|
StyleSheet.Load( "/styles/rootpanel.scss" );
|
|
}
|
|
|
|
public override void Delete( bool immediate = true )
|
|
{
|
|
base.Delete( immediate );
|
|
}
|
|
|
|
public override void OnDeleted()
|
|
{
|
|
base.OnDeleted();
|
|
|
|
GlobalContext.Current.UISystem.RemoveRoot( this );
|
|
}
|
|
|
|
internal override void AddToLists()
|
|
{
|
|
base.AddToLists();
|
|
|
|
Sandbox.Internal.IPanel.InspectablePanels.Add( this );
|
|
}
|
|
|
|
internal override void RemoveFromLists()
|
|
{
|
|
base.RemoveFromLists();
|
|
|
|
Sandbox.Internal.IPanel.InspectablePanels.Remove( this );
|
|
}
|
|
|
|
/// <summary>
|
|
/// This is called from tests to emulate the regular root panel simulate loop
|
|
/// </summary>
|
|
internal void Layout()
|
|
{
|
|
TickInternal();
|
|
PreLayout();
|
|
CalculateLayout();
|
|
PostLayout();
|
|
}
|
|
|
|
int layoutHash;
|
|
|
|
/// <summary>
|
|
/// Called before layout to lock the bounds of this root panel to the screen size (which is passed).
|
|
/// Internally this sets PanelBounds to rect and calls UpdateScale.
|
|
/// </summary>
|
|
protected virtual void UpdateBounds( Rect rect )
|
|
{
|
|
PanelBounds = rect;
|
|
|
|
if ( IsVR && IsHighQualityVR )
|
|
{
|
|
PanelBounds = new Rect( 0, 0, 3840, 2400 );
|
|
return;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Work out scaling here. Default is to scale relative to the screen being
|
|
/// 1920 wide. ie - scale = screensize.Width / 1920.0f;
|
|
/// </summary>
|
|
protected virtual void UpdateScale( Rect screenSize )
|
|
{
|
|
Scale = screenSize.Height / 1080.0f;
|
|
|
|
if ( Game.IsRunningOnHandheld )
|
|
{
|
|
Scale = Scale * 1.333f;
|
|
}
|
|
|
|
if ( IsVR && IsHighQualityVR )
|
|
{
|
|
Scale = 2.33f;
|
|
}
|
|
}
|
|
|
|
internal void TickInputInternal()
|
|
{
|
|
ChildrenWantMouseInput = WantsMouseInput();
|
|
}
|
|
|
|
internal void PreLayout( Rect screenSize )
|
|
{
|
|
UpdateBounds( screenSize );
|
|
UpdateScale( PanelBounds );
|
|
|
|
Scale = MathX.Clamp( Scale, 0.1f, 10.0f );
|
|
|
|
PreLayout();
|
|
}
|
|
|
|
internal void PreLayout()
|
|
{
|
|
var cascade = new LayoutCascade
|
|
{
|
|
Scale = Scale,
|
|
Root = this,
|
|
};
|
|
|
|
Style.Left = 0.0f;
|
|
Style.Top = 0.0f;
|
|
Style.Width = PanelBounds.Width * (1 / Scale);
|
|
Style.Height = PanelBounds.Height * (1 / Scale);
|
|
|
|
var hash = HashCode.Combine( PanelBounds.Width, PanelBounds.Height, Scale );
|
|
if ( hash != layoutHash )
|
|
{
|
|
layoutHash = hash;
|
|
StyleSelectorsChanged( true, true );
|
|
SkipAllTransitions();
|
|
|
|
cascade.SelectorChanged = true;
|
|
}
|
|
|
|
BuildStyleRules();
|
|
|
|
PushRootValues();
|
|
|
|
PreLayout( cascade );
|
|
}
|
|
|
|
internal void CalculateLayout()
|
|
{
|
|
if ( YogaNode == null )
|
|
return;
|
|
|
|
using var perfScope = Performance.Scope( "CalculateLayout" );
|
|
PushRootValues();
|
|
YogaNode.CalculateLayout();
|
|
}
|
|
|
|
internal void PostLayout()
|
|
{
|
|
PushRootValues();
|
|
FinalLayout( Vector2.Zero );
|
|
}
|
|
|
|
internal void PushRootValues()
|
|
{
|
|
Length.RootSize = new Vector2( PanelBounds.Width, PanelBounds.Height );
|
|
Length.RootFontSize = ComputedStyle?.FontSize ?? Length.Pixels( 13 ).Value;
|
|
Length.RootScale = ScaleToScreen;
|
|
}
|
|
|
|
public override void OnLayout( ref Rect layoutRect )
|
|
{
|
|
layoutRect = PanelBounds;
|
|
}
|
|
|
|
internal void Render( float opacity = 1.0f )
|
|
{
|
|
ThreadSafe.AssertIsMainThread();
|
|
GlobalContext.Current.UISystem.Renderer.Render( this, opacity );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Render this panel manually. This gives more flexibility to where UI is rendered, to texture for example.
|
|
/// <see cref="RenderedManually"/> must be set to true.
|
|
/// </summary>
|
|
public void RenderManual( float opacity = 1.0f )
|
|
{
|
|
Graphics.AssertRenderBlock();
|
|
|
|
if ( !RenderedManually && !IsWorldPanel )
|
|
throw new Exception( $"{nameof( RenderedManually )} must be set to true to render this panel manually." );
|
|
|
|
Render( opacity );
|
|
}
|
|
|
|
[Event( "ui.skiptransitions" )]
|
|
internal void SkipAllTransitions()
|
|
{
|
|
SkipTransitions();
|
|
}
|
|
|
|
[Event( "language.changed" )]
|
|
internal void OnLanguageChanged()
|
|
{
|
|
LanguageChanged();
|
|
}
|
|
|
|
/// <summary>
|
|
/// A list of panels that are waiting to have their styles re-evaluated
|
|
/// </summary>
|
|
readonly HashSet<Panel> styleRuleUpdates = new();
|
|
|
|
/// <summary>
|
|
/// Add this panel to a list to have their styles re-evaluated. This should be done any
|
|
/// time the panel changes in a way that could affect its style selector.. like if its child
|
|
/// index changed, or classes added or removed, or became hovered etc.
|
|
/// </summary>
|
|
internal void AddToBuildStyleRulesList( Panel panel )
|
|
{
|
|
styleRuleUpdates.Add( panel );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Run through all panels that are pending a re-check on their style rules.
|
|
/// Only properly invalidate them if their rules actually change.
|
|
/// </summary>
|
|
internal void BuildStyleRules()
|
|
{
|
|
if ( styleRuleUpdates.Count == 0 )
|
|
return;
|
|
|
|
var timer = FastTimer.StartNew();
|
|
int count = styleRuleUpdates.Count;
|
|
int locks = 0;
|
|
|
|
var l = new object();
|
|
|
|
//
|
|
// Anything in BuildRules should be thread safe
|
|
//
|
|
#if true
|
|
{
|
|
Parallel.ForEach( styleRuleUpdates, panel =>
|
|
{
|
|
if ( !panel.IsValid )
|
|
return;
|
|
|
|
if ( panel.Style.BuildRulesInThread() )
|
|
{
|
|
lock ( l )
|
|
{
|
|
locks++;
|
|
panel.SetNeedsPreLayout();
|
|
}
|
|
}
|
|
|
|
panel.MarkStylesRebuilt();
|
|
|
|
} );
|
|
|
|
}
|
|
#else
|
|
{
|
|
foreach ( var panel in styleRuleUpdates )
|
|
{
|
|
if ( !panel.IsValid )
|
|
return;
|
|
|
|
if ( panel.Style.BuildRulesInThread() )
|
|
{
|
|
lock ( l )
|
|
{
|
|
locks++;
|
|
panel.SetNeedsPreLayout();
|
|
}
|
|
}
|
|
};
|
|
}
|
|
#endif
|
|
|
|
styleRuleUpdates.Clear();
|
|
|
|
if ( timer.ElapsedMilliSeconds > 0.5 )
|
|
{
|
|
Log.Trace( $"BuildStyleRules {count:n0} ({locks}) took {timer.ElapsedMilliSeconds}ms" );
|
|
}
|
|
}
|
|
}
|