mirror of
https://github.com/Facepunch/sbox-public.git
synced 2026-01-12 16:28:27 -05:00
This commit imports the C# engine code and game files, excluding C++ source code. [Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
320 lines
7.1 KiB
C#
320 lines
7.1 KiB
C#
namespace Sandbox.UI;
|
|
|
|
public sealed class PanelStyle : Styles
|
|
{
|
|
Panel panel;
|
|
|
|
internal Styles Cached = new Styles();
|
|
internal Styles Final = new Styles();
|
|
|
|
/// <summary>
|
|
/// This could be a local variable if we wanted to create a new class every time
|
|
/// </summary>
|
|
List<StyleSelector> activeRules;
|
|
|
|
/// <summary>
|
|
/// Store the last active rules so we can compare them when they change and trigger sounds etc on new styles
|
|
/// </summary>
|
|
internal List<StyleSelector> LastActiveRules;
|
|
|
|
/// <summary>
|
|
/// Cache of the active rules that are applied, that way we can trigger stuff only if they actually changed
|
|
/// </summary>
|
|
int ActiveRulesGuid;
|
|
|
|
private bool isDirty = true;
|
|
internal bool skipTransitions = true;
|
|
|
|
bool rulesChanged = true;
|
|
|
|
public override void Dirty() => isDirty = true;
|
|
internal bool IsDirty => isDirty;
|
|
|
|
internal PanelStyle( Panel panel )
|
|
{
|
|
this.panel = panel;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Should be called when a stylesheet in our bundle has changed. This can happen as a result of
|
|
/// editing it in the style editor.
|
|
/// </summary>
|
|
internal void UnderlyingStyleHasChanged()
|
|
{
|
|
ActiveRulesGuid = -1;
|
|
}
|
|
|
|
/// <summary>
|
|
/// All these styles could possibly apply to us. To get this list we get the stylesheets from
|
|
/// ourself and our anscestors and then filter them by the broadphase. The broadphase is a check
|
|
/// against classes, element names and ids, things that don't change in a recursive way.
|
|
/// </summary>
|
|
StyleBlock[] StyleBlocks;
|
|
|
|
/// <summary>
|
|
/// A hash of the things that are checked in the broadphase.
|
|
/// </summary>
|
|
int broadPhaseHash = 0;
|
|
|
|
bool _hasBeforeElement;
|
|
bool _hasAfterElement;
|
|
|
|
/// <summary>
|
|
/// This style has a ::before element available. This is signalling to the panel system that if we
|
|
/// apply this style, we should also create a ::before element.
|
|
/// </summary>
|
|
public bool HasBeforeElement => _hasBeforeElement;
|
|
|
|
/// <summary>
|
|
/// This style has a ::after element available. This is signalling to the panel system that if we
|
|
/// apply this style, we should also create a ::after element.
|
|
/// </summary>
|
|
public bool HasAfterElement => _hasAfterElement;
|
|
|
|
/// <summary>
|
|
/// Called when a stylesheet has been added or removed from ourselves or one of
|
|
/// our ancestor panels - because under that condition we need to rebuild our
|
|
/// broadphase.
|
|
/// </summary>
|
|
internal void InvalidateBroadphase()
|
|
{
|
|
if ( StyleBlocks == null )
|
|
return;
|
|
|
|
StyleBlocks = null;
|
|
|
|
foreach ( var child in panel.Children )
|
|
{
|
|
child.Style.InvalidateBroadphase();
|
|
}
|
|
}
|
|
|
|
void BuildApplicableBlocks()
|
|
{
|
|
StyleBlocks = panel.AllStyleSheets
|
|
.SelectMany( x => x.Nodes )
|
|
.Where( x => x.TestBroadphase( panel ) )
|
|
.ToArray();
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Called from the root panel in a thread. We replace activeRules with all of the rules that
|
|
/// we want applied and return true if the rules changed.
|
|
/// </summary>
|
|
internal bool BuildRulesInThread()
|
|
{
|
|
activeRules?.Clear();
|
|
|
|
var hash = HashCode.Combine( panel.Id, panel.ElementName, panel.Classes );
|
|
if ( StyleBlocks == null || hash != broadPhaseHash )
|
|
{
|
|
BuildApplicableBlocks();
|
|
}
|
|
broadPhaseHash = hash;
|
|
|
|
_hasBeforeElement = false;
|
|
_hasAfterElement = false;
|
|
|
|
bool isBeforeOrAfter = (panel as IStyleTarget).IsBeforeOrAfter;
|
|
|
|
foreach ( var c in StyleBlocks )
|
|
{
|
|
//
|
|
// If we're not a ::before or ::after element, see if we have any styles with ::before or ::after elements.
|
|
//
|
|
if ( !isBeforeOrAfter )
|
|
{
|
|
_hasBeforeElement = _hasBeforeElement || c.Test( panel, PseudoClass.Before ) != null;
|
|
_hasAfterElement = _hasAfterElement || c.Test( panel, PseudoClass.After ) != null;
|
|
}
|
|
|
|
var winningSelector = c.Test( panel );
|
|
if ( winningSelector == null ) continue;
|
|
|
|
activeRules ??= new();
|
|
activeRules.Add( winningSelector );
|
|
}
|
|
|
|
int ruleguid = 0;
|
|
if ( activeRules != null )
|
|
{
|
|
activeRules.Sort( StyleOrderer.Instance );
|
|
|
|
foreach ( var entry in activeRules )
|
|
{
|
|
ruleguid = HashCode.Combine( ruleguid, entry );
|
|
}
|
|
}
|
|
|
|
rulesChanged = rulesChanged || ruleguid != ActiveRulesGuid;
|
|
ActiveRulesGuid = ruleguid;
|
|
|
|
return rulesChanged;
|
|
}
|
|
|
|
internal bool BuildCached( ref LayoutCascade cascade )
|
|
{
|
|
if ( !isDirty && !cascade.SelectorChanged && !rulesChanged )
|
|
return false;
|
|
|
|
isDirty = false;
|
|
|
|
Cached.From( Styles.Default );
|
|
|
|
if ( activeRules != null )
|
|
{
|
|
foreach ( var entry in activeRules )
|
|
{
|
|
Cached.Add( entry.Block.Styles );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Rules changed
|
|
//
|
|
if ( rulesChanged )
|
|
{
|
|
rulesChanged = false;
|
|
cascade.SelectorChanged = true;
|
|
|
|
LastActiveRules ??= new();
|
|
activeRules ??= new();
|
|
|
|
foreach ( var rule in activeRules.Except( LastActiveRules ) )
|
|
{
|
|
OnRuleAdded( rule );
|
|
}
|
|
|
|
foreach ( var rule in LastActiveRules.Except( activeRules ) )
|
|
{
|
|
OnRuleRemoved( rule );
|
|
}
|
|
|
|
LastActiveRules.Clear();
|
|
LastActiveRules.AddRange( activeRules );
|
|
}
|
|
|
|
Cached.Add( this );
|
|
|
|
Cached.ApplyScale( cascade.Scale );
|
|
return true;
|
|
}
|
|
|
|
internal Styles BuildFinal( ref LayoutCascade cascade, out bool changed )
|
|
{
|
|
var time = panel.TimeNow;
|
|
var timeDelta = panel.TimeDelta;
|
|
|
|
cascade.SkipTransitions = skipTransitions || cascade.SkipTransitions;
|
|
changed = BuildCached( ref cascade );
|
|
|
|
if ( !cascade.SkipTransitions && !HasTransitions && !HasAnimation )
|
|
{
|
|
Final.CopyShadows( Cached );
|
|
}
|
|
|
|
if ( cascade.SkipTransitions )
|
|
{
|
|
Final.CopyShadows( Cached );
|
|
panel.Transitions.Kill();
|
|
skipTransitions = false;
|
|
}
|
|
else if ( changed && Final != null )
|
|
{
|
|
panel.Transitions.Kill( Final );
|
|
panel.Transitions.Add( Final, Cached, time - timeDelta );
|
|
}
|
|
|
|
Final.From( Cached );
|
|
cascade.ApplyCascading( Final );
|
|
Final.FillDefaults();
|
|
|
|
if ( panel.Transitions.Run( Final, time ) )
|
|
{
|
|
changed = true;
|
|
}
|
|
|
|
if ( Final.ApplyAnimation( panel ) )
|
|
{
|
|
changed = true;
|
|
}
|
|
|
|
return Final;
|
|
}
|
|
|
|
public override bool Set( string property, string value )
|
|
{
|
|
isDirty = true;
|
|
|
|
return base.Set( property, value );
|
|
}
|
|
|
|
internal void OnRuleAdded( StyleSelector selector )
|
|
{
|
|
if ( selector.Block.Styles.SoundIn != null )
|
|
{
|
|
panel.PlaySound( selector.Block.Styles.SoundIn );
|
|
}
|
|
}
|
|
|
|
internal void OnRuleRemoved( StyleSelector selector )
|
|
{
|
|
if ( selector.Block.Styles.SoundOut != null )
|
|
{
|
|
panel.PlaySound( selector.Block.Styles.SoundOut );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Helpers
|
|
//
|
|
|
|
|
|
public void SetBackgroundImage( Texture texture )
|
|
{
|
|
BackgroundImage = texture;
|
|
}
|
|
|
|
public void SetBackgroundImage( string image )
|
|
{
|
|
SetBackgroundImage( Texture.Load( image ) );
|
|
}
|
|
|
|
public async Task SetBackgroundImageAsync( string image )
|
|
{
|
|
SetBackgroundImage( await Texture.LoadAsync( image ) );
|
|
}
|
|
|
|
public void SetRect( Rect rect )
|
|
{
|
|
Left = rect.Left;
|
|
Top = rect.Top;
|
|
Width = rect.Width;
|
|
Height = rect.Height;
|
|
|
|
Dirty();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if we have the style
|
|
/// </summary>
|
|
internal bool ContainsStyle( Styles style )
|
|
{
|
|
if ( LastActiveRules == null ) return false;
|
|
|
|
return LastActiveRules.Any( x => x.Block.Styles == style );
|
|
}
|
|
}
|
|
|
|
internal class StyleOrderer : IComparer<StyleSelector>
|
|
{
|
|
internal static StyleOrderer Instance = new StyleOrderer();
|
|
|
|
public int Compare( StyleSelector x, StyleSelector y )
|
|
{
|
|
return x.Score - y.Score;
|
|
}
|
|
}
|