makepad_widgets/
tab.rs

1use {
2    crate::{
3        tab_close_button::{TabCloseButtonAction, TabCloseButton},
4        makepad_draw::*,
5    }
6};
7
8live_design!{
9    link widgets;
10    use link::theme::*;
11    use link::widgets::*;
12    use makepad_draw::shader::std::*;
13    
14    pub TabBase = {{Tab}} {}
15    pub Tab = <TabBase> {
16        width: Fit, height: Fit, //Fixed((THEME_TAB_HEIGHT)),
17        
18        align: {x: 0.0, y: 0.5}
19        padding: <THEME_MSPACE_3> { top: (THEME_SPACE_2 * 1.2) }
20        margin: {right: (THEME_SPACE_1), top: (THEME_SPACE_FACTOR)}
21        
22        close_button: <TabCloseButton> {}
23
24        draw_text: {
25            text_style: <THEME_FONT_REGULAR> {
26                font_size: (THEME_FONT_SIZE_P)
27            }
28            instance hover: 0.0
29            instance active: 0.0
30
31            uniform color: (THEME_COLOR_LABEL_INNER_INACTIVE)
32            uniform color_hover: (THEME_COLOR_LABEL_INNER_HOVER)
33            uniform color_active: (THEME_COLOR_LABEL_INNER_ACTIVE)
34
35            fn get_color(self) -> vec4 {
36                return mix(
37                    mix(
38                        self.color,
39                        self.color_active,
40                        self.active
41                    ),
42                    self.color_hover,
43                    self.hover
44                )
45            }
46        }
47        
48        draw_bg: {
49            instance hover: float
50            instance active: float
51
52            uniform border_size: 1.
53            uniform border_radius: (THEME_CORNER_RADIUS)
54            uniform color_dither: 1.
55
56            uniform color: (THEME_COLOR_D_HIDDEN)
57            uniform color_hover: (THEME_COLOR_D_2)
58            uniform color_active: (THEME_COLOR_BG_APP)
59
60            uniform border_color_1: (THEME_COLOR_U_HIDDEN)
61            uniform border_color_1_hover: (THEME_COLOR_U_HIDDEN)
62            uniform border_color_1_active: (THEME_COLOR_BEVEL_OUTSET_1)
63
64            uniform border_color_2: (THEME_COLOR_D_HIDDEN)
65            uniform border_color_2_hover: (THEME_COLOR_D_HIDDEN)
66            uniform border_color_2_active: (THEME_COLOR_D_HIDDEN)
67
68            uniform overlap_fix: 1.
69              
70            fn pixel(self) -> vec4 {
71                let sdf = Sdf2d::viewport(self.pos * self.rect_size);
72                let dither = Math::random_2d(self.pos.xy) * 0.04 * self.color_dither;
73
74                let border_sz_uv = vec2(
75                    self.border_size / self.rect_size.x,
76                    self.border_size / self.rect_size.y
77                )
78
79                let scale_factor_border = vec2(
80                    self.rect_size.x / self.rect_size.x,
81                    self.rect_size.y / self.rect_size.y
82                );
83
84                let gradient_border = vec2(
85                    self.pos.x * scale_factor_border.x + dither,
86                    self.pos.y * scale_factor_border.y + dither
87                )
88
89                let sz_inner_px = vec2(
90                    self.rect_size.x - self.border_size * 2.,
91                    self.rect_size.y - self.border_size * 2.
92                );
93
94                let scale_factor_fill = vec2(
95                    self.rect_size.x / sz_inner_px.x,
96                    self.rect_size.y / sz_inner_px.y
97                );
98
99                let gradient_fill = vec2(
100                    self.pos.x * scale_factor_fill.x - border_sz_uv.x * 2. + dither,
101                    self.pos.y * scale_factor_fill.y - border_sz_uv.y * 2. + dither
102                )
103
104                sdf.box_y(
105                    self.border_size,
106                    self.border_size,
107                    self.rect_size.x - self.border_size * 2.,
108                    self.rect_size.y,
109                    self.border_radius,
110                    self.border_size * 0.5
111                )
112
113                sdf.fill_keep(
114                    mix(
115                        mix(
116                            self.color,
117                            self.color_hover,
118                            self.hover
119                        ),
120                        self.color_active,
121                        self.active
122                    )
123                )
124
125                sdf.stroke(
126                    mix(
127                        mix(
128                            mix(self.border_color_1, self.border_color_2, gradient_border.y),
129                            mix(self.border_color_1_hover, self.border_color_2_hover, gradient_border.y),
130                            self.hover
131                        ),
132                        mix(self.border_color_1_active, self.border_color_2_active, gradient_border.y),
133                        self.active
134                    ), self.border_size
135                )
136
137                return sdf.result
138            }
139        }
140        
141        animator: {
142            hover = {
143                default: off
144                off = {
145                    from: {all: Forward {duration: 0.2}}
146                    apply: {
147                        draw_bg: {hover: 0.0}
148                        draw_text: {hover: 0.0}
149                    }
150                }
151                
152                on = {
153                    cursor: Hand,
154                    from: {all: Forward {duration: 0.1}}
155                    apply: {
156                        draw_bg: {hover: [{time: 0.0, value: 1.0}]}
157                        draw_text: {hover: [{time: 0.0, value: 1.0}]}
158                    }
159                }
160            }
161            
162            active = {
163                default: off
164                off = {
165                    from: {all: Forward {duration: 0.3}}
166                    apply: {
167                        close_button: {draw_button: {active: 0.0}}
168                        draw_bg: {active: 0.0}
169                        draw_text: {active: 0.0}
170                    }
171                }
172                
173                on = {
174                    from: {all: Snap}
175                    apply: {
176                        close_button: {draw_button: {active: 1.0}}
177                        draw_bg: {active: 1.0}
178                        draw_text: {active: 1.0}
179                    }
180                }
181            }
182        }
183    }
184
185    pub TabFlat = <Tab> {
186        margin: 0.
187        padding: <THEME_MSPACE_3> { }
188
189        draw_bg: {
190            border_size: 1.
191            border_radius: 0.5
192            color_dither: 1.
193
194            color: (THEME_COLOR_D_HIDDEN)
195            color_hover: (THEME_COLOR_D_HIDDEN)
196            color_active: (THEME_COLOR_FG_APP)
197
198            border_color_1: (THEME_COLOR_U_HIDDEN)
199            border_color_1_hover: (THEME_COLOR_U_HIDDEN)
200            border_color_1_active: (THEME_COLOR_U_HIDDEN)
201
202            border_color_2: (THEME_COLOR_D_HIDDEN)
203            border_color_2_hover: (THEME_COLOR_D_HIDDEN)
204            border_color_2_active: (THEME_COLOR_D_HIDDEN)
205            
206            overlap_fix: 0.
207        }
208    }
209
210    pub TabGradientX = <Tab> {
211        draw_bg: {
212            border_size: 1.
213            border_radius: (THEME_CORNER_RADIUS)
214            color_dither: 1.
215
216            uniform color_1: (THEME_COLOR_D_HIDDEN)
217            uniform color_1_hover: (THEME_COLOR_D_2)
218            uniform color_1_active: (THEME_COLOR_BG_APP)
219
220            uniform color_2: (THEME_COLOR_D_HIDDEN)
221            uniform color_2_hover: (THEME_COLOR_D_2)
222            uniform color_2_active: (THEME_COLOR_BG_APP)
223
224            border_color_1: (THEME_COLOR_U_HIDDEN)
225            border_color_1_hover: (THEME_COLOR_U_HIDDEN)
226            border_color_1_active: (THEME_COLOR_BEVEL_OUTSET_1)
227
228            border_color_2: (THEME_COLOR_D_HIDDEN)
229            border_color_2_hover: (THEME_COLOR_D_HIDDEN)
230            border_color_2_active: (THEME_COLOR_D_HIDDEN)
231              
232            fn pixel(self) -> vec4 {
233                let sdf = Sdf2d::viewport(self.pos * self.rect_size);
234                let dither = Math::random_2d(self.pos.xy) * 0.04 * self.color_dither;
235
236                let border_sz_uv = vec2(
237                    self.border_size / self.rect_size.x,
238                    self.border_size / self.rect_size.y
239                )
240
241                let scale_factor_border = vec2(
242                    self.rect_size.x / self.rect_size.x,
243                    self.rect_size.y / self.rect_size.y
244                );
245
246                let gradient_border = vec2(
247                    self.pos.x * scale_factor_border.x + dither,
248                    self.pos.y * scale_factor_border.y + dither
249                )
250
251                let sz_inner_px = vec2(
252                    self.rect_size.x - self.border_size * 2.,
253                    self.rect_size.y - self.border_size * 2.
254                );
255
256                let scale_factor_fill = vec2(
257                    self.rect_size.x / sz_inner_px.x,
258                    self.rect_size.y / sz_inner_px.y
259                );
260
261                let gradient_fill = vec2(
262                    self.pos.x * scale_factor_fill.x - border_sz_uv.x * 2. + dither,
263                    self.pos.y * scale_factor_fill.y - border_sz_uv.y * 2. + dither
264                )
265
266                sdf.box_y(
267                    self.border_size,
268                    self.border_size,
269                    self.rect_size.x - self.border_size * 2.,
270                    self.rect_size.y,
271                    self.border_radius,
272                    self.border_size * 0.5
273                )
274
275                sdf.fill_keep(
276                    mix(
277                        mix(
278                            mix(self.color_1, self.color_2, gradient_fill.x),
279                            mix(self.color_1_hover, self.color_2_hover, grydient_fill.x),
280                            self.hover
281                        ),
282                        mix(self.color_1_active, self.color_2_active, gradient_fill.x),
283                        self.active
284                    )
285                )
286
287                sdf.stroke(
288                    mix(
289                        mix(
290                            mix(self.border_color_1, self.border_color_2, gradient_border.y),
291                            mix(self.border_color_1_hover, self.border_color_2_hover, gradient_border.y),
292                            self.hover
293                        ),
294                        mix(self.border_color_1_active, self.border_color_2_active, gradient_border.y),
295                        self.active
296                    ), self.border_size
297                )
298
299                return sdf.result
300            }
301        }
302    }
303
304    pub TabGradientY = <TabGradientX> {
305        draw_bg: {
306            border_size: (THEME_BEVELING)
307            border_radius: (THEME_CORNER_RADIUS)
308            color_dither: 1.
309
310            color_1: (THEME_COLOR_D_HIDDEN)
311            color_1_hover: (THEME_COLOR_U_HIDDEN)
312            color_1_active: (THEME_COLOR_BG_APP)
313
314            color_2: (THEME_COLOR_D_HIDDEN)
315            color_2_hover: (THEME_COLOR_U_HIDDEN)
316            color_2_active: (THEME_COLOR_BG_APP)
317
318            border_color_1: (THEME_COLOR_U_HIDDEN)
319            border_color_1_hover: (THEME_COLOR_U_HIDDEN)
320            border_color_1_active: (THEME_COLOR_BEVEL_OUTSET_1)
321
322            border_color_2: (THEME_COLOR_D_HIDDEN)
323            border_color_2_hover: (THEME_COLOR_D_HIDDEN)
324            border_color_2_active: (THEME_COLOR_D_HIDDEN)
325              
326            fn pixel(self) -> vec4 {
327                let sdf = Sdf2d::viewport(self.pos * self.rect_size);
328                let dither = Math::random_2d(self.pos.xy) * 0.04 * self.color_dither;
329
330                let border_sz_uv = vec2(
331                    self.border_size / self.rect_size.x,
332                    self.border_size / self.rect_size.y
333                )
334
335                let scale_factor_border = vec2(
336                    self.rect_size.x / self.rect_size.x,
337                    self.rect_size.y / self.rect_size.y
338                );
339
340                let gradient_border = vec2(
341                    self.pos.x * scale_factor_border.x + dither,
342                    self.pos.y * scale_factor_border.y + dither
343                )
344
345                let sz_inner_px = vec2(
346                    self.rect_size.x - self.border_size * 2.,
347                    self.rect_size.y - self.border_size * 2.
348                );
349
350                let scale_factor_fill = vec2(
351                    self.rect_size.x / sz_inner_px.x,
352                    self.rect_size.y / sz_inner_px.y
353                );
354
355                let gradient_fill = vec2(
356                    self.pos.x * scale_factor_fill.x - border_sz_uv.x * 2. + dither,
357                    self.pos.y * scale_factor_fill.y - border_sz_uv.y * 2. + dither
358                )
359
360                sdf.box_y(
361                    self.border_size,
362                    self.border_size,
363                    self.rect_size.x - self.border_size * 2.,
364                    self.rect_size.y,
365                    self.border_radius,
366                    self.border_size * 0.5
367                )
368
369                sdf.fill_keep(
370                    mix(
371                        mix(
372                            mix(self.color_1, self.color_2, gradient_fill.y),
373                            mix(self.color_1_hover, self.color_2_hover, gradient_fill.y),
374                            self.hover
375                        ),
376                        mix(self.color_1_active, self.color_2_active, gradient_fill.y),
377                        self.active
378                    )
379                )
380
381                sdf.stroke(
382                    mix(
383                        mix(
384                            mix(self.border_color_1, self.border_color_2, gradient_border.y),
385                            mix(self.border_color_1_hover, self.border_color_2_hover, gradient_border.y),
386                            self.hover
387                        ),
388                        mix(self.border_color_1_active, self.border_color_2_active, gradient_border.y),
389                        self.active
390                    ), self.border_size
391                )
392
393                return sdf.result
394            }
395        }
396
397    }
398    
399}
400
401#[derive(Live, LiveHook, LiveRegister)]
402pub struct Tab {
403    #[rust] is_active: bool,
404    #[rust] is_dragging: bool,
405    
406    #[live] draw_bg: DrawQuad,
407    #[live] draw_icon: DrawIcon,
408    #[live] draw_text: DrawText,
409    #[live] icon_walk: Walk,
410    //#[live] draw_drag: DrawColor,
411    
412    #[animator] animator: Animator,
413    
414    #[live] close_button: TabCloseButton,
415    
416    // height: f32,
417    #[live] closeable: bool,
418    #[live] hover: f32,
419    #[live] active: f32,
420    
421    #[live(10.0)] min_drag_dist: f64,
422    
423    #[walk] walk: Walk,
424    #[layout] layout: Layout,
425    
426}
427
428pub enum TabAction {
429    WasPressed,
430    CloseWasPressed,
431    ShouldTabStartDrag,
432    ShouldTabStopDrag
433    //DragHit(DragHit)
434}
435
436
437impl Tab {
438    
439    pub fn is_active(&self) -> bool {
440        self.is_active
441    }
442    
443    pub fn set_is_active(&mut self, cx: &mut Cx, is_active: bool, animate: Animate) {
444        self.is_active = is_active;
445        self.animator_toggle(cx, is_active, animate, id!(active.on), id!(active.off));
446    }
447    
448    pub fn draw(&mut self, cx: &mut Cx2d, name: &str) {
449        //self.bg_quad.color = self.color(self.is_active);
450        self.draw_bg.begin(cx, self.walk, self.layout);
451        //self.name_text.color = self.name_color(self.is_active);
452        if self.closeable{
453            self.close_button.draw(cx);
454        }
455        
456        self.draw_icon.draw_walk(cx, self.icon_walk);
457        //cx.turtle_align_y();
458        self.draw_text.draw_walk(cx, Walk::fit(), Align::default(), name);
459        //cx.turtle_align_y();
460        self.draw_bg.end(cx);
461        
462        //if self.is_dragged {
463        //    self.draw_drag.draw_abs(cx, self.draw_bg.area().get_clipped_rect(cx));
464        //}
465    }
466    
467    pub fn area(&self) -> Area {
468        self.draw_bg.area()
469    }
470    
471    pub fn handle_event_with(
472        &mut self,
473        cx: &mut Cx,
474        event: &Event,
475        dispatch_action: &mut dyn FnMut(&mut Cx, TabAction),
476    ) {
477        self.animator_handle_event(cx, event);
478        
479        let mut block_hover_out = false;
480        match self.close_button.handle_event(cx, event) {
481            TabCloseButtonAction::WasPressed if self.closeable => dispatch_action(cx, TabAction::CloseWasPressed),
482            TabCloseButtonAction::HoverIn => block_hover_out = true,
483            TabCloseButtonAction::HoverOut => self.animator_play(cx, id!(hover.off)),
484            _ => ()
485        };
486        
487        match event.hits(cx, self.draw_bg.area()) {
488            Hit::FingerHoverIn(_) => {
489                self.animator_play(cx, id!(hover.on));
490            }
491            Hit::FingerHoverOut(_) => if !block_hover_out {
492                self.animator_play(cx, id!(hover.off));
493            }
494            Hit::FingerMove(e) => {
495                if !self.is_dragging && (e.abs - e.abs_start).length() > self.min_drag_dist {
496                    self.is_dragging = true;
497                    dispatch_action(cx, TabAction::ShouldTabStartDrag);
498                }
499            }
500            Hit::FingerUp(_) => {
501                if self.is_dragging {
502                    dispatch_action(cx, TabAction::ShouldTabStopDrag);
503                    self.is_dragging = false;
504                }
505            }
506            Hit::FingerDown(fde) => {
507                // A primary click/touch selects the tab, but a middle click closes it.
508                if fde.is_primary_hit() {
509                    dispatch_action(cx, TabAction::WasPressed);
510                } else if self.closeable && fde.mouse_button().is_some_and(|b| b.is_middle()) {
511                    dispatch_action(cx, TabAction::CloseWasPressed);
512                }
513            }
514            _ => {}
515        }
516    }
517}
518