use crate::theme::{liquid_colors, liquid_typography};
use cranpose_animation::animateColorAsState;
use cranpose_macros::composable;
use cranpose_ui::rememberMutableInteractionSource;
use cranpose_ui::text::{SpanStyle, TextStyle};
use cranpose_ui::widgets::{Box, BoxSpec, Column, ColumnSpec, Text};
use cranpose_ui::Modifier;
use cranpose_ui_graphics::{Brush, Color, CornerRadii};
use std::cell::RefCell;
use std::rc::Rc;
use crate::motion::LiquidMotion;
const CARD_RADIUS: f32 = 20.0;
#[composable]
#[allow(non_snake_case)]
pub fn LiquidCard(modifier: Modifier, content: impl FnMut() + 'static) {
let colors = liquid_colors();
let surface = colors.surface;
let shadow_alpha = if colors.is_dark { 0.35 } else { 0.07 };
let base = Modifier::empty()
.drop_shadow(
cranpose_ui_graphics::LayerShape::Rounded(
cranpose_ui_graphics::RoundedCornerShape::uniform(CARD_RADIUS),
),
move |scope| {
scope.radius = 14.0;
scope.offset.y = 3.0;
scope.color = Color::BLACK.with_alpha(shadow_alpha);
},
)
.rounded_corners(CARD_RADIUS)
.draw_behind(move |scope| {
scope.draw_round_rect(Brush::solid(surface), CornerRadii::uniform(CARD_RADIUS));
});
Box(base.then(modifier), BoxSpec::default(), content);
}
#[composable]
#[allow(non_snake_case)]
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]
#[allow(non_snake_case)]
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 on_click = Rc::new(RefCell::new(on_click));
let base = Modifier::empty()
.fill_max_width()
.press_interaction_source(interaction)
.clickable(move |_point| {
(on_click.borrow_mut())();
})
.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 - 0.5,
width: (size.width - 16.0).max(0.0),
height: 0.5,
},
Brush::solid(separator_color),
);
}
})
.padding_symmetric(16.0, 12.0);
Box(base.then(modifier), BoxSpec::default(), content);
}