using System; using System.ComponentModel; using Robust.Client.Graphics; using Robust.Client.UserInterface.CustomControls; using Robust.Shared.IoC; using Robust.Shared.Maths; namespace Robust.Client.UserInterface.Controls { /// /// Represents an operating system-based UI window. /// /// public class OSWindow : Control { [Dependency] private readonly IClyde _clyde = default!; private string _title = "Window"; private WindowRoot? _root; /// /// The window instance backing this window instance. /// Not guaranteed to be available unless the window is currently open. /// public IClydeWindow? ClydeWindow { get; private set; } /// /// The window that will "own" this window. /// Owned windows always appear on top of their owners and have some other misc behavior depending on the OS. /// public IClydeWindow? Owner { get; set; } /// /// Whether the window is currently open. /// public bool IsOpen => ClydeWindow != null; /// /// The title of the window. /// /// /// Can be changed while the window is open. /// public string Title { get => _title; set { _title = value; if (ClydeWindow != null) ClydeWindow.Title = value; } } /// /// The location to place the window at when it is opened. /// /// /// Changing this while the window is open has no effect. /// public WindowStartupLocation StartupLocation { get; set; } /// /// Controls whether to automatically size one or both axis of the window to fit the content. /// /// /// Changing this while the window is open has no effect. /// public WindowSizeToContent SizeToContent { get; set; } /// /// Specifies window styling options for the window. /// /// /// Changing this while the window is open has no effect. /// public OSWindowStyles WindowStyles { get; set; } /// /// Raised when the window is being closed /// (via calling or the user clicking the close button). /// Can be cancelled. /// public event Action? Closing; /// /// Raised when the window has been closed. /// public event Action? Closed; public OSWindow() { IoCManager.InjectDependencies(this); } /// /// Show the window to the user. /// public void Show() { if (IsOpen) return; var parameters = new WindowCreateParameters(); if (!float.IsNaN(SetWidth)) parameters.Width = (int) SetWidth; if (!float.IsNaN(SetHeight)) parameters.Height = (int) SetHeight; if (SizeToContent != WindowSizeToContent.Manual) { Measure(Vector2.Infinity); if ((SizeToContent & WindowSizeToContent.Width) != 0) parameters.Width = (int)DesiredSize.X; if ((SizeToContent & WindowSizeToContent.Height) != 0) parameters.Height = (int)DesiredSize.Y; } parameters.Title = _title; parameters.Styles = WindowStyles; parameters.Owner = Owner; parameters.StartupLocation = StartupLocation; ClydeWindow = _clyde.CreateWindow(parameters); ClydeWindow.RequestClosed += OnWindowRequestClosed; ClydeWindow.Destroyed += OnWindowDestroyed; ClydeWindow.Resized += OnWindowResized; _root = UserInterfaceManager.CreateWindowRoot(ClydeWindow); _root.AddChild(this); } /// /// Try to close the window. /// /// /// This can be cancelled by anything handling . /// public void Close() { if (ClydeWindow == null) return; var eventArgs = new CancelEventArgs(); Closing?.Invoke(eventArgs); if (eventArgs.Cancel) return; ClydeWindow.Dispose(); } private void OnWindowRequestClosed(WindowRequestClosedEventArgs eventArgs) { Close(); } private void OnWindowDestroyed(WindowDestroyedEventArgs obj) { // I give it a 75% chance that some Linux user can force close the window, // breaking GLFW, // and forcing us to have a code path that ignores the RequestClosed code. RealClosed(); } private void OnWindowResized(WindowResizedEventArgs obj) { SetSize = obj.NewSize; } private void RealClosed() { Orphan(); ClydeWindow = null; _root = null; Closed?.Invoke(); } } [Flags] public enum WindowSizeToContent : byte { Manual = 0, Width = 1 << 0, Height = 1 << 1, WidthAndHeight = Width | Height } }