ui: a badge bar with a Trigger badge, and the trigger form in the slide-over
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
a40863fa45
commit
5fb479bd69
9 files changed
+215
-7
No files matched your search
@@ -273,6 +273,7 @@ add_executable(udpscope
|
|||||||
src/ui_layout.cpp
|
src/ui_layout.cpp
|
||||||
src/ui_source.cpp
|
src/ui_source.cpp
|
||||||
src/ui_slideover.cpp
|
src/ui_slideover.cpp
|
||||||
|
src/ui_badges.cpp
|
||||||
src/ui_trigger.cpp
|
src/ui_trigger.cpp
|
||||||
src/ui_signals.cpp
|
src/ui_signals.cpp
|
||||||
src/ui_context_help.cpp
|
src/ui_context_help.cpp
|
||||||
|
|||||||
@@ -429,6 +429,16 @@ precedent, not an exception.
|
|||||||
read `click_inside_closes=0 still_open=1 click_outside_closes=1 esc_closes=1 closed=1`. For a screenshot
|
read `click_inside_closes=0 still_open=1 click_outside_closes=1 esc_closes=1 closed=1`. For a screenshot
|
||||||
add `UDPSCOPE_PROBE_SLIDEOVER_HOLD=1`, which leaves the page open after line 1 (run with `GSK_RENDERER=cairo`
|
add `UDPSCOPE_PROBE_SLIDEOVER_HOLD=1`, which leaves the page open after line 1 (run with `GSK_RENDERER=cairo`
|
||||||
and `import -window root`): the panel must be drawn over the traces, opaque.
|
and `import -window root`): the panel must be drawn over the traces, opaque.
|
||||||
|
- `UDPSCOPE_PROBE_BADGES_MS=2000` with `UDPSCOPE_EXIT_AFTER_MS=5000` and `UDPSCOPE_ENABLE_ALL_MS=500` -- the badge
|
||||||
|
bar under the plot. One `[badges]` line, which must read `table_bad=0 fixed=1 open_marked=1 page_open=1
|
||||||
|
closed_unmarked=1`: `table_bad` counts the rows of the 16-input `trigger_badge_state()` table the probe
|
||||||
|
gets wrong; `fixed=1` is the bar's height being the same (and at least 36 px) before and after the
|
||||||
|
`trigger` page was opened and closed -- `0` means the bar's height depends on content and the plot above it
|
||||||
|
moves; `open_marked`/`closed_unmarked` are the `udpscope-badge-open` class following the slide-over's page.
|
||||||
|
`trigger_state=` is the state word as read then (`off` with the trigger toggle off). **The trigger form is
|
||||||
|
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.
|
||||||
- `UDPSCOPE_PROBE_SOURCE_KINDS=2000` with `UDPSCOPE_EXIT_AFTER_MS=5000`, run as `--source probe --source-opt rate=500`
|
- `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
|
-- 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.
|
synthetic sine with one panel), so it is the only way a run sees that kind; its value is the delay in ms.
|
||||||
|
|||||||
+14
-4
@@ -3380,6 +3380,7 @@ gboolean tick(gpointer user) {
|
|||||||
if (app->window == nullptr) return G_SOURCE_REMOVE;
|
if (app->window == nullptr) return G_SOURCE_REMOVE;
|
||||||
note_panel_controls(*app);
|
note_panel_controls(*app);
|
||||||
sync_source_panels(*app);
|
sync_source_panels(*app);
|
||||||
|
sync_badge_bar(*app);
|
||||||
// The tour, re-measured every frame: the target's allocation is 0 on the frame it is first shown
|
// The tour, re-measured every frame: the target's allocation is 0 on the frame it is first shown
|
||||||
// (see the allocation bullets in `docs/claude/invariants-trigger-and-pinning.md`) and the widget
|
// (see the allocation bullets in `docs/claude/invariants-trigger-and-pinning.md`) and the widget
|
||||||
// moves under the spotlight on a window resize, so a one-shot measurement is wrong both ways.
|
// moves under the spotlight on a window resize, so a one-shot measurement is wrong both ways.
|
||||||
@@ -5021,13 +5022,17 @@ void on_activate(GtkApplication* application, gpointer user) {
|
|||||||
GtkWidget* side_switcher = GTK_WIDGET(gtk_builder_get_object(app->window_ui, "side_switcher"));
|
GtkWidget* side_switcher = GTK_WIDGET(gtk_builder_get_object(app->window_ui, "side_switcher"));
|
||||||
GtkWidget* measure_page = GTK_WIDGET(gtk_builder_get_object(app->window_ui, "measure_page"));
|
GtkWidget* measure_page = GTK_WIDGET(gtk_builder_get_object(app->window_ui, "measure_page"));
|
||||||
GtkWidget* signals_page = GTK_WIDGET(gtk_builder_get_object(app->window_ui, "signals_page"));
|
GtkWidget* signals_page = GTK_WIDGET(gtk_builder_get_object(app->window_ui, "signals_page"));
|
||||||
|
app->badge_bar = GTK_WIDGET(gtk_builder_get_object(app->window_ui, "badge_bar"));
|
||||||
|
app->badge_signals = GTK_WIDGET(gtk_builder_get_object(app->window_ui, "badge_signals"));
|
||||||
|
app->badge_system = GTK_WIDGET(gtk_builder_get_object(app->window_ui, "badge_system"));
|
||||||
// A missing id is the one mistake neither the build nor `GtkBuilder` sees, and every use below would
|
// A missing id is the one mistake neither the build nor `GtkBuilder` sees, and every use below would
|
||||||
// be a null pointer.
|
// be a null pointer.
|
||||||
if (app->window == nullptr || side_view == nullptr || events_divider == nullptr ||
|
if (app->window == nullptr || side_view == nullptr || events_divider == nullptr ||
|
||||||
app->plot_box == nullptr || app->plot_overlay == nullptr || app->plot_split == nullptr ||
|
app->plot_box == nullptr || app->plot_overlay == nullptr || app->plot_split == nullptr ||
|
||||||
app->status_badge == nullptr ||
|
app->status_badge == nullptr ||
|
||||||
app->status_label == nullptr || app->status_info_button == nullptr ||
|
app->status_label == nullptr || app->status_info_button == nullptr ||
|
||||||
app->status_row == nullptr ||
|
app->status_row == nullptr || app->badge_bar == nullptr || app->badge_signals == nullptr ||
|
||||||
|
app->badge_system == nullptr ||
|
||||||
side == nullptr || side_stack == nullptr || side_switcher == nullptr ||
|
side == nullptr || side_stack == nullptr || side_switcher == nullptr ||
|
||||||
measure_page == nullptr || signals_page == nullptr) {
|
measure_page == nullptr || signals_page == nullptr) {
|
||||||
return;
|
return;
|
||||||
@@ -5067,6 +5072,8 @@ void on_activate(GtkApplication* application, gpointer user) {
|
|||||||
}
|
}
|
||||||
// The slide-over panel: an overlay child of the plot, above the GL area.
|
// The slide-over panel: an overlay child of the plot, above the GL area.
|
||||||
build_slideover(*app);
|
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
|
// 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 --
|
// size it *asks* for, and a window whose content has a larger minimum opens at that minimum instead --
|
||||||
@@ -5188,10 +5195,10 @@ void on_activate(GtkApplication* application, gpointer user) {
|
|||||||
gtk_box_append(GTK_BOX(measure_page), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL));
|
gtk_box_append(GTK_BOX(measure_page), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL));
|
||||||
gtk_box_append(GTK_BOX(measure_page), build_measures_panel(*app));
|
gtk_box_append(GTK_BOX(measure_page), build_measures_panel(*app));
|
||||||
|
|
||||||
// Signals: the trigger picks a channel from the list beside it.
|
// The trigger form lives in the slide-over (reached by the Trigger badge); the signal list stays in the
|
||||||
|
// sidebar until the dock replaces it.
|
||||||
app->trigger_panel = build_trigger_panel(*app);
|
app->trigger_panel = build_trigger_panel(*app);
|
||||||
gtk_box_append(GTK_BOX(signals_page), app->trigger_panel);
|
slideover_add_page(*app, "trigger", "Trigger", app->trigger_panel);
|
||||||
gtk_box_append(GTK_BOX(signals_page), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL));
|
|
||||||
app->signal_panel = build_signal_panel(*app);
|
app->signal_panel = build_signal_panel(*app);
|
||||||
gtk_box_append(GTK_BOX(signals_page), app->signal_panel);
|
gtk_box_append(GTK_BOX(signals_page), app->signal_panel);
|
||||||
|
|
||||||
@@ -5912,6 +5919,9 @@ int run_ui(int argc, char** argv, const Config& cfg, ScopeModel& model, Acquisit
|
|||||||
if (const char* env = g_getenv("UDPSCOPE_PROBE_SLIDEOVER_MS")) {
|
if (const char* env = g_getenv("UDPSCOPE_PROBE_SLIDEOVER_MS")) {
|
||||||
g_timeout_add(static_cast<guint>(std::atoi(env)), slideover_probe, &app);
|
g_timeout_add(static_cast<guint>(std::atoi(env)), slideover_probe, &app);
|
||||||
}
|
}
|
||||||
|
if (const char* env = g_getenv("UDPSCOPE_PROBE_BADGES_MS")) {
|
||||||
|
g_timeout_add(static_cast<guint>(std::atoi(env)), badges_probe, &app);
|
||||||
|
}
|
||||||
if (const char* env = g_getenv("UDPSCOPE_PROBE_SEARCH_MS")) {
|
if (const char* env = g_getenv("UDPSCOPE_PROBE_SEARCH_MS")) {
|
||||||
g_timeout_add(static_cast<guint>(std::atoi(env)), search_probe, &app);
|
g_timeout_add(static_cast<guint>(std::atoi(env)), search_probe, &app);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -150,6 +150,32 @@
|
|||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
|
<child>
|
||||||
|
<!-- Setup is reached by the object it configures: a badge per signal on the left
|
||||||
|
(step 2), the system badges (Trigger, Source) on the right. A fixed height, so
|
||||||
|
the plot above it never moves when badges come and go. -->
|
||||||
|
<object class="GtkBox" id="badge_bar">
|
||||||
|
<property name="orientation">horizontal</property>
|
||||||
|
<property name="spacing">6</property>
|
||||||
|
<property name="height-request">36</property>
|
||||||
|
<property name="margin-start">6</property>
|
||||||
|
<property name="margin-end">6</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox" id="badge_signals">
|
||||||
|
<property name="orientation">horizontal</property>
|
||||||
|
<property name="spacing">6</property>
|
||||||
|
<property name="hexpand">1</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox" id="badge_system">
|
||||||
|
<property name="orientation">horizontal</property>
|
||||||
|
<property name="spacing">6</property>
|
||||||
|
<property name="halign">end</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkBox" id="status_row">
|
<object class="GtkBox" id="status_row">
|
||||||
<property name="orientation">horizontal</property>
|
<property name="orientation">horizontal</property>
|
||||||
|
|||||||
@@ -130,6 +130,22 @@ struct SlideOver {
|
|||||||
};
|
};
|
||||||
constexpr int kSlideOverWidth = 360;
|
constexpr int kSlideOverWidth = 360;
|
||||||
|
|
||||||
|
/// One badge: a button with a name and a state line. `page` is the slide-over page it opens.
|
||||||
|
struct Badge {
|
||||||
|
GtkWidget* button = nullptr;
|
||||||
|
GtkWidget* name = nullptr;
|
||||||
|
GtkWidget* state = nullptr;
|
||||||
|
std::string page;
|
||||||
|
};
|
||||||
|
/// The badge bar under the plot (src/ui_badges.cpp).
|
||||||
|
struct BadgeBar {
|
||||||
|
GtkWidget* bar = nullptr; ///< main-window.ui `badge_bar`, fixed height
|
||||||
|
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
|
||||||
|
};
|
||||||
|
|
||||||
struct App {
|
struct App {
|
||||||
ScopeModel* model = nullptr;
|
ScopeModel* model = nullptr;
|
||||||
Acquisition* acq = nullptr;
|
Acquisition* acq = nullptr;
|
||||||
@@ -140,6 +156,11 @@ struct App {
|
|||||||
/// The `GtkOverlay` around `plot_box` (`main-window.ui`), which the slide-over panel is a child of.
|
/// The `GtkOverlay` around `plot_box` (`main-window.ui`), which the slide-over panel is a child of.
|
||||||
GtkWidget* plot_overlay = nullptr;
|
GtkWidget* plot_overlay = nullptr;
|
||||||
SlideOver slideover;
|
SlideOver slideover;
|
||||||
|
/// The badge bar under the plot (`main-window.ui` `badge_bar`, with its two slots).
|
||||||
|
BadgeBar badges;
|
||||||
|
GtkWidget* badge_bar = nullptr;
|
||||||
|
GtkWidget* badge_signals = nullptr;
|
||||||
|
GtkWidget* badge_system = nullptr;
|
||||||
/// The one GL area, drawing the `RowSet`'s figure: every row is a row of that
|
/// The one GL area, drawing the `RowSet`'s figure: every row is a row of that
|
||||||
/// figure, so there is one widget, one context and one `::render` whatever the row
|
/// figure, so there is one widget, one context and one `::render` whatever the row
|
||||||
/// count is.
|
/// count is.
|
||||||
@@ -1901,6 +1922,12 @@ bool slideover_escape(App& app); // closes if open; true
|
|||||||
/// x, y in the overlay's coordinates; closes unless the point is inside the panel; true when it closed.
|
/// x, y in the overlay's coordinates; closes unless the point is inside the panel; true when it closed.
|
||||||
bool slideover_click_away(App& app, double x, double y);
|
bool slideover_click_away(App& app, double x, double y);
|
||||||
gboolean slideover_probe(gpointer user); // UDPSCOPE_PROBE_SLIDEOVER_MS
|
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 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);
|
gboolean source_kinds_probe_report(gpointer user);
|
||||||
gboolean source_kinds_probe(gpointer user);
|
gboolean source_kinds_probe(gpointer user);
|
||||||
/// MCC E-1608 end to end against `tools/mock_e1608.py`: the stream, the calibration, the panels and
|
/// MCC E-1608 end to end against `tools/mock_e1608.py`: the stream, the calibration, the panels and
|
||||||
|
|||||||
@@ -0,0 +1,120 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
// SPDX-FileCopyrightText: 2026 Martino Ferrari
|
||||||
|
|
||||||
|
// The badge bar under the plot: setup is reached by the object it configures.
|
||||||
|
|
||||||
|
#include "ui_app.hpp"
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
namespace udpscope {
|
||||||
|
namespace ui_impl {
|
||||||
|
|
||||||
|
const char* trigger_badge_state(bool enabled, bool armed, bool pending, bool has_event) {
|
||||||
|
if (!enabled) return "off";
|
||||||
|
if (has_event && !pending) return "triggered";
|
||||||
|
if (pending) return "capturing";
|
||||||
|
return armed ? "armed" : "disarmed";
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
void set_text_if_changed(GtkWidget* label, const char* text) {
|
||||||
|
if (std::strcmp(gtk_label_get_text(GTK_LABEL(label)), text) != 0) gtk_label_set_text(GTK_LABEL(label), text);
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_open(GtkWidget* button, bool open) {
|
||||||
|
if ((gtk_widget_has_css_class(button, "udpscope-badge-open") != FALSE) == open) return;
|
||||||
|
if (open) {
|
||||||
|
gtk_widget_add_css_class(button, "udpscope-badge-open");
|
||||||
|
} else {
|
||||||
|
gtk_widget_remove_css_class(button, "udpscope-badge-open");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A badge: a flat pill, name on the left, state dimmed beside it. Words only.
|
||||||
|
void make_badge(App& app, Badge& b, GtkWidget* parent, const char* name, const std::string& page) {
|
||||||
|
b.page = page;
|
||||||
|
b.button = gtk_button_new();
|
||||||
|
gtk_widget_add_css_class(b.button, "flat");
|
||||||
|
gtk_widget_add_css_class(b.button, "udpscope-badge");
|
||||||
|
GtkWidget* row = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8);
|
||||||
|
b.name = gtk_label_new(name);
|
||||||
|
b.state = gtk_label_new("");
|
||||||
|
gtk_widget_add_css_class(b.state, "udpscope-badge-state");
|
||||||
|
gtk_box_append(GTK_BOX(row), b.name);
|
||||||
|
gtk_box_append(GTK_BOX(row), b.state);
|
||||||
|
gtk_button_set_child(GTK_BUTTON(b.button), row);
|
||||||
|
g_object_set_data(G_OBJECT(b.button), "udpscope-app", &app);
|
||||||
|
g_signal_connect(b.button, "clicked", G_CALLBACK(on_badge_clicked), &b);
|
||||||
|
gtk_box_append(GTK_BOX(parent), b.button);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void build_badge_bar(App& app) {
|
||||||
|
BadgeBar& bb = app.badges;
|
||||||
|
bb.bar = app.badge_bar;
|
||||||
|
bb.signals_slot = app.badge_signals;
|
||||||
|
bb.system_slot = app.badge_system;
|
||||||
|
make_badge(app, bb.trigger, bb.system_slot, "Trigger", "trigger");
|
||||||
|
sync_badge_bar(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sync_badge_bar(App& app) {
|
||||||
|
BadgeBar& bb = app.badges;
|
||||||
|
if (bb.trigger.button == nullptr) return;
|
||||||
|
const bool enabled = app.trigger_toggle != nullptr &&
|
||||||
|
gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(app.trigger_toggle)) != FALSE;
|
||||||
|
const bool armed = app.model->trigger_armed().load(std::memory_order_relaxed);
|
||||||
|
const bool pending = app.model->trigger_pending();
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
gboolean badges_probe(gpointer user) {
|
||||||
|
App* a = static_cast<App*>(user);
|
||||||
|
// The whole table of the pure function: 16 inputs, 5 words.
|
||||||
|
struct Row { bool e, a, p, v; const char* want; };
|
||||||
|
static const Row kRows[] = {
|
||||||
|
{false, false, false, false, "off"}, {false, false, false, true, "off"},
|
||||||
|
{false, false, true, false, "off"}, {false, false, true, true, "off"},
|
||||||
|
{false, true, false, false, "off"}, {false, true, false, true, "off"},
|
||||||
|
{false, true, true, false, "off"}, {false, true, true, true, "off"},
|
||||||
|
{true, false, false, false, "disarmed"}, {true, false, false, true, "triggered"},
|
||||||
|
{true, false, true, false, "capturing"}, {true, false, true, true, "capturing"},
|
||||||
|
{true, true, false, false, "armed"}, {true, true, false, true, "triggered"},
|
||||||
|
{true, true, true, false, "capturing"}, {true, true, true, true, "capturing"},
|
||||||
|
};
|
||||||
|
int bad = 0;
|
||||||
|
for (const Row& r : kRows) {
|
||||||
|
if (std::strcmp(trigger_badge_state(r.e, r.a, r.p, r.v), r.want) != 0) ++bad;
|
||||||
|
}
|
||||||
|
// The geometry: fixed height whatever the state, and the open mark follows the slide-over.
|
||||||
|
const int h_closed = gtk_widget_get_height(a->badges.bar);
|
||||||
|
slideover_toggle(*a, "trigger");
|
||||||
|
const bool marked_open = gtk_widget_has_css_class(a->badges.trigger.button, "udpscope-badge-open") != FALSE;
|
||||||
|
const bool page_open = slideover_page(*a) == "trigger";
|
||||||
|
slideover_toggle(*a, "trigger");
|
||||||
|
const bool marked_closed = gtk_widget_has_css_class(a->badges.trigger.button, "udpscope-badge-open") == FALSE;
|
||||||
|
const int h_after = gtk_widget_get_height(a->badges.bar);
|
||||||
|
std::fprintf(stderr,
|
||||||
|
"[badges] table_bad=%d trigger_state=%s bar_h=%d bar_h_after=%d fixed=%d open_marked=%d "
|
||||||
|
"page_open=%d closed_unmarked=%d\n",
|
||||||
|
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);
|
||||||
|
return G_SOURCE_REMOVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ui_impl
|
||||||
|
} // namespace udpscope
|
||||||
+10
-1
@@ -195,7 +195,8 @@ gboolean exit_probe_step3(gpointer user) {
|
|||||||
const int on_signals = page != nullptr && std::strcmp(page, "signals") == 0 ? 1 : 0;
|
const int on_signals = page != nullptr && std::strcmp(page, "signals") == 0 ? 1 : 0;
|
||||||
a->dbg_signal_page_current =
|
a->dbg_signal_page_current =
|
||||||
on_signals && gtk_widget_get_first_child(a->signal_list) != nullptr ? 1 : 0;
|
on_signals && gtk_widget_get_first_child(a->signal_list) != nullptr ? 1 : 0;
|
||||||
a->dbg_trigger_sensitive = on_signals && gtk_widget_get_sensitive(a->trigger_body) ? 1 : 0;
|
// The trigger form is no longer on a sidebar tab, so the reading is the body's own sensitivity.
|
||||||
|
a->dbg_trigger_sensitive = gtk_widget_get_sensitive(a->trigger_body) ? 1 : 0;
|
||||||
// `gtk_widget_get_visible` on the sidebar's GtkBox is always 1: AdwOverlaySplitView hides its
|
// `gtk_widget_get_visible` on the sidebar's GtkBox is always 1: AdwOverlaySplitView hides its
|
||||||
// sidebar child via `gtk_widget_set_child_visible()`, not `gtk_widget_set_visible()`. The useful
|
// sidebar child via `gtk_widget_set_child_visible()`, not `gtk_widget_set_visible()`. The useful
|
||||||
// reading is whether the split view is currently showing the sidebar panel.
|
// reading is whether the split view is currently showing the sidebar panel.
|
||||||
@@ -212,6 +213,7 @@ gboolean exit_probe_step3(gpointer user) {
|
|||||||
}
|
}
|
||||||
// `dbg_window_text` is kept by `sync_window_display()`, the only place that knows the span the
|
// `dbg_window_text` is kept by `sync_window_display()`, the only place that knows the span the
|
||||||
// window control is showing: there is no label to read it back from any more.
|
// window control is showing: there is no label to read it back from any more.
|
||||||
|
slideover_close(*a); // opened by step 1 for the allocations read above
|
||||||
if (window != nullptr) gtk_window_destroy(GTK_WINDOW(window));
|
if (window != nullptr) gtk_window_destroy(GTK_WINDOW(window));
|
||||||
return G_SOURCE_REMOVE;
|
return G_SOURCE_REMOVE;
|
||||||
}
|
}
|
||||||
@@ -4030,6 +4032,9 @@ gboolean force_probe(gpointer user) {
|
|||||||
// trigger, so a second run against the same source would otherwise start armed and never read the
|
// trigger, so a second run against the same source would otherwise start armed and never read the
|
||||||
// off case at all.
|
// off case at all.
|
||||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(a->trigger_toggle), FALSE);
|
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(a->trigger_toggle), FALSE);
|
||||||
|
// The buttons' widths are allocations, and the trigger form is a slide-over page: open it, and step 2
|
||||||
|
// reads 300 ms later, past the 120 ms reveal.
|
||||||
|
slideover_open(*a, "trigger");
|
||||||
g_timeout_add(300, force_probe_step2, a);
|
g_timeout_add(300, force_probe_step2, a);
|
||||||
return G_SOURCE_REMOVE;
|
return G_SOURCE_REMOVE;
|
||||||
}
|
}
|
||||||
@@ -4178,6 +4183,10 @@ gboolean export_probe(gpointer user) {
|
|||||||
|
|
||||||
gboolean exit_probe_step1(gpointer user) {
|
gboolean exit_probe_step1(gpointer user) {
|
||||||
App* a = static_cast<App*>(user);
|
App* a = static_cast<App*>(user);
|
||||||
|
// The trigger form is a slide-over page now, and a page that is not the open one has no allocation:
|
||||||
|
// open it here, and the chain below reads its widgets 2.1 s later, long after the 120 ms reveal. Step 3
|
||||||
|
// closes it again. Sensitivity, visibility and values need no open page; widths and heights do.
|
||||||
|
slideover_open(*a, "trigger");
|
||||||
// Record the panel states before the toggles are exercised.
|
// Record the panel states before the toggles are exercised.
|
||||||
a->dbg_trigger_sensitive_default = gtk_widget_get_sensitive(a->trigger_body) ? 1 : 0;
|
a->dbg_trigger_sensitive_default = gtk_widget_get_sensitive(a->trigger_body) ? 1 : 0;
|
||||||
// The two [edit] buttons, in each mode: the icon is the only thing that says which mode the pane is
|
// The two [edit] buttons, in each mode: the icon is the only thing that says which mode the pane is
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ bool slideover_open(App& app, const std::string& id) {
|
|||||||
}
|
}
|
||||||
gtk_widget_set_can_target(s.revealer, TRUE);
|
gtk_widget_set_can_target(s.revealer, TRUE);
|
||||||
gtk_revealer_set_reveal_child(GTK_REVEALER(s.revealer), TRUE);
|
gtk_revealer_set_reveal_child(GTK_REVEALER(s.revealer), TRUE);
|
||||||
// task 2: sync_badge_bar(app);
|
sync_badge_bar(app);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,7 +150,7 @@ void slideover_close(App& app) {
|
|||||||
s.page.clear();
|
s.page.clear();
|
||||||
gtk_widget_set_can_target(s.revealer, FALSE);
|
gtk_widget_set_can_target(s.revealer, FALSE);
|
||||||
gtk_revealer_set_reveal_child(GTK_REVEALER(s.revealer), FALSE);
|
gtk_revealer_set_reveal_child(GTK_REVEALER(s.revealer), FALSE);
|
||||||
// task 2: sync_badge_bar(app);
|
sync_badge_bar(app);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool slideover_toggle(App& app, const std::string& id) {
|
bool slideover_toggle(App& app, const std::string& id) {
|
||||||
|
|||||||
@@ -74,6 +74,11 @@ void install_css() {
|
|||||||
".udpscope-slideover { background-color: @window_bg_color; border-left: 1px solid @borders;"
|
".udpscope-slideover { background-color: @window_bg_color; border-left: 1px solid @borders;"
|
||||||
" box-shadow: -3px 0 10px rgba(0, 0, 0, 0.35); }\n"
|
" box-shadow: -3px 0 10px rgba(0, 0, 0, 0.35); }\n"
|
||||||
".udpscope-slideover-title { font-weight: bold; }\n"
|
".udpscope-slideover-title { font-weight: bold; }\n"
|
||||||
|
// The badge bar: a badge is a pill carrying a name and a state; the one whose page is open is
|
||||||
|
// marked, as a bench scope marks the open menu's button.
|
||||||
|
".udpscope-badge { padding: 2px 12px; min-height: 0; border-radius: 14px; }\n"
|
||||||
|
".udpscope-badge-open { background-color: alpha(@accent_bg_color, 0.28); }\n"
|
||||||
|
".udpscope-badge-state { opacity: 0.7; }\n"
|
||||||
// The contextual-help "(i)": a *mark*, not a button. Adwaita's menu button is a 34x34 control
|
// The contextual-help "(i)": a *mark*, not a button. Adwaita's menu button is a 34x34 control
|
||||||
// (a 24 px floor plus 5 px of padding) with a 16 px glyph, which beside a heading reads as a
|
// (a 24 px floor plus 5 px of padding) with a 16 px glyph, which beside a heading reads as a
|
||||||
// second toolbar button. Measured on libadwaita 1.10: the outer `menubutton` node *and* its
|
// second toolbar button. Measured on libadwaita 1.10: the outer `menubutton` node *and* its
|
||||||
|
|||||||
Reference in new issue
Block a user