mirror of
https://github.com/Facepunch/sbox-public.git
synced 2025-12-23 22:48:07 -05:00
- Remove static LastSelected SceneViewportWidget. It should be scoped to the last selected viewport within a particular Scene view - Add LastSelectedViewportWidget to SceneViewWidget - Re-add focusing the last selected widget that was hotfixed in #114 --------- Co-authored-by: aidencurtis <109600275+aidencurtis@users.noreply.github.com> Co-authored-by: Carson Kompon <carsokompo@gmail.com>
489 lines
12 KiB
C#
489 lines
12 KiB
C#
|
|
namespace Editor;
|
|
|
|
public partial class SceneViewWidget : Widget
|
|
{
|
|
public enum ViewportLayoutMode
|
|
{
|
|
[Title( "One" ), Icon( "check_box_outline_blank" )]
|
|
One,
|
|
[Title( "Two (Horizontal)" ), Icon( "view_column" )]
|
|
TwoHorizontal,
|
|
[Title( "Two (Vertical)" ), Icon( "view_agenda" )]
|
|
TwoVertical,
|
|
[Title( "Three (Left)" ), Icon( "space_dashboard" )]
|
|
ThreeLeft,
|
|
[Title( "Three (Right)" ), Icon( "space_dashboard" )]
|
|
ThreeRight,
|
|
[Title( "Three (Top)" ), Icon( "space_dashboard" )]
|
|
ThreeTop,
|
|
[Title( "Three (Bottom)" ), Icon( "space_dashboard" )]
|
|
ThreeBottom,
|
|
[Title( "Four" ), Icon( "grid_view" )]
|
|
Four
|
|
}
|
|
|
|
private ViewportLayoutMode _viewportLayout;
|
|
|
|
public ViewportLayoutMode ViewportLayout
|
|
{
|
|
get => _viewportLayout;
|
|
set => SetLayout( value );
|
|
}
|
|
|
|
public static SceneViewWidget Current { get; private set; }
|
|
|
|
public SceneViewportWidget LastSelectedViewportWidget { get; set; }
|
|
|
|
public EditorToolManager Tools { get; private set; }
|
|
|
|
/// <summary>
|
|
/// The currently active session for this scene view. The game session if playing, otherwise the editor session.
|
|
/// </summary>
|
|
public SceneEditorSession Session => _editorSession.GameSession ?? _editorSession;
|
|
private SceneEditorSession _editorSession;
|
|
|
|
private List<LinkableSplitter> _splitters;
|
|
public Dictionary<int, SceneViewportWidget> _viewports;
|
|
|
|
ViewportTools _viewportTools;
|
|
|
|
public SceneViewWidget( SceneEditorSession session, Widget parent ) : base( parent )
|
|
{
|
|
_editorSession = session;
|
|
Tools = new EditorToolManager();
|
|
|
|
Layout = Layout.Column();
|
|
|
|
_splitters = new List<LinkableSplitter>();
|
|
_viewports = new();
|
|
|
|
FocusMode = FocusMode.None;
|
|
|
|
RestoreState();
|
|
}
|
|
|
|
public override void OnDestroyed()
|
|
{
|
|
SaveState();
|
|
base.OnDestroyed();
|
|
}
|
|
|
|
protected override void OnPaint()
|
|
{
|
|
Paint.ClearPen();
|
|
Paint.SetBrush( Theme.TabBackground );
|
|
Paint.DrawRect( LocalRect, Theme.ControlRadius );
|
|
}
|
|
|
|
static int selectionHash = 0;
|
|
|
|
[EditorEvent.Frame]
|
|
public void Frame()
|
|
{
|
|
var session = Session;
|
|
if ( session is null ) return;
|
|
if ( !session.Scene.IsValid() ) return;
|
|
|
|
using var scope = session.Scene.Push();
|
|
|
|
bool isActive = session == SceneEditorSession.Active;
|
|
|
|
// Update inspector with current selection, if changed
|
|
if ( isActive && selectionHash != session.Selection.GetHashCode() )
|
|
{
|
|
// todo - multiselect
|
|
EditorUtility.InspectorObject = session.Selection.ToArray();
|
|
selectionHash = session.Selection.GetHashCode();
|
|
}
|
|
|
|
// All this shit below is scene specific
|
|
if ( CurrentView != ViewMode.Game )
|
|
{
|
|
//
|
|
// Ticks the deferred undo buffer, periodically saves camera state
|
|
//
|
|
session.Tick();
|
|
|
|
bool shouldUpdate = Visible; // Update if the scene window is visible
|
|
|
|
if ( !shouldUpdate )
|
|
return;
|
|
|
|
if ( isActive )
|
|
{
|
|
Current = this;
|
|
}
|
|
|
|
session.Scene.EditorTick( RealTime.Now, RealTime.Delta );
|
|
}
|
|
|
|
if ( lastView != CurrentView )
|
|
{
|
|
_viewportTools.Update();
|
|
lastView = CurrentView;
|
|
}
|
|
}
|
|
|
|
public bool TryGetViewport( int id, out SceneViewportWidget viewport )
|
|
{
|
|
return _viewports.TryGetValue( id, out viewport );
|
|
}
|
|
|
|
private SceneViewportWidget CreateViewport( int id = -1 )
|
|
{
|
|
SceneViewportWidget widget = new SceneViewportWidget( this, id == -1 ? _viewports.Count : id );
|
|
_viewports.Add( widget.Id, widget );
|
|
return widget;
|
|
}
|
|
|
|
public void SetLayout( ViewportLayoutMode Preset )
|
|
{
|
|
bool isRefresh = _viewportLayout == Preset;
|
|
_viewportLayout = Preset;
|
|
|
|
RebuildLayout();
|
|
if ( isRefresh )
|
|
{
|
|
RestoreSplitterState();
|
|
}
|
|
|
|
SaveState();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Side panel widget to hide when in game view.
|
|
/// </summary>
|
|
Widget _sidePanel;
|
|
|
|
public void RebuildLayout()
|
|
{
|
|
Layout.Clear( true );
|
|
|
|
_viewportTools = Layout.Add( new ViewportTools( this ) );
|
|
|
|
var sideLayout = Layout.AddRow( 1 );
|
|
_sidePanel = sideLayout.Add( new ViewportToolBar( this ) );
|
|
|
|
var viewportLayout = sideLayout.AddRow( 1 );
|
|
|
|
_splitters.Clear();
|
|
_viewports.Clear();
|
|
|
|
switch ( ViewportLayout )
|
|
{
|
|
case ViewportLayoutMode.TwoHorizontal:
|
|
{
|
|
var horizontal = new LinkableSplitter( Orientation.Horizontal, this );
|
|
horizontal.AddWidget( CreateViewport() );
|
|
horizontal.AddWidget( CreateViewport() );
|
|
_splitters.Add( horizontal );
|
|
viewportLayout.Add( horizontal );
|
|
break;
|
|
}
|
|
case ViewportLayoutMode.TwoVertical:
|
|
{
|
|
var vertical = new LinkableSplitter( Orientation.Vertical, this );
|
|
vertical.IsVertical = true;
|
|
vertical.AddWidget( CreateViewport() );
|
|
vertical.AddWidget( CreateViewport() );
|
|
_splitters.Add( vertical );
|
|
viewportLayout.Add( vertical );
|
|
break;
|
|
}
|
|
case ViewportLayoutMode.ThreeLeft:
|
|
{
|
|
var vertical = new LinkableSplitter( Orientation.Vertical, this );
|
|
vertical.IsVertical = true;
|
|
vertical.AddWidget( CreateViewport( 1 ) );
|
|
vertical.AddWidget( CreateViewport( 2 ) );
|
|
_splitters.Add( vertical );
|
|
|
|
var horizontal = new LinkableSplitter( Orientation.Horizontal, this );
|
|
horizontal.AddWidget( CreateViewport( 0 ) );
|
|
horizontal.AddWidget( vertical );
|
|
_splitters.Add( horizontal );
|
|
viewportLayout.Add( horizontal );
|
|
break;
|
|
}
|
|
case ViewportLayoutMode.ThreeRight:
|
|
{
|
|
var vertical = new LinkableSplitter( Orientation.Vertical, this );
|
|
vertical.IsVertical = true;
|
|
vertical.AddWidget( CreateViewport( 1 ) );
|
|
vertical.AddWidget( CreateViewport( 2 ) );
|
|
_splitters.Add( vertical );
|
|
|
|
var horizontal = new LinkableSplitter( Orientation.Horizontal, this );
|
|
horizontal.AddWidget( vertical );
|
|
horizontal.AddWidget( CreateViewport( 0 ) );
|
|
_splitters.Add( horizontal );
|
|
viewportLayout.Add( horizontal );
|
|
break;
|
|
}
|
|
case ViewportLayoutMode.ThreeTop:
|
|
{
|
|
var vertical = new LinkableSplitter( Orientation.Vertical, this );
|
|
vertical.IsVertical = true;
|
|
vertical.AddWidget( CreateViewport() );
|
|
_splitters.Add( vertical );
|
|
|
|
var horizontal = new LinkableSplitter( Orientation.Horizontal, this );
|
|
horizontal.AddWidget( CreateViewport() );
|
|
horizontal.AddWidget( CreateViewport() );
|
|
vertical.AddWidget( horizontal );
|
|
_splitters.Add( horizontal );
|
|
viewportLayout.Add( vertical );
|
|
break;
|
|
}
|
|
case ViewportLayoutMode.ThreeBottom:
|
|
{
|
|
var horizontal = new LinkableSplitter( Orientation.Horizontal, this );
|
|
horizontal.AddWidget( CreateViewport( 1 ) );
|
|
horizontal.AddWidget( CreateViewport( 2 ) );
|
|
_splitters.Add( horizontal );
|
|
|
|
var vertical = new LinkableSplitter( Orientation.Vertical, this );
|
|
vertical.IsVertical = true;
|
|
vertical.AddWidget( horizontal );
|
|
vertical.AddWidget( CreateViewport( 0 ) );
|
|
_splitters.Add( vertical );
|
|
viewportLayout.Add( vertical );
|
|
break;
|
|
}
|
|
case ViewportLayoutMode.Four:
|
|
{
|
|
var verticalA = new LinkableSplitter( Orientation.Vertical, this );
|
|
verticalA.AddWidget( CreateViewport() );
|
|
verticalA.AddWidget( CreateViewport() );
|
|
_splitters.Add( verticalA );
|
|
|
|
var verticalB = new LinkableSplitter( Orientation.Vertical, this );
|
|
verticalB.AddWidget( CreateViewport() );
|
|
verticalB.AddWidget( CreateViewport() );
|
|
_splitters.Add( verticalB );
|
|
|
|
var horizontal = new LinkableSplitter( Orientation.Horizontal, this );
|
|
horizontal.AddWidget( verticalA );
|
|
horizontal.AddWidget( verticalB );
|
|
_splitters.Add( horizontal );
|
|
viewportLayout.Add( horizontal );
|
|
|
|
verticalA.LinkWith( verticalB );
|
|
verticalB.LinkWith( verticalA );
|
|
|
|
break;
|
|
}
|
|
default:
|
|
case ViewportLayoutMode.One:
|
|
Layout.AddStretchCell();
|
|
viewportLayout.Add( CreateViewport() );
|
|
Layout.AddStretchCell();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
file class ViewportToolBar : Widget
|
|
{
|
|
Layout _sidebar;
|
|
Layout _toolbar;
|
|
Layout _footer;
|
|
|
|
public ViewportToolBar( Widget parent ) : base( parent )
|
|
{
|
|
Layout = Layout.Column();
|
|
Layout.Margin = 0;
|
|
Layout.Spacing = 0;
|
|
|
|
var top = Layout.AddRow();
|
|
var middle = Layout.AddRow();
|
|
_footer = Layout.AddRow();
|
|
|
|
_toolbar = middle.AddColumn();
|
|
_sidebar = middle.AddColumn();
|
|
}
|
|
|
|
void OnToolChanged()
|
|
{
|
|
// Prevent flicker when changing tools
|
|
using var x = SuspendUpdates.For( this );
|
|
|
|
var rootTool = SceneViewWidget.Current?.Tools.CurrentTool;
|
|
var subTool = SceneViewWidget.Current?.Tools.CurrentSubTool;
|
|
|
|
_toolbar?.Clear( true );
|
|
_sidebar?.Clear( true );
|
|
_footer?.Clear( true );
|
|
|
|
// Update toolbar contents
|
|
if ( rootTool.Tools?.Any() ?? false )
|
|
{
|
|
var toolbar = new EditorSubToolBarWidget( rootTool );
|
|
toolbar.Build();
|
|
_toolbar.Add( toolbar, 0 );
|
|
}
|
|
|
|
// Update sidebar
|
|
var toolWidget = subTool?.CreateToolSidebar() ?? rootTool?.CreateToolSidebar();
|
|
|
|
if ( toolWidget.IsValid() )
|
|
{
|
|
var scroller = new ScrollArea( null );
|
|
scroller.FixedWidth = 240;
|
|
toolWidget.FixedWidth = 240;
|
|
scroller.HorizontalSizeMode = SizeMode.Flexible;
|
|
scroller.VerticalSizeMode = SizeMode.Flexible;
|
|
scroller.HorizontalScrollbarMode = ScrollbarMode.Off;
|
|
scroller.Canvas = toolWidget;
|
|
_sidebar.Add( scroller );
|
|
|
|
toolWidget.Focus();
|
|
}
|
|
else
|
|
{
|
|
if ( SceneViewWidget.Current?.LastSelectedViewportWidget?.IsValid() ?? false )
|
|
{
|
|
SceneViewWidget.Current.LastSelectedViewportWidget.Focus();
|
|
}
|
|
}
|
|
|
|
// Update footer
|
|
var footerWidget = subTool?.CreateToolFooter() ?? rootTool?.CreateToolFooter();
|
|
if ( footerWidget.IsValid() )
|
|
{
|
|
_footer.Add( footerWidget );
|
|
}
|
|
|
|
if ( rootTool?.CreateShortcutsWidget() is { } rootShortcutWidget ) _footer.Add( rootShortcutWidget );
|
|
if ( subTool?.CreateShortcutsWidget() is { } subShortcutWidget ) _footer.Add( subShortcutWidget );
|
|
}
|
|
|
|
EditorTool _activeTool;
|
|
int _selectionHash;
|
|
|
|
[EditorEvent.Frame]
|
|
public void Frame()
|
|
{
|
|
var tool = SceneViewWidget.Current?.Tools.CurrentSubTool ?? SceneViewWidget.Current?.Tools.CurrentTool;
|
|
var selectionHash = tool?.Selection?.GetHashCode() ?? 0;
|
|
|
|
if ( _activeTool != tool || (selectionHash != _selectionHash && (tool?.RebuildSidebarOnSelectionChange ?? false)) )
|
|
{
|
|
_activeTool = tool;
|
|
_selectionHash = selectionHash;
|
|
OnToolChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
file class EditorSubToolBarWidget : VerticalToolbarGroup
|
|
{
|
|
readonly EditorTool _tool;
|
|
|
|
public EditorSubToolBarWidget( EditorTool tool ) : base( null, null, null )
|
|
{
|
|
_tool = tool;
|
|
FixedWidth = 32 + 4 + 4;
|
|
ContentMargins = 4;
|
|
}
|
|
|
|
public override void Build()
|
|
{
|
|
Layout.Clear( true );
|
|
|
|
if ( _tool == null || _tool.Tools is null || !_tool.Tools.Any() )
|
|
{
|
|
FixedWidth = 0;
|
|
return;
|
|
}
|
|
|
|
Title = DisplayInfo.For( _tool ).Name.ToUpperInvariant() + " MODE";
|
|
|
|
foreach ( var subtool in _tool.Tools.Select( x => EditorTypeLibrary.GetType( x.GetType() ) ) )
|
|
{
|
|
if ( subtool is null )
|
|
continue;
|
|
|
|
var toolButton = new EditorSubToolButton( subtool, _tool );
|
|
Layout.Add( toolButton );
|
|
}
|
|
|
|
Layout.AddStretchCell();
|
|
}
|
|
}
|
|
|
|
file class EditorSubToolButton : Widget
|
|
{
|
|
public TypeDescription Type { get; }
|
|
|
|
private readonly EditorTool _parent;
|
|
|
|
public EditorSubToolButton( TypeDescription type, EditorTool parent = null ) : base( null )
|
|
{
|
|
_parent = parent;
|
|
|
|
Type = type;
|
|
FixedSize = 32;
|
|
Cursor = CursorShape.Finger;
|
|
|
|
var title = Type.Title;
|
|
if ( Type.GetAttribute<AliasAttribute>() is AliasAttribute alias && !string.IsNullOrEmpty( alias.Value.FirstOrDefault() ) )
|
|
{
|
|
var keys = EditorShortcuts.GetKeys( alias.Value.FirstOrDefault() );
|
|
if ( !string.IsNullOrEmpty( keys ) )
|
|
{
|
|
title += $" ({keys.Trim().ToUpperInvariant()})";
|
|
}
|
|
}
|
|
ToolTip = $"<b>{title}</b><br>{Type.Description}";
|
|
}
|
|
|
|
public bool IsSelectedMode => _parent?.CurrentTool is not null && Type == EditorTypeLibrary.GetType( _parent?.CurrentTool.GetType() );
|
|
|
|
protected override void OnMousePress( MouseEvent e )
|
|
{
|
|
if ( e.LeftMouseButton )
|
|
{
|
|
Activate();
|
|
}
|
|
}
|
|
|
|
public void Activate()
|
|
{
|
|
if ( _parent is null || _parent.Tools is null )
|
|
return;
|
|
|
|
var tool = _parent.Tools.FirstOrDefault( x => x.GetType() == Type.TargetType );
|
|
_parent.CurrentTool = tool == _parent.CurrentTool ? null : tool;
|
|
}
|
|
|
|
protected override void OnPaint()
|
|
{
|
|
Paint.Antialiasing = true;
|
|
Paint.TextAntialiasing = true;
|
|
|
|
if ( IsSelectedMode )
|
|
{
|
|
Paint.ClearPen();
|
|
Paint.SetBrush( Theme.Blue );
|
|
|
|
Paint.DrawRect( LocalRect.Shrink( 1 ), 4 );
|
|
|
|
Paint.SetPen( Theme.TextButton );
|
|
}
|
|
else
|
|
{
|
|
Paint.ClearPen();
|
|
Paint.SetBrush( Theme.ControlBackground );
|
|
|
|
Paint.DrawRect( LocalRect.Shrink( 1 ), 4 );
|
|
|
|
Paint.ClearPen();
|
|
Paint.SetPen( Theme.TextLight );
|
|
}
|
|
|
|
Paint.DrawIcon( LocalRect, Type.Icon, HeaderBarStyle.IconSize, TextFlag.Center );
|
|
}
|
|
}
|