Launch upper-left with 75px padding; stop restoring window position
Creator ruling: open in the upper-left of the screen with ~75px padding. The restore path was placing the window below the fold. RestoreWindowGeometry now ignores the saved position and calls the pure TopLeftPlacement(workArea, w, h, padding): work-area origin + LaunchPadding (75, in DIPs so it scales with DPI), clamped so the window is never pushed off-screen. A window that fits stays wholly visible; one too big to fit falls back to the work-area origin. Size and maximized state are still persisted -- only position was dropped. The size clamp is unchanged (saved values clamp to [MinWidth, work.Width], which is why a saved 800x500 returns as 1366x768 per the XAML minimums). The old clamp tolerated Left as low as work.Left - width + 120 and Top up to work.Bottom - 80, ranges wide enough to leave the window almost entirely off-screen while still looking like a successful clamp. A clamp that permits the window to be unreachable is worse than none: it reads as a guarantee it does not keep. BroadcastPullOutTests is renamed ..._Window_Launches_TopLeft_... and now asserts the constructor applies the derived placement for both an on-screen and a -99999 saved position. Placement math is covered by WindowPlacementTests as a pure function needing no HWND. Suite 395/395, 0 warnings.
This commit is contained in:
+41
-16
@@ -42,31 +42,56 @@ public partial class MainWindow : Window
|
||||
_viewModel.InitWebView2();
|
||||
}
|
||||
|
||||
// ─── Window geometry persistence (launch size/position) ───
|
||||
// Saved on close (RestoreBounds so a maximized window remembers its
|
||||
// un-maximize rect); restored in the ctor before the window is shown.
|
||||
// Values clamp to the minimums and to the primary work area — a position
|
||||
// saved on a since-disconnected secondary monitor falls back on-screen.
|
||||
// ─── Window geometry (launch position/size) ───
|
||||
// Launch POSITION is derived, never restored: upper-left of the work area plus
|
||||
// padding (creator ruling 2026-10-04). The old restore clamped the saved position
|
||||
// to [work.Left - width + 120, work.Right - 120] horizontally and
|
||||
// [work.Top, work.Bottom - 80] vertically — ranges wide enough to leave the window
|
||||
// almost entirely off-screen, which is what the creator was seeing ("below the
|
||||
// fold"). Size and maximized state are still persisted. Placement math is pure and
|
||||
// tested (WindowPlacementTests); changing it back to restore position is reverting
|
||||
// this block only, since the saved values are still written.
|
||||
|
||||
/// <summary>Launch inset from the work-area corner, in device-independent units —
|
||||
/// so it scales with DPI rather than being a fixed physical pixel count.</summary>
|
||||
internal const double LaunchPadding = 75;
|
||||
|
||||
/// <summary>Upper-left of <paramref name="workArea"/> plus padding, clamped so the
|
||||
/// window is never pushed off-screen. The clamp is the point: a window that fits
|
||||
/// stays wholly visible, and one too big to fit falls back to the work-area origin
|
||||
/// rather than hanging off an edge.</summary>
|
||||
internal static Point TopLeftPlacement(Rect workArea, double width, double height, double padding)
|
||||
{
|
||||
var left = workArea.Left + padding;
|
||||
var top = workArea.Top + padding;
|
||||
|
||||
if (width <= workArea.Width) left = Math.Min(left, workArea.Right - width);
|
||||
else left = workArea.Left;
|
||||
|
||||
if (height <= workArea.Height) top = Math.Min(top, workArea.Bottom - height);
|
||||
else top = workArea.Top;
|
||||
|
||||
return new Point(left, top);
|
||||
}
|
||||
|
||||
private void RestoreWindowGeometry()
|
||||
{
|
||||
try
|
||||
{
|
||||
var saved = _viewModel.RestoreWindowState();
|
||||
if (saved == null) return;
|
||||
var (left, top, width, height, state) = saved.Value;
|
||||
|
||||
var work = SystemParameters.WorkArea;
|
||||
width = Math.Clamp(width, MinWidth, work.Width);
|
||||
height = Math.Clamp(height, MinHeight, work.Height);
|
||||
left = Math.Clamp(left, work.Left - width + 120, work.Right - 120);
|
||||
top = Math.Clamp(top, work.Top, work.Bottom - 80);
|
||||
var saved = _viewModel.RestoreWindowState();
|
||||
|
||||
Left = left;
|
||||
Top = top;
|
||||
// No saved state: keep the XAML-declared size rather than collapsing to Min*.
|
||||
var width = Math.Clamp(saved?.Width ?? Width, MinWidth, work.Width);
|
||||
var height = Math.Clamp(saved?.Height ?? Height, MinHeight, work.Height);
|
||||
Width = width;
|
||||
Height = height;
|
||||
if (state == "Maximized") WindowState = WindowState.Maximized;
|
||||
|
||||
var place = TopLeftPlacement(work, width, height, LaunchPadding);
|
||||
Left = place.X;
|
||||
Top = place.Y;
|
||||
|
||||
if (saved?.State == "Maximized") WindowState = WindowState.Maximized;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user