use crate::layout::style::FlexStyle;
use crate::layout::types::FlexAlign;
use crate::theme::{Theme, current};
use crate::view::node::ViewNode;
use crate::view::paint::{FontWeight, PaintStyle, TextStyle};
use crate::widget::layout::LayoutAttr;
use crate::widget::{BuildContext, Widget};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum CardVariant {
#[default]
Default,
Compact,
Selectable,
}
pub struct Card {
title: Option<String>,
header: Vec<Box<dyn Widget>>,
body: Vec<Box<dyn Widget>>,
footer: Vec<Box<dyn Widget>>,
layout: LayoutAttr,
variant: CardVariant,
}
impl Card {
pub fn new() -> Self {
Self {
title: None,
header: Vec::new(),
body: Vec::new(),
footer: Vec::new(),
layout: LayoutAttr::new().padding(16.0),
variant: CardVariant::Default,
}
}
pub fn title(mut self, t: impl Into<String>) -> Self {
self.title = Some(t.into());
self
}
pub fn header(mut self, w: impl Widget + 'static) -> Self {
self.header.push(Box::new(w));
self
}
pub fn body(mut self, w: impl Widget + 'static) -> Self {
self.body.push(Box::new(w));
self
}
pub fn footer(mut self, w: impl Widget + 'static) -> Self {
self.footer.push(Box::new(w));
self
}
pub fn padding(mut self, p: f32) -> Self {
self.layout = self.layout.padding(p);
self
}
pub fn layout(mut self, l: LayoutAttr) -> Self {
self.layout = l;
self
}
pub fn variant(mut self, v: CardVariant) -> Self {
self.variant = v;
self
}
pub fn flex_shrink(mut self, v: f32) -> Self {
self.layout = self.layout.flex_shrink(v);
self
}
}
impl Default for Card {
fn default() -> Self {
Self::new()
}
}
impl Widget for Card {
fn build(&self, ctx: &mut BuildContext) -> ViewNode {
let t = current();
let mut children: Vec<ViewNode> = Vec::new();
if self.title.is_some() || !self.header.is_empty() {
let mut header_children: Vec<ViewNode> = Vec::new();
if let Some(title) = &self.title {
header_children.push(ViewNode::Text {
content: title.clone(),
style: TextStyle {
font_size: t.font.lg,
color: t.text.regular_default,
font_weight: FontWeight::Bold,
..Default::default()
},
layout: FlexStyle::default(),
key: None,
listeners: vec![],
});
}
for (i, w) in self.header.iter().enumerate() {
header_children.push(ctx.child(i, w.as_ref()));
}
children.push(ViewNode::Div {
layout: FlexStyle::column().gap(8.0).padding_bottom(12.0),
paint: PaintStyle::new(),
children: header_children,
listeners: vec![],
key: Some("header".into()),
});
children.push(separator(t));
}
if !self.body.is_empty() {
let mut body_children: Vec<ViewNode> = Vec::new();
for (i, w) in self.body.iter().enumerate() {
body_children.push(ctx.child(1000 + i, w.as_ref()));
}
children.push(ViewNode::Div {
layout: FlexStyle::column()
.gap(8.0)
.padding_top(12.0)
.padding_bottom(if self.footer.is_empty() { 0.0 } else { 12.0 }),
paint: PaintStyle::new(),
children: body_children,
listeners: vec![],
key: Some("body".into()),
});
}
if !self.footer.is_empty() {
children.push(separator(t));
let mut footer_children: Vec<ViewNode> = Vec::new();
for (i, w) in self.footer.iter().enumerate() {
footer_children.push(ctx.child(2000 + i, w.as_ref()));
}
children.push(ViewNode::Div {
layout: FlexStyle::row()
.align_items(FlexAlign::Center)
.gap(8.0)
.padding_top(12.0),
paint: PaintStyle::new(),
children: footer_children,
listeners: vec![],
key: Some("footer".into()),
});
}
let pad = match self.variant {
CardVariant::Compact => 12.0,
_ => self.layout.padding,
};
let mut card_paint = PaintStyle::new()
.background(t.background.primary_default)
.border(1.0, t.border.default)
.radius(t.radius.medium)
.shadow(t.shadow.sm);
if self.variant == CardVariant::Selectable {
card_paint = card_paint.hover_background(t.background.secondary_default);
}
let mut layout = FlexStyle::column().padding_all(pad);
layout = self.layout.apply(layout);
ViewNode::Div {
layout,
paint: card_paint,
children,
listeners: vec![],
key: None,
}
}
}
fn separator(t: Theme) -> ViewNode {
ViewNode::Div {
layout: FlexStyle::default().height(1.0),
paint: PaintStyle::new().background(t.border.default),
children: vec![],
listeners: vec![],
key: None,
}
}