Implement new functionality since original PR

This commit is contained in:
Critical Impact 2025-11-29 13:48:34 +10:00
parent 660781393e
commit 2b67ebf466
3 changed files with 118 additions and 35 deletions

View file

@ -118,6 +118,16 @@ public interface IWindow
/// </summary> /// </summary>
bool AllowClickthrough { get; set; } bool AllowClickthrough { get; set; }
/// <summary>
/// Gets a value indicating whether this window is pinned.
/// </summary>
public bool IsPinned { get; set; }
/// <summary>
/// Gets a value indicating whether this window is click-through.
/// </summary>
public bool IsClickthrough { get; set; }
/// <summary> /// <summary>
/// Gets or sets a list of available title bar buttons. /// Gets or sets a list of available title bar buttons.
/// ///

View file

@ -99,6 +99,12 @@ public abstract class Window : IWindow
/// <inheritdoc/> /// <inheritdoc/>
public bool AllowClickthrough { get; set; } = true; public bool AllowClickthrough { get; set; } = true;
/// <inheritdoc/>
public bool IsPinned { get; set; }
/// <inheritdoc/>
public bool IsClickthrough { get; set; }
/// <inheritdoc/> /// <inheritdoc/>
public List<TitleBarButton> TitleBarButtons { get; set; } = []; public List<TitleBarButton> TitleBarButtons { get; set; } = [];

View file

