iced_aw/widget/
tabs.rs

1//! Displays a [`Tabs`] widget to select the content to be displayed.
2//!
3//! This is a wrapper around the [`TabBar`] widget.
4//! Unlike the [`TabBar`] widget it will also handle
5//! the content of the tabs.
6//!
7//! *This API requires the following crate features to be activated: tabs*
8
9pub mod tab_bar_position;
10pub use crate::tab_bar::Position;
11use crate::{
12    TabLabel,
13    style::{
14        Status, StyleFn,
15        tab_bar::{Catalog, Style},
16    },
17    widget::tab_bar::TabBar,
18};
19
20use iced_core::{
21    Clipboard, Element, Event, Font, Layout, Length, Padding, Pixels, Point, Rectangle, Shell,
22    Size, Vector, Widget,
23    layout::{Limits, Node},
24    mouse::{self, Cursor},
25    overlay, renderer,
26    widget::{
27        Operation, Tree,
28        tree::{State, Tag},
29    },
30};
31use iced_widget::{Row, text};
32
33pub use tab_bar_position::TabBarPosition;
34
35/// A [`Tabs`] widget for showing a [`TabBar`]
36/// along with the tab's content.
37///
38/// # Example
39/// ```ignore
40/// # use iced_aw::{TabLabel, tabs::Tabs};
41/// # use iced_widget::Text;
42/// #
43/// #[derive(Debug, Clone)]
44/// enum Message {
45///     TabSelected(TabId),
46/// }
47///
48/// #[derive(Debug, Clone)]
49/// enum TabId {
50///    One,
51///    Two,
52///    Three,
53/// }
54///
55/// let tabs = Tabs::new(Message::TabSelected)
56/// .push(TabId::One, TabLabel::Text(String::from("One")), Text::new(String::from("One")))
57/// .push(TabId::Two, TabLabel::Text(String::from("Two")), Text::new(String::from("Two")))
58/// .push(TabId::Three, TabLabel::Text(String::from("Three")), Text::new(String::from("Three")))
59/// .set_active_tab(&TabId::Two);Theme
60/// ```
61///
62#[allow(missing_debug_implementations)]
63pub struct Tabs<'a, Message, TabId, Theme = iced_widget::Theme, Renderer = iced_widget::Renderer>
64where
65    Renderer: 'a + renderer::Renderer + iced_core::text::Renderer,
66    Theme: Catalog,
67    TabId: Eq + Clone,
68{
69    /// The [`TabBar`](crate::widget::TabBar) of the [`Tabs`].
70    tab_bar: TabBar<'a, Message, TabId, Theme, Renderer>,
71    /// The vector containing the content of the tabs.
72    children: Vec<Element<'a, Message, Theme, Renderer>>,
73    /// The vector containing the indices of the tabs.
74    indices: Vec<TabId>,
75    /// The position of the [`TabBar`](crate::widget::TabBar).
76    tab_bar_position: TabBarPosition,
77    /// The position of the [`TabBar`](crate::widget::TabBar) Icon.
78    tab_icon_position: Position,
79    /// the width of the [`Tabs`].
80    width: Length,
81    /// The height of the [`Tabs`].
82    height: Length,
83}
84
85impl<'a, Message, TabId, Theme, Renderer> Tabs<'a, Message, TabId, Theme, Renderer>
86where
87    Renderer: 'a + renderer::Renderer + iced_core::text::Renderer<Font = Font>,
88    Theme: Catalog + text::Catalog,
89    TabId: Eq + Clone,
90{
91    /// Creates a new [`Tabs`] widget with the index of the selected tab and a
92    /// specified message which will be send when a tab is selected by the user.
93    ///
94    /// It expects:
95    ///     * the index of the currently active tab.
96    ///     * the function that will be called if a tab is selected by the user.
97    ///         It takes the index of the selected tab.
98    pub fn new<F>(on_select: F) -> Self
99    where
100        F: 'static + Fn(TabId) -> Message,
101    {
102        Self::new_with_tabs(Vec::new(), on_select)
103    }
104
105    /// Similar to `new` but with a given Vector of the
106    /// [`TabLabel`] along with the tab's content.
107    ///
108    /// It expects:
109    ///     * the index of the currently active tab.
110    ///     * a vector containing the [`TabLabel`]s along with the content
111    ///         [`Element`]s of the [`Tabs`].
112    ///     * the function that will be called if a tab is selected by the user.
113    ///         It takes the index of the selected tab.
114    pub fn new_with_tabs<F>(
115        tabs: impl IntoIterator<Item = (TabId, TabLabel, Element<'a, Message, Theme, Renderer>)>,
116        on_select: F,
117    ) -> Self
118    where
119        F: 'static + Fn(TabId) -> Message,
120    {
121        let tabs = tabs.into_iter();
122        let n_tabs = tabs.size_hint().0;
123
124        let mut tab_labels = Vec::with_capacity(n_tabs);
125        let mut elements = Vec::with_capacity(n_tabs);
126        let mut indices = Vec::with_capacity(n_tabs);
127
128        for (id, tab_label, element) in tabs {
129            tab_labels.push((id.clone(), tab_label));
130            indices.push(id);
131            elements.push(element);
132        }
133
134        Tabs {
135            tab_bar: TabBar::with_tab_labels(tab_labels, on_select),
136            children: elements,
137            indices,
138            tab_bar_position: TabBarPosition::Top,
139            tab_icon_position: Position::Left,
140            width: Length::Fill,
141            height: Length::Fill,
142        }
143    }
144
145    /// Sets the size of the close icon of the
146    /// [`TabLabel`] of the
147    /// [`TabBar`].
148    #[must_use]
149    pub fn close_size(mut self, close_size: f32) -> Self {
150        self.tab_bar = self.tab_bar.close_size(close_size);
151        self
152    }
153
154    /// Sets the Tabs Icon render Position
155    /// [`TabLabel`] of the
156    /// [`TabBar`].
157    #[must_use]
158    pub fn tab_icon_position(mut self, position: Position) -> Self {
159        self.tab_icon_position = position;
160        self
161    }
162
163    /// Sets the height of the [`Tabs`].
164    #[must_use]
165    pub fn height(mut self, height: impl Into<Length>) -> Self {
166        self.height = height.into();
167        self
168    }
169
170    /// Sets the font of the icons of the
171    /// [`TabLabel`]s of the
172    /// [`TabBar`].
173    #[must_use]
174    pub fn icon_font(mut self, font: Font) -> Self {
175        self.tab_bar = self.tab_bar.icon_font(font);
176        self
177    }
178
179    /// Sets the icon size of the [`TabLabel`] of the
180    /// [`TabBar`].
181    #[must_use]
182    pub fn icon_size(mut self, icon_size: f32) -> Self {
183        self.tab_bar = self.tab_bar.icon_size(icon_size);
184        self
185    }
186
187    /// Sets the message that will be produced when the close icon of a tab
188    /// on the [`TabBar`] is pressed.
189    ///
190    /// Setting this enables the drawing of a close icon on the tabs.
191    #[must_use]
192    pub fn on_close<F>(mut self, on_close: F) -> Self
193    where
194        F: 'static + Fn(TabId) -> Message,
195    {
196        self.tab_bar = self.tab_bar.on_close(on_close);
197        self
198    }
199
200    /// Pushes a [`TabLabel`] along with the tabs
201    /// content to the [`Tabs`].
202    #[must_use]
203    pub fn push<E>(mut self, id: TabId, tab_label: TabLabel, element: E) -> Self
204    where
205        E: Into<Element<'a, Message, Theme, Renderer>>,
206    {
207        self.tab_bar = self
208            .tab_bar
209            .push(id.clone(), tab_label)
210            .set_position(self.tab_icon_position);
211        self.children.push(element.into());
212        self.indices.push(id);
213        self
214    }
215
216    /// Sets the active tab of the [`Tabs`] using the ``TabId``.
217    #[must_use]
218    pub fn set_active_tab(mut self, id: &TabId) -> Self {
219        self.tab_bar = self.tab_bar.set_active_tab(id);
220        self
221    }
222
223    /// Sets the height of the [`TabBar`] of the [`Tabs`].
224    #[must_use]
225    pub fn tab_bar_height(mut self, height: Length) -> Self {
226        self.tab_bar = self.tab_bar.height(height);
227        self
228    }
229
230    /// Sets the maximum height of the [`TabBar`] of the [`Tabs`].
231    #[must_use]
232    pub fn tab_bar_max_height(mut self, max_height: f32) -> Self {
233        self.tab_bar = self.tab_bar.max_height(max_height);
234        self
235    }
236
237    /// Sets the width of the [`TabBar`] of the [`Tabs`].
238    #[must_use]
239    pub fn tab_bar_width(mut self, width: Length) -> Self {
240        self.tab_bar = self.tab_bar.width(width);
241        self
242    }
243
244    /// Sets the [`TabBarPosition`] of the [`TabBar`].
245    #[must_use]
246    pub fn tab_bar_position(mut self, position: TabBarPosition) -> Self {
247        self.tab_bar_position = position;
248        self
249    }
250
251    /// Sets the style of the [`TabBar`].
252    #[must_use]
253    pub fn tab_bar_style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self
254    where
255        <Theme as Catalog>::Class<'a>: From<StyleFn<'a, Theme, Style>>,
256    {
257        self.tab_bar = self.tab_bar.style(style);
258        self
259    }
260
261    /// Sets the padding of the tabs of the [`TabBar`].
262    #[must_use]
263    pub fn tab_label_padding(mut self, padding: impl Into<Padding>) -> Self {
264        self.tab_bar = self.tab_bar.padding(padding);
265        self
266    }
267
268    /// Sets the spacing between the tabs of the
269    /// [`TabBar`].
270    #[must_use]
271    pub fn tab_label_spacing(mut self, spacing: impl Into<Pixels>) -> Self {
272        self.tab_bar = self.tab_bar.spacing(spacing);
273        self
274    }
275
276    /// Sets the font of the text of the
277    /// [`TabLabel`]s of the
278    /// [`TabBar`].
279    #[must_use]
280    pub fn text_font(mut self, text_font: Font) -> Self {
281        self.tab_bar = self.tab_bar.text_font(text_font);
282        self
283    }
284
285    /// Sets the text size of the [`TabLabel`] of the
286    /// [`TabBar`].
287    #[must_use]
288    pub fn text_size(mut self, text_size: f32) -> Self {
289        self.tab_bar = self.tab_bar.text_size(text_size);
290        self
291    }
292
293    /// Sets the width of the [`Tabs`].
294    #[must_use]
295    pub fn width(mut self, width: impl Into<Length>) -> Self {
296        self.width = width.into();
297        self
298    }
299}
300
301impl<Message, TabId, Theme, Renderer> Widget<Message, Theme, Renderer>
302    for Tabs<'_, Message, TabId, Theme, Renderer>
303where
304    Renderer: renderer::Renderer + iced_core::text::Renderer<Font = Font>,
305    Theme: Catalog + text::Catalog,
306    TabId: Eq + Clone,
307{
308    fn children(&self) -> Vec<Tree> {
309        let tabs = Tree {
310            tag: Tag::stateless(),
311            state: State::None,
312            children: self.children.iter().map(Tree::new).collect(),
313        };
314
315        let bar = Tree {
316            tag: self.tab_bar.tag(),
317            state: self.tab_bar.state(),
318            children: self.tab_bar.children(),
319        };
320
321        vec![bar, tabs]
322    }
323
324    fn diff(&self, tree: &mut Tree) {
325        if tree.children.is_empty() {
326            tree.children = self.children();
327        }
328
329        if let Some(tabs) = tree.children.get_mut(1) {
330            tabs.diff_children(&self.children);
331        }
332    }
333
334    fn size(&self) -> Size<Length> {
335        Size::new(self.width, self.height)
336    }
337
338    fn layout(&mut self, tree: &mut Tree, renderer: &Renderer, limits: &Limits) -> Node {
339        let tab_bar_limits = limits.width(self.width).height(Length::Fill);
340        let mut tab_bar_node =
341            self.tab_bar
342                .layout(&mut tree.children[0], renderer, &tab_bar_limits);
343
344        let tab_content_limits = limits
345            .width(self.width)
346            .height(self.height)
347            .shrink([0.0, tab_bar_node.size().height]);
348
349        let mut tab_content_node =
350            if let Some(element) = self.children.get_mut(self.tab_bar.get_active_tab_idx()) {
351                element.as_widget_mut().layout(
352                    &mut tree.children[1].children[self.tab_bar.get_active_tab_idx()],
353                    renderer,
354                    &tab_content_limits,
355                )
356            } else {
357                Row::<Message, Theme, Renderer>::new()
358                    .width(Length::Fill)
359                    .height(Length::Fill)
360                    .layout(tree, renderer, &tab_content_limits)
361            };
362
363        let tab_bar_bounds = tab_bar_node.bounds();
364        tab_bar_node = tab_bar_node.move_to(Point::new(
365            tab_bar_bounds.x,
366            tab_bar_bounds.y
367                + match self.tab_bar_position {
368                    TabBarPosition::Top => 0.0,
369                    TabBarPosition::Bottom => tab_content_node.bounds().height,
370                },
371        ));
372
373        let tab_content_bounds = tab_content_node.bounds();
374        tab_content_node = tab_content_node.move_to(Point::new(
375            tab_content_bounds.x,
376            tab_content_bounds.y
377                + match self.tab_bar_position {
378                    TabBarPosition::Top => tab_bar_node.bounds().height,
379                    TabBarPosition::Bottom => 0.0,
380                },
381        ));
382
383        Node::with_children(
384            Size::new(
385                tab_content_node.size().width,
386                tab_bar_node.size().height + tab_content_node.size().height,
387            ),
388            match self.tab_bar_position {
389                TabBarPosition::Top => vec![tab_bar_node, tab_content_node],
390                TabBarPosition::Bottom => vec![tab_content_node, tab_bar_node],
391            },
392        )
393    }
394
395    fn update(
396        &mut self,
397        state: &mut Tree,
398        event: &Event,
399        layout: Layout<'_>,
400        cursor: Cursor,
401        renderer: &Renderer,
402        clipboard: &mut dyn Clipboard,
403        shell: &mut Shell<'_, Message>,
404        viewport: &Rectangle,
405    ) {
406        let mut children = layout.children();
407        let (tab_bar_layout, tab_content_layout) = match self.tab_bar_position {
408            TabBarPosition::Top => {
409                let tab_bar_layout = children
410                    .next()
411                    .expect("widget: Layout should have a TabBar layout at top position");
412                let tab_content_layout = children
413                    .next()
414                    .expect("widget: Layout should have a tab content layout at top position");
415                (tab_bar_layout, tab_content_layout)
416            }
417            TabBarPosition::Bottom => {
418                let tab_content_layout = children
419                    .next()
420                    .expect("widget: Layout should have a tab content layout at bottom position");
421                let tab_bar_layout = children
422                    .next()
423                    .expect("widget: Layout should have a TabBar layout at bottom position");
424                (tab_bar_layout, tab_content_layout)
425            }
426        };
427
428        self.tab_bar.update(
429            &mut Tree::empty(),
430            event,
431            tab_bar_layout,
432            cursor,
433            renderer,
434            clipboard,
435            shell,
436            viewport,
437        );
438        let idx = self.tab_bar.get_active_tab_idx();
439        if let Some(element) = self.children.get_mut(idx) {
440            element.as_widget_mut().update(
441                &mut state.children[1].children[idx],
442                event,
443                tab_content_layout,
444                cursor,
445                renderer,
446                clipboard,
447                shell,
448                viewport,
449            );
450        }
451    }
452
453    fn mouse_interaction(
454        &self,
455        state: &Tree,
456        layout: Layout<'_>,
457        cursor: Cursor,
458        viewport: &Rectangle,
459        renderer: &Renderer,
460    ) -> mouse::Interaction {
461        // Tab bar
462        let mut children = layout.children();
463        let tab_bar_layout = match self.tab_bar_position {
464            TabBarPosition::Top => children
465                .next()
466                .expect("widget: There should be a TabBar at the top position"),
467            TabBarPosition::Bottom => children
468                .last()
469                .expect("widget: There should be a TabBar at the bottom position"),
470        };
471
472        let mut mouse_interaction = mouse::Interaction::default();
473        let new_mouse_interaction = self.tab_bar.mouse_interaction(
474            &Tree::empty(),
475            tab_bar_layout,
476            cursor,
477            viewport,
478            renderer,
479        );
480
481        if new_mouse_interaction > mouse_interaction {
482            mouse_interaction = new_mouse_interaction;
483        }
484
485        // Tab content
486        let mut children = layout.children();
487        let tab_content_layout = match self.tab_bar_position {
488            TabBarPosition::Top => children
489                .last()
490                .expect("Graphics: There should be a TabBar at the top position"),
491            TabBarPosition::Bottom => children
492                .next()
493                .expect("Graphics: There should be a TabBar at the bottom position"),
494        };
495        let idx = self.tab_bar.get_active_tab_idx();
496        if let Some(element) = self.children.get(idx) {
497            let new_mouse_interaction = element.as_widget().mouse_interaction(
498                &state.children[1].children[idx],
499                tab_content_layout,
500                cursor,
501                viewport,
502                renderer,
503            );
504
505            if new_mouse_interaction > mouse_interaction {
506                mouse_interaction = new_mouse_interaction;
507            }
508        }
509
510        mouse_interaction
511    }
512
513    fn draw(
514        &self,
515        state: &Tree,
516        renderer: &mut Renderer,
517        theme: &Theme,
518        style: &renderer::Style,
519        layout: Layout<'_>,
520        cursor: Cursor,
521        viewport: &Rectangle,
522    ) {
523        let mut children = layout.children();
524        let tab_bar_layout = match self.tab_bar_position {
525            TabBarPosition::Top => children
526                .next()
527                .expect("widget: There should be a TabBar at the top position"),
528            TabBarPosition::Bottom => children
529                .last()
530                .expect("widget: There should be a TabBar at the bottom position"),
531        };
532
533        self.tab_bar.draw(
534            &Tree::empty(),
535            renderer,
536            theme,
537            style,
538            tab_bar_layout,
539            cursor,
540            viewport,
541        );
542
543        let mut children = layout.children();
544
545        let tab_content_layout = match self.tab_bar_position {
546            TabBarPosition::Top => children
547                .last()
548                .expect("Graphics: There should be a TabBar at the top position"),
549            TabBarPosition::Bottom => children
550                .next()
551                .expect("Graphics: There should be a TabBar at the bottom position"),
552        };
553
554        let idx = self.tab_bar.get_active_tab_idx();
555        if let Some(element) = self.children.get(idx) {
556            element.as_widget().draw(
557                &state.children[1].children[idx],
558                renderer,
559                theme,
560                style,
561                tab_content_layout,
562                cursor,
563                viewport,
564            );
565        }
566    }
567
568    fn overlay<'b>(
569        &'b mut self,
570        state: &'b mut Tree,
571        layout: Layout<'b>,
572        renderer: &Renderer,
573        viewport: &Rectangle,
574        translation: Vector,
575    ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
576        let layout = match self.tab_bar_position {
577            TabBarPosition::Top => layout.children().nth(1),
578            TabBarPosition::Bottom => layout.children().next(),
579        };
580
581        layout.and_then(|layout| {
582            let idx = self.tab_bar.get_active_tab_idx();
583            self.children
584                .get_mut(idx)
585                .map(Element::as_widget_mut)
586                .and_then(|w| {
587                    w.overlay(
588                        &mut state.children[1].children[idx],
589                        layout,
590                        renderer,
591                        viewport,
592                        translation,
593                    )
594                })
595        })
596    }
597
598    fn operate(
599        &mut self,
600        tree: &mut Tree,
601        layout: Layout<'_>,
602        renderer: &Renderer,
603        operation: &mut dyn Operation<()>,
604    ) {
605        let active_tab = self.tab_bar.get_active_tab_idx();
606        operation.container(None, layout.bounds());
607        operation.traverse(&mut |operation| {
608            self.children[active_tab].as_widget_mut().operate(
609                &mut tree.children[1].children[active_tab],
610                layout
611                    .children()
612                    .nth(1)
613                    .expect("TabBar is 0th child, contents are 1st node"),
614                renderer,
615                operation,
616            );
617        });
618    }
619}
620
621impl<'a, Message, TabId, Theme, Renderer> From<Tabs<'a, Message, TabId, Theme, Renderer>>
622    for Element<'a, Message, Theme, Renderer>
623where
624    Renderer: 'a + renderer::Renderer + iced_core::text::Renderer<Font = Font>,
625    Theme: 'a + Catalog + text::Catalog,
626    Message: 'a,
627    TabId: 'a + Eq + Clone,
628{
629    fn from(tabs: Tabs<'a, Message, TabId, Theme, Renderer>) -> Self {
630        Element::new(tabs)
631    }
632}