Themes are no longer passed by ref, and instead are passed by pointer.

Layers are now added AFTER UiManager is initialized and the instance is set.
TryCreateUi no longer automatically starts the UI thread, for you to add layers & do any custom ImGui setup before starting the thread.
This commit is contained in:
Evan Husted 2024-07-30 01:43:26 -05:00
parent adc3dc0513
commit 7e3b65266d
13 changed files with 227 additions and 170 deletions

4
.gitignore vendored
View file

@ -1,7 +1,7 @@
.idea/
.vs/
src/Bot/bin
src/Bot/obj
src/Bot/bin/
src/Bot/obj/
src/Bot/Properties/
src/UI/bin/
src/UI/obj/

View file

@ -1,3 +1,4 @@
{
"FSharp.suggestGitignore": false
"FSharp.suggestGitignore": false,
"editor.fontFamily": "JetBrains Mono, monospace"
}

View file

@ -9,8 +9,13 @@ public sealed partial class BotOwnerModule
public Task<ActionResult> UiAsync(
[Description("Desired font size of the UI.")] int fontSize = 17)
{
return UiManager.TryCreateUi(VolteBot.GetUiParams(fontSize), out var err)
? None(() => Context.Message.AddReactionAsync(Emojis.BallotBoxWithCheck))
: BadRequest($"Could not create UI thread: {err?.Message}");
var uiParams = VolteBot.GetUiParams(fontSize);
if (!UiManager.TryCreateUi(uiParams, out var err))
return BadRequest($"Could not create UI thread: {err?.Message}");
UiManager.AddView(new VolteUiView(Context.Services));
UiManager.StartThread(uiParams.ThreadName);
return None(() => Context.Message.AddReactionAsync(Emojis.BallotBoxWithCheck));
}
}

View file

@ -81,7 +81,7 @@ public class TagModule : VolteModule
return Ok($"Successfully modified the content of tag **{tag.Name}**.");
}
[Command("Delete", "Del", "Rem")]
[Command("Delete", "Remove", "Del", "Rem")]
[Description("Deletes a tag if it exists.")]
[RequireGuildModerator]
public async Task<ActionResult> TagDeleteAsync([Remainder, Description("The tag to delete.")]

View file

@ -26,7 +26,7 @@ public class WelcomeModule : VolteModule
public Task<ActionResult> WelcomeMessageAsync([Remainder] string message = null)
{
if (message is null)
return Ok($"The current welcome message for this guild is: {Format.Code(Context.GuildData.Configuration.Welcome.WelcomeMessage, string.Empty)}");
return Ok($"The current welcome message for this guild is: {Format.Code(Context.GuildData.Configuration.Welcome.WelcomeMessage ?? "None", string.Empty)}");
Context.Modify(data => data.Configuration.Welcome.WelcomeMessage = message);
var welcomeChannel = Context.Guild.GetTextChannel(Context.GuildData.Configuration.Welcome.WelcomeChannel);
@ -59,7 +59,7 @@ public class WelcomeModule : VolteModule
if (message is null)
return Ok(new StringBuilder()
.AppendLine(
$"The current leaving message for this guild is: {Format.Code(Context.GuildData.Configuration.Welcome.LeavingMessage, string.Empty)}"));
$"The current leaving message for this guild is: {Format.Code(Context.GuildData.Configuration.Welcome.LeavingMessage ?? "None", string.Empty)}"));
Context.GuildData.Configuration.Welcome.LeavingMessage = message;
Db.Save(Context.GuildData);
@ -86,7 +86,7 @@ public class WelcomeModule : VolteModule
{
if (message is null)
return Ok(
$"Unset the WelcomeDmMessage that was previously set to: {Format.Code(Context.GuildData.Configuration.Welcome.WelcomeDmMessage)}");
$"Unset the WelcomeDmMessage that was previously set to: {Format.Code(Context.GuildData.Configuration.Welcome.WelcomeDmMessage ?? "None")}");
Context.GuildData.Configuration.Welcome.WelcomeDmMessage = message;
Db.Save(Context.GuildData);

View file

