Skip to content

Addresses a portion of #10619, 2.6 Selection, highlight & inspection#10910

Open
jorge-ferreira-pii wants to merge 7 commits into
The-OpenROAD-Project:masterfrom
The-OpenROAD-Project-staging:feature-WebGUI-selection-highlight-inspection
Open

Addresses a portion of #10619, 2.6 Selection, highlight & inspection#10910
jorge-ferreira-pii wants to merge 7 commits into
The-OpenROAD-Project:masterfrom
The-OpenROAD-Project-staging:feature-WebGUI-selection-highlight-inspection

Conversation

@jorge-ferreira-pii

Copy link
Copy Markdown
Contributor

Addresses a portion of #10619

2.6 Selection, highlight & inspection

Feature GUI Web Status
Inspector editing Descriptor::getEditors / getActions set_property / trigger_action commands; editable properties become inline fields or dropdowns (X/Y, orient, placement status, master), object actions run from the toolbar (e.g. Delete behind a styled confirm modal) filtered by a blocklist, and rejected edits surface the reason via a toast — where the Qt GUI reverts silently ❌ → ✅
Highlight groups 16-group HighlightSet colored by Painter::kHighlightColors highlight / unhighlight / clear_highlights commands with a 4×4 color-picker popover and a tinted badge; the server keeps highlight_groups plus derived overlay shapes (rects and flight lines for unrouted nets), and the 16-color palette is served in the tech response. Note: 16 groups, not 8 as the issue states ❌ → ✅
Selection browser selectHighlightWindow (2 tabs) Selection Browser panel (Windows menu) with Selected/Highlighted tabs and live counters, substring filter, sortable columns, and per-row zoom-to / de-select / inspect resolved by stable server index ❌ → ✅
Selection animation selectionAnimation (QTimer 300 ms, outline weight cycling 1→2→3 px with a brush flash, 6 repeats) client-side animateSelection replicating the Qt cadence on a Leaflet path with no per-tick layout repaint; triggers on multi-selection cycling and on selection-browser row hover (continuous), and respects prefers-reduced-motion by falling back to a single pulse 🟡 → ✅
Auto-connectivity Ctrl+LeftClick → selectHighlightConnectedNets Ctrl/Cmd+click sends show_connectivity; the server pulls the clicked instance's SIGNAL nets into the selection and reports connected_added (surfaced as a toast), with unrouted nets rendered as flight lines in the overlay ❌ → ✅
Hover pre-highlight web-only: hovering the layout outlines the object under the cursor before selection; the issue attributes this to the Qt GUI, but it is a Web addition ✅ (web-only)

