teksilo_widgets/styles/
recipe_dialog_style.rs1use teksilo_canvas::{Canvas, Rect, Size, SizeProposal};
19use teksilo_core::accessibility::AccessNodeBuilder;
20use teksilo_core::build_context::BuildContext;
21use teksilo_core::styles::{DialogStyle, DialogStyleConfig};
22use teksilo_core::widget::{
23 LayoutContext, LayoutResponse, PaintContext, PendingChild, Widget, WidgetPlacement,
24};
25use teksilo_core::widget_id::WidgetId;
26use teksilo_tokens::{CornerRadius, SurfaceRole};
27
28use crate::primitives::RectWidget;
29
30pub const DIALOG_CONTENT_PADDING: f32 = 24.0;
32pub const DIALOG_MIN_WIDTH: f32 = 280.0;
33pub const DIALOG_CORNER_RADIUS: f32 = 8.0;
34
35#[derive(Debug, Clone, Copy, PartialEq)]
38pub struct DialogRecipe {
39 pub content_padding: f32,
40 pub min_width: f32,
41 pub corner_radius: f32,
42}
43
44impl Default for DialogRecipe {
45 fn default() -> Self {
46 Self {
47 content_padding: DIALOG_CONTENT_PADDING,
48 min_width: DIALOG_MIN_WIDTH,
49 corner_radius: DIALOG_CORNER_RADIUS,
50 }
51 }
52}
53
54#[derive(Debug, Default, Clone, Copy)]
58pub struct RecipeDialogStyle {
59 pub recipe: DialogRecipe,
60}
61
62impl RecipeDialogStyle {
63 pub fn new(recipe: DialogRecipe) -> Self {
64 Self { recipe }
65 }
66}
67
68impl DialogStyle for RecipeDialogStyle {
69 fn make_panel(&self, cfg: &DialogStyleConfig, ctx: &mut BuildContext) -> WidgetId {
70 ctx.add(DialogPanel {
71 child_id: None,
72 pending_child: Some(PendingChild::Id(cfg.content)),
73 padding: cfg.padding_override.unwrap_or(self.recipe.content_padding),
74 min_width: cfg.min_width_override.unwrap_or(self.recipe.min_width),
75 recipe: self.recipe,
76 })
77 }
78
79 fn make_scrim(&self, ctx: &mut BuildContext) -> WidgetId {
80 ctx.add(RectWidget::new().background(SurfaceRole::Scrim))
81 }
82}
83
84struct DialogPanel {
90 child_id: Option<WidgetId>,
91 pending_child: Option<PendingChild>,
92 padding: f32,
93 min_width: f32,
94 recipe: DialogRecipe,
95}
96
97impl std::fmt::Debug for DialogPanel {
98 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99 f.debug_struct("DialogPanel")
100 .field("padding", &self.padding)
101 .field("min_width", &self.min_width)
102 .finish()
103 }
104}
105
106impl Widget for DialogPanel {
107 fn build(&mut self, ctx: &mut BuildContext) -> Vec<WidgetId> {
108 if let Some(pending) = self.pending_child.take() {
109 self.child_id = Some(match pending {
110 PendingChild::Id(id) => id,
111 PendingChild::Deferred(w) => ctx.add_boxed(w),
112 });
113 }
114 self.child_id.into_iter().collect()
115 }
116
117 fn layout_response(&self, proposal: SizeProposal, ctx: &LayoutContext) -> LayoutResponse {
118 let inset = self.padding * 2.0;
119 let content = self
120 .child_id
121 .and_then(|id| {
122 ctx.child_size(
123 id,
124 SizeProposal {
125 width: proposal.width.map(|width| (width - inset).max(0.0)),
126 height: proposal.height.map(|height| (height - inset).max(0.0)),
127 },
128 )
129 })
130 .unwrap_or_else(|| proposal.resolve(240.0, 120.0));
131
132 Size::new(
133 (content.width + inset).max(self.min_width),
134 content.height + inset,
135 )
136 .into()
137 }
138
139 fn place_children(
140 &self,
141 bounds: Rect,
142 _proposal: SizeProposal,
143 children: &mut [WidgetPlacement],
144 _ctx: &LayoutContext,
145 ) {
146 let pad = self.padding;
147 for child in children.iter_mut() {
148 child.origin = teksilo_canvas::Point::new(bounds.x + pad, bounds.y + pad);
149 child.size = Size::new(
150 (bounds.width - pad * 2.0).max(0.0),
151 (bounds.height - pad * 2.0).max(0.0),
152 );
153 }
154 }
155
156 fn paint(&self, bounds: Rect, canvas: &mut Canvas, ctx: &PaintContext) {
157 let radius = CornerRadius::uniform(self.recipe.corner_radius);
158 canvas.fill_rounded_rect(bounds, radius, ctx.theme.colors.surface_main);
159 canvas.stroke_rounded_rect(
160 bounds,
161 radius,
162 ctx.theme.colors.border_strong,
163 ctx.theme.shape.border_width,
164 );
165 }
166
167 fn accessibility(&self, builder: &mut AccessNodeBuilder) {
168 builder.set_hidden();
171 }
172
173 fn children(&self) -> Vec<WidgetId> {
174 self.child_id.into_iter().collect()
175 }
176}