From 388687d684ce0e69c4497bc794ae9015ed3c7a3c Mon Sep 17 00:00:00 2001 From: Uladzimir Date: Thu, 9 Jul 2026 21:20:53 +0300 Subject: [PATCH] =?UTF-8?q?fix(macos):=20=D0=BC=D0=B0=D1=81=D1=88=D1=82?= =?UTF-8?q?=D0=B0=D0=B1=20=D0=BA=D0=B0=D0=BD=D0=B2=D1=8B=20=D0=B1=D0=B5?= =?UTF-8?q?=D1=80=D1=91=D0=BC=20=D1=83=20=D0=BE=D0=BA=D0=BD=D0=B0=20=D0=BA?= =?UTF-8?q?=D0=BE=D0=BD=D1=82=D1=80=D0=BE=D0=BB=D0=B0,=20=D0=B0=20=D0=BD?= =?UTF-8?q?=D0=B5=20=D1=83=20=D0=B3=D0=BB=D0=B0=D0=B2=D0=BD=D0=BE=D0=B3?= =?UTF-8?q?=D0=BE=20=D1=8D=D0=BA=D1=80=D0=B0=D0=BD=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit backingScaleFactor главного экрана врёт, когда окно лежит на другом мониторе. Спрашиваем масштаб у NSWindow того NSView, которому принадлежит паинтбокс: это ровно то число, в котором Cocoa рисует эту канву, и оно меняется само при перетаскивании окна между мониторами. Objective-C по-прежнему живёт только в MacScale; наружу торчит кроссплатформенная GetControlScale, которая на Windows/Linux сворачивается в Result := 1. Co-Authored-By: Claude Opus 4.8 --- MacScale.pas | 16 ++++++++++++++++ PanZoomBar.pas | 2 +- PlatformUtils.pas | 44 ++++++++++++++++++++++++++++++++++++++++++++ RulerView.pas | 2 +- SMeterView.pas | 2 +- 5 files changed, 63 insertions(+), 3 deletions(-) diff --git a/MacScale.pas b/MacScale.pas index a316616..febd4ec 100644 --- a/MacScale.pas +++ b/MacScale.pas @@ -22,6 +22,10 @@ interface // Целочисленный масштаб основного экрана: 2 на Retina, 1 иначе. function MacBackingScale: Integer; +// Масштаб окна, которому принадлежит NSView (Handle контрола LCL). Именно в +// этом масштабе Cocoa рисует канву контрола, и он меняется сам, когда окно +// перетаскивают между мониторами. Возвращает 0, если окна ещё нет. +function MacBackingScaleForView(AView: Pointer): Integer; implementation @@ -39,4 +43,16 @@ begin if Result < 1 then Result := 1; end; +function MacBackingScaleForView(AView: Pointer): Integer; +var + Wnd: NSWindow; +begin + Result := 0; + if AView = nil then Exit; + Wnd := NSView(AView).window; + if Wnd = nil then Exit; // контрол ещё не показан — масштаб неизвестен + Result := Round(Wnd.backingScaleFactor); + if Result < 1 then Result := 1; +end; + end. diff --git a/PanZoomBar.pas b/PanZoomBar.pas index a4175b6..425fc8b 100644 --- a/PanZoomBar.pas +++ b/PanZoomBar.pas @@ -343,7 +343,7 @@ begin if (W <= 0) or (H <= 0) then Exit; // Битмап в физических пикселях канвы, иначе Cocoa растянет его и замылит. - FScale := GetScreenScale; + FScale := GetControlScale(FPb); BmpW := W * FScale; BmpH := H * FScale; if (FBmp.Width <> BmpW) or (FBmp.Height <> BmpH) then FBmp.SetSize(BmpW, BmpH); diff --git a/PlatformUtils.pas b/PlatformUtils.pas index c4e5b2c..c8817f4 100644 --- a/PlatformUtils.pas +++ b/PlatformUtils.pas @@ -10,11 +10,23 @@ unit PlatformUtils; interface +{$IFNDEF HEADLESS} +uses + Controls; +{$ENDIF} + function HasOpenGLSpectrumSwitch: Boolean; function CurrentScreenDPI: Integer; // Целочисленный масштаб канвы экрана: 2 на Retina, 1 везде остальном. // Windows/Linux всегда получают 1 ⇒ умножение на него ничего не меняет. function GetScreenScale: Integer; +// То же, но для канвы конкретного контрола: на macOS масштаб берётся у окна, +// которому контрол принадлежит, а не у главного экрана. Это то самое число, в +// котором Cocoa рисует эту канву, и оно меняется, когда окно перетаскивают на +// монитор с другим масштабом. Так и надо считать размер offscreen-битмапа. +{$IFNDEF HEADLESS} +function GetControlScale(AControl: TControl): Integer; +{$ENDIF} // Возвращает каталог для хранения конфигурации (с завершающим разделителем). // macOS: ~/Library/Application Support/ewsdr/ // Linux: ~/.config/ewsdr/ @@ -57,6 +69,38 @@ begin {$ENDIF} end; +{$IFNDEF HEADLESS} +function GetControlScale(AControl: TControl): Integer; +{$IF DEFINED(DARWIN)} +var + Host: TWinControl; +begin + // У TPaintBox и прочих TGraphicControl своего NSView нет — рисуют они на + // канве ближайшего родителя с хендлом, его окно и спрашиваем. + if AControl is TWinControl then + Host := TWinControl(AControl) + else if AControl <> nil then + Host := AControl.Parent + else + Host := nil; + while (Host <> nil) and not Host.HandleAllocated do + Host := Host.Parent; + + if Host <> nil then + Result := MacBackingScaleForView(Pointer(Host.Handle)) + else + Result := 0; + + // Контрола ещё нет на экране: до первой отрисовки сгодится главный экран. + if Result < 1 then + Result := GetScreenScale; +{$ELSE} +begin + Result := 1; +{$ENDIF} +end; +{$ENDIF} + function CurrentScreenDPI: Integer; begin {$IFNDEF HEADLESS} diff --git a/RulerView.pas b/RulerView.pas index 264b527..20942c6 100644 --- a/RulerView.pas +++ b/RulerView.pas @@ -261,7 +261,7 @@ begin // Окно могли перетащить на монитор с другим масштабом — кэш линейки тогда // держит битмап прежнего разрешения, его надо выбросить. - NewScale := GetScreenScale; + NewScale := GetControlScale(PB); if NewScale <> FScale then begin FScale := NewScale; diff --git a/SMeterView.pas b/SMeterView.pas index ab67702..01321e9 100644 --- a/SMeterView.pas +++ b/SMeterView.pas @@ -500,7 +500,7 @@ begin // Битмап держим в физических пикселях канвы, иначе Cocoa растянет его вдвое // и картинка замылится. На Windows/Linux FScale=1 и всё как было. - FScale := GetScreenScale; + FScale := GetControlScale(PB); BmpW := W * FScale; BmpH := H * FScale; if (FSmBitmap = nil) or (FSmBitmap.Width <> BmpW) or (FSmBitmap.Height <> BmpH) then