You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
184 lines
7.2 KiB
184 lines
7.2 KiB
using System; |
|
using System.IO; |
|
using System.Linq; |
|
using System.Text; |
|
using gaemstone.Client.Utility; |
|
using gaemstone.ECS; |
|
using gaemstone.Flecs; |
|
using gaemstone.Utility; |
|
using ImGuiNET; |
|
using Silk.NET.Input; |
|
using Silk.NET.OpenGL.Extensions.ImGui; |
|
using static gaemstone.Client.Components.InputComponents; |
|
using static gaemstone.Client.Systems.Windowing; |
|
|
|
namespace gaemstone.Client.Systems; |
|
|
|
[Module] |
|
[DependsOn<gaemstone.Client.Components.InputComponents>] |
|
[DependsOn<gaemstone.Client.Systems.InputManager>] |
|
[DependsOn<gaemstone.Client.Systems.Windowing>] |
|
public class ImGuiManager |
|
{ |
|
[Entity, Add<Pipeline.Phase>] |
|
[DependsOn<SystemPhase.OnLoad>] |
|
public struct ImGuiUpdatePhase { } |
|
|
|
[Entity, Add<Pipeline.Phase>] |
|
[DependsOn<SystemPhase.OnStore>] |
|
public struct ImGuiRenderPhase { } |
|
|
|
[Component, Singleton(AutoAdd = false)] |
|
public class ImGuiData |
|
{ |
|
public ImGuiController Controller { get; } |
|
internal ImGuiData(ImGuiController controller) => Controller = controller; |
|
} |
|
|
|
private unsafe static ImFontPtr AddFontFromResources( |
|
ImGuiIOPtr io, string name, int size, Action<ImFontConfigPtr>? cfgAction = null, |
|
float offset = 0, float minAdvance = 0, |
|
(int Min, int Max)[]? ranges = null, bool merge = false) |
|
{ |
|
// TODO: FontConfig can be freed at the end of this method. |
|
// Unfortunately, data has to stick around until font atlas is built. |
|
var cfg = new ImFontConfigPtr(ImGuiNative.ImFontConfig_ImFontConfig()) { |
|
GlyphOffset = new(0, offset), |
|
GlyphMinAdvanceX = minAdvance, |
|
MergeMode = merge, |
|
}; |
|
// Set cfg.Name so the font has a nice display name in ImGui. |
|
var fullName = $"{name.Replace('.', ' ')} {size}px"; |
|
Encoding.UTF8.GetBytes(fullName, new Span<byte>(cfg.Name.Data, cfg.Name.Count)); |
|
// If glyph ranges are specified, allocate unmanaged heap memory for them. |
|
if (ranges != null) { |
|
var rangesSpan = GlobalHeapAllocator.Instance.Allocate<char>(ranges.Length * 2 + 1); |
|
for (var i = 0; i < ranges.Length; i++) { |
|
rangesSpan[i * 2 ] = (char)ranges[i].Min; |
|
rangesSpan[i * 2 + 1] = (char)ranges[i].Max; |
|
} |
|
rangesSpan[^1] = default; |
|
fixed (void* rangesPtr = rangesSpan) |
|
cfg.GlyphRanges = (IntPtr)rangesPtr; |
|
} |
|
// Use cfgAction to allow changing other font configs. |
|
cfgAction?.Invoke(cfg); |
|
|
|
// Grab the stream for this font from the assembly's resources. |
|
var file = $"gaemstone.Client.Resources.{name}.ttf"; |
|
using var stream = typeof(Resources).Assembly.GetManifestResourceStream(file) |
|
?? throw new InvalidOperationException($"Resource '{file}' was not found"); |
|
|
|
using var memoryStream = new MemoryStream(); |
|
stream.CopyTo(memoryStream); // Write font file from resources to memory stream. |
|
memoryStream.WriteByte(0); // Add a NUL termination character. |
|
|
|
// Copy the data into unmanaged memory and pass it to ImGui. |
|
var fontData = memoryStream.ToArray(); |
|
var fontDataSpan = GlobalHeapAllocator.Instance.AllocateCopy<byte>(fontData); |
|
fixed (byte* dataPtr = fontDataSpan) |
|
return io.Fonts.AddFontFromMemoryTTF((IntPtr)dataPtr, size, size, cfg); |
|
} |
|
|
|
[System<SystemPhase.OnLoad>] |
|
public unsafe void Initialize(Universe universe, GameWindow window, Canvas canvas, |
|
[Source<Input>] IInputContext inputContext, [Not] ImGuiData _) |
|
=> universe.LookupByTypeOrThrow<ImGuiData>().Set(new ImGuiData( |
|
new(canvas.GL, window.Handle, inputContext, () => { |
|
var io = ImGui.GetIO(); |
|
var style = ImGui.GetStyle(); |
|
|
|
var fontSize = 16; |
|
void MergeIcons() => AddFontFromResources(io, "ForkAwesome", fontSize, |
|
minAdvance: 18, ranges: new[]{ (ForkAwesome.Min, ForkAwesome.Max) }, merge: true); |
|
AddFontFromResources(io, "OpenSans" , fontSize, offset: -1); MergeIcons(); |
|
AddFontFromResources(io, "OpenSans.Bold" , fontSize, offset: -1); MergeIcons(); |
|
AddFontFromResources(io, "OpenSans.Italic", fontSize, offset: -1); MergeIcons(); |
|
|
|
// Do not save a settings or log file. |
|
io.NativePtr->IniFilename = null; |
|
io.NativePtr->LogFilename = null; |
|
|
|
io.BackendFlags |= ImGuiBackendFlags.HasMouseCursors |
|
| ImGuiBackendFlags.HasSetMousePos; |
|
io.ConfigFlags |= ImGuiConfigFlags.NavEnableKeyboard |
|
| ImGuiConfigFlags.NavEnableSetMousePos; |
|
// | ImGuiConfigFlags.DockingEnable; |
|
io.ConfigWindowsResizeFromEdges = true; |
|
io.ConfigWindowsMoveFromTitleBarOnly = true; |
|
|
|
style.WindowRounding = style.ChildRounding |
|
= style.PopupRounding = style.TabRounding |
|
= style.FrameRounding = style.ScrollbarRounding = 6; |
|
|
|
style.WindowTitleAlign = new(0.5f, 0.5f); |
|
style.ColorButtonPosition = ImGuiDir.Left; |
|
style.IndentSpacing = 8; |
|
|
|
// Set up key mappings. |
|
foreach (var imguiKey in Enum.GetValues<ImGuiKey>()) { |
|
var name = imguiKey.ToString(); |
|
|
|
// Adjust ImGuiKey enum names to match Silk.NET.Input.Key enum. |
|
if (name.StartsWith("_")) name = name.Replace("_", "Number"); |
|
if (name.EndsWith("Arrow")) name = name[..^"Arrow".Length]; |
|
if (name.EndsWith("Ctrl" )) name = name.Replace("Ctrl", "Control"); |
|
|
|
if (!name.EndsWith("Bracket")) { // Leave "LeftBracket" and "RightBracket" alone. |
|
if (name.StartsWith("Left" )) name = name["Left" .Length..] + "Left"; |
|
if (name.StartsWith("Right")) name = name["Right".Length..] + "Right"; |
|
} |
|
|
|
if (Enum.TryParse<Key>(name, true, out var silkKey)) |
|
io.KeyMap[(int)imguiKey] = (int)silkKey; |
|
} |
|
}))); |
|
|
|
[System<SystemPhase.OnLoad>] |
|
public static void UpdateMouse(Universe universe, |
|
[Source<Mouse>] IMouse impl, ImGuiData _) |
|
{ |
|
var input = universe.LookupByTypeOrThrow<Input>(); |
|
var module = universe.LookupByTypeOrThrow<ImGuiManager>(); |
|
var capturedBy = input.GetTargets<MouseInputCapturedBy>().FirstOrDefault();; |
|
var isCaptured = (capturedBy != null); |
|
// If another system has the mouse captured, don't do anything here. |
|
if (isCaptured && (capturedBy != module)) return; |
|
|
|
var IO = ImGui.GetIO(); |
|
|
|
// Set the mouse position if ImGui wants to move it. |
|
if (IO.WantSetMousePos) impl.Position = IO.MousePos; |
|
|
|
// Capture the mouse input it is above GUI elements. |
|
if (IO.WantCaptureMouse != isCaptured) { |
|
if (IO.WantCaptureMouse) input.Add <MouseInputCapturedBy>(module); |
|
else input.Remove<MouseInputCapturedBy>(module); |
|
} |
|
|
|
var cursor = ImGui.GetMouseCursor(); |
|
impl.Cursor.CursorMode = (cursor == ImGuiMouseCursor.None) |
|
? CursorMode.Hidden : CursorMode.Normal; |
|
// TODO: Use additional cursors once Silk.NET supports GLFW 3.4. |
|
impl.Cursor.StandardCursor = cursor switch { |
|
ImGuiMouseCursor.Arrow => StandardCursor.Arrow, |
|
ImGuiMouseCursor.TextInput => StandardCursor.IBeam, |
|
// ImGuiMouseCursor.ResizeAll => StandardCursor., |
|
ImGuiMouseCursor.ResizeNS => StandardCursor.VResize, |
|
ImGuiMouseCursor.ResizeEW => StandardCursor.HResize, |
|
// ImGuiMouseCursor.ResizeNESW => StandardCursor., |
|
// ImGuiMouseCursor.ResizeNWSE => StandardCursor., |
|
ImGuiMouseCursor.Hand => StandardCursor.Hand, |
|
// ImGuiMouseCursor.NotAllowed => StandardCursor., |
|
_ => StandardCursor.Default, |
|
}; |
|
} |
|
|
|
[System<ImGuiUpdatePhase>] |
|
public static void Update(TimeSpan delta, ImGuiData imgui) |
|
=> imgui.Controller.Update((float)delta.TotalSeconds); |
|
|
|
[System<ImGuiRenderPhase>] |
|
public static void Render(ImGuiData imgui) |
|
=> imgui.Controller.Render(); |
|
}
|
|
|