#![warn(missing_docs)]
use crate::{
core::{
algebra::Vector2, math::Rect, pool::Handle, reflect::prelude::*, type_traits::prelude::*,
variable::InheritableVariable, visitor::prelude::*,
},
define_constructor,
draw::{CommandTexture, Draw, DrawingContext},
message::UiMessage,
style::{resource::StyleResourceExt, Style, StyledProperty},
widget::{Widget, WidgetBuilder},
BuildContext, Control, MessageDirection, Thickness, UiNode, UserInterface,
};
use fyrox_graph::constructor::{ConstructorProvider, GraphNodeConstructor};
use std::ops::{Deref, DerefMut};
#[derive(Default, Clone, Visit, Reflect, Debug, TypeUuidProvider, ComponentProvider)]
#[type_uuid(id = "6aba3dc5-831d-481a-bc83-ec10b2b2bf12")]
pub struct Border {
pub widget: Widget,
pub stroke_thickness: InheritableVariable<StyledProperty<Thickness>>,
#[visit(optional)]
pub corner_radius: InheritableVariable<StyledProperty<f32>>,
#[visit(optional)]
pub pad_by_corner_radius: InheritableVariable<bool>,
}
impl ConstructorProvider<UiNode, UserInterface> for Border {
fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
GraphNodeConstructor::new::<Self>()
.with_variant("Border", |ui| {
BorderBuilder::new(WidgetBuilder::new().with_name("Border"))
.build(&mut ui.build_ctx())
.into()
})
.with_group("Visual")
}
}
crate::define_widget_deref!(Border);
#[derive(Debug, Clone, PartialEq)]
pub enum BorderMessage {
StrokeThickness(StyledProperty<Thickness>),
CornerRadius(StyledProperty<f32>),
PadByCornerRadius(bool),
}
impl BorderMessage {
define_constructor!(
BorderMessage:StrokeThickness => fn stroke_thickness(StyledProperty<Thickness>), layout: false
);
define_constructor!(
BorderMessage:CornerRadius => fn corner_radius(StyledProperty<f32>), layout: false
);
define_constructor!(
BorderMessage:PadByCornerRadius => fn pad_by_corner_radius(bool), layout: false
);
}
fn corner_offset(radius: f32) -> f32 {
radius * 0.5 * (std::f32::consts::SQRT_2 - 1.0)
}
impl Control for Border {
fn measure_override(&self, ui: &UserInterface, available_size: Vector2<f32>) -> Vector2<f32> {
let corner_offset = if *self.pad_by_corner_radius {
corner_offset(**self.corner_radius)
} else {
0.0
};
let double_corner_offset = 2.0 * corner_offset;
let margin_x =
self.stroke_thickness.left + self.stroke_thickness.right + double_corner_offset;
let margin_y =
self.stroke_thickness.top + self.stroke_thickness.bottom + double_corner_offset;
let size_for_child = Vector2::new(available_size.x - margin_x, available_size.y - margin_y);
let mut desired_size = Vector2::default();
for child_handle in self.widget.children() {
ui.measure_node(*child_handle, size_for_child);
let child = ui.nodes.borrow(*child_handle);
let child_desired_size = child.desired_size();
if child_desired_size.x > desired_size.x {
desired_size.x = child_desired_size.x;
}
if child_desired_size.y > desired_size.y {
desired_size.y = child_desired_size.y;
}
}
desired_size.x += margin_x;
desired_size.y += margin_y;
desired_size
}
fn arrange_override(&self, ui: &UserInterface, final_size: Vector2<f32>) -> Vector2<f32> {
let corner_offset = if *self.pad_by_corner_radius {
corner_offset(**self.corner_radius)
} else {
0.0
};
let double_corner_offset = 2.0 * corner_offset;
let rect_for_child = Rect::new(
self.stroke_thickness.left + corner_offset,
self.stroke_thickness.top + corner_offset,
final_size.x
- (self.stroke_thickness.right + self.stroke_thickness.left + double_corner_offset),
final_size.y
- (self.stroke_thickness.bottom + self.stroke_thickness.top + double_corner_offset),
);
for child_handle in self.widget.children() {
ui.arrange_node(*child_handle, &rect_for_child);
}
final_size
}
fn draw(&self, drawing_context: &mut DrawingContext) {
let bounds = self.widget.bounding_rect();
if (**self.corner_radius).eq(&0.0) {
DrawingContext::push_rect_filled(drawing_context, &bounds, None);
drawing_context.commit(
self.clip_bounds(),
self.widget.background(),
CommandTexture::None,
None,
);
drawing_context.push_rect_vary(&bounds, **self.stroke_thickness);
drawing_context.commit(
self.clip_bounds(),
self.widget.foreground(),
CommandTexture::None,
None,
);
} else {
let thickness = self.stroke_thickness.left;
let half_thickness = thickness / 2.0;
DrawingContext::push_rounded_rect_filled(
drawing_context,
&bounds.deflate(half_thickness, half_thickness),
**self.corner_radius,
16,
);
drawing_context.commit(
self.clip_bounds(),
self.widget.background(),
CommandTexture::None,
None,
);
drawing_context.push_rounded_rect(&bounds, thickness, **self.corner_radius, 16);
drawing_context.commit(
self.clip_bounds(),
self.widget.foreground(),
CommandTexture::None,
None,
);
}
}
fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
self.widget.handle_routed_message(ui, message);
if message.destination() == self.handle()
&& message.direction() == MessageDirection::ToWidget
{
if let Some(msg) = message.data::<BorderMessage>() {
match msg {
BorderMessage::StrokeThickness(thickness) => {
if *thickness != *self.stroke_thickness {
self.stroke_thickness
.set_value_and_mark_modified(thickness.clone());
ui.send_message(message.reverse());
self.invalidate_layout();
}
}
BorderMessage::CornerRadius(radius) => {
if *radius != *self.corner_radius {
self.corner_radius
.set_value_and_mark_modified(radius.clone());
ui.send_message(message.reverse());
self.invalidate_layout();
}
}
BorderMessage::PadByCornerRadius(pad) => {
if *pad != *self.pad_by_corner_radius {
self.pad_by_corner_radius.set_value_and_mark_modified(*pad);
ui.send_message(message.reverse());
self.invalidate_layout();
}
}
}
}
}
}
}
pub struct BorderBuilder {
pub widget_builder: WidgetBuilder,
pub stroke_thickness: StyledProperty<Thickness>,
pub corner_radius: StyledProperty<f32>,
pub pad_by_corner_radius: bool,
}
impl BorderBuilder {
pub fn new(widget_builder: WidgetBuilder) -> Self {
Self {
widget_builder,
stroke_thickness: Thickness::uniform(1.0).into(),
corner_radius: 0.0.into(),
pad_by_corner_radius: true,
}
}
pub fn with_stroke_thickness(mut self, stroke_thickness: StyledProperty<Thickness>) -> Self {
self.stroke_thickness = stroke_thickness;
self
}
pub fn with_corner_radius(mut self, corner_radius: StyledProperty<f32>) -> Self {
self.corner_radius = corner_radius;
self
}
pub fn with_pad_by_corner_radius(mut self, pad: bool) -> Self {
self.pad_by_corner_radius = pad;
self
}
pub fn build_border(mut self, ctx: &BuildContext) -> Border {
if self.widget_builder.foreground.is_none() {
self.widget_builder.foreground = Some(ctx.style.property(Style::BRUSH_PRIMARY));
}
Border {
widget: self.widget_builder.build(ctx),
stroke_thickness: self.stroke_thickness.into(),
corner_radius: self.corner_radius.into(),
pad_by_corner_radius: self.pad_by_corner_radius.into(),
}
}
pub fn build(self, ctx: &mut BuildContext<'_>) -> Handle<UiNode> {
ctx.add_node(UiNode::new(self.build_border(ctx)))
}
}
#[cfg(test)]
mod test {
use crate::border::BorderBuilder;
use crate::{test::test_widget_deletion, widget::WidgetBuilder};
#[test]
fn test_deletion() {
test_widget_deletion(|ctx| BorderBuilder::new(WidgetBuilder::new()).build(ctx));
}
}