1pub mod tab_label;
9
10use iced_core::{
11 Alignment, Background, Border, Clipboard, Color, Element, Event, Font, Layout, Length, Padding,
12 Pixels, Point, Rectangle, Shadow, Shell, Size, Widget,
13 alignment::{self, Vertical},
14 layout::{Limits, Node},
15 mouse::{self, Cursor},
16 renderer, touch,
17 widget::Tree,
18 window,
19};
20use iced_widget::{
21 Column, Row, Text,
22 text::{self, LineHeight, Wrapping},
23};
24
25use std::marker::PhantomData;
26
27pub use crate::style::{
28 Status, StyleFn,
29 tab_bar::{self, Catalog, Style},
30};
31use crate::{ICED_AW_FONT, iced_aw_font::advanced_text::cancel};
32pub use tab_label::TabLabel;
33
34const DEFAULT_ICON_SIZE: f32 = 16.0;
36const DEFAULT_TEXT_SIZE: f32 = 16.0;
38const DEFAULT_CLOSE_SIZE: f32 = 16.0;
40const DEFAULT_PADDING: Padding = Padding::new(5.0);
42const DEFAULT_SPACING: Pixels = Pixels::ZERO;
44
45#[allow(missing_debug_implementations)]
72pub struct TabBar<'a, Message, TabId, Theme = iced_widget::Theme, Renderer = iced_widget::Renderer>
73where
74 Renderer: renderer::Renderer + iced_core::text::Renderer,
75 Theme: Catalog,
76 TabId: Eq + Clone,
77{
78 active_tab: usize,
80 tab_labels: Vec<TabLabel>,
82 tab_indices: Vec<TabId>,
84 tab_statuses: Vec<(Option<Status>, Option<bool>)>,
86 on_select: Box<dyn Fn(TabId) -> Message>,
88 on_close: Option<Box<dyn Fn(TabId) -> Message>>,
90 width: Length,
92 tab_width: Length,
94 height: Length,
96 max_height: f32,
98 icon_size: f32,
100 text_size: f32,
102 close_size: f32,
104 padding: Padding,
106 spacing: Pixels,
108 font: Option<Font>,
110 text_font: Option<Font>,
112 class: <Theme as Catalog>::Class<'a>,
114 position: Position,
116 #[allow(clippy::missing_docs_in_private_items)]
117 _renderer: PhantomData<Renderer>,
118}
119
120#[derive(Clone, Copy, Default)]
121pub enum Position {
123 Top,
125 Right,
127 Bottom,
129 #[default]
130 Left,
132}
133
134impl<'a, Message, TabId, Theme, Renderer> TabBar<'a, Message, TabId, Theme, Renderer>
135where
136 Renderer: renderer::Renderer + iced_core::text::Renderer<Font = iced_core::Font>,
137 Theme: Catalog,
138 TabId: Eq + Clone,
139{
140 pub fn new<F>(on_select: F) -> Self
148 where
149 F: 'static + Fn(TabId) -> Message,
150 {
151 Self::with_tab_labels(Vec::new(), on_select)
152 }
153
154 pub fn with_tab_labels<F>(tab_labels: Vec<(TabId, TabLabel)>, on_select: F) -> Self
162 where
163 F: 'static + Fn(TabId) -> Message,
164 {
165 Self {
166 active_tab: 0,
167 tab_indices: tab_labels.iter().map(|(id, _)| id.clone()).collect(),
168 tab_statuses: tab_labels.iter().map(|_| (None, None)).collect(),
169 tab_labels: tab_labels.into_iter().map(|(_, label)| label).collect(),
170 on_select: Box::new(on_select),
171 on_close: None,
172 width: Length::Fill,
173 tab_width: Length::Fill,
174 height: Length::Shrink,
175 max_height: u32::MAX as f32,
176 icon_size: DEFAULT_ICON_SIZE,
177 text_size: DEFAULT_TEXT_SIZE,
178 close_size: DEFAULT_CLOSE_SIZE,
179 padding: DEFAULT_PADDING,
180 spacing: DEFAULT_SPACING,
181 font: None,
182 text_font: None,
183 class: <Theme as Catalog>::default(),
184 position: Position::default(),
185 _renderer: PhantomData,
186 }
187 }
188
189 #[must_use]
192 pub fn close_size(mut self, close_size: f32) -> Self {
193 self.close_size = close_size;
194 self
195 }
196
197 #[must_use]
199 pub fn get_active_tab_id(&self) -> Option<&TabId> {
200 self.tab_indices.get(self.active_tab)
201 }
202
203 #[must_use]
205 pub fn get_active_tab_idx(&self) -> usize {
206 self.active_tab
207 }
208
209 #[must_use]
211 pub fn get_height(&self) -> Length {
212 self.height
213 }
214
215 #[must_use]
217 pub fn get_width(&self) -> Length {
218 self.width
219 }
220
221 #[must_use]
223 pub fn height(mut self, height: impl Into<Length>) -> Self {
224 self.height = height.into();
225 self
226 }
227
228 #[must_use]
231 pub fn icon_font(mut self, font: Font) -> Self {
232 self.font = Some(font);
233 self
234 }
235
236 #[must_use]
238 pub fn icon_size(mut self, icon_size: f32) -> Self {
239 self.icon_size = icon_size;
240 self
241 }
242
243 #[must_use]
245 pub fn max_height(mut self, max_height: f32) -> Self {
246 self.max_height = max_height;
247 self
248 }
249
250 #[must_use]
255 pub fn on_close<F>(mut self, on_close: F) -> Self
256 where
257 F: 'static + Fn(TabId) -> Message,
258 {
259 self.on_close = Some(Box::new(on_close));
260 self
261 }
262
263 #[must_use]
265 pub fn padding(mut self, padding: impl Into<Padding>) -> Self {
266 self.padding = padding.into();
267 self
268 }
269
270 #[must_use]
272 pub fn push(mut self, id: TabId, tab_label: TabLabel) -> Self {
273 self.tab_labels.push(tab_label);
274 self.tab_indices.push(id);
275 self.tab_statuses.push((None, None));
276 self
277 }
278
279 #[must_use]
281 pub fn size(&self) -> usize {
282 self.tab_indices.len()
283 }
284
285 #[must_use]
287 pub fn spacing(mut self, spacing: impl Into<Pixels>) -> Self {
288 self.spacing = spacing.into();
289 self
290 }
291
292 #[must_use]
295 pub fn text_font(mut self, text_font: Font) -> Self {
296 self.text_font = Some(text_font);
297 self
298 }
299
300 #[must_use]
302 pub fn text_size(mut self, text_size: f32) -> Self {
303 self.text_size = text_size;
304 self
305 }
306
307 #[must_use]
309 pub fn tab_width(mut self, width: Length) -> Self {
310 self.tab_width = width;
311 self
312 }
313
314 #[must_use]
316 pub fn set_active_tab(mut self, active_tab: &TabId) -> Self {
317 self.active_tab = self
318 .tab_indices
319 .iter()
320 .position(|id| id == active_tab)
321 .map_or(0, |a| a);
322 self
323 }
324
325 #[must_use]
326 pub fn set_position(mut self, position: Position) -> Self {
328 self.position = position;
329 self
330 }
331
332 #[must_use]
334 pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self
335 where
336 <Theme as Catalog>::Class<'a>: From<StyleFn<'a, Theme, Style>>,
337 {
338 self.class = (Box::new(style) as StyleFn<'a, Theme, Style>).into();
339 self
340 }
341
342 #[must_use]
344 pub fn class(mut self, class: impl Into<<Theme as Catalog>::Class<'a>>) -> Self {
345 self.class = class.into();
346 self
347 }
348
349 #[must_use]
351 pub fn width(mut self, width: impl Into<Length>) -> Self {
352 self.width = width.into();
353 self
354 }
355}
356
357impl<Message, TabId, Theme, Renderer> Widget<Message, Theme, Renderer>
358 for TabBar<'_, Message, TabId, Theme, Renderer>
359where
360 Renderer: renderer::Renderer + iced_core::text::Renderer<Font = iced_core::Font>,
361 Theme: Catalog + text::Catalog,
362 TabId: Eq + Clone,
363{
364 fn size(&self) -> Size<Length> {
365 Size::new(self.width, self.height)
366 }
367
368 fn layout(&mut self, tree: &mut Tree, renderer: &Renderer, limits: &Limits) -> Node {
369 fn layout_icon<Theme, Renderer>(
370 icon: &char,
371 size: f32,
372 font: Option<Font>,
373 ) -> Text<'_, Theme, Renderer>
374 where
375 Renderer: iced_core::text::Renderer,
376 Renderer::Font: From<Font>,
377 Theme: iced_widget::text::Catalog,
378 {
379 Text::<Theme, Renderer>::new(icon.to_string())
380 .size(size)
381 .font(font.unwrap_or_default())
382 .align_x(alignment::Horizontal::Center)
383 .align_y(alignment::Vertical::Center)
384 .shaping(iced_core::text::Shaping::Advanced)
385 .width(Length::Shrink)
386 }
387
388 fn layout_text<Theme, Renderer>(
389 text: &str,
390 size: f32,
391 font: Option<Font>,
392 ) -> Text<'_, Theme, Renderer>
393 where
394 Renderer: iced_core::text::Renderer,
395 Renderer::Font: From<Font>,
396 Theme: iced_widget::text::Catalog,
397 {
398 Text::<Theme, Renderer>::new(text)
399 .size(size)
400 .font(font.unwrap_or_default())
401 .align_x(alignment::Horizontal::Center)
402 .shaping(text::Shaping::Advanced)
403 .width(Length::Shrink)
404 }
405
406 let row = self
407 .tab_labels
408 .iter()
409 .fold(Row::<Message, Theme, Renderer>::new(), |row, tab_label| {
410 let mut label_row = Row::new()
411 .push(
412 match tab_label {
413 TabLabel::Icon(icon) => Column::new()
414 .align_x(Alignment::Center)
415 .push(layout_icon(icon, self.icon_size + 1.0, self.font)),
416
417 TabLabel::Text(text) => Column::new()
418 .padding(5.0)
419 .align_x(Alignment::Center)
420 .push(layout_text(text, self.text_size + 1.0, self.text_font)),
421
422 TabLabel::IconText(icon, text) => {
423 let mut column = Column::new().align_x(Alignment::Center);
424
425 match self.position {
426 Position::Top => {
427 column = column
428 .push(layout_icon(
429 icon,
430 self.icon_size + 1.0,
431 self.font,
432 ))
433 .push(layout_text(
434 text,
435 self.text_size + 1.0,
436 self.text_font,
437 ));
438 }
439 Position::Right => {
440 column = column.push(
441 Row::new()
442 .align_y(Alignment::Center)
443 .push(layout_text(
444 text,
445 self.text_size + 1.0,
446 self.text_font,
447 ))
448 .push(layout_icon(
449 icon,
450 self.icon_size + 1.0,
451 self.font,
452 )),
453 );
454 }
455 Position::Left => {
456 column = column.push(
457 Row::new()
458 .align_y(Alignment::Center)
459 .push(layout_icon(
460 icon,
461 self.icon_size + 1.0,
462 self.font,
463 ))
464 .push(layout_text(
465 text,
466 self.text_size + 1.0,
467 self.text_font,
468 )),
469 );
470 }
471 Position::Bottom => {
472 column = column
473 .height(Length::Fill)
474 .push(layout_text(
475 text,
476 self.text_size + 1.0,
477 self.text_font,
478 ))
479 .push(layout_icon(
480 icon,
481 self.icon_size + 1.0,
482 self.font,
483 ));
484 }
485 }
486
487 column
488 }
489 }
490 .width(self.tab_width)
491 .height(self.height),
492 )
493 .align_y(Alignment::Center)
494 .padding(self.padding)
495 .width(self.tab_width);
496
497 if self.on_close.is_some() {
498 label_row = label_row.push(
499 Row::new()
500 .width(Length::Fixed(self.close_size * 1.3 + 1.0))
501 .height(Length::Fixed(self.close_size * 1.3 + 1.0))
502 .align_y(Alignment::Center),
503 );
504 }
505
506 row.push(label_row)
507 })
508 .width(self.width)
509 .height(self.height)
510 .spacing(self.spacing)
511 .align_y(Alignment::Center);
512
513 let mut element: Element<Message, Theme, Renderer> = Element::new(row);
514 let tab_tree = if let Some(child_tree) = tree.children.get_mut(0) {
515 child_tree.diff(element.as_widget_mut());
516 child_tree
517 } else {
518 let child_tree = Tree::new(element.as_widget());
519 tree.children.insert(0, child_tree);
520 &mut tree.children[0]
521 };
522
523 element
524 .as_widget_mut()
525 .layout(tab_tree, renderer, &limits.loose())
526 }
527
528 fn update(
529 &mut self,
530 _state: &mut Tree,
531 event: &Event,
532 layout: Layout<'_>,
533 cursor: Cursor,
534 _renderer: &Renderer,
535 _clipboard: &mut dyn Clipboard,
536 shell: &mut Shell<'_, Message>,
537 _viewport: &Rectangle,
538 ) {
539 match event {
540 Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
541 | Event::Touch(touch::Event::FingerPressed { .. }) => {
542 if cursor
543 .position()
544 .is_some_and(|pos| layout.bounds().contains(pos))
545 {
546 let tabs_map: Vec<bool> = layout
547 .children()
548 .map(|layout| {
549 cursor
550 .position()
551 .is_some_and(|pos| layout.bounds().contains(pos))
552 })
553 .collect();
554
555 if let Some(new_selected) = tabs_map.iter().position(|b| *b) {
556 shell.publish(
557 self.on_close
558 .as_ref()
559 .filter(|_on_close| {
560 let tab_layout = layout.children().nth(new_selected).expect("widget: Layout should have a tab layout at the selected index");
561 let cross_layout = tab_layout.children().nth(1).expect("widget: Layout should have a close layout");
562
563 cursor.position().is_some_and(|pos| cross_layout.bounds().contains(pos))
564 })
565 .map_or_else(
566 || (self.on_select)(self.tab_indices[new_selected].clone()),
567 |on_close| (on_close)(self.tab_indices[new_selected].clone()),
568 ),
569 );
570 shell.capture_event();
571 }
572 }
573 }
574 _ => {}
575 }
576
577 let mut request_redraw = false;
578 let children = layout.children();
579 for ((i, _tab), layout) in self.tab_labels.iter().enumerate().zip(children) {
580 let active_idx = self.get_active_tab_idx();
581 let tab_status = self.tab_statuses.get_mut(i).expect("Should have a status.");
582
583 let current_status = if cursor.is_over(layout.bounds()) {
584 Status::Hovered
585 } else if i == active_idx {
586 Status::Active
587 } else {
588 Status::Disabled
589 };
590
591 let mut is_cross_hovered = None;
592 let mut children = layout.children();
593 if let Some(cross_layout) = children.next_back() {
594 is_cross_hovered = Some(cursor.is_over(cross_layout.bounds()));
595 }
596
597 if let Event::Window(window::Event::RedrawRequested(_now)) = event {
598 *tab_status = (Some(current_status), is_cross_hovered);
599 } else if tab_status.0.is_some_and(|status| status != current_status)
600 || tab_status.1 != is_cross_hovered
601 {
602 request_redraw = true;
603 }
604 }
605
606 if request_redraw {
607 shell.request_redraw();
608 }
609 }
610
611 fn mouse_interaction(
612 &self,
613 _state: &Tree,
614 layout: Layout<'_>,
615 cursor: Cursor,
616 _viewport: &Rectangle,
617 _renderer: &Renderer,
618 ) -> mouse::Interaction {
619 let children = layout.children();
620 let mut mouse_interaction = mouse::Interaction::default();
621
622 for layout in children {
623 let is_mouse_over = cursor
624 .position()
625 .is_some_and(|pos| layout.bounds().contains(pos));
626 let new_mouse_interaction = if is_mouse_over {
627 mouse::Interaction::Pointer
628 } else {
629 mouse::Interaction::default()
630 };
631
632 if new_mouse_interaction > mouse_interaction {
633 mouse_interaction = new_mouse_interaction;
634 }
635 }
636
637 mouse_interaction
638 }
639
640 fn draw(
641 &self,
642 _state: &Tree,
643 renderer: &mut Renderer,
644 theme: &Theme,
645 _style: &renderer::Style,
646 layout: Layout<'_>,
647 cursor: Cursor,
648 viewport: &Rectangle,
649 ) {
650 let bounds = layout.bounds();
651 let children = layout.children();
652 let is_mouse_over = cursor.position().is_some_and(|pos| bounds.contains(pos));
653 let style_sheet = if is_mouse_over {
654 tab_bar::Catalog::style(theme, &self.class, Status::Hovered)
655 } else {
656 tab_bar::Catalog::style(theme, &self.class, Status::Disabled)
657 };
658
659 if bounds.intersects(viewport) {
660 renderer.fill_quad(
661 renderer::Quad {
662 bounds,
663 border: Border {
664 radius: (0.0).into(),
665 width: style_sheet.border_width,
666 color: style_sheet.border_color.unwrap_or(Color::TRANSPARENT),
667 },
668 shadow: Shadow::default(),
669 ..renderer::Quad::default()
670 },
671 style_sheet
672 .background
673 .unwrap_or_else(|| Color::TRANSPARENT.into()),
674 );
675 }
676
677 for ((i, tab), layout) in self.tab_labels.iter().enumerate().zip(children) {
678 let tab_status = self.tab_statuses.get(i).expect("Should have a status.");
679
680 draw_tab(
681 renderer,
682 tab,
683 tab_status,
684 layout,
685 self.position,
686 theme,
687 &self.class,
688 cursor,
689 (self.font.unwrap_or(ICED_AW_FONT), self.icon_size),
690 (self.text_font.unwrap_or_default(), self.text_size),
691 self.close_size,
692 viewport,
693 );
694 }
695 }
696}
697
698#[allow(
700 clippy::borrowed_box,
701 clippy::too_many_lines,
702 clippy::too_many_arguments
703)]
704fn draw_tab<Theme, Renderer>(
705 renderer: &mut Renderer,
706 tab: &TabLabel,
707 tab_status: &(Option<Status>, Option<bool>),
708 layout: Layout<'_>,
709 position: Position,
710 theme: &Theme,
711 class: &<Theme as Catalog>::Class<'_>,
712 _cursor: Cursor,
713 icon_data: (Font, f32),
714 text_data: (Font, f32),
715 close_size: f32,
716 viewport: &Rectangle,
717) where
718 Renderer: renderer::Renderer + iced_core::text::Renderer<Font = iced_core::Font>,
719 Theme: Catalog + text::Catalog,
720{
721 fn icon_bound_rectangle(item: Option<Layout<'_>>) -> Rectangle {
722 item.expect("Graphics: Layout should have an icons layout for an IconText")
723 .bounds()
724 }
725
726 fn text_bound_rectangle(item: Option<Layout<'_>>) -> Rectangle {
727 item.expect("Graphics: Layout should have an texts layout for an IconText")
728 .bounds()
729 }
730
731 let bounds = layout.bounds();
732
733 let style = tab_bar::Catalog::style(theme, class, tab_status.0.unwrap_or(Status::Disabled));
734
735 let mut children = layout.children();
736 let label_layout = children
737 .next()
738 .expect("Graphics: Layout should have a label layout");
739 let mut label_layout_children = label_layout.children();
740
741 if bounds.intersects(viewport) {
742 renderer.fill_quad(
743 renderer::Quad {
744 bounds,
745 border: Border {
746 radius: (0.0).into(),
747 width: style.tab_label_border_width,
748 color: style.tab_label_border_color,
749 },
750 shadow: Shadow::default(),
751 ..renderer::Quad::default()
752 },
753 style.tab_label_background,
754 );
755 }
756
757 match tab {
758 TabLabel::Icon(icon) => {
759 let icon_bounds = icon_bound_rectangle(label_layout_children.next());
760
761 renderer.fill_text(
762 iced_core::text::Text {
763 content: icon.to_string(),
764 bounds: Size::new(icon_bounds.width, icon_bounds.height),
765 size: Pixels(icon_data.1),
766 font: icon_data.0,
767 align_x: text::Alignment::Center,
768 align_y: Vertical::Center,
769 line_height: LineHeight::Relative(1.3),
770 shaping: iced_core::text::Shaping::Advanced,
771 wrapping: Wrapping::default(),
772 },
773 Point::new(icon_bounds.center_x(), icon_bounds.center_y()),
774 style.icon_color,
775 icon_bounds,
776 );
777 }
778
779 TabLabel::Text(text) => {
780 let text_bounds = text_bound_rectangle(label_layout_children.next());
781
782 renderer.fill_text(
783 iced_core::text::Text {
784 content: text.clone(),
785 bounds: Size::new(text_bounds.width, text_bounds.height),
786 size: Pixels(text_data.1),
787 font: text_data.0,
788 align_x: text::Alignment::Center,
789 align_y: Vertical::Center,
790 line_height: LineHeight::Relative(1.3),
791 shaping: iced_core::text::Shaping::Advanced,
792 wrapping: Wrapping::default(),
793 },
794 Point::new(text_bounds.center_x(), text_bounds.center_y()),
795 style.text_color,
796 text_bounds,
797 );
798 }
799 TabLabel::IconText(icon, text) => {
800 let icon_bounds: Rectangle;
801 let text_bounds: Rectangle;
802
803 match position {
804 Position::Top => {
805 icon_bounds = icon_bound_rectangle(label_layout_children.next());
806 text_bounds = text_bound_rectangle(label_layout_children.next());
807 }
808 Position::Right => {
809 let mut row_childern = label_layout_children
810 .next()
811 .expect("Graphics: Right Layout should have have a row with one child")
812 .children();
813 text_bounds = text_bound_rectangle(row_childern.next());
814 icon_bounds = icon_bound_rectangle(row_childern.next());
815 }
816 Position::Left => {
817 let mut row_childern = label_layout_children
818 .next()
819 .expect("Graphics: Left Layout should have have a row with one child")
820 .children();
821 icon_bounds = icon_bound_rectangle(row_childern.next());
822 text_bounds = text_bound_rectangle(row_childern.next());
823 }
824 Position::Bottom => {
825 text_bounds = text_bound_rectangle(label_layout_children.next());
826 icon_bounds = icon_bound_rectangle(label_layout_children.next());
827 }
828 }
829
830 renderer.fill_text(
831 iced_core::text::Text {
832 content: icon.to_string(),
833 bounds: Size::new(icon_bounds.width, icon_bounds.height),
834 size: Pixels(icon_data.1),
835 font: icon_data.0,
836 align_x: text::Alignment::Center,
837 align_y: Vertical::Center,
838 line_height: LineHeight::Relative(1.3),
839 shaping: iced_core::text::Shaping::Advanced,
840 wrapping: Wrapping::default(),
841 },
842 Point::new(icon_bounds.center_x(), icon_bounds.center_y()),
843 style.icon_color,
844 icon_bounds,
845 );
846
847 renderer.fill_text(
848 iced_core::text::Text {
849 content: text.clone(),
850 bounds: Size::new(text_bounds.width, text_bounds.height),
851 size: Pixels(text_data.1),
852 font: text_data.0,
853 align_x: text::Alignment::Center,
854 align_y: Vertical::Center,
855 line_height: LineHeight::Relative(1.3),
856 shaping: iced_core::text::Shaping::Advanced,
857 wrapping: Wrapping::default(),
858 },
859 Point::new(text_bounds.center_x(), text_bounds.center_y()),
860 style.text_color,
861 text_bounds,
862 );
863 }
864 }
865
866 if let Some(cross_layout) = children.next() {
867 let cross_bounds = cross_layout.bounds();
868 let is_mouse_over_cross = tab_status.1.unwrap_or(false);
869
870 let (content, font, shaping) = cancel();
871
872 renderer.fill_text(
873 iced_core::text::Text {
874 content,
875 bounds: Size::new(cross_bounds.width, cross_bounds.height),
876 size: Pixels(close_size + if is_mouse_over_cross { 1.0 } else { 0.0 }),
877 font,
878 align_x: text::Alignment::Center,
879 align_y: Vertical::Center,
880 line_height: LineHeight::Relative(1.3),
881 shaping,
882 wrapping: Wrapping::default(),
883 },
884 Point::new(cross_bounds.center_x(), cross_bounds.center_y()),
885 style.text_color,
886 cross_bounds,
887 );
888
889 if is_mouse_over_cross && cross_bounds.intersects(viewport) {
890 renderer.fill_quad(
891 renderer::Quad {
892 bounds: cross_bounds,
893 border: Border {
894 radius: style.icon_border_radius,
895 width: style.border_width,
896 color: style.border_color.unwrap_or(Color::TRANSPARENT),
897 },
898 shadow: Shadow::default(),
899 ..renderer::Quad::default()
900 },
901 style
902 .icon_background
903 .unwrap_or(Background::Color(Color::TRANSPARENT)),
904 );
905 }
906 }
907}
908
909impl<'a, Message, TabId, Theme, Renderer> From<TabBar<'a, Message, TabId, Theme, Renderer>>
910 for Element<'a, Message, Theme, Renderer>
911where
912 Renderer: 'a + renderer::Renderer + iced_core::text::Renderer<Font = iced_core::Font>,
913 Theme: 'a + Catalog + text::Catalog,
914 Message: 'a,
915 TabId: 'a + Eq + Clone,
916{
917 fn from(tab_bar: TabBar<'a, Message, TabId, Theme, Renderer>) -> Self {
918 Element::new(tab_bar)
919 }
920}