tab.rs

  1use std::marker::PhantomData;
  2
  3use crate::prelude::*;
  4use crate::{Icon, IconColor, IconElement, Label, LabelColor};
  5
  6#[derive(Element, Clone)]
  7pub struct Tab<S: 'static + Send + Sync + Clone> {
  8    state_type: PhantomData<S>,
  9    title: String,
 10    icon: Option<Icon>,
 11    current: bool,
 12    dirty: bool,
 13    fs_status: FileSystemStatus,
 14    git_status: GitStatus,
 15    diagnostic_status: DiagnosticStatus,
 16    close_side: IconSide,
 17}
 18
 19impl<S: 'static + Send + Sync + Clone> Tab<S> {
 20    pub fn new() -> Self {
 21        Self {
 22            state_type: PhantomData,
 23            title: "untitled".to_string(),
 24            icon: None,
 25            current: false,
 26            dirty: false,
 27            fs_status: FileSystemStatus::None,
 28            git_status: GitStatus::None,
 29            diagnostic_status: DiagnosticStatus::None,
 30            close_side: IconSide::Right,
 31        }
 32    }
 33
 34    pub fn current(mut self, current: bool) -> Self {
 35        self.current = current;
 36        self
 37    }
 38
 39    pub fn title(mut self, title: String) -> Self {
 40        self.title = title;
 41        self
 42    }
 43
 44    pub fn icon<I>(mut self, icon: I) -> Self
 45    where
 46        I: Into<Option<Icon>>,
 47    {
 48        self.icon = icon.into();
 49        self
 50    }
 51
 52    pub fn dirty(mut self, dirty: bool) -> Self {
 53        self.dirty = dirty;
 54        self
 55    }
 56
 57    pub fn fs_status(mut self, fs_status: FileSystemStatus) -> Self {
 58        self.fs_status = fs_status;
 59        self
 60    }
 61
 62    pub fn git_status(mut self, git_status: GitStatus) -> Self {
 63        self.git_status = git_status;
 64        self
 65    }
 66
 67    pub fn diagnostic_status(mut self, diagnostic_status: DiagnosticStatus) -> Self {
 68        self.diagnostic_status = diagnostic_status;
 69        self
 70    }
 71
 72    pub fn close_side(mut self, close_side: IconSide) -> Self {
 73        self.close_side = close_side;
 74        self
 75    }
 76
 77    fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
 78        let color = ThemeColor::new(cx);
 79        let has_fs_conflict = self.fs_status == FileSystemStatus::Conflict;
 80        let is_deleted = self.fs_status == FileSystemStatus::Deleted;
 81
 82        let label = match (self.git_status, is_deleted) {
 83            (_, true) | (GitStatus::Deleted, false) => Label::new(self.title.clone())
 84                .color(LabelColor::Hidden)
 85                .set_strikethrough(true),
 86            (GitStatus::None, false) => Label::new(self.title.clone()),
 87            (GitStatus::Created, false) => {
 88                Label::new(self.title.clone()).color(LabelColor::Created)
 89            }
 90            (GitStatus::Modified, false) => {
 91                Label::new(self.title.clone()).color(LabelColor::Modified)
 92            }
 93            (GitStatus::Renamed, false) => Label::new(self.title.clone()).color(LabelColor::Accent),
 94            (GitStatus::Conflict, false) => Label::new(self.title.clone()),
 95        };
 96
 97        let close_icon = IconElement::new(Icon::Close).color(IconColor::Muted);
 98
 99        let (tab_bg, tab_hover_bg, tab_active_bg) = match self.current {
100            true => (
101                color.ghost_element,
102                color.ghost_element_hover,
103                color.ghost_element_active,
104            ),
105            false => (
106                color.filled_element,
107                color.filled_element_hover,
108                color.filled_element_active,
109            ),
110        };
111
112        div()
113            .px_2()
114            .py_0p5()
115            .flex()
116            .items_center()
117            .justify_center()
118            .bg(tab_bg)
119            .hover(|h| h.bg(tab_hover_bg))
120            // .active(|a| a.bg(tab_active_bg))
121            .child(
122                div()
123                    .px_1()
124                    .flex()
125                    .items_center()
126                    .gap_1()
127                    .children(has_fs_conflict.then(|| {
128                        IconElement::new(Icon::ExclamationTriangle)
129                            .size(crate::IconSize::Small)
130                            .color(IconColor::Warning)
131                    }))
132                    .children(self.icon.map(IconElement::new))
133                    .children(if self.close_side == IconSide::Left {
134                        Some(close_icon.clone())
135                    } else {
136                        None
137                    })
138                    .child(label)
139                    .children(if self.close_side == IconSide::Right {
140                        Some(close_icon)
141                    } else {
142                        None
143                    }),
144            )
145    }
146}
147
148#[cfg(feature = "stories")]
149pub use stories::*;
150
151#[cfg(feature = "stories")]
152mod stories {
153    use strum::IntoEnumIterator;
154
155    use crate::{h_stack, v_stack, Icon, Story};
156
157    use super::*;
158
159    #[derive(Element)]
160    pub struct TabStory<S: 'static + Send + Sync + Clone> {
161        state_type: PhantomData<S>,
162    }
163
164    impl<S: 'static + Send + Sync + Clone> TabStory<S> {
165        pub fn new() -> Self {
166            Self {
167                state_type: PhantomData,
168            }
169        }
170
171        fn render(
172            &mut self,
173            _view: &mut S,
174            cx: &mut ViewContext<S>,
175        ) -> impl Element<ViewState = S> {
176            let git_statuses = GitStatus::iter();
177            let fs_statuses = FileSystemStatus::iter();
178
179            Story::container(cx)
180                .child(Story::title_for::<_, Tab<S>>(cx))
181                .child(
182                    h_stack().child(
183                        v_stack()
184                            .gap_2()
185                            .child(Story::label(cx, "Default"))
186                            .child(Tab::new()),
187                    ),
188                )
189                .child(
190                    h_stack().child(
191                        v_stack().gap_2().child(Story::label(cx, "Current")).child(
192                            h_stack()
193                                .gap_4()
194                                .child(Tab::new().title("Current".to_string()).current(true))
195                                .child(Tab::new().title("Not Current".to_string()).current(false)),
196                        ),
197                    ),
198                )
199                .child(
200                    h_stack().child(
201                        v_stack()
202                            .gap_2()
203                            .child(Story::label(cx, "Titled"))
204                            .child(Tab::new().title("label".to_string())),
205                    ),
206                )
207                .child(
208                    h_stack().child(
209                        v_stack()
210                            .gap_2()
211                            .child(Story::label(cx, "With Icon"))
212                            .child(
213                                Tab::new()
214                                    .title("label".to_string())
215                                    .icon(Some(Icon::Envelope)),
216                            ),
217                    ),
218                )
219                .child(
220                    h_stack().child(
221                        v_stack()
222                            .gap_2()
223                            .child(Story::label(cx, "Close Side"))
224                            .child(
225                                h_stack()
226                                    .gap_4()
227                                    .child(
228                                        Tab::new()
229                                            .title("Left".to_string())
230                                            .close_side(IconSide::Left),
231                                    )
232                                    .child(Tab::new().title("Right".to_string())),
233                            ),
234                    ),
235                )
236                .child(
237                    v_stack()
238                        .gap_2()
239                        .child(Story::label(cx, "Git Status"))
240                        .child(h_stack().gap_4().children(git_statuses.map(|git_status| {
241                            Tab::new()
242                                .title(git_status.to_string())
243                                .git_status(git_status)
244                        }))),
245                )
246                .child(
247                    v_stack()
248                        .gap_2()
249                        .child(Story::label(cx, "File System Status"))
250                        .child(h_stack().gap_4().children(fs_statuses.map(|fs_status| {
251                            Tab::new().title(fs_status.to_string()).fs_status(fs_status)
252                        }))),
253                )
254        }
255    }
256}