bones_framework/render/ui/widgets/
bordered_button.rs1#![allow(dead_code)] use egui::{
4 NumExt, Response, RichText, Sense, TextStyle, Ui, Widget, WidgetInfo, WidgetText, WidgetType,
5};
6
7use crate::prelude::*;
8
9pub struct BorderedButton<'a> {
11 text: WidgetText,
12 wrap: Option<bool>,
13 sense: Sense,
14 min_size: Vec2,
15 focus_on_hover: bool,
16 default_border: Option<&'a BorderImageMeta>,
17 on_focus_border: Option<&'a BorderImageMeta>,
18 on_click_border: Option<&'a BorderImageMeta>,
19 margin: egui::style::Margin,
20 padding: egui::style::Margin,
21}
22
23impl<'a> BorderedButton<'a> {
24 #[must_use = "You must call .show() to render the button"]
26 pub fn new(text: impl Into<WidgetText>) -> Self {
27 Self {
28 text: text.into(),
29 sense: Sense::click(),
30 min_size: Vec2::ZERO,
31 focus_on_hover: true,
32 wrap: None,
33 default_border: None,
34 on_focus_border: None,
35 on_click_border: None,
36 margin: Default::default(),
37 padding: Default::default(),
38 }
39 }
40
41 #[must_use = "You must call .show() to render the button"]
43 pub fn themed(
44 button_theme: &'a ButtonThemeMeta,
45 label: impl Into<RichText>,
46 ) -> BorderedButton<'a> {
47 BorderedButton::new(
48 label
49 .into()
50 .font(button_theme.font.id())
51 .color(button_theme.font.color),
52 )
53 .border(&button_theme.borders.default)
54 .on_click_border(Some(&button_theme.borders.clicked))
55 .on_focus_border(Some(&button_theme.borders.focused))
56 .padding(button_theme.padding)
57 }
58
59 pub fn focus_on_hover(mut self, focus_on_hover: bool) -> Self {
61 self.focus_on_hover = focus_on_hover;
62 self
63 }
64
65 #[inline]
73 #[must_use = "You must call .show() to render the button"]
74 pub fn wrap(mut self, wrap: bool) -> Self {
75 self.wrap = Some(wrap);
76 self
77 }
78
79 #[must_use = "You must call .show() to render the button"]
81 pub fn margin(mut self, margin: impl Into<egui::style::Margin>) -> Self {
82 self.margin = margin.into();
83
84 self
85 }
86
87 #[must_use = "You must call .show() to render the button"]
89 pub fn padding(mut self, padding: impl Into<egui::style::Margin>) -> Self {
90 self.padding = padding.into();
91
92 self
93 }
94
95 #[must_use = "You must call .show() to render the button"]
97 pub fn border(mut self, border: &'a BorderImageMeta) -> Self {
98 self.default_border = Some(border);
99 self
100 }
101
102 #[must_use = "You must call .show() to render the button"]
104 pub fn on_focus_border(mut self, border: Option<&'a BorderImageMeta>) -> Self {
105 self.on_focus_border = border;
106 self
107 }
108
109 #[must_use = "You must call .show() to render the button"]
111 pub fn on_click_border(mut self, border: Option<&'a BorderImageMeta>) -> Self {
112 self.on_click_border = border;
113 self
114 }
115
116 #[must_use = "You must call .show() to render the button"]
119 pub fn sense(mut self, sense: Sense) -> Self {
120 self.sense = sense;
121 self
122 }
123
124 #[must_use = "You must call .show() to render the button"]
126 pub fn min_size(mut self, min_size: Vec2) -> Self {
127 self.min_size = min_size;
128 self
129 }
130
131 #[must_use = "You must call .show() to render the button"]
133 pub fn show(self, ui: &mut Ui) -> egui::Response {
134 self.ui(ui)
135 }
136}
137
138impl<'a> Widget for BorderedButton<'a> {
139 fn ui(self, ui: &mut Ui) -> Response {
140 let BorderedButton {
141 text,
142 sense,
143 min_size,
144 focus_on_hover,
145 wrap,
146 default_border,
147 on_focus_border,
148 on_click_border,
149 margin,
150 padding,
151 }: BorderedButton = self;
152
153 let total_extra = padding.sum() + margin.sum();
154
155 let wrap_width = ui.available_width() - total_extra.x;
156 let text = text.into_galley(ui, wrap, wrap_width, TextStyle::Button);
157
158 let mut desired_size = text.size() + total_extra;
159 desired_size = desired_size.at_least(egui::vec2(min_size.x, min_size.y));
160
161 let (rect, response) = ui.allocate_at_least(desired_size, sense);
162 response.widget_info(|| WidgetInfo::labeled(WidgetType::Button, text.text()));
163
164 if response.hovered()
166 && ui.ctx().input(|i| i.pointer.velocity().length_sq() > 0.0)
167 && focus_on_hover
168 {
169 response.request_focus();
170 }
171
172 if ui.is_rect_visible(rect) {
173 let visuals = ui.style().interact(&response);
174
175 let mut text_rect = rect;
176 text_rect.min += padding.left_top() + margin.left_top();
177 text_rect.max -= padding.right_bottom() + margin.right_bottom();
178 text_rect.max.x = text_rect.max.x.max(text_rect.min.x);
179 text_rect.max.y = text_rect.max.y.max(text_rect.min.y);
180
181 let label_pos = ui
182 .layout()
183 .align_size_within_rect(text.size(), text_rect)
184 .min;
185
186 let border = if response.is_pointer_button_down_on() {
187 on_click_border.or(default_border)
188 } else if response.has_focus() || response.hovered() {
189 on_focus_border.or(default_border)
190 } else {
191 default_border
192 };
193
194 let mut border_rect = rect;
195 border_rect.min += margin.left_top();
196 border_rect.max -= margin.right_bottom();
197 border_rect.max.x = border_rect.max.x.max(border_rect.min.x);
198 border_rect.max.y = border_rect.max.y.max(border_rect.min.y);
199
200 if let Some(border) = border {
201 let texture = ui.data(|map| {
202 map.get_temp::<AtomicResource<EguiTextures>>(egui::Id::null())
203 .unwrap()
204 .borrow()
205 .unwrap()
206 .get(border.image)
207 });
208 ui.painter()
209 .add(BorderedFrame::new(border).paint(texture, border_rect));
210 }
211
212 text.paint_with_visuals(ui.painter(), label_pos, visuals);
213 }
214
215 response
216 }
217}