Skip to repository content665 lines · 26.2 KB · rust
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T04:27:36.886Z Public web read
NIP-34 coordinate
30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omegaMaintainersHidden in public view
References2 branches · 1 tag
Read-only clone
git clone https://openagents.com/git/tenant.openagents/omega.gitBrowse files
toggle_button.rs
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