1#![warn(missing_docs)]
22
23use crate::{
27 core::{
28 algebra::Vector2, math::Rect, pool::Handle, reflect::prelude::*, type_traits::prelude::*,
29 variable::InheritableVariable, visitor::prelude::*,
30 },
31 define_constructor,
32 draw::{CommandTexture, Draw, DrawingContext},
33 message::UiMessage,
34 style::{resource::StyleResourceExt, Style, StyledProperty},
35 widget::{Widget, WidgetBuilder},
36 BuildContext, Control, MessageDirection, Thickness, UiNode, UserInterface,
37};
38use fyrox_graph::constructor::{ConstructorProvider, GraphNodeConstructor};
39use std::ops::{Deref, DerefMut};
40
41#[derive(Default, Clone, Visit, Reflect, Debug, TypeUuidProvider, ComponentProvider)]
104#[type_uuid(id = "6aba3dc5-831d-481a-bc83-ec10b2b2bf12")]
105#[reflect(derived_type = "UiNode")]
106pub struct Border {
107 pub widget: Widget,
109 pub stroke_thickness: InheritableVariable<StyledProperty<Thickness>>,
111 #[visit(optional)]
113 pub corner_radius: InheritableVariable<StyledProperty<f32>>,
114 #[visit(optional)]
117 pub pad_by_corner_radius: InheritableVariable<bool>,
118}
119
120impl ConstructorProvider<UiNode, UserInterface> for Border {
121 fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
122 GraphNodeConstructor::new::<Self>()
123 .with_variant("Border", |ui| {
124 BorderBuilder::new(WidgetBuilder::new().with_name("Border"))
125 .build(&mut ui.build_ctx())
126 .into()
127 })
128 .with_group("Visual")
129 }
130}
131
132crate::define_widget_deref!(Border);
133
134#[derive(Debug, Clone, PartialEq)]
136pub enum BorderMessage {
137 StrokeThickness(StyledProperty<Thickness>),
139 CornerRadius(StyledProperty<f32>),
141 PadByCornerRadius(bool),
144}
145
146impl BorderMessage {
147 define_constructor!(
148 BorderMessage:StrokeThickness => fn stroke_thickness(StyledProperty<Thickness>), layout: false
150 );
151 define_constructor!(
152 BorderMessage:CornerRadius => fn corner_radius(StyledProperty<f32>), layout: false
154 );
155 define_constructor!(
156 BorderMessage:PadByCornerRadius => fn pad_by_corner_radius(bool), layout: false
158 );
159}
160
161fn corner_offset(radius: f32) -> f32 {
162 radius * 0.5 * (std::f32::consts::SQRT_2 - 1.0)
163}
164
165impl Control for Border {
166 fn measure_override(&self, ui: &UserInterface, available_size: Vector2<f32>) -> Vector2<f32> {
167 let corner_offset = if *self.pad_by_corner_radius {
168 corner_offset(**self.corner_radius)
169 } else {
170 0.0
171 };
172 let double_corner_offset = 2.0 * corner_offset;
173
174 let margin_x =
175 self.stroke_thickness.left + self.stroke_thickness.right + double_corner_offset;
176 let margin_y =
177 self.stroke_thickness.top + self.stroke_thickness.bottom + double_corner_offset;
178
179 let size_for_child = Vector2::new(available_size.x - margin_x, available_size.y - margin_y);
180 let mut desired_size = Vector2::default();
181
182 for child_handle in self.widget.children() {
183 ui.measure_node(*child_handle, size_for_child);
184 let child = ui.nodes.borrow(*child_handle);
185 let child_desired_size = child.desired_size();
186 if child_desired_size.x > desired_size.x {
187 desired_size.x = child_desired_size.x;
188 }
189 if child_desired_size.y > desired_size.y {
190 desired_size.y = child_desired_size.y;
191 }
192 }
193
194 desired_size.x += margin_x;
195 desired_size.y += margin_y;
196
197 desired_size
198 }
199
200 fn arrange_override(&self, ui: &UserInterface, final_size: Vector2<f32>) -> Vector2<f32> {
201 let corner_offset = if *self.pad_by_corner_radius {
202 corner_offset(**self.corner_radius)
203 } else {
204 0.0
205 };
206 let double_corner_offset = 2.0 * corner_offset;
207
208 let rect_for_child = Rect::new(
209 self.stroke_thickness.left + corner_offset,
210 self.stroke_thickness.top + corner_offset,
211 final_size.x
212 - (self.stroke_thickness.right + self.stroke_thickness.left + double_corner_offset),
213 final_size.y
214 - (self.stroke_thickness.bottom + self.stroke_thickness.top + double_corner_offset),
215 );
216
217 for child_handle in self.widget.children() {
218 ui.arrange_node(*child_handle, &rect_for_child);
219 }
220
221 final_size
222 }
223
224 fn draw(&self, drawing_context: &mut DrawingContext) {
225 let bounds = self.widget.bounding_rect();
226
227 if (**self.corner_radius).eq(&0.0) {
228 DrawingContext::push_rect_filled(drawing_context, &bounds, None);
229 drawing_context.commit(
230 self.clip_bounds(),
231 self.widget.background(),
232 CommandTexture::None,
233 &self.material,
234 None,
235 );
236
237 drawing_context.push_rect_vary(&bounds, **self.stroke_thickness);
238 drawing_context.commit(
239 self.clip_bounds(),
240 self.widget.foreground(),
241 CommandTexture::None,
242 &self.material,
243 None,
244 );
245 } else {
246 let corner_arc_length = std::f32::consts::TAU * **self.corner_radius * (90.0 / 360.0);
247 let corner_subdivisions = ((corner_arc_length as usize) / 2).min(4);
248
249 let thickness = self.stroke_thickness.left;
250 let half_thickness = thickness / 2.0;
251
252 DrawingContext::push_rounded_rect_filled(
253 drawing_context,
254 &bounds.deflate(half_thickness, half_thickness),
255 **self.corner_radius,
256 corner_subdivisions,
257 );
258 drawing_context.commit(
259 self.clip_bounds(),
260 self.widget.background(),
261 CommandTexture::None,
262 &self.material,
263 None,
264 );
265
266 drawing_context.push_rounded_rect(
267 &bounds,
268 thickness,
269 **self.corner_radius,
270 corner_subdivisions,
271 );
272 drawing_context.commit(
273 self.clip_bounds(),
274 self.widget.foreground(),
275 CommandTexture::None,
276 &self.material,
277 None,
278 );
279 }
280 }
281
282 fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
283 self.widget.handle_routed_message(ui, message);
284
285 if message.destination() == self.handle()
286 && message.direction() == MessageDirection::ToWidget
287 {
288 if let Some(msg) = message.data::<BorderMessage>() {
289 match msg {
290 BorderMessage::StrokeThickness(thickness) => {
291 if *thickness != *self.stroke_thickness {
292 self.stroke_thickness
293 .set_value_and_mark_modified(thickness.clone());
294 ui.send_message(message.reverse());
295 self.invalidate_layout();
296 }
297 }
298 BorderMessage::CornerRadius(radius) => {
299 if *radius != *self.corner_radius {
300 self.corner_radius
301 .set_value_and_mark_modified(radius.clone());
302 ui.send_message(message.reverse());
303 self.invalidate_layout();
304 }
305 }
306 BorderMessage::PadByCornerRadius(pad) => {
307 if *pad != *self.pad_by_corner_radius {
308 self.pad_by_corner_radius.set_value_and_mark_modified(*pad);
309 ui.send_message(message.reverse());
310 self.invalidate_layout();
311 }
312 }
313 }
314 }
315 }
316 }
317}
318
319pub struct BorderBuilder {
321 pub widget_builder: WidgetBuilder,
323 pub stroke_thickness: StyledProperty<Thickness>,
325 pub corner_radius: StyledProperty<f32>,
327 pub pad_by_corner_radius: bool,
330}
331
332impl BorderBuilder {
333 pub fn new(widget_builder: WidgetBuilder) -> Self {
335 Self {
336 widget_builder,
337 stroke_thickness: Thickness::uniform(1.0).into(),
338 corner_radius: 0.0.into(),
339 pad_by_corner_radius: true,
340 }
341 }
342
343 pub fn with_stroke_thickness(mut self, stroke_thickness: StyledProperty<Thickness>) -> Self {
345 self.stroke_thickness = stroke_thickness;
346 self
347 }
348
349 pub fn with_corner_radius(mut self, corner_radius: StyledProperty<f32>) -> Self {
351 self.corner_radius = corner_radius;
352 self
353 }
354
355 pub fn with_pad_by_corner_radius(mut self, pad: bool) -> Self {
357 self.pad_by_corner_radius = pad;
358 self
359 }
360
361 pub fn build_border(mut self, ctx: &BuildContext) -> Border {
363 if self.widget_builder.foreground.is_none() {
364 self.widget_builder.foreground = Some(ctx.style.property(Style::BRUSH_PRIMARY));
365 }
366 Border {
367 widget: self.widget_builder.build(ctx),
368 stroke_thickness: self.stroke_thickness.into(),
369 corner_radius: self.corner_radius.into(),
370 pad_by_corner_radius: self.pad_by_corner_radius.into(),
371 }
372 }
373
374 pub fn build(self, ctx: &mut BuildContext<'_>) -> Handle<UiNode> {
376 ctx.add_node(UiNode::new(self.build_border(ctx)))
377 }
378}
379
380#[cfg(test)]
381mod test {
382 use crate::border::BorderBuilder;
383 use crate::{test::test_widget_deletion, widget::WidgetBuilder};
384
385 #[test]
386 fn test_deletion() {
387 test_widget_deletion(|ctx| BorderBuilder::new(WidgetBuilder::new()).build(ctx));
388 }
389}