Support make clickthrough

This commit is contained in:
Soreepeong 2025-08-16 16:42:30 +09:00
parent e5451c37af
commit 544f8b28bf
3 changed files with 79 additions and 91 deletions

View file

@ -299,11 +299,12 @@ internal sealed partial class Win32InputHandler
private static void ViewportFlagsToWin32Styles(ImGuiViewportFlags flags, out int style, out int exStyle) private static void ViewportFlagsToWin32Styles(ImGuiViewportFlags flags, out int style, out int exStyle)
{ {
style = (int)(flags.HasFlag(ImGuiViewportFlags.NoDecoration) ? WS.WS_POPUP : WS.WS_OVERLAPPEDWINDOW); style = (flags & ImGuiViewportFlags.NoDecoration) != 0 ? unchecked((int)WS.WS_POPUP) : WS.WS_OVERLAPPEDWINDOW;
exStyle = exStyle = (flags & ImGuiViewportFlags.NoTaskBarIcon) != 0 ? WS.WS_EX_TOOLWINDOW : WS.WS_EX_APPWINDOW;
(int)(flags.HasFlag(ImGuiViewportFlags.NoTaskBarIcon) ? WS.WS_EX_TOOLWINDOW : (uint)WS.WS_EX_APPWINDOW);
exStyle |= WS.WS_EX_NOREDIRECTIONBITMAP; exStyle |= WS.WS_EX_NOREDIRECTIONBITMAP;
if (flags.HasFlag(ImGuiViewportFlags.TopMost)) if ((flags & ImGuiViewportFlags.TopMost) != 0)
exStyle |= WS.WS_EX_TOPMOST; exStyle |= WS.WS_EX_TOPMOST;
if ((flags & ImGuiViewportFlags.NoInputs) != 0)
exStyle |= WS.WS_EX_TRANSPARENT | WS.WS_EX_LAYERED;
} }
} }

View file

@ -8,6 +8,7 @@ using System.Text;
using Dalamud.Bindings.ImGui; using Dalamud.Bindings.ImGui;
using Dalamud.Memory; using Dalamud.Memory;
using Dalamud.Utility;
using Serilog; using Serilog;
@ -446,19 +447,19 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
ClientToScreen(this.hWnd, &pos); ClientToScreen(this.hWnd, &pos);
SetCursorPos(pos.x, pos.y); SetCursorPos(pos.x, pos.y);
} }
}
// (Optional) Fallback to provide mouse position when focused (WM_MOUSEMOVE already provides this when hovered or captured) // (Optional) Fallback to provide mouse position when focused (WM_MOUSEMOVE already provides this when hovered or captured)
if (!io.WantSetMousePos && !this.mouseTracked && hasMouseScreenPos) if (!io.WantSetMousePos && !this.mouseTracked && hasMouseScreenPos)
{ {
// Single viewport mode: mouse position in client window coordinates (io.MousePos is (0,0) when the mouse is on the upper-left corner of the app window) // Single viewport mode: mouse position in client window coordinates (io.MousePos is (0,0) when the mouse is on the upper-left corner of the app window)
// (This is the position you can get with ::GetCursorPos() + ::ScreenToClient() or WM_MOUSEMOVE.) // (This is the position you can get with ::GetCursorPos() + ::ScreenToClient() or WM_MOUSEMOVE.)
// Multi-viewport mode: mouse position in OS absolute coordinates (io.MousePos is (0,0) when the mouse is on the upper-left of the primary monitor) // Multi-viewport mode: mouse position in OS absolute coordinates (io.MousePos is (0,0) when the mouse is on the upper-left of the primary monitor)
// (This is the position you can get with ::GetCursorPos() or WM_MOUSEMOVE + ::ClientToScreen(). In theory adding viewport->Pos to a client position would also be the same.) // (This is the position you can get with ::GetCursorPos() or WM_MOUSEMOVE + ::ClientToScreen(). In theory adding viewport->Pos to a client position would also be the same.)
var mousePos = mouseScreenPos; var mousePos = mouseScreenPos;
if ((io.ConfigFlags & ImGuiConfigFlags.ViewportsEnable) == 0) if ((io.ConfigFlags & ImGuiConfigFlags.ViewportsEnable) == 0)
ClientToScreen(focusedWindow, &mousePos); ClientToScreen(focusedWindow, &mousePos);
io.AddMousePosEvent(mousePos.x, mousePos.y); io.AddMousePosEvent(mousePos.x, mousePos.y);
}
} }
// (Optional) When using multiple viewports: call io.AddMouseViewportEvent() with the viewport the OS mouse cursor is hovering. // (Optional) When using multiple viewports: call io.AddMouseViewportEvent() with the viewport the OS mouse cursor is hovering.
@ -827,6 +828,9 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
null); null);
} }
if (data->Hwnd == 0)
Util.Fatal($"CreateWindowExW failed: {GetLastError()}", "ImGui Viewport error");
data->HwndOwned = true; data->HwndOwned = true;
viewport.PlatformRequestResize = false; viewport.PlatformRequestResize = false;
viewport.PlatformHandle = viewport.PlatformHandleRaw = data->Hwnd; viewport.PlatformHandle = viewport.PlatformHandleRaw = data->Hwnd;

