feat(2.0): pane handles — named panes, whole-render state, pane-scoped events & export [D145]–[D150] - #6
Merged
Merged
Conversation
…ne protocol capture_state now covers every pane (was: pane 0 only on pg/mpl, nothing on composite) and restore matches by pane label. RenderHandle grows panes() — one PaneHandle facade per surface (PgPane / MplPane / _WebPane; composite flattens children) — and the base class derives whole-render capture/restore from it, written once in core. ViewState stays the per-surface record; restore_state(ViewState) remains a first-pane shorthand. Also fixes the nested-grid crash: a grid containing a Layout child now routes to the LayoutHost (backend cell renderers take Element/Overlay only). Design: design/pane-handles.md (S1) + design/layout-panes-plan.md.
…aic, grid(mapping), with_pane Layout children can be named: mapping children (grid/tabs/splitter; tab captions default to pane labels), retained mosaic labels incl. the subplot_mosaic list form with multi-char labels, or explicit labels=. layout[label] looks up (nested too); with_pane swaps immutably; Layout.grid(cells=) places panes programmatically with mosaic-grade overlap/span validation. flat_pane_labels() is the one source of pane identity — backends and the composite host now key panes (and therefore LayoutState capture/restore) by the given labels, so state survives root swaps that reorder panes. Design: design/pane-handles.md (S2).
…tviz' Public downstream surface for individual panes: set_range/autorange/select/ capture/restore per surface, plus .native (the [D53] escape valve per pane) and .elements. Facades wrap the current render and go dead with it — pane.alive guards, DisposedError (new, qtviz.errors) on use-after-rebuild instead of touching freed widgets. view.root pairs with Layout.with_pane for the declarative per-pane update. Docs: api.md pane section (PaneHandle/ LayoutState as [D135] return types, not __all__). Design: design/pane-handles.md (S3).
…), label shim Every event carries pane (kw-only, None-defaulted) — stamped by a per-surface PaneBus proxy in pg/mpl (so every element emit site inherits it with no signature changes), at the handle in webengine, and mapped local→flat by the composite host's delivery shim for hosted/third-party children. RangeEvent/TapEvent source_id becomes the pane label (was a per-render uuid — never exposed, nothing could depend on it). view.on grows pane=, composable with source=. Design: design/pane-handles.md (S4).
pyqtgraph: ImageExporter on the PlotItem subtree (png, one cell of a shared scene — spiked). matplotlib: figure cropped to the axes' tight bbox (png/svg/pdf, dpi/transparent honored; geometric-crop caveat documented). webengine: delegates to the per-figure export. Dead panes raise DisposedError; formats follow each backend's export capabilities. Design: design/pane-handles.md (S5).
…ow + cross-backend linking [D146]: link_x/link_y widen to bool | 'col' | 'row'. Groups come from the same cells that decide grid shape (core link_groups, union-find over spans — a spanning pane merges every column/row it covers, the subplot_mosaic rule). pyqtgraph links natively per group (setXLink to the group leader); matplotlib shares axes per group (sharex/sharey at add_subplot). col/row on a non-grid kind raises at construction. [D151]: linking now crosses host panes (mixed backends, splitter/tabs, nested grids) — previously warned-and-ignored. A _LinkController on the composite handle propagates RangeEvents within link groups via pane.set_range, echo-guarded by a reentrancy flag (sync emits) and a value guard (async webengine round-trips converge instead of ping-ponging). Nested-layout panes are excluded from cross-pane groups with a warning; the host honors link_x/link_y (_HOST_LAYOUT_HONORED). Design: design/pane-handles.md status note; layout-panes-plan.md [D146]/[D151].
Owner
Author
|
Added the sharing track (34af96d) — the arc's final piece:
+12 tests ( |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this is
The downstream-use half of the plot-organization arc: every surface of a render (grid cell, tab, splitter pane — or the whole plot) becomes an addressable pane with a stable label, and that label carries through state capture/restore, event scoping, the native escape hatch, and export. Design docs:
design/layout-panes-plan.md(review + [D145]–[D151]) anddesign/pane-handles.md(detailed design; §5 status). One commit per step S1–S5.Fixes (S1, bug tier)
_plots[0]/_surfaces[0]); composites captured nothing. Zoom pane 2, switch theme/backend → snap back to autorange. Nowcapture_state()returns aLayoutStatecovering every pane, restored by label.AttributeError: 'Layout' has no attribute 'lower') — now routed through the LayoutHost.Architecture
_panes()factory (onePaneHandlefacade per surface); whole-render capture/restore is written once in core on top of it.ViewStateunchanged as the per-surface record;LayoutStateadds first-pane passthroughs so single-surface reads keep working.PaneHandle— the "Axes of qtviz" ([D147]): interaction-side verbs only (set_range/autorange/select/capture/restore/.native/.elements/.export). Describe-side config stays on the node (.opts(),with_pane+set_root) — one-way data flow preserved. Facades are owned by their render: a pane kept across a rebuild goes dead (alive) and raises the newqtviz.errors.DisposedError.Layout.labels(value-hashed) +flat_pane_labels()as the single source of truth — shared by backends, the composite host, state keys, and event stamping.Event.pane(kw-only), a per-surfacePaneBusproxy in pg/mpl (element emit sites inherit pane identity with zero signature changes), and a local→flat label shim on the composite bus — third-party single-surface backends get pane-correct events with no edits ([D125] holds).Layoutchild now hosts per-pane;can_host("grid")means a flat grid (all the cell renderers ever supported).Creation-side sugar ([D145]/[D148])
Mapping children (
Layout.grid({"price": p, ...}), tabs/splitter too), retained mosaic labels + thesubplot_mosaiclist form with multi-char labels, explicitcells=with mosaic-grade overlap validation,layout[label],Layout.with_pane.Behavior changes (CHANGELOG'd)
capture_state()returnsLayoutState(wasViewState);restore_stateaccepts both (bareViewState= first-pane shorthand).RangeEvent/TapEvent.source_idis the pane label (was a per-render uuid — never exposed, nothing could depend on it).FROZEN_2_0is untouched —PaneHandle/LayoutStatefollow the [D135] return-type convention; docs get a Panes section inapi.md.Testing
+53 tests across
test_pane_state.py/test_pane_labels.py/test_pane_handle.py/test_pane_events.py(tier-1 core + tier-2 parametrized over pyqtgraph/matplotlib, composite/splitter/nested cases; webengine display-gated as usual). Full suite 1037 passed; ruff/mypy/mkdocs --strict clean. Both export approaches (pgImageExporter(plotItem), mpl tightbbox crop) were spiked before landing.Not in this PR
The sharing track — [D146]
link_x="col"/"row"+ mixed-pane honor-or-warn, then the [D151] cross-backend linking gate (event-loop/throttle risk; own go/no-go).🤖 Generated with Claude Code