1use egui::{
4 Color32, CornerRadius, Response, Sense, Stroke, Ui, Vec2, Widget, WidgetInfo, WidgetText,
5 WidgetType,
6};
7
8use crate::theme::{with_alpha, Accent, Theme};
9
10#[must_use = "Add with `ui.add(...)`."]
24pub struct SegmentedButton<'a> {
25 on: &'a mut bool,
26 label: WidgetText,
27 accent: Accent,
28 dim_when_on: bool,
31 rounded: bool,
32 corner_radius: Option<CornerRadius>,
33 min_width: Option<f32>,
34}
35
36impl<'a> std::fmt::Debug for SegmentedButton<'a> {
37 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38 f.debug_struct("SegmentedButton")
39 .field("on", &*self.on)
40 .field("label", &self.label.text())
41 .field("accent", &self.accent)
42 .field("dim_when_on", &self.dim_when_on)
43 .field("rounded", &self.rounded)
44 .field("corner_radius", &self.corner_radius)
45 .field("min_width", &self.min_width)
46 .finish()
47 }
48}
49
50impl<'a> SegmentedButton<'a> {
51 pub fn new(on: &'a mut bool, label: impl Into<WidgetText>) -> Self {
53 Self {
54 on,
55 label: label.into(),
56 accent: Accent::Green,
57 dim_when_on: false,
58 rounded: true,
59 corner_radius: None,
60 min_width: None,
61 }
62 }
63
64 pub fn accent(mut self, accent: Accent) -> Self {
66 self.accent = accent;
67 self
68 }
69
70 pub fn dim_when_on(mut self, dim: bool) -> Self {
73 self.dim_when_on = dim;
74 self
75 }
76
77 pub fn rounded(mut self, rounded: bool) -> Self {
80 self.rounded = rounded;
81 self
82 }
83
84 pub fn corner_radius(mut self, radius: impl Into<CornerRadius>) -> Self {
87 self.corner_radius = Some(radius.into());
88 self
89 }
90
91 pub fn min_width(mut self, width: f32) -> Self {
94 self.min_width = Some(width);
95 self
96 }
97
98 fn on_fill(&self, theme: &Theme) -> Color32 {
99 theme.palette.accent_fill(self.accent)
100 }
101
102 fn on_fill_hover(&self, theme: &Theme) -> Color32 {
103 theme.palette.accent_hover(self.accent)
104 }
105}
106
107impl<'a> Widget for SegmentedButton<'a> {
108 fn ui(self, ui: &mut Ui) -> Response {
109 let theme = Theme::current(ui.ctx());
110 let p = &theme.palette;
111 let t = &theme.typography;
112
113 let pad_x = theme.control_padding_x;
114 let pad_y = 10.0;
117 let led_size = 8.0;
118 let led_gap = 7.0;
119
120 let galley =
121 crate::theme::placeholder_galley(ui, self.label.text(), t.button, true, f32::INFINITY);
122
123 let content_w = led_size + led_gap + galley.size().x;
124 let mut desired = Vec2::new(pad_x * 2.0 + content_w, pad_y * 2.0 + galley.size().y);
125 if let Some(min_w) = self.min_width {
126 desired.x = desired.x.max(min_w);
127 }
128 let (rect, mut response) = ui.allocate_exact_size(desired, Sense::click());
129
130 if response.clicked() {
131 *self.on = !*self.on;
132 response.mark_changed();
133 }
134
135 if ui.is_rect_visible(rect) {
136 let on = *self.on;
137 let hovered = response.hovered();
138 let is_down = response.is_pointer_button_down_on();
139
140 let (fill, text_color, led_color, led_glow) = if on {
141 let mut fill = if is_down {
142 crate::theme::mix(self.on_fill_hover(&theme), Color32::BLACK, 0.1)
143 } else if hovered {
144 self.on_fill_hover(&theme)
145 } else {
146 self.on_fill(&theme)
147 };
148 let mut text = Color32::WHITE;
149 if self.dim_when_on {
150 fill = crate::theme::mix(fill, p.card, 0.55);
151 text = p.text_muted;
152 }
153 let led = Color32::WHITE;
154 let glow = !self.dim_when_on;
155 (fill, text, led, glow)
156 } else {
157 let fill = if hovered {
158 p.depth_tint(p.input_bg, 0.05)
159 } else {
160 p.input_bg
161 };
162 let text = if hovered { p.text_muted } else { p.text_faint };
163 let led = p.text_faint;
164 (fill, text, led, false)
165 };
166
167 let radius = self.corner_radius.unwrap_or_else(|| {
168 if self.rounded {
169 CornerRadius::same(theme.control_radius as u8 + 2)
170 } else {
171 CornerRadius::ZERO
172 }
173 });
174 ui.painter()
175 .rect(rect, radius, fill, Stroke::NONE, egui::StrokeKind::Inside);
176
177 let content_start = rect.center().x - content_w * 0.5;
179 let led_center = egui::pos2(content_start + led_size * 0.5, rect.center().y);
180 if led_glow {
181 ui.painter().circle_filled(
182 led_center,
183 led_size * 0.5 + 2.0,
184 with_alpha(Color32::WHITE, 70),
185 );
186 }
187 ui.painter()
188 .circle_filled(led_center, led_size * 0.5, led_color);
189
190 let text_pos = egui::pos2(
191 led_center.x + led_size * 0.5 + led_gap,
192 rect.center().y - galley.size().y * 0.5,
193 );
194 ui.painter().galley(text_pos, galley, text_color);
195 }
196
197 response.widget_info(|| {
198 WidgetInfo::selected(WidgetType::Checkbox, true, *self.on, self.label.text())
199 });
200 response
201 }
202}