From f81485b03da5e8d2ab32979dd636cce93a2760f7 Mon Sep 17 00:00:00 2001 From: Dan Walmsley <4672627+danwalmsley@users.noreply.github.com> Date: Thu, 26 Feb 2026 11:04:50 +0000 Subject: [PATCH 1/7] Reapply macOS drag preview smoothing fixes --- .../Internal/DragPreviewHelper.cs | 246 +++++++++++++++++- 1 file changed, 232 insertions(+), 14 deletions(-) diff --git a/src/Dock.Avalonia/Internal/DragPreviewHelper.cs b/src/Dock.Avalonia/Internal/DragPreviewHelper.cs index d701e5f6f..7cc75d315 100644 --- a/src/Dock.Avalonia/Internal/DragPreviewHelper.cs +++ b/src/Dock.Avalonia/Internal/DragPreviewHelper.cs @@ -1,6 +1,9 @@ +using System; using System.Linq; +using System.Runtime.InteropServices; using Avalonia; using Avalonia.Controls; +using Avalonia.Threading; using Avalonia.VisualTree; using Dock.Avalonia.Controls; using Dock.Model.Core; @@ -17,19 +20,25 @@ internal class DragPreviewHelper private static bool s_managedTemplatesInitialized; private static DragPreviewControl? s_managedControl; private static ManagedWindowLayer? s_managedLayer; + private static readonly bool s_useWindowMoveCoalescing = RuntimeInformation.IsOSPlatform(OSPlatform.OSX); + private static PixelPoint s_pendingWindowPosition; + private static string s_pendingStatus = string.Empty; + private static bool s_hasPendingWindowMove; + private static bool s_windowMoveFlushScheduled; + private static bool s_windowSizeFrozen; + private static bool s_windowSizeFreezeScheduled; + private static double s_frozenWindowWidthPixels; + private static double s_frozenWindowHeightPixels; + private static double s_frozenContentWidthPixels = double.NaN; + private static double s_frozenContentHeightPixels = double.NaN; + private static double s_lastFrozenWindowScaling = 1.0; + private static int s_windowMoveSessionId; + private static long s_windowMovePostSequence; private static PixelPoint GetPositionWithinWindow(Window window, PixelPoint position, PixelPoint offset) { - var screen = window.Screens.ScreenFromPoint(position); - if (screen is { }) - { - var target = position + offset; - if (screen.WorkingArea.Contains(target)) - { - return target; - } - } - return position; + _ = window; + return position + offset; } private static Size GetPreviewSize(IDockable dockable) @@ -98,6 +107,185 @@ private static double ClampOpacity(double value) return value > 1.0 ? 1.0 : value; } + private static void QueueWindowMove(DragPreviewWindow window, DragPreviewControl control, PixelPoint targetPosition, string status) + { + var currentPosition = window.Position; + var currentStatus = control.Status; + + if (s_windowMoveFlushScheduled && s_hasPendingWindowMove) + { + if (s_pendingWindowPosition == targetPosition + && string.Equals(s_pendingStatus, status, StringComparison.Ordinal)) + { + return; + } + } + + if (!s_windowMoveFlushScheduled + && currentPosition == targetPosition + && string.Equals(currentStatus, status, StringComparison.Ordinal)) + { + return; + } + + if (!s_useWindowMoveCoalescing) + { + ApplyWindowMove(window, control, targetPosition, status); + return; + } + + s_pendingWindowPosition = targetPosition; + s_pendingStatus = status; + s_hasPendingWindowMove = true; + if (!s_windowMoveFlushScheduled) + { + s_windowMoveFlushScheduled = true; + var sessionId = s_windowMoveSessionId; + var postSequence = ++s_windowMovePostSequence; + Dispatcher.UIThread.Post(() => FlushPendingWindowMove(sessionId, postSequence), DispatcherPriority.Render); + } + } + + private static void FlushPendingWindowMove(int sessionId, long postSequence) + { + lock (s_sync) + { + if (sessionId != s_windowMoveSessionId || postSequence != s_windowMovePostSequence) + { + return; + } + + s_windowMoveFlushScheduled = false; + if (!s_hasPendingWindowMove || s_window is null || s_control is null) + { + s_hasPendingWindowMove = false; + return; + } + + ApplyWindowMove(s_window, s_control, s_pendingWindowPosition, s_pendingStatus); + s_hasPendingWindowMove = false; + + if (s_hasPendingWindowMove && !s_windowMoveFlushScheduled) + { + s_windowMoveFlushScheduled = true; + var nextSessionId = s_windowMoveSessionId; + var nextPostSequence = ++s_windowMovePostSequence; + Dispatcher.UIThread.Post(() => FlushPendingWindowMove(nextSessionId, nextPostSequence), DispatcherPriority.Render); + } + } + } + + private static void ApplyWindowMove(DragPreviewWindow window, DragPreviewControl control, PixelPoint targetPosition, string status) + { + var hadStatus = !string.IsNullOrEmpty(control.Status); + if (!string.Equals(control.Status, status, StringComparison.Ordinal)) + { + control.Status = status; + } + + var currentPosition = window.Position; + var willMove = currentPosition != targetPosition; + if (willMove) + { + window.Position = targetPosition; + } + + var appliedPosition = willMove ? targetPosition : currentPosition; + + if (s_windowSizeFrozen) + { + MaintainFrozenWindowSize(window, control); + } + + if (!s_windowSizeFrozen && !s_windowSizeFreezeScheduled && !hadStatus && !string.IsNullOrEmpty(status)) + { + s_windowSizeFreezeScheduled = true; + Dispatcher.UIThread.Post(FreezeWindowSizeIfNeeded, DispatcherPriority.Render); + } + } + + private static double GetWindowScaling(Window window) + { + var scaling = window.RenderScaling; + return scaling > 0 ? scaling : 1.0; + } + + private static void MaintainFrozenWindowSize(DragPreviewWindow window, DragPreviewControl control) + { + var scaling = GetWindowScaling(window); + if (Math.Abs(scaling - s_lastFrozenWindowScaling) < 0.0001) + { + return; + } + + s_lastFrozenWindowScaling = scaling; + + var width = s_frozenWindowWidthPixels / scaling; + var height = s_frozenWindowHeightPixels / scaling; + + if (Math.Abs(window.Width - width) > 0.01) + { + window.Width = width; + } + + if (Math.Abs(window.Height - height) > 0.01) + { + window.Height = height; + } + + if (!double.IsNaN(s_frozenContentWidthPixels)) + { + var contentWidth = s_frozenContentWidthPixels / scaling; + if (Math.Abs(control.PreviewContentWidth - contentWidth) > 0.01) + { + control.PreviewContentWidth = contentWidth; + } + } + + if (!double.IsNaN(s_frozenContentHeightPixels)) + { + var contentHeight = s_frozenContentHeightPixels / scaling; + if (Math.Abs(control.PreviewContentHeight - contentHeight) > 0.01) + { + control.PreviewContentHeight = contentHeight; + } + } + } + + private static void FreezeWindowSizeIfNeeded() + { + lock (s_sync) + { + s_windowSizeFreezeScheduled = false; + if (s_windowSizeFrozen || s_window is null || !s_window.IsVisible) + { + return; + } + + var bounds = s_window.Bounds; + if (bounds.Width <= 0 || bounds.Height <= 0) + { + return; + } + + var scaling = GetWindowScaling(s_window); + s_lastFrozenWindowScaling = scaling; + s_frozenWindowWidthPixels = bounds.Width * scaling; + s_frozenWindowHeightPixels = bounds.Height * scaling; + s_frozenContentWidthPixels = !double.IsNaN(s_control?.PreviewContentWidth ?? double.NaN) + ? (s_control!.PreviewContentWidth * scaling) + : double.NaN; + s_frozenContentHeightPixels = !double.IsNaN(s_control?.PreviewContentHeight ?? double.NaN) + ? (s_control!.PreviewContentHeight * scaling) + : double.NaN; + + s_window.SizeToContent = SizeToContent.Manual; + s_window.Width = bounds.Width; + s_window.Height = bounds.Height; + s_windowSizeFrozen = true; + } + } + public void Show(IDockable dockable, PixelPoint position, PixelPoint offset, Visual? context = null, Size? preferredSize = null) { lock (s_sync) @@ -153,6 +341,19 @@ public void Show(IDockable dockable, PixelPoint position, PixelPoint offset, Vis s_window.Width = preferredSize is { } ps1 && ps1.Width > 0 ? ps1.Width : double.NaN; s_window.Height = preferredSize is { } ps2 && ps2.Height > 0 ? ps2.Height : double.NaN; s_window.Position = GetPositionWithinWindow(s_window, position, offset); + s_pendingWindowPosition = s_window.Position; + s_pendingStatus = s_control.Status; + s_hasPendingWindowMove = false; + s_windowMoveFlushScheduled = false; + s_windowMoveSessionId++; + s_windowMovePostSequence = 0; + s_windowSizeFrozen = false; + s_windowSizeFreezeScheduled = false; + s_frozenWindowWidthPixels = 0; + s_frozenWindowHeightPixels = 0; + s_frozenContentWidthPixels = double.NaN; + s_frozenContentHeightPixels = double.NaN; + s_lastFrozenWindowScaling = 1.0; if (!s_window.IsVisible) { @@ -176,8 +377,8 @@ public void Move(PixelPoint position, PixelPoint offset, string status) return; } - s_control.Status = status; - s_window.Position = GetPositionWithinWindow(s_window, position, offset); + var targetPosition = GetPositionWithinWindow(s_window, position, offset); + QueueWindowMove(s_window, s_control, targetPosition, status); } } @@ -206,6 +407,17 @@ public void Hide() return; } + s_hasPendingWindowMove = false; + s_windowMoveFlushScheduled = false; + s_windowMoveSessionId++; + s_windowMovePostSequence = 0; + s_windowSizeFrozen = false; + s_windowSizeFreezeScheduled = false; + s_frozenWindowWidthPixels = 0; + s_frozenWindowHeightPixels = 0; + s_frozenContentWidthPixels = double.NaN; + s_frozenContentHeightPixels = double.NaN; + s_lastFrozenWindowScaling = 1.0; s_window.Close(); s_window = null; s_control = null; @@ -359,9 +571,15 @@ private static void MoveManaged(ManagedWindowLayer layer, PixelPoint position, P return; } - s_managedControl.Status = status; - s_managedControl.Measure(Size.Infinity); var localPosition = GetManagedPosition(layer, position, offset); + if (!string.Equals(s_managedControl.Status, status, StringComparison.Ordinal)) + { + s_managedControl.Status = status; + s_managedControl.Measure(Size.Infinity); + layer.ShowOverlay("DragPreview", s_managedControl, localPosition, s_managedControl.DesiredSize, false); + return; + } + layer.ShowOverlay("DragPreview", s_managedControl, localPosition, s_managedControl.DesiredSize, false); } From 3032d78dbcd972d4a7f19e12b5a189fe199ab358 Mon Sep 17 00:00:00 2001 From: Dan Walmsley <4672627+danwalmsley@users.noreply.github.com> Date: Thu, 26 Feb 2026 11:28:10 +0000 Subject: [PATCH 2/7] make drag preview size freezing work on windows also. --- src/Dock.Avalonia/Internal/DragPreviewHelper.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Dock.Avalonia/Internal/DragPreviewHelper.cs b/src/Dock.Avalonia/Internal/DragPreviewHelper.cs index 7cc75d315..d7d9f6979 100644 --- a/src/Dock.Avalonia/Internal/DragPreviewHelper.cs +++ b/src/Dock.Avalonia/Internal/DragPreviewHelper.cs @@ -177,7 +177,6 @@ private static void FlushPendingWindowMove(int sessionId, long postSequence) private static void ApplyWindowMove(DragPreviewWindow window, DragPreviewControl control, PixelPoint targetPosition, string status) { - var hadStatus = !string.IsNullOrEmpty(control.Status); if (!string.Equals(control.Status, status, StringComparison.Ordinal)) { control.Status = status; @@ -197,7 +196,7 @@ private static void ApplyWindowMove(DragPreviewWindow window, DragPreviewControl MaintainFrozenWindowSize(window, control); } - if (!s_windowSizeFrozen && !s_windowSizeFreezeScheduled && !hadStatus && !string.IsNullOrEmpty(status)) + if (!s_windowSizeFrozen && !s_windowSizeFreezeScheduled && !string.IsNullOrEmpty(status)) { s_windowSizeFreezeScheduled = true; Dispatcher.UIThread.Post(FreezeWindowSizeIfNeeded, DispatcherPriority.Render); @@ -265,6 +264,10 @@ private static void FreezeWindowSizeIfNeeded() var bounds = s_window.Bounds; if (bounds.Width <= 0 || bounds.Height <= 0) { + // Retry on the next render pass; on some platforms the preview window + // may not have a stable measured size on the first callback. + s_windowSizeFreezeScheduled = true; + Dispatcher.UIThread.Post(FreezeWindowSizeIfNeeded, DispatcherPriority.Render); return; } From bafce4a36a742a1855840d8e0e063ef52f9a196f Mon Sep 17 00:00:00 2001 From: Dan Walmsley <4672627+danwalmsley@users.noreply.github.com> Date: Thu, 26 Feb 2026 11:32:16 +0000 Subject: [PATCH 3/7] set drag preview window size earlier. --- src/Dock.Avalonia/Internal/DragPreviewHelper.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/Dock.Avalonia/Internal/DragPreviewHelper.cs b/src/Dock.Avalonia/Internal/DragPreviewHelper.cs index d7d9f6979..61ae981e6 100644 --- a/src/Dock.Avalonia/Internal/DragPreviewHelper.cs +++ b/src/Dock.Avalonia/Internal/DragPreviewHelper.cs @@ -341,6 +341,7 @@ public void Show(IDockable dockable, PixelPoint position, PixelPoint offset, Vis s_window.DataContext = dockable; s_control.Status = string.Empty; s_window.Opacity = ClampOpacity(DockSettings.DragPreviewOpacity); + s_window.SizeToContent = SizeToContent.WidthAndHeight; s_window.Width = preferredSize is { } ps1 && ps1.Width > 0 ? ps1.Width : double.NaN; s_window.Height = preferredSize is { } ps2 && ps2.Height > 0 ? ps2.Height : double.NaN; s_window.Position = GetPositionWithinWindow(s_window, position, offset); @@ -358,6 +359,18 @@ public void Show(IDockable dockable, PixelPoint position, PixelPoint offset, Vis s_frozenContentHeightPixels = double.NaN; s_lastFrozenWindowScaling = 1.0; + // Pre-measure and lock the initial window size before Show() so platforms + // like Win32 don't briefly auto-size and then resize again on first updates. + s_control.ApplyTemplate(); + s_control.Measure(Size.Infinity); + var initialSize = s_control.DesiredSize; + if (initialSize.Width > 0 && initialSize.Height > 0) + { + s_window.SizeToContent = SizeToContent.Manual; + s_window.Width = initialSize.Width; + s_window.Height = initialSize.Height; + } + if (!s_window.IsVisible) { s_window.Show(); From 9a1e7813d1ac937ed4165c2ebee28f453c359ff0 Mon Sep 17 00:00:00 2001 From: Dan Walmsley <4672627+danwalmsley@users.noreply.github.com> Date: Thu, 26 Feb 2026 11:38:24 +0000 Subject: [PATCH 4/7] fix --- .../Internal/DragPreviewHelper.cs | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/Dock.Avalonia/Internal/DragPreviewHelper.cs b/src/Dock.Avalonia/Internal/DragPreviewHelper.cs index 61ae981e6..f3daa15f2 100644 --- a/src/Dock.Avalonia/Internal/DragPreviewHelper.cs +++ b/src/Dock.Avalonia/Internal/DragPreviewHelper.cs @@ -359,16 +359,26 @@ public void Show(IDockable dockable, PixelPoint position, PixelPoint offset, Vis s_frozenContentHeightPixels = double.NaN; s_lastFrozenWindowScaling = 1.0; - // Pre-measure and lock the initial window size before Show() so platforms - // like Win32 don't briefly auto-size and then resize again on first updates. - s_control.ApplyTemplate(); - s_control.Measure(Size.Infinity); - var initialSize = s_control.DesiredSize; - if (initialSize.Width > 0 && initialSize.Height > 0) + var hasPreferredOuterSize = preferredSize is { Width: > 0, Height: > 0 }; + if (hasPreferredOuterSize) { s_window.SizeToContent = SizeToContent.Manual; - s_window.Width = initialSize.Width; - s_window.Height = initialSize.Height; + s_window.Width = preferredSize!.Value.Width; + s_window.Height = preferredSize.Value.Height; + } + else + { + // Pre-measure and lock the initial window size before Show() so platforms + // like Win32 don't briefly auto-size and then resize again on first updates. + s_control.ApplyTemplate(); + s_control.Measure(Size.Infinity); + var initialSize = s_control.DesiredSize; + if (initialSize.Width > 0 && initialSize.Height > 0) + { + s_window.SizeToContent = SizeToContent.Manual; + s_window.Width = initialSize.Width; + s_window.Height = initialSize.Height; + } } if (!s_window.IsVisible) From 03721b3989fb053d545a428042cf0bc0629dc5c3 Mon Sep 17 00:00:00 2001 From: Dan Walmsley <4672627+danwalmsley@users.noreply.github.com> Date: Thu, 26 Feb 2026 11:41:59 +0000 Subject: [PATCH 5/7] capture margins when sizing drag preview. --- .../Internal/DockControlState.cs | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/Dock.Avalonia/Internal/DockControlState.cs b/src/Dock.Avalonia/Internal/DockControlState.cs index a17f3bb1b..250f9b365 100644 --- a/src/Dock.Avalonia/Internal/DockControlState.cs +++ b/src/Dock.Avalonia/Internal/DockControlState.cs @@ -99,6 +99,20 @@ private static bool CanFloatDockable(IDockable dockable) DockCapabilityResolver.ResolveOperationDock(dockable)); } + private static Size? GetPreferredPreviewSize(Control dragControl) + { + if (dragControl is not DocumentTabStripItem) + { + return null; + } + + var bounds = dragControl.Bounds.Size; + var margin = dragControl.Margin; + var width = Math.Max(0, bounds.Width + margin.Left + margin.Right); + var height = Math.Max(0, bounds.Height + margin.Top + margin.Bottom); + return new Size(width, height); + } + public void StartDrag(Control dragControl, Point startPoint, Point point, DockControl activeDockControl) { if (!dragControl.GetValue(DockProperties.IsDragEnabledProperty)) @@ -119,7 +133,7 @@ public void StartDrag(Control dragControl, Point startPoint, Point point, DockCo { DockHelpers.ShowWindows(targetDockable); var sp = activeDockControl.PointToScreen(point); - Size? preferredPreviewSize = dragControl is DocumentTabStripItem ? dragControl.Bounds.Size : null; + Size? preferredPreviewSize = GetPreferredPreviewSize(dragControl); _context.DragOffset = DragOffsetCalculator.CalculateOffset( dragControl, activeDockControl, @@ -566,9 +580,7 @@ public void Process(Point point, Vector delta, EventType eventType, DragAction d { DockHelpers.ShowWindows(targetDockable); var sp = inputActiveDockControl.PointToScreen(point); - Size? preferredPreviewSize = _context.DragControl is DocumentTabStripItem - ? _context.DragControl.Bounds.Size - : null; + Size? preferredPreviewSize = GetPreferredPreviewSize(_context.DragControl); _context.DragOffset = DragOffsetCalculator.CalculateOffset( _context.DragControl, inputActiveDockControl, _context.DragStartPoint); From 3e00841aa480b2e7a0b95c6bd832dabdce33db43 Mon Sep 17 00:00:00 2001 From: Dan Walmsley <4672627+danwalmsley@users.noreply.github.com> Date: Thu, 26 Feb 2026 11:48:38 +0000 Subject: [PATCH 6/7] add tracing --- .../Internal/DockControlState.cs | 9 +++++++ .../Internal/DragPreviewHelper.cs | 26 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/src/Dock.Avalonia/Internal/DockControlState.cs b/src/Dock.Avalonia/Internal/DockControlState.cs index 250f9b365..1d78cff3c 100644 --- a/src/Dock.Avalonia/Internal/DockControlState.cs +++ b/src/Dock.Avalonia/Internal/DockControlState.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for details. using System; using System.Collections.Generic; +using System.Diagnostics; using Avalonia; using Avalonia.Controls; using Avalonia.VisualTree; @@ -110,6 +111,14 @@ private static bool CanFloatDockable(IDockable dockable) var margin = dragControl.Margin; var width = Math.Max(0, bounds.Width + margin.Left + margin.Right); var height = Math.Max(0, bounds.Height + margin.Top + margin.Bottom); + var scaling = (dragControl.GetVisualRoot() as TopLevel)?.RenderScaling ?? 1.0; + Debug.WriteLine( + $"[Dock PreviewSize Capture] " + + $"tabBoundsDip=({bounds.Width:F2},{bounds.Height:F2}) " + + $"marginDip=({margin.Left:F2},{margin.Top:F2},{margin.Right:F2},{margin.Bottom:F2}) " + + $"preferredDip=({width:F2},{height:F2}) " + + $"preferredPx~=({width * scaling:F2},{height * scaling:F2}) " + + $"scale={scaling:F2}"); return new Size(width, height); } diff --git a/src/Dock.Avalonia/Internal/DragPreviewHelper.cs b/src/Dock.Avalonia/Internal/DragPreviewHelper.cs index f3daa15f2..aed6dcb89 100644 --- a/src/Dock.Avalonia/Internal/DragPreviewHelper.cs +++ b/src/Dock.Avalonia/Internal/DragPreviewHelper.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Linq; using System.Runtime.InteropServices; using Avalonia; @@ -381,10 +382,35 @@ public void Show(IDockable dockable, PixelPoint position, PixelPoint offset, Vis } } + Debug.WriteLine( + $"[Dock PreviewSize Window] " + + $"preferredDip={(preferredSize is { } p ? $"({p.Width:F2},{p.Height:F2})" : "null")} " + + $"appliedWindowDip=({s_window.Width:F2},{s_window.Height:F2}) " + + $"sizeToContent={s_window.SizeToContent} " + + $"previewContentDip=({s_control.PreviewContentWidth:F2},{s_control.PreviewContentHeight:F2})"); + if (!s_window.IsVisible) { s_window.Show(); } + + Dispatcher.UIThread.Post(() => + { + if (s_window is null || s_control is null) + { + return; + } + + var content = s_window.Content as Control; + var wb = s_window.Bounds; + var cb = content?.Bounds ?? default; + Debug.WriteLine( + $"[Dock PreviewSize AfterShow] " + + $"windowBoundsDip=({wb.Width:F2},{wb.Height:F2}) " + + $"contentBoundsDip=({cb.Width:F2},{cb.Height:F2}) " + + $"previewContentDip=({s_control.PreviewContentWidth:F2},{s_control.PreviewContentHeight:F2}) " + + $"scale={s_window.RenderScaling:F2}"); + }, DispatcherPriority.Render); } } From 284388d1b716158586e1e1551526c709f3090620 Mon Sep 17 00:00:00 2001 From: Dan Walmsley <4672627+danwalmsley@users.noreply.github.com> Date: Thu, 26 Feb 2026 12:02:56 +0000 Subject: [PATCH 7/7] more tracing --- src/Dock.Avalonia/Internal/DockControlState.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Dock.Avalonia/Internal/DockControlState.cs b/src/Dock.Avalonia/Internal/DockControlState.cs index 1d78cff3c..91aa6635a 100644 --- a/src/Dock.Avalonia/Internal/DockControlState.cs +++ b/src/Dock.Avalonia/Internal/DockControlState.cs @@ -112,11 +112,19 @@ private static bool CanFloatDockable(IDockable dockable) var width = Math.Max(0, bounds.Width + margin.Left + margin.Right); var height = Math.Max(0, bounds.Height + margin.Top + margin.Bottom); var scaling = (dragControl.GetVisualRoot() as TopLevel)?.RenderScaling ?? 1.0; + var desired = dragControl.DesiredSize; + var boundsPxW = bounds.Width * scaling; + var boundsPxH = bounds.Height * scaling; Debug.WriteLine( $"[Dock PreviewSize Capture] " + + $"tabBoundsRectDip=({dragControl.Bounds.X:F2},{dragControl.Bounds.Y:F2},{dragControl.Bounds.Width:F2},{dragControl.Bounds.Height:F2}) " + $"tabBoundsDip=({bounds.Width:F2},{bounds.Height:F2}) " + + $"tabDesiredDip=({desired.Width:F2},{desired.Height:F2}) " + $"marginDip=({margin.Left:F2},{margin.Top:F2},{margin.Right:F2},{margin.Bottom:F2}) " + $"preferredDip=({width:F2},{height:F2}) " + + $"tabBoundsPxExact=({boundsPxW:F2},{boundsPxH:F2}) " + + $"tabBoundsPxRound=({Math.Round(boundsPxW):F0},{Math.Round(boundsPxH):F0}) " + + $"tabBoundsPxCeil=({Math.Ceiling(boundsPxW):F0},{Math.Ceiling(boundsPxH):F0}) " + $"preferredPx~=({width * scaling:F2},{height * scaling:F2}) " + $"scale={scaling:F2}"); return new Size(width, height);