1pub 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#[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 tab_bar: TabBar<'a, Message, TabId, Theme, Renderer>,
71 children: Vec<Element<'a, Message, Theme, Renderer>>,
73 indices: Vec<TabId>,
75 tab_bar_position: TabBarPosition,
77 tab_icon_position: Position,
79 width: Length,
81 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 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 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 #[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 #[must_use]
158 pub fn tab_icon_position(mut self, position: Position) -> Self {
159 self.tab_icon_position = position;
160 self
161 }
162
163 #[must_use]
165 pub fn height(mut self, height: impl Into<Length>) -> Self {
166 self.height = height.into();
167 self
168 }
169
170 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[must_use]
246 pub fn tab_bar_position(mut self, position: TabBarPosition) -> Self {
247 self.tab_bar_position = position;
248 self
249 }
250
251 #[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 #[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 #[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 #[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 #[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 #[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 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 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}