From ccd914d6feb365d66635524ff70aa69f4ed1e2cd Mon Sep 17 00:00:00 2001 From: Aleksandr Neychev Date: Thu, 3 Sep 2026 09:40:58 +0500 Subject: [PATCH 1/2] 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) -- 2.55.0 From 53720e95dbb0f916516de3930360e7e093ea525f Mon Sep 17 00:00:00 2001 From: Aleksandr Neychev Date: Thu, 3 Sep 2026 09:55:29 +0500 Subject: [PATCH 2/2] fixed width resize when change screen scale --- .../Services/MainWindowPlacement.cs | 37 ++++++++++++++++--- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/CursorLang.Settings/Services/MainWindowPlacement.cs b/CursorLang.Settings/Services/MainWindowPlacement.cs index 79e4569..672d625 100644 --- a/CursorLang.Settings/Services/MainWindowPlacement.cs +++ b/CursorLang.Settings/Services/MainWindowPlacement.cs @@ -1,6 +1,7 @@ using System.Windows; using System.Windows.Interop; using System.Windows.Media; +using System.Windows.Threading; using CursorLang.Core.Interop; using CursorLang.Settings.Interop; @@ -29,6 +30,10 @@ public sealed class MainWindowPlacement // previous, shorter one left behind private double? _designMaxHeight; + // Captured once, so a DPI change has a known-correct value to reassert — see + // RestoreDesignWidth + private double? _designWidth; + /// /// Takes over the placement of the window: puts it in place by the first show /// and follows where the user moves it. @@ -91,6 +96,7 @@ public sealed class MainWindowPlacement window.SourceInitialized -= OnSourceInitialized; _designMaxHeight = window.MaxHeight; + _designWidth = window.Width; LimitHeightToOwnMonitor(window); window.UpdateLayout(); @@ -109,13 +115,32 @@ public sealed class MainWindowPlacement return; } - LimitHeightToOwnMonitor(window); + // Deferred rather than run inline: this event fires while WPF is still in + // the middle of its own response to the same DPI change (its per-monitor + // rescale of Width touches it after this handler if the fix-up runs + // synchronously, undoing it). Posting behind that on the dispatcher queue + // lets our fix-up run once WPF's own pass has finished. + window.Dispatcher.BeginInvoke(DispatcherPriority.ContextIdle, new Action(() => + { + LimitHeightToOwnMonitor(window); + RestoreDesignWidth(window); + ReapplySizeToContent(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); + // The width is a plain, explicit value rather than something SizeToContent + // computes, and WPF's own per-monitor rescaling does not reliably keep it at the + // same logical width when the system's scaling changes live under an + // already-open window, as opposed to the window being dragged onto a different + // monitor — it can come out scaled by roughly the ratio between the old and the + // new DPI instead of staying put. Reasserting the original value here is simpler + // than chasing exactly where that rescale goes wrong. + private void RestoreDesignWidth(Window window) + { + if (_designWidth is { } designWidth) + { + window.Width = designWidth; + } } // UpdateLayout alone settles the measure/arrange pass of the visual tree, but -- 2.55.0