cranpose_liquid/widgets/
chip.rs1use crate::material::{Glass, LiquidModifierExt};
4use crate::motion::liquid_press_scale;
5use crate::theme::{liquid_colors, liquid_typography};
6use cranpose_animation::animateColorAsState;
7use cranpose_macros::composable;
8use cranpose_services::{default_haptics, HapticFeedback};
9use cranpose_ui::rememberMutableInteractionSource;
10use cranpose_ui::text::FontWeight;
11use cranpose_ui::text::{SpanStyle, TextStyle};
12use cranpose_ui::widgets::{Box, BoxSpec, Text};
13use cranpose_ui::Modifier;
14use cranpose_ui_graphics::{Brush, CornerRadii};
15use cranpose_ui_layout::Alignment;
16use std::cell::RefCell;
17use std::rc::Rc;
18
19use crate::motion::LiquidMotion;
20
21#[composable]
24#[allow(non_snake_case)]
25pub fn LiquidChip(
26 modifier: Modifier,
27 selected: bool,
28 on_click: impl Fn() + 'static,
29 label: impl Into<String>,
30) {
31 let colors = liquid_colors();
32 let typography = liquid_typography();
33 let interaction = rememberMutableInteractionSource();
34 let (pressed_modifier, _pressed, content_alpha) =
35 liquid_press_scale(Modifier::empty(), interaction.clone(), 1.18);
36
37 let label_color = animateColorAsState(
38 if selected {
39 colors.accent
40 } else {
41 colors.secondary_label
42 },
43 LiquidMotion::smooth(),
44 "chip-label",
45 );
46
47 let mut base = Modifier::empty();
48 if selected {
49 base = base.glass_effect(Glass::regular().adaptive_frost(colors.accent, 0.65));
50 } else {
51 let fill = colors.fill;
52 base = base.clip_to_bounds().draw_behind(move |scope| {
53 scope.draw_round_rect(Brush::solid(fill), CornerRadii::uniform(999.0));
54 });
55 }
56
57 let on_click = Rc::new(RefCell::new(on_click));
58 let base = base
59 .press_interaction_source(interaction)
60 .clickable(move |_point| {
61 default_haptics().perform(HapticFeedback::Selection);
62 (on_click.borrow_mut())();
63 })
64 .padding_symmetric(14.0, 7.0);
65
66 let label = label.into();
67 let chip = base.then(modifier);
68 Box(pressed_modifier, BoxSpec::default(), move || {
69 let label = label.clone();
70 let typography = typography.clone();
71 Box(
72 chip.clone(),
73 BoxSpec::default().content_alignment(Alignment::CENTER),
74 move || {
75 let label = label.clone();
76 let style = TextStyle {
77 span_style: SpanStyle {
78 color: Some(label_color.get()),
79 font_weight: Some(if selected {
80 FontWeight::SEMI_BOLD
81 } else {
82 FontWeight::MEDIUM
83 }),
84 ..typography.subheadline.span_style.clone()
85 },
86 ..typography.subheadline.clone()
87 };
88 let content_layer =
89 Modifier::empty().graphics_layer(move || cranpose_ui_graphics::GraphicsLayer {
90 alpha: content_alpha.get().clamp(0.0, 1.0),
91 ..Default::default()
92 });
93 Text(label, content_layer, style);
94 },
95 );
96 });
97}