Metadata-Version: 2.4
Name: qtflex
Version: 0.1.0
Summary: Responsive width adaptation for Qt: tier/priority label-collapse bars, width-class container, column-collapse header. PySide6/2, PyQt6/5.
License: MIT
Project-URL: Repository, https://github.com/zPirx/qtflex
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# 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)`.

## 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
