1use crate::controls::MacControlSize;
4use crate::layout::subpixel_snap_rect;
5use crate::symbols::MacSymbol;
6use crate::theme::MacTheme;
7use egui::{Align2, FontId, Pos2, Rect, Response, Sense, Stroke, Ui, Vec2, Widget};
8
9pub struct MacHelpButton<'a> {
22 control_size: MacControlSize,
23 fixed_rect: Option<Rect>,
24 enabled: bool,
25 theme: Option<&'a MacTheme>,
26}
27
28impl<'a> Default for MacHelpButton<'a> {
29 fn default() -> Self {
30 Self::new()
31 }
32}
33
34impl<'a> MacHelpButton<'a> {
35 pub fn new() -> Self {
37 Self {
38 control_size: MacControlSize::Regular,
39 fixed_rect: None,
40 enabled: true,
41 theme: None,
42 }
43 }
44
45 pub fn control_size(mut self, size: MacControlSize) -> Self {
47 self.control_size = size;
48 self
49 }
50
51 pub fn fixed_rect(mut self, rect: Rect) -> Self {
53 self.fixed_rect = Some(rect);
54 self
55 }
56
57 pub fn enabled(mut self, enabled: bool) -> Self {
59 self.enabled = enabled;
60 self
61 }
62
63 pub fn theme(mut self, theme: &'a MacTheme) -> Self {
65 self.theme = Some(theme);
66 self
67 }
68
69 pub fn diameter(&self) -> f32 {
71 let theme_cow = MacTheme::resolve_or_default(self.theme);
72 let hm = theme_cow.help_button();
73 match self.control_size {
74 MacControlSize::Regular | MacControlSize::Large => hm.diameter.points(),
75 MacControlSize::Small | MacControlSize::CompactInfobox => hm.small_diameter.points(),
76 MacControlSize::Mini => hm.mini_diameter.points(),
77 }
78 }
79
80 pub fn font_size(&self) -> f32 {
82 let theme_cow = MacTheme::resolve_or_default(self.theme);
83 let hm = theme_cow.help_button();
84 match self.control_size {
85 MacControlSize::Regular | MacControlSize::Large => hm.font_size.points(),
86 MacControlSize::Small | MacControlSize::CompactInfobox => hm.small_font_size.points(),
87 MacControlSize::Mini => hm.mini_font_size.points(),
88 }
89 }
90
91 pub fn show(self, ui: &mut Ui) -> Response {
93 ui.add(self)
94 }
95}
96
97impl<'a> Widget for MacHelpButton<'a> {
98 fn ui(self, ui: &mut Ui) -> Response {
99 let theme_cow = MacTheme::resolve(self.theme, ui.ctx());
100 let theme: &MacTheme = &theme_cow;
101 let hm = theme.help_button();
102
103 let enabled = self.enabled && ui.is_enabled();
104 let diam = self.diameter();
105 let border_w = hm.border_width.points();
106 let shadow_dy = hm.shadow_offset_y.points();
107 let font_sz = self.font_size();
108 let sense = if enabled { Sense::click() } else { Sense::hover() };
109 let ppp = ui.ctx().pixels_per_point();
110 let (rect, response) = if let Some(fixed) = self.fixed_rect {
111 let snapped = subpixel_snap_rect(fixed, ppp);
112 let id = ui.next_auto_id().with("mac_help_btn_fixed");
113 ui.skip_ahead_auto_ids(1);
114 (snapped, ui.interact(snapped, id, sense))
115 } else {
116 let (r, resp) = ui.allocate_exact_size(Vec2::splat(diam), sense);
117 (subpixel_snap_rect(r, ppp), resp)
118 };
119
120 let alpha = if enabled { 1.0 } else { theme.disabled_opacity() };
121 let painter = ui.painter_at(rect);
122 let visual_rect = theme.clamp_nib_visual_rect(rect, diam, ppp);
123 let center = visual_rect.center();
124 let visual_diam = diam.min(visual_rect.width()).min(visual_rect.height());
125 let radius = visual_diam * 0.5;
126
127 if enabled && response.has_focus() {
128 theme.paint_focus_ring(&painter, Rect::from_center_size(center, Vec2::splat(visual_diam)), radius);
129 }
130
131 painter.circle_filled(
132 Pos2::new(center.x, center.y + shadow_dy),
133 radius,
134 theme.drop_shadow().gamma_multiply(alpha),
135 );
136
137 let fill = if !enabled {
138 theme.control_background().gamma_multiply(alpha)
139 } else if response.is_pointer_button_down_on() {
140 theme.selected_control_inactive()
141 } else if response.hovered() {
142 theme.control()
143 } else {
144 theme.control_background()
145 };
146
147 painter.circle(center, radius, fill, Stroke::new(border_w, theme.separator().gamma_multiply(alpha)));
148 let text_color = theme.label().gamma_multiply(alpha);
149 let (font_id, has_semibold) = theme.semibold_font_id(ui, font_sz);
150 let galley = theme.layout_tracked_galley(ui, "?", font_id, text_color, f32::INFINITY);
151 let text_pos = Align2::CENTER_CENTER.anchor_size(center, galley.size()).min;
152 painter.galley(text_pos, galley.clone(), text_color);
153 if !has_semibold {
154 let boost = theme.label_metrics().semibold_stroke_boost.points();
155 painter.galley(Pos2::new(text_pos.x + boost, text_pos.y), galley, text_color);
156 }
157 response
158 }
159}
160
161pub struct MacInlineActionButton<'a> {
174 glyph: &'a str,
175 fixed_rect: Option<Rect>,
176 enabled: bool,
177 theme: Option<&'a MacTheme>,
178}
179
180impl<'a> MacInlineActionButton<'a> {
181 pub fn new(glyph: &'a str) -> Self {
183 Self {
184 glyph,
185 fixed_rect: None,
186 enabled: true,
187 theme: None,
188 }
189 }
190
191 pub fn from_symbol(symbol: MacSymbol) -> Self {
193 Self::new(symbol.as_str())
194 }
195
196 pub fn fixed_rect(mut self, rect: Rect) -> Self {
198 self.fixed_rect = Some(rect);
199 self
200 }
201
202 pub fn enabled(mut self, enabled: bool) -> Self {
204 self.enabled = enabled;
205 self
206 }
207
208 pub fn theme(mut self, theme: &'a MacTheme) -> Self {
210 self.theme = Some(theme);
211 self
212 }
213
214 pub fn diameter(&self) -> f32 {
216 MacTheme::resolve_or_default(self.theme).inline_action_button().diameter.points()
217 }
218
219 pub fn show(self, ui: &mut Ui) -> Response {
221 ui.add(self)
222 }
223}
224
225impl<'a> Widget for MacInlineActionButton<'a> {
226 fn ui(self, ui: &mut Ui) -> Response {
227 let theme_cow = MacTheme::resolve(self.theme, ui.ctx());
228 let theme: &MacTheme = &theme_cow;
229 let im = theme.inline_action_button();
230
231 let enabled = self.enabled && ui.is_enabled();
232 let diam = im.diameter.points();
233 let border_w = im.border_width.points();
234 let icon_sz = im.icon_size.points();
235 let sense = if enabled { Sense::click() } else { Sense::hover() };
236 let ppp = ui.ctx().pixels_per_point();
237 let (rect, response) = if let Some(fixed) = self.fixed_rect {
238 let snapped = subpixel_snap_rect(fixed, ppp);
239 let id = ui.next_auto_id().with("mac_inline_action_fixed");
240 ui.skip_ahead_auto_ids(1);
241 (snapped, ui.interact(snapped, id, sense))
242 } else {
243 let (r, resp) = ui.allocate_exact_size(Vec2::splat(diam), sense);
244 (subpixel_snap_rect(r, ppp), resp)
245 };
246
247 let alpha = if enabled { 1.0 } else { theme.disabled_opacity() };
248 let painter = ui.painter_at(rect);
249 let visual_rect = theme.clamp_nib_visual_rect(rect, diam, ppp);
250 let center = visual_rect.center();
251 let visual_diam = diam.min(visual_rect.width()).min(visual_rect.height());
252 let visual_radius = visual_diam * 0.5;
253
254 if enabled && response.has_focus() {
255 theme.paint_focus_ring(&painter, Rect::from_center_size(center, Vec2::splat(visual_diam)), visual_radius);
256 }
257
258 let fill = if !enabled {
259 theme.control().gamma_multiply(alpha)
260 } else if response.is_pointer_button_down_on() {
261 theme.selected_control_inactive()
262 } else if response.hovered() {
263 theme.control_background()
264 } else {
265 theme.control()
266 };
267
268 painter.circle(center, visual_radius, fill, Stroke::new(border_w, theme.separator().gamma_multiply(alpha)));
269 let surface_bg = theme.active_surface_at(ui, center);
270 let fg = if !enabled {
271 theme.tertiary_label_on(surface_bg)
272 } else if response.hovered() {
273 theme.label_on(surface_bg)
274 } else {
275 theme.secondary_label_on(surface_bg)
276 };
277 let galley = theme.layout_tracked_galley(ui, self.glyph, FontId::proportional(icon_sz), fg, f32::INFINITY);
278 let pos = Align2::CENTER_CENTER.anchor_size(center, galley.size()).min;
279 painter.galley(pos, galley, fg);
280 response
281 }
282}