use teksilo_core::build_context::BuildContext;
use teksilo_core::color_prop::ColorProp;
use teksilo_core::signal::Signal;
use teksilo_core::styles::{IconButtonSize, IconButtonStyle, IconButtonStyleConfig};
use teksilo_core::widget_id::WidgetId;
use teksilo_tokens::CornerRadius;
use teksilo_tokens::{BorderRole, SurfaceRole};
use crate::primitives::{Center, FixedSize, RectWidget, ZStack};
pub const ICON_BUTTON_SIZE_COMPACT: f32 = 24.0;
pub const ICON_BUTTON_SIZE_DEFAULT: f32 = 24.0;
pub const ICON_BUTTON_SIZE_TOOLBAR: f32 = 30.0;
pub const ICON_BUTTON_SIZE_LARGE: f32 = 40.0;
pub const ICON_BUTTON_SIZE_HERO: f32 = 50.0;
pub const ICON_BUTTON_ICON_SIZE: f32 = 16.0;
pub const ICON_BUTTON_ICON_SIZE_TOOLBAR: f32 = 18.0;
pub const ICON_BUTTON_ICON_SIZE_LARGE: f32 = 24.0;
pub const ICON_BUTTON_ICON_SIZE_HERO: f32 = 32.0;
pub const ICON_BUTTON_CORNER_RADIUS: f32 = 8.0;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct IconButtonRecipe {
pub size_compact: f32,
pub size_default: f32,
pub size_toolbar: f32,
pub size_large: f32,
pub size_hero: f32,
pub icon_size: f32,
pub icon_size_toolbar: f32,
pub icon_size_large: f32,
pub icon_size_hero: f32,
pub corner_radius: f32,
}
impl Default for IconButtonRecipe {
fn default() -> Self {
Self {
size_compact: ICON_BUTTON_SIZE_COMPACT,
size_default: ICON_BUTTON_SIZE_DEFAULT,
size_toolbar: ICON_BUTTON_SIZE_TOOLBAR,
size_large: ICON_BUTTON_SIZE_LARGE,
size_hero: ICON_BUTTON_SIZE_HERO,
icon_size: ICON_BUTTON_ICON_SIZE,
icon_size_toolbar: ICON_BUTTON_ICON_SIZE_TOOLBAR,
icon_size_large: ICON_BUTTON_ICON_SIZE_LARGE,
icon_size_hero: ICON_BUTTON_ICON_SIZE_HERO,
corner_radius: ICON_BUTTON_CORNER_RADIUS,
}
}
}
#[derive(Debug, Default, Clone, Copy)]
pub struct RecipeIconButtonStyle {
pub recipe: IconButtonRecipe,
}
impl RecipeIconButtonStyle {
pub fn new(recipe: IconButtonRecipe) -> Self {
Self { recipe }
}
}
impl IconButtonStyle for RecipeIconButtonStyle {
fn make_body(&self, cfg: &IconButtonStyleConfig, ctx: &mut BuildContext) -> WidgetId {
let focus_ring_width = ctx.theme().shape.focus_ring_width;
let corner_radius = self.recipe.corner_radius;
let button_dim = resolve_size(cfg.size, &self.recipe);
let bg_role: ColorProp = match cfg.is_on.clone() {
Some(on) => {
bistate_bg_role(&cfg.is_pressed, &cfg.is_hovered, &cfg.is_disabled, &on).into()
}
None => plain_bg_role(&cfg.is_pressed, &cfg.is_hovered, &cfg.is_disabled).into(),
};
let border_role: ColorProp = cfg
.is_focused
.map(|focused| {
if *focused {
BorderRole::Focused
} else {
BorderRole::Transparent
}
})
.into();
let border_width = cfg
.is_focused
.map(move |focused| if *focused { focus_ring_width } else { 0.0 });
let bg_id = ctx.add(
RectWidget::new()
.background(bg_role)
.border_color(border_role)
.border_width(border_width)
.corner_radius(CornerRadius::uniform(corner_radius)),
);
let centered_id = ctx.add(Center::new().child_id(cfg.icon));
let zstack_id = ctx.add(ZStack::new().add_child(bg_id).add_child(centered_id));
ctx.add(
FixedSize::new()
.width(button_dim)
.height(button_dim)
.child_id(zstack_id),
)
}
}
fn plain_bg_role(
is_pressed: &Signal<bool>,
is_hovered: &Signal<bool>,
is_disabled: &Signal<bool>,
) -> Signal<SurfaceRole> {
is_pressed
.zip3(is_hovered, is_disabled)
.map(|(pressed, hovered, disabled)| {
if *disabled {
SurfaceRole::Transparent
} else if *pressed {
SurfaceRole::Pressed
} else if *hovered {
SurfaceRole::Hover
} else {
SurfaceRole::Transparent
}
})
}
fn bistate_bg_role(
is_pressed: &Signal<bool>,
is_hovered: &Signal<bool>,
is_disabled: &Signal<bool>,
is_on: &Signal<bool>,
) -> Signal<SurfaceRole> {
let combined = is_pressed.zip3(is_hovered, is_disabled);
combined
.zip(is_on)
.map(|((pressed, hovered, disabled), on)| {
if *disabled {
SurfaceRole::Transparent
} else if *on {
if *pressed {
SurfaceRole::Pressed
} else {
SurfaceRole::Selected
}
} else if *pressed {
SurfaceRole::Pressed
} else if *hovered {
SurfaceRole::Hover
} else {
SurfaceRole::Transparent
}
})
}
fn resolve_size(size: IconButtonSize, recipe: &IconButtonRecipe) -> f32 {
match size {
IconButtonSize::Compact => recipe.size_compact,
IconButtonSize::Default => recipe.size_default,
IconButtonSize::Toolbar => recipe.size_toolbar,
IconButtonSize::Large => recipe.size_large,
IconButtonSize::Hero => recipe.size_hero,
}
}