teksilo_widgets/styles/
recipe_split_button_style.rs1use teksilo_core::build_context::BuildContext;
19use teksilo_core::signal::Signal;
20use teksilo_core::styles::{SplitButtonStyle, SplitButtonStyleConfig};
21use teksilo_core::widget_id::WidgetId;
22use teksilo_tokens::{BorderRole, CornerRadius, SurfaceRole};
23
24use crate::button::{ButtonVariant, InteractionState};
25use crate::primitives::{MinSize, RectWidget, ZStack};
26use crate::split_button::{
27 SPLIT_BUTTON_BORDER_WIDTH, SPLIT_BUTTON_CHEVRON_WIDTH, SPLIT_BUTTON_CORNER_RADIUS,
28 SPLIT_BUTTON_DIVIDER_WIDTH, SPLIT_BUTTON_HEIGHT, SPLIT_BUTTON_MIN_WIDTH, SplitButtonFamily,
29 classify,
30};
31
32#[derive(Debug, Default, Clone, Copy)]
37pub struct RecipeSplitButtonStyle;
38
39impl SplitButtonStyle for RecipeSplitButtonStyle {
40 fn make_body(&self, cfg: &SplitButtonStyleConfig, ctx: &mut BuildContext) -> WidgetId {
41 let variant = cfg.variant;
42
43 let state: Signal<InteractionState> = cfg
49 .is_pressed
50 .zip3(&cfg.is_focused, &cfg.is_hovered)
51 .map(|(pressed, focused, hovered)| {
52 if *pressed {
53 InteractionState::Pressed
54 } else if *focused {
55 InteractionState::Focused
56 } else if *hovered {
57 InteractionState::Hovered
58 } else {
59 InteractionState::Idle
60 }
61 });
62
63 let bg_role = state.map(move |s| resolve_bg_role(variant, *s));
64 let border_role = state.map(move |s| resolve_border_role(variant, *s));
65
66 let normal_bw = SPLIT_BUTTON_BORDER_WIDTH;
67 let focus_bw = ctx.theme().shape.focus_ring_width;
68 let border_width =
69 state.map(move |s| resolve_border_width(variant, *s, normal_bw, focus_bw));
70
71 let bg_id = ctx.add(
73 RectWidget::new()
74 .background(bg_role)
75 .border_color(border_role)
76 .border_width(border_width)
77 .corner_radius(CornerRadius::uniform(SPLIT_BUTTON_CORNER_RADIUS)),
78 );
79
80 let frame_id = ctx.add(ZStack::new().add_child(bg_id).add_child(cfg.content));
81
82 let total_min_width =
84 SPLIT_BUTTON_MIN_WIDTH + SPLIT_BUTTON_DIVIDER_WIDTH + SPLIT_BUTTON_CHEVRON_WIDTH;
85 ctx.add(MinSize::new(total_min_width, SPLIT_BUTTON_HEIGHT).child_id(frame_id))
86 }
87}
88
89fn resolve_bg_role(variant: ButtonVariant, state: InteractionState) -> SurfaceRole {
97 match (classify(variant), state) {
98 (SplitButtonFamily::FilledLike, InteractionState::Disabled) => SurfaceRole::AccentDisabled,
99 (SplitButtonFamily::FilledLike, InteractionState::Pressed) => SurfaceRole::AccentPressed,
100 (SplitButtonFamily::FilledLike, InteractionState::Hovered) => SurfaceRole::AccentHover,
101 (SplitButtonFamily::FilledLike, _) => SurfaceRole::Accent,
102
103 (SplitButtonFamily::PlainLike, InteractionState::Pressed) => SurfaceRole::Pressed,
104 (SplitButtonFamily::PlainLike, InteractionState::Hovered) => SurfaceRole::Hover,
105 (SplitButtonFamily::PlainLike, _) => SurfaceRole::Main,
106
107 (SplitButtonFamily::GhostLike, InteractionState::Pressed) => SurfaceRole::Pressed,
108 (SplitButtonFamily::GhostLike, InteractionState::Hovered) => SurfaceRole::Hover,
109 (SplitButtonFamily::GhostLike, _) => SurfaceRole::Transparent,
110 }
111}
112
113fn resolve_border_role(variant: ButtonVariant, state: InteractionState) -> BorderRole {
114 if state == InteractionState::Focused {
115 return BorderRole::Focused;
116 }
117 match classify(variant) {
118 SplitButtonFamily::FilledLike | SplitButtonFamily::GhostLike => BorderRole::Transparent,
119 SplitButtonFamily::PlainLike => match state {
120 InteractionState::Hovered | InteractionState::Pressed => BorderRole::Strong,
121 _ => BorderRole::Default,
122 },
123 }
124}
125
126fn resolve_border_width(
129 variant: ButtonVariant,
130 state: InteractionState,
131 normal_bw: f32,
132 focus_bw: f32,
133) -> f32 {
134 if state == InteractionState::Focused {
135 return focus_bw;
136 }
137 match classify(variant) {
138 SplitButtonFamily::FilledLike | SplitButtonFamily::GhostLike => 0.0,
139 SplitButtonFamily::PlainLike => normal_bw,
140 }
141}