use crate::graph::VowlGraph;
use crate::{Result, VowlError};
use std::collections::HashSet;
#[derive(Debug, Clone, Default)]
pub struct PinManager {
pinned_nodes: HashSet<String>,
enabled: bool,
}
impl PinManager {
pub fn new() -> Self {
Self {
pinned_nodes: HashSet::new(),
enabled: true,
}
}
pub fn pin_node(&mut self, graph: &mut VowlGraph, node_id: &str) -> Result<()> {
if !self.enabled {
return Err(VowlError::InteractionError(
"Pinning is disabled".to_string(),
));
}
let node = graph
.get_node_mut(node_id)
.ok_or_else(|| VowlError::GraphError(format!("Node '{}' not found", node_id)))?;
node.visual.fixed = true;
self.pinned_nodes.insert(node_id.to_string());
#[cfg(target_arch = "wasm32")]
{
web_sys::console::log_1(
&format!(
"📌 Pinned node '{}' at ({:.1}, {:.1})",
node_id, node.visual.x, node.visual.y
)
.into(),
);
}
Ok(())
}
pub fn pin_node_at(
&mut self,
graph: &mut VowlGraph,
node_id: &str,
x: f64,
y: f64,
) -> Result<()> {
if !self.enabled {
return Err(VowlError::InteractionError(
"Pinning is disabled".to_string(),
));
}
let node = graph
.get_node_mut(node_id)
.ok_or_else(|| VowlError::GraphError(format!("Node '{}' not found", node_id)))?;
node.visual.x = x;
node.visual.y = y;
node.visual.vx = 0.0;
node.visual.vy = 0.0;
node.visual.fixed = true;
self.pinned_nodes.insert(node_id.to_string());
#[cfg(target_arch = "wasm32")]
{
web_sys::console::log_1(
&format!("📌 Pinned node '{}' at ({:.1}, {:.1})", node_id, x, y).into(),
);
}
Ok(())
}
pub fn unpin_node(&mut self, graph: &mut VowlGraph, node_id: &str) -> Result<()> {
let node = graph
.get_node_mut(node_id)
.ok_or_else(|| VowlError::GraphError(format!("Node '{}' not found", node_id)))?;
node.visual.fixed = false;
self.pinned_nodes.remove(node_id);
#[cfg(target_arch = "wasm32")]
{
web_sys::console::log_1(&format!("📍 Unpinned node '{}'", node_id).into());
}
Ok(())
}
pub fn is_pinned(&self, node_id: &str) -> bool {
self.pinned_nodes.contains(node_id)
}
pub fn reset(&mut self, graph: &mut VowlGraph) {
for node_id in self.pinned_nodes.drain() {
if let Some(node) = graph.get_node_mut(&node_id) {
node.visual.fixed = false;
}
}
#[cfg(target_arch = "wasm32")]
{
web_sys::console::log_1(&"🔓 Reset all pinned nodes".into());
}
}
pub fn set_enabled(&mut self, enabled: bool) {
self.enabled = enabled;
}
pub fn is_enabled(&self) -> bool {
self.enabled
}
pub fn pinned_count(&self) -> usize {
self.pinned_nodes.len()
}
pub fn pinned_nodes(&self) -> Vec<String> {
self.pinned_nodes.iter().cloned().collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::graph::{node::NodeBuilder, NodeType};
fn create_test_graph() -> VowlGraph {
let mut graph = VowlGraph::new();
for i in 1..=3 {
let node = NodeBuilder::new(format!("node{}", i))
.label(format!("Node {}", i))
.node_type(NodeType::Class)
.position(i as f64 * 10.0, i as f64 * 20.0)
.build();
graph.add_node(node).unwrap();
}
graph
}
#[test]
fn test_pin_manager_creation() {
let manager = PinManager::new();
assert!(manager.is_enabled());
assert_eq!(manager.pinned_count(), 0);
}
#[test]
fn test_pin_node() {
let mut graph = create_test_graph();
let mut manager = PinManager::new();
let result = manager.pin_node(&mut graph, "node1");
assert!(result.is_ok());
assert!(manager.is_pinned("node1"));
assert_eq!(manager.pinned_count(), 1);
let node = graph.get_node("node1").unwrap();
assert!(node.visual.fixed);
}
#[test]
fn test_pin_node_at_position() {
let mut graph = create_test_graph();
let mut manager = PinManager::new();
let result = manager.pin_node_at(&mut graph, "node1", 100.0, 200.0);
assert!(result.is_ok());
assert!(manager.is_pinned("node1"));
let node = graph.get_node("node1").unwrap();
assert_eq!(node.visual.x, 100.0);
assert_eq!(node.visual.y, 200.0);
assert!(node.visual.fixed);
assert_eq!(node.visual.vx, 0.0);
assert_eq!(node.visual.vy, 0.0);
}
#[test]
fn test_unpin_node() {
let mut graph = create_test_graph();
let mut manager = PinManager::new();
manager.pin_node(&mut graph, "node1").unwrap();
assert!(manager.is_pinned("node1"));
let result = manager.unpin_node(&mut graph, "node1");
assert!(result.is_ok());
assert!(!manager.is_pinned("node1"));
assert_eq!(manager.pinned_count(), 0);
let node = graph.get_node("node1").unwrap();
assert!(!node.visual.fixed);
}
#[test]
fn test_pin_nonexistent_node() {
let mut graph = create_test_graph();
let mut manager = PinManager::new();
let result = manager.pin_node(&mut graph, "invalid");
assert!(result.is_err());
}
#[test]
fn test_pin_multiple_nodes() {
let mut graph = create_test_graph();
let mut manager = PinManager::new();
manager.pin_node(&mut graph, "node1").unwrap();
manager.pin_node(&mut graph, "node2").unwrap();
manager.pin_node(&mut graph, "node3").unwrap();
assert_eq!(manager.pinned_count(), 3);
assert!(manager.is_pinned("node1"));
assert!(manager.is_pinned("node2"));
assert!(manager.is_pinned("node3"));
}
#[test]
fn test_reset_pins() {
let mut graph = create_test_graph();
let mut manager = PinManager::new();
manager.pin_node(&mut graph, "node1").unwrap();
manager.pin_node(&mut graph, "node2").unwrap();
manager.reset(&mut graph);
assert_eq!(manager.pinned_count(), 0);
assert!(!manager.is_pinned("node1"));
assert!(!manager.is_pinned("node2"));
assert!(!graph.get_node("node1").unwrap().visual.fixed);
assert!(!graph.get_node("node2").unwrap().visual.fixed);
}
#[test]
fn test_enable_disable() {
let mut graph = create_test_graph();
let mut manager = PinManager::new();
manager.set_enabled(false);
assert!(!manager.is_enabled());
let result = manager.pin_node(&mut graph, "node1");
assert!(result.is_err());
manager.set_enabled(true);
assert!(manager.is_enabled());
let result = manager.pin_node(&mut graph, "node1");
assert!(result.is_ok());
}
#[test]
fn test_get_pinned_nodes() {
let mut graph = create_test_graph();
let mut manager = PinManager::new();
manager.pin_node(&mut graph, "node1").unwrap();
manager.pin_node(&mut graph, "node3").unwrap();
let pinned = manager.pinned_nodes();
assert_eq!(pinned.len(), 2);
assert!(pinned.contains(&"node1".to_string()));
assert!(pinned.contains(&"node3".to_string()));
}
#[test]
fn test_pin_same_node_twice() {
let mut graph = create_test_graph();
let mut manager = PinManager::new();
manager.pin_node(&mut graph, "node1").unwrap();
manager.pin_node(&mut graph, "node1").unwrap();
assert_eq!(manager.pinned_count(), 1);
}
}