@ -42,11 +42,8 @@ public class VolteBot
IsRunning = true;
if (Program.CommandLineArguments.TryGetValue("ui", out var sizeStr)
&& !UiManager.TryCreateUi(GetUiParams(sizeStr.TryParse<int>(out var fsz) ? fsz : 17),
out var uiStartError)
) Error(LogSource.UI, $"Could not create UI: {uiStartError!.Message}");
if (Program.CommandLineArguments.TryGetValue("ui", out var sizeStr))
CreateUi(sizeStr);
_client = ServiceProvider.Get<DiscordSocketClient>();
_cts = ServiceProvider.Get<CancellationTokenSource>();
@ -120,39 +117,51 @@ public class VolteBot
videoMode: VideoMode.Default
);
private static void CreateUi(string sizeStr)
{
var uiParams = GetUiParams(sizeStr.TryParse<int>(out var fsz) ? fsz : 17);
if (UiManager.TryCreateUi(uiParams,out var uiStartError))
{
UiManager.AddView(new VolteUiView(ServiceProvider));
UiManager.StartThread(uiParams.ThreadName);
}
else Error(LogSource.UI, $"Could not create UI: {uiStartError!.Message}");
}
public static UiManager.CreateParams GetUiParams(int fontSize)
{
return new UiManager.CreateParams
unsafe //Spectrum.Dark/Light are pointers
{
Font = getFont(),
WindowIcon = getIcon(),
WOptions = DefaultWindowOptions,
Layers = [new VolteUiLayer(ServiceProvider)],
ThreadName = "Volte UI Thread"
};
ImGuiFontConfig getFont()
{
var ttf = FilePath.Data / "UiFont.ttf";
if (!ttf.ExistsAsFile)
return new UiManager.CreateParams
{
using var embeddedFont = Assembly.GetExecutingAssembly().GetManifestResourceStream("UIFont");
if (embeddedFont != null)
OnConfigureIO = io =>
{
using var fs = ttf.OpenCreate();
embeddedFont.Seek(0, SeekOrigin.Begin);
embeddedFont.CopyTo(fs);
}
}
return new ImGuiFontConfig(ttf.ToString(), fontSize);
var ttf = FilePath.Data / "UiFont.ttf";
if (!ttf.ExistsAsFile)
{
using var embeddedFont = Assembly.GetExecutingAssembly().GetManifestResourceStream("UIFont");
if (embeddedFont != null)
{
using var fs = ttf.OpenCreate();
embeddedFont.Seek(0, SeekOrigin.Begin);
embeddedFont.CopyTo(fs);
}
}
io.Fonts.AddFontFromFileTTF(ttf.ToString(), fontSize);
},
WindowIcon = getIcon(),
WOptions = DefaultWindowOptions,
Theme = Spectrum.Dark,
ThreadName = "Volte UI Thread"
};
}
Image<Rgba32> getIcon()
{
Stream iconStream;
return (iconStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("VolteIcon")) == null
? null
return (iconStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("VolteIcon")) == null
? null
: Image.Load<Rgba32>(iconStream);
}
}

View file

@ -26,7 +26,7 @@ public static partial class Extensions
LogLevel = Config.DebugEnabled || Version.IsDevelopment
? LogSeverity.Debug
: LogSeverity.Verbose,
GatewayIntents = _intents,
GatewayIntents = Intents,
AlwaysDownloadUsers = true,
ConnectionTimeout = 10000,
MessageCacheSize = 50
@ -48,7 +48,7 @@ public static partial class Extensions
Info(LogSource.Volte, $"Injected {l.Count()} services into the provider.");
});
private const GatewayIntents _intents
private const GatewayIntents Intents
= GatewayIntents.Guilds | GatewayIntents.GuildMessageReactions | GatewayIntents.GuildMembers |
GatewayIntents.GuildMessages | GatewayIntents.GuildPresences | GatewayIntents.MessageContent;

View file

