Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T05:34:12.965Z 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

toggle_button.rs

665 lines · 26.2 KB · rust
1use std::rc::Rc;
2
3use gpui::{AnyView, ClickEvent, relative};
4
5use crate::{ButtonLike, ButtonLikeRounding, TintColor, Tooltip, prelude::*};
6
7/// The position of a [`ToggleButton`] within a group of buttons.
8#[derive(Debug, PartialEq, Eq, Clone, Copy)]
9pub struct ToggleButtonPosition {
10    /// The toggle button is one of the leftmost of the group.
11    leftmost: bool,
12    /// The toggle button is one of the rightmost of the group.
13    rightmost: bool,
14    /// The toggle button is one of the topmost of the group.
15    topmost: bool,
16    /// The toggle button is one of the bottommost of the group.
17    bottommost: bool,
18}
19
20impl ToggleButtonPosition {
21    pub const HORIZONTAL_FIRST: Self = Self {
22        leftmost: true,
23        ..Self::HORIZONTAL_MIDDLE
24    };
25    pub const HORIZONTAL_MIDDLE: Self = Self {
26        leftmost: false,
27        rightmost: false,
28        topmost: true,
29        bottommost: true,
30    };
31    pub const HORIZONTAL_LAST: Self = Self {
32        rightmost: true,
33        ..Self::HORIZONTAL_MIDDLE
34    };
35
36    pub(crate) fn to_rounding(self) -> ButtonLikeRounding {
37        ButtonLikeRounding {
38            top_left: self.topmost && self.leftmost,
39            top_right: self.topmost && self.rightmost,
40            bottom_right: self.bottommost && self.rightmost,
41            bottom_left: self.bottommost && self.leftmost,
42        }
43    }
44}
45
46pub struct ButtonConfiguration {
47    label: SharedString,
48    icon: Option<IconName>,
49    on_click: Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>,
50    selected: bool,
51    tooltip: Option<Rc<dyn Fn(&mut Window, &mut App) -> AnyView>>,
52}
53
54mod private {
55    pub trait ToggleButtonStyle {}
56}
57
58pub trait ButtonBuilder: 'static + private::ToggleButtonStyle {
59    fn into_configuration(self) -> ButtonConfiguration;
60}
61
62pub struct ToggleButtonSimple {
63    label: SharedString,
64    on_click: Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>,
65    selected: bool,
66    tooltip: Option<Rc<dyn Fn(&mut Window, &mut App) -> AnyView>>,
67}
68
69impl ToggleButtonSimple {
70    pub fn new(
71        label: impl Into<SharedString>,
72        on_click: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
73    ) -> Self {
74        Self {
75            label: label.into(),
76            on_click: Box::new(on_click),
77            selected: false,
78            tooltip: None,
79        }
80    }
81
82    pub fn selected(mut self, selected: bool) -> Self {
83        self.selected = selected;
84        self
85    }
86
87    pub fn tooltip(mut self, tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static) -> Self {
88        self.tooltip = Some(Rc::new(tooltip));
89        self
90    }
91}
92
93impl private::ToggleButtonStyle for ToggleButtonSimple {}
94
95impl ButtonBuilder for ToggleButtonSimple {
96    fn into_configuration(self) -> ButtonConfiguration {
97        ButtonConfiguration {
98            label: self.label,
99            icon: None,
100            on_click: self.on_click,
101            selected: self.selected,
102            tooltip: self.tooltip,
103        }
104    }
105}
106
107pub struct ToggleButtonWithIcon {
108    label: SharedString,
109    icon: IconName,
110    on_click: Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>,
111    selected: bool,
112    tooltip: Option<Rc<dyn Fn(&mut Window, &mut App) -> AnyView>>,
113}
114
115impl ToggleButtonWithIcon {
116    pub fn new(
117        label: impl Into<SharedString>,
118        icon: IconName,
119        on_click: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
120    ) -> Self {
121        Self {
122            label: label.into(),
123            icon,
124            on_click: Box::new(on_click),
125            selected: false,
126            tooltip: None,
127        }
128    }
129
130    pub fn selected(mut self, selected: bool) -> Self {
131        self.selected = selected;
132        self
133    }
134
135    pub fn tooltip(mut self, tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static) -> Self {
136        self.tooltip = Some(Rc::new(tooltip));
137        self
138    }
139}
140
141impl private::ToggleButtonStyle for ToggleButtonWithIcon {}
142
143impl ButtonBuilder for ToggleButtonWithIcon {
144    fn into_configuration(self) -> ButtonConfiguration {
145        ButtonConfiguration {
146            label: self.label,
147            icon: Some(self.icon),
148            on_click: self.on_click,
149            selected: self.selected,
150            tooltip: self.tooltip,
151        }
152    }
153}
154
155#[derive(Clone, Copy, PartialEq)]
156pub enum ToggleButtonGroupStyle {
157    Transparent,
158    Filled,
159    Outlined,
160}
161
162#[derive(Clone, Copy, PartialEq)]
163pub enum ToggleButtonGroupSize {
164    Default,
165    Medium,
166    Large,
167    Custom(Rems),
168}
169
170#[derive(IntoElement)]
171pub struct ToggleButtonGroup<T, const COLS: usize = 3, const ROWS: usize = 1>
172where
173    T: ButtonBuilder,
174{
175    group_name: SharedString,
176    rows: [[T; COLS]; ROWS],
177    style: ToggleButtonGroupStyle,
178    size: ToggleButtonGroupSize,
179    label_size: LabelSize,
180    group_width: Option<DefiniteLength>,
181    auto_width: bool,
182    selected_index: usize,
183    tab_index: Option<isize>,
184}
185
186impl<T: ButtonBuilder, const COLS: usize> ToggleButtonGroup<T, COLS> {
187    pub fn single_row(group_name: impl Into<SharedString>, buttons: [T; COLS]) -> Self {
188        Self {
189            group_name: group_name.into(),
190            rows: [buttons],
191            style: ToggleButtonGroupStyle::Transparent,
192            size: ToggleButtonGroupSize::Default,
193            label_size: LabelSize::Small,
194            group_width: None,
195            auto_width: false,
196            selected_index: 0,
197            tab_index: None,
198        }
199    }
200}
201
202impl<T: ButtonBuilder, const COLS: usize> ToggleButtonGroup<T, COLS, 2> {
203    pub fn two_rows(
204        group_name: impl Into<SharedString>,
205        first_row: [T; COLS],
206        second_row: [T; COLS],
207    ) -> Self {
208        Self {
209            group_name: group_name.into(),
210            rows: [first_row, second_row],
211            style: ToggleButtonGroupStyle::Transparent,
212            size: ToggleButtonGroupSize::Default,
213            label_size: LabelSize::Small,
214            group_width: None,
215            auto_width: false,
216            selected_index: 0,
217            tab_index: None,
218        }
219    }
220}
221
222impl<T: ButtonBuilder, const COLS: usize, const ROWS: usize> ToggleButtonGroup<T, COLS, ROWS> {
223    pub fn style(mut self, style: ToggleButtonGroupStyle) -> Self {
224        self.style = style;
225        self
226    }
227
228    pub fn size(mut self, size: ToggleButtonGroupSize) -> Self {
229        self.size = size;
230        self
231    }
232
233    pub fn selected_index(mut self, index: usize) -> Self {
234        self.selected_index = index;
235        self
236    }
237
238    /// Makes the button group size itself to fit the content of the buttons,
239    /// rather than filling the full width of its parent.
240    pub fn auto_width(mut self) -> Self {
241        self.auto_width = true;
242        self
243    }
244
245    pub fn label_size(mut self, label_size: LabelSize) -> Self {
246        self.label_size = label_size;
247        self
248    }
249
250    /// Sets the tab index for the toggle button group.
251    /// The tab index is set to the initial value provided, then the
252    /// value is incremented by the number of buttons in the group.
253    pub fn tab_index(mut self, tab_index: &mut isize) -> Self {
254        self.tab_index = Some(*tab_index);
255        *tab_index += (COLS * ROWS) as isize;
256        self
257    }
258
259    const fn button_width() -> DefiniteLength {
260        relative(1. / COLS as f32)
261    }
262}
263
264impl<T: ButtonBuilder, const COLS: usize, const ROWS: usize> FixedWidth
265    for ToggleButtonGroup<T, COLS, ROWS>
266{
267    fn width(mut self, width: impl Into<DefiniteLength>) -> Self {
268        self.group_width = Some(width.into());
269        self
270    }
271
272    fn full_width(mut self) -> Self {
273        self.group_width = Some(relative(1.));
274        self
275    }
276}
277
278impl<T: ButtonBuilder, const COLS: usize, const ROWS: usize> RenderOnce
279    for ToggleButtonGroup<T, COLS, ROWS>
280{
281    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
282        let custom_height = match self.size {
283            ToggleButtonGroupSize::Custom(height) => Some(height),
284            _ => None,
285        };
286
287        let entries =
288            self.rows.into_iter().enumerate().map(|(row_index, row)| {
289                let group_name = self.group_name.clone();
290                row.into_iter().enumerate().map(move |(col_index, button)| {
291                    let ButtonConfiguration {
292                        label,
293                        icon,
294                        on_click,
295                        selected,
296                        tooltip,
297                    } = button.into_configuration();
298
299                    let entry_index = row_index * COLS + col_index;
300
301                    ButtonLike::new((group_name.clone(), entry_index))
302                        .when(!self.auto_width, |this| this.full_width())
303                        .rounding(Some(
304                            ToggleButtonPosition {
305                                leftmost: col_index == 0,
306                                rightmost: col_index == COLS - 1,
307                                topmost: row_index == 0,
308                                bottommost: row_index == ROWS - 1,
309                            }
310                            .to_rounding(),
311                        ))
312                        .when_some(self.tab_index, |this, tab_index| {
313                            this.tab_index(tab_index + entry_index as isize)
314                        })
315                        .when(entry_index == self.selected_index || selected, |this| {
316                            this.toggle_state(true)
317                                .selected_style(ButtonStyle::Tinted(TintColor::Accent))
318                        })
319                        .when(self.style == ToggleButtonGroupStyle::Filled, |button| {
320                            button.style(ButtonStyle::Filled)
321                        })
322                        .when(self.size == ToggleButtonGroupSize::Medium, |button| {
323                            button.size(ButtonSize::Medium)
324                        })
325                        .when(self.size == ToggleButtonGroupSize::Large, |button| {
326                            button.size(ButtonSize::Large)
327                        })
328                        .when_some(custom_height, |button, height| button.height(height.into()))
329                        .child(
330                            h_flex()
331                                .w_full()
332                                .px_2()
333                                .gap_1p5()
334                                .justify_center()
335                                .flex_none()
336                                .when_some(icon, |this, icon| {
337                                    this.py_2()
338                                        .child(Icon::new(icon).size(IconSize::XSmall).map(|this| {
339                                            if entry_index == self.selected_index || selected {
340                                                this.color(Color::Accent)
341                                            } else {
342                                                this.color(Color::Muted)
343                                            }
344                                        }))
345                                })
346                                .child(Label::new(label).size(self.label_size).when(
347                                    entry_index == self.selected_index || selected,
348                                    |this| this.color(Color::Accent),
349                                )),
350                        )
351                        .when_some(tooltip, |this, tooltip| {
352                            this.tooltip(move |window, cx| tooltip(window, cx))
353                        })
354                        .on_click(on_click)
355                        .into_any_element()
356                })
357            });
358
359        let border_color = cx.theme().colors().border.opacity(0.6);
360        let is_outlined_or_filled = self.style == ToggleButtonGroupStyle::Outlined
361            || self.style == ToggleButtonGroupStyle::Filled;
362        let is_transparent = self.style == ToggleButtonGroupStyle::Transparent;
363
364        v_flex()
365            .map(|this| {
366                if let Some(width) = self.group_width {
367                    this.w(width)
368                } else if self.auto_width {
369                    this
370                } else {
371                    this.w_full()
372                }
373            })
374            .rounded_md()
375            .overflow_hidden()
376            .map(|this| {
377                if is_transparent {
378                    this.gap_px()
379                } else {
380                    this.border_1().border_color(border_color)
381                }
382            })
383            .children(entries.enumerate().map(|(row_index, row)| {
384                let last_row = row_index == ROWS - 1;
385                h_flex()
386                    .when(!is_outlined_or_filled, |this| this.gap_px())
387                    .when(is_outlined_or_filled && !last_row, |this| {
388                        this.border_b_1().border_color(border_color)
389                    })
390                    .children(row.enumerate().map(|(item_index, item)| {
391                        let last_item = item_index == COLS - 1;
392                        div()
393                            .when(is_outlined_or_filled && !last_item, |this| {
394                                this.border_r_1().border_color(border_color)
395                            })
396                            .when(!self.auto_width, |this| this.w(Self::button_width()))
397                            .overflow_hidden()
398                            .child(item)
399                    }))
400            }))
401    }
402}
403
404fn register_toggle_button_group() {
405    component::register_component::<ToggleButtonGroup<ToggleButtonSimple>>();
406}
407
408component::__private::inventory::submit! {
409    component::ComponentFn::new(register_toggle_button_group)
410}
411
412impl<T: ButtonBuilder, const COLS: usize, const ROWS: usize> Component
413    for ToggleButtonGroup<T, COLS, ROWS>
414{
415    fn name() -> &'static str {
416        "ToggleButtonGroup"
417    }
418
419    fn scope() -> ComponentScope {
420        ComponentScope::Input
421    }
422
423    fn sort_name() -> &'static str {
424        "ButtonG"
425    }
426
427    fn description() -> &'static str {
428        "A grouped set of toggle buttons arranged in rows and columns, \
429        where each button represents a mutually exclusive option in a segmented control."
430    }
431
432    fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement {
433        v_flex()
434            .gap_6()
435            .children(vec![example_group_with_title(
436                "Transparent Variant",
437                vec![
438                    single_example(
439                        "Single Row Group",
440                        ToggleButtonGroup::single_row(
441                            "single_row_test",
442                            [
443                                ToggleButtonSimple::new("First", |_, _, _| {}),
444                                ToggleButtonSimple::new("Second", |_, _, _| {}),
445                                ToggleButtonSimple::new("Third", |_, _, _| {}),
446                            ],
447                        )
448                        .selected_index(1)
449                        .into_any_element(),
450                    ),
451                    single_example(
452                        "Single Row Group with icons",
453                        ToggleButtonGroup::single_row(
454                            "single_row_test_icon",
455                            [
456                                ToggleButtonWithIcon::new("First", IconName::AiZed, |_, _, _| {}),
457                                ToggleButtonWithIcon::new("Second", IconName::AiZed, |_, _, _| {}),
458                                ToggleButtonWithIcon::new("Third", IconName::AiZed, |_, _, _| {}),
459                            ],
460                        )
461                        .selected_index(1)
462                        .into_any_element(),
463                    ),
464                    single_example(
465                        "Multiple Row Group",
466                        ToggleButtonGroup::two_rows(
467                            "multiple_row_test",
468                            [
469                                ToggleButtonSimple::new("First", |_, _, _| {}),
470                                ToggleButtonSimple::new("Second", |_, _, _| {}),
471                                ToggleButtonSimple::new("Third", |_, _, _| {}),
472                            ],
473                            [
474                                ToggleButtonSimple::new("Fourth", |_, _, _| {}),
475                                ToggleButtonSimple::new("Fifth", |_, _, _| {}),
476                                ToggleButtonSimple::new("Sixth", |_, _, _| {}),
477                            ],
478                        )
479                        .selected_index(3)
480                        .into_any_element(),
481                    ),
482                    single_example(
483                        "Multiple Row Group with Icons",
484                        ToggleButtonGroup::two_rows(
485                            "multiple_row_test_icons",
486                            [
487                                ToggleButtonWithIcon::new("First", IconName::AiZed, |_, _, _| {}),
488                                ToggleButtonWithIcon::new("Second", IconName::AiZed, |_, _, _| {}),
489                                ToggleButtonWithIcon::new("Third", IconName::AiZed, |_, _, _| {}),
490                            ],
491                            [
492                                ToggleButtonWithIcon::new("Fourth", IconName::AiZed, |_, _, _| {}),
493                                ToggleButtonWithIcon::new("Fifth", IconName::AiZed, |_, _, _| {}),
494                                ToggleButtonWithIcon::new("Sixth", IconName::AiZed, |_, _, _| {}),
495                            ],
496                        )
497                        .selected_index(3)
498                        .into_any_element(),
499                    ),
500                ],
501            )])
502            .children(vec![example_group_with_title(
503                "Outlined Variant",
504                vec![
505                    single_example(
506                        "Single Row Group",
507                        ToggleButtonGroup::single_row(
508                            "single_row_test_outline",
509                            [
510                                ToggleButtonSimple::new("First", |_, _, _| {}),
511                                ToggleButtonSimple::new("Second", |_, _, _| {}),
512                                ToggleButtonSimple::new("Third", |_, _, _| {}),
513                            ],
514                        )
515                        .selected_index(1)
516                        .style(ToggleButtonGroupStyle::Outlined)
517                        .into_any_element(),
518                    ),
519                    single_example(
520                        "Single Row Group with icons",
521                        ToggleButtonGroup::single_row(
522                            "single_row_test_icon_outlined",
523                            [
524                                ToggleButtonWithIcon::new("First", IconName::AiZed, |_, _, _| {}),
525                                ToggleButtonWithIcon::new("Second", IconName::AiZed, |_, _, _| {}),
526                                ToggleButtonWithIcon::new("Third", IconName::AiZed, |_, _, _| {}),
527                            ],
528                        )
529                        .selected_index(1)
530                        .style(ToggleButtonGroupStyle::Outlined)
531                        .into_any_element(),
532                    ),
533                    single_example(
534                        "Multiple Row Group",
535                        ToggleButtonGroup::two_rows(
536                            "multiple_row_test",
537                            [
538                                ToggleButtonSimple::new("First", |_, _, _| {}),
539                                ToggleButtonSimple::new("Second", |_, _, _| {}),
540                                ToggleButtonSimple::new("Third", |_, _, _| {}),
541                            ],
542                            [
543                                ToggleButtonSimple::new("Fourth", |_, _, _| {}),
544                                ToggleButtonSimple::new("Fifth", |_, _, _| {}),
545                                ToggleButtonSimple::new("Sixth", |_, _, _| {}),
546                            ],
547                        )
548                        .selected_index(3)
549                        .style(ToggleButtonGroupStyle::Outlined)
550                        .into_any_element(),
551                    ),
552                    single_example(
553                        "Multiple Row Group with Icons",
554                        ToggleButtonGroup::two_rows(
555                            "multiple_row_test",
556                            [
557                                ToggleButtonWithIcon::new("First", IconName::AiZed, |_, _, _| {}),
558                                ToggleButtonWithIcon::new("Second", IconName::AiZed, |_, _, _| {}),
559                                ToggleButtonWithIcon::new("Third", IconName::AiZed, |_, _, _| {}),
560                            ],
561                            [
562                                ToggleButtonWithIcon::new("Fourth", IconName::AiZed, |_, _, _| {}),
563                                ToggleButtonWithIcon::new("Fifth", IconName::AiZed, |_, _, _| {}),
564                                ToggleButtonWithIcon::new("Sixth", IconName::AiZed, |_, _, _| {}),
565                            ],
566                        )
567                        .selected_index(3)
568                        .style(ToggleButtonGroupStyle::Outlined)
569                        .into_any_element(),
570                    ),
571                ],
572            )])
573            .children(vec![example_group_with_title(
574                "Filled Variant",
575                vec![
576                    single_example(
577                        "Single Row Group",
578                        ToggleButtonGroup::single_row(
579                            "single_row_test_outline",
580                            [
581                                ToggleButtonSimple::new("First", |_, _, _| {}),
582                                ToggleButtonSimple::new("Second", |_, _, _| {}),
583                                ToggleButtonSimple::new("Third", |_, _, _| {}),
584                            ],
585                        )
586                        .selected_index(2)
587                        .style(ToggleButtonGroupStyle::Filled)
588                        .into_any_element(),
589                    ),
590                    single_example(
591                        "Single Row Group with icons",
592                        ToggleButtonGroup::single_row(
593                            "single_row_test_icon_outlined",
594                            [
595                                ToggleButtonWithIcon::new("First", IconName::AiZed, |_, _, _| {}),
596                                ToggleButtonWithIcon::new("Second", IconName::AiZed, |_, _, _| {}),
597                                ToggleButtonWithIcon::new("Third", IconName::AiZed, |_, _, _| {}),
598                            ],
599                        )
600                        .selected_index(1)
601                        .style(ToggleButtonGroupStyle::Filled)
602                        .into_any_element(),
603                    ),
604                    single_example(
605                        "Multiple Row Group",
606                        ToggleButtonGroup::two_rows(
607                            "multiple_row_test",
608                            [
609                                ToggleButtonSimple::new("First", |_, _, _| {}),
610                                ToggleButtonSimple::new("Second", |_, _, _| {}),
611                                ToggleButtonSimple::new("Third", |_, _, _| {}),
612                            ],
613                            [
614                                ToggleButtonSimple::new("Fourth", |_, _, _| {}),
615                                ToggleButtonSimple::new("Fifth", |_, _, _| {}),
616                                ToggleButtonSimple::new("Sixth", |_, _, _| {}),
617                            ],
618                        )
619                        .selected_index(3)
620                        .width(rems_from_px(100.))
621                        .style(ToggleButtonGroupStyle::Filled)
622                        .into_any_element(),
623                    ),
624                    single_example(
625                        "Multiple Row Group with Icons",
626                        ToggleButtonGroup::two_rows(
627                            "multiple_row_test",
628                            [
629                                ToggleButtonWithIcon::new("First", IconName::AiZed, |_, _, _| {}),
630                                ToggleButtonWithIcon::new("Second", IconName::AiZed, |_, _, _| {}),
631                                ToggleButtonWithIcon::new("Third", IconName::AiZed, |_, _, _| {}),
632                            ],
633                            [
634                                ToggleButtonWithIcon::new("Fourth", IconName::AiZed, |_, _, _| {}),
635                                ToggleButtonWithIcon::new("Fifth", IconName::AiZed, |_, _, _| {}),
636                                ToggleButtonWithIcon::new("Sixth", IconName::AiZed, |_, _, _| {}),
637                            ],
638                        )
639                        .selected_index(3)
640                        .width(rems_from_px(100.))
641                        .style(ToggleButtonGroupStyle::Filled)
642                        .into_any_element(),
643                    ),
644                ],
645            )])
646            .children(vec![single_example(
647                "With Tooltips",
648                ToggleButtonGroup::single_row(
649                    "with_tooltips",
650                    [
651                        ToggleButtonSimple::new("First", |_, _, _| {})
652                            .tooltip(Tooltip::text("This is a tooltip. Hello!")),
653                        ToggleButtonSimple::new("Second", |_, _, _| {})
654                            .tooltip(Tooltip::text("This is a tooltip. Hey?")),
655                        ToggleButtonSimple::new("Third", |_, _, _| {})
656                            .tooltip(Tooltip::text("This is a tooltip. Get out of here now!")),
657                    ],
658                )
659                .selected_index(1)
660                .into_any_element(),
661            )])
662            .into_any_element()
663    }
664}
665
Served at tenant.openagents/omega Member data and write actions are omitted.