use std::{cell::RefCell, rc::Rc};
use cranpose_animation::animateColorAsState;
use cranpose_macros::composable;
use cranpose_ui::{
Modifier, rememberMutableInteractionSource,
text::{SpanStyle, TextStyle},
widgets::{Box, BoxSpec, Column, ColumnSpec, Text},
};
use cranpose_ui_graphics::{Brush, CornerRadii};
use crate::{
material::{LiquidModifierExt, LiquidShape},
motion::LiquidMotion,
theme::{liquid_colors, liquid_typography},
};
const CARD_RADIUS: f32 = 20.0;
#[composable]
pub fn Surface(modifier: Modifier, content: impl FnMut() + 'static) {
let color = liquid_colors().surface;
Box(
modifier.draw_behind(move |scope| {
scope.draw_round_rect(Brush::solid(color), CornerRadii::uniform(12.0));
}),
BoxSpec::default(),
content,
);
}
#[composable]
pub fn Card(modifier: Modifier, content: impl FnMut() + 'static) {
LiquidCard(modifier, content);
}
#[composable]
pub fn LiquidCard(modifier: Modifier, content: impl FnMut() + 'static) {
let colors = liquid_colors();
let base = Modifier::empty().glass_effect(
super::glass_surface::card_surface_material(colors.label)
.shape(LiquidShape::RoundedRect(CARD_RADIUS)),
);
Box(modifier.then(base), BoxSpec::default(), content);
}
#[composable]
pub fn LiquidListSection(
modifier: Modifier,
header: impl Into<String>,
content: impl FnMut() + 'static,
) {
let colors = liquid_colors();
let typography = liquid_typography();
let header = header.into();
let content = Rc::new(RefCell::new(content));
Column(modifier, ColumnSpec::default(), move || {
if !header.is_empty() {
let style = TextStyle {
span_style: SpanStyle {
color: Some(colors.secondary_label),
..typography.footnote.span_style.clone()
},
..typography.footnote.clone()
};
Text(
header.to_uppercase(),
Modifier::empty().padding_each(20.0, 0.0, 20.0, 6.0),
style,
);
}
let content = Rc::clone(&content);
LiquidCard(Modifier::empty().fill_max_width(), move || {
let content = Rc::clone(&content);
Column(
Modifier::empty().fill_max_width(),
ColumnSpec::default(),
move || {
(content.borrow_mut())();
},
);
});
});
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct LiquidListRowSpec {
pub separator: bool,
}
impl LiquidListRowSpec {
pub fn with_separator(mut self, separator: bool) -> Self {
self.separator = separator;
self
}
}
#[composable]
pub fn LiquidListRow(
modifier: Modifier,
spec: LiquidListRowSpec,
on_click: impl Fn() + 'static,
content: impl FnMut() + 'static,
) {
let colors = liquid_colors();
let interaction = rememberMutableInteractionSource();
let pressed = interaction.collectIsPressedAsState();
let wash = animateColorAsState(
if pressed.get() {
colors.surface_pressed
} else {
colors.surface_pressed.with_alpha(0.0)
},
LiquidMotion::snappy(),
"row-wash",
);
let separator = spec.separator;
let separator_color = colors.separator;
let base = Modifier::empty()
.fill_max_width()
.press_interaction_source(interaction)
.clickable(move |_point| {
on_click();
})
.draw_behind(move |scope| {
let size = scope.size();
scope.draw_rect(Brush::solid(wash.get()));
if separator {
scope.draw_rect_at(
cranpose_ui_graphics::Rect {
x: 16.0,
y: size.height - 1.0,
width: (size.width - 32.0).max(0.0),
height: 1.0,
},
Brush::solid(separator_color),
);
}
})
.padding_symmetric(16.0, 12.0);
Box(modifier.then(base), BoxSpec::default(), content);
}
#[cfg(test)]
#[path = "tests/card_tests.rs"]
mod tests;