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:
+105
-85
@@ -577,15 +577,17 @@ def resolve_slot_anchor(comp_tag, slot_name):
|
||||
return box, box[1] + offset
|
||||
|
||||
|
||||
def draw_knob(x, y, on_sheet, off_sheet, extra_tags=()):
|
||||
def draw_knob(canvas, x, y, on_sheet, off_sheet, extra_tags=()):
|
||||
"""Marks a slot whose links cannot be shown as a visible wire.
|
||||
|
||||
Painted in the wire colour when the far end is on this sheet but unreachable, and
|
||||
grey when it is on another wiresheet, so the two cases read differently at a
|
||||
glance. A slot carrying both is split down the middle, wire colour on the left
|
||||
and grey on the right.
|
||||
|
||||
The canvas is passed in rather than read off `app_state`: the Link View draws the
|
||||
same grey marker on a canvas of its own, and a knob is a knob wherever it is shown.
|
||||
"""
|
||||
canvas = app_state.canvas
|
||||
tags = ("link", "link_knob") + tuple(extra_tags)
|
||||
box = (x - KNOB_RADIUS, y - KNOB_RADIUS, x + KNOB_RADIUS, y + KNOB_RADIUS)
|
||||
|
||||
@@ -737,7 +739,8 @@ def draw_links():
|
||||
)
|
||||
|
||||
for (x, y), entry in knobs.items():
|
||||
draw_knob(x, y, entry["on_sheet"], entry["off_sheet"], tuple(entry["tags"]))
|
||||
draw_knob(canvas, x, y, entry["on_sheet"], entry["off_sheet"],
|
||||
tuple(entry["tags"]))
|
||||
|
||||
# Wires sit behind the boxes so they never paint over a neighbouring component.
|
||||
# Nothing should actually be back there now, since a wire that cannot avoid a box is
|
||||
@@ -832,6 +835,96 @@ def deselect_link():
|
||||
|
||||
# --- Component painting ---
|
||||
|
||||
def draw_component_box(canvas, comp, x, y, fonts, linked_slot_index, tags,
|
||||
outline_tags=()):
|
||||
"""Paints one component box with its top-left corner at (x, y).
|
||||
|
||||
Returns (width, height, slot_offsets), where slot_offsets maps a slot name to the y
|
||||
offset of its row's centre from the box top -- which is what a wire anchors to.
|
||||
|
||||
Every caller gets the same box. The wiresheet paints these at their meta positions
|
||||
and the Link View arranges them around one component; a box that read differently
|
||||
between the two would suggest the user was looking at something else. `tags` goes on
|
||||
every item, so the whole box is moved and picked as one, and the sheet adds
|
||||
`outline_tags` to the outline alone -- the item it strokes to show a selection.
|
||||
"""
|
||||
name_font, type_font, slot_font = fonts
|
||||
|
||||
(box_width, box_height, slot_data_rows,
|
||||
full_display_name, display_type) = measure_box(comp, fonts, linked_slot_index)
|
||||
|
||||
# Filled in as the rows are drawn below; keyed by slot name for link anchoring.
|
||||
slot_offsets = {}
|
||||
|
||||
create_round_rectangle(
|
||||
canvas, x, y, x + box_width, y + box_height,
|
||||
radius=5, fill="white", outline="#7f9db9", width=2,
|
||||
tags=tuple(tags) + tuple(outline_tags)
|
||||
)
|
||||
# Draw Header bar
|
||||
canvas.create_rectangle(
|
||||
x + 1, y + 1, x + box_width - 1, y + HEADER_HEIGHT,
|
||||
fill="#dce6f2", outline="", tags=tags
|
||||
)
|
||||
canvas.create_line(
|
||||
x, y + HEADER_HEIGHT, x + box_width, y + HEADER_HEIGHT,
|
||||
fill="#7f9db9", tags=tags
|
||||
)
|
||||
|
||||
# Header textual labels, ellipsised to leave room for the indicator square
|
||||
header_text_width = box_width - 26
|
||||
canvas.create_text(
|
||||
x + 5, y + 8, anchor="w",
|
||||
text=fit_text(full_display_name, name_font, header_text_width),
|
||||
font=name_font, fill="black", tags=tags
|
||||
)
|
||||
canvas.create_text(
|
||||
x + 5, y + 19, anchor="w",
|
||||
text=fit_text(display_type, type_font, header_text_width),
|
||||
font=type_font, fill="#555555", tags=tags
|
||||
)
|
||||
|
||||
# Quick access indicator icon background slot placeholder
|
||||
canvas.create_rectangle(
|
||||
x + box_width - 17, y + 6, x + box_width - 5, y + 18,
|
||||
fill="#ffe699", outline="#b4c6e7", tags=tags
|
||||
)
|
||||
|
||||
# Render dynamic slots rows
|
||||
current_row_y = y + HEADER_HEIGHT
|
||||
for idx, (raw_name, s_name, val_str) in enumerate(slot_data_rows):
|
||||
row_bg = "#eaeaea" if idx % 2 == 0 else "#dfdfdf"
|
||||
slot_offsets[raw_name] = (current_row_y + (SLOT_ROW_HEIGHT // 2)) - y
|
||||
|
||||
canvas.create_rectangle(
|
||||
x + 1, current_row_y, x + box_width - 1, current_row_y + SLOT_ROW_HEIGHT,
|
||||
fill=row_bg, outline="", tags=tags
|
||||
)
|
||||
|
||||
# Value keeps its full text; the name yields space to it when the row is tight
|
||||
value_width = slot_font.measure(val_str)
|
||||
label_space = box_width - 16 - value_width
|
||||
|
||||
canvas.create_text(
|
||||
x + 6, current_row_y + (SLOT_ROW_HEIGHT // 2), anchor="w",
|
||||
text=fit_text(s_name, slot_font, label_space),
|
||||
font=slot_font, fill="black", tags=tags
|
||||
)
|
||||
|
||||
canvas.create_text(
|
||||
x + box_width - 6, current_row_y + (SLOT_ROW_HEIGHT // 2), anchor="e",
|
||||
text=val_str, font=slot_font, fill="#333333", tags=tags
|
||||
)
|
||||
|
||||
current_row_y += SLOT_ROW_HEIGHT
|
||||
canvas.create_line(
|
||||
x, current_row_y, x + box_width, current_row_y,
|
||||
fill="#b8b8b8", tags=tags
|
||||
)
|
||||
|
||||
return box_width, box_height, slot_offsets
|
||||
|
||||
|
||||
def render_components(components_to_render):
|
||||
"""Handles parsing and visual painting of given component listings on canvas with slot attributes."""
|
||||
canvas = app_state.canvas
|
||||
@@ -857,10 +950,6 @@ def render_components(components_to_render):
|
||||
max_reached_x = 1000
|
||||
max_reached_y = 1000
|
||||
|
||||
header_height = HEADER_HEIGHT
|
||||
slot_row_height = SLOT_ROW_HEIGHT
|
||||
footer_height = FOOTER_HEIGHT
|
||||
|
||||
for comp in components_to_render:
|
||||
meta_prop = comp.find("prop[@name='meta']")
|
||||
meta_str = meta_prop.get("val") if meta_prop is not None else None
|
||||
@@ -869,90 +958,21 @@ def render_components(components_to_render):
|
||||
coord_x = raw_x * meta_scale
|
||||
coord_y = raw_y * meta_scale
|
||||
|
||||
(box_width, box_height, slot_data_rows,
|
||||
full_display_name, display_type) = measure_box(comp, fonts, linked_slot_index)
|
||||
comp_tag = f"comp_group_{id(comp)}"
|
||||
app_state.canvas_comp_map[comp_tag] = comp
|
||||
app_state.canvas_tag_by_element[comp] = comp_tag
|
||||
|
||||
box_width, box_height, slot_offsets = draw_component_box(
|
||||
canvas, comp, coord_x, coord_y, fonts, linked_slot_index,
|
||||
("component", comp_tag), outline_tags=("block_outline",)
|
||||
)
|
||||
app_state.component_slot_offsets[comp_tag] = slot_offsets
|
||||
|
||||
if coord_x + box_width > max_reached_x:
|
||||
max_reached_x = coord_x + box_width
|
||||
if coord_y + box_height > max_reached_y:
|
||||
max_reached_y = coord_y + box_height
|
||||
|
||||
comp_tag = f"comp_group_{id(comp)}"
|
||||
app_state.canvas_comp_map[comp_tag] = comp
|
||||
app_state.canvas_tag_by_element[comp] = comp_tag
|
||||
|
||||
# Filled in as rows are drawn below; keyed by slot name for link anchoring.
|
||||
slot_offsets = {}
|
||||
app_state.component_slot_offsets[comp_tag] = slot_offsets
|
||||
|
||||
create_round_rectangle(
|
||||
canvas, coord_x, coord_y, coord_x + box_width, coord_y + box_height,
|
||||
radius=5, fill="white", outline="#7f9db9", width=2,
|
||||
tags=("component", comp_tag, "block_outline")
|
||||
)
|
||||
# Draw Header bar
|
||||
canvas.create_rectangle(
|
||||
coord_x + 1, coord_y + 1, coord_x + box_width - 1, coord_y + header_height,
|
||||
fill="#dce6f2", outline="", tags=("component", comp_tag)
|
||||
)
|
||||
canvas.create_line(
|
||||
coord_x, coord_y + header_height, coord_x + box_width, coord_y + header_height,
|
||||
fill="#7f9db9", tags=("component", comp_tag)
|
||||
)
|
||||
|
||||
# Header textual labels, ellipsised to leave room for the indicator square
|
||||
header_text_width = box_width - 26
|
||||
canvas.create_text(
|
||||
coord_x + 5, coord_y + 8, anchor="w",
|
||||
text=fit_text(full_display_name, name_font, header_text_width),
|
||||
font=name_font, fill="black",
|
||||
tags=("component", comp_tag)
|
||||
)
|
||||
canvas.create_text(
|
||||
coord_x + 5, coord_y + 19, anchor="w",
|
||||
text=fit_text(display_type, type_font, header_text_width),
|
||||
font=type_font, fill="#555555",
|
||||
tags=("component", comp_tag)
|
||||
)
|
||||
|
||||
# Quick access indicator icon background slot placeholder
|
||||
canvas.create_rectangle(
|
||||
coord_x + box_width - 17, coord_y + 6, coord_x + box_width - 5, coord_y + 18,
|
||||
fill="#ffe699", outline="#b4c6e7", tags=("component", comp_tag)
|
||||
)
|
||||
|
||||
# Render dynamic slots rows
|
||||
current_row_y = coord_y + header_height
|
||||
for idx, (raw_name, s_name, val_str) in enumerate(slot_data_rows):
|
||||
row_bg = "#eaeaea" if idx % 2 == 0 else "#dfdfdf"
|
||||
slot_offsets[raw_name] = (current_row_y + (slot_row_height // 2)) - coord_y
|
||||
|
||||
canvas.create_rectangle(
|
||||
coord_x + 1, current_row_y, coord_x + box_width - 1, current_row_y + slot_row_height,
|
||||
fill=row_bg, outline="", tags=("component", comp_tag)
|
||||
)
|
||||
|
||||
# Value keeps its full text; the name yields space to it when the row is tight
|
||||
value_width = slot_font.measure(val_str)
|
||||
label_space = box_width - 16 - value_width
|
||||
|
||||
canvas.create_text(
|
||||
coord_x + 6, current_row_y + (slot_row_height // 2), anchor="w",
|
||||
text=fit_text(s_name, slot_font, label_space),
|
||||
font=slot_font, fill="black", tags=("component", comp_tag)
|
||||
)
|
||||
|
||||
canvas.create_text(
|
||||
coord_x + box_width - 6, current_row_y + (slot_row_height // 2), anchor="e",
|
||||
text=val_str, font=slot_font, fill="#333333", tags=("component", comp_tag)
|
||||
)
|
||||
|
||||
current_row_y += slot_row_height
|
||||
canvas.create_line(
|
||||
coord_x, current_row_y, coord_x + box_width, current_row_y,
|
||||
fill="#b8b8b8", tags=("component", comp_tag)
|
||||
)
|
||||
|
||||
draw_links()
|
||||
apply_sheet_zoom(fonts, max_reached_x + 200, max_reached_y + 200)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user