use std::rc::Rc;
use blinc_core::{
Brush, Color, CornerRadius, DrawContext, Gradient, Path, Rect, Shadow, Transform,
};
use taffy::{prelude::*, Overflow};
use crate::canvas::{CanvasBounds, CanvasRenderFn};
use crate::div::{ElementBuilder, ElementTypeId};
use crate::element::{Material, RenderLayer, RenderProps};
use crate::event_handler::EventHandlers;
use crate::tree::{LayoutNodeId, LayoutTree};
use crate::Div;
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct CornerConfig {
pub radius: f32,
pub style: CornerStyle,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub enum CornerStyle {
#[default]
None,
Convex,
Concave,
Step,
}
impl CornerConfig {
pub const NONE: Self = Self {
radius: 0.0,
style: CornerStyle::None,
};
pub fn convex(radius: f32) -> Self {
Self {
radius,
style: CornerStyle::Convex,
}
}
pub fn concave(radius: f32) -> Self {
Self {
radius,
style: CornerStyle::Concave,
}
}
pub fn step(depth: f32) -> Self {
Self {
radius: depth,
style: CornerStyle::Step,
}
}
pub fn is_concave(&self) -> bool {
matches!(self.style, CornerStyle::Concave)
}
pub fn is_step(&self) -> bool {
matches!(self.style, CornerStyle::Step)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct CornersConfig {
pub top_left: CornerConfig,
pub top_right: CornerConfig,
pub bottom_right: CornerConfig,
pub bottom_left: CornerConfig,
}
impl CornersConfig {
pub const NONE: Self = Self {
top_left: CornerConfig::NONE,
top_right: CornerConfig::NONE,
bottom_right: CornerConfig::NONE,
bottom_left: CornerConfig::NONE,
};
pub fn has_concave_curves(&self) -> bool {
self.top_left.is_concave()
|| self.top_right.is_concave()
|| self.bottom_right.is_concave()
|| self.bottom_left.is_concave()
}
pub fn has_step_corners(&self) -> bool {
self.top_left.is_step()
|| self.top_right.is_step()
|| self.bottom_right.is_step()
|| self.bottom_left.is_step()
}
pub fn needs_custom_rendering(&self) -> bool {
self.has_concave_curves() || self.has_step_corners()
}
pub fn to_corner_radius(&self) -> CornerRadius {
CornerRadius {
top_left: if self.top_left.is_concave() || self.top_left.is_step() {
0.0
} else {
self.top_left.radius
},
top_right: if self.top_right.is_concave() || self.top_right.is_step() {
0.0
} else {
self.top_right.radius
},
bottom_right: if self.bottom_right.is_concave() || self.bottom_right.is_step() {
0.0
} else {
self.bottom_right.radius
},
bottom_left: if self.bottom_left.is_concave() || self.bottom_left.is_step() {
0.0
} else {
self.bottom_left.radius
},
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct CenterScoop {
pub width: f32,
pub depth: f32,
pub corner_radius: f32,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct CenterBulge {
pub width: f32,
pub height: f32,
pub corner_radius: f32,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct CenterCut {
pub width: f32,
pub depth: f32,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct CenterPeak {
pub width: f32,
pub height: f32,
}
pub struct Notch {
pub(crate) corners: CornersConfig,
pub(crate) top_center_scoop: Option<CenterScoop>,
pub(crate) bottom_center_scoop: Option<CenterScoop>,
pub(crate) top_center_bulge: Option<CenterBulge>,
pub(crate) bottom_center_bulge: Option<CenterBulge>,
pub(crate) top_center_cut: Option<CenterCut>,
pub(crate) bottom_center_cut: Option<CenterCut>,
pub(crate) top_center_peak: Option<CenterPeak>,
pub(crate) bottom_center_peak: Option<CenterPeak>,
pub(crate) style: Style,
pub(crate) children: Vec<Box<dyn ElementBuilder>>,
pub(crate) background: Option<Brush>,
pub(crate) border_color: Option<Color>,
pub(crate) border_width: f32,
pub(crate) shadow: Option<Shadow>,
pub(crate) material: Option<Material>,
pub(crate) opacity: f32,
pub(crate) render_layer: RenderLayer,
pub(crate) event_handlers: EventHandlers,
pub(crate) element_id: Option<String>,
pub inner: Div,
}
impl Notch {
pub fn new() -> Self {
Self {
corners: CornersConfig::NONE,
top_center_scoop: None,
bottom_center_scoop: None,
top_center_bulge: None,
bottom_center_bulge: None,
top_center_cut: None,
bottom_center_cut: None,
top_center_peak: None,
bottom_center_peak: None,
style: Style::default(),
children: Vec::new(),
background: None,
border_color: None,
border_width: 0.0,
shadow: None,
material: None,
opacity: 1.0,
render_layer: RenderLayer::default(),
event_handlers: EventHandlers::default(),
element_id: None,
inner: Div::new(),
}
}
pub fn corner_top(mut self, radius: f32) -> Self {
if radius < 0.0 {
self.corners.top_left = CornerConfig::concave(-radius);
self.corners.top_right = CornerConfig::concave(-radius);
} else {
self.corners.top_left = CornerConfig::convex(radius);
self.corners.top_right = CornerConfig::convex(radius);
}
self
}
pub fn corner_bottom(mut self, radius: f32) -> Self {
if radius < 0.0 {
self.corners.bottom_left = CornerConfig::concave(-radius);
self.corners.bottom_right = CornerConfig::concave(-radius);
} else {
self.corners.bottom_left = CornerConfig::convex(radius);
self.corners.bottom_right = CornerConfig::convex(radius);
}
self
}
pub fn corner_left(mut self, radius: f32) -> Self {
if radius < 0.0 {
self.corners.top_left = CornerConfig::concave(-radius);
self.corners.bottom_left = CornerConfig::concave(-radius);
} else {
self.corners.top_left = CornerConfig::convex(radius);
self.corners.bottom_left = CornerConfig::convex(radius);
}
self
}
pub fn corner_right(mut self, radius: f32) -> Self {
if radius < 0.0 {
self.corners.top_right = CornerConfig::concave(-radius);
self.corners.bottom_right = CornerConfig::concave(-radius);
} else {
self.corners.top_right = CornerConfig::convex(radius);
self.corners.bottom_right = CornerConfig::convex(radius);
}
self
}
pub fn corner_tl(mut self, radius: f32) -> Self {
if radius < 0.0 {
self.corners.top_left = CornerConfig::concave(-radius);
} else {
self.corners.top_left = CornerConfig::convex(radius);
}
self
}
pub fn corner_tr(mut self, radius: f32) -> Self {
if radius < 0.0 {
self.corners.top_right = CornerConfig::concave(-radius);
} else {
self.corners.top_right = CornerConfig::convex(radius);
}
self
}
pub fn corner_br(mut self, radius: f32) -> Self {
if radius < 0.0 {
self.corners.bottom_right = CornerConfig::concave(-radius);
} else {
self.corners.bottom_right = CornerConfig::convex(radius);
}
self
}
pub fn corner_bl(mut self, radius: f32) -> Self {
if radius < 0.0 {
self.corners.bottom_left = CornerConfig::concave(-radius);
} else {
self.corners.bottom_left = CornerConfig::convex(radius);
}
self
}
pub fn concave_left(mut self, radius: f32) -> Self {
self.corners.top_left = CornerConfig::concave(radius);
self.corners.bottom_left = CornerConfig::concave(radius);
self
}
pub fn concave_right(mut self, radius: f32) -> Self {
self.corners.top_right = CornerConfig::concave(radius);
self.corners.bottom_right = CornerConfig::concave(radius);
self
}
pub fn concave_top(mut self, radius: f32) -> Self {
self.corners.top_left = CornerConfig::concave(radius);
self.corners.top_right = CornerConfig::concave(radius);
self
}
pub fn concave_bottom(mut self, radius: f32) -> Self {
self.corners.bottom_left = CornerConfig::concave(radius);
self.corners.bottom_right = CornerConfig::concave(radius);
self
}
pub fn concave_tl(mut self, radius: f32) -> Self {
self.corners.top_left = CornerConfig::concave(radius);
self
}
pub fn concave_tr(mut self, radius: f32) -> Self {
self.corners.top_right = CornerConfig::concave(radius);
self
}
pub fn concave_br(mut self, radius: f32) -> Self {
self.corners.bottom_right = CornerConfig::concave(radius);
self
}
pub fn concave_bl(mut self, radius: f32) -> Self {
self.corners.bottom_left = CornerConfig::concave(radius);
self
}
pub fn step_top(mut self, depth: f32) -> Self {
self.corners.top_left = CornerConfig::step(depth);
self.corners.top_right = CornerConfig::step(depth);
self
}
pub fn step_bottom(mut self, depth: f32) -> Self {
self.corners.bottom_left = CornerConfig::step(depth);
self.corners.bottom_right = CornerConfig::step(depth);
self
}
pub fn step_left(mut self, depth: f32) -> Self {
self.corners.top_left = CornerConfig::step(depth);
self.corners.bottom_left = CornerConfig::step(depth);
self
}
pub fn step_right(mut self, depth: f32) -> Self {
self.corners.top_right = CornerConfig::step(depth);
self.corners.bottom_right = CornerConfig::step(depth);
self
}
pub fn step_tl(mut self, depth: f32) -> Self {
self.corners.top_left = CornerConfig::step(depth);
self
}
pub fn step_tr(mut self, depth: f32) -> Self {
self.corners.top_right = CornerConfig::step(depth);
self
}
pub fn step_br(mut self, depth: f32) -> Self {
self.corners.bottom_right = CornerConfig::step(depth);
self
}
pub fn step_bl(mut self, depth: f32) -> Self {
self.corners.bottom_left = CornerConfig::step(depth);
self
}
pub fn center_scoop_top(mut self, width: f32, depth: f32) -> Self {
self.top_center_scoop = Some(CenterScoop {
width,
depth,
corner_radius: 0.0,
});
self
}
pub fn center_scoop_top_rounded(mut self, width: f32, depth: f32, corner_radius: f32) -> Self {
self.top_center_scoop = Some(CenterScoop {
width,
depth,
corner_radius,
});
self
}
pub fn center_scoop_bottom(mut self, width: f32, depth: f32) -> Self {
self.bottom_center_scoop = Some(CenterScoop {
width,
depth,
corner_radius: 0.0,
});
self
}
pub fn center_scoop_bottom_rounded(
mut self,
width: f32,
depth: f32,
corner_radius: f32,
) -> Self {
self.bottom_center_scoop = Some(CenterScoop {
width,
depth,
corner_radius,
});
self
}
pub fn center_bulge_top(mut self, width: f32, height: f32) -> Self {
self.top_center_bulge = Some(CenterBulge {
width,
height,
corner_radius: 0.0,
});
self
}
pub fn center_bulge_top_rounded(mut self, width: f32, height: f32, corner_radius: f32) -> Self {
self.top_center_bulge = Some(CenterBulge {
width,
height,
corner_radius,
});
self
}
pub fn center_bulge_bottom(mut self, width: f32, height: f32) -> Self {
self.bottom_center_bulge = Some(CenterBulge {
width,
height,
corner_radius: 0.0,
});
self
}
pub fn center_bulge_bottom_rounded(
mut self,
width: f32,
height: f32,
corner_radius: f32,
) -> Self {
self.bottom_center_bulge = Some(CenterBulge {
width,
height,
corner_radius,
});
self
}
pub fn center_cut_top(mut self, width: f32, depth: f32) -> Self {
self.top_center_cut = Some(CenterCut { width, depth });
self
}
pub fn center_cut_bottom(mut self, width: f32, depth: f32) -> Self {
self.bottom_center_cut = Some(CenterCut { width, depth });
self
}
pub fn center_peak_top(mut self, width: f32, height: f32) -> Self {
self.top_center_peak = Some(CenterPeak { width, height });
self
}
pub fn center_peak_bottom(mut self, width: f32, height: f32) -> Self {
self.bottom_center_peak = Some(CenterPeak { width, height });
self
}
pub fn rounded(mut self, radius: f32) -> Self {
self.corners.top_left = CornerConfig::convex(radius);
self.corners.top_right = CornerConfig::convex(radius);
self.corners.bottom_right = CornerConfig::convex(radius);
self.corners.bottom_left = CornerConfig::convex(radius);
self
}
pub fn rounded_bottom(mut self, radius: f32) -> Self {
self.corners.bottom_left = CornerConfig::convex(radius);
self.corners.bottom_right = CornerConfig::convex(radius);
self
}
pub fn rounded_top(mut self, radius: f32) -> Self {
self.corners.top_left = CornerConfig::convex(radius);
self.corners.top_right = CornerConfig::convex(radius);
self
}
pub fn rounded_left(mut self, radius: f32) -> Self {
self.corners.top_left = CornerConfig::convex(radius);
self.corners.bottom_left = CornerConfig::convex(radius);
self
}
pub fn rounded_right(mut self, radius: f32) -> Self {
self.corners.top_right = CornerConfig::convex(radius);
self.corners.bottom_right = CornerConfig::convex(radius);
self
}
pub fn rounded_tl(mut self, radius: f32) -> Self {
self.corners.top_left = CornerConfig::convex(radius);
self
}
pub fn rounded_tr(mut self, radius: f32) -> Self {
self.corners.top_right = CornerConfig::convex(radius);
self
}
pub fn rounded_br(mut self, radius: f32) -> Self {
self.corners.bottom_right = CornerConfig::convex(radius);
self
}
pub fn rounded_bl(mut self, radius: f32) -> Self {
self.corners.bottom_left = CornerConfig::convex(radius);
self
}
pub fn rounded_full(mut self) -> Self {
self.corners.top_left = CornerConfig::convex(9999.0);
self.corners.top_right = CornerConfig::convex(9999.0);
self.corners.bottom_right = CornerConfig::convex(9999.0);
self.corners.bottom_left = CornerConfig::convex(9999.0);
self
}
pub fn w(mut self, width: f32) -> Self {
self.style.size.width = Dimension::Length(width);
self
}
pub fn h(mut self, height: f32) -> Self {
self.style.size.height = Dimension::Length(height);
self
}
pub fn size(mut self, size: f32) -> Self {
self.style.size.width = Dimension::Length(size);
self.style.size.height = Dimension::Length(size);
self
}
pub fn w_full(mut self) -> Self {
self.style.size.width = Dimension::Percent(1.0);
self
}
pub fn h_full(mut self) -> Self {
self.style.size.height = Dimension::Percent(1.0);
self
}
pub fn w_fit(mut self) -> Self {
self.style.size.width = Dimension::Auto;
self
}
pub fn h_fit(mut self) -> Self {
self.style.size.height = Dimension::Auto;
self
}
pub fn min_w(mut self, width: f32) -> Self {
self.style.min_size.width = Dimension::Length(width);
self
}
pub fn min_h(mut self, height: f32) -> Self {
self.style.min_size.height = Dimension::Length(height);
self
}
pub fn max_w(mut self, width: f32) -> Self {
self.style.max_size.width = Dimension::Length(width);
self
}
pub fn max_h(mut self, height: f32) -> Self {
self.style.max_size.height = Dimension::Length(height);
self
}
pub fn p(mut self, padding: f32) -> Self {
self.style.padding = taffy::Rect {
left: LengthPercentage::Length(padding),
right: LengthPercentage::Length(padding),
top: LengthPercentage::Length(padding),
bottom: LengthPercentage::Length(padding),
};
self
}
pub fn px(mut self, padding: f32) -> Self {
self.style.padding.left = LengthPercentage::Length(padding);
self.style.padding.right = LengthPercentage::Length(padding);
self
}
pub fn py(mut self, padding: f32) -> Self {
self.style.padding.top = LengthPercentage::Length(padding);
self.style.padding.bottom = LengthPercentage::Length(padding);
self
}
pub fn pt(mut self, padding: f32) -> Self {
self.style.padding.top = LengthPercentage::Length(padding);
self
}
pub fn pb(mut self, padding: f32) -> Self {
self.style.padding.bottom = LengthPercentage::Length(padding);
self
}
pub fn pl(mut self, padding: f32) -> Self {
self.style.padding.left = LengthPercentage::Length(padding);
self
}
pub fn pr(mut self, padding: f32) -> Self {
self.style.padding.right = LengthPercentage::Length(padding);
self
}
pub fn m(mut self, margin: f32) -> Self {
self.style.margin = taffy::Rect {
left: LengthPercentageAuto::Length(margin),
right: LengthPercentageAuto::Length(margin),
top: LengthPercentageAuto::Length(margin),
bottom: LengthPercentageAuto::Length(margin),
};
self
}
pub fn mx(mut self, margin: f32) -> Self {
self.style.margin.left = LengthPercentageAuto::Length(margin);
self.style.margin.right = LengthPercentageAuto::Length(margin);
self
}
pub fn my(mut self, margin: f32) -> Self {
self.style.margin.top = LengthPercentageAuto::Length(margin);
self.style.margin.bottom = LengthPercentageAuto::Length(margin);
self
}
pub fn mx_auto(mut self) -> Self {
self.style.margin.left = LengthPercentageAuto::Auto;
self.style.margin.right = LengthPercentageAuto::Auto;
self
}
pub fn flex_col(mut self) -> Self {
self.style.display = Display::Flex;
self.style.flex_direction = FlexDirection::Column;
self
}
pub fn flex_row(mut self) -> Self {
self.style.display = Display::Flex;
self.style.flex_direction = FlexDirection::Row;
self
}
pub fn gap(mut self, gap: f32) -> Self {
self.style.gap = taffy::Size {
width: LengthPercentage::Length(gap),
height: LengthPercentage::Length(gap),
};
self
}
pub fn flex_center(mut self) -> Self {
self.style.display = Display::Flex;
self.style.justify_content = Some(JustifyContent::Center);
self.style.align_items = Some(AlignItems::Center);
self
}
pub fn justify_start(mut self) -> Self {
self.style.justify_content = Some(JustifyContent::Start);
self
}
pub fn justify_center(mut self) -> Self {
self.style.justify_content = Some(JustifyContent::Center);
self
}
pub fn justify_end(mut self) -> Self {
self.style.justify_content = Some(JustifyContent::End);
self
}
pub fn justify_between(mut self) -> Self {
self.style.justify_content = Some(JustifyContent::SpaceBetween);
self
}
pub fn items_start(mut self) -> Self {
self.style.align_items = Some(AlignItems::Start);
self
}
pub fn items_center(mut self) -> Self {
self.style.align_items = Some(AlignItems::Center);
self
}
pub fn items_end(mut self) -> Self {
self.style.align_items = Some(AlignItems::End);
self
}
pub fn flex_grow(mut self) -> Self {
self.style.flex_grow = 1.0;
self
}
pub fn flex_shrink(mut self) -> Self {
self.style.flex_shrink = 1.0;
self
}
pub fn flex_none(mut self) -> Self {
self.style.flex_grow = 0.0;
self.style.flex_shrink = 0.0;
self
}
pub fn absolute(mut self) -> Self {
self.style.position = Position::Absolute;
self
}
pub fn relative(mut self) -> Self {
self.style.position = Position::Relative;
self
}
pub fn top(mut self, offset: f32) -> Self {
self.style.inset.top = LengthPercentageAuto::Length(offset);
self
}
pub fn bottom(mut self, offset: f32) -> Self {
self.style.inset.bottom = LengthPercentageAuto::Length(offset);
self
}
pub fn left(mut self, offset: f32) -> Self {
self.style.inset.left = LengthPercentageAuto::Length(offset);
self
}
pub fn right(mut self, offset: f32) -> Self {
self.style.inset.right = LengthPercentageAuto::Length(offset);
self
}
pub fn bg(mut self, color: impl Into<Color>) -> Self {
self.background = Some(Brush::Solid(color.into()));
self
}
pub fn background(mut self, brush: impl Into<Brush>) -> Self {
self.background = Some(brush.into());
self
}
pub fn border(mut self, width: f32, color: impl Into<Color>) -> Self {
self.border_width = width;
self.border_color = Some(color.into());
self
}
pub fn border_width(mut self, width: f32) -> Self {
self.border_width = width;
self
}
pub fn border_color(mut self, color: impl Into<Color>) -> Self {
self.border_color = Some(color.into());
self
}
pub fn shadow(mut self, shadow: Shadow) -> Self {
self.shadow = Some(shadow);
self
}
pub fn shadow_sm(mut self) -> Self {
self.shadow = Some(Shadow::new(0.0, 1.0, 3.0, Color::rgba(0.0, 0.0, 0.0, 0.1)));
self
}
pub fn shadow_md(mut self) -> Self {
self.shadow = Some(Shadow::new(0.0, 4.0, 6.0, Color::rgba(0.0, 0.0, 0.0, 0.1)));
self
}
pub fn shadow_lg(mut self) -> Self {
self.shadow = Some(Shadow::new(
0.0,
10.0,
15.0,
Color::rgba(0.0, 0.0, 0.0, 0.1),
));
self
}
pub fn opacity(mut self, opacity: f32) -> Self {
self.opacity = opacity;
self
}
pub fn glass(mut self) -> Self {
self.material = Some(Material::Glass(Default::default()));
self.render_layer = RenderLayer::Glass;
self
}
pub fn overflow_clip(mut self) -> Self {
self.style.overflow.x = Overflow::Hidden;
self.style.overflow.y = Overflow::Hidden;
self
}
pub fn overflow_visible(mut self) -> Self {
self.style.overflow.x = Overflow::Visible;
self.style.overflow.y = Overflow::Visible;
self
}
pub fn child(mut self, child: impl ElementBuilder + 'static) -> Self {
self.children.push(Box::new(child));
self
}
pub fn children(
mut self,
children: impl IntoIterator<Item = impl ElementBuilder + 'static>,
) -> Self {
for child in children {
self.children.push(Box::new(child));
}
self
}
#[inline]
pub fn when<F>(self, condition: bool, f: F) -> Self
where
F: FnOnce(Self) -> Self,
{
if condition {
f(self)
} else {
self
}
}
pub fn on_click<F>(mut self, handler: F) -> Self
where
F: Fn(&crate::event_handler::EventContext) + Send + Sync + 'static,
{
self.inner = self.inner.on_click(handler);
self
}
pub fn on_mouse_down<F>(mut self, handler: F) -> Self
where
F: Fn(&crate::event_handler::EventContext) + Send + Sync + 'static,
{
self.inner = self.inner.on_mouse_down(handler);
self
}
pub fn on_mouse_up<F>(mut self, handler: F) -> Self
where
F: Fn(&crate::event_handler::EventContext) + Send + Sync + 'static,
{
self.inner = self.inner.on_mouse_up(handler);
self
}
pub fn on_mouse_move<F>(mut self, handler: F) -> Self
where
F: Fn(&crate::event_handler::EventContext) + Send + Sync + 'static,
{
self.inner = self.inner.on_mouse_move(handler);
self
}
pub fn on_drag<F>(mut self, handler: F) -> Self
where
F: Fn(&crate::event_handler::EventContext) + Send + Sync + 'static,
{
self.inner = self.inner.on_drag(handler);
self
}
pub fn on_scroll<F>(mut self, handler: F) -> Self
where
F: Fn(&crate::event_handler::EventContext) + Send + Sync + 'static,
{
self.inner = self.inner.on_scroll(handler);
self
}
pub fn on_hover_enter<F>(mut self, handler: F) -> Self
where
F: Fn(&crate::event_handler::EventContext) + Send + Sync + 'static,
{
self.inner = self.inner.on_hover_enter(handler);
self
}
pub fn on_hover_leave<F>(mut self, handler: F) -> Self
where
F: Fn(&crate::event_handler::EventContext) + Send + Sync + 'static,
{
self.inner = self.inner.on_hover_leave(handler);
self
}
pub fn on_focus<F>(mut self, handler: F) -> Self
where
F: Fn(&crate::event_handler::EventContext) + Send + Sync + 'static,
{
self.inner = self.inner.on_focus(handler);
self
}
pub fn on_blur<F>(mut self, handler: F) -> Self
where
F: Fn(&crate::event_handler::EventContext) + Send + Sync + 'static,
{
self.inner = self.inner.on_blur(handler);
self
}
pub fn id(mut self, id: impl Into<String>) -> Self {
self.element_id = Some(id.into());
self
}
}
impl Default for Notch {
fn default() -> Self {
Self::new()
}
}
fn apply_brush_opacity(brush: Brush, opacity: f32) -> Brush {
if opacity >= 1.0 {
return brush;
}
match brush {
Brush::Solid(color) => Brush::Solid(color.with_alpha(color.a * opacity)),
Brush::Gradient(g) => {
let new_stops: Vec<_> = g
.stops()
.iter()
.map(|stop| {
blinc_core::GradientStop::new(
stop.offset,
stop.color.with_alpha(stop.color.a * opacity),
)
})
.collect();
let new_gradient = match g {
Gradient::Linear {
start,
end,
space,
spread,
..
} => Gradient::Linear {
start,
end,
stops: new_stops,
space,
spread,
},
Gradient::Radial {
center,
radius,
focal,
space,
spread,
..
} => Gradient::Radial {
center,
radius,
focal,
stops: new_stops,
space,
spread,
},
Gradient::Conic {
center,
start_angle,
space,
..
} => Gradient::Conic {
center,
start_angle,
stops: new_stops,
space,
},
};
Brush::Gradient(new_gradient)
}
other => other,
}
}
#[allow(clippy::too_many_arguments)]
fn build_shape_path(
bounds: Rect,
corners: &CornersConfig,
top_center_scoop: Option<&CenterScoop>,
bottom_center_scoop: Option<&CenterScoop>,
top_center_bulge: Option<&CenterBulge>,
bottom_center_bulge: Option<&CenterBulge>,
top_center_cut: Option<&CenterCut>,
bottom_center_cut: Option<&CenterCut>,
top_center_peak: Option<&CenterPeak>,
bottom_center_peak: Option<&CenterPeak>,
) -> Path {
let w = bounds.width();
let h = bounds.height();
let x = bounds.x();
let y = bounds.y();
let max_radius = (w.min(h)) / 2.0;
let clamp = |r: f32| r.min(max_radius);
let tl = &corners.top_left;
let tr = &corners.top_right;
let br = &corners.bottom_right;
let bl = &corners.bottom_left;
let tl_r = clamp(tl.radius);
let tr_r = clamp(tr.radius);
let br_r = clamp(br.radius);
let bl_r = clamp(bl.radius);
let mut path = Path::new();
let top_start_x = match tl.style {
CornerStyle::Concave => x - tl_r, CornerStyle::Convex if tl_r > 0.0 => x + tl_r, _ => x,
};
path = path.move_to(top_start_x, y);
let top_end_x = match tr.style {
CornerStyle::Concave => x + w + tr_r, CornerStyle::Convex if tr_r > 0.0 => x + w - tr_r, _ => x + w,
};
if let Some(scoop) = top_center_scoop {
let center_x = x + w / 2.0;
let scoop_start_x = center_x - scoop.width / 2.0;
let scoop_end_x = center_x + scoop.width / 2.0;
let scoop_bottom_y = y + scoop.depth;
let cr = scoop.corner_radius.min(scoop.width / 4.0).max(0.0);
const K: f32 = 0.552_284_8;
if cr > 0.0 {
let effective_start_x = scoop_start_x + cr;
let effective_end_x = scoop_end_x - cr;
let effective_start_y = y + cr;
let effective_rx = (effective_end_x - effective_start_x) / 2.0;
let effective_ry = scoop.depth - cr;
path = path.line_to(scoop_start_x, y);
path = path.quad_to(
scoop_start_x + cr * 0.55,
y, effective_start_x,
effective_start_y, );
path = path.cubic_to(
effective_start_x,
effective_start_y + K * effective_ry, center_x - K * effective_rx,
scoop_bottom_y, center_x,
scoop_bottom_y, );
path = path.cubic_to(
center_x + K * effective_rx,
scoop_bottom_y, effective_end_x,
effective_start_y + K * effective_ry, effective_end_x,
effective_start_y, );
path = path.quad_to(
scoop_end_x - cr * 0.55,
y, scoop_end_x,
y, );
path = path.line_to(top_end_x, y);
} else {
let rx = scoop.width / 2.0;
let ry = scoop.depth;
path = path.line_to(scoop_start_x, y);
path = path.cubic_to(
scoop_start_x,
y + K * ry, center_x - K * rx,
scoop_bottom_y, center_x,
scoop_bottom_y, );
path = path.cubic_to(
center_x + K * rx,
scoop_bottom_y, scoop_end_x,
y + K * ry, scoop_end_x,
y, );
path = path.line_to(top_end_x, y);
}
} else if let Some(bulge) = top_center_bulge {
let center_x = x + w / 2.0;
let bulge_start_x = center_x - bulge.width / 2.0;
let bulge_end_x = center_x + bulge.width / 2.0;
let bulge_top_y = y - bulge.height;
let cr = bulge.corner_radius.min(bulge.width / 4.0).max(0.0);
const K: f32 = 0.552_284_8;
if cr > 0.0 {
let effective_start_x = bulge_start_x + cr;
let effective_end_x = bulge_end_x - cr;
let effective_start_y = y - cr; let effective_rx = (effective_end_x - effective_start_x) / 2.0;
let effective_ry = bulge.height - cr;
path = path.line_to(bulge_start_x, y);
path = path.quad_to(
effective_start_x, y, effective_start_x,
effective_start_y, );
path = path.cubic_to(
effective_start_x,
effective_start_y - K * effective_ry, center_x - K * effective_rx,
bulge_top_y, center_x,
bulge_top_y, );
path = path.cubic_to(
center_x + K * effective_rx,
bulge_top_y, effective_end_x,
effective_start_y - K * effective_ry, effective_end_x,
effective_start_y, );
path = path.quad_to(
effective_end_x, y, bulge_end_x,
y, );
path = path.line_to(top_end_x, y);
} else {
let rx = bulge.width / 2.0;
path = path.line_to(bulge_start_x, y);
path = path.cubic_to(
bulge_start_x + K * rx, y, center_x - K * rx, bulge_top_y, center_x,
bulge_top_y, );
path = path.cubic_to(
center_x + K * rx, bulge_top_y, bulge_end_x - K * rx, y, bulge_end_x,
y, );
path = path.line_to(top_end_x, y);
}
} else if let Some(cut) = top_center_cut {
let center_x = x + w / 2.0;
let cut_start_x = center_x - cut.width / 2.0;
let cut_end_x = center_x + cut.width / 2.0;
let cut_point_y = y + cut.depth;
path = path.line_to(cut_start_x, y);
path = path.line_to(center_x, cut_point_y);
path = path.line_to(cut_end_x, y);
path = path.line_to(top_end_x, y);
} else if let Some(peak) = top_center_peak {
let center_x = x + w / 2.0;
let peak_start_x = center_x - peak.width / 2.0;
let peak_end_x = center_x + peak.width / 2.0;
let peak_point_y = y - peak.height;
path = path.line_to(peak_start_x, y);
path = path.line_to(center_x, peak_point_y);
path = path.line_to(peak_end_x, y);
path = path.line_to(top_end_x, y);
} else {
path = path.line_to(top_end_x, y);
}
match tr.style {
CornerStyle::Step => {
path = path.line_to(x + w + tr_r, y);
path = path.line_to(x + w + tr_r, y + tr_r);
path = path.line_to(x + w, y + tr_r);
}
CornerStyle::Concave => {
path = path.quad_to(x + w, y, x + w, y + tr_r);
}
CornerStyle::Convex if tr_r > 0.0 => {
path = path.quad_to(x + w, y, x + w, y + tr_r);
}
_ => {}
}
let right_end_y = match br.style {
CornerStyle::Concave => y + h + br_r, CornerStyle::Convex if br_r > 0.0 => y + h - br_r, _ => y + h,
};
path = path.line_to(x + w, right_end_y);
match br.style {
CornerStyle::Step => {
path = path.line_to(x + w, y + h + br_r);
path = path.line_to(x + w - br_r, y + h + br_r);
path = path.line_to(x + w - br_r, y + h);
}
CornerStyle::Concave => {
path = path.quad_to(x + w, y + h, x + w - br_r, y + h);
}
CornerStyle::Convex if br_r > 0.0 => {
path = path.quad_to(x + w, y + h, x + w - br_r, y + h);
}
_ => {}
}
let bottom_end_x = match bl.style {
CornerStyle::Concave => x - bl_r, CornerStyle::Convex if bl_r > 0.0 => x + bl_r, _ => x,
};
if let Some(scoop) = bottom_center_scoop {
let center_x = x + w / 2.0;
let scoop_start_x = center_x + scoop.width / 2.0; let scoop_end_x = center_x - scoop.width / 2.0; let scoop_top_y = y + h - scoop.depth;
let cr = scoop.corner_radius.min(scoop.width / 4.0).max(0.0);
const K: f32 = 0.552_284_8;
if cr > 0.0 {
let effective_start_x = scoop_start_x - cr; let effective_end_x = scoop_end_x + cr; let effective_start_y = y + h - cr;
let effective_rx = (effective_start_x - effective_end_x) / 2.0;
let effective_ry = scoop.depth - cr;
path = path.line_to(scoop_start_x, y + h);
path = path.quad_to(
scoop_start_x - cr * 0.55,
y + h, effective_start_x,
effective_start_y, );
path = path.cubic_to(
effective_start_x,
effective_start_y - K * effective_ry, center_x + K * effective_rx,
scoop_top_y, center_x,
scoop_top_y, );
path = path.cubic_to(
center_x - K * effective_rx,
scoop_top_y, effective_end_x,
effective_start_y - K * effective_ry, effective_end_x,
effective_start_y, );
path = path.quad_to(
scoop_end_x + cr * 0.55,
y + h, scoop_end_x,
y + h, );
path = path.line_to(bottom_end_x, y + h);
} else {
let rx = scoop.width / 2.0;
let ry = scoop.depth;
path = path.line_to(scoop_start_x, y + h);
path = path.cubic_to(
scoop_start_x,
y + h - K * ry, center_x + K * rx,
scoop_top_y, center_x,
scoop_top_y, );
path = path.cubic_to(
center_x - K * rx,
scoop_top_y, scoop_end_x,
y + h - K * ry, scoop_end_x,
y + h, );
path = path.line_to(bottom_end_x, y + h);
}
} else if let Some(bulge) = bottom_center_bulge {
let center_x = x + w / 2.0;
let bulge_start_x = center_x + bulge.width / 2.0; let bulge_end_x = center_x - bulge.width / 2.0; let bulge_bottom_y = y + h + bulge.height;
let cr = bulge.corner_radius.min(bulge.width / 4.0).max(0.0);
const K: f32 = 0.552_284_8;
if cr > 0.0 {
let effective_start_x = bulge_start_x - cr; let effective_end_x = bulge_end_x + cr; let effective_start_y = y + h + cr; let effective_rx = (effective_start_x - effective_end_x) / 2.0;
let effective_ry = bulge.height - cr;
path = path.line_to(bulge_start_x, y + h);
path = path.quad_to(
effective_start_x, y + h, effective_start_x,
effective_start_y, );
path = path.cubic_to(
effective_start_x,
effective_start_y + K * effective_ry, center_x + K * effective_rx,
bulge_bottom_y, center_x,
bulge_bottom_y, );
path = path.cubic_to(
center_x - K * effective_rx,
bulge_bottom_y, effective_end_x,
effective_start_y + K * effective_ry, effective_end_x,
effective_start_y, );
path = path.quad_to(
effective_end_x, y + h, bulge_end_x,
y + h, );
path = path.line_to(bottom_end_x, y + h);
} else {
let rx = bulge.width / 2.0;
path = path.line_to(bulge_start_x, y + h);
path = path.cubic_to(
bulge_start_x - K * rx, y + h, center_x + K * rx, bulge_bottom_y, center_x,
bulge_bottom_y, );
path = path.cubic_to(
center_x - K * rx, bulge_bottom_y, bulge_end_x + K * rx, y + h, bulge_end_x,
y + h, );
path = path.line_to(bottom_end_x, y + h);
}
} else if let Some(cut) = bottom_center_cut {
let center_x = x + w / 2.0;
let cut_start_x = center_x + cut.width / 2.0; let cut_end_x = center_x - cut.width / 2.0; let cut_point_y = y + h - cut.depth;
path = path.line_to(cut_start_x, y + h);
path = path.line_to(center_x, cut_point_y);
path = path.line_to(cut_end_x, y + h);
path = path.line_to(bottom_end_x, y + h);
} else if let Some(peak) = bottom_center_peak {
let center_x = x + w / 2.0;
let peak_start_x = center_x + peak.width / 2.0; let peak_end_x = center_x - peak.width / 2.0; let peak_point_y = y + h + peak.height;
path = path.line_to(peak_start_x, y + h);
path = path.line_to(center_x, peak_point_y);
path = path.line_to(peak_end_x, y + h);
path = path.line_to(bottom_end_x, y + h);
} else {
path = path.line_to(bottom_end_x, y + h);
}
match bl.style {
CornerStyle::Step => {
path = path.line_to(x - bl_r, y + h);
path = path.line_to(x - bl_r, y + h - bl_r);
path = path.line_to(x, y + h - bl_r);
}
CornerStyle::Concave => {
path = path.quad_to(x, y + h, x, y + h - bl_r);
}
CornerStyle::Convex if bl_r > 0.0 => {
path = path.quad_to(x, y + h, x, y + h - bl_r);
}
_ => {}
}
let left_end_y = match tl.style {
CornerStyle::Concave => y + tl_r, CornerStyle::Convex if tl_r > 0.0 => y + tl_r, _ => y,
};
path = path.line_to(x, left_end_y);
match tl.style {
CornerStyle::Step => {
path = path.line_to(x, y - tl_r);
path = path.line_to(x - tl_r, y - tl_r);
path = path.line_to(x - tl_r, y);
}
CornerStyle::Concave => {
path = path.quad_to(x, y, top_start_x, y);
}
CornerStyle::Convex if tl_r > 0.0 => {
path = path.quad_to(x, y, top_start_x, y);
}
_ => {}
}
path.close()
}
fn offset_path_by(path: &Path, amount: f32, bounds: Rect) -> Path {
use blinc_core::PathCommand;
if amount <= 0.0 {
return path.clone();
}
let cx = bounds.x() + bounds.width() / 2.0;
let cy = bounds.y() + bounds.height() / 2.0;
let scale_x = if bounds.width() > 0.0 {
(bounds.width() + amount * 2.0) / bounds.width()
} else {
1.0
};
let scale_y = if bounds.height() > 0.0 {
(bounds.height() + amount * 2.0) / bounds.height()
} else {
1.0
};
let transform_point = |x: f32, y: f32| -> (f32, f32) {
let new_x = cx + (x - cx) * scale_x;
let new_y = cy + (y - cy) * scale_y;
(new_x, new_y)
};
let mut new_path = Path::new();
for cmd in path.commands() {
match cmd {
PathCommand::MoveTo(p) => {
let (x, y) = transform_point(p.x, p.y);
new_path = new_path.move_to(x, y);
}
PathCommand::LineTo(p) => {
let (x, y) = transform_point(p.x, p.y);
new_path = new_path.line_to(x, y);
}
PathCommand::QuadTo { control, end } => {
let (cx, cy) = transform_point(control.x, control.y);
let (ex, ey) = transform_point(end.x, end.y);
new_path = new_path.quad_to(cx, cy, ex, ey);
}
PathCommand::CubicTo {
control1,
control2,
end,
} => {
let (c1x, c1y) = transform_point(control1.x, control1.y);
let (c2x, c2y) = transform_point(control2.x, control2.y);
let (ex, ey) = transform_point(end.x, end.y);
new_path = new_path.cubic_to(c1x, c1y, c2x, c2y, ex, ey);
}
PathCommand::ArcTo {
radii,
rotation,
large_arc,
sweep,
end,
} => {
let (ex, ey) = transform_point(end.x, end.y);
let new_radii = blinc_core::Vec2::new(radii.x * scale_x, radii.y * scale_y);
new_path = new_path.arc_to(new_radii, *rotation, *large_arc, *sweep, ex, ey);
}
PathCommand::Close => {
new_path = new_path.close();
}
}
}
new_path
}
fn draw_path_shadow(ctx: &mut dyn DrawContext, path: &Path, bounds: Rect, shadow: &Shadow) {
let blur = shadow.blur;
if blur <= 0.0 {
return;
}
let layers = ((blur / 1.5).ceil() as usize).clamp(8, 24);
let offset_x = shadow.offset_x;
let offset_y = shadow.offset_y;
for i in (0..layers).rev() {
let t = (i as f32 + 0.5) / layers as f32;
let layer_offset = (blur + shadow.spread) * t;
let gaussian = (-t * t * 3.0).exp();
let alpha = shadow.color.a * gaussian * 0.15;
if alpha < 0.005 {
continue;
}
let offset_path = offset_path_by(path, layer_offset, bounds);
ctx.push_transform(Transform::translate(offset_x, offset_y));
let shadow_color = Color::rgba(shadow.color.r, shadow.color.g, shadow.color.b, alpha);
ctx.fill_path(&offset_path, Brush::Solid(shadow_color));
ctx.pop_transform();
}
}
#[derive(Clone)]
pub struct NotchRenderData {
pub corners: CornersConfig,
pub background: Option<Brush>,
pub border_color: Option<Color>,
pub border_width: f32,
pub shadow: Option<Shadow>,
}
impl std::fmt::Debug for NotchRenderData {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("NotchRenderData")
.field("has_concave_curves", &self.corners.has_concave_curves())
.finish()
}
}
impl ElementBuilder for Notch {
fn build(&self, tree: &mut LayoutTree) -> LayoutNodeId {
let mut style = self.style.clone();
if let Some(scoop) = &self.top_center_scoop {
let current_top = match style.padding.top {
LengthPercentage::Length(v) => v,
LengthPercentage::Percent(_) => 0.0, };
style.padding.top = LengthPercentage::Length(current_top + scoop.depth);
}
if let Some(scoop) = &self.bottom_center_scoop {
let current_bottom = match style.padding.bottom {
LengthPercentage::Length(v) => v,
LengthPercentage::Percent(_) => 0.0, };
style.padding.bottom = LengthPercentage::Length(current_bottom + scoop.depth);
}
let node = tree.create_node(style);
for child in &self.children {
let child_node = child.build(tree);
tree.add_child(node, child_node);
}
node
}
fn render_props(&self) -> RenderProps {
let has_center_scoop =
self.top_center_scoop.is_some() || self.bottom_center_scoop.is_some();
let has_center_bulge =
self.top_center_bulge.is_some() || self.bottom_center_bulge.is_some();
let has_center_cut = self.top_center_cut.is_some() || self.bottom_center_cut.is_some();
let has_center_peak = self.top_center_peak.is_some() || self.bottom_center_peak.is_some();
let needs_custom = self.corners.needs_custom_rendering()
|| has_center_scoop
|| has_center_bulge
|| has_center_cut
|| has_center_peak;
if needs_custom {
RenderProps {
background: None, border_radius: CornerRadius::ZERO,
border_color: None,
border_width: 0.0,
border_sides: Default::default(),
layer: self.render_layer,
material: self.material.clone(),
shadow: None, transform: None,
opacity: self.opacity,
..Default::default()
}
} else {
RenderProps {
background: self.background.clone(),
border_radius: self.corners.to_corner_radius(),
border_color: self.border_color,
border_width: self.border_width,
border_sides: Default::default(),
layer: self.render_layer,
material: self.material.clone(),
shadow: self.shadow,
transform: None,
opacity: self.opacity,
..Default::default()
}
}
}
fn children_builders(&self) -> &[Box<dyn ElementBuilder>] {
&self.children
}
fn element_type_id(&self) -> ElementTypeId {
let has_center_scoop =
self.top_center_scoop.is_some() || self.bottom_center_scoop.is_some();
let has_center_bulge =
self.top_center_bulge.is_some() || self.bottom_center_bulge.is_some();
let has_center_cut = self.top_center_cut.is_some() || self.bottom_center_cut.is_some();
let has_center_peak = self.top_center_peak.is_some() || self.bottom_center_peak.is_some();
let needs_custom = self.corners.needs_custom_rendering()
|| has_center_scoop
|| has_center_bulge
|| has_center_cut
|| has_center_peak;
if needs_custom {
ElementTypeId::Canvas
} else {
ElementTypeId::Div
}
}
fn layout_style(&self) -> Option<&taffy::Style> {
Some(&self.style)
}
fn canvas_render_info(&self) -> Option<CanvasRenderFn> {
let has_center_scoop =
self.top_center_scoop.is_some() || self.bottom_center_scoop.is_some();
let has_center_bulge =
self.top_center_bulge.is_some() || self.bottom_center_bulge.is_some();
let has_center_cut = self.top_center_cut.is_some() || self.bottom_center_cut.is_some();
let has_center_peak = self.top_center_peak.is_some() || self.bottom_center_peak.is_some();
let needs_custom = self.corners.needs_custom_rendering()
|| has_center_scoop
|| has_center_bulge
|| has_center_cut
|| has_center_peak;
if !needs_custom {
return None;
}
let corners = self.corners;
let top_center_scoop = self.top_center_scoop;
let bottom_center_scoop = self.bottom_center_scoop;
let top_center_bulge = self.top_center_bulge;
let bottom_center_bulge = self.bottom_center_bulge;
let top_center_cut = self.top_center_cut;
let bottom_center_cut = self.bottom_center_cut;
let top_center_peak = self.top_center_peak;
let bottom_center_peak = self.bottom_center_peak;
let background = self.background.clone();
let border_color = self.border_color;
let border_width = self.border_width;
let shadow = self.shadow;
let opacity = self.opacity;
Some(Rc::new(
move |ctx: &mut dyn DrawContext, bounds: CanvasBounds| {
let corner_types_arr = [
if corners.top_left.is_concave() {
1.0
} else {
0.0
},
if corners.top_right.is_concave() {
1.0
} else {
0.0
},
if corners.bottom_right.is_concave() {
1.0
} else {
0.0
},
if corners.bottom_left.is_concave() {
1.0
} else {
0.0
},
];
let corner_radii_arr = [
corners.top_left.radius,
corners.top_right.radius,
corners.bottom_right.radius,
corners.bottom_left.radius,
];
let top_mod = if let Some(s) = &top_center_scoop {
[1.0, s.width, s.depth, s.corner_radius]
} else if let Some(b) = &top_center_bulge {
[2.0, b.width, b.height, b.corner_radius]
} else if let Some(c) = &top_center_cut {
[3.0, c.width, c.depth, 0.0]
} else if let Some(p) = &top_center_peak {
[4.0, p.width, p.height, 0.0]
} else {
[0.0; 4]
};
let bottom_mod = if let Some(s) = &bottom_center_scoop {
[1.0, s.width, s.depth, s.corner_radius]
} else if let Some(b) = &bottom_center_bulge {
[2.0, b.width, b.height, b.corner_radius]
} else if let Some(c) = &bottom_center_cut {
[3.0, c.width, c.depth, 0.0]
} else if let Some(p) = &bottom_center_peak {
[4.0, p.width, p.height, 0.0]
} else {
[0.0; 4]
};
let outer_rect = Rect::new(0.0, 0.0, bounds.width, bounds.height);
let fill_brush = if opacity < 1.0 {
background.clone().map(|b| apply_brush_opacity(b, opacity))
} else {
background.clone()
};
let border_arg = if let (Some(c), w) = (border_color, border_width) {
if w > 0.0 {
let stroked_color = if opacity < 1.0 {
c.with_alpha(c.a * opacity)
} else {
c
};
Some((w, stroked_color))
} else {
None
}
} else {
None
};
let brush = fill_brush.unwrap_or(Brush::Solid(Color::rgba(0.0, 0.0, 0.0, 0.0)));
ctx.fill_notch(
outer_rect,
corner_radii_arr,
corner_types_arr,
top_mod,
bottom_mod,
border_arg,
shadow,
brush,
);
},
))
}
fn event_handlers(&self) -> Option<&EventHandlers> {
if !self.event_handlers.is_empty() {
Some(&self.event_handlers)
} else {
None
}
}
}
pub fn notch() -> Notch {
Notch::new()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_notch_no_concave_curves() {
let s = notch().rounded(8.0);
assert!(!s.corners.has_concave_curves());
}
#[test]
fn test_notch_with_concave_curves() {
let s = notch().concave_top(24.0);
assert!(s.corners.has_concave_curves());
assert!(s.corners.top_left.is_concave());
assert!(s.corners.top_right.is_concave());
assert!(!s.corners.bottom_left.is_concave());
assert!(!s.corners.bottom_right.is_concave());
}
#[test]
fn test_notched_dropdown() {
let s = notch().concave_top(24.0).rounded_bottom(16.0);
assert!(s.corners.top_left.is_concave());
assert_eq!(s.corners.top_left.radius, 24.0);
assert!(s.corners.top_right.is_concave());
assert_eq!(s.corners.top_right.radius, 24.0);
assert!(!s.corners.bottom_left.is_concave());
assert_eq!(s.corners.bottom_left.radius, 16.0);
assert!(!s.corners.bottom_right.is_concave());
assert_eq!(s.corners.bottom_right.radius, 16.0);
}
#[test]
fn test_element_type_changes() {
let s1 = notch().rounded(8.0);
assert_eq!(s1.element_type_id(), ElementTypeId::Div);
let s2 = notch().concave_top(24.0);
assert_eq!(s2.element_type_id(), ElementTypeId::Canvas);
let s3 = notch().step_top(20.0);
assert_eq!(s3.element_type_id(), ElementTypeId::Canvas);
}
#[test]
fn test_signed_radius_positive() {
let s = notch().corner_top(16.0);
assert!(!s.corners.top_left.is_concave());
assert!(!s.corners.top_right.is_concave());
assert_eq!(s.corners.top_left.radius, 16.0);
assert_eq!(s.corners.top_right.radius, 16.0);
}
#[test]
fn test_signed_radius_negative() {
let s = notch().corner_top(-24.0);
assert!(s.corners.top_left.is_concave());
assert!(s.corners.top_right.is_concave());
assert_eq!(s.corners.top_left.radius, 24.0); assert_eq!(s.corners.top_right.radius, 24.0);
}
#[test]
fn test_signed_radius_zero() {
let s = notch().corner_top(0.0);
assert!(!s.corners.top_left.is_concave());
assert_eq!(s.corners.top_left.radius, 0.0);
}
#[test]
fn test_signed_radius_mixed() {
let s = notch().corner_top(-24.0).corner_bottom(16.0);
assert!(s.corners.top_left.is_concave());
assert!(s.corners.top_right.is_concave());
assert!(!s.corners.bottom_left.is_concave());
assert!(!s.corners.bottom_right.is_concave());
assert_eq!(s.corners.top_left.radius, 24.0);
assert_eq!(s.corners.bottom_left.radius, 16.0);
}
#[test]
fn test_signed_radius_individual_corners() {
let s = notch()
.corner_tl(-10.0)
.corner_tr(20.0)
.corner_br(-30.0)
.corner_bl(40.0);
assert!(s.corners.top_left.is_concave());
assert_eq!(s.corners.top_left.radius, 10.0);
assert!(!s.corners.top_right.is_concave());
assert_eq!(s.corners.top_right.radius, 20.0);
assert!(s.corners.bottom_right.is_concave());
assert_eq!(s.corners.bottom_right.radius, 30.0);
assert!(!s.corners.bottom_left.is_concave());
assert_eq!(s.corners.bottom_left.radius, 40.0);
}
#[test]
fn test_step_corners() {
let s = notch().step_top(20.0);
assert!(s.corners.top_left.is_step());
assert!(s.corners.top_right.is_step());
assert!(!s.corners.bottom_left.is_step());
assert!(!s.corners.bottom_right.is_step());
assert_eq!(s.corners.top_left.radius, 20.0);
}
#[test]
fn test_step_with_rounded_bottom() {
let s = notch().step_top(24.0).rounded_bottom(12.0);
assert!(s.corners.top_left.is_step());
assert!(s.corners.top_right.is_step());
assert!(!s.corners.bottom_left.is_step());
assert!(!s.corners.bottom_right.is_step());
assert_eq!(s.corners.top_left.radius, 24.0);
assert_eq!(s.corners.bottom_left.radius, 12.0);
}
#[test]
fn test_needs_custom_rendering() {
let s1 = notch().rounded(8.0);
assert!(!s1.corners.needs_custom_rendering());
let s2 = notch().concave_top(24.0);
assert!(s2.corners.needs_custom_rendering());
let s3 = notch().step_top(20.0);
assert!(s3.corners.needs_custom_rendering());
}
}