@ -6,7 +6,7 @@ using Color = System.Drawing.Color;
namespace Volte.UI;
public partial class VolteUiLayer
public partial class VolteUiView
{
private void CommandStats(double _)
{
@ -28,21 +28,22 @@ public partial class VolteUiLayer
{
//using var __ = PushStyle(ImGuiStyleVar.WindowMinSize, new Vector2(385, 299));
ImGui.Text("Discord Gateway:");
ImGui.SameLine();
// ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault
// default is a meaningless case here i dont fucking care rider
switch (_state.Client.ConnectionState)
{
case ConnectionState.Connected:
ColoredText(" Connected", Color.LawnGreen);
ColoredText("Connected", Color.LawnGreen);
break;
case ConnectionState.Connecting:
ColoredText(" Connecting...", Color.Yellow);
ColoredText("Connecting...", Color.Yellow);
break;
case ConnectionState.Disconnecting:
ColoredText(" Disconnecting...", Color.OrangeRed);
ColoredText("Disconnecting...", Color.OrangeRed);
break;
case ConnectionState.Disconnected:
ColoredText(" Disconnected!", Color.Red);
ColoredText("Disconnected!", Color.Red);
break;
}
@ -92,9 +93,15 @@ public partial class VolteUiLayer
ImGui.Text($"Owner: @{selectedGuild.Owner}");
ImGui.Text($"Text Channels: {selectedGuild.TextChannels.Count}");
ImGui.Text($"Voice Channels: {selectedGuild.VoiceChannels.Count}");
ImGui.Text($"{selectedGuildMembers.Length} members");
ColoredText($" + {selectedGuildMembers.Length - botMembers} users", Color.LawnGreen);
ColoredText($" - {botMembers} bots", Color.OrangeRed);
ImGui.Text($"{selectedGuildMembers.Length} members |");
ImGui.SameLine();
ColoredText($"{selectedGuildMembers.Length - botMembers} users", Color.LawnGreen);
ImGui.SameLine();
ImGui.Text("|");
ImGui.SameLine();
ColoredText($"{botMembers} bots", Color.OrangeRed);
ImGui.Separator();
var destructiveMenuEnabled = AllKeysPressed(Key.ShiftLeft, Key.ControlLeft);

View file

@ -25,11 +25,11 @@ public sealed class VolteUiState
public ulong SelectedGuildId = 0;
}
public partial class VolteUiLayer : UiLayer
public partial class VolteUiView : UiView
{
private readonly VolteUiState _state;
public VolteUiLayer(IServiceProvider provider)
public VolteUiView(IServiceProvider provider)
{
_state = new VolteUiState(provider);
@ -64,9 +64,7 @@ public partial class VolteUiLayer : UiLayer
ImGui.MenuItem($"{Io.Framerate:###} FPS ({1000f / Io.Framerate:0.##} ms/frame)", false);
if (Config.DebugEnabled || Version.IsDevelopment)
{
ImGui.MenuItem($"Delta: {delta:0.00000}", false);
}
ImGui.EndMenu();
}
@ -76,9 +74,11 @@ public partial class VolteUiLayer : UiLayer
if (ImGui.MenuItem(_state.SelectedTheme ? "Swap to Light" : "Swap to Dark"))
{
_state.SelectedTheme = !_state.SelectedTheme;
if (_state.SelectedTheme)
SetColors(ref Spectrum.Dark, true);
else SetColors(ref Spectrum.Light, false);
unsafe
{
UiManager.SetColors(_state.SelectedTheme ? Spectrum.Dark : Spectrum.Light);
}
}
if (ImGui.RadioButton("Show Style Editor", _state.ShowStyleEditor))

View file

@ -1,7 +1,6 @@
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Gommon;
@ -14,7 +13,7 @@ using Silk.NET.Windowing;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
using Image = SixLabors.ImageSharp.Image;
using Color = Silk.NET.SDL.Color;
namespace Volte.UI;
@ -25,7 +24,7 @@ public sealed partial class UiManager : IDisposable
private bool _isActive;
private readonly IWindow _window;
private readonly ImGuiFontConfig? _fontConfig;
private readonly Action<ImGuiIOPtr> _onConfigureIO;
private Image<Rgba32>? _windowIcon;
private ImGuiController? _controller;
@ -36,19 +35,24 @@ public sealed partial class UiManager : IDisposable
{
_gl = GL.GetApi(_window);
_inputContext = _window.CreateInput();
_controller = new ImGuiController(_gl, _window, _inputContext, _fontConfig, () =>
_controller = new ImGuiController(_gl, _window, _inputContext, default, () =>
{
var io = ImGui.GetIO();
io.ConfigFlags |= ImGuiConfigFlags.DockingEnable;
io.ConfigDockingWithShift = false;
UiLayer.SetColors(ref Spectrum.Dark, true);
unsafe
{
if (Theme != null)
SetColors(Theme);
}
var fonts = FilePath.Data.Resolve("fonts", true);
if (fonts.ExistsAsDirectory)
fonts.GetFiles()
.Where(x => x.Extension is "ttf")
.ForEach(fp => io.Fonts.AddFontFromFileTTF(fp.Path, 17));
_onConfigureIO(io);
FilePath.Data.Resolve("fonts", true)
.GetFiles()?
.Where(x => x.Extension is "ttf")?
.ForEach(fp => io.Fonts.AddFontFromFileTTF(fp.Path, 17));
}
);
@ -77,4 +81,75 @@ public sealed partial class UiManager : IDisposable
_window.SetWindowIcon(ref rawIcon);
}
public static unsafe void SetColors(ThemedColors* theme)
{
var style = ImGui.GetStyle();
style.GrabRounding = 4f;
style.FrameRounding = 6f;
style.WindowMenuButtonPosition = ImGuiDir.None;
style.FrameBorderSize = 1f;
style.TabBorderSize = 1f;
style.WindowTitleAlign = new Vector2(0.5f);
style.SeparatorTextBorderSize = 9f;
set(ImGuiCol.Text, theme->Gray800);
set(ImGuiCol.TextDisabled, theme->Gray500);
set(ImGuiCol.WindowBg, theme->Gray100);
set(ImGuiCol.ChildBg, Spectrum.Static.None);
set(ImGuiCol.PopupBg, theme->Gray50);
set(ImGuiCol.Border, theme->Gray300);
set(ImGuiCol.BorderShadow, Spectrum.Static.None);
set(ImGuiCol.FrameBg, theme->Gray75);
set(ImGuiCol.FrameBgHovered, theme->Gray50);
set(ImGuiCol.FrameBgActive, theme->Gray200);
set(ImGuiCol.TitleBg, theme->Gray300);
set(ImGuiCol.TitleBgActive, theme->Gray200);
set(ImGuiCol.TitleBgCollapsed, theme->Gray400);
set(ImGuiCol.TabUnfocusedActive, theme->Blue400);
set(ImGuiCol.MenuBarBg, theme->Gray100);
set(ImGuiCol.ScrollbarBg, theme->Gray100);
set(ImGuiCol.ScrollbarGrab, theme->Gray400);
set(ImGuiCol.ScrollbarGrabHovered, theme->Gray600);
set(ImGuiCol.ScrollbarGrabActive, theme->Gray700);
set(ImGuiCol.CheckMark, theme->Blue500);
set(ImGuiCol.SliderGrab, theme->Gray700);
set(ImGuiCol.SliderGrabActive, theme->Gray800);
set(ImGuiCol.Button, theme->Gray75);
set(ImGuiCol.ButtonHovered, theme->Gray50);
set(ImGuiCol.ButtonActive, theme->Gray200);
set(ImGuiCol.Header, theme->Blue400);
set(ImGuiCol.HeaderHovered, theme->Blue500);
set(ImGuiCol.HeaderActive, theme->Blue600);
set(ImGuiCol.Separator, theme->Gray400);
set(ImGuiCol.SeparatorHovered, theme->Gray600);
set(ImGuiCol.SeparatorActive, theme->Gray700);
set(ImGuiCol.ResizeGrip, theme->Gray400);
set(ImGuiCol.ResizeGripHovered, theme->Gray600);
set(ImGuiCol.ResizeGripActive, theme->Gray700);
set(ImGuiCol.PlotLines, theme->Blue400);
set(ImGuiCol.PlotLinesHovered, theme->Blue600);
set(ImGuiCol.PlotHistogram, theme->Blue400);
set(ImGuiCol.PlotHistogramHovered, theme->Blue600);
setVec(ImGuiCol.TextSelectedBg, ImGui.ColorConvertU32ToFloat4((colorValue(theme->Blue400) & 0x00FFFFFF) | 0x33000000));
setVec(ImGuiCol.DragDropTarget, new Vector4(1.00f, 1.00f, 0.00f, 0.90f));
setVec(ImGuiCol.NavHighlight, ImGui.ColorConvertU32ToFloat4((colorValue(theme->Gray900) & 0x00FFFFFF) | 0x0A000000));
setVec(ImGuiCol.NavWindowingHighlight, new Vector4(1.00f, 1.00f, 1.00f, 0.70f));
setVec(ImGuiCol.NavWindowingDimBg, new Vector4(0.80f, 0.80f, 0.80f, 0.20f));
setVec(ImGuiCol.ModalWindowDimBg, new Vector4(0.20f, 0.20f, 0.20f, 0.35f));
return;
void set(ImGuiCol colorVar, Color color)
=> setVec(colorVar, new Vector4(color.R / 255f, color.G / 255f, color.B / 255f, 1f));
void setVec(ImGuiCol colorVar, Vector4 colorVec)
=> style.Colors[(int)colorVar] = colorVec;
uint colorValue(Color color) => ((uint)color.R << 16)
| ((uint)color.G << 8)
| color.B;
}
}

View file

@ -1,11 +1,11 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Numerics;
using System.Threading;
using System.Threading.Tasks;
using Gommon;
using ImGuiNET;
using Silk.NET.OpenGL.Extensions.ImGui;
using Silk.NET.Windowing;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
@ -19,22 +19,27 @@ public sealed partial class UiManager
{
public readonly ConcurrentQueue<Func<Task>> TaskQueue = new();
public UiLayer[] Layers { get; }
public readonly List<UiView> Views = [];
private int CurrentLayerIdx { get; set; }
public unsafe ThemedColors* Theme { get; }
private void SetLayer(int layerIndex) =>
CurrentLayerIdx = layerIndex.CoerceAtLeast(0).CoerceAtMost(Layers.Length - 1);
private int CurrentViewIdx { get; set; }
private void SetView(int viewIndex) =>
CurrentViewIdx = viewIndex.CoerceAtLeast(0).CoerceAtMost(Views.Count - 1);
public UiLayer CurrentLayer => Layers[CurrentLayerIdx];
public UiView CurrentView => Views[CurrentViewIdx];
private UiManager(CreateParams @params)
{
Layers = @params.Layers;
unsafe
{
Theme = @params.Theme;
}
_window = Window.Create(@params.WOptions);
_fontConfig = @params.Font;
_onConfigureIO = @params.OnConfigureIO;
_windowIcon = @params.WindowIcon;
_window.Load += OnWindowLoad;
@ -73,33 +78,35 @@ public sealed partial class UiManager
// shoutout https://gist.github.com/moebiussurfing/8dbc7fef5964adcd29428943b78e45d2
// for showing me how to properly setup dock space
const ImGuiWindowFlags windowFlags =
ImGuiWindowFlags.MenuBar | ImGuiWindowFlags.NoDocking | ImGuiWindowFlags.NoTitleBar |
var windowFlags =
ImGuiWindowFlags.NoDocking | ImGuiWindowFlags.NoTitleBar |
ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoMove |
ImGuiWindowFlags.NoBringToFrontOnFocus | ImGuiWindowFlags.NoNavFocus;
var currView = CurrentView;
if (currView.MainMenuBar != null)
windowFlags |= ImGuiWindowFlags.MenuBar;
var viewport = ImGui.GetMainViewport();
ImGui.SetNextWindowPos(viewport.WorkPos);
ImGui.SetNextWindowSize(viewport.WorkSize);
ImGui.SetNextWindowViewport(viewport.ID);
using (var _ = new ScopedStyleVar(ImGuiStyleVar.WindowRounding, 0f))
using (var __ = new ScopedStyleVar(ImGuiStyleVar.WindowBorderSize, 0f))
using (new ScopedStyleVar(ImGuiStyleVar.WindowRounding, 0f))
using (new ScopedStyleVar(ImGuiStyleVar.WindowBorderSize, 0f))
ImGui.Begin("Dock Space", windowFlags);
ImGui.DockSpace(ImGui.GetID("DockSpace"), Vector2.Zero);
var currentLayer = CurrentLayer;
if (currentLayer.MainMenuBar is { } menuBar)
if (currView.MainMenuBar is { } menuBar)
if (ImGui.BeginMenuBar())
{
menuBar(delta);
ImGui.EndMenuBar();
}
currentLayer.RenderInternal(delta);
currView.RenderInternal(delta);
ImGui.End();
@ -112,11 +119,13 @@ public sealed partial class UiManager
public readonly struct CreateParams
{
public readonly WindowOptions WOptions { get; init; }
public readonly UiLayer[] Layers { get; init; }
public readonly ImGuiFontConfig Font { get; init; }
public readonly Image<Rgba32>? WindowIcon { get; init; }
public readonly string ThreadName { get; init; }
public WindowOptions WOptions { get; init; }
public Action<ImGuiIOPtr> OnConfigureIO { get; init; }
public Image<Rgba32>? WindowIcon { get; init; }
public unsafe ThemedColors* Theme { get; init; }
public string ThreadName { get; init; }
}
public static bool TryCreateUi(CreateParams createParams, out Exception? error)
@ -130,14 +139,6 @@ public sealed partial class UiManager
try
{
Instance = new UiManager(createParams);
// declared as illegal code by the Silk God (Main thread isn't the controller of the Window)
new Thread(() =>
{
Instance.Run(); //returns when UI is closed
Instance.Dispose();
Instance = null;
}) { Name = createParams.ThreadName }.Start();
}
catch (Exception e)
{
@ -149,4 +150,17 @@ public sealed partial class UiManager
return true;
}
public static void StartThread(string threadName)
{
// declared as illegal code by the Silk God (Main thread isn't the controller of the Window)
new Thread(() =>
{
Instance?.Run(); //returns when UI is closed
Instance?.Dispose();
Instance = null;
}) { Name = threadName }.Start();
}
public static void AddView(UiView view) => Instance!.Views.Add(view);
}

View file

@ -5,11 +5,27 @@ namespace Volte.UI;
// https://github.com/adobe/imgui/blob/master/imgui_spectrum.h
public static class Spectrum
{
public static ThemedColors Dark = new DarkThemedColors();
public static ThemedColors Light = new LightThemeColors();
public static unsafe ThemedColors* Dark
{
get
{
fixed (DarkThemedColors* ptr = &DarkThemedColors.Instance)
return (ThemedColors*)ptr;
}
}
public static unsafe ThemedColors* Light
{
get
{
fixed (LightThemedColors* ptr = &LightThemedColors.Instance)
return (ThemedColors*)ptr;
}
}
public class DarkThemedColors : ThemedColors
{
internal static DarkThemedColors Instance = new();
internal override Color Gray50 => Color(0x252525);
internal override Color Gray75 => Color(0x2F2F2F);
internal override Color Gray100 => Color(0x323232);
@ -71,8 +87,9 @@ public static class Spectrum
internal override Color Purple700 => Color(0xB483F0);
}
public class LightThemeColors : ThemedColors
public class LightThemedColors : ThemedColors
{
internal static LightThemedColors Instance = new();
internal override Color Gray50 => Color(0xFFFFFF);
internal override Color Gray75 => Color(0xFAFAFA);
internal override Color Gray100 => Color(0xF5F5F5);

View file

@ -9,7 +9,7 @@ using Silk.NET.SDL;
namespace Volte.UI;
public abstract class UiLayer
public abstract class UiView
{
private readonly List<Action<double>> _panels = [];
@ -56,77 +56,6 @@ public abstract class UiLayer
foreach (var renderPanel in _panels)
renderPanel(delta);
}
public static void SetColors(ref ThemedColors theme, bool dark)
{
var style = ImGui.GetStyle();
style.GrabRounding = 4f;
style.FrameRounding = 6f;
style.WindowMenuButtonPosition = ImGuiDir.None;
style.FrameBorderSize = 1f;
style.TabBorderSize = 1f;
style.WindowTitleAlign = new Vector2(0.5f);
style.SeparatorTextBorderSize = 9f;
set(ImGuiCol.Text, theme.Gray800);
set(ImGuiCol.TextDisabled, theme.Gray500);
set(ImGuiCol.WindowBg, theme.Gray100);
set(ImGuiCol.ChildBg, Spectrum.Static.None);
set(ImGuiCol.PopupBg, theme.Gray50);
set(ImGuiCol.Border, theme.Gray300);
set(ImGuiCol.BorderShadow, Spectrum.Static.None);
set(ImGuiCol.FrameBg, theme.Gray75);
set(ImGuiCol.FrameBgHovered, theme.Gray50);
set(ImGuiCol.FrameBgActive, theme.Gray200);
set(ImGuiCol.TitleBg, theme.Gray300);
set(ImGuiCol.TitleBgActive, theme.Gray200);
set(ImGuiCol.TitleBgCollapsed, theme.Gray400);
set(ImGuiCol.TabUnfocusedActive, theme.Blue400);
set(ImGuiCol.MenuBarBg, theme.Gray100);
set(ImGuiCol.ScrollbarBg, theme.Gray100);
set(ImGuiCol.ScrollbarGrab, theme.Gray400);
set(ImGuiCol.ScrollbarGrabHovered, theme.Gray600);
set(ImGuiCol.ScrollbarGrabActive, theme.Gray700);
set(ImGuiCol.CheckMark, theme.Blue500);
set(ImGuiCol.SliderGrab, theme.Gray700);
set(ImGuiCol.SliderGrabActive, theme.Gray800);
set(ImGuiCol.Button, theme.Gray75);
set(ImGuiCol.ButtonHovered, theme.Gray50);
set(ImGuiCol.ButtonActive, theme.Gray200);
set(ImGuiCol.Header, theme.Blue400);
set(ImGuiCol.HeaderHovered, theme.Blue500);
set(ImGuiCol.HeaderActive, theme.Blue600);
set(ImGuiCol.Separator, theme.Gray400);
set(ImGuiCol.SeparatorHovered, theme.Gray600);
set(ImGuiCol.SeparatorActive, theme.Gray700);
set(ImGuiCol.ResizeGrip, theme.Gray400);
set(ImGuiCol.ResizeGripHovered, theme.Gray600);
set(ImGuiCol.ResizeGripActive, theme.Gray700);
set(ImGuiCol.PlotLines, theme.Blue400);
set(ImGuiCol.PlotLinesHovered, theme.Blue600);
set(ImGuiCol.PlotHistogram, theme.Blue400);
set(ImGuiCol.PlotHistogramHovered, theme.Blue600);
setVec(ImGuiCol.TextSelectedBg, ImGui.ColorConvertU32ToFloat4((colorValue(theme.Blue400) & 0x00FFFFFF) | 0x33000000));
setVec(ImGuiCol.DragDropTarget, new Vector4(1.00f, 1.00f, 0.00f, 0.90f));
setVec(ImGuiCol.NavHighlight, ImGui.ColorConvertU32ToFloat4((colorValue(theme.Gray900) & 0x00FFFFFF) | 0x0A000000));
setVec(ImGuiCol.NavWindowingHighlight, new Vector4(1.00f, 1.00f, 1.00f, 0.70f));
setVec(ImGuiCol.NavWindowingDimBg, new Vector4(0.80f, 0.80f, 0.80f, 0.20f));
setVec(ImGuiCol.ModalWindowDimBg, new Vector4(0.20f, 0.20f, 0.20f, 0.35f));
return;
void set(ImGuiCol colorVar, Color color)
=> setVec(colorVar, new Vector4(color.R / 255f, color.G / 255f, color.B / 255f, 1f));
void setVec(ImGuiCol colorVar, Vector4 colorVec)
=> style.Colors[(int)colorVar] = colorVec;
uint colorValue(Color color) => ((uint)color.R << 16)
| ((uint)color.G << 8)
| color.B;
}
protected static void Await(Func<Task> task) => UiManager.Instance!.TaskQueue.Enqueue(task);
protected static void Await(Task task) => Await(() => task);