mirror of
https://github.com/Facepunch/sbox-public.git
synced 2026-01-15 01:39:39 -05:00
This commit imports the C# engine code and game files, excluding C++ source code. [Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
558 lines
15 KiB
C#
558 lines
15 KiB
C#
using NativeEngine;
|
|
using System.Diagnostics;
|
|
|
|
namespace Editor;
|
|
|
|
public class EditorMainWindow : DockWindow
|
|
{
|
|
internal static EditorMainWindow Current;
|
|
|
|
Menu FileMenu { get; init; }
|
|
public Menu AppsMenu { get; init; }
|
|
public Menu ViewsMenu { get; init; }
|
|
public Menu GameMenu { get; init; }
|
|
Menu RecentScenesMenu { get; init; }
|
|
Menu EditMenu { get; init; }
|
|
|
|
internal ConsoleWidget Console => ConsoleWidget.Instance;
|
|
|
|
protected override bool OnClose()
|
|
{
|
|
// Check the editor scenes for one with unsaved changes
|
|
if ( GetUnsavedScenes().Any() || GetUnsavedResources().Any() )
|
|
{
|
|
ShowCloseDialog();
|
|
return false;
|
|
}
|
|
|
|
ProjectCookie?.Set( $"gizmo.settings", EditorScene.GizmoSettings );
|
|
|
|
return true;
|
|
}
|
|
|
|
private static List<SceneEditorSession> GetUnsavedScenes()
|
|
{
|
|
return SceneEditorSession.All.Where( x => x.HasUnsavedChanges ).ToList();
|
|
}
|
|
|
|
private static List<GameResource> GetUnsavedResources()
|
|
{
|
|
return ResourceLibrary.GetAll<GameResource>().Where( x =>
|
|
{
|
|
var asset = AssetSystem.FindByPath( x.ResourcePath );
|
|
var compiledFile = asset?.GetCompiledFile( true );
|
|
var isCloud = compiledFile != null && compiledFile.Contains( ".sbox/cloud/" );
|
|
return x.HasUnsavedChanges && !isCloud;
|
|
}
|
|
).ToList();
|
|
}
|
|
|
|
public void ShowCloseDialog()
|
|
{
|
|
var popup = new PopupDialogWidget( "💾" );
|
|
var saveableScenes = GetUnsavedScenes();
|
|
var saveableResources = GetUnsavedResources();
|
|
popup.FixedWidth = 512;
|
|
popup.WindowTitle = "Unsaved Changes";
|
|
popup.MessageLabel.Text = $"Do you want to save the changes you made?";
|
|
if ( saveableScenes.Any() )
|
|
{
|
|
popup.MessageLabel.Text += $"\n\nScenes:\n\n{string.Join( "\n", saveableScenes.Select( x => x.Scene.Name ) )}";
|
|
}
|
|
if ( saveableResources.Any() )
|
|
{
|
|
popup.MessageLabel.Text += $"\n\nResources:\n\n{string.Join( "\n", saveableResources.Select( x => x.ResourceName ) )}";
|
|
}
|
|
|
|
popup.ButtonLayout.Spacing = 4;
|
|
popup.ButtonLayout.AddStretchCell();
|
|
popup.ButtonLayout.Add( new Button( "Save" )
|
|
{
|
|
Clicked = () =>
|
|
{
|
|
// Save all the scenes
|
|
saveableScenes.ForEach( x => x.Save( false ) );
|
|
saveableResources.ForEach( x =>
|
|
{
|
|
var asset = AssetSystem.FindByPath( x.ResourcePath );
|
|
asset.SaveToDisk( x );
|
|
asset.Compile( false );
|
|
} );
|
|
|
|
popup.Destroy();
|
|
Destroy();
|
|
|
|
foreach ( var session in saveableScenes )
|
|
{
|
|
session.Destroy();
|
|
}
|
|
}
|
|
} );
|
|
|
|
popup.ButtonLayout.Add( new Button( "Don't Save" ) { Clicked = () => { Destroy(); popup.Destroy(); } } );
|
|
popup.ButtonLayout.Add( new Button( "Cancel" ) { Clicked = () => { EditorMainWindow.showLauncherOnExit = false; popup.Destroy(); } } );
|
|
|
|
popup.SetModal( true, true );
|
|
popup.Hide();
|
|
popup.Show();
|
|
}
|
|
|
|
static bool isEngineLoggingVerbose;
|
|
|
|
private Option save;
|
|
private Option saveAs;
|
|
private Option discard;
|
|
private Option undoOption;
|
|
private Option redoOption;
|
|
|
|
internal EditorMainWindow()
|
|
{
|
|
Current = this;
|
|
Visible = false;
|
|
Enabled = false;
|
|
WindowTitle = "s&box editor";
|
|
DeleteOnClose = true;
|
|
FullScreenManager = new();
|
|
DockManager.OnLayoutLoaded += OnDockLayoutLoaded;
|
|
|
|
{
|
|
FileMenu = MenuBar.AddMenu( "File" );
|
|
FileMenu.AddOption( "New Scene", "note_add", EditorScene.NewScene, "editor.new" );
|
|
FileMenu.AddOption( "Open", "file_open", EditorScene.Open, "editor.open" );
|
|
RecentScenesMenu = FileMenu.AddMenu( "Open Recent", "restore" );
|
|
RecentScenesMenu.AboutToShow += BuildRecentScenes;
|
|
FileMenu.AddSeparator();
|
|
save = FileMenu.AddOption( "Save", "save", EditorScene.SaveSession, "editor.save" );
|
|
saveAs = FileMenu.AddOption( "Save As..", "save_as", EditorScene.SaveSessionAs, "editor.save-as" );
|
|
FileMenu.AddOption( "Save All", null, EditorScene.SaveAllSessions, "editor.save-all" );
|
|
discard = FileMenu.AddOption( "Discard Changes", "auto_delete", EditorScene.Discard );
|
|
FileMenu.AddSeparator();
|
|
FileMenu.AddOption( "Close Project", "disabled_by_default", () => { showLauncherOnExit = true; Quit(); } );
|
|
FileMenu.AddOption( "Quit", "logout", Quit, "editor.quit" );
|
|
FileMenu.AboutToShow += OnFileMenuAboutToShow;
|
|
}
|
|
|
|
{
|
|
EditMenu = MenuBar.AddMenu( "Edit" );
|
|
|
|
undoOption = EditMenu.AddOption( "Undo", "undo", Undo, "editor.undo" );
|
|
redoOption = EditMenu.AddOption( "Redo", "redo", Redo, "editor.redo" );
|
|
|
|
EditMenu.AddSeparator();
|
|
EditMenu.AddOption( "Cut", "cut", EditorScene.Cut, "editor.cut" );
|
|
EditMenu.AddOption( "Copy", "copy", EditorScene.Copy, "editor.copy" );
|
|
EditMenu.AddOption( "Paste", "paste", EditorScene.Paste, "editor.paste" );
|
|
EditMenu.AddOption( "Paste As Child", null, EditorScene.PasteAsChild, "editor.paste-as-child" );
|
|
EditMenu.AboutToShow += OnEditMenuAboutToShow;
|
|
}
|
|
|
|
{
|
|
ViewsMenu = MenuBar.AddMenu( "View" );
|
|
ViewsMenu.AboutToShow += OnViewsMenuAboutToShow;
|
|
}
|
|
|
|
{
|
|
var projectMenu = MenuBar.AddMenu( "Project" );
|
|
projectMenu.AddOption( "Play", "play_arrow", EditorScene.TogglePlay, "editor.toggle-play" );
|
|
|
|
projectMenu.AddOption( new Option()
|
|
{
|
|
Checkable = true,
|
|
Checked = EditorScene.PlayMode,
|
|
Toggled = ( b ) => EditorScene.PlayMode = b,
|
|
Text = "Play in Game Mode",
|
|
Icon = "sports_esports"
|
|
} );
|
|
|
|
projectMenu.AddSeparator();
|
|
projectMenu.AddOption( "Open Project Folder", "folder", () => EditorUtility.OpenFolder( Project.Current.GetRootPath() ) );
|
|
projectMenu.AddOption( "Open Solution", "integration_instructions", OpenSolution, "editor.open-solution" );
|
|
}
|
|
|
|
{
|
|
MenuBar.AddMenu( "Scene" );
|
|
AppsMenu = MenuBar.AddMenu( "Tools" );
|
|
}
|
|
|
|
{
|
|
MenuBar.AddMenu( "Settings" );
|
|
var debug = MenuBar.AddMenu( "Debug" );
|
|
|
|
debug.AddOption( "Widget Debugger", null, () => g_pBindSystemGlobalHotkeys.Cmd_ShowWidgetDebugger() );
|
|
debug.AddOption( "Input Debugger", null, () => g_pBindSystemGlobalHotkeys.Cmd_ShowInputDebugger() );
|
|
|
|
debug.AddSeparator();
|
|
|
|
var help = MenuBar.AddMenu( "Help" );
|
|
|
|
help.AddOption( "Open Log Folder", "source", () => EditorUtility.OpenFolder( FileSystem.Root.GetFullPath( "/logs/" ) ) );
|
|
help.AddOption( "Developer Documentation", "article", () => EditorUtility.OpenFolder( "https://sbox.game/dev/" ) );
|
|
help.AddOption( "Report a Bug", "bug_report", () => EditorUtility.OpenFolder( "https://github.com/Facepunch/sbox-issues" ) );
|
|
|
|
help.AddSeparator();
|
|
help.AddOption( "About s&box editor", "info", () =>
|
|
{
|
|
var aboutWidget = new AboutWidget();
|
|
aboutWidget.SetModal( true, true );
|
|
aboutWidget.Show();
|
|
} );
|
|
|
|
help.AddSeparator();
|
|
|
|
var di = DisplayInfo.ForEnumValues<LogLevel>();
|
|
var rootRule = Logging.GetDefaultLevel();
|
|
|
|
{
|
|
var o = help.AddOption( "Trace Logging" );
|
|
o.Checkable = true;
|
|
o.FetchCheckedState = () => Logging.GetDefaultLevel() == LogLevel.Trace;
|
|
o.Toggled = ( b ) =>
|
|
{
|
|
Logging.SetRule( "*", b ? LogLevel.Trace : LogLevel.Info );
|
|
EditorCookie.Set( "DefaultLoggingLevel", b ? LogLevel.Trace : LogLevel.Info );
|
|
};
|
|
}
|
|
|
|
{
|
|
var o = help.AddOption( "Verbose Engine Logging" );
|
|
o.Checkable = true;
|
|
o.FetchCheckedState = () => isEngineLoggingVerbose;
|
|
o.Toggled = ( b ) =>
|
|
{
|
|
EngineGlue.SetEngineLoggingVerbose( b );
|
|
isEngineLoggingVerbose = b;
|
|
};
|
|
}
|
|
|
|
{
|
|
var o = help.AddOption( "Verbose Hotload Logging" );
|
|
o.Checkable = true;
|
|
o.FetchCheckedState = () => HotloadManager.hotload_log == 2;
|
|
o.Toggled = ( b ) =>
|
|
{
|
|
HotloadManager.hotload_log = b ? 2 : 0;
|
|
};
|
|
}
|
|
}
|
|
|
|
EditorWindow = this;
|
|
}
|
|
|
|
[Shortcut( "editor.open-solution", "CTRL+P", ShortcutType.Window )]
|
|
void OpenSolution()
|
|
{
|
|
CodeEditor.OpenSolution();
|
|
}
|
|
|
|
[Shortcut( "editor.undo", "CTRL+Z", ShortcutType.Window )]
|
|
static void Undo()
|
|
{
|
|
using ( SceneEditorSession.Scope() )
|
|
{
|
|
if ( SceneEditorSession.Active.IsUndoScopeOpen )
|
|
{
|
|
if ( EditorPreferences.UndoSounds )
|
|
{
|
|
EditorUtility.PlayRawSound( "sounds/editor/fail.wav" );
|
|
}
|
|
|
|
return;
|
|
}
|
|
SceneEditorSession.Active.UndoSystem.Undo();
|
|
}
|
|
}
|
|
|
|
[Shortcut( "editor.redo", "CTRL+Y", ShortcutType.Window )]
|
|
static void Redo()
|
|
{
|
|
using ( SceneEditorSession.Scope() )
|
|
{
|
|
if ( SceneEditorSession.Active.IsUndoScopeOpen )
|
|
{
|
|
if ( EditorPreferences.UndoSounds )
|
|
{
|
|
EditorUtility.PlayRawSound( "sounds/editor/fail.wav" );
|
|
}
|
|
|
|
return;
|
|
}
|
|
SceneEditorSession.Active.UndoSystem.Redo();
|
|
}
|
|
}
|
|
|
|
[Shortcut( "editor.quit", "CTRL+Q", ShortcutType.Window )]
|
|
static void Quit()
|
|
{
|
|
Current?.Close();
|
|
}
|
|
|
|
[Shortcut( "editor.video", "F6", ShortcutType.Window )]
|
|
static void ToggleVideo()
|
|
{
|
|
if ( Game.IsPlaying ) return;
|
|
ConVarSystem.Run( "video" );
|
|
}
|
|
|
|
protected override void OnPaint()
|
|
{
|
|
if ( Game.IsPlaying )
|
|
{
|
|
Paint.ClearPen();
|
|
Paint.SetBrush( Theme.Overlay );
|
|
Paint.DrawRect( LocalRect );
|
|
return;
|
|
}
|
|
|
|
base.OnPaint();
|
|
}
|
|
|
|
internal void OnStartupLoadingFinished()
|
|
{
|
|
// Load gizmo settings
|
|
EditorScene.RestoreState();
|
|
|
|
// Register our menu bar and dock options, doesn't open anything
|
|
MenuAttribute.RegisterMenuBar( "Editor", MenuBar );
|
|
DockAttribute.RegisterWindow( "Editor", this );
|
|
|
|
// This will attempt to restore the last used layout (or default layout if first time)
|
|
// Which means it will create dock widgets and move them around
|
|
// This also involves creating SceneDocks which open scenes
|
|
StateCookie = "SboxSceneEditor";
|
|
|
|
// fucking horrible
|
|
string geometryCookie = EditorCookie.GetString( $"Window.{StateCookie}.Geometry", null );
|
|
if ( geometryCookie is null )
|
|
{
|
|
// no saved geometry, so default to center
|
|
Center();
|
|
}
|
|
|
|
EditorEvent.Run( "editor.created", this );
|
|
|
|
RebuildApps();
|
|
|
|
SetVisible( true );
|
|
|
|
// Register the main editor window as an SDL window and tell the input system it's the main window
|
|
// We need this for focusing and relative mouse capture mode
|
|
NativeEngine.InputSystem.RegisterWindowWithSDL( _widget.winId() );
|
|
NativeEngine.InputSystem.SetEditorMainWindow( _widget.winId() );
|
|
}
|
|
|
|
record struct LayoutFile( string Name, string Json );
|
|
|
|
protected override void RestoreDefaultDockLayout()
|
|
{
|
|
var layout = FileSystem.Config.ReadJsonOrDefault<LayoutFile>( $"/editor/layout/default.json", default );
|
|
if ( layout.Name is null ) return;
|
|
if ( layout.Json is null ) return;
|
|
|
|
DockManager.State = layout.Json;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called when the layout is loaded. We want to force all the scene views to be visible!
|
|
/// </summary>
|
|
void OnDockLayoutLoaded()
|
|
{
|
|
SceneEditorSession.OnEditorWindowRestoreLayout();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called when the console key is pressed while the game is focused. Should
|
|
/// do everything possible to switch to the actual console.
|
|
/// </summary>
|
|
internal void ConsoleFocus()
|
|
{
|
|
// focus the editor window instead of the game window
|
|
EditorWindow.Blur();
|
|
EditorWindow.Focus( true );
|
|
|
|
// focus the console input if it's visible. The console key
|
|
// is used to switch between game and editor too, so don't be
|
|
// heavy handed by forcing the console to be visible etc.
|
|
if ( Console?.Visible ?? false )
|
|
{
|
|
Console.Input.Focus();
|
|
}
|
|
}
|
|
|
|
internal static bool showLauncherOnExit = false;
|
|
public override void OnDestroyed()
|
|
{
|
|
// Unsubscribe from events
|
|
if ( DockManager != null ) DockManager.OnLayoutLoaded -= OnDockLayoutLoaded;
|
|
if ( RecentScenesMenu != null ) RecentScenesMenu.AboutToShow -= BuildRecentScenes;
|
|
if ( FileMenu != null ) FileMenu.AboutToShow -= OnFileMenuAboutToShow;
|
|
if ( ViewsMenu != null ) ViewsMenu.AboutToShow -= OnViewsMenuAboutToShow;
|
|
if ( EditMenu != null ) EditMenu.AboutToShow -= OnEditMenuAboutToShow;
|
|
|
|
base.OnDestroyed();
|
|
|
|
if ( Sandbox.Internal.GlobalToolsNamespace.EditorWindow != this )
|
|
return;
|
|
|
|
Sandbox.Internal.GlobalToolsNamespace.EditorWindow = null;
|
|
EditorUtility.Quit( showLauncherOnExit );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called once to create the editor
|
|
/// </summary>
|
|
internal void Startup()
|
|
{
|
|
Size = new Vector2( 1920, 1080 );
|
|
|
|
g_pToolFramework2.SetStallMonitorMainThreadWindow( _widget );
|
|
|
|
OnStartupLoadingFinished();
|
|
NativeEngine.EngineGlobal.UpdateWindowSize();
|
|
}
|
|
|
|
[Event( "refresh" )]
|
|
void RebuildApps()
|
|
{
|
|
AppsMenu?.Clear();
|
|
|
|
foreach ( var tool in EngineTools.All )
|
|
{
|
|
var option = AppsMenu.AddOption( tool.Name, tool.Icon, () => EngineTools.ShowTool( tool.Name ) );
|
|
option.StatusTip = tool.Description;
|
|
option.ToolTip = $"{tool.Name} - {tool.Description}";
|
|
}
|
|
|
|
AppsMenu.AddSeparator();
|
|
|
|
foreach ( var tool in EditorTypeLibrary.GetAttributes<EditorAppAttribute>().OrderBy( x => x.Title ) )
|
|
{
|
|
var option = AppsMenu.AddOption( tool.Title, tool.Icon, () => tool.Open() );
|
|
option.StatusTip = tool.Description;
|
|
option.ToolTip = $"{tool.Title} - {tool.Description}";
|
|
}
|
|
|
|
// Force a repaint
|
|
Update();
|
|
}
|
|
|
|
FullScreenManager FullScreenManager { get; set; }
|
|
|
|
/// <summary>
|
|
/// Is a widget currently the fullscreen widget
|
|
/// </summary>
|
|
public bool IsFullscreen( Widget widget )
|
|
{
|
|
return FullScreenManager.Widget == widget;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets a widget as the fullscreen widget
|
|
/// </summary>
|
|
/// <param name="widget"></param>
|
|
/// <returns>whether or not the widget is now fullscreen</returns>
|
|
public bool SetFullscreen( Widget widget )
|
|
{
|
|
if ( FullScreenManager.Widget == widget || !widget.IsValid() )
|
|
{
|
|
FullScreenManager.Clear();
|
|
return false;
|
|
}
|
|
|
|
FullScreenManager.SetWidget( widget );
|
|
|
|
return FullScreenManager.Widget == widget;
|
|
}
|
|
|
|
[Event( "asset.selected" )]
|
|
public void OnAssetSelected( Asset asset )
|
|
{
|
|
// maybe an option for this if people bitch about it
|
|
EditorUtility.PlayAssetSound( asset );
|
|
}
|
|
|
|
public void SetVisible( bool visible )
|
|
{
|
|
if ( visible )
|
|
{
|
|
EditorWindow.Enabled = true;
|
|
EditorWindow.Visible = true;
|
|
EditorWindow.Focus();
|
|
}
|
|
else
|
|
{
|
|
EditorWindow.Enabled = false;
|
|
EditorWindow.Visible = false;
|
|
}
|
|
|
|
Update();
|
|
}
|
|
|
|
private void OnFileMenuAboutToShow()
|
|
{
|
|
save.Enabled = SceneEditorSession.Active?.HasUnsavedChanges ?? true;
|
|
saveAs.Enabled = SceneEditorSession.Active?.HasUnsavedChanges ?? true;
|
|
discard.Enabled = SceneEditorSession.Active?.HasUnsavedChanges ?? false;
|
|
}
|
|
|
|
private void OnEditMenuAboutToShow()
|
|
{
|
|
UpdateEditMenu( undoOption, redoOption );
|
|
}
|
|
|
|
private void OnViewsMenuAboutToShow()
|
|
{
|
|
CreateDynamicViewMenu( ViewsMenu );
|
|
}
|
|
|
|
public void UpdateEditorTitle( string title )
|
|
{
|
|
var projectName = Project.Current?.Config.Title ?? "No Project";
|
|
Title = $"{title} - {projectName} - s&box editor{(Global.IsApiConnected ? "" : " - offline")}";
|
|
}
|
|
|
|
void BuildRecentScenes()
|
|
{
|
|
RecentScenesMenu.Clear();
|
|
|
|
var recentScenes = AssetSystem.All
|
|
.Where( x => x.LastOpened is not null )
|
|
.Where( x => x.AssetType.FileExtension == "scene" || x.AssetType.FileExtension == "prefab" )
|
|
.OrderByDescending( x => x.LastOpened )
|
|
.Take( 20 );
|
|
|
|
foreach ( var asset in recentScenes )
|
|
{
|
|
var attribute = EditorTypeLibrary.GetAttributes<AssetTypeAttribute>().Where( x => x.Extension == asset.AssetType.FileExtension ).FirstOrDefault();
|
|
RecentScenesMenu.AddOption( $"{asset.Name} ({asset.Path})", string.Empty, () => { asset.OpenInEditor(); } );
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates Undo/Redo states and text
|
|
/// </summary>
|
|
void UpdateEditMenu( Option undoOption, Option redoOption )
|
|
{
|
|
if ( SceneEditorSession.Active?.UndoSystem.Back.TryPeek( out var undoEntry ) ?? false )
|
|
{
|
|
undoOption.Enabled = true;
|
|
undoOption.Text = $"Undo {undoEntry.Name}";
|
|
}
|
|
else
|
|
{
|
|
undoOption.Enabled = false;
|
|
undoOption.Text = "Undo";
|
|
}
|
|
|
|
if ( SceneEditorSession.Active?.UndoSystem.Forward.TryPeek( out var redoEntry ) ?? false )
|
|
{
|
|
redoOption.Enabled = true;
|
|
redoOption.Text = $"Redo {redoEntry.Name}";
|
|
}
|
|
else
|
|
{
|
|
redoOption.Enabled = false;
|
|
redoOption.Text = "Redo";
|
|
}
|
|
}
|
|
}
|