easy_imgui/
multisel.rs

1use super::*;
2
3impl<A> Ui<A> {
4    pub fn set_next_item_selection_user_data(&self, i: usize) {
5        unsafe {
6            ImGui_SetNextItemSelectionUserData(i as ImGuiSelectionUserData);
7        }
8    }
9    pub fn is_item_toggled_selection(&self) -> bool {
10        unsafe { ImGui_IsItemToggledSelection() }
11    }
12    /// Wraps the call to `f` between `BeginMultiselect` and `EndMultiselect`.
13    ///
14    /// The `storage` argument defines how the selection status will be stored.
15    pub fn with_multi_select<R, Storage: MultiSelectStorage>(
16        &self,
17        flags: MultiSelectFlags,
18        items_count: Option<usize>,
19        mut storage: Storage,
20        f: impl FnOnce(&mut Storage, &mut MultiSelect) -> R,
21    ) -> R {
22        let selection_size = storage
23            .size()
24            .and_then(|x| i32::try_from(x).ok())
25            .unwrap_or(-1);
26        let items_count = items_count
27            .and_then(|x| i32::try_from(x).ok())
28            .unwrap_or(-1);
29
30        let ms = unsafe { ImGui_BeginMultiSelect(flags.bits(), selection_size, items_count) };
31        let mut ms = MultiSelect(ms);
32        storage.apply_requests(&mut ms);
33
34        let res = f(&mut storage, &mut ms);
35
36        let ms = unsafe { ImGui_EndMultiSelect() };
37        let mut ms = MultiSelect(ms);
38        storage.apply_requests(&mut ms);
39
40        res
41    }
42}
43
44pub struct MultiSelect(*mut ImGuiMultiSelectIO);
45
46impl MultiSelect {
47    pub fn range_src_item(&self) -> usize {
48        unsafe { (*self.0).RangeSrcItem as usize }
49    }
50    pub fn nav_id_item(&self) -> usize {
51        unsafe { (*self.0).NavIdItem as usize }
52    }
53    pub fn nav_id_selected(&self) -> bool {
54        unsafe { (*self.0).NavIdSelected }
55    }
56    pub fn items_count(&self) -> usize {
57        unsafe { (*self.0).ItemsCount as usize }
58    }
59    pub fn set_range_src_reset(&mut self) {
60        unsafe {
61            (*self.0).RangeSrcReset = true;
62        }
63    }
64    pub fn iter(&self) -> impl Iterator<Item = SelectionRequest<'_>> {
65        unsafe { (*self.0).Requests.iter().map(SelectionRequest) }
66    }
67}
68
69pub struct SelectionRequest<'a>(&'a ImGuiSelectionRequest);
70
71impl SelectionRequest<'_> {
72    pub fn request_type(&self) -> SelectionRequestType {
73        SelectionRequestType::from_bits(self.0.Type).unwrap()
74    }
75    pub fn selected(&self) -> bool {
76        self.0.Selected
77    }
78    pub fn range_direction(&self) -> i8 {
79        self.0.RangeDirection
80    }
81    pub fn range_first_item(&self) -> usize {
82        self.0.RangeFirstItem as usize
83    }
84    pub fn range_last_item(&self) -> usize {
85        self.0.RangeLastItem as usize
86    }
87}
88
89/// How the multi-select data will be stored.
90///
91/// There is a blank implementation for mutable references
92/// to a type that implements this trait, so that you can use
93/// a storage without consuming it. Or you can pass a temporary
94/// adaptor without getting a reference.
95pub trait MultiSelectStorage {
96    fn size(&self) -> Option<usize>;
97    fn apply_requests(&mut self, ms: &mut MultiSelect);
98}
99
100/// Use the storage without consuming it.
101impl<T: MultiSelectStorage> MultiSelectStorage for &mut T {
102    fn size(&self) -> Option<usize> {
103        T::size(self)
104    }
105    fn apply_requests(&mut self, ms: &mut MultiSelect) {
106        T::apply_requests(self, ms)
107    }
108}
109
110////////////////////////
111
112type BoxFnIdxToID<'a> = Box<dyn FnMut(usize) -> ImGuiID + 'a>;
113
114extern "C" fn default_adapter_index_to_storage_id(
115    _: *mut ImGuiSelectionBasicStorage,
116    idx: i32,
117) -> ImGuiID {
118    idx as ImGuiID
119}
120
121extern "C" fn adapter_index_to_storage_id(
122    this: *mut ImGuiSelectionBasicStorage,
123    idx: i32,
124) -> ImGuiID {
125    unsafe {
126        let f = (*this).UserData as *mut BoxFnIdxToID;
127        if f.is_null() { 0 } else { (*f)(idx as usize) }
128    }
129}
130
131/// Simple multi-selection storage facility.
132///
133/// By default it stores the indices in a collection.
134/// Use the `with_callback_id()` function to do something different.
135pub struct SelectionBasicStorage {
136    inner: ImGuiSelectionBasicStorage,
137}
138
139impl SelectionBasicStorage {
140    /// Creates a new selection storage.
141    ///
142    /// By default the index is the ID.
143    pub fn new() -> Self {
144        let mut inner = unsafe { ImGuiSelectionBasicStorage::new() };
145        inner.AdapterIndexToStorageId = Some(default_adapter_index_to_storage_id);
146        SelectionBasicStorage { inner }
147    }
148    /// Decorates this storage with a map function.
149    ///
150    /// The callback maps an index into an ID. This allows to re-sort
151    /// the list without messing with the selection.
152    pub fn with_callback_id<'a>(
153        &'a mut self,
154        f: impl FnMut(usize) -> ImGuiID + 'a,
155    ) -> SelectionBasicStorageWithCallback<'a> {
156        let f: BoxFnIdxToID<'a> = Box::new(f);
157        let f = Box::new(f);
158        self.inner.AdapterIndexToStorageId = Some(adapter_index_to_storage_id);
159        SelectionBasicStorageWithCallback {
160            inner: self,
161            boxed_f: f,
162        }
163    }
164    pub fn set_preserve_order(&mut self, preserve_order: bool) {
165        self.inner.PreserveOrder = preserve_order;
166    }
167    pub fn contains(&self, id: ImGuiID) -> bool {
168        unsafe { self.inner.Contains(id) }
169    }
170    pub fn clear(&mut self) {
171        unsafe {
172            self.inner.Clear();
173        }
174    }
175    pub fn set_item_selected(&mut self, id: ImGuiID, selected: bool) {
176        unsafe {
177            self.inner.SetItemSelected(id, selected);
178        }
179    }
180    pub fn swap(&mut self, other: &mut SelectionBasicStorage) {
181        unsafe {
182            self.inner.Swap(&mut other.inner);
183        }
184    }
185    pub fn iter(&self) -> SelectionBasicStorageIter<'_> {
186        SelectionBasicStorageIter {
187            inner: self,
188            ptr: std::ptr::null_mut(),
189        }
190    }
191}
192
193impl<'a> IntoIterator for &'a SelectionBasicStorage {
194    type Item = ImGuiID;
195    type IntoIter = SelectionBasicStorageIter<'a>;
196    fn into_iter(self) -> SelectionBasicStorageIter<'a> {
197        self.iter()
198    }
199}
200
201pub struct SelectionBasicStorageIter<'a> {
202    inner: &'a SelectionBasicStorage,
203    ptr: *mut c_void,
204}
205
206impl Iterator for SelectionBasicStorageIter<'_> {
207    type Item = ImGuiID;
208    fn next(&mut self) -> Option<ImGuiID> {
209        unsafe {
210            let mut id = 0;
211            // GetNextSelectedItem() is not const in C++, but it should be, I think so we can cast
212            // the self argument into a mut pointer. It should be safe, probably.
213            let this = &self.inner.inner as *const _ as *mut _;
214            if ImGuiSelectionBasicStorage_GetNextSelectedItem(this, &mut self.ptr, &mut id) {
215                Some(id)
216            } else {
217                None
218            }
219        }
220    }
221}
222
223impl Default for SelectionBasicStorage {
224    fn default() -> Self {
225        Self::new()
226    }
227}
228
229impl MultiSelectStorage for SelectionBasicStorage {
230    fn size(&self) -> Option<usize> {
231        Some(self.inner.Size as usize)
232    }
233    fn apply_requests(&mut self, ms: &mut MultiSelect) {
234        unsafe {
235            self.inner.ApplyRequests(ms.0);
236        }
237    }
238}
239
240pub struct SelectionBasicStorageWithCallback<'a> {
241    inner: &'a mut SelectionBasicStorage,
242    boxed_f: BoxFnIdxToID<'a>,
243}
244
245impl SelectionBasicStorageWithCallback<'_> {
246    /// Gets the inner container.
247    pub fn inner(&mut self) -> &mut SelectionBasicStorage {
248        self.inner
249    }
250    /// Returns `self.inner().contains(id)`.
251    ///
252    /// Provided just for convenience.
253    pub fn contains(&self, id: ImGuiID) -> bool {
254        self.inner.contains(id)
255    }
256}
257
258impl Drop for SelectionBasicStorageWithCallback<'_> {
259    fn drop(&mut self) {
260        // Restore the values before creating self.
261        self.inner.inner.UserData = std::ptr::null_mut();
262        self.inner.inner.AdapterIndexToStorageId = Some(default_adapter_index_to_storage_id);
263    }
264}
265
266impl<'a> MultiSelectStorage for SelectionBasicStorageWithCallback<'a> {
267    fn size(&self) -> Option<usize> {
268        self.inner.size()
269    }
270    fn apply_requests(&mut self, ms: &mut MultiSelect) {
271        self.inner.inner.UserData = &mut self.boxed_f as *mut BoxFnIdxToID<'a> as *mut c_void;
272        self.inner.apply_requests(ms);
273    }
274}
275
276/////////////////////
277
278type BoxFnExtSetter<'f, Storage> = Box<dyn FnMut(&mut Storage, usize, bool) + 'f>;
279type StorageAndSet<'f, Storage> = (Storage, BoxFnExtSetter<'f, Storage>);
280
281/// MultiSelectStorage that forwards the selection data to another place.
282///
283/// `Storage` is the inner storage type, usually a mutable reference, but not
284/// necessarily.
285/// `'f` is the lifetime of the setter function, usually `'static`.
286pub struct SelectionExternalStorage<'f, Storage> {
287    inner: ImGuiSelectionExternalStorage,
288    #[allow(clippy::type_complexity)]
289    selection_size: Box<dyn Fn(&Storage) -> Option<usize> + 'f>,
290    storage: StorageAndSet<'f, Storage>,
291}
292
293extern "C" fn adapter_set_item_selected<Storage>(
294    this: *mut ImGuiSelectionExternalStorage,
295    idx: i32,
296    selected: bool,
297) {
298    unsafe {
299        let storage_and_set = (*this).UserData as *mut StorageAndSet<'_, Storage>;
300        let (storage, setter) = &mut *storage_and_set;
301        setter(storage, idx as usize, selected);
302    }
303}
304
305impl<'f, Storage> SelectionExternalStorage<'f, Storage> {
306    /// Creates a new `SelectionExternalStorage`.
307    ///
308    /// The intended usage is that you keep the `storage` in your code
309    /// and when multi-select happens, you pass a mutable reference to it in `storage`,
310    /// along with a setter function to set the selection status of an item.
311    ///
312    /// Additionally, pass a function to count the number of selected items. If you don't
313    /// want to compute that, use `|_| None`.
314    pub fn new(
315        storage: Storage,
316        selection_size: impl Fn(&Storage) -> Option<usize> + 'f,
317        setter: impl FnMut(&mut Storage, usize, bool) + 'f,
318    ) -> Self {
319        let mut inner = unsafe { ImGuiSelectionExternalStorage::new() };
320        let setter: BoxFnExtSetter<'f, Storage> = Box::new(setter);
321        let storage = (storage, setter);
322        inner.AdapterSetItemSelected = Some(adapter_set_item_selected::<Storage>);
323        SelectionExternalStorage {
324            inner,
325            selection_size: Box::new(selection_size),
326            storage,
327        }
328    }
329    /// Gets a reference to the inner storage.
330    pub fn storage(&mut self) -> &mut Storage {
331        &mut self.storage.0
332    }
333    /// Unwraps the inner storage.
334    pub fn into_storage(self) -> Storage {
335        self.storage.0
336    }
337}
338
339impl<'f, Storage> MultiSelectStorage for SelectionExternalStorage<'f, Storage> {
340    fn size(&self) -> Option<usize> {
341        (self.selection_size)(&self.storage.0)
342    }
343    fn apply_requests(&mut self, ms: &mut MultiSelect) {
344        self.inner.UserData = &mut self.storage as *mut StorageAndSet<'f, Storage> as *mut c_void;
345        unsafe {
346            self.inner.ApplyRequests(ms.0);
347        }
348    }
349}