ui: the Source badge, and the source panels as slide-over pages
This commit is contained in:
1 parent
5fb479bd69
commit
31ff778e07
6 files changed
+101
-28
No files matched your search
@@ -439,6 +439,15 @@ precedent, not an exception.
|
||||
a slide-over page**, so a probe that reads one of its widgets' *allocation* (a width, a height) must
|
||||
`slideover_open(*a, "trigger")` and read after the 120 ms reveal, as `exit_probe_step1` and `force_probe` do;
|
||||
a sensitivity, a visibility or a value needs no open page.
|
||||
**The source kind's panels are slide-over pages too** (`source-<id>`: the E-1608's `source-ao` and
|
||||
`source-dio`, Flat UDP's, the probe kind's), so the same rule holds for them. The probe appends a second
|
||||
`[badges]` line for the Source badge: `[badges] source name=<kind> state=<connected|disconnected> pages=<n>
|
||||
switch_visible=<0|1> can_target=<0|1> second_page=<id|-> second_ok=<0|1> open_marked=<0|1|-1>
|
||||
closed_unmarked=1`. `pages` is the number of source pages, `switch_visible` is the segmented switch
|
||||
being on screen after the first source page was opened (`1` for the E-1608's two pages, `0` for one page
|
||||
or none), `second_page` is what `slideover_page()` read after the switch selected the second page
|
||||
(`second_ok=1` when it is that page), `can_target=0` with `pages=0` is the UDPS badge being a read-out and
|
||||
`open_marked=-1` is that no-page case (there is nothing to open).
|
||||
- `UDPSCOPE_PROBE_SOURCE_KINDS=2000` with `UDPSCOPE_EXIT_AFTER_MS=5000`, run as `--source probe --source-opt rate=500`
|
||||
-- the source-plugin path end to end. The variable's presence also registers the test-only `probe` kind (a
|
||||
synthetic sine with one panel), so it is the only way a run sees that kind; its value is the delay in ms.
|
||||
@@ -461,8 +470,8 @@ precedent, not an exception.
|
||||
- `UDPSCOPE_PROBE_E1608_GEN=1500` with `UDPSCOPE_EXIT_AFTER_MS=5500`, run as `--source e1608 --source-opt
|
||||
address=127.0.0.1 --source-opt port=54711 --source-opt ao0_init=0 --source-opt gen_rate=100` against
|
||||
`tools/mock_e1608.py --port 54711 --log /tmp/e1608-gen100.log` -- the generator's update rate: it starts a Sine on
|
||||
DAC 0 through the Analog out panel (and switches the side stack to that page, since the panel refreshes on its
|
||||
frame clock, which does not tick for a page that is off screen), waits 2.5 s, reads the achieved rate and the
|
||||
DAC 0 through the Analog out panel (and opens the `source-ao` slide-over page, since the panel refreshes on its
|
||||
frame clock, which does not tick for a page that is off screen; the page is closed again at the end), waits 2.5 s, reads the achieved rate and the
|
||||
panel's "Update rate" row, stops the generator and reads both again 400 ms later. Two lines, in
|
||||
`probe-readings.md`. Then count the mock log's `AOUT` lines: about 250 with `gen_rate=100` (the generator ran
|
||||
for about 2.5 s) and about 125 for a run without the option (default 50 Hz). Short and serial, the mock killed
|
||||
|
||||
+3
-2
@@ -5072,8 +5072,6 @@ void on_activate(GtkApplication* application, gpointer user) {
|
||||
}
|
||||
// The slide-over panel: an overlay child of the plot, above the GL area.
|
||||
build_slideover(*app);
|
||||
// The badge bar reads `trigger_toggle`, which `build_toolbar()` made above.
|
||||
build_badge_bar(*app);
|
||||
|
||||
// The size the window opens at, stated in code as well as in the `.ui`: `default-width` there is the
|
||||
// size it *asks* for, and a window whose content has a larger minimum opens at that minimum instead --
|
||||
@@ -5222,6 +5220,9 @@ void on_activate(GtkApplication* application, gpointer user) {
|
||||
// are hidden first and kept hidden for the pages this call adds.
|
||||
keep_tabs_icon_free(*app);
|
||||
build_source_panels(*app);
|
||||
// The badge bar reads `trigger_toggle` (made by `build_toolbar()` above) and the source's pages, which
|
||||
// `build_source_panels()` has just registered: the Source badge opens the first of them.
|
||||
build_badge_bar(*app);
|
||||
|
||||
// The left side is the events pane *and* the plot, in one row: the pane is a column of its own
|
||||
// (the item's "collapsable pane on the left"), revealed by trigger mode.
|
||||
|
||||
+7
-7
@@ -109,11 +109,11 @@ struct TourView {
|
||||
void* done_user = nullptr;
|
||||
};
|
||||
|
||||
/// One capability panel a source kind contributed to the side stack. `content` is the kind's own widget
|
||||
/// One capability panel a source kind contributed to the slide-over. `content` is the kind's own widget
|
||||
/// (made insensitive while the source is disconnected); the page wrapping it is named `name`.
|
||||
struct SourcePanelPage {
|
||||
std::string name; ///< stack page name: "source-" + the kind's panel id
|
||||
std::string title; ///< the pill's tooltip and accessible name; the page's own title stays empty
|
||||
std::string name; ///< slide-over page id: "source-" + the kind's panel id
|
||||
std::string title; ///< the slide-over page's title, and the segmented switch's label
|
||||
GtkWidget* content = nullptr;
|
||||
};
|
||||
|
||||
@@ -143,7 +143,7 @@ struct BadgeBar {
|
||||
GtkWidget* signals_slot = nullptr; ///< left; empty in step 1, step 2 fills it
|
||||
GtkWidget* system_slot = nullptr; ///< right: Trigger, Source
|
||||
Badge trigger;
|
||||
Badge source; ///< task 3
|
||||
Badge source; ///< opens the first source page; a read-out when the source has none
|
||||
};
|
||||
|
||||
struct App {
|
||||
@@ -1606,8 +1606,8 @@ void save_layout_now(App& app);
|
||||
void keep_tabs_icon_free(App& app);
|
||||
|
||||
/// Capability panels (`src/ui_source.cpp`). `build_source_panels` adds the source kind's panels as
|
||||
/// pages of `side_stack`, once, before the first tick. `apply_source_panels` sets every panel's
|
||||
/// sensitivity from `connected` -- disabled, never hidden: the tab switcher owns visibility.
|
||||
/// pages of the slide-over (`source-<id>`), once, before the first tick. `apply_source_panels` sets every panel's
|
||||
/// sensitivity from `connected` -- disabled, never hidden.
|
||||
/// `sync_source_panels` is the per-tick call: it applies the model's own connection flag.
|
||||
void build_source_panels(App& app);
|
||||
void apply_source_panels(App& app, bool connected);
|
||||
@@ -1925,7 +1925,7 @@ gboolean slideover_probe(gpointer user); // UDPSCOPE_PROBE_SLIDEOVER_MS
|
||||
/// The badge bar (`src/ui_badges.cpp`): setup is reached by the object it configures.
|
||||
/// The Trigger badge's state word. Pure, so the probe can assert the whole table.
|
||||
const char* trigger_badge_state(bool enabled, bool armed, bool pending, bool has_event);
|
||||
void build_badge_bar(App& app); // after build_slideover and after the toolbar (it reads trigger_toggle)
|
||||
void build_badge_bar(App& app); // after build_slideover, the toolbar (it reads trigger_toggle) and build_source_panels (the Source badge's page)
|
||||
void sync_badge_bar(App& app); // per tick and on every slide-over open/close; only writes what changed
|
||||
gboolean badges_probe(gpointer user); // UDPSCOPE_PROBE_BADGES_MS
|
||||
gboolean source_kinds_probe_report(gpointer user);
|
||||
|
||||
+70
-1
@@ -3,8 +3,12 @@
|
||||
|
||||
// The badge bar under the plot: setup is reached by the object it configures.
|
||||
|
||||
#include "source/registry.hpp"
|
||||
#include "ui_app.hpp"
|
||||
|
||||
#include <adwaita.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
@@ -37,7 +41,15 @@ void on_badge_clicked(GtkButton*, gpointer user) {
|
||||
Badge* b = static_cast<Badge*>(user);
|
||||
// The App is reached through the badge's button: the badges live inside App, so the pointer is stable.
|
||||
App* app = static_cast<App*>(g_object_get_data(G_OBJECT(b->button), "udpscope-app"));
|
||||
if (app != nullptr) slideover_toggle(*app, b->page);
|
||||
if (app == nullptr) return;
|
||||
// The Source badge opens the first source page; with another source page already open, a click on it
|
||||
// closes the panel rather than jumping back to the first page (the switch inside the panel moves between
|
||||
// them).
|
||||
if (b == &app->badges.source && slideover_page(*app).rfind("source-", 0) == 0) {
|
||||
slideover_close(*app);
|
||||
return;
|
||||
}
|
||||
slideover_toggle(*app, b->page);
|
||||
}
|
||||
|
||||
/// A badge: a flat pill, name on the left, state dimmed beside it. Words only.
|
||||
@@ -66,6 +78,14 @@ void build_badge_bar(App& app) {
|
||||
bb.signals_slot = app.badge_signals;
|
||||
bb.system_slot = app.badge_system;
|
||||
make_badge(app, bb.trigger, bb.system_slot, "Trigger", "trigger");
|
||||
// The first source page is where the badge goes; a source with none (UDPS) has nothing to open, so its
|
||||
// badge is a read-out: not a target, not focusable, but still on screen.
|
||||
const std::string first = app.source_panels.empty() ? std::string() : app.source_panels.front().name;
|
||||
make_badge(app, bb.source, bb.system_slot, "Source", first);
|
||||
if (first.empty()) {
|
||||
gtk_widget_set_can_target(bb.source.button, FALSE);
|
||||
gtk_widget_set_focusable(bb.source.button, FALSE);
|
||||
}
|
||||
sync_badge_bar(app);
|
||||
}
|
||||
|
||||
@@ -79,6 +99,21 @@ void sync_badge_bar(App& app) {
|
||||
const bool has_event = app.model->event() != nullptr;
|
||||
set_text_if_changed(bb.trigger.state, trigger_badge_state(enabled, armed, pending, has_event));
|
||||
set_open(bb.trigger.button, slideover_page(app) == bb.trigger.page);
|
||||
|
||||
if (bb.source.button == nullptr) return;
|
||||
// The source's name and link state. A file has neither a link nor a kind worth naming.
|
||||
const bool file_mode = app.replaying || !app.model->config().open_waveform.empty();
|
||||
std::string name = "Source";
|
||||
const char* state = "";
|
||||
if (file_mode) {
|
||||
name = "File";
|
||||
} else {
|
||||
if (SourceKind* kind = find_kind(app.source_kind)) name = kind->display_name();
|
||||
state = app.model->stats().connected.load(std::memory_order_relaxed) ? "connected" : "disconnected";
|
||||
}
|
||||
set_text_if_changed(bb.source.name, name.c_str());
|
||||
set_text_if_changed(bb.source.state, state);
|
||||
set_open(bb.source.button, slideover_page(app).rfind("source-", 0) == 0);
|
||||
}
|
||||
|
||||
gboolean badges_probe(gpointer user) {
|
||||
@@ -113,6 +148,40 @@ gboolean badges_probe(gpointer user) {
|
||||
bad, gtk_label_get_text(GTK_LABEL(a->badges.trigger.state)), h_closed, h_after,
|
||||
h_closed == h_after && h_closed >= 36 ? 1 : 0, marked_open ? 1 : 0, page_open ? 1 : 0,
|
||||
marked_closed ? 1 : 0);
|
||||
// The Source badge: its read-out, and the segmented switch of a source with several panels.
|
||||
const std::string first = a->badges.source.page;
|
||||
bool switch_visible = false;
|
||||
bool second_ok = true;
|
||||
std::string second_page;
|
||||
if (!first.empty()) {
|
||||
slideover_open(*a, first);
|
||||
switch_visible = gtk_widget_get_visible(a->slideover.source_switch) != FALSE;
|
||||
if (a->slideover.source_pages.size() >= 2) {
|
||||
adw_toggle_group_set_active_name(ADW_TOGGLE_GROUP(a->slideover.source_switch),
|
||||
a->slideover.source_pages[1].c_str());
|
||||
second_page = slideover_page(*a);
|
||||
second_ok = second_page == a->slideover.source_pages[1];
|
||||
}
|
||||
}
|
||||
const bool marked = gtk_widget_has_css_class(a->badges.source.button, "udpscope-badge-open") != FALSE;
|
||||
// The widest minimum width of any source panel: the slide-over's scroller never scrolls horizontally, so
|
||||
// a panel whose minimum exceeds the page (360 px less the scroller's own chrome) would be clipped.
|
||||
int widest = 0;
|
||||
for (const SourcePanelPage& p : a->source_panels) {
|
||||
int min_w = 0;
|
||||
gtk_widget_measure(p.content, GTK_ORIENTATION_HORIZONTAL, -1, &min_w, nullptr, nullptr, nullptr);
|
||||
widest = std::max(widest, min_w);
|
||||
}
|
||||
slideover_close(*a);
|
||||
std::fprintf(stderr,
|
||||
"[badges] source name=%s state=%s pages=%zu switch_visible=%d can_target=%d second_page=%s "
|
||||
"second_ok=%d open_marked=%d closed_unmarked=%d panel_min_w=%d\n",
|
||||
gtk_label_get_text(GTK_LABEL(a->badges.source.name)),
|
||||
gtk_label_get_text(GTK_LABEL(a->badges.source.state)), a->slideover.source_pages.size(),
|
||||
switch_visible ? 1 : 0, gtk_widget_get_can_target(a->badges.source.button) ? 1 : 0,
|
||||
second_page.empty() ? "-" : second_page.c_str(), second_ok ? 1 : 0,
|
||||
first.empty() ? -1 : marked ? 1 : 0,
|
||||
gtk_widget_has_css_class(a->badges.source.button, "udpscope-badge-open") ? 0 : 1, widest);
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
|
||||
+6
-7
@@ -1957,9 +1957,7 @@ gboolean source_kinds_probe(gpointer user) {
|
||||
const SourcePanelPage* page = a->source_panels.empty() ? nullptr : &a->source_panels.front();
|
||||
const auto report = [&](const char* state) {
|
||||
const bool present =
|
||||
page != nullptr && a->side_stack != nullptr &&
|
||||
adw_view_stack_get_child_by_name(ADW_VIEW_STACK(a->side_stack), page->name.c_str()) !=
|
||||
nullptr;
|
||||
page != nullptr && slideover_has_page(*a, page->name);
|
||||
std::fprintf(stderr, "[source-kinds] panel %s: panels=%zu present=%d sensitive=%d\n", state,
|
||||
a->source_panels.size(), present ? 1 : 0,
|
||||
page != nullptr && gtk_widget_get_sensitive(page->content) ? 1 : 0);
|
||||
@@ -2399,8 +2397,7 @@ gboolean e1608_probe(gpointer user) {
|
||||
const std::string page = std::string("source-") + id;
|
||||
GtkWidget* content = source_panel_content(a, page.c_str());
|
||||
const bool present =
|
||||
content != nullptr && a->side_stack != nullptr &&
|
||||
adw_view_stack_get_child_by_name(ADW_VIEW_STACK(a->side_stack), page.c_str()) != nullptr;
|
||||
content != nullptr && slideover_has_page(*a, page);
|
||||
std::fprintf(stderr, "[e1608] panel %s: present=%d sensitive=%d connected=%d\n", id,
|
||||
present ? 1 : 0, content != nullptr && gtk_widget_get_sensitive(content) ? 1 : 0,
|
||||
up ? 1 : 0);
|
||||
@@ -2445,6 +2442,7 @@ gboolean e1608_gen_probe_stopped(gpointer user) {
|
||||
std::fprintf(stderr, "[e1608] generator stopped: achieved=%.1f panel='%s' ok=%d\n", hz,
|
||||
sub != nullptr ? sub : "", hz == 0.0 && off ? 1 : 0);
|
||||
std::fflush(stderr);
|
||||
slideover_close(*a);
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
@@ -2493,8 +2491,9 @@ gboolean e1608_gen_probe(gpointer user) {
|
||||
std::fflush(stderr);
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
// The panel refreshes on its frame clock, which only ticks while the page is on screen.
|
||||
if (a->side_stack != nullptr) adw_view_stack_set_visible_child_name(ADW_VIEW_STACK(a->side_stack), "source-ao");
|
||||
// The panel refreshes on its frame clock, which only ticks while the page is on screen; the page stays
|
||||
// open for the probe's reads and `e1608_gen_probe_stopped` closes it.
|
||||
slideover_open(*a, "source-ao");
|
||||
adw_combo_row_set_selected(ADW_COMBO_ROW(mode), 1); // Sine
|
||||
adw_switch_row_set_active(ADW_SWITCH_ROW(run), TRUE);
|
||||
g_timeout_add(2500, e1608_gen_probe_report, a);
|
||||
|
||||
+4
-9
@@ -54,7 +54,7 @@ void keep_tabs_icon_free(App& app) {
|
||||
}
|
||||
|
||||
void build_source_panels(App& app) {
|
||||
if (app.side_stack == nullptr || app.acq == nullptr || app.acq->source() == nullptr) return;
|
||||
if (app.slideover.stack == nullptr || app.acq == nullptr || app.acq->source() == nullptr) return;
|
||||
SourceKind* kind = find_kind(app.source_kind);
|
||||
if (kind == nullptr) return;
|
||||
for (const SourcePanel& p : kind->make_panels(*app.acq->source())) {
|
||||
@@ -63,15 +63,10 @@ void build_source_panels(App& app) {
|
||||
p.widget == nullptr ? "no widget" : "an empty id");
|
||||
continue;
|
||||
}
|
||||
// Each page scrolls on its own, like Measure and Signals: panels differ in height.
|
||||
GtkWidget* scroller = gtk_scrolled_window_new();
|
||||
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroller), GTK_POLICY_NEVER,
|
||||
GTK_POLICY_AUTOMATIC);
|
||||
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scroller), p.widget);
|
||||
const std::string name = "source-" + p.id;
|
||||
// The tab is its title: the sidebar's pills are words, not glyphs.
|
||||
adw_view_stack_add_titled(ADW_VIEW_STACK(app.side_stack), scroller, name.c_str(),
|
||||
p.title.c_str());
|
||||
// The panel is a slide-over page now: the slide-over wraps it in its own scroller, and with two or
|
||||
// more source pages it draws the segmented switch (the E-1608: Analog out, Digital I/O).
|
||||
if (!slideover_add_page(app, name, p.title, p.widget)) continue;
|
||||
app.source_panels.push_back({name, p.title, p.widget});
|
||||
}
|
||||
// Disconnected until the first Connected event says otherwise.
|
||||
|
||||
Reference in new issue
Block a user