use crate::material::{Glass, GlassDynamics, LiquidModifierExt, LiquidShape};
use crate::motion::liquid_press_scale;
use crate::theme::{liquid_colors, liquid_typography};
use cranpose_macros::composable;
use cranpose_services::{default_haptics, HapticFeedback};
use cranpose_ui::rememberMutableInteractionSource;
use cranpose_ui::text::TextStyle;
use cranpose_ui::widgets::{Box, BoxSpec, Text};
use cranpose_ui::{Modifier, Size};
use cranpose_ui_graphics::{Color, GraphicsLayer};
use cranpose_ui_layout::Alignment;
use std::cell::RefCell;
use std::rc::Rc;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum GlassButtonStyle {
#[default]
Glass,
Prominent,
Plain,
Destructive,
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct GlassButtonSpec {
pub style: GlassButtonStyle,
pub glass: Option<Glass>,
pub content_color: Option<Color>,
}
impl GlassButtonSpec {
pub fn glass() -> Self {
Self::default()
}
pub fn prominent() -> Self {
Self {
style: GlassButtonStyle::Prominent,
..Self::default()
}
}
pub fn plain() -> Self {
Self {
style: GlassButtonStyle::Plain,
..Self::default()
}
}
pub fn destructive() -> Self {
Self {
style: GlassButtonStyle::Destructive,
..Self::default()
}
}
pub fn with_glass(mut self, glass: Glass) -> Self {
self.glass = Some(glass);
self
}
pub fn with_content_color(mut self, color: Color) -> Self {
self.content_color = Some(color);
self
}
pub fn content_color(&self, colors: &crate::theme::LiquidColors) -> Color {
if let Some(color) = self.content_color {
return color;
}
match self.style {
GlassButtonStyle::Glass => colors.accent,
GlassButtonStyle::Prominent => colors.on_accent,
GlassButtonStyle::Plain => colors.accent,
GlassButtonStyle::Destructive => colors.destructive,
}
}
pub fn icon_color(&self, colors: &crate::theme::LiquidColors) -> Color {
if let Some(color) = self.content_color {
return color;
}
match self.style {
GlassButtonStyle::Glass | GlassButtonStyle::Plain => colors.label,
GlassButtonStyle::Prominent => colors.on_accent,
GlassButtonStyle::Destructive => colors.destructive,
}
}
fn resolve_material(&self, colors: &crate::theme::LiquidColors) -> Option<Glass> {
if let Some(glass) = &self.glass {
return Some(glass.clone());
}
match self.style {
GlassButtonStyle::Glass => Some(Glass::regular()),
GlassButtonStyle::Prominent => {
Some(Glass::regular().tint(colors.accent.with_alpha(0.75)))
}
GlassButtonStyle::Plain | GlassButtonStyle::Destructive => None,
}
}
}
#[composable]
#[allow(non_snake_case)]
pub fn GlassButton(
modifier: Modifier,
spec: GlassButtonSpec,
on_click: impl Fn() + 'static,
content: impl FnMut() + 'static,
) {
let colors = liquid_colors();
let interaction = rememberMutableInteractionSource();
let (pressed_modifier, pressed, content_alpha) =
liquid_press_scale(Modifier::empty(), interaction.clone(), 1.08);
let material = spec.resolve_material(&colors);
let mut base = pressed_modifier;
if let Some(glass) = material {
let pressed_for_glass = pressed;
base = base.glass_effect_with(glass, move || GlassDynamics {
tilt: (0.0, 0.0),
highlight_boost: if pressed_for_glass.get() { 0.85 } else { 0.0 },
..Default::default()
});
}
let on_click = Rc::new(RefCell::new(on_click));
let base = base
.press_interaction_source(interaction)
.clickable(move |_point| {
default_haptics().perform(HapticFeedback::ImpactLight);
(on_click.borrow_mut())();
})
.padding_symmetric(16.0, 10.0);
let content_layer = Modifier::empty().graphics_layer(move || GraphicsLayer {
alpha: content_alpha.get().clamp(0.0, 1.0),
..Default::default()
});
let content = Rc::new(RefCell::new(content));
Box(
base.then(modifier),
BoxSpec::default().content_alignment(Alignment::CENTER),
move || {
let content = Rc::clone(&content);
Box(
content_layer.clone(),
BoxSpec::default().content_alignment(Alignment::CENTER),
move || (content.borrow_mut())(),
);
},
);
}
#[composable]
#[allow(non_snake_case)]
pub fn GlassButtonLabel(text: impl Into<String>, spec: GlassButtonSpec) {
let typography = liquid_typography();
let color = spec.content_color(&liquid_colors());
let style = TextStyle {
span_style: cranpose_ui::text::SpanStyle {
color: Some(color),
..typography.headline.span_style.clone()
},
..typography.headline.clone()
};
Text(text.into(), Modifier::empty(), style);
}
#[composable]
#[allow(non_snake_case)]
pub fn GlassIconButton(
modifier: Modifier,
spec: GlassButtonSpec,
diameter: f32,
on_click: impl Fn() + 'static,
icon_path: &'static str,
) {
let colors = liquid_colors();
let interaction = rememberMutableInteractionSource();
let (pressed_modifier, pressed, content_alpha) =
liquid_press_scale(Modifier::empty(), interaction.clone(), 1.12);
let material = spec
.resolve_material(&colors)
.map(|glass| glass.shape(LiquidShape::Circle));
let mut base = pressed_modifier;
if let Some(glass) = material {
let pressed_for_glass = pressed;
base = base.glass_effect_with(glass, move || GlassDynamics {
tilt: (0.0, 0.0),
highlight_boost: if pressed_for_glass.get() { 0.85 } else { 0.0 },
..Default::default()
});
}
let icon_color = spec.icon_color(&colors);
let on_click = Rc::new(RefCell::new(on_click));
let base = base
.press_interaction_source(interaction)
.clickable(move |_point| {
default_haptics().perform(HapticFeedback::ImpactLight);
(on_click.borrow_mut())();
})
.size(Size::new(diameter, diameter));
let content_layer = Modifier::empty().graphics_layer(move || GraphicsLayer {
alpha: content_alpha.get().clamp(0.0, 1.0),
..Default::default()
});
Box(
base.then(modifier),
BoxSpec::default().content_alignment(Alignment::CENTER),
move || {
Box(
content_layer.clone(),
BoxSpec::default().content_alignment(Alignment::CENTER),
move || {
crate::icons::Icon(icon_path, diameter * 0.5, icon_color);
},
);
},
);
}