View file

@ -1,9 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Numerics; using System.Numerics;
using System.Runtime.InteropServices;
using System.Threading.Tasks; using System.Threading.Tasks;
using CheapLoc; using CheapLoc;
@ -19,10 +16,13 @@ using Dalamud.Interface.Utility.Internal;
using Dalamud.Interface.Utility.Raii; using Dalamud.Interface.Utility.Raii;
using Dalamud.Interface.Windowing.Persistence; using Dalamud.Interface.Windowing.Persistence;
using Dalamud.Logging.Internal; using Dalamud.Logging.Internal;
using Dalamud.Utility;
using FFXIVClientStructs.FFXIV.Client.UI; using FFXIVClientStructs.FFXIV.Client.UI;
using TerraFX.Interop.Windows;
using static TerraFX.Interop.Windows.Windows;
namespace Dalamud.Interface.Windowing; namespace Dalamud.Interface.Windowing;
/// <summary> /// <summary>
@ -31,11 +31,15 @@ namespace Dalamud.Interface.Windowing;
public abstract class Window public abstract class Window
{ {
private const float FadeInOutTime = 0.072f; private const float FadeInOutTime = 0.072f;
private const string AdditionsPopupName = "WindowSystemContextActions";
private static readonly ModuleLog Log = new("WindowSystem"); private static readonly ModuleLog Log = new("WindowSystem");
private static bool wasEscPressedLastFrame = false; private static bool wasEscPressedLastFrame = false;
private readonly TitleBarButton additionsButton;
private readonly List<TitleBarButton> allButtons = [];
private bool internalLastIsOpen = false; private bool internalLastIsOpen = false;
private bool internalIsOpen = false; private bool internalIsOpen = false;
private bool internalIsPinned = false; private bool internalIsPinned = false;
@ -69,6 +73,20 @@ public abstract class Window
this.WindowName = name; this.WindowName = name;
this.Flags = flags; this.Flags = flags;
this.ForceMainWindow = forceMainWindow; this.ForceMainWindow = forceMainWindow;
this.additionsButton = new()
{
Icon = FontAwesomeIcon.Bars,
IconOffset = new Vector2(2.5f, 1),
Click = _ =>
{
this.internalIsClickthrough = false;
this.presetDirty = false;
ImGui.OpenPopup(AdditionsPopupName);
},
Priority = int.MinValue,
AvailableClickthrough = true,
};
} }
/// <summary> /// <summary>
@ -448,11 +466,12 @@ public abstract class Window
ImGuiP.GetCurrentWindow().InheritNoInputs = this.internalIsClickthrough; ImGuiP.GetCurrentWindow().InheritNoInputs = this.internalIsClickthrough;
} }
// Not supported yet on non-main viewports if (ImGui.GetWindowViewport().ID != ImGui.GetMainViewport().ID)
if (this.internalIsClickthrough && ImGui.GetWindowViewport().ID != ImGui.GetMainViewport().ID)
{ {
this.internalIsClickthrough = false; if ((flags & ImGuiWindowFlags.NoInputs) == ImGuiWindowFlags.NoInputs)
this.presetDirty = true; ImGui.GetWindowViewport().Flags |= ImGuiViewportFlags.NoInputs;
else
ImGui.GetWindowViewport().Flags &= ~ImGuiViewportFlags.NoInputs;
} }
// Draw the actual window contents // Draw the actual window contents
@ -466,7 +485,6 @@ public abstract class Window
} }
} }
const string additionsPopupName = "WindowSystemContextActions";
var flagsApplicableForTitleBarIcons = !flags.HasFlag(ImGuiWindowFlags.NoDecoration) && var flagsApplicableForTitleBarIcons = !flags.HasFlag(ImGuiWindowFlags.NoDecoration) &&
!flags.HasFlag(ImGuiWindowFlags.NoTitleBar); !flags.HasFlag(ImGuiWindowFlags.NoTitleBar);
var showAdditions = (this.AllowPinning || this.AllowClickthrough) && var showAdditions = (this.AllowPinning || this.AllowClickthrough) &&
@ -477,7 +495,7 @@ public abstract class Window
{ {
ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 1f); ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 1f);
if (ImGui.BeginPopup(additionsPopupName, ImGuiWindowFlags.NoMove)) if (ImGui.BeginPopup(AdditionsPopupName, ImGuiWindowFlags.NoMove))
{ {
if (this.internalIsClickthrough) if (this.internalIsClickthrough)
ImGui.BeginDisabled(); ImGui.BeginDisabled();
@ -498,11 +516,6 @@ public abstract class Window
if (this.internalIsClickthrough) if (this.internalIsClickthrough)
ImGui.EndDisabled(); ImGui.EndDisabled();
var isAvailable = ImGuiHelpers.CheckIsWindowOnMainViewport();
if (!isAvailable)
ImGui.BeginDisabled();
if (this.AllowClickthrough) if (this.AllowClickthrough)
{ {
if (ImGui.Checkbox( if (ImGui.Checkbox(
@ -516,9 +529,6 @@ public abstract class Window
Loc.Localize("WindowSystemContextActionClickthroughHint", "Clickthrough windows will not receive mouse input, move or resize. They are completely inert.")); Loc.Localize("WindowSystemContextActionClickthroughHint", "Clickthrough windows will not receive mouse input, move or resize. They are completely inert."));
} }
if (!isAvailable)
ImGui.EndDisabled();
var alpha = (this.internalAlpha ?? ImGui.GetStyle().Alpha) * 100f; var alpha = (this.internalAlpha ?? ImGui.GetStyle().Alpha) * 100f;
if (ImGui.SliderFloat(Loc.Localize("WindowSystemContextActionAlpha", "Opacity"), ref alpha, 20f, if (ImGui.SliderFloat(Loc.Localize("WindowSystemContextActionAlpha", "Opacity"), ref alpha, 20f,
100f)) 100f))
@ -534,18 +544,11 @@ public abstract class Window
this.presetDirty = true; this.presetDirty = true;
} }
if (isAvailable) ImGui.TextColored(
{ ImGuiColors.DalamudGrey,
ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize(
Loc.Localize("WindowSystemContextActionClickthroughDisclaimer", "WindowSystemContextActionClickthroughDisclaimer",
"Open this menu again by clicking the three dashes to disable clickthrough.")); "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 (ImGui.Button(Loc.Localize("WindowSystemContextActionPrintWindow", "Print window"))) if (ImGui.Button(Loc.Localize("WindowSystemContextActionPrintWindow", "Print window")))
printWindow = true; printWindow = true;
@ -556,34 +559,15 @@ public abstract class Window
ImGui.PopStyleVar(); ImGui.PopStyleVar();
} }
unsafe if (flagsApplicableForTitleBarIcons)
{ {
var window = ImGuiP.GetCurrentWindow(); this.allButtons.Clear();
this.allButtons.EnsureCapacity(this.TitleBarButtons.Count + 1);
ImRect outRect; this.allButtons.AddRange(this.TitleBarButtons);
ImGuiP.TitleBarRect(&outRect, window); if (showAdditions)
this.allButtons.Add(this.additionsButton);
var additionsButton = new TitleBarButton this.allButtons.Sort(static (a, b) => b.Priority - a.Priority);
{ this.DrawTitleBarButtons();
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) if (wasFocused)
@ -740,8 +724,11 @@ public abstract class Window
} }
} }
private unsafe void DrawTitleBarButtons(ImGuiWindowPtr window, ImGuiWindowFlags flags, ImRect titleBarRect, IEnumerable<TitleBarButton> buttons) private unsafe void DrawTitleBarButtons()
{ {
var window = ImGuiP.GetCurrentWindow();
var flags = window.Flags;
var titleBarRect = window.TitleBarRect();
ImGui.PushClipRect(ImGui.GetWindowPos(), ImGui.GetWindowPos() + ImGui.GetWindowSize(), false); ImGui.PushClipRect(ImGui.GetWindowPos(), ImGui.GetWindowPos() + ImGui.GetWindowSize(), false);
var style = ImGui.GetStyle(); var style = ImGui.GetStyle();
@ -776,26 +763,22 @@ public abstract class Window
var max = pos + new Vector2(fontSize, fontSize); var max = pos + new Vector2(fontSize, fontSize);
ImRect bb = new(pos, max); ImRect bb = new(pos, max);
var isClipped = !ImGuiP.ItemAdd(bb, id, null, 0); var isClipped = !ImGuiP.ItemAdd(bb, id, null, 0);
bool hovered, held; bool hovered, held, pressed;
var pressed = false;
if (this.internalIsClickthrough) if (this.internalIsClickthrough)
{ {
hovered = false;
held = false;
// ButtonBehavior does not function if the window is clickthrough, so we have to do it ourselves // ButtonBehavior does not function if the window is clickthrough, so we have to do it ourselves
if (ImGui.IsMouseHoveringRect(pos, max)) var pad = ImGui.GetStyle().TouchExtraPadding;
{ var rect = new ImRect(pos - pad, max + pad);
hovered = true; hovered = rect.Contains(ImGui.GetMousePos());
// We can't use ImGui native functions here, because they don't work with clickthrough // Temporarily enable inputs
if ((global::Windows.Win32.PInvoke.GetKeyState((int)VirtualKey.LBUTTON) & 0x8000) != 0) // This will be reset on next frame, and then enabled again if it is still being hovered
{ if (hovered && ImGui.GetWindowViewport().ID != ImGui.GetMainViewport().ID)
held = true; ImGui.GetWindowViewport().Flags &= ~ImGuiViewportFlags.NoInputs;
pressed = true;
} // We can't use ImGui native functions here, because they don't work with clickthrough
} pressed = held = hovered && (GetKeyState(VK.VK_LBUTTON) & 0x8000) != 0;
} }
else else
{ {
@ -824,7 +807,7 @@ public abstract class Window
return pressed; return pressed;
} }
foreach (var button in buttons.OrderBy(x => x.Priority)) foreach (var button in this.allButtons)
{ {
if (this.internalIsClickthrough && !button.AvailableClickthrough) if (this.internalIsClickthrough && !button.AvailableClickthrough)
return; return;
@ -932,7 +915,7 @@ public abstract class Window
/// <summary> /// <summary>
/// Gets or sets an action that is called when the button is clicked. /// Gets or sets an action that is called when the button is clicked.
/// </summary> /// </summary>
public Action<ImGuiMouseButton> Click { get; set; } public Action<ImGuiMouseButton>? Click { get; set; }
/// <summary> /// <summary>
/// Gets or sets the priority the button shall be shown in. /// Gets or sets the priority the button shall be shown in.