fix: the overlaid sidebar steps aside for a drag, and the compact view follows the trigger
Two reports, both real, and neither was about the lock itself.
**A drag out of an overlaid sidebar had nowhere to land.** In a window too narrow to show both columns,
`AdwOverlaySplitView` puts the sidebar *on* the plot — that is what `collapsed` means — so the pointer
stays over the list and never over a drop target: a signal could be dragged but not dropped, which reads
as "dragging does not work". `row_drag_sidebar()` now hides the sidebar on `drag-begin` and restores it on
`drag-end`, and only when collapsed: side by side the plot is already visible, and hiding the list the drag
started in would be startling. The reading begins a drag through the *source* with the state forced
collapsed, the way the sidebar probe forces it:
pane drag: sidebar hidden for the drag=1 restored after it=1
**The compact view did not follow the trigger switch.** The locked line's first branch reads
`tc.enabled` — with trigger mode off it says `stream (trigger off)` and nothing else — and
`on_trigger_toggled()` never re-synced it, so the line kept saying whichever it had been. One call to
`sync_trigger_summary()` beside the switch sync. The dump's two `panes:` lines were already the test and
now pass it: with a session whose trigger is off, the line as found reads `stream (trigger off)` and the
live one reads the settings, because the exit probe turns the trigger on in between. Both reading `stream`
was the defect.
Verified: the two lines above; `panes: the same line as the probe found it: 'mode: stream (trigger off)'`
against the live `'signal: square | condition: window, rising | level: 0.3 .. 0.6 | mode: normal'`;
`make test-ui` 14 files; zero `Gtk-CRITICAL`. Docs: the `pane drag:` reading and the invariants bullet for
the overlay, and the `panes:` pair's second job — the refresh test — written down so the next reader knows
what both lines reading `stream` means.
This commit is contained in:
1 parent
a43d3830f0
commit
8b2dae7655
8 files changed
+95
-3
No files matched your search
@@ -2405,7 +2405,9 @@ now — they are listed so they are not mistaken for anything else:
|
||||
on, and neither offers a control that changes the configuration — each heading has an `[edit]` button
|
||||
to switch to the full pane, and back. Dragging a signal onto a plot still works while locked — filling
|
||||
plots is what the list is for, not configuration — and a locked row can be dragged from anywhere on it,
|
||||
the colour chip included. A **first run** (no session file for that source) comes up
|
||||
the colour chip included. In a window too narrow to show both, where the sidebar is *overlaid on* the
|
||||
plot, the sidebar steps aside for the length of the drag so there is something under the pointer to drop
|
||||
onto. A **first run** (no session file for that source) comes up
|
||||
unlocked, because configuring the host is what a first run is for. Each pane's heading also carries its **own switch**, the same control as the
|
||||
header bar's button — clicking either moves both — because a pane that is *disabled* while its
|
||||
feature is off is a pane whose controls you would otherwise have to leave to turn it back on. The
|
||||
|
||||
@@ -188,6 +188,13 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear
|
||||
**tooltip and accessible name** from the page's name, then hides the empty label that would otherwise
|
||||
leave the icon off-centre. An icon-only pill is still a named control for a screen reader, and
|
||||
`switcher: … pills=2 labels=0 names=2` is the reading for all three facts at once.
|
||||
- **An *overlaid* sidebar covers the plot, so a drag out of it has nowhere to land.** `collapsed` in an
|
||||
`AdwOverlaySplitView` means the sidebar sits *on* the plot rather than beside it: the pointer is over the
|
||||
list, never over a drop target, and a signal can be dragged but not dropped. `row_drag_sidebar()` hides
|
||||
the sidebar on `drag-begin` and restores it on `drag-end` — only when collapsed, because side by side the
|
||||
plot is already visible and hiding the list the drag started in would be startling. `pane drag: sidebar
|
||||
hidden for the drag=1 restored after it=1` is the reading; the state is forced collapsed the way the
|
||||
sidebar probe forces it.
|
||||
- **A stock icon name has to be one *Adwaita* has, and a blank button is the failure mode.** The two
|
||||
panes' `[edit]` buttons are `document-edit-symbolic` (the pencil: edit) and `object-select-symbolic`
|
||||
(the check: done). The done one was `emblem-ok-symbolic`, which breeze ships and **Adwaita does not** —
|
||||
|
||||
@@ -1142,12 +1142,19 @@ library measures and draws the rect in tests of its own.
|
||||
## `panes:` — the locked and configuration modes
|
||||
|
||||
```
|
||||
[udpscope] pane drag: sidebar hidden for the drag=1 restored after it=1
|
||||
[udpscope] pane edits: locked=document-edit-symbolic(ok) config=object-select-symbolic(ok)
|
||||
[udpscope] panes: trigger=locked signals=locked body_visible=0 summary='signal: square | condition: window, falling | level: 0.3 .. 0.6 | mode: single | noise band: 0.2 | pre-trigger: 20 % | hold-off: 1000 ms'
|
||||
[udpscope] panes: trigger=config signals=config body_visible=1 summary=''
|
||||
[udpscope] panes: the same line as the probe found it: 'mode: stream (trigger off)'
|
||||
```
|
||||
|
||||
**`pane drag:`** is a drag begun through the *source* with the split view forced into its **overlaid**
|
||||
state, which is what a window with no room beside the plot produces. Both must read `1`: with the sidebar
|
||||
covering the plot there is no drop target under the pointer, so a drag out of the signal list is
|
||||
*impossible* rather than merely awkward, and `row_drag_sidebar()` steps the sidebar aside for the length of
|
||||
the drag and puts it back after. `hidden=0` is an overlaid drag that can never land anywhere.
|
||||
|
||||
**`pane edits:`** is the two `[edit]` buttons in each mode, by name and verdict, and it exists because of
|
||||
a real defect: the "done" icon was `emblem-ok-symbolic`, which **Adwaita does not have** (breeze does), so
|
||||
the button that leaves configuration mode was blank — and a blank icon button is invisible in every other
|
||||
@@ -1164,7 +1171,10 @@ its pane.
|
||||
- **With trigger mode off the summary is one line**: `mode: stream (trigger off)`. The four lines below
|
||||
describe a trigger that is not running, so quoting them would describe nothing. The dump carries the line
|
||||
twice — once as the probe found it and once at the end — because only a run whose *session* has the
|
||||
trigger off shows the stream form, and a run whose session has it on shows the settings.
|
||||
trigger off shows the stream form, and a run whose session has it on shows the settings. **The pair is
|
||||
also the refresh test**: the probe toggles the trigger, so a run whose session has it off must show
|
||||
`stream (trigger off)` in the line it found and the settings in the live one. Both showing `stream` is a
|
||||
compact view that did not refresh when the mode changed, which is a defect this pair caught.
|
||||
- **`trigger panel sensitive …=1` is now a guard, not a state.** The trigger's body is never disabled —
|
||||
a trigger is configured before it is switched on — so this reads `1` always, and a `0` means the
|
||||
settings have been made unreachable again. The *cursors'* body is disabled with the cursors, and that
|
||||
|
||||
+21
@@ -4436,6 +4436,25 @@ void on_pane_edit_clicked(GtkButton* button, gpointer user) {
|
||||
apply_pane_lock(*app);
|
||||
}
|
||||
|
||||
/// Hides the sidebar for the length of a drag, when it is *overlaid*, and restores it after.
|
||||
///
|
||||
/// With the split view collapsed the sidebar covers the plot -- that is what collapsed means -- so a drag
|
||||
/// that starts in the signal list has nowhere to land: the pointer is over the sidebar and never over a
|
||||
/// drop target, and the drag is impossible rather than merely awkward. Side by side nothing is done: the
|
||||
/// plot is already visible, and hiding the list the drag started in would be startling.
|
||||
void row_drag_sidebar(App& app, bool dragging) {
|
||||
if (app.side_view == nullptr) return;
|
||||
AdwOverlaySplitView* split = ADW_OVERLAY_SPLIT_VIEW(app.side_view);
|
||||
if (dragging) {
|
||||
if (!adw_overlay_split_view_get_collapsed(split)) return;
|
||||
app.sidebar_hidden_for_drag = adw_overlay_split_view_get_show_sidebar(split) != FALSE;
|
||||
if (app.sidebar_hidden_for_drag) adw_overlay_split_view_set_show_sidebar(split, FALSE);
|
||||
} else if (app.sidebar_hidden_for_drag) {
|
||||
app.sidebar_hidden_for_drag = false;
|
||||
adw_overlay_split_view_set_show_sidebar(split, TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
void run_app_action(App& app, AppAction action) {
|
||||
const auto toggle = [](GtkWidget* button) {
|
||||
if (button == nullptr) return;
|
||||
@@ -5782,6 +5801,8 @@ int run_ui(int argc, char** argv, const Config& cfg, ScopeModel& model, Acquisit
|
||||
app.window_limit_s);
|
||||
{
|
||||
GtkIconTheme* theme = gtk_icon_theme_get_for_display(gdk_display_get_default());
|
||||
std::printf("[udpscope] pane drag: sidebar hidden for the drag=%d restored after it=%d\n",
|
||||
app.dbg_drag_hid_sidebar, app.dbg_drag_restored_sidebar);
|
||||
std::printf("[udpscope] pane edits: locked=%s(%s) config=%s(%s)\n",
|
||||
app.dbg_pane_edit_icon.c_str(),
|
||||
gtk_icon_theme_has_icon(theme, app.dbg_pane_edit_icon.c_str()) ? "ok" : "MISSING",
|
||||
|
||||
@@ -261,6 +261,9 @@ struct App {
|
||||
/// True while `sync_panel_switches()` is writing the switches: setting a `GtkSwitch` emits its own
|
||||
/// `notify::active`, and without this the program's own write would come back as a click.
|
||||
bool panel_switch_sync = false;
|
||||
/// True while a drag has the overlaid sidebar hidden: what it was showing before, so `drag-end` puts it
|
||||
/// back rather than guessing.
|
||||
bool sidebar_hidden_for_drag = false;
|
||||
/// The channel a session's trigger names, until `rebuild_trigger_signals()` can resolve it: the
|
||||
/// file stores a *name* and the trigger needs an index into the acquired channels, which do not
|
||||
/// exist until the stream has been described. Empty means "nothing pending".
|
||||
@@ -822,6 +825,9 @@ struct App {
|
||||
int dbg_signal_rows = 0;
|
||||
/// How many rows of the list carry a `GtkDragSource`: `rows - 1`, the divider not being draggable.
|
||||
int dbg_signal_drag_sources = -1;
|
||||
/// The drag-with-an-overlaid-sidebar pair: the sidebar hid for the drag, and came back after it.
|
||||
int dbg_drag_hid_sidebar = -1;
|
||||
int dbg_drag_restored_sidebar = -1;
|
||||
std::size_t dbg_signal_plain = 0;
|
||||
int dbg_side_column_after_hide = 1;
|
||||
std::string dbg_mode_triggered;
|
||||
@@ -1462,6 +1468,10 @@ const char* trigger_mode_text(TriggerMode m);
|
||||
TriggerMode trigger_mode_from_text(const std::string& s);
|
||||
/// The panel's controls from the model, and the session's trigger into both.
|
||||
void write_trigger_widgets(App& app);
|
||||
/// Hides the sidebar for the length of a drag when it is *overlaid*, and puts it back after. With the
|
||||
/// split view collapsed the sidebar covers the plot, so a drag that starts in the signal list has nowhere
|
||||
/// to land: the pointer is over the sidebar, not over a drop target.
|
||||
void row_drag_sidebar(App& app, bool dragging);
|
||||
/// The compact line the trigger pane shows while it is locked, and the lock itself: the panes'
|
||||
/// bodies, their [edit] buttons and the signal list's rows all follow from `trigger_locked` and
|
||||
/// `signals_locked`.
|
||||
|
||||
@@ -103,6 +103,35 @@ gboolean exit_probe_step3(gpointer user) {
|
||||
}
|
||||
a->dbg_signal_drag_sources = sources;
|
||||
}
|
||||
// A drag out of an *overlaid* sidebar: the sidebar has to step aside or the drop has nowhere to land.
|
||||
// The state is forced the way the sidebar probe forces it -- collapsed is what a window with no room
|
||||
// beside the plot produces -- and the drag is begun through the *source*, so the handler is the one a
|
||||
// real drag reaches.
|
||||
if (a->side_view != nullptr && a->signal_list != nullptr) {
|
||||
AdwOverlaySplitView* split = ADW_OVERLAY_SPLIT_VIEW(a->side_view);
|
||||
GtkWidget* row = gtk_widget_get_first_child(a->signal_list);
|
||||
adw_overlay_split_view_set_collapsed(split, TRUE);
|
||||
adw_overlay_split_view_set_show_sidebar(split, TRUE);
|
||||
if (row != nullptr) {
|
||||
GListModel* controllers = gtk_widget_observe_controllers(row);
|
||||
const guint n = controllers != nullptr ? g_list_model_get_n_items(controllers) : 0;
|
||||
for (guint i = 0; i < n; ++i) {
|
||||
GObject* c = G_OBJECT(g_list_model_get_item(controllers, i));
|
||||
if (GTK_IS_DRAG_SOURCE(c)) {
|
||||
g_signal_emit_by_name(c, "drag-begin", nullptr);
|
||||
a->dbg_drag_hid_sidebar =
|
||||
adw_overlay_split_view_get_show_sidebar(split) == FALSE ? 1 : 0;
|
||||
row_drag_sidebar(*a, false);
|
||||
a->dbg_drag_restored_sidebar =
|
||||
adw_overlay_split_view_get_show_sidebar(split) != FALSE ? 1 : 0;
|
||||
}
|
||||
g_object_unref(c);
|
||||
}
|
||||
}
|
||||
// Left as the window's own width decides, so the readings taken before this are the ones that stand.
|
||||
adw_overlay_split_view_set_collapsed(split, FALSE);
|
||||
adw_overlay_split_view_set_show_sidebar(split, TRUE);
|
||||
}
|
||||
// The panes' own switches, and *both* directions of the sync: the header bar's trigger button is
|
||||
// flipped here and the pane's switch has to follow, then the cursors' switch is driven from the
|
||||
// pane and the button is what has to follow. Both are read back after the fact, so the two pairs in
|
||||
|
||||
@@ -457,6 +457,17 @@ void rebuild_signal_list(App& app, const std::vector<SignalInfo>& signals) {
|
||||
g_signal_connect_data(source, "prepare", G_CALLBACK(on_drag_prepare), ctx,
|
||||
[](gpointer data, GClosure*) { delete static_cast<DragContext*>(data); },
|
||||
G_CONNECT_DEFAULT);
|
||||
// A drag out of an *overlaid* sidebar would have nowhere to land (see `row_drag_sidebar()`), so the
|
||||
// sidebar steps out of the way for as long as the drag lasts.
|
||||
g_signal_connect(source, "drag-begin", G_CALLBACK(+[](GtkDragSource*, GdkDrag*, gpointer user) {
|
||||
row_drag_sidebar(*static_cast<App*>(user), true);
|
||||
}),
|
||||
&app);
|
||||
g_signal_connect(source, "drag-end",
|
||||
G_CALLBACK(+[](GtkDragSource*, GdkDrag*, gboolean, gpointer user) {
|
||||
row_drag_sidebar(*static_cast<App*>(user), false);
|
||||
}),
|
||||
&app);
|
||||
gtk_widget_add_controller(row, GTK_EVENT_CONTROLLER(source));
|
||||
|
||||
gtk_list_box_append(GTK_LIST_BOX(list), row);
|
||||
|
||||
+3
-1
@@ -413,8 +413,10 @@ void on_trigger_toggled(GtkToggleButton* button, gpointer user) {
|
||||
// Arming is a *runtime* control: with trigger mode off there is no engine to arm, and the button is the
|
||||
// one part of this pane that is not a setting. (The body itself stays live -- see above.)
|
||||
if (app->arm_button != nullptr) gtk_widget_set_sensitive(app->arm_button, on);
|
||||
// The pane's own switch shows the same state, whichever of the two was clicked.
|
||||
// The pane's own switch shows the same state, whichever of the two was clicked, and the compact line
|
||||
// changes shape with it: with trigger mode off it says "stream" and nothing else.
|
||||
sync_panel_switches(*app);
|
||||
sync_trigger_summary(*app);
|
||||
}
|
||||
|
||||
/// The way out of trigger mode, from inside the panel that froze the view: it is
|
||||
|
||||
Reference in new issue
Block a user