use std::borrow::Cow;
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct Selection<T> {
items: Vec<T>,
primary_idx: Option<usize>,
}
impl<T: PartialEq + Clone> Selection<T> {
#[must_use]
pub fn new() -> Self {
Self {
items: Vec::new(),
primary_idx: None,
}
}
pub fn select(&mut self, item: T) {
self.items.clear();
self.items.push(item);
self.primary_idx = Some(0);
tracing::debug!(count = 1, "selection replaced");
}
pub fn toggle(&mut self, item: T) {
if let Some(pos) = self.items.iter().position(|i| *i == item) {
self.items.remove(pos);
self.primary_idx = self.adjust_primary_after_remove(pos);
} else {
self.items.push(item);
self.primary_idx = Some(self.items.len() - 1);
}
tracing::debug!(count = self.items.len(), "selection toggled");
}
pub fn add(&mut self, item: T) {
let pos = if let Some(existing) = self.items.iter().position(|i| *i == item) {
existing
} else {
self.items.push(item);
self.items.len() - 1
};
self.primary_idx = Some(pos);
tracing::debug!(count = self.items.len(), "selection add");
}
pub fn remove(&mut self, item: &T) {
if let Some(pos) = self.items.iter().position(|i| i == item) {
self.items.remove(pos);
self.primary_idx = self.adjust_primary_after_remove(pos);
tracing::debug!(count = self.items.len(), "selection remove");
}
}
fn adjust_primary_after_remove(&self, removed_pos: usize) -> Option<usize> {
let primary = self.primary_idx?;
if self.items.is_empty() {
return None;
}
if removed_pos == primary {
Some(self.items.len().saturating_sub(1))
} else if removed_pos < primary {
Some(primary - 1)
} else {
Some(primary)
}
}
pub fn clear(&mut self) {
self.items.clear();
self.primary_idx = None;
tracing::debug!("selection cleared");
}
#[must_use]
#[inline]
pub fn contains(&self, item: &T) -> bool {
self.items.contains(item)
}
#[must_use]
#[inline]
pub fn primary(&self) -> Option<&T> {
self.primary_idx.and_then(|i| self.items.get(i))
}
#[must_use]
#[inline]
pub fn items(&self) -> &[T] {
&self.items
}
#[must_use]
#[inline]
pub fn len(&self) -> usize {
self.items.len()
}
#[must_use]
#[inline]
pub fn is_empty(&self) -> bool {
self.items.is_empty()
}
#[must_use]
#[inline]
pub fn is_single(&self) -> bool {
self.items.len() == 1
}
pub fn select_many(&mut self, items: impl IntoIterator<Item = T>) {
self.items.clear();
for item in items {
if !self.items.contains(&item) {
self.items.push(item);
}
}
self.primary_idx = self.items.len().checked_sub(1);
tracing::debug!(count = self.items.len(), "selection set");
}
}
impl<T: PartialEq + Clone> Default for Selection<T> {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
pub struct PanelStates {
panels: HashMap<Cow<'static, str>, bool>,
}
impl PanelStates {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn register(&mut self, name: &'static str, visible: bool) {
self.panels.insert(Cow::Borrowed(name), visible);
tracing::debug!(panel = name, visible, "panel registered");
}
#[must_use]
#[inline]
pub fn is_visible(&self, name: &str) -> bool {
self.panels.get(name).copied().unwrap_or(false)
}
pub fn set_visible(&mut self, name: &str, visible: bool) {
if let Some(v) = self.panels.get_mut(name) {
*v = visible;
tracing::debug!(panel = name, visible, "panel visibility changed");
} else {
tracing::warn!(panel = name, "set_visible called on unregistered panel");
}
}
pub fn toggle(&mut self, name: &str) -> Option<bool> {
let v = self.panels.get_mut(name)?;
*v = !*v;
tracing::debug!(panel = name, visible = *v, "panel toggled");
Some(*v)
}
#[must_use]
#[inline]
pub fn panels(&self) -> &HashMap<Cow<'static, str>, bool> {
&self.panels
}
#[must_use]
#[inline]
pub fn len(&self) -> usize {
self.panels.len()
}
#[must_use]
#[inline]
pub fn is_empty(&self) -> bool {
self.panels.is_empty()
}
pub fn show_all(&mut self) {
for v in self.panels.values_mut() {
*v = true;
}
tracing::debug!("all panels shown");
}
pub fn hide_all(&mut self) {
for v in self.panels.values_mut() {
*v = false;
}
tracing::debug!("all panels hidden");
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn selection_empty() {
let sel: Selection<u64> = Selection::new();
assert!(sel.is_empty());
assert_eq!(sel.len(), 0);
assert!(sel.primary().is_none());
}
#[test]
fn selection_select_single() {
let mut sel = Selection::new();
sel.select(42u64);
assert_eq!(sel.len(), 1);
assert!(sel.is_single());
assert!(sel.contains(&42));
assert_eq!(sel.primary(), Some(&42));
}
#[test]
fn selection_select_replaces() {
let mut sel = Selection::new();
sel.select(1u64);
sel.select(2);
assert_eq!(sel.len(), 1);
assert!(!sel.contains(&1));
assert!(sel.contains(&2));
}
#[test]
fn selection_toggle_add_remove() {
let mut sel = Selection::new();
sel.toggle(1u64);
assert!(sel.contains(&1));
sel.toggle(2);
assert_eq!(sel.len(), 2);
sel.toggle(1); assert_eq!(sel.len(), 1);
assert!(!sel.contains(&1));
assert!(sel.contains(&2));
}
#[test]
fn selection_add_no_duplicates() {
let mut sel = Selection::new();
sel.add(1u64);
sel.add(1);
sel.add(2);
assert_eq!(sel.len(), 2);
}
#[test]
fn selection_remove() {
let mut sel = Selection::new();
sel.select_many([1u64, 2, 3]);
sel.remove(&2);
assert_eq!(sel.items(), &[1, 3]);
}
#[test]
fn selection_clear() {
let mut sel = Selection::new();
sel.select_many([1u64, 2, 3]);
sel.clear();
assert!(sel.is_empty());
assert!(sel.primary().is_none());
}
#[test]
fn selection_primary_tracks_last() {
let mut sel = Selection::new();
sel.select_many([1u64, 2, 3]);
assert_eq!(sel.primary(), Some(&3));
sel.toggle(2); assert_eq!(sel.primary(), Some(&3));
sel.toggle(5); assert_eq!(sel.primary(), Some(&5));
}
#[test]
fn selection_select_many_deduplicates() {
let mut sel = Selection::new();
sel.select_many([1u64, 2, 1, 3, 2]);
assert_eq!(sel.len(), 3);
assert_eq!(sel.items(), &[1, 2, 3]);
}
#[test]
fn selection_remove_preserves_primary() {
let mut sel = Selection::new();
sel.select_many([1u64, 2, 3, 4, 5]);
assert_eq!(sel.primary(), Some(&5));
sel.remove(&2);
assert_eq!(sel.items(), &[1, 3, 4, 5]);
assert_eq!(sel.primary(), Some(&5));
sel.remove(&5);
assert_eq!(sel.items(), &[1, 3, 4]);
assert_eq!(sel.primary(), Some(&4));
}
#[test]
fn selection_toggle_off_preserves_primary() {
let mut sel = Selection::new();
sel.select_many([1u64, 2, 3]);
assert_eq!(sel.primary(), Some(&3));
sel.toggle(1);
assert_eq!(sel.items(), &[2, 3]);
assert_eq!(sel.primary(), Some(&3)); }
#[test]
fn selection_remove_after_primary_no_shift() {
let mut sel = Selection::new();
sel.select(1u64);
sel.add(2);
sel.add(3);
assert_eq!(sel.primary(), Some(&3));
sel.add(1);
assert_eq!(sel.primary(), Some(&1));
sel.remove(&3);
assert_eq!(sel.primary(), Some(&1)); }
#[test]
fn selection_select_many_empty() {
let mut sel = Selection::new();
sel.select_many(Vec::<u64>::new());
assert!(sel.is_empty());
assert!(sel.primary().is_none());
}
#[test]
fn selection_default() {
let sel: Selection<u32> = Selection::default();
assert!(sel.is_empty());
}
#[test]
fn panel_states_register_and_query() {
let mut panels = PanelStates::new();
panels.register("inspector", true);
panels.register("hierarchy", true);
panels.register("viewport", false);
assert!(panels.is_visible("inspector"));
assert!(panels.is_visible("hierarchy"));
assert!(!panels.is_visible("viewport"));
assert!(!panels.is_visible("unknown")); assert_eq!(panels.len(), 3);
}
#[test]
fn panel_states_set_visible() {
let mut panels = PanelStates::new();
panels.register("inspector", true);
panels.set_visible("inspector", false);
assert!(!panels.is_visible("inspector"));
}
#[test]
fn panel_states_toggle() {
let mut panels = PanelStates::new();
panels.register("inspector", true);
let new_state = panels.toggle("inspector");
assert_eq!(new_state, Some(false));
assert!(!panels.is_visible("inspector"));
let new_state = panels.toggle("inspector");
assert_eq!(new_state, Some(true));
}
#[test]
fn panel_states_toggle_unregistered() {
let mut panels = PanelStates::new();
assert_eq!(panels.toggle("nope"), None);
}
#[test]
fn panel_states_show_hide_all() {
let mut panels = PanelStates::new();
panels.register("a", true);
panels.register("b", false);
panels.register("c", true);
panels.hide_all();
assert!(!panels.is_visible("a"));
assert!(!panels.is_visible("b"));
assert!(!panels.is_visible("c"));
panels.show_all();
assert!(panels.is_visible("a"));
assert!(panels.is_visible("b"));
assert!(panels.is_visible("c"));
}
#[test]
fn panel_states_serde_roundtrip() {
let mut panels = PanelStates::new();
panels.register("inspector", true);
panels.register("hierarchy", false);
let json = serde_json::to_string(&panels).unwrap();
let deserialized: PanelStates = serde_json::from_str(&json).unwrap();
assert!(deserialized.is_visible("inspector"));
assert!(!deserialized.is_visible("hierarchy"));
}
#[test]
fn panel_states_default_empty() {
let panels = PanelStates::default();
assert!(panels.is_empty());
}
}