WindowSystem: fix clickthrough option not applying to child windows, persist options

Persistence is pretty WIP. I want to offer multiple presets in the future, and save more things like window positions.
This commit is contained in:
goat 2024-12-30 21:14:08 +01:00
parent 49a18e3c1e
commit 35b49823e5
9 changed files with 301 additions and 80 deletions

View file

@ -12,6 +12,7 @@ using Dalamud.Interface.FontIdentifier;
using Dalamud.Interface.Internal;
using Dalamud.Interface.Internal.ReShadeHandling;
using Dalamud.Interface.Style;
using Dalamud.Interface.Windowing.Persistence;
using Dalamud.IoC.Internal;
using Dalamud.Plugin.Internal.AutoUpdate;
using Dalamud.Plugin.Internal.Profiles;
@ -264,8 +265,7 @@ internal sealed class DalamudConfiguration : IInternalDisposableService
/// Gets or sets a value indicating whether or not an additional button allowing pinning and clickthrough options should be shown
/// on plugin title bars when using the Window System.
/// </summary>
[JsonProperty("EnablePluginUiAdditionalOptionsExperimental")]
public bool EnablePluginUiAdditionalOptions { get; set; } = false;
public bool EnablePluginUiAdditionalOptions { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating whether viewports should always be disabled.
@ -351,6 +351,11 @@ internal sealed class DalamudConfiguration : IInternalDisposableService
/// </summary>
public bool ProfilesHasSeenTutorial { get; set; } = false;
/// <summary>
/// Gets or sets the default UI preset.
/// </summary>
public PresetModel DefaultUiPreset { get; set; } = new();
/// <summary>
/// Gets or sets the order of DTR elements, by title.
/// </summary>

View file

@ -27,6 +27,8 @@ using Dalamud.Interface.ManagedFontAtlas.Internals;
using Dalamud.Interface.Style;
using Dalamud.Interface.Utility;
using Dalamud.Interface.Windowing;
using Dalamud.Interface.Windowing.Persistence;
using Dalamud.IoC.Internal;
using Dalamud.Logging.Internal;
using Dalamud.Utility;
using Dalamud.Utility.Timing;
@ -60,6 +62,7 @@ namespace Dalamud.Interface.Internal;
/// This class manages interaction with the ImGui interface.
/// </summary>
[ServiceManager.EarlyLoadedService]
[InherentDependency<WindowSystemPersistence>] // Used by window system windows to restore state from the configuration
internal partial class InterfaceManager : IInternalDisposableService
{
/// <summary>

View file

@ -39,18 +39,6 @@ public class SettingsTabExperimental : SettingsTab
new GapSettingsEntry(5),
new SettingsEntry<bool>(
Loc.Localize(
"DalamudSettingEnablePluginUIAdditionalOptions",
"Add a button to the title bar of plugin windows to open additional options"),
Loc.Localize(
"DalamudSettingEnablePluginUIAdditionalOptionsHint",
"This will allow you to pin certain plugin windows, make them clickthrough or adjust their opacity.\nThis may not be supported by all of your plugins. Contact the plugin author if you want them to support this feature."),
c => c.EnablePluginUiAdditionalOptions,
(v, c) => c.EnablePluginUiAdditionalOptions = v),
new GapSettingsEntry(5),
new ButtonSettingsEntry(
Loc.Localize("DalamudSettingsClearHidden", "Clear hidden plugins"),
Loc.Localize(
@ -65,7 +53,7 @@ public class SettingsTabExperimental : SettingsTab
new GapSettingsEntry(5, true),
new DevPluginsSettingsEntry(),
new SettingsEntry<bool>(
Loc.Localize(
"DalamudSettingEnableImGuiAsserts",
@ -75,7 +63,7 @@ public class SettingsTabExperimental : SettingsTab
"If this setting is enabled, a window containing further details will be shown when an internal assertion in ImGui fails.\nWe recommend enabling this when developing plugins."),
c => Service<InterfaceManager>.Get().ShowAsserts,
(v, _) => Service<InterfaceManager>.Get().ShowAsserts = v),
new SettingsEntry<bool>(
Loc.Localize(
"DalamudSettingEnableImGuiAssertsAtStartup",

View file

@ -24,7 +24,7 @@ namespace Dalamud.Interface.Internal.Windows.Settings.Tabs;
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Internals")]
public class SettingsTabLook : SettingsTab
{
private static readonly (string, float)[] GlobalUiScalePresets =
private static readonly (string, float)[] GlobalUiScalePresets =
{
("80%##DalamudSettingsGlobalUiScaleReset96", 0.8f),
("100%##DalamudSettingsGlobalUiScaleReset12", 1f),
@ -107,7 +107,17 @@ public class SettingsTabLook : SettingsTab
Loc.Localize("DalamudSettingToggleDockingHint", "This will allow you to fuse and tab plugin windows."),
c => c.IsDocking,
(v, c) => c.IsDocking = v),
new SettingsEntry<bool>(
Loc.Localize(
"DalamudSettingEnablePluginUIAdditionalOptions",
"Add a button to the title bar of plugin windows to open additional options"),
Loc.Localize(
"DalamudSettingEnablePluginUIAdditionalOptionsHint",
"This will allow you to pin certain plugin windows, make them clickthrough or adjust their opacity.\nThis may not be supported by all of your plugins. Contact the plugin author if you want them to support this feature."),
c => c.EnablePluginUiAdditionalOptions,
(v, c) => c.EnablePluginUiAdditionalOptions = v),
new SettingsEntry<bool>(
Loc.Localize("DalamudSettingEnablePluginUISoundEffects", "Enable sound effects for plugin windows"),
Loc.Localize("DalamudSettingEnablePluginUISoundEffectsHint", "This will allow you to enable or disable sound effects generated by plugin user interfaces.\nThis is affected by your in-game `System Sounds` volume settings."),
@ -125,19 +135,19 @@ public class SettingsTabLook : SettingsTab
Loc.Localize("DalamudSettingToggleTsmHint", "This will allow you to access certain Dalamud and Plugin functionality from the title screen.\nDisabling this will also hide the Dalamud version text on the title screen."),
c => c.ShowTsm,
(v, c) => c.ShowTsm = v),
new SettingsEntry<bool>(
Loc.Localize("DalamudSettingInstallerOpenDefault", "Open the Plugin Installer to the \"Installed Plugins\" tab by default"),
Loc.Localize("DalamudSettingInstallerOpenDefaultHint", "This will allow you to open the Plugin Installer to the \"Installed Plugins\" tab by default, instead of the \"Available Plugins\" tab."),
c => c.PluginInstallerOpen == PluginInstallerOpenKind.InstalledPlugins,
(v, c) => c.PluginInstallerOpen = v ? PluginInstallerOpenKind.InstalledPlugins : PluginInstallerOpenKind.AllPlugins),
new SettingsEntry<bool>(
Loc.Localize("DalamudSettingReducedMotion", "Reduce motions"),
Loc.Localize("DalamudSettingReducedMotionHint", "This will suppress certain animations from Dalamud, such as the notification popup."),
c => c.ReduceMotions ?? false,
(v, c) => c.ReduceMotions = v),
new SettingsEntry<float>(
Loc.Localize("DalamudSettingImeStateIndicatorOpacity", "IME State Indicator Opacity (CJK only)"),
Loc.Localize("DalamudSettingImeStateIndicatorOpacityHint", "When any of CJK IMEs is in use, the state of IME will be shown with the opacity specified here."),

View file

@ -0,0 +1,53 @@
using System.Collections.Generic;
using Newtonsoft.Json;
namespace Dalamud.Interface.Windowing.Persistence;
/// <summary>
/// Class representing a Window System preset.
/// </summary>
internal class PresetModel
{
/// <summary>
/// Gets or sets the ID of this preset.
/// </summary>
[JsonProperty("id")]
public Guid Id { get; set; }
/// <summary>
/// Gets or sets the name of this preset.
/// </summary>
[JsonProperty("n")]
public string Name { get; set; } = "New Preset";
/// <summary>
/// Gets or sets a dictionary containing the windows in the preset, mapping their ID to the preset.
/// </summary>
[JsonProperty("w")]
public Dictionary<uint, PresetWindow> Windows { get; set; } = new();
/// <summary>
/// Class representing a window in a preset.
/// </summary>
internal class PresetWindow
{
/// <summary>
/// Gets or sets a value indicating whether the window is pinned.
/// </summary>
[JsonProperty("p")]
public bool IsPinned { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the window is clickthrough.
/// </summary>
[JsonProperty("ct")]
public bool IsClickThrough { get; set; }
/// <summary>
/// Gets or sets the window's opacity override.
/// </summary>
[JsonProperty("a")]
public float? Alpha { get; set; }
}
}

View file

@ -0,0 +1,47 @@
using Dalamud.Configuration.Internal;
namespace Dalamud.Interface.Windowing.Persistence;
/// <summary>
/// Class handling persistence for window system windows.
/// </summary>
[ServiceManager.EarlyLoadedService]
internal class WindowSystemPersistence : IServiceType
{
[ServiceManager.ServiceDependency]
private readonly DalamudConfiguration config = Service<DalamudConfiguration>.Get();
/// <summary>
/// Initializes a new instance of the <see cref="WindowSystemPersistence"/> class.
/// </summary>
[ServiceManager.ServiceConstructor]
public WindowSystemPersistence()
{
}
/// <summary>
/// Gets the active window system preset.
/// </summary>
public PresetModel ActivePreset => this.config.DefaultUiPreset;
/// <summary>
/// Get or add a window to the active preset.
/// </summary>
/// <param name="id">The ID of the window.</param>
/// <returns>The preset window instance, or null if the preset does not contain this window.</returns>
public PresetModel.PresetWindow? GetWindow(uint id)
{
return this.ActivePreset.Windows.TryGetValue(id, out var window) ? window : null;
}
/// <summary>
/// Persist the state of a window to the active preset.
/// </summary>
/// <param name="id">The ID of the window.</param>
/// <param name="window">The preset window instance.</param>
public void SaveWindow(uint id, PresetModel.PresetWindow window)
{
this.ActivePreset.Windows[id] = window;
this.config.QueueSave();
}
}

View file

@ -1,15 +1,17 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
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.Components;
using Dalamud.Interface.Internal;
using Dalamud.Interface.Utility;
using Dalamud.Interface.Windowing.Persistence;
using Dalamud.Logging.Internal;
using FFXIVClientStructs.FFXIV.Client.UI;
@ -26,7 +28,7 @@ public abstract class Window
private static readonly ModuleLog Log = new("WindowSystem");
private static bool wasEscPressedLastFrame = false;
private bool internalLastIsOpen = false;
private bool internalIsOpen = false;
private bool internalIsPinned = false;
@ -35,15 +37,19 @@ public abstract class Window
private float? internalAlpha = null;
private bool nextFrameBringToFront = false;
private bool hasInitializedFromPreset = false;
private PresetModel.PresetWindow? presetWindow;
private bool presetDirty = false;
/// <summary>
/// Initializes a new instance of the <see cref="Window"/> class.
/// </summary>
/// <param name="name">The name/ID of this window.
/// If you have multiple windows with the same name, you will need to
/// append an unique ID to it by specifying it after "###" behind the window title.
/// append a unique ID to it by specifying it after "###" behind the window title.
/// </param>
/// <param name="flags">The <see cref="ImGuiWindowFlags"/> of this window.</param>
/// <param name="forceMainWindow">Whether or not this window should be limited to the main game window.</param>
/// <param name="forceMainWindow">Whether this window should be limited to the main game window.</param>
protected Window(string name, ImGuiWindowFlags flags = ImGuiWindowFlags.None, bool forceMainWindow = false)
{
this.WindowName = name;
@ -51,6 +57,33 @@ public abstract class Window
this.ForceMainWindow = forceMainWindow;
}
/// <summary>
/// Flags to control window behavior.
/// </summary>
[Flags]
internal enum WindowDrawFlags
{
/// <summary>
/// Nothing.
/// </summary>
None = 0,
/// <summary>
/// Enable window opening/closing sound effects.
/// </summary>
UseSoundEffects = 1 << 0,
/// <summary>
/// Hook into the game's focus management.
/// </summary>
UseFocusManagement = 1 << 1,
/// <summary>
/// Enable the built-in "additional options" menu on the title bar.
/// </summary>
UseAdditionalOptions = 1 << 2,
}
/// <summary>
/// Gets or sets the namespace of the window.
/// </summary>
@ -87,7 +120,7 @@ public abstract class Window
/// Gets or sets a value representing the sound effect id to be played when the window is closed.
/// </summary>
public uint OnCloseSfxId { get; set; } = 24u;
/// <summary>
/// Gets or sets the position of this window.
/// </summary>
@ -155,7 +188,7 @@ public abstract class Window
/// <summary>
/// Gets or sets a list of available title bar buttons.
///
///
/// If <see cref="AllowPinning"/> or <see cref="AllowClickthrough"/> 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.
@ -170,7 +203,7 @@ public abstract class Window
get => this.internalIsOpen;
set => this.internalIsOpen = value;
}
private bool CanShowCloseButton => this.ShowCloseButton && !this.internalIsClickthrough;
/// <summary>
@ -267,17 +300,16 @@ public abstract class Window
public virtual void Update()
{
}
/// <summary>
/// Draw the window via ImGui.
/// </summary>
/// <param name="configuration">Configuration instance used to check if certain window management features should be enabled.</param>
internal void DrawInternal(DalamudConfiguration? configuration)
/// <param name="internalDrawFlags">Flags controlling window behavior.</param>
/// <param name="persistence">Handler for window persistence data.</param>
internal void DrawInternal(WindowDrawFlags internalDrawFlags, WindowSystemPersistence? persistence)
{
this.PreOpenCheck();
var doSoundEffects = configuration?.EnablePluginUISoundEffects ?? false;
if (!this.IsOpen)
{
if (this.internalIsOpen != this.internalLastIsOpen)
@ -286,8 +318,9 @@ public abstract class Window
this.OnClose();
this.IsFocused = false;
if (doSoundEffects && !this.DisableWindowSounds) UIGlobals.PlaySoundEffect(this.OnCloseSfxId);
if (internalDrawFlags.HasFlag(WindowDrawFlags.UseSoundEffects) && !this.DisableWindowSounds)
UIGlobals.PlaySoundEffect(this.OnCloseSfxId);
}
return;
@ -301,13 +334,16 @@ public abstract class Window
if (hasNamespace)
ImGui.PushID(this.Namespace);
this.PreHandlePreset(persistence);
if (this.internalLastIsOpen != this.internalIsOpen && this.internalIsOpen)
{
this.internalLastIsOpen = this.internalIsOpen;
this.OnOpen();
if (doSoundEffects && !this.DisableWindowSounds) UIGlobals.PlaySoundEffect(this.OnOpenSfxId);
if (internalDrawFlags.HasFlag(WindowDrawFlags.UseSoundEffects) && !this.DisableWindowSounds)
UIGlobals.PlaySoundEffect(this.OnOpenSfxId);
}
this.PreDraw();
@ -340,6 +376,8 @@ public abstract class Window
if (this.CanShowCloseButton ? ImGui.Begin(this.WindowName, ref this.internalIsOpen, flags) : ImGui.Begin(this.WindowName, flags))
{
ImGuiNativeAdditions.igCustom_WindowSetInheritNoInputs(this.internalIsClickthrough);
// Draw the actual window contents
try
{
@ -355,7 +393,7 @@ public abstract class Window
var flagsApplicableForTitleBarIcons = !flags.HasFlag(ImGuiWindowFlags.NoDecoration) &&
!flags.HasFlag(ImGuiWindowFlags.NoTitleBar);
var showAdditions = (this.AllowPinning || this.AllowClickthrough) &&
(configuration?.EnablePluginUiAdditionalOptions ?? true) &&
internalDrawFlags.HasFlag(WindowDrawFlags.UseAdditionalOptions) &&
flagsApplicableForTitleBarIcons;
if (showAdditions)
{
@ -364,10 +402,10 @@ public abstract class Window
if (ImGui.BeginPopup(additionsPopupName, ImGuiWindowFlags.NoMove))
{
var isAvailable = ImGuiHelpers.CheckIsWindowOnMainViewport();
if (!isAvailable)
ImGui.BeginDisabled();
if (this.internalIsClickthrough)
ImGui.BeginDisabled();
@ -375,36 +413,51 @@ public abstract class Window
{
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."));
}
if (this.internalIsClickthrough)
ImGui.EndDisabled();
if (this.AllowClickthrough)
ImGui.Checkbox(Loc.Localize("WindowSystemContextActionClickthrough", "Make clickthrough"), ref this.internalIsClickthrough);
{
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 = alpha / 100f;
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 to disable clickthrough."));
ImGui.TextColored(ImGuiColors.DalamudGrey,
Loc.Localize("WindowSystemContextActionDisclaimer",
"These options may not work for all plugins at the moment."));
"Open this menu again by clicking the three dashes to disable clickthrough."));
}
else
{
@ -415,7 +468,7 @@ public abstract class Window
if (!isAvailable)
ImGui.EndDisabled();
ImGui.EndPopup();
}
@ -457,8 +510,7 @@ public abstract class Window
this.IsFocused = ImGui.IsWindowFocused(ImGuiFocusedFlags.RootAndChildWindows);
var isAllowed = configuration?.IsFocusManagementEnabled ?? false;
if (isAllowed)
if (internalDrawFlags.HasFlag(WindowDrawFlags.UseFocusManagement))
{
var escapeDown = Service<KeyState>.Get()[VirtualKey.ESCAPE];
if (escapeDown && this.IsFocused && !wasEscPressedLastFrame && this.RespectCloseHotkey)
@ -476,6 +528,8 @@ public abstract class Window
this.PostDraw();
this.PostHandlePreset(persistence);
if (hasNamespace)
ImGui.PopID();
}
@ -511,7 +565,7 @@ 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)
{
@ -519,21 +573,65 @@ public abstract class Window
}
}
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(void* window, ImGuiWindowFlags flags, Vector4 titleBarRect, IEnumerable<TitleBarButton> 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++;
@ -543,15 +641,15 @@ public abstract class Window
// Pad to the left, to get out of the way of the native buttons
padR += numNativeButtons * (buttonSize + style.ItemInnerSpacing.X);
Vector2 GetCenter(Vector4 rect) => new((rect.X + rect.Z) * 0.5f, (rect.Y + rect.W) * 0.5f);
Vector2 GetCenter(Vector4 rect) => new((rect.X + rect.Z) * 0.5f, (rect.Y + rect.W) * 0.5f);
var numButtons = 0;
bool DrawButton(TitleBarButton button, Vector2 pos)
{
var id = ImGui.GetID($"###CustomTbButton{numButtons}");
numButtons++;
var min = pos;
var max = pos + new Vector2(fontSize, fontSize);
Vector4 bb = new(min.X, min.Y, max.X, max.Y);
@ -563,12 +661,12 @@ public abstract class Window
{
hovered = false;
held = false;
// ButtonBehavior does not function if the window is clickthrough, so we have to do it ourselves
if (ImGui.IsMouseHoveringRect(min, max))
{
hovered = true;
// We can't use ImGui native functions here, because they don't work with clickthrough
if ((User32.GetKeyState((int)VirtualKey.LBUTTON) & 0x8000) != 0)
{
@ -581,7 +679,7 @@ public abstract class Window
{
pressed = ImGuiNativeAdditions.igButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags.None);
}
if (isClipped)
return pressed;
@ -590,10 +688,10 @@ public abstract class Window
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.X + offset.X, bb.Y + offset.Y), textCol, button.Icon.ToIconString());
if (hovered)
button.ShowTooltip?.Invoke();
@ -608,14 +706,14 @@ public abstract class Window
{
if (this.internalIsClickthrough && !button.AvailableClickthrough)
return;
Vector2 position = new(titleBarRect.Z - padR - buttonSize, titleBarRect.Y + style.FramePadding.Y);
padR += buttonSize + style.ItemInnerSpacing.X;
if (DrawButton(button, position))
button.Click?.Invoke(ImGuiMouseButton.Left);
}
ImGui.PopClipRect();
}
@ -625,7 +723,7 @@ public abstract class Window
public struct WindowSizeConstraints
{
private Vector2 internalMaxSize = new(float.MaxValue);
/// <summary>
/// Initializes a new instance of the <see cref="WindowSizeConstraints"/> struct.
/// </summary>
@ -637,7 +735,7 @@ public abstract class Window
/// Gets or sets the minimum size of the window.
/// </summary>
public Vector2 MinimumSize { get; set; } = new(0);
/// <summary>
/// Gets or sets the maximum size of the window.
/// </summary>
@ -646,12 +744,12 @@ public abstract class Window
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)
if (this.internalMaxSize.X < currentMin.X || this.internalMaxSize.Y < currentMin.Y)
return new Vector2(float.MaxValue);
return this.internalMaxSize;
@ -667,53 +765,56 @@ public abstract class Window
/// Gets or sets the icon of the button.
/// </summary>
public FontAwesomeIcon Icon { get; set; }
/// <summary>
/// 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.
/// </summary>
public Vector2 IconOffset { get; set; }
/// <summary>
/// Gets or sets an action that is called when a tooltip shall be drawn.
/// May be null if no tooltip shall be drawn.
/// </summary>
public Action? ShowTooltip { get; set; }
/// <summary>
/// Gets or sets an action that is called when the button is clicked.
/// </summary>
public Action<ImGuiMouseButton> Click { get; set; }
/// <summary>
/// Gets or sets the priority the button shall be shown in.
/// Lower = closer to ImGui default buttons.
/// </summary>
public int Priority { get; set; }
/// <summary>
/// Gets or sets a value indicating whether or not the button shall be clickable
/// when the respective window is set to clickthrough.
/// </summary>
public bool AvailableClickthrough { 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 bool igItemAdd(Vector4 bb, uint id, Vector4* navBb, uint flags);
[DllImport("cimgui", CallingConvention = CallingConvention.Cdecl)]
public static extern bool igButtonBehavior(Vector4 bb, uint id, bool* outHovered, bool* outHeld, ImGuiButtonFlags flags);
[DllImport("cimgui", CallingConvention = CallingConvention.Cdecl)]
public static extern void* igGetCurrentWindow();
[DllImport("cimgui", CallingConvention = CallingConvention.Cdecl)]
public static extern void igStartMouseMovingWindow(void* window);
[DllImport("cimgui", CallingConvention = CallingConvention.Cdecl)]
public static extern void ImGuiWindow_TitleBarRect(Vector4* pOut, void* window);
[DllImport("cimgui", CallingConvention = CallingConvention.Cdecl)]
public static extern void igCustom_WindowSetInheritNoInputs(bool inherit);
}
}

View file

@ -2,6 +2,7 @@ using System.Collections.Generic;
using System.Linq;
using Dalamud.Configuration.Internal;
using Dalamud.Interface.Windowing.Persistence;
using ImGuiNET;
using Serilog;
@ -103,15 +104,28 @@ public class WindowSystem
if (hasNamespace)
ImGui.PushID(this.Namespace);
// These must be nullable, people are using stock WindowSystems and Windows without Dalamud for tests
var config = Service<DalamudConfiguration>.GetNullable();
var persistence = Service<WindowSystemPersistence>.GetNullable();
var flags = Window.WindowDrawFlags.None;
if (config?.EnablePluginUISoundEffects ?? false)
flags |= Window.WindowDrawFlags.UseSoundEffects;
if (config?.EnablePluginUiAdditionalOptions ?? false)
flags |= Window.WindowDrawFlags.UseAdditionalOptions;
if (config?.IsFocusManagementEnabled ?? false)
flags |= Window.WindowDrawFlags.UseFocusManagement;
// 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())
{
#if DEBUG
// Log.Verbose($"[WS{(hasNamespace ? "/" + this.Namespace : string.Empty)}] Drawing {window.WindowName}");
// Log.Verbose($"[WS{(hasNamespace ? "/" + this.Namespace : string.Empty)}] Drawing {window.WindowName}");
#endif
window.DrawInternal(config);
window.DrawInternal(flags, persistence);
}
var focusedWindow = this.windows.FirstOrDefault(window => window.IsFocused && window.RespectCloseHotkey);

@ -1 +1 @@
Subproject commit 7002b2884e9216d8bef3e792722d88abe31788f8
Subproject commit 122ee16819437eea7eefe0c04398b44174106d86