dear_imgui_rs/widget/
multi_select.rs

1//! Multi-select helpers (BeginMultiSelect/EndMultiSelect)
2//!
3//! This module provides a small, safe wrapper around Dear ImGui's multi-select
4//! API introduced in 1.92 (`BeginMultiSelect` / `EndMultiSelect`), following
5//! the "external storage" pattern described in the official docs:
6//! https://github.com/ocornut/imgui/wiki/Multi-Select
7//!
8//! The main entry point is [`Ui::multi_select_indexed`], which:
9//! - wraps `BeginMultiSelect()` / `EndMultiSelect()`
10//! - wires `SetNextItemSelectionUserData()` for each item (index-based)
11//! - applies selection requests to your storage using a simple trait.
12
13#![allow(
14    clippy::cast_possible_truncation,
15    clippy::cast_sign_loss,
16    clippy::as_conversions
17)]
18
19use crate::Ui;
20use crate::sys;
21use std::collections::HashSet;
22
23bitflags::bitflags! {
24    /// Flags controlling multi-selection behavior.
25    ///
26    /// These mirror Dear ImGui's `ImGuiMultiSelectFlags` and control how
27    /// selection works (single vs multi, box-select, keyboard shortcuts, etc).
28    #[repr(transparent)]
29    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
30    pub struct MultiSelectFlags: i32 {
31        /// No flags.
32        const NONE = sys::ImGuiMultiSelectFlags_None as i32;
33        /// Single-selection scope. Ctrl/Shift range selection is disabled.
34        const SINGLE_SELECT = sys::ImGuiMultiSelectFlags_SingleSelect as i32;
35        /// Disable `Ctrl+A` "select all" shortcut.
36        const NO_SELECT_ALL = sys::ImGuiMultiSelectFlags_NoSelectAll as i32;
37        /// Disable range selection (Shift+click / Shift+arrow).
38        const NO_RANGE_SELECT = sys::ImGuiMultiSelectFlags_NoRangeSelect as i32;
39        /// Disable automatic selection of newly focused items.
40        const NO_AUTO_SELECT = sys::ImGuiMultiSelectFlags_NoAutoSelect as i32;
41        /// Disable automatic clearing of selection when focus moves within the scope.
42        const NO_AUTO_CLEAR = sys::ImGuiMultiSelectFlags_NoAutoClear as i32;
43        /// Disable automatic clearing when reselecting the same range.
44        const NO_AUTO_CLEAR_ON_RESELECT =
45            sys::ImGuiMultiSelectFlags_NoAutoClearOnReselect as i32;
46        /// Enable 1D box-select (same x, full-width rows).
47        const BOX_SELECT_1D = sys::ImGuiMultiSelectFlags_BoxSelect1d as i32;
48        /// Enable 2D box-select (arbitrary item layout).
49        const BOX_SELECT_2D = sys::ImGuiMultiSelectFlags_BoxSelect2d as i32;
50        /// Disable drag-scrolling when box-selecting near edges of the scope.
51        const BOX_SELECT_NO_SCROLL = sys::ImGuiMultiSelectFlags_BoxSelectNoScroll as i32;
52        /// Clear selection when pressing Escape while the scope is focused.
53        const CLEAR_ON_ESCAPE = sys::ImGuiMultiSelectFlags_ClearOnEscape as i32;
54        /// Clear selection when clicking on empty space (void) inside the scope.
55        const CLEAR_ON_CLICK_VOID = sys::ImGuiMultiSelectFlags_ClearOnClickVoid as i32;
56        /// Scope is the whole window (default).
57        const SCOPE_WINDOW = sys::ImGuiMultiSelectFlags_ScopeWindow as i32;
58        /// Scope is a rectangular region between `BeginMultiSelect()`/`EndMultiSelect()`.
59        const SCOPE_RECT = sys::ImGuiMultiSelectFlags_ScopeRect as i32;
60        /// Apply selection to items on mouse down.
61        const SELECT_ON_CLICK = sys::ImGuiMultiSelectFlags_SelectOnClick as i32;
62        /// Apply selection on mouse release (allows dragging without altering selection).
63        const SELECT_ON_CLICK_RELEASE =
64            sys::ImGuiMultiSelectFlags_SelectOnClickRelease as i32;
65        /// Enable X-axis navigation wrap helper.
66        const NAV_WRAP_X = sys::ImGuiMultiSelectFlags_NavWrapX as i32;
67        /// Disable default right-click behavior that selects item before opening a context menu.
68        const NO_SELECT_ON_RIGHT_CLICK =
69            sys::ImGuiMultiSelectFlags_NoSelectOnRightClick as i32;
70    }
71}
72
73/// Selection container backed by Dear ImGui's `ImGuiSelectionBasicStorage`.
74///
75/// This stores a set of selected `ImGuiID` values using the optimized helper
76/// provided by Dear ImGui. It is suitable when items are naturally identified
77/// by stable IDs (e.g. table rows, tree nodes).
78#[derive(Debug)]
79pub struct BasicSelection {
80    raw: *mut sys::ImGuiSelectionBasicStorage,
81}
82
83impl BasicSelection {
84    /// Create an empty selection storage.
85    pub fn new() -> Self {
86        unsafe {
87            let ptr = sys::ImGuiSelectionBasicStorage_ImGuiSelectionBasicStorage();
88            if ptr.is_null() {
89                panic!("ImGuiSelectionBasicStorage_ImGuiSelectionBasicStorage() returned null");
90            }
91            Self { raw: ptr }
92        }
93    }
94
95    /// Return the number of selected items.
96    pub fn len(&self) -> usize {
97        unsafe { (*self.raw).Size as usize }
98    }
99
100    /// Returns true if the selection is empty.
101    pub fn is_empty(&self) -> bool {
102        self.len() == 0
103    }
104
105    /// Clear the selection set.
106    pub fn clear(&mut self) {
107        unsafe {
108            sys::ImGuiSelectionBasicStorage_Clear(self.raw);
109        }
110    }
111
112    /// Returns true if the given id is selected.
113    pub fn contains(&self, id: crate::Id) -> bool {
114        unsafe { sys::ImGuiSelectionBasicStorage_Contains(self.raw, id.raw()) }
115    }
116
117    /// Set selection state for a given id.
118    pub fn set_selected(&mut self, id: crate::Id, selected: bool) {
119        unsafe {
120            sys::ImGuiSelectionBasicStorage_SetItemSelected(self.raw, id.raw(), selected);
121        }
122    }
123
124    /// Iterate over selected ids.
125    pub fn iter(&self) -> BasicSelectionIter<'_> {
126        BasicSelectionIter {
127            storage: self,
128            it: std::ptr::null_mut(),
129        }
130    }
131
132    /// Expose raw pointer for internal helpers.
133    pub(crate) fn as_raw(&self) -> *mut sys::ImGuiSelectionBasicStorage {
134        self.raw
135    }
136}
137
138impl Default for BasicSelection {
139    fn default() -> Self {
140        Self::new()
141    }
142}
143
144impl Drop for BasicSelection {
145    fn drop(&mut self) {
146        unsafe {
147            if !self.raw.is_null() {
148                sys::ImGuiSelectionBasicStorage_destroy(self.raw);
149                self.raw = std::ptr::null_mut();
150            }
151        }
152    }
153}
154
155/// Iterator over selected ids stored in [`BasicSelection`].
156pub struct BasicSelectionIter<'a> {
157    storage: &'a BasicSelection,
158    it: *mut std::os::raw::c_void,
159}
160
161impl<'a> Iterator for BasicSelectionIter<'a> {
162    type Item = crate::Id;
163
164    fn next(&mut self) -> Option<Self::Item> {
165        unsafe {
166            let mut out_id: sys::ImGuiID = 0;
167            let has_next = sys::ImGuiSelectionBasicStorage_GetNextSelectedItem(
168                self.storage.as_raw(),
169                &mut self.it,
170                &mut out_id,
171            );
172            if has_next {
173                Some(crate::Id::from(out_id))
174            } else {
175                None
176            }
177        }
178    }
179}
180
181/// Index-based selection storage for multi-select helpers.
182///
183/// Implement this trait for your selection container (e.g. `Vec<bool>`,
184/// `Vec<MyItem { selected: bool }>` or a custom type) to use
185/// [`Ui::multi_select_indexed`].
186pub trait MultiSelectIndexStorage {
187    /// Total number of items in the selection scope.
188    fn len(&self) -> usize;
189
190    /// Returns whether item at `index` is currently selected.
191    fn is_selected(&self, index: usize) -> bool;
192
193    /// Updates selection state for item at `index`.
194    fn set_selected(&mut self, index: usize, selected: bool);
195
196    /// Optional hint for current selection size.
197    ///
198    /// If provided, this is forwarded to `BeginMultiSelect()` to improve the
199    /// behavior of shortcuts such as `ImGuiMultiSelectFlags_ClearOnEscape`.
200    /// When `None` (default), the size is treated as "unknown".
201    fn selected_count_hint(&self) -> Option<usize> {
202        None
203    }
204}
205
206impl MultiSelectIndexStorage for Vec<bool> {
207    fn len(&self) -> usize {
208        self.len()
209    }
210
211    fn is_selected(&self, index: usize) -> bool {
212        self.get(index).copied().unwrap_or(false)
213    }
214
215    fn set_selected(&mut self, index: usize, selected: bool) {
216        if index < self.len() {
217            self[index] = selected;
218        }
219    }
220
221    fn selected_count_hint(&self) -> Option<usize> {
222        // For typical lists this is cheap enough; callers with large datasets
223        // can implement the trait manually with a more efficient counter.
224        Some(self.iter().filter(|&&b| b).count())
225    }
226}
227
228impl<'a> MultiSelectIndexStorage for &'a mut [bool] {
229    fn len(&self) -> usize {
230        (**self).len()
231    }
232
233    fn is_selected(&self, index: usize) -> bool {
234        self.get(index).copied().unwrap_or(false)
235    }
236
237    fn set_selected(&mut self, index: usize, selected: bool) {
238        if index < self.len() {
239            self[index] = selected;
240        }
241    }
242
243    fn selected_count_hint(&self) -> Option<usize> {
244        Some(self.iter().filter(|&&b| b).count())
245    }
246}
247
248/// Index-based selection storage backed by a key slice + `HashSet` of selected keys.
249///
250/// This is convenient when your application stores selection as a set of
251/// arbitrary keys (e.g. `HashSet<u32>` or `HashSet<MyId>`), but you still
252/// want to drive a multi-select scope using contiguous indices.
253pub struct KeySetSelection<'a, K>
254where
255    K: Eq + std::hash::Hash + Copy,
256{
257    keys: &'a [K],
258    selected: &'a mut HashSet<K>,
259}
260
261impl<'a, K> KeySetSelection<'a, K>
262where
263    K: Eq + std::hash::Hash + Copy,
264{
265    /// Create a new index-based view over a key slice and a selection set.
266    ///
267    /// - `keys`: stable index->key mapping (e.g. your backing array).
268    /// - `selected`: set of currently selected keys.
269    pub fn new(keys: &'a [K], selected: &'a mut HashSet<K>) -> Self {
270        Self { keys, selected }
271    }
272}
273
274impl<'a, K> MultiSelectIndexStorage for KeySetSelection<'a, K>
275where
276    K: Eq + std::hash::Hash + Copy,
277{
278    fn len(&self) -> usize {
279        self.keys.len()
280    }
281
282    fn is_selected(&self, index: usize) -> bool {
283        self.keys
284            .get(index)
285            .map(|k| self.selected.contains(k))
286            .unwrap_or(false)
287    }
288
289    fn set_selected(&mut self, index: usize, selected: bool) {
290        if let Some(&key) = self.keys.get(index) {
291            if selected {
292                self.selected.insert(key);
293            } else {
294                self.selected.remove(&key);
295            }
296        }
297    }
298
299    fn selected_count_hint(&self) -> Option<usize> {
300        Some(self.selected.len())
301    }
302}
303
304/// Apply `ImGuiMultiSelectIO` requests to index-based selection storage.
305///
306/// This mirrors `ImGuiSelectionExternalStorage::ApplyRequests` from Dear ImGui,
307/// but operates on the safe [`MultiSelectIndexStorage`] trait instead of relying
308/// on C callbacks.
309unsafe fn apply_multi_select_requests_indexed<S: MultiSelectIndexStorage>(
310    ms_io: *mut sys::ImGuiMultiSelectIO,
311    storage: &mut S,
312) {
313    unsafe {
314        if ms_io.is_null() {
315            return;
316        }
317
318        let io_ref: &mut sys::ImGuiMultiSelectIO = &mut *ms_io;
319        let items_count = if io_ref.ItemsCount < 0 {
320            0
321        } else {
322            io_ref.ItemsCount as usize
323        };
324
325        let requests = &mut io_ref.Requests;
326        if requests.Data.is_null() || requests.Size <= 0 {
327            return;
328        }
329
330        let slice = std::slice::from_raw_parts_mut(requests.Data, requests.Size as usize);
331
332        for req in slice {
333            if req.Type == sys::ImGuiSelectionRequestType_SetAll {
334                for idx in 0..items_count {
335                    storage.set_selected(idx, req.Selected);
336                }
337            } else if req.Type == sys::ImGuiSelectionRequestType_SetRange {
338                let first = req.RangeFirstItem as i32;
339                let last = req.RangeLastItem as i32;
340                if first < 0 || last < first {
341                    continue;
342                }
343                let last_clamped = std::cmp::min(last as usize, items_count.saturating_sub(1));
344                for idx in first as usize..=last_clamped {
345                    storage.set_selected(idx, req.Selected);
346                }
347            }
348        }
349    }
350}
351
352/// RAII wrapper around `BeginMultiSelect()` / `EndMultiSelect()` for advanced users.
353///
354/// This gives direct, but scoped, access to the underlying `ImGuiMultiSelectIO`
355/// struct. It does not perform any selection updates by itself; you are expected
356/// to call helper methods or use the raw IO to drive your own storage.
357pub struct MultiSelectScope<'ui> {
358    ms_io_begin: *mut sys::ImGuiMultiSelectIO,
359    items_count: i32,
360    _marker: std::marker::PhantomData<&'ui Ui>,
361}
362
363impl<'ui> MultiSelectScope<'ui> {
364    fn new(flags: MultiSelectFlags, selection_size: Option<i32>, items_count: usize) -> Self {
365        let selection_size_i32 = selection_size.unwrap_or(-1);
366        let ms_io_begin = unsafe {
367            sys::igBeginMultiSelect(flags.bits(), selection_size_i32, items_count as i32)
368        };
369        Self {
370            ms_io_begin,
371            items_count: items_count as i32,
372            _marker: std::marker::PhantomData,
373        }
374    }
375
376    /// Access the IO struct returned by `BeginMultiSelect()`.
377    pub fn begin_io(&self) -> &sys::ImGuiMultiSelectIO {
378        unsafe { &*self.ms_io_begin }
379    }
380
381    /// Mutable access to the IO struct returned by `BeginMultiSelect()`.
382    pub fn begin_io_mut(&mut self) -> &mut sys::ImGuiMultiSelectIO {
383        unsafe { &mut *self.ms_io_begin }
384    }
385
386    /// Apply selection requests from `BeginMultiSelect()` to index-based storage.
387    pub fn apply_begin_requests_indexed<S: MultiSelectIndexStorage>(&mut self, storage: &mut S) {
388        unsafe {
389            apply_multi_select_requests_indexed(self.ms_io_begin, storage);
390        }
391    }
392
393    /// Finalize the multi-select scope and return an IO view for the end state.
394    ///
395    /// This calls `EndMultiSelect()` and returns a `MultiSelectEnd` wrapper
396    /// that can be used to apply the final selection requests.
397    pub fn end(self) -> MultiSelectEnd<'ui> {
398        let ms_io_end = unsafe { sys::igEndMultiSelect() };
399        MultiSelectEnd {
400            ms_io_end,
401            items_count: self.items_count,
402            _marker: std::marker::PhantomData,
403        }
404    }
405}
406
407/// IO view returned after calling `EndMultiSelect()` via [`MultiSelectScope::end`].
408pub struct MultiSelectEnd<'ui> {
409    ms_io_end: *mut sys::ImGuiMultiSelectIO,
410    items_count: i32,
411    _marker: std::marker::PhantomData<&'ui Ui>,
412}
413
414impl<'ui> MultiSelectEnd<'ui> {
415    /// Access the IO struct returned by `EndMultiSelect()`.
416    pub fn io(&self) -> &sys::ImGuiMultiSelectIO {
417        unsafe { &*self.ms_io_end }
418    }
419
420    /// Mutable access to the IO struct returned by `EndMultiSelect()`.
421    pub fn io_mut(&mut self) -> &mut sys::ImGuiMultiSelectIO {
422        unsafe { &mut *self.ms_io_end }
423    }
424
425    /// Apply selection requests from `EndMultiSelect()` to index-based storage.
426    pub fn apply_requests_indexed<S: MultiSelectIndexStorage>(&mut self, storage: &mut S) {
427        unsafe {
428            apply_multi_select_requests_indexed(self.ms_io_end, storage);
429        }
430    }
431
432    /// Apply selection requests from `EndMultiSelect()` to a [`BasicSelection`].
433    pub fn apply_requests_basic<G>(&mut self, selection: &mut BasicSelection, mut id_at_index: G)
434    where
435        G: FnMut(usize) -> crate::Id,
436    {
437        unsafe {
438            apply_multi_select_requests_basic(
439                self.ms_io_end,
440                selection,
441                self.items_count as usize,
442                &mut id_at_index,
443            );
444        }
445    }
446}
447
448impl Ui {
449    /// Low-level entry point: begin a multi-select scope and return a RAII wrapper.
450    ///
451    /// This is the closest safe wrapper to the raw `BeginMultiSelect()` /
452    /// `EndMultiSelect()` pair. It does not drive any selection storage by
453    /// itself; use `begin_io()` / `end().io()` and the helper methods to
454    /// implement custom patterns.
455    pub fn begin_multi_select_raw(
456        &self,
457        flags: MultiSelectFlags,
458        selection_size: Option<i32>,
459        items_count: usize,
460    ) -> MultiSelectScope<'_> {
461        MultiSelectScope::new(flags, selection_size, items_count)
462    }
463    /// Multi-select helper for index-based storage.
464    ///
465    /// This wraps `BeginMultiSelect()` / `EndMultiSelect()` and applies
466    /// selection requests to an index-addressable selection container.
467    ///
468    /// Typical usage:
469    ///
470    /// ```no_run
471    /// # use dear_imgui_rs::*;
472    /// # let mut ctx = Context::create();
473    /// # let ui = ctx.frame();
474    /// let mut selected = vec![false; 128];
475    ///
476    /// ui.multi_select_indexed(&mut selected, MultiSelectFlags::NONE, |ui, idx, is_selected| {
477    ///     ui.text(format!(
478    ///         "{} {}",
479    ///         if is_selected { "[x]" } else { "[ ]" },
480    ///         idx
481    ///     ));
482    /// });
483    /// ```
484    ///
485    /// Notes:
486    /// - `storage.len()` defines `items_count`.
487    /// - This helper uses the "external storage" pattern where selection is
488    ///   stored entirely on the application side.
489    /// - Per-item selection toggles can be queried via
490    ///   [`Ui::is_item_toggled_selection`].
491    pub fn multi_select_indexed<S, F>(
492        &self,
493        storage: &mut S,
494        flags: MultiSelectFlags,
495        mut render_item: F,
496    ) where
497        S: MultiSelectIndexStorage,
498        F: FnMut(&Ui, usize, bool),
499    {
500        let items_count = storage.len();
501        let selection_size_i32 = storage
502            .selected_count_hint()
503            .and_then(|n| i32::try_from(n).ok())
504            .unwrap_or(-1);
505
506        // Begin multi-select scope.
507        let ms_io_begin = unsafe {
508            sys::igBeginMultiSelect(flags.bits(), selection_size_i32, items_count as i32)
509        };
510
511        // Apply SetAll requests (if any) before submitting items.
512        unsafe {
513            apply_multi_select_requests_indexed(ms_io_begin, storage);
514        }
515
516        // Submit items: for each index we set SelectionUserData and let user
517        // draw widgets, passing the current selection state as `is_selected`.
518        for idx in 0..items_count {
519            unsafe {
520                sys::igSetNextItemSelectionUserData(idx as sys::ImGuiSelectionUserData);
521            }
522            let is_selected = storage.is_selected(idx);
523            render_item(self, idx, is_selected);
524        }
525
526        // End scope and apply requests generated during item submission.
527        let ms_io_end = unsafe { sys::igEndMultiSelect() };
528        unsafe {
529            apply_multi_select_requests_indexed(ms_io_end, storage);
530        }
531    }
532
533    /// Multi-select helper for index-based storage inside an active table.
534    ///
535    /// This is a convenience wrapper over [`Ui::multi_select_indexed`] that
536    /// automatically advances table rows and starts each row at column 0.
537    ///
538    /// It expects to be called between `BeginTable`/`EndTable`.
539    pub fn table_multi_select_indexed<S, F>(
540        &self,
541        storage: &mut S,
542        flags: MultiSelectFlags,
543        mut build_row: F,
544    ) where
545        S: MultiSelectIndexStorage,
546        F: FnMut(&Ui, usize, bool),
547    {
548        let row_count = storage.len();
549        let selection_size_i32 = storage
550            .selected_count_hint()
551            .and_then(|n| i32::try_from(n).ok())
552            .unwrap_or(-1);
553
554        let ms_io_begin =
555            unsafe { sys::igBeginMultiSelect(flags.bits(), selection_size_i32, row_count as i32) };
556
557        unsafe {
558            apply_multi_select_requests_indexed(ms_io_begin, storage);
559        }
560
561        for row in 0..row_count {
562            unsafe {
563                sys::igSetNextItemSelectionUserData(row as sys::ImGuiSelectionUserData);
564            }
565            // Start a new table row and move to first column.
566            self.table_next_row();
567            self.table_next_column();
568
569            let is_selected = storage.is_selected(row);
570            build_row(self, row, is_selected);
571        }
572
573        let ms_io_end = unsafe { sys::igEndMultiSelect() };
574        unsafe {
575            apply_multi_select_requests_indexed(ms_io_end, storage);
576        }
577    }
578
579    /// Multi-select helper using [`BasicSelection`] as underlying storage.
580    ///
581    /// This variant is suitable when items are naturally identified by `ImGuiID`
582    /// (e.g. stable ids for rows or tree nodes).
583    ///
584    /// - `items_count`: number of items in the scope.
585    /// - `id_at_index`: maps `[0, items_count)` to the corresponding item id.
586    /// - `render_item`: called once per index to emit widgets for that item.
587    pub fn multi_select_basic<G, F>(
588        &self,
589        selection: &mut BasicSelection,
590        flags: MultiSelectFlags,
591        items_count: usize,
592        mut id_at_index: G,
593        mut render_item: F,
594    ) where
595        G: FnMut(usize) -> crate::Id,
596        F: FnMut(&Ui, usize, crate::Id, bool),
597    {
598        let selection_size_i32 = i32::try_from(selection.len()).unwrap_or(-1);
599
600        let ms_io_begin = unsafe {
601            sys::igBeginMultiSelect(flags.bits(), selection_size_i32, items_count as i32)
602        };
603
604        unsafe {
605            apply_multi_select_requests_basic(
606                ms_io_begin,
607                selection,
608                items_count,
609                &mut id_at_index,
610            );
611        }
612
613        for idx in 0..items_count {
614            unsafe {
615                sys::igSetNextItemSelectionUserData(idx as sys::ImGuiSelectionUserData);
616            }
617            let id = id_at_index(idx);
618            let is_selected = selection.contains(id);
619            render_item(self, idx, id, is_selected);
620        }
621
622        let ms_io_end = unsafe { sys::igEndMultiSelect() };
623        unsafe {
624            apply_multi_select_requests_basic(ms_io_end, selection, items_count, &mut id_at_index);
625        }
626    }
627}
628
629/// Apply multi-select requests to a `BasicSelection` using an index→id mapping.
630unsafe fn apply_multi_select_requests_basic<G>(
631    ms_io: *mut sys::ImGuiMultiSelectIO,
632    selection: &mut BasicSelection,
633    items_count: usize,
634    id_at_index: &mut G,
635) where
636    G: FnMut(usize) -> crate::Id,
637{
638    unsafe {
639        if ms_io.is_null() {
640            return;
641        }
642
643        let io_ref: &mut sys::ImGuiMultiSelectIO = &mut *ms_io;
644        let requests = &mut io_ref.Requests;
645        if requests.Data.is_null() || requests.Size <= 0 {
646            return;
647        }
648
649        let slice = std::slice::from_raw_parts_mut(requests.Data, requests.Size as usize);
650
651        for req in slice {
652            if req.Type == sys::ImGuiSelectionRequestType_SetAll {
653                for idx in 0..items_count {
654                    let id = id_at_index(idx);
655                    selection.set_selected(id, req.Selected);
656                }
657            } else if req.Type == sys::ImGuiSelectionRequestType_SetRange {
658                let first = req.RangeFirstItem as i32;
659                let last = req.RangeLastItem as i32;
660                if first < 0 || last < first {
661                    continue;
662                }
663                let last_clamped = std::cmp::min(last as usize, items_count.saturating_sub(1));
664                for idx in first as usize..=last_clamped {
665                    let id = id_at_index(idx);
666                    selection.set_selected(id, req.Selected);
667                }
668            }
669        }
670    }
671}