Help screen, explore→set-connector-type Enter, settype UI polish.
- New `screen_help.cpp` (`screen_idx = 6`). Left column: menu of 13 topics (Overview, Dashboard, Console, Palette, Explore, Connect/plug, set-connector-type, Signal types, NC pins, Analyze, Scripting, Save/restore, Quitting). Centre column: paragraphs of the focused topic, word-wrapped via `paragraph()` and scrollable. Right column: standard help panel. - `help` bare → opens the screen; `help <name>` keeps the existing textual command-help behaviour for scripts. - Dashboard `[h]` shortcut opens the screen, and the dashboard help panel (both the loaded and the no-system branch) lists it. - Console: title gets the standard breadcrumb (`essim → console — type commands, read textual output`). Module/connection counters moved off (they live on the dashboard now). - Explore Enter on a part jumps to `set-connector-type` with the exact-match index pre-computed in the filtered list (avoids the substring-match collision where `J20` would land on the wrong row when J200/J21 also matched). - set-connector-type screen: bind `focused_entry` to `selected` on both menus so the cursor `>` tracks the selected row when state is pre-seeded from outside. Right column drops its strict `size(WIDTH, EQUAL, 40)` in favour of `flex`, and the `new type` input uses `xflex` so it actually stretches across the column. - Esc on `set-connector-type` honours `screen_back_idx` — when entered via Enter on a part in `explore`, Esc returns to explore; otherwise it returns to the dashboard like every other screen. Standalone command entries explicitly reset the back-link. - Net-member rows in the explore detail pane carry a `module\tsignal` payload so Enter opens the popup scoped to the peer module rather than mis-firing on the locally selected one. Same scheme for local-pin rows. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
11
DESIGN.md
11
DESIGN.md
@@ -182,7 +182,16 @@ The Connection record stores `Module*`/`Part*` for both endpoints (added to `Con
|
||||
|
||||
**Signal-type popup (shared)**: Enter on a signal entry in the `explore` screen opens a modal (`Tui::sigtype_dialog_open`) that lets the user pick `power | gnd | other` for the currently-selected signal. Built in `screen_sigtype_modal.cpp::BuildSignalTypeModal()`, attached to both screens via the `Modal(...)` decorator in `Run()`. Inside the modal, Enter applies + closes + records `set-signal-type <m> <s> <t>` in the script-save buffer (`recorded`); Esc closes without applying. Two safeguards: (a) re-selecting the type the pin already has is a no-op and records nothing; (b) the recorder collapses consecutive edits of the same `(module, signal)` — if the previous line in `recorded` already targets the same pair, it is replaced rather than appended. The outer `CatchEvent` in `Run()` cedes Tab/Esc to the modal whenever `sigtype_dialog_open` is true, so the underlying screen doesn't yank focus back. In `explore` the popup also fires from the detail pane when browsing `parts`: each `pin → signal` row carries its signal name in the parallel `explore_detail_sig` vector, and Enter on a non-`(NC)` row opens the popup for that signal.
|
||||
|
||||
**Net tracing in `explore`**: when the user selects a signal entry in `explore` (type tab = `signals`), the detail pane shows two sections: the local pins of that signal (module/part/pin labels), then — if `find_net(sys, module, signal)` returns ≥ 2 members — a `Net members (across connections)` section listing every `(module, signal, type)` reachable through `Connection::pin_map`. The signal-detail header includes `K net members across N module(s)` (or `INCONSISTENT` if `net_type_consistent` returns false). Members are sorted naturally and pushed into `explore_detail_sig` so pressing Enter on a member opens the signal-type popup for it. This is what replaced the former dedicated `net` screen.
|
||||
**Net tracing in `explore`**: when the user selects a signal entry in `explore` (type tab = `signals`), the detail pane shows two sections: the local pins of that signal (module/part/pin labels), then — if `find_net(sys, module, signal)` returns ≥ 2 members — a `Net members (across connections)` section listing every `(module, signal, type)` reachable through `Connection::pin_map`. The signal-detail header includes `K net members across N module(s)` (or `INCONSISTENT` if `net_type_consistent` returns false). This is what replaced the former dedicated `net` screen.
|
||||
|
||||
**Enter-to-edit matrix in `explore`** — `explore_detail_sig` stores a `module\tsignal` payload per detail row (empty = read-only). The detail-menu `on_enter` parses the `\t` and calls `OpenSignalTypeDialog(mod, sig)`, so Enter works correctly for peer-module rows. Today:
|
||||
|
||||
| Focus | parts | signals | connections |
|
||||
|--------------------|-----------------------|----------------------------------|-------------|
|
||||
| `children_menu` | jump to set-connector-type screen pre-filled on this part (focus on type input) | open signal-type popup for the selected signal | — |
|
||||
| `detail_menu` | open signal-type popup for the pin's signal (skipped on `(NC)`) | open signal-type popup for the row's `(module, signal)` — works for both local pins (= current signal) and cross-module net members | — |
|
||||
|
||||
Module / type / filter focuses do not have an Enter binding.
|
||||
|
||||
**Long-running scripts (`source`)**: `Source(file)` is event-paced. It reads all lines, sets `loading = true`, then spawns a detached pacing thread that posts `Event::Special("\x02tick")` every ~30 ms — **but only one tick at a time**: the main thread acks each one (`tick_in_flight = false` at the end of `ProcessNextSourceLine`) before the ticker sleeps and posts the next. Without this, FTXUI batches all queued events into one render pass; a long line (e.g. a heavy Mentor parse) would let the ticker queue many ticks and the modal would freeze. Each tick triggers `ProcessNextSourceLine`, which processes one effective line (skipping comments/blanks) via `Submit()`. A centred `borderDouble` modal (`" Computing… "` + filename + `N / M lines`) is overlaid via `dbox` while `loading` is true.
|
||||
|
||||
|
||||
@@ -126,8 +126,16 @@ Every classification is advisory. To force a different type:
|
||||
|
||||
- **Signal type**: from the `explore` screen, press Enter on a
|
||||
signal entry → a popup lets you pick `power` / `gnd` / `other`.
|
||||
Or type `set-signal-type <module> <signal> <type>` in the console
|
||||
(or from the palette).
|
||||
The same popup also opens when you press Enter on a pin row in
|
||||
the parts detail (changes the pin's signal type) or on a net
|
||||
member row in the signal detail (works across modules). Or type
|
||||
`set-signal-type <module> <signal> <type>` in the console (or
|
||||
from the palette).
|
||||
- **Connector type**: from `explore` with `type = parts`, press
|
||||
Enter on a part to jump to the dedicated `set-connector-type`
|
||||
screen with that part pre-selected and the cursor on the type
|
||||
input. The `set-connector-type` command also keeps working from
|
||||
the console / palette.
|
||||
- **Connector type**: `set-connector-type <module> <part> <connector-kind>`
|
||||
(also via the dashboard `[t]` shortcut). This drives the pin role
|
||||
expectations, which feed the `pin-role` check.
|
||||
|
||||
@@ -27,24 +27,9 @@ void Tui::RegisterCommands() {
|
||||
{{"command name (optional)", Completion::Command}},
|
||||
[this](const std::vector<std::string> &args) {
|
||||
if (args.empty()) {
|
||||
size_t maxw = 0;
|
||||
for (const auto &kv : commands) maxw = std::max(maxw, kv.first.size());
|
||||
auto print_group = [&](const std::string &title, bool want_interactive) {
|
||||
bool printed_any = false;
|
||||
for (const auto &kv : commands) {
|
||||
if (kv.second.interactive != want_interactive) continue;
|
||||
if (!printed_any) { Print(title); printed_any = true; }
|
||||
Print(" " + kv.first
|
||||
+ std::string(maxw - kv.first.size() + 2, ' ')
|
||||
+ kv.second.description);
|
||||
}
|
||||
};
|
||||
Print("Commands — type `help <name>` for details.");
|
||||
print_group("Interactive (open a full-screen mode):", true);
|
||||
print_group("Other:", false);
|
||||
Print("Keys: Esc cancels a multi-step prompt or leaves an interactive screen;");
|
||||
Print(" Tab completes commands/paths or cycles focus in interactive screens;");
|
||||
Print(" PageUp/PageDown scroll output (10 lines), Home/End jump to top/bottom.");
|
||||
// Bare → open the feature-reference screen.
|
||||
screen_back_idx = -1;
|
||||
screen_idx = 6;
|
||||
return;
|
||||
}
|
||||
const std::string &name = args[0];
|
||||
@@ -70,7 +55,9 @@ void Tui::RegisterCommands() {
|
||||
}
|
||||
},
|
||||
/*prompt_for_missing=*/ false,
|
||||
"show command help (optionally for a specific command)",
|
||||
"bare → open the help screen; `help <name>` → textual command help",
|
||||
/*scriptable=*/ false,
|
||||
/*interactive=*/ true,
|
||||
};
|
||||
commands["clear"] = { {}, [this](auto &) { output.clear(); }, true,
|
||||
"clear the visualization area" };
|
||||
@@ -397,6 +384,7 @@ void Tui::RegisterCommands() {
|
||||
settype_type.clear();
|
||||
settype_status.clear();
|
||||
settype_focus_idx = 0;
|
||||
screen_back_idx = -1; // standalone entry — Esc → dashboard
|
||||
screen_idx = 2;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ Component Tui::BuildDashboardScreen() {
|
||||
Element early_help = RenderHelpPanel("dashboard", {
|
||||
{"c", "console"},
|
||||
{"a", "analyze"},
|
||||
{"h", "help screen"},
|
||||
{"q", "quit"},
|
||||
{"Ctrl-P", "palette"},
|
||||
});
|
||||
@@ -295,9 +296,9 @@ Component Tui::BuildDashboardScreen() {
|
||||
Element help = RenderHelpPanel("dashboard", {
|
||||
{"c", "console"},
|
||||
{"p", "plug"},
|
||||
{"t", "set-connector-type"},
|
||||
{"e", "explore"},
|
||||
{"a", "analyze (verify + groups)"},
|
||||
{"h", "help screen"},
|
||||
{"PgUp", "scroll up"},
|
||||
{"PgDn", "scroll down"},
|
||||
{"Home", "scroll top"},
|
||||
|
||||
@@ -36,9 +36,49 @@ Component Tui::BuildExploreScreen() {
|
||||
child_opt.entries = &explore_children;
|
||||
child_opt.selected = &explore_child_idx;
|
||||
child_opt.on_enter = [this]() {
|
||||
if (explore_type_idx != 1 || explore_children.empty()) return;
|
||||
OpenSignalTypeDialog(explore_modules[explore_module_idx],
|
||||
explore_children[explore_child_idx]);
|
||||
if (!sys || explore_children.empty()) return;
|
||||
const std::string &mod_name = explore_modules[explore_module_idx];
|
||||
const std::string &child = explore_children[explore_child_idx];
|
||||
if (explore_type_idx == 1) {
|
||||
// Signal → signal-type popup.
|
||||
OpenSignalTypeDialog(mod_name, child);
|
||||
return;
|
||||
}
|
||||
if (explore_type_idx == 0) {
|
||||
// Part → jump to the set-connector-type screen pre-filled on
|
||||
// this part, with focus on the type input so the user can type
|
||||
// the kind directly.
|
||||
settype_modules.clear();
|
||||
for (auto &mk : *sys->modules()) settype_modules.push_back(mk.first);
|
||||
std::sort(settype_modules.begin(), settype_modules.end(), NaturalLess);
|
||||
auto it = std::find(settype_modules.begin(),
|
||||
settype_modules.end(), mod_name);
|
||||
if (it == settype_modules.end()) return;
|
||||
settype_m_idx = (int)(it - settype_modules.begin());
|
||||
settype_p_filter = child; // narrows the part menu to this row
|
||||
|
||||
// Predict the filtered + sorted list to find the *exact-match*
|
||||
// index — substring match can put the target at a non-zero
|
||||
// index (e.g. filtering "J2" surfaces J2, J20, J21 ; the right
|
||||
// pane needs J2 selected, not whichever sorts first).
|
||||
Module *mod = sys->modules()->get(mod_name);
|
||||
std::vector<std::string> matches;
|
||||
std::string needle = ToLower(child);
|
||||
for (auto &pkv : *mod)
|
||||
if (ToLower(pkv.first).find(needle) != std::string::npos)
|
||||
matches.push_back(pkv.first);
|
||||
std::sort(matches.begin(), matches.end(), NaturalLess);
|
||||
auto pit = std::find(matches.begin(), matches.end(), child);
|
||||
settype_p_idx = (pit == matches.end())
|
||||
? 0 : (int)(pit - matches.begin());
|
||||
|
||||
settype_type.clear();
|
||||
settype_status.clear();
|
||||
settype_focus_idx = 3; // straight to the type input
|
||||
screen_back_idx = 3; // Esc on settype → back to explore
|
||||
screen_idx = 2;
|
||||
}
|
||||
// type_idx == 2 (connections): no edit yet — left as a no-op.
|
||||
};
|
||||
auto children_menu = Menu(child_opt);
|
||||
|
||||
@@ -47,12 +87,17 @@ Component Tui::BuildExploreScreen() {
|
||||
MenuOption detail_opt = MenuOption::Vertical();
|
||||
detail_opt.entries = &explore_detail;
|
||||
detail_opt.selected = &explore_detail_idx;
|
||||
// Each `explore_detail_sig` slot is either empty (no action) or a
|
||||
// `module\tsignal` pair. The cross-module form is what lets Enter on a
|
||||
// net member row open the popup for that peer module's signal.
|
||||
detail_opt.on_enter = [this]() {
|
||||
if (explore_detail_idx < 0
|
||||
|| explore_detail_idx >= (int)explore_detail_sig.size()) return;
|
||||
const std::string &sig = explore_detail_sig[explore_detail_idx];
|
||||
if (sig.empty()) return;
|
||||
OpenSignalTypeDialog(explore_modules[explore_module_idx], sig);
|
||||
const std::string &payload = explore_detail_sig[explore_detail_idx];
|
||||
if (payload.empty()) return;
|
||||
size_t tab = payload.find('\t');
|
||||
if (tab == std::string::npos) return;
|
||||
OpenSignalTypeDialog(payload.substr(0, tab), payload.substr(tab + 1));
|
||||
};
|
||||
auto detail_menu = Menu(detail_opt);
|
||||
|
||||
@@ -142,9 +187,12 @@ Component Tui::BuildExploreScreen() {
|
||||
+ r.second;
|
||||
if (keep_detail(line)) {
|
||||
explore_detail.push_back(line);
|
||||
// "(NC)" → no underlying Signal to retype.
|
||||
// "(NC)" → no underlying Signal to retype; for a
|
||||
// real signal we store `module\tsignal` so Enter
|
||||
// opens the popup scoped correctly.
|
||||
explore_detail_sig.push_back(
|
||||
r.second == "(NC)" ? std::string{} : r.second);
|
||||
r.second == "(NC)" ? std::string{}
|
||||
: (cur_mod->name + "\t" + r.second));
|
||||
}
|
||||
}
|
||||
} else if (explore_type_idx == 1) {
|
||||
@@ -171,9 +219,13 @@ Component Tui::BuildExploreScreen() {
|
||||
+ " pins • type: " + signal_type_name(s->type)
|
||||
+ " • " + net_hdr;
|
||||
|
||||
// Local pins first.
|
||||
// Local pins first. Enter on any of these reopens the
|
||||
// signal-type popup for the current signal (= same as
|
||||
// Enter on the signal in the children menu — redundant
|
||||
// but natural).
|
||||
explore_detail.push_back(" Local pins:");
|
||||
explore_detail_sig.push_back({});
|
||||
std::string self_payload = cur_mod->name + "\t" + s->name;
|
||||
std::vector<std::string> rows;
|
||||
for (auto &pin_kv : *s) {
|
||||
Pin *pin = pin_kv.second;
|
||||
@@ -186,32 +238,32 @@ Component Tui::BuildExploreScreen() {
|
||||
for (const auto &r : rows)
|
||||
if (keep_detail(r)) {
|
||||
explore_detail.push_back(" " + r);
|
||||
explore_detail_sig.push_back({});
|
||||
explore_detail_sig.push_back(self_payload);
|
||||
}
|
||||
|
||||
// Cross-module net members (only when truly bridged).
|
||||
// Net-member rows are read-only: Enter is a no-op (we
|
||||
// intentionally push an empty `explore_detail_sig` slot
|
||||
// — the popup takes a (module, signal) pair scoped to
|
||||
// the *currently selected* module, which would mis-fire
|
||||
// for a member living on a peer module).
|
||||
// Each row carries its own `module\tsignal` payload so
|
||||
// Enter opens the popup for that peer-module signal —
|
||||
// not the locally-selected module.
|
||||
if (n.members.size() >= 2) {
|
||||
explore_detail.push_back("");
|
||||
explore_detail_sig.push_back({});
|
||||
explore_detail.push_back(" Net members (across connections):");
|
||||
explore_detail_sig.push_back({});
|
||||
std::vector<std::string> net_rows;
|
||||
std::vector<std::pair<std::string, std::string>> net_rows;
|
||||
for (const auto &mp : n.members) {
|
||||
std::string label = mp.first->name + "/" + mp.second->name
|
||||
+ " (" + signal_type_name(mp.second->type)
|
||||
+ ")";
|
||||
net_rows.push_back(std::move(label));
|
||||
net_rows.emplace_back(std::move(label),
|
||||
mp.first->name + "\t" + mp.second->name);
|
||||
}
|
||||
std::sort(net_rows.begin(), net_rows.end(), NaturalLess);
|
||||
std::sort(net_rows.begin(), net_rows.end(),
|
||||
[](const auto &a, const auto &b) { return NaturalLess(a.first, b.first); });
|
||||
for (const auto &r : net_rows)
|
||||
if (keep_detail(r)) {
|
||||
explore_detail.push_back(" " + r);
|
||||
explore_detail_sig.push_back({});
|
||||
if (keep_detail(r.first)) {
|
||||
explore_detail.push_back(" " + r.first);
|
||||
explore_detail_sig.push_back(r.second);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
236
src/tui/screen_help.cpp
Normal file
236
src/tui/screen_help.cpp
Normal file
@@ -0,0 +1,236 @@
|
||||
#include "tui/tui.hpp"
|
||||
#include "tui/tui_helpers.hpp"
|
||||
|
||||
#include <ftxui/component/component.hpp>
|
||||
#include <ftxui/component/component_options.hpp>
|
||||
#include <ftxui/dom/elements.hpp>
|
||||
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
using namespace ftxui;
|
||||
|
||||
namespace {
|
||||
|
||||
// Topic table: (menu label, body). Body paragraphs are separated by an
|
||||
// empty line ("\n\n") and rendered with `paragraph()` so they wrap to
|
||||
// the available column width.
|
||||
const std::vector<std::pair<std::string, std::string>> &topics() {
|
||||
static const std::vector<std::pair<std::string, std::string>> data = {
|
||||
{"Overview",
|
||||
"essim is a digital twin for the inter-card connections inside "
|
||||
"a system. Load each card's netlist or pinout, tag its connectors, "
|
||||
"wire the connectors together, then ask essim to verify pin roles "
|
||||
"and trace nets across modules.\n\n"
|
||||
"The dashboard is the home screen. From there you reach every "
|
||||
"other view by a single keystroke; the global palette (Ctrl-P) "
|
||||
"fuzzy-finds commands, modules and signals from anywhere."},
|
||||
|
||||
{"Dashboard",
|
||||
"Read-only overview of the loaded system. Sections:\n\n"
|
||||
"• Overview — modules, parts, signals, connections counters.\n\n"
|
||||
"• Health — verify pin-role mismatches, bridged-net inconsistencies, "
|
||||
"NC orphans. Green = clean, yellow = something to inspect.\n\n"
|
||||
"• Analysis — diff pairs, diff buses, plain buses, anomaly count.\n\n"
|
||||
"• Modules — per-module breakdown: parts × signals, the connector "
|
||||
"types assigned (grouped), Power/Gnd inference summary.\n\n"
|
||||
"Letter shortcuts open the other screens. PgUp/PgDn scroll the "
|
||||
"dashboard if its content overflows."},
|
||||
|
||||
{"Console",
|
||||
"The textual shell. Reach it from the dashboard with [c]. Type any "
|
||||
"registered command; Enter submits, ↑/↓ walks history (persistent on "
|
||||
"disk), Tab completes command names and file paths.\n\n"
|
||||
"Multi-step commands (load, save, restore, source, set, "
|
||||
"set-signal-type, …) prompt for each argument in turn when called "
|
||||
"with no arguments. The same commands accept the full arg list "
|
||||
"inline (scriptable form).\n\n"
|
||||
"PageUp / PageDown / Home / End scroll the output buffer. Esc "
|
||||
"cancels a pending prompt; a second Esc returns to the dashboard."},
|
||||
|
||||
{"Palette (Ctrl-P)",
|
||||
"Press Ctrl-P from any screen to open a fuzzy search modal. "
|
||||
"Type characters and the result list ranks commands, module names "
|
||||
"and per-module signal names (qualified `module/signal`) by "
|
||||
"subsequence match.\n\n"
|
||||
"Enter activates the highlighted entry: a command runs (with its "
|
||||
"wizard if it needs arguments), a module jumps to explore prefilled "
|
||||
"on that module, a signal jumps to explore on the signals tab with "
|
||||
"the child filter pre-seeded.\n\n"
|
||||
"Esc closes the palette without acting."},
|
||||
|
||||
{"Explore",
|
||||
"Browse the system module-first. Four columns: module → type "
|
||||
"(parts / signals / connections) → filtered children → detail.\n\n"
|
||||
"Pressing Enter modifies the focused element when it makes sense:\n\n"
|
||||
"• on a part (children, type=parts) → jumps to set-connector-type "
|
||||
"pre-filled on that part, focus on the type input;\n\n"
|
||||
"• on a signal (children, type=signals) → opens the signal-type "
|
||||
"popup (power / gnd / other);\n\n"
|
||||
"• on a pin row (detail, type=parts) → opens the signal-type popup "
|
||||
"for that pin's signal;\n\n"
|
||||
"• on a net-member row (detail, type=signals) → opens the popup "
|
||||
"for that peer-module signal — Enter works across modules.\n\n"
|
||||
"When a signal is selected the detail pane shows the local pins "
|
||||
"plus the BFS net members across all connections, with the count "
|
||||
"and an INCONSISTENT flag if the bridged net mixes Power and Gnd."},
|
||||
|
||||
{"Connect / plug",
|
||||
"Wire two parts that sit on different modules. Reachable from the "
|
||||
"dashboard via [p] (plug) or as the `connect` command. The screen "
|
||||
"has two endpoint columns (module + part filter + part menu) and a "
|
||||
"Connect button.\n\n"
|
||||
"The transform is picked automatically from the registered "
|
||||
"(connector_type_A, connector_type_B) pair via TransformRegistry. "
|
||||
"If neither side carries a registered type, an identity transform "
|
||||
"matches pins by canonical name (e.g. A1 ↔ A001) and materialises "
|
||||
"missing NC pads on the shorter side."},
|
||||
|
||||
{"set-connector-type",
|
||||
"Tag a part with a connector kind such as `vpx-3u-bkp-p0`. Driven "
|
||||
"by the transform registry: tagging two parts on compatible kinds "
|
||||
"makes the next `connect` pick the right transform. Tagging also "
|
||||
"populates each pin's expected signal type via the pin_role lookup, "
|
||||
"which feeds the `pin-role` mismatch check in the analyze screen.\n\n"
|
||||
"Reach the screen from explore by pressing Enter on a part, or run "
|
||||
"the command directly (inline or interactive)."},
|
||||
|
||||
{"Signal types",
|
||||
"Every signal carries a type: Power, GndShield, or Other (default).\n\n"
|
||||
"The classification runs at the end of every `load` (and after "
|
||||
"`duplicate`). Rules:\n\n"
|
||||
"• Gnd: name matches GND / GROUND / EARTH / SHIELD / CHASSIS or "
|
||||
"starts with any of those followed by `_`. Name alone is enough.\n\n"
|
||||
"• Power: requires (a) the name suggests Power (contains PWR / VCC "
|
||||
"/ VDD / VEE / VSS / VBAT, starts with VS_ or +Nv / -Nv), AND (b) "
|
||||
"fan-out ≥ 3 (hard floor — never Power below this), AND (c) at "
|
||||
"least one of: fan-out ≥ 4, or a voltage pattern in the name "
|
||||
"(3V3, 5V, 12V…).\n\n"
|
||||
"• Other: everything else.\n\n"
|
||||
"`set-signal-type` overrides the inference. The analyze screen's "
|
||||
"Types tab lists every classification with the reason attached."},
|
||||
|
||||
{"NC pins",
|
||||
"Pins that have no signal attached. Three origins:\n\n"
|
||||
"• Imported NC — the netlist explicitly marks the pin as "
|
||||
"unconnected (Mentor uses signal name `unconnected`; Altium simply "
|
||||
"omits the pin from every signal block).\n\n"
|
||||
"• Dropped singleton — at end of `load`, every signal whose pin "
|
||||
"set has size 1 is detached (a one-pin net carries no signal). "
|
||||
"The lone pin survives as NC.\n\n"
|
||||
"• Filled at connect — when wiring two parts of unequal length, "
|
||||
"the shorter side gets new NC pads on the missing canonical "
|
||||
"positions. These are bridged via the connection's pin_map and "
|
||||
"are NOT counted as orphans."},
|
||||
|
||||
{"Analyze",
|
||||
"Unified verify + structural analysis. Three tabs, switch with "
|
||||
"Tab or ←/→:\n\n"
|
||||
"• Issues — pin-role mismatches (typed connector pin disagreeing "
|
||||
"with the signal landing on it), net-mix (bridged net carrying "
|
||||
"both Power and Gnd), and structural anomalies (orphan _P, gap in "
|
||||
"a bus, missing lane in a diff bus).\n\n"
|
||||
"• Groups — every detected diff pair, diff bus, plain bus with "
|
||||
"count + member list.\n\n"
|
||||
"• Types — every Power / Gnd classification with the reason "
|
||||
"(name + fan-out + voltage). Lists `[Suspect Power]` rows for "
|
||||
"names that look like Power but failed the structural check."},
|
||||
|
||||
{"Scripting",
|
||||
"`source <file>` runs a text script line by line. Comments start "
|
||||
"with `#`, blank lines are skipped. While running, a centred "
|
||||
"modal shows progress; the file is paced one effective line per "
|
||||
"~30 ms so FTXUI redraws between commands.\n\n"
|
||||
"`set <name> <value>` declares a session-scoped variable; "
|
||||
"subsequent commands expand `$name` and `${name}` in their args. "
|
||||
"`script-save <file>` writes every command issued since the "
|
||||
"last `new` to a replay file (excluding clear / help / quit / "
|
||||
"exit / source / script-save themselves, plus bare interactive "
|
||||
"commands).\n\n"
|
||||
"`new` resets the recorded buffer and the variable table."},
|
||||
|
||||
{"Save / restore",
|
||||
"`save <file>` writes a tab-delimited snapshot of the whole "
|
||||
"system (modules, parts with connector types, pins with signal "
|
||||
"links and NC origin tags, signal type overrides, connections "
|
||||
"with their transform and pin maps). `restore <file>` replaces "
|
||||
"the current system with the snapshot.\n\n"
|
||||
"The snapshot format is versioned by a `# essim system snapshot "
|
||||
"v1` header. The S-record line tags a signal type only when it "
|
||||
"differs from the default (Other), keeping snapshots small."},
|
||||
|
||||
{"Quitting",
|
||||
"From the dashboard: press `q`, or run the `quit` (or `exit`) "
|
||||
"command from the console / palette. Quit works from any "
|
||||
"screen — the command calls into the FTXUI screen directly so "
|
||||
"the loop returns immediately."},
|
||||
};
|
||||
return data;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Component Tui::BuildHelpScreen() {
|
||||
help_topic_names.clear();
|
||||
for (const auto &t : topics()) help_topic_names.push_back(t.first);
|
||||
|
||||
MenuOption topic_opt = MenuOption::Vertical();
|
||||
topic_opt.entries = &help_topic_names;
|
||||
topic_opt.selected = &help_topic_idx;
|
||||
topic_opt.focused_entry = &help_topic_idx;
|
||||
auto topic_menu = Menu(topic_opt);
|
||||
|
||||
return Renderer(topic_menu, [this, topic_menu] {
|
||||
auto title = hbox({
|
||||
text(" essim ") | bold,
|
||||
text("→ ") | dim,
|
||||
text("help") | bold,
|
||||
text(" — main feature reference") | dim,
|
||||
});
|
||||
|
||||
int idx = help_topic_idx;
|
||||
if (idx < 0) idx = 0;
|
||||
if (idx >= (int)topics().size())
|
||||
idx = (int)topics().size() - 1;
|
||||
const auto &topic = topics()[idx];
|
||||
|
||||
// Render the body: split on blank lines so each chunk becomes a
|
||||
// paragraph (FTXUI's `paragraph()` wraps to the available width).
|
||||
Elements body;
|
||||
body.push_back(text(" " + topic.first + " ") | bold);
|
||||
body.push_back(separator());
|
||||
const std::string &b = topic.second;
|
||||
size_t pos = 0;
|
||||
while (pos < b.size()) {
|
||||
size_t end = b.find("\n\n", pos);
|
||||
std::string chunk = (end == std::string::npos)
|
||||
? b.substr(pos)
|
||||
: b.substr(pos, end - pos);
|
||||
body.push_back(paragraph(chunk));
|
||||
body.push_back(text("")); // blank line between paragraphs
|
||||
if (end == std::string::npos) break;
|
||||
pos = end + 2;
|
||||
}
|
||||
|
||||
auto left = vbox({
|
||||
text(" topic ") | bold,
|
||||
separator(),
|
||||
topic_menu->Render() | yframe | flex,
|
||||
}) | size(WIDTH, EQUAL, 28);
|
||||
|
||||
auto center = vbox(std::move(body))
|
||||
| vscroll_indicator | yframe | flex;
|
||||
|
||||
Element help = RenderHelpPanel("help", {
|
||||
{"↑/↓", "pick topic"},
|
||||
{"Ctrl-P", "palette"},
|
||||
{"Esc", "dashboard"},
|
||||
});
|
||||
|
||||
return vbox({
|
||||
title,
|
||||
separator(),
|
||||
hbox({left, separator(), center, separator(), help}) | flex,
|
||||
}) | border;
|
||||
});
|
||||
}
|
||||
@@ -1,10 +1,6 @@
|
||||
#include "tui/tui.hpp"
|
||||
#include "tui/tui_helpers.hpp"
|
||||
|
||||
#include "system/connect.hpp"
|
||||
#include "system/modules.hpp"
|
||||
#include "system/system.hpp"
|
||||
|
||||
#include <ftxui/component/component.hpp>
|
||||
#include <ftxui/component/component_options.hpp>
|
||||
#include <ftxui/component/screen_interactive.hpp>
|
||||
@@ -27,17 +23,15 @@ Component Tui::BuildMainScreen(ScreenInteractive &screen) {
|
||||
return Renderer(input_component, [this, &screen, input_component] {
|
||||
if (quit) screen.Exit();
|
||||
|
||||
std::string subtitle = sys
|
||||
? std::to_string(sys->modules()->size()) + " module(s), "
|
||||
+ std::to_string(sys->connections()->size()) + " connection(s)"
|
||||
: "no system loaded";
|
||||
// Same title pattern as every other screen — the console is no
|
||||
// longer the home screen, so it gets the standard breadcrumb. The
|
||||
// module/connection counters live on the dashboard now.
|
||||
auto title = hbox({
|
||||
text(" essim ") | bold,
|
||||
text("— system digital twin") | dim,
|
||||
filler(),
|
||||
text(subtitle) | dim,
|
||||
text(" "),
|
||||
}) | bgcolor(Color::Default);
|
||||
text("→ ") | dim,
|
||||
text("console") | bold,
|
||||
text(" — type commands, read textual output") | dim,
|
||||
});
|
||||
|
||||
// Clamp scroll offset to a meaningful range and pick the line to focus.
|
||||
int n = (int)output.size();
|
||||
|
||||
@@ -26,9 +26,24 @@ Component Tui::BuildSettypeScreen() {
|
||||
return el;
|
||||
};
|
||||
|
||||
auto module_menu = Menu(&settype_modules, &settype_m_idx);
|
||||
// Bind `focused_entry` to the same int as `selected` on every Menu so
|
||||
// the cursor (`>`) always tracks the selected row. Without this, when
|
||||
// we pre-seed `settype_m_idx` / `settype_p_idx` from outside (e.g.
|
||||
// jumping from `explore`), the highlight lands on the target while the
|
||||
// cursor stays on whatever the user last hovered → visually confusing.
|
||||
MenuOption mopt = MenuOption::Vertical();
|
||||
mopt.entries = &settype_modules;
|
||||
mopt.selected = &settype_m_idx;
|
||||
mopt.focused_entry = &settype_m_idx;
|
||||
auto module_menu = Menu(mopt);
|
||||
|
||||
auto part_filter = Input(&settype_p_filter, "filter…", pf_opt);
|
||||
auto part_menu = Menu(&settype_p_list, &settype_p_idx);
|
||||
|
||||
MenuOption popt = MenuOption::Vertical();
|
||||
popt.entries = &settype_p_list;
|
||||
popt.selected = &settype_p_idx;
|
||||
popt.focused_entry = &settype_p_idx;
|
||||
auto part_menu = Menu(popt);
|
||||
|
||||
InputOption type_opt;
|
||||
type_opt.multiline = false;
|
||||
@@ -114,13 +129,13 @@ Component Tui::BuildSettypeScreen() {
|
||||
text(" " + current),
|
||||
separator(),
|
||||
FocusLabel(text(" new type: "), settype_focus_idx == 3) | bold,
|
||||
hbox({text(" "), type_input->Render() | flex}) | border,
|
||||
hbox({text(" "), type_input->Render() | xflex}) | border,
|
||||
text(known_line) | dim,
|
||||
filler(),
|
||||
hbox({filler(),
|
||||
FocusLabel(button->Render(), settype_focus_idx == 4),
|
||||
filler()}),
|
||||
}) | size(WIDTH, EQUAL, 40);
|
||||
}) | flex;
|
||||
|
||||
Element status = settype_status.empty()
|
||||
? text("") | dim
|
||||
@@ -144,7 +159,7 @@ Component Tui::BuildSettypeScreen() {
|
||||
return vbox({
|
||||
title,
|
||||
separator(),
|
||||
hbox({left, separator(), middle, separator(), right | flex,
|
||||
hbox({left, separator(), middle, separator(), right,
|
||||
separator(), help}) | flex,
|
||||
separator(),
|
||||
status,
|
||||
|
||||
@@ -41,10 +41,11 @@ void Tui::Run() {
|
||||
&sigtype_dialog_open);
|
||||
auto dashboard_screen = BuildDashboardScreen();
|
||||
auto analyze_screen = BuildAnalyzeScreen();
|
||||
auto help_screen = BuildHelpScreen();
|
||||
|
||||
auto tab = Container::Tab(
|
||||
{main_screen, connect_screen, settype_screen, explore_screen,
|
||||
dashboard_screen, analyze_screen},
|
||||
dashboard_screen, analyze_screen, help_screen},
|
||||
&screen_idx);
|
||||
|
||||
// Palette is a global Modal — overlays the tab on every screen.
|
||||
@@ -58,8 +59,12 @@ void Tui::Run() {
|
||||
if (e == Event::CtrlP) { OpenPalette(); return true; }
|
||||
|
||||
// screen_idx mapping: 0 = console, 1 = connect, 2 = set-connector-type,
|
||||
// 3 = explore, 4 = dashboard (home), 5 = analyze.
|
||||
// 3 = explore, 4 = dashboard (home), 5 = analyze, 6 = help.
|
||||
switch (screen_idx) {
|
||||
case 6: // help
|
||||
if (e == Event::Escape) { screen_idx = 4; return true; }
|
||||
return false;
|
||||
|
||||
case 5: // analyze
|
||||
if (e == Event::Escape) { screen_idx = 4; return true; }
|
||||
if (e == Event::Tab || e == Event::ArrowRight) {
|
||||
@@ -84,9 +89,9 @@ void Tui::Run() {
|
||||
if (e == Event::Character("q")) { Dispatch("quit"); return true; }
|
||||
if (e == Event::Character("c")) { screen_idx = 0; return true; }
|
||||
if (e == Event::Character("p")) { Dispatch("connect"); return true; }
|
||||
if (e == Event::Character("t")) { Dispatch("set-connector-type"); return true; }
|
||||
if (e == Event::Character("e")) { Dispatch("explore"); return true; }
|
||||
if (e == Event::Character("a")) { screen_idx = 5; return true; }
|
||||
if (e == Event::Character("h")) { screen_idx = 6; return true; }
|
||||
return false;
|
||||
|
||||
case 3: // explore
|
||||
@@ -96,7 +101,18 @@ void Tui::Run() {
|
||||
return false;
|
||||
|
||||
case 2: // set-connector-type
|
||||
if (e == Event::Escape) { screen_idx = 4; return true; }
|
||||
if (e == Event::Escape) {
|
||||
// Honour an inter-screen back-link (e.g. came via Enter on
|
||||
// a part in `explore`). Otherwise fall through to the
|
||||
// dashboard like every other Esc.
|
||||
if (screen_back_idx >= 0) {
|
||||
screen_idx = screen_back_idx;
|
||||
screen_back_idx = -1;
|
||||
} else {
|
||||
screen_idx = 4;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (e == Event::Tab) { settype_focus_idx = (settype_focus_idx + 1) % 5; return true; }
|
||||
if (e == Event::TabReverse) { settype_focus_idx = (settype_focus_idx + 4) % 5; return true; }
|
||||
return false;
|
||||
|
||||
@@ -55,6 +55,11 @@ class Tui {
|
||||
|
||||
// ---- Screen orchestration ----
|
||||
int screen_idx;
|
||||
// Where Esc should send the user *next* if they came via an inter-screen
|
||||
// jump (e.g. Enter on a part in `explore` → set-connector-type). −1 means
|
||||
// "no back-link, Esc goes to the dashboard like usual". Always reset
|
||||
// after consumption to avoid stale links across unrelated navigation.
|
||||
int screen_back_idx = -1;
|
||||
|
||||
// ---- Connect screen state ----
|
||||
std::vector<std::string> connect_modules;
|
||||
@@ -97,6 +102,10 @@ class Tui {
|
||||
// ---- Dashboard scroll state (0 = top; grows as the user scrolls down) ----
|
||||
int dashboard_scroll_offset = 0;
|
||||
|
||||
// ---- Help screen state ----
|
||||
int help_topic_idx = 0;
|
||||
std::vector<std::string> help_topic_names; ///< populated by BuildHelpScreen
|
||||
|
||||
// ---- Analyze screen state (unified verify + analyze) ----
|
||||
int analyze_focus_idx = 0; ///< 0=issues 1=groups 2=types
|
||||
std::vector<std::string> analyze_issues;
|
||||
@@ -186,6 +195,7 @@ private:
|
||||
ftxui::Component BuildExploreScreen();
|
||||
ftxui::Component BuildDashboardScreen();
|
||||
ftxui::Component BuildAnalyzeScreen();
|
||||
ftxui::Component BuildHelpScreen();
|
||||
ftxui::Component BuildSignalTypeModal();
|
||||
ftxui::Component BuildPaletteModal();
|
||||
// Open palette (resets query/index, builds initial list).
|
||||
|
||||
Reference in New Issue
Block a user