using System.Numerics; using Dalamud.Configuration.Internal; using Dalamud.Game.ClientState.Keys; using ImGuiNET; namespace Dalamud.Interface.Windowing; /// /// Base class you can use to implement an ImGui window for use with the built-in . /// public abstract class Window { private static bool wasEscPressedLastFrame = false; private bool internalLastIsOpen = false; private bool internalIsOpen = false; private bool nextFrameBringToFront = false; /// /// Initializes a new instance of the class. /// /// The name/ID of this window. /// If you have multiple windows with the same name, you will need to /// append an unique ID to it by specifying it after "###" behind the window title. /// /// The of this window. /// Whether or not this window should be limited to the main game window. protected Window(string name, ImGuiWindowFlags flags = ImGuiWindowFlags.None, bool forceMainWindow = false) { this.WindowName = name; this.Flags = flags; this.ForceMainWindow = forceMainWindow; } /// /// Gets or sets the namespace of the window. /// public string? Namespace { get; set; } /// /// Gets or sets the name of the window. /// If you have multiple windows with the same name, you will need to /// append an unique ID to it by specifying it after "###" behind the window title. /// public string WindowName { get; set; } /// /// Gets a value indicating whether the window is focused. /// public bool IsFocused { get; private set; } /// /// Gets or sets a value indicating whether this window is to be closed with a hotkey, like Escape, and keep game addons open in turn if it is closed. /// public bool RespectCloseHotkey { get; set; } = true; /// /// Gets or sets the position of this window. /// public Vector2? Position { get; set; } /// /// Gets or sets the condition that defines when the position of this window is set. /// public ImGuiCond PositionCondition { get; set; } /// /// Gets or sets the size of the window. The size provided will be scaled by the global scale. /// public Vector2? Size { get; set; } /// /// Gets or sets the condition that defines when the size of this window is set. /// public ImGuiCond SizeCondition { get; set; } /// /// Gets or sets the size constraints of the window. The size constraints provided will be scaled by the global scale. /// public WindowSizeConstraints? SizeConstraints { get; set; } /// /// Gets or sets a value indicating whether or not this window is collapsed. /// public bool? Collapsed { get; set; } /// /// Gets or sets the condition that defines when the collapsed state of this window is set. /// public ImGuiCond CollapsedCondition { get; set; } /// /// Gets or sets the window flags. /// public ImGuiWindowFlags Flags { get; set; } /// /// Gets or sets a value indicating whether or not this ImGui window will be forced to stay inside the main game window. /// public bool ForceMainWindow { get; set; } /// /// Gets or sets this window's background alpha value. /// public float? BgAlpha { get; set; } /// /// Gets or sets a value indicating whether or not this ImGui window should display a close button in the title bar. /// public bool ShowCloseButton { get; set; } = true; /// /// Gets or sets a value indicating whether or not this window will stay open. /// public bool IsOpen { get => this.internalIsOpen; set => this.internalIsOpen = value; } /// /// Toggle window is open state. /// public void Toggle() { this.IsOpen ^= true; } /// /// Bring this window to the front. /// public void BringToFront() { if (!this.IsOpen) return; this.nextFrameBringToFront = true; } /// /// Code to always be executed before the open-state of the window is checked. /// public virtual void PreOpenCheck() { } /// /// Additional conditions for the window to be drawn, regardless of its open-state. /// /// /// True if the window should be drawn, false otherwise. /// /// /// Not being drawn due to failing this condition will not change focus or trigger OnClose. /// This is checked before PreDraw, but after Update. /// public virtual bool DrawConditions() { return true; } /// /// Code to be executed before conditionals are applied and the window is drawn. /// public virtual void PreDraw() { } /// /// Code to be executed after the window is drawn. /// public virtual void PostDraw() { } /// /// Code to be executed every time the window renders. /// /// /// In this method, implement your drawing code. /// You do NOT need to ImGui.Begin your window. /// public abstract void Draw(); /// /// Code to be executed when the window is opened. /// public virtual void OnOpen() { } /// /// Code to be executed when the window is closed. /// public virtual void OnClose() { } /// /// Code to be executed every frame, even when the window is collapsed. /// public virtual void Update() { } /// /// Draw the window via ImGui. /// internal void DrawInternal() { this.PreOpenCheck(); if (!this.IsOpen) { if (this.internalIsOpen != this.internalLastIsOpen) { this.internalLastIsOpen = this.internalIsOpen; this.OnClose(); this.IsFocused = false; } return; } this.Update(); if (!this.DrawConditions()) return; var hasNamespace = !string.IsNullOrEmpty(this.Namespace); if (hasNamespace) ImGui.PushID(this.Namespace); this.PreDraw(); this.ApplyConditionals(); if (this.ForceMainWindow) ImGuiHelpers.ForceNextWindowMainViewport(); if (this.internalLastIsOpen != this.internalIsOpen && this.internalIsOpen) { this.internalLastIsOpen = this.internalIsOpen; this.OnOpen(); } var wasFocused = this.IsFocused; if (wasFocused) { var style = ImGui.GetStyle(); var focusedHeaderColor = style.Colors[(int)ImGuiCol.TitleBgActive]; ImGui.PushStyleColor(ImGuiCol.TitleBgCollapsed, focusedHeaderColor); } if (this.nextFrameBringToFront) { ImGui.SetNextWindowFocus(); this.nextFrameBringToFront = false; } if (this.ShowCloseButton ? ImGui.Begin(this.WindowName, ref this.internalIsOpen, this.Flags) : ImGui.Begin(this.WindowName, this.Flags)) { // Draw the actual window contents this.Draw(); } if (wasFocused) { ImGui.PopStyleColor(); } this.IsFocused = ImGui.IsWindowFocused(ImGuiFocusedFlags.RootAndChildWindows); var escapeDown = Service.Get()[VirtualKey.ESCAPE]; var isAllowed = Service.Get().IsFocusManagementEnabled; if (escapeDown && this.IsFocused && isAllowed && !wasEscPressedLastFrame && this.RespectCloseHotkey) { this.IsOpen = false; wasEscPressedLastFrame = true; } else if (!escapeDown && wasEscPressedLastFrame) { wasEscPressedLastFrame = false; } ImGui.End(); this.PostDraw(); if (hasNamespace) ImGui.PopID(); } // private void CheckState() // { // if (this.internalLastIsOpen != this.internalIsOpen) // { // if (this.internalIsOpen) // { // this.OnOpen(); // } // else // { // this.OnClose(); // } // } // } private void ApplyConditionals() { if (this.Position.HasValue) { var pos = this.Position.Value; if (this.ForceMainWindow) pos += ImGuiHelpers.MainViewport.Pos; ImGui.SetNextWindowPos(pos, this.PositionCondition); } if (this.Size.HasValue) { ImGui.SetNextWindowSize(this.Size.Value * ImGuiHelpers.GlobalScale, this.SizeCondition); } if (this.Collapsed.HasValue) { ImGui.SetNextWindowCollapsed(this.Collapsed.Value, this.CollapsedCondition); } if (this.SizeConstraints.HasValue) { ImGui.SetNextWindowSizeConstraints(this.SizeConstraints.Value.MinimumSize * ImGuiHelpers.GlobalScale, this.SizeConstraints.Value.MaximumSize * ImGuiHelpers.GlobalScale); } if (this.BgAlpha.HasValue) { ImGui.SetNextWindowBgAlpha(this.BgAlpha.Value); } } /// /// Structure detailing the size constraints of a window. /// public struct WindowSizeConstraints { /// /// Gets or sets the minimum size of the window. /// public Vector2 MinimumSize { get; set; } /// /// Gets or sets the maximum size of the window. /// public Vector2 MaximumSize { get; set; } } }