#![allow(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::as_conversions
)]
use crate::Ui;
use crate::sys;
use std::collections::HashSet;
bitflags::bitflags! {
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct MultiSelectFlags: i32 {
const NONE = sys::ImGuiMultiSelectFlags_None as i32;
const SINGLE_SELECT = sys::ImGuiMultiSelectFlags_SingleSelect as i32;
const NO_SELECT_ALL = sys::ImGuiMultiSelectFlags_NoSelectAll as i32;
const NO_RANGE_SELECT = sys::ImGuiMultiSelectFlags_NoRangeSelect as i32;
const NO_AUTO_SELECT = sys::ImGuiMultiSelectFlags_NoAutoSelect as i32;
const NO_AUTO_CLEAR = sys::ImGuiMultiSelectFlags_NoAutoClear as i32;
const NO_AUTO_CLEAR_ON_RESELECT =
sys::ImGuiMultiSelectFlags_NoAutoClearOnReselect as i32;
const BOX_SELECT_NO_SCROLL = sys::ImGuiMultiSelectFlags_BoxSelectNoScroll as i32;
const CLEAR_ON_ESCAPE = sys::ImGuiMultiSelectFlags_ClearOnEscape as i32;
const CLEAR_ON_CLICK_VOID = sys::ImGuiMultiSelectFlags_ClearOnClickVoid as i32;
const NO_SELECT_ON_RIGHT_CLICK =
sys::ImGuiMultiSelectFlags_NoSelectOnRightClick as i32;
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum MultiSelectBoxSelect {
OneDimensional,
TwoDimensional,
}
impl MultiSelectBoxSelect {
#[inline]
const fn raw(self) -> i32 {
match self {
Self::OneDimensional => sys::ImGuiMultiSelectFlags_BoxSelect1d as i32,
Self::TwoDimensional => sys::ImGuiMultiSelectFlags_BoxSelect2d as i32,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum MultiSelectClickPolicy {
Auto,
ClickAlways,
ClickRelease,
}
impl MultiSelectClickPolicy {
#[inline]
const fn raw(self) -> i32 {
match self {
Self::Auto => sys::ImGuiMultiSelectFlags_SelectOnAuto as i32,
Self::ClickAlways => sys::ImGuiMultiSelectFlags_SelectOnClickAlways as i32,
Self::ClickRelease => sys::ImGuiMultiSelectFlags_SelectOnClickRelease as i32,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum MultiSelectScopeKind {
Window,
WindowWithNavWrapX,
Rect,
}
impl MultiSelectScopeKind {
#[inline]
const fn raw(self) -> i32 {
match self {
Self::Window => sys::ImGuiMultiSelectFlags_ScopeWindow as i32,
Self::WindowWithNavWrapX => {
(sys::ImGuiMultiSelectFlags_ScopeWindow | sys::ImGuiMultiSelectFlags_NavWrapX)
as i32
}
Self::Rect => sys::ImGuiMultiSelectFlags_ScopeRect as i32,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct MultiSelectOptions {
pub flags: MultiSelectFlags,
pub click_policy: Option<MultiSelectClickPolicy>,
pub box_select: Option<MultiSelectBoxSelect>,
pub scope: Option<MultiSelectScopeKind>,
}
impl MultiSelectOptions {
pub const fn new() -> Self {
Self {
flags: MultiSelectFlags::NONE,
click_policy: None,
box_select: None,
scope: None,
}
}
pub fn flags(mut self, flags: MultiSelectFlags) -> Self {
self.flags = flags;
self
}
pub fn click_policy(mut self, policy: MultiSelectClickPolicy) -> Self {
self.click_policy = Some(policy);
self
}
pub fn box_select(mut self, mode: MultiSelectBoxSelect) -> Self {
self.box_select = Some(mode);
self
}
pub fn scope(mut self, scope: MultiSelectScopeKind) -> Self {
self.scope = Some(scope);
self
}
pub fn bits(self) -> i32 {
self.raw()
}
#[inline]
pub(crate) fn raw(self) -> i32 {
self.flags.bits()
| self.click_policy.map_or(0, MultiSelectClickPolicy::raw)
| self.box_select.map_or(0, MultiSelectBoxSelect::raw)
| self.scope.map_or(0, MultiSelectScopeKind::raw)
}
}
impl Default for MultiSelectOptions {
fn default() -> Self {
Self::new()
}
}
impl From<MultiSelectFlags> for MultiSelectOptions {
fn from(flags: MultiSelectFlags) -> Self {
Self::new().flags(flags)
}
}
#[derive(Debug)]
pub struct BasicSelection {
raw: *mut sys::ImGuiSelectionBasicStorage,
}
impl BasicSelection {
pub fn new() -> Self {
unsafe {
let ptr = sys::ImGuiSelectionBasicStorage_ImGuiSelectionBasicStorage();
if ptr.is_null() {
panic!("ImGuiSelectionBasicStorage_ImGuiSelectionBasicStorage() returned null");
}
Self { raw: ptr }
}
}
pub fn len(&self) -> usize {
unsafe {
let size = (*self.raw).Size;
if size <= 0 { 0 } else { size as usize }
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn clear(&mut self) {
unsafe {
sys::ImGuiSelectionBasicStorage_Clear(self.raw);
}
}
pub fn contains(&self, id: crate::Id) -> bool {
unsafe { sys::ImGuiSelectionBasicStorage_Contains(self.raw, id.raw()) }
}
pub fn set_selected(&mut self, id: crate::Id, selected: bool) {
unsafe {
sys::ImGuiSelectionBasicStorage_SetItemSelected(self.raw, id.raw(), selected);
}
}
pub fn iter(&self) -> BasicSelectionIter<'_> {
BasicSelectionIter {
storage: self,
it: std::ptr::null_mut(),
}
}
pub(crate) fn as_raw(&self) -> *mut sys::ImGuiSelectionBasicStorage {
self.raw
}
}
impl Default for BasicSelection {
fn default() -> Self {
Self::new()
}
}
impl Drop for BasicSelection {
fn drop(&mut self) {
unsafe {
if !self.raw.is_null() {
sys::ImGuiSelectionBasicStorage_destroy(self.raw);
self.raw = std::ptr::null_mut();
}
}
}
}
pub struct BasicSelectionIter<'a> {
storage: &'a BasicSelection,
it: *mut std::os::raw::c_void,
}
impl<'a> Iterator for BasicSelectionIter<'a> {
type Item = crate::Id;
fn next(&mut self) -> Option<Self::Item> {
unsafe {
let mut out_id: sys::ImGuiID = 0;
let has_next = sys::ImGuiSelectionBasicStorage_GetNextSelectedItem(
self.storage.as_raw(),
&mut self.it,
&mut out_id,
);
if has_next {
Some(crate::Id::from(out_id))
} else {
None
}
}
}
}
pub trait MultiSelectIndexStorage {
fn len(&self) -> usize;
fn is_empty(&self) -> bool {
self.len() == 0
}
fn is_selected(&self, index: usize) -> bool;
fn set_selected(&mut self, index: usize, selected: bool);
fn selected_count_hint(&self) -> Option<usize> {
None
}
}
impl MultiSelectIndexStorage for Vec<bool> {
fn len(&self) -> usize {
self.len()
}
fn is_selected(&self, index: usize) -> bool {
self.get(index).copied().unwrap_or(false)
}
fn set_selected(&mut self, index: usize, selected: bool) {
if index < self.len() {
self[index] = selected;
}
}
fn selected_count_hint(&self) -> Option<usize> {
Some(self.iter().filter(|&&b| b).count())
}
}
impl MultiSelectIndexStorage for &mut [bool] {
fn len(&self) -> usize {
(**self).len()
}
fn is_selected(&self, index: usize) -> bool {
self.get(index).copied().unwrap_or(false)
}
fn set_selected(&mut self, index: usize, selected: bool) {
if index < self.len() {
self[index] = selected;
}
}
fn selected_count_hint(&self) -> Option<usize> {
Some(self.iter().filter(|&&b| b).count())
}
}
pub struct KeySetSelection<'a, K>
where
K: Eq + std::hash::Hash + Copy,
{
keys: &'a [K],
selected: &'a mut HashSet<K>,
}
impl<'a, K> KeySetSelection<'a, K>
where
K: Eq + std::hash::Hash + Copy,
{
pub fn new(keys: &'a [K], selected: &'a mut HashSet<K>) -> Self {
Self { keys, selected }
}
}
impl<'a, K> MultiSelectIndexStorage for KeySetSelection<'a, K>
where
K: Eq + std::hash::Hash + Copy,
{
fn len(&self) -> usize {
self.keys.len()
}
fn is_selected(&self, index: usize) -> bool {
self.keys
.get(index)
.map(|k| self.selected.contains(k))
.unwrap_or(false)
}
fn set_selected(&mut self, index: usize, selected: bool) {
if let Some(&key) = self.keys.get(index) {
if selected {
self.selected.insert(key);
} else {
self.selected.remove(&key);
}
}
}
fn selected_count_hint(&self) -> Option<usize> {
Some(self.selected.len())
}
}
unsafe fn apply_multi_select_requests_indexed<S: MultiSelectIndexStorage>(
ms_io: *mut sys::ImGuiMultiSelectIO,
storage: &mut S,
) {
unsafe {
if ms_io.is_null() {
return;
}
let io_ref: &mut sys::ImGuiMultiSelectIO = &mut *ms_io;
let items_count = usize::try_from(io_ref.ItemsCount).unwrap_or(0);
let requests = &mut io_ref.Requests;
if requests.Data.is_null() || requests.Size <= 0 {
return;
}
let len = match usize::try_from(requests.Size) {
Ok(len) => len,
Err(_) => return,
};
let slice = std::slice::from_raw_parts_mut(requests.Data, len);
for req in slice {
if req.Type == sys::ImGuiSelectionRequestType_SetAll {
for idx in 0..items_count {
storage.set_selected(idx, req.Selected);
}
} else if req.Type == sys::ImGuiSelectionRequestType_SetRange {
let first = req.RangeFirstItem as i32;
let last = req.RangeLastItem as i32;
if first < 0 || last < first {
continue;
}
let last_clamped = std::cmp::min(last as usize, items_count.saturating_sub(1));
for idx in first as usize..=last_clamped {
storage.set_selected(idx, req.Selected);
}
}
}
}
}
pub struct MultiSelectScope<'ui> {
ms_io_begin: *mut sys::ImGuiMultiSelectIO,
items_count: i32,
_marker: std::marker::PhantomData<&'ui Ui>,
}
impl<'ui> MultiSelectScope<'ui> {
fn new(
flags: impl Into<MultiSelectOptions>,
selection_size: Option<i32>,
items_count: usize,
) -> Self {
let options = flags.into();
let selection_size_i32 = selection_size.unwrap_or(-1);
let items_count_i32 = i32::try_from(items_count).unwrap_or(i32::MAX);
let ms_io_begin =
unsafe { sys::igBeginMultiSelect(options.raw(), selection_size_i32, items_count_i32) };
Self {
ms_io_begin,
items_count: items_count_i32,
_marker: std::marker::PhantomData,
}
}
pub fn begin_io(&self) -> &sys::ImGuiMultiSelectIO {
unsafe { &*self.ms_io_begin }
}
pub fn begin_io_mut(&mut self) -> &mut sys::ImGuiMultiSelectIO {
unsafe { &mut *self.ms_io_begin }
}
pub fn apply_begin_requests_indexed<S: MultiSelectIndexStorage>(&mut self, storage: &mut S) {
unsafe {
apply_multi_select_requests_indexed(self.ms_io_begin, storage);
}
}
pub fn end(self) -> MultiSelectEnd<'ui> {
let ms_io_end = unsafe { sys::igEndMultiSelect() };
MultiSelectEnd {
ms_io_end,
items_count: self.items_count,
_marker: std::marker::PhantomData,
}
}
}
pub struct MultiSelectEnd<'ui> {
ms_io_end: *mut sys::ImGuiMultiSelectIO,
items_count: i32,
_marker: std::marker::PhantomData<&'ui Ui>,
}
impl<'ui> MultiSelectEnd<'ui> {
pub fn io(&self) -> &sys::ImGuiMultiSelectIO {
unsafe { &*self.ms_io_end }
}
pub fn io_mut(&mut self) -> &mut sys::ImGuiMultiSelectIO {
unsafe { &mut *self.ms_io_end }
}
pub fn apply_requests_indexed<S: MultiSelectIndexStorage>(&mut self, storage: &mut S) {
unsafe {
apply_multi_select_requests_indexed(self.ms_io_end, storage);
}
}
pub fn apply_requests_basic<G>(&mut self, selection: &mut BasicSelection, mut id_at_index: G)
where
G: FnMut(usize) -> crate::Id,
{
unsafe {
apply_multi_select_requests_basic(
self.ms_io_end,
selection,
self.items_count as usize,
&mut id_at_index,
);
}
}
}
impl Ui {
pub fn begin_multi_select_raw(
&self,
flags: impl Into<MultiSelectOptions>,
selection_size: Option<i32>,
items_count: usize,
) -> MultiSelectScope<'_> {
MultiSelectScope::new(flags, selection_size, items_count)
}
pub fn multi_select_indexed<S, F>(
&self,
storage: &mut S,
flags: impl Into<MultiSelectOptions>,
mut render_item: F,
) where
S: MultiSelectIndexStorage,
F: FnMut(&Ui, usize, bool),
{
let options = flags.into();
let items_count = storage.len();
let selection_size_i32 = storage
.selected_count_hint()
.and_then(|n| i32::try_from(n).ok())
.unwrap_or(-1);
let ms_io_begin = unsafe {
sys::igBeginMultiSelect(options.raw(), selection_size_i32, items_count as i32)
};
unsafe {
apply_multi_select_requests_indexed(ms_io_begin, storage);
}
for idx in 0..items_count {
unsafe {
sys::igSetNextItemSelectionUserData(idx as sys::ImGuiSelectionUserData);
}
let is_selected = storage.is_selected(idx);
render_item(self, idx, is_selected);
}
let ms_io_end = unsafe { sys::igEndMultiSelect() };
unsafe {
apply_multi_select_requests_indexed(ms_io_end, storage);
}
}
pub fn table_multi_select_indexed<S, F>(
&self,
storage: &mut S,
flags: impl Into<MultiSelectOptions>,
mut build_row: F,
) where
S: MultiSelectIndexStorage,
F: FnMut(&Ui, usize, bool),
{
let options = flags.into();
let row_count = storage.len();
let selection_size_i32 = storage
.selected_count_hint()
.and_then(|n| i32::try_from(n).ok())
.unwrap_or(-1);
let ms_io_begin =
unsafe { sys::igBeginMultiSelect(options.raw(), selection_size_i32, row_count as i32) };
unsafe {
apply_multi_select_requests_indexed(ms_io_begin, storage);
}
for row in 0..row_count {
unsafe {
sys::igSetNextItemSelectionUserData(row as sys::ImGuiSelectionUserData);
}
self.table_next_row();
self.table_next_column();
let is_selected = storage.is_selected(row);
build_row(self, row, is_selected);
}
let ms_io_end = unsafe { sys::igEndMultiSelect() };
unsafe {
apply_multi_select_requests_indexed(ms_io_end, storage);
}
}
pub fn multi_select_basic<G, F>(
&self,
selection: &mut BasicSelection,
flags: impl Into<MultiSelectOptions>,
items_count: usize,
mut id_at_index: G,
mut render_item: F,
) where
G: FnMut(usize) -> crate::Id,
F: FnMut(&Ui, usize, crate::Id, bool),
{
let options = flags.into();
let selection_size_i32 = i32::try_from(selection.len()).unwrap_or(-1);
let ms_io_begin = unsafe {
sys::igBeginMultiSelect(options.raw(), selection_size_i32, items_count as i32)
};
unsafe {
apply_multi_select_requests_basic(
ms_io_begin,
selection,
items_count,
&mut id_at_index,
);
}
for idx in 0..items_count {
unsafe {
sys::igSetNextItemSelectionUserData(idx as sys::ImGuiSelectionUserData);
}
let id = id_at_index(idx);
let is_selected = selection.contains(id);
render_item(self, idx, id, is_selected);
}
let ms_io_end = unsafe { sys::igEndMultiSelect() };
unsafe {
apply_multi_select_requests_basic(ms_io_end, selection, items_count, &mut id_at_index);
}
}
}
unsafe fn apply_multi_select_requests_basic<G>(
ms_io: *mut sys::ImGuiMultiSelectIO,
selection: &mut BasicSelection,
items_count: usize,
id_at_index: &mut G,
) where
G: FnMut(usize) -> crate::Id,
{
unsafe {
if ms_io.is_null() {
return;
}
let io_ref: &mut sys::ImGuiMultiSelectIO = &mut *ms_io;
let requests = &mut io_ref.Requests;
if requests.Data.is_null() || requests.Size <= 0 {
return;
}
let len = match usize::try_from(requests.Size) {
Ok(len) => len,
Err(_) => return,
};
let slice = std::slice::from_raw_parts_mut(requests.Data, len);
for req in slice {
if req.Type == sys::ImGuiSelectionRequestType_SetAll {
for idx in 0..items_count {
let id = id_at_index(idx);
selection.set_selected(id, req.Selected);
}
} else if req.Type == sys::ImGuiSelectionRequestType_SetRange {
let first = req.RangeFirstItem as i32;
let last = req.RangeLastItem as i32;
if first < 0 || last < first {
continue;
}
let last_clamped = std::cmp::min(last as usize, items_count.saturating_sub(1));
for idx in first as usize..=last_clamped {
let id = id_at_index(idx);
selection.set_selected(id, req.Selected);
}
}
}
}
}