use std::any::Any;
use astrelis_core::math::Vec2;
use astrelis_render::Color;
use astrelis_text::FontRenderer;
use crate::style::Style;
use crate::tree::{LayoutRect, NodeId};
use crate::widgets::Widget;
use super::types::{PanelConstraints, SplitDirection, calculate_separator_bounds};
pub const DEFAULT_SEPARATOR_SIZE: f32 = 2.0;
pub fn default_separator_color() -> Color {
Color::from_rgb_u8(30, 30, 40)
}
pub fn default_separator_hover_color() -> Color {
Color::from_rgb_u8(90, 120, 200)
}
#[derive(Clone)]
pub struct DockSplitter {
pub style: Style,
pub children: Vec<NodeId>,
pub direction: SplitDirection,
pub split_ratio: f32,
pub separator_size: f32,
pub separator_color: Color,
pub separator_hover_color: Color,
pub is_separator_hovered: bool,
pub is_separator_dragging: bool,
pub first_constraints: PanelConstraints,
pub second_constraints: PanelConstraints,
pub separator_tolerance: Option<f32>,
}
impl DockSplitter {
pub fn horizontal() -> Self {
Self::new(SplitDirection::Horizontal)
}
pub fn vertical() -> Self {
Self::new(SplitDirection::Vertical)
}
pub fn new(direction: SplitDirection) -> Self {
Self {
style: Style::new().display(taffy::Display::Flex),
children: Vec::new(),
direction,
split_ratio: 0.5,
separator_size: DEFAULT_SEPARATOR_SIZE,
separator_color: default_separator_color(),
separator_hover_color: default_separator_hover_color(),
is_separator_hovered: false,
is_separator_dragging: false,
first_constraints: PanelConstraints::default(),
second_constraints: PanelConstraints::default(),
separator_tolerance: None,
}
}
pub fn split_ratio(mut self, ratio: f32) -> Self {
self.split_ratio = ratio.clamp(0.0, 1.0);
self
}
pub fn separator_size(mut self, size: f32) -> Self {
self.separator_size = size.max(1.0);
self
}
pub fn separator_colors(mut self, normal: Color, hover: Color) -> Self {
self.separator_color = normal;
self.separator_hover_color = hover;
self
}
pub fn first_constraints(mut self, constraints: PanelConstraints) -> Self {
self.first_constraints = constraints;
self
}
pub fn second_constraints(mut self, constraints: PanelConstraints) -> Self {
self.second_constraints = constraints;
self
}
pub fn separator_tolerance(mut self, tolerance: f32) -> Self {
self.separator_tolerance = Some(tolerance);
self
}
pub fn separator_bounds(&self, layout: &LayoutRect) -> LayoutRect {
if layout.width <= 0.0 || layout.height <= 0.0 {
return LayoutRect {
x: 0.0,
y: 0.0,
width: 0.0,
height: 0.0,
};
}
calculate_separator_bounds(
layout,
self.direction,
self.split_ratio,
self.separator_size,
)
}
pub fn separator_hit_bounds(&self, layout: &LayoutRect, tolerance: f32) -> LayoutRect {
let sep = self.separator_bounds(layout);
match self.direction {
SplitDirection::Horizontal => {
LayoutRect {
x: sep.x - tolerance,
y: sep.y,
width: sep.width + tolerance * 2.0,
height: sep.height,
}
}
SplitDirection::Vertical => {
LayoutRect {
x: sep.x,
y: sep.y - tolerance,
width: sep.width,
height: sep.height + tolerance * 2.0,
}
}
}
}
pub fn is_point_in_separator(&self, layout: &LayoutRect, point: Vec2, tolerance: f32) -> bool {
let sep = self.separator_hit_bounds(layout, tolerance);
point.x >= sep.x
&& point.x <= sep.x + sep.width
&& point.y >= sep.y
&& point.y <= sep.y + sep.height
}
pub fn apply_drag_delta(&mut self, delta: Vec2, layout: &LayoutRect) -> f32 {
self.apply_drag_delta_from_original(delta, layout, self.split_ratio)
}
pub fn apply_drag_delta_from_original(
&mut self,
delta: Vec2,
layout: &LayoutRect,
original_ratio: f32,
) -> f32 {
let total_size = match self.direction {
SplitDirection::Horizontal => layout.width,
SplitDirection::Vertical => layout.height,
};
if total_size <= 0.0 {
return self.split_ratio;
}
let delta_component = match self.direction {
SplitDirection::Horizontal => delta.x,
SplitDirection::Vertical => delta.y,
};
let ratio_delta = delta_component / total_size;
let new_ratio = (original_ratio + ratio_delta).clamp(0.0, 1.0);
let first_size = total_size * new_ratio - self.separator_size / 2.0;
let second_size = total_size * (1.0 - new_ratio) - self.separator_size / 2.0;
let first_clamped = self.first_constraints.clamp(first_size);
let second_clamped = self.second_constraints.clamp(second_size);
let final_ratio = if first_clamped != first_size {
(first_clamped + self.separator_size / 2.0) / total_size
} else if second_clamped != second_size {
1.0 - (second_clamped + self.separator_size / 2.0) / total_size
} else {
new_ratio
};
self.split_ratio = final_ratio.clamp(0.0, 1.0);
self.split_ratio
}
pub fn current_separator_color(&self) -> Color {
if self.is_separator_dragging || self.is_separator_hovered {
self.separator_hover_color
} else {
self.separator_color
}
}
pub fn set_separator_hovered(&mut self, hovered: bool) {
self.is_separator_hovered = hovered;
}
pub fn set_separator_dragging(&mut self, dragging: bool) {
self.is_separator_dragging = dragging;
}
pub fn first_panel_layout(&self, layout: &LayoutRect) -> LayoutRect {
let half_sep = self.separator_size / 2.0;
match self.direction {
SplitDirection::Horizontal => {
let width = (layout.width * self.split_ratio - half_sep).max(0.0);
LayoutRect {
x: layout.x,
y: layout.y,
width,
height: layout.height,
}
}
SplitDirection::Vertical => {
let height = (layout.height * self.split_ratio - half_sep).max(0.0);
LayoutRect {
x: layout.x,
y: layout.y,
width: layout.width,
height,
}
}
}
}
pub fn second_panel_layout(&self, layout: &LayoutRect) -> LayoutRect {
let half_sep = self.separator_size / 2.0;
match self.direction {
SplitDirection::Horizontal => {
let split_x = layout.width * self.split_ratio;
let x = layout.x + split_x + half_sep;
let width = (layout.width - split_x - half_sep).max(0.0);
LayoutRect {
x,
y: layout.y,
width,
height: layout.height,
}
}
SplitDirection::Vertical => {
let split_y = layout.height * self.split_ratio;
let y = layout.y + split_y + half_sep;
let height = (layout.height - split_y - half_sep).max(0.0);
LayoutRect {
x: layout.x,
y,
width: layout.width,
height,
}
}
}
}
}
impl Widget for DockSplitter {
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
fn style(&self) -> &Style {
&self.style
}
fn style_mut(&mut self) -> &mut Style {
&mut self.style
}
fn children(&self) -> &[NodeId] {
debug_assert!(
self.children.len() == 2 || self.children.is_empty(),
"DockSplitter must have exactly 0 or 2 children, found {}",
self.children.len()
);
&self.children
}
fn children_mut(&mut self) -> Option<&mut Vec<NodeId>> {
Some(&mut self.children)
}
fn measure(&self, _available_space: Vec2, _font_renderer: Option<&FontRenderer>) -> Vec2 {
Vec2::ZERO
}
fn clone_box(&self) -> Box<dyn Widget> {
Box::new(self.clone())
}
}