Skip to main content

Responsive width adaptation for Qt: tier/priority label-collapse bars, width-class container, column-collapse header. PySide6/2, PyQt6/5.

Project description

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:

  1. PROMOTE: restore wider label tiers while they fit
  2. compress spacing (max_spacing -> min_spacing)
  3. DEMOTE: the widest button with a shorter tier drops one tier (repeat)
  4. 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 via set_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 as 1.1k / 2.3M above 999, keeping label width bounded as counts grow. set_count re-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

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

qtflex-0.1.1.tar.gz (8.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

qtflex-0.1.1-py3-none-any.whl (10.7 kB view details)

Uploaded Python 3

File details

Details for the file qtflex-0.1.1.tar.gz.

File metadata

  • Download URL: qtflex-0.1.1.tar.gz
  • Upload date:
  • Size: 8.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for qtflex-0.1.1.tar.gz
Algorithm Hash digest
SHA256 94ea08ab9c9118c9d83e162243f7f71c607c22313295202f67ba1bace2d3473d
MD5 dbec48bcc8949ab0299577c65351c076
BLAKE2b-256 58cac305bed170d64e4bac797dfc95feca40d32288a87e47c0030132dca13d7d

See more details on using hashes here.

Provenance

The following attestation bundles were made for qtflex-0.1.1.tar.gz:

Publisher: publish.yml on zPirx/qtflex

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file qtflex-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: qtflex-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 10.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for qtflex-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 1b781b3edfdf5188a4b9c37d4771b1765f426334134fa0a47f4ed2b958226cd6
MD5 9239ae03079814888052e32c785c54e6
BLAKE2b-256 db001b3a5bd25806ded14711efcc0c907e8329827af15cba5f554a35200332ae

See more details on using hashes here.

Provenance

The following attestation bundles were made for qtflex-0.1.1-py3-none-any.whl:

Publisher: publish.yml on zPirx/qtflex

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page