using System.Windows; using System.Windows.Interop; using CursorLang.Core.Interop; using CursorLang.Settings.Interop; namespace CursorLang.Settings.Services; /// /// Decides where the settings window shows up: for the first time in a session — in /// the centre of the monitor the user is working on, and after that — where they /// left that window. /// /// /// The position lives in memory only and is not kept between launches: the set of /// monitors may be different by the next launch, while "in the centre of the active /// one" is always right. /// public sealed class MainWindowPlacement { private PopupWindowNative.Point? _position; // The window reports a move when we move it ourselves as well; // what has to be remembered is only what the user chose private bool _isPlacing; /// /// Takes over the placement of the window: puts it in place by the first show /// and follows where the user moves it. /// public void Attach(Window window) { window.SourceInitialized += OnSourceInitialized; window.LocationChanged += OnLocationChanged; } /// /// Returns the window to the remembered place, and when it has not been shown /// yet during this session — puts it in the centre of the active monitor. /// public void Apply(Window window) { // A minimized window has no meaningful bounds: it is restored in its former // place, and it can be positioned only after that if (window.WindowState != WindowState.Normal) { return; } IntPtr handle = new WindowInteropHelper(window).Handle; if (handle == IntPtr.Zero || WindowPlacementNative.TryGetBounds(handle) is not { } bounds) { return; } PopupWindowNative.Point? wanted = _position ?? CenterOnActiveMonitor(bounds); if (wanted is null || KeepOnScreen(wanted.Value, bounds) is not { } target) { return; } _isPlacing = true; try { PopupWindowNative.MoveTo(handle, target.X, target.Y); } finally { _isPlacing = false; } _position = target; } // The window height adapts to its content and is unknown until the first layout // pass — an empty window frame would end up in the centre. So we ask for the // layout to be computed right away: by that moment the window is not shown yet, // so it will not flash in its former place private void OnSourceInitialized(object? sender, EventArgs e) { if (sender is not Window window) { return; } window.SourceInitialized -= OnSourceInitialized; window.UpdateLayout(); Apply(window); } private void OnLocationChanged(object? sender, EventArgs e) { if (_isPlacing || sender is not Window { WindowState: WindowState.Normal } window) { return; } IntPtr handle = new WindowInteropHelper(window).Handle; if (handle != IntPtr.Zero && WindowPlacementNative.TryGetBounds(handle) is { } bounds) { _position = new PopupWindowNative.Point { X = bounds.Left, Y = bounds.Top }; } } private static PopupWindowNative.Point? CenterOnActiveMonitor(PopupWindowNative.Rect bounds) { (PopupWindowNative.Rect work, _) = PopupWindowNative.GetActiveMonitorWorkArea(); return IsEmpty(work) ? null : Center(bounds, work); } /// /// The point at which a window with the given bounds ends up in the centre of the work area. /// internal static PopupWindowNative.Point Center( PopupWindowNative.Rect bounds, PopupWindowNative.Rect work) => new() { X = work.Left + (((work.Right - work.Left) - Width(bounds)) / 2), Y = work.Top + (((work.Bottom - work.Top) - Height(bounds)) / 2), }; /// /// Pulls the window into the work area of the nearest monitor. /// /// /// Needed in two cases. The monitor the user put the window on may be disconnected /// during the session — returning the window to its place would then leave the /// user without a window, so the remembered position is a wish here rather than an /// order. And the window height equals the height of its content and may exceed /// the work area on a short monitor — then the title bar of a window placed in the /// centre would go past the top edge. /// private static PopupWindowNative.Point? KeepOnScreen( PopupWindowNative.Point position, PopupWindowNative.Rect bounds) { int width = Width(bounds); int height = Height(bounds); var wanted = new PopupWindowNative.Rect { Left = position.X, Top = position.Y, Right = position.X + width, Bottom = position.Y + height, }; if (WindowPlacementNative.TryGetWorkAreaNear(wanted) is not { } work || IsEmpty(work)) { return null; } return Clamp(position, bounds, work); } /// /// Pulls the point so that a window with the given bounds fits into the work area /// entirely. A window taller than the work area gets its top edge: the title bar /// is needed more than the lower part of the window. /// internal static PopupWindowNative.Point Clamp( PopupWindowNative.Point position, PopupWindowNative.Rect bounds, PopupWindowNative.Rect work) { int width = Width(bounds); int height = Height(bounds); return new PopupWindowNative.Point { X = Math.Clamp(position.X, work.Left, Math.Max(work.Left, work.Right - width)), Y = Math.Clamp(position.Y, work.Top, Math.Max(work.Top, work.Bottom - height)), }; } private static int Width(PopupWindowNative.Rect rect) => rect.Right - rect.Left; private static int Height(PopupWindowNative.Rect rect) => rect.Bottom - rect.Top; internal static bool IsEmpty(PopupWindowNative.Rect rect) => rect.Right <= rect.Left || rect.Bottom <= rect.Top; }