Bring the web viewer's inspector to parity with the Qt GUI's editing
support (issue The-OpenROAD-Project#10619, table 2.6), using the descriptor Editor/Action
API that was already linked but never exposed:

- Inspect payloads now carry editable/editor{type,options} per property
  (from Descriptor::getEditors) and a descriptor actions list filtered
  by a blocklist (Qt-dialog actions would crash without a QApplication;
  focus/guides/zoom already have per-session web equivalents).
- New set_property request: strings/numbers/bools marshal per the Qt
  delegate rules (numbers always double); list editors commit by option
  index so the option's exact std::any reaches the callback, with an
  option_name guard against stale option lists.
- New trigger_action request: re-resolves the action by name at commit
  time; the returned Selected becomes the inspected object. Delete is
  confirmed in a themed modal. The reserved "deselect" action now runs
  whenever the inspected object changes.
- ScopedDbuFormat also installs Property::convert_string (the library
  default returns 0 without setting the ok flag, silently rejecting
  coordinate edits such as dbInst X/Y).
- Multi-client safety: destroying any selectable object (trigger_action
  or a Tcl command) raises a per-session staleness flag via the odb
  destroy callbacks; handlers drop the dangling Selected state before
  the next dereference and clients receive selection_invalidated.
  Accepted edits broadcast a refresh to every session. This also fixes
  a pre-existing crash vector for destroys issued from Tcl.
- Rejected edits show the reason in a toast instead of Qt's silent
  revert; editable values get a visible affordance.
- Selected::getDescriptorActions() (inline in gui.h) exposes the
  descriptor's own actions without the "Zoom to" entry appended by
  Selected::getActions(), which lives in the full gui library and is
  unavailable to code linking only gui_descriptors.

Tested: 16 new gtest cases (SetPropertyTest/TriggerActionTest) and 11
new JS cases; validated live against nangate45/gcd 5_2_route.odb with
two concurrent websocket clients.

Signed-off-by: Jorge Ferreira <jorge.ferreira@precisioninno.com>
Bring the web viewer's highlight groups to parity with the Qt GUI
(issue The-OpenROAD-Project#10619, table 2.6 — note the Qt GUI has 16 groups, not the 8
the issue's table says):

- Per-session state mirrors Qt's HighlightSet: 16 fixed
  gui::SelectionSet groups colored by gui::Painter::kHighlightColors.
  The derived overlay shapes are ColoredRects fed through the existing
  DRC/timing colored-rect rendering (translucent fill + solid outline),
  appended last so groups paint on top — no renderer changes.
- New requests: highlight {group} puts the inspected object into a
  group, always removing it from any other first (the Qt add path can
  leave an object in two groups; the web enforces uniqueness);
  unhighlight removes it; clear_highlights {group|-1} clears one or all
  (Qt's Clear → Highlights semantics).
- Inspect payloads now carry highlight_group (-1..15) so the client
  shows the state in place — the Qt GUI only shows it in the separate
  Selections/Highlights dock.
- The 16-color palette is served in the tech response straight from
  gui::Painter::kHighlightColors, so client swatches cannot drift from
  what the overlay renders.
- Inspector toolbar gains a palette button with a one-click 4x4 swatch
  popover (the Qt GUI opens a modal 16-radio dialog) plus a Remove
  entry; the button doubles as a badge tinted with the current group's
  color.  Tools menu gains Clear Highlights.
- Highlight groups join the multi-client staleness handling: destroys
  clear them before the next dereference (the Qt GUI keeps stale
  pointers to deleted objects).

Tested: 10 new gtest cases (add/move-unique/unhighlight/clear/invalid/
payload/overlay/staleness) plus a tech-palette check, and 3 new JS
cases for the picker UI; validated live against nangate45/gcd
5_2_route.odb with PNG pixel decoding — group 4 renders red and group 0
green in the overlay, groups persist across selection changes, and
clear empties the overlay.

Signed-off-by: Jorge Ferreira <jorge.ferreira@precisioninno.com>
Bring the web viewer's selection browser to parity with the Qt GUI's
Selected/Highlighted Shapes dock (issue The-OpenROAD-Project#10619, table 2.6), filling the
SelectHighlight GoldenLayout stub:

- New requests: list_selection returns the selection set and the 16
  highlight groups (name/type/bbox per row, capped at 1000 rows per set
  with a truncated flag — the Qt dock renders everything and freezes on
  huge selections); inspect_selection {index} / inspect_group {group,
  index} make a browser row the inspected object by stable set index,
  never through state.selectables (which belongs to the last
  inspect-style response and backs the Inspector's clickable links);
  deselect {index} removes one object from the selection set (Qt parity:
  MainWindow::removeFromSelected — the web had no per-item removal).
- selection-browser.js: Selected/Highlighted tabs with live counters, a
  real substring filter over name and type (the Qt dock only has
  keyboardSearch — and its Highlighted tab reads the Selected tab's
  search box), sortable resizable columns, a color chip + "Group N" per
  highlighted row, per-row zoom (client-side, the bbox ships with the
  list) and de-select buttons, row click → inspector, double click →
  focus the Inspector panel, and a Clear-all-highlights button.  Rows
  carry the stable index from the last fetch, so sorting can never act
  on the wrong object (the Qt dock's context menus use proxy rows
  without mapToSource and mis-target under sorting).
- Refresh piggybacks on the overlay scheduler: every selection or
  highlight mutation already refreshes the overlay, so one hook keeps
  the panel in sync (debounced, skipped while hidden), mirroring the Qt
  dock's 100 ms coalescing timers.
- Panel added to the default layout (LAYOUT_VERSION 3 → 4) and to the
  Windows menu.

Tested: 6 new gtest cases (list/inspect-by-index/deselect/invalid
indices/staleness) and 8 JS widget cases; validated live against
nangate45/gcd 5_2_route.odb — shift-click multi-select, group listing,
row inspection by index, de-select, and the panel mounting in a real
headless-Chrome session.

Signed-off-by: Jorge Ferreira <jorge.ferreira@precisioninno.com>
The client's coordinate transforms (designScale/Origin/MaxDXDY) were
frozen at boot from a single bounds fetch, but the server's getBounds()
is dynamic — it is the block bbox inflated by the pin-label margin, and
DB edits change it: moving an instance outside the bbox via
set_property X/Y, deleting an edge instance, or renaming a BTerm (the
longest pin name sizes the label margin).  When that happened, tiles
re-rendered against the new georeference while every connected client
kept the old transforms, so clicks selected the wrong object and
selection boxes drew offset from the layout (die-edge instances became
unselectable).  A stale tab reconnecting to a restarted server had the
same problem.

- The {"type":"refresh"} broadcast sent after set_property /
  trigger_action now carries the current bounds, so clients resync
  without an extra round-trip.
- New pure helpers computeBoundsTransforms/boundsEqual (ui-utils) back
  an applyBounds/resyncBounds pair in main.js: on a refresh push with
  different bounds the client rebuilds its transforms in place, keeping
  the DBU point at the center of the view, drops client-side rectangles
  that hold stale latlngs, and redraws.
- WebSocketManager gains an onReconnected hook (fired on any reconnect
  after the first connection); main.js uses it to re-fetch bounds and,
  if they changed — typically a restarted server or different design —
  reload through the well-tested boot path.

Tested: gtest asserts the broadcast carries bounds matching
serializeBoundsResponse and documents that bounds follow block-bbox
changes; JS tests cover the transform math, boundsEqual, and the
reconnect hook.  Validated live on nangate45/ibex with a real headless
Chrome: after moving an instance outside the bbox from a second
session, the browser's effective transforms (measured by dispatching
real clicks) follow the server; a mutation missed while the socket was
down triggers reload-on-reconnect and realigns.

Signed-off-by: Jorge Ferreira <jorge.ferreira@precisioninno.com>
Qt parity for the last item of issue The-OpenROAD-Project#10619 table 2.6:

- Ctrl+click sends show_connectivity; the server expands the selection
  with the SIGNAL nets on the picked instance's ITerms (Qt's
  selectHighlightConnectedNets) and reports connected_added, surfaced
  as a toast in the client.
- ShapeCollector now captures Painter::drawLine, and SessionState keeps
  highlight_lines rendered by the overlay next to timing lines, so
  unrouted-net highlights draw their flight lines.
- animateSelection replicates Qt's selection animation (outline weight
  cycling 1-2-3 px with a brush flash, 300 ms ticks, 6 repeats,
  repeats=0 continuous) as a client-side Leaflet path - no layout
  repaint per tick. Triggers: cycling a multi-selection, and hovering a
  selection-browser row (continuous until mouseleave). Respects
  prefers-reduced-motion by falling back to the one-shot pulse.
- Fix writeInspectPayload to use pointer-form any_cast for nets so
  isNet() wrappers without a bare dbNet* payload cannot throw.
- Shift+click and Ctrl+click documented in the Help panel.

Signed-off-by: Jorge Ferreira <jorge.ferreira@precisioninno.com>
Pre-PR pass anticipating the repo's gemini-code-assist and Codex review
bots, plus a duplication cleanup.

Correctness:
- handleOverlayTile read state.drc_lines under selection_mutex while it
  is written under drc_mutex; move the read back into the drc_mutex
  block (thread-safety regression from the earlier refactor).
- rebuildHighlightGroupShapesLocked dropped ShapeCollector::drawLine
  output, so highlight-group members that only draw flight lines (e.g.
  unrouted nets) were invisible in the overlay; collect them into a new
  highlight_group_lines tinted with the group color.
- handleSetProperty/handleTriggerAction built the refresh broadcast
  (getBounds reads odb) after releasing the STA lock; capture the
  payload under the lock and broadcast afterwards.
- Ctrl+click connectivity now also accepts Cmd+click, so it is reachable
  on macOS where Ctrl+click is the context-menu gesture.
- Selection-browser hover animation could be stranded when a background
  refresh rebuilt the table under the cursor (mouseleave never fires);
  stop the animation on table re-render, on updateInspector(null), and
  on the selection_invalidated push.
- The 'refresh' push redrew all layers twice when the bounds changed;
  resyncBounds now reports whether it already redrew.

Simplification:
- Extract writeInspectTrailer() for the inspection-response epilogue
  duplicated across eight handlers (payload + selection cursor fields +
  selectables swap), removing the copy-paste that had already diverged.

Signed-off-by: Jorge Ferreira <jorge.ferreira@precisioninno.com>

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new "Selection Browser" component to the web interface, providing parity with the Qt GUI's Selected/Highlighted tabs, and adds support for server-backed property editing, highlight groups, selection animations, and coordinate-transform synchronization. On the backend, it implements corresponding WebSocket handlers and enhances shape collection to support flight lines for unrouted nets. The review feedback suggests optimizing the Selection Browser by checking its visibility state before refreshing to prevent redundant background network requests, and clearing any pending debounced refresh timers upon container destruction to avoid potential memory leaks.

Comment thread src/web/src/selection-browser.js Outdated
Comment on lines +55 to +56
refresh() {
if (isStaticMode(this._app) || this._inFlight) return;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To prevent unnecessary background network requests and DOM rendering when the Selection Browser tab is hidden, the refresh method should check the visibility state (this._visible) before proceeding.

Suggested change
refresh() {
if (isStaticMode(this._app) || this._inFlight) return;
refresh() {
if (!this._visible || isStaticMode(this._app) || this._inFlight) return;

Comment on lines +30 to +37
// GoldenLayout tab visibility — skip background refreshes.
container.on?.('show', () => {
this._visible = true;
this.refresh();
});
container.on?.('hide', () => {
this._visible = false;
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

When the GoldenLayout container is destroyed (e.g., when the user closes the Selection Browser tab), any pending debounced refresh timer (this._pendingRefresh) should be cleared. Failing to do so can cause the timer to fire on a destroyed component, leading to errors and memory leaks.

        // GoldenLayout tab visibility — skip background refreshes.
        container.on?.('show', () => {
            this._visible = true;
            this.refresh();
        });
        container.on?.('hide', () => {
            this._visible = false;
        });
        container.on?.('destroy', () => {
            this._visible = false;
            if (this._pendingRefresh !== null) {
                clearTimeout(this._pendingRefresh);
                this._pendingRefresh = null;
            }
        });

- selection-browser: guard refresh() on visibility so a hidden panel (or
  a debounced refresh that fires after a 'hide') issues no list_selection
  request or re-render; and clear the pending debounced timer on the
  GoldenLayout 'destroy' event so it cannot fire on a torn-down panel.
  Both flagged by gemini-code-assist on The-OpenROAD-Project#10910.
- TestRequestHandler: join a line that violated clang-format (CI red).
- Add regression tests for the hidden-refresh skip and destroy cleanup.

Signed-off-by: Jorge Ferreira <jorge.ferreira@precisioninno.com>
@jorge-ferreira-pii jorge-ferreira-pii marked this pull request as ready for review July 14, 2026 19:32
@jorge-ferreira-pii jorge-ferreira-pii requested review from a team as code owners July 14, 2026 19:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant