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 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 }});
274 }
275 }
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 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 }