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")]
105pub struct Border {
106 pub widget: Widget,
108 pub stroke_thickness: InheritableVariable<StyledProperty<Thickness>>,
110 #[visit(optional)]
112 pub corner_radius: InheritableVariable<StyledProperty<f32>>,
113 #[visit(optional)]
116 pub pad_by_corner_radius: InheritableVariable<bool>,
117}
118
119impl ConstructorProvider<UiNode, UserInterface> for Border {
120 fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
121 GraphNodeConstructor::new::<Self>()
122 .with_variant("Border", |ui| {
123 BorderBuilder::new(WidgetBuilder::new().with_name("Border"))
124 .build(&mut ui.build_ctx())
125 .into()
126 })
127 .with_group("Visual")
128 }
129}
130
131crate::define_widget_deref!(Border);
132
133#[derive(Debug, Clone, PartialEq)]
135pub enum BorderMessage {
136 StrokeThickness(StyledProperty<Thickness>),
138 CornerRadius(StyledProperty<f32>),
140 PadByCornerRadius(bool),
143}
144
145impl BorderMessage {
146 define_constructor!(
147 BorderMessage:StrokeThickness => fn stroke_thickness(StyledProperty<Thickness>), layout: false
149 );
150 define_constructor!(
151 BorderMessage:CornerRadius => fn corner_radius(StyledProperty<f32>), layout: false
153 );
154 define_constructor!(
155 BorderMessage:PadByCornerRadius => fn pad_by_corner_radius(bool), layout: false
157 );
158}
159
160fn corner_offset(radius: f32) -> f32 {
161 radius * 0.5 * (std::f32::consts::SQRT_2 - 1.0)
162}
163
164impl Control for Border {
165 fn measure_override(&self, ui: &UserInterface, available_size: Vector2<f32>) -> Vector2<f32> {
166 let corner_offset = if *self.pad_by_corner_radius {
167 corner_offset(**self.corner_radius)
168 } else {
169 0.0
170 };
171 let double_corner_offset = 2.0 * corner_offset;
172
173 let margin_x =
174 self.stroke_thickness.left + self.stroke_thickness.right + double_corner_offset;
175 let margin_y =
176 self.stroke_thickness.top + self.stroke_thickness.bottom + double_corner_offset;
177
178 let size_for_child = Vector2::new(available_size.x - margin_x, available_size.y - margin_y);
179 let mut desired_size = Vector2::default();
180
181 for child_handle in self.widget.children() {
182 ui.measure_node(*child_handle, size_for_child);
183 let child = ui.nodes.borrow(*child_handle);
184 let child_desired_size = child.desired_size();
185 if child_desired_size.x > desired_size.x {
186 desired_size.x = child_desired_size.x;
187 }
188 if child_desired_size.y > desired_size.y {
189 desired_size.y = child_desired_size.y;
190 }
191 }
192
193 desired_size.x += margin_x;
194 desired_size.y += margin_y;
195
196 desired_size
197 }
198
199 fn arrange_override(&self, ui: &UserInterface, final_size: Vector2<f32>) -> Vector2<f32> {
200 let corner_offset = if *self.pad_by_corner_radius {
201 corner_offset(**self.corner_radius)
202 } else {
203 0.0
204 };
205 let double_corner_offset = 2.0 * corner_offset;
206
207 let rect_for_child = Rect::new(
208 self.stroke_thickness.left + corner_offset,
209 self.stroke_thickness.top + corner_offset,
210 final_size.x
211 - (self.stroke_thickness.right + self.stroke_thickness.left + double_corner_offset),
212 final_size.y
213 - (self.stroke_thickness.bottom + self.stroke_thickness.top + double_corner_offset),
214 );
215
216 for child_handle in self.widget.children() {
217 ui.arrange_node(*child_handle, &rect_for_child);
218 }
219
220 final_size
221 }
222
223 fn draw(&self, drawing_context: &mut DrawingContext) {
224 let bounds = self.widget.bounding_rect();
225
226 if (**self.corner_radius).eq(&0.0) {
227 DrawingContext::push_rect_filled(drawing_context, &bounds, None);
228 drawing_context.commit(
229 self.clip_bounds(),
230 self.widget.background(),
231 CommandTexture::None,
232 None,
233 );
234
235 drawing_context.push_rect_vary(&bounds, **self.stroke_thickness);
236 drawing_context.commit(
237 self.clip_bounds(),
238 self.widget.foreground(),
239 CommandTexture::None,
240 None,
241 );
242 } else {
243 let thickness = self.stroke_thickness.left;
244 let half_thickness = thickness / 2.0;
245
246 DrawingContext::push_rounded_rect_filled(
247 drawing_context,
248 &bounds.deflate(half_thickness, half_thickness),
249 **self.corner_radius,
250 16,
251 );
252 drawing_context.commit(
253 self.clip_bounds(),
254 self.widget.background(),
255 CommandTexture::None,
256 None,
257 );
258
259 drawing_context.push_rounded_rect(&bounds, thickness, **self.corner_radius, 16);
260 drawing_context.commit(
261 self.clip_bounds(),
262 self.widget.foreground(),
263 CommandTexture::None,
264 None,
265 );
266 }
267 }
268
269 fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
270 self.widget.handle_routed_message(ui, message);
271
272 if message.destination() == self.handle()
273 && message.direction() == MessageDirection::ToWidget
274 {
275 if let Some(msg) = message.data::<BorderMessage>() {
276 match msg {
277 BorderMessage::StrokeThickness(thickness) => {
278 if *thickness != *self.stroke_thickness {
279 self.stroke_thickness
280 .set_value_and_mark_modified(thickness.clone());
281 ui.send_message(message.reverse());
282 self.invalidate_layout();
283 }
284 }
285 BorderMessage::CornerRadius(radius) => {
286 if *radius != *self.corner_radius {
287 self.corner_radius
288 .set_value_and_mark_modified(radius.clone());
289 ui.send_message(message.reverse());
290 self.invalidate_layout();
291 }
292 }
293 BorderMessage::PadByCornerRadius(pad) => {
294 if *pad != *self.pad_by_corner_radius {
295 self.pad_by_corner_radius.set_value_and_mark_modified(*pad);
296 ui.send_message(message.reverse());
297 self.invalidate_layout();
298 }
299 }
300 }
301 }
302 }
303 }
304}
305
306pub struct BorderBuilder {
308 pub widget_builder: WidgetBuilder,
310 pub stroke_thickness: StyledProperty<Thickness>,
312 pub corner_radius: StyledProperty<f32>,
314 pub pad_by_corner_radius: bool,
317}
318
319impl BorderBuilder {
320 pub fn new(widget_builder: WidgetBuilder) -> Self {
322 Self {
323 widget_builder,
324 stroke_thickness: Thickness::uniform(1.0).into(),
325 corner_radius: 0.0.into(),
326 pad_by_corner_radius: true,
327 }
328 }
329
330 pub fn with_stroke_thickness(mut self, stroke_thickness: StyledProperty<Thickness>) -> Self {
332 self.stroke_thickness = stroke_thickness;
333 self
334 }
335
336 pub fn with_corner_radius(mut self, corner_radius: StyledProperty<f32>) -> Self {
338 self.corner_radius = corner_radius;
339 self
340 }
341
342 pub fn with_pad_by_corner_radius(mut self, pad: bool) -> Self {
344 self.pad_by_corner_radius = pad;
345 self
346 }
347
348 pub fn build_border(mut self, ctx: &BuildContext) -> Border {
350 if self.widget_builder.foreground.is_none() {
351 self.widget_builder.foreground = Some(ctx.style.property(Style::BRUSH_PRIMARY));
352 }
353 Border {
354 widget: self.widget_builder.build(ctx),
355 stroke_thickness: self.stroke_thickness.into(),
356 corner_radius: self.corner_radius.into(),
357 pad_by_corner_radius: self.pad_by_corner_radius.into(),
358 }
359 }
360
361 pub fn build(self, ctx: &mut BuildContext<'_>) -> Handle<UiNode> {
363 ctx.add_node(UiNode::new(self.build_border(ctx)))
364 }
365}
366
367#[cfg(test)]
368mod test {
369 use crate::border::BorderBuilder;
370 use crate::{test::test_widget_deletion, widget::WidgetBuilder};
371
372 #[test]
373 fn test_deletion() {
374 test_widget_deletion(|ctx| BorderBuilder::new(WidgetBuilder::new()).build(ctx));
375 }
376}