diff --git a/Dalamud/Interface/Windowing/Window.cs b/Dalamud/Interface/Windowing/Window.cs
index f0914bb21..600e423e1 100644
--- a/Dalamud/Interface/Windowing/Window.cs
+++ b/Dalamud/Interface/Windowing/Window.cs
@@ -1,7 +1,10 @@
+using System.Diagnostics.CodeAnalysis;
using System.Numerics;
-
+using System.Runtime.InteropServices;
+using CheapLoc;
using Dalamud.Configuration.Internal;
using Dalamud.Game.ClientState.Keys;
+using Dalamud.Interface.Colors;
using Dalamud.Interface.Utility;
using Dalamud.Logging.Internal;
using FFXIVClientStructs.FFXIV.Client.UI;
@@ -20,6 +23,11 @@ public abstract class Window
private bool internalLastIsOpen = false;
private bool internalIsOpen = false;
+ private bool internalIsPinned = false;
+ private bool internalIsClickthrough = false;
+ private DateTimeOffset internalLastDisableClick = DateTimeOffset.MinValue;
+ private bool didPushInternalAlpha = false;
+ private float? internalAlpha = null;
private bool nextFrameBringToFront = false;
///
@@ -130,6 +138,16 @@ public abstract class Window
///
public bool ShowCloseButton { get; set; } = true;
+ ///
+ /// Gets or sets a value indicating whether or not 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 or not this window should offer to be made click-through via the window's titlebar context menu.
+ ///
+ public bool AllowClickthrough { get; set; } = true;
+
///
/// Gets or sets a value indicating whether or not this window will stay open.
///
@@ -185,6 +203,11 @@ public abstract class Window
///
public virtual void PreDraw()
{
+ if (this.internalAlpha.HasValue)
+ {
+ ImGui.PushStyleVar(ImGuiStyleVar.Alpha, this.internalAlpha.Value);
+ this.didPushInternalAlpha = true;
+ }
}
///
@@ -192,6 +215,11 @@ public abstract class Window
///
public virtual void PostDraw()
{
+ if (this.didPushInternalAlpha)
+ {
+ ImGui.PopStyleVar();
+ this.didPushInternalAlpha = false;
+ }
}
///
@@ -286,7 +314,15 @@ public abstract class Window
this.nextFrameBringToFront = false;
}
- if (this.ShowCloseButton ? ImGui.Begin(this.WindowName, ref this.internalIsOpen, this.Flags) : ImGui.Begin(this.WindowName, this.Flags))
+ var flags = this.Flags;
+
+ if (this.internalIsPinned)
+ flags |= ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize;
+
+ if (this.internalIsClickthrough)
+ flags |= ImGuiWindowFlags.NoInputs | ImGuiWindowFlags.NoNav | ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoScrollWithMouse | ImGuiWindowFlags.NoMouseInputs;
+
+ if (this.ShowCloseButton ? ImGui.Begin(this.WindowName, ref this.internalIsOpen, flags) : ImGui.Begin(this.WindowName, flags))
{
// Draw the actual window contents
try
@@ -297,6 +333,80 @@ public abstract class Window
{
Log.Error(ex, $"Error during Draw(): {this.WindowName}");
}
+
+ if (this.AllowPinning || this.AllowClickthrough)
+ {
+ ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 1f);
+
+ var popupName = "WindowSystemContextActions";
+ if (ImGui.BeginPopup(popupName))
+ {
+ if (this.internalIsClickthrough)
+ ImGui.BeginDisabled();
+
+ if (this.AllowPinning)
+ ImGui.Checkbox(Loc.Localize("WindowSystemContextActionPin", "Pin Window"), ref this.internalIsPinned);
+
+ if (this.internalIsClickthrough)
+ ImGui.EndDisabled();
+
+ if (this.AllowClickthrough)
+ {
+ if (ImGui.Checkbox(Loc.Localize("WindowSystemContextActionClickthrough", "Make clickthrough"),
+ ref this.internalIsClickthrough))
+ {
+ if (this.internalIsClickthrough)
+ this.internalIsPinned = true;
+ }
+ }
+
+ var alpha = (this.internalAlpha ?? ImGui.GetStyle().Alpha) * 100f;
+ if (ImGui.SliderFloat(Loc.Localize("WindowSystemContextActionAlpha", "Opacity"), ref alpha, 20f,
+ 100f))
+ {
+ this.internalAlpha = alpha / 100f;
+ }
+
+ ImGui.SameLine();
+ if (ImGui.Button(Loc.Localize("WindowSystemContextActionReset", "Reset")))
+ {
+ this.internalAlpha = null;
+ }
+
+ ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("WindowSystemContextActionDoubleClick", "Double click the title bar to disable clickthrough."));
+ ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("WindowSystemContextActionDisclaimer", "These options may not work for all plugins at the moment."));
+
+ ImGui.EndPopup();
+ }
+
+ ImGui.PopStyleVar();
+
+ var titleBarRect = Vector4.Zero;
+ unsafe
+ {
+ var window = ImGuiNativeAdditions.igGetCurrentWindow();
+ ImGuiNativeAdditions.ImGuiWindow_TitleBarRect(&titleBarRect, window);
+ }
+
+ if (ImGui.IsMouseHoveringRect(new Vector2(titleBarRect.X, titleBarRect.Y), new Vector2(titleBarRect.Z, titleBarRect.W), false))
+ {
+ if (ImGui.IsWindowHovered() && ImGui.IsMouseClicked(ImGuiMouseButton.Right))
+ {
+ ImGui.OpenPopup(popupName);
+ }
+
+ if (ImGui.IsMouseDown(ImGuiMouseButton.Left))
+ {
+ if (DateTime.Now - this.internalLastDisableClick < TimeSpan.FromMilliseconds(100))
+ {
+ this.internalIsPinned = false;
+ this.internalIsClickthrough = false;
+ }
+
+ this.internalLastDisableClick = DateTime.Now;
+ }
+ }
+ }
}
if (wasFocused)
@@ -375,6 +485,12 @@ public abstract class Window
{
ImGui.SetNextWindowBgAlpha(this.BgAlpha.Value);
}
+
+ // Manually set alpha takes precedence, if devs don't want that, they should turn it off
+ if (this.internalAlpha.HasValue)
+ {
+ ImGui.SetNextWindowBgAlpha(this.internalAlpha.Value);
+ }
}
///
@@ -392,4 +508,14 @@ public abstract class Window
///
public Vector2 MaximumSize { get; set; }
}
+
+ [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:Element should begin with upper-case letter", Justification = "imports")]
+ private static unsafe class ImGuiNativeAdditions
+ {
+ [DllImport("cimgui", CallingConvention = CallingConvention.Cdecl)]
+ public static extern unsafe void* igGetCurrentWindow();
+
+ [DllImport("cimgui", CallingConvention = CallingConvention.Cdecl)]
+ public static extern unsafe void ImGuiWindow_TitleBarRect(Vector4* pOut, void* window);
+ }
}