use std::collections::HashMap;
use bevy::prelude::*;
use bevy_egui::egui;
use crate::model::StateMachineGraph;
use super::canvas::CanvasTransform;
use crate::types::EntityId;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StateKind { Leaf, Sequence, Parallel }
#[derive(Debug, Clone)]
pub struct StateView {
pub id: EntityId,
pub rect: egui::Rect,
pub label: String,
pub kind: StateKind,
}
#[derive(Debug, Clone)]
pub struct EdgeView {
pub id: EntityId,
pub source: EntityId,
pub target: EntityId,
pub label: String,
pub rect: egui::Rect,
pub pill_parent: Option<EntityId>,
}
#[derive(Debug, Default, Clone)]
pub struct LayoutTree {
pub parent_of: HashMap<EntityId, Option<EntityId>>,
pub children_of: HashMap<EntityId, Vec<EntityId>>,
pub containers: std::collections::HashSet<EntityId>,
}
#[derive(Debug, Default, Clone)]
pub struct ViewScene {
pub states: HashMap<EntityId, StateView>,
pub edges: HashMap<EntityId, EdgeView>,
pub node_rects: HashMap<EntityId, egui::Rect>,
pub tree: LayoutTree,
pub draw_order: Vec<EntityId>,
}
#[derive(Debug, Default)]
pub struct GraphDoc {
pub graph: Option<StateMachineGraph>,
pub transform: CanvasTransform,
pub dragging: Option<EntityId>,
pub drag_anchor_world: Option<egui::Vec2>,
pub scene: ViewScene,
pub initial_substate_of: HashMap<EntityId, EntityId>,
pub is_initial_child: std::collections::HashSet<EntityId>,
pub label_px_cache: std::sync::RwLock<HashMap<(String, u32), egui::Vec2>>,
pub node_flash: std::collections::HashMap<EntityId, f32>,
pub edge_flash: std::collections::HashMap<EntityId, f32>,
pub node_fade: std::collections::HashMap<EntityId, f32>,
}
impl GraphDoc {
pub fn cached_label_size_screen(&self, label: &str, zoom: f32, painter: &egui::Painter) -> egui::Vec2 {
let font_px = (14.0 * zoom).clamp(6.0, 64.0);
let key = (label.to_string(), font_px.round() as u32);
if let Some(sz) = self.label_px_cache.read().ok().and_then(|m| m.get(&key).cloned()) { return sz; }
let font_id = egui::FontId::proportional(font_px);
let galley = painter.layout_no_wrap(label.to_string(), font_id, egui::Color32::WHITE);
let size = galley.size();
if let Ok(mut map) = self.label_px_cache.write() { map.insert(key, size); }
size
}
pub fn cached_label_size_world(&self, label: &str, zoom: f32, painter: &egui::Painter) -> egui::Vec2 {
let size_s = self.cached_label_size_screen(label, zoom, painter);
egui::vec2(size_s.x / zoom, size_s.y / zoom)
}
pub fn get_rect(&self, id: &EntityId) -> Option<egui::Rect> {
self.scene.node_rects.get(id).copied()
}
pub fn set_rect(&mut self, id: &EntityId, rect: egui::Rect) {
if let Some(r) = self.scene.node_rects.get_mut(id) { *r = rect; }
if let Some(sv) = self.scene.states.get_mut(id) { sv.rect = rect; }
if let Some(ev) = self.scene.edges.get_mut(id) { ev.rect = rect; }
}
pub fn parent_of(&self, id: &EntityId) -> Option<EntityId> {
self.scene.tree.parent_of.get(id).and_then(|p| *p)
}
pub fn flash_edge(&mut self, id: EntityId) {
self.edge_flash.insert(id, 1.0);
}
pub fn tick_highlights(&mut self, decay: f32) -> bool {
let eps = 0.01;
let mut any = false;
self.node_flash.retain(|_, v| {
*v *= decay;
if *v > eps { any = true; true } else { false }
});
self.edge_flash.retain(|_, v| {
*v *= decay;
if *v > eps { any = true; true } else { false }
});
self.node_fade.retain(|_, v| {
*v *= decay;
if *v > eps { any = true; true } else { false }
});
any
}
}