Editor/MappingToolSettingsWindow.cs
using Editor;
using Sandbox.UI;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using static Editor.Label;
internal sealed class MappingToolSettingsWindow : BaseWindow
{
private NavigationView Navigation { get; }
[Menu( "Editor", "Mapping Tools/Settings", Icon = "settings" )]
public static MappingToolSettingsWindow Open()
{
var window = new MappingToolSettingsWindow();
window.Show();
return window;
}
public MappingToolSettingsWindow()
{
SetModal( true, true );
Size = new Vector2( 640, 420 );
MinimumSize = Size;
TranslucentBackground = true;
NoSystemBackground = true;
WindowTitle = "Mapping Tool Settings";
SetWindowIcon( "settings" );
Layout = Layout.Column();
Layout.Margin = 4;
Layout.Spacing = 4;
Navigation = new NavigationView();
Layout.Add( Navigation );
BuildPages();
}
private void BuildPages()
{
Navigation.AddSectionHeader( "Mapping Tools" );
Navigation.AddPage( "General", "tune", new PageGeneral( this ) );
Navigation.AddPage( "Materials Gallery", "image", new PageMaterialBrowser( this ) );
Navigation.AddPage( "Render Pie Menu", "menu", new PageRenderPieMenu( this ) );
Navigation.AddPage( "Mapping Pie Menu", "edit", new PageMappingPieMenu( this ) );
Navigation.AddPage( "Auto-Save", "save", new PageAutoSave( this ) );
}
}
internal sealed class PageGeneral : Widget
{
public PageGeneral( Widget parent ) : base( parent )
{
Layout = Layout.Column();
Layout.Margin = 32;
Layout.Spacing = 16;
Layout.Add( new Subtitle( "General Settings" ) );
Layout.Add( new Editor.Label( "General mapping workflow enhancements" ) { WordWrap = true } );
var sheet = new Editor.ControlSheet();
sheet.AddProperty( () => MappingToolSettings.DoubleClickToMeshMode );
Layout.Add( sheet );
Layout.AddStretchCell();
}
}
internal sealed class PageMaterialBrowser : Widget
{
private Layout _orgListLayout;
public PageMaterialBrowser( Widget parent ) : base( parent )
{
Layout = Layout.Column();
Layout.Margin = 32;
Layout.Spacing = 16;
Layout.Add( new Subtitle( "Materials Gallery" ) );
var sheet = new Editor.ControlSheet();
// Add default org property
sheet.AddProperty( () => MappingToolSettings.DefaultOrganization );
Layout.Add( sheet );
// Organizations list section
Layout.Add( new Editor.Label.Title( "Additional Organizations" ) );
Layout.Add( new Editor.Label( "Add organizations to search for materials" ) { WordWrap = true } );
// Scrollable container for organizations
var scrollArea = new ScrollArea( this );
scrollArea.MinimumHeight = 150;
scrollArea.MaximumHeight = 200;
Layout.Add( scrollArea );
var container = new Widget( scrollArea );
_orgListLayout = container.Layout = Layout.Column();
_orgListLayout.Spacing = 4;
scrollArea.Canvas = container;
// Add organization controls
var addRow = Layout.AddRow();
addRow.Spacing = 8;
var orgInput = new LineEdit();
orgInput.PlaceholderText = "Enter organization name...";
orgInput.MinimumWidth = 200;
addRow.Add( orgInput, 1 );
var addButton = new Editor.Button( "Add", "add" );
addButton.Clicked += () =>
{
var org = orgInput.Text?.Trim();
if ( !string.IsNullOrEmpty( org ) )
{
MappingToolSettings.AddOrganization( org );
orgInput.Text = "";
RefreshOrgList();
}
};
addRow.Add( addButton );
var resetButton = new Editor.Button( "Reset to Defaults" );
resetButton.Clicked += () =>
{
MappingToolSettings.ResetMaterialGalleryDefaults();
RefreshOrgList();
};
Layout.Add( resetButton );
Layout.AddStretchCell();
RefreshOrgList();
}
private void RefreshOrgList()
{
// Clear existing items
_orgListLayout.Clear( true );
var orgs = MappingToolSettings.AdditionalOrganizations.ToList();
if ( orgs.Count == 0 )
{
var emptyLabel = new Editor.Label( "No additional organizations added" );
emptyLabel.SetStyles( "color: rgba(255,255,255,0.4); font-style: italic;" );
_orgListLayout.Add( emptyLabel );
return;
}
foreach ( var org in orgs )
{
var row = _orgListLayout.AddRow();
row.Spacing = 8;
// Org name label
var nameLabel = new Editor.Label( org );
nameLabel.MinimumWidth = 150;
row.Add( nameLabel, 1 );
// Remove button
var removeBtn = new Editor.Button.Primary( "", "close" );
removeBtn.MinimumWidth = 32;
removeBtn.MaximumWidth = 32;
removeBtn.ToolTip = $"Remove {org}";
removeBtn.Clicked += () =>
{
MappingToolSettings.RemoveOrganization( org );
RefreshOrgList();
};
row.Add( removeBtn );
}
}
}
internal sealed class PageRenderPieMenu : Widget
{
public PageRenderPieMenu( Widget parent ) : base( parent )
{
Layout = Layout.Column();
Layout.Margin = 32;
Layout.Spacing = 16;
Layout.Add( new Subtitle( "Render Pie Menu" ) );
Layout.Add( new Editor.Label( "Quick access to render modes and viewport settings" ) { WordWrap = true } );
var sheet = new Editor.ControlSheet();
// Automatically generate UI from the settings properties
sheet.AddProperty( () => MappingToolSettings.PieMenuButton );
sheet.AddProperty( () => MappingToolSettings.PieMenuUseModifier );
sheet.AddProperty( () => MappingToolSettings.PieMenuModifierKey );
sheet.AddProperty( () => MappingToolSettings.PieMenuSize );
var resetButton = new Editor.Button( "Reset to Defaults" );
resetButton.Clicked += () =>
{
MappingToolSettings.ResetPieMenuDefaults();
};
Layout.Add( sheet );
Layout.Add( resetButton );
Layout.AddStretchCell();
}
}
internal sealed class PageMappingPieMenu : Widget
{
public PageMappingPieMenu( Widget parent ) : base( parent )
{
Layout = Layout.Column();
Layout.Margin = 32;
Layout.Spacing = 16;
Layout.Add( new Subtitle( "Mapping Pie Menu" ) );
Layout.Add( new Editor.Label( "Quick access to mesh editing modes (Object, Vertex, Edge, Face)" ) { WordWrap = true } );
var sheet = new Editor.ControlSheet();
// Automatically generate UI from the settings properties
sheet.AddProperty( () => MappingToolSettings.MappingPieMenuButton );
sheet.AddProperty( () => MappingToolSettings.MappingPieMenuUseModifier );
sheet.AddProperty( () => MappingToolSettings.MappingPieMenuModifierKey );
sheet.AddProperty( () => MappingToolSettings.MappingPieMenuSize );
var resetButton = new Editor.Button( "Reset to Defaults" );
resetButton.Clicked += () =>
{
MappingToolSettings.ResetMappingPieMenuDefaults();
};
Layout.Add( sheet );
Layout.Add( resetButton );
Layout.AddStretchCell();
}
}
// Add this new page class
internal sealed class PageAutoSave : Widget
{
public PageAutoSave( Widget parent ) : base( parent )
{
Layout = Layout.Column();
Layout.Margin = 32;
Layout.Spacing = 16;
Layout.Add( new Subtitle( "Auto-Save" ) );
Layout.Add( new Editor.Label( "Automatically create backup saves at regular intervals" ) { WordWrap = true } );
var sheet = new Editor.ControlSheet();
sheet.AddProperty( () => MappingToolSettings.AutoSaveEnabled );
sheet.AddProperty( () => MappingToolSettings.AutoSaveIntervalMinutes );
sheet.AddProperty( () => MappingToolSettings.AutoSaveMaxBackups );
sheet.AddProperty( () => MappingToolSettings.AutoSaveShowNotification );
Layout.Add( sheet );
// Force save button
var saveNowButton = new Editor.Button( "Save Backup Now", "save" );
saveNowButton.Clicked += () => AutoSave.ForceAutoSave();
Layout.Add( saveNowButton );
// Open folder button
var openFolderButton = new Editor.Button( "Open Autosave Folder", "folder_open" );
openFolderButton.Clicked += OpenAutoSaveFolder;
Layout.Add( openFolderButton );
var resetButton = new Editor.Button( "Reset to Defaults" );
resetButton.Clicked += () => MappingToolSettings.ResetAutoSaveDefaults();
Layout.Add( resetButton );
Layout.AddStretchCell();
}
private void OpenAutoSaveFolder()
{
var session = SceneEditorSession.Active;
if ( session?.Scene?.Source?.ResourcePath == null )
{
Log.Info( "No active scene" );
return;
}
var sceneDirectory = Path.GetDirectoryName( session.Scene.Source.ResourcePath );
var autoSaveFolder = Path.Combine( sceneDirectory, "autosave" );
var fullPath = Editor.FileSystem.ProjectTemporary.GetFullPath( autoSaveFolder );
// Create the folder if it doesn't exist
if ( !Directory.Exists( fullPath ) )
{
Directory.CreateDirectory( fullPath );
}
System.Diagnostics.Process.Start( "explorer.exe", fullPath );
}
}
/// <summary>
/// Settings for mapping tools including pie menu keybinds
/// </summary>
public static class MappingToolSettings
{
private const string PreferencePrefix = "MappingTools.";
// Render Pie Menu Settings
[Title( "Mouse Button" )]
[Description( "Mouse button to open the render pie menu" )]
public static MouseButtons PieMenuButton
{
get => (MouseButtons)EditorCookie.Get( PreferencePrefix + "PieMenuButton", (int)MouseButtons.Forward );
set => EditorCookie.Set( PreferencePrefix + "PieMenuButton", (int)value );
}
[Title( "Use Modifier Key" )]
[Description( "Whether to require a modifier key to open the render pie menu" )]
public static bool PieMenuUseModifier
{
get => EditorCookie.Get( PreferencePrefix + "PieMenuUseModifier", false );
set => EditorCookie.Set( PreferencePrefix + "PieMenuUseModifier", value );
}
[Title( "Modifier Key" )]
[Description( "Modifier key to open the render pie menu with" )]
public static KeyCode PieMenuModifierKey
{
get => (KeyCode)EditorCookie.Get( PreferencePrefix + "PieMenuModifierKey", (int)KeyCode.Control );
set => EditorCookie.Set( PreferencePrefix + "PieMenuModifierKey", (int)value );
}
[Title( "Menu Size" )]
[Description( "Radius of the render pie menu in pixels" )]
[Range( 100, 400 )]
public static float PieMenuSize
{
get => EditorCookie.Get( PreferencePrefix + "PieMenuSize", 180f );
set => EditorCookie.Set( PreferencePrefix + "PieMenuSize", value );
}
// Mapping Pie Menu Settings
[Title( "Mouse Button" )]
[Description( "Mouse button to open the mapping mode pie menu" )]
public static MouseButtons MappingPieMenuButton
{
get => (MouseButtons)EditorCookie.Get( PreferencePrefix + "MappingPieMenuButton", (int)MouseButtons.Back );
set => EditorCookie.Set( PreferencePrefix + "MappingPieMenuButton", (int)value );
}
[Title( "Use Modifier Key" )]
[Description( "Whether to require a modifier key to open the mapping pie menu" )]
public static bool MappingPieMenuUseModifier
{
get => EditorCookie.Get( PreferencePrefix + "MappingPieMenuUseModifier", false );
set => EditorCookie.Set( PreferencePrefix + "MappingPieMenuUseModifier", value );
}
[Title( "Modifier Key" )]
[Description( "Modifier key to open the mapping pie menu with" )]
public static KeyCode MappingPieMenuModifierKey
{
get => (KeyCode)EditorCookie.Get( PreferencePrefix + "MappingPieMenuModifierKey", (int)KeyCode.Control );
set => EditorCookie.Set( PreferencePrefix + "MappingPieMenuModifierKey", (int)value );
}
[Title( "Menu Size" )]
[Description( "Radius of the mapping pie menu in pixels" )]
[Range( 100, 400 )]
public static float MappingPieMenuSize
{
get => EditorCookie.Get( PreferencePrefix + "MappingPieMenuSize", 180f );
set => EditorCookie.Set( PreferencePrefix + "MappingPieMenuSize", value );
}
// Material Gallery Settings
[Title( "Default Organization" )]
[Description( "Primary organization to search for materials" )]
public static string DefaultOrganization
{
get => EditorCookie.Get( PreferencePrefix + "DefaultOrganization", "facepunch" );
set => EditorCookie.Set( PreferencePrefix + "DefaultOrganization", value );
}
public static IEnumerable<string> AdditionalOrganizations
{
get
{
var json = EditorCookie.Get( PreferencePrefix + "AdditionalOrganizations", "[]" );
return Json.Deserialize<List<string>>( json ) ?? new List<string>();
}
set
{
var json = Json.Serialize( value );
EditorCookie.Set( PreferencePrefix + "AdditionalOrganizations", json );
}
}
public static void AddOrganization( string org )
{
var orgs = AdditionalOrganizations.ToList();
if ( !orgs.Contains( org, StringComparer.OrdinalIgnoreCase ) )
{
orgs.Add( org );
AdditionalOrganizations = orgs;
}
}
public static void RemoveOrganization( string org )
{
var orgs = AdditionalOrganizations.ToList();
orgs.RemoveAll( o => o.Equals( org, StringComparison.OrdinalIgnoreCase ) );
AdditionalOrganizations = orgs;
}
public static void ResetPieMenuDefaults()
{
PieMenuButton = MouseButtons.Forward;
PieMenuUseModifier = false;
PieMenuModifierKey = KeyCode.Control;
PieMenuSize = 180f;
}
public static void ResetMappingPieMenuDefaults()
{
MappingPieMenuButton = MouseButtons.Back;
MappingPieMenuUseModifier = false;
MappingPieMenuModifierKey = KeyCode.Control;
MappingPieMenuSize = 180f;
}
public static void ResetMaterialGalleryDefaults()
{
DefaultOrganization = "facepunch";
AdditionalOrganizations = new List<string>();
}
// Add these to MappingToolSettings class:
// Auto-Save Settings
[Title( "Enable Auto-Save" )]
[Description( "Automatically save backups of your scene at regular intervals" )]
public static bool AutoSaveEnabled
{
get => EditorCookie.Get( PreferencePrefix + "AutoSaveEnabled", true );
set => EditorCookie.Set( PreferencePrefix + "AutoSaveEnabled", value );
}
[Title( "Interval (Minutes)" )]
[Description( "How often to create a backup save" )]
[Range( 1, 60 )]
public static float AutoSaveIntervalMinutes
{
get => EditorCookie.Get( PreferencePrefix + "AutoSaveIntervalMinutes", 5f );
set => EditorCookie.Set( PreferencePrefix + "AutoSaveIntervalMinutes", value );
}
[Title( "Maximum Backups" )]
[Description( "Maximum number of backup files to keep per scene (0 = unlimited)" )]
[Range( 0, 50 )]
public static int AutoSaveMaxBackups
{
get => EditorCookie.Get( PreferencePrefix + "AutoSaveMaxBackups", 10 );
set => EditorCookie.Set( PreferencePrefix + "AutoSaveMaxBackups", value );
}
[Title( "Show Notification" )]
[Description( "Show a toast notification when auto-save completes" )]
public static bool AutoSaveShowNotification
{
get => EditorCookie.Get( PreferencePrefix + "AutoSaveShowNotification", true );
set => EditorCookie.Set( PreferencePrefix + "AutoSaveShowNotification", value );
}
// Double-Click Settings
[Title( "Double-Click to Mesh Mode" )]
[Description( "Double-click on a GameObject with MeshComponent to enter Mesh Tool mode" )]
public static bool DoubleClickToMeshMode
{
get => EditorCookie.Get( PreferencePrefix + "DoubleClickToMeshMode", false );
set => EditorCookie.Set( PreferencePrefix + "DoubleClickToMeshMode", value );
}
public static void ResetAutoSaveDefaults()
{
AutoSaveEnabled = true;
AutoSaveIntervalMinutes = 5f;
AutoSaveMaxBackups = 10;
AutoSaveShowNotification = true;
}
}