Link View: every link of a component, drawn around it
Right-clicking a component now offers Link View alongside Properties, from the wiresheet and from the navigation tree alike. It takes over the editor pane the same way and answers one question -- what is wired to this component, and where does it go. The chosen component sits in the middle wearing the stroke a selected box wears on the sheet, with what links into it on the left and what it links out to on the right, so every arrow points the way the signal travels. Pale yellow ground and no grid: nothing here can be positioned, because the layout is derived from the links every time it is drawn and no part of it is stored in the file. Read-only throughout -- no drag, no add, no delete -- so a component's wiring can be read without the risk of rearranging it. Every line is grey, since this is not a wiresheet and a wire-blue line would invite the reader to treat it as one. A link on the same sheet is solid; one that crosses is dashed, keeps the grey knob that sheet paints on the slot, and has the far component drawn in with its sheet named beneath it. A reference the document cannot resolve gets a placeholder naming the path, because a link the view silently dropped would be the one thing a reader could not find out from here. Clicking a wire or a component follows the link: it opens the sheet of the end that is not the component the view is about, with the link selected in red. A link that stays on one sheet therefore goes back to that sheet where both ends are drawn, and one that crosses opens the other sheet -- the end the view could only show as a knob. Supporting changes: - draw_component_box is factored out of render_components, so the sheet and this view paint a component with the same code and a box cannot read differently between them. draw_knob takes its canvas for the same reason. - The editor pane's hand-over is now shared: _take_editor_pane, _give_editor_pane_back and close_editor_panel replace the Properties-only versions, so "you have not saved that" is still asked once, in one place, whichever panel has the pane. - Fixes the jump to a link's other sheet, which arrived with the link not selected. An after_idle callback is not late enough -- ttk dispatches its own selection event later than one of those, so the repaint wiped the selection. The link is left in _pending_link_selection for on_tree_select to honour once it has painted. - Each gap is sized to hold a lane per wire crossing it, and lanes are handed out nearest-target-first, so a fan of a dozen links nests instead of crowding a dozen verticals into a 30px band. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -50,13 +50,15 @@ not inside the window. Tk always hands it to the system menu bar.
|
||||
| `undo.py` | Undo/redo stack of reversible DOM commands | no |
|
||||
| `wiresheet.py` | Grid, component boxes, link routing and painting | yes |
|
||||
| `properties.py` | The Properties view: every slot of one component, read and written | yes |
|
||||
| `link_view.py` | The Link View: every link touching one component, drawn around it | yes |
|
||||
| `sax_file.py` | Open/save, navigation tree, cascading delete | yes |
|
||||
| `main.py` | Widgets, menus, event handlers, dialogs, main loop | yes |
|
||||
| `run.py` | Entry point | yes |
|
||||
|
||||
Imports form a DAG — `sax_dom`/`schema` depend on nothing UI-shaped, `wiresheet` and
|
||||
`sax_file` build on them, `properties` builds on `schema` and `undo`, and `main` wires it
|
||||
together. Event handlers live in `main`, which is what keeps the graph acyclic.
|
||||
`sax_file` build on them, `properties` builds on `schema` and `undo`, `link_view` builds
|
||||
on `wiresheet`, and `main` wires it together. Event handlers live in `main`, which is what
|
||||
keeps the graph acyclic.
|
||||
|
||||
### Shared state
|
||||
|
||||
@@ -133,23 +135,36 @@ walk cannot rewrite the trail it is following.
|
||||
### The component popup
|
||||
|
||||
Right-clicking a component offers the same actions from the wiresheet and from the
|
||||
navigation tree -- Properties, Delete Component, Link Mark, and Link From / Link To naming
|
||||
the marked component once one is marked. `fill_component_popup` builds both, and its entries close
|
||||
over the component rather than reading a selection back, which is what lets each popup act
|
||||
on the row or box that was actually clicked. The tree's root row keeps its own popup.
|
||||
navigation tree -- Properties, Link View, Delete Component, Link Mark, and Link From /
|
||||
Link To naming the marked component once one is marked. `fill_component_popup` builds
|
||||
both, and its entries close over the component rather than reading a selection back,
|
||||
which is what lets each popup act on the row or box that was actually clicked. The tree's
|
||||
root row keeps its own popup.
|
||||
|
||||
### The editor pane
|
||||
|
||||
The pane to the right of the tree holds one of three things: the **wiresheet**, the
|
||||
**Properties view** or the **Link View**. The latter two take the pane over rather than
|
||||
floating above it, so a sheet and a table of one component's slots are never both half in
|
||||
view. The canvas is unpacked, never destroyed, so it keeps its items and its scroll
|
||||
position and **Close** puts it back exactly as it was. It is still painted while it is
|
||||
hidden, which costs nothing and means there is no catching up to do when it returns.
|
||||
|
||||
`_take_editor_pane` and `_give_editor_pane_back` in `main` are the hand-over, and
|
||||
`close_editor_panel` is the single way out of whichever panel has the pane: "you have not
|
||||
saved that" is therefore asked once, in one place, whether the user pressed Close, picked
|
||||
another sheet, walked the history or opened a different component. Only the Properties
|
||||
view has anything to ask about.
|
||||
|
||||
Choosing a node in the tree means "show me that sheet", so it stands a panel down rather
|
||||
than leaving the selection looking like it did nothing. So does deleting the component one
|
||||
is showing, from anywhere -- every delete in the UI goes through `delete_component`,
|
||||
because `sax_file` does its own view refresh rather than going through `refresh_all_views`.
|
||||
|
||||
### The Properties view
|
||||
|
||||
**Properties**, from either component popup, **takes over the editor pane** -- the sheet
|
||||
and the slot table are the same piece of screen, so only one of them is ever the thing
|
||||
being looked at. The canvas is unpacked, never destroyed, so it keeps its items and its
|
||||
scroll position and **Close** puts it back exactly as it was. It is still painted while it
|
||||
is hidden, which costs nothing and means there is no catching up to do when it returns.
|
||||
|
||||
Choosing a node in the tree means "show me that sheet", so it stands the view down rather
|
||||
than leaving the selection looking like it did nothing. So does deleting the component it
|
||||
is showing, from anywhere -- every delete in the UI goes through `delete_component`,
|
||||
because `sax_file` does its own view refresh rather than going through `refresh_all_views`.
|
||||
**Properties**, from either component popup, is the one place a slot's value is meant to
|
||||
be changed, and the only view here that writes to the document.
|
||||
|
||||
It lists every slot the manifest declares -- bar one kind, see **Hidden slots** below --
|
||||
in manifest order, each row numbered with the same slot id the wiresheet box uses. The
|
||||
@@ -348,6 +363,46 @@ an `Entry`, a `TCombobox`, a `Text`. A backspace aimed at a text field must neve
|
||||
component. Save still checks the component is in the document before writing, since an
|
||||
undo can take it out from under the view.
|
||||
|
||||
### The Link View
|
||||
|
||||
**Link View**, from either component popup, answers one question -- what is wired to this
|
||||
component, and where does it go -- and takes over the editor pane to do it. It is
|
||||
read-only: no drag, no add, no delete, nothing that writes to the document, so a
|
||||
component's wiring can be read without the risk of rearranging it.
|
||||
|
||||
The chosen component is drawn in the middle, with the components linking **into** it on
|
||||
the left and the ones it links **out to** on the right, so every arrow points the way the
|
||||
signal travels and the picture reads left to right the way a wiresheet does. The chosen
|
||||
box wears the same dark stroke a selected box wears on the sheet. The ground is **pale
|
||||
yellow** and carries **no grid**: nothing here can be positioned, because the layout is
|
||||
derived from the links every time it is drawn and no part of it is stored in the file
|
||||
(`meta` records a component's place on its own sheet, which says nothing about where it
|
||||
belongs relative to another one).
|
||||
|
||||
Every line is **grey**, because this is not a wiresheet and a wire-blue line would invite
|
||||
the reader to treat it as one:
|
||||
|
||||
| Link | Drawn as |
|
||||
|---|---|
|
||||
| link on the same wiresheet | solid grey line |
|
||||
| link to another wiresheet | dashed grey line, plus the grey knob that sheet paints on the slot, with the far component drawn in and its sheet named beneath it |
|
||||
|
||||
Boxes come from `wiresheet.draw_component_box` -- the same function the sheet paints
|
||||
with -- so a component reads exactly as it does on its own sheet and a wire lands on the
|
||||
same slot row. A component wired twice on one side is one box with two wires, as it would
|
||||
be on a sheet; a reference the document cannot resolve gets a dashed placeholder naming
|
||||
the path, because a link the view silently dropped would be the one thing a reader could
|
||||
not find out from here.
|
||||
|
||||
Each gap is sized by `column_gap` to hold a lane for every wire crossing it, and lanes are
|
||||
handed out nearest-target-first, so a wire reaching a distant box turns outside one
|
||||
reaching a near box instead of across it. A dozen links widen the gap rather than
|
||||
crowding a dozen verticals into a 30px band.
|
||||
|
||||
Hovering a wire darkens it and names the link in the footer; clicking a wire or a
|
||||
component follows it. `collect_links` and `sheet_display_name` are pure DOM work, so what
|
||||
the view reads out of a document can be exercised without a window.
|
||||
|
||||
### Following a link to its other sheet
|
||||
|
||||
Right-clicking a wire or a knob offers **Go to Link Wire Sheet "<name>"** alongside Delete
|
||||
@@ -355,21 +410,32 @@ Link, naming the sheet the far end of that link is on -- the end a grey knob sta
|
||||
for. The entry is left off a link with both ends on the open sheet, which has nowhere to
|
||||
go. `find_component_by_path` in `sax_dom` turns a `<link>`'s from/to reference back into
|
||||
the component it points at, the inverse of `get_component_path`, and the sheet is that
|
||||
component's parent; a parent of `<app>` is the file's root node, which the popup names
|
||||
after the file.
|
||||
component's parent; a parent of `<app>` is the file's root node, which
|
||||
`link_view.sheet_display_name` names after the file.
|
||||
|
||||
The jump selects the same link again once it arrives. It has to be found by its element
|
||||
rather than by the tag it was picked on, because a repaint hands out fresh wire tags, and
|
||||
it has to wait for that repaint: ttk queues its selection event rather than dispatching
|
||||
it, so the sheet is not painted at the point the tree node is selected -- and painting a
|
||||
sheet clears whatever link was selected. The selection is therefore made from an
|
||||
`after_idle` callback, and the link is scrolled to the middle of the view. Arriving is an
|
||||
ordinary sheet opening, so **Back** returns to the sheet the link was picked on.
|
||||
Clicking a link in the Link View follows it the same way, through the same
|
||||
`go_to_sheet_with_link`: it opens the sheet of the end that is **not** the component the
|
||||
view is about, so a link that stays on one sheet goes back to that sheet where both ends
|
||||
are drawn, and one that crosses opens the other sheet -- the end the view could only show
|
||||
as a knob.
|
||||
|
||||
Either way the link is selected again once the sheet arrives. It has to be found by its
|
||||
element rather than by the tag it was picked on, because a repaint hands out fresh wire
|
||||
tags -- and it has to be picked out **after** that repaint, since painting a sheet clears
|
||||
whatever link was selected. An `after_idle` callback is not late enough: ttk dispatches
|
||||
its own selection event later than one of those, so a link selected from an idle callback
|
||||
was wiped by the repaint that followed it. The link is therefore left in
|
||||
`_pending_link_selection` for `on_tree_select` to honour once it has painted, and that
|
||||
handler reads and clears the request first thing -- so a selection that turns out not to
|
||||
paint a sheet drops it rather than leaving it to be applied to some later one. The link is
|
||||
then scrolled to the middle of the view. Arriving is an ordinary sheet opening, so **Back**
|
||||
returns to the sheet the link was followed from.
|
||||
|
||||
### Testing without a window
|
||||
|
||||
`app_state`, `sax_dom`, `schema`, `undo` and `wiresheet` import with no display, so the
|
||||
rules can be exercised directly:
|
||||
rules can be exercised directly. `link_view.collect_links` is pure DOM work too, and reads
|
||||
a component's links without one:
|
||||
|
||||
```python
|
||||
import xml.etree.ElementTree as ET
|
||||
@@ -500,3 +566,5 @@ And two things worth knowing about the manifests themselves rather than the code
|
||||
which is `x1 + radius` rather than `x1`, so snapping is offset by the corner radius.
|
||||
- Slot values are edited from the Properties view only; a wiresheet box row is still
|
||||
display-only.
|
||||
- The Link View stacks each side in one column, so a component with a dozen peers makes a
|
||||
picture taller than the pane. It scrolls, but it does not wrap into a second column.
|
||||
|
||||
Reference in New Issue
Block a user