armas_basic/components/
icon_button.rs1use crate::components::ButtonVariant;
6use crate::ext::ArmasContextExt;
7use crate::icon::{render_icon_data, IconData, OwnedIconData};
8use egui::{Color32, Response, Sense, Ui, Vec2};
9
10pub struct IconButton<'a> {
17 vertices: &'a [(f32, f32)],
18 indices: &'a [u32],
19 viewbox_width: f32,
20 viewbox_height: f32,
21 variant: ButtonVariant,
22 size: f32,
23 padding: Option<f32>,
24 enabled: bool,
25 icon_color: Option<Color32>,
26 hover_icon_color: Option<Color32>,
27}
28
29impl<'a> IconButton<'a> {
30 #[must_use]
32 pub const fn new(icon_data: &'a IconData) -> Self {
33 Self {
34 vertices: icon_data.vertices,
35 indices: icon_data.indices,
36 viewbox_width: icon_data.viewbox_width,
37 viewbox_height: icon_data.viewbox_height,
38 variant: ButtonVariant::Default,
39 size: 16.0,
40 padding: None,
41 enabled: true,
42 icon_color: None,
43 hover_icon_color: None,
44 }
45 }
46
47 #[must_use]
49 pub fn from_owned(data: &'a OwnedIconData) -> Self {
50 Self {
51 vertices: &data.vertices,
52 indices: &data.indices,
53 viewbox_width: data.viewbox_width,
54 viewbox_height: data.viewbox_height,
55 variant: ButtonVariant::Default,
56 size: 16.0,
57 padding: None,
58 enabled: true,
59 icon_color: None,
60 hover_icon_color: None,
61 }
62 }
63
64 #[must_use]
66 pub const fn variant(mut self, variant: ButtonVariant) -> Self {
67 self.variant = variant;
68 self
69 }
70
71 #[must_use]
73 pub const fn size(mut self, size: f32) -> Self {
74 self.size = size;
75 self
76 }
77
78 #[must_use]
80 pub const fn padding(mut self, padding: f32) -> Self {
81 self.padding = Some(padding);
82 self
83 }
84
85 #[must_use]
87 pub const fn enabled(mut self, enabled: bool) -> Self {
88 self.enabled = enabled;
89 self
90 }
91
92 #[must_use]
94 pub const fn icon_color(mut self, color: Color32) -> Self {
95 self.icon_color = Some(color);
96 self
97 }
98
99 #[must_use]
101 pub const fn hover_icon_color(mut self, color: Color32) -> Self {
102 self.hover_icon_color = Some(color);
103 self
104 }
105
106 pub fn show(self, ui: &mut Ui) -> Response {
108 let theme = ui.ctx().armas_theme();
109
110 let padding = self.padding.unwrap_or_else(|| match self.variant {
113 ButtonVariant::Ghost | ButtonVariant::Link => 0.0,
114 _ => {
115 let bp = ui.spacing().button_padding;
116 bp.x.max(bp.y)
117 }
118 });
119
120 let total_size = Vec2::splat(self.size);
122 let icon_draw_size = (self.size - padding * 2.0).max(1.0);
124
125 let sense = if self.enabled {
126 Sense::click()
127 } else {
128 Sense::hover()
129 };
130
131 let (rect, response) = ui.allocate_exact_size(total_size, sense);
132
133 if ui.is_rect_visible(rect) {
134 let (bg_color, mut icon_color) = match self.variant {
135 ButtonVariant::Default => {
136 let bg = if response.is_pointer_button_down_on() {
137 theme.primary().linear_multiply(0.9)
138 } else if response.hovered() {
139 theme.primary().linear_multiply(1.08)
140 } else {
141 theme.primary()
142 };
143 (Some(bg), theme.primary_foreground())
144 }
145 ButtonVariant::Secondary => {
146 let bg = if response.is_pointer_button_down_on() {
147 theme.secondary()
148 } else if response.hovered() {
149 theme.secondary().linear_multiply(1.08)
150 } else {
151 theme.secondary()
152 };
153 (Some(bg), theme.secondary_foreground())
154 }
155 ButtonVariant::Outline => {
156 let bg = if response.hovered() {
157 Some(theme.accent())
158 } else {
159 None
160 };
161 let icon = if response.hovered() {
162 theme.accent_foreground()
163 } else {
164 theme.foreground()
165 };
166 (bg, icon)
167 }
168 ButtonVariant::Ghost | ButtonVariant::Link => {
169 let bg = if response.hovered() {
170 Some(theme.muted())
171 } else {
172 None
173 };
174 let icon = if response.hovered() {
175 theme.foreground()
176 } else {
177 theme.muted_foreground()
178 };
179 (bg, icon)
180 }
181 };
182
183 if response.hovered() {
184 if let Some(custom_hover_color) = self.hover_icon_color {
185 icon_color = custom_hover_color;
186 }
187 } else if let Some(custom_color) = self.icon_color {
188 icon_color = custom_color;
189 }
190
191 if !self.enabled {
192 icon_color = icon_color.linear_multiply(0.5);
193 }
194
195 if let Some(bg) = bg_color {
196 let rounding = match self.variant {
197 ButtonVariant::Default | ButtonVariant::Secondary => total_size.x / 2.0,
198 ButtonVariant::Ghost | ButtonVariant::Link => 2.0,
199 ButtonVariant::Outline => 6.0,
200 };
201 let final_bg = if self.enabled {
202 bg
203 } else {
204 bg.linear_multiply(0.5)
205 };
206 ui.painter().rect_filled(rect, rounding, final_bg);
207 }
208
209 if self.variant == ButtonVariant::Outline {
210 let stroke = egui::Stroke::new(1.0, theme.border());
211 ui.painter()
212 .rect_stroke(rect, 6.0, stroke, egui::epaint::StrokeKind::Inside);
213 }
214
215 let icon_rect =
216 egui::Rect::from_center_size(rect.center(), Vec2::splat(icon_draw_size));
217 render_icon_data(
218 ui.painter(),
219 icon_rect,
220 self.vertices,
221 self.indices,
222 self.viewbox_width,
223 self.viewbox_height,
224 icon_color,
225 );
226 }
227
228 response
229 }
230}