agg_gui/widgets/
splitter.rs1use crate::color::Color;
6use crate::event::{Event, EventResult, MouseButton};
7use crate::geometry::{Point, Rect, Size};
8use crate::draw_ctx::DrawCtx;
9use crate::layout_props::{HAnchor, Insets, VAnchor, WidgetBase};
10use crate::widget::Widget;
11
12pub struct Splitter {
16 bounds: Rect,
17 children: Vec<Box<dyn Widget>>, base: WidgetBase,
19 pub ratio: f64,
21 pub divider_width: f64,
23
24 hovered: bool,
25 dragging: bool,
26}
27
28impl Splitter {
29 pub fn new(left: Box<dyn Widget>, right: Box<dyn Widget>) -> Self {
30 Self {
31 bounds: Rect::default(),
32 children: vec![left, right],
33 base: WidgetBase::new(),
34 ratio: 0.5,
35 divider_width: 6.0,
36 hovered: false,
37 dragging: false,
38 }
39 }
40
41 pub fn with_ratio(mut self, ratio: f64) -> Self {
42 self.ratio = ratio.clamp(0.05, 0.95);
43 self
44 }
45
46 pub fn with_divider_width(mut self, w: f64) -> Self {
47 self.divider_width = w;
48 self
49 }
50
51 pub fn with_margin(mut self, m: Insets) -> Self { self.base.margin = m; self }
52 pub fn with_h_anchor(mut self, h: HAnchor) -> Self { self.base.h_anchor = h; self }
53 pub fn with_v_anchor(mut self, v: VAnchor) -> Self { self.base.v_anchor = v; self }
54 pub fn with_min_size(mut self, s: Size) -> Self { self.base.min_size = s; self }
55 pub fn with_max_size(mut self, s: Size) -> Self { self.base.max_size = s; self }
56
57 fn divider_x(&self) -> f64 {
58 (self.bounds.width - self.divider_width) * self.ratio
59 }
60}
61
62impl Widget for Splitter {
63 fn type_name(&self) -> &'static str { "Splitter" }
64 fn bounds(&self) -> Rect { self.bounds }
65 fn set_bounds(&mut self, b: Rect) { self.bounds = b; }
66 fn children(&self) -> &[Box<dyn Widget>] { &self.children }
67 fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> { &mut self.children }
68
69 fn margin(&self) -> Insets { self.base.margin }
70 fn h_anchor(&self) -> HAnchor { self.base.h_anchor }
71 fn v_anchor(&self) -> VAnchor { self.base.v_anchor }
72 fn min_size(&self) -> Size { self.base.min_size }
73 fn max_size(&self) -> Size { self.base.max_size }
74
75 fn hit_test(&self, local_pos: Point) -> bool {
76 if self.dragging { return true; }
78 let b = self.bounds();
79 local_pos.x >= 0.0 && local_pos.x <= b.width
80 && local_pos.y >= 0.0 && local_pos.y <= b.height
81 }
82
83 fn layout(&mut self, available: Size) -> Size {
84 let div = self.divider_width;
85 let left_w = ((available.width - div) * self.ratio).max(0.0);
86 let right_w = (available.width - div - left_w).max(0.0);
87 let h = available.height;
88
89 if self.children.len() >= 2 {
90 self.children[0].layout(Size::new(left_w, h));
91 self.children[0].set_bounds(Rect::new(0.0, 0.0, left_w, h));
92
93 let right_x = left_w + div;
94 self.children[1].layout(Size::new(right_w, h));
95 self.children[1].set_bounds(Rect::new(right_x, 0.0, right_w, h));
96 }
97
98 available
99 }
100
101 fn paint(&mut self, ctx: &mut dyn DrawCtx) {
102 let div_x = self.divider_x();
103 let h = self.bounds.height;
104
105 let color = if self.dragging {
106 Color::rgba(0.22, 0.45, 0.88, 0.6)
107 } else if self.hovered {
108 Color::rgba(0.0, 0.0, 0.0, 0.15)
109 } else {
110 Color::rgba(0.0, 0.0, 0.0, 0.08)
111 };
112 ctx.set_fill_color(color);
113 ctx.begin_path();
114 ctx.rect(div_x, 0.0, self.divider_width, h);
115 ctx.fill();
116
117 if h > 30.0 {
119 let grip_color = if self.hovered || self.dragging {
120 Color::rgba(0.22, 0.45, 0.88, 0.7)
121 } else {
122 Color::rgba(0.0, 0.0, 0.0, 0.25)
123 };
124 ctx.set_fill_color(grip_color);
125 let cx = div_x + self.divider_width * 0.5;
126 let cy = h * 0.5;
127 for i in -1i32..=1 {
128 ctx.begin_path();
129 ctx.circle(cx, cy + i as f64 * 5.0, 1.5);
130 ctx.fill();
131 }
132 }
133 }
134
135 fn on_event(&mut self, event: &Event) -> EventResult {
136 let div_x = self.divider_x();
137 let div_end = div_x + self.divider_width;
138
139 match event {
140 Event::MouseMove { pos } => {
141 let over_div = pos.x >= div_x - 2.0 && pos.x <= div_end + 2.0;
142 let was = self.hovered;
143 self.hovered = over_div;
144 if self.dragging {
145 let total = self.bounds.width;
146 if total > self.divider_width {
147 self.ratio = (pos.x / total).clamp(0.05, 0.95);
148 }
149 crate::animation::request_tick();
150 EventResult::Consumed
151 } else {
152 if was != self.hovered { crate::animation::request_tick(); }
153 EventResult::Ignored
154 }
155 }
156 Event::MouseDown { pos, button: MouseButton::Left, .. } => {
157 if pos.x >= div_x - 2.0 && pos.x <= div_end + 2.0 {
158 self.dragging = true;
159 EventResult::Consumed
163 } else {
164 EventResult::Ignored
165 }
166 }
167 Event::MouseUp { button: MouseButton::Left, .. } => {
168 let was_dragging = self.dragging;
169 self.dragging = false;
170 if was_dragging {
171 crate::animation::request_tick();
172 EventResult::Consumed
173 } else {
174 EventResult::Ignored
175 }
176 }
177 _ => EventResult::Ignored,
178 }
179 }
180}