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_CLICK = sys::ImGuiMultiSelectFlags_SelectOnClick as i32;
62 const SELECT_ON_CLICK_RELEASE =
64 sys::ImGuiMultiSelectFlags_SelectOnClickRelease as i32;
65 const NAV_WRAP_X = sys::ImGuiMultiSelectFlags_NavWrapX as i32;
67 const NO_SELECT_ON_RIGHT_CLICK =
69 sys::ImGuiMultiSelectFlags_NoSelectOnRightClick as i32;
70 }
71}
72
73#[derive(Debug)]
79pub struct BasicSelection {
80 raw: *mut sys::ImGuiSelectionBasicStorage,
81}
82
83impl BasicSelection {
84 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 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 pub fn is_empty(&self) -> bool {
105 self.len() == 0
106 }
107
108 pub fn clear(&mut self) {
110 unsafe {
111 sys::ImGuiSelectionBasicStorage_Clear(self.raw);
112 }
113 }
114
115 pub fn contains(&self, id: crate::Id) -> bool {
117 unsafe { sys::ImGuiSelectionBasicStorage_Contains(self.raw, id.raw()) }
118 }
119
120 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 pub fn iter(&self) -> BasicSelectionIter<'_> {
129 BasicSelectionIter {
130 storage: self,
131 it: std::ptr::null_mut(),
132 }
133 }
134
135 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
158pub 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
184pub trait MultiSelectIndexStorage {
190 fn len(&self) -> usize;
192
193 fn is_empty(&self) -> bool {
195 self.len() == 0
196 }
197
198 fn is_selected(&self, index: usize) -> bool;
200
201 fn set_selected(&mut self, index: usize, selected: bool);
203
204 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 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
256pub 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 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
312unsafe 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
360pub 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 pub fn begin_io(&self) -> &sys::ImGuiMultiSelectIO {
386 unsafe { &*self.ms_io_begin }
387 }
388
389 pub fn begin_io_mut(&mut self) -> &mut sys::ImGuiMultiSelectIO {
391 unsafe { &mut *self.ms_io_begin }
392 }
393
394 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 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
415pub 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 pub fn io(&self) -> &sys::ImGuiMultiSelectIO {
425 unsafe { &*self.ms_io_end }
426 }
427
428 pub fn io_mut(&mut self) -> &mut sys::ImGuiMultiSelectIO {
430 unsafe { &mut *self.ms_io_end }
431 }
432
433 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 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 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 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 let ms_io_begin = unsafe {
516 sys::igBeginMultiSelect(flags.bits(), selection_size_i32, items_count as i32)
517 };
518
519 unsafe {
521 apply_multi_select_requests_indexed(ms_io_begin, storage);
522 }
523
524 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 let ms_io_end = unsafe { sys::igEndMultiSelect() };
536 unsafe {
537 apply_multi_select_requests_indexed(ms_io_end, storage);
538 }
539 }
540
541 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 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 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
637unsafe 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}