1use crate::{
2 makepad_derive_widget::*,
3 makepad_micro_serde::*,
4 makepad_draw::*,
5 widget::*,
6};
7
8live_design!{
9 link widgets;
10 use link::theme::*;
11 use makepad_draw::shader::std::*;
12
13 pub DrawSplitter= {{DrawSplitter}} {}
14 pub SplitterBase = {{Splitter}} {}
15 pub Splitter = <SplitterBase> {
16 draw_bg: {
17 instance drag: 0.0
18 instance hover: 0.0
19
20 uniform size: 110.0
21
22 uniform color: (THEME_COLOR_D_HIDDEN),
23 uniform color_hover: (THEME_COLOR_OUTSET_HOVER),
24 uniform color_drag: (THEME_COLOR_OUTSET_DRAG)
25
26 uniform border_radius: 1.0
27 uniform splitter_pad: 1.0
28
29 fn pixel(self) -> vec4 {
30 let sdf = Sdf2d::viewport(self.pos * self.rect_size);
31 sdf.clear(THEME_COLOR_BG_APP); if self.is_vertical > 0.5 {
34 sdf.box(
35 self.splitter_pad,
36 self.rect_size.y * 0.5 - self.size * 0.5,
37 self.rect_size.x - 2.0 * self.splitter_pad,
38 self.size,
39 self.border_radius
40 );
41 }
42 else {
43 sdf.box(
44 self.rect_size.x * 0.5 - self.size * 0.5,
45 self.splitter_pad,
46 self.size,
47 self.rect_size.y - 2.0 * self.splitter_pad,
48 self.border_radius
49 );
50 }
51
52 return sdf.fill_keep(
53 mix(
54 self.color,
55 mix(
56 self.color_hover,
57 self.color_drag,
58 self.drag
59 ),
60 self.hover
61 )
62 );
63 }
64 }
65
66 size: (THEME_SPLITTER_SIZE)
67 min_horizontal: (THEME_SPLITTER_MIN_HORIZONTAL)
68 max_horizontal: (THEME_SPLITTER_MAX_HORIZONTAL)
69 min_vertical: (THEME_SPLITTER_MIN_VERTICAL)
70 max_vertical: (THEME_SPLITTER_MAX_VERTICAL)
71
72 animator: {
73 hover = {
74 default: off
75 off = {
76 from: {all: Forward {duration: 0.1}}
77 apply: {
78 draw_bg: {drag: 0.0, hover: 0.0}
79 }
80 }
81
82 on = {
83 from: {
84 all: Forward {duration: 0.1}
85 state_drag: Forward {duration: 0.01}
86 }
87 apply: {
88 draw_bg: {
89 drag: 0.0,
90 hover: [{time: 0.0, value: 1.0}],
91 }
92 }
93 }
94
95 drag = {
96 from: { all: Forward { duration: 0.1 }}
97 apply: {
98 draw_bg: {
99 drag: [{time: 0.0, value: 1.0}],
100 hover: 1.0,
101 }
102 }
103 }
104 }
105 }
106 }
107
108}
109
110
111#[derive(Live, LiveHook, LiveRegister)]
112#[repr(C)]
113pub struct DrawSplitter {
114 #[deref] draw_super: DrawQuad,
115 #[live] is_vertical: f32,
116}
117
118#[derive(Copy, Clone, Debug, Live, LiveHook, SerRon, DeRon)]
119#[live_ignore]
120pub enum SplitterAxis {
121 #[pick] Horizontal,
122 Vertical
123}
124
125impl Default for SplitterAxis {
126 fn default() -> Self {
127 SplitterAxis::Horizontal
128 }
129}
130
131
132#[derive(Clone, Copy, Debug, Live, LiveHook, SerRon, DeRon)]
133#[live_ignore]
134pub enum SplitterAlign {
135 #[live(50.0)] FromA(f64),
136 #[live(50.0)] FromB(f64),
137 #[pick(0.5)] Weighted(f64),
138}
139
140impl SplitterAlign {
141 fn to_position(self, axis: SplitterAxis, rect: Rect) -> f64 {
142 match axis {
143 SplitterAxis::Horizontal => match self {
144 Self::FromA(position) => position,
145 Self::FromB(position) => rect.size.x - position,
146 Self::Weighted(weight) => weight * rect.size.x,
147 },
148 SplitterAxis::Vertical => match self {
149 Self::FromA(position) => position,
150 Self::FromB(position) => rect.size.y - position,
151 Self::Weighted(weight) => weight * rect.size.y,
152 },
153 }
154 }
155}
156
157#[derive(Live, LiveHook, Widget)]
158pub struct Splitter {
159 #[live(SplitterAxis::Horizontal)] pub axis: SplitterAxis,
160 #[live(SplitterAlign::Weighted(0.5))] pub align: SplitterAlign,
161 #[rust] rect: Rect,
162 #[rust] position: f64,
163 #[rust] drag_start_align: Option<SplitterAlign>,
164 #[rust] area_a: Area,
165 #[rust] area_b: Area,
166 #[animator] animator: Animator,
167
168 #[live] min_vertical: f64,
169 #[live] max_vertical: f64,
170 #[live] min_horizontal: f64,
171 #[live] max_horizontal: f64,
172
173 #[redraw] #[live] draw_bg: DrawSplitter,
174 #[live] size: f64,
175
176 #[rust] draw_state: DrawStateWrap<DrawState>,
178 #[find] #[live] a: WidgetRef,
179 #[find] #[live] b: WidgetRef,
180 #[walk] walk: Walk,
181}
182
183#[derive(Clone)]
184enum DrawState {
185 DrawA,
186 DrawSplit,
187 DrawB
188}
189
190impl Widget for Splitter {
191 fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {
192 let uid = self.widget_uid();
193
194 self.animator_handle_event(cx, event);
195 match event.hits_with_options(cx, self.draw_bg.area(), HitOptions::new().with_margin(self.margin())) {
196 Hit::FingerHoverIn(_) => {
197 match self.axis {
198 SplitterAxis::Horizontal => cx.set_cursor(MouseCursor::ColResize),
199 SplitterAxis::Vertical => cx.set_cursor(MouseCursor::RowResize),
200 }
201 self.animator_play(cx, id!(hover.on));
202 }
203 Hit::FingerHoverOut(_) => {
204 self.animator_play(cx, id!(hover.off));
205 },
206 Hit::FingerDown(_) => {
207 match self.axis {
208 SplitterAxis::Horizontal => cx.set_cursor(MouseCursor::ColResize),
209 SplitterAxis::Vertical => cx.set_cursor(MouseCursor::RowResize),
210 }
211 self.animator_play(cx, id!(hover.drag));
212 self.drag_start_align = Some(self.align);
213 }
214 Hit::FingerUp(f) => {
215 self.drag_start_align = None;
216 if f.is_over && f.device.has_hovers() {
217 self.animator_play(cx, id!(hover.on));
218 }
219 else {
220 self.animator_play(cx, id!(hover.off));
221 }
222 }
223 Hit::FingerMove(f) => {
224 if let Some(drag_start_align) = self.drag_start_align {
225 let delta = match self.axis {
226 SplitterAxis::Horizontal => f.abs.x - f.abs_start.x,
227 SplitterAxis::Vertical => f.abs.y - f.abs_start.y,
228 };
229 let new_position =
230 drag_start_align.to_position(self.axis, self.rect) + delta;
231 self.align = match self.axis {
232 SplitterAxis::Horizontal => {
233 let center = self.rect.size.x / 2.0;
234 if new_position < center - 30.0 {
235 SplitterAlign::FromA(new_position.max(self.min_vertical))
236 } else if new_position > center + 30.0 {
237 SplitterAlign::FromB((self.rect.size.x - new_position).max(self.max_vertical))
238 } else {
239 SplitterAlign::Weighted(new_position / self.rect.size.x)
240 }
241 }
242 SplitterAxis::Vertical => {
243 let center = self.rect.size.y / 2.0;
244 if new_position < center - 30.0 {
245 SplitterAlign::FromA(new_position.max(self.min_horizontal))
246 } else if new_position > center + 30.0 {
247 SplitterAlign::FromB((self.rect.size.y - new_position).max(self.max_horizontal))
248 } else {
249 SplitterAlign::Weighted(new_position / self.rect.size.y)
250 }
251 }
252 };
253 self.draw_bg.redraw(cx);
254 cx.widget_action(uid, &scope.path, SplitterAction::Changed {axis: self.axis, align: self.align});
255
256 self.a.redraw(cx);
257 self.b.redraw(cx);
258 }
259 }
260 _ => {}
261 }
262 self.a.handle_event(cx, event, scope);
263 self.b.handle_event(cx, event, scope);
264 }
265
266 fn draw_walk(&mut self, cx: &mut Cx2d, scope:&mut Scope, walk: Walk) -> DrawStep {
267 if self.draw_state.begin(cx, DrawState::DrawA) {
268 self.begin(cx, walk);
269 }
270 if let Some(DrawState::DrawA) = self.draw_state.get() {
271 self.a.draw(cx, scope) ?;
272 self.draw_state.set(DrawState::DrawSplit);
273 }
274 if let Some(DrawState::DrawSplit) = self.draw_state.get() {
275 self.middle(cx);
276 self.draw_state.set(DrawState::DrawB)
277 }
278 if let Some(DrawState::DrawB) = self.draw_state.get() {
279 self.b.draw(cx, scope) ?;
280 self.end(cx);
281 self.draw_state.end();
282 }
283 DrawStep::done()
284 }
285}
286
287impl Splitter {
288 pub fn begin(&mut self, cx: &mut Cx2d, walk: Walk) {
289 match self.axis {
291 SplitterAxis::Horizontal => {
292 cx.begin_turtle(walk, Layout::flow_right());
293 }
294 SplitterAxis::Vertical => {
295 cx.begin_turtle(walk, Layout::flow_down());
296 }
297 }
298
299 self.rect = cx.turtle().padded_rect();
300 self.position = self.align.to_position(self.axis, self.rect);
301
302 let walk = match self.axis {
303 SplitterAxis::Horizontal => Walk::size(Size::Fixed(self.position), Size::Fill),
304 SplitterAxis::Vertical => Walk::size(Size::Fill, Size::Fixed(self.position)),
305 };
306 cx.begin_turtle(walk, Layout::flow_down());
307 }
308
309 pub fn middle(&mut self, cx: &mut Cx2d) {
310 cx.end_turtle_with_area(&mut self.area_a);
311 match self.axis {
312 SplitterAxis::Horizontal => {
313 self.draw_bg.is_vertical = 1.0;
314 self.draw_bg.draw_walk(cx, Walk::size(Size::Fixed(self.size), Size::Fill));
315 }
316 SplitterAxis::Vertical => {
317 self.draw_bg.is_vertical = 0.0;
318 self.draw_bg.draw_walk(cx, Walk::size(Size::Fill, Size::Fixed(self.size)));
319 }
320 }
321 cx.begin_turtle(Walk::default(), Layout::flow_down());
322 }
323
324 pub fn end(&mut self, cx: &mut Cx2d) {
325 cx.end_turtle_with_area(&mut self.area_b);
326 cx.end_turtle();
327 }
328
329 pub fn axis(&self) -> SplitterAxis {
330 self.axis
331 }
332
333 pub fn area_a(&self) -> Area {
334 self.area_a
335 }
336
337 pub fn area_b(&self) -> Area {
338 self.area_b
339 }
340
341 pub fn set_axis(&mut self, axis: SplitterAxis) {
342 self.axis = axis;
343 }
344
345 pub fn align(&self) -> SplitterAlign {
346 self.align
347 }
348
349 pub fn set_align(&mut self, align: SplitterAlign) {
350 self.align = align;
351 }
352
353 fn margin(&self) -> Margin {
354 match self.axis {
355 SplitterAxis::Horizontal => Margin {
356 left: 3.0,
357 top: 0.0,
358 right: 3.0,
359 bottom: 0.0,
360 },
361 SplitterAxis::Vertical => Margin {
362 left: 0.0,
363 top: 3.0,
364 right: 0.0,
365 bottom: 3.0,
366 },
367 }
368 }
369}
370
371#[derive(Clone, Debug, DefaultNone)]
372pub enum SplitterAction {
373 None,
374 Changed {axis: SplitterAxis, align: SplitterAlign},
375}