1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
//! Implements basic tabs.
use std::fmt::Debug;

use ambient_cb::{cb, Cb};
use ambient_element::{to_owned, use_state, Element, ElementComponent, ElementComponentExt, Hooks};
use ambient_guest_bridge::core::layout::components::{padding, space_between_items};
use glam::vec4;

use crate::{
    button::{Button, ButtonStyle},
    default_theme::STREET,
    layout::{FlowColumn, FlowRow},
};

#[derive(Clone, Debug)]
/// A header bar of tabs. Does not contain the tab content.
pub struct TabBar<T: ToString + PartialEq + Clone + Debug + Sync + Send + 'static> {
    /// The tabs to display.
    pub tabs: Vec<T>,
    /// The currently selected tab.
    pub value: T,
    /// The callback to call when a tab is selected. Called with the tab value.
    pub on_change: Cb<dyn Fn(T) + Sync + Send>,
}
impl<T: ToString + PartialEq + Clone + Debug + Sync + Send + 'static> ElementComponent
    for TabBar<T>
{
    fn render(self: Box<Self>, _: &mut Hooks) -> Element {
        let Self {
            tabs,
            value,
            on_change,
        } = *self;
        FlowRow(
            tabs.into_iter()
                .map(|tab| {
                    Button::new(tab.to_string(), {
                        to_owned![on_change, tab];
                        move |_| on_change.0(tab.clone())
                    })
                    .toggled(tab == value)
                    .style(ButtonStyle::Card)
                    .el()
                    .with(padding(), vec4(0.0, STREET, 0.0, STREET))
                })
                .collect(),
        )
        .el()
    }
}

#[derive(Clone, Debug)]
/// A set of tabs. Contains a `TabBar` and the content of the selected tab.
pub struct Tabs<T: ToString + PartialEq + Default + Clone + Debug + Sync + Send + 'static> {
    /// The tabs to display.
    tabs: Vec<(T, Cb<dyn Fn() -> Element + Sync + Send>)>,
}
impl<T: ToString + PartialEq + Default + Clone + Debug + Sync + Send + 'static> Tabs<T> {
    /// Creates a new `Tabs` with no tabs.
    pub fn new() -> Self {
        Self {
            tabs: Default::default(),
        }
    }

    /// Adds a tab to the `Tabs`. The callback is called when the tab is selected, and should return the content of the tab.
    pub fn with_tab(
        mut self,
        tab: T,
        callback: impl Fn() -> Element + Sync + Send + 'static,
    ) -> Self {
        self.tabs.push((tab, cb(callback)));
        self
    }
}
impl<T: ToString + PartialEq + Default + Clone + Debug + Sync + Send + 'static> ElementComponent
    for Tabs<T>
{
    fn render(self: Box<Self>, hooks: &mut Hooks) -> Element {
        let (value, set_value) = use_state(hooks, T::default());
        let selected_tab = self
            .tabs
            .iter()
            .find(|it| it.0 == value)
            .map(|it| it.1.clone())
            .unwrap_or(cb(Element::new));
        let key = value.to_string();

        FlowColumn::el([
            TabBar {
                tabs: self.tabs.iter().map(|it| it.0.clone()).collect(),
                value,
                on_change: cb(move |value| set_value(value)),
            }
            .el(),
            selected_tab().key(key),
        ])
        .with(space_between_items(), STREET)
    }
}