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