@ -34,9 +34,6 @@ public class WindowHost
private static bool wasEscPressedLastFrame = false; private static bool wasEscPressedLastFrame = false;
private bool internalLastIsOpen = false; private bool internalLastIsOpen = false;
private bool internalIsOpen = false;
private bool internalIsPinned = false;
private bool internalIsClickthrough = false;
private bool didPushInternalAlpha = false; private bool didPushInternalAlpha = false;
private float? internalAlpha = null; private float? internalAlpha = null;
@ -51,6 +48,9 @@ public class WindowHost
private Vector2 fadeOutSize = Vector2.Zero; private Vector2 fadeOutSize = Vector2.Zero;
private Vector2 fadeOutOrigin = Vector2.Zero; private Vector2 fadeOutOrigin = Vector2.Zero;
private bool hasError = false;
private Exception? lastError;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="WindowHost"/> class. /// Initializes a new instance of the <see cref="WindowHost"/> class.
/// </summary> /// </summary>
@ -97,7 +97,7 @@ public class WindowHost
/// </summary> /// </summary>
public IWindow Window { get; set; } public IWindow Window { get; set; }
private bool CanShowCloseButton => this.Window.ShowCloseButton && !this.internalIsClickthrough; private bool CanShowCloseButton => this.Window.ShowCloseButton && !this.Window.IsClickthrough;
/// <summary> /// <summary>
/// Draw the window via ImGui. /// Draw the window via ImGui.
@ -111,9 +111,9 @@ public class WindowHost
if (!this.Window.IsOpen) if (!this.Window.IsOpen)
{ {
if (this.internalIsOpen != this.internalLastIsOpen) if (this.Window.IsOpen != this.internalLastIsOpen)
{ {
this.internalLastIsOpen = this.internalIsOpen; this.internalLastIsOpen = this.Window.IsOpen;
this.Window.OnClose(); this.Window.OnClose();
this.Window.IsFocused = false; this.Window.IsFocused = false;
@ -156,9 +156,9 @@ public class WindowHost
this.PreHandlePreset(persistence); this.PreHandlePreset(persistence);
if (this.internalLastIsOpen != this.internalIsOpen && this.internalIsOpen) if (this.internalLastIsOpen != this.Window.IsOpen && this.Window.IsOpen)
{ {
this.internalLastIsOpen = this.internalIsOpen; this.internalLastIsOpen = this.Window.IsOpen;
this.Window.OnOpen(); this.Window.OnOpen();
if (internalDrawFlags.HasFlag(WindowDrawFlags.UseSoundEffects) && !this.Window.DisableWindowSounds) if (internalDrawFlags.HasFlag(WindowDrawFlags.UseSoundEffects) && !this.Window.DisableWindowSounds)
@ -194,38 +194,56 @@ public class WindowHost
var flags = this.Window.Flags; var flags = this.Window.Flags;
if (this.internalIsPinned || this.internalIsClickthrough) if (this.Window.IsPinned || this.Window.IsClickthrough)
flags |= ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize; flags |= ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize;
if (this.internalIsClickthrough) if (this.Window.IsClickthrough)
flags |= ImGuiWindowFlags.NoInputs | ImGuiWindowFlags.NoNav | ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoScrollWithMouse | ImGuiWindowFlags.NoMouseInputs; flags |= ImGuiWindowFlags.NoInputs | ImGuiWindowFlags.NoNav | ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoScrollWithMouse | ImGuiWindowFlags.NoMouseInputs;
if (this.CanShowCloseButton ? ImGui.Begin(this.Window.WindowName, ref this.internalIsOpen, flags) : ImGui.Begin(this.Window.WindowName, flags)) 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(); var context = ImGui.GetCurrentContext();
if (!context.IsNull) if (!context.IsNull)
{ {
ImGuiP.GetCurrentWindow().InheritNoInputs = this.internalIsClickthrough; ImGuiP.GetCurrentWindow().InheritNoInputs = this.Window.IsClickthrough;
} }
// Not supported yet on non-main viewports // Not supported yet on non-main viewports
if ((this.internalIsPinned || this.internalIsClickthrough || this.internalAlpha.HasValue) && if ((this.Window.IsPinned || this.Window.IsClickthrough || this.internalAlpha.HasValue) &&
ImGui.GetWindowViewport().ID != ImGui.GetMainViewport().ID) ImGui.GetWindowViewport().ID != ImGui.GetMainViewport().ID)
{ {
this.internalAlpha = null; this.internalAlpha = null;
this.internalIsPinned = false; this.Window.IsPinned = false;
this.internalIsClickthrough = false; this.Window.IsClickthrough = false;
this.presetDirty = true; this.presetDirty = true;
} }
// Draw the actual window contents // Draw the actual window contents
try if (this.hasError)
{ {
this.Window.Draw(); this.DrawErrorMessage();
} }
catch (Exception ex) else
{ {
Log.Error(ex, "Error during Draw(): {WindowName}", this.Window.WindowName); // 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;
}
} }
} }
@ -247,15 +265,15 @@ public class WindowHost
if (!isAvailable) if (!isAvailable)
ImGui.BeginDisabled(); ImGui.BeginDisabled();
if (this.internalIsClickthrough) if (this.Window.IsClickthrough)
ImGui.BeginDisabled(); ImGui.BeginDisabled();
if (this.Window.AllowPinning) if (this.Window.AllowPinning)
{ {
var showAsPinned = this.internalIsPinned || this.internalIsClickthrough; var showAsPinned = this.Window.IsPinned || this.Window.IsClickthrough;
if (ImGui.Checkbox(Loc.Localize("WindowSystemContextActionPin", "Pin Window"), ref showAsPinned)) if (ImGui.Checkbox(Loc.Localize("WindowSystemContextActionPin", "Pin Window"), ref showAsPinned))
{ {
this.internalIsPinned = showAsPinned; this.Window.IsPinned = showAsPinned;
this.presetDirty = true; this.presetDirty = true;
} }
@ -263,15 +281,17 @@ public class WindowHost
Loc.Localize("WindowSystemContextActionPinHint", "Pinned windows will not move or resize when you click and drag them, nor will they close when escape is pressed.")); 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) if (this.Window.IsClickthrough)
ImGui.EndDisabled(); ImGui.EndDisabled();
if (this.Window.AllowClickthrough) if (this.Window.AllowClickthrough)
{ {
var isClickthrough = this.Window.IsClickthrough;
if (ImGui.Checkbox( if (ImGui.Checkbox(
Loc.Localize("WindowSystemContextActionClickthrough", "Make clickthrough"), Loc.Localize("WindowSystemContextActionClickthrough", "Make clickthrough"),
ref this.internalIsClickthrough)) ref isClickthrough))
{ {
this.Window.IsClickthrough = isClickthrough;
this.presetDirty = true; this.presetDirty = true;
} }
@ -283,7 +303,7 @@ public class WindowHost
if (ImGui.SliderFloat(Loc.Localize("WindowSystemContextActionAlpha", "Opacity"), ref alpha, 20f, if (ImGui.SliderFloat(Loc.Localize("WindowSystemContextActionAlpha", "Opacity"), ref alpha, 20f,
100f)) 100f))
{ {
this.internalAlpha = alpha / 100f; this.internalAlpha = Math.Clamp(alpha / 100f, 0.2f, 1f);
this.presetDirty = true; this.presetDirty = true;
} }
@ -332,7 +352,7 @@ public class WindowHost
IconOffset = new Vector2(2.5f, 1), IconOffset = new Vector2(2.5f, 1),
Click = _ => Click = _ =>
{ {
this.internalIsClickthrough = false; this.Window.IsClickthrough = false;
this.presetDirty = false; this.presetDirty = false;
ImGui.OpenPopup(additionsPopupName); ImGui.OpenPopup(additionsPopupName);
}, },
@ -356,7 +376,7 @@ public class WindowHost
this.Window.IsFocused = ImGui.IsWindowFocused(ImGuiFocusedFlags.RootAndChildWindows); this.Window.IsFocused = ImGui.IsWindowFocused(ImGuiFocusedFlags.RootAndChildWindows);
if (internalDrawFlags.HasFlag(WindowDrawFlags.UseFocusManagement) && !this.internalIsPinned) if (internalDrawFlags.HasFlag(WindowDrawFlags.UseFocusManagement) && !this.Window.IsPinned)
{ {
var escapeDown = Service<KeyState>.Get()[VirtualKey.ESCAPE]; var escapeDown = Service<KeyState>.Get()[VirtualKey.ESCAPE];
if (escapeDown && this.Window.IsFocused && !wasEscPressedLastFrame && this.Window.RespectCloseHotkey) if (escapeDown && this.Window.IsFocused && !wasEscPressedLastFrame && this.Window.RespectCloseHotkey)
@ -388,10 +408,11 @@ public class WindowHost
// easier with the new bindings. // easier with the new bindings.
// TODO: No fade-out if docking is enabled and the window is docked, since this makes them "unsnap". // 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. // 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) if (!this.Window.IsOpen && this.fadeOutTexture == null && doFades && !isCollapsed && !isDocked)
{ {
this.fadeOutTexture = Service<TextureManager>.Get().CreateDrawListTexture( this.fadeOutTexture = Service<TextureManager>.Get().CreateDrawListTexture(
"WindowFadeOutTexture"); "WindowFadeOutTexture");
Log.Verbose("Attempting to fade out {WindowName}", this.Window.WindowName);
this.fadeOutTexture.ResizeAndDrawWindow(this.Window.WindowName, Vector2.One); this.fadeOutTexture.ResizeAndDrawWindow(this.Window.WindowName, Vector2.One);
this.fadeOutTimer = FadeInOutTime; this.fadeOutTimer = FadeInOutTime;
} }
@ -498,8 +519,8 @@ public class WindowHost
return; return;
} }
this.internalIsPinned = this.presetWindow.IsPinned; this.Window.IsPinned = this.presetWindow.IsPinned;
this.internalIsClickthrough = this.presetWindow.IsClickThrough; this.Window.IsClickthrough = this.presetWindow.IsClickThrough;
this.internalAlpha = this.presetWindow.Alpha; this.internalAlpha = this.presetWindow.Alpha;
} }
@ -512,8 +533,8 @@ public class WindowHost
if (this.presetDirty) if (this.presetDirty)
{ {
this.presetWindow.IsPinned = this.internalIsPinned; this.presetWindow.IsPinned = this.Window.IsPinned;
this.presetWindow.IsClickThrough = this.internalIsClickthrough; this.presetWindow.IsClickThrough = this.Window.IsClickthrough;
this.presetWindow.Alpha = this.internalAlpha; this.presetWindow.Alpha = this.internalAlpha;
var id = ImGui.GetID(this.Window.WindowName); var id = ImGui.GetID(this.Window.WindowName);
@ -563,7 +584,7 @@ public class WindowHost
bool hovered, held; bool hovered, held;
var pressed = false; var pressed = false;
if (this.internalIsClickthrough) if (this.Window.IsClickthrough)
{ {
hovered = false; hovered = false;
held = false; held = false;
@ -602,7 +623,7 @@ public class WindowHost
button.ShowTooltip?.Invoke(); button.ShowTooltip?.Invoke();
// Switch to moving the window after mouse is moved beyond the initial drag threshold // Switch to moving the window after mouse is moved beyond the initial drag threshold
if (ImGui.IsItemActive() && ImGui.IsMouseDragging(ImGuiMouseButton.Left) && !this.internalIsClickthrough) if (ImGui.IsItemActive() && ImGui.IsMouseDragging(ImGuiMouseButton.Left) && !this.Window.IsClickthrough)
ImGuiP.StartMouseMovingWindow(window); ImGuiP.StartMouseMovingWindow(window);
return pressed; return pressed;
@ -610,7 +631,7 @@ public class WindowHost
foreach (var button in buttons.OrderBy(x => x.Priority)) foreach (var button in buttons.OrderBy(x => x.Priority))
{ {
if (this.internalIsClickthrough && !button.AvailableClickthrough) if (this.Window.IsClickthrough && !button.AvailableClickthrough)
return; return;
Vector2 position = new(titleBarRect.Max.X - padR - buttonSize, titleBarRect.Min.Y + style.FramePadding.Y); Vector2 position = new(titleBarRect.Max.X - padR - buttonSize, titleBarRect.Min.Y + style.FramePadding.Y);
@ -651,4 +672,50 @@ public class WindowHost
ImGui.End(); 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());
}
}
}
} }