Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,20 @@ to Driver"; both clear the view first, like "Show Equipotential") and from the
schematic (right-click a pin -> "Trace to Driver"; this one *adds* to the view
instead of clearing it).

Extending the schematic by hand works pin by pin. A box drawn with a dashed
border shows only the pins on nets already in view; double-clicking its body
sends `expand_instance_terms` and reveals the rest. Pins whose net isn't in
the view yet are "open" (`Port::open`, drawn with a hollow-circle stub), and a
**single click** on one sends `load_equipotential` for its net (the stub fills
while it loads; `SchematicInteraction::PendingRequests` stops repeat sends).
A single click on a merged bus pin shows its bits instead. Hovering a pin
highlights it and shows a tooltip naming its click action. When the requested
net arrives the view pans so the clicked box stays put on screen, even if the
hierarchy frames re-lay everything out. Pins are hit-tested nearest-first by
`SchematicInteraction::pickPin`. `GUIData::addEquipotential()` rejects a net
whose endpoints are all already shown (`equipotentialCovers()`), so neither a
re-click nor a trace re-listing a shown net draws its wires twice.

Occurrence `path` entries (in `equipotential_response` and each
`trace_driver_response` net) are `[name, child_id, model_name]`; the third
element is optional on parse (`InstTermOccurrence::pathModels`, `""` when
Expand Down Expand Up @@ -406,6 +420,17 @@ appear as boxes/pins there).
instance placement and column de-overlap) and `layoutHierarchyGroups()`
(module frames). Changes to how the schematic is laid out belong here,
with a test; `EquipotentialView` only turns the result into drawn shapes.
- **`SchematicInteraction`** — the pure side of the schematic's pin
interactions: `pickPin()` (nearest-pin hit test, radius capped in world
units so a pin doesn't swallow box-body clicks at low zoom) and
`PendingRequests` (in-flight pin requests, which expire after 5 s).
`tests/SchematicInteractionTest.cpp` tests them. The click flow itself is
covered end to end by `tests/EquipotentialViewTest.cpp`. That test drives
the real `EquipotentialView::renderSchematic()` in a headless ImGui context
with no render backend: it moves the mouse and clicks through the ImGui IO
API, then asserts on what `FakeNetlistProvider` received and on the
geometry, read through `EquipotentialView::schematicForTesting()`. Use the
same pattern for new canvas interactions.

### Diagnosis overlay

Expand Down
9 changes: 7 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ set(CORE_SOURCES
src/NetlistTree.cpp
src/EquipotentialView.cpp
src/SchematicLayout.cpp
src/SchematicInteraction.cpp
src/SchematicView.cpp
src/DiagnosisStore.cpp
src/DiagnosisView.cpp
Expand Down Expand Up @@ -209,8 +210,9 @@ endif()
# Native only: exercises the logic layer (JSON parsing, DiagnosisStore,
# SourceStore, NetlistTree structure, SchematicLayout geometry) without SDL/OpenGL/naja. Links only
# ImGui's core (imgui.cpp/imgui_draw.cpp/imgui_tables.cpp/imgui_widgets.cpp),
# not the SDL2/OpenGL3 backends, since none of the tested code needs a real
# render backend -- render()/draw calls are never invoked from tests.
# not the SDL2/OpenGL3 backends: tests that do run UI code (e.g.
# EquipotentialViewTest) drive a headless ImGui context with simulated input
# and never need anything actually drawn to a screen.
if(NOT EMSCRIPTEN_BUILD)
option(NAJA_SCHEMATIC_BUILD_TESTS "Build naja-schematic-tests" ON)
if(NAJA_SCHEMATIC_BUILD_TESTS)
Expand All @@ -235,6 +237,9 @@ if(NOT EMSCRIPTEN_BUILD)
src/SourceStore.cpp
src/PropertiesStore.cpp
src/SchematicLayout.cpp
src/SchematicInteraction.cpp
src/EquipotentialView.cpp
src/SchematicView.cpp
${IMGUI_DIR}/imgui.cpp
${IMGUI_DIR}/imgui_draw.cpp
${IMGUI_DIR}/imgui_tables.cpp
Expand Down
5 changes: 5 additions & 0 deletions REUSE.toml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ path = [
"src/PropertiesView.cpp",
"src/PropertiesView.h",
"src/RenderTypes.h",
"src/SchematicInteraction.cpp",
"src/SchematicInteraction.h",
"src/SchematicLayout.cpp",
"src/SchematicLayout.h",
"src/SchematicView.cpp",
Expand All @@ -85,8 +87,11 @@ path = [
"src/main_native.cpp",
"src/main_wasm.cpp",
"tests/DiagnosisStoreTest.cpp",
"tests/EquipotentialViewTest.cpp",
"tests/FakeNetlistProvider.h",
"tests/GUIDataTest.cpp",
"tests/NetlistTreeTest.cpp",
"tests/SchematicInteractionTest.cpp",
"tests/SchematicLayoutTest.cpp",
"tests/SourceStoreTest.cpp",
"tests/TypesJsonTest.cpp",
Expand Down
20 changes: 13 additions & 7 deletions src/AppLogic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -189,22 +189,28 @@ void setupProvider(AppState& state) {
parent->createNetNode(net.name, net.msb, net.lsb);
}
} else if (resp == "equipotential_response") {
Console::Log("Equipotential data received");
state.guiData->addEquipotential(new Equipotential(j.get<Equipotential>()));
state.tableEquipotentialCount = 1;
if (state.guiData->addEquipotential(new Equipotential(j.get<Equipotential>()))) {
Console::Log("Equipotential data received");
state.tableEquipotentialCount = 1;
} else {
Console::Log("Equipotential already shown");
}
} else if (resp == "trace_driver_response") {
// Nets arrive breadth-first from the traced net toward the drivers, so
// adding them in order lets the layout chain each one off an instance
// that's already placed.
size_t n = 0;
// Nets already on screen (e.g. the one the trace started from, when
// it's launched from a schematic pin) are skipped rather than drawn twice.
size_t n = 0, added = 0;
if (j.contains("equipotentials") && j["equipotentials"].is_array()) {
for (const auto& e : j["equipotentials"]) {
state.guiData->addEquipotential(new Equipotential(e.get<Equipotential>()));
++n;
if (state.guiData->addEquipotential(new Equipotential(e.get<Equipotential>()))) ++added;
}
}
state.tableEquipotentialCount = std::max<size_t>(n, 1);
Console::Log("Driver trace received: " + std::to_string(n) + " net(s)" +
state.tableEquipotentialCount = std::max<size_t>(added, 1);
Console::Log("Driver trace received: " + std::to_string(n) + " net(s), " +
std::to_string(added) + " new" +
(j.value("truncated", false) ? " (truncated)" : ""));
} else if (resp == "expanded_instance_terms") {
std::string pathKey = j.value("path_key", std::string(""));
Expand Down
Loading
Loading