Files
sbox-public/game/addons/tools/Code/Widgets/StickyPopup.cs
s&box team 71f266059a Open source release
This commit imports the C# engine code and game files, excluding C++ source code.

[Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
2025-11-24 09:05:18 +00:00

226 lines
4.9 KiB
C#

namespace Editor;
public partial class StickyPopup : Widget
{
public static readonly HashSet<StickyPopup> All = new();
public Widget Owner { get; init; }
public StickyPopup( Widget parent = null ) : base( parent )
{
MouseTracking = true;
WindowFlags = WindowFlags.Tool | WindowFlags.FramelessWindowHint | WindowFlags.WindowStaysOnTopHint;
TranslucentBackground = true;
SetSizeMode( SizeMode.CanShrink, SizeMode.CanShrink );
Layout = Layout.Column();
Layout.Margin = 1;
All.Add( this );
}
/// <summary>
/// Try to align the popup with a widget
/// </summary>
/// <param name="widget"></param>
public void AlignTo( Widget widget )
{
var screenRect = EditorWindow.ScreenRect;
var targetRect = widget.ScreenRect;
var posBelowRight = targetRect.BottomRight - new Vector2( Width, 0 );
var bottomEdge = posBelowRight.y + Height;
if ( bottomEdge > screenRect.Bottom )
{
Position = targetRect.TopRight - new Vector2( Width, Height );
}
else
{
Position = posBelowRight;
}
}
public override void OnDestroyed()
{
All.Remove( this );
base.OnDestroyed();
}
/// <summary>
/// Create a popup editor for the given serialized object.
/// If the type has a <see cref="InspectorWidget"/> it'll use that instead of a control sheet.
/// </summary>
/// <param name="so"></param>
public void CreateProperties( SerializedObject so )
{
if ( !so.IsValid() ) return;
var scrollArea = new ScrollArea( this );
scrollArea.Canvas = new Widget( this );
scrollArea.Canvas.SetSizeMode( SizeMode.CanShrink, SizeMode.CanShrink );
scrollArea.TranslucentBackground = true;
scrollArea.NoSystemBackground = true;
scrollArea.SetStyles( "QScrollArea { border: 0px solid red; }" );
scrollArea.Canvas.Layout = Layout.Column();
scrollArea.Canvas.Layout.Margin = 8;
scrollArea.Canvas.Name = "StickyPopupCanvas";
//
// Check for a custom inspector
// It'll take priority over the control sheet
//
var inspector = InspectorWidget.Create( so );
if ( inspector.IsValid() )
{
scrollArea.Canvas.Layout.Add( inspector, 1 );
}
else
{
scrollArea.Canvas.Layout.Add( ControlSheet.Create( so ) );
}
scrollArea.Canvas.Layout.AddStretchCell();
Layout.Add( scrollArea );
}
protected override void DoLayout()
{
AdjustSize();
AlignTo( Owner );
}
protected override void OnPaint()
{
Paint.SetBrushAndPen( Theme.WindowBackground.WithAlpha( 0.9f ) );
Paint.DrawRect( LocalRect );
}
protected override Vector2 SizeHint()
{
var baseHint = base.SizeHint();
return new Vector2( 386f, baseHint.y );
}
protected override void OnKeyPress( KeyEvent e )
{
if ( e.Key == KeyCode.Escape )
{
Destroy();
e.Accepted = true;
return;
}
base.OnKeyPress( e );
}
/// <summary>
/// Destroys popups that are not related (not in the same popup chain/tree).
/// Related means: same root popup, or ancestor/descendant at any depth.
/// </summary>
public void DestroyUnrelatedPopups()
{
var myRoot = GetRootPopup( this );
foreach ( var popup in StickyPopup.All.ToList() )
{
if ( !popup.IsValid() ) continue;
if ( ReferenceEquals( popup, this ) ) continue;
var theirRoot = GetRootPopup( popup );
if ( ReferenceEquals( myRoot, theirRoot ) || IsAncestor( this, popup ) || IsAncestor( popup, this ) )
{
continue;
}
popup.Destroy();
}
}
private static StickyPopup FindParentPopup( Widget start )
{
var current = start;
while ( current != null )
{
if ( current is StickyPopup popup )
return popup;
current = current.Parent;
}
return null;
}
private static StickyPopup GetRootPopup( StickyPopup popup )
{
var root = popup;
var current = popup;
var parentPopup = FindParentPopup( current.Owner );
while ( parentPopup != null )
{
root = parentPopup;
current = parentPopup;
parentPopup = FindParentPopup( current.Owner );
}
return root;
}
private static bool IsAncestor( StickyPopup maybeAncestor, StickyPopup node )
{
var curWidget = node.Owner;
while ( curWidget != null )
{
var popup = curWidget as StickyPopup ?? FindParentPopup( curWidget );
if ( popup == null ) break;
if ( ReferenceEquals( popup, maybeAncestor ) )
return true;
curWidget = popup.Owner;
}
return false;
}
private Vector2 _lastPos;
private Vector2 _lastSize;
private bool _initialized;
/// <summary>
/// This is bullshit, but I can't figure out a better way right now.
/// Subwindow doesn't work because it doesn't capture input.
/// </summary>
[EditorEvent.Frame]
void OnFrame()
{
// If the application isn't in focus, destroy the popup
if ( !Application.FocusWidget.IsValid() )
{
Destroy();
return;
}
var window = EditorWindow;
if ( window == null ) return;
if ( !_initialized )
{
_lastPos = window.Position;
_lastSize = window.Size;
_initialized = true;
return;
}
if ( window.Position != _lastPos || window.Size != _lastSize )
{
Destroy();
return;
}
_lastPos = window.Position;
_lastSize = window.Size;
}
}