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 {
98            let size = (*self.raw).Size;
99            if size <= 0 { 0 } else { size as usize }
100        }
101    }
102
103    /// Returns true if the selection is empty.
104    pub fn is_empty(&self) -> bool {
105        self.len() == 0
106    }
107
108    /// Clear the selection set.
109    pub fn clear(&mut self) {
110        unsafe {
111            sys::ImGuiSelectionBasicStorage_Clear(self.raw);
112        }
113    }
114
115    /// Returns true if the given id is selected.
116    pub fn contains(&self, id: crate::Id) -> bool {
117        unsafe { sys::ImGuiSelectionBasicStorage_Contains(self.raw, id.raw()) }
118    }
119
120    /// Set selection state for a given id.
121    pub fn set_selected(&mut self, id: crate::Id, selected: bool) {
122        unsafe {
123            sys::ImGuiSelectionBasicStorage_SetItemSelected(self.raw, id.raw(), selected);
124        }
125    }
126
127    /// Iterate over selected ids.
128    pub fn iter(&self) -> BasicSelectionIter<'_> {
129        BasicSelectionIter {
130            storage: self,
131            it: std::ptr::null_mut(),
132        }
133    }
134
135    /// Expose raw pointer for internal helpers.
136    pub(crate) fn as_raw(&self) -> *mut sys::ImGuiSelectionBasicStorage {
137        self.raw
138    }
139}
140
141impl Default for BasicSelection {
142    fn default() -> Self {
143        Self::new()
144    }
145}
146
147impl Drop for BasicSelection {
148    fn drop(&mut self) {
149        unsafe {
150            if !self.raw.is_null() {
151                sys::ImGuiSelectionBasicStorage_destroy(self.raw);
152                self.raw = std::ptr::null_mut();
153            }
154        }
155    }
156}
157
158/// Iterator over selected ids stored in [`BasicSelection`].
159pub struct BasicSelectionIter<'a> {
160    storage: &'a BasicSelection,
161    it: *mut std::os::raw::c_void,
162}
163
164impl<'a> Iterator for BasicSelectionIter<'a> {
165    type Item = crate::Id;
166
167    fn next(&mut self) -> Option<Self::Item> {
168        unsafe {
169            let mut out_id: sys::ImGuiID = 0;
170            let has_next = sys::ImGuiSelectionBasicStorage_GetNextSelectedItem(
171                self.storage.as_raw(),
172                &mut self.it,
173                &mut out_id,
174            );
175            if has_next {
176                Some(crate::Id::from(out_id))
177            } else {
178                None
179            }
180        }
181    }
182}
183
184/// Index-based selection storage for multi-select helpers.
185///
186/// Implement this trait for your selection container (e.g. `Vec<bool>`,
187/// `Vec<MyItem { selected: bool }>` or a custom type) to use
188/// [`Ui::multi_select_indexed`].
189pub trait MultiSelectIndexStorage {
190    /// Total number of items in the selection scope.
191    fn len(&self) -> usize;
192
193    /// Returns `true` if the selection scope is empty.
194    fn is_empty(&self) -> bool {
195        self.len() == 0
196    }
197
198    /// Returns whether item at `index` is currently selected.
199    fn is_selected(&self, index: usize) -> bool;
200
201    /// Updates selection state for item at `index`.
202    fn set_selected(&mut self, index: usize, selected: bool);
203
204    /// Optional hint for current selection size.
205    ///
206    /// If provided, this is forwarded to `BeginMultiSelect()` to improve the
207    /// behavior of shortcuts such as `ImGuiMultiSelectFlags_ClearOnEscape`.
208    /// When `None` (default), the size is treated as "unknown".
209    fn selected_count_hint(&self) -> Option<usize> {
210        None
211    }
212}
213
214impl MultiSelectIndexStorage for Vec<bool> {
215    fn len(&self) -> usize {
216        self.len()
217    }
218
219    fn is_selected(&self, index: usize) -> bool {
220        self.get(index).copied().unwrap_or(false)
221    }
222
223    fn set_selected(&mut self, index: usize, selected: bool) {
224        if index < self.len() {
225            self[index] = selected;
226        }
227    }
228
229    fn selected_count_hint(&self) -> Option<usize> {
230        // For typical lists this is cheap enough; callers with large datasets
231        // can implement the trait manually with a more efficient counter.
232        Some(self.iter().filter(|&&b| b).count())
233    }
234}
235
236impl MultiSelectIndexStorage for &mut [bool] {
237    fn len(&self) -> usize {
238        (**self).len()
239    }
240
241    fn is_selected(&self, index: usize) -> bool {
242        self.get(index).copied().unwrap_or(false)
243    }
244
245    fn set_selected(&mut self, index: usize, selected: bool) {
246        if index < self.len() {
247            self[index] = selected;
248        }
249    }
250
251    fn selected_count_hint(&self) -> Option<usize> {
252        Some(self.iter().filter(|&&b| b).count())
253    }
254}
255
256/// Index-based selection storage backed by a key slice + `HashSet` of selected keys.
257///
258/// This is convenient when your application stores selection as a set of
259/// arbitrary keys (e.g. `HashSet<u32>` or `HashSet<MyId>`), but you still
260/// want to drive a multi-select scope using contiguous indices.
261pub struct KeySetSelection<'a, K>
262where
263    K: Eq + std::hash::Hash + Copy,
264{
265    keys: &'a [K],
266    selected: &'a mut HashSet<K>,
267}
268
269impl<'a, K> KeySetSelection<'a, K>
270where
271    K: Eq + std::hash::Hash + Copy,
272{
273    /// Create a new index-based view over a key slice and a selection set.
274    ///
275    /// - `keys`: stable index->key mapping (e.g. your backing array).
276    /// - `selected`: set of currently selected keys.
277    pub fn new(keys: &'a [K], selected: &'a mut HashSet<K>) -> Self {
278        Self { keys, selected }
279    }
280}
281
282impl<'a, K> MultiSelectIndexStorage for KeySetSelection<'a, K>
283where
284    K: Eq + std::hash::Hash + Copy,
285{
286    fn len(&self) -> usize {
287        self.keys.len()
288    }
289
290    fn is_selected(&self, index: usize) -> bool {
291        self.keys
292            .get(index)
293            .map(|k| self.selected.contains(k))
294            .unwrap_or(false)
295    }
296
297    fn set_selected(&mut self, index: usize, selected: bool) {
298        if let Some(&key) = self.keys.get(index) {
299            if selected {
300                self.selected.insert(key);
301            } else {
302                self.selected.remove(&key);
303            }
304        }
305    }
306
307    fn selected_count_hint(&self) -> Option<usize> {
308        Some(self.selected.len())
309    }
310}
311
312/// Apply `ImGuiMultiSelectIO` requests to index-based selection storage.
313///
314/// This mirrors `ImGuiSelectionExternalStorage::ApplyRequests` from Dear ImGui,
315/// but operates on the safe [`MultiSelectIndexStorage`] trait instead of relying
316/// on C callbacks.
317unsafe fn apply_multi_select_requests_indexed<S: MultiSelectIndexStorage>(
318    ms_io: *mut sys::ImGuiMultiSelectIO,
319    storage: &mut S,
320) {
321    unsafe {
322        if ms_io.is_null() {
323            return;
324        }
325
326        let io_ref: &mut sys::ImGuiMultiSelectIO = &mut *ms_io;
327        let items_count = usize::try_from(io_ref.ItemsCount).unwrap_or(0);
328
329        let requests = &mut io_ref.Requests;
330        if requests.Data.is_null() || requests.Size <= 0 {
331            return;
332        }
333
334        let len = match usize::try_from(requests.Size) {
335            Ok(len) => len,
336            Err(_) => return,
337        };
338        let slice = std::slice::from_raw_parts_mut(requests.Data, len);
339
340        for req in slice {
341            if req.Type == sys::ImGuiSelectionRequestType_SetAll {
342                for idx in 0..items_count {
343                    storage.set_selected(idx, req.Selected);
344                }
345            } else if req.Type == sys::ImGuiSelectionRequestType_SetRange {
346                let first = req.RangeFirstItem as i32;
347                let last = req.RangeLastItem as i32;
348                if first < 0 || last < first {
349                    continue;
350                }
351                let last_clamped = std::cmp::min(last as usize, items_count.saturating_sub(1));
352                for idx in first as usize..=last_clamped {
353                    storage.set_selected(idx, req.Selected);
354                }
355            }
356        }
357    }
358}
359
360/// RAII wrapper around `BeginMultiSelect()` / `EndMultiSelect()` for advanced users.
361///
362/// This gives direct, but scoped, access to the underlying `ImGuiMultiSelectIO`
363/// struct. It does not perform any selection updates by itself; you are expected
364/// to call helper methods or use the raw IO to drive your own storage.
365pub struct MultiSelectScope<'ui> {
366    ms_io_begin: *mut sys::ImGuiMultiSelectIO,
367    items_count: i32,
368    _marker: std::marker::PhantomData<&'ui Ui>,
369}
370
371impl<'ui> MultiSelectScope<'ui> {
372    fn new(flags: MultiSelectFlags, selection_size: Option<i32>, items_count: usize) -> Self {
373        let selection_size_i32 = selection_size.unwrap_or(-1);
374        let items_count_i32 = i32::try_from(items_count).unwrap_or(i32::MAX);
375        let ms_io_begin =
376            unsafe { sys::igBeginMultiSelect(flags.bits(), selection_size_i32, items_count_i32) };
377        Self {
378            ms_io_begin,
379            items_count: items_count_i32,
380            _marker: std::marker::PhantomData,
381        }
382    }
383
384    /// Access the IO struct returned by `BeginMultiSelect()`.
385    pub fn begin_io(&self) -> &sys::ImGuiMultiSelectIO {
386        unsafe { &*self.ms_io_begin }
387    }
388
389    /// Mutable access to the IO struct returned by `BeginMultiSelect()`.
390    pub fn begin_io_mut(&mut self) -> &mut sys::ImGuiMultiSelectIO {
391        unsafe { &mut *self.ms_io_begin }
392    }
393
394    /// Apply selection requests from `BeginMultiSelect()` to index-based storage.
395    pub fn apply_begin_requests_indexed<S: MultiSelectIndexStorage>(&mut self, storage: &mut S) {
396        unsafe {
397            apply_multi_select_requests_indexed(self.ms_io_begin, storage);
398        }
399    }
400
401    /// Finalize the multi-select scope and return an IO view for the end state.
402    ///
403    /// This calls `EndMultiSelect()` and returns a `MultiSelectEnd` wrapper
404    /// that can be used to apply the final selection requests.
405    pub fn end(self) -> MultiSelectEnd<'ui> {
406        let ms_io_end = unsafe { sys::igEndMultiSelect() };
407        MultiSelectEnd {
408            ms_io_end,
409            items_count: self.items_count,
410            _marker: std::marker::PhantomData,
411        }
412    }
413}
414
415/// IO view returned after calling `EndMultiSelect()` via [`MultiSelectScope::end`].
416pub struct MultiSelectEnd<'ui> {
417    ms_io_end: *mut sys::ImGuiMultiSelectIO,
418    items_count: i32,
419    _marker: std::marker::PhantomData<&'ui Ui>,
420}
421
422impl<'ui> MultiSelectEnd<'ui> {
423    /// Access the IO struct returned by `EndMultiSelect()`.
424    pub fn io(&self) -> &sys::ImGuiMultiSelectIO {
425        unsafe { &*self.ms_io_end }
426    }
427
428    /// Mutable access to the IO struct returned by `EndMultiSelect()`.
429    pub fn io_mut(&mut self) -> &mut sys::ImGuiMultiSelectIO {
430        unsafe { &mut *self.ms_io_end }
431    }
432
433    /// Apply selection requests from `EndMultiSelect()` to index-based storage.
434    pub fn apply_requests_indexed<S: MultiSelectIndexStorage>(&mut self, storage: &mut S) {
435        unsafe {
436            apply_multi_select_requests_indexed(self.ms_io_end, storage);
437        }
438    }
439
440    /// Apply selection requests from `EndMultiSelect()` to a [`BasicSelection`].
441    pub fn apply_requests_basic<G>(&mut self, selection: &mut BasicSelection, mut id_at_index: G)
442    where
443        G: FnMut(usize) -> crate::Id,
444    {
445        unsafe {
446            apply_multi_select_requests_basic(
447                self.ms_io_end,
448                selection,
449                self.items_count as usize,
450                &mut id_at_index,
451            );
452        }
453    }
454}
455
456impl Ui {
457    /// Low-level entry point: begin a multi-select scope and return a RAII wrapper.
458    ///
459    /// This is the closest safe wrapper to the raw `BeginMultiSelect()` /
460    /// `EndMultiSelect()` pair. It does not drive any selection storage by
461    /// itself; use `begin_io()` / `end().io()` and the helper methods to
462    /// implement custom patterns.
463    pub fn begin_multi_select_raw(
464        &self,
465        flags: MultiSelectFlags,
466        selection_size: Option<i32>,
467        items_count: usize,
468    ) -> MultiSelectScope<'_> {
469        MultiSelectScope::new(flags, selection_size, items_count)
470    }
471    /// Multi-select helper for index-based storage.
472    ///
473    /// This wraps `BeginMultiSelect()` / `EndMultiSelect()` and applies
474    /// selection requests to an index-addressable selection container.
475    ///
476    /// Typical usage:
477    ///
478    /// ```no_run
479    /// # use dear_imgui_rs::*;
480    /// # let mut ctx = Context::create();
481    /// # let ui = ctx.frame();
482    /// let mut selected = vec![false; 128];
483    ///
484    /// ui.multi_select_indexed(&mut selected, MultiSelectFlags::NONE, |ui, idx, is_selected| {
485    ///     ui.text(format!(
486    ///         "{} {}",
487    ///         if is_selected { "[x]" } else { "[ ]" },
488    ///         idx
489    ///     ));
490    /// });
491    /// ```
492    ///
493    /// Notes:
494    /// - `storage.len()` defines `items_count`.
495    /// - This helper uses the "external storage" pattern where selection is
496    ///   stored entirely on the application side.
497    /// - Per-item selection toggles can be queried via
498    ///   [`Ui::is_item_toggled_selection`].
499    pub fn multi_select_indexed<S, F>(
500        &self,
501        storage: &mut S,
502        flags: MultiSelectFlags,
503        mut render_item: F,
504    ) where
505        S: MultiSelectIndexStorage,
506        F: FnMut(&Ui, usize, bool),
507    {
508        let items_count = storage.len();
509        let selection_size_i32 = storage
510            .selected_count_hint()
511            .and_then(|n| i32::try_from(n).ok())
512            .unwrap_or(-1);
513
514        // Begin multi-select scope.
515        let ms_io_begin = unsafe {
516            sys::igBeginMultiSelect(flags.bits(), selection_size_i32, items_count as i32)
517        };
518
519        // Apply SetAll requests (if any) before submitting items.
520        unsafe {
521            apply_multi_select_requests_indexed(ms_io_begin, storage);
522        }
523
524        // Submit items: for each index we set SelectionUserData and let user
525        // draw widgets, passing the current selection state as `is_selected`.
526        for idx in 0..items_count {
527            unsafe {
528                sys::igSetNextItemSelectionUserData(idx as sys::ImGuiSelectionUserData);
529            }
530            let is_selected = storage.is_selected(idx);
531            render_item(self, idx, is_selected);
532        }
533
534        // End scope and apply requests generated during item submission.
535        let ms_io_end = unsafe { sys::igEndMultiSelect() };
536        unsafe {
537            apply_multi_select_requests_indexed(ms_io_end, storage);
538        }
539    }
540
541    /// Multi-select helper for index-based storage inside an active table.
542    ///
543    /// This is a convenience wrapper over [`Ui::multi_select_indexed`] that
544    /// automatically advances table rows and starts each row at column 0.
545    ///
546    /// It expects to be called between `BeginTable`/`EndTable`.
547    pub fn table_multi_select_indexed<S, F>(
548        &self,
549        storage: &mut S,
550        flags: MultiSelectFlags,
551        mut build_row: F,
552    ) where
553        S: MultiSelectIndexStorage,
554        F: FnMut(&Ui, usize, bool),
555    {
556        let row_count = storage.len();
557        let selection_size_i32 = storage
558            .selected_count_hint()
559            .and_then(|n| i32::try_from(n).ok())
560            .unwrap_or(-1);
561
562        let ms_io_begin =
563            unsafe { sys::igBeginMultiSelect(flags.bits(), selection_size_i32, row_count as i32) };
564
565        unsafe {
566            apply_multi_select_requests_indexed(ms_io_begin, storage);
567        }
568
569        for row in 0..row_count {
570            unsafe {
571                sys::igSetNextItemSelectionUserData(row as sys::ImGuiSelectionUserData);
572            }
573            // Start a new table row and move to first column.
574            self.table_next_row();
575            self.table_next_column();
576
577            let is_selected = storage.is_selected(row);
578            build_row(self, row, is_selected);
579        }
580
581        let ms_io_end = unsafe { sys::igEndMultiSelect() };
582        unsafe {
583            apply_multi_select_requests_indexed(ms_io_end, storage);
584        }
585    }
586
587    /// Multi-select helper using [`BasicSelection`] as underlying storage.
588    ///
589    /// This variant is suitable when items are naturally identified by `ImGuiID`
590    /// (e.g. stable ids for rows or tree nodes).
591    ///
592    /// - `items_count`: number of items in the scope.
593    /// - `id_at_index`: maps `[0, items_count)` to the corresponding item id.
594    /// - `render_item`: called once per index to emit widgets for that item.
595    pub fn multi_select_basic<G, F>(
596        &self,
597        selection: &mut BasicSelection,
598        flags: MultiSelectFlags,
599        items_count: usize,
600        mut id_at_index: G,
601        mut render_item: F,
602    ) where
603        G: FnMut(usize) -> crate::Id,
604        F: FnMut(&Ui, usize, crate::Id, bool),
605    {
606        let selection_size_i32 = i32::try_from(selection.len()).unwrap_or(-1);
607
608        let ms_io_begin = unsafe {
609            sys::igBeginMultiSelect(flags.bits(), selection_size_i32, items_count as i32)
610        };
611
612        unsafe {
613            apply_multi_select_requests_basic(
614                ms_io_begin,
615                selection,
616                items_count,
617                &mut id_at_index,
618            );
619        }
620
621        for idx in 0..items_count {
622            unsafe {
623                sys::igSetNextItemSelectionUserData(idx as sys::ImGuiSelectionUserData);
624            }
625            let id = id_at_index(idx);
626            let is_selected = selection.contains(id);
627            render_item(self, idx, id, is_selected);
628        }
629
630        let ms_io_end = unsafe { sys::igEndMultiSelect() };
631        unsafe {
632            apply_multi_select_requests_basic(ms_io_end, selection, items_count, &mut id_at_index);
633        }
634    }
635}
636
637/// Apply multi-select requests to a `BasicSelection` using an index→id mapping.
638unsafe fn apply_multi_select_requests_basic<G>(
639    ms_io: *mut sys::ImGuiMultiSelectIO,
640    selection: &mut BasicSelection,
641    items_count: usize,
642    id_at_index: &mut G,
643) where
644    G: FnMut(usize) -> crate::Id,
645{
646    unsafe {
647        if ms_io.is_null() {
648            return;
649        }
650
651        let io_ref: &mut sys::ImGuiMultiSelectIO = &mut *ms_io;
652        let requests = &mut io_ref.Requests;
653        if requests.Data.is_null() || requests.Size <= 0 {
654            return;
655        }
656
657        let len = match usize::try_from(requests.Size) {
658            Ok(len) => len,
659            Err(_) => return,
660        };
661        let slice = std::slice::from_raw_parts_mut(requests.Data, len);
662
663        for req in slice {
664            if req.Type == sys::ImGuiSelectionRequestType_SetAll {
665                for idx in 0..items_count {
666                    let id = id_at_index(idx);
667                    selection.set_selected(id, req.Selected);
668                }
669            } else if req.Type == sys::ImGuiSelectionRequestType_SetRange {
670                let first = req.RangeFirstItem as i32;
671                let last = req.RangeLastItem as i32;
672                if first < 0 || last < first {
673                    continue;
674                }
675                let last_clamped = std::cmp::min(last as usize, items_count.saturating_sub(1));
676                for idx in first as usize..=last_clamped {
677                    let id = id_at_index(idx);
678                    selection.set_selected(id, req.Selected);
679                }
680            }
681        }
682    }
683}