use std::{
any::Any,
borrow::Cow,
rc::Rc,
};
use freya_engine::prelude::{
Canvas,
ClipOp,
Paint,
PaintStyle,
PathBuilder,
SkBlurStyle,
SkMaskFilter,
SkPath,
SkPathFillType,
SkPoint,
SkRRect,
SkRect,
};
use rustc_hash::FxHashMap;
use torin::{
prelude::Area,
scaled::Scaled,
};
use crate::{
diff_key::DiffKey,
element::{
ClipContext,
ElementExt,
EventHandlerType,
EventMeasurementContext,
RenderContext,
},
events::name::EventName,
layers::Layer,
prelude::*,
style::{
font_size::FontSize,
scale::Scale,
shadow::{
Shadow,
ShadowPosition,
},
},
tree::DiffModifies,
};
pub fn rect() -> Rect {
Rect::empty()
}
#[derive(PartialEq, Clone)]
pub struct RectElement {
pub style: StyleState,
pub layout: LayoutData,
pub text_style_data: TextStyleData,
pub relative_layer: Layer,
pub event_handlers: FxHashMap<EventName, EventHandlerType>,
pub accessibility: AccessibilityData,
pub effect: Option<EffectData>,
}
impl Default for RectElement {
fn default() -> Self {
let mut accessibility = AccessibilityData::default();
accessibility
.builder
.set_role(accesskit::Role::GenericContainer);
Self {
style: Default::default(),
layout: Default::default(),
text_style_data: Default::default(),
relative_layer: Default::default(),
event_handlers: Default::default(),
accessibility,
effect: Default::default(),
}
}
}
impl RectElement {
pub fn render_shadow(
canvas: &Canvas,
path: &mut SkPath,
rounded_rect: SkRRect,
_area: Area,
shadow: &Shadow,
corner_radius: &CornerRadius,
) {
let mut shadow_path = PathBuilder::new();
let mut shadow_paint = Paint::default();
shadow_paint.set_anti_alias(true);
shadow_paint.set_color(shadow.color);
let outset: SkPoint = 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(SkMaskFilter::blur(
SkBlurStyle::Normal,
shadow.blur / 2.0,
false,
));
}
if corner_radius.smoothing > 0.0 {
shadow_path.add_path(&corner_radius.smoothed_path(rounded_rect.with_outset(outset)));
} else {
shadow_path.add_rrect(rounded_rect.with_outset(outset), None, 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,
);
let shadow_path = shadow_path.detach();
canvas.draw_path(&shadow_path, &shadow_paint);
canvas.restore();
}
pub fn render_border(
canvas: &Canvas,
rect: SkRect,
border: &Border,
corner_radius: &CornerRadius,
) {
let mut border_paint = Paint::default();
border_paint.set_style(PaintStyle::Fill);
border_paint.set_anti_alias(true);
border_paint.set_color(border.fill);
match Self::border_shape(rect, corner_radius, border) {
BorderShape::DRRect(outer, inner) => {
canvas.draw_drrect(outer, inner, &border_paint);
}
BorderShape::Path(path) => {
canvas.draw_path(&path, &border_paint);
}
}
}
pub fn border_shape(
base_rect: SkRect,
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 = SkRRect::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 = SkRRect::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 = PathBuilder::new();
path.set_fill_type(SkPathFillType::EvenOdd);
path.add_path(&outer_corner_radius.smoothed_path(outer_rrect));
path.add_path(&inner_corner_radius.smoothed_path(inner_rrect));
let path = path.detach();
BorderShape::Path(path)
} else {
BorderShape::DRRect(outer_rrect, inner_rrect)
}
}
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
}
}
impl ElementExt for RectElement {
fn changed(&self, other: &Rc<dyn ElementExt>) -> bool {
let Some(rect) = (other.as_ref() as &dyn Any).downcast_ref::<Self>() else {
return false;
};
self != rect
}
fn diff(&self, other: &Rc<dyn ElementExt>) -> DiffModifies {
let Some(rect) = (other.as_ref() as &dyn Any).downcast_ref::<Self>() else {
return DiffModifies::all();
};
let mut diff = DiffModifies::empty();
if self.style != rect.style {
diff.insert(DiffModifies::STYLE);
}
if self.effect != rect.effect {
diff.insert(DiffModifies::EFFECT);
}
if !self.layout.self_layout_eq(&rect.layout.layout) {
diff.insert(DiffModifies::STYLE);
diff.insert(DiffModifies::LAYOUT);
}
if !self.layout.inner_layout_eq(&rect.layout.layout) {
diff.insert(DiffModifies::STYLE);
diff.insert(DiffModifies::INNER_LAYOUT);
}
if self.accessibility != rect.accessibility {
diff.insert(DiffModifies::ACCESSIBILITY);
}
if self.relative_layer != rect.relative_layer {
diff.insert(DiffModifies::LAYER);
}
if self.event_handlers != rect.event_handlers {
diff.insert(DiffModifies::EVENT_HANDLERS);
}
if self.text_style_data != rect.text_style_data {
diff.insert(DiffModifies::TEXT_STYLE);
}
diff
}
fn layout(&'_ self) -> Cow<'_, LayoutData> {
Cow::Borrowed(&self.layout)
}
fn effect(&'_ self) -> Option<Cow<'_, EffectData>> {
self.effect.as_ref().map(Cow::Borrowed)
}
fn style(&'_ self) -> Cow<'_, StyleState> {
Cow::Borrowed(&self.style)
}
fn text_style(&'_ self) -> Cow<'_, TextStyleData> {
Cow::Borrowed(&self.text_style_data)
}
fn accessibility(&'_ self) -> Cow<'_, AccessibilityData> {
Cow::Borrowed(&self.accessibility)
}
fn layer(&self) -> Layer {
self.relative_layer
}
fn events_handlers(&'_ self) -> Option<Cow<'_, FxHashMap<EventName, EventHandlerType>>> {
Some(Cow::Borrowed(&self.event_handlers))
}
fn is_point_inside(&self, context: EventMeasurementContext) -> bool {
let area = context.layout_node.visible_area();
let cursor = context.cursor.to_f32();
let rounded_rect = self.render_rect(&area, context.scale_factor as f32);
rounded_rect.contains(SkRect::new(
cursor.x,
cursor.y,
cursor.x + 0.0001,
cursor.y + 0.0001,
))
}
fn clip(&self, context: ClipContext) {
let area = context.visible_area;
let rounded_rect = self.render_rect(area, context.scale_factor as f32);
context
.canvas
.clip_rrect(rounded_rect, ClipOp::Intersect, true);
}
fn render(&self, context: RenderContext) {
let style = self.style();
let area = context.layout_node.visible_area();
let corner_radius = style.corner_radius.with_scale(context.scale_factor as f32);
let mut path = PathBuilder::new();
let mut paint = Paint::default();
paint.set_anti_alias(true);
paint.set_style(PaintStyle::Fill);
style.background.apply_to_paint(&mut paint, area);
let rounded_rect = self.render_rect(&area, context.scale_factor as f32);
if corner_radius.smoothing > 0.0 {
path.add_path(&corner_radius.smoothed_path(rounded_rect));
} else {
path.add_rrect(rounded_rect, None, None);
}
let mut path = path.detach();
context.canvas.draw_path(&path, &paint);
for shadow in style.shadows.iter() {
if shadow.color != Color::TRANSPARENT {
let shadow = shadow.with_scale(context.scale_factor as f32);
Self::render_shadow(
context.canvas,
&mut path,
rounded_rect,
area,
&shadow,
&corner_radius,
);
}
}
for border in style.borders.iter() {
if border.is_visible() {
let border = border.with_scale(context.scale_factor as f32);
let rect = *rounded_rect.rect();
Self::render_border(context.canvas, rect, &border, &corner_radius);
}
}
}
}
pub struct Rect {
element: RectElement,
elements: Vec<Element>,
key: DiffKey,
}
impl ChildrenExt for Rect {
fn get_children(&mut self) -> &mut Vec<Element> {
&mut self.elements
}
}
impl KeyExt for Rect {
fn write_key(&mut self) -> &mut DiffKey {
&mut self.key
}
}
impl EventHandlersExt for Rect {
fn get_event_handlers(&mut self) -> &mut FxHashMap<EventName, EventHandlerType> {
&mut self.element.event_handlers
}
}
impl AccessibilityExt for Rect {
fn get_accessibility_data(&mut self) -> &mut AccessibilityData {
&mut self.element.accessibility
}
}
impl TextStyleExt for Rect {
fn get_text_style_data(&mut self) -> &mut TextStyleData {
&mut self.element.text_style_data
}
}
impl StyleExt for Rect {
fn get_style(&mut self) -> &mut StyleState {
&mut self.element.style
}
}
impl MaybeExt for Rect {}
impl LayerExt for Rect {
fn get_layer(&mut self) -> &mut Layer {
&mut self.element.relative_layer
}
}
impl LayoutExt for Rect {
fn get_layout(&mut self) -> &mut LayoutData {
&mut self.element.layout
}
}
impl ContainerExt for Rect {}
impl ContainerWithContentExt for Rect {}
impl ScrollableExt for Rect {
fn get_effect(&mut self) -> &mut EffectData {
if self.element.effect.is_none() {
self.element.effect = Some(EffectData::default())
}
self.element.effect.as_mut().unwrap()
}
}
impl InteractiveExt for Rect {
fn get_effect(&mut self) -> &mut EffectData {
if self.element.effect.is_none() {
self.element.effect = Some(EffectData::default())
}
self.element.effect.as_mut().unwrap()
}
}
impl From<Rect> for Element {
fn from(value: Rect) -> Self {
Element::Element {
key: value.key,
element: Rc::new(value.element),
elements: value.elements,
}
}
}
impl Rect {
pub fn empty() -> Self {
Self {
element: RectElement::default(),
elements: Vec::default(),
key: DiffKey::None,
}
}
pub fn try_downcast(element: &dyn ElementExt) -> Option<RectElement> {
(element as &dyn Any).downcast_ref::<RectElement>().cloned()
}
pub fn color(mut self, color: impl Into<Color>) -> Self {
self.element.text_style_data.color = Some(color.into());
self
}
pub fn font_size(mut self, font_size: impl Into<FontSize>) -> Self {
self.element.text_style_data.font_size = Some(font_size.into());
self
}
pub fn overflow<S: Into<Overflow>>(mut self, overflow: S) -> Self {
self.element
.effect
.get_or_insert_with(Default::default)
.overflow = overflow.into();
self
}
pub fn rotate<R: Into<Option<f32>>>(mut self, rotation: R) -> Self {
self.element
.effect
.get_or_insert_with(Default::default)
.rotation = rotation.into();
self
}
pub fn scale(mut self, scale: impl Into<Scale>) -> Self {
self.element
.effect
.get_or_insert_with(Default::default)
.scale = Some(scale.into());
self
}
pub fn opacity(mut self, opacity: impl Into<f32>) -> Self {
self.element
.effect
.get_or_insert_with(Default::default)
.opacity = Some(opacity.into());
self
}
pub fn blur(mut self, blur: impl Into<f32>) -> Self {
self.element
.effect
.get_or_insert_with(Default::default)
.blur = Some(blur.into());
self
}
}