mirror of
https://github.com/Facepunch/sbox-public.git
synced 2026-01-24 14:20:39 -05:00
This commit imports the C# engine code and game files, excluding C++ source code. [Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
364 lines
8.3 KiB
C#
364 lines
8.3 KiB
C#
using Sandbox.Engine;
|
|
using Sandbox.Internal;
|
|
using Sandbox.Modals;
|
|
using Sandbox.UI;
|
|
using Sandbox.VR;
|
|
|
|
namespace Sandbox;
|
|
|
|
/// <summary>
|
|
/// Holds onto a list of root panels to tick, input and draw
|
|
/// </summary>
|
|
internal class UISystem
|
|
{
|
|
internal PanelRenderer Renderer = new PanelRenderer();
|
|
|
|
internal PanelInput Input { get; } = new();
|
|
internal VROverlayInput VROverlayInput { get; } = new();
|
|
|
|
internal List<RootPanel> RootPanels = new();
|
|
internal List<Panel> DeletionList = new();
|
|
internal InputEventQueue InputEventQueue = new();
|
|
|
|
// focus
|
|
internal Panel CurrentFocus { get; set; }
|
|
internal Panel NextFocus { get; set; }
|
|
internal bool FocusPendingChange { get; set; }
|
|
|
|
internal void AddRoot( RootPanel rootPanel )
|
|
{
|
|
if ( RootPanels.Contains( rootPanel ) )
|
|
throw new System.Exception( "Adding root panel twice" );
|
|
|
|
RootPanels.Add( rootPanel );
|
|
}
|
|
|
|
internal void RemoveRoot( RootPanel rootPanel )
|
|
{
|
|
RootPanels.Remove( rootPanel );
|
|
}
|
|
|
|
internal void DeleteAllRoots()
|
|
{
|
|
var deleteList = new List<RootPanel>( RootPanels );
|
|
foreach ( var rootPanel in deleteList )
|
|
{
|
|
rootPanel.Delete( true );
|
|
|
|
// User can override Delete/OnDelete, so let's make sure we always remove from lists
|
|
rootPanel.RemoveFromLists();
|
|
RootPanels.Remove( rootPanel );
|
|
}
|
|
|
|
RunDeferredDeletion( true );
|
|
DeletionList.Clear();
|
|
}
|
|
|
|
internal void Render( float opacity = 1.0f )
|
|
{
|
|
Graphics.Attributes.SetCombo( "D_WORLDPANEL", 0 );
|
|
|
|
for ( int i = RootPanels.Count() - 1; i >= 0; i-- )
|
|
{
|
|
if ( !RootPanels[i].IsValid ) continue;
|
|
if ( RootPanels[i].RenderedManually || RootPanels[i].IsWorldPanel ) continue;
|
|
|
|
RootPanels[i].Render( opacity );
|
|
}
|
|
}
|
|
|
|
internal void Simulate( bool allowMouseInput )
|
|
{
|
|
using ( Performance.Scope( "Update Screen Size" ) )
|
|
{
|
|
Screen.UpdateFromEngine();
|
|
}
|
|
|
|
using ( Performance.Scope( "Tick Panels" ) )
|
|
{
|
|
TickPanels();
|
|
}
|
|
|
|
using ( Performance.Scope( "Tick Input" ) )
|
|
{
|
|
TickInput( allowMouseInput );
|
|
}
|
|
|
|
using ( Performance.Scope( "Pre Layout" ) )
|
|
{
|
|
PreLayout();
|
|
}
|
|
|
|
using ( Performance.Scope( "Deferred Deletion" ) )
|
|
{
|
|
RunDeferredDeletion();
|
|
}
|
|
|
|
using ( Performance.Scope( "Layout" ) )
|
|
{
|
|
Layout();
|
|
}
|
|
|
|
using ( Performance.Scope( "Post Layout" ) )
|
|
{
|
|
PostLayout();
|
|
}
|
|
|
|
using ( Performance.Scope( "Deferred Deletion" ) )
|
|
{
|
|
RunDeferredDeletion();
|
|
}
|
|
}
|
|
|
|
internal void DirtyAllStyles()
|
|
{
|
|
for ( int i = RootPanels.Count() - 1; i >= 0; i-- )
|
|
{
|
|
if ( !RootPanels[i].IsValid ) continue;
|
|
RootPanels[i].DirtyStylesRecursive();
|
|
}
|
|
}
|
|
|
|
internal void TickPanels()
|
|
{
|
|
RootPanels.RemoveAll( x => x == null );
|
|
|
|
for ( int i = 0; i < RootPanels.Count(); i++ )
|
|
{
|
|
if ( !RootPanels[i].IsValid ) continue;
|
|
RootPanels[i].TickInternal();
|
|
}
|
|
}
|
|
|
|
internal void PreLayout()
|
|
{
|
|
var width = Screen.Width;
|
|
var height = Screen.Height;
|
|
|
|
var screenRect = new Rect( 0, 0, width, height );
|
|
|
|
for ( int i = 0; i < RootPanels.Count(); i++ )
|
|
{
|
|
if ( !RootPanels[i].IsValid ) continue;
|
|
RootPanels[i].PreLayout( screenRect );
|
|
}
|
|
}
|
|
|
|
internal void Layout()
|
|
{
|
|
for ( int i = 0; i < RootPanels.Count(); i++ )
|
|
{
|
|
if ( !RootPanels[i].IsValid ) continue;
|
|
RootPanels[i].CalculateLayout();
|
|
}
|
|
}
|
|
|
|
internal void PostLayout()
|
|
{
|
|
for ( int i = 0; i < RootPanels.Count(); i++ )
|
|
{
|
|
if ( !RootPanels[i].IsValid ) continue;
|
|
RootPanels[i].PostLayout();
|
|
}
|
|
}
|
|
|
|
internal void TickInput( bool allowMouseInput )
|
|
{
|
|
for ( int i = 0; i < RootPanels.Count(); i++ )
|
|
{
|
|
if ( !RootPanels[i].IsValid ) continue;
|
|
RootPanels[i].TickInputInternal();
|
|
}
|
|
|
|
//
|
|
// Tick various input systems
|
|
//
|
|
Input.Tick( RootPanels.Where( p => !p.IsWorldPanel ).OrderByDescending( x => x.ComputedStyle?.ZIndex ?? 0 ), allowMouseInput && DoAnyPanelsWantMouseVisible() );
|
|
|
|
VROverlayInput.Tick( null, allowMouseInput && DoAnyPanelsWantMouseVisible() ); // Only needs to Tick it's own focused panel
|
|
WorldInputInternal.TickAll( RootPanels.Where( p => p.IsWorldPanel ) );
|
|
|
|
//
|
|
// We tick focus here, after the layout. This way any styles that
|
|
// were set at the same time as changing focus will be applied so
|
|
// that when we judge elibility the logic will be correct
|
|
//
|
|
InputFocus.Tick();
|
|
|
|
//
|
|
// Send all key events to the focused panel
|
|
//
|
|
InputEventQueue.TickFocused( CurrentFocus );
|
|
|
|
//
|
|
// Pass our global input events ( mouse move, double click ) to 2d panels
|
|
// WorldInputs simulate this themselves in WorldInputInternal.Tick
|
|
//
|
|
InputEventQueue.Tick( Input.Hovered, Input.Active );
|
|
|
|
//
|
|
// Set mouse delta to 0 so it doesn't repeat the last frame's
|
|
// delta on the next frame
|
|
//
|
|
Mouse.Frame();
|
|
|
|
bool inGame = IGameInstance.Current is not null;
|
|
|
|
var mouseState = Sandbox.Engine.InputContext.InputState.Ignore;
|
|
var buttonState = Sandbox.Engine.InputContext.InputState.Ignore;
|
|
|
|
if ( Game.IsMenu )
|
|
{
|
|
if ( !inGame )
|
|
{
|
|
mouseState = InputContext.InputState.UI;
|
|
buttonState = Sandbox.Engine.InputContext.InputState.Game;
|
|
|
|
if ( CurrentFocus is not null )
|
|
buttonState = CurrentFocus.ButtonInput == PanelInputType.Game ? InputContext.InputState.Game : InputContext.InputState.UI;
|
|
}
|
|
|
|
//
|
|
// A modal is open
|
|
//
|
|
if ( (IModalSystem.Current?.HasModalsOpen() ?? false) )
|
|
{
|
|
mouseState = Sandbox.Engine.InputContext.InputState.UI;
|
|
buttonState = Sandbox.Engine.InputContext.InputState.Game;
|
|
|
|
if ( CurrentFocus is not null )
|
|
buttonState = CurrentFocus.ButtonInput == PanelInputType.Game ? InputContext.InputState.Game : InputContext.InputState.UI;
|
|
}
|
|
|
|
//
|
|
// The loading screen is visible
|
|
//
|
|
if ( Sandbox.LoadingScreen.IsVisible )
|
|
{
|
|
mouseState = Sandbox.Engine.InputContext.InputState.UI;
|
|
buttonState = Sandbox.Engine.InputContext.InputState.UI;
|
|
}
|
|
|
|
//
|
|
// The developer console is open
|
|
//
|
|
if ( IMenuSystem.Current?.ForceCursorVisible ?? false )
|
|
{
|
|
mouseState = Sandbox.Engine.InputContext.InputState.UI;
|
|
buttonState = Sandbox.Engine.InputContext.InputState.UI;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If we're a game menu, and there is no client - then treat all input as game input by default.
|
|
//
|
|
if ( !Game.IsMenu )
|
|
{
|
|
mouseState = Sandbox.Engine.InputContext.InputState.Game;
|
|
buttonState = Sandbox.Engine.InputContext.InputState.Game;
|
|
|
|
if ( DoAnyPanelsWantMouseVisible() )
|
|
mouseState = InputContext.InputState.UI;
|
|
|
|
if ( CurrentFocus is not null )
|
|
buttonState = CurrentFocus.ButtonInput == PanelInputType.Game ? InputContext.InputState.Game : InputContext.InputState.UI;
|
|
|
|
// No input if we're not playing
|
|
if ( !inGame )
|
|
{
|
|
mouseState = InputContext.InputState.UI;
|
|
buttonState = InputContext.InputState.UI;
|
|
}
|
|
|
|
if ( Application.IsEditor && !Game.IsPlaying )
|
|
{
|
|
mouseState = InputContext.InputState.Ignore;
|
|
buttonState = InputContext.InputState.Ignore;
|
|
}
|
|
}
|
|
|
|
Game.InputContext.UpdateInputFromUI( mouseState, Input.Hovered, Panel.MouseCapture is not null, buttonState, CurrentFocus );
|
|
}
|
|
|
|
bool DoAnyPanelsWantMouseVisible()
|
|
{
|
|
if ( Mouse.Visibility == MouseVisibility.Visible ) return true;
|
|
if ( Mouse.Visibility == MouseVisibility.Hidden ) return false;
|
|
|
|
for ( int i = 0; i < RootPanels.Count; i++ )
|
|
{
|
|
if ( !RootPanels[i].IsValid )
|
|
continue;
|
|
|
|
if ( !RootPanels[i].IsVisible )
|
|
continue;
|
|
|
|
if ( RootPanels[i].IsWorldPanel )
|
|
continue;
|
|
|
|
if ( !RootPanels[i].ChildrenWantMouseInput )
|
|
continue;
|
|
|
|
if ( Game.IsMenu && RootPanels[i].RenderedManually && !Game.IsMainMenuVisible ) continue;
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// This panel should get deleted at some point
|
|
/// </summary>
|
|
internal void AddDeferredDeletion( Panel panel )
|
|
{
|
|
Assert.NotNull( panel );
|
|
|
|
DeletionList.Add( panel );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Delete all panels that were deferred and are no longer playing outro transitions
|
|
/// </summary>
|
|
internal void RunDeferredDeletion( bool force = false )
|
|
{
|
|
for ( int i = 0; i < DeletionList.Count; i++ )
|
|
{
|
|
var p = DeletionList[i];
|
|
if ( !force && p.HasActiveTransitions ) continue;
|
|
|
|
p.Delete( true );
|
|
DeletionList.RemoveAt( i );
|
|
i--;
|
|
}
|
|
}
|
|
|
|
internal void OnHotload()
|
|
{
|
|
for ( int i = 0; i < RootPanels.Count(); i++ )
|
|
{
|
|
if ( !RootPanels[i].IsValid ) continue;
|
|
RootPanels[i].OnHotloaded();
|
|
}
|
|
}
|
|
|
|
internal void Clear()
|
|
{
|
|
foreach ( var rp in RootPanels.ToArray() )
|
|
{
|
|
try
|
|
{
|
|
rp.Delete();
|
|
}
|
|
catch ( System.Exception e )
|
|
{
|
|
Log.Warning( e );
|
|
}
|
|
|
|
rp.RemoveFromLists();
|
|
}
|
|
|
|
RootPanels.Clear();
|
|
}
|
|
}
|