use super::id::{NodeId, BranchId};
#[derive(Clone, Debug)]
pub enum SeparatorLevel {
Node {
parent_id: BranchId, child_a: u64, child_b: u64, },
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum SeparatorOrientation {
Vertical,
Horizontal,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum SeparatorState {
Idle,
Hover,
Dragging,
}
pub struct Separator {
pub orientation: SeparatorOrientation,
pub position: f32,
pub start: f32,
pub length: f32,
#[allow(dead_code)]
thickness: f32,
hit_width: f32,
pub state: SeparatorState,
pub level: SeparatorLevel,
}
impl Separator {
pub fn new(orientation: SeparatorOrientation, position: f32, start: f32, length: f32, level: SeparatorLevel) -> Self {
Self {
orientation,
position,
start,
length,
thickness: 2.0,
hit_width: 8.0,
state: SeparatorState::Idle,
level,
}
}
pub fn child_a(&self) -> Option<u64> {
match &self.level {
SeparatorLevel::Node { child_a, .. } => Some(*child_a),
}
}
pub fn child_b(&self) -> Option<u64> {
match &self.level {
SeparatorLevel::Node { child_b, .. } => Some(*child_b),
}
}
pub fn hit_test(&self, x: f32, y: f32) -> bool {
match self.orientation {
SeparatorOrientation::Vertical => {
let min_x = self.position - self.hit_width / 2.0;
let max_x = self.position + self.hit_width / 2.0;
x >= min_x && x <= max_x && y >= self.start && y <= self.start + self.length
}
SeparatorOrientation::Horizontal => {
let min_y = self.position - self.hit_width / 2.0;
let max_y = self.position + self.hit_width / 2.0;
y >= min_y && y <= max_y && x >= self.start && x <= self.start + self.length
}
}
}
pub fn thickness_for_state(&self) -> f32 {
match self.state {
SeparatorState::Idle => 2.0,
SeparatorState::Hover | SeparatorState::Dragging => 4.0,
}
}
}
#[derive(Clone, Debug)]
pub struct SeparatorDragState {
separator_idx: usize,
#[allow(dead_code)]
container_id: NodeId,
#[allow(dead_code)]
start_pos: f32,
start_shares: Vec<f32>,
}
pub struct SeparatorController {
dragging: Option<SeparatorDragState>,
}
impl SeparatorController {
pub fn new() -> Self {
Self { dragging: None }
}
pub fn start_drag(
&mut self,
separator_idx: usize,
container_id: NodeId,
pos: f32,
shares: Vec<f32>,
) {
self.dragging = Some(SeparatorDragState {
separator_idx,
container_id,
start_pos: pos,
start_shares: shares,
});
}
pub fn update_drag(
&self,
delta: f32,
children: &[NodeId],
min_sizes: &[f32],
total_size: f32,
) -> Option<Vec<f32>> {
let drag = self.dragging.as_ref()?;
if children.len() < 2 || min_sizes.len() != children.len() {
return None;
}
let mut new_shares = drag.start_shares.clone();
let idx = drag.separator_idx;
if idx >= children.len() - 1 {
return None; }
let total_shares: f32 = new_shares.iter().sum();
if total_shares <= 0.0 {
return None;
}
let delta_ratio = delta / total_size;
let share_delta = delta_ratio * total_shares;
new_shares[idx] += share_delta;
new_shares[idx + 1] -= share_delta;
for (i, &share) in new_shares.iter().enumerate() {
if share < 0.0 {
return None;
}
let pixel_size = (share / total_shares) * total_size;
if pixel_size < min_sizes[i] {
return None;
}
}
let current_sum: f32 = new_shares.iter().sum();
if current_sum <= 0.0 {
return None;
}
let scale = total_shares / current_sum;
for share in &mut new_shares {
*share *= scale;
}
Some(new_shares)
}
pub fn end_drag(&mut self) {
self.dragging = None;
}
pub fn is_dragging(&self) -> bool {
self.dragging.is_some()
}
pub fn drag_state(&self) -> Option<&SeparatorDragState> {
self.dragging.as_ref()
}
}
impl Default for SeparatorController {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_separator_hit_test_vertical() {
let separator = Separator::new(
SeparatorOrientation::Vertical, 100.0, 0.0, 200.0,
SeparatorLevel::Node { parent_id: BranchId(0), child_a: 0, child_b: 1 }
);
assert!(separator.hit_test(100.0, 100.0));
assert!(separator.hit_test(96.0, 100.0)); assert!(separator.hit_test(104.0, 100.0));
assert!(!separator.hit_test(90.0, 100.0)); assert!(!separator.hit_test(110.0, 100.0));
assert!(!separator.hit_test(100.0, -10.0)); assert!(!separator.hit_test(100.0, 210.0)); }
#[test]
fn test_separator_hit_test_horizontal() {
let separator = Separator::new(
SeparatorOrientation::Horizontal, 100.0, 0.0, 200.0,
SeparatorLevel::Node { parent_id: BranchId(0), child_a: 0, child_b: 1 }
);
assert!(separator.hit_test(100.0, 100.0));
assert!(separator.hit_test(100.0, 96.0)); assert!(separator.hit_test(100.0, 104.0));
assert!(!separator.hit_test(100.0, 90.0)); assert!(!separator.hit_test(100.0, 110.0));
assert!(!separator.hit_test(-10.0, 100.0)); assert!(!separator.hit_test(210.0, 100.0)); }
#[test]
fn test_separator_thickness() {
let mut separator = Separator::new(
SeparatorOrientation::Vertical, 100.0, 0.0, 200.0,
SeparatorLevel::Node { parent_id: BranchId(0), child_a: 0, child_b: 1 }
);
separator.state = SeparatorState::Idle;
assert_eq!(separator.thickness_for_state(), 2.0);
separator.state = SeparatorState::Hover;
assert_eq!(separator.thickness_for_state(), 4.0);
separator.state = SeparatorState::Dragging;
assert_eq!(separator.thickness_for_state(), 4.0);
}
#[test]
fn test_controller_normal_resize() {
let mut controller = SeparatorController::new();
let children = vec![NodeId(1), NodeId(2)];
let min_sizes = vec![100.0, 100.0];
controller.start_drag(0, NodeId(10), 200.0, vec![1.0, 1.0]);
let new_shares = controller.update_drag(50.0, &children, &min_sizes, 400.0);
assert!(new_shares.is_some());
let shares = new_shares.unwrap();
assert_eq!(shares.len(), 2);
let total: f32 = shares.iter().sum();
assert!((total - 2.0).abs() < 0.01);
let left_size = (shares[0] / total) * 400.0;
let right_size = (shares[1] / total) * 400.0;
assert!((left_size - 250.0).abs() < 1.0);
assert!((right_size - 150.0).abs() < 1.0);
}
#[test]
fn test_controller_constraint_violation() {
let mut controller = SeparatorController::new();
let children = vec![NodeId(1), NodeId(2)];
let min_sizes = vec![100.0, 100.0];
controller.start_drag(0, NodeId(10), 200.0, vec![1.0, 1.0]);
let new_shares = controller.update_drag(150.0, &children, &min_sizes, 400.0);
assert!(new_shares.is_none());
}
#[test]
fn test_controller_drag_left() {
let mut controller = SeparatorController::new();
let children = vec![NodeId(1), NodeId(2)];
let min_sizes = vec![100.0, 100.0];
controller.start_drag(0, NodeId(10), 200.0, vec![1.0, 1.0]);
let new_shares = controller.update_drag(-50.0, &children, &min_sizes, 400.0);
assert!(new_shares.is_some());
let shares = new_shares.unwrap();
let total: f32 = shares.iter().sum();
let left_size = (shares[0] / total) * 400.0;
let right_size = (shares[1] / total) * 400.0;
assert!((left_size - 150.0).abs() < 1.0);
assert!((right_size - 250.0).abs() < 1.0);
}
#[test]
fn test_controller_three_panels() {
let mut controller = SeparatorController::new();
let children = vec![NodeId(1), NodeId(2), NodeId(3)];
let min_sizes = vec![100.0, 100.0, 100.0];
controller.start_drag(0, NodeId(10), 200.0, vec![1.0, 1.0, 1.0]);
let new_shares = controller.update_drag(50.0, &children, &min_sizes, 600.0);
assert!(new_shares.is_some());
let shares = new_shares.unwrap();
let total: f32 = shares.iter().sum();
let sizes: Vec<f32> = shares.iter().map(|&s| (s / total) * 600.0).collect();
assert!((sizes[0] - 250.0).abs() < 1.0); assert!((sizes[1] - 150.0).abs() < 1.0); assert!((sizes[2] - 200.0).abs() < 1.0); }
#[test]
fn test_controller_drag_lifecycle() {
let mut controller = SeparatorController::new();
assert!(!controller.is_dragging());
controller.start_drag(0, NodeId(10), 200.0, vec![1.0, 1.0]);
assert!(controller.is_dragging());
controller.end_drag();
assert!(!controller.is_dragging());
}
}