Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T04:36:39.593Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

tab.rs

236 lines · 7.4 KB · rust
1use std::cmp::Ordering;
2
3use gpui::{AnyElement, IntoElement, Stateful};
4use smallvec::SmallVec;
5
6use crate::prelude::*;
7
8const START_TAB_SLOT_SIZE: Pixels = px(12.);
9const END_TAB_SLOT_SIZE: Pixels = px(14.);
10
11/// The position of a [`Tab`] within a list of tabs.
12#[derive(Debug, PartialEq, Eq, Clone, Copy)]
13pub enum TabPosition {
14    /// The tab is first in the list.
15    First,
16
17    /// The tab is in the middle of the list (i.e., it is not the first or last tab).
18    ///
19    /// The [`Ordering`] is where this tab is positioned with respect to the selected tab.
20    Middle(Ordering),
21
22    /// The tab is last in the list.
23    Last,
24}
25
26#[derive(Debug, PartialEq, Eq, Clone, Copy)]
27pub enum TabCloseSide {
28    Start,
29    End,
30}
31
32#[derive(IntoElement, RegisterComponent)]
33pub struct Tab {
34    div: Stateful<Div>,
35    selected: bool,
36    position: TabPosition,
37    close_side: TabCloseSide,
38    start_slot: Option<AnyElement>,
39    end_slot: Option<AnyElement>,
40    children: SmallVec<[AnyElement; 2]>,
41}
42
43impl Tab {
44    pub fn new(id: impl Into<ElementId>) -> Self {
45        let id = id.into();
46        Self {
47            div: div()
48                .id(id.clone())
49                .debug_selector(|| format!("TAB-{}", id)),
50            selected: false,
51            position: TabPosition::First,
52            close_side: TabCloseSide::End,
53            start_slot: None,
54            end_slot: None,
55            children: SmallVec::new(),
56        }
57    }
58
59    pub fn position(mut self, position: TabPosition) -> Self {
60        self.position = position;
61        self
62    }
63
64    pub fn close_side(mut self, close_side: TabCloseSide) -> Self {
65        self.close_side = close_side;
66        self
67    }
68
69    pub fn start_slot<E: IntoElement>(mut self, element: impl Into<Option<E>>) -> Self {
70        self.start_slot = element.into().map(IntoElement::into_any_element);
71        self
72    }
73
74    pub fn end_slot<E: IntoElement>(mut self, element: impl Into<Option<E>>) -> Self {
75        self.end_slot = element.into().map(IntoElement::into_any_element);
76        self
77    }
78
79    pub fn content_height(cx: &App) -> Pixels {
80        DynamicSpacing::Base32.px(cx) - px(1.)
81    }
82
83    pub fn container_height(cx: &App) -> Pixels {
84        DynamicSpacing::Base32.px(cx)
85    }
86}
87
88impl InteractiveElement for Tab {
89    fn interactivity(&mut self) -> &mut gpui::Interactivity {
90        self.div.interactivity()
91    }
92}
93
94impl StatefulInteractiveElement for Tab {}
95
96impl Toggleable for Tab {
97    fn toggle_state(mut self, selected: bool) -> Self {
98        self.selected = selected;
99        self
100    }
101}
102
103impl ParentElement for Tab {
104    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
105        self.children.extend(elements)
106    }
107}
108
109impl RenderOnce for Tab {
110    #[allow(refining_impl_trait)]
111    fn render(self, _: &mut Window, cx: &mut App) -> Stateful<Div> {
112        let (text_color, tab_bg, _tab_hover_bg, _tab_active_bg) = match self.selected {
113            false => (
114                cx.theme().colors().text_muted,
115                cx.theme().colors().tab_inactive_background,
116                cx.theme().colors().ghost_element_hover,
117                cx.theme().colors().ghost_element_active,
118            ),
119            true => (
120                cx.theme().colors().text,
121                cx.theme().colors().tab_active_background,
122                cx.theme().colors().element_hover,
123                cx.theme().colors().element_active,
124            ),
125        };
126
127        let (start_slot, end_slot) = {
128            let start_slot = h_flex()
129                .size(START_TAB_SLOT_SIZE)
130                .justify_center()
131                .children(self.start_slot);
132
133            let end_slot = h_flex()
134                .size(END_TAB_SLOT_SIZE)
135                .justify_center()
136                .children(self.end_slot);
137
138            match self.close_side {
139                TabCloseSide::End => (start_slot, end_slot),
140                TabCloseSide::Start => (end_slot, start_slot),
141            }
142        };
143
144        self.div
145            .h(Tab::container_height(cx))
146            .bg(tab_bg)
147            .border_color(cx.theme().colors().border)
148            .map(|this| match self.position {
149                TabPosition::First => {
150                    if self.selected {
151                        this.pl_px().border_r_1().pb_px()
152                    } else {
153                        this.pl_px().pr_px().border_b_1()
154                    }
155                }
156                TabPosition::Last => {
157                    if self.selected {
158                        this.border_l_1().border_r_1().pb_px()
159                    } else {
160                        this.pl_px().border_b_1().border_r_1()
161                    }
162                }
163                TabPosition::Middle(Ordering::Equal) => this.border_l_1().border_r_1().pb_px(),
164                TabPosition::Middle(Ordering::Less) => this.border_l_1().pr_px().border_b_1(),
165                TabPosition::Middle(Ordering::Greater) => this.border_r_1().pl_px().border_b_1(),
166            })
167            .cursor_pointer()
168            .child(
169                h_flex()
170                    .group("")
171                    .relative()
172                    .h(Tab::content_height(cx))
173                    .px(DynamicSpacing::Base04.px(cx))
174                    .gap(DynamicSpacing::Base04.rems(cx))
175                    .text_color(text_color)
176                    .child(start_slot)
177                    .children(self.children)
178                    .child(end_slot),
179            )
180    }
181}
182
183impl Component for Tab {
184    fn scope() -> ComponentScope {
185        ComponentScope::Navigation
186    }
187
188    fn description() -> &'static str {
189        "A tab component that can be used in a tabbed interface, \
190        supporting different positions and states."
191    }
192
193    fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement {
194        v_flex()
195            .gap_6()
196            .children(vec![example_group_with_title(
197                "Variations",
198                vec![
199                    single_example(
200                        "Default",
201                        Tab::new("default").child("Default Tab").into_any_element(),
202                    ),
203                    single_example(
204                        "Selected",
205                        Tab::new("selected")
206                            .toggle_state(true)
207                            .child("Selected Tab")
208                            .into_any_element(),
209                    ),
210                    single_example(
211                        "First",
212                        Tab::new("first")
213                            .position(TabPosition::First)
214                            .child("First Tab")
215                            .into_any_element(),
216                    ),
217                    single_example(
218                        "Middle",
219                        Tab::new("middle")
220                            .position(TabPosition::Middle(Ordering::Equal))
221                            .child("Middle Tab")
222                            .into_any_element(),
223                    ),
224                    single_example(
225                        "Last",
226                        Tab::new("last")
227                            .position(TabPosition::Last)
228                            .child("Last Tab")
229                            .into_any_element(),
230                    ),
231                ],
232            )])
233            .into_any_element()
234    }
235}
236
Served at tenant.openagents/omega Member data and write actions are omitted.