use egui::{ScrollArea, Ui};
use std::{
fmt::Debug,
ops::{Deref, DerefMut},
vec::IntoIter,
};
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum ChangeType<T> {
Removed(T),
Reordered,
}
#[derive(Debug, Clone)]
pub struct EguiList<T> {
is_scrollable: bool,
is_editable: bool,
is_reorderable: bool,
had_list_update: Option<ChangeType<T>>,
backing: Vec<T>,
}
impl<T> Default for EguiList<T> {
fn default() -> Self {
Self {
is_scrollable: false,
is_editable: false,
is_reorderable: false,
backing: vec![],
had_list_update: None,
}
}
}
impl<T> EguiList<T> {
#[must_use]
pub fn had_update(&mut self) -> Option<ChangeType<T>> {
std::mem::take(&mut self.had_list_update)
}
#[must_use]
pub const fn is_scrollable(mut self, is_scrollable: bool) -> Self {
self.is_scrollable = is_scrollable;
self
}
#[must_use]
pub const fn is_editable(mut self, is_editable: bool) -> Self {
self.is_editable = is_editable;
self
}
#[must_use]
pub const fn is_reorderable(mut self, is_reorderable: bool) -> Self {
self.is_reorderable = is_reorderable;
self
}
fn display_inner(&mut self, ui: &mut Ui, label: impl Fn(&T, usize) -> String) {
if self.backing.is_empty() {
return;
}
let mut need_to_remove = None; let mut up = None; let mut down = None;
for (i, arg) in self.backing.iter().enumerate() {
ui.horizontal(|ui| {
ui.label(label(arg, i));
if self.had_list_update.is_none() {
if self.is_editable && ui.button("Remove?").clicked() {
need_to_remove = Some(i);
}
if self.is_reorderable {
if ui.button("Up?").clicked() {
up = Some(i);
}
if ui.button("Down?").clicked() {
down = Some(i);
}
}
}
});
}
let len_minus_one = self.backing.len() - 1;
if let Some(need_to_remove) = need_to_remove {
self.had_list_update = Some(ChangeType::Removed(self.backing.remove(need_to_remove)));
} else if let Some(up) = up {
self.had_list_update = Some(ChangeType::Reordered);
if up > 0 {
self.backing.swap(up, up - 1);
} else {
self.backing.swap(0, len_minus_one);
}
} else if let Some(down) = down {
self.had_list_update = Some(ChangeType::Reordered);
if down < len_minus_one {
self.backing.swap(down, down + 1);
} else {
self.backing.swap(len_minus_one, 0);
}
}
}
pub fn display(&mut self, ui: &mut Ui, label: impl Fn(&T, usize) -> String) {
if self.is_scrollable {
ScrollArea::vertical().max_height(300.0).show(ui, |ui| {
self.display_inner(ui, label);
});
} else {
self.display_inner(ui, label);
}
}
}
impl<T> Deref for EguiList<T> {
type Target = Vec<T>;
fn deref(&self) -> &Self::Target {
&self.backing
}
}
impl<T> DerefMut for EguiList<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.backing
}
}
impl<T> From<Vec<T>> for EguiList<T> {
fn from(value: Vec<T>) -> Self {
Self {
backing: value,
..Default::default()
}
}
}
impl<T> IntoIterator for EguiList<T> {
type Item = T;
type IntoIter = IntoIter<T>;
fn into_iter(self) -> Self::IntoIter {
self.backing.into_iter()
}
}
impl<T: Clone> EguiList<T> {
#[must_use]
pub fn backing_vec(&self) -> Vec<T> {
self.backing.clone()
}
}
impl<T> AsRef<[T]> for EguiList<T> {
fn as_ref(&self) -> &[T] {
&self.backing
}
}