Drag-reorder sessions in the sidebar #1
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Context
Terminalko is a GTK4/libadwaita terminal emulator in Rust, organised around a
vertical session sidebar instead of a tab bar. Sessions live in folders (one
level, by design) and their order is user-visible and persisted.
Relevant code:
crates/terminalko/src/window.rs— builds the sidebar (rebuild_sidebar,sidebar_item), holdsimp.order(session id per row, in display order) andthe session actions.
crates/terminalko-config/src/session.rs— the model. Order and membershiplive only in each folder's
sessionslist key (andscratch-sessionsforunfiled ones).
Store::move_session(id, folder, position)andStore::shift_session(id, delta)already exist and are unit-tested.docs/spec/session-sidebar.md— the sidebar's specification, which recordsthis gap.
Problem
Reordering sessions, and moving them between folders, is currently only possible
through the row's context menu (Move Up / Move Down / Move to folder).
Dragging a row is the obvious gesture and does nothing.
Why it was not done
adw::Sidebar(libadwaita 1.9) exposes the drop side only —connect_drop,connect_drop_enter,connect_drop_value_loaded,setup_drop_target— and has no per-row drag source, so a row cannot bepicked up. Verified against libadwaita 0.9.2 bindings;
AdwSidebarItemis aplain
GObjectwith no widget and no setters, so nothing can be attached to arow either.
Reordering by dragging therefore means replacing
adw::Sidebarwith ahand-built list.
Suggested approach
Replace the sidebar widget with
GtkListView+GtkSelectionModelover aGListStoreof row objects, keeping the same visual language:TkSessionRowGObject holding session id, name, kind icon, accentand state, so a row widget can be bound to it and updated in place (this also
removes the current rebuild-everything approach).
GtkListItemFactoryproducing a row widget: 4px accent bar, kind icon, name,badge cluster, state dot, close-on-hover — see
accent::row_suffix.GtkDragSourceon each row carrying the session id, andGtkDropTargetonrows and on folder headers. Drop above/below is decided from the pointer
position within the row.
Store::move_session(id, folder, position); themodel is already the single source of truth, so nothing else needs to change.
GtkListView's header factoryor a flattened model carrying header rows.
Keep the existing keyboard reordering: dragging must not become the only way.
Acceptance criteria
order survives restarting the application.
./build-aux/ci.shpasses (fmt, clippy with-D warnings, tests, releasebuild) and
./build-aux/smoke.shpasses (needs a display).docs/spec/session-sidebar.mdupdated: the "drag-and-drop reordering is notimplemented" paragraph replaced with what it now does.
Notes
from GNOME Console or Ptyxis. See
CONTRIBUTING.mdfor the rule and thespec-first process.
headless (
GSETTINGS_BACKEND=memory), so put reorder logic there rather thanin the widget where possible.
Numbers from #17, since they change what this issue is worth and one of its design choices.
rebuild_sidebaris 152.6 ms at five hundred sessions across twenty folders on a release build, and one rebuild stops fitting in a 60 Hz frame at about sixty sessions. So the list view here is not only about dragging — it is the fix for the rebuild cost too, and the second reason to do it.Two findings that bear on the approach:
Store::layoutplus every per-row settings read is 1.7 ms of the 152.6 ms, so the model side needs nothing. AGtkListViewthat builds widgets only for visible rows fixes essentially all of it, andTkSessionRowneed not cache anything from the store to be fast.AdwSidebarSection— header factory or flattened model — should not build a widget per folder eagerly. This is a point in favour ofGtkListView's header factory over a flattened model carrying header rows.Reproduce with
cargo run -p terminalko-config --example gen-tree(added in #27); it refuses to run unlessGSETTINGS_BACKENDiskeyfileormemory, because it erases the tree first.