qtflex - responsive width adaptation for Qt
Qt widgets don't adapt their labels to available width. qtflex adds a small, cooperating set: a container that measures and broadcasts a width CLASS, and consumers (action bars, tree headers) that adapt to it - CSS-media-query thinking, but content-driven instead of pixel breakpoints.
Bindings: PySide6 (tested live), PyQt6 (import-smoked), PySide2/PyQt5 (supported by the shim, untested). Zero dependencies beyond your Qt binding.
ResponsiveContainer
A width-broadcasting parent. Emits width_class_changed(str) with
'full' | 'compact' | 'min', classified against the CONTENT (member children's
natural width vs available), not magic px:
- full: everything fits at natural width
- compact: fits only when children shrink (>= 60% of natural)
- min: the floor
API: add(w, member=True) (member widths define the breakpoints),
add_layout(l), add_stretch(), layout_box(), width_class property.
Implementation note: a Qt container cannot shrink below its children's
minimumSizeHint, so the container uses Ignored horizontal size policy and
classifies against the resize EVENT width, never self.width().
ResponsiveBar
An action bar that degrades gracefully as width shrinks, in order:
- PROMOTE: restore wider label tiers while they fit
- compress spacing (max_spacing -> min_spacing)
- DEMOTE: the widest button with a shorter tier drops one tier (repeat)
- SPILL: at the tier floor, lowest-priority buttons hide into a
»overflow menu (menu label = shortest tier, click = original handler). The bar is never fully empty - the highest-priority button always stays visible.
API:
add_button(tiers, tooltip=None, on_click=None, style=None, cursor=True, priority=100)- tiers is a list widest->shortest, e.g.["Re-read all ({n})", "Re-read {n}", "RR"];{n}templates update viaset_count(btn, n). Lower priority spills first. Returns the QPushButton.add_widget(w)- fixed pass-through widget (dropdown, LED), never demoted.add_stretch()subscribe_to(container)- drive layout from a ResponsiveContainer; bars sharing one container demote in concert. Without it the bar self-measures via its own resizeEvent.
Constructor: min_spacing=1, max_spacing=6, margins=(5, 0, 5, 5), spill=True, demote="staged", compact_counts=False.
spill=False: buttons stop at their shortest tier, no overflow menu; below the floor they shrink proportionally (elide) rather than paint past the bar's edges.demote="staged"(default): buttons demote one step at a time, ordered by the tier FRACTION the step would reach - unequal tier-list lengths degrade proportionally, no button hits its floor while others sit untouched. Promotion mirrors this: the deepest-demoted (by fraction) recovers first, with a hysteresis band so boundary widths never flap. ("uniform"reserved, not yet implemented.)compact_counts=True:{n}counts render as1.1k/2.3Mabove 999, keeping label width bounded as counts grow.set_countre-runs the layout, so a growing number can trigger demotion without a resize.
Tier state is tracked internally (never re-derived from button text) - count changes cannot desync the tier machine.
Driving the bar from a parent widget instead of a ResponsiveContainer:
set bar._driven = True and call bar._apply_layout(bar.width()) from
the parent's resizeEvent. Exactly ONE width source per bar - a bar fed
by two sources (own resizeEvent + a parent driver) will oscillate.
ResponsiveHeader
Column collapse for QTreeView/QTreeWidget, same priority vocabulary.
ResponsiveHeader(tree, {2: 10, 3: 20}) - lower priority hides first;
column 0 is never hidden; unlisted columns default to priority 100.
apply(width_class): full = all shown; compact = hide 1; min = hide half.
subscribe_to(container) joins the same broadcast.
Wiring
from qtflex import ResponsiveBar, ResponsiveContainer, ResponsiveHeader
cont = ResponsiveContainer()
bar = ResponsiveBar()
cont.add(bar)
bar.subscribe_to(cont)
hdr = ResponsiveHeader(tree, {2: 10, 3: 20})
hdr.subscribe_to(cont)
qtcompat
Internal binding shim (PySide6 > PySide2 > PyQt6 > PyQt5, first importable
wins); Signal normalized across PySide/PyQt. qtflex.QT_BINDING names the
active binding.
License
MIT
FloorCap - stop child widgets from hard-flooring your window
Qt sums every child's minimumSizeHint into the window's hard minimum; a
multi-pane panel can silently block window-edge resize at ~window width.
setSizePolicy(Ignored) does NOT shrink the hint - only an override does.
from qtflex import FloorCap, floor_cap
class MyPanel(FloorCap, QWidget): # mixin form
FLOOR_W = 200
Capped = floor_cap(SomePanel, w=200) # wrapper form
Metadata
Release files for qtflex 0.1.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| qtflex-0.1.2.tar.gz | 9.3 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| qtflex-0.1.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 20.7 kB
Release files / qtflex-0.1.2.tar.gz
| Download URL | qtflex-0.1.2.tar.gz |
|---|---|
| Size | 9.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
fb768926bd9ef7f0660136bf68123971a901dda6e024f8393a29eda4b582ce6b
|
|
BLAKE2b-256 checksum How to use checksums |
6a3c705649267c88ae08dccd09fa90b3b1edb8f8bea3dc8b34d5f8a3656adf4f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 4, 2026.
Transparency logRelease files / qtflex-0.1.2-py3-none-any.whl
| Download URL | qtflex-0.1.2-py3-none-any.whl |
|---|---|
| Size | 11.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2a2cf571e2def8ff5d323e5eb428eb58d8523ac9dc383664fd830a1481f87535
|
|
BLAKE2b-256 checksum How to use checksums |
679a20c2782b3145cdc029a8f03b493abba9bc9d8259b192f264afe3b6261bc6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 4, 2026.
Transparency logSecurity for qtflex 0.1.2
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for qtflex-0.1.2.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 4, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.