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 show(self, ui: &mut Ui) -> Response {
82 ui.add(self)
83 }
84}
85
86impl<'a> Widget for MacHelpButton<'a> {
87 fn ui(self, ui: &mut Ui) -> Response {
88 let theme_cow = MacTheme::resolve(self.theme, ui.ctx());
89 let theme: &MacTheme = &theme_cow;
90 let hm = theme.help_button();
91
92 let diam = self.diameter();
93 let radius = diam * 0.5;
94 let border_w = hm.border_width.points();
95 let shadow_dy = hm.shadow_offset_y.points();
96 let font_sz = hm.font_size.points();
97 let sense = if self.enabled { Sense::click() } else { Sense::hover() };
98 let ppp = ui.ctx().pixels_per_point();
99 let (rect, response) = if let Some(fixed) = self.fixed_rect {
100 let snapped = subpixel_snap_rect(fixed, ppp);
101 let id = ui.next_auto_id().with("mac_help_btn_fixed");
102 (snapped, ui.interact(snapped, id, sense))
103 } else {
104 let (r, resp) = ui.allocate_exact_size(Vec2::splat(diam), sense);
105 (subpixel_snap_rect(r, ppp), resp)
106 };
107
108 let alpha = if self.enabled { 1.0 } else { theme.disabled_opacity() };
109 let painter = ui.painter_at(rect);
110 let center = rect.center();
111
112 if self.enabled && response.has_focus() {
113 theme.paint_focus_ring(&painter, Rect::from_center_size(center, Vec2::splat(diam)), radius);
114 }
115
116 painter.circle_filled(
117 Pos2::new(center.x, center.y + shadow_dy),
118 radius,
119 theme.drop_shadow().gamma_multiply(alpha),
120 );
121
122 let fill = if !self.enabled {
123 theme.control_background().gamma_multiply(alpha)
124 } else if response.is_pointer_button_down_on() {
125 theme.selected_control_inactive()
126 } else if response.hovered() {
127 theme.control()
128 } else {
129 theme.control_background()
130 };
131
132 painter.circle(center, radius, fill, Stroke::new(border_w, theme.separator().gamma_multiply(alpha)));
133 let text_color = theme.label().gamma_multiply(alpha);
134 let galley = theme.layout_tracked_galley(ui, "?", FontId::proportional(font_sz), text_color, f32::INFINITY);
135 let text_pos = Align2::CENTER_CENTER.anchor_size(center, galley.size()).min;
136 painter.galley(text_pos, galley, text_color);
137 response
138 }
139}
140
141pub struct MacInlineActionButton<'a> {
154 glyph: &'a str,
155 fixed_rect: Option<Rect>,
156 enabled: bool,
157 theme: Option<&'a MacTheme>,
158}
159
160impl<'a> MacInlineActionButton<'a> {
161 pub fn new(glyph: &'a str) -> Self {
163 Self {
164 glyph,
165 fixed_rect: None,
166 enabled: true,
167 theme: None,
168 }
169 }
170
171 pub fn from_symbol(symbol: MacSymbol) -> Self {
173 Self::new(symbol.as_str())
174 }
175
176 pub fn fixed_rect(mut self, rect: Rect) -> Self {
178 self.fixed_rect = Some(rect);
179 self
180 }
181
182 pub fn enabled(mut self, enabled: bool) -> Self {
184 self.enabled = enabled;
185 self
186 }
187
188 pub fn theme(mut self, theme: &'a MacTheme) -> Self {
190 self.theme = Some(theme);
191 self
192 }
193
194 pub fn diameter(&self) -> f32 {
196 MacTheme::resolve_or_default(self.theme).inline_action_button().diameter.points()
197 }
198
199 pub fn show(self, ui: &mut Ui) -> Response {
201 ui.add(self)
202 }
203}
204
205impl<'a> Widget for MacInlineActionButton<'a> {
206 fn ui(self, ui: &mut Ui) -> Response {
207 let theme_cow = MacTheme::resolve(self.theme, ui.ctx());
208 let theme: &MacTheme = &theme_cow;
209 let im = theme.inline_action_button();
210
211 let diam = im.diameter.points();
212 let radius = diam * 0.5;
213 let border_w = im.border_width.points();
214 let icon_sz = im.icon_size.points();
215 let sense = if self.enabled { Sense::click() } else { Sense::hover() };
216 let ppp = ui.ctx().pixels_per_point();
217 let (rect, response) = if let Some(fixed) = self.fixed_rect {
218 let snapped = subpixel_snap_rect(fixed, ppp);
219 let id = ui.next_auto_id().with("mac_inline_action_fixed");
220 (snapped, ui.interact(snapped, id, sense))
221 } else {
222 let (r, resp) = ui.allocate_exact_size(Vec2::splat(diam), sense);
223 (subpixel_snap_rect(r, ppp), resp)
224 };
225
226 let alpha = if self.enabled { 1.0 } else { theme.disabled_opacity() };
227 let painter = ui.painter_at(rect);
228 let center = rect.center();
229
230 let fill = if !self.enabled {
231 theme.control().gamma_multiply(alpha)
232 } else if response.is_pointer_button_down_on() {
233 theme.selected_control_inactive()
234 } else if response.hovered() {
235 theme.control_background()
236 } else {
237 theme.control()
238 };
239
240 painter.circle(center, radius, fill, Stroke::new(border_w, theme.separator().gamma_multiply(alpha)));
241 let fg = if response.hovered() && self.enabled { theme.label() } else { theme.secondary_label().gamma_multiply(alpha) };
242 let galley = theme.layout_tracked_galley(ui, self.glyph, FontId::proportional(icon_sz), fg, f32::INFINITY);
243 let pos = Align2::CENTER_CENTER.anchor_size(center, galley.size()).min;
244 painter.galley(pos, galley, fg);
245 response
246 }
247}