1#![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 #[repr(transparent)]
29 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
30 pub struct MultiSelectFlags: i32 {
31 const NONE = sys::ImGuiMultiSelectFlags_None as i32;
33 const SINGLE_SELECT = sys::ImGuiMultiSelectFlags_SingleSelect as i32;
35 const NO_SELECT_ALL = sys::ImGuiMultiSelectFlags_NoSelectAll as i32;
37 const NO_RANGE_SELECT = sys::ImGuiMultiSelectFlags_NoRangeSelect as i32;
39 const NO_AUTO_SELECT = sys::ImGuiMultiSelectFlags_NoAutoSelect as i32;
41 const NO_AUTO_CLEAR = sys::ImGuiMultiSelectFlags_NoAutoClear as i32;
43 const NO_AUTO_CLEAR_ON_RESELECT =
45 sys::ImGuiMultiSelectFlags_NoAutoClearOnReselect as i32;
46 const BOX_SELECT_1D = sys::ImGuiMultiSelectFlags_BoxSelect1d as i32;
48 const BOX_SELECT_2D = sys::ImGuiMultiSelectFlags_BoxSelect2d as i32;
50 const BOX_SELECT_NO_SCROLL = sys::ImGuiMultiSelectFlags_BoxSelectNoScroll as i32;
52 const CLEAR_ON_ESCAPE = sys::ImGuiMultiSelectFlags_ClearOnEscape as i32;
54 const CLEAR_ON_CLICK_VOID = sys::ImGuiMultiSelectFlags_ClearOnClickVoid as i32;
56 const SCOPE_WINDOW = sys::ImGuiMultiSelectFlags_ScopeWindow as i32;
58 const SCOPE_RECT = sys::ImGuiMultiSelectFlags_ScopeRect as i32;
60 const SELECT_ON_AUTO = sys::ImGuiMultiSelectFlags_SelectOnAuto as i32;
64 const SELECT_ON_CLICK = sys::ImGuiMultiSelectFlags_SelectOnAuto as i32;
68 const SELECT_ON_CLICK_ALWAYS =
70 sys::ImGuiMultiSelectFlags_SelectOnClickAlways as i32;
71 const SELECT_ON_CLICK_RELEASE =
73 sys::ImGuiMultiSelectFlags_SelectOnClickRelease as i32;
74 const NAV_WRAP_X = sys::ImGuiMultiSelectFlags_NavWrapX as i32;
76 const NO_SELECT_ON_RIGHT_CLICK =
78 sys::ImGuiMultiSelectFlags_NoSelectOnRightClick as i32;
79 }
80}
81
82#[derive(Debug)]
88pub struct BasicSelection {
89 raw: *mut sys::ImGuiSelectionBasicStorage,
90}
91
92impl BasicSelection {
93 pub fn new() -> Self {
95 unsafe {
96 let ptr = sys::ImGuiSelectionBasicStorage_ImGuiSelectionBasicStorage();
97 if ptr.is_null() {
98 panic!("ImGuiSelectionBasicStorage_ImGuiSelectionBasicStorage() returned null");
99 }
100 Self { raw: ptr }
101 }
102 }
103
104 pub fn len(&self) -> usize {
106 unsafe {
107 let size = (*self.raw).Size;
108 if size <= 0 { 0 } else { size as usize }
109 }
110 }
111
112 pub fn is_empty(&self) -> bool {
114 self.len() == 0
115 }
116
117 pub fn clear(&mut self) {
119 unsafe {
120 sys::ImGuiSelectionBasicStorage_Clear(self.raw);
121 }
122 }
123
124 pub fn contains(&self, id: crate::Id) -> bool {
126 unsafe { sys::ImGuiSelectionBasicStorage_Contains(self.raw, id.raw()) }
127 }
128
129 pub fn set_selected(&mut self, id: crate::Id, selected: bool) {
131 unsafe {
132 sys::ImGuiSelectionBasicStorage_SetItemSelected(self.raw, id.raw(), selected);
133 }
134 }
135
136 pub fn iter(&self) -> BasicSelectionIter<'_> {
138 BasicSelectionIter {
139 storage: self,
140 it: std::ptr::null_mut(),
141 }
142 }
143
144 pub(crate) fn as_raw(&self) -> *mut sys::ImGuiSelectionBasicStorage {
146 self.raw
147 }
148}
149
150impl Default for BasicSelection {
151 fn default() -> Self {
152 Self::new()
153 }
154}
155
156impl Drop for BasicSelection {
157 fn drop(&mut self) {
158 unsafe {
159 if !self.raw.is_null() {
160 sys::ImGuiSelectionBasicStorage_destroy(self.raw);
161 self.raw = std::ptr::null_mut();
162 }
163 }
164 }
165}
166
167pub struct BasicSelectionIter<'a> {
169 storage: &'a BasicSelection,
170 it: *mut std::os::raw::c_void,
171}
172
173impl<'a> Iterator for BasicSelectionIter<'a> {
174 type Item = crate::Id;
175
176 fn next(&mut self) -> Option<Self::Item> {
177 unsafe {
178 let mut out_id: sys::ImGuiID = 0;
179 let has_next = sys::ImGuiSelectionBasicStorage_GetNextSelectedItem(
180 self.storage.as_raw(),
181 &mut self.it,
182 &mut out_id,
183 );
184 if has_next {
185 Some(crate::Id::from(out_id))
186 } else {
187 None
188 }
189 }
190 }
191}
192
193pub trait MultiSelectIndexStorage {
199 fn len(&self) -> usize;
201
202 fn is_empty(&self) -> bool {
204 self.len() == 0
205 }
206
207 fn is_selected(&self, index: usize) -> bool;
209
210 fn set_selected(&mut self, index: usize, selected: bool);
212
213 fn selected_count_hint(&self) -> Option<usize> {
219 None
220 }
221}
222
223impl MultiSelectIndexStorage for Vec<bool> {
224 fn len(&self) -> usize {
225 self.len()
226 }
227
228 fn is_selected(&self, index: usize) -> bool {
229 self.get(index).copied().unwrap_or(false)
230 }
231
232 fn set_selected(&mut self, index: usize, selected: bool) {
233 if index < self.len() {
234 self[index] = selected;
235 }
236 }
237
238 fn selected_count_hint(&self) -> Option<usize> {
239 Some(self.iter().filter(|&&b| b).count())
242 }
243}
244
245impl MultiSelectIndexStorage for &mut [bool] {
246 fn len(&self) -> usize {
247 (**self).len()
248 }
249
250 fn is_selected(&self, index: usize) -> bool {
251 self.get(index).copied().unwrap_or(false)
252 }
253
254 fn set_selected(&mut self, index: usize, selected: bool) {
255 if index < self.len() {
256 self[index] = selected;
257 }
258 }
259
260 fn selected_count_hint(&self) -> Option<usize> {
261 Some(self.iter().filter(|&&b| b).count())
262 }
263}
264
265pub struct KeySetSelection<'a, K>
271where
272 K: Eq + std::hash::Hash + Copy,
273{
274 keys: &'a [K],
275 selected: &'a mut HashSet<K>,
276}
277
278impl<'a, K> KeySetSelection<'a, K>
279where
280 K: Eq + std::hash::Hash + Copy,
281{
282 pub fn new(keys: &'a [K], selected: &'a mut HashSet<K>) -> Self {
287 Self { keys, selected }
288 }
289}
290
291impl<'a, K> MultiSelectIndexStorage for KeySetSelection<'a, K>
292where
293 K: Eq + std::hash::Hash + Copy,
294{
295 fn len(&self) -> usize {
296 self.keys.len()
297 }
298
299 fn is_selected(&self, index: usize) -> bool {
300 self.keys
301 .get(index)
302 .map(|k| self.selected.contains(k))
303 .unwrap_or(false)
304 }
305
306 fn set_selected(&mut self, index: usize, selected: bool) {
307 if let Some(&key) = self.keys.get(index) {
308 if selected {
309 self.selected.insert(key);
310 } else {
311 self.selected.remove(&key);
312 }
313 }
314 }
315
316 fn selected_count_hint(&self) -> Option<usize> {
317 Some(self.selected.len())
318 }
319}
320
321unsafe fn apply_multi_select_requests_indexed<S: MultiSelectIndexStorage>(
327 ms_io: *mut sys::ImGuiMultiSelectIO,
328 storage: &mut S,
329) {
330 unsafe {
331 if ms_io.is_null() {
332 return;
333 }
334
335 let io_ref: &mut sys::ImGuiMultiSelectIO = &mut *ms_io;
336 let items_count = usize::try_from(io_ref.ItemsCount).unwrap_or(0);
337
338 let requests = &mut io_ref.Requests;
339 if requests.Data.is_null() || requests.Size <= 0 {
340 return;
341 }
342
343 let len = match usize::try_from(requests.Size) {
344 Ok(len) => len,
345 Err(_) => return,
346 };
347 let slice = std::slice::from_raw_parts_mut(requests.Data, len);
348
349 for req in slice {
350 if req.Type == sys::ImGuiSelectionRequestType_SetAll {
351 for idx in 0..items_count {
352 storage.set_selected(idx, req.Selected);
353 }
354 } else if req.Type == sys::ImGuiSelectionRequestType_SetRange {
355 let first = req.RangeFirstItem as i32;
356 let last = req.RangeLastItem as i32;
357 if first < 0 || last < first {
358 continue;
359 }
360 let last_clamped = std::cmp::min(last as usize, items_count.saturating_sub(1));
361 for idx in first as usize..=last_clamped {
362 storage.set_selected(idx, req.Selected);
363 }
364 }
365 }
366 }
367}
368
369pub struct MultiSelectScope<'ui> {
375 ms_io_begin: *mut sys::ImGuiMultiSelectIO,
376 items_count: i32,
377 _marker: std::marker::PhantomData<&'ui Ui>,
378}
379
380impl<'ui> MultiSelectScope<'ui> {
381 fn new(flags: MultiSelectFlags, selection_size: Option<i32>, items_count: usize) -> Self {
382 let selection_size_i32 = selection_size.unwrap_or(-1);
383 let items_count_i32 = i32::try_from(items_count).unwrap_or(i32::MAX);
384 let ms_io_begin =
385 unsafe { sys::igBeginMultiSelect(flags.bits(), selection_size_i32, items_count_i32) };
386 Self {
387 ms_io_begin,
388 items_count: items_count_i32,
389 _marker: std::marker::PhantomData,
390 }
391 }
392
393 pub fn begin_io(&self) -> &sys::ImGuiMultiSelectIO {
395 unsafe { &*self.ms_io_begin }
396 }
397
398 pub fn begin_io_mut(&mut self) -> &mut sys::ImGuiMultiSelectIO {
400 unsafe { &mut *self.ms_io_begin }
401 }
402
403 pub fn apply_begin_requests_indexed<S: MultiSelectIndexStorage>(&mut self, storage: &mut S) {
405 unsafe {
406 apply_multi_select_requests_indexed(self.ms_io_begin, storage);
407 }
408 }
409
410 pub fn end(self) -> MultiSelectEnd<'ui> {
415 let ms_io_end = unsafe { sys::igEndMultiSelect() };
416 MultiSelectEnd {
417 ms_io_end,
418 items_count: self.items_count,
419 _marker: std::marker::PhantomData,
420 }
421 }
422}
423
424pub struct MultiSelectEnd<'ui> {
426 ms_io_end: *mut sys::ImGuiMultiSelectIO,
427 items_count: i32,
428 _marker: std::marker::PhantomData<&'ui Ui>,
429}
430
431impl<'ui> MultiSelectEnd<'ui> {
432 pub fn io(&self) -> &sys::ImGuiMultiSelectIO {
434 unsafe { &*self.ms_io_end }
435 }
436
437 pub fn io_mut(&mut self) -> &mut sys::ImGuiMultiSelectIO {
439 unsafe { &mut *self.ms_io_end }
440 }
441
442 pub fn apply_requests_indexed<S: MultiSelectIndexStorage>(&mut self, storage: &mut S) {
444 unsafe {
445 apply_multi_select_requests_indexed(self.ms_io_end, storage);
446 }
447 }
448
449 pub fn apply_requests_basic<G>(&mut self, selection: &mut BasicSelection, mut id_at_index: G)
451 where
452 G: FnMut(usize) -> crate::Id,
453 {
454 unsafe {
455 apply_multi_select_requests_basic(
456 self.ms_io_end,
457 selection,
458 self.items_count as usize,
459 &mut id_at_index,
460 );
461 }
462 }
463}
464
465impl Ui {
466 pub fn begin_multi_select_raw(
473 &self,
474 flags: MultiSelectFlags,
475 selection_size: Option<i32>,
476 items_count: usize,
477 ) -> MultiSelectScope<'_> {
478 MultiSelectScope::new(flags, selection_size, items_count)
479 }
480 pub fn multi_select_indexed<S, F>(
509 &self,
510 storage: &mut S,
511 flags: MultiSelectFlags,
512 mut render_item: F,
513 ) where
514 S: MultiSelectIndexStorage,
515 F: FnMut(&Ui, usize, bool),
516 {
517 let items_count = storage.len();
518 let selection_size_i32 = storage
519 .selected_count_hint()
520 .and_then(|n| i32::try_from(n).ok())
521 .unwrap_or(-1);
522
523 let ms_io_begin = unsafe {
525 sys::igBeginMultiSelect(flags.bits(), selection_size_i32, items_count as i32)
526 };
527
528 unsafe {
530 apply_multi_select_requests_indexed(ms_io_begin, storage);
531 }
532
533 for idx in 0..items_count {
536 unsafe {
537 sys::igSetNextItemSelectionUserData(idx as sys::ImGuiSelectionUserData);
538 }
539 let is_selected = storage.is_selected(idx);
540 render_item(self, idx, is_selected);
541 }
542
543 let ms_io_end = unsafe { sys::igEndMultiSelect() };
545 unsafe {
546 apply_multi_select_requests_indexed(ms_io_end, storage);
547 }
548 }
549
550 pub fn table_multi_select_indexed<S, F>(
557 &self,
558 storage: &mut S,
559 flags: MultiSelectFlags,
560 mut build_row: F,
561 ) where
562 S: MultiSelectIndexStorage,
563 F: FnMut(&Ui, usize, bool),
564 {
565 let row_count = storage.len();
566 let selection_size_i32 = storage
567 .selected_count_hint()
568 .and_then(|n| i32::try_from(n).ok())
569 .unwrap_or(-1);
570
571 let ms_io_begin =
572 unsafe { sys::igBeginMultiSelect(flags.bits(), selection_size_i32, row_count as i32) };
573
574 unsafe {
575 apply_multi_select_requests_indexed(ms_io_begin, storage);
576 }
577
578 for row in 0..row_count {
579 unsafe {
580 sys::igSetNextItemSelectionUserData(row as sys::ImGuiSelectionUserData);
581 }
582 self.table_next_row();
584 self.table_next_column();
585
586 let is_selected = storage.is_selected(row);
587 build_row(self, row, is_selected);
588 }
589
590 let ms_io_end = unsafe { sys::igEndMultiSelect() };
591 unsafe {
592 apply_multi_select_requests_indexed(ms_io_end, storage);
593 }
594 }
595
596 pub fn multi_select_basic<G, F>(
605 &self,
606 selection: &mut BasicSelection,
607 flags: MultiSelectFlags,
608 items_count: usize,
609 mut id_at_index: G,
610 mut render_item: F,
611 ) where
612 G: FnMut(usize) -> crate::Id,
613 F: FnMut(&Ui, usize, crate::Id, bool),
614 {
615 let selection_size_i32 = i32::try_from(selection.len()).unwrap_or(-1);
616
617 let ms_io_begin = unsafe {
618 sys::igBeginMultiSelect(flags.bits(), selection_size_i32, items_count as i32)
619 };
620
621 unsafe {
622 apply_multi_select_requests_basic(
623 ms_io_begin,
624 selection,
625 items_count,
626 &mut id_at_index,
627 );
628 }
629
630 for idx in 0..items_count {
631 unsafe {
632 sys::igSetNextItemSelectionUserData(idx as sys::ImGuiSelectionUserData);
633 }
634 let id = id_at_index(idx);
635 let is_selected = selection.contains(id);
636 render_item(self, idx, id, is_selected);
637 }
638
639 let ms_io_end = unsafe { sys::igEndMultiSelect() };
640 unsafe {
641 apply_multi_select_requests_basic(ms_io_end, selection, items_count, &mut id_at_index);
642 }
643 }
644}
645
646unsafe fn apply_multi_select_requests_basic<G>(
648 ms_io: *mut sys::ImGuiMultiSelectIO,
649 selection: &mut BasicSelection,
650 items_count: usize,
651 id_at_index: &mut G,
652) where
653 G: FnMut(usize) -> crate::Id,
654{
655 unsafe {
656 if ms_io.is_null() {
657 return;
658 }
659
660 let io_ref: &mut sys::ImGuiMultiSelectIO = &mut *ms_io;
661 let requests = &mut io_ref.Requests;
662 if requests.Data.is_null() || requests.Size <= 0 {
663 return;
664 }
665
666 let len = match usize::try_from(requests.Size) {
667 Ok(len) => len,
668 Err(_) => return,
669 };
670 let slice = std::slice::from_raw_parts_mut(requests.Data, len);
671
672 for req in slice {
673 if req.Type == sys::ImGuiSelectionRequestType_SetAll {
674 for idx in 0..items_count {
675 let id = id_at_index(idx);
676 selection.set_selected(id, req.Selected);
677 }
678 } else if req.Type == sys::ImGuiSelectionRequestType_SetRange {
679 let first = req.RangeFirstItem as i32;
680 let last = req.RangeLastItem as i32;
681 if first < 0 || last < first {
682 continue;
683 }
684 let last_clamped = std::cmp::min(last as usize, items_count.saturating_sub(1));
685 for idx in first as usize..=last_clamped {
686 let id = id_at_index(idx);
687 selection.set_selected(id, req.Selected);
688 }
689 }
690 }
691 }
692}