using System.Collections.Generic; using System.Linq; using Dalamud.Bindings.ImGui; using Dalamud.Configuration.Internal; using Dalamud.Interface.Windowing.Persistence; using Serilog; namespace Dalamud.Interface.Windowing; /// /// Class running a WindowSystem using implementations to simplify ImGui windowing. /// public class WindowSystem { private static DateTimeOffset lastAnyFocus; private readonly List windows = new(); private string lastFocusedWindowName = string.Empty; /// /// Initializes a new instance of the class. /// /// The name/ID-space of this . public WindowSystem(string? imNamespace = null) { this.Namespace = imNamespace; } /// /// 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; /// /// Gets the name of the currently focused window system that is redirecting normal escape functionality. /// public static string FocusedWindowSystemNamespace { get; internal set; } = string.Empty; /// /// Gets the timespan since the last time any window was focused. /// public static TimeSpan TimeSinceLastAnyFocus => DateTimeOffset.Now - lastAnyFocus; /// /// Gets a read-only list of all s in this . /// public IReadOnlyList Windows => this.windows; /// /// 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; } /// /// Gets or sets a value indicating whether ATK close events should be inhibited while any window has focus. /// Does not respect windows that are pinned or clickthrough. /// 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) { if (this.windows.Any(x => x.WindowName == window.WindowName)) throw new ArgumentException("A window with this name/ID already exists."); this.windows.Add(window); } /// /// Remove a window from this . /// Will not dispose your window, if it is disposable. /// /// The window to remove. public void RemoveWindow(Window window) { if (!this.windows.Contains(window)) throw new ArgumentException("This window is not registered on this WindowSystem."); this.windows.Remove(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); if (hasNamespace) ImGui.PushID(this.Namespace); // These must be nullable, people are using stock WindowSystems and Windows without Dalamud for tests var config = Service.GetNullable(); var persistence = Service.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; if (config?.ReduceMotions ?? false) flags |= Window.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()) { #if DEBUG // Log.Verbose($"[WS{(hasNamespace ? "/" + this.Namespace : string.Empty)}] Drawing {window.WindowName}"); #endif window.DrawInternal(flags, persistence); } var focusedWindow = this.windows.FirstOrDefault(window => window.IsFocused); this.HasAnyFocus = focusedWindow != default; if (this.HasAnyFocus) { if (this.lastFocusedWindowName != focusedWindow.WindowName) { Log.Verbose($"WindowSystem \"{this.Namespace}\" Window \"{focusedWindow.WindowName}\" has focus now"); this.lastFocusedWindowName = focusedWindow.WindowName; } HasAnyWindowSystemFocus = true; FocusedWindowSystemNamespace = this.Namespace; lastAnyFocus = DateTimeOffset.Now; } else { if (this.lastFocusedWindowName != string.Empty) { Log.Verbose($"WindowSystem \"{this.Namespace}\" Window \"{this.lastFocusedWindowName}\" lost focus"); this.lastFocusedWindowName = string.Empty; } } ShouldInhibitAtkCloseEvents |= this.windows.Any(w => w.IsFocused && w.RespectCloseHotkey && !w.IsPinned && !w.IsClickthrough); if (hasNamespace) ImGui.PopID(); } }