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_NO_SCROLL = sys::ImGuiMultiSelectFlags_BoxSelectNoScroll as i32;
48 const CLEAR_ON_ESCAPE = sys::ImGuiMultiSelectFlags_ClearOnEscape as i32;
50 const CLEAR_ON_CLICK_VOID = sys::ImGuiMultiSelectFlags_ClearOnClickVoid as i32;
52 const NO_SELECT_ON_RIGHT_CLICK =
54 sys::ImGuiMultiSelectFlags_NoSelectOnRightClick as i32;
55 }
56}
57
58#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
60pub enum MultiSelectBoxSelect {
61 OneDimensional,
63 TwoDimensional,
65}
66
67impl MultiSelectBoxSelect {
68 #[inline]
69 const fn raw(self) -> i32 {
70 match self {
71 Self::OneDimensional => sys::ImGuiMultiSelectFlags_BoxSelect1d as i32,
72 Self::TwoDimensional => sys::ImGuiMultiSelectFlags_BoxSelect2d as i32,
73 }
74 }
75}
76
77#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
79pub enum MultiSelectClickPolicy {
80 Auto,
83 ClickAlways,
85 ClickRelease,
87}
88
89impl MultiSelectClickPolicy {
90 #[inline]
91 const fn raw(self) -> i32 {
92 match self {
93 Self::Auto => sys::ImGuiMultiSelectFlags_SelectOnAuto as i32,
94 Self::ClickAlways => sys::ImGuiMultiSelectFlags_SelectOnClickAlways as i32,
95 Self::ClickRelease => sys::ImGuiMultiSelectFlags_SelectOnClickRelease as i32,
96 }
97 }
98}
99
100#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
102pub enum MultiSelectScopeKind {
103 Window,
105 WindowWithNavWrapX,
107 Rect,
109}
110
111impl MultiSelectScopeKind {
112 #[inline]
113 const fn raw(self) -> i32 {
114 match self {
115 Self::Window => sys::ImGuiMultiSelectFlags_ScopeWindow as i32,
116 Self::WindowWithNavWrapX => {
117 (sys::ImGuiMultiSelectFlags_ScopeWindow | sys::ImGuiMultiSelectFlags_NavWrapX)
118 as i32
119 }
120 Self::Rect => sys::ImGuiMultiSelectFlags_ScopeRect as i32,
121 }
122 }
123}
124
125#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
128pub struct MultiSelectOptions {
129 pub flags: MultiSelectFlags,
130 pub click_policy: Option<MultiSelectClickPolicy>,
131 pub box_select: Option<MultiSelectBoxSelect>,
132 pub scope: Option<MultiSelectScopeKind>,
133}
134
135impl MultiSelectOptions {
136 pub const fn new() -> Self {
137 Self {
138 flags: MultiSelectFlags::NONE,
139 click_policy: None,
140 box_select: None,
141 scope: None,
142 }
143 }
144
145 pub fn flags(mut self, flags: MultiSelectFlags) -> Self {
146 self.flags = flags;
147 self
148 }
149
150 pub fn click_policy(mut self, policy: MultiSelectClickPolicy) -> Self {
151 self.click_policy = Some(policy);
152 self
153 }
154
155 pub fn box_select(mut self, mode: MultiSelectBoxSelect) -> Self {
156 self.box_select = Some(mode);
157 self
158 }
159
160 pub fn scope(mut self, scope: MultiSelectScopeKind) -> Self {
161 self.scope = Some(scope);
162 self
163 }
164
165 pub fn bits(self) -> i32 {
166 self.raw()
167 }
168
169 #[inline]
170 pub(crate) fn raw(self) -> i32 {
171 self.flags.bits()
172 | self.click_policy.map_or(0, MultiSelectClickPolicy::raw)
173 | self.box_select.map_or(0, MultiSelectBoxSelect::raw)
174 | self.scope.map_or(0, MultiSelectScopeKind::raw)
175 }
176}
177
178impl Default for MultiSelectOptions {
179 fn default() -> Self {
180 Self::new()
181 }
182}
183
184impl From<MultiSelectFlags> for MultiSelectOptions {
185 fn from(flags: MultiSelectFlags) -> Self {
186 Self::new().flags(flags)
187 }
188}
189
190#[derive(Debug)]
196pub struct BasicSelection {
197 raw: *mut sys::ImGuiSelectionBasicStorage,
198}
199
200impl BasicSelection {
201 pub fn new() -> Self {
203 unsafe {
204 let ptr = sys::ImGuiSelectionBasicStorage_ImGuiSelectionBasicStorage();
205 if ptr.is_null() {
206 panic!("ImGuiSelectionBasicStorage_ImGuiSelectionBasicStorage() returned null");
207 }
208 Self { raw: ptr }
209 }
210 }
211
212 pub fn len(&self) -> usize {
214 unsafe {
215 let size = (*self.raw).Size;
216 if size <= 0 { 0 } else { size as usize }
217 }
218 }
219
220 pub fn is_empty(&self) -> bool {
222 self.len() == 0
223 }
224
225 pub fn clear(&mut self) {
227 unsafe {
228 sys::ImGuiSelectionBasicStorage_Clear(self.raw);
229 }
230 }
231
232 pub fn contains(&self, id: crate::Id) -> bool {
234 unsafe { sys::ImGuiSelectionBasicStorage_Contains(self.raw, id.raw()) }
235 }
236
237 pub fn set_selected(&mut self, id: crate::Id, selected: bool) {
239 unsafe {
240 sys::ImGuiSelectionBasicStorage_SetItemSelected(self.raw, id.raw(), selected);
241 }
242 }
243
244 pub fn iter(&self) -> BasicSelectionIter<'_> {
246 BasicSelectionIter {
247 storage: self,
248 it: std::ptr::null_mut(),
249 }
250 }
251
252 pub(crate) fn as_raw(&self) -> *mut sys::ImGuiSelectionBasicStorage {
254 self.raw
255 }
256}
257
258impl Default for BasicSelection {
259 fn default() -> Self {
260 Self::new()
261 }
262}
263
264impl Drop for BasicSelection {
265 fn drop(&mut self) {
266 unsafe {
267 if !self.raw.is_null() {
268 sys::ImGuiSelectionBasicStorage_destroy(self.raw);
269 self.raw = std::ptr::null_mut();
270 }
271 }
272 }
273}
274
275pub struct BasicSelectionIter<'a> {
277 storage: &'a BasicSelection,
278 it: *mut std::os::raw::c_void,
279}
280
281impl<'a> Iterator for BasicSelectionIter<'a> {
282 type Item = crate::Id;
283
284 fn next(&mut self) -> Option<Self::Item> {
285 unsafe {
286 let mut out_id: sys::ImGuiID = 0;
287 let has_next = sys::ImGuiSelectionBasicStorage_GetNextSelectedItem(
288 self.storage.as_raw(),
289 &mut self.it,
290 &mut out_id,
291 );
292 if has_next {
293 Some(crate::Id::from(out_id))
294 } else {
295 None
296 }
297 }
298 }
299}
300
301pub trait MultiSelectIndexStorage {
307 fn len(&self) -> usize;
309
310 fn is_empty(&self) -> bool {
312 self.len() == 0
313 }
314
315 fn is_selected(&self, index: usize) -> bool;
317
318 fn set_selected(&mut self, index: usize, selected: bool);
320
321 fn selected_count_hint(&self) -> Option<usize> {
327 None
328 }
329}
330
331impl MultiSelectIndexStorage for Vec<bool> {
332 fn len(&self) -> usize {
333 self.len()
334 }
335
336 fn is_selected(&self, index: usize) -> bool {
337 self.get(index).copied().unwrap_or(false)
338 }
339
340 fn set_selected(&mut self, index: usize, selected: bool) {
341 if index < self.len() {
342 self[index] = selected;
343 }
344 }
345
346 fn selected_count_hint(&self) -> Option<usize> {
347 Some(self.iter().filter(|&&b| b).count())
350 }
351}
352
353impl MultiSelectIndexStorage for &mut [bool] {
354 fn len(&self) -> usize {
355 (**self).len()
356 }
357
358 fn is_selected(&self, index: usize) -> bool {
359 self.get(index).copied().unwrap_or(false)
360 }
361
362 fn set_selected(&mut self, index: usize, selected: bool) {
363 if index < self.len() {
364 self[index] = selected;
365 }
366 }
367
368 fn selected_count_hint(&self) -> Option<usize> {
369 Some(self.iter().filter(|&&b| b).count())
370 }
371}
372
373pub struct KeySetSelection<'a, K>
379where
380 K: Eq + std::hash::Hash + Copy,
381{
382 keys: &'a [K],
383 selected: &'a mut HashSet<K>,
384}
385
386impl<'a, K> KeySetSelection<'a, K>
387where
388 K: Eq + std::hash::Hash + Copy,
389{
390 pub fn new(keys: &'a [K], selected: &'a mut HashSet<K>) -> Self {
395 Self { keys, selected }
396 }
397}
398
399impl<'a, K> MultiSelectIndexStorage for KeySetSelection<'a, K>
400where
401 K: Eq + std::hash::Hash + Copy,
402{
403 fn len(&self) -> usize {
404 self.keys.len()
405 }
406
407 fn is_selected(&self, index: usize) -> bool {
408 self.keys
409 .get(index)
410 .map(|k| self.selected.contains(k))
411 .unwrap_or(false)
412 }
413
414 fn set_selected(&mut self, index: usize, selected: bool) {
415 if let Some(&key) = self.keys.get(index) {
416 if selected {
417 self.selected.insert(key);
418 } else {
419 self.selected.remove(&key);
420 }
421 }
422 }
423
424 fn selected_count_hint(&self) -> Option<usize> {
425 Some(self.selected.len())
426 }
427}
428
429unsafe fn apply_multi_select_requests_indexed<S: MultiSelectIndexStorage>(
435 ms_io: *mut sys::ImGuiMultiSelectIO,
436 storage: &mut S,
437) {
438 unsafe {
439 if ms_io.is_null() {
440 return;
441 }
442
443 let io_ref: &mut sys::ImGuiMultiSelectIO = &mut *ms_io;
444 let items_count = usize::try_from(io_ref.ItemsCount).unwrap_or(0);
445
446 let requests = &mut io_ref.Requests;
447 if requests.Data.is_null() || requests.Size <= 0 {
448 return;
449 }
450
451 let len = match usize::try_from(requests.Size) {
452 Ok(len) => len,
453 Err(_) => return,
454 };
455 let slice = std::slice::from_raw_parts_mut(requests.Data, len);
456
457 for req in slice {
458 if req.Type == sys::ImGuiSelectionRequestType_SetAll {
459 for idx in 0..items_count {
460 storage.set_selected(idx, req.Selected);
461 }
462 } else if req.Type == sys::ImGuiSelectionRequestType_SetRange {
463 let first = req.RangeFirstItem as i32;
464 let last = req.RangeLastItem as i32;
465 if first < 0 || last < first {
466 continue;
467 }
468 let last_clamped = std::cmp::min(last as usize, items_count.saturating_sub(1));
469 for idx in first as usize..=last_clamped {
470 storage.set_selected(idx, req.Selected);
471 }
472 }
473 }
474 }
475}
476
477pub struct MultiSelectScope<'ui> {
483 ms_io_begin: *mut sys::ImGuiMultiSelectIO,
484 items_count: i32,
485 _marker: std::marker::PhantomData<&'ui Ui>,
486}
487
488impl<'ui> MultiSelectScope<'ui> {
489 fn new(
490 flags: impl Into<MultiSelectOptions>,
491 selection_size: Option<i32>,
492 items_count: usize,
493 ) -> Self {
494 let options = flags.into();
495 let selection_size_i32 = selection_size.unwrap_or(-1);
496 let items_count_i32 = i32::try_from(items_count).unwrap_or(i32::MAX);
497 let ms_io_begin =
498 unsafe { sys::igBeginMultiSelect(options.raw(), selection_size_i32, items_count_i32) };
499 Self {
500 ms_io_begin,
501 items_count: items_count_i32,
502 _marker: std::marker::PhantomData,
503 }
504 }
505
506 pub fn begin_io(&self) -> &sys::ImGuiMultiSelectIO {
508 unsafe { &*self.ms_io_begin }
509 }
510
511 pub fn begin_io_mut(&mut self) -> &mut sys::ImGuiMultiSelectIO {
513 unsafe { &mut *self.ms_io_begin }
514 }
515
516 pub fn apply_begin_requests_indexed<S: MultiSelectIndexStorage>(&mut self, storage: &mut S) {
518 unsafe {
519 apply_multi_select_requests_indexed(self.ms_io_begin, storage);
520 }
521 }
522
523 pub fn end(self) -> MultiSelectEnd<'ui> {
528 let ms_io_end = unsafe { sys::igEndMultiSelect() };
529 MultiSelectEnd {
530 ms_io_end,
531 items_count: self.items_count,
532 _marker: std::marker::PhantomData,
533 }
534 }
535}
536
537pub struct MultiSelectEnd<'ui> {
539 ms_io_end: *mut sys::ImGuiMultiSelectIO,
540 items_count: i32,
541 _marker: std::marker::PhantomData<&'ui Ui>,
542}
543
544impl<'ui> MultiSelectEnd<'ui> {
545 pub fn io(&self) -> &sys::ImGuiMultiSelectIO {
547 unsafe { &*self.ms_io_end }
548 }
549
550 pub fn io_mut(&mut self) -> &mut sys::ImGuiMultiSelectIO {
552 unsafe { &mut *self.ms_io_end }
553 }
554
555 pub fn apply_requests_indexed<S: MultiSelectIndexStorage>(&mut self, storage: &mut S) {
557 unsafe {
558 apply_multi_select_requests_indexed(self.ms_io_end, storage);
559 }
560 }
561
562 pub fn apply_requests_basic<G>(&mut self, selection: &mut BasicSelection, mut id_at_index: G)
564 where
565 G: FnMut(usize) -> crate::Id,
566 {
567 unsafe {
568 apply_multi_select_requests_basic(
569 self.ms_io_end,
570 selection,
571 self.items_count as usize,
572 &mut id_at_index,
573 );
574 }
575 }
576}
577
578impl Ui {
579 pub fn begin_multi_select_raw(
586 &self,
587 flags: impl Into<MultiSelectOptions>,
588 selection_size: Option<i32>,
589 items_count: usize,
590 ) -> MultiSelectScope<'_> {
591 MultiSelectScope::new(flags, selection_size, items_count)
592 }
593 pub fn multi_select_indexed<S, F>(
622 &self,
623 storage: &mut S,
624 flags: impl Into<MultiSelectOptions>,
625 mut render_item: F,
626 ) where
627 S: MultiSelectIndexStorage,
628 F: FnMut(&Ui, usize, bool),
629 {
630 let options = flags.into();
631 let items_count = storage.len();
632 let selection_size_i32 = storage
633 .selected_count_hint()
634 .and_then(|n| i32::try_from(n).ok())
635 .unwrap_or(-1);
636
637 let ms_io_begin = unsafe {
639 sys::igBeginMultiSelect(options.raw(), selection_size_i32, items_count as i32)
640 };
641
642 unsafe {
644 apply_multi_select_requests_indexed(ms_io_begin, storage);
645 }
646
647 for idx in 0..items_count {
650 unsafe {
651 sys::igSetNextItemSelectionUserData(idx as sys::ImGuiSelectionUserData);
652 }
653 let is_selected = storage.is_selected(idx);
654 render_item(self, idx, is_selected);
655 }
656
657 let ms_io_end = unsafe { sys::igEndMultiSelect() };
659 unsafe {
660 apply_multi_select_requests_indexed(ms_io_end, storage);
661 }
662 }
663
664 pub fn table_multi_select_indexed<S, F>(
671 &self,
672 storage: &mut S,
673 flags: impl Into<MultiSelectOptions>,
674 mut build_row: F,
675 ) where
676 S: MultiSelectIndexStorage,
677 F: FnMut(&Ui, usize, bool),
678 {
679 let options = flags.into();
680 let row_count = storage.len();
681 let selection_size_i32 = storage
682 .selected_count_hint()
683 .and_then(|n| i32::try_from(n).ok())
684 .unwrap_or(-1);
685
686 let ms_io_begin =
687 unsafe { sys::igBeginMultiSelect(options.raw(), selection_size_i32, row_count as i32) };
688
689 unsafe {
690 apply_multi_select_requests_indexed(ms_io_begin, storage);
691 }
692
693 for row in 0..row_count {
694 unsafe {
695 sys::igSetNextItemSelectionUserData(row as sys::ImGuiSelectionUserData);
696 }
697 self.table_next_row();
699 self.table_next_column();
700
701 let is_selected = storage.is_selected(row);
702 build_row(self, row, is_selected);
703 }
704
705 let ms_io_end = unsafe { sys::igEndMultiSelect() };
706 unsafe {
707 apply_multi_select_requests_indexed(ms_io_end, storage);
708 }
709 }
710
711 pub fn multi_select_basic<G, F>(
720 &self,
721 selection: &mut BasicSelection,
722 flags: impl Into<MultiSelectOptions>,
723 items_count: usize,
724 mut id_at_index: G,
725 mut render_item: F,
726 ) where
727 G: FnMut(usize) -> crate::Id,
728 F: FnMut(&Ui, usize, crate::Id, bool),
729 {
730 let options = flags.into();
731 let selection_size_i32 = i32::try_from(selection.len()).unwrap_or(-1);
732
733 let ms_io_begin = unsafe {
734 sys::igBeginMultiSelect(options.raw(), selection_size_i32, items_count as i32)
735 };
736
737 unsafe {
738 apply_multi_select_requests_basic(
739 ms_io_begin,
740 selection,
741 items_count,
742 &mut id_at_index,
743 );
744 }
745
746 for idx in 0..items_count {
747 unsafe {
748 sys::igSetNextItemSelectionUserData(idx as sys::ImGuiSelectionUserData);
749 }
750 let id = id_at_index(idx);
751 let is_selected = selection.contains(id);
752 render_item(self, idx, id, is_selected);
753 }
754
755 let ms_io_end = unsafe { sys::igEndMultiSelect() };
756 unsafe {
757 apply_multi_select_requests_basic(ms_io_end, selection, items_count, &mut id_at_index);
758 }
759 }
760}
761
762unsafe fn apply_multi_select_requests_basic<G>(
764 ms_io: *mut sys::ImGuiMultiSelectIO,
765 selection: &mut BasicSelection,
766 items_count: usize,
767 id_at_index: &mut G,
768) where
769 G: FnMut(usize) -> crate::Id,
770{
771 unsafe {
772 if ms_io.is_null() {
773 return;
774 }
775
776 let io_ref: &mut sys::ImGuiMultiSelectIO = &mut *ms_io;
777 let requests = &mut io_ref.Requests;
778 if requests.Data.is_null() || requests.Size <= 0 {
779 return;
780 }
781
782 let len = match usize::try_from(requests.Size) {
783 Ok(len) => len,
784 Err(_) => return,
785 };
786 let slice = std::slice::from_raw_parts_mut(requests.Data, len);
787
788 for req in slice {
789 if req.Type == sys::ImGuiSelectionRequestType_SetAll {
790 for idx in 0..items_count {
791 let id = id_at_index(idx);
792 selection.set_selected(id, req.Selected);
793 }
794 } else if req.Type == sys::ImGuiSelectionRequestType_SetRange {
795 let first = req.RangeFirstItem as i32;
796 let last = req.RangeLastItem as i32;
797 if first < 0 || last < first {
798 continue;
799 }
800 let last_clamped = std::cmp::min(last as usize, items_count.saturating_sub(1));
801 for idx in first as usize..=last_clamped {
802 let id = id_at_index(idx);
803 selection.set_selected(id, req.Selected);
804 }
805 }
806 }
807 }
808}