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

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();
}