mirror of
https://github.com/Ryubing/RyujinxHelper.git
synced 2025-05-12 18:20:36 +01:00
Avalonia UI shell. Based on TKMM.
ImGui was very fun to use but ultimately, it's very intensive for what it is. It showed 3 panes and sat at 20% CPU usage on my 13700F. I'm not sure if my code was just bad or if this is how ImGui is.
This commit is contained in:
parent
e4e66f3ec3
commit
13cd4d264e
29 changed files with 455 additions and 1191 deletions
18
.vscode/launch.json
vendored
18
.vscode/launch.json
vendored
|
@ -1,6 +1,22 @@
|
|||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
// Use IntelliSense to find out which attributes exist for C# debugging
|
||||
// Use hover for the description of the existing attributes
|
||||
// For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
|
||||
"name": ".NET Core Launch (console)",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"preLaunchTask": "build",
|
||||
// If you have changed target frameworks, make sure to update the program path.
|
||||
"program": "${workspaceFolder}/src/UI/bin/Debug/net8.0/Volte.UI.dll",
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}/src/UI/bin/Debug/net8.0",
|
||||
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
|
||||
"console": "integratedTerminal",
|
||||
"stopAtEntry": false
|
||||
},
|
||||
{
|
||||
// Use IntelliSense to find out which attributes exist for C# debugging
|
||||
// Use hover for the description of the existing attributes
|
||||
|
@ -11,7 +27,7 @@
|
|||
"preLaunchTask": "build",
|
||||
// If you have changed target frameworks, make sure to update the program path.
|
||||
"program": "${workspaceFolder}/src/Bot/bin/Debug/net8.0/Volte.dll",
|
||||
"args": ["--ui"],
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}/src/Bot/bin/Debug/net8.0",
|
||||
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
|
||||
"console": "integratedTerminal",
|
||||
|
|
18
Volte.sln
18
Volte.sln
|
@ -5,7 +5,7 @@ VisualStudioVersion = 16.0.28721.148
|
|||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volte", "src\Bot\Volte.csproj", "{5D4A85B0-1326-4CA2-A26C-D646D9579342}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volte.UI", "src\UI\Volte.UI.csproj", "{70624B23-1B94-4801-9536-DD4FE86E6671}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volte.UI", "src\UI\Volte.UI.csproj", "{BCD42127-89FE-44F3-AD2F-D224ED94B03A}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
|
@ -23,14 +23,14 @@ Global
|
|||
{5D4A85B0-1326-4CA2-A26C-D646D9579342}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{5D4A85B0-1326-4CA2-A26C-D646D9579342}.Release|x64.ActiveCfg = Release|x64
|
||||
{5D4A85B0-1326-4CA2-A26C-D646D9579342}.Release|x64.Build.0 = Release|x64
|
||||
{70624B23-1B94-4801-9536-DD4FE86E6671}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{70624B23-1B94-4801-9536-DD4FE86E6671}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{70624B23-1B94-4801-9536-DD4FE86E6671}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{70624B23-1B94-4801-9536-DD4FE86E6671}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{70624B23-1B94-4801-9536-DD4FE86E6671}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{70624B23-1B94-4801-9536-DD4FE86E6671}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{70624B23-1B94-4801-9536-DD4FE86E6671}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{70624B23-1B94-4801-9536-DD4FE86E6671}.Release|x64.Build.0 = Release|Any CPU
|
||||
{BCD42127-89FE-44F3-AD2F-D224ED94B03A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{BCD42127-89FE-44F3-AD2F-D224ED94B03A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{BCD42127-89FE-44F3-AD2F-D224ED94B03A}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{BCD42127-89FE-44F3-AD2F-D224ED94B03A}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{BCD42127-89FE-44F3-AD2F-D224ED94B03A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{BCD42127-89FE-44F3-AD2F-D224ED94B03A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{BCD42127-89FE-44F3-AD2F-D224ED94B03A}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{BCD42127-89FE-44F3-AD2F-D224ED94B03A}.Release|x64.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
using Volte.UI;
|
||||
|
||||
namespace Volte.Commands.Text.Modules;
|
||||
|
||||
public sealed partial class BotOwnerModule
|
||||
{
|
||||
[Command("CreateUi", "Cui")]
|
||||
[Description("Create the ImGui UI on the machine running Volte.")]
|
||||
public Task<ActionResult> UiAsync(
|
||||
[Description("Desired font size of the UI.")] int fontSize = 17)
|
||||
{
|
||||
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());
|
||||
UiManager.StartThread("Volte UI Thread");
|
||||
return None(() => Context.Message.AddReactionAsync(Emojis.BallotBoxWithCheck));
|
||||
|
||||
}
|
||||
}
|
12
src/Bot/Core/Entities/EventArgs/VolteLogEventArgs.cs
Normal file
12
src/Bot/Core/Entities/EventArgs/VolteLogEventArgs.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
namespace Volte.Entities;
|
||||
|
||||
#nullable enable
|
||||
|
||||
public class VolteLogEventArgs
|
||||
{
|
||||
public required LogSeverity Severity;
|
||||
public required LogSource Source;
|
||||
public required string Message;
|
||||
public required string[] PrintedLines;
|
||||
public required Exception? Error;
|
||||
}
|
|
@ -1,13 +1,4 @@
|
|||
using System.IO;
|
||||
using ImGuiNET;
|
||||
using Silk.NET.Maths;
|
||||
using Silk.NET.OpenGL.Extensions.ImGui;
|
||||
using Silk.NET.Windowing;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using Volte.Commands.Text.Modules;
|
||||
using Volte.UI;
|
||||
using Image = SixLabors.ImageSharp.Image;
|
||||
|
||||
namespace Volte;
|
||||
|
||||
|
@ -15,7 +6,7 @@ public class VolteBot
|
|||
{
|
||||
public static Task StartAsync()
|
||||
{
|
||||
Console.Title = DefaultWindowOptions.Title;
|
||||
Console.Title = $"Volte {Version.InformationVersion}";
|
||||
Console.CursorVisible = false;
|
||||
return new VolteBot().LoginAsync();
|
||||
}
|
||||
|
@ -39,9 +30,6 @@ public class VolteBot
|
|||
|
||||
ServiceProvider = new ServiceCollection().AddAllServices().BuildServiceProvider();
|
||||
|
||||
if (Program.CommandLineArguments.TryGetValue("ui", out var sizeStr))
|
||||
CreateUi(sizeStr);
|
||||
|
||||
_client = ServiceProvider.Get<DiscordSocketClient>();
|
||||
_cts = ServiceProvider.Get<CancellationTokenSource>();
|
||||
|
||||
|
@ -96,64 +84,4 @@ public class VolteBot
|
|||
|
||||
Environment.Exit(0);
|
||||
}
|
||||
|
||||
// WindowOptions.Default with custom title and larger base window
|
||||
public static readonly WindowOptions DefaultWindowOptions = new(
|
||||
isVisible: true,
|
||||
position: new Vector2D<int>(50, 50),
|
||||
size: new Vector2D<int>(1600, 900),
|
||||
framesPerSecond: 0,
|
||||
updatesPerSecond: 0.0,
|
||||
api: GraphicsAPI.Default,
|
||||
title: $"Volte {Version.InformationVersion}",
|
||||
windowState: WindowState.Normal,
|
||||
windowBorder: WindowBorder.Resizable,
|
||||
isVSync: true,
|
||||
shouldSwapAutomatically: true,
|
||||
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());
|
||||
UiManager.StartThread("Volte UI Thread");
|
||||
}
|
||||
else Error(LogSource.UI, $"Could not create UI: {uiStartError!.Message}");
|
||||
}
|
||||
|
||||
private static readonly string[] UiFontResourceKeys = [ "Regular", "Bold", "BoldItalic", "Italic" ];
|
||||
|
||||
public static UiManager.CreateParams GetUiParams(int fontSize)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return new UiManager.CreateParams
|
||||
{
|
||||
WindowIcon = loadIcon(),
|
||||
WOptions = DefaultWindowOptions,
|
||||
Theme = Spectrum.Dark,
|
||||
OnConfigureIo = _ =>
|
||||
{
|
||||
UiFontResourceKeys.ForEach(key =>
|
||||
{
|
||||
using var embeddedFont = Assembly.GetExecutingAssembly().GetManifestResourceStream(key);
|
||||
if (embeddedFont != null)
|
||||
UiManager.LoadFontFromStream(embeddedFont, key, fontSize);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Image<Rgba32> loadIcon()
|
||||
{
|
||||
using var iconStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("VolteIcon");
|
||||
return iconStream == null
|
||||
? null
|
||||
: Image.Load<Rgba32>(iconStream);
|
||||
}
|
||||
}
|
||||
}
|
68
src/Bot/Helpers/Event.cs
Normal file
68
src/Bot/Helpers/Event.cs
Normal file
|
@ -0,0 +1,68 @@
|
|||
using System.Collections.Immutable;
|
||||
|
||||
namespace Volte.Helpers;
|
||||
|
||||
internal class Event<T>
|
||||
where T : class
|
||||
{
|
||||
private readonly object _subLock = new();
|
||||
private ImmutableArray<T> _subscriptions = [];
|
||||
|
||||
public bool HasSubscribers
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_subLock)
|
||||
return _subscriptions.Length != 0;
|
||||
}
|
||||
}
|
||||
|
||||
public IReadOnlyList<T> Subscriptions
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_subLock)
|
||||
return _subscriptions;
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(T subscriber)
|
||||
{
|
||||
Guard.Require(subscriber, nameof(subscriber));
|
||||
lock (_subLock)
|
||||
_subscriptions = _subscriptions.Add(subscriber);
|
||||
}
|
||||
|
||||
public void Remove(T subscriber)
|
||||
{
|
||||
Guard.Require(subscriber, nameof(subscriber));
|
||||
lock (_subLock)
|
||||
_subscriptions = _subscriptions.Remove(subscriber);
|
||||
}
|
||||
}
|
||||
|
||||
internal static class EventExtensions
|
||||
{
|
||||
public static void Call(this Event<Action> eventHandler)
|
||||
=> eventHandler.Subscriptions.ForEach(x => x());
|
||||
|
||||
public static void Call<T>(this Event<Action<T>> eventHandler,
|
||||
T arg
|
||||
) => eventHandler.Subscriptions.ForEach(x => x(arg));
|
||||
|
||||
public static void Call<T1, T2>(this Event<Action<T1, T2>> eventHandler,
|
||||
T1 arg1, T2 arg2
|
||||
) => eventHandler.Subscriptions.ForEach(x => x(arg1, arg2));
|
||||
|
||||
public static void Call<T1, T2, T3>(this Event<Action<T1, T2, T3>> eventHandler,
|
||||
T1 arg1, T2 arg2, T3 arg3
|
||||
) => eventHandler.Subscriptions.ForEach(x => x(arg1, arg2, arg3));
|
||||
|
||||
public static void Call<T1, T2, T3, T4>(this Event<Action<T1, T2, T3, T4>> eventHandler,
|
||||
T1 arg1, T2 arg2, T3 arg3, T4 arg4
|
||||
) => eventHandler.Subscriptions.ForEach(x => x(arg1, arg2, arg3, arg4));
|
||||
|
||||
public static void Call<T1, T2, T3, T4, T5>(this Event<Action<T1, T2, T3, T4, T5>> eventHandler,
|
||||
T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5
|
||||
) => eventHandler.Subscriptions.ForEach(x => x(arg1, arg2, arg3, arg4, arg5));
|
||||
}
|
|
@ -55,7 +55,7 @@ public static partial class Logger
|
|||
{
|
||||
if (_logFileNoticePrinted || !(Config.EnabledFeatures?.LogToFile ?? false)) return;
|
||||
|
||||
GetRelevantLogPath().AppendAllText($"{Side}RESTARTING{Side}\n");
|
||||
GetLogFilePath(DateTime.Now).AppendAllText($"{Side}RESTARTING{Side}\n");
|
||||
|
||||
_logFileNoticePrinted = true;
|
||||
}
|
||||
|
@ -113,10 +113,14 @@ public static partial class Logger
|
|||
if (e != null)
|
||||
{
|
||||
e.SentryCapture(scope =>
|
||||
scope.AddBreadcrumb("This exception might not have been thrown, and may not be important; it is merely being logged."));
|
||||
Append(Environment.NewLine + (e.Message.IsNullOrEmpty() ? "No message provided" : e.Message) +
|
||||
Environment.NewLine + e.StackTrace,
|
||||
Color.IndianRed, ref content);
|
||||
scope.AddBreadcrumb("This exception might not have been thrown, and may not be important; it is merely being logged.")
|
||||
);
|
||||
|
||||
Append(errorString(), Color.IndianRed, ref content);
|
||||
|
||||
string errorString()
|
||||
=> Environment.NewLine + (e.Message.IsNullOrEmpty() ? "No message provided" : e.Message) +
|
||||
Environment.NewLine + e.StackTrace;
|
||||
}
|
||||
|
||||
if (Environment.NewLine != content[^1].ToString())
|
||||
|
@ -126,12 +130,22 @@ public static partial class Logger
|
|||
}
|
||||
|
||||
if (Config.EnabledFeatures?.LogToFile ?? false)
|
||||
GetRelevantLogPath().AppendAllText(content.ToString().TrimEnd('\n').Append("\n"));
|
||||
GetLogFilePath(DateTime.Now).AppendAllText(content.ToString().TrimEnd('\n').Append("\n"));
|
||||
|
||||
if (!_logEventHandler.HasSubscribers) return;
|
||||
|
||||
_logEventHandler.Call(new VolteLogEventArgs
|
||||
{
|
||||
Severity = s,
|
||||
Source = src,
|
||||
Message = message,
|
||||
PrintedLines = content.ToString().TrimEnd('\n').Split('\n', StringSplitOptions.RemoveEmptyEntries),
|
||||
Error = e
|
||||
});
|
||||
}
|
||||
|
||||
private static FilePath GetLogFilePath(DateTime date) => new FilePath("logs") / string.Intern($"{date.Month}-{date.Day}-{date.Year}.log");
|
||||
|
||||
private static FilePath GetRelevantLogPath() => GetLogFilePath(DateTime.Now);
|
||||
private static FilePath GetLogFilePath(DateTime date)
|
||||
=> new FilePath("logs") / string.Intern($"{date.Year}-{date.Month}-{date.Day}.log");
|
||||
|
||||
private static void Append(string m, Color c)
|
||||
{
|
||||
|
|
|
@ -1,12 +1,18 @@
|
|||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Color = System.Drawing.Color;
|
||||
using Optional = Gommon.Optional;
|
||||
|
||||
namespace Volte.Helpers;
|
||||
|
||||
public static partial class Logger
|
||||
{
|
||||
public static event Action<VolteLogEventArgs> LogEvent
|
||||
{
|
||||
add => _logEventHandler.Add(value);
|
||||
remove => _logEventHandler.Remove(value);
|
||||
}
|
||||
|
||||
private static readonly Event<Action<VolteLogEventArgs>> _logEventHandler = new();
|
||||
|
||||
public static bool IsDebugLoggingEnabled => Config.DebugEnabled || Version.IsDevelopment;
|
||||
|
||||
public static void HandleLogEvent(LogEventArgs args) =>
|
||||
|
@ -71,8 +77,9 @@ public static partial class Logger
|
|||
/// This method calls <see cref="SentrySdk"/>'s CaptureException, so it is logged to Sentry.
|
||||
/// </summary>
|
||||
/// <param name="e">Exception to print.</param>
|
||||
public static void Error<TData>(Exception e, InvocationInfo<TData> caller)
|
||||
=> Execute(LogSeverity.Error, LogSource.Volte, string.Empty, e, caller);
|
||||
/// <param name="src">Source to print the message from.</param>
|
||||
public static void Error<TData>(Exception e, InvocationInfo<TData> caller, LogSource src = LogSource.Volte)
|
||||
=> Execute(LogSeverity.Error, src, string.Empty, e, caller);
|
||||
|
||||
#endregion
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using Discord.Interactions;
|
||||
using Silk.NET.Input;
|
||||
using Volte.Interactions;
|
||||
using RunMode = Qmmands.RunMode;
|
||||
|
||||
|
|
|
@ -12,7 +12,12 @@ public static class Program
|
|||
if (output.Error is not InvalidOperationException)
|
||||
Error(output.Error);
|
||||
|
||||
CommandLineArguments = new ReadOnlyDictionary<string, string>(output.Parsed ?? new Dictionary<string, string>());
|
||||
await Main(output.Parsed);
|
||||
}
|
||||
|
||||
private static async Task Main(Dictionary<string, string> args)
|
||||
{
|
||||
CommandLineArguments = new ReadOnlyDictionary<string, string>(args ?? new Dictionary<string, string>());
|
||||
await VolteBot.StartAsync();
|
||||
}
|
||||
}
|
|
@ -1,145 +0,0 @@
|
|||
using System.Collections.Immutable;
|
||||
// needed for commented code //using System.Numerics;
|
||||
using ImGuiNET;
|
||||
using Silk.NET.Input;
|
||||
using Color = System.Drawing.Color;
|
||||
|
||||
namespace Volte.UI;
|
||||
|
||||
public partial class VolteUiView
|
||||
{
|
||||
private void CommandStats(double _)
|
||||
{
|
||||
//using var __ = PushStyle(ImGuiStyleVar.WindowMinSize, new Vector2(201, 188));
|
||||
|
||||
ImGui.SeparatorText("Total executions");
|
||||
Gui.SameLineText($"Successful: {_state.Messages.AllTimeSuccessfulCommandCalls}", Color.LawnGreen);
|
||||
Gui.SameLineText("+");
|
||||
Gui.SameLineText($"Failed: {_state.Messages.AllTimeFailedCommandCalls}", Color.OrangeRed);
|
||||
Gui.SameLineText("=");
|
||||
ImGui.Text($"Total: {_state.Messages.AllTimeCommandCalls}");
|
||||
|
||||
ImGui.SeparatorText("This Session");
|
||||
Gui.SameLineText($"Successful: {CalledCommandsInfo.ThisSessionSuccess + _state.Messages.UnsavedSuccessfulCommandCalls}", Color.LawnGreen);
|
||||
Gui.SameLineText("+");
|
||||
Gui.SameLineText($"Failed: {CalledCommandsInfo.ThisSessionFailed + _state.Messages.UnsavedFailedCommandCalls}", Color.OrangeRed);
|
||||
Gui.SameLineText("=");
|
||||
ImGui.Text($"Total: {
|
||||
CalledCommandsInfo.ThisSessionSuccess + CalledCommandsInfo.ThisSessionFailed +
|
||||
_state.Messages.UnsavedFailedCommandCalls + _state.Messages.UnsavedSuccessfulCommandCalls
|
||||
}");
|
||||
}
|
||||
|
||||
private void BotManagement(double _)
|
||||
{
|
||||
//using var __ = PushStyle(ImGuiStyleVar.WindowMinSize, new Vector2(385, 299));
|
||||
Gui.SameLineText("Discord Gateway:");
|
||||
// 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);
|
||||
break;
|
||||
case ConnectionState.Connecting:
|
||||
ColoredText("Connecting...", Color.Yellow);
|
||||
break;
|
||||
case ConnectionState.Disconnecting:
|
||||
ColoredText("Disconnecting...", Color.OrangeRed);
|
||||
break;
|
||||
case ConnectionState.Disconnected:
|
||||
ColoredText("Disconnected!", Color.Red);
|
||||
break;
|
||||
}
|
||||
|
||||
if (_state.Client.ConnectionState == ConnectionState.Connected)
|
||||
{
|
||||
ImGui.Text($"Connected as: {_state.Client.CurrentUser.Username}#{_state.Client.CurrentUser.DiscriminatorValue}");
|
||||
// ToString()ing the CurrentUser has weird question marks on both sides of Volte-dev's name,
|
||||
// so we do it manually in case that happens on other bot accounts too
|
||||
|
||||
var currentStatus = _state.Client.Status;
|
||||
|
||||
if (ImGui.BeginMenu($"Bot status: {currentStatus}"))
|
||||
{
|
||||
if (ImGui.MenuItem("Online", currentStatus != UserStatus.Online))
|
||||
Await(_state.Client.SetStatusAsync(UserStatus.Online));
|
||||
if (ImGui.MenuItem("Idle", currentStatus != UserStatus.Idle))
|
||||
Await(_state.Client.SetStatusAsync(UserStatus.Idle));
|
||||
if (ImGui.MenuItem("Do Not Disturb", currentStatus != UserStatus.DoNotDisturb))
|
||||
Await(_state.Client.SetStatusAsync(UserStatus.DoNotDisturb));
|
||||
if (ImGui.MenuItem("Invisible", currentStatus != UserStatus.Invisible))
|
||||
Await(_state.Client.SetStatusAsync(UserStatus.Invisible));
|
||||
|
||||
ImGui.EndMenu();
|
||||
}
|
||||
}
|
||||
|
||||
var process = Process.GetCurrentProcess();
|
||||
ImGui.Text($"Process memory: {process.GetMemoryUsage()} ({process.GetMemoryUsage(MemoryType.Kilobytes)})");
|
||||
|
||||
if (ImGui.Button("Reload Config"))
|
||||
Config.Reload();
|
||||
}
|
||||
|
||||
#region Guild Manager
|
||||
|
||||
private void GuildManager(double _)
|
||||
{
|
||||
//using var __ = PushStyle(ImGuiStyleVar.WindowMinSize, new Vector2(418, 300));
|
||||
|
||||
if (_state.SelectedGuildId != 0)
|
||||
{
|
||||
var selectedGuild = _state.Client.GetGuild(_state.SelectedGuildId);
|
||||
var selectedGuildMembers = selectedGuild.Users.ToImmutableArray();
|
||||
var botMembers = selectedGuildMembers.Count(sgu => sgu.IsBot);
|
||||
|
||||
ImGui.SeparatorText(selectedGuild.Name);
|
||||
ImGui.Text($"Owner: @{selectedGuild.Owner}");
|
||||
ImGui.Text($"Text Channels: {selectedGuild.TextChannels.Count}");
|
||||
ImGui.Text($"Voice Channels: {selectedGuild.VoiceChannels.Count}");
|
||||
|
||||
Gui.SameLineText($"{selectedGuildMembers.Length} members |");
|
||||
Gui.SameLineText($"{selectedGuildMembers.Length - botMembers} users", Color.LawnGreen);
|
||||
Gui.SameLineText("|");
|
||||
ColoredText($"{botMembers} bots", Color.OrangeRed);
|
||||
|
||||
ImGui.Separator();
|
||||
|
||||
var destructiveMenuEnabled = AllKeysPressed(Key.ShiftLeft, Key.ControlLeft);
|
||||
|
||||
if (ImGui.BeginMenu("Destructive Actions (Shift + Ctrl)", destructiveMenuEnabled))
|
||||
{
|
||||
if (ImGui.MenuItem("Leave Guild", destructiveMenuEnabled))
|
||||
{
|
||||
Await(selectedGuild.LeaveAsync());
|
||||
_state.SelectedGuildId = 0; //resets this pane back to just the "select a guild" button
|
||||
}
|
||||
|
||||
if (ImGui.MenuItem("Reset Configuration", destructiveMenuEnabled))
|
||||
_state.Database.Save(GuildData.CreateFrom(selectedGuild));
|
||||
|
||||
ImGui.EndMenu();
|
||||
}
|
||||
|
||||
ImGui.Separator();
|
||||
}
|
||||
|
||||
GuildSelect();
|
||||
}
|
||||
|
||||
private void GuildSelect()
|
||||
{
|
||||
if (!ImGui.BeginMenu("Select a Guild")) return;
|
||||
|
||||
_state.Client.Guilds.ForEach(guild =>
|
||||
{
|
||||
if (ImGui.MenuItem(guild.Name, guild.Id != _state.SelectedGuildId))
|
||||
_state.SelectedGuildId = guild.Id;
|
||||
});
|
||||
|
||||
ImGui.EndMenu();
|
||||
}
|
||||
|
||||
#endregion Guild Manager
|
||||
}
|
|
@ -1,85 +0,0 @@
|
|||
using ImGuiNET;
|
||||
using Color = System.Drawing.Color;
|
||||
// ReSharper disable InvertIf
|
||||
|
||||
namespace Volte.UI;
|
||||
|
||||
public sealed class VolteUiState
|
||||
{
|
||||
public bool SelectedTheme = true;
|
||||
public bool ShowStyleEditor;
|
||||
|
||||
public VolteUiState(IServiceProvider provider)
|
||||
{
|
||||
Cts = provider.Get<CancellationTokenSource>();
|
||||
Client = provider.Get<DiscordSocketClient>();
|
||||
Messages = provider.Get<MessageService>();
|
||||
Database = provider.Get<DatabaseService>();
|
||||
}
|
||||
|
||||
public readonly CancellationTokenSource Cts;
|
||||
public readonly DiscordSocketClient Client;
|
||||
public readonly MessageService Messages;
|
||||
public readonly DatabaseService Database;
|
||||
|
||||
public ulong SelectedGuildId = 0;
|
||||
}
|
||||
|
||||
public partial class VolteUiView : UiView
|
||||
{
|
||||
private readonly VolteUiState _state;
|
||||
|
||||
public VolteUiView()
|
||||
{
|
||||
_state = new VolteUiState(VolteBot.ServiceProvider);
|
||||
|
||||
MainMenuBar = MenuBar;
|
||||
|
||||
Panel("Command Stats", CommandStats);
|
||||
Panel("Bot Management", BotManagement);
|
||||
Panel("Guild Manager", GuildManager);
|
||||
Panel(_ =>
|
||||
{
|
||||
if (_state.ShowStyleEditor)
|
||||
{
|
||||
ImGui.Begin("Style Editor");
|
||||
ImGui.ShowStyleEditor(ImGui.GetStyle());
|
||||
ImGui.End();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void MenuBar(double delta)
|
||||
{
|
||||
if (ImGui.BeginMenu("File"))
|
||||
{
|
||||
if (ImGui.Button("Shutdown"))
|
||||
_state.Cts.Cancel();
|
||||
|
||||
ImGui.EndMenu();
|
||||
}
|
||||
|
||||
if (ImGui.BeginMenu("Theming"))
|
||||
{
|
||||
if (ImGui.MenuItem(_state.SelectedTheme ? "Swap to Light" : "Swap to Dark"))
|
||||
{
|
||||
_state.SelectedTheme = !_state.SelectedTheme;
|
||||
unsafe
|
||||
{
|
||||
UiManager.SetColors(_state.SelectedTheme ? Spectrum.Dark : Spectrum.Light);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (ImGui.RadioButton("Show Style Editor", _state.ShowStyleEditor))
|
||||
_state.ShowStyleEditor = !_state.ShowStyleEditor;
|
||||
|
||||
ImGui.EndMenu();
|
||||
}
|
||||
|
||||
ImGui.BeginMenu($"{Io.Framerate:###} FPS", false);
|
||||
}
|
||||
|
||||
private static void ColoredText(string fmt, Color color) =>
|
||||
ImGui.TextColored(color.AsVec4(), fmt);
|
||||
}
|
|
@ -31,10 +31,10 @@
|
|||
<EmbeddedResource Include="Resources/Mono/JetBrainsMonoNL-Italic.ttf" LogicalName="Italic" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Colorful.Console" Version="1.2.15" />
|
||||
<PackageReference Include="Discord.Net.Interactions" Version="3.15.3" />
|
||||
<PackageReference Include="Discord.Net.WebSocket" Version="3.15.3" />
|
||||
<PackageReference Include="Gommon" Version="2.6.4" />
|
||||
<PackageReference Include="GreemDev.Colorful.Console" Version="1.3.0" />
|
||||
<PackageReference Include="Humanizer.Core" Version="2.14.1" />
|
||||
<PackageReference Include="LiteDB" Version="5.0.21" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.10.0" />
|
||||
|
@ -43,9 +43,6 @@
|
|||
<PackageReference Include="Sentry" Version="4.9.0" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.5" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../UI/Volte.UI.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Resources\" />
|
||||
</ItemGroup>
|
||||
|
|
26
src/UI/Avalonia/App.axaml
Normal file
26
src/UI/Avalonia/App.axaml
Normal file
|
@ -0,0 +1,26 @@
|
|||
<Application xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:sty="using:FluentAvalonia.Styling"
|
||||
x:Class="Volte.UI.App">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.ThemeDictionaries>
|
||||
<ResourceDictionary x:Key="Light">
|
||||
<SolidColorBrush x:Key="SystemChromeHighColor">#FFACACAC</SolidColorBrush>
|
||||
<SolidColorBrush x:Key="SystemChromeLowColor">Transparent</SolidColorBrush>
|
||||
<SolidColorBrush x:Key="SystemChromeMediumColor">#FFF2F2F2</SolidColorBrush>
|
||||
<SolidColorBrush x:Key="SystemChromeMediumLowColor">#FFE6E6E6</SolidColorBrush>
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary x:Key="Dark">
|
||||
<SolidColorBrush x:Key="SystemChromeHighColor">#FF737373</SolidColorBrush>
|
||||
<SolidColorBrush x:Key="SystemChromeLowColor">Transparent</SolidColorBrush>
|
||||
<SolidColorBrush x:Key="SystemChromeMediumColor">#FF2B2B2B</SolidColorBrush>
|
||||
<SolidColorBrush x:Key="SystemChromeMediumLowColor">#FF1F1F1F</SolidColorBrush>
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary.ThemeDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
<Application.Styles>
|
||||
<sty:FluentAvaloniaTheme PreferUserAccentColor="True" />
|
||||
</Application.Styles>
|
||||
</Application>
|
23
src/UI/Avalonia/App.axaml.cs
Normal file
23
src/UI/Avalonia/App.axaml.cs
Normal file
|
@ -0,0 +1,23 @@
|
|||
using Avalonia;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace Volte.UI;
|
||||
|
||||
public class App : Application
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public override void OnFrameworkInitializationCompleted()
|
||||
{
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
desktop.MainWindow = new UIShellView();
|
||||
}
|
||||
|
||||
base.OnFrameworkInitializationCompleted();
|
||||
}
|
||||
}
|
71
src/UI/Avalonia/UIShellView.axaml
Normal file
71
src/UI/Avalonia/UIShellView.axaml
Normal file
|
@ -0,0 +1,71 @@
|
|||
<faw:AppWindow xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:fac="using:FluentAvalonia.UI.Controls"
|
||||
xmlns:faw="using:FluentAvalonia.UI.Windowing"
|
||||
xmlns:v="using:Volte"
|
||||
xmlns:local="using:Volte.UI"
|
||||
xmlns:h="using:Volte.UI.Helpers"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Volte.UI.UIShellView"
|
||||
Title="{Binding Title}">
|
||||
<faw:AppWindow.DataContext>
|
||||
<local:UIShellViewModel />
|
||||
</faw:AppWindow.DataContext>
|
||||
<Grid Background="Transparent" RowDefinitions="32,*,25">
|
||||
<Grid ColumnDefinitions="Auto,*">
|
||||
<Viewbox Width="26"
|
||||
Height="48"
|
||||
MaxWidth="48"
|
||||
MinWidth="48"
|
||||
Margin="3,4"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Stretch="Fill">
|
||||
<Grid>
|
||||
<Viewbox Width="12"
|
||||
Height="12"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center">
|
||||
<Border Padding="25">
|
||||
<Image Source="{Binding Icon}"></Image>
|
||||
</Border>
|
||||
</Viewbox>
|
||||
</Grid>
|
||||
</Viewbox>
|
||||
|
||||
<Menu Name="MainMenu" Grid.Column="1" />
|
||||
</Grid>
|
||||
<fac:NavigationView Name="MainNavigation"
|
||||
Grid.Row="1"
|
||||
FooterMenuItemsSource="{Binding FooterPages, Source={x:Static h:PageManager.Shared}}"
|
||||
IsSettingsVisible="False"
|
||||
MenuItemsSource="{Binding Pages, Source={x:Static h:PageManager.Shared}}"
|
||||
PaneDisplayMode="LeftCompact"
|
||||
PaneTitle="{Binding Title}"
|
||||
SelectedItem="{Binding Current, Mode=TwoWay, Source={x:Static h:PageManager.Shared}}">
|
||||
<fac:NavigationView.MenuItemTemplate>
|
||||
<DataTemplate x:DataType="h:PageData">
|
||||
<fac:NavigationViewItem Content="{Binding Title}"
|
||||
IconSource="{Binding Icon}"
|
||||
Tag="{Binding Content}"
|
||||
ToolTip.Tip="{Binding Description}" />
|
||||
</DataTemplate>
|
||||
</fac:NavigationView.MenuItemTemplate>
|
||||
</fac:NavigationView>
|
||||
<Grid Grid.Row="2"
|
||||
ColumnDefinitions="Auto,Auto,*,Auto,Auto"
|
||||
IsHitTestVisible="False">
|
||||
<Border Grid.ColumnSpan="5" Background="{DynamicResource SystemAccentColor}" />
|
||||
<TextBlock Grid.Column="3"
|
||||
Margin="5,0"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="12">
|
||||
<Run Text="{Binding Title}" />
|
||||
<Run Text=" |" />
|
||||
<Run Text="{Binding Source={x:Static v:Version.InformationVersion}}" />
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</faw:AppWindow>
|
37
src/UI/Avalonia/UIShellView.axaml.cs
Normal file
37
src/UI/Avalonia/UIShellView.axaml.cs
Normal file
|
@ -0,0 +1,37 @@
|
|||
using Avalonia;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Media.Imaging;
|
||||
using Avalonia.Platform;
|
||||
using FluentAvalonia.UI.Windowing;
|
||||
using Gommon;
|
||||
|
||||
namespace Volte.UI;
|
||||
|
||||
public partial class UIShellView : AppWindow
|
||||
{
|
||||
public UIShellView()
|
||||
{
|
||||
InitializeComponent();
|
||||
TitleBar.ExtendsContentIntoTitleBar = true;
|
||||
TitleBar.TitleBarHitTestType = TitleBarHitTestType.Complex;
|
||||
|
||||
using var bitmap = new Bitmap(AssetLoader.Open(new Uri("avares://Volte.UI/Assets/icon.ico")));
|
||||
Icon = bitmap.CreateScaledBitmap(new PixelSize(48, 48));
|
||||
|
||||
DataContext = new UIShellViewModel
|
||||
{
|
||||
OpenDevTools = new KeyGesture(Key.F4, KeyModifiers.Control),
|
||||
Icon = Icon
|
||||
};
|
||||
|
||||
#if DEBUG
|
||||
this.AttachDevTools(DataContext.Cast<UIShellViewModel>().OpenDevTools);
|
||||
#endif
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
20
src/UI/Avalonia/ViewModels/UIShellViewModel.cs
Normal file
20
src/UI/Avalonia/ViewModels/UIShellViewModel.cs
Normal file
|
@ -0,0 +1,20 @@
|
|||
using Avalonia.Input;
|
||||
using Avalonia.Media;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace Volte.UI;
|
||||
|
||||
public partial class UIShellViewModel : ObservableObject
|
||||
{
|
||||
public KeyGesture OpenDevTools { get; init; }
|
||||
|
||||
public IImage Icon { get; init; }
|
||||
|
||||
[ObservableProperty]
|
||||
private string _title = "Volte";
|
||||
|
||||
public UIShellViewModel()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
|
@ -1,73 +0,0 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using Gommon;
|
||||
using Silk.NET.SDL;
|
||||
|
||||
namespace Volte.UI;
|
||||
|
||||
public static class Extensions
|
||||
{
|
||||
#region Color
|
||||
public static System.Drawing.Color AsColor(this Vector3 vec3)
|
||||
=> System.Drawing.Color.FromArgb(255,
|
||||
(int)(vec3.X.CoerceAtMost(1) * 255),
|
||||
(int)(vec3.Y.CoerceAtMost(1) * 255),
|
||||
(int)(vec3.Z.CoerceAtMost(1) * 255));
|
||||
|
||||
public static Vector3 AsVec3(this System.Drawing.Color color)
|
||||
=> new(color.R / 255f, color.G / 255f, color.B / 255f);
|
||||
|
||||
public static Vector4 AsVec4(this System.Drawing.Color color)
|
||||
=> new(color.R / 255f, color.G / 255f, color.B / 255f, color.A / 255f);
|
||||
|
||||
public static Vector3 AsVec3(this Color color)
|
||||
=> new(color.R / 255f, color.G / 255f, color.B / 255f);
|
||||
|
||||
public static Vector4 AsVec4(this Color color)
|
||||
=> new(color.R / 255f, color.G / 255f, color.B / 255f, color.A / 255f);
|
||||
|
||||
#endregion Color
|
||||
}
|
||||
|
||||
public static class Buffers
|
||||
{
|
||||
public static void CopyBytesTo(this string str, Span<byte> span, Encoding encoding = null)
|
||||
{
|
||||
encoding ??= Encoding.UTF8;
|
||||
var bytes = encoding.GetBytes(str);
|
||||
|
||||
bytes.CopyTo(span);
|
||||
span[bytes.Length] = 0; //null terminator
|
||||
}
|
||||
|
||||
public static unsafe void CopyBytesTo(this string str, byte* buffer, int bufferSize)
|
||||
=> CopyBytesTo(str, new Span<byte>(buffer, bufferSize));
|
||||
|
||||
/**
|
||||
* Reads the <see cref="Stream"/> into a <see cref="Span{T}"/> of bytes.
|
||||
* Will throw by default if the read byte count is less than the <see cref="Stream"/>'s length.
|
||||
* <param name="stream">The stream to read from.</param>
|
||||
* <param name="throwOnUnderRead">Whether to throw if the read byte count is less than the stream's length.</param>
|
||||
* <param name="fromStart">Whether to seek to the beginning of the stream before reading.</param>
|
||||
*/
|
||||
public static Span<byte> ToSpan(this Stream stream, bool throwOnUnderRead = true, bool fromStart = true)
|
||||
{
|
||||
if (fromStart)
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
var data = new byte[stream.Length];
|
||||
|
||||
// no using is a deliberate choice here; the user is responsible for disposing the stream,
|
||||
// plus that might not be a desired side effect of calling this.
|
||||
var read = new BinaryReader(stream).Read(data, 0, data.Length);
|
||||
if (throwOnUnderRead && read != data.Length)
|
||||
throw new InvalidDataException("Could not read all bytes from the stream.");
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
public static unsafe void CopyBytesFromString(byte* buffer, int bufferSize, string str)
|
||||
=> str.CopyBytesTo(buffer, bufferSize);
|
||||
}
|
73
src/UI/Helpers/PageManager.cs
Normal file
73
src/UI/Helpers/PageManager.cs
Normal file
|
@ -0,0 +1,73 @@
|
|||
using System.Collections.ObjectModel;
|
||||
using Avalonia.Controls;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
|
||||
namespace Volte.UI.Helpers;
|
||||
|
||||
public partial class PageManager : ObservableObject
|
||||
{
|
||||
// ReSharper disable once InconsistentNaming
|
||||
private static readonly Lazy<PageManager> _shared = new(() => new PageManager());
|
||||
public static PageManager Shared => _shared.Value;
|
||||
|
||||
[ObservableProperty]
|
||||
private PageData? _current = null;
|
||||
|
||||
private readonly Dictionary<Page, (int Index, bool IsFooter)> _lookup = [];
|
||||
public ObservableCollection<PageData> Pages { get; } = [];
|
||||
public ObservableCollection<PageData> FooterPages { get; } = [];
|
||||
|
||||
public PageData this[Page page] {
|
||||
get {
|
||||
var (index, isFooter) = _lookup[page];
|
||||
return (isFooter ? FooterPages : Pages)[index];
|
||||
}
|
||||
}
|
||||
|
||||
public void Register(Page page, string title, object? content, Symbol icon, string? description = null, bool isDefault = false, bool isFooter = false)
|
||||
{
|
||||
var source = isFooter ? FooterPages : Pages;
|
||||
_lookup[page] = (source.Count, isFooter);
|
||||
|
||||
source.Add(new PageData {
|
||||
Title = title,
|
||||
Content = content,
|
||||
Description = description,
|
||||
Icon = icon
|
||||
});
|
||||
|
||||
if (isDefault) {
|
||||
Focus(page);
|
||||
}
|
||||
}
|
||||
|
||||
public void Focus(Page page)
|
||||
{
|
||||
Current = this[page];
|
||||
}
|
||||
|
||||
public T Get<T>(Page page) where T : ObservableObject
|
||||
{
|
||||
var (index, isFooter) = _lookup[page];
|
||||
if ((isFooter ? FooterPages : Pages)[index].Content is UserControl { DataContext: T value }) {
|
||||
return value;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException(
|
||||
$"Invalid ViewModel type for '{page}'");
|
||||
}
|
||||
}
|
||||
|
||||
public enum Page
|
||||
{
|
||||
Home
|
||||
}
|
||||
|
||||
public class PageData
|
||||
{
|
||||
public required string Title { get; set; }
|
||||
public object? Content { get; set; }
|
||||
public required Symbol Icon { get; set; }
|
||||
public string? Description { get; set; }
|
||||
}
|
|
@ -1,74 +0,0 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
using ImGuiNET;
|
||||
using Silk.NET.SDL;
|
||||
|
||||
namespace Volte.UI;
|
||||
|
||||
/**
|
||||
* A class that contains all helper extension methods for ImGui.
|
||||
* Named Gui instead of ImGui to avoid conflicts.
|
||||
*/
|
||||
public static class Gui
|
||||
{
|
||||
public static void SameLineText(ReadOnlySpan<char> text)
|
||||
{
|
||||
ImGui.Text(text);
|
||||
ImGui.SameLine();
|
||||
}
|
||||
|
||||
public static void SameLineText(ReadOnlySpan<char> text, Color color)
|
||||
{
|
||||
ImGui.TextColored(color.AsVec4(), text);
|
||||
ImGui.SameLine();
|
||||
}
|
||||
|
||||
public static void SameLineText(ReadOnlySpan<char> text, System.Drawing.Color color)
|
||||
{
|
||||
ImGui.TextColored(color.AsVec4(), text);
|
||||
ImGui.SameLine();
|
||||
}
|
||||
|
||||
public static void Text(ReadOnlySpan<char> text, Color color)
|
||||
=> ImGui.TextColored(color.AsVec4(), text);
|
||||
|
||||
public static void Text(ReadOnlySpan<char> text, System.Drawing.Color color)
|
||||
=> ImGui.TextColored(color.AsVec4(), text);
|
||||
|
||||
public static IDisposable PushValue(this ImGuiStyleVar styleVar, Vector2 value) => new ScopedStyleVar(styleVar, value);
|
||||
public static IDisposable PushValue(this ImGuiStyleVar styleVar, float value) => new ScopedStyleVar(styleVar, value);
|
||||
|
||||
public static IDisposable PushValue(this ImGuiCol colorVar, Vector4 value) => new ScopedStyleColor(colorVar, value);
|
||||
public static IDisposable PushValue(this ImGuiCol colorVar, Vector3 value) => new ScopedStyleColor(colorVar, value);
|
||||
public static IDisposable PushValue(this ImGuiCol colorVar, Color value) => new ScopedStyleColor(colorVar, value.AsVec4());
|
||||
|
||||
public static IDisposable PushValue(this ImGuiCol colorVar, System.Drawing.Color value) =>
|
||||
new ScopedStyleColor(colorVar, value.AsVec4());
|
||||
|
||||
public static IDisposable PushValue(this ImGuiCol colorVar, uint value) => new ScopedStyleColor(colorVar, value);
|
||||
}
|
||||
|
||||
public struct ScopedStyleVar : IDisposable
|
||||
{
|
||||
internal ScopedStyleVar(ImGuiStyleVar styleVar, Vector2 value)
|
||||
=> ImGui.PushStyleVar(styleVar, value);
|
||||
|
||||
internal ScopedStyleVar(ImGuiStyleVar styleVar, float value)
|
||||
=> ImGui.PushStyleVar(styleVar, value);
|
||||
|
||||
void IDisposable.Dispose() => ImGui.PopStyleVar();
|
||||
}
|
||||
|
||||
public struct ScopedStyleColor : IDisposable
|
||||
{
|
||||
internal ScopedStyleColor(ImGuiCol colorVar, Vector4 value)
|
||||
=> ImGui.PushStyleColor(colorVar, value);
|
||||
|
||||
internal ScopedStyleColor(ImGuiCol colorVar, Vector3 value)
|
||||
=> ImGui.PushStyleColor(colorVar, new Vector4(value, 1f));
|
||||
|
||||
internal ScopedStyleColor(ImGuiCol colorVar, uint value) =>
|
||||
ImGui.PushStyleColor(colorVar, value);
|
||||
|
||||
void IDisposable.Dispose() => ImGui.PopStyleColor();
|
||||
}
|
|
@ -1,152 +0,0 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Gommon;
|
||||
using ImGuiNET;
|
||||
using Silk.NET.Core;
|
||||
using Silk.NET.Input;
|
||||
using Silk.NET.OpenGL;
|
||||
using Silk.NET.OpenGL.Extensions.ImGui;
|
||||
using Silk.NET.Windowing;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.Advanced;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using Color = Silk.NET.SDL.Color;
|
||||
|
||||
namespace Volte.UI;
|
||||
|
||||
#nullable enable
|
||||
|
||||
public sealed partial class UiManager : IDisposable
|
||||
{
|
||||
private bool _isActive;
|
||||
|
||||
private readonly IWindow _window;
|
||||
private readonly Action<ImGuiIOPtr> _onConfigureIo;
|
||||
private Image<Rgba32>? _windowIcon;
|
||||
|
||||
private ImGuiController? _controller;
|
||||
private GL? _gl;
|
||||
private IInputContext? _inputContext;
|
||||
|
||||
private void OnWindowLoad()
|
||||
{
|
||||
_gl = GL.GetApi(_window);
|
||||
_inputContext = _window.CreateInput();
|
||||
_controller = new ImGuiController(_gl, _window, _inputContext, onConfigureIO: () =>
|
||||
{
|
||||
var io = ImGui.GetIO();
|
||||
io.ConfigFlags |= ImGuiConfigFlags.DockingEnable;
|
||||
io.ConfigDockingWithShift = false;
|
||||
|
||||
unsafe
|
||||
{
|
||||
if (_theme != null)
|
||||
SetColors(_theme);
|
||||
else
|
||||
ImGui.StyleColorsDark();
|
||||
}
|
||||
|
||||
_onConfigureIo(io);
|
||||
}
|
||||
);
|
||||
|
||||
SetWindowIcon();
|
||||
}
|
||||
|
||||
private void SetWindowIcon()
|
||||
{
|
||||
// shoutout https://github.com/dotnet/Silk.NET/blob/b079b28cd51ce447183cfedde0a85412b9b226ee/src/Lab/Experiments/BlankWindow/Program.cs#L82-L95
|
||||
|
||||
if (_windowIcon == null) return;
|
||||
|
||||
Span<byte> span = new byte[_windowIcon.GetPixelMemoryGroup().TotalLength * Unsafe.SizeOf<Rgba32>()];
|
||||
var block = MemoryMarshal.Cast<byte, Rgba32>(span);
|
||||
|
||||
foreach (var memory in _windowIcon.GetPixelMemoryGroup())
|
||||
{
|
||||
memory.Span.CopyTo(block);
|
||||
block = block[memory.Length..];
|
||||
}
|
||||
|
||||
var rawIcon = new RawImage(_windowIcon.Width, _windowIcon.Height, span.ToArray());
|
||||
|
||||
_windowIcon.Dispose();
|
||||
_windowIcon = null;
|
||||
|
||||
_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;
|
||||
}
|
||||
}
|
|
@ -1,187 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Gommon;
|
||||
using ImGuiNET;
|
||||
using Silk.NET.Windowing;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Volte.UI;
|
||||
|
||||
// adapted from https://github.com/dotnet/Silk.NET/blob/main/examples/CSharp/OpenGL%20Demos/ImGui/Program.cs
|
||||
public sealed partial class UiManager
|
||||
{
|
||||
public readonly ConcurrentQueue<Task> TaskQueue = new();
|
||||
|
||||
private readonly List<UiView> _views = [];
|
||||
|
||||
private readonly unsafe ThemedColors* _theme;
|
||||
|
||||
private int CurrentViewIdx { get; set; }
|
||||
|
||||
private void SetView(int viewIndex) =>
|
||||
CurrentViewIdx = viewIndex.CoerceAtLeast(0).CoerceAtMost(_views.Count - 1);
|
||||
|
||||
public UiView CurrentView => _views[CurrentViewIdx];
|
||||
|
||||
private unsafe UiManager(CreateParams @params)
|
||||
{
|
||||
_theme = @params.Theme;
|
||||
|
||||
_window = Window.Create(@params.WOptions);
|
||||
|
||||
_onConfigureIo = @params.OnConfigureIo;
|
||||
_windowIcon = @params.WindowIcon;
|
||||
|
||||
_window.Load += OnWindowLoad;
|
||||
_window.Render += OnWindowRender;
|
||||
_window.FramebufferResize += sz => _gl?.Viewport(sz);
|
||||
|
||||
_window.Closing += () =>
|
||||
{
|
||||
_isActive = false;
|
||||
_controller?.Dispose();
|
||||
_inputContext?.Dispose();
|
||||
_gl?.Dispose();
|
||||
};
|
||||
}
|
||||
|
||||
public void Run()
|
||||
{
|
||||
_isActive = true;
|
||||
|
||||
Lambda
|
||||
.Repeat(async () =>
|
||||
{
|
||||
await Task.Delay(
|
||||
125); // tasks dont get added too often; so there's no need to run the polling below it as fast as possible.
|
||||
if (TaskQueue.TryDequeue(out var task))
|
||||
await task.ConfigureAwait(false);
|
||||
})
|
||||
.While(() => _isActive)
|
||||
.Finally(() => TaskQueue.Clear())
|
||||
.Async();
|
||||
|
||||
_window.Run();
|
||||
}
|
||||
|
||||
private const ImGuiWindowFlags DockSpaceFlags =
|
||||
ImGuiWindowFlags.NoDocking | ImGuiWindowFlags.NoTitleBar |
|
||||
ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoMove |
|
||||
ImGuiWindowFlags.NoBringToFrontOnFocus | ImGuiWindowFlags.NoNavFocus;
|
||||
|
||||
private void OnWindowRender(double delta)
|
||||
{
|
||||
if (_window.WindowState == WindowState.Minimized) return;
|
||||
|
||||
_controller?.Update((float)delta);
|
||||
|
||||
// shoutout https://gist.github.com/moebiussurfing/8dbc7fef5964adcd29428943b78e45d2
|
||||
// for showing me how to properly setup dock space
|
||||
|
||||
var currView = CurrentView;
|
||||
|
||||
var viewport = ImGui.GetMainViewport();
|
||||
|
||||
ImGui.SetNextWindowPos(viewport.WorkPos);
|
||||
ImGui.SetNextWindowSize(viewport.WorkSize);
|
||||
ImGui.SetNextWindowViewport(viewport.ID);
|
||||
|
||||
using (ImGuiStyleVar.WindowRounding.PushValue(0f))
|
||||
using (ImGuiStyleVar.WindowBorderSize.PushValue(0f))
|
||||
ImGui.Begin("Dock Space",
|
||||
currView.MainMenuBar != null
|
||||
? DockSpaceFlags | ImGuiWindowFlags.MenuBar
|
||||
: DockSpaceFlags
|
||||
);
|
||||
|
||||
ImGui.DockSpace(ImGui.GetID("DockSpace"), Vector2.Zero);
|
||||
|
||||
if (currView.MainMenuBar is { } menuBar)
|
||||
if (ImGui.BeginMenuBar())
|
||||
{
|
||||
menuBar(delta);
|
||||
ImGui.EndMenuBar();
|
||||
}
|
||||
|
||||
currView.RenderInternal(delta);
|
||||
|
||||
ImGui.End();
|
||||
|
||||
_controller?.Render();
|
||||
}
|
||||
|
||||
public void Dispose() => _window.Dispose();
|
||||
|
||||
public static UiManager? Instance { get; private set; }
|
||||
|
||||
public readonly struct CreateParams
|
||||
{
|
||||
public WindowOptions WOptions { get; init; }
|
||||
public Action<ImGuiIOPtr> OnConfigureIo { get; init; }
|
||||
public Image<Rgba32>? WindowIcon { get; init; }
|
||||
public unsafe ThemedColors* Theme { get; init; }
|
||||
}
|
||||
|
||||
public static bool TryCreateUi(CreateParams createParams, out Exception? error)
|
||||
{
|
||||
if (Instance is not null)
|
||||
{
|
||||
error = new InvalidOperationException("UI is already open.");
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Instance = new UiManager(createParams);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
error = e;
|
||||
return false;
|
||||
}
|
||||
|
||||
error = null;
|
||||
|
||||
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);
|
||||
|
||||
public static unsafe void LoadFontFromStream(Stream stream, string fontName, float fontSize)
|
||||
{
|
||||
var fontData = stream.ToSpan();
|
||||
|
||||
fixed (byte* fontDataPtr = fontData)
|
||||
{
|
||||
var fontConfig = ImGuiNative.ImFontConfig_ImFontConfig();
|
||||
fontConfig->FontData = fontDataPtr;
|
||||
fontConfig->FontDataSize = fontData.Length;
|
||||
fontConfig->SizePixels = fontSize;
|
||||
|
||||
Buffers.CopyBytesFromString(fontConfig->Name, 40, fontName); //40 is the size of the name buffer in ImFontConfig
|
||||
|
||||
ImGuiNative.ImFontAtlas_AddFont(ImGui.GetIO().Fonts, fontConfig);
|
||||
}
|
||||
}
|
||||
}
|
27
src/UI/Program.cs
Normal file
27
src/UI/Program.cs
Normal file
|
@ -0,0 +1,27 @@
|
|||
using Avalonia;
|
||||
using Volte.Helpers;
|
||||
|
||||
namespace Volte.UI;
|
||||
|
||||
public class Program
|
||||
{
|
||||
// Initialization code. Don't use any Avalonia, third-party APIs or any
|
||||
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
|
||||
// yet and stuff might break.
|
||||
[STAThread]
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
if (!UnixHelper.TryParseNamedArguments(args, out var output) && output.Error is not InvalidOperationException)
|
||||
Logger.Error(output.Error);
|
||||
|
||||
return BuildAvaloniaApp()
|
||||
.StartWithClassicDesktopLifetime(args);
|
||||
}
|
||||
|
||||
// Avalonia configuration, don't remove; also used by visual designer.
|
||||
private static AppBuilder BuildAvaloniaApp()
|
||||
=> AppBuilder.Configure<App>()
|
||||
.UsePlatformDetect()
|
||||
.WithInterFont()
|
||||
.LogToTrace(Avalonia.Logging.LogEventLevel.Debug, "TabView");
|
||||
}
|
4
src/UI/README.md
Normal file
4
src/UI/README.md
Normal file
|
@ -0,0 +1,4 @@
|
|||
# Volte's Avalonia UI
|
||||
|
||||
This UI built with Avalonia and FluentAvalonia, and is a direct modification of the UI of the [Tears of the Kingdom Mod Merger](https://github.com/TKMM-Team/Tkmm/).<br/>
|
||||
Obviously Volte has no mod merging functionality, so what it's using is basically just TKMM's styling & instrumentation code.
|
|
@ -1,254 +0,0 @@
|
|||
using Silk.NET.SDL;
|
||||
|
||||
namespace Volte.UI;
|
||||
|
||||
// https://github.com/adobe/imgui/blob/master/imgui_spectrum.h
|
||||
public static class Spectrum
|
||||
{
|
||||
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);
|
||||
internal override Color Gray200 => Color(0x393939);
|
||||
internal override Color Gray300 => Color(0x3E3E3E);
|
||||
internal override Color Gray400 => Color(0x4D4D4D);
|
||||
internal override Color Gray500 => Color(0x5C5C5C);
|
||||
internal override Color Gray600 => Color(0x7B7B7B);
|
||||
internal override Color Gray700 => Color(0x999999);
|
||||
internal override Color Gray800 => Color(0xCDCDCD);
|
||||
internal override Color Gray900 => Color(0xFFFFFF);
|
||||
internal override Color Blue400 => Color(0x2680EB);
|
||||
internal override Color Blue500 => Color(0x378EF0);
|
||||
internal override Color Blue600 => Color(0x4B9CF5);
|
||||
internal override Color Blue700 => Color(0x5AA9FA);
|
||||
internal override Color Red400 => Color(0xE34850);
|
||||
internal override Color Red500 => Color(0xEC5B62);
|
||||
internal override Color Red600 => Color(0xF76D74);
|
||||
internal override Color Red700 => Color(0xFF7B82);
|
||||
internal override Color Orange400 => Color(0xE68619);
|
||||
internal override Color Orange500 => Color(0xF29423);
|
||||
internal override Color Orange600 => Color(0xF9A43F);
|
||||
internal override Color Orange700 => Color(0xFFB55B);
|
||||
internal override Color Green400 => Color(0x2D9D78);
|
||||
internal override Color Green500 => Color(0x33AB84);
|
||||
internal override Color Green600 => Color(0x39B990);
|
||||
internal override Color Green700 => Color(0x3FC89C);
|
||||
internal override Color Indigo400 => Color(0x6767EC);
|
||||
internal override Color Indigo500 => Color(0x7575F1);
|
||||
internal override Color Indigo600 => Color(0x8282F6);
|
||||
internal override Color Indigo700 => Color(0x9090FA);
|
||||
internal override Color Celery400 => Color(0x44B556);
|
||||
internal override Color Celery500 => Color(0x4BC35F);
|
||||
internal override Color Celery600 => Color(0x51D267);
|
||||
internal override Color Celery700 => Color(0x58E06F);
|
||||
internal override Color Magenta400 => Color(0xD83790);
|
||||
internal override Color Magenta500 => Color(0xE2499D);
|
||||
internal override Color Magenta600 => Color(0xEC5AAA);
|
||||
internal override Color Magenta700 => Color(0xF56BB7);
|
||||
internal override Color Yellow400 => Color(0xDFBF00);
|
||||
internal override Color Yellow500 => Color(0xEDCC00);
|
||||
internal override Color Yellow600 => Color(0xFAD900);
|
||||
internal override Color Yellow700 => Color(0xFFE22E);
|
||||
internal override Color Fuchsia400 => Color(0xC038CC);
|
||||
internal override Color Fuchsia500 => Color(0xCF3EDC);
|
||||
internal override Color Fuchsia600 => Color(0xD951E5);
|
||||
internal override Color Fuchsia700 => Color(0xE366EF);
|
||||
internal override Color Seafoam400 => Color(0x1B959A);
|
||||
internal override Color Seafoam500 => Color(0x20A3A8);
|
||||
internal override Color Seafoam600 => Color(0x23B2B8);
|
||||
internal override Color Seafoam700 => Color(0x26C0C7);
|
||||
internal override Color Chartreuse400 => Color(0x85D044);
|
||||
internal override Color Chartreuse500 => Color(0x8EDE49);
|
||||
internal override Color Chartreuse600 => Color(0x9BEC54);
|
||||
internal override Color Chartreuse700 => Color(0xA3F858);
|
||||
internal override Color Purple400 => Color(0x9256D9);
|
||||
internal override Color Purple500 => Color(0x9D64E1);
|
||||
internal override Color Purple600 => Color(0xA873E9);
|
||||
internal override Color Purple700 => Color(0xB483F0);
|
||||
}
|
||||
|
||||
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);
|
||||
internal override Color Gray200 => Color(0xEAEAEA);
|
||||
internal override Color Gray300 => Color(0xE1E1E1);
|
||||
internal override Color Gray400 => Color(0xCACACA);
|
||||
internal override Color Gray500 => Color(0xB3B3B3);
|
||||
internal override Color Gray600 => Color(0x8E8E8E);
|
||||
internal override Color Gray700 => Color(0x707070);
|
||||
internal override Color Gray800 => Color(0x4B4B4B);
|
||||
internal override Color Gray900 => Color(0x2C2C2C);
|
||||
internal override Color Blue400 => Color(0x2680EB);
|
||||
internal override Color Blue500 => Color(0x1473E6);
|
||||
internal override Color Blue600 => Color(0x0D66D0);
|
||||
internal override Color Blue700 => Color(0x095ABA);
|
||||
internal override Color Red400 => Color(0xE34850);
|
||||
internal override Color Red500 => Color(0xD7373F);
|
||||
internal override Color Red600 => Color(0xC9252D);
|
||||
internal override Color Red700 => Color(0xBB121A);
|
||||
internal override Color Orange400 => Color(0xE68619);
|
||||
internal override Color Orange500 => Color(0xDA7B11);
|
||||
internal override Color Orange600 => Color(0xCB6F10);
|
||||
internal override Color Orange700 => Color(0xBD640D);
|
||||
internal override Color Green400 => Color(0x2D9D78);
|
||||
internal override Color Green500 => Color(0x268E6C);
|
||||
internal override Color Green600 => Color(0x12805C);
|
||||
internal override Color Green700 => Color(0x107154);
|
||||
internal override Color Indigo400 => Color(0x6767EC);
|
||||
internal override Color Indigo500 => Color(0x5C5CE0);
|
||||
internal override Color Indigo600 => Color(0x5151D3);
|
||||
internal override Color Indigo700 => Color(0x4646C6);
|
||||
internal override Color Celery400 => Color(0x44B556);
|
||||
internal override Color Celery500 => Color(0x3DA74E);
|
||||
internal override Color Celery600 => Color(0x379947);
|
||||
internal override Color Celery700 => Color(0x318B40);
|
||||
internal override Color Magenta400 => Color(0xD83790);
|
||||
internal override Color Magenta500 => Color(0xCE2783);
|
||||
internal override Color Magenta600 => Color(0xBC1C74);
|
||||
internal override Color Magenta700 => Color(0xAE0E66);
|
||||
internal override Color Yellow400 => Color(0xDFBF00);
|
||||
internal override Color Yellow500 => Color(0xD2B200);
|
||||
internal override Color Yellow600 => Color(0xC4A600);
|
||||
internal override Color Yellow700 => Color(0xB79900);
|
||||
internal override Color Fuchsia400 => Color(0xC038CC);
|
||||
internal override Color Fuchsia500 => Color(0xB130BD);
|
||||
internal override Color Fuchsia600 => Color(0xA228AD);
|
||||
internal override Color Fuchsia700 => Color(0x93219E);
|
||||
internal override Color Seafoam400 => Color(0x1B959A);
|
||||
internal override Color Seafoam500 => Color(0x16878C);
|
||||
internal override Color Seafoam600 => Color(0x0F797D);
|
||||
internal override Color Seafoam700 => Color(0x096C6F);
|
||||
internal override Color Chartreuse400 => Color(0x85D044);
|
||||
internal override Color Chartreuse500 => Color(0x7CC33F);
|
||||
internal override Color Chartreuse600 => Color(0x73B53A);
|
||||
internal override Color Chartreuse700 => Color(0x6AA834);
|
||||
internal override Color Purple400 => Color(0x9256D9);
|
||||
internal override Color Purple500 => Color(0x864CCC);
|
||||
internal override Color Purple600 => Color(0x7A42BF);
|
||||
internal override Color Purple700 => Color(0x6F38B1);
|
||||
}
|
||||
|
||||
|
||||
public static class Static
|
||||
{
|
||||
internal static Color None = Color(0x000000);
|
||||
internal static Color Gray200 = Color(0xF4F4F4);
|
||||
internal static Color Gray300 = Color(0xEAEAEA);
|
||||
internal static Color Gray400 = Color(0xD3D3D3);
|
||||
internal static Color Gray500 = Color(0xBCBCBC);
|
||||
internal static Color Gray600 = Color(0x959595);
|
||||
internal static Color Gray700 = Color(0x767676);
|
||||
internal static Color Gray800 = Color(0x505050);
|
||||
internal static Color Gray900 = Color(0x323232);
|
||||
internal static Color Blue400 = Color(0x378EF0);
|
||||
internal static Color Blue500 = Color(0x2680EB);
|
||||
internal static Color Blue600 = Color(0x1473E6);
|
||||
internal static Color Blue700 = Color(0x0D66D0);
|
||||
internal static Color Red400 = Color(0xEC5B62);
|
||||
internal static Color Red500 = Color(0xE34850);
|
||||
internal static Color Red600 = Color(0xD7373F);
|
||||
internal static Color Red700 = Color(0xC9252D);
|
||||
internal static Color Orange400 = Color(0xF29423);
|
||||
internal static Color Orange500 = Color(0xE68619);
|
||||
internal static Color Orange600 = Color(0xDA7B11);
|
||||
internal static Color Orange700 = Color(0xCB6F10);
|
||||
internal static Color Green400 = Color(0x33AB84);
|
||||
internal static Color Green500 = Color(0x2D9D78);
|
||||
internal static Color Green600 = Color(0x268E6C);
|
||||
internal static Color Green700 = Color(0x12805C);
|
||||
}
|
||||
|
||||
private static Color Color(uint raw)
|
||||
{
|
||||
var r = (raw >> 16) & 0xFF;
|
||||
var g = (raw >> 8) & 0xFF;
|
||||
var b = (raw >> 0) & 0xFF;
|
||||
return new Color((byte)r, (byte)g, (byte)b);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class ThemedColors
|
||||
{
|
||||
internal abstract Color Gray50 { get; }
|
||||
internal abstract Color Gray75 { get; }
|
||||
internal abstract Color Gray100 { get; }
|
||||
internal abstract Color Gray200 { get; }
|
||||
internal abstract Color Gray300 { get; }
|
||||
internal abstract Color Gray400 { get; }
|
||||
internal abstract Color Gray500 { get; }
|
||||
internal abstract Color Gray600 { get; }
|
||||
internal abstract Color Gray700 { get; }
|
||||
internal abstract Color Gray800 { get; }
|
||||
internal abstract Color Gray900 { get; }
|
||||
internal abstract Color Blue400 { get; }
|
||||
internal abstract Color Blue500 { get; }
|
||||
internal abstract Color Blue600 { get; }
|
||||
internal abstract Color Blue700 { get; }
|
||||
internal abstract Color Red400 { get; }
|
||||
internal abstract Color Red500 { get; }
|
||||
internal abstract Color Red600 { get; }
|
||||
internal abstract Color Red700 { get; }
|
||||
internal abstract Color Orange400 { get; }
|
||||
internal abstract Color Orange500 { get; }
|
||||
internal abstract Color Orange600 { get; }
|
||||
internal abstract Color Orange700 { get; }
|
||||
internal abstract Color Green400 { get; }
|
||||
internal abstract Color Green500 { get; }
|
||||
internal abstract Color Green600 { get; }
|
||||
internal abstract Color Green700 { get; }
|
||||
internal abstract Color Indigo400 { get; }
|
||||
internal abstract Color Indigo500 { get; }
|
||||
internal abstract Color Indigo600 { get; }
|
||||
internal abstract Color Indigo700 { get; }
|
||||
internal abstract Color Celery400 { get; }
|
||||
internal abstract Color Celery500 { get; }
|
||||
internal abstract Color Celery600 { get; }
|
||||
internal abstract Color Celery700 { get; }
|
||||
internal abstract Color Magenta400 { get; }
|
||||
internal abstract Color Magenta500 { get; }
|
||||
internal abstract Color Magenta600 { get; }
|
||||
internal abstract Color Magenta700 { get; }
|
||||
internal abstract Color Yellow400 { get; }
|
||||
internal abstract Color Yellow500 { get; }
|
||||
internal abstract Color Yellow600 { get; }
|
||||
internal abstract Color Yellow700 { get; }
|
||||
internal abstract Color Fuchsia400 { get; }
|
||||
internal abstract Color Fuchsia500 { get; }
|
||||
internal abstract Color Fuchsia600 { get; }
|
||||
internal abstract Color Fuchsia700 { get; }
|
||||
internal abstract Color Seafoam400 { get; }
|
||||
internal abstract Color Seafoam500 { get; }
|
||||
internal abstract Color Seafoam600 { get; }
|
||||
internal abstract Color Seafoam700 { get; }
|
||||
internal abstract Color Chartreuse400 { get; }
|
||||
internal abstract Color Chartreuse500 { get; }
|
||||
internal abstract Color Chartreuse600 { get; }
|
||||
internal abstract Color Chartreuse700 { get; }
|
||||
internal abstract Color Purple400 { get; }
|
||||
internal abstract Color Purple500 { get; }
|
||||
internal abstract Color Purple600 { get; }
|
||||
internal abstract Color Purple700 { get; }
|
||||
}
|
|
@ -1,76 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Threading.Tasks;
|
||||
using ImGuiNET;
|
||||
using Silk.NET.Input;
|
||||
using Silk.NET.SDL;
|
||||
|
||||
namespace Volte.UI;
|
||||
|
||||
[SuppressMessage("ReSharper", "VirtualMemberNeverOverridden.Global")] //this is an API
|
||||
public abstract class UiView
|
||||
{
|
||||
private readonly List<Action<double>> _panels = [];
|
||||
|
||||
public Action<double> MainMenuBar { get; protected init; }
|
||||
|
||||
protected static ImGuiIOPtr Io => ImGui.GetIO();
|
||||
|
||||
public static bool IsKeyPressed(Key key)
|
||||
=> Io.KeysDown[(int)key];
|
||||
|
||||
public static bool IsMouseButtonPressed(MouseButton mb)
|
||||
=> Io.MouseDown[(int)mb];
|
||||
|
||||
public static bool AllKeysPressed(params Key[] keys)
|
||||
{
|
||||
if (keys.Length == 1)
|
||||
return IsKeyPressed(keys[0]);
|
||||
|
||||
var io = Io;
|
||||
return keys.Select(key => io.KeysDown[(int)key])
|
||||
.All(x => x);
|
||||
}
|
||||
|
||||
public static bool AllMouseButtonsPressed(params MouseButton[] mouseButtons)
|
||||
{
|
||||
if (mouseButtons.Length == 1)
|
||||
return Io.MouseDown[(int)mouseButtons[0]];
|
||||
|
||||
var io = Io;
|
||||
return mouseButtons.Select(mb => io.MouseDown[(int)mb])
|
||||
.All(x => x);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this function for custom one-off rendering.
|
||||
* The return value determines whether your <see cref="UiView"/>'s defined panels will be rendered.
|
||||
* (false = no render, true = render)
|
||||
*/
|
||||
protected virtual bool Render(double _) => true;
|
||||
|
||||
internal void RenderInternal(double delta)
|
||||
{
|
||||
if (!Render(delta)) return;
|
||||
|
||||
foreach (var renderPanel in _panels)
|
||||
renderPanel(delta);
|
||||
}
|
||||
|
||||
protected static void Await(Func<Task> task) => Await(task());
|
||||
protected static void Await(Task task) => UiManager.Instance!.TaskQueue.Enqueue(task);
|
||||
|
||||
protected void Panel(string label, Action<double> render) => _panels.Add(delta =>
|
||||
{
|
||||
if (ImGui.Begin(label))
|
||||
{
|
||||
render(delta);
|
||||
ImGui.End();
|
||||
}
|
||||
});
|
||||
|
||||
protected void Panel(Action<double> render) => _panels.Add(render);
|
||||
}
|
|
@ -1,28 +1,33 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<Authors>GreemDev</Authors>
|
||||
<Company>Polyhaze</Company>
|
||||
<RepositoryUrl>https://github.com/Polyhaze/Volte</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<Version>1.0.1</Version>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<NoWarn>$(NoWarn);CS8500</NoWarn>
|
||||
<Description>The mini abstraction used for the UI of Volte. https://github.com/Polyhaze/Volte</Description>
|
||||
<PackageReleaseNotes>
|
||||
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.
|
||||
</PackageReleaseNotes>
|
||||
<PackageTags>Silk.NET;Silk;Silk.NET.Windowing;Silk.NET.OpenGL;Silk.NET.OpenGL.Extensions.ImGui;Silk.NET.Input;ImGui;UI;GUI</PackageTags>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Gommon" Version="2.6.4" />
|
||||
<PackageReference Include="Silk.NET.Input" Version="2.21.0" />
|
||||
<PackageReference Include="Silk.NET.OpenGL" Version="2.21.0" />
|
||||
<PackageReference Include="Silk.NET.OpenGL.Extensions.ImGui" Version="2.21.0" />
|
||||
<PackageReference Include="Silk.NET.Windowing" Version="2.21.0" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.5" />
|
||||
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.1.0" />
|
||||
<PackageReference Include="ConfigFactory.Avalonia" Version="0.4.2" />
|
||||
<PackageReference Include="Avalonia" Version="11.1.0"/>
|
||||
<PackageReference Include="Avalonia.Desktop" Version="11.1.0"/>
|
||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.1.0"/>
|
||||
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.1.0"/>
|
||||
<PackageReference Include="FluentAvaloniaUI" Version="2.0.5" />
|
||||
|
||||
<ProjectReference Include="../Bot/Volte.csproj"/>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AvaloniaResource Include="..\Bot\Resources\Volte.ico" Link="Assets\icon.ico" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Update="Avalonia\App.axaml.cs">
|
||||
<DependentUpon>App.axaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Update="Avalonia\UIShellView.axaml.cs">
|
||||
<DependentUpon>MainWindow.axaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
dotnet publish -r Release
|
Loading…
Reference in a new issue