mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:37:33 +00:00
Fix window geometry save/restore: DPI scaling, position drift, maximized state
- LFM: Position = poDesigned (was poScreenCenter, overriding our restore) - RestoreWindowBounds: remove position scaling (L,T invariant across DPI); scale size correctly per backend: LCLWin32 uses CurDPI/SavedDPI (physical px), Qt6/GTK/Cocoa use SavedDPI/CurDPI (logical px, inverse); fix min-size inflation on Qt6 (was MulDiv(900,DPI,96) which exceeds logical screen width at 150%+ scale, forcing window to 0,0) - SaveWindowBounds moved from FormDestroy to FormClose while window is still stable; add guard for bogus RestoredLeft/Top values on Qt6 when maximized - RestoreWindowBounds stores FRestoreL/T and re-applies position in AfterShowTick (200ms after show) when WM frame extents are already known to Qt, fixing the systematic right/down drift caused by unknown frame extents at FormCreate time - Save and restore WindowState (maximized flag); skip AfterShowTick position re-apply when window is maximized Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -8,7 +8,7 @@ object MainForm: TMainForm
|
||||
Font.Color = clSilver
|
||||
Font.Height = -13
|
||||
Font.Name = 'Courier New'
|
||||
Position = poScreenCenter
|
||||
Position = poDesigned
|
||||
LCLVersion = '4.6.0.0'
|
||||
OnClose = FormClose
|
||||
OnCreate = FormCreate
|
||||
|
||||
+52
-6
@@ -343,6 +343,8 @@ type
|
||||
FMeterTimer: TTimer;
|
||||
FSpectrumTimer: TTimer;
|
||||
FAfterShowTimer:TTimer; // однократный таймер для пост-инициализации
|
||||
FRestoreL, FRestoreT: Integer; // позиция для отложенного восстановления
|
||||
FHasPendingRestore: Boolean;
|
||||
|
||||
// ---- Top panel (toolbar + VFO group + S-meter) ----
|
||||
PanelToolbar: TPanel;
|
||||
@@ -933,10 +935,11 @@ end;
|
||||
procedure TMainForm.RestoreWindowBounds;
|
||||
var
|
||||
L, T, Wd, Ht, SavedDPI, CurDPI: Integer;
|
||||
Maximized: Boolean;
|
||||
R, WA: TRect;
|
||||
Mon: TMonitor;
|
||||
begin
|
||||
FSettings.LoadWindowBounds(L, T, Wd, Ht, SavedDPI);
|
||||
FSettings.LoadWindowBounds(L, T, Wd, Ht, SavedDPI, Maximized);
|
||||
if (Wd > 400) and (Ht > 300) then
|
||||
begin
|
||||
CurDPI := CurrentScreenDPI;
|
||||
@@ -944,14 +947,32 @@ begin
|
||||
|
||||
if SavedDPI <> CurDPI then
|
||||
begin
|
||||
L := MulDiv(L, CurDPI, SavedDPI);
|
||||
T := MulDiv(T, CurDPI, SavedDPI);
|
||||
// Позицию не масштабируем: на LCLWin32 координаты физические и не зависят
|
||||
// от DPI-фактора; на Qt6/GTK/Cocoa — логические (device-independent), тоже
|
||||
// инвариантны. Выход за рабочую область поймает EnsureRange ниже.
|
||||
//
|
||||
// Размер масштабируем, чтобы окно выглядело одинаково при смене DPI:
|
||||
// LCLWin32: физические пиксели → Wd × CurDPI/SavedDPI
|
||||
// Qt6/GTK/Cocoa: логические пиксели → Wd × SavedDPI/CurDPI (обратное)
|
||||
{$IFDEF LCLWin32}
|
||||
Wd := MulDiv(Wd, CurDPI, SavedDPI);
|
||||
Ht := MulDiv(Ht, CurDPI, SavedDPI);
|
||||
{$ELSE}
|
||||
Wd := MulDiv(Wd, SavedDPI, CurDPI);
|
||||
Ht := MulDiv(Ht, SavedDPI, CurDPI);
|
||||
{$ENDIF}
|
||||
end;
|
||||
|
||||
// Минимальный размер:
|
||||
// LCLWin32 — в физических пикселях, масштабируется с DPI.
|
||||
// Qt6/GTK/Cocoa — в логических пикселях, device-independent, фиксированный.
|
||||
{$IFDEF LCLWin32}
|
||||
Wd := Max(MulDiv(900, CurDPI, 96), Wd);
|
||||
Ht := Max(MulDiv(560, CurDPI, 96), Ht);
|
||||
{$ELSE}
|
||||
Wd := Max(900, Wd);
|
||||
Ht := Max(560, Ht);
|
||||
{$ENDIF}
|
||||
R := Bounds(L, T, Wd, Ht);
|
||||
Mon := Screen.MonitorFromRect(R);
|
||||
if Mon <> nil then WA := Mon.WorkareaRect
|
||||
@@ -962,15 +983,27 @@ begin
|
||||
L := EnsureRange(L, WA.Left, Max(WA.Left, WA.Right - Wd));
|
||||
T := EnsureRange(T, WA.Top, Max(WA.Top, WA.Bottom - Ht));
|
||||
|
||||
Position := poDesigned;
|
||||
SetBounds(L, T, Wd, Ht);
|
||||
if Maximized then
|
||||
WindowState := wsMaximized
|
||||
else
|
||||
begin
|
||||
// На Qt6/X11 frame extents ещё не известны Qt во время FormCreate,
|
||||
// поэтому move() может поставить контент туда, куда мы хотели рамку.
|
||||
// Повторно применим позицию в AfterShowTick, когда WM уже декорировал окно.
|
||||
FRestoreL := L;
|
||||
FRestoreT := T;
|
||||
FHasPendingRestore := True;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TMainForm.SaveWindowBounds;
|
||||
var
|
||||
L, T, Wd, Ht, DPI: Integer;
|
||||
Maximized: Boolean;
|
||||
begin
|
||||
Maximized := WindowState = wsMaximized;
|
||||
if WindowState = wsNormal then
|
||||
begin
|
||||
L := Left; T := Top; Wd := Width; Ht := Height;
|
||||
@@ -981,9 +1014,12 @@ begin
|
||||
Wd := RestoredWidth; Ht := RestoredHeight;
|
||||
end;
|
||||
|
||||
// RestoredLeft/Top могут вернуть мусор на Qt6 при максимизации;
|
||||
// ставим разумные запасные значения
|
||||
if (L < -32000) or (T < -32000) then begin L := 80; T := 80; end;
|
||||
if (Wd <= 400) or (Ht <= 300) then Exit;
|
||||
DPI := CurrentScreenDPI;
|
||||
FSettings.SaveWindowBounds(L, T, Wd, Ht, DPI);
|
||||
FSettings.SaveWindowBounds(L, T, Wd, Ht, DPI, Maximized);
|
||||
end;
|
||||
|
||||
function TMainForm.MakeBandSettings: TBandSettings;
|
||||
@@ -1497,7 +1533,6 @@ begin
|
||||
end;
|
||||
// Размер окна сохраняем всегда (не зависит от подключения)
|
||||
FSettings.SaveStartupPreview(FVfoA, FVfoB, FSampleRate);
|
||||
SaveWindowBounds;
|
||||
FSettings.Save;
|
||||
FSettings.Free;
|
||||
FWebServer.Stop;
|
||||
@@ -1509,6 +1544,9 @@ end;
|
||||
|
||||
procedure TMainForm.FormClose(Sender: TObject; var CloseAction: TCloseAction);
|
||||
begin
|
||||
// Сохраняем позицию здесь — окно ещё полностью видимо и стабильно.
|
||||
// В FormDestroy Qt-виджет может быть уже в нестабильном состоянии.
|
||||
SaveWindowBounds;
|
||||
// Останавливаем трансивер как при нажатии STOP
|
||||
if FNetwork.Connected then
|
||||
begin
|
||||
@@ -6968,6 +7006,14 @@ begin
|
||||
// Вызывается один раз через 200 мс после старта формы
|
||||
FAfterShowTimer.Enabled := False;
|
||||
|
||||
// Повторно применяем сохранённую позицию: к этому моменту WM уже декорировал
|
||||
// окно и Qt знает frame extents, поэтому move() ставит рамку точно.
|
||||
if FHasPendingRestore then
|
||||
begin
|
||||
SetBounds(FRestoreL, FRestoreT, Width, Height);
|
||||
FHasPendingRestore := False;
|
||||
end;
|
||||
|
||||
try
|
||||
FAudioOut.Open;
|
||||
except
|
||||
|
||||
+7
-5
@@ -282,8 +282,8 @@ type
|
||||
class procedure DefaultGlobal(out G: TGlobalSettings);
|
||||
class procedure DefaultTX(out T: TTXSettings);
|
||||
// Размер окна — не привязан к MAC, хранится в корне JSON
|
||||
procedure SaveWindowBounds(L, T, W, H, DPI: Integer);
|
||||
procedure LoadWindowBounds(out L, T, W, H, DPI: Integer);
|
||||
procedure SaveWindowBounds(L, T, W, H, DPI: Integer; Maximized: Boolean);
|
||||
procedure LoadWindowBounds(out L, T, W, H, DPI: Integer; out Maximized: Boolean);
|
||||
procedure SaveStartupPreview(VfoA, VfoB: Double; SampleRate: Integer);
|
||||
function LoadStartupPreview(out VfoA, VfoB: Double; out SampleRate: Integer): Boolean;
|
||||
procedure SaveAudioBufferSize(BufferSize: Integer);
|
||||
@@ -757,7 +757,7 @@ begin
|
||||
B.FMStepIdx := EnsureRange(JI(O,'fmstep_idx', B.FMStepIdx), 0, 3);
|
||||
end;
|
||||
|
||||
procedure TSettingsManager.SaveWindowBounds(L, T, W, H, DPI: Integer);
|
||||
procedure TSettingsManager.SaveWindowBounds(L, T, W, H, DPI: Integer; Maximized: Boolean);
|
||||
var O: TJSONObject;
|
||||
begin
|
||||
O := EnsureObj(FRoot, 'window');
|
||||
@@ -766,12 +766,13 @@ begin
|
||||
JW(O, 'width', W);
|
||||
JW(O, 'height', H);
|
||||
JW(O, 'dpi', DPI);
|
||||
JW(O, 'maximized', Maximized);
|
||||
end;
|
||||
|
||||
procedure TSettingsManager.LoadWindowBounds(out L, T, W, H, DPI: Integer);
|
||||
procedure TSettingsManager.LoadWindowBounds(out L, T, W, H, DPI: Integer; out Maximized: Boolean);
|
||||
var O: TJSONObject;
|
||||
begin
|
||||
L := 80; T := 80; W := 1400; H := 900; DPI := 0;
|
||||
L := 80; T := 80; W := 1400; H := 900; DPI := 0; Maximized := False;
|
||||
if FRoot.Find('window') = nil then Exit;
|
||||
O := EnsureObj(FRoot, 'window');
|
||||
L := JI(O, 'left', 80);
|
||||
@@ -779,6 +780,7 @@ begin
|
||||
W := JI(O, 'width', 1400);
|
||||
H := JI(O, 'height', 900);
|
||||
DPI := JI(O, 'dpi', 0);
|
||||
Maximized := JB(O, 'maximized', False);
|
||||
end;
|
||||
|
||||
procedure TSettingsManager.SaveStartupPreview(VfoA, VfoB: Double; SampleRate: Integer);
|
||||
|
||||
Reference in New Issue
Block a user