use std::collections::HashSet;
use super::types::SourceType;
#[derive(Clone, Debug, Default)]
pub struct ObjectTreeState {
pub selected_ids: HashSet<usize>,
pub last_selected_id: Option<usize>,
pub selection_anchor: Option<usize>,
pub expanded_groups: HashSet<String>,
pub expanded_items: HashSet<usize>,
pub data_window_expanded: bool,
pub filter_text: String,
pub filter_type: Option<SourceType>,
pub context_menu_open: bool,
pub context_menu_pos: egui::Pos2,
pub context_menu_target: Option<usize>,
pub dragging_id: Option<usize>,
pub drag_start_pos: Option<egui::Pos2>,
pub drop_target: Option<usize>,
pub renaming_id: Option<usize>,
pub rename_text: String,
pub hovered_id: Option<usize>,
}
impl ObjectTreeState {
pub fn new() -> Self {
let mut state = Self::default();
state.expanded_groups.insert("Indicators".to_string());
state.expanded_groups.insert("Drawings".to_string());
state.expanded_groups.insert("Templates".to_string());
state.data_window_expanded = true;
state
}
pub fn select(&mut self, id: usize) {
self.selected_ids.clear();
self.selected_ids.insert(id);
self.last_selected_id = Some(id);
self.selection_anchor = Some(id);
}
pub fn toggle_selection(&mut self, id: usize) {
if self.selected_ids.contains(&id) {
self.selected_ids.remove(&id);
} else {
self.selected_ids.insert(id);
}
self.last_selected_id = Some(id);
}
pub fn range_select(&mut self, id: usize, ordered_ids: &[usize]) {
if let Some(anchor) = self.selection_anchor {
let anchor_idx = ordered_ids.iter().position(|&x| x == anchor);
let target_idx = ordered_ids.iter().position(|&x| x == id);
if let (Some(start), Some(end)) = (anchor_idx, target_idx) {
let (min, max) = if start < end {
(start, end)
} else {
(end, start)
};
self.selected_ids.clear();
for &item_id in &ordered_ids[min..=max] {
self.selected_ids.insert(item_id);
}
}
} else {
self.select(id);
}
self.last_selected_id = Some(id);
}
pub fn select_all(&mut self, all_ids: &[usize]) {
self.selected_ids = all_ids.iter().copied().collect();
}
pub fn clear_selection(&mut self) {
self.selected_ids.clear();
self.last_selected_id = None;
}
pub fn is_selected(&self, id: usize) -> bool {
self.selected_ids.contains(&id)
}
pub fn selection_count(&self) -> usize {
self.selected_ids.len()
}
pub fn toggle_group(&mut self, group: &str) {
if self.expanded_groups.contains(group) {
self.expanded_groups.remove(group);
} else {
self.expanded_groups.insert(group.to_string());
}
}
pub fn is_group_expanded(&self, group: &str) -> bool {
self.expanded_groups.contains(group)
}
pub fn toggle_item(&mut self, id: usize) {
if self.expanded_items.contains(&id) {
self.expanded_items.remove(&id);
} else {
self.expanded_items.insert(id);
}
}
pub fn is_item_expanded(&self, id: usize) -> bool {
self.expanded_items.contains(&id)
}
pub fn expand_all_groups(&mut self) {
self.expanded_groups.insert("Indicators".to_string());
self.expanded_groups.insert("Drawings".to_string());
self.expanded_groups.insert("Templates".to_string());
}
pub fn collapse_all_groups(&mut self) {
self.expanded_groups.clear();
}
pub fn set_filter(&mut self, text: impl Into<String>) {
self.filter_text = text.into();
}
pub fn clear_filter(&mut self) {
self.filter_text.clear();
self.filter_type = None;
}
pub fn has_filter(&self) -> bool {
!self.filter_text.is_empty() || self.filter_type.is_some()
}
pub fn open_context_menu(&mut self, pos: egui::Pos2, target: Option<usize>) {
self.context_menu_open = true;
self.context_menu_pos = pos;
self.context_menu_target = target;
}
pub fn close_context_menu(&mut self) {
self.context_menu_open = false;
self.context_menu_target = None;
}
pub fn start_rename(&mut self, id: usize, current_name: &str) {
self.renaming_id = Some(id);
self.rename_text = current_name.to_string();
}
pub fn cancel_rename(&mut self) {
self.renaming_id = None;
self.rename_text.clear();
}
pub fn finish_rename(&mut self) -> Option<(usize, String)> {
if let Some(id) = self.renaming_id.take() {
let name = std::mem::take(&mut self.rename_text);
if !name.is_empty() {
return Some((id, name));
}
}
None
}
pub fn start_drag(&mut self, id: usize, pos: egui::Pos2) {
self.dragging_id = Some(id);
self.drag_start_pos = Some(pos);
}
pub fn set_drop_target(&mut self, target: Option<usize>) {
self.drop_target = target;
}
pub fn end_drag(&mut self) -> Option<(usize, usize)> {
let result = match (self.dragging_id, self.drop_target) {
(Some(src), Some(dst)) if src != dst => Some((src, dst)),
_ => None,
};
self.dragging_id = None;
self.drag_start_pos = None;
self.drop_target = None;
result
}
pub fn is_dragging(&self) -> bool {
self.dragging_id.is_some()
}
pub fn sync_selected_drawing(&mut self, selected_drawing: Option<usize>) {
if let Some(drawing_id) = selected_drawing
&& !self.selected_ids.contains(&drawing_id)
{
self.selected_ids.clear();
self.selected_ids.insert(drawing_id);
self.last_selected_id = Some(drawing_id);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_selection() {
let mut state = ObjectTreeState::new();
state.select(1);
assert!(state.is_selected(1));
assert_eq!(state.selection_count(), 1);
state.toggle_selection(2);
assert!(state.is_selected(1));
assert!(state.is_selected(2));
assert_eq!(state.selection_count(), 2);
state.toggle_selection(1);
assert!(!state.is_selected(1));
assert!(state.is_selected(2));
}
#[test]
fn test_range_select() {
let mut state = ObjectTreeState::new();
let ids = vec![1, 2, 3, 4, 5];
state.select(2);
state.range_select(4, &ids);
assert!(state.is_selected(2));
assert!(state.is_selected(3));
assert!(state.is_selected(4));
assert!(!state.is_selected(1));
assert!(!state.is_selected(5));
}
}