Files
RobustToolbox/Robust.Client/UserInterface/Controls/OSWindow.cs
PJB3005 e7f75ab35d Fix size of OSWindows on macOS (& probably Wayland)
The way SDL handles window coordinates passes through the native platform API's behavior instead of trying to make a consistent API, so the way sizes are handled on macOS is different.
2026-01-31 08:13:19 +01:00

225 lines
6.6 KiB
C#

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
{
/// <summary>
/// Represents an operating system-based UI window.
/// </summary>
/// <seealso cref="BaseWindow"/>
[Virtual]
public class OSWindow : Control
{
[Dependency] private readonly IClyde _clyde = default!;
private string _title = "Window";
private WindowRoot? _root;
/// <summary>
/// The window instance backing this window instance.
/// Not guaranteed to be available unless the window is currently open.
/// </summary>
public IClydeWindow? ClydeWindow { get; private set; }
/// <summary>
/// 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.
/// </summary>
public IClydeWindow? Owner { get; set; }
/// <summary>
/// Whether the window is created and currently open.
/// </summary>
public bool IsOpen => ClydeWindow?.IsVisible ?? false;
/// <summary>
/// The title of the window.
/// </summary>
/// <remarks>
/// Can be changed while the window is open.
/// </remarks>
public string Title
{
get => _title;
set
{
_title = value;
if (ClydeWindow != null)
ClydeWindow.Title = value;
}
}
/// <summary>
/// The location to place the window at when it is opened.
/// </summary>
/// <remarks>
/// Changing this while the window is open has no effect.
/// </remarks>
public WindowStartupLocation StartupLocation { get; set; }
/// <summary>
/// Controls whether to automatically size one or both axis of the window to fit the content.
/// </summary>
/// <remarks>
/// Changing this while the window is open has no effect.
/// </remarks>
public WindowSizeToContent SizeToContent { get; set; }
/// <summary>
/// Specifies window styling options for the window.
/// </summary>
/// <remarks>
/// Changing this while the window is open has no effect.
/// </remarks>
public OSWindowStyles WindowStyles { get; set; }
/// <summary>
/// Raised when the window is being closed
/// (via calling <see cref="Close"/> or the user clicking the close button).
/// Can be cancelled.
/// </summary>
public event Action<CancelEventArgs>? Closing;
/// <summary>
/// Raised when the window has been closed.
/// </summary>
public event Action? Closed;
public OSWindow()
{
IoCManager.InjectDependencies(this);
}
/// <summary>
/// Create the window if not already created.
/// This window is not visible by default, call <see cref="Show"/> to display it.
/// </summary>
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;
}
/// <summary>
/// Show the window to the user, creating it if necessary
/// </summary>
public void Show()
{
ClydeWindow = Create();
ClydeWindow.IsVisible = true;
Shown();
}
protected virtual void Shown()
{
}
/// <summary>
/// Try to close the window.
/// </summary>
/// <remarks>
/// This can be cancelled by anything handling <see cref="Closing"/>.
/// </remarks>
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
}
}