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;
}