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::{SnackbarStyle, SnackbarStyleConfig};
19use teksilo_core::widget::{
20 LayoutContext, LayoutResponse, PaintContext, PendingChild, Widget, WidgetPlacement,
21};
22use teksilo_core::widget_id::WidgetId;
23use teksilo_tokens::CornerRadius;
24
25pub const SNACKBAR_PADDING_HORIZONTAL: f32 = 12.0;
27pub const SNACKBAR_PADDING_VERTICAL: f32 = 10.0;
28pub const SNACKBAR_CORNER_RADIUS: f32 = 8.0;
29
30#[derive(Debug, Clone, Copy, PartialEq)]
32pub struct SnackbarRecipe {
33 pub padding_horizontal: f32,
34 pub padding_vertical: f32,
35 pub corner_radius: f32,
36}
37
38impl Default for SnackbarRecipe {
39 fn default() -> Self {
40 Self {
41 padding_horizontal: SNACKBAR_PADDING_HORIZONTAL,
42 padding_vertical: SNACKBAR_PADDING_VERTICAL,
43 corner_radius: SNACKBAR_CORNER_RADIUS,
44 }
45 }
46}
47
48#[derive(Debug, Default, Clone, Copy)]
51pub struct RecipeSnackbarStyle {
52 pub recipe: SnackbarRecipe,
53}
54
55impl RecipeSnackbarStyle {
56 pub fn new(recipe: SnackbarRecipe) -> Self {
57 Self { recipe }
58 }
59}
60
61impl SnackbarStyle for RecipeSnackbarStyle {
62 fn make_body(&self, cfg: &SnackbarStyleConfig, ctx: &mut BuildContext) -> WidgetId {
63 ctx.add(SnackbarFrame {
64 child_id: None,
65 pending_child: Some(PendingChild::Id(cfg.content)),
66 recipe: self.recipe,
67 })
68 }
69}
70
71struct SnackbarFrame {
76 child_id: Option<WidgetId>,
77 pending_child: Option<PendingChild>,
78 recipe: SnackbarRecipe,
79}
80
81impl std::fmt::Debug for SnackbarFrame {
82 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83 f.debug_struct("SnackbarFrame").finish()
84 }
85}
86
87impl Widget for SnackbarFrame {
88 fn build(&mut self, ctx: &mut BuildContext) -> Vec<WidgetId> {
89 if let Some(pending) = self.pending_child.take() {
90 self.child_id = Some(match pending {
91 PendingChild::Id(id) => id,
92 PendingChild::Deferred(w) => ctx.add_boxed(w),
93 });
94 }
95 self.child_id.into_iter().collect()
96 }
97
98 fn layout_response(&self, proposal: SizeProposal, ctx: &LayoutContext) -> LayoutResponse {
99 let inset_x = self.recipe.padding_horizontal * 2.0;
100 let inset_y = self.recipe.padding_vertical * 2.0;
101 let content = self
102 .child_id
103 .and_then(|id| {
104 ctx.child_size(
105 id,
106 SizeProposal {
107 width: proposal.width.map(|width| (width - inset_x).max(0.0)),
108 height: proposal.height.map(|height| (height - inset_y).max(0.0)),
109 },
110 )
111 })
112 .unwrap_or_else(|| proposal.resolve(220.0, 44.0));
113
114 Size::new(content.width + inset_x, content.height + inset_y).into()
115 }
116
117 fn place_children(
118 &self,
119 bounds: Rect,
120 _proposal: SizeProposal,
121 children: &mut [WidgetPlacement],
122 _ctx: &LayoutContext,
123 ) {
124 for child in children.iter_mut() {
125 child.origin = teksilo_canvas::Point::new(
126 bounds.x + self.recipe.padding_horizontal,
127 bounds.y + self.recipe.padding_vertical,
128 );
129 child.size = Size::new(
130 (bounds.width - self.recipe.padding_horizontal * 2.0).max(0.0),
131 (bounds.height - self.recipe.padding_vertical * 2.0).max(0.0),
132 );
133 }
134 }
135
136 fn paint(&self, bounds: Rect, canvas: &mut Canvas, ctx: &PaintContext) {
137 let radius = CornerRadius::uniform(self.recipe.corner_radius);
138 canvas.fill_rounded_rect(bounds, radius, ctx.theme.colors.tooltip_bg);
140 canvas.stroke_rounded_rect(
141 bounds,
142 radius,
143 ctx.theme.colors.tooltip_border,
144 ctx.theme.shape.border_width,
145 );
146 }
147
148 fn accessibility(&self, builder: &mut AccessNodeBuilder) {
149 builder.set_hidden();
152 }
153
154 fn children(&self) -> Vec<WidgetId> {
155 self.child_id.into_iter().collect()
156 }
157}