makepad_widgets/
splitter.rs

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    // framecomponent mode
40    #[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        // we should start a fill turtle in the layout direction of choice
117        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}