Files
sbox-public/engine/Sandbox.Tools/Theme.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

398 lines
15 KiB
C#

using System.Runtime.InteropServices;
namespace Editor;
public static partial class Theme
{
public static Color32[] QtColors = new Color32[(int)QPalette.ColorRole.NumColorRoles];
internal static void CollectQtColors()
{
QtColors[(int)QPalette.ColorRole.WindowText] = Text;
QtColors[(int)QPalette.ColorRole.Mid] = Color.Transparent;
QtColors[(int)QPalette.ColorRole.Text] = Text;
QtColors[(int)QPalette.ColorRole.BrightText] = TextLight;
QtColors[(int)QPalette.ColorRole.ButtonText] = TextButton;
QtColors[(int)QPalette.ColorRole.Base] = Base;
QtColors[(int)QPalette.ColorRole.AlternateBase] = BaseAlt;
QtColors[(int)QPalette.ColorRole.Window] = WindowBackground;
QtColors[(int)QPalette.ColorRole.Shadow] = Shadow;
QtColors[(int)QPalette.ColorRole.Highlight] = Highlight;
QtColors[(int)QPalette.ColorRole.HighlightedText] = TextHighlight;
QtColors[(int)QPalette.ColorRole.Link] = TextLink;
QtColors[(int)QPalette.ColorRole.LinkVisited] = TextLink;
QtColors[(int)QPalette.ColorRole.NoRole] = SurfaceBackground;
QtColors[(int)QPalette.ColorRole.ToolTipBase] = SurfaceBackground;
QtColors[(int)QPalette.ColorRole.ToolTipText] = Text;
QtColors[(int)QPalette.ColorRole.PlaceholderText] = TextLight;
QtColors[(int)QPalette.ColorRole.Grid] = Base;
QtColors[(int)QPalette.ColorRole.DarkSplitterHandle] = Border;
QtColors[(int)QPalette.ColorRole.HeaderSectionBackground] = WindowBackground;
QtColors[(int)QPalette.ColorRole.Black] = Color.Black;
QtColors[(int)QPalette.ColorRole.Red] = Color.Red;
QtColors[(int)QPalette.ColorRole.Green] = Color.Green;
QtColors[(int)QPalette.ColorRole.Blue] = Color.Blue;
QtColors[(int)QPalette.ColorRole.CheckmarkPossible] = Primary;
QtColors[(int)QPalette.ColorRole.FlatSelection] = Color.Transparent;
QtColors[(int)QPalette.ColorRole.DarkSplitter] = SurfaceBackground;
QtColors[(int)QPalette.ColorRole.MenuBackground] = WindowBackground;
QtColors[(int)QPalette.ColorRole.MenuBarBackground] = WindowBackground;
QtColors[(int)QPalette.ColorRole.ToolBarBackground] = WindowBackground;
QtColors[(int)QPalette.ColorRole.ViewportBackground] = WindowBackground;
QtColors[(int)QPalette.ColorRole.ViewportText] = TextLight;
QtColors[(int)QPalette.ColorRole.DockWidgetTitleBackground] = SurfaceBackground;
QtColors[(int)QPalette.ColorRole.TabBarBackgroundNotSelected] = SurfaceBackground;
QtColors[(int)QPalette.ColorRole.TabTextInactive] = TextLight;
QtColors[(int)QPalette.ColorRole.TabBarBackground] = TabBarBackground;
QtColors[(int)QPalette.ColorRole.Button] = ButtonBackground;
QtColors[(int)QPalette.ColorRole.DefaultButtonBackground] = SurfaceBackground;
QtColors[(int)QPalette.ColorRole.ButtonBackground] = ButtonBackground;
QtColors[(int)QPalette.ColorRole.ButtonHighlight] = ButtonBackground;
QtColors[(int)QPalette.ColorRole.ButtonPressed] = ButtonBackground;
QtColors[(int)QPalette.ColorRole.DefaultButtonOuterBorder] = Border;
QtColors[(int)QPalette.ColorRole.DefaultButtonInnerBorder] = Border;
QtColors[(int)QPalette.ColorRole.ToolButtonBackground] = ButtonBackground;
QtColors[(int)QPalette.ColorRole.ToolButtonActiveBackground] = ButtonBackground;
QtColors[(int)QPalette.ColorRole.LightSplitterHandle] = Color.Transparent;
QtColors[(int)QPalette.ColorRole.ScrollbarBackground] = Color.Transparent;
QtColors[(int)QPalette.ColorRole.HeaderSectionLine] = Color.Transparent;
QtColors[(int)QPalette.ColorRole.InnerHighlight] = Color.Transparent;
QtColors[(int)QPalette.ColorRole.LighterLine] = BorderLight;
QtColors[(int)QPalette.ColorRole.UpperLine] = BorderLight;
QtColors[(int)QPalette.ColorRole.LowerLine] = BorderLight;
QtColors[(int)QPalette.ColorRole.ToolButtonBorder] = Color.Transparent;
QtColors[(int)QPalette.ColorRole.ToolBarBorderLight] = Color.Transparent;
QtColors[(int)QPalette.ColorRole.ToolBarBorderDark] = Color.Transparent;
QtColors[(int)QPalette.ColorRole.ButtonBorder] = BorderButton;
QtColors[(int)QPalette.ColorRole.ToolButtonActiveBorder] = Border;
QtColors[(int)QPalette.ColorRole.ViewportToolBarBorderLight] = BorderLight;
QtColors[(int)QPalette.ColorRole.ViewportToolBarBorderDark] = SurfaceBackground;
QtColors[(int)QPalette.ColorRole.Light] = BorderLight;
QtColors[(int)QPalette.ColorRole.Midlight] = BorderLight;
QtColors[(int)QPalette.ColorRole.Dark] = BorderLight;
}
private static GCHandle PaletteHandle;
private static unsafe Color32* PalettePtr;
internal static unsafe Color32* GetPaletteColors()
{
return PalettePtr;
}
internal static string GetDefaultFont()
{
return DefaultFont;
}
public static Color Blue;
public static Color Green;
public static Color Red;
public static Color Yellow;
public static Color Pink;
public static Color Prefab;
public static Color Folder;
public static Color TabBackground;
public static Color TabBarBackground;
public static Color TabInactiveBackground;
public static Color SurfaceBackground;
public static Color SurfaceLightBackground;
public static Color SidebarBackground;
public static Color WindowBackground;
public static Color WidgetBackground;
public static Color ControlBackground;
public static Color ButtonBackground;
public static Color SelectedBackground;
public static Color StatusBarBackground;
public static Color Text;
public static Color TextControl;
public static Color TextLight;
public static Color TextWidget;
public static Color TextButton;
public static Color TextSelected;
public static Color TextLink;
public static Color TextHighlight;
public static Color TextDisabled;
public static Color Border;
public static Color BorderLight;
public static Color BorderButton;
public static Color Shadow;
public static Color Primary;
public static Color Overlay;
public static Color MultipleValues;
public static Color Highlight;
public static Color TextDark;
public static Color Base;
public static Color BaseAlt;
public static Color ToggleEnabled;
public static Color ToggleDisabled;
public static float ControlRadius;
public static float ControlHeight;
public static float RowHeight;
public static string HeadingFont;
public static string DefaultFont;
public static string MonospaceFont;
static unsafe Theme()
{
LoadFromFile();
CollectQtColors();
PaletteHandle = GCHandle.Alloc( QtColors, GCHandleType.Pinned );
PalettePtr = (Color32*)PaletteHandle.AddrOfPinnedObject();
}
private static void LoadFromFile()
{
var themeJson = FileSystem.Root.ReadAllText( "/addons/tools/assets/styles/theme.json" );
var theme = Json.Deserialize<Dictionary<string, string>>( themeJson );
TabBackground = Color.Parse( theme["TabBackground"] ) ?? Color.Parse( "#3b3b3b" ).Value;
TabBarBackground = Color.Parse( theme["TabBarBackground"] ) ?? Color.Parse( "#242424" ).Value;
TabInactiveBackground = Color.Parse( theme["TabInactiveBackground"] ) ?? Color.Parse( "#242424" ).Value;
SurfaceBackground = Color.Parse( theme["SurfaceBackground"] ) ?? Color.Parse( "#3b3b3b" ).Value;
SurfaceLightBackground = Color.Parse( theme["SurfaceLightBackground"] ) ?? Color.Parse( "#696969" ).Value;
SidebarBackground = Color.Parse( theme["SidebarBackground"] ) ?? Color.Parse( "#242424" ).Value;
WindowBackground = Color.Parse( theme["WindowBackground"] ) ?? Color.Parse( "#181818" ).Value;
WidgetBackground = Color.Parse( theme["WidgetBackground"] ) ?? Color.Parse( "#242424" ).Value;
ControlBackground = Color.Parse( theme["ControlBackground"] ) ?? Color.Parse( "#181818" ).Value;
ButtonBackground = Color.Parse( theme["ButtonBackground"] ) ?? Color.Parse( "#181818" ).Value;
SelectedBackground = Color.Parse( theme["SelectedBackground"] ) ?? Color.Parse( "#808080" ).Value;
StatusBarBackground = Color.Parse( theme["StatusBarBackground"] ) ?? Color.Parse( "#242424" ).Value;
Text = Color.Parse( theme["Text"] ) ?? Color.Parse( "#FFFFFF" ).Value;
TextControl = Color.Parse( theme["TextControl"] ) ?? Color.Parse( "#FFFFFF" ).Value;
TextLight = Color.Parse( theme["TextLight"] ) ?? Color.Parse( "#9E9E9E" ).Value;
TextWidget = Color.Parse( theme["TextWidget"] ) ?? Color.Parse( "#FFFFFF" ).Value;
TextButton = Color.Parse( theme["TextButton"] ) ?? Color.Parse( "#FFFFFF" ).Value;
TextSelected = Color.Parse( theme["TextSelected"] ) ?? Color.Parse( "#66a3ff" ).Value;
TextLink = Color.Parse( theme["TextLink"] ) ?? Color.Parse( "#FFFFFF" ).Value;
TextHighlight = Color.Parse( theme["TextHighlight"] ) ?? Color.Parse( "#66a3ff" ).Value;
TextDisabled = Color.Parse( theme["TextDisabled"] ) ?? Color.Parse( "#FFFFFF55" ).Value;
Border = Color.Parse( theme["Border"] ) ?? Color.Parse( "#525252" ).Value;
BorderLight = Color.Parse( theme["BorderLight"] ) ?? Color.Parse( "#696969" ).Value;
BorderButton = Color.Parse( theme["BorderButton"] ) ?? Color.Parse( "#696969" ).Value;
Shadow = Color.Parse( theme["Shadow"] ) ?? Color.Parse( "#242424" ).Value;
Primary = Color.Parse( theme["Primary"] ) ?? Color.Parse( "#5a8deb" ).Value;
Overlay = Color.Parse( theme["Overlay"] ) ?? Color.Parse( "#242424" ).Value;
MultipleValues = Color.Parse( theme["MultipleValues"] ) ?? Color.Parse( "#808080" ).Value;
Highlight = Color.Parse( theme["Highlight"] ) ?? Color.Parse( "#9E9E9E" ).Value;
TextDark = Color.Parse( theme["TextDark"] ) ?? Color.Parse( "#000000" ).Value;
Base = Color.Parse( theme["Base"] ) ?? Color.Parse( "#202020" ).Value;
BaseAlt = Color.Parse( theme["BaseAlt"] ) ?? Color.Parse( "#242424" ).Value;
ToggleEnabled = Color.Parse( theme["ToggleEnabled"] ) ?? Color.Parse( "#5aeb5c" ).Value;
ToggleDisabled = Color.Parse( theme["ToggleDisabled"] ) ?? Color.Parse( "#566e56" ).Value;
Blue = Color.Parse( theme["Blue"] ) ?? Color.Parse( "#3273EB" ).Value;
Green = Color.Parse( theme["Green"] ) ?? Color.Parse( "#B0E24D" ).Value;
Red = Color.Parse( theme["Red"] ) ?? Color.Parse( "#FB5A5A" ).Value;
Yellow = Color.Parse( theme["Yellow"] ) ?? Color.Parse( "#E6DB74" ).Value;
Pink = Color.Parse( theme["Pink"] ) ?? Color.Parse( "#DF9194" ).Value;
Prefab = Color.Parse( theme["Prefab"] ) ?? Color.Parse( "#3273EB" ).Value;
Folder = Color.Parse( theme["Folder"] ) ?? Color.Parse( "#E6DB74" ).Value;
if ( !float.TryParse( theme["ControlRadius"], out ControlRadius ) )
ControlRadius = 3.0f;
if ( !float.TryParse( theme["ControlHeight"], out ControlHeight ) )
ControlHeight = 18.0f;
if ( !float.TryParse( theme["RowHeight"], out RowHeight ) )
RowHeight = 16.0f;
HeadingFont = theme["HeadingFont"];
DefaultFont = theme["DefaultFont"];
MonospaceFont = theme["MonospaceFont"];
}
public static Color GetTint( EditorTint tint )
{
return tint switch
{
EditorTint.Blue => Blue,
EditorTint.Green => Green,
EditorTint.Red => Red,
EditorTint.Yellow => Yellow,
EditorTint.Pink => Pink,
_ => Color.White
};
}
public static void DrawFilename( Rect rect, string filename, TextFlag flags, Color color )
{
var dir = System.IO.Path.GetDirectoryName( filename ) + "/";
var file = System.IO.Path.GetFileNameWithoutExtension( filename );
var extension = System.IO.Path.GetExtension( filename );
// if we really cared we could do this better
var size = Paint.MeasureText( rect, filename, flags );
var overshoot = (size.Width - rect.Width) + 5;
if ( overshoot > 0 )
{
overshoot += 10;
var startIndex = (overshoot / 4).CeilToInt();
if ( startIndex < dir.Length )
dir = ".." + dir.Substring( startIndex );
else
dir = "";
}
dir = dir.Replace( '\\', '/' );
Paint.SetPen( color.Darken( 0.3f ) );
var r = Paint.DrawText( rect, dir, flags );
rect.Left += r.Width;
Paint.SetPen( color );
r = Paint.DrawText( rect, file, flags );
rect.Left += r.Width;
Paint.SetPen( color.Darken( 0.1f ) );
r = Paint.DrawText( rect, extension, flags );
}
/// <summary>
/// Draw a button with a background color, text, and an optional icon. We'll use Paint.HasPressed and Paint.HasMouseOver to determine the button's state.
/// </summary>
public static void DrawButton( Rect LocalRect, string text = null, string Icon = null, bool Enabled = true, Color? Tint = null )
{
var c = Tint?.ToHsv() ?? Color.Parse( "#48494c" )?.ToHsv() ?? default;
var bg = c;
if ( Enabled )
{
if ( Paint.HasPressed )
{
bg = c with { Value = (c.Value + 0.1f) };
}
else if ( Paint.HasMouseOver )
{
bg = c with { Value = (c.Value + 0.2f) };
}
}
else
{
bg = c = Theme.SurfaceLightBackground;
}
if ( !Enabled )
{
c = c.WithSaturation( 0.1f ).WithAlpha( 0.5f );
bg = c.WithAlpha( 0.2f );
}
if ( bg.Alpha > 0 )
{
float radius = 3;
Paint.Antialiasing = true;
Paint.ClearPen();
Paint.SetBrush( bg with { Value = (bg.Value + 0.04f), Saturation = (c.Saturation * 0.8f) } );
Paint.DrawRect( LocalRect, radius );
Paint.SetBrushLinear( LocalRect.TopLeft, LocalRect.BottomRight, bg, bg with { Value = (bg.Value - 0.03f) } );
Paint.DrawRect( LocalRect.Shrink( 1, 1, 1, 1 ), radius );
}
else
{
c = Color.White.WithAlpha( 0.5f );
}
Paint.SetDefaultFont();
if ( !string.IsNullOrWhiteSpace( text ) )
{
var textSize = Paint.MeasureText( text );
float iconSize = 16;
float spacing = 4;
float startX = 8;
float centerY = LocalRect.Center.y;
Paint.SetPen( c with { Value = 0.99f, Saturation = c.Saturation * 0.20f } );
if ( !string.IsNullOrWhiteSpace( Icon ) )
{
var iconRect = new Rect( startX, centerY - iconSize / 2, iconSize, iconSize );
Paint.DrawIcon( iconRect, Icon, iconSize );
startX += iconSize + spacing;
}
Paint.SetPen( c with { Value = 0.99f, Saturation = c.Saturation * 0.20f, Alpha = 0.75f } );
var textRect = new Rect( startX, LocalRect.Top, textSize.x, LocalRect.Height );
Paint.DrawText( textRect, text, TextFlag.Center );
}
}
/// <summary>
/// Draw a dropdown control with a text label and an icon. Has a up/down chevron icon to indicate the dropdown state.
/// </summary>
public static void DrawDropdown( Rect rect, string text, string icon, bool open, bool disabled = false )
{
Paint.ClearPen();
if ( open ) Paint.SetBrush( Theme.ControlBackground.LerpTo( Theme.Green, 0.05f ) );
else Paint.SetBrush( Theme.ControlBackground );
Paint.DrawRect( rect, Theme.ControlRadius );
// cap off the bottom
if ( open )
{
Paint.DrawRect( rect.Shrink( 0, 4, 0, 0 ) );
}
if ( disabled ) Paint.SetPen( Theme.Text.WithAlpha( 0.2f ) );
else if ( open ) Paint.SetPen( Theme.Green );
else if ( Paint.HasMouseOver ) Paint.SetPen( Theme.TextButton.WithAlpha( 0.7f ) );
else Paint.SetPen( Theme.TextButton.WithAlpha( 0.5f ) );
Paint.DrawIcon( rect.Shrink( 0, 0, 4, 0 ), open ? "expand_less" : "expand_more", rect.Height - 3, TextFlag.RightCenter );
Paint.DrawIcon( rect.Shrink( 4, 0, 0, 0 ), icon, 16, TextFlag.LeftCenter );
Paint.SetDefaultFont();
Paint.DrawText( rect.Shrink( 30, 0, 26, 0 ), text, TextFlag.LeftCenter );
}
internal static string ParseVariables( string input )
{
var values = new Dictionary<string, string>();
var fields = typeof( Theme ).GetFields();
foreach ( var field in fields )
{
var value = field.GetValue( null );
if ( value is Color color )
{
values.Add( $"${field.Name}", color.Hex );
}
else if ( value is float f )
{
values.Add( $"${field.Name}", $"{f}px" );
}
else if ( value is string s )
{
values.Add( $"${field.Name}", s );
}
}
// Sort by length so we don't stomp stuff, e.g. $Surface and $SurfaceSubtle
values = values.OrderByDescending( x => x.Key.Length ).ToDictionary();
foreach ( var color in values )
{
input = input.Replace( color.Key, color.Value );
}
return input;
}
}