ui/components/
segmented_control.rs1use gpui::{AnyElement, ElementId, IntoElement, white};
2use std::rc::Rc;
3
4use crate::prelude::*;
5
6#[derive(IntoElement, RegisterComponent)]
13pub struct SegmentedControl {
14 id: ElementId,
15 segments: Vec<SharedString>,
16 active: usize,
17 disabled: bool,
18 on_change: Option<Rc<dyn Fn(usize, &mut Window, &mut App) + 'static>>,
19}
20
21impl SegmentedControl {
22 pub fn new(
23 id: impl Into<ElementId>,
24 segments: impl IntoIterator<Item = impl Into<SharedString>>,
25 ) -> Self {
26 Self {
27 id: id.into(),
28 segments: segments.into_iter().map(Into::into).collect(),
29 active: 0,
30 disabled: false,
31 on_change: None,
32 }
33 }
34
35 pub fn active(mut self, index: usize) -> Self {
37 self.active = index;
38 self
39 }
40
41 pub fn disabled(mut self, disabled: bool) -> Self {
42 self.disabled = disabled;
43 self
44 }
45
46 pub fn on_change(mut self, handler: impl Fn(usize, &mut Window, &mut App) + 'static) -> Self {
48 self.on_change = Some(Rc::new(handler));
49 self
50 }
51}
52
53impl RenderOnce for SegmentedControl {
54 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
55 let active = self.active;
56 let disabled = self.disabled;
57 let group_id = self.id.clone();
58 let on_change = self.on_change;
59
60 let mut row = h_flex()
61 .id(self.id)
62 .rounded_md()
63 .border_1()
64 .border_color(semantic::border(cx))
65 .bg(semantic::surface(cx))
66 .p(px(2.))
67 .gap_0p5();
68
69 for (i, segment) in self.segments.into_iter().enumerate() {
70 let is_active = i == active;
71 let on_change = on_change.clone();
72
73 let mut cell = h_flex()
74 .id((group_id.clone(), i.to_string()))
75 .debug_selector({
82 let group_id = group_id.clone();
83 move || format!("SEGMENT-{}-{}", group_id, i)
84 })
85 .flex_1()
86 .justify_center()
87 .px_3()
88 .py_1()
89 .rounded_sm()
90 .when(is_active, |this| this.bg(palette::primary(600)))
91 .when(disabled, |this| this.opacity(0.5))
92 .child(
93 Label::new(segment)
94 .size(LabelSize::Small)
95 .color(if is_active {
96 Color::Custom(white())
97 } else {
98 Color::Default
99 }),
100 );
101
102 if !disabled {
103 cell = cell.cursor_pointer().on_click(move |_, window, cx| {
104 if let Some(handler) = &on_change {
105 handler(i, window, cx);
106 }
107 });
108 }
109
110 row = row.child(cell);
111 }
112
113 row
114 }
115}
116
117impl Component for SegmentedControl {
118 fn scope() -> ComponentScope {
119 ComponentScope::Input
120 }
121
122 fn description() -> Option<&'static str> {
123 Some("A connected row of segments where exactly one is active.")
124 }
125
126 fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
127 Some(
128 v_flex()
129 .gap_4()
130 .child(
131 SegmentedControl::new("segmented-default", ["Day", "Week", "Month"])
132 .active(1)
133 .into_any_element(),
134 )
135 .child(
136 SegmentedControl::new("segmented-disabled", ["List", "Grid"])
137 .disabled(true)
138 .into_any_element(),
139 )
140 .into_any_element(),
141 )
142 }
143}