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