makepad_widgets/
tab_bar.rs

1use {
2    crate::{
3        makepad_derive_widget::*,
4        makepad_draw::*,
5        widget::*,
6        scroll_bars::ScrollBars,
7        tab::{TabAction, Tab},
8    },
9};
10
11live_design!{
12    link widgets;
13    use link::theme::*;
14    use link::widgets::*;
15    use makepad_draw::shader::std::*;
16    
17    pub TabBarBase = {{TabBar}} {}
18    pub TabBar = <TabBarBase> {
19        CloseableTab = <Tab> {closeable: true}
20        PermanentTab = <Tab> {closeable: false}
21
22        width: Fill, height: (THEME_TAB_HEIGHT)
23        margin: 0.
24
25        draw_drag: {
26            draw_depth: 10
27            color: (THEME_COLOR_BG_CONTAINER)
28        }
29
30        draw_fill: {
31            uniform color_dither: 1.0
32            uniform border_radius: (THEME_CORNER_RADIUS)
33            color: (THEME_COLOR_BG_APP * 0.9);
34
35            fn pixel(self) -> vec4 {
36                let sdf = Sdf2d::viewport(self.pos * self.rect_size)
37                let dither = Math::random_2d(self.pos.xy) * 0.04 * self.color_dither;
38
39                sdf.box_all(
40                    1.,
41                    1.,
42                    self.rect_size.x - 2.,
43                    self.rect_size.y - 2.,
44                    0.5,
45                    self.border_radius,
46                    0.0,
47                    0.5
48                )
49
50                sdf.fill(self.color);
51                return sdf.result
52            }
53        }
54        
55        draw_bg: {
56            uniform color_dither: 1.0
57            uniform border_radius: (THEME_CORNER_RADIUS)
58            color: (THEME_COLOR_BG_APP * 0.9);
59
60            fn pixel(self) -> vec4 {
61                let sdf = Sdf2d::viewport(self.pos * self.rect_size)
62                let dither = Math::random_2d(self.pos.xy) * 0.04 * self.color_dither;
63
64                sdf.box(
65                    1.,
66                    1.,
67                    self.rect_size.x - 2.0,
68                    self.rect_size.y - 2.0,
69                    self.border_radius
70                )
71
72                sdf.fill(self.color);
73                return sdf.result
74            }
75        }
76
77        scroll_bars: <ScrollBarsTabs> {
78            show_scroll_x: true
79            show_scroll_y: false
80            scroll_bar_x: {
81                draw_bg: {
82                    color_hover: #fff6
83                    size: 5.0
84                }
85                bar_size: 7.5
86                use_vertical_finger_scroll: true
87            }
88        }
89    }
90    
91    pub TabBarFlat = <TabBar> {
92        height: (THEME_TAB_FLAT_HEIGHT)
93        CloseableTab = <TabFlat> {closeable: true}
94        PermanentTab = <TabFlat> {closeable: false}
95    }
96
97    pub TabBarGradientX = <TabBar> {
98        CloseableTab = <TabGradientX> {closeable: true}
99        PermanentTab = <TabGradientX> {closeable: false}
100
101        draw_bg: {
102            uniform color_dither: 1.0
103            uniform border_radius: (THEME_CORNER_RADIUS)
104            uniform color_1: (THEME_COLOR_BG_APP * 0.8);
105            uniform color_2: (THEME_COLOR_BG_APP * 1.2);
106
107            fn pixel(self) -> vec4 {
108                let sdf = Sdf2d::viewport(self.pos * self.rect_size)
109                let dither = Math::random_2d(self.pos.xy) * 0.04 * self.color_dither;
110
111                sdf.box(
112                    1.,
113                    1.,
114                    self.rect_size.x - 2.0,
115                    self.rect_size.y - 2.0,
116                    self.border_radius
117                )
118
119                sdf.fill(mix(self.color_1, self.color_2, self.pos.x + dither));
120
121                return sdf.result
122            }
123        }
124    }
125
126    pub TabBarGradientY = <TabBar> {
127        CloseableTab = <TabGradientY> {closeable: true}
128        PermanentTab = <TabGradientY> {closeable: false}
129        draw_bg: {
130            uniform color_dither: 1.0
131            uniform border_radius: 0.
132            uniform border_size: (THEME_BEVELING)
133            uniform color_1: (THEME_COLOR_BG_APP * 0.9);
134            uniform color_2: #282828;
135
136            fn pixel(self) -> vec4 {
137                let sdf = Sdf2d::viewport(self.pos * self.rect_size)
138                let dither = Math::random_2d(self.pos.xy) * 0.04 * self.color_dither;
139
140                sdf.rect(
141                    1.,
142                    1.,
143                    self.rect_size.x - 1.5,
144                    self.rect_size.y - 1.5
145                )
146
147                sdf.fill_keep(mix(self.color_1, self.color_2, pow(self.pos.y, 7.5) + dither));
148
149                sdf.stroke(
150                    mix(#fff0, (THEME_COLOR_BEVEL_OUTSET_1), pow(self.pos.y, 80.)), self.border_size
151                )
152                return sdf.result
153            }
154        }
155
156        draw_fill: {
157            uniform color_dither: 1.0
158            uniform border_radius: (THEME_CORNER_RADIUS)
159            uniform color_1: (THEME_COLOR_BG_APP * 0.9);
160            uniform color_2: #282828;
161
162            fn pixel(self) -> vec4 {
163                let sdf = Sdf2d::viewport(self.pos * self.rect_size)
164                let dither = Math::random_2d(self.pos.xy) * 0.04 * self.color_dither;
165
166                sdf.box_all(
167                    1.,
168                    1.,
169                    self.rect_size.x - 2.,
170                    self.rect_size.y - 2.,
171                    0.5,
172                    self.border_radius,
173                    0.0,
174                    0.5
175                )
176
177                sdf.fill(mix(self.color_1, self.color_2, pow(self.pos.y, 7.5) + dither));
178                return sdf.result
179            }
180        }
181    }
182
183}
184
185#[derive(Live, Widget)]
186pub struct TabBar {
187    
188    #[redraw] #[live] scroll_bars: ScrollBars,
189    #[live] draw_drag: DrawColor,
190
191    #[live] draw_bg: DrawColor,
192    #[live] draw_fill: DrawColor,
193    #[walk] walk: Walk,
194    
195    #[rust] draw_state: DrawStateWrap<()>,
196    #[rust] view_area: Area,
197
198    #[rust] tab_order: Vec<LiveId>,
199    
200    #[rust] is_dragged: bool,
201    
202    #[rust] templates: ComponentMap<LiveId, LivePtr>,
203    #[rust] tabs: ComponentMap<LiveId, (Tab, LiveId)>,
204    
205    #[rust] active_tab: Option<usize>,
206    
207    #[rust] active_tab_id: Option<LiveId>,
208    #[rust] next_active_tab_id: Option<LiveId>,
209}
210
211impl LiveHook for TabBar {
212    /*fn after_apply(&mut self, cx: &mut Cx, apply: &mut Apply, index: usize, nodes: &[LiveNode]) {
213        if let Some(index) = nodes.child_by_name(index, live_id!(tab).as_field()) {
214            for (tab, templl) in self.tabs.values_mut() {
215                tab.apply(cx, apply, index, nodes);
216            }
217        }
218        self.view_area.redraw(cx);
219    }*/
220    
221    // hook the apply flow to collect our templates and apply to instanced childnodes
222    fn apply_value_instance(&mut self, cx: &mut Cx, apply: &mut Apply, index: usize, nodes: &[LiveNode]) -> usize {
223        if nodes[index].is_instance_prop() {
224            if let Some(live_ptr) = apply.from.to_live_ptr(cx, index){
225                let id = nodes[index].id;
226                self.templates.insert(id, live_ptr);
227                for (_, (node, templ_id)) in self.tabs.iter_mut() {
228                    if *templ_id == id {
229                        node.apply(cx, apply, index, nodes);
230                    }
231                }
232            }
233        }
234        else {
235            cx.apply_error_no_matching_field(live_error_origin!(), index, nodes);
236        }
237        nodes.skip_node(index)
238    }
239}
240
241impl Widget for TabBar{
242    
243    fn handle_event(
244        &mut self,
245        cx: &mut Cx,
246        event: &Event,
247        scope: &mut Scope
248    ){
249        let uid = self.widget_uid();
250        if self.scroll_bars.handle_event(cx, event, scope).len()>0{
251            self.view_area.redraw(cx);
252        };
253                
254        if let Some(tab_id) = self.next_active_tab_id.take() {
255            cx.widget_action(uid, &scope.path, TabBarAction::TabWasPressed(tab_id));
256        }
257        for (tab_id, (tab,_)) in self.tabs.iter_mut() {
258            tab.handle_event_with(cx, event, &mut | cx, action | match action {
259                TabAction::WasPressed => {
260                    cx.widget_action(uid, &scope.path, TabBarAction::TabWasPressed(*tab_id));
261                }
262                TabAction::CloseWasPressed => {
263                    cx.widget_action(uid, &scope.path, TabBarAction::TabCloseWasPressed(*tab_id));
264                }
265                TabAction::ShouldTabStartDrag=>{
266                    cx.widget_action(uid, &scope.path, TabBarAction::ShouldTabStartDrag(*tab_id));
267                }
268                TabAction::ShouldTabStopDrag=>{
269                }/*
270                TabAction::DragHit(hit)=>{
271                    dispatch_action(cx, TabBarAction::DragHitTab(hit, *tab_id));
272                }*/
273            });
274        }
275        /*
276        match event.drag_hits(cx, self.scroll_bars.area()) {
277            DragHit::NoHit=>(),
278            hit=>dispatch_action(cx, TabBarAction::DragHitTabBar(hit))
279        }*/
280        /*
281        match event.drag_hits(cx, self.scroll_view.area()) {
282            DragHit::Drag(f) => match f.state {
283                DragState::In => {
284                    self.is_dragged = true;
285                    self.redraw(cx);
286                    f.action.set(DragAction::Copy);
287                }
288                DragState::Out => {
289                    self.is_dragged = false;
290                    self.redraw(cx);
291                }
292                DragState::Over => match event {
293                    Event::Drag(event) => {
294                        event.action.set(DragAction::Copy);
295                    }
296                    _ => panic!(),
297                },
298            },
299            DragHit::Drop(f) => {
300                self.is_dragged = false;
301                self.redraw(cx);
302                dispatch_action(cx, TabBarAction::ReceivedDraggedItem(f.dragged_item.clone()))
303            }
304            _ => {}
305        }*/
306    }
307    
308    fn draw_walk(&mut self, cx: &mut Cx2d, _scope: &mut Scope, _walk: Walk) -> DrawStep {
309        if self.draw_state.begin(cx, ()) {
310            return DrawStep::make_step()
311        }
312        if let Some(()) = self.draw_state.get() {
313            self.draw_state.end();
314        }
315        DrawStep::done()
316    }
317}
318
319
320impl TabBar {
321    pub fn begin(&mut self, cx: &mut Cx2d, active_tab: Option<usize>, walk:Walk) {
322        self.active_tab = active_tab;
323        //if active_tab.is_some(){
324        //    self.active_tab_id = None
325        // }
326        self.scroll_bars.begin(cx, walk, Layout::flow_right());
327        self.draw_bg.draw_abs(cx, cx.turtle().unscrolled_rect());
328        self.tab_order.clear();
329    }
330    
331    pub fn end(&mut self, cx: &mut Cx2d) {
332        if self.is_dragged {
333            self.draw_drag.draw_walk(
334                cx,
335                Walk {
336                    width: Size::Fill,
337                    height: Size::Fill,
338                    ..Walk::default()
339                },
340            );
341        }
342        self.tabs.retain_visible();
343        self.draw_fill.draw_walk(cx, Walk::size(Size::Fill, Size::Fill));
344        self.scroll_bars.end(cx);
345    }
346    
347    pub fn draw_tab(&mut self, cx: &mut Cx2d, tab_id: LiveId, name: &str, template:LiveId) {
348        if let Some(active_tab) = self.active_tab {
349            let tab_order_len = self.tab_order.len();
350            let tab = self.get_or_create_tab(cx, tab_id, template);
351            if tab_order_len == active_tab {
352                tab.set_is_active(cx, true, Animate::No);
353            }
354            else {
355                tab.set_is_active(cx, false, Animate::No);
356            }
357            tab.draw(cx, name);
358            if tab_order_len == active_tab {
359                self.active_tab_id = Some(tab_id);
360            }
361            self.tab_order.push(tab_id);
362        }
363        else {
364            self.tab_order.push(tab_id);
365            let tab = self.get_or_create_tab(cx, tab_id, template);
366            tab.draw(cx, name);
367        }
368    }
369    
370    fn get_or_create_tab(&mut self, cx: &mut Cx, tab_id: LiveId, template:LiveId) -> &mut Tab {
371        let ptr = self.templates.get(&template).cloned();
372        let (tab,_) = self.tabs.get_or_insert(cx, tab_id, | cx | {
373            (Tab::new_from_ptr(cx, ptr),template)
374        });
375        tab
376    }
377    
378    pub fn active_tab_id(&self) -> Option<LiveId> {
379        self.active_tab_id
380    }
381    
382    pub fn set_active_tab_id(&mut self, cx: &mut Cx, tab_id: Option<LiveId>, animate: Animate) {
383        if self.active_tab_id == tab_id {
384            return;
385        }
386        if let Some(tab_id) = self.active_tab_id {
387            let (tab,_) = &mut self.tabs[tab_id];
388            tab.set_is_active(cx, false, animate);
389        }
390        self.active_tab_id = tab_id;
391        if let Some(tab_id) = self.active_tab_id {
392            let (tab,_) = &mut self.tabs[tab_id];
393            tab.set_is_active(cx, true, animate);
394        }
395        self.view_area.redraw(cx);
396    }
397    
398    
399    pub fn set_next_active_tab(&mut self, cx: &mut Cx, tab_id: LiveId, animate: Animate) {
400        if let Some(index) = self.tab_order.iter().position( | id | *id == tab_id) {
401            if self.active_tab_id != Some(tab_id) {
402                self.next_active_tab_id = self.active_tab_id;
403            }
404            else if index >0 {
405                self.next_active_tab_id = Some(self.tab_order[index - 1]);
406                self.set_active_tab_id(cx, self.next_active_tab_id, animate);
407            }
408            else if index + 1 < self.tab_order.len() {
409                self.next_active_tab_id = Some(self.tab_order[index + 1]);
410                self.set_active_tab_id(cx, self.next_active_tab_id, animate);
411            }
412            else {
413                self.set_active_tab_id(cx, None, animate);
414            }
415            cx.new_next_frame();
416        }
417        
418    }
419    pub fn redraw(&mut self, cx: &mut Cx) {
420        self.view_area.redraw(cx)
421    }
422    
423    pub fn is_over_tab(&self, cx:&Cx, abs:DVec2)->Option<(LiveId,Rect)>{
424        for (tab_id, (tab,_)) in self.tabs.iter() {
425            let rect = tab.area().rect(cx);
426            if rect.contains(abs){
427                return Some((*tab_id, rect))
428            }
429        }
430        None
431    }
432    
433    pub fn is_over_tab_bar(&self, cx:&Cx, abs:DVec2)->Option<Rect>{
434        let rect = self.scroll_bars.area().rect(cx);
435        if rect.contains(abs){
436            return Some(rect)
437        }
438        None
439    }
440    
441
442}
443
444#[derive(Clone, Debug, DefaultNone)]
445pub enum TabBarAction {
446    TabWasPressed(LiveId),
447    ShouldTabStartDrag(LiveId),
448    TabCloseWasPressed(LiveId),
449    None
450    //DragHitTab(DragHit, LiveId),
451    //DragHitTabBar(DragHit)
452}