1use makepad_render::*;
2use serde::*;
3use crate::widgetstyle::*;
4
5#[derive(Clone)]
6pub struct Splitter {
7 pub axis: Axis,
8 pub align: SplitterAlign,
9 pub pos: f32,
10
11 pub min_size: f32,
12 pub split_size: f32,
13 pub bg: Quad,
14 pub animator: Animator,
15 pub realign_dist: f32,
16 pub split_view: View,
17 pub _split_area: Area,
18 pub _calc_pos: f32,
19 pub _is_moving: bool,
20 pub _drag_point: f32,
21 pub _drag_pos_start: f32,
22 pub _drag_max_pos: f32,
23 pub _hit_state_margin: Option<Margin>,
24}
25
26#[derive(Clone, PartialEq, Serialize, Deserialize)]
27pub enum SplitterAlign {
28 First,
29 Last,
30 Weighted
31}
32
33#[derive(Clone, PartialEq)]
34pub enum SplitterEvent {
35 None,
36 Moving {new_pos: f32},
37 MovingEnd {new_align: SplitterAlign, new_pos: f32}
38}
39
40impl Splitter {
41 pub fn proto(cx: &mut Cx) -> Self {
42 Self {
43 axis: Axis::Vertical,
44 align: SplitterAlign::First,
45 pos: 0.0,
46
47 _split_area: Area::Empty,
48 _calc_pos: 0.0,
49 _is_moving: false,
50 _drag_point: 0.,
51 _drag_pos_start: 0.,
52 _drag_max_pos: 0.0,
53 _hit_state_margin: None,
54 realign_dist: 30.,
55 split_size: 2.0,
56 min_size: 25.0,
57 split_view: View::proto(cx),
58 bg: Quad::proto(cx),
59 animator: Animator::default(),
60 }
61 }
62
63 pub fn anim_default() -> AnimId {uid!()}
64 pub fn anim_over() -> AnimId {uid!()}
65 pub fn anim_down() -> AnimId {uid!()}
66 pub fn shader_bg() -> ShaderId {uid!()}
67
68 pub fn style(cx: &mut Cx, _opt: &StyleOptions) {
69
70 Self::anim_default().set(cx, Anim::new(Play::Cut {duration: 0.5}, vec![
71 Track::color(Quad::instance_color(), Ease::Lin, vec![(1.0, Theme::color_bg_splitter().get(cx))]),
72 ]));
73
74 Self::anim_over().set(cx, Anim::new(Play::Cut {duration: 0.05}, vec![
75 Track::color(Quad::instance_color(), Ease::Lin, vec![(1.0, Theme::color_bg_splitter_over().get(cx))]),
76 ]));
77
78 Self::anim_down().set(cx, Anim::new(Play::Cut {duration: 0.2}, vec![
79 Track::color(Quad::instance_color(), Ease::Lin, vec![
80 (0.0, Theme::color_bg_splitter_peak().get(cx)),
81 (1.0, Theme::color_bg_splitter_drag().get(cx))
82 ]),
83 ]));
84
85 Self::shader_bg().set(cx, Quad::def_quad_shader().compose(shader_ast!({
86
87 fn pixel() -> vec4 {
88 df_viewport(pos * vec2(w, h));
89 df_box(0., 0., w, h, 0.5);
90 return df_fill(color);
91 }
92 })));
93 }
94
95 pub fn handle_splitter(&mut self, cx: &mut Cx, event: &mut Event) -> SplitterEvent {
96 match event.hits(cx, self._split_area, HitOpt {margin: self._hit_state_margin, ..Default::default()}) {
97 Event::Animate(ae) => {
98 self.animator.calc_area(cx, self._split_area, ae.time);
99 },
100 Event::AnimEnded(_) => self.animator.end(),
101 Event::FingerDown(fe) => {
102 self._is_moving = true;
103 self.animator.play_anim(cx, Self::anim_down().get(cx));
104 match self.axis {
105 Axis::Horizontal => cx.set_down_mouse_cursor(MouseCursor::RowResize),
106 Axis::Vertical => cx.set_down_mouse_cursor(MouseCursor::ColResize)
107 };
108 self._drag_pos_start = self.pos;
109 self._drag_point = match self.axis {
110 Axis::Horizontal => {fe.rel.y},
111 Axis::Vertical => {fe.rel.x}
112 }
113 },
114 Event::FingerHover(fe) => {
115 match self.axis {
116 Axis::Horizontal => cx.set_hover_mouse_cursor(MouseCursor::RowResize),
117 Axis::Vertical => cx.set_hover_mouse_cursor(MouseCursor::ColResize)
118 };
119 if !self._is_moving {
120 match fe.hover_state {
121 HoverState::In => {
122 self.animator.play_anim(cx, Self::anim_over().get(cx));
123 },
124 HoverState::Out => {
125 self.animator.play_anim(cx, Self::anim_default().get(cx));
126 },
127 _ => ()
128 }
129 }
130 },
131 Event::FingerUp(fe) => {
132 self._is_moving = false;
133 if fe.is_over {
134 if !fe.is_touch {
135 self.animator.play_anim(cx, Self::anim_over().get(cx));
136 }
137 else {
138 self.animator.play_anim(cx, Self::anim_default().get(cx));
139 }
140 }
141 else {
142 self.animator.play_anim(cx, Self::anim_default().get(cx));
143 }
144 let center = self._drag_max_pos * 0.5;
147 if self._calc_pos > center - self.realign_dist &&
148 self._calc_pos < center + self.realign_dist {
149 self.align = SplitterAlign::Weighted;
150 self.pos = self._calc_pos / self._drag_max_pos;
151 }
152 else if self._calc_pos < center - self.realign_dist {
153
154 self.align = SplitterAlign::First;
155 self.pos = self._calc_pos;
156 }
157 else {
158 self.align = SplitterAlign::Last;
159 self.pos = self._drag_max_pos - self._calc_pos;
160 }
161
162 return SplitterEvent::MovingEnd {
163 new_align: self.align.clone(),
164 new_pos: self.pos
165 }
166 },
167 Event::FingerMove(fe) => {
168 let delta = match self.axis {
169 Axis::Horizontal => {
170 fe.abs_start.y - fe.abs.y
171 },
172 Axis::Vertical => {
173 fe.abs_start.x - fe.abs.x
174 }
175 };
176 let mut pos = match self.align {
177 SplitterAlign::First => self._drag_pos_start - delta,
178 SplitterAlign::Last => self._drag_pos_start + delta,
179 SplitterAlign::Weighted => self._drag_pos_start * self._drag_max_pos - delta
180 };
181 if pos > self._drag_max_pos - self.min_size {
182 pos = self._drag_max_pos - self.min_size
183 }
184 else if pos < self.min_size {
185 pos = self.min_size
186 };
187 let calc_pos = match self.align {
188 SplitterAlign::First => {
189 self.pos = pos;
190 pos
191 },
192 SplitterAlign::Last => {
193 self.pos = pos;
194 self._drag_max_pos - pos
195 },
196 SplitterAlign::Weighted => {
197 self.pos = pos / self._drag_max_pos;
198 pos
199 }
200 };
201 if calc_pos != self._calc_pos {
203 self._calc_pos = calc_pos;
204 cx.redraw_child_area(self._split_area);
205 return SplitterEvent::Moving {new_pos: self.pos};
206 }
207 }
208 _ => ()
209 };
210 SplitterEvent::None
211 }
212
213 pub fn set_splitter_state(&mut self, align: SplitterAlign, pos: f32, axis: Axis) {
214 self.axis = axis;
215 self.align = align;
216 self.pos = pos;
217 match self.axis {
218 Axis::Horizontal => {
219 self._hit_state_margin = Some(Margin {
220 l: 0.,
221 t: 3.,
222 r: 0.,
223 b: 7.,
224 })
225 },
226 Axis::Vertical => {
227 self._hit_state_margin = Some(Margin {
228 l: 3.,
229 t: 0.,
230 r: 7.,
231 b: 0.,
232 })
233 }
234 }
235 }
236
237 pub fn begin_splitter(&mut self, cx: &mut Cx) {
238 self.animator.init(cx, | cx | Self::anim_default().get(cx));
239 let rect = cx.get_turtle_rect();
240 self._calc_pos = match self.align {
241 SplitterAlign::First => self.pos,
242 SplitterAlign::Last => match self.axis {
243 Axis::Horizontal => rect.h - self.pos,
244 Axis::Vertical => rect.w - self.pos
245 },
246 SplitterAlign::Weighted => self.pos * match self.axis {
247 Axis::Horizontal => rect.h,
248 Axis::Vertical => rect.w
249 }
250 };
251 let dpi_factor = cx.get_dpi_factor_of(&self._split_area);
252 self._calc_pos -= self._calc_pos % (1.0 / dpi_factor);
253 match self.axis {
254 Axis::Horizontal => {
255 cx.begin_turtle(Layout {
256 walk: Walk::wh(Width::Fill, Height::Fix(self._calc_pos)),
257 ..Layout::default()
258 }, Area::Empty)
259 },
260 Axis::Vertical => {
261 cx.begin_turtle(Layout {
262 walk: Walk::wh(Width::Fix(self._calc_pos), Height::Fill),
263 ..Layout::default()
264 }, Area::Empty)
265 }
266 }
267 }
268
269 pub fn mid_splitter(&mut self, cx: &mut Cx) {
270 cx.end_turtle(Area::Empty);
271 let rect = cx.get_turtle_rect();
272 let origin = cx.get_turtle_origin();
273 self.bg.shader = Self::shader_bg().get(cx);
274 self.bg.color = self.animator.last_color(cx, Quad::instance_color());
275 match self.axis {
276 Axis::Horizontal => {
277 cx.set_turtle_pos(Vec2 {x: origin.x, y: origin.y + self._calc_pos});
278 if let Ok(_) = self.split_view.begin_view(cx, Layout {
279 walk: Walk::wh(Width::Fix(rect.w), Height::Fix(self.split_size)),
280 ..Layout::default()
281 }) {
282 self._split_area = self.bg.draw_quad_rel(cx, Rect {x: 0., y: 0., w: rect.w, h: self.split_size}).into();
283 self.split_view.end_view(cx);
284 }
285 cx.set_turtle_pos(Vec2 {x: origin.x, y: origin.y + self._calc_pos + self.split_size});
286 },
287 Axis::Vertical => {
288 cx.set_turtle_pos(Vec2 {x: origin.x + self._calc_pos, y: origin.y});
289 if let Ok(_) = self.split_view.begin_view(cx, Layout {
290 walk: Walk::wh(Width::Fix(self.split_size), Height::Fix(rect.h)),
291 ..Layout::default()
292 }) {
293 self._split_area = self.bg.draw_quad_rel(cx, Rect {x: 0., y: 0., w: self.split_size, h: rect.h}).into();
294 self.split_view.end_view(cx);
295 }
296 }
297 };
298 cx.begin_turtle(Layout::default(), Area::Empty);
299 }
300
301 pub fn end_splitter(&mut self, cx: &mut Cx) {
302 cx.end_turtle(Area::Empty);
303 let rect = cx.get_turtle_rect();
305
306 match self.axis {
307 Axis::Horizontal => {
308 self._drag_max_pos = rect.h;
309 },
310 Axis::Vertical => {
311 self._drag_max_pos = rect.w;
312 }
313 };
314
315 self.animator.set_area(cx, self._split_area);
316 }
317}