Skip to main content

armas_basic/components/
icon_button.rs

1//! Icon Button Component
2//!
3//! A button variant specifically designed for rendering icons.
4
5use crate::components::ButtonVariant;
6use crate::ext::ArmasContextExt;
7use crate::icon::{render_icon_data, IconData, OwnedIconData};
8use egui::{Color32, Response, Sense, Ui, Vec2};
9
10/// Icon Button component
11///
12/// A button specifically designed for icons.
13///
14/// # Example
15///
16/// ```rust,no_run
17/// # use egui::Ui;
18/// # use armas_basic::icon::IconData;
19/// # static MY_ICON: IconData = IconData {
20/// #     name: "test", vertices: &[], indices: &[],
21/// #     viewbox_width: 24.0, viewbox_height: 24.0,
22/// # };
23/// # fn example(ui: &mut Ui) {
24/// use armas_basic::components::{IconButton, ButtonVariant};
25///
26/// if IconButton::new(&MY_ICON)
27///     .variant(ButtonVariant::Filled)
28///     .size(24.0)
29///     .show(ui)
30///     .clicked()
31/// {
32///     // Handle button click
33/// }
34/// # }
35/// ```
36pub struct IconButton<'a> {
37    vertices: &'a [(f32, f32)],
38    indices: &'a [u32],
39    viewbox_width: f32,
40    viewbox_height: f32,
41    variant: ButtonVariant,
42    size: f32,
43    padding: f32,
44    enabled: bool,
45    icon_color: Option<Color32>,
46    hover_icon_color: Option<Color32>,
47}
48
49impl<'a> IconButton<'a> {
50    /// Create a new icon button from static [`IconData`].
51    #[must_use]
52    pub const fn new(icon_data: &'a IconData) -> Self {
53        Self {
54            vertices: icon_data.vertices,
55            indices: icon_data.indices,
56            viewbox_width: icon_data.viewbox_width,
57            viewbox_height: icon_data.viewbox_height,
58            variant: ButtonVariant::Default,
59            size: 24.0,
60            padding: 8.0,
61            enabled: true,
62            icon_color: None,
63            hover_icon_color: None,
64        }
65    }
66
67    /// Create a new icon button from [`OwnedIconData`].
68    #[must_use]
69    pub fn from_owned(data: &'a OwnedIconData) -> Self {
70        Self {
71            vertices: &data.vertices,
72            indices: &data.indices,
73            viewbox_width: data.viewbox_width,
74            viewbox_height: data.viewbox_height,
75            variant: ButtonVariant::Default,
76            size: 24.0,
77            padding: 8.0,
78            enabled: true,
79            icon_color: None,
80            hover_icon_color: None,
81        }
82    }
83
84    /// Set the button variant
85    #[must_use]
86    pub const fn variant(mut self, variant: ButtonVariant) -> Self {
87        self.variant = variant;
88        self
89    }
90
91    /// Set the icon size
92    #[must_use]
93    pub const fn size(mut self, size: f32) -> Self {
94        self.size = size;
95        self
96    }
97
98    /// Set the padding around the icon
99    #[must_use]
100    pub const fn padding(mut self, padding: f32) -> Self {
101        self.padding = padding;
102        self
103    }
104
105    /// Set enabled state
106    #[must_use]
107    pub const fn enabled(mut self, enabled: bool) -> Self {
108        self.enabled = enabled;
109        self
110    }
111
112    /// Set custom icon color (overrides default)
113    #[must_use]
114    pub const fn icon_color(mut self, color: Color32) -> Self {
115        self.icon_color = Some(color);
116        self
117    }
118
119    /// Set custom hover icon color (overrides default)
120    #[must_use]
121    pub const fn hover_icon_color(mut self, color: Color32) -> Self {
122        self.hover_icon_color = Some(color);
123        self
124    }
125
126    /// Show the icon button
127    pub fn show(self, ui: &mut Ui) -> Response {
128        let theme = ui.ctx().armas_theme();
129        let total_size = Vec2::splat(self.size + self.padding * 2.0);
130
131        let sense = if self.enabled {
132            Sense::click()
133        } else {
134            Sense::hover()
135        };
136
137        let (rect, response) = ui.allocate_exact_size(total_size, sense);
138
139        if ui.is_rect_visible(rect) {
140            // Determine colors based on variant and state
141            let (bg_color, mut icon_color) = match self.variant {
142                ButtonVariant::Default => {
143                    let bg = if response.is_pointer_button_down_on() {
144                        theme.primary().linear_multiply(0.9)
145                    } else if response.hovered() {
146                        theme.primary().linear_multiply(1.08)
147                    } else {
148                        theme.primary()
149                    };
150                    (Some(bg), theme.primary_foreground())
151                }
152                ButtonVariant::Secondary => {
153                    let bg = if response.is_pointer_button_down_on() {
154                        theme.secondary()
155                    } else if response.hovered() {
156                        theme.secondary().linear_multiply(1.08)
157                    } else {
158                        theme.secondary()
159                    };
160                    (Some(bg), theme.secondary_foreground())
161                }
162                ButtonVariant::Outline | ButtonVariant::Ghost | ButtonVariant::Link => {
163                    let bg = if response.hovered() {
164                        Some(theme.accent())
165                    } else {
166                        None
167                    };
168                    let icon = if response.hovered() {
169                        theme.accent_foreground()
170                    } else {
171                        theme.foreground()
172                    };
173                    (bg, icon)
174                }
175            };
176
177            // Apply custom colors if provided
178            if response.hovered() {
179                if let Some(custom_hover_color) = self.hover_icon_color {
180                    icon_color = custom_hover_color;
181                }
182            } else if let Some(custom_color) = self.icon_color {
183                icon_color = custom_color;
184            }
185
186            // Apply disabled state
187            if !self.enabled {
188                icon_color = icon_color.linear_multiply(0.5);
189            }
190
191            // Draw background if needed
192            if let Some(bg) = bg_color {
193                let rounding = match self.variant {
194                    ButtonVariant::Default | ButtonVariant::Secondary => total_size.x / 2.0, // Circular
195                    _ => 6.0, // rounded-md
196                };
197                let final_bg = if self.enabled {
198                    bg
199                } else {
200                    bg.linear_multiply(0.5)
201                };
202                ui.painter().rect_filled(rect, rounding, final_bg);
203            }
204
205            // Draw outline for outline variant
206            if self.variant == ButtonVariant::Outline {
207                let stroke = egui::Stroke::new(1.0, theme.border());
208                ui.painter()
209                    .rect_stroke(rect, 6.0, stroke, egui::epaint::StrokeKind::Inside);
210            }
211
212            // Draw icon
213            let icon_rect = egui::Rect::from_center_size(rect.center(), Vec2::splat(self.size));
214            render_icon_data(
215                ui.painter(),
216                icon_rect,
217                self.vertices,
218                self.indices,
219                self.viewbox_width,
220                self.viewbox_height,
221                icon_color,
222            );
223        }
224
225        response
226    }
227}