bones_framework/render/ui/widgets/
bordered_frame.rs

1use crate::prelude::*;
2
3/// A 9-patch style bordered frame.
4pub struct BorderedFrame {
5    bg_handle: Handle<Image>,
6    border_scale: f32,
7    texture_size: egui::Vec2,
8    texture_border_size: egui::style::Margin,
9    padding: egui::style::Margin,
10    margin: egui::style::Margin,
11    border_only: bool,
12}
13
14impl BorderedFrame {
15    /// Create a new frame with the given [`BorderImageMeta`]
16    #[must_use = "You must call .show() to render the frame"]
17    pub fn new(border_image: &BorderImageMeta) -> Self {
18        let s = border_image.image_size;
19        Self {
20            bg_handle: border_image.image,
21            border_scale: border_image.scale,
22            texture_size: egui::Vec2::new(s.x as f32, s.y as f32),
23            texture_border_size: border_image.border_size.into(),
24            padding: Default::default(),
25            margin: Default::default(),
26            border_only: false,
27        }
28    }
29
30    /// Set the padding. This will be applied on the inside of the border.
31    #[must_use = "You must call .show() to render the frame"]
32    pub fn padding(mut self, margin: impl Into<egui::style::Margin>) -> Self {
33        self.padding = margin.into();
34
35        self
36    }
37
38    /// Set the margin. This will be applied on the outside of the border.
39    #[must_use = "You must call .show() to render the frame"]
40    pub fn margin(mut self, margin: egui::style::Margin) -> Self {
41        self.margin = margin;
42
43        self
44    }
45
46    /// Set the scale of the border image.
47    #[allow(unused)] // We just haven't needed this method yet
48    #[must_use = "You must call .show() to render the frame"]
49    pub fn border_scale(mut self, scale: f32) -> Self {
50        self.border_scale = scale;
51
52        self
53    }
54
55    #[allow(unused)] // We just haven't needed this method yet
56    /// If border_only is set to `true`, then the middle section of the frame will be transparent,
57    /// only the border will be rendered.
58    #[must_use = "You must call .show() to render the frame"]
59    pub fn border_only(mut self, border_only: bool) -> Self {
60        self.border_only = border_only;
61
62        self
63    }
64
65    /// Render the frame
66    pub fn show<R>(
67        self,
68        ui: &mut egui::Ui,
69        add_contents: impl FnOnce(&mut egui::Ui) -> R,
70    ) -> egui::InnerResponse<R> {
71        self.show_dyn(ui, Box::new(add_contents))
72    }
73
74    fn show_dyn<'c, R>(
75        self,
76        ui: &mut egui::Ui,
77        add_contents: Box<dyn FnOnce(&mut egui::Ui) -> R + 'c>,
78    ) -> egui::InnerResponse<R> {
79        let mut prepared = self.begin(ui);
80        let ret = add_contents(&mut prepared.content_ui);
81        let response = prepared.end(ui);
82
83        egui::InnerResponse {
84            inner: ret,
85            response,
86        }
87    }
88
89    fn begin(self, ui: &mut egui::Ui) -> BorderedFramePrepared {
90        let background_shape_idx = ui.painter().add(egui::Shape::Noop);
91
92        let mut content_rect = ui.available_rect_before_wrap();
93        content_rect.min += self.padding.left_top() + self.margin.left_top();
94        content_rect.max -= self.padding.right_bottom() + self.margin.right_bottom();
95
96        // Avoid negative size
97        content_rect.max.x = content_rect.max.x.max(content_rect.min.x);
98        content_rect.max.y = content_rect.max.y.max(content_rect.min.y);
99
100        let content_ui = ui.child_ui(content_rect, *ui.layout());
101
102        BorderedFramePrepared {
103            frame: self,
104            background_shape_idx,
105            content_ui,
106        }
107    }
108
109    /// Paint the frame into the given rect.
110    pub fn paint(&self, texture_id: egui::TextureId, paint_rect: egui::Rect) -> egui::Shape {
111        use egui::{Pos2, Rect, Vec2};
112        let white = egui::Color32::WHITE;
113
114        let mut mesh = egui::Mesh {
115            texture_id,
116            ..Default::default()
117        };
118
119        let s = self.texture_size;
120        let b = self.texture_border_size;
121        let pr = paint_rect;
122        // UV border
123        let buv = egui::style::Margin {
124            left: b.left / s.x,
125            right: b.right / s.x,
126            top: b.top / s.y,
127            bottom: b.bottom / s.y,
128        };
129        let b = egui::style::Margin {
130            left: b.left * self.border_scale,
131            right: b.right * self.border_scale,
132            top: b.top * self.border_scale,
133            bottom: b.bottom * self.border_scale,
134        };
135
136        // Build the 9-patches
137
138        // Top left
139        mesh.add_rect_with_uv(
140            Rect::from_min_size(pr.min, Vec2::new(b.left, b.top)),
141            egui::Rect::from_min_size(Pos2::ZERO, Vec2::new(buv.left, buv.top)),
142            white,
143        );
144        // Top center
145        mesh.add_rect_with_uv(
146            Rect::from_min_size(
147                pr.min + Vec2::new(b.left, 0.0),
148                Vec2::new(pr.width() - b.left - b.right, b.top),
149            ),
150            egui::Rect::from_min_size(
151                Pos2::new(buv.left, 0.0),
152                Vec2::new(1.0 - buv.left - buv.right, buv.top),
153            ),
154            white,
155        );
156        // Top right
157        mesh.add_rect_with_uv(
158            Rect::from_min_size(
159                pr.right_top() - Vec2::new(b.right, 0.0),
160                Vec2::new(b.right, b.top),
161            ),
162            egui::Rect::from_min_size(
163                Pos2::new(1.0 - buv.right, 0.0),
164                Vec2::new(buv.right, buv.top),
165            ),
166            white,
167        );
168        // Middle left
169        mesh.add_rect_with_uv(
170            Rect::from_min_size(
171                pr.min + Vec2::new(0.0, b.top),
172                Vec2::new(b.left, pr.height() - b.top - b.bottom),
173            ),
174            egui::Rect::from_min_size(
175                Pos2::new(0.0, buv.top),
176                Vec2::new(buv.left, 1.0 - buv.top - buv.bottom),
177            ),
178            white,
179        );
180        // Middle center
181        if !self.border_only {
182            mesh.add_rect_with_uv(
183                Rect::from_min_size(
184                    pr.min + Vec2::new(b.left, b.top),
185                    Vec2::new(
186                        pr.width() - b.left - b.right,
187                        pr.height() - b.top - b.bottom,
188                    ),
189                ),
190                egui::Rect::from_min_size(
191                    Pos2::new(buv.left, buv.top),
192                    Vec2::new(1.0 - buv.left - buv.top, 1.0 - buv.top - buv.bottom),
193                ),
194                white,
195            );
196        }
197        // Middle right
198        mesh.add_rect_with_uv(
199            Rect::from_min_size(
200                pr.min + Vec2::new(pr.width() - b.right, b.top),
201                Vec2::new(b.right, pr.height() - b.top - b.bottom),
202            ),
203            egui::Rect::from_min_size(
204                Pos2::new(1.0 - buv.right, buv.top),
205                Vec2::new(buv.right, 1.0 - buv.top - buv.bottom),
206            ),
207            white,
208        );
209        // Bottom left
210        mesh.add_rect_with_uv(
211            Rect::from_min_size(
212                pr.min + Vec2::new(0.0, pr.height() - b.bottom),
213                Vec2::new(b.left, b.bottom),
214            ),
215            egui::Rect::from_min_size(
216                Pos2::new(0.0, 1.0 - buv.bottom),
217                Vec2::new(buv.left, buv.bottom),
218            ),
219            white,
220        );
221        // Bottom center
222        mesh.add_rect_with_uv(
223            Rect::from_min_size(
224                pr.min + Vec2::new(b.left, pr.height() - b.bottom),
225                Vec2::new(pr.width() - b.left - b.right, b.bottom),
226            ),
227            egui::Rect::from_min_size(
228                Pos2::new(buv.left, 1.0 - buv.bottom),
229                Vec2::new(1.0 - buv.left - buv.right, buv.bottom),
230            ),
231            white,
232        );
233        // Bottom right
234        mesh.add_rect_with_uv(
235            Rect::from_min_size(
236                pr.min + Vec2::new(pr.width() - b.right, pr.height() - b.bottom),
237                Vec2::new(b.right, b.bottom),
238            ),
239            egui::Rect::from_min_size(
240                Pos2::new(1.0 - buv.right, 1.0 - buv.bottom),
241                Vec2::new(buv.right, buv.bottom),
242            ),
243            white,
244        );
245
246        egui::Shape::Mesh(mesh)
247    }
248}
249
250/// Internal helper struct for rendering the [`BorderedFrame`]
251struct BorderedFramePrepared {
252    frame: BorderedFrame,
253    background_shape_idx: egui::layers::ShapeIdx,
254    content_ui: egui::Ui,
255}
256
257impl BorderedFramePrepared {
258    fn end(self, ui: &mut egui::Ui) -> egui::Response {
259        use egui::Vec2;
260
261        let min_rect = self.content_ui.min_rect();
262        let m = self.frame.padding;
263        let paint_rect = egui::Rect {
264            min: min_rect.min - Vec2::new(m.left, m.top),
265            max: min_rect.max + Vec2::new(m.right, m.bottom),
266        };
267        if ui.is_rect_visible(paint_rect) {
268            let texture = ui.data(|map| {
269                map.get_temp::<AtomicResource<EguiTextures>>(egui::Id::null())
270                    .unwrap()
271                    .borrow()
272                    .unwrap()
273                    .get(self.frame.bg_handle)
274            });
275            let shape = self.frame.paint(texture, paint_rect);
276            ui.painter().set(self.background_shape_idx, shape);
277        }
278
279        ui.allocate_rect(paint_rect, egui::Sense::hover())
280    }
281}