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