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. /// /// [Virtual] public partial class OSWindow : Control { [Dependency] private 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 created and currently open. /// public bool IsOpen => ClydeWindow?.IsVisible ?? false; /// /// 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); } /// /// Create the window if not already created. /// This window is not visible by default, call to display it. /// public IClydeWindow Create() { if (ClydeWindow != null) return ClydeWindow; 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(Vector2Helpers.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; parameters.Visible = false; ClydeWindow = _clyde.CreateWindow(parameters); ClydeWindow.RequestClosed += OnWindowRequestClosed; ClydeWindow.Destroyed += OnWindowDestroyed; ClydeWindow.Resized += OnWindowResized; _root = UserInterfaceManager.CreateWindowRoot(ClydeWindow); _root.CreateRootControls(); // Add ourselves *after* creating the root. // This way root controls are valid in EnteredTree(). // We have to re-organize the controls after, of course. _root.AddChild(this); SetPositionFirst(); // Resize the window by our UIScale ClydeWindow.Size = new((int)(parameters.Width * UIScale), (int)(parameters.Height * UIScale)); return ClydeWindow; } /// /// Show the window to the user, creating it if necessary /// public void Show() { ClydeWindow = Create(); ClydeWindow.IsVisible = true; Shown(); } protected virtual void Shown() { } /// /// 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 / UIScale; } 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 } }