teksilo_widgets/styles/
recipe_split_button_style.rs1use teksilo_canvas::Size;
19use teksilo_core::build_context::BuildContext;
20use teksilo_core::signal::Signal;
21use teksilo_core::styles::density::density_min_size;
22use teksilo_core::styles::{SplitButtonStyle, SplitButtonStyleConfig};
23use teksilo_core::widget_id::WidgetId;
24use teksilo_tokens::{BorderRole, CornerRadius, InputTokens, SurfaceRole, TargetAxes};
25
26use crate::button::{ButtonVariant, InteractionState};
27use crate::primitives::{MinSize, RectWidget, ZStack};
28use crate::split_button::{
29 SPLIT_BUTTON_BORDER_WIDTH, SPLIT_BUTTON_CHEVRON_WIDTH, SPLIT_BUTTON_CORNER_RADIUS,
30 SPLIT_BUTTON_DIVIDER_WIDTH, SPLIT_BUTTON_HEIGHT, SPLIT_BUTTON_MIN_WIDTH, SplitButtonFamily,
31 classify,
32};
33
34#[derive(Debug, Default, Clone, Copy)]
39pub struct RecipeSplitButtonStyle;
40
41impl RecipeSplitButtonStyle {
42 pub fn for_tokens(_tokens: &InputTokens) -> Self {
51 Self
52 }
53}
54
55impl SplitButtonStyle for RecipeSplitButtonStyle {
56 fn make_body(&self, cfg: &SplitButtonStyleConfig, ctx: &mut BuildContext) -> WidgetId {
57 let variant = cfg.variant;
58
59 let state: Signal<InteractionState> = cfg
65 .is_pressed
66 .zip3(&cfg.is_focused, &cfg.is_hovered)
67 .map(|(pressed, focused, hovered)| {
68 if *pressed {
69 InteractionState::Pressed
70 } else if *focused {
71 InteractionState::Focused
72 } else if *hovered {
73 InteractionState::Hovered
74 } else {
75 InteractionState::Idle
76 }
77 });
78
79 let bg_role = state.map(move |s| resolve_bg_role(variant, *s));
80 let border_role = state.map(move |s| resolve_border_role(variant, *s));
81
82 let normal_bw = SPLIT_BUTTON_BORDER_WIDTH;
83 let focus_bw = ctx.theme().shape.focus_ring_width;
84 let border_width =
85 state.map(move |s| resolve_border_width(variant, *s, normal_bw, focus_bw));
86
87 let bg_id = ctx.add(
89 RectWidget::new()
90 .background(bg_role)
91 .border_color(border_role)
92 .border_width(border_width)
93 .corner_radius(CornerRadius::uniform(SPLIT_BUTTON_CORNER_RADIUS)),
94 );
95
96 let frame_id = ctx.add(ZStack::new().child(bg_id).child(cfg.content));
97
98 let total_min_width =
103 SPLIT_BUTTON_MIN_WIDTH + SPLIT_BUTTON_DIVIDER_WIDTH + SPLIT_BUTTON_CHEVRON_WIDTH;
104 let min = density_min_size(
105 Size::new(total_min_width, SPLIT_BUTTON_HEIGHT),
106 TargetAxes::HEIGHT,
107 &ctx.theme().input,
108 );
109 ctx.add(MinSize::new(min.width, min.height).child(frame_id))
110 }
111}
112
113fn resolve_bg_role(variant: ButtonVariant, state: InteractionState) -> SurfaceRole {
121 match (classify(variant), state) {
122 (SplitButtonFamily::FilledLike, InteractionState::Disabled) => SurfaceRole::AccentDisabled,
123 (SplitButtonFamily::FilledLike, InteractionState::Pressed) => SurfaceRole::AccentPressed,
124 (SplitButtonFamily::FilledLike, InteractionState::Hovered) => SurfaceRole::AccentHover,
125 (SplitButtonFamily::FilledLike, _) => SurfaceRole::Accent,
126
127 (SplitButtonFamily::PlainLike, InteractionState::Pressed) => SurfaceRole::Pressed,
128 (SplitButtonFamily::PlainLike, InteractionState::Hovered) => SurfaceRole::Hover,
129 (SplitButtonFamily::PlainLike, _) => SurfaceRole::Main,
130
131 (SplitButtonFamily::GhostLike, InteractionState::Pressed) => SurfaceRole::Pressed,
132 (SplitButtonFamily::GhostLike, InteractionState::Hovered) => SurfaceRole::Hover,
133 (SplitButtonFamily::GhostLike, _) => SurfaceRole::Transparent,
134 }
135}
136
137fn resolve_border_role(variant: ButtonVariant, state: InteractionState) -> BorderRole {
138 if state == InteractionState::Focused {
139 return BorderRole::Focused;
140 }
141 match classify(variant) {
142 SplitButtonFamily::FilledLike | SplitButtonFamily::GhostLike => BorderRole::Transparent,
143 SplitButtonFamily::PlainLike => match state {
144 InteractionState::Hovered | InteractionState::Pressed => BorderRole::Strong,
145 _ => BorderRole::Default,
146 },
147 }
148}
149
150fn resolve_border_width(
153 variant: ButtonVariant,
154 state: InteractionState,
155 normal_bw: f32,
156 focus_bw: f32,
157) -> f32 {
158 if state == InteractionState::Focused {
159 return focus_bw;
160 }
161 match classify(variant) {
162 SplitButtonFamily::FilledLike | SplitButtonFamily::GhostLike => 0.0,
163 SplitButtonFamily::PlainLike => normal_bw,
164 }
165}