From ccd914d6feb365d66635524ff70aa69f4ed1e2cd Mon Sep 17 00:00:00 2001 From: Aleksandr Neychev Date: Thu, 3 Sep 2026 09:40:58 +0500 Subject: [PATCH] fix resize on different screens try -1 --- .../Services/MainWindowPlacement.cs | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/CursorLang.Settings/Services/MainWindowPlacement.cs b/CursorLang.Settings/Services/MainWindowPlacement.cs index 17b288f..79e4569 100644 --- a/CursorLang.Settings/Services/MainWindowPlacement.cs +++ b/CursorLang.Settings/Services/MainWindowPlacement.cs @@ -1,5 +1,6 @@ using System.Windows; using System.Windows.Interop; +using System.Windows.Media; using CursorLang.Core.Interop; using CursorLang.Settings.Interop; @@ -23,6 +24,11 @@ public sealed class MainWindowPlacement // what has to be remembered is only what the user chose private bool _isPlacing; + // Captured once, before anything narrows MaxHeight to a particular monitor — + // otherwise a later, more generous monitor would stay capped at whatever a + // previous, shorter one left behind + private double? _designMaxHeight; + /// /// Takes over the placement of the window: puts it in place by the first show /// and follows where the user moves it. @@ -31,6 +37,7 @@ public sealed class MainWindowPlacement { window.SourceInitialized += OnSourceInitialized; window.LocationChanged += OnLocationChanged; + window.DpiChanged += OnDpiChanged; } /// @@ -83,10 +90,88 @@ public sealed class MainWindowPlacement } window.SourceInitialized -= OnSourceInitialized; + _designMaxHeight = window.MaxHeight; + + LimitHeightToOwnMonitor(window); window.UpdateLayout(); Apply(window); } + // A monitor is not necessarily final at creation time — Windows may place the new + // window on one monitor before Apply moves it to another, and the user is free to + // drag it to a third one later. Every one of those is a real DPI change, and this + // runs again for each: recomputing the cap from whichever monitor holds the window + // right now, rather than trusting a value worked out for a previous one. + private void OnDpiChanged(object sender, DpiChangedEventArgs e) + { + if (sender is not Window window) + { + return; + } + + LimitHeightToOwnMonitor(window); + + // Only the size is revisited here, not the position: the window is commonly + // being dragged across monitors right when its DPI changes, and moving it + // ourselves in the middle of that would fight the drag the user is already + // doing + ReapplySizeToContent(window); + } + + // UpdateLayout alone settles the measure/arrange pass of the visual tree, but + // does not reliably make WPF redo its own step of resizing the native window to + // match SizeToContent when the change originates from a DPI event rather than an + // ordinary content change. Left alone, the window can end up either too tall — a + // blank strip below the real content, once MaxHeight has just pulled the content + // shorter — or stuck too short after being dragged back to a monitor with room to + // grow again. Turning SizeToContent off and back on forces that resizing step to + // run again from scratch. + private static void ReapplySizeToContent(Window window) + { + SizeToContent original = window.SizeToContent; + window.SizeToContent = SizeToContent.Manual; + window.SizeToContent = original; + window.UpdateLayout(); + } + + // MaxHeight in XAML is a constant tuned for an ordinary desktop monitor at 100% + // scaling. At a high scale factor the same number of device-independent pixels + // turns into more physical pixels than a short or heavily scaled monitor has, and + // SizeToContent then grows the window past the bottom of the screen instead of + // asking the ScrollViewer inside it to scroll — with nothing to grab, the excess + // is unreachable. Capping MaxHeight to what the window's own monitor really offers + // keeps the whole window on screen and lets the ScrollViewer take over. + private void LimitHeightToOwnMonitor(Window window) + { + if (_designMaxHeight is not { } designMaxHeight) + { + return; + } + + IntPtr handle = new WindowInteropHelper(window).Handle; + if (handle == IntPtr.Zero + || WindowPlacementNative.TryGetBounds(handle) is not { } bounds + || WindowPlacementNative.TryGetWorkAreaNear(bounds) is not { } work + || IsEmpty(work)) + { + return; + } + + double scale = VisualTreeHelper.GetDpi(window).DpiScaleY; + if (scale <= 0) + { + return; + } + + // Leaves room for the title bar and a margin from the edges of the screen, + // in the same units as the work area height once the monitor's scale is + // divided out + const double reservedForChrome = 80; + + double workAreaHeight = (work.Bottom - work.Top) / scale; + window.MaxHeight = Math.Min(designMaxHeight, Math.Max(200, workAreaHeight - reservedForChrome)); + } + private void OnLocationChanged(object? sender, EventArgs e) { if (_isPlacing || sender is not Window { WindowState: WindowState.Normal } window)