egui_macview/controls/
button_cell.rs1use crate::controls::MacControlSize;
7use crate::layout::subpixel_snap_rect;
8use crate::theme::MacTheme;
9use egui::{Color32, CornerRadius, Rect, Response, Stroke, StrokeKind, Ui, Vec2, Widget};
10
11pub struct MacButtonCell<'a> {
32 text: &'a str,
33 selected: bool,
34 enabled: bool,
35 theme: Option<&'a MacTheme>,
36 control_size: MacControlSize,
37 width: Option<f32>,
38 height: Option<f32>,
39 corner_radius: Option<f32>,
40 fixed_rect: Option<Rect>,
41}
42
43pub type MacButton<'a> = MacButtonCell<'a>;
45
46impl<'a> MacButtonCell<'a> {
47 pub fn new(text: &'a str) -> Self {
49 Self {
50 text,
51 selected: false,
52 enabled: true,
53 theme: None,
54 control_size: MacControlSize::Regular,
55 width: None,
56 height: None,
57 corner_radius: None,
58 fixed_rect: None,
59 }
60 }
61
62 pub fn control_size(mut self, size: MacControlSize) -> Self {
64 self.control_size = size;
65 self
66 }
67
68 pub fn fixed_rect(mut self, rect: Rect) -> Self {
70 self.fixed_rect = Some(rect);
71 self
72 }
73
74 pub fn show(self, ui: &mut Ui) -> Response {
76 ui.add(self)
77 }
78
79 pub fn enabled(mut self, enabled: bool) -> Self {
81 self.enabled = enabled;
82 self
83 }
84
85 pub fn theme(mut self, theme: &'a MacTheme) -> Self {
87 self.theme = Some(theme);
88 self
89 }
90
91 pub fn selected(mut self, selected: bool) -> Self {
93 self.selected = selected;
94 self
95 }
96
97 pub fn width(mut self, width: f32) -> Self {
99 self.width = Some(width);
100 self
101 }
102
103 pub fn height(mut self, height: f32) -> Self {
105 self.height = Some(height);
106 self
107 }
108
109 pub fn corner_radius(mut self, r: f32) -> Self {
111 self.corner_radius = Some(r);
112 self
113 }
114}
115
116impl<'a> Widget for MacButtonCell<'a> {
117 fn ui(self, ui: &mut Ui) -> Response {
118 let theme_cow = MacTheme::resolve(self.theme, ui.ctx());
119 let theme: &MacTheme = &theme_cow;
120
121 let metrics = match self.control_size {
122 MacControlSize::Regular | MacControlSize::Large => &theme.button().regular,
123 MacControlSize::Small | MacControlSize::CompactInfobox => &theme.button().small,
124 MacControlSize::Mini => &theme.button().mini,
125 };
126 let height = self
127 .fixed_rect
128 .map(|r| r.height())
129 .or(self.height)
130 .unwrap_or_else(|| metrics.height.points());
131 let font_sz = if height <= theme.button().mini.height.points() {
132 theme.button().mini.font_size.points()
133 } else if height <= theme.button().small.height.points() {
134 theme.button().small.font_size.points()
135 } else {
136 metrics.font_size.points()
137 };
138 let corner_radius = self
139 .corner_radius
140 .unwrap_or_else(|| metrics.corner_radius.points());
141 let padding_h = metrics
142 .padding_horizontal
143 .as_ref()
144 .map(|d| d.points())
145 .unwrap_or(font_sz);
146
147 let font_id = egui::FontId::proportional(font_sz);
148 let text_layout = theme.layout_tracked_galley(
149 ui,
150 self.text,
151 font_id,
152 Color32::PLACEHOLDER,
153 f32::INFINITY,
154 );
155 let desired_width = self
156 .width
157 .unwrap_or_else(|| text_layout.size().x + padding_h * 2.0);
158
159 let enabled = self.enabled && ui.is_enabled();
160 let sense = if enabled {
161 egui::Sense::click()
162 } else {
163 egui::Sense::hover()
164 };
165 let ppp = ui.ctx().pixels_per_point();
166 let (rect, response) = if let Some(fixed) = self.fixed_rect {
167 let snapped = subpixel_snap_rect(fixed, ppp);
168 let id = ui.next_auto_id().with("mac_button_cell_fixed");
169 ui.skip_ahead_auto_ids(1);
170 let resp = ui.interact(snapped, id, sense);
171 (snapped, resp)
172 } else {
173 let (r, resp) = ui.allocate_exact_size(Vec2::new(desired_width, height), sense);
174 (subpixel_snap_rect(r, ppp), resp)
175 };
176 let painter = ui.painter_at(rect);
177
178 let alpha_factor = if enabled {
179 1.0
180 } else {
181 theme.disabled_opacity()
182 };
183
184 let base_bg = if !enabled {
186 if self.selected {
187 theme.selected_control_inactive()
188 } else {
189 theme.control_background()
190 }
191 } else if response.is_pointer_button_down_on() {
192 theme.selected_control_inactive()
193 } else if self.selected {
194 theme.selected_control()
195 } else if response.hovered() {
196 theme.control()
197 } else {
198 theme.control_background()
199 };
200 let bg_color = base_bg.gamma_multiply(alpha_factor);
201
202 let border_stroke = Stroke::new(
203 metrics.border_width.points(),
204 theme.separator().gamma_multiply(alpha_factor),
205 );
206
207 painter.rect(
208 rect,
209 CornerRadius::same(corner_radius as u8),
210 bg_color,
211 border_stroke,
212 StrokeKind::Inside,
213 );
214
215 let bg_style = theme.background_style(base_bg);
216 let text_color = bg_style
217 .primary_text_color(theme)
218 .gamma_multiply(alpha_factor);
219
220 let text_pos = egui::Align2::CENTER_CENTER
221 .anchor_size(rect.center(), text_layout.size())
222 .min;
223 painter.galley(text_pos, text_layout, text_color);
224
225 response
226 }
227}