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 { (*self.raw).Size as usize }
98 }
99
100 pub fn is_empty(&self) -> bool {
102 self.len() == 0
103 }
104
105 pub fn clear(&mut self) {
107 unsafe {
108 sys::ImGuiSelectionBasicStorage_Clear(self.raw);
109 }
110 }
111
112 pub fn contains(&self, id: crate::Id) -> bool {
114 unsafe { sys::ImGuiSelectionBasicStorage_Contains(self.raw, id.raw()) }
115 }
116
117 pub fn set_selected(&mut self, id: crate::Id, selected: bool) {
119 unsafe {
120 sys::ImGuiSelectionBasicStorage_SetItemSelected(self.raw, id.raw(), selected);
121 }
122 }
123
124 pub fn iter(&self) -> BasicSelectionIter<'_> {
126 BasicSelectionIter {
127 storage: self,
128 it: std::ptr::null_mut(),
129 }
130 }
131
132 pub(crate) fn as_raw(&self) -> *mut sys::ImGuiSelectionBasicStorage {
134 self.raw
135 }
136}
137
138impl Default for BasicSelection {
139 fn default() -> Self {
140 Self::new()
141 }
142}
143
144impl Drop for BasicSelection {
145 fn drop(&mut self) {
146 unsafe {
147 if !self.raw.is_null() {
148 sys::ImGuiSelectionBasicStorage_destroy(self.raw);
149 self.raw = std::ptr::null_mut();
150 }
151 }
152 }
153}
154
155pub struct BasicSelectionIter<'a> {
157 storage: &'a BasicSelection,
158 it: *mut std::os::raw::c_void,
159}
160
161impl<'a> Iterator for BasicSelectionIter<'a> {
162 type Item = crate::Id;
163
164 fn next(&mut self) -> Option<Self::Item> {
165 unsafe {
166 let mut out_id: sys::ImGuiID = 0;
167 let has_next = sys::ImGuiSelectionBasicStorage_GetNextSelectedItem(
168 self.storage.as_raw(),
169 &mut self.it,
170 &mut out_id,
171 );
172 if has_next {
173 Some(crate::Id::from(out_id))
174 } else {
175 None
176 }
177 }
178 }
179}
180
181pub trait MultiSelectIndexStorage {
187 fn len(&self) -> usize;
189
190 fn is_selected(&self, index: usize) -> bool;
192
193 fn set_selected(&mut self, index: usize, selected: bool);
195
196 fn selected_count_hint(&self) -> Option<usize> {
202 None
203 }
204}
205
206impl MultiSelectIndexStorage for Vec<bool> {
207 fn len(&self) -> usize {
208 self.len()
209 }
210
211 fn is_selected(&self, index: usize) -> bool {
212 self.get(index).copied().unwrap_or(false)
213 }
214
215 fn set_selected(&mut self, index: usize, selected: bool) {
216 if index < self.len() {
217 self[index] = selected;
218 }
219 }
220
221 fn selected_count_hint(&self) -> Option<usize> {
222 Some(self.iter().filter(|&&b| b).count())
225 }
226}
227
228impl<'a> MultiSelectIndexStorage for &'a mut [bool] {
229 fn len(&self) -> usize {
230 (**self).len()
231 }
232
233 fn is_selected(&self, index: usize) -> bool {
234 self.get(index).copied().unwrap_or(false)
235 }
236
237 fn set_selected(&mut self, index: usize, selected: bool) {
238 if index < self.len() {
239 self[index] = selected;
240 }
241 }
242
243 fn selected_count_hint(&self) -> Option<usize> {
244 Some(self.iter().filter(|&&b| b).count())
245 }
246}
247
248pub struct KeySetSelection<'a, K>
254where
255 K: Eq + std::hash::Hash + Copy,
256{
257 keys: &'a [K],
258 selected: &'a mut HashSet<K>,
259}
260
261impl<'a, K> KeySetSelection<'a, K>
262where
263 K: Eq + std::hash::Hash + Copy,
264{
265 pub fn new(keys: &'a [K], selected: &'a mut HashSet<K>) -> Self {
270 Self { keys, selected }
271 }
272}
273
274impl<'a, K> MultiSelectIndexStorage for KeySetSelection<'a, K>
275where
276 K: Eq + std::hash::Hash + Copy,
277{
278 fn len(&self) -> usize {
279 self.keys.len()
280 }
281
282 fn is_selected(&self, index: usize) -> bool {
283 self.keys
284 .get(index)
285 .map(|k| self.selected.contains(k))
286 .unwrap_or(false)
287 }
288
289 fn set_selected(&mut self, index: usize, selected: bool) {
290 if let Some(&key) = self.keys.get(index) {
291 if selected {
292 self.selected.insert(key);
293 } else {
294 self.selected.remove(&key);
295 }
296 }
297 }
298
299 fn selected_count_hint(&self) -> Option<usize> {
300 Some(self.selected.len())
301 }
302}
303
304unsafe fn apply_multi_select_requests_indexed<S: MultiSelectIndexStorage>(
310 ms_io: *mut sys::ImGuiMultiSelectIO,
311 storage: &mut S,
312) {
313 unsafe {
314 if ms_io.is_null() {
315 return;
316 }
317
318 let io_ref: &mut sys::ImGuiMultiSelectIO = &mut *ms_io;
319 let items_count = if io_ref.ItemsCount < 0 {
320 0
321 } else {
322 io_ref.ItemsCount as usize
323 };
324
325 let requests = &mut io_ref.Requests;
326 if requests.Data.is_null() || requests.Size <= 0 {
327 return;
328 }
329
330 let slice = std::slice::from_raw_parts_mut(requests.Data, requests.Size as usize);
331
332 for req in slice {
333 if req.Type == sys::ImGuiSelectionRequestType_SetAll {
334 for idx in 0..items_count {
335 storage.set_selected(idx, req.Selected);
336 }
337 } else if req.Type == sys::ImGuiSelectionRequestType_SetRange {
338 let first = req.RangeFirstItem as i32;
339 let last = req.RangeLastItem as i32;
340 if first < 0 || last < first {
341 continue;
342 }
343 let last_clamped = std::cmp::min(last as usize, items_count.saturating_sub(1));
344 for idx in first as usize..=last_clamped {
345 storage.set_selected(idx, req.Selected);
346 }
347 }
348 }
349 }
350}
351
352pub struct MultiSelectScope<'ui> {
358 ms_io_begin: *mut sys::ImGuiMultiSelectIO,
359 items_count: i32,
360 _marker: std::marker::PhantomData<&'ui Ui>,
361}
362
363impl<'ui> MultiSelectScope<'ui> {
364 fn new(flags: MultiSelectFlags, selection_size: Option<i32>, items_count: usize) -> Self {
365 let selection_size_i32 = selection_size.unwrap_or(-1);
366 let ms_io_begin = unsafe {
367 sys::igBeginMultiSelect(flags.bits(), selection_size_i32, items_count as i32)
368 };
369 Self {
370 ms_io_begin,
371 items_count: items_count as i32,
372 _marker: std::marker::PhantomData,
373 }
374 }
375
376 pub fn begin_io(&self) -> &sys::ImGuiMultiSelectIO {
378 unsafe { &*self.ms_io_begin }
379 }
380
381 pub fn begin_io_mut(&mut self) -> &mut sys::ImGuiMultiSelectIO {
383 unsafe { &mut *self.ms_io_begin }
384 }
385
386 pub fn apply_begin_requests_indexed<S: MultiSelectIndexStorage>(&mut self, storage: &mut S) {
388 unsafe {
389 apply_multi_select_requests_indexed(self.ms_io_begin, storage);
390 }
391 }
392
393 pub fn end(self) -> MultiSelectEnd<'ui> {
398 let ms_io_end = unsafe { sys::igEndMultiSelect() };
399 MultiSelectEnd {
400 ms_io_end,
401 items_count: self.items_count,
402 _marker: std::marker::PhantomData,
403 }
404 }
405}
406
407pub struct MultiSelectEnd<'ui> {
409 ms_io_end: *mut sys::ImGuiMultiSelectIO,
410 items_count: i32,
411 _marker: std::marker::PhantomData<&'ui Ui>,
412}
413
414impl<'ui> MultiSelectEnd<'ui> {
415 pub fn io(&self) -> &sys::ImGuiMultiSelectIO {
417 unsafe { &*self.ms_io_end }
418 }
419
420 pub fn io_mut(&mut self) -> &mut sys::ImGuiMultiSelectIO {
422 unsafe { &mut *self.ms_io_end }
423 }
424
425 pub fn apply_requests_indexed<S: MultiSelectIndexStorage>(&mut self, storage: &mut S) {
427 unsafe {
428 apply_multi_select_requests_indexed(self.ms_io_end, storage);
429 }
430 }
431
432 pub fn apply_requests_basic<G>(&mut self, selection: &mut BasicSelection, mut id_at_index: G)
434 where
435 G: FnMut(usize) -> crate::Id,
436 {
437 unsafe {
438 apply_multi_select_requests_basic(
439 self.ms_io_end,
440 selection,
441 self.items_count as usize,
442 &mut id_at_index,
443 );
444 }
445 }
446}
447
448impl Ui {
449 pub fn begin_multi_select_raw(
456 &self,
457 flags: MultiSelectFlags,
458 selection_size: Option<i32>,
459 items_count: usize,
460 ) -> MultiSelectScope<'_> {
461 MultiSelectScope::new(flags, selection_size, items_count)
462 }
463 pub fn multi_select_indexed<S, F>(
492 &self,
493 storage: &mut S,
494 flags: MultiSelectFlags,
495 mut render_item: F,
496 ) where
497 S: MultiSelectIndexStorage,
498 F: FnMut(&Ui, usize, bool),
499 {
500 let items_count = storage.len();
501 let selection_size_i32 = storage
502 .selected_count_hint()
503 .and_then(|n| i32::try_from(n).ok())
504 .unwrap_or(-1);
505
506 let ms_io_begin = unsafe {
508 sys::igBeginMultiSelect(flags.bits(), selection_size_i32, items_count as i32)
509 };
510
511 unsafe {
513 apply_multi_select_requests_indexed(ms_io_begin, storage);
514 }
515
516 for idx in 0..items_count {
519 unsafe {
520 sys::igSetNextItemSelectionUserData(idx as sys::ImGuiSelectionUserData);
521 }
522 let is_selected = storage.is_selected(idx);
523 render_item(self, idx, is_selected);
524 }
525
526 let ms_io_end = unsafe { sys::igEndMultiSelect() };
528 unsafe {
529 apply_multi_select_requests_indexed(ms_io_end, storage);
530 }
531 }
532
533 pub fn table_multi_select_indexed<S, F>(
540 &self,
541 storage: &mut S,
542 flags: MultiSelectFlags,
543 mut build_row: F,
544 ) where
545 S: MultiSelectIndexStorage,
546 F: FnMut(&Ui, usize, bool),
547 {
548 let row_count = storage.len();
549 let selection_size_i32 = storage
550 .selected_count_hint()
551 .and_then(|n| i32::try_from(n).ok())
552 .unwrap_or(-1);
553
554 let ms_io_begin =
555 unsafe { sys::igBeginMultiSelect(flags.bits(), selection_size_i32, row_count as i32) };
556
557 unsafe {
558 apply_multi_select_requests_indexed(ms_io_begin, storage);
559 }
560
561 for row in 0..row_count {
562 unsafe {
563 sys::igSetNextItemSelectionUserData(row as sys::ImGuiSelectionUserData);
564 }
565 self.table_next_row();
567 self.table_next_column();
568
569 let is_selected = storage.is_selected(row);
570 build_row(self, row, is_selected);
571 }
572
573 let ms_io_end = unsafe { sys::igEndMultiSelect() };
574 unsafe {
575 apply_multi_select_requests_indexed(ms_io_end, storage);
576 }
577 }
578
579 pub fn multi_select_basic<G, F>(
588 &self,
589 selection: &mut BasicSelection,
590 flags: MultiSelectFlags,
591 items_count: usize,
592 mut id_at_index: G,
593 mut render_item: F,
594 ) where
595 G: FnMut(usize) -> crate::Id,
596 F: FnMut(&Ui, usize, crate::Id, bool),
597 {
598 let selection_size_i32 = i32::try_from(selection.len()).unwrap_or(-1);
599
600 let ms_io_begin = unsafe {
601 sys::igBeginMultiSelect(flags.bits(), selection_size_i32, items_count as i32)
602 };
603
604 unsafe {
605 apply_multi_select_requests_basic(
606 ms_io_begin,
607 selection,
608 items_count,
609 &mut id_at_index,
610 );
611 }
612
613 for idx in 0..items_count {
614 unsafe {
615 sys::igSetNextItemSelectionUserData(idx as sys::ImGuiSelectionUserData);
616 }
617 let id = id_at_index(idx);
618 let is_selected = selection.contains(id);
619 render_item(self, idx, id, is_selected);
620 }
621
622 let ms_io_end = unsafe { sys::igEndMultiSelect() };
623 unsafe {
624 apply_multi_select_requests_basic(ms_io_end, selection, items_count, &mut id_at_index);
625 }
626 }
627}
628
629unsafe fn apply_multi_select_requests_basic<G>(
631 ms_io: *mut sys::ImGuiMultiSelectIO,
632 selection: &mut BasicSelection,
633 items_count: usize,
634 id_at_index: &mut G,
635) where
636 G: FnMut(usize) -> crate::Id,
637{
638 unsafe {
639 if ms_io.is_null() {
640 return;
641 }
642
643 let io_ref: &mut sys::ImGuiMultiSelectIO = &mut *ms_io;
644 let requests = &mut io_ref.Requests;
645 if requests.Data.is_null() || requests.Size <= 0 {
646 return;
647 }
648
649 let slice = std::slice::from_raw_parts_mut(requests.Data, requests.Size as usize);
650
651 for req in slice {
652 if req.Type == sys::ImGuiSelectionRequestType_SetAll {
653 for idx in 0..items_count {
654 let id = id_at_index(idx);
655 selection.set_selected(id, req.Selected);
656 }
657 } else if req.Type == sys::ImGuiSelectionRequestType_SetRange {
658 let first = req.RangeFirstItem as i32;
659 let last = req.RangeLastItem as i32;
660 if first < 0 || last < first {
661 continue;
662 }
663 let last_clamped = std::cmp::min(last as usize, items_count.saturating_sub(1));
664 for idx in first as usize..=last_clamped {
665 let id = id_at_index(idx);
666 selection.set_selected(id, req.Selected);
667 }
668 }
669 }
670 }
671}