Files
ewsdr/PlatformUtils.pas
ew8bakandClaude Opus 4.8 388687d684 fix(macos): масштаб канвы берём у окна контрола, а не у главного экрана
backingScaleFactor главного экрана врёт, когда окно лежит на другом
мониторе. Спрашиваем масштаб у NSWindow того NSView, которому принадлежит
паинтбокс: это ровно то число, в котором Cocoa рисует эту канву, и оно
меняется само при перетаскивании окна между мониторами.

Objective-C по-прежнему живёт только в MacScale; наружу торчит
кроссплатформенная GetControlScale, которая на Windows/Linux сворачивается
в Result := 1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 21:20:53 +03:00

131 lines
4.1 KiB
ObjectPascal
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
unit PlatformUtils;
{
PlatformUtils.pas - small helpers for desktop/runtime platform checks.
}
{$IFDEF FPC}
{$MODE Delphi}
{$ENDIF}
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/
// Windows: %APPDATA%\ewsdr\
function GetAppCfgDir: string;
implementation
uses
SysUtils {$IFNDEF HEADLESS}, Forms{$ENDIF}
{$IF DEFINED(DARWIN) AND NOT DEFINED(HEADLESS)}, MacScale{$ENDIF};
function HasOpenGLSpectrumSwitch: Boolean;
var
I: Integer;
S: string;
begin
Result := False;
for I := 1 to ParamCount do
begin
S := LowerCase(ParamStr(I));
if (S = '--opengl') or (S = '-opengl') or (S = '/opengl') then
Exit(True);
end;
// Бандл под LaunchServices не может передать argv, поэтому тот же
// переключатель читается из окружения (Info.plist -> LSEnvironment).
S := LowerCase(Trim(GetEnvironmentVariable('EWSDR_OPENGL')));
if (S = '1') or (S = 'true') or (S = 'yes') or (S = 'on') then
Exit(True);
end;
function GetScreenScale: Integer;
begin
{$IF DEFINED(DARWIN) AND NOT DEFINED(HEADLESS)}
// Cocoa рисует канву в физических пикселях, а TBitmap всегда 1x. Кто рисует
// через offscreen-битмап, должен растить его в GetScreenScale раз.
Result := MacBackingScale;
{$ELSE}
Result := 1;
{$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}
Result := Screen.PixelsPerInch;
if Result <= 0 then Result := 96;
{$ELSE}
Result := 96; // headless-демон без LCL: экрана нет, дефолт DPI
{$ENDIF}
end;
function GetAppCfgDir: string;
begin
{$IFDEF WINDOWS}
Result := GetEnvironmentVariable('APPDATA');
if Result = '' then
Result := GetEnvironmentVariable('LOCALAPPDATA');
if Result = '' then
Result := ExtractFilePath(ParamStr(0));
Result := IncludeTrailingPathDelimiter(Result) + 'ewsdr' + PathDelim;
{$ELSE}
Result := IncludeTrailingPathDelimiter(GetAppConfigDir(False));
{$ENDIF}
if not DirectoryExists(Result) then
ForceDirectories(Result);
end;
end.