Skip to main content

guise/input/
segmented.rs

1//! `SegmentedControl` — a stateful single-choice segmented switch (gpui entity).
2
3use gpui::prelude::*;
4use gpui::{div, px, Context, EventEmitter, IntoElement, SharedString, Window};
5
6use super::control_metrics;
7use crate::theme::{theme, ColorName, Size};
8
9/// Emitted when the selected segment changes. Carries the option index.
10#[derive(Debug, Clone)]
11pub struct SegmentedControlEvent(pub usize);
12
13/// A segmented control. Create with
14/// `cx.new(|cx| SegmentedControl::new(cx).data(["Day", "Week", "Month"]))`.
15pub struct SegmentedControl {
16    options: Vec<SharedString>,
17    selected: usize,
18    size: Size,
19}
20
21impl EventEmitter<SegmentedControlEvent> for SegmentedControl {}
22
23impl SegmentedControl {
24    pub fn new(_cx: &mut Context<Self>) -> Self {
25        SegmentedControl {
26            options: Vec::new(),
27            selected: 0,
28            size: Size::Sm,
29        }
30    }
31
32    pub fn data<I, S>(mut self, options: I) -> Self
33    where
34        I: IntoIterator<Item = S>,
35        S: Into<SharedString>,
36    {
37        self.options = options.into_iter().map(Into::into).collect();
38        self
39    }
40
41    pub fn selected(mut self, index: usize) -> Self {
42        self.selected = index;
43        self
44    }
45
46    pub fn size(mut self, size: Size) -> Self {
47        self.size = size;
48        self
49    }
50
51    pub fn selected_index(&self) -> usize {
52        self.selected
53    }
54}
55
56impl Render for SegmentedControl {
57    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
58        let t = theme(cx);
59        let (height, pad_x, font) = control_metrics(self.size);
60        let radius = t.radius(Size::Sm);
61        let track = t.color(ColorName::Gray, if t.scheme.is_dark() { 8 } else { 1 }).hsla();
62        let active_bg = t.surface().hsla();
63        let active_fg = t.text().hsla();
64        let inactive_fg = t.dimmed().hsla();
65
66        let count = self.options.len();
67        let selected = if count == 0 { 0 } else { self.selected.min(count - 1) };
68
69        let mut row = div()
70            .flex()
71            .items_center()
72            .gap(px(2.0))
73            .p(px(3.0))
74            .rounded(px(radius + 2.0))
75            .bg(track);
76
77        for (i, option) in self.options.iter().enumerate() {
78            let is_active = i == selected;
79            let mut seg = div()
80                .id(("guise-segment", i))
81                .flex()
82                .items_center()
83                .justify_center()
84                .h(px(height - 6.0))
85                .px(px(pad_x))
86                .rounded(px(radius))
87                .text_size(px(font))
88                .text_color(if is_active { active_fg } else { inactive_fg })
89                .child(option.clone())
90                .on_click(cx.listener(move |this, _ev, _window, cx| {
91                    this.selected = i;
92                    cx.emit(SegmentedControlEvent(i));
93                    cx.notify();
94                }));
95            if is_active {
96                seg = seg.bg(active_bg).shadow_sm();
97            }
98            row = row.child(seg);
99        }
100        row
101    }
102}