pub mod tab_bar_position;
pub use crate::tab_bar::Position;
use crate::{
TabLabel,
style::{
Status, StyleFn,
tab_bar::{Catalog, Style},
},
widget::tab_bar::TabBar,
};
use iced_core::{
Clipboard, Element, Event, Font, Layout, Length, Padding, Pixels, Point, Rectangle, Shell,
Size, Vector, Widget,
layout::{Limits, Node},
mouse::{self, Cursor},
overlay, renderer,
widget::{
Operation, Tree,
tree::{State, Tag},
},
};
use iced_widget::{Row, text};
pub use tab_bar_position::TabBarPosition;
#[allow(missing_debug_implementations)]
pub struct Tabs<'a, Message, TabId, Theme = iced_widget::Theme, Renderer = iced_widget::Renderer>
where
Renderer: 'a + renderer::Renderer + iced_core::text::Renderer,
Theme: Catalog,
TabId: Eq + Clone,
{
tab_bar: TabBar<'a, Message, TabId, Theme, Renderer>,
children: Vec<Element<'a, Message, Theme, Renderer>>,
indices: Vec<TabId>,
tab_bar_position: TabBarPosition,
tab_icon_position: Position,
width: Length,
height: Length,
}
impl<'a, Message, TabId, Theme, Renderer> Tabs<'a, Message, TabId, Theme, Renderer>
where
Renderer: 'a + renderer::Renderer + iced_core::text::Renderer<Font = Font>,
Theme: Catalog + text::Catalog,
TabId: Eq + Clone,
{
pub fn new<F>(on_select: F) -> Self
where
F: 'static + Fn(TabId) -> Message,
{
Self::new_with_tabs(Vec::new(), on_select)
}
pub fn new_with_tabs<F>(
tabs: impl IntoIterator<Item = (TabId, TabLabel, Element<'a, Message, Theme, Renderer>)>,
on_select: F,
) -> Self
where
F: 'static + Fn(TabId) -> Message,
{
let tabs = tabs.into_iter();
let n_tabs = tabs.size_hint().0;
let mut tab_labels = Vec::with_capacity(n_tabs);
let mut elements = Vec::with_capacity(n_tabs);
let mut indices = Vec::with_capacity(n_tabs);
for (id, tab_label, element) in tabs {
tab_labels.push((id.clone(), tab_label));
indices.push(id);
elements.push(element);
}
Tabs {
tab_bar: TabBar::with_tab_labels(tab_labels, on_select),
children: elements,
indices,
tab_bar_position: TabBarPosition::Top,
tab_icon_position: Position::Left,
width: Length::Fill,
height: Length::Fill,
}
}
#[must_use]
pub fn close_size(mut self, close_size: f32) -> Self {
self.tab_bar = self.tab_bar.close_size(close_size);
self
}
#[must_use]
pub fn tab_icon_position(mut self, position: Position) -> Self {
self.tab_icon_position = position;
self
}
#[must_use]
pub fn height(mut self, height: impl Into<Length>) -> Self {
self.height = height.into();
self
}
#[must_use]
pub fn icon_font(mut self, font: Font) -> Self {
self.tab_bar = self.tab_bar.icon_font(font);
self
}
#[must_use]
pub fn icon_size(mut self, icon_size: f32) -> Self {
self.tab_bar = self.tab_bar.icon_size(icon_size);
self
}
#[must_use]
pub fn on_close<F>(mut self, on_close: F) -> Self
where
F: 'static + Fn(TabId) -> Message,
{
self.tab_bar = self.tab_bar.on_close(on_close);
self
}
#[must_use]
pub fn push<E>(mut self, id: TabId, tab_label: TabLabel, element: E) -> Self
where
E: Into<Element<'a, Message, Theme, Renderer>>,
{
self.tab_bar = self
.tab_bar
.push(id.clone(), tab_label)
.set_position(self.tab_icon_position);
self.children.push(element.into());
self.indices.push(id);
self
}
#[must_use]
pub fn set_active_tab(mut self, id: &TabId) -> Self {
self.tab_bar = self.tab_bar.set_active_tab(id);
self
}
#[must_use]
pub fn tab_bar_height(mut self, height: Length) -> Self {
self.tab_bar = self.tab_bar.height(height);
self
}
#[must_use]
pub fn tab_bar_max_height(mut self, max_height: f32) -> Self {
self.tab_bar = self.tab_bar.max_height(max_height);
self
}
#[must_use]
pub fn tab_bar_width(mut self, width: Length) -> Self {
self.tab_bar = self.tab_bar.width(width);
self
}
#[must_use]
pub fn tab_bar_position(mut self, position: TabBarPosition) -> Self {
self.tab_bar_position = position;
self
}
#[must_use]
pub fn tab_bar_style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self
where
<Theme as Catalog>::Class<'a>: From<StyleFn<'a, Theme, Style>>,
{
self.tab_bar = self.tab_bar.style(style);
self
}
#[must_use]
pub fn tab_label_padding(mut self, padding: impl Into<Padding>) -> Self {
self.tab_bar = self.tab_bar.padding(padding);
self
}
#[must_use]
pub fn tab_label_spacing(mut self, spacing: impl Into<Pixels>) -> Self {
self.tab_bar = self.tab_bar.spacing(spacing);
self
}
#[must_use]
pub fn text_font(mut self, text_font: Font) -> Self {
self.tab_bar = self.tab_bar.text_font(text_font);
self
}
#[must_use]
pub fn text_size(mut self, text_size: f32) -> Self {
self.tab_bar = self.tab_bar.text_size(text_size);
self
}
#[must_use]
pub fn width(mut self, width: impl Into<Length>) -> Self {
self.width = width.into();
self
}
}
impl<Message, TabId, Theme, Renderer> Widget<Message, Theme, Renderer>
for Tabs<'_, Message, TabId, Theme, Renderer>
where
Renderer: renderer::Renderer + iced_core::text::Renderer<Font = Font>,
Theme: Catalog + text::Catalog,
TabId: Eq + Clone,
{
fn children(&self) -> Vec<Tree> {
let tabs = Tree {
tag: Tag::stateless(),
state: State::None,
children: self.children.iter().map(Tree::new).collect(),
};
let bar = Tree {
tag: self.tab_bar.tag(),
state: self.tab_bar.state(),
children: self.tab_bar.children(),
};
vec![bar, tabs]
}
fn diff(&self, tree: &mut Tree) {
if tree.children.len() != 2 {
tree.children = self.children();
}
if let Some(tabs) = tree.children.get_mut(1) {
tabs.diff_children(&self.children);
}
}
fn size(&self) -> Size<Length> {
Size::new(self.width, self.height)
}
fn layout(&mut self, tree: &mut Tree, renderer: &Renderer, limits: &Limits) -> Node {
let tab_bar_limits = limits.width(self.width).height(Length::Fill);
let mut tab_bar_node =
self.tab_bar
.layout(&mut tree.children[0], renderer, &tab_bar_limits);
let tab_content_limits = limits
.width(self.width)
.height(self.height)
.shrink([0.0, tab_bar_node.size().height]);
let mut tab_content_node = if let (Some(element), Some(child)) = (
self.children.get_mut(self.tab_bar.get_active_tab_idx()),
tree.children.get_mut(1),
) {
element.as_widget_mut().layout(
&mut child.children[self.tab_bar.get_active_tab_idx()],
renderer,
&tab_content_limits,
)
} else {
Row::<Message, Theme, Renderer>::new()
.width(Length::Fill)
.height(Length::Fill)
.layout(tree, renderer, &tab_content_limits)
};
let tab_bar_bounds = tab_bar_node.bounds();
tab_bar_node = tab_bar_node.move_to(Point::new(
tab_bar_bounds.x,
tab_bar_bounds.y
+ match self.tab_bar_position {
TabBarPosition::Top => 0.0,
TabBarPosition::Bottom => tab_content_node.bounds().height,
},
));
let tab_content_bounds = tab_content_node.bounds();
tab_content_node = tab_content_node.move_to(Point::new(
tab_content_bounds.x,
tab_content_bounds.y
+ match self.tab_bar_position {
TabBarPosition::Top => tab_bar_node.bounds().height,
TabBarPosition::Bottom => 0.0,
},
));
Node::with_children(
Size::new(
tab_content_node.size().width,
tab_bar_node.size().height + tab_content_node.size().height,
),
match self.tab_bar_position {
TabBarPosition::Top => vec![tab_bar_node, tab_content_node],
TabBarPosition::Bottom => vec![tab_content_node, tab_bar_node],
},
)
}
fn update(
&mut self,
state: &mut Tree,
event: &Event,
layout: Layout<'_>,
cursor: Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
viewport: &Rectangle,
) {
let mut children = layout.children();
let (tab_bar_layout, tab_content_layout) = match self.tab_bar_position {
TabBarPosition::Top => {
let tab_bar_layout = children
.next()
.expect("widget: Layout should have a TabBar layout at top position");
let tab_content_layout = children
.next()
.expect("widget: Layout should have a tab content layout at top position");
(tab_bar_layout, tab_content_layout)
}
TabBarPosition::Bottom => {
let tab_content_layout = children
.next()
.expect("widget: Layout should have a tab content layout at bottom position");
let tab_bar_layout = children
.next()
.expect("widget: Layout should have a TabBar layout at bottom position");
(tab_bar_layout, tab_content_layout)
}
};
self.tab_bar.update(
&mut Tree::empty(),
event,
tab_bar_layout,
cursor,
renderer,
clipboard,
shell,
viewport,
);
let idx = self.tab_bar.get_active_tab_idx();
if let Some(element) = self.children.get_mut(idx) {
element.as_widget_mut().update(
&mut state.children[1].children[idx],
event,
tab_content_layout,
cursor,
renderer,
clipboard,
shell,
viewport,
);
}
}
fn mouse_interaction(
&self,
state: &Tree,
layout: Layout<'_>,
cursor: Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
let mut children = layout.children();
let tab_bar_layout = match self.tab_bar_position {
TabBarPosition::Top => children
.next()
.expect("widget: There should be a TabBar at the top position"),
TabBarPosition::Bottom => children
.last()
.expect("widget: There should be a TabBar at the bottom position"),
};
let mut mouse_interaction = mouse::Interaction::default();
let new_mouse_interaction = self.tab_bar.mouse_interaction(
&Tree::empty(),
tab_bar_layout,
cursor,
viewport,
renderer,
);
if new_mouse_interaction > mouse_interaction {
mouse_interaction = new_mouse_interaction;
}
let mut children = layout.children();
let tab_content_layout = match self.tab_bar_position {
TabBarPosition::Top => children
.last()
.expect("Graphics: There should be a TabBar at the top position"),
TabBarPosition::Bottom => children
.next()
.expect("Graphics: There should be a TabBar at the bottom position"),
};
let idx = self.tab_bar.get_active_tab_idx();
if let Some(element) = self.children.get(idx) {
let new_mouse_interaction = element.as_widget().mouse_interaction(
&state.children[1].children[idx],
tab_content_layout,
cursor,
viewport,
renderer,
);
if new_mouse_interaction > mouse_interaction {
mouse_interaction = new_mouse_interaction;
}
}
mouse_interaction
}
fn draw(
&self,
state: &Tree,
renderer: &mut Renderer,
theme: &Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor: Cursor,
viewport: &Rectangle,
) {
let mut children = layout.children();
let tab_bar_layout = match self.tab_bar_position {
TabBarPosition::Top => children
.next()
.expect("widget: There should be a TabBar at the top position"),
TabBarPosition::Bottom => children
.last()
.expect("widget: There should be a TabBar at the bottom position"),
};
self.tab_bar.draw(
&Tree::empty(),
renderer,
theme,
style,
tab_bar_layout,
cursor,
viewport,
);
let mut children = layout.children();
let tab_content_layout = match self.tab_bar_position {
TabBarPosition::Top => children
.last()
.expect("Graphics: There should be a TabBar at the top position"),
TabBarPosition::Bottom => children
.next()
.expect("Graphics: There should be a TabBar at the bottom position"),
};
let idx = self.tab_bar.get_active_tab_idx();
if let Some(element) = self.children.get(idx) {
element.as_widget().draw(
&state.children[1].children[idx],
renderer,
theme,
style,
tab_content_layout,
cursor,
viewport,
);
}
}
fn overlay<'b>(
&'b mut self,
state: &'b mut Tree,
layout: Layout<'b>,
renderer: &Renderer,
viewport: &Rectangle,
translation: Vector,
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
let layout = match self.tab_bar_position {
TabBarPosition::Top => layout.children().nth(1),
TabBarPosition::Bottom => layout.children().next(),
};
layout.and_then(|layout| {
let idx = self.tab_bar.get_active_tab_idx();
self.children
.get_mut(idx)
.map(Element::as_widget_mut)
.and_then(|w| {
w.overlay(
&mut state.children[1].children[idx],
layout,
renderer,
viewport,
translation,
)
})
})
}
fn operate(
&mut self,
tree: &mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
operation: &mut dyn Operation<()>,
) {
let active_tab = self.tab_bar.get_active_tab_idx();
operation.container(None, layout.bounds());
operation.traverse(&mut |operation| {
self.children[active_tab].as_widget_mut().operate(
&mut tree.children[1].children[active_tab],
layout
.children()
.nth(1)
.expect("TabBar is 0th child, contents are 1st node"),
renderer,
operation,
);
});
}
}
impl<'a, Message, TabId, Theme, Renderer> From<Tabs<'a, Message, TabId, Theme, Renderer>>
for Element<'a, Message, Theme, Renderer>
where
Renderer: 'a + renderer::Renderer + iced_core::text::Renderer<Font = Font>,
Theme: 'a + Catalog + text::Catalog,
Message: 'a,
TabId: 'a + Eq + Clone,
{
fn from(tabs: Tabs<'a, Message, TabId, Theme, Renderer>) -> Self {
Element::new(tabs)
}
}