diff --git a/Dalamud/Interface/Windowing/IWindow.cs b/Dalamud/Interface/Windowing/IWindow.cs
new file mode 100644
index 000000000..1376f406f
--- /dev/null
+++ b/Dalamud/Interface/Windowing/IWindow.cs
@@ -0,0 +1,216 @@
+using System.Collections.Generic;
+using System.Numerics;
+
+using Dalamud.Bindings.ImGui;
+
+namespace Dalamud.Interface.Windowing;
+
+///
+/// Represents a ImGui window for use with the built-in .
+///
+public interface IWindow
+{
+ ///
+ /// Gets or sets the namespace of the window.
+ ///
+ 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.
+ ///
+ string WindowName { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether the window is focused.
+ ///
+ bool IsFocused { get; 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.
+ ///
+ bool RespectCloseHotkey { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether this window should not generate sound effects when opening and closing.
+ ///
+ bool DisableWindowSounds { get; set; }
+
+ ///
+ /// Gets or sets a value representing the sound effect id to be played when the window is opened.
+ ///
+ uint OnOpenSfxId { get; set; }
+
+ ///
+ /// Gets or sets a value representing the sound effect id to be played when the window is closed.
+ ///
+ uint OnCloseSfxId { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether this window should not fade in and out, regardless of the users'
+ /// preference.
+ ///
+ bool DisableFadeInFadeOut { get; set; }
+
+ ///
+ /// Gets or sets the position of this window.
+ ///
+ Vector2? Position { get; set; }
+
+ ///
+ /// Gets or sets the condition that defines when the position of this window is set.
+ ///
+ ImGuiCond PositionCondition { get; set; }
+
+ ///
+ /// Gets or sets the size of the window. The size provided will be scaled by the global scale.
+ ///
+ Vector2? Size { get; set; }
+
+ ///
+ /// Gets or sets the condition that defines when the size of this window is set.
+ ///
+ ImGuiCond SizeCondition { get; set; }
+
+ ///
+ /// Gets or sets the size constraints of the window. The size constraints provided will be scaled by the global scale.
+ ///
+ WindowSizeConstraints? SizeConstraints { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether this window is collapsed.
+ ///
+ bool? Collapsed { get; set; }
+
+ ///
+ /// Gets or sets the condition that defines when the collapsed state of this window is set.
+ ///
+ ImGuiCond CollapsedCondition { get; set; }
+
+ ///
+ /// Gets or sets the window flags.
+ ///
+ ImGuiWindowFlags Flags { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether this ImGui window will be forced to stay inside the main game window.
+ ///
+ bool ForceMainWindow { get; set; }
+
+ ///
+ /// Gets or sets this window's background alpha value.
+ ///
+ float? BgAlpha { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether this ImGui window should display a close button in the title bar.
+ ///
+ bool ShowCloseButton { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether this window should offer to be pinned via the window's titlebar context menu.
+ ///
+ bool AllowPinning { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether this window should offer to be made click-through via the window's titlebar context menu.
+ ///
+ bool AllowClickthrough { get; set; }
+
+ ///
+ /// Gets a value indicating whether this window is pinned.
+ ///
+ public bool IsPinned { get; set; }
+
+ ///
+ /// Gets a value indicating whether this window is click-through.
+ ///
+ public bool IsClickthrough { get; set; }
+
+ ///
+ /// Gets or sets a list of available title bar buttons.
+ ///
+ /// If or are set to true, and this features is not
+ /// disabled globally by the user, an internal title bar button to manage these is added when drawing, but it will
+ /// not appear in this collection. If you wish to remove this button, set both of these values to false.
+ ///
+ List TitleBarButtons { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether this window will stay open.
+ ///
+ bool IsOpen { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether this window will request focus from the window system next frame.
+ ///
+ public bool RequestFocus { get; set; }
+
+ ///
+ /// Toggle window is open state.
+ ///
+ void Toggle();
+
+ ///
+ /// Bring this window to the front.
+ ///
+ void BringToFront();
+
+ ///
+ /// Code to always be executed before the open-state of the window is checked.
+ ///
+ 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.
+ ///
+ bool DrawConditions();
+
+ ///
+ /// Code to be executed before conditionals are applied and the window is drawn.
+ ///
+ void PreDraw();
+
+ ///
+ /// Code to be executed after the window is drawn.
+ ///
+ 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.
+ ///
+ void Draw();
+
+ ///
+ /// Code to be executed when the window is opened.
+ ///
+ void OnOpen();
+
+ ///
+ /// Code to be executed when the window is closed.
+ ///
+ void OnClose();
+
+ ///
+ /// Code to be executed when the window is safe to be disposed or removed from the window system.
+ /// Doing so in may result in animations not playing correctly.
+ ///
+ void OnSafeToRemove();
+
+ ///
+ /// Code to be executed every frame, even when the window is collapsed.
+ ///
+ void Update();
+}
diff --git a/Dalamud/Interface/Windowing/IWindowSystem.cs b/Dalamud/Interface/Windowing/IWindowSystem.cs
new file mode 100644
index 000000000..500f3d96b
--- /dev/null
+++ b/Dalamud/Interface/Windowing/IWindowSystem.cs
@@ -0,0 +1,51 @@
+using System.Collections.Generic;
+
+namespace Dalamud.Interface.Windowing;
+
+///
+/// Class running a WindowSystem using implementations to simplify ImGui windowing.
+///
+public interface IWindowSystem
+{
+ ///
+ /// Gets a read-only list of all s in this .
+ ///
+ IReadOnlyList Windows { get; }
+
+ ///
+ /// Gets a value indicating whether any window in this has focus and is
+ /// not marked to be excluded from consideration.
+ ///
+ bool HasAnyFocus { get; }
+
+ ///
+ /// Gets or sets the name/ID-space of this .
+ ///
+ string? Namespace { get; set; }
+
+ ///
+ /// Add a window to this .
+ /// The window system doesn't own your window, it just renders it
+ /// You need to store a reference to it to use it later.
+ ///
+ /// The window to add.
+ void AddWindow(IWindow window);
+
+ ///
+ /// Remove a window from this .
+ /// Will not dispose your window, if it is disposable.
+ ///
+ /// The window to remove.
+ void RemoveWindow(IWindow window);
+
+ ///
+ /// Remove all windows from this .
+ /// Will not dispose your windows, if they are disposable.
+ ///
+ void RemoveAllWindows();
+
+ ///
+ /// Draw all registered windows using ImGui.
+ ///
+ void Draw();
+}
diff --git a/Dalamud/Interface/Windowing/TitleBarButton.cs b/Dalamud/Interface/Windowing/TitleBarButton.cs
new file mode 100644
index 000000000..5fa1eab11
--- /dev/null
+++ b/Dalamud/Interface/Windowing/TitleBarButton.cs
@@ -0,0 +1,45 @@
+using System.Numerics;
+
+using Dalamud.Bindings.ImGui;
+
+namespace Dalamud.Interface.Windowing;
+
+///
+/// Structure describing a title bar button.
+///
+public class TitleBarButton
+{
+ ///
+ /// Gets or sets the icon of the button.
+ ///
+ public FontAwesomeIcon Icon { get; set; }
+
+ ///
+ /// Gets or sets a vector by which the position of the icon within the button shall be offset.
+ /// Automatically scaled by the global font scale for you.
+ ///
+ public Vector2 IconOffset { get; set; }
+
+ ///
+ /// Gets or sets an action that is called when a tooltip shall be drawn.
+ /// May be null if no tooltip shall be drawn.
+ ///
+ public Action? ShowTooltip { get; set; }
+
+ ///
+ /// Gets or sets an action that is called when the button is clicked.
+ ///
+ public Action Click { get; set; }
+
+ ///
+ /// Gets or sets the priority the button shall be shown in.
+ /// Lower = closer to ImGui default buttons.
+ ///
+ public int Priority { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether the button shall be clickable
+ /// when the respective window is set to clickthrough.
+ ///
+ public bool AvailableClickthrough { get; set; }
+}
diff --git a/Dalamud/Interface/Windowing/Window.cs b/Dalamud/Interface/Windowing/Window.cs
index f12e87099..fc2ab959f 100644
--- a/Dalamud/Interface/Windowing/Window.cs
+++ b/Dalamud/Interface/Windowing/Window.cs
@@ -1,63 +1,13 @@
using System.Collections.Generic;
-using System.Diagnostics;
-using System.Diagnostics.CodeAnalysis;
-using System.Linq;
using System.Numerics;
-using System.Runtime.InteropServices;
-using System.Threading.Tasks;
-using CheapLoc;
using Dalamud.Bindings.ImGui;
-using Dalamud.Game.ClientState.Keys;
-using Dalamud.Interface.Colors;
-using Dalamud.Interface.Components;
-using Dalamud.Interface.Internal;
-using Dalamud.Interface.Textures.Internal;
-using Dalamud.Interface.Textures.TextureWraps;
-using Dalamud.Interface.Utility;
-using Dalamud.Interface.Utility.Internal;
-using Dalamud.Interface.Utility.Raii;
-using Dalamud.Interface.Windowing.Persistence;
-using Dalamud.Logging.Internal;
-using Dalamud.Utility;
-
-using FFXIVClientStructs.FFXIV.Client.UI;
namespace Dalamud.Interface.Windowing;
-///
-/// Base class you can use to implement an ImGui window for use with the built-in .
-///
-public abstract class Window
+///
+public abstract class Window : IWindow
{
- private const float FadeInOutTime = 0.072f;
-
- private static readonly ModuleLog Log = new("WindowSystem");
-
- private static bool wasEscPressedLastFrame = false;
-
- private bool internalLastIsOpen = false;
- private bool internalIsOpen = false;
- private bool internalIsPinned = false;
- private bool internalIsClickthrough = false;
- private bool didPushInternalAlpha = false;
- private float? internalAlpha = null;
- private bool nextFrameBringToFront = false;
-
- private bool hasInitializedFromPreset = false;
- private PresetModel.PresetWindow? presetWindow;
- private bool presetDirty = false;
-
- private bool pushedFadeInAlpha = false;
- private float fadeInTimer = 0f;
- private float fadeOutTimer = 0f;
- private IDrawListTextureWrap? fadeOutTexture = null;
- private Vector2 fadeOutSize = Vector2.Zero;
- private Vector2 fadeOutOrigin = Vector2.Zero;
-
- private bool hasError = false;
- private Exception? lastError;
-
///
/// Initializes a new instance of the class.
///
@@ -86,936 +36,142 @@ public abstract class Window
{
}
- ///
- /// Flags to control window behavior.
- ///
- [Flags]
- internal enum WindowDrawFlags
- {
- ///
- /// Nothing.
- ///
- None = 0,
-
- ///
- /// Enable window opening/closing sound effects.
- ///
- UseSoundEffects = 1 << 0,
-
- ///
- /// Hook into the game's focus management.
- ///
- UseFocusManagement = 1 << 1,
-
- ///
- /// Enable the built-in "additional options" menu on the title bar.
- ///
- UseAdditionalOptions = 1 << 2,
-
- ///
- /// Do not draw non-critical animations.
- ///
- IsReducedMotion = 1 << 3,
- }
-
- ///
- /// 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; }
+ ///
+ public bool IsFocused { get; 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 a value indicating whether this window should not generate sound effects when opening and closing.
- ///
+ ///
public bool DisableWindowSounds { get; set; } = false;
- ///
- /// Gets or sets a value representing the sound effect id to be played when the window is opened.
- ///
+ ///
public uint OnOpenSfxId { get; set; } = 23u;
- ///
- /// Gets or sets a value representing the sound effect id to be played when the window is closed.
- ///
+ ///
public uint OnCloseSfxId { get; set; } = 24u;
- ///
- /// Gets or sets a value indicating whether this window should not fade in and out, regardless of the users'
- /// preference.
- ///
+ ///
public bool DisableFadeInFadeOut { get; set; } = false;
- ///
- /// 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 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 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 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 this window should offer to be pinned via the window's titlebar context menu.
- ///
+ ///
public bool AllowPinning { get; set; } = true;
- ///
- /// Gets or sets a value indicating whether this window should offer to be made click-through via the window's titlebar context menu.
- ///
+ ///
public bool AllowClickthrough { get; set; } = true;
- ///
- /// Gets a value indicating whether this window is pinned.
- ///
- public bool IsPinned => this.internalIsPinned;
+ ///
+ public bool IsPinned { get; set; }
- ///
- /// Gets a value indicating whether this window is click-through.
- ///
- public bool IsClickthrough => this.internalIsClickthrough;
+ ///
+ public bool IsClickthrough { get; set; }
- ///
- /// Gets or sets a list of available title bar buttons.
- ///
- /// If or are set to true, and this features is not
- /// disabled globally by the user, an internal title bar button to manage these is added when drawing, but it will
- /// not appear in this collection. If you wish to remove this button, set both of these values to false.
- ///
- public List TitleBarButtons { get; set; } = new();
+ ///
+ public List TitleBarButtons { get; set; } = [];
- ///
- /// Gets or sets a value indicating whether this window will stay open.
- ///
- public bool IsOpen
- {
- get => this.internalIsOpen;
- set => this.internalIsOpen = value;
- }
+ ///
+ public bool IsOpen { get; set; }
- private bool CanShowCloseButton => this.ShowCloseButton && !this.internalIsClickthrough;
+ ///
+ public bool RequestFocus { get; set; }
- ///
- /// 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;
+ this.RequestFocus = 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()
{
- if (this.internalAlpha.HasValue)
- {
- ImGui.PushStyleVar(ImGuiStyleVar.Alpha, this.internalAlpha.Value);
- this.didPushInternalAlpha = true;
- }
}
- ///
- /// Code to be executed after the window is drawn.
- ///
+ ///
public virtual void PostDraw()
{
- if (this.didPushInternalAlpha)
- {
- ImGui.PopStyleVar();
- this.didPushInternalAlpha = false;
- }
}
- ///
- /// 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 when the window is safe to be disposed or removed from the window system.
- /// Doing so in may result in animations not playing correctly.
- ///
+ ///
public virtual void OnSafeToRemove()
{
}
- ///
- /// Code to be executed every frame, even when the window is collapsed.
- ///
+ ///
public virtual void Update()
{
}
-
- ///
- /// Draw the window via ImGui.
- ///
- /// Flags controlling window behavior.
- /// Handler for window persistence data.
- internal void DrawInternal(WindowDrawFlags internalDrawFlags, WindowSystemPersistence? persistence)
- {
- this.PreOpenCheck();
- var doFades = !internalDrawFlags.HasFlag(WindowDrawFlags.IsReducedMotion) && !this.DisableFadeInFadeOut;
-
- if (!this.IsOpen)
- {
- if (this.internalIsOpen != this.internalLastIsOpen)
- {
- this.internalLastIsOpen = this.internalIsOpen;
- this.OnClose();
-
- this.IsFocused = false;
-
- if (internalDrawFlags.HasFlag(WindowDrawFlags.UseSoundEffects) && !this.DisableWindowSounds)
- UIGlobals.PlaySoundEffect(this.OnCloseSfxId);
- }
-
- if (this.fadeOutTexture != null)
- {
- this.fadeOutTimer -= ImGui.GetIO().DeltaTime;
- if (this.fadeOutTimer <= 0f)
- {
- this.fadeOutTexture.Dispose();
- this.fadeOutTexture = null;
- this.OnSafeToRemove();
- }
- else
- {
- this.DrawFakeFadeOutWindow();
- }
- }
-
- this.fadeInTimer = doFades ? 0f : FadeInOutTime;
- return;
- }
-
- this.fadeInTimer += ImGui.GetIO().DeltaTime;
- if (this.fadeInTimer > FadeInOutTime)
- this.fadeInTimer = FadeInOutTime;
-
- this.Update();
- if (!this.DrawConditions())
- return;
-
- var hasNamespace = !string.IsNullOrEmpty(this.Namespace);
-
- if (hasNamespace)
- ImGui.PushID(this.Namespace);
-
- this.PreHandlePreset(persistence);
-
- if (this.internalLastIsOpen != this.internalIsOpen && this.internalIsOpen)
- {
- this.internalLastIsOpen = this.internalIsOpen;
- this.OnOpen();
-
- if (internalDrawFlags.HasFlag(WindowDrawFlags.UseSoundEffects) && !this.DisableWindowSounds)
- UIGlobals.PlaySoundEffect(this.OnOpenSfxId);
- }
-
- this.PreDraw();
- this.ApplyConditionals();
-
- if (this.ForceMainWindow)
- ImGuiHelpers.ForceNextWindowMainViewport();
-
- 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;
- }
-
- var flags = this.Flags;
-
- if (this.internalIsPinned || this.internalIsClickthrough)
- flags |= ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize;
-
- if (this.internalIsClickthrough)
- flags |= ImGuiWindowFlags.NoInputs | ImGuiWindowFlags.NoNav | ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoScrollWithMouse | ImGuiWindowFlags.NoMouseInputs;
-
- if (this.CanShowCloseButton ? ImGui.Begin(this.WindowName, ref this.internalIsOpen, flags) : ImGui.Begin(this.WindowName, flags))
- {
- var context = ImGui.GetCurrentContext();
- if (!context.IsNull)
- {
- ImGuiP.GetCurrentWindow().InheritNoInputs = this.internalIsClickthrough;
- }
-
- // Not supported yet on non-main viewports
- if ((this.internalIsPinned || this.internalIsClickthrough || this.internalAlpha.HasValue) &&
- ImGui.GetWindowViewport().ID != ImGui.GetMainViewport().ID)
- {
- this.internalAlpha = null;
- this.internalIsPinned = false;
- this.internalIsClickthrough = false;
- this.presetDirty = true;
- }
-
- if (this.hasError)
- {
- this.DrawErrorMessage();
- }
- else
- {
- // Draw the actual window contents
- try
- {
- this.Draw();
- }
- catch (Exception ex)
- {
- Log.Error(ex, "Error during Draw(): {WindowName}", this.WindowName);
-
- this.hasError = true;
- this.lastError = ex;
- }
- }
- }
-
- const string additionsPopupName = "WindowSystemContextActions";
- var flagsApplicableForTitleBarIcons = !flags.HasFlag(ImGuiWindowFlags.NoDecoration) &&
- !flags.HasFlag(ImGuiWindowFlags.NoTitleBar);
- var showAdditions = (this.AllowPinning || this.AllowClickthrough) &&
- internalDrawFlags.HasFlag(WindowDrawFlags.UseAdditionalOptions) &&
- flagsApplicableForTitleBarIcons;
- var printWindow = false;
- if (showAdditions)
- {
- ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 1f);
-
- if (ImGui.BeginPopup(additionsPopupName, ImGuiWindowFlags.NoMove))
- {
- var isAvailable = ImGuiHelpers.CheckIsWindowOnMainViewport();
-
- if (!isAvailable)
- ImGui.BeginDisabled();
-
- if (this.internalIsClickthrough)
- ImGui.BeginDisabled();
-
- if (this.AllowPinning)
- {
- var showAsPinned = this.internalIsPinned || this.internalIsClickthrough;
- if (ImGui.Checkbox(Loc.Localize("WindowSystemContextActionPin", "Pin Window"), ref showAsPinned))
- {
- this.internalIsPinned = showAsPinned;
- this.presetDirty = true;
- }
-
- ImGuiComponents.HelpMarker(
- Loc.Localize("WindowSystemContextActionPinHint", "Pinned windows will not move or resize when you click and drag them, nor will they close when escape is pressed."));
- }
-
- if (this.internalIsClickthrough)
- ImGui.EndDisabled();
-
- if (this.AllowClickthrough)
- {
- if (ImGui.Checkbox(
- Loc.Localize("WindowSystemContextActionClickthrough", "Make clickthrough"),
- ref this.internalIsClickthrough))
- {
- this.presetDirty = true;
- }
-
- ImGuiComponents.HelpMarker(
- Loc.Localize("WindowSystemContextActionClickthroughHint", "Clickthrough windows will not receive mouse input, move or resize. They are completely inert."));
- }
-
- var alpha = (this.internalAlpha ?? ImGui.GetStyle().Alpha) * 100f;
- if (ImGui.SliderFloat(Loc.Localize("WindowSystemContextActionAlpha", "Opacity"), ref alpha, 20f,
- 100f))
- {
- this.internalAlpha = Math.Clamp(alpha / 100f, 0.2f, 1f);
- this.presetDirty = true;
- }
-
- ImGui.SameLine();
- if (ImGui.Button(Loc.Localize("WindowSystemContextActionReset", "Reset")))
- {
- this.internalAlpha = null;
- this.presetDirty = true;
- }
-
- if (isAvailable)
- {
- ImGui.TextColored(ImGuiColors.DalamudGrey,
- Loc.Localize("WindowSystemContextActionClickthroughDisclaimer",
- "Open this menu again by clicking the three dashes to disable clickthrough."));
- }
- else
- {
- ImGui.TextColored(ImGuiColors.DalamudGrey,
- Loc.Localize("WindowSystemContextActionViewportDisclaimer",
- "These features are only available if this window is inside the game window."));
- }
-
- if (!isAvailable)
- ImGui.EndDisabled();
-
- if (ImGui.Button(Loc.Localize("WindowSystemContextActionPrintWindow", "Print window")))
- printWindow = true;
-
- ImGui.EndPopup();
- }
-
- ImGui.PopStyleVar();
- }
-
- unsafe
- {
- var window = ImGuiP.GetCurrentWindow();
-
- ImRect outRect;
- ImGuiP.TitleBarRect(&outRect, window);
-
- var additionsButton = new TitleBarButton
- {
- Icon = FontAwesomeIcon.Bars,
- IconOffset = new Vector2(2.5f, 1),
- Click = _ =>
- {
- this.internalIsClickthrough = false;
- this.presetDirty = false;
- ImGui.OpenPopup(additionsPopupName);
- },
- Priority = int.MinValue,
- AvailableClickthrough = true,
- };
-
- if (flagsApplicableForTitleBarIcons)
- {
- this.DrawTitleBarButtons(window, flags, outRect,
- showAdditions
- ? this.TitleBarButtons.Append(additionsButton)
- : this.TitleBarButtons);
- }
- }
-
- if (wasFocused)
- {
- ImGui.PopStyleColor();
- }
-
- this.IsFocused = ImGui.IsWindowFocused(ImGuiFocusedFlags.RootAndChildWindows);
-
- if (internalDrawFlags.HasFlag(WindowDrawFlags.UseFocusManagement) && !this.internalIsPinned)
- {
- var escapeDown = Service.Get()[VirtualKey.ESCAPE];
- if (escapeDown && this.IsFocused && !wasEscPressedLastFrame && this.RespectCloseHotkey)
- {
- this.IsOpen = false;
- wasEscPressedLastFrame = true;
- }
- else if (!escapeDown && wasEscPressedLastFrame)
- {
- wasEscPressedLastFrame = false;
- }
- }
-
- this.fadeOutSize = ImGui.GetWindowSize();
- this.fadeOutOrigin = ImGui.GetWindowPos();
- var isCollapsed = ImGui.IsWindowCollapsed();
- var isDocked = ImGui.IsWindowDocked();
-
- ImGui.End();
-
- if (this.pushedFadeInAlpha)
- {
- ImGui.PopStyleVar();
- this.pushedFadeInAlpha = false;
- }
-
- // TODO: No fade-out if the window is collapsed. We could do this if we knew the "FullSize" of the window
- // from the internal ImGuiWindow, but I don't want to mess with that here for now. We can do this a lot
- // easier with the new bindings.
- // TODO: No fade-out if docking is enabled and the window is docked, since this makes them "unsnap".
- // Ideally we should get rid of this "fake window" thing and just insert a new drawlist at the correct spot.
- if (!this.internalIsOpen && this.fadeOutTexture == null && doFades && !isCollapsed && !isDocked)
- {
- this.fadeOutTexture = Service.Get().CreateDrawListTexture(
- "WindowFadeOutTexture");
- this.fadeOutTexture.ResizeAndDrawWindow(this.WindowName, Vector2.One);
- this.fadeOutTimer = FadeInOutTime;
- }
-
- if (printWindow)
- {
- var tex = Service.Get().CreateDrawListTexture(
- Loc.Localize("WindowSystemContextActionPrintWindow", "Print window"));
- tex.ResizeAndDrawWindow(this.WindowName, Vector2.One);
- _ = Service.Get().ShowTextureSaveMenuAsync(
- this.WindowName,
- this.WindowName,
- Task.FromResult(tex));
- }
-
- this.PostDraw();
-
- this.PostHandlePreset(persistence);
-
- if (hasNamespace)
- ImGui.PopID();
- }
-
- private unsafe 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);
- }
-
- var maxBgAlpha = this.internalAlpha ?? this.BgAlpha;
- var fadeInAlpha = this.fadeInTimer / FadeInOutTime;
- if (fadeInAlpha < 1f)
- {
- maxBgAlpha = maxBgAlpha.HasValue ?
- Math.Clamp(maxBgAlpha.Value * fadeInAlpha, 0f, 1f) :
- (*ImGui.GetStyleColorVec4(ImGuiCol.WindowBg)).W * fadeInAlpha;
- ImGui.PushStyleVar(ImGuiStyleVar.Alpha, ImGui.GetStyle().Alpha * fadeInAlpha);
- this.pushedFadeInAlpha = true;
- }
-
- if (maxBgAlpha.HasValue)
- {
- ImGui.SetNextWindowBgAlpha(maxBgAlpha.Value);
- }
- }
-
- private void PreHandlePreset(WindowSystemPersistence? persistence)
- {
- if (persistence == null || this.hasInitializedFromPreset)
- return;
-
- var id = ImGui.GetID(this.WindowName);
- this.presetWindow = persistence.GetWindow(id);
-
- this.hasInitializedFromPreset = true;
-
- // Fresh preset - don't apply anything
- if (this.presetWindow == null)
- {
- this.presetWindow = new PresetModel.PresetWindow();
- this.presetDirty = true;
- return;
- }
-
- this.internalIsPinned = this.presetWindow.IsPinned;
- this.internalIsClickthrough = this.presetWindow.IsClickThrough;
- this.internalAlpha = this.presetWindow.Alpha;
- }
-
- private void PostHandlePreset(WindowSystemPersistence? persistence)
- {
- if (persistence == null)
- return;
-
- Debug.Assert(this.presetWindow != null, "this.presetWindow != null");
-
- if (this.presetDirty)
- {
- this.presetWindow.IsPinned = this.internalIsPinned;
- this.presetWindow.IsClickThrough = this.internalIsClickthrough;
- this.presetWindow.Alpha = this.internalAlpha;
-
- var id = ImGui.GetID(this.WindowName);
- persistence.SaveWindow(id, this.presetWindow!);
- this.presetDirty = false;
-
- Log.Verbose("Saved preset for {WindowName}", this.WindowName);
- }
- }
-
- private unsafe void DrawTitleBarButtons(ImGuiWindowPtr window, ImGuiWindowFlags flags, ImRect titleBarRect, IEnumerable buttons)
- {
- ImGui.PushClipRect(ImGui.GetWindowPos(), ImGui.GetWindowPos() + ImGui.GetWindowSize(), false);
-
- var style = ImGui.GetStyle();
- var fontSize = ImGui.GetFontSize();
- var drawList = ImGui.GetWindowDrawList();
-
- var padR = 0f;
- var buttonSize = ImGui.GetFontSize();
-
- var numNativeButtons = 0;
- if (this.CanShowCloseButton)
- numNativeButtons++;
-
- if (!flags.HasFlag(ImGuiWindowFlags.NoCollapse) && style.WindowMenuButtonPosition == ImGuiDir.Right)
- numNativeButtons++;
-
- // If there are no native buttons, pad from the right to make some space
- if (numNativeButtons == 0)
- padR += style.FramePadding.X;
-
- // Pad to the left, to get out of the way of the native buttons
- padR += numNativeButtons * (buttonSize + style.ItemInnerSpacing.X);
-
- Vector2 GetCenter(ImRect rect) => new((rect.Min.X + rect.Max.X) * 0.5f, (rect.Min.Y + rect.Max.Y) * 0.5f);
-
- var numButtons = 0;
- bool DrawButton(TitleBarButton button, Vector2 pos)
- {
- var id = ImGui.GetID($"###CustomTbButton{numButtons}");
- numButtons++;
-
- var max = pos + new Vector2(fontSize, fontSize);
- ImRect bb = new(pos, max);
- var isClipped = !ImGuiP.ItemAdd(bb, id, null, 0);
- bool hovered, held;
- var pressed = false;
-
- if (this.internalIsClickthrough)
- {
- hovered = false;
- held = false;
-
- // ButtonBehavior does not function if the window is clickthrough, so we have to do it ourselves
- if (ImGui.IsMouseHoveringRect(pos, max))
- {
- hovered = true;
-
- // We can't use ImGui native functions here, because they don't work with clickthrough
- if ((Windows.Win32.PInvoke.GetKeyState((int)VirtualKey.LBUTTON) & 0x8000) != 0)
- {
- held = true;
- pressed = true;
- }
- }
- }
- else
- {
- pressed = ImGuiP.ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags.None);
- }
-
- if (isClipped)
- return pressed;
-
- // Render
- var bgCol = ImGui.GetColorU32((held && hovered) ? ImGuiCol.ButtonActive : hovered ? ImGuiCol.ButtonHovered : ImGuiCol.Button);
- var textCol = ImGui.GetColorU32(ImGuiCol.Text);
- if (hovered || held)
- drawList.AddCircleFilled(GetCenter(bb) + new Vector2(0.0f, -0.5f), (fontSize * 0.5f) + 1.0f, bgCol);
-
- var offset = button.IconOffset * ImGuiHelpers.GlobalScale;
- drawList.AddText(InterfaceManager.IconFont, (float)(fontSize * 0.8), new Vector2(bb.Min.X + offset.X, bb.Min.Y + offset.Y), textCol, button.Icon.ToIconString());
-
- if (hovered)
- button.ShowTooltip?.Invoke();
-
- // Switch to moving the window after mouse is moved beyond the initial drag threshold
- if (ImGui.IsItemActive() && ImGui.IsMouseDragging(ImGuiMouseButton.Left) && !this.internalIsClickthrough)
- ImGuiP.StartMouseMovingWindow(window);
-
- return pressed;
- }
-
- foreach (var button in buttons.OrderBy(x => x.Priority))
- {
- if (this.internalIsClickthrough && !button.AvailableClickthrough)
- return;
-
- Vector2 position = new(titleBarRect.Max.X - padR - buttonSize, titleBarRect.Min.Y + style.FramePadding.Y);
- padR += buttonSize + style.ItemInnerSpacing.X;
-
- if (DrawButton(button, position))
- button.Click?.Invoke(ImGuiMouseButton.Left);
- }
-
- ImGui.PopClipRect();
- }
-
- private void DrawFakeFadeOutWindow()
- {
- // Draw a fake window to fade out, so that the fade out texture stays in the right place in the
- // focus order
- ImGui.SetNextWindowPos(this.fadeOutOrigin);
- ImGui.SetNextWindowSize(this.fadeOutSize);
-
- using var style = ImRaii.PushStyle(ImGuiStyleVar.WindowPadding, Vector2.Zero);
- style.Push(ImGuiStyleVar.WindowBorderSize, 0);
- style.Push(ImGuiStyleVar.FrameBorderSize, 0);
-
- const ImGuiWindowFlags flags = ImGuiWindowFlags.NoInputs | ImGuiWindowFlags.NoNav |
- ImGuiWindowFlags.NoScrollWithMouse | ImGuiWindowFlags.NoMouseInputs |
- ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoBackground;
- if (ImGui.Begin(this.WindowName, flags))
- {
- var dl = ImGui.GetWindowDrawList();
- dl.AddImage(
- this.fadeOutTexture!.Handle,
- this.fadeOutOrigin,
- this.fadeOutOrigin + this.fadeOutSize,
- Vector2.Zero,
- Vector2.One,
- ImGui.ColorConvertFloat4ToU32(new(1f, 1f, 1f, Math.Clamp(this.fadeOutTimer / FadeInOutTime, 0f, 1f))));
- }
-
- ImGui.End();
- }
-
- private void DrawErrorMessage()
- {
- // TODO: Once window systems are services, offer to reload the plugin
- ImGui.TextColoredWrapped(ImGuiColors.DalamudRed,Loc.Localize("WindowSystemErrorOccurred", "An error occurred while rendering this window. Please contact the developer for details."));
-
- ImGuiHelpers.ScaledDummy(5);
-
- if (ImGui.Button(Loc.Localize("WindowSystemErrorRecoverButton", "Attempt to retry")))
- {
- this.hasError = false;
- this.lastError = null;
- }
-
- ImGui.SameLine();
-
- if (ImGui.Button(Loc.Localize("WindowSystemErrorClose", "Close Window")))
- {
- this.IsOpen = false;
- this.hasError = false;
- this.lastError = null;
- }
-
- ImGuiHelpers.ScaledDummy(10);
-
- if (this.lastError != null)
- {
- using var child = ImRaii.Child("##ErrorDetails", new Vector2(0, 200 * ImGuiHelpers.GlobalScale), true);
- using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudGrey))
- {
- ImGui.TextWrapped(Loc.Localize("WindowSystemErrorDetails", "Error Details:"));
- ImGui.Separator();
- ImGui.TextWrapped(this.lastError.ToString());
- }
-
- var childWindowSize = ImGui.GetWindowSize();
- var copyText = Loc.Localize("WindowSystemErrorCopy", "Copy");
- var buttonWidth = ImGuiComponents.GetIconButtonWithTextWidth(FontAwesomeIcon.Copy, copyText);
- ImGui.SetCursorPos(new Vector2(childWindowSize.X - buttonWidth - ImGui.GetStyle().FramePadding.X,
- ImGui.GetStyle().FramePadding.Y));
- if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Copy, copyText))
- {
- ImGui.SetClipboardText(this.lastError.ToString());
- }
- }
- }
-
- ///
- /// Structure detailing the size constraints of a window.
- ///
- public struct WindowSizeConstraints
- {
- private Vector2 internalMaxSize = new(float.MaxValue);
-
- ///
- /// Initializes a new instance of the struct.
- ///
- public WindowSizeConstraints()
- {
- }
-
- ///
- /// Gets or sets the minimum size of the window.
- ///
- public Vector2 MinimumSize { get; set; } = new(0);
-
- ///
- /// Gets or sets the maximum size of the window.
- ///
- public Vector2 MaximumSize
- {
- get => this.GetSafeMaxSize();
- set => this.internalMaxSize = value;
- }
-
- private Vector2 GetSafeMaxSize()
- {
- var currentMin = this.MinimumSize;
-
- if (this.internalMaxSize.X < currentMin.X || this.internalMaxSize.Y < currentMin.Y)
- return new Vector2(float.MaxValue);
-
- return this.internalMaxSize;
- }
- }
-
- ///
- /// Structure describing a title bar button.
- ///
- public class TitleBarButton
- {
- ///
- /// Gets or sets the icon of the button.
- ///
- public FontAwesomeIcon Icon { get; set; }
-
- ///
- /// Gets or sets a vector by which the position of the icon within the button shall be offset.
- /// Automatically scaled by the global font scale for you.
- ///
- public Vector2 IconOffset { get; set; }
-
- ///
- /// Gets or sets an action that is called when a tooltip shall be drawn.
- /// May be null if no tooltip shall be drawn.
- ///
- public Action? ShowTooltip { get; set; }
-
- ///
- /// Gets or sets an action that is called when the button is clicked.
- ///
- public Action Click { get; set; }
-
- ///
- /// Gets or sets the priority the button shall be shown in.
- /// Lower = closer to ImGui default buttons.
- ///
- public int Priority { get; set; }
-
- ///
- /// Gets or sets a value indicating whether the button shall be clickable
- /// when the respective window is set to clickthrough.
- ///
- public bool AvailableClickthrough { get; set; }
- }
}
diff --git a/Dalamud/Interface/Windowing/WindowHost.cs b/Dalamud/Interface/Windowing/WindowHost.cs
new file mode 100644
index 000000000..1fcf11379
--- /dev/null
+++ b/Dalamud/Interface/Windowing/WindowHost.cs
@@ -0,0 +1,721 @@
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Numerics;
+using System.Threading.Tasks;
+
+using CheapLoc;
+using Dalamud.Bindings.ImGui;
+using Dalamud.Game.ClientState.Keys;
+using Dalamud.Interface.Colors;
+using Dalamud.Interface.Components;
+using Dalamud.Interface.Internal;
+using Dalamud.Interface.Textures.Internal;
+using Dalamud.Interface.Textures.TextureWraps;
+using Dalamud.Interface.Utility;
+using Dalamud.Interface.Utility.Internal;
+using Dalamud.Interface.Utility.Raii;
+using Dalamud.Interface.Windowing.Persistence;
+using Dalamud.Logging.Internal;
+
+using FFXIVClientStructs.FFXIV.Client.UI;
+
+namespace Dalamud.Interface.Windowing;
+
+///
+/// Base class you can use to implement an ImGui window for use with the built-in .
+///
+public class WindowHost
+{
+ private const float FadeInOutTime = 0.072f;
+
+ private static readonly ModuleLog Log = new("WindowSystem");
+
+ private static bool wasEscPressedLastFrame = false;
+
+ private bool internalLastIsOpen = false;
+ private bool didPushInternalAlpha = false;
+ private float? internalAlpha = null;
+
+ private bool hasInitializedFromPreset = false;
+ private PresetModel.PresetWindow? presetWindow;
+ private bool presetDirty = false;
+
+ private bool pushedFadeInAlpha = false;
+ private float fadeInTimer = 0f;
+ private float fadeOutTimer = 0f;
+ private IDrawListTextureWrap? fadeOutTexture = null;
+ private Vector2 fadeOutSize = Vector2.Zero;
+ private Vector2 fadeOutOrigin = Vector2.Zero;
+
+ private bool hasError = false;
+ private Exception? lastError;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// A plugin provided window.
+ internal WindowHost(IWindow window)
+ {
+ this.Window = window;
+ }
+
+ ///
+ /// Flags to control window behavior.
+ ///
+ [Flags]
+ internal enum WindowDrawFlags
+ {
+ ///
+ /// Nothing.
+ ///
+ None = 0,
+
+ ///
+ /// Enable window opening/closing sound effects.
+ ///
+ UseSoundEffects = 1 << 0,
+
+ ///
+ /// Hook into the game's focus management.
+ ///
+ UseFocusManagement = 1 << 1,
+
+ ///
+ /// Enable the built-in "additional options" menu on the title bar.
+ ///
+ UseAdditionalOptions = 1 << 2,
+
+ ///
+ /// Do not draw non-critical animations.
+ ///
+ IsReducedMotion = 1 << 3,
+ }
+
+ ///
+ /// Gets or sets the backing window provided by the plugin.
+ ///
+ public IWindow Window { get; set; }
+
+ private bool CanShowCloseButton => this.Window.ShowCloseButton && !this.Window.IsClickthrough;
+
+ ///
+ /// Draw the window via ImGui.
+ ///
+ /// Flags controlling window behavior.
+ /// Handler for window persistence data.
+ internal void DrawInternal(WindowDrawFlags internalDrawFlags, WindowSystemPersistence? persistence)
+ {
+ this.Window.PreOpenCheck();
+ var doFades = !internalDrawFlags.HasFlag(WindowDrawFlags.IsReducedMotion) && !this.Window.DisableFadeInFadeOut;
+
+ if (!this.Window.IsOpen)
+ {
+ if (this.Window.IsOpen != this.internalLastIsOpen)
+ {
+ this.internalLastIsOpen = this.Window.IsOpen;
+ this.Window.OnClose();
+
+ this.Window.IsFocused = false;
+
+ if (internalDrawFlags.HasFlag(WindowDrawFlags.UseSoundEffects) && !this.Window.DisableWindowSounds)
+ UIGlobals.PlaySoundEffect(this.Window.OnCloseSfxId);
+ }
+
+ if (this.fadeOutTexture != null)
+ {
+ this.fadeOutTimer -= ImGui.GetIO().DeltaTime;
+ if (this.fadeOutTimer <= 0f)
+ {
+ this.fadeOutTexture.Dispose();
+ this.fadeOutTexture = null;
+ this.Window.OnSafeToRemove();
+ }
+ else
+ {
+ this.DrawFakeFadeOutWindow();
+ }
+ }
+
+ this.fadeInTimer = doFades ? 0f : FadeInOutTime;
+ return;
+ }
+
+ this.fadeInTimer += ImGui.GetIO().DeltaTime;
+ if (this.fadeInTimer > FadeInOutTime)
+ this.fadeInTimer = FadeInOutTime;
+
+ this.Window.Update();
+ if (!this.Window.DrawConditions())
+ return;
+
+ var hasNamespace = !string.IsNullOrEmpty(this.Window.Namespace);
+
+ if (hasNamespace)
+ ImGui.PushID(this.Window.Namespace);
+
+ this.PreHandlePreset(persistence);
+
+ if (this.internalLastIsOpen != this.Window.IsOpen && this.Window.IsOpen)
+ {
+ this.internalLastIsOpen = this.Window.IsOpen;
+ this.Window.OnOpen();
+
+ if (internalDrawFlags.HasFlag(WindowDrawFlags.UseSoundEffects) && !this.Window.DisableWindowSounds)
+ UIGlobals.PlaySoundEffect(this.Window.OnOpenSfxId);
+ }
+
+ // TODO: We may have to allow for windows to configure if they should fade
+ if (this.internalAlpha.HasValue)
+ {
+ ImGui.PushStyleVar(ImGuiStyleVar.Alpha, this.internalAlpha.Value);
+ this.didPushInternalAlpha = true;
+ }
+
+ this.Window.PreDraw();
+ this.ApplyConditionals();
+
+ if (this.Window.ForceMainWindow)
+ ImGuiHelpers.ForceNextWindowMainViewport();
+
+ var wasFocused = this.Window.IsFocused;
+ if (wasFocused)
+ {
+ var style = ImGui.GetStyle();
+ var focusedHeaderColor = style.Colors[(int)ImGuiCol.TitleBgActive];
+ ImGui.PushStyleColor(ImGuiCol.TitleBgCollapsed, focusedHeaderColor);
+ }
+
+ if (this.Window.RequestFocus)
+ {
+ ImGui.SetNextWindowFocus();
+ this.Window.RequestFocus = false;
+ }
+
+ var flags = this.Window.Flags;
+
+ if (this.Window.IsPinned || this.Window.IsClickthrough)
+ flags |= ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize;
+
+ if (this.Window.IsClickthrough)
+ flags |= ImGuiWindowFlags.NoInputs | ImGuiWindowFlags.NoNav | ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoScrollWithMouse | ImGuiWindowFlags.NoMouseInputs;
+
+ var isWindowOpen = this.Window.IsOpen;
+
+ if (this.CanShowCloseButton ? ImGui.Begin(this.Window.WindowName, ref isWindowOpen, flags) : ImGui.Begin(this.Window.WindowName, flags))
+ {
+ if (this.Window.IsOpen != isWindowOpen)
+ {
+ this.Window.IsOpen = isWindowOpen;
+ }
+
+ var context = ImGui.GetCurrentContext();
+ if (!context.IsNull)
+ {
+ ImGuiP.GetCurrentWindow().InheritNoInputs = this.Window.IsClickthrough;
+ }
+
+ // Not supported yet on non-main viewports
+ if ((this.Window.IsPinned || this.Window.IsClickthrough || this.internalAlpha.HasValue) &&
+ ImGui.GetWindowViewport().ID != ImGui.GetMainViewport().ID)
+ {
+ this.internalAlpha = null;
+ this.Window.IsPinned = false;
+ this.Window.IsClickthrough = false;
+ this.presetDirty = true;
+ }
+
+ // Draw the actual window contents
+ if (this.hasError)
+ {
+ this.DrawErrorMessage();
+ }
+ else
+ {
+ // Draw the actual window contents
+ try
+ {
+ this.Window.Draw();
+ }
+ catch (Exception ex)
+ {
+ Log.Error(ex, "Error during Draw(): {WindowName}", this.Window.WindowName);
+
+ this.hasError = true;
+ this.lastError = ex;
+ }
+ }
+ }
+
+ const string additionsPopupName = "WindowSystemContextActions";
+ var flagsApplicableForTitleBarIcons = !flags.HasFlag(ImGuiWindowFlags.NoDecoration) &&
+ !flags.HasFlag(ImGuiWindowFlags.NoTitleBar);
+ var showAdditions = (this.Window.AllowPinning || this.Window.AllowClickthrough) &&
+ internalDrawFlags.HasFlag(WindowDrawFlags.UseAdditionalOptions) &&
+ flagsApplicableForTitleBarIcons;
+ var printWindow = false;
+ if (showAdditions)
+ {
+ ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 1f);
+
+ if (ImGui.BeginPopup(additionsPopupName, ImGuiWindowFlags.NoMove))
+ {
+ var isAvailable = ImGuiHelpers.CheckIsWindowOnMainViewport();
+
+ if (!isAvailable)
+ ImGui.BeginDisabled();
+
+ if (this.Window.IsClickthrough)
+ ImGui.BeginDisabled();
+
+ if (this.Window.AllowPinning)
+ {
+ var showAsPinned = this.Window.IsPinned || this.Window.IsClickthrough;
+ if (ImGui.Checkbox(Loc.Localize("WindowSystemContextActionPin", "Pin Window"), ref showAsPinned))
+ {
+ this.Window.IsPinned = showAsPinned;
+ this.presetDirty = true;
+ }
+
+ ImGuiComponents.HelpMarker(
+ Loc.Localize("WindowSystemContextActionPinHint", "Pinned windows will not move or resize when you click and drag them, nor will they close when escape is pressed."));
+ }
+
+ if (this.Window.IsClickthrough)
+ ImGui.EndDisabled();
+
+ if (this.Window.AllowClickthrough)
+ {
+ var isClickthrough = this.Window.IsClickthrough;
+ if (ImGui.Checkbox(
+ Loc.Localize("WindowSystemContextActionClickthrough", "Make clickthrough"),
+ ref isClickthrough))
+ {
+ this.Window.IsClickthrough = isClickthrough;
+ this.presetDirty = true;
+ }
+
+ ImGuiComponents.HelpMarker(
+ Loc.Localize("WindowSystemContextActionClickthroughHint", "Clickthrough windows will not receive mouse input, move or resize. They are completely inert."));
+ }
+
+ var alpha = (this.internalAlpha ?? ImGui.GetStyle().Alpha) * 100f;
+ if (ImGui.SliderFloat(Loc.Localize("WindowSystemContextActionAlpha", "Opacity"), ref alpha, 20f,
+ 100f))
+ {
+ this.internalAlpha = Math.Clamp(alpha / 100f, 0.2f, 1f);
+ this.presetDirty = true;
+ }
+
+ ImGui.SameLine();
+ if (ImGui.Button(Loc.Localize("WindowSystemContextActionReset", "Reset")))
+ {
+ this.internalAlpha = null;
+ this.presetDirty = true;
+ }
+
+ if (isAvailable)
+ {
+ ImGui.TextColored(ImGuiColors.DalamudGrey,
+ Loc.Localize("WindowSystemContextActionClickthroughDisclaimer",
+ "Open this menu again by clicking the three dashes to disable clickthrough."));
+ }
+ else
+ {
+ ImGui.TextColored(ImGuiColors.DalamudGrey,
+ Loc.Localize("WindowSystemContextActionViewportDisclaimer",
+ "These features are only available if this window is inside the game window."));
+ }
+
+ if (!isAvailable)
+ ImGui.EndDisabled();
+
+ if (ImGui.Button(Loc.Localize("WindowSystemContextActionPrintWindow", "Print window")))
+ printWindow = true;
+
+ ImGui.EndPopup();
+ }
+
+ ImGui.PopStyleVar();
+ }
+
+ unsafe
+ {
+ var window = ImGuiP.GetCurrentWindow();
+
+ ImRect outRect;
+ ImGuiP.TitleBarRect(&outRect, window);
+
+ var additionsButton = new TitleBarButton
+ {
+ Icon = FontAwesomeIcon.Bars,
+ IconOffset = new Vector2(2.5f, 1),
+ Click = _ =>
+ {
+ this.Window.IsClickthrough = false;
+ this.presetDirty = false;
+ ImGui.OpenPopup(additionsPopupName);
+ },
+ Priority = int.MinValue,
+ AvailableClickthrough = true,
+ };
+
+ if (flagsApplicableForTitleBarIcons)
+ {
+ this.DrawTitleBarButtons(window, flags, outRect,
+ showAdditions
+ ? this.Window.TitleBarButtons.Append(additionsButton)
+ : this.Window.TitleBarButtons);
+ }
+ }
+
+ if (wasFocused)
+ {
+ ImGui.PopStyleColor();
+ }
+
+ this.Window.IsFocused = ImGui.IsWindowFocused(ImGuiFocusedFlags.RootAndChildWindows);
+
+ if (internalDrawFlags.HasFlag(WindowDrawFlags.UseFocusManagement) && !this.Window.IsPinned)
+ {
+ var escapeDown = Service.Get()[VirtualKey.ESCAPE];
+ if (escapeDown && this.Window.IsFocused && !wasEscPressedLastFrame && this.Window.RespectCloseHotkey)
+ {
+ this.Window.IsOpen = false;
+ wasEscPressedLastFrame = true;
+ }
+ else if (!escapeDown && wasEscPressedLastFrame)
+ {
+ wasEscPressedLastFrame = false;
+ }
+ }
+
+ this.fadeOutSize = ImGui.GetWindowSize();
+ this.fadeOutOrigin = ImGui.GetWindowPos();
+ var isCollapsed = ImGui.IsWindowCollapsed();
+ var isDocked = ImGui.IsWindowDocked();
+
+ ImGui.End();
+
+ if (this.pushedFadeInAlpha)
+ {
+ ImGui.PopStyleVar();
+ this.pushedFadeInAlpha = false;
+ }
+
+ // TODO: No fade-out if the window is collapsed. We could do this if we knew the "FullSize" of the window
+ // from the internal ImGuiWindow, but I don't want to mess with that here for now. We can do this a lot
+ // easier with the new bindings.
+ // TODO: No fade-out if docking is enabled and the window is docked, since this makes them "unsnap".
+ // Ideally we should get rid of this "fake window" thing and just insert a new drawlist at the correct spot.
+ if (!this.Window.IsOpen && this.fadeOutTexture == null && doFades && !isCollapsed && !isDocked)
+ {
+ this.fadeOutTexture = Service.Get().CreateDrawListTexture(
+ "WindowFadeOutTexture");
+ Log.Verbose("Attempting to fade out {WindowName}", this.Window.WindowName);
+ this.fadeOutTexture.ResizeAndDrawWindow(this.Window.WindowName, Vector2.One);
+ this.fadeOutTimer = FadeInOutTime;
+ }
+
+ if (printWindow)
+ {
+ var tex = Service.Get().CreateDrawListTexture(
+ Loc.Localize("WindowSystemContextActionPrintWindow", "Print window"));
+ tex.ResizeAndDrawWindow(this.Window.WindowName, Vector2.One);
+ _ = Service.Get().ShowTextureSaveMenuAsync(
+ this.Window.WindowName,
+ this.Window.WindowName,
+ Task.FromResult(tex));
+ }
+
+ if (this.didPushInternalAlpha)
+ {
+ ImGui.PopStyleVar();
+ this.didPushInternalAlpha = false;
+ }
+
+ this.Window.PostDraw();
+
+ this.PostHandlePreset(persistence);
+
+ if (hasNamespace)
+ ImGui.PopID();
+ }
+
+ private unsafe void ApplyConditionals()
+ {
+ if (this.Window.Position.HasValue)
+ {
+ var pos = this.Window.Position.Value;
+
+ if (this.Window.ForceMainWindow)
+ pos += ImGuiHelpers.MainViewport.Pos;
+
+ ImGui.SetNextWindowPos(pos, this.Window.PositionCondition);
+ }
+
+ if (this.Window.Size.HasValue)
+ {
+ ImGui.SetNextWindowSize(this.Window.Size.Value * ImGuiHelpers.GlobalScale, this.Window.SizeCondition);
+ }
+
+ if (this.Window.Collapsed.HasValue)
+ {
+ ImGui.SetNextWindowCollapsed(this.Window.Collapsed.Value, this.Window.CollapsedCondition);
+ }
+
+ if (this.Window.SizeConstraints.HasValue)
+ {
+ var (min, max) = this.GetValidatedConstraints(this.Window.SizeConstraints.Value);
+ ImGui.SetNextWindowSizeConstraints(
+ min * ImGuiHelpers.GlobalScale,
+ max * ImGuiHelpers.GlobalScale);
+ }
+
+ var maxBgAlpha = this.internalAlpha ?? this.Window.BgAlpha;
+ var fadeInAlpha = this.fadeInTimer / FadeInOutTime;
+ if (fadeInAlpha < 1f)
+ {
+ maxBgAlpha = maxBgAlpha.HasValue ?
+ Math.Clamp(maxBgAlpha.Value * fadeInAlpha, 0f, 1f) :
+ (*ImGui.GetStyleColorVec4(ImGuiCol.WindowBg)).W * fadeInAlpha;
+ ImGui.PushStyleVar(ImGuiStyleVar.Alpha, ImGui.GetStyle().Alpha * fadeInAlpha);
+ this.pushedFadeInAlpha = true;
+ }
+
+ if (maxBgAlpha.HasValue)
+ {
+ ImGui.SetNextWindowBgAlpha(maxBgAlpha.Value);
+ }
+ }
+
+ private (Vector2 Min, Vector2 Max) GetValidatedConstraints(WindowSizeConstraints constraints)
+ {
+ var min = constraints.MinimumSize;
+ var max = constraints.MaximumSize;
+
+ // If max < min, treat as "no constraint" (float.MaxValue)
+ if (max.X < min.X || max.Y < min.Y)
+ max = new Vector2(float.MaxValue);
+
+ return (min, max);
+ }
+
+ private void PreHandlePreset(WindowSystemPersistence? persistence)
+ {
+ if (persistence == null || this.hasInitializedFromPreset)
+ return;
+
+ var id = ImGui.GetID(this.Window.WindowName);
+ this.presetWindow = persistence.GetWindow(id);
+
+ this.hasInitializedFromPreset = true;
+
+ // Fresh preset - don't apply anything
+ if (this.presetWindow == null)
+ {
+ this.presetWindow = new PresetModel.PresetWindow();
+ this.presetDirty = true;
+ return;
+ }
+
+ this.Window.IsPinned = this.presetWindow.IsPinned;
+ this.Window.IsClickthrough = this.presetWindow.IsClickThrough;
+ this.internalAlpha = this.presetWindow.Alpha;
+ }
+
+ private void PostHandlePreset(WindowSystemPersistence? persistence)
+ {
+ if (persistence == null)
+ return;
+
+ Debug.Assert(this.presetWindow != null, "this.presetWindow != null");
+
+ if (this.presetDirty)
+ {
+ this.presetWindow.IsPinned = this.Window.IsPinned;
+ this.presetWindow.IsClickThrough = this.Window.IsClickthrough;
+ this.presetWindow.Alpha = this.internalAlpha;
+
+ var id = ImGui.GetID(this.Window.WindowName);
+ persistence.SaveWindow(id, this.presetWindow!);
+ this.presetDirty = false;
+
+ Log.Verbose("Saved preset for {WindowName}", this.Window.WindowName);
+ }
+ }
+
+ private unsafe void DrawTitleBarButtons(ImGuiWindowPtr window, ImGuiWindowFlags flags, ImRect titleBarRect, IEnumerable buttons)
+ {
+ ImGui.PushClipRect(ImGui.GetWindowPos(), ImGui.GetWindowPos() + ImGui.GetWindowSize(), false);
+
+ var style = ImGui.GetStyle();
+ var fontSize = ImGui.GetFontSize();
+ var drawList = ImGui.GetWindowDrawList();
+
+ var padR = 0f;
+ var buttonSize = ImGui.GetFontSize();
+
+ var numNativeButtons = 0;
+ if (this.CanShowCloseButton)
+ numNativeButtons++;
+
+ if (!flags.HasFlag(ImGuiWindowFlags.NoCollapse) && style.WindowMenuButtonPosition == ImGuiDir.Right)
+ numNativeButtons++;
+
+ // If there are no native buttons, pad from the right to make some space
+ if (numNativeButtons == 0)
+ padR += style.FramePadding.X;
+
+ // Pad to the left, to get out of the way of the native buttons
+ padR += numNativeButtons * (buttonSize + style.ItemInnerSpacing.X);
+
+ Vector2 GetCenter(ImRect rect) => new((rect.Min.X + rect.Max.X) * 0.5f, (rect.Min.Y + rect.Max.Y) * 0.5f);
+
+ var numButtons = 0;
+ bool DrawButton(TitleBarButton button, Vector2 pos)
+ {
+ var id = ImGui.GetID($"###CustomTbButton{numButtons}");
+ numButtons++;
+
+ var max = pos + new Vector2(fontSize, fontSize);
+ ImRect bb = new(pos, max);
+ var isClipped = !ImGuiP.ItemAdd(bb, id, null, 0);
+ bool hovered, held;
+ var pressed = false;
+
+ if (this.Window.IsClickthrough)
+ {
+ hovered = false;
+ held = false;
+
+ // ButtonBehavior does not function if the window is clickthrough, so we have to do it ourselves
+ if (ImGui.IsMouseHoveringRect(pos, max))
+ {
+ hovered = true;
+
+ // We can't use ImGui native functions here, because they don't work with clickthrough
+ if ((global::Windows.Win32.PInvoke.GetKeyState((int)VirtualKey.LBUTTON) & 0x8000) != 0)
+ {
+ held = true;
+ pressed = true;
+ }
+ }
+ }
+ else
+ {
+ pressed = ImGuiP.ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags.None);
+ }
+
+ if (isClipped)
+ return pressed;
+
+ // Render
+ var bgCol = ImGui.GetColorU32((held && hovered) ? ImGuiCol.ButtonActive : hovered ? ImGuiCol.ButtonHovered : ImGuiCol.Button);
+ var textCol = ImGui.GetColorU32(ImGuiCol.Text);
+ if (hovered || held)
+ drawList.AddCircleFilled(GetCenter(bb) + new Vector2(0.0f, -0.5f), (fontSize * 0.5f) + 1.0f, bgCol);
+
+ var offset = button.IconOffset * ImGuiHelpers.GlobalScale;
+ drawList.AddText(InterfaceManager.IconFont, (float)(fontSize * 0.8), new Vector2(bb.Min.X + offset.X, bb.Min.Y + offset.Y), textCol, button.Icon.ToIconString());
+
+ if (hovered)
+ button.ShowTooltip?.Invoke();
+
+ // Switch to moving the window after mouse is moved beyond the initial drag threshold
+ if (ImGui.IsItemActive() && ImGui.IsMouseDragging(ImGuiMouseButton.Left) && !this.Window.IsClickthrough)
+ ImGuiP.StartMouseMovingWindow(window);
+
+ return pressed;
+ }
+
+ foreach (var button in buttons.OrderBy(x => x.Priority))
+ {
+ if (this.Window.IsClickthrough && !button.AvailableClickthrough)
+ return;
+
+ Vector2 position = new(titleBarRect.Max.X - padR - buttonSize, titleBarRect.Min.Y + style.FramePadding.Y);
+ padR += buttonSize + style.ItemInnerSpacing.X;
+
+ if (DrawButton(button, position))
+ button.Click?.Invoke(ImGuiMouseButton.Left);
+ }
+
+ ImGui.PopClipRect();
+ }
+
+ private void DrawFakeFadeOutWindow()
+ {
+ // Draw a fake window to fade out, so that the fade out texture stays in the right place in the
+ // focus order
+ ImGui.SetNextWindowPos(this.fadeOutOrigin);
+ ImGui.SetNextWindowSize(this.fadeOutSize);
+
+ using var style = ImRaii.PushStyle(ImGuiStyleVar.WindowPadding, Vector2.Zero);
+ style.Push(ImGuiStyleVar.WindowBorderSize, 0);
+ style.Push(ImGuiStyleVar.FrameBorderSize, 0);
+
+ const ImGuiWindowFlags flags = ImGuiWindowFlags.NoInputs | ImGuiWindowFlags.NoNav |
+ ImGuiWindowFlags.NoScrollWithMouse | ImGuiWindowFlags.NoMouseInputs |
+ ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoBackground;
+ if (ImGui.Begin(this.Window.WindowName, flags))
+ {
+ var dl = ImGui.GetWindowDrawList();
+ dl.AddImage(
+ this.fadeOutTexture!.Handle,
+ this.fadeOutOrigin,
+ this.fadeOutOrigin + this.fadeOutSize,
+ Vector2.Zero,
+ Vector2.One,
+ ImGui.ColorConvertFloat4ToU32(new(1f, 1f, 1f, Math.Clamp(this.fadeOutTimer / FadeInOutTime, 0f, 1f))));
+ }
+
+ ImGui.End();
+ }
+
+ private void DrawErrorMessage()
+ {
+ // TODO: Once window systems are services, offer to reload the plugin
+ ImGui.TextColoredWrapped(ImGuiColors.DalamudRed,Loc.Localize("WindowSystemErrorOccurred", "An error occurred while rendering this window. Please contact the developer for details."));
+
+ ImGuiHelpers.ScaledDummy(5);
+
+ if (ImGui.Button(Loc.Localize("WindowSystemErrorRecoverButton", "Attempt to retry")))
+ {
+ this.hasError = false;
+ this.lastError = null;
+ }
+
+ ImGui.SameLine();
+
+ if (ImGui.Button(Loc.Localize("WindowSystemErrorClose", "Close Window")))
+ {
+ this.Window.IsOpen = false;
+ this.hasError = false;
+ this.lastError = null;
+ }
+
+ ImGuiHelpers.ScaledDummy(10);
+
+ if (this.lastError != null)
+ {
+ using var child = ImRaii.Child("##ErrorDetails", new Vector2(0, 200 * ImGuiHelpers.GlobalScale), true);
+ using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudGrey))
+ {
+ ImGui.TextWrapped(Loc.Localize("WindowSystemErrorDetails", "Error Details:"));
+ ImGui.Separator();
+ ImGui.TextWrapped(this.lastError.ToString());
+ }
+
+ var childWindowSize = ImGui.GetWindowSize();
+ var copyText = Loc.Localize("WindowSystemErrorCopy", "Copy");
+ var buttonWidth = ImGuiComponents.GetIconButtonWithTextWidth(FontAwesomeIcon.Copy, copyText);
+ ImGui.SetCursorPos(new Vector2(childWindowSize.X - buttonWidth - ImGui.GetStyle().FramePadding.X,
+ ImGui.GetStyle().FramePadding.Y));
+ if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Copy, copyText))
+ {
+ ImGui.SetClipboardText(this.lastError.ToString());
+ }
+ }
+ }
+}
diff --git a/Dalamud/Interface/Windowing/WindowSizeConstraints.cs b/Dalamud/Interface/Windowing/WindowSizeConstraints.cs
new file mode 100644
index 000000000..2389ed95e
--- /dev/null
+++ b/Dalamud/Interface/Windowing/WindowSizeConstraints.cs
@@ -0,0 +1,26 @@
+using System.Numerics;
+
+namespace Dalamud.Interface.Windowing;
+
+///
+/// Structure detailing the size constraints of a window.
+///
+public struct WindowSizeConstraints
+{
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ public 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; }
+}
diff --git a/Dalamud/Interface/Windowing/WindowSystem.cs b/Dalamud/Interface/Windowing/WindowSystem.cs
index d6e9649bb..9676a9939 100644
--- a/Dalamud/Interface/Windowing/WindowSystem.cs
+++ b/Dalamud/Interface/Windowing/WindowSystem.cs
@@ -8,14 +8,12 @@ using Serilog;
namespace Dalamud.Interface.Windowing;
-///
-/// Class running a WindowSystem using implementations to simplify ImGui windowing.
-///
-public class WindowSystem
+///
+public class WindowSystem : IWindowSystem
{
private static DateTimeOffset lastAnyFocus;
- private readonly List windows = new();
+ private readonly List windows = new();
private string lastFocusedWindowName = string.Empty;
@@ -29,7 +27,7 @@ public class WindowSystem
}
///
- /// Gets a value indicating whether any contains any
+ /// Gets a value indicating whether any contains any
/// that has focus and is not marked to be excluded from consideration.
///
public static bool HasAnyWindowSystemFocus { get; internal set; } = false;
@@ -44,20 +42,13 @@ public class WindowSystem
///
public static TimeSpan TimeSinceLastAnyFocus => DateTimeOffset.Now - lastAnyFocus;
- ///
- /// Gets a read-only list of all s in this .
- ///
- public IReadOnlyList Windows => this.windows;
+ ///
+ public IReadOnlyList Windows => this.windows.Select(c => c.Window).ToList();
- ///
- /// Gets a value indicating whether any window in this has focus and is
- /// not marked to be excluded from consideration.
- ///
+ ///
public bool HasAnyFocus { get; private set; }
- ///
- /// Gets or sets the name/ID-space of this .
- ///
+ ///
public string? Namespace { get; set; }
///
@@ -66,42 +57,28 @@ public class WindowSystem
///
internal static bool ShouldInhibitAtkCloseEvents { get; set; }
- ///
- /// Add a window to this .
- /// The window system doesn't own your window, it just renders it
- /// You need to store a reference to it to use it later.
- ///
- /// The window to add.
- public void AddWindow(Window window)
+ ///
+ public void AddWindow(IWindow window)
{
- if (this.windows.Any(x => x.WindowName == window.WindowName))
+ if (this.windows.Any(x => x.Window.WindowName == window.WindowName))
throw new ArgumentException("A window with this name/ID already exists.");
- this.windows.Add(window);
+ this.windows.Add(new WindowHost(window));
}
- ///
- /// Remove a window from this .
- /// Will not dispose your window, if it is disposable.
- ///
- /// The window to remove.
- public void RemoveWindow(Window window)
+ ///
+ public void RemoveWindow(IWindow window)
{
- if (!this.windows.Contains(window))
+ if (this.windows.All(c => c.Window != window))
throw new ArgumentException("This window is not registered on this WindowSystem.");
- this.windows.Remove(window);
+ this.windows.RemoveAll(c => c.Window == window);
}
- ///
- /// Remove all windows from this .
- /// Will not dispose your windows, if they are disposable.
- ///
+ ///
public void RemoveAllWindows() => this.windows.Clear();
- ///
- /// Draw all registered windows using ImGui.
- ///
+ ///
public void Draw()
{
var hasNamespace = !string.IsNullOrEmpty(this.Namespace);
@@ -113,19 +90,19 @@ public class WindowSystem
var config = Service.GetNullable();
var persistence = Service.GetNullable();
- var flags = Window.WindowDrawFlags.None;
+ var flags = WindowHost.WindowDrawFlags.None;
if (config?.EnablePluginUISoundEffects ?? false)
- flags |= Window.WindowDrawFlags.UseSoundEffects;
+ flags |= WindowHost.WindowDrawFlags.UseSoundEffects;
if (config?.EnablePluginUiAdditionalOptions ?? false)
- flags |= Window.WindowDrawFlags.UseAdditionalOptions;
+ flags |= WindowHost.WindowDrawFlags.UseAdditionalOptions;
if (config?.IsFocusManagementEnabled ?? false)
- flags |= Window.WindowDrawFlags.UseFocusManagement;
+ flags |= WindowHost.WindowDrawFlags.UseFocusManagement;
if (config?.ReduceMotions ?? false)
- flags |= Window.WindowDrawFlags.IsReducedMotion;
+ flags |= WindowHost.WindowDrawFlags.IsReducedMotion;
// Shallow clone the list of windows so that we can edit it without modifying it while the loop is iterating
foreach (var window in this.windows.ToArray())
@@ -136,15 +113,15 @@ public class WindowSystem
window.DrawInternal(flags, persistence);
}
- var focusedWindow = this.windows.FirstOrDefault(window => window.IsFocused);
+ var focusedWindow = this.windows.FirstOrDefault(window => window.Window.IsFocused);
this.HasAnyFocus = focusedWindow != default;
if (this.HasAnyFocus)
{
- if (this.lastFocusedWindowName != focusedWindow.WindowName)
+ if (this.lastFocusedWindowName != focusedWindow.Window.WindowName)
{
- Log.Verbose($"WindowSystem \"{this.Namespace}\" Window \"{focusedWindow.WindowName}\" has focus now");
- this.lastFocusedWindowName = focusedWindow.WindowName;
+ Log.Verbose($"WindowSystem \"{this.Namespace}\" Window \"{focusedWindow.Window.WindowName}\" has focus now");
+ this.lastFocusedWindowName = focusedWindow.Window.WindowName;
}
HasAnyWindowSystemFocus = true;
@@ -161,10 +138,10 @@ public class WindowSystem
}
}
- ShouldInhibitAtkCloseEvents |= this.windows.Any(w => w.IsFocused &&
- w.RespectCloseHotkey &&
- !w.IsPinned &&
- !w.IsClickthrough);
+ ShouldInhibitAtkCloseEvents |= this.windows.Any(w => w.Window.IsFocused &&
+ w.Window.RespectCloseHotkey &&
+ !w.Window.IsPinned &&
+ !w.Window.IsClickthrough);
if (hasNamespace)
ImGui.PopID();