feat(ui): adapt to the screen and its scaling, not to fixed pixels
Two things differ between machines and only one of them is width: a 4K panel
has more pixels, while a 125%/150% display has the same logical pixels holding
LESS, because every label and margin is taller. Breakpoints written as raw
pixels only hold on the machine they were tuned on.
* ui_scale() derives a factor from font height (1.0 at the 15px line the
layouts were measured against) and every narrow-guard threshold is
multiplied by it, so panes fold when the content is cramped rather than
when a number is crossed.
* The window takes a share of the available screen (80% × 85%) with the old
1180×760 as the floor, instead of opening at that size on any monitor.
* Moving the window to another screen re-pins the assistant and re-decides
the fold, since the new screen's work area and scaling may differ.
Found by tools/check_multi_screen.py, which walks 5 window sizes × 3 font
scales:
* At 150%, Schedule was clipped on 1280 and 1366 screens and the window's
own minimum grew to 1459px — wider than a 1280 laptop, so the app could
not fit at all. The cause was not the lanes: the one-line lane-count
summary in the header reported a sizeHint wide enough to set the minimum
width of the entire window. It now yields first (its text stays in the
tooltip); the window minimum drops 1459 → 752 and holds there at every
scale.
Also: these checkers exited 0xC0000409 from a Qt teardown crash AFTER printing
their verdict. check_probes_bite decides whether a probe caught its mutation by
reading exit codes, so a crash would have counted as "caught" — the round could
have passed while proving nothing. They now flush and os._exit with the real
verdict, and round 5 still catches all six mutations.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1013,6 +1013,11 @@ class MainWindow(QMainWindow):
|
||||
self.help_agent.apply_theme() # chat body follows theme (header stays fixed)
|
||||
|
||||
# ---- sizing ------------------------------------------------------
|
||||
# Share of the available screen the window takes when it has room to. Fixed
|
||||
# pixels do not travel: 1180×760 fills a laptop and looks lost on a 4K
|
||||
# panel. `want_*` stays the floor so a small screen behaves as before.
|
||||
_SCREEN_SHARE_W, _SCREEN_SHARE_H = 0.80, 0.85
|
||||
|
||||
def _fit_to_screen(self, want_w: int, want_h: int) -> None:
|
||||
screen = self.screen() or QGuiApplication.primaryScreen()
|
||||
avail = screen.availableGeometry() if screen else None
|
||||
@@ -1020,8 +1025,12 @@ class MainWindow(QMainWindow):
|
||||
self.resize(want_w, want_h)
|
||||
return
|
||||
margin = 60
|
||||
w = min(want_w, avail.width() - margin)
|
||||
h = min(want_h, avail.height() - margin)
|
||||
# Take a share of the screen, never less than the asked-for size and
|
||||
# never more than the screen can show.
|
||||
w = min(max(want_w, int(avail.width() * self._SCREEN_SHARE_W)),
|
||||
avail.width() - margin)
|
||||
h = min(max(want_h, int(avail.height() * self._SCREEN_SHARE_H)),
|
||||
avail.height() - margin)
|
||||
# minimum must never exceed what the screen can show
|
||||
self.setMinimumSize(min(820, avail.width() - margin), min(520, avail.height() - margin))
|
||||
self.resize(max(w, 1), max(h, 1))
|
||||
@@ -1029,6 +1038,25 @@ class MainWindow(QMainWindow):
|
||||
frame.moveCenter(avail.center())
|
||||
self.move(frame.topLeft())
|
||||
|
||||
def moveEvent(self, event): # noqa: N802 - Qt override
|
||||
super().moveEvent(event)
|
||||
# Dragged to another monitor: its work area (and scaling) may differ, so
|
||||
# the floating assistant re-pins and the panes re-decide if they fit.
|
||||
self._on_screen_maybe_changed()
|
||||
|
||||
def _on_screen_maybe_changed(self) -> None:
|
||||
screen = self.screen()
|
||||
if screen is getattr(self, "_last_screen", None):
|
||||
return
|
||||
self._last_screen = screen
|
||||
avail = screen.availableGeometry() if screen else None
|
||||
if avail is not None:
|
||||
self.setMinimumSize(min(820, avail.width() - 60),
|
||||
min(520, avail.height() - 60))
|
||||
if getattr(self, "help_agent", None) is not None:
|
||||
self._update_dock_guard()
|
||||
self.help_agent.reposition()
|
||||
|
||||
# ---- lifecycle ---------------------------------------------------
|
||||
def closeEvent(self, event) -> None: # noqa: N802
|
||||
keep = (self.tray is not None
|
||||
|
||||
Reference in New Issue
Block a user