use freya_engine::prelude::*;
use freya_native_core::real_dom::NodeImmutable;
use freya_node_state::{
Border,
BorderAlignment,
CanvasRunnerContext,
CornerRadius,
Fill,
ReferencesState,
ShadowPosition,
StyleState,
};
use torin::{
prelude::{
Area,
CursorPoint,
LayoutNode,
Point2D,
Size2D,
},
scaled::Scaled,
};
use super::utils::ElementUtils;
use crate::dom::DioxusNode;
enum BorderShape {
DRRect(RRect, RRect),
Path(Path),
}
pub struct RectElement;
impl RectElement {
fn get_rounded_rect(
&self,
layout_node: &LayoutNode,
node_ref: &DioxusNode,
scale_factor: f32,
) -> RRect {
let area = layout_node.visible_area().to_f32();
let node_style = &*node_ref.get::<StyleState>().unwrap();
let mut radius = node_style.corner_radius;
radius.scale(scale_factor);
RRect::new_rect_radii(
Rect::new(area.min_x(), area.min_y(), area.max_x(), area.max_y()),
&[
(radius.top_left, radius.top_left).into(),
(radius.top_right, radius.top_right).into(),
(radius.bottom_right, radius.bottom_right).into(),
(radius.bottom_left, radius.bottom_left).into(),
],
)
}
fn outer_border_path_corner_radius(
alignment: BorderAlignment,
corner_radius: f32,
width_1: f32,
width_2: f32,
) -> f32 {
if alignment == BorderAlignment::Inner || corner_radius == 0.0 {
return corner_radius;
}
let mut offset = if width_1 == 0.0 {
width_2
} else if width_2 == 0.0 {
width_1
} else {
width_1.min(width_2)
};
if alignment == BorderAlignment::Center {
offset *= 0.5;
}
corner_radius + offset
}
fn inner_border_path_corner_radius(
alignment: BorderAlignment,
corner_radius: f32,
width_1: f32,
width_2: f32,
) -> f32 {
if alignment == BorderAlignment::Outer || corner_radius == 0.0 {
return corner_radius;
}
let mut offset = if width_1 == 0.0 {
width_2
} else if width_2 == 0.0 {
width_1
} else {
width_1.min(width_2)
};
if alignment == BorderAlignment::Center {
offset *= 0.5;
}
corner_radius - offset
}
fn border_shape(
base_rect: Rect,
base_corner_radius: CornerRadius,
border: &Border,
) -> BorderShape {
let border_alignment = border.alignment;
let border_width = border.width;
let (outer_rrect, outer_corner_radius) = {
let corner_radius = CornerRadius {
top_left: Self::outer_border_path_corner_radius(
border_alignment,
base_corner_radius.top_left,
border_width.top,
border_width.left,
),
top_right: Self::outer_border_path_corner_radius(
border_alignment,
base_corner_radius.top_right,
border_width.top,
border_width.right,
),
bottom_left: Self::outer_border_path_corner_radius(
border_alignment,
base_corner_radius.bottom_left,
border_width.bottom,
border_width.left,
),
bottom_right: Self::outer_border_path_corner_radius(
border_alignment,
base_corner_radius.bottom_right,
border_width.bottom,
border_width.right,
),
smoothing: base_corner_radius.smoothing,
};
let rrect = RRect::new_rect_radii(
{
let mut rect = base_rect;
let alignment_scale = match border_alignment {
BorderAlignment::Outer => 1.0,
BorderAlignment::Center => 0.5,
BorderAlignment::Inner => 0.0,
};
rect.left -= border_width.left * alignment_scale;
rect.top -= border_width.top * alignment_scale;
rect.right += border_width.right * alignment_scale;
rect.bottom += border_width.bottom * alignment_scale;
rect
},
&[
(corner_radius.top_left, corner_radius.top_left).into(),
(corner_radius.top_right, corner_radius.top_right).into(),
(corner_radius.bottom_right, corner_radius.bottom_right).into(),
(corner_radius.bottom_left, corner_radius.bottom_left).into(),
],
);
(rrect, corner_radius)
};
let (inner_rrect, inner_corner_radius) = {
let corner_radius = CornerRadius {
top_left: Self::inner_border_path_corner_radius(
border_alignment,
base_corner_radius.top_left,
border_width.top,
border_width.left,
),
top_right: Self::inner_border_path_corner_radius(
border_alignment,
base_corner_radius.top_right,
border_width.top,
border_width.right,
),
bottom_left: Self::inner_border_path_corner_radius(
border_alignment,
base_corner_radius.bottom_left,
border_width.bottom,
border_width.left,
),
bottom_right: Self::inner_border_path_corner_radius(
border_alignment,
base_corner_radius.bottom_right,
border_width.bottom,
border_width.right,
),
smoothing: base_corner_radius.smoothing,
};
let rrect = RRect::new_rect_radii(
{
let mut rect = base_rect;
let alignment_scale = match border_alignment {
BorderAlignment::Outer => 0.0,
BorderAlignment::Center => 0.5,
BorderAlignment::Inner => 1.0,
};
rect.left += border_width.left * alignment_scale;
rect.top += border_width.top * alignment_scale;
rect.right -= border_width.right * alignment_scale;
rect.bottom -= border_width.bottom * alignment_scale;
rect
},
&[
(corner_radius.top_left, corner_radius.top_left).into(),
(corner_radius.top_right, corner_radius.top_right).into(),
(corner_radius.bottom_right, corner_radius.bottom_right).into(),
(corner_radius.bottom_left, corner_radius.bottom_left).into(),
],
);
(rrect, corner_radius)
};
if base_corner_radius.smoothing > 0.0 {
let mut path = Path::new();
path.set_fill_type(PathFillType::EvenOdd);
path.add_path(
&outer_corner_radius.smoothed_path(outer_rrect),
Point::new(outer_rrect.rect().x(), outer_rrect.rect().y()),
None,
);
path.add_path(
&inner_corner_radius.smoothed_path(inner_rrect),
Point::new(inner_rrect.rect().x(), inner_rrect.rect().y()),
None,
);
BorderShape::Path(path)
} else {
BorderShape::DRRect(outer_rrect, inner_rrect)
}
}
}
impl ElementUtils for RectElement {
fn is_point_inside_area(
&self,
point: &CursorPoint,
node_ref: &DioxusNode,
layout_node: &LayoutNode,
scale_factor: f32,
) -> bool {
let rounded_rect = self.get_rounded_rect(layout_node, node_ref, scale_factor);
let point = point.to_f32();
rounded_rect.contains(Rect::new(point.x, point.y, point.x + 1., point.y + 1.))
}
fn clip(
&self,
layout_node: &LayoutNode,
node_ref: &DioxusNode,
canvas: &Canvas,
scale_factor: f32,
) {
let rounded_rect = self.get_rounded_rect(layout_node, node_ref, scale_factor);
canvas.clip_rrect(rounded_rect, ClipOp::Intersect, true);
}
fn render(
self,
layout_node: &LayoutNode,
node_ref: &DioxusNode,
canvas: &Canvas,
font_collection: &mut FontCollection,
_font_manager: &FontMgr,
_default_fonts: &[String],
scale_factor: f32,
) {
let node_style = &*node_ref.get::<StyleState>().unwrap();
let area = layout_node.visible_area().to_f32();
let mut path = Path::new();
let mut paint = Paint::default();
paint.set_anti_alias(true);
paint.set_style(PaintStyle::Fill);
match &node_style.background {
Fill::Color(color) => {
paint.set_color(*color);
}
Fill::LinearGradient(gradient) => {
paint.set_shader(gradient.into_shader(area));
}
Fill::RadialGradient(gradient) => {
paint.set_shader(gradient.into_shader(area));
}
Fill::ConicGradient(gradient) => {
paint.set_shader(gradient.into_shader(area));
}
}
let mut corner_radius = node_style.corner_radius;
corner_radius.scale(scale_factor);
let rounded_rect = RRect::new_rect_radii(
Rect::new(area.min_x(), area.min_y(), area.max_x(), area.max_y()),
&[
(corner_radius.top_left, corner_radius.top_left).into(),
(corner_radius.top_right, corner_radius.top_right).into(),
(corner_radius.bottom_right, corner_radius.bottom_right).into(),
(corner_radius.bottom_left, corner_radius.bottom_left).into(),
],
);
if corner_radius.smoothing > 0.0 {
path.add_path(
&corner_radius.smoothed_path(rounded_rect),
(area.min_x(), area.min_y()),
None,
);
} else {
path.add_rrect(rounded_rect, None);
}
canvas.draw_path(&path, &paint);
for mut shadow in node_style.shadows.clone().into_iter() {
if shadow.fill != Fill::Color(Color::TRANSPARENT) {
shadow.scale(scale_factor);
let mut shadow_path = Path::new();
let mut shadow_paint = Paint::default();
shadow_paint.set_anti_alias(true);
match &shadow.fill {
Fill::Color(color) => {
shadow_paint.set_color(*color);
}
Fill::LinearGradient(gradient) => {
shadow_paint.set_shader(gradient.into_shader(area));
}
Fill::RadialGradient(gradient) => {
shadow_paint.set_shader(gradient.into_shader(area));
}
Fill::ConicGradient(gradient) => {
shadow_paint.set_shader(gradient.into_shader(area));
}
}
let outset: Point = match shadow.position {
ShadowPosition::Normal => {
shadow_paint.set_style(PaintStyle::Fill);
(shadow.spread, shadow.spread).into()
}
ShadowPosition::Inset => {
shadow_paint.set_style(PaintStyle::Stroke);
shadow_paint.set_stroke_width(shadow.blur / 2.0 + shadow.spread);
(-shadow.spread / 2.0, -shadow.spread / 2.0).into()
}
};
if shadow.blur > 0.0 {
shadow_paint.set_mask_filter(MaskFilter::blur(
BlurStyle::Normal,
shadow.blur / 2.0,
false,
));
}
if corner_radius.smoothing > 0.0 {
shadow_path.add_path(
&node_style
.corner_radius
.smoothed_path(rounded_rect.with_outset(outset)),
Point::new(area.min_x(), area.min_y()) - outset,
None,
);
} else {
shadow_path.add_rrect(rounded_rect.with_outset(outset), None);
}
shadow_path.offset((shadow.x, shadow.y));
canvas.save();
canvas.clip_path(
&path,
match shadow.position {
ShadowPosition::Normal => ClipOp::Difference,
ShadowPosition::Inset => ClipOp::Intersect,
},
true,
);
canvas.draw_path(&shadow_path, &shadow_paint);
canvas.restore();
}
}
for mut border in node_style.borders.clone().into_iter() {
if border.is_visible() {
border.scale(scale_factor);
let mut border_paint = Paint::default();
border_paint.set_style(PaintStyle::Fill);
border_paint.set_anti_alias(true);
match &border.fill {
Fill::Color(color) => {
border_paint.set_color(*color);
}
Fill::LinearGradient(gradient) => {
border_paint.set_shader(gradient.into_shader(area));
}
Fill::RadialGradient(gradient) => {
border_paint.set_shader(gradient.into_shader(area));
}
Fill::ConicGradient(gradient) => {
border_paint.set_shader(gradient.into_shader(area));
}
}
match Self::border_shape(*rounded_rect.rect(), corner_radius, &border) {
BorderShape::DRRect(outer, inner) => {
canvas.draw_drrect(outer, inner, &border_paint);
}
BorderShape::Path(path) => {
canvas.draw_path(&path, &border_paint);
}
}
}
}
let references = node_ref.get::<ReferencesState>().unwrap();
if let Some(canvas_ref) = &references.canvas_ref {
let mut ctx = CanvasRunnerContext {
canvas,
font_collection,
area,
scale_factor,
};
(canvas_ref.runner)(&mut ctx);
}
}
#[inline]
fn element_needs_cached_area(&self, node_ref: &DioxusNode) -> bool {
let node_style = &*node_ref.get::<StyleState>().unwrap();
!node_style.borders.is_empty() || !node_style.shadows.is_empty()
}
fn element_drawing_area(
&self,
layout_node: &LayoutNode,
node_ref: &DioxusNode,
scale_factor: f32,
) -> Area {
let node_style = &*node_ref.get::<StyleState>().unwrap();
let mut area = layout_node.visible_area();
if node_style.borders.is_empty() && node_style.shadows.is_empty() {
return area;
}
let mut path = Path::new();
let mut radius = node_style.corner_radius;
radius.scale(scale_factor);
let rounded_rect = RRect::new_rect_radii(
Rect::new(area.min_x(), area.min_y(), area.max_x(), area.max_y()),
&[
(radius.top_left, radius.top_left).into(),
(radius.top_right, radius.top_right).into(),
(radius.bottom_right, radius.bottom_right).into(),
(radius.bottom_left, radius.bottom_left).into(),
],
);
if radius.smoothing > 0.0 {
path.add_path(
&radius.smoothed_path(rounded_rect),
(area.min_x(), area.min_y()),
None,
);
} else {
path.add_rrect(rounded_rect, None);
}
for mut shadow in node_style.shadows.clone().into_iter() {
if shadow.fill != Fill::Color(Color::TRANSPARENT) {
shadow.scale(scale_factor);
let mut shadow_path = Path::new();
let outset: Option<Point> = match shadow.position {
ShadowPosition::Normal => Some(
(
shadow.spread.max(shadow.blur),
shadow.spread.max(shadow.blur),
)
.into(),
),
ShadowPosition::Inset => None, };
if let Some(outset) = outset {
if radius.smoothing > 0.0 {
shadow_path.add_path(
&node_style
.corner_radius
.smoothed_path(rounded_rect.with_outset(outset)),
Point::new(area.min_x(), area.min_y()) - outset,
None,
);
} else {
shadow_path.add_rrect(rounded_rect.with_outset(outset), None);
}
}
shadow_path.offset((shadow.x, shadow.y));
let shadow_bounds = shadow_path.bounds();
let shadow_area = Area::new(
Point2D::new(shadow_bounds.x(), shadow_bounds.y()),
Size2D::new(shadow_bounds.width(), shadow_bounds.height()),
);
area = area.union(&shadow_area);
}
}
for mut border in node_style.borders.clone().into_iter() {
if border.is_visible() {
border.scale(scale_factor);
let border_shape =
Self::border_shape(*rounded_rect.rect(), node_style.corner_radius, &border);
let border_bounds = match border_shape {
BorderShape::DRRect(ref outer, _) => outer.bounds(),
BorderShape::Path(ref path) => path.bounds(),
};
let border_area = Area::new(
Point2D::new(border_bounds.x(), border_bounds.y()),
Size2D::new(border_bounds.width(), border_bounds.height()),
);
area = area.union(&border_area.round_out());
}
}
area
}
}