iced_aw/widget/
tab_bar.rs

1//! Displays a [`TabBar`] to select the content to be displayed.
2//!
3//! You have to manage the logic to show the contend by yourself or you may want
4//! to use the [`Tabs`](super::tabs::Tabs) widget instead.
5//!
6//! *This API requires the following crate features to be activated: `tab_bar`*
7
8pub 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
34/// The default icon size.
35const DEFAULT_ICON_SIZE: f32 = 16.0;
36/// The default text size.
37const DEFAULT_TEXT_SIZE: f32 = 16.0;
38/// The default size of the close icon.
39const DEFAULT_CLOSE_SIZE: f32 = 16.0;
40/// The default padding between the tabs.
41const DEFAULT_PADDING: Padding = Padding::new(5.0);
42/// The default spacing around the tabs.
43const DEFAULT_SPACING: Pixels = Pixels::ZERO;
44
45/// A tab bar to show tabs.
46///
47/// # Example
48/// ```ignore
49/// # use iced_aw::{TabLabel, TabBar};
50/// #
51/// #[derive(Debug, Clone)]
52/// enum Message {
53///     TabSelected(TabId),
54/// }
55///
56/// #[derive(PartialEq, Hash)]
57/// enum TabId {
58///    One,
59///    Two,
60///    Three,
61/// }
62///
63/// let tab_bar = TabBar::new(
64///     Message::TabSelected,
65/// )
66/// .push(TabId::One, TabLabel::Text(String::from("One")))
67/// .push(TabId::Two, TabLabel::Text(String::from("Two")))
68/// .push(TabId::Three, TabLabel::Text(String::from("Three")))
69/// .set_active_tab(&TabId::One);
70/// ```
71#[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    /// The index of the currently active tab.
79    active_tab: usize,
80    /// The vector containing the labels of the tabs.
81    tab_labels: Vec<TabLabel>,
82    /// The vector containing the indices of the tabs.
83    tab_indices: Vec<TabId>,
84    /// The statuses of the [`TabLabel`] and cross
85    tab_statuses: Vec<(Option<Status>, Option<bool>)>,
86    /// The function that produces the message when a tab is selected.
87    on_select: Box<dyn Fn(TabId) -> Message>,
88    /// The function that produces the message when the close icon was pressed.
89    on_close: Option<Box<dyn Fn(TabId) -> Message>>,
90    /// The width of the [`TabBar`].
91    width: Length,
92    /// The width of the tabs of the [`TabBar`].
93    tab_width: Length,
94    /// The width of the [`TabBar`].
95    height: Length,
96    /// The maximum height of the [`TabBar`].
97    max_height: f32,
98    /// The icon size.
99    icon_size: f32,
100    /// The text size.
101    text_size: f32,
102    /// The size of the close icon.
103    close_size: f32,
104    /// The padding of the tabs of the [`TabBar`].
105    padding: Padding,
106    /// The spacing of the tabs of the [`TabBar`].
107    spacing: Pixels,
108    /// The optional icon font of the [`TabBar`].
109    font: Option<Font>,
110    /// The optional text font of the [`TabBar`].
111    text_font: Option<Font>,
112    /// The style of the [`TabBar`].
113    class: <Theme as Catalog>::Class<'a>,
114    /// Where the icon is placed relative to text
115    position: Position,
116    #[allow(clippy::missing_docs_in_private_items)]
117    _renderer: PhantomData<Renderer>,
118}
119
120#[derive(Clone, Copy, Default)]
121/// The [`Position`] of the icon relative to text, this enum is only relative if [`TabLabel::IconText`] is used.
122pub enum Position {
123    /// Icon is placed above of the text.
124    Top,
125    /// Icon is placed right of the text.
126    Right,
127    /// Icon is placed below of the text.
128    Bottom,
129    #[default]
130    /// Icon is placed left of the text, the default.
131    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    /// Creates a new [`TabBar`] with the index of the selected tab and a specified
141    /// message which will be send when a tab is selected by the user.
142    ///
143    /// It expects:
144    ///     * the index of the currently active tab.
145    ///     * the function that will be called if a tab is selected by the user.
146    ///         It takes the index of the selected tab.
147    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    /// Similar to [`new`](Self::new) but with a given Vector of the [`TabLabel`]s.
155    ///
156    /// It expects:
157    ///     * the index of the currently active tab.
158    ///     * a vector containing the [`TabLabel`]s of the [`TabBar`].
159    ///     * the function that will be called if a tab is selected by the user.
160    ///         It takes the index of the selected tab.
161    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    /// Sets the size of the close icon of the
190    /// [`TabLabel`]s of the [`TabBar`].
191    #[must_use]
192    pub fn close_size(mut self, close_size: f32) -> Self {
193        self.close_size = close_size;
194        self
195    }
196
197    /// Gets the id of the currently active tab on the [`TabBar`].
198    #[must_use]
199    pub fn get_active_tab_id(&self) -> Option<&TabId> {
200        self.tab_indices.get(self.active_tab)
201    }
202
203    /// Gets the index of the currently active tab on the [`TabBar`].
204    #[must_use]
205    pub fn get_active_tab_idx(&self) -> usize {
206        self.active_tab
207    }
208
209    /// Gets the width of the [`TabBar`].
210    #[must_use]
211    pub fn get_height(&self) -> Length {
212        self.height
213    }
214
215    /// Gets the width of the [`TabBar`].
216    #[must_use]
217    pub fn get_width(&self) -> Length {
218        self.width
219    }
220
221    /// Sets the height of the [`TabBar`].
222    #[must_use]
223    pub fn height(mut self, height: impl Into<Length>) -> Self {
224        self.height = height.into();
225        self
226    }
227
228    /// Sets the font of the icons of the
229    /// [`TabLabel`]s of the [`TabBar`].
230    #[must_use]
231    pub fn icon_font(mut self, font: Font) -> Self {
232        self.font = Some(font);
233        self
234    }
235
236    /// Sets the icon size of the [`TabLabel`]s of the [`TabBar`].
237    #[must_use]
238    pub fn icon_size(mut self, icon_size: f32) -> Self {
239        self.icon_size = icon_size;
240        self
241    }
242
243    /// Sets the maximum height of the [`TabBar`].
244    #[must_use]
245    pub fn max_height(mut self, max_height: f32) -> Self {
246        self.max_height = max_height;
247        self
248    }
249
250    /// Sets the message that will be produced when the close icon of a tab
251    /// on the [`TabBar`] is pressed.
252    ///
253    /// Setting this enables the drawing of a close icon on the tabs.
254    #[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    /// Sets the padding of the tabs of the [`TabBar`].
264    #[must_use]
265    pub fn padding(mut self, padding: impl Into<Padding>) -> Self {
266        self.padding = padding.into();
267        self
268    }
269
270    /// Pushes a [`TabLabel`] to the [`TabBar`].
271    #[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    /// Gets the amount of tabs on the [`TabBar`].
280    #[must_use]
281    pub fn size(&self) -> usize {
282        self.tab_indices.len()
283    }
284
285    /// Sets the spacing between the tabs of the [`TabBar`].
286    #[must_use]
287    pub fn spacing(mut self, spacing: impl Into<Pixels>) -> Self {
288        self.spacing = spacing.into();
289        self
290    }
291
292    /// Sets the font of the text of the
293    /// [`TabLabel`]s of the [`TabBar`].
294    #[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    /// Sets the text size of the [`TabLabel`]s of the [`TabBar`].
301    #[must_use]
302    pub fn text_size(mut self, text_size: f32) -> Self {
303        self.text_size = text_size;
304        self
305    }
306
307    /// Sets the width of a tab on the [`TabBar`].
308    #[must_use]
309    pub fn tab_width(mut self, width: Length) -> Self {
310        self.tab_width = width;
311        self
312    }
313
314    /// Sets up the active tab on the [`TabBar`].
315    #[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    /// Sets the [`Position`] of the Icon next to Text, Only used in [`TabLabel::IconText`]
327    pub fn set_position(mut self, position: Position) -> Self {
328        self.position = position;
329        self
330    }
331
332    /// Sets the style of the [`TabBar`].
333    #[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    /// Sets the class of the input of the [`TabBar`].
343    #[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    /// Sets the width of the [`TabBar`].
350    #[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/// Draws a tab.
699#[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}