teksilo_widgets/styles/
recipe_snackbar_style.rs1use teksilo_canvas::{Canvas, Rect, Size, SizeProposal};
16use teksilo_core::accessibility::AccessNodeBuilder;
17use teksilo_core::build_context::BuildContext;
18use teksilo_core::styles::density::spacing;
19use teksilo_core::styles::{SnackbarStyle, SnackbarStyleConfig};
20use teksilo_core::widget::{
21 LayoutContext, LayoutResponse, PaintContext, PendingChild, Widget, WidgetPlacement,
22};
23use teksilo_core::widget_id::WidgetId;
24use teksilo_tokens::{CornerRadius, InputTokens};
25
26pub const SNACKBAR_PADDING_HORIZONTAL: f32 = 12.0;
28pub const SNACKBAR_PADDING_VERTICAL: f32 = 10.0;
29pub const SNACKBAR_CORNER_RADIUS: f32 = 8.0;
30
31#[derive(Debug, Clone, Copy, PartialEq)]
33pub struct SnackbarRecipe {
34 pub padding_horizontal: f32,
35 pub padding_vertical: f32,
36 pub corner_radius: f32,
37}
38
39impl SnackbarRecipe {
40 pub fn for_tokens(tokens: &InputTokens) -> Self {
46 Self {
47 padding_horizontal: spacing(SNACKBAR_PADDING_HORIZONTAL, tokens),
48 padding_vertical: spacing(SNACKBAR_PADDING_VERTICAL, tokens),
49 corner_radius: SNACKBAR_CORNER_RADIUS,
50 }
51 }
52}
53
54impl Default for SnackbarRecipe {
55 fn default() -> Self {
56 Self::for_tokens(&InputTokens::default())
57 }
58}
59
60#[derive(Debug, Default, Clone, Copy)]
63pub struct RecipeSnackbarStyle {
64 pub recipe: SnackbarRecipe,
65}
66
67impl RecipeSnackbarStyle {
68 pub fn new(recipe: SnackbarRecipe) -> Self {
69 Self { recipe }
70 }
71
72 pub fn for_tokens(tokens: &InputTokens) -> Self {
79 Self {
80 recipe: SnackbarRecipe::for_tokens(tokens),
81 }
82 }
83}
84
85impl SnackbarStyle for RecipeSnackbarStyle {
86 fn make_body(&self, cfg: &SnackbarStyleConfig, ctx: &mut BuildContext) -> WidgetId {
87 ctx.add(SnackbarFrame {
88 child_id: None,
89 pending_child: Some(PendingChild::Id(cfg.content)),
90 recipe: self.recipe,
91 })
92 }
93}
94
95struct SnackbarFrame {
100 child_id: Option<WidgetId>,
101 pending_child: Option<PendingChild>,
102 recipe: SnackbarRecipe,
103}
104
105impl std::fmt::Debug for SnackbarFrame {
106 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
107 f.debug_struct("SnackbarFrame").finish()
108 }
109}
110
111impl Widget for SnackbarFrame {
112 fn build(&mut self, ctx: &mut BuildContext) -> Vec<WidgetId> {
113 if let Some(pending) = self.pending_child.take() {
114 self.child_id = Some(match pending {
115 PendingChild::Id(id) => id,
116 PendingChild::Deferred(w) => ctx.add_boxed(w),
117 });
118 }
119 self.child_id.into_iter().collect()
120 }
121
122 fn layout_response(&self, proposal: SizeProposal, ctx: &LayoutContext) -> LayoutResponse {
123 let inset_x = self.recipe.padding_horizontal * 2.0;
124 let inset_y = self.recipe.padding_vertical * 2.0;
125 let content = self
126 .child_id
127 .and_then(|id| {
128 ctx.child_size(
129 id,
130 SizeProposal {
131 width: proposal.width.map(|width| (width - inset_x).max(0.0)),
132 height: proposal.height.map(|height| (height - inset_y).max(0.0)),
133 },
134 )
135 })
136 .unwrap_or_else(|| proposal.resolve(220.0, 44.0));
137
138 Size::new(content.width + inset_x, content.height + inset_y).into()
139 }
140
141 fn place_children(
142 &self,
143 bounds: Rect,
144 _proposal: SizeProposal,
145 children: &mut [WidgetPlacement],
146 _ctx: &LayoutContext,
147 ) {
148 for child in children.iter_mut() {
149 child.origin = teksilo_canvas::Point::new(
150 bounds.x + self.recipe.padding_horizontal,
151 bounds.y + self.recipe.padding_vertical,
152 );
153 child.size = Size::new(
154 (bounds.width - self.recipe.padding_horizontal * 2.0).max(0.0),
155 (bounds.height - self.recipe.padding_vertical * 2.0).max(0.0),
156 );
157 }
158 }
159
160 fn paint(&self, bounds: Rect, canvas: &mut Canvas, ctx: &PaintContext) {
161 let radius = CornerRadius::uniform(self.recipe.corner_radius);
162 canvas.fill_rounded_rect(bounds, radius, ctx.theme.colors.tooltip_bg);
164 canvas.stroke_rounded_rect(
165 bounds,
166 radius,
167 ctx.theme.colors.tooltip_border,
168 ctx.theme.shape.border_width,
169 );
170 }
171
172 fn accessibility(&self, builder: &mut AccessNodeBuilder) {
173 builder.set_role(teksilo_core::accesskit::Role::GenericContainer);
188 builder.set_live(teksilo_core::accesskit::Live::Off);
189 }
190
191 fn children(&self) -> Vec<WidgetId> {
192 self.child_id.into_iter().collect()
193 }
194}