1use crate::{
2 makepad_derive_widget::*,
3 makepad_draw::*,
4 widget::*,
5};
6
7live_design!{
8 DrawSplitter= {{DrawSplitter}} {}
9 SplitterBase = {{Splitter}} {}
10}
11
12
13#[derive(Live, LiveHook)]
14#[repr(C)]
15pub struct DrawSplitter {
16 #[deref] draw_super: DrawQuad,
17 #[live] is_vertical: f32,
18}
19
20#[derive(Live)]
21pub struct Splitter {
22 #[live(Axis::Horizontal)] pub axis: Axis,
23 #[live(SplitterAlign::Weighted(0.5))] pub align: SplitterAlign,
24 #[rust] rect: Rect,
25 #[rust] position: f64,
26 #[rust] drag_start_align: Option<SplitterAlign>,
27 #[rust] area_a: Area,
28 #[rust] area_b: Area,
29 #[animator] animator: Animator,
30
31 #[live] min_vertical: f64,
32 #[live] max_vertical: f64,
33 #[live] min_horizontal: f64,
34 #[live] max_horizontal: f64,
35
36 #[live] draw_splitter: DrawSplitter,
37 #[live] split_bar_size: f64,
38
39 #[rust] draw_state: DrawStateWrap<DrawState>,
41 #[live] a: WidgetRef,
42 #[live] b: WidgetRef,
43 #[walk] walk: Walk,
44}
45
46impl LiveHook for Splitter{
47 fn before_live_design(cx:&mut Cx){
48 register_widget!(cx,Splitter)
49 }
50}
51
52#[derive(Clone)]
53enum DrawState {
54 DrawA,
55 DrawSplit,
56 DrawB
57}
58
59impl Widget for Splitter {
60 fn handle_widget_event_with(
61 &mut self,
62 cx: &mut Cx,
63 event: &Event,
64 dispatch_action: &mut dyn FnMut(&mut Cx, WidgetActionItem)
65 ) {
66 let mut redraw = false;
67 let uid = self.widget_uid();
68 self.handle_event_with(cx, event, &mut | cx, action | {
69 dispatch_action(cx, WidgetActionItem::new(action.into(), uid));
70 redraw = true;
71 });
72 self.a.handle_widget_event_with(cx, event, dispatch_action);
73 self.b.handle_widget_event_with(cx, event, dispatch_action);
74 if redraw {
75 self.a.redraw(cx);
76 self.b.redraw(cx);
77 }
78 }
79
80 fn walk(&mut self, _cx:&mut Cx) -> Walk {
81 self.walk
82 }
83
84 fn redraw(&mut self, cx:&mut Cx){
85 self.draw_splitter.redraw(cx)
86 }
87
88 fn find_widgets(&mut self, path: &[LiveId], cached: WidgetCache, results:&mut WidgetSet) {
89 self.a.find_widgets(path, cached, results);
90 self.b.find_widgets(path, cached, results);
91 }
92
93 fn draw_walk_widget(&mut self, cx: &mut Cx2d, walk: Walk) -> WidgetDraw {
94 if self.draw_state.begin(cx, DrawState::DrawA) {
95 self.begin(cx, walk);
96 }
97 if let Some(DrawState::DrawA) = self.draw_state.get() {
98 self.a.draw_widget(cx) ?;
99 self.draw_state.set(DrawState::DrawSplit);
100 }
101 if let Some(DrawState::DrawSplit) = self.draw_state.get() {
102 self.middle(cx);
103 self.draw_state.set(DrawState::DrawB)
104 }
105 if let Some(DrawState::DrawB) = self.draw_state.get() {
106 self.b.draw_widget(cx) ?;
107 self.end(cx);
108 self.draw_state.end();
109 }
110 WidgetDraw::done()
111 }
112}
113
114impl Splitter {
115 pub fn begin(&mut self, cx: &mut Cx2d, walk: Walk) {
116 match self.axis {
118 Axis::Horizontal => {
119 cx.begin_turtle(walk, Layout::flow_right());
120 }
121 Axis::Vertical => {
122 cx.begin_turtle(walk, Layout::flow_down());
123 }
124 }
125
126 self.rect = cx.turtle().padded_rect();
127 self.position = self.align.to_position(self.axis, self.rect);
128
129 let walk = match self.axis {
130 Axis::Horizontal => Walk::size(Size::Fixed(self.position), Size::Fill),
131 Axis::Vertical => Walk::size(Size::Fill, Size::Fixed(self.position)),
132 };
133 cx.begin_turtle(walk, Layout::flow_down());
134 }
135
136 pub fn middle(&mut self, cx: &mut Cx2d) {
137 cx.end_turtle_with_area(&mut self.area_a);
138 match self.axis {
139 Axis::Horizontal => {
140 self.draw_splitter.is_vertical = 1.0;
141 self.draw_splitter.draw_walk(cx, Walk::size(Size::Fixed(self.split_bar_size), Size::Fill));
142 }
143 Axis::Vertical => {
144 self.draw_splitter.is_vertical = 0.0;
145 self.draw_splitter.draw_walk(cx, Walk::size(Size::Fill, Size::Fixed(self.split_bar_size)));
146 }
147 }
148 cx.begin_turtle(Walk::default(), Layout::flow_down());
149 }
150
151 pub fn end(&mut self, cx: &mut Cx2d) {
152 cx.end_turtle_with_area(&mut self.area_b);
153 cx.end_turtle();
154 }
155
156 pub fn axis(&self) -> Axis {
157 self.axis
158 }
159
160 pub fn area_a(&self) -> Area {
161 self.area_a
162 }
163
164 pub fn area_b(&self) -> Area {
165 self.area_b
166 }
167
168 pub fn set_axis(&mut self, axis: Axis) {
169 self.axis = axis;
170 }
171
172 pub fn align(&self) -> SplitterAlign {
173 self.align
174 }
175
176 pub fn set_align(&mut self, align: SplitterAlign) {
177 self.align = align;
178 }
179
180 pub fn handle_event_with(
181 &mut self,
182 cx: &mut Cx,
183 event: &Event,
184 dispatch_action: &mut dyn FnMut(&mut Cx, SplitterAction),
185 ) {
186 self.animator_handle_event(cx, event);
187 match event.hits_with_options(cx, self.draw_splitter.area(), HitOptions::new().with_margin(self.margin())) {
188 Hit::FingerHoverIn(_) => {
189 match self.axis {
190 Axis::Horizontal => cx.set_cursor(MouseCursor::ColResize),
191 Axis::Vertical => cx.set_cursor(MouseCursor::RowResize),
192 }
193 self.animator_play(cx, id!(hover.on));
194 }
195 Hit::FingerHoverOut(_) => {
196 self.animator_play(cx, id!(hover.off));
197 },
198 Hit::FingerDown(_) => {
199 match self.axis {
200 Axis::Horizontal => cx.set_cursor(MouseCursor::ColResize),
201 Axis::Vertical => cx.set_cursor(MouseCursor::RowResize),
202 }
203 self.animator_play(cx, id!(hover.pressed));
204 self.drag_start_align = Some(self.align);
205 }
206 Hit::FingerUp(f) => {
207 self.drag_start_align = None;
208 if f.is_over && f.device.has_hovers() {
209 self.animator_play(cx, id!(hover.on));
210 }
211 else {
212 self.animator_play(cx, id!(hover.off));
213 }
214 }
215 Hit::FingerMove(f) => {
216 if let Some(drag_start_align) = self.drag_start_align {
217 let delta = match self.axis {
218 Axis::Horizontal => f.abs.x - f.abs_start.x,
219 Axis::Vertical => f.abs.y - f.abs_start.y,
220 };
221 let new_position =
222 drag_start_align.to_position(self.axis, self.rect) + delta;
223 self.align = match self.axis {
224 Axis::Horizontal => {
225 let center = self.rect.size.x / 2.0;
226 if new_position < center - 30.0 {
227 SplitterAlign::FromA(new_position.max(self.min_vertical))
228 } else if new_position > center + 30.0 {
229 SplitterAlign::FromB((self.rect.size.x - new_position).max(self.max_vertical))
230 } else {
231 SplitterAlign::Weighted(new_position / self.rect.size.x)
232 }
233 }
234 Axis::Vertical => {
235 let center = self.rect.size.y / 2.0;
236 if new_position < center - 30.0 {
237 SplitterAlign::FromA(new_position.max(self.min_horizontal))
238 } else if new_position > center + 30.0 {
239 SplitterAlign::FromB((self.rect.size.y - new_position).max(self.max_horizontal))
240 } else {
241 SplitterAlign::Weighted(new_position / self.rect.size.y)
242 }
243 }
244 };
245 self.draw_splitter.redraw(cx);
246 dispatch_action(cx, SplitterAction::Changed {axis: self.axis, align: self.align});
247 }
248 }
249 _ => {}
250 }
251}
252
253fn margin(&self) -> Margin {
254 match self.axis {
255 Axis::Horizontal => Margin {
256 left: 3.0,
257 top: 0.0,
258 right: 3.0,
259 bottom: 0.0,
260 },
261 Axis::Vertical => Margin {
262 left: 0.0,
263 top: 3.0,
264 right: 0.0,
265 bottom: 3.0,
266 },
267 }
268}
269}
270
271#[derive(Clone, Copy, Debug, Live, LiveHook)]
272#[live_ignore]
273pub enum SplitterAlign {
274 #[live(50.0)] FromA(f64),
275 #[live(50.0)] FromB(f64),
276 #[pick(0.5)] Weighted(f64),
277}
278
279impl SplitterAlign {
280 fn to_position(self, axis: Axis, rect: Rect) -> f64 {
281 match axis {
282 Axis::Horizontal => match self {
283 Self::FromA(position) => position,
284 Self::FromB(position) => rect.size.x - position,
285 Self::Weighted(weight) => weight * rect.size.x,
286 },
287 Axis::Vertical => match self {
288 Self::FromA(position) => position,
289 Self::FromB(position) => rect.size.y - position,
290 Self::Weighted(weight) => weight * rect.size.y,
291 },
292 }
293 }
294}
295
296#[derive(Clone, WidgetAction)]
297pub enum SplitterAction {
298 None,
299 Changed {axis: Axis, align: SplitterAlign},
300}