use anyhow::Result;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use skia_safe::{Canvas, PaintStyle, Rect};
use crate::engine::renderer::paint_from_hex;
use crate::layout::{layout_flex, layout_grid_with_config, Constraints, LayoutNode};
use crate::schema::{CardDisplay, LayerStyle};
use crate::traits::{
Border, Bordered, BorderedMut, Container, FlexConfig, FlexContainer,
FlexContainerMut, GridConfig, RenderContext, Rounded, RoundedMut, Shadow, Shadowed,
ShadowedMut, TimingConfig, Widget,
};
use super::flex::FlexSize;
use super::ChildComponent;
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct Card {
#[serde(default)]
pub children: Vec<ChildComponent>,
#[serde(default)]
pub size: Option<FlexSize>,
#[serde(flatten)]
pub timing: TimingConfig,
#[serde(default)]
pub style: LayerStyle,
}
crate::impl_traits!(Card {
Animatable => style,
Timed => timing,
Styled => style,
});
impl Container for Card {
fn children(&self) -> &[ChildComponent] {
&self.children
}
}
impl FlexContainer for Card {
fn flex_config(&self) -> &FlexConfig {
unreachable!("Use style directly for flex config")
}
}
impl FlexContainerMut for Card {
fn flex_config_mut(&mut self) -> &mut FlexConfig {
unreachable!("Use style directly for flex config")
}
}
impl Card {
fn grid_config_owned(&self) -> GridConfig {
GridConfig {
grid_template_columns: self.style.grid_template_columns.clone(),
grid_template_rows: self.style.grid_template_rows.clone(),
gap: self.style.gap_or(0.0),
}
}
}
impl Bordered for Card {
fn border(&self) -> Option<&Border> {
None }
}
impl BorderedMut for Card {
fn set_border(&mut self, _border: Option<Border>) {}
}
impl Rounded for Card {
fn corner_radius(&self) -> f32 {
self.style.border_radius_or(12.0)
}
}
impl RoundedMut for Card {
fn set_corner_radius(&mut self, radius: f32) {
self.style.border_radius = Some(radius);
}
}
impl Shadowed for Card {
fn shadow(&self) -> Option<&Shadow> {
None }
}
impl ShadowedMut for Card {
fn set_shadow(&mut self, _shadow: Option<Shadow>) {}
}
impl crate::traits::Backgrounded for Card {
fn background(&self) -> Option<&str> {
self.style.background.as_deref()
}
}
impl crate::traits::BackgroundedMut for Card {
fn set_background(&mut self, bg: Option<String>) {
self.style.background = bg;
}
}
impl crate::traits::Clipped for Card {
fn clip(&self) -> bool {
true
}
}
impl Widget for Card {
fn render(&self, canvas: &Canvas, layout: &LayoutNode, ctx: &RenderContext, props: &crate::engine::animator::AnimatedProperties) -> Result<()> {
let corner_radius = self.style.border_radius_or(12.0);
let rect = Rect::from_xywh(0.0, 0.0, layout.width, layout.height);
let rrect = skia_safe::RRect::new_rect_xy(rect, corner_radius, corner_radius);
let has_3d = props.rotate_x.abs() > 0.01 || props.rotate_y.abs() > 0.01 || props.perspective >= 0.0;
if !has_3d {
if let Some(ref shadow) = self.style.box_shadow {
let shadow_rect = Rect::from_xywh(
shadow.offset_x, shadow.offset_y,
layout.width, layout.height,
);
let shadow_rrect = skia_safe::RRect::new_rect_xy(
shadow_rect, corner_radius, corner_radius,
);
let mut shadow_paint = paint_from_hex(&shadow.color);
if shadow.blur > 0.0 {
shadow_paint.set_mask_filter(skia_safe::MaskFilter::blur(
skia_safe::BlurStyle::Normal,
shadow.blur / 2.0,
false,
));
}
canvas.draw_rrect(shadow_rrect, &shadow_paint);
}
}
if let Some(ref bg) = self.style.background {
let bg_paint = paint_from_hex(bg);
canvas.draw_rrect(rrect, &bg_paint);
}
let should_clip = self.style.background.is_some();
if should_clip {
canvas.save();
canvas.clip_rrect(rrect, skia_safe::ClipOp::Intersect, true);
}
crate::engine::render_v2::render_children_with_stagger(canvas, &self.children, layout, ctx, self.style.stagger)?;
if should_clip {
canvas.restore();
}
if let Some(ref border) = self.style.border {
let mut border_paint = paint_from_hex(&border.color);
border_paint.set_style(PaintStyle::Stroke);
border_paint.set_stroke_width(border.width);
canvas.draw_rrect(rrect, &border_paint);
}
if let Some(ref gb) = self.style.gradient_border {
super::draw_gradient_border(canvas, &rrect, gb);
}
Ok(())
}
fn measure(&self, constraints: &Constraints) -> (f32, f32) {
let layout = self.layout(constraints);
(layout.width, layout.height)
}
fn layout(&self, constraints: &Constraints) -> LayoutNode {
let c = super::flex::resolve_size_constraints(&self.size, constraints);
match self.style.display_or(CardDisplay::Flex) {
CardDisplay::Flex => layout_flex(self, &c),
CardDisplay::Grid => layout_grid_for_card(self, &c),
}
}
}
fn layout_grid_for_card(card: &Card, constraints: &Constraints) -> LayoutNode {
let grid_config = card.grid_config_owned();
layout_grid_with_config(card, &grid_config, constraints)
}