fix(window): stop forcing maximised on a window the user left normal

The previous version over-corrected: a normal window came back maximised
after Win+D. Two causes, both fixed.

- It read FMX's WindowState, which does not reliably follow a native
  un-maximise, so FWantMaximized never cleared. Now reads Win32 IsZoomed /
  IsIconic, which is the actual truth.
- The restore fires its own resize, reporting whatever FMX decided — recording
  that erased the very intent we were preserving. FWasMinimized now gates the
  restore's resize out of the tracking.

Activate then corrects both directions: re-maximise when FMX dropped a
maximised window, restore when Windows brought a normal one back maximised.
Degrades safely — if the minimise resize never fires, it simply does nothing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
r-zakarya
2026-07-25 20:06:34 +01:00
parent 03a8a6f8a6
commit 8204ccb316
+36 -11
View File
@@ -79,6 +79,9 @@ type
// which happens however the user comes back. FWantMaximized mirrors the
// user's LAST deliberate choice, so we never fight a normal-size window.
FWantMaximized: Boolean;
// True between an external minimise and the following activate, so the
// restore's own resize is not mistaken for a user resize.
FWasMinimized: Boolean;
FServer: TPMHTTPServer;
FBridge: TPMBridge;
// Accumulates base64 chunks for large file saves (attachments too big
@@ -174,6 +177,7 @@ begin
// The .fmx ships WindowState = wsMaximized, so that is the starting intent.
// Wired here (not in the .fmx) to keep the designer file untouched.
FWantMaximized := (WindowState = TWindowState.wsMaximized);
FWasMinimized := False;
OnResize := FormResizeTrackState;
OnActivate := FormActivateRestoreState;
@@ -1503,23 +1507,44 @@ begin
Application.Terminate;
end;
// Remember the window state the USER settled on. Minimised is skipped on
// purpose: that is precisely the state we have to survive, not record.
// Remember the state the USER settled on. Reads Win32 (IsZoomed/IsIconic)
// rather than FMX's WindowState, which does not reliably follow a native
// un-maximise — that is why the first version never cleared the flag.
//
// The restore itself fires a resize too, and that one must NOT be recorded:
// it reports whatever FMX decided (often "normal"), which would erase the
// intent we are trying to preserve. FWasMinimized gates it out.
procedure TMainForm.FormResizeTrackState(Sender: TObject);
var
LHwnd: HWND;
begin
if WindowState = TWindowState.wsMaximized then
FWantMaximized := True
else if WindowState = TWindowState.wsNormal then
FWantMaximized := False;
LHwnd := WindowHandleToPlatform(Self.Handle).Wnd;
if LHwnd = 0 then Exit;
if IsIconic(LHwnd) then
begin
FWasMinimized := True; // the state we must survive, not record
Exit;
end;
if FWasMinimized then Exit; // this resize IS the restore
FWantMaximized := IsZoomed(LHwnd); // genuine user-driven resize
end;
// Re-apply "maximised" when the window comes back from an external minimise.
// Runs however the user returns (taskbar, tray, Alt+Tab), because none of our
// own restore paths are involved in a plain taskbar click.
// Re-apply the remembered state once the window is back. Runs however the
// user returns (taskbar, tray, Alt+Tab) — a plain taskbar restore never goes
// through any of our own restore paths. Corrects BOTH directions: FMX drops
// a maximised window to normal, and Windows can restore a normal window
// maximised.
procedure TMainForm.FormActivateRestoreState(Sender: TObject);
var
LHwnd: HWND;
begin
if FWantMaximized and (WindowState = TWindowState.wsNormal) then
WindowState := TWindowState.wsMaximized;
if not FWasMinimized then Exit;
LHwnd := WindowHandleToPlatform(Self.Handle).Wnd;
if (LHwnd = 0) or IsIconic(LHwnd) then Exit;
FWasMinimized := False;
if FWantMaximized = IsZoomed(LHwnd) then Exit; // already right
if FWantMaximized then ShowWindow(LHwnd, SW_SHOWMAXIMIZED)
else ShowWindow(LHwnd, SW_RESTORE);
end;
procedure TMainForm.AutofillTimerTick(Sender: TObject);