spacr.qt.live_zoom

Hold Z and turn the wheel to resize the interface’s text, live.

THE LIVE ZOOM GESTURE, and the measurement that shaped it.

The request came with a condition – “only if possible to do fast without lag” – so the first question was what a notch costs. On one 1440x900 MainWindow with 847 widgets:

stylesheet() rebuild 1.4 ms app.setStyleSheet(…) + repolish 587.5 ms <- Preferences repolish the visible screen only 107 ms QApplication.setFont(…) 14 ms

Only the last one is live, and it moves nothing: the application sheet carries 49 hardcoded font-size: <N>px declarations, and a QSS font-size beats the inherited application font.

THE ESCAPE THE INSTRUCTION PROPOSED DOES NOT EXIST. Part 2 of 378 assumed the 49 declarations could be rewritten in em or % so that one setFont moved everything. Qt does not implement either for font-size: QCss accepts only pt, px and the CSS size keywords, and silently drops any other unit, so font-size: 2em leaves the widget at the inherited size. Measured on this PySide6, and pinned by test_qt_still_has_no_relative_font_size, which fails the day Qt gains the unit and makes the simpler design available.

WHAT IS LIVE INSTEAD. An explicit QWidget.setFont does beat the application sheet’s font-size – it is the same escape hatch that lets a per-widget sheet win – and it costs one font assignment and one relayout rather than a global unpolish/repolish. So a notch snapshots each visible widget’s font once, then re-scales every widget from that snapshot by the ratio the wheel has travelled. The role hierarchy survives because each widget is scaled from its own baseline: a 22 px title and a 13 px caption stay in proportion without this module knowing which is which.

WHAT IS NOT LIVE, AND WHY THAT WAS ACCEPTED. Every size the font scale pins from Python – row heights, column widths, icon sizes, tile geometry, all of spacr.qt.preferences.scaled_px() – moves only when the stylesheet is rebuilt, and that is the 587 ms number. The choice made: “Text live, spacing on release.” Text follows the wheel; the spacing around it catches up in one step when the wheel stops or Z comes up. It is a deliberate compromise, not an oversight, and it is why the settle exists.

AND THE ICONS DID NOT MOVE AT ALL, which was NOT part of that compromise. The spacing caught up at the settle; the icons never caught up, because an icon size is neither a stylesheet value nor a scaled_px call that anything re-ran – it is a widget PROPERTY written once when the widget was built, so a scale that grew every caption left every glyph beside those captions exactly where it was. The settle now re-derives them, through spacr.qt.preferences._rescale_icon_sizes(), inside the same apply_preferences_to_app step that rebuilds the sheet: 5.5 ms for the 2,122 widgets of a window with two modules open, against the 395-877 ms that step costs when the scale really changes. Nothing was added to the live half.

Classes

ColumnTextScale

Ctrl + wheel over a module screen's right-hand column sizes its text.

LiveZoomFilter

Application-wide filter that turns Z + wheel into a live font scale.

Functions

font_size_rules(→ list)

Every (selector, size, unit) in sheet that sets a font size.

install_column_text_scale(→ Optional[ColumnTextScale])

Install the Ctrl + wheel column gesture on the application, once.

install_live_zoom(→ Optional[LiveZoomFilter])

Install the Z + wheel font gesture on a running application.

register_text_column(→ Optional[ColumnTextScale])

Let Ctrl + wheel over root size the text inside it.

scaled_font_sheet(→ str)

The font sizes of sheet, and nothing else, multiplied by ratio.

Module Contents

class spacr.qt.live_zoom.ColumnTextScale(parent: PySide6.QtCore.QObject | None = None)[source]

Bases: PySide6.QtCore.QObject

Ctrl + wheel over a module screen’s right-hand column sizes its text.

Holding Ctrl and turning the wheel over any panel in that column makes its text larger or smaller.

ONE SIZE FOR EVERY COLUMN, persisted in spacr.qt.preferences.get_runtime_text_scale(). It multiplies the size the rest of the interface has, so the whole-GUI scale (471) and the Z gesture (378) still move the column with everything else.

A STYLE SHEET ON THE COLUMN, NOT setFont. The Z gesture’s setFont is undone by the next repolish (measured: a label set to 20 px is back at its sheet’s 13 px after unpolish/polish), and the column’s widgets repolish every time a card folds or a run starts. scaled_font_sheet() re-declares the inherited sizes on the column itself, which only its descendants see. A widget that sizes its own text from Python and so outranks any sheet – the console’s entries – takes the size through an apply_column_text_scale(scale) method instead.

AN APPLICATION FILTER, because the console and every scroll area in the column accept the wheel before a parent could see it. It yields to:

  • the Z gesture while Z is held – Z + wheel is 378’s;

  • any widget between the pointer and the column that handles the wheel in Python – the plaque and live-preview canvases zoom on Ctrl + wheel and a figure canvas has its own scroll – which keeps the gesture to the text panels;

  • everything outside a registered column.

Ctrl+0 with the pointer over a column whose text is not at 100 % puts it back; at 100 % the key is left to Go home, which it otherwise is.

Create the filter with the stored size and no columns yet.

eventFilter(watched, event)[source]

Take Ctrl + wheel and Ctrl+0 over a column; restyle on a change.

Parameters:
  • watched – the object the event is for.

  • event – the event.

