Dalamud/Dalamud/Interface/Windowing/WindowSystem.cs

172 lines
6.2 KiB
C#

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;
/// <summary>
/// Class running a WindowSystem using <see cref="Window"/> implementations to simplify ImGui windowing.
/// </summary>
public class WindowSystem
{
private static DateTimeOffset lastAnyFocus;
private readonly List<Window> windows = new();
private string lastFocusedWindowName = string.Empty;
/// <summary>
/// Initializes a new instance of the <see cref="WindowSystem"/> class.
/// </summary>
/// <param name="imNamespace">The name/ID-space of this <see cref="WindowSystem"/>.</param>
public WindowSystem(string? imNamespace = null)
{
this.Namespace = imNamespace;
}
/// <summary>
/// Gets a value indicating whether any <see cref="WindowSystem"/> contains any <see cref="Window"/>
/// that has focus and is not marked to be excluded from consideration.
/// </summary>
public static bool HasAnyWindowSystemFocus { get; internal set; } = false;
/// <summary>
/// Gets the name of the currently focused window system that is redirecting normal escape functionality.
/// </summary>
public static string FocusedWindowSystemNamespace { get; internal set; } = string.Empty;
/// <summary>
/// Gets the timespan since the last time any window was focused.
/// </summary>
public static TimeSpan TimeSinceLastAnyFocus => DateTimeOffset.Now - lastAnyFocus;
/// <summary>
/// Gets a read-only list of all <see cref="Window"/>s in this <see cref="WindowSystem"/>.
/// </summary>
public IReadOnlyList<Window> Windows => this.windows;
/// <summary>
/// Gets a value indicating whether any window in this <see cref="WindowSystem"/> has focus and is
/// not marked to be excluded from consideration.
/// </summary>
public bool HasAnyFocus { get; private set; }
/// <summary>
/// Gets or sets the name/ID-space of this <see cref="WindowSystem"/>.
/// </summary>
public string? Namespace { get; set; }
/// <summary>
/// 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.
/// </summary>
internal static bool ShouldInhibitAtkCloseEvents { get; set; }
/// <summary>
/// Add a window to this <see cref="WindowSystem"/>.
/// The window system doesn't own your window, it just renders it
/// You need to store a reference to it to use it later.
/// </summary>
/// <param name="window">The window to add.</param>
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);
}
/// <summary>
/// Remove a window from this <see cref="WindowSystem"/>.
/// Will not dispose your window, if it is disposable.
/// </summary>
/// <param name="window">The window to remove.</param>
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);
}
/// <summary>
/// Remove all windows from this <see cref="WindowSystem"/>.
/// Will not dispose your windows, if they are disposable.
/// </summary>
public void RemoveAllWindows() => this.windows.Clear();
/// <summary>
/// Draw all registered windows using ImGui.
/// </summary>
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<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;
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();
}
}