SelectionModel
SelectionModel — index-based selection state for collection widgets.
Public types
| Kind | Name |
|---|---|
enum | SelectionMode — Selection behavior mode |
struct | SelectionModel — Manages selection state for a collection widget |
Public functions
SelectionModel
| Returns | Function |
|---|---|
| Constructors | |
Self | new(mode: SelectionMode) |
| Builder methods | |
Self | debug_named(_name: impl Into<String>) |
| Methods | |
SelectionMode | mode() |
Signal<BTreeSet<usize>> | selection_signal() |
bool | is_selected(index: usize) |
Vec<usize> | selected_indices() |
usize | count() |
select(index: usize) | |
toggle(index: usize) | |
extend_to(index: usize) | |
extend_to_additive(index: usize) | |
select_indices(indices: impl IntoIterator<Item = usize>, additive: bool) | |
select_all(count: usize) | |
clear() | |
adjust_for_insert(start: usize, count: usize) | |
adjust_for_remove(start: usize, count: usize) | |
adjust_for_move(from: usize, to: usize, count: usize) | |
invalidate_anchor_from(first_changed: usize) |
Detailed description
SelectionModel manages which flat indices are selected in a
ListView, TreeView, TableView, or GridView. It is a
share-by-clone handle (Rc<RefCell<…>> internally): pass a clone to
each view that should share selection state. The current selection is
exposed as a reactive Signal<BTreeSet<usize>> so widgets can bind to
it without polling.
Three selection behaviours are available via SelectionMode: None
(read-only / no interaction), Single (at most one item), and Multi
(Ctrl+click toggle + Shift+click range extension via an internal anchor).
Mutators automatically notify all Signal observers after every change,
and the helper methods adjust_for_insert / adjust_for_remove /
adjust_for_move keep selected indices consistent when the underlying
source mutates.
When to use SelectionModel vs KeyedSelectionModel
Use SelectionModel (this type) for views that are backed by a plain
ListModel<T> or a SortFilterListModel<T> where position is the
natural identity. Use crate::KeyedSelectionModel when items carry a
stable app-defined key (e.g. a NodeId or a UUID) and selection must
survive sort/filter rebuilds or window slides that renumber visible indices.
#![allow(unused)] fn main() { use teksilo_data::{SelectionModel, SelectionMode}; let sel = SelectionModel::new(SelectionMode::Multi); sel.select(2); // clear-and-select index 2, anchor = 2 sel.toggle(5); // add index 5 (Ctrl+click behaviour) sel.extend_to(8); // extend from anchor 5 to 8 (Shift+click behaviour) assert!(sel.is_selected(2)); assert_eq!(sel.count(), 5); // 2, 5, 6, 7, 8 sel.clear(); assert_eq!(sel.count(), 0); }
API reference
📖 Full rustdoc API for this module
pub enum SelectionMode
Import: use teksilo::data::SelectionMode;
Selection behavior mode.
#![allow(unused)] fn main() { pub enum SelectionMode { /* variants */ } }
Variants
None— No selection allowed.Single— At most one item selected at a time.Multi— Multiple items can be selected (Ctrl+click toggles, Shift+click extends).
pub struct SelectionModel
Import: use teksilo::data::SelectionModel;
Manages selection state for a collection widget.
The selection is exposed as a Signal<BTreeSet<usize>> so widgets can
observe changes reactively.
#![allow(unused)] fn main() { pub struct SelectionModel { /* fields */ } }
Methods
pub fn new(mode: SelectionMode) -> Self
Create a new selection model with the given mode.
pub fn mode(&self) -> SelectionMode
The selection mode.
pub fn selection_signal(&self) -> Signal<BTreeSet<usize>>
Get a clone of the selection signal for reactive binding.
pub fn is_selected(&self, index: usize) -> bool
Whether the given index is currently selected.
pub fn selected_indices(&self) -> Vec<usize>
The currently selected indices, sorted.
pub fn count(&self) -> usize
Number of selected items.
pub fn select(&self, index: usize)
Select a single index. In Single mode, clears previous selection.
In Multi mode, clears previous and selects just this one (use toggle
for Ctrl+click behavior). Sets the anchor for subsequent Shift+click.
pub fn toggle(&self, index: usize)
Toggle selection of a single index (for Ctrl+click in Multi mode).
In Single mode, behaves like select().
pub fn extend_to(&self, index: usize)
Extend the selection from the anchor to the given index (for Shift+click
and Shift+navigation). In Single mode, behaves like select().
The result is base ∪ anchor..=index, where base is the selection as
it stood at the last non-extending mutation — not the current
selection. So the range tracks the anchor in both directions: reversing
a Shift gesture shrinks it, and Shift+End followed by Shift+Home
leaves one row selected rather than the whole collection. The anchor
itself does not move.
pub fn extend_to_additive(&self, index: usize)
Extend from the anchor to index, keeping whatever was selected when
this gesture began (Ctrl+Shift+navigation).
The difference from extend_to is only which set the
range is unioned with: a plain gesture starts from the base committed by
the last click or toggle, while this one captures the live selection on
its first keystroke, so a second disjoint range can be built without
losing the first. Subsequent keystrokes in the same gesture reuse that
capture, so the range still shrinks when reversed.
pub fn select_indices(&self, indices: impl IntoIterator<Item = usize>, additive: bool)
Replace the selection with indices (or, when additive, union them
into the current selection). Used by rubber-band / marquee selection,
where the selected set is an arbitrary subset rather than a range. In
Single mode the highest index wins; None mode is a no-op.
pub fn select_all(&self, count: usize)
Select all indices from 0 to count-1.
A no-op in None mode, and also in Single mode — "select all" has
no coherent meaning for a control that holds at most one item, and
silently selecting one arbitrary row would be worse than doing
nothing. This mirrors what the gated call sites already do
(ListView's Ctrl+A handler, which documents it as "Multi selection
only — a no-op for Single / None, matching every list control", and
TableView's select_all helper, which matches only the Multi
modes). Enforcing it here too keeps an ungated caller — GridView's
Ctrl+A handler is one — from breaking the Single invariant that
every other mutator on this type upholds.
pub fn clear(&self)
Clear the selection.
pub fn adjust_for_insert(&self, start: usize, count: usize)
Adjust selection indices after items are inserted.
Indices >= start are shifted up by count.
pub fn adjust_for_remove(&self, start: usize, count: usize)
Adjust selection indices after items are removed.
Indices in start..start+count are deselected; indices above are shifted down.
pub fn adjust_for_move(&self, from: usize, to: usize, count: usize)
Adjust selection indices after a block of count items moved from
from to to (a post-removal index, matching ListModel::move_item).
Selected indices follow their items, so a dragged row stays selected.
pub fn invalidate_anchor_from(&self, first_changed: usize)
Drop the range anchor when a projection has renumbered the rows under
it, so the next Shift gesture starts from the cursor rather than from
a row that has since moved.
A sort/filter proxy signals a blanket reset rather than a per-row
delta, so adjust_for_* never runs and an index anchor silently comes
to mean a different row. Views that read
first_changed_index() from SortFilterListModel / TreeSlice /
TreeDataSlice / SortFilterTreeModel call this with it: everything
before that index still means what it meant, so an anchor there
survives. Qt hit the same bug and fixed it by making the anchor a
persistent index; KeyedSelectionModel avoids it by construction.
pub fn debug_named(self, _name: impl Into<String>) -> Self
Register this selection model with the debug inspector under
name. In release builds (!cfg(debug_assertions)) this is a
no-op pass-through so call sites stay free of #[cfg] lines.
Idempotent on repeated calls — the latest registration wins.
The registration drops automatically when the last
SelectionModel handle is freed (the strong adapter Rc lives
inside a shared holder; the registry holds only a Weak).