From 0dddea53f8333b7a01d2411af8f73f9b2f566a75 Mon Sep 17 00:00:00 2001 From: Arda Aydin Date: Sun, 18 Oct 2026 15:34:27 +0300 Subject: [PATCH] Wiresheet: keep off-sheet input knobs on the sheet A box with a link arriving from another sheet draws a knob on its left edge. Tidy Layout moves the first column one grid step in when any of its boxes has one, and dragging and arrow nudges stop such a box one step inside the margin, so the knob never sits out in the grey. Co-Authored-By: Claude Opus 5.5 --- app_state.py | 2 +- main.py | 16 +++++++++++----- wiresheet.py | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 6 deletions(-) diff --git a/app_state.py b/app_state.py index 68891de..16ffb82 100644 --- a/app_state.py +++ b/app_state.py @@ -8,7 +8,7 @@ so later rebinding would only ever be seen by the module that did it. import configparser import os -VERSION = "0.0.0.180" +VERSION = "0.0.0.181" PROPERTIES_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "editor.properties") DEFAULT_GRID_SIZE = 10 DEFAULT_ADD_TIMESTAMP = True diff --git a/main.py b/main.py index b723572..448cea0 100644 --- a/main.py +++ b/main.py @@ -2135,14 +2135,17 @@ def on_canvas_drag(event): dy = cy - app_state.drag_data["y"] xs = [entry["origin"][0] for entry in group] ys = [entry["origin"][1] for entry in group] - dx = min(limit - max(xs), max(margin - min(xs), dx)) + # A box with an off-sheet input stops a step further in, keeping its knob on the sheet. + left = wiresheet.box_left_limits([entry["tag"] for entry in group], margin, step) + dx = min(limit - max(xs), + max(max(left[e["tag"]] - e["origin"][0] for e in group), dx)) dy = min(limit - max(ys), max(margin - min(ys), dy)) shifted = False for entry in group: # Land each box on a step every step of the way. start_x, start_y = entry["origin"] - target_x = min(limit, max(margin, round((start_x + dx) / step) * step)) + target_x = min(limit, max(left[entry["tag"]], round((start_x + dx) / step) * step)) target_y = min(limit, max(margin, round((start_y + dy) / step) * step)) origin = wiresheet.get_box_origin(entry["tag"]) if origin is None: @@ -2178,6 +2181,7 @@ def on_canvas_release(event): changes = [] failed = [] + left = wiresheet.box_left_limits([entry["tag"] for entry in group], margin, step) for entry in group: group_tag = entry["tag"] origin = wiresheet.get_box_origin(group_tag) @@ -2185,7 +2189,7 @@ def on_canvas_release(event): continue current_x, current_y = origin - snapped_x = min(limit, max(margin, round(current_x / step) * step)) + snapped_x = min(limit, max(left[group_tag], round(current_x / step) * step)) snapped_y = min(limit, max(margin, round(current_y / step) * step)) shift_x = snapped_x - current_x @@ -2255,9 +2259,11 @@ def nudge_selected_boxes(dx, dy): origins = {t: wiresheet.get_box_origin(t) for t in tags} if any(o is None for o in origins.values()): return "break" - # The group stops as one at the margin strip and the sheet's far edge. + # The group stops as one at the margin strip and the sheet's far edge; a box with an + # off-sheet input stops a step further in, keeping its knob on the sheet. + left = wiresheet.box_left_limits(tags, margin, step) shift_x = min(limit - max(o[0] for o in origins.values()), - max(margin - min(o[0] for o in origins.values()), dx * step)) + max(max(left[t] - o[0] for t, o in origins.items()), dx * step)) shift_y = min(limit - max(o[1] for o in origins.values()), max(margin - min(o[1] for o in origins.values()), dy * step)) if not shift_x and not shift_y: diff --git a/wiresheet.py b/wiresheet.py index c46557d..df8811d 100644 --- a/wiresheet.py +++ b/wiresheet.py @@ -154,6 +154,36 @@ def suggest_box_width_for_grid(width, grid_size): return proposed +def offsheet_input_comps(components): + """The components with a link arriving from off this sheet, drawn as a knob on + their left edge.""" + root = app_state.xml_root_element + links_elem = root.find("links") if root is not None else None + if links_elem is None: + return set() + comp_by_path = {get_component_path(comp, root): comp for comp in components} + found = set() + for link in links_elem.findall("link"): + from_ref, to_ref = link.get("from", ""), link.get("to", "") + if "." not in from_ref or "." not in to_ref: + continue + dst = comp_by_path.get(to_ref.rsplit(".", 1)[0]) + if dst is not None and from_ref.rsplit(".", 1)[0] not in comp_by_path: + found.add(dst) + return found + + +def box_left_limits(tags, margin, step): + """Left-most x each box on the open sheet may be moved to, keyed by canvas tag. + + The margin, or one step further in for a box with an off-sheet input, so its knob + stays on the sheet rather than out in the grey margin. + """ + knobbed = offsheet_input_comps(list(app_state.canvas_comp_map.values())) + return {tag: margin + step if app_state.canvas_comp_map.get(tag) in knobbed + else margin for tag in tags} + + def layout_margin(grid_size=None): """The top/left edge offset a box may not encroach on, snapped onto the grid.""" if grid_size is None: @@ -1575,6 +1605,10 @@ def plan_columns(columns, column_keys, demand, scale, wires=()): # Column x positions depend only on widths and wire lanes, so they come first. column_x = [] cursor_x = margin # the left-most column clears the canvas edge by the margin + if count and any(item.get("knob_in") for item in columns[column_keys[0]]): + # One step further in, so a knob on a first-column box's left edge stays on + # the sheet instead of out in the margin. + cursor_x = snap_up(margin + app_state.GRID_SIZE, scale) for index, grid_x in enumerate(column_keys): column_x.append(cursor_x) gap = column_gap_for(demand.get(index, 0)) @@ -1868,6 +1902,7 @@ def build_layout_items(components): linked_slot_index = build_linked_slot_index() pairs = sheet_link_pairs(components) on_sheet = {comp for src, _, dst, _ in pairs for comp in (src, dst)} + knobbed = offsheet_input_comps(components) linked = [] loose = [] @@ -1880,6 +1915,7 @@ def build_layout_items(components): "comp": comp, "meta": meta_val, "grid_x": grid_x, "grid_y": grid_y, "width": width, "height": height, + "knob_in": comp in knobbed, # Where each slot's wire meets the box, as painted by draw_component_box. "slot_offsets": { raw_name: HEADER_HEIGHT + row * SLOT_ROW_HEIGHT + SLOT_ROW_HEIGHT // 2