1use gpui::prelude::*;
4use gpui::{div, px, App, Context, Entity, EventEmitter, IntoElement, SharedString, Window};
5
6use super::control_metrics;
7use crate::devtools::Probed;
8use crate::reactive::Signal;
9use crate::theme::{theme, ColorName, Size};
10
11#[derive(Debug, Clone)]
13pub struct SegmentedControlEvent(pub usize);
14
15pub struct SegmentedControl {
18 options: Vec<SharedString>,
19 selected: usize,
20 size: Size,
21}
22
23impl EventEmitter<SegmentedControlEvent> for SegmentedControl {}
24
25impl SegmentedControl {
26 pub fn new(_cx: &mut Context<Self>) -> Self {
27 SegmentedControl {
28 options: Vec::new(),
29 selected: 0,
30 size: Size::Sm,
31 }
32 }
33
34 pub fn data<I, S>(mut self, options: I) -> Self
35 where
36 I: IntoIterator<Item = S>,
37 S: Into<SharedString>,
38 {
39 self.options = options.into_iter().map(Into::into).collect();
40 self.selected = self.selected.min(self.options.len().saturating_sub(1));
41 self
42 }
43
44 pub fn selected(mut self, index: usize) -> Self {
45 self.selected = index;
46 self
47 }
48
49 pub fn size(mut self, size: Size) -> Self {
50 self.size = size;
51 self
52 }
53
54 pub fn selected_index(&self) -> usize {
55 self.selected
56 }
57
58 pub fn bind(entity: &Entity<SegmentedControl>, signal: &Signal<usize>, cx: &mut App) {
64 let initial = signal.get(cx);
65 entity.update(cx, |this, cx| this.sync_selected(initial, cx));
66 let sink = signal.clone();
67 cx.subscribe(
68 entity,
69 move |_control, event: &SegmentedControlEvent, cx| {
70 sink.set_if_changed(cx, event.0);
71 },
72 )
73 .detach();
74 let control = entity.downgrade();
75 cx.observe(signal.entity(), move |observed, cx| {
76 let index = *observed.read(cx);
77 control
78 .update(cx, |this, cx| this.sync_selected(index, cx))
79 .ok();
80 })
81 .detach();
82 }
83
84 fn sync_selected(&mut self, index: usize, cx: &mut Context<Self>) {
86 let selected = index.min(self.options.len().saturating_sub(1));
87 if self.selected != selected {
88 self.selected = selected;
89 cx.notify();
90 }
91 }
92}
93
94impl Render for SegmentedControl {
95 fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
96 let t = theme(cx);
97 let (height, pad_x, font) = control_metrics(self.size);
98 let radius = t.radius(Size::Sm);
99 let track = t
100 .color(ColorName::Gray, if t.scheme.is_dark() { 8 } else { 1 })
101 .hsla();
102 let active_bg = t.surface().hsla();
103 let active_fg = t.text().hsla();
104 let inactive_fg = t.dimmed().hsla();
105
106 let count = self.options.len();
107 let selected = if count == 0 {
108 0
109 } else {
110 self.selected.min(count - 1)
111 };
112
113 let mut row = div()
114 .flex()
115 .items_center()
116 .gap(px(2.0))
117 .p(px(3.0))
118 .rounded(px(radius + 2.0))
119 .bg(track);
120
121 for (i, option) in self.options.iter().enumerate() {
122 let is_active = i == selected;
123 let mut seg = div()
124 .id(("guise-segment", i))
125 .flex()
126 .items_center()
127 .justify_center()
128 .h(px(height - 6.0))
129 .px(px(pad_x))
130 .rounded(px(radius))
131 .text_size(px(font))
132 .text_color(if is_active { active_fg } else { inactive_fg })
133 .child(option.clone())
134 .on_click(cx.listener(move |this, _ev, _window, cx| {
135 this.selected = i;
136 cx.emit(SegmentedControlEvent(i));
137 cx.notify();
138 }));
139 if is_active {
140 seg = seg.bg(active_bg).shadow_sm();
141 }
142 row = row.child(seg);
143 }
144 row.probe("SegmentedControl")
145 }
146}