Returns:

True when the event was the gesture’s.

register(root) → None[source]

Make root a column whose text Ctrl + wheel sizes.

Parameters:

root – the container; everything inside it follows.

reset() → float[source]

Put the columns’ text back to the interface’s size, now.

restyle(root) → bool[source]

Re-declare root’s inherited sizes at the present scale.

Idempotent: nothing is set unless the scale or an inherited sheet changed since the last time, which is what lets this run on every StyleChange the column receives – setting the column’s own sheet sends it one. A column never scaled wears no sheet of its own; one that was scaled keeps declaring its sizes at 100 %, because taking a font rule away does not give Qt’s widgets their old font back (measured: 23 of the Measure column’s 58 widgets kept the larger size when the sheet was emptied).

Parameters:

root – a registered column.

Returns:

whether the column’s sheet was replaced.

restyle_all() → int[source]

Restyle every column; returns how many sheets were replaced.

root_of(widget)[source]

The registered column widget is inside, or None.

Parameters:

widget – any object; only a widget can be inside a column.

roots() → list[source]

The registered columns that are still alive.

scale() → float[source]

The columns’ text size, 1.0 for the interface’s own.

set_scale(scale: float, *, remember: bool = True) → float[source]

Give every column the text size scale, within its bounds.

Parameters:
  • scale – 1.0 for the interface’s own size.

  • remember – store it, once the wheel is still.

Returns:

the size applied.

class spacr.qt.live_zoom.LiveZoomFilter(parent: PySide6.QtCore.QObject | None = None)[source]

Bases: PySide6.QtCore.QObject

Application-wide filter that turns Z + wheel into a live font scale.

Installed on the QApplication because the gesture has to work on every screen – a per-screen handler works in some places and not others, which is the complaint that produced 315’s warning about application filters. That warning is about COST, so the body is two integer comparisons for an event it does not want, and the wheel branch is not even reached unless Z is down. Measured at 1.2 us per uninteresting event on this machine, which is the Python call itself rather than anything this does inside it.

Create the filter that scales the interface while the key is held.

The starting scale and the widgets’ original fonts are recorded so the gesture can be undone exactly, including which of them carried a font of their own rather than inheriting one – restoring an inherited font as an explicit one would pin it against every later theme change.

Parameters:

parent – parent object, or None.

eventFilter(watched, event)[source]

Watch the widgets this filter is installed on.

Parameters:
  • watched – the object the event is for.

  • event – the event.

Returns:

True to stop the event going further.

settle(released: bool = True) → None[source]

End the gesture: persist the scale and let the spacing catch up.

THE EXPENSIVE HALF, ON PURPOSE. Everything scaled_px() pins is rebuilt here, in one 587 ms step, rather than twenty times a second while the wheel turns – which is the compromise chosen over a gesture that stutters. Called when the wheel has been still for _SETTLE_MS, when Z comes up, and when the window loses focus with Z still down.

THE ICONS CATCH UP HERE TOO, and here only. They ride inside apply_preferences_to_app rather than being resized per notch, because the condition on this gesture was that it be fast without lag and the live half is the half that has to answer for it. The sweep is 5.5 ms on 2,122 widgets, so it COULD have gone in the live half – it is here because the icons would then have grown inside spacing that had not, which is the mismatch that made the icons look wrong in the first place.

The QSettings write happens here too, for the same reason: twenty writes a second to a settings file is not free.

Parameters:

released – whether Z is now up. False when the wheel merely went quiet: the user is still holding the key and may keep scrolling, and disarming under them would make the second half of one gesture scroll the list. The next notch simply starts a fresh gesture from the scale this one just saved.

spacr.qt.live_zoom.font_size_rules(sheet: str) → list[source]

Every (selector, size, unit) in sheet that sets a font size.

Parameters:

sheet – Qt style sheet text.

spacr.qt.live_zoom.install_column_text_scale(app=None) → ColumnTextScale | None[source]

Install the Ctrl + wheel column gesture on the application, once.

Parameters:

app – optional QApplication; falls back to the running instance.

Returns:

the filter, or None when there is no application to hold it.

spacr.qt.live_zoom.install_live_zoom(app=None) → LiveZoomFilter | None[source]

Install the Z + wheel font gesture on a running application.

Idempotent: the filter is retained on the application object, so a second call returns the one already installed rather than stacking a second filter onto every event in the process.

Parameters:

app – optional QApplication; falls back to the running instance.

Returns:

the filter, or None when there is no application to hold it.

spacr.qt.live_zoom.register_text_column(root) → ColumnTextScale | None[source]

Let Ctrl + wheel over root size the text inside it.

Parameters:

root – a module screen’s right-hand column.

Returns:

the filter, or None without an application.

spacr.qt.live_zoom.scaled_font_sheet(sheet: str, ratio: float) → str[source]

The font sizes of sheet, and nothing else, multiplied by ratio.

Set on a container, this re-declares every size the window’s sheet gives the widgets inside it: a container’s sheet beats an ancestor’s whatever the specificity, so the whole hierarchy of sizes – a 15 px card title over 12 px captions – moves together and stays in proportion, and widgets built later inside the container follow without being visited.

Parameters:
  • sheet – the sheet the container inherits.

  • ratio – the multiplier; 1.0 gives the same sizes back.