using Microsoft.AspNetCore.Components.Rendering;
using Sandbox.UI;
namespace Sandbox;
[Category( "UI Panels" )]
[Icon( "widgets" )]
public abstract partial class PanelComponent : Component, IPanelComponent
{
Panel panel;
///
/// The panel. Can be null if the panel doesn't exist yet.
///
public Panel Panel => panel;
string loadedStyleSheet;
internal override void OnEnabledInternal()
{
EnsurePanelCreated();
base.OnEnabledInternal();
}
internal override void OnDisabledInternal()
{
base.OnDisabledInternal();
DestroyPanel();
}
private void EnsurePanelCreated()
{
loadedStyleSheet = null;
panel = new CustomBuildPanel( this, BuildRenderTree, OnTreeRenderedInternal, GetRenderTreeChecksum, BuildRenderHash );
panel.ElementName = GetType().Name.ToLower();
LoadStyleSheet();
UpdateParent();
var type = Game.TypeLibrary?.GetType( GetType() );
if ( type is not null )
{
panel.SourceFile = type.SourceFile;
panel.SourceLine = type.SourceLine;
}
}
internal void EnsureParentPanel()
{
if ( panel is not null && panel.Parent is null )
{
if ( panel.IsValid() )
{
UpdateParent();
}
else
{
// Make sure panel is created, it may have been deleted on parent disable.
EnsurePanelCreated();
}
}
}
private void DestroyPanel()
{
if ( !panel.IsValid() )
return;
panel.Parent = null;
panel.Delete();
panel = null;
}
protected override void OnStart()
{
UpdateParent();
}
protected override void OnParentChanged( GameObject oldParent, GameObject newParent )
{
UpdateParent();
}
void UpdateParent()
{
if ( !panel.IsValid() ) return;
panel.Parent = FindParentPanel();
}
Panel FindParentPanel()
{
// do we have any root panels with us?
if ( Components.Get() is IRootPanelComponent r )
{
return r.GetPanel();
}
// Do we have any parent panels we can become a child of?
var parentPanel = GameObject.Components.Get( FindMode.InAncestors | FindMode.Enabled );
return parentPanel?.GetPanel();
}
Panel IPanelComponent.GetPanel()
{
return panel;
}
///
/// Gets overridden by .razor file
///
protected virtual void BuildRenderTree( RenderTreeBuilder v ) { }
///
/// Gets overridden by .razor file
///
protected virtual string GetRenderTreeChecksum() => string.Empty;
private int BuildRenderHash()
{
if ( !panel.IsValid() ) return 0;
return HashCode.Combine( BuildHash(), panel.Parent );
}
///
/// Called when the razor ui has been built.
///
protected virtual void OnTreeFirstBuilt()
{
}
///
/// Called after the tree has been built. This can happen any time the contents change.
///
protected virtual void OnTreeBuilt()
{
}
private void OnTreeRenderedInternal( bool firstTime )
{
if ( firstTime )
{
OnTreeFirstBuilt();
}
OnTreeBuilt();
}
///
/// When this has changes, we will re-render this panel. This is usually
/// implemented as a HashCode.Combine containing stuff that causes the
/// panel's content to change.
///
protected virtual int BuildHash() => 0;
void LoadStyleSheet()
{
var type = Game.TypeLibrary?.GetType( GetType() );
if ( type is null )
return;
// Get the shortest class file (incase we have MyPanel.SomeStuff.Blah)
var location = type.GetAttributes()
.MinBy( x => x.Path.Length );
if ( location is null )
return;
var path = BaseFileSystem.NormalizeFilename( location.Path + ".scss" );
// Nothing to do
if ( loadedStyleSheet == path ) return;
// Remove old sheet
if ( !string.IsNullOrWhiteSpace( loadedStyleSheet ) )
panel.StyleSheet.Remove( loadedStyleSheet );
// Add new one
loadedStyleSheet = path;
panel.StyleSheet.Load( loadedStyleSheet );
}
///
/// Should be called when you want the component to be re-rendered.
///
public void StateHasChanged()
{
panel?.StateHasChanged();
}
///
internal protected virtual void OnMouseDown( MousePanelEvent e ) { }
///
internal protected virtual void OnMouseMove( MousePanelEvent e ) { }
///
internal protected virtual void OnMouseUp( MousePanelEvent e ) { }
///
internal protected virtual void OnMouseOut( MousePanelEvent e ) { }
///
internal protected virtual void OnMouseOver( MousePanelEvent e ) { }
///
internal protected virtual void OnMouseWheel( Vector2 value ) { }
}
///
/// A panel where we control the tree build.
///
file class CustomBuildPanel : Panel
{
PanelComponent component;
Action treeBuilder;
Action treeRendered;
Func treeChecksum;
Func buildHash;
public CustomBuildPanel( PanelComponent component, Action treeBuilder, Action treeRendered, Func treeChecksum, Func buildHash )
{
this.component = component;
this.treeBuilder = treeBuilder;
this.treeRendered = treeRendered;
this.treeChecksum = treeChecksum;
this.buildHash = buildHash;
}
protected override void BuildRenderTree( RenderTreeBuilder v ) => treeBuilder?.Invoke( v );
protected override void OnAfterTreeRender( bool firstTime ) => treeRendered?.Invoke( firstTime );
protected override string GetRenderTreeChecksum() => treeChecksum?.Invoke() ?? "";
protected override int BuildHash() => buildHash?.Invoke() ?? 0;
protected override void OnMouseDown( MousePanelEvent e ) => component.OnMouseDown( e );
protected override void OnMouseMove( MousePanelEvent e ) => component.OnMouseMove( e );
protected override void OnMouseUp( MousePanelEvent e ) => component.OnMouseUp( e );
protected override void OnMouseOut( MousePanelEvent e ) => component.OnMouseOut( e );
protected override void OnMouseOver( MousePanelEvent e ) => component.OnMouseOver( e );
public override void OnMouseWheel( Vector2 value ) => component.OnMouseWheel( value );
}