Merge pull request 'Fix certification comment' (#15) from fix-certification-comment into master
Release / release (push) Failing after 7m3s
Release / release (push) Failing after 7m3s
Reviewed-on: #15
This commit was merged in pull request #15.
This commit is contained in:
@@ -1,5 +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;
|
||||
|
||||
@@ -23,6 +25,15 @@ 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;
|
||||
|
||||
// Captured once, so a DPI change has a known-correct value to reassert — see
|
||||
// RestoreDesignWidth
|
||||
private double? _designWidth;
|
||||
|
||||
/// <summary>
|
||||
/// Takes over the placement of the window: puts it in place by the first show
|
||||
/// and follows where the user moves it.
|
||||
@@ -31,6 +42,7 @@ public sealed class MainWindowPlacement
|
||||
{
|
||||
window.SourceInitialized += OnSourceInitialized;
|
||||
window.LocationChanged += OnLocationChanged;
|
||||
window.DpiChanged += OnDpiChanged;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -83,10 +95,108 @@ public sealed class MainWindowPlacement
|
||||
}
|
||||
|
||||
window.SourceInitialized -= OnSourceInitialized;
|
||||
_designMaxHeight = window.MaxHeight;
|
||||
_designWidth = window.Width;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}));
|
||||
}
|
||||
|
||||
// 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
|
||||
// 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)
|
||||
|
||||
Reference in New Issue
Block a user