makepad_widget/
tab.rs

1use makepad_render::*;
2use crate::buttonlogic::*;
3use crate::tabclose::*;
4use crate::widgetstyle::*;
5
6#[derive(Clone)]
7pub struct Tab {
8    pub bg: Quad,
9    pub text: Text,
10    pub tab_close: TabClose,
11    pub label: String,
12    pub is_closeable: bool,
13    pub animator: Animator,
14    pub z: f32,
15    pub abs_origin: Option<Vec2>,
16    pub _is_selected: bool,
17    pub _is_focussed: bool,
18    pub _bg_area: Area,
19    pub _bg_inst: Option<InstanceArea>,
20    pub _text_area: Area,
21    pub _close_anim_rect: Rect,
22    pub _is_down: bool,
23    pub _is_drag: bool
24}
25
26#[derive(Clone, PartialEq)]
27pub enum TabEvent {
28    None,
29    DragMove(FingerMoveEvent),
30    DragEnd(FingerUpEvent),
31    Closing,
32    Close,
33    Select,
34}
35
36impl Tab {
37    pub fn proto(cx: &mut Cx) -> Self {
38        let mut tab = Self {
39            label: "Tab".to_string(),
40            is_closeable: true,
41            z: 0.,
42            bg: Quad ::proto(cx),
43            tab_close: TabClose::proto(cx),
44            text: Text::proto(cx),
45            animator: Animator::default(),
46            abs_origin: None,
47            _is_selected: false,
48            _is_focussed: false,
49            _is_down: false,
50            _is_drag: false,
51            _close_anim_rect: Rect::default(),
52            _text_area: Area::Empty,
53            _bg_area: Area::Empty,
54            _bg_inst: None,
55        };
56        tab.animator.set_anim_as_last_values(&tab.anim_default(cx));
57        tab
58    }
59    
60    pub fn layout_bg() -> LayoutId {uid!()}
61    pub fn text_style_title() -> TextStyleId {uid!()}
62    pub fn instance_border_color() -> InstanceColor {uid!()}
63    pub fn tab_closing() -> InstanceFloat {uid!()}
64    pub fn shader_bg() -> ShaderId {uid!()}
65    
66    pub fn style(cx: &mut Cx, opt: &StyleOptions) {
67        
68        Self::layout_bg().set(cx, Layout {
69            align: Align::left_center(),
70            walk: Walk::wh(Width::Compute, Height::Fix(40. * opt.scale.powf(0.5))),
71            padding: Padding {l: 16.0, t: 1.0, r: 16.0, b: 0.0},
72            ..Default::default()
73        });
74        
75        Self::text_style_title().set(cx, Theme::text_style_normal().get(cx));
76        
77        Self::shader_bg().set(cx, Quad::def_quad_shader().compose(shader_ast!({
78            
79            let border_color: Self::instance_border_color();
80            const border_width: float = 1.0;
81            
82            fn pixel() -> vec4 {
83                df_viewport(pos * vec2(w, h));
84                df_rect(-1., -1., w + 2., h + 2.);
85                df_fill(color);
86                df_move_to(w, 0.);
87                df_line_to(w, h);
88                df_move_to(0., 0.);
89                df_line_to(0., h);
90                return df_stroke(border_color, 1.);
91            }
92        })));
93    }
94    
95    pub fn get_bg_color(&self, cx: &Cx) -> Color {
96        if self._is_selected {
97            Theme::color_bg_selected().get(cx)
98        }
99        else {
100            Theme::color_bg_normal().get(cx)
101        }
102    }
103    
104    pub fn get_text_color(&self, cx: &Cx) -> Color {
105        if self._is_selected {
106            if self._is_focussed {
107                Theme::color_text_selected_focus().get(cx)
108            }
109            else {
110                Theme::color_text_selected_defocus().get(cx)
111            }
112        }
113        else {
114            if self._is_focussed {
115                Theme::color_text_deselected_focus().get(cx)
116            }
117            else {
118                Theme::color_text_deselected_defocus().get(cx)
119            }
120        }
121    }
122    
123    pub fn anim_default(&self, cx: &Cx) -> Anim {
124        Anim::new(Play::Cut {duration: 0.05}, vec![
125            Track::color(Quad::instance_color(), Ease::Lin, vec![(1.0, self.get_bg_color(cx))]),
126            Track::color(Self::instance_border_color(), Ease::Lin, vec![(1.0, Theme::color_bg_selected().get(cx))]),
127            Track::color(Text::instance_color(), Ease::Lin, vec![(1.0, self.get_text_color(cx))]),
128            //Track::color_id(cx, "icon.color", Ease::Lin, vec![(1.0, self.get_text_color(cx))])
129        ])
130    }
131    
132    pub fn anim_over(&self, cx: &Cx) -> Anim {
133        Anim::new(Play::Cut {duration: 0.01}, vec![
134            Track::color(Quad::instance_color(), Ease::Lin, vec![(1.0, self.get_bg_color(cx))]),
135            Track::color(Self::instance_border_color(), Ease::Lin, vec![(1.0, Theme::color_bg_selected().get(cx))]),
136            Track::color(Text::instance_color(), Ease::Lin, vec![(1.0, self.get_text_color(cx))]),
137            //Track::color_id(cx, "icon.color", Ease::Lin, vec![(1.0, self.get_text_color(cx))])
138        ])
139    }
140    
141    pub fn anim_down(&self, cx: &Cx) -> Anim {
142        Anim::new(Play::Cut {duration: 0.01}, vec![
143            Track::color(Quad::instance_color(), Ease::Lin, vec![(1.0, self.get_bg_color(cx))]),
144            Track::color(Self::instance_border_color(), Ease::Lin, vec![(1.0, Theme::color_bg_selected().get(cx))]),
145            Track::color(Text::instance_color(), Ease::Lin, vec![(1.0, self.get_text_color(cx))]),
146            // Track::color_id(cx, "icon.color", Ease::Lin, vec![(1.0, self.get_text_color(cx))])
147        ])
148    }
149    
150    pub fn anim_close(&self, _cx: &Cx) -> Anim {
151        Anim::new(Play::Single {duration: 0.1, cut: true, term: true, end: 1.0}, vec![
152            Track::float(Self::tab_closing(), Ease::OutExp, vec![(0.0, 1.0), (1.0, 0.0)]),
153        ])
154    }
155    
156    pub fn set_tab_focus(&mut self, cx: &mut Cx, focus: bool) {
157        if focus != self._is_focussed {
158            self._is_focussed = focus;
159            self.animator.play_anim(cx, self.anim_default(cx));
160        }
161    }
162    
163    pub fn set_tab_selected(&mut self, cx: &mut Cx, selected: bool) {
164        if selected != self._is_selected {
165            self._is_selected = selected;
166            self.animator.play_anim(cx, self.anim_default(cx));
167        }
168    }
169    
170    pub fn set_tab_state(&mut self, cx: &mut Cx, selected: bool, focus: bool) {
171        self._is_selected = selected;
172        self._is_focussed = focus;
173        self.animator.set_anim_as_last_values(&self.anim_default(cx));
174    }
175    
176    pub fn handle_tab(&mut self, cx: &mut Cx, event: &mut Event) -> TabEvent {
177        
178        if !self.animator.term_anim_playing() {
179            match self.tab_close.handle_tab_close(cx, event) {
180                ButtonEvent::Down => {
181                    self._close_anim_rect = self._bg_area.get_rect(cx);
182                    self.animator.play_anim(cx, self.anim_close(cx));
183                    return TabEvent::Closing;
184                },
185                _ => ()
186            }
187        }
188        
189        match event.hits(cx, self._bg_area, HitOpt::default()) {
190            Event::Animate(ae) => {
191                // its playing the term anim, run a redraw
192                if self.animator.term_anim_playing() {
193                    self.animator.calc_float(cx, Self::tab_closing(), ae.time);
194                    cx.redraw_child_area(self._bg_area);
195                }
196                else {
197                    self.animator.calc_area(cx, self._bg_area, ae.time);
198                    self.animator.calc_area(cx, self._text_area, ae.time);
199                }
200            },
201            Event::AnimEnded(_ae) => {
202                if self.animator.term_anim_playing() {
203                    return TabEvent::Close;
204                }
205                else {
206                    self.animator.end();
207                }
208            },
209            Event::FingerDown(_fe) => {
210                if self.animator.term_anim_playing() {
211                    return TabEvent::None
212                }
213                cx.set_down_mouse_cursor(MouseCursor::Hand);
214                self._is_down = true;
215                self._is_drag = false;
216                self._is_selected = true;
217                self._is_focussed = true;
218                self.animator.play_anim(cx, self.anim_down(cx));
219                return TabEvent::Select;
220            },
221            Event::FingerHover(fe) => {
222                cx.set_hover_mouse_cursor(MouseCursor::Hand);
223                match fe.hover_state {
224                    HoverState::In => {
225                        if self._is_down {
226                            self.animator.play_anim(cx, self.anim_down(cx));
227                        }
228                        else {
229                            self.animator.play_anim(cx, self.anim_over(cx));
230                        }
231                    },
232                    HoverState::Out => {
233                        self.animator.play_anim(cx, self.anim_default(cx));
234                    },
235                    _ => ()
236                }
237            },
238            Event::FingerUp(fe) => {
239                self._is_down = false;
240                
241                if fe.is_over {
242                    if !fe.is_touch {
243                        self.animator.play_anim(cx, self.anim_over(cx));
244                    }
245                    else {
246                        self.animator.play_anim(cx, self.anim_default(cx));
247                    }
248                }
249                else {
250                    self.animator.play_anim(cx, self.anim_default(cx));
251                }
252                if self._is_drag {
253                    self._is_drag = false;
254                    return TabEvent::DragEnd(fe);
255                }
256            },
257            Event::FingerMove(fe) => {
258                if !self._is_drag {
259                    if fe.move_distance() > 10. {
260                        //cx.set_down_mouse_cursor(MouseCursor::Hidden);
261                        self._is_drag = true;
262                    }
263                }
264                if self._is_drag {
265                    return TabEvent::DragMove(fe);
266                }
267                //self.animator.play_anim(cx, self.animator.default.clone());
268            },
269            _ => ()
270        };
271        TabEvent::None
272    }
273    
274    pub fn get_tab_rect(&mut self, cx: &Cx) -> Rect {
275        self._bg_area.get_rect(cx)
276    }
277    
278    pub fn begin_tab(&mut self, cx: &mut Cx) -> Result<(), ()> {
279        // pull the bg color from our animation system, uses 'default' value otherwise
280        self.bg.shader = Self::shader_bg().get(cx);
281        self.bg.z = self.z;
282        self.bg.color = self.animator.last_color(cx, Quad::instance_color());
283        
284        // check if we are closing
285        if self.animator.term_anim_playing() {
286            // so so BUT how would we draw this thing with its own clipping
287            let bg_inst = self.bg.draw_quad(
288                cx,
289                Walk::wh(
290                    Width::Fix(self._close_anim_rect.w * self.animator.last_float(cx, Self::tab_closing())),
291                    Height::Fix(self._close_anim_rect.h),
292                )
293            );
294            bg_inst.push_last_color(cx, &self.animator, Self::instance_border_color());
295            self._bg_area = bg_inst.into();
296            self.animator.set_area(cx, self._bg_area);
297            return Err(())
298        }
299        else {
300            let layout = if let Some(abs_origin) = self.abs_origin {
301                Layout {abs_origin: Some(abs_origin), ..Self::layout_bg().get(cx)}
302            }
303            else {
304                Self::layout_bg().get(cx)
305            };
306            let bg_inst = self.bg.begin_quad(cx, layout);
307            bg_inst.push_last_color(cx, &self.animator, Self::instance_border_color());
308            if self.is_closeable {
309                self.tab_close.draw_tab_close(cx);
310                cx.turtle_align_y();
311            }
312            // push the 2 vars we added to bg shader
313            self.text.z = self.z;
314            self.text.text_style = Self::text_style_title().get(cx);
315            self.text.color = self.animator.last_color(cx, Text::instance_color());
316            self._text_area = self.text.draw_text(cx, &self.label);
317            cx.turtle_align_y();
318            self._bg_inst = Some(bg_inst);
319            
320            return Ok(())
321        }
322    }
323    
324    pub fn end_tab(&mut self, cx: &mut Cx) {
325        if let Some(bg_inst) = self._bg_inst.take() {
326            self._bg_area = self.bg.end_quad(cx, &bg_inst);
327            self.animator.set_area(cx, self._bg_area); // if our area changed, update animation
328        }
329    }
330    
331    pub fn draw_tab(&mut self, cx: &mut Cx) {
332        if self.begin_tab(cx).is_err() {return};
333        self.end_tab(cx);
334    }
335    
336}