Interactive net screen, main + per-screen title bars, focus highlight, help split.
- New `net` full-screen layout (`screen_net.cpp`, `screen_idx = 5`): three columns (module menu / signal filter + menu / live BFS result). Bare `net` opens the screen; `net <m> <s>` keeps the inline path. - Main screen grows a title bar: " essim — system digital twin " (bold + dim) on the left, live "N module(s), M connection(s)" on the right. - Every interactive screen now renders the same breadcrumb at the top: " essim → <name> — <short description> ", followed by a separator. - `tui_helpers.hpp` exports `FocusLabel(elem, focused)`. Every interactive screen wraps its field labels with it so the active field's label flips to inverted video. Buttons (Connect, Apply) invert as a whole. - `CommandSpec` gains a `bool interactive`. `help` (no args) splits the listing into "Interactive (open a full-screen mode)" and "Other". `help <name>` tags interactive entries with [interactive] and explains the bare-vs-inline duality. - `DESIGN.md` (renamed from `CLAUDE.md`): refreshed Layout, TUI, and screen-recipe sections to cover the new field, the title idiom, FocusLabel, the `net` screen, and the event-paced `Computing…` modal during `source`. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -25,15 +25,23 @@ void Tui::RegisterCommands() {
|
||||
{{"command name (optional)", Completion::Command}},
|
||||
[this](const std::vector<std::string> &args) {
|
||||
if (args.empty()) {
|
||||
Print("Commands — type `help <name>` for details.");
|
||||
size_t maxw = 0;
|
||||
for (const auto &kv : commands) maxw = std::max(maxw, kv.first.size());
|
||||
for (const auto &kv : commands) {
|
||||
Print(" " + kv.first
|
||||
+ std::string(maxw - kv.first.size() + 2, ' ')
|
||||
+ kv.second.description);
|
||||
}
|
||||
Print("Keys: Esc cancels a multi-step prompt; Tab completes commands or paths;");
|
||||
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.");
|
||||
return;
|
||||
}
|
||||
@@ -41,7 +49,8 @@ void Tui::RegisterCommands() {
|
||||
auto it = commands.find(name);
|
||||
if (it == commands.end()) { Print("unknown command: " + name); return; }
|
||||
const auto &spec = it->second;
|
||||
Print(name + " — " + spec.description);
|
||||
std::string tag = spec.interactive ? " [interactive]" : "";
|
||||
Print(name + tag + " — " + spec.description);
|
||||
if (spec.params.empty()) {
|
||||
Print(" no arguments.");
|
||||
} else {
|
||||
@@ -49,8 +58,11 @@ void Tui::RegisterCommands() {
|
||||
Print(" arg " + std::to_string(i + 1) + ": " + spec.params[i].name);
|
||||
}
|
||||
}
|
||||
if (!spec.prompt_for_missing) {
|
||||
Print(" run with no args for the interactive form.");
|
||||
if (spec.interactive) {
|
||||
Print(" run with no args to open the interactive screen,");
|
||||
Print(" or with all args for inline (scriptable) execution.");
|
||||
} else if (!spec.prompt_for_missing) {
|
||||
Print(" no per-arg prompt — provide all args inline (or use the bare form).");
|
||||
} else if (!spec.params.empty()) {
|
||||
Print(" missing args trigger a prompt for each one.");
|
||||
}
|
||||
@@ -245,6 +257,25 @@ void Tui::RegisterCommands() {
|
||||
{"signal name", Completion::None}},
|
||||
[this](const std::vector<std::string> &args) {
|
||||
if (!sys) { Print("no system: run 'new' first."); return; }
|
||||
|
||||
if (args.empty()) {
|
||||
net_modules.clear();
|
||||
for (auto &m : *sys->modules()) net_modules.push_back(m.first);
|
||||
std::sort(net_modules.begin(), net_modules.end(), NaturalLess);
|
||||
if (net_modules.empty()) { Print("no modules loaded."); return; }
|
||||
net_module_idx = 0;
|
||||
net_sig_filter.clear();
|
||||
net_sig_idx = 0;
|
||||
net_focus_idx = 0;
|
||||
screen_idx = 5;
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.size() != 2) {
|
||||
Print("usage: net <module> <signal> (or no args for interactive)");
|
||||
return;
|
||||
}
|
||||
|
||||
Module *mod;
|
||||
try { mod = sys->modules()->get(args[0]); }
|
||||
catch (const std::exception &) {
|
||||
@@ -267,8 +298,11 @@ void Tui::RegisterCommands() {
|
||||
+ " (" + signal_type_name(mp.second->type) + ")");
|
||||
}
|
||||
},
|
||||
/*prompt_for_missing=*/ true,
|
||||
"show all signals reachable from <module>/<signal> through connections",
|
||||
/*prompt_for_missing=*/ false,
|
||||
"show all signals reachable from <module>/<signal> through connections "
|
||||
"(interactive screen if no args)",
|
||||
/*scriptable=*/ true,
|
||||
/*interactive=*/ true,
|
||||
};
|
||||
|
||||
commands["set-signal-type"] = {
|
||||
@@ -364,6 +398,8 @@ void Tui::RegisterCommands() {
|
||||
},
|
||||
/*prompt_for_missing=*/ false,
|
||||
"tag a part's connector type for transform lookup",
|
||||
/*scriptable=*/ true,
|
||||
/*interactive=*/ true,
|
||||
};
|
||||
|
||||
commands["connect"] = {
|
||||
@@ -498,6 +534,8 @@ void Tui::RegisterCommands() {
|
||||
},
|
||||
/*prompt_for_missing=*/ false,
|
||||
"connect a part across two modules (interactive screen if no args)",
|
||||
/*scriptable=*/ true,
|
||||
/*interactive=*/ true,
|
||||
};
|
||||
|
||||
commands["explore"] = { {}, [this](auto &) {
|
||||
@@ -515,7 +553,8 @@ void Tui::RegisterCommands() {
|
||||
explore_focus_idx = 0;
|
||||
screen_idx = 4;
|
||||
}, true, "browse modules → parts/signals/connections → details (interactive)",
|
||||
/*scriptable=*/ false };
|
||||
/*scriptable=*/ false,
|
||||
/*interactive=*/ true };
|
||||
|
||||
commands["search"] = {
|
||||
{{"module", Completion::None},
|
||||
@@ -575,6 +614,8 @@ void Tui::RegisterCommands() {
|
||||
},
|
||||
/*prompt_for_missing=*/ false,
|
||||
"list parts/signals matching a pattern (interactive screen if no args)",
|
||||
/*scriptable=*/ true,
|
||||
/*interactive=*/ true,
|
||||
};
|
||||
|
||||
commands["duplicate"] = {
|
||||
|
||||
@@ -120,26 +120,39 @@ Component Tui::BuildConnectScreen() {
|
||||
connect_p2_filter, connect_p2_list, connect_p2_idx);
|
||||
|
||||
auto col = [&](const std::string &title,
|
||||
Component mm, Component pf, Component pm) {
|
||||
Component mm, Component pf, Component pm,
|
||||
int m_focus, int f_focus, int p_focus) {
|
||||
return vbox({
|
||||
text(title) | bold,
|
||||
text("module") | dim,
|
||||
FocusLabel(text(" module "), connect_focus_idx == m_focus) | dim,
|
||||
mm->Render() | yframe | size(HEIGHT, LESS_THAN, 8),
|
||||
separator(),
|
||||
hbox({text(" filter: "), pf->Render() | flex}) | border,
|
||||
text("part") | dim,
|
||||
hbox({FocusLabel(text(" filter: "), connect_focus_idx == f_focus),
|
||||
pf->Render() | flex}) | border,
|
||||
FocusLabel(text(" part "), connect_focus_idx == p_focus) | dim,
|
||||
pm->Render() | yframe | flex,
|
||||
}) | flex;
|
||||
};
|
||||
|
||||
auto title = hbox({
|
||||
text(" essim ") | bold,
|
||||
text("→ ") | dim,
|
||||
text("connect") | bold,
|
||||
text(" — wire two parts across modules (TransformRegistry-driven)") | dim,
|
||||
});
|
||||
|
||||
return vbox({
|
||||
title,
|
||||
separator(),
|
||||
hbox({
|
||||
col("endpoint 1", m1_menu, p1_filter, p1_menu),
|
||||
col("endpoint 1", m1_menu, p1_filter, p1_menu, 0, 1, 2),
|
||||
separator(),
|
||||
col("endpoint 2", m2_menu, p2_filter, p2_menu),
|
||||
col("endpoint 2", m2_menu, p2_filter, p2_menu, 3, 4, 5),
|
||||
}) | flex,
|
||||
separator(),
|
||||
hbox({filler(), connect_button->Render(), filler()}),
|
||||
hbox({filler(),
|
||||
FocusLabel(connect_button->Render(), connect_focus_idx == 6),
|
||||
filler()}),
|
||||
text(" Tab: cycle focus | Enter on [Connect]: confirm | Esc: leave ") | dim,
|
||||
}) | border;
|
||||
});
|
||||
|
||||
@@ -171,33 +171,49 @@ Component Tui::BuildExploreScreen() {
|
||||
}
|
||||
|
||||
auto col1 = vbox({
|
||||
text("module") | bold,
|
||||
FocusLabel(text(" module "), explore_focus_idx == 0) | bold,
|
||||
module_menu->Render() | yframe | flex,
|
||||
}) | size(WIDTH, EQUAL, 24);
|
||||
|
||||
auto col2 = vbox({
|
||||
text("type") | bold,
|
||||
FocusLabel(text(" type "), explore_focus_idx == 1) | bold,
|
||||
type_menu->Render() | flex,
|
||||
}) | size(WIDTH, EQUAL, 12);
|
||||
|
||||
auto col3 = vbox({
|
||||
text(explore_types[explore_type_idx]) | bold,
|
||||
hbox({text(" filter: "), child_filter->Render() | flex}) | border,
|
||||
FocusLabel(text(" " + explore_types[explore_type_idx] + " "),
|
||||
explore_focus_idx == 3) | bold,
|
||||
hbox({FocusLabel(text(" filter: "), explore_focus_idx == 2),
|
||||
child_filter->Render() | flex}) | border,
|
||||
children_menu->Render() | yframe | flex,
|
||||
}) | size(WIDTH, EQUAL, 36);
|
||||
|
||||
auto col4 = vbox({
|
||||
text(explore_header) | bold,
|
||||
hbox({text(" filter: "), detail_filter->Render() | flex}) | border,
|
||||
FocusLabel(text(" " + explore_header + " "),
|
||||
explore_focus_idx == 5) | bold,
|
||||
hbox({FocusLabel(text(" filter: "), explore_focus_idx == 4),
|
||||
detail_filter->Render() | flex}) | border,
|
||||
detail_menu->Render() | vscroll_indicator | yframe | flex,
|
||||
}) | flex;
|
||||
|
||||
auto title = hbox({
|
||||
text(" essim ") | bold,
|
||||
text("→ ") | dim,
|
||||
text("explore") | bold,
|
||||
text(" — browse modules → parts/signals/connections → details") | dim,
|
||||
});
|
||||
|
||||
return vbox({
|
||||
title,
|
||||
separator(),
|
||||
hbox({col1, separator(), col2, separator(), col3, separator(), col4}) | flex,
|
||||
text(" Tab: cycle focus (incl. detail to scroll) | Esc: leave explore ") | dim,
|
||||
}) | border;
|
||||
} catch (const std::exception &e) {
|
||||
return vbox({
|
||||
hbox({text(" essim ") | bold, text("→ ") | dim,
|
||||
text("explore") | bold}),
|
||||
separator(),
|
||||
text("explore: render error") | bold,
|
||||
text(std::string(" ") + e.what()) | dim,
|
||||
text(" Esc: leave explore ") | dim,
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
#include "tui/tui.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>
|
||||
@@ -22,6 +26,18 @@ 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";
|
||||
auto title = hbox({
|
||||
text(" essim ") | bold,
|
||||
text("— system digital twin") | dim,
|
||||
filler(),
|
||||
text(subtitle) | dim,
|
||||
text(" "),
|
||||
}) | bgcolor(Color::Default);
|
||||
|
||||
// Clamp scroll offset to a meaningful range and pick the line to focus.
|
||||
int n = (int)output.size();
|
||||
if (scroll_offset < 0) scroll_offset = 0;
|
||||
@@ -50,6 +66,8 @@ Component Tui::BuildMainScreen(ScreenInteractive &screen) {
|
||||
: "";
|
||||
|
||||
auto base = vbox({
|
||||
title,
|
||||
separator(),
|
||||
view,
|
||||
separator(),
|
||||
hbox({text(label), input_component->Render(), filler(), text(status) | dim}),
|
||||
|
||||
119
src/tui/screen_net.cpp
Normal file
119
src/tui/screen_net.cpp
Normal file
@@ -0,0 +1,119 @@
|
||||
#include "tui/tui.hpp"
|
||||
#include "tui/tui_helpers.hpp"
|
||||
|
||||
#include "system/modules.hpp"
|
||||
#include "system/nets.hpp"
|
||||
#include "system/signals.hpp"
|
||||
#include "system/system.hpp"
|
||||
|
||||
#include <ftxui/component/component.hpp>
|
||||
#include <ftxui/component/component_options.hpp>
|
||||
#include <ftxui/dom/elements.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <exception>
|
||||
|
||||
using namespace ftxui;
|
||||
|
||||
Component Tui::BuildNetScreen() {
|
||||
InputOption filter_opt;
|
||||
filter_opt.multiline = false;
|
||||
filter_opt.transform = [](InputState s) {
|
||||
auto el = s.element;
|
||||
if (s.is_placeholder) el |= dim;
|
||||
return el;
|
||||
};
|
||||
auto filter_input = Input(&net_sig_filter, "filter signals…", filter_opt);
|
||||
auto module_menu = Menu(&net_modules, &net_module_idx);
|
||||
auto signal_menu = Menu(&net_sigs, &net_sig_idx);
|
||||
|
||||
auto components = Container::Vertical(
|
||||
{filter_input, module_menu, signal_menu}, &net_focus_idx);
|
||||
|
||||
return Renderer(components,
|
||||
[this, filter_input, module_menu, signal_menu] {
|
||||
// Rebuild filtered signal list for current module + filter.
|
||||
net_sigs.clear();
|
||||
Module *cur_mod = nullptr;
|
||||
if (sys && !net_modules.empty()) {
|
||||
try { cur_mod = sys->modules()->get(net_modules[net_module_idx]); }
|
||||
catch (const std::exception &) {}
|
||||
}
|
||||
if (cur_mod) {
|
||||
std::string needle = ToLower(net_sig_filter);
|
||||
for (auto &skv : *cur_mod->signals) {
|
||||
if (needle.empty()
|
||||
|| ToLower(skv.first).find(needle) != std::string::npos)
|
||||
net_sigs.push_back(skv.first);
|
||||
}
|
||||
std::sort(net_sigs.begin(), net_sigs.end(), NaturalLess);
|
||||
}
|
||||
if (net_sig_idx >= (int)net_sigs.size())
|
||||
net_sig_idx = std::max(0, (int)net_sigs.size() - 1);
|
||||
|
||||
// Compute the net for the current selection.
|
||||
Net net;
|
||||
SignalType dom = SignalType::Other;
|
||||
bool consistent = true;
|
||||
if (cur_mod && !net_sigs.empty()) {
|
||||
try {
|
||||
Signal *sig = cur_mod->signals->get(net_sigs[net_sig_idx]);
|
||||
net = find_net(sys.get(), cur_mod, sig);
|
||||
consistent = net_type_consistent(net, dom);
|
||||
} catch (const std::exception &) {}
|
||||
}
|
||||
|
||||
Elements member_lines;
|
||||
for (const auto &mp : net.members)
|
||||
member_lines.push_back(text(
|
||||
" " + mp.first->name + "/" + mp.second->name
|
||||
+ " (" + signal_type_name(mp.second->type) + ")"));
|
||||
|
||||
std::string header;
|
||||
if (cur_mod && !net_sigs.empty()) {
|
||||
header = cur_mod->name + "/" + net_sigs[net_sig_idx]
|
||||
+ " — " + std::to_string(net.members.size()) + " signal(s)"
|
||||
+ (consistent ? "" : " [INCONSISTENT]")
|
||||
+ " / dominant: " + signal_type_name(dom);
|
||||
} else {
|
||||
header = "(select a module + signal)";
|
||||
}
|
||||
|
||||
auto left = vbox({
|
||||
FocusLabel(text(" module "), net_focus_idx == 1) | bold,
|
||||
module_menu->Render() | yframe | flex,
|
||||
}) | size(WIDTH, EQUAL, 24);
|
||||
|
||||
auto filter_row = hbox({
|
||||
FocusLabel(text(" filter: "), net_focus_idx == 0),
|
||||
filter_input->Render() | flex,
|
||||
}) | border;
|
||||
|
||||
auto middle = vbox({
|
||||
filter_row,
|
||||
FocusLabel(text(" signal "), net_focus_idx == 2) | bold,
|
||||
text(std::to_string(net_sigs.size()) + " signal(s)") | dim,
|
||||
signal_menu->Render() | yframe | flex,
|
||||
}) | size(WIDTH, EQUAL, 32);
|
||||
|
||||
auto right = vbox({
|
||||
text(header) | bold,
|
||||
separator(),
|
||||
vbox(std::move(member_lines)) | yframe | flex,
|
||||
}) | flex;
|
||||
|
||||
auto title = hbox({
|
||||
text(" essim ") | bold,
|
||||
text("→ ") | dim,
|
||||
text("net") | bold,
|
||||
text(" — BFS of (module, signal) bridged through connections") | dim,
|
||||
});
|
||||
|
||||
return vbox({
|
||||
title,
|
||||
separator(),
|
||||
hbox({left, separator(), middle, separator(), right}) | flex,
|
||||
text(" Tab: cycle focus (filter ↔ module ↔ signal) | Esc: leave net ") | dim,
|
||||
}) | border;
|
||||
});
|
||||
}
|
||||
@@ -62,20 +62,30 @@ Component Tui::BuildSearchScreen() {
|
||||
text(" " + h.first + " (" + std::to_string(h.second) + " pins)"));
|
||||
|
||||
auto left = vbox({
|
||||
text("module") | bold,
|
||||
FocusLabel(text(" module "), search_focus_idx == 1) | bold,
|
||||
module_menu->Render() | yframe | flex,
|
||||
separator(),
|
||||
text("type") | bold,
|
||||
FocusLabel(text(" type "), search_focus_idx == 2) | bold,
|
||||
type_menu->Render(),
|
||||
}) | size(WIDTH, EQUAL, 28);
|
||||
|
||||
auto right = vbox({
|
||||
hbox({text(" search: "), query_input->Render() | flex}) | border,
|
||||
hbox({FocusLabel(text(" search: "), search_focus_idx == 0),
|
||||
query_input->Render() | flex}) | border,
|
||||
text(std::to_string(hits.size()) + " match(es)") | dim,
|
||||
vbox(std::move(result_lines)) | yframe | flex,
|
||||
}) | flex;
|
||||
|
||||
auto title = hbox({
|
||||
text(" essim ") | bold,
|
||||
text("→ ") | dim,
|
||||
text("search") | bold,
|
||||
text(" — filter parts and signals by pattern") | dim,
|
||||
});
|
||||
|
||||
return vbox({
|
||||
title,
|
||||
separator(),
|
||||
hbox({left, separator(), right}) | flex,
|
||||
text(" Tab: cycle focus | Esc: leave search ") | dim,
|
||||
}) | border;
|
||||
|
||||
@@ -98,13 +98,14 @@ Component Tui::BuildSettypeScreen() {
|
||||
else for (const auto &k : known_types) known_line += " " + k;
|
||||
|
||||
auto left = vbox({
|
||||
text("module") | bold,
|
||||
FocusLabel(text(" module "), settype_focus_idx == 0) | bold,
|
||||
module_menu->Render() | yframe | flex,
|
||||
}) | size(WIDTH, EQUAL, 28);
|
||||
|
||||
auto middle = vbox({
|
||||
hbox({text(" filter: "), part_filter->Render() | flex}) | border,
|
||||
text("part") | dim,
|
||||
hbox({FocusLabel(text(" filter: "), settype_focus_idx == 1),
|
||||
part_filter->Render() | flex}) | border,
|
||||
FocusLabel(text(" part "), settype_focus_idx == 2) | dim,
|
||||
part_menu->Render() | yframe | flex,
|
||||
}) | flex;
|
||||
|
||||
@@ -112,18 +113,29 @@ Component Tui::BuildSettypeScreen() {
|
||||
text("current type: ") | bold,
|
||||
text(" " + current),
|
||||
separator(),
|
||||
text("new type:") | bold,
|
||||
FocusLabel(text(" new type: "), settype_focus_idx == 3) | bold,
|
||||
hbox({text(" "), type_input->Render() | flex}) | border,
|
||||
text(known_line) | dim,
|
||||
filler(),
|
||||
hbox({filler(), button->Render(), filler()}),
|
||||
hbox({filler(),
|
||||
FocusLabel(button->Render(), settype_focus_idx == 4),
|
||||
filler()}),
|
||||
}) | size(WIDTH, EQUAL, 40);
|
||||
|
||||
Element status = settype_status.empty()
|
||||
? text("") | dim
|
||||
: text(" " + settype_status) | bold;
|
||||
|
||||
auto title = hbox({
|
||||
text(" essim ") | bold,
|
||||
text("→ ") | dim,
|
||||
text("set-type") | bold,
|
||||
text(" — tag a part with its connector kind (drives transforms + pin roles)") | dim,
|
||||
});
|
||||
|
||||
return vbox({
|
||||
title,
|
||||
separator(),
|
||||
hbox({left, separator(), middle, separator(), right}) | flex,
|
||||
separator(),
|
||||
status,
|
||||
|
||||
@@ -23,6 +23,7 @@ Tui::Tui()
|
||||
explore_types{"parts", "signals", "connections"},
|
||||
explore_type_idx(0), explore_child_idx(0),
|
||||
explore_detail_idx(0), explore_focus_idx(0),
|
||||
net_module_idx(0), net_sig_idx(0), net_focus_idx(0),
|
||||
settype_m_idx(0), settype_p_idx(0), settype_focus_idx(0)
|
||||
{
|
||||
LoadHistory();
|
||||
@@ -41,13 +42,21 @@ void Tui::Run() {
|
||||
auto connect_screen = BuildConnectScreen();
|
||||
auto settype_screen = BuildSettypeScreen();
|
||||
auto explore_screen = BuildExploreScreen();
|
||||
auto net_screen = BuildNetScreen();
|
||||
|
||||
auto tab = Container::Tab(
|
||||
{main_screen, search_screen, connect_screen, settype_screen, explore_screen},
|
||||
{main_screen, search_screen, connect_screen, settype_screen, explore_screen,
|
||||
net_screen},
|
||||
&screen_idx);
|
||||
|
||||
auto root = CatchEvent(tab, [this](Event e) {
|
||||
switch (screen_idx) {
|
||||
case 5: // net
|
||||
if (e == Event::Escape) { screen_idx = 0; return true; }
|
||||
if (e == Event::Tab) { net_focus_idx = (net_focus_idx + 1) % 3; return true; }
|
||||
if (e == Event::TabReverse) { net_focus_idx = (net_focus_idx + 2) % 3; return true; }
|
||||
return false;
|
||||
|
||||
case 4: // explore
|
||||
if (e == Event::Escape) { screen_idx = 0; return true; }
|
||||
if (e == Event::Tab) { explore_focus_idx = (explore_focus_idx + 1) % 6; return true; }
|
||||
|
||||
@@ -33,6 +33,7 @@ class Tui {
|
||||
bool prompt_for_missing = true;
|
||||
std::string description;
|
||||
bool scriptable = true;
|
||||
bool interactive = false; ///< opens a full-screen mode when called bare
|
||||
};
|
||||
|
||||
// ---- Shell state ----
|
||||
@@ -99,6 +100,14 @@ class Tui {
|
||||
bool loading_prev_in_source;
|
||||
ftxui::ScreenInteractive *screen_ptr; ///< set in Run() so Source() can post events.
|
||||
|
||||
// ---- Net screen state ----
|
||||
std::vector<std::string> net_modules;
|
||||
int net_module_idx;
|
||||
std::string net_sig_filter;
|
||||
std::vector<std::string> net_sigs; ///< rebuilt every frame from filter
|
||||
int net_sig_idx;
|
||||
int net_focus_idx;
|
||||
|
||||
// ---- Set-type screen state ----
|
||||
std::vector<std::string> settype_modules;
|
||||
int settype_m_idx;
|
||||
@@ -152,6 +161,7 @@ private:
|
||||
ftxui::Component BuildConnectScreen();
|
||||
ftxui::Component BuildSettypeScreen();
|
||||
ftxui::Component BuildExploreScreen();
|
||||
ftxui::Component BuildNetScreen();
|
||||
};
|
||||
|
||||
#endif // _TUI_HPP_
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
#ifndef _TUI_HELPERS_HPP_
|
||||
#define _TUI_HELPERS_HPP_
|
||||
|
||||
#include <ftxui/dom/elements.hpp>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// Free helpers shared across the TUI translation units.
|
||||
|
||||
// Highlight the label of a focused field in interactive screens. Used so the
|
||||
// user can see at a glance which field will receive their next keystroke.
|
||||
inline ftxui::Element FocusLabel(ftxui::Element e, bool focused) {
|
||||
return focused ? (e | ftxui::inverted) : e;
|
||||
}
|
||||
|
||||
std::string ToLower(std::string s);
|
||||
|
||||
// Case-insensitive natural-order comparison: digit runs compared as integers,
|
||||
|
||||
Reference in New Issue
Block a user