teksilo_widgets/styles/
recipe_tooltip_style.rs1use teksilo_canvas::{Canvas, Rect, Size, SizeProposal};
17use teksilo_core::accessibility::AccessNodeBuilder;
18use teksilo_core::build_context::BuildContext;
19use teksilo_core::styles::density::spacing;
20use teksilo_core::styles::{TooltipStyle, TooltipStyleConfig};
21use teksilo_core::widget::{
22 LayoutContext, LayoutResponse, PaintContext, PendingChild, Widget, WidgetPlacement,
23};
24use teksilo_core::widget_id::WidgetId;
25use teksilo_tokens::{CornerRadius, InputTokens};
26
27pub const TOOLTIP_PADDING_HORIZONTAL: f32 = 10.0;
30pub const TOOLTIP_PADDING_VERTICAL: f32 = 6.0;
31pub const TOOLTIP_CORNER_RADIUS: f32 = 8.0;
32pub const TOOLTIP_MAX_WIDTH: f32 = 320.0;
33pub const TOOLTIP_SHADOW_DENSITY: f32 = 1.0;
35
36pub const COMPOSITE_TOOLTIP_PADDING_HORIZONTAL: f32 = 12.0;
37pub const COMPOSITE_TOOLTIP_PADDING_VERTICAL: f32 = 12.0;
38pub const COMPOSITE_TOOLTIP_CORNER_RADIUS: f32 = 8.0;
39pub const COMPOSITE_TOOLTIP_MAX_WIDTH: f32 = 480.0;
40pub const COMPOSITE_TOOLTIP_MAX_HEIGHT: f32 = 480.0;
41pub const COMPOSITE_TOOLTIP_SHADOW_DENSITY: f32 = 0.7;
43
44#[derive(Debug, Clone, Copy, PartialEq)]
50pub struct TooltipRecipe {
51 pub padding_horizontal: f32,
52 pub padding_vertical: f32,
53 pub corner_radius: f32,
54 pub max_width: f32,
55 pub shadow_density: f32,
57}
58
59impl TooltipRecipe {
60 pub fn for_tokens(tokens: &InputTokens) -> Self {
66 Self {
67 padding_horizontal: spacing(TOOLTIP_PADDING_HORIZONTAL, tokens),
68 padding_vertical: spacing(TOOLTIP_PADDING_VERTICAL, tokens),
69 corner_radius: TOOLTIP_CORNER_RADIUS,
70 max_width: TOOLTIP_MAX_WIDTH,
71 shadow_density: TOOLTIP_SHADOW_DENSITY,
72 }
73 }
74}
75
76impl Default for TooltipRecipe {
77 fn default() -> Self {
78 Self::for_tokens(&InputTokens::default())
79 }
80}
81
82#[derive(Debug, Default, Clone, Copy)]
85pub struct RecipeTooltipStyle {
86 pub recipe: TooltipRecipe,
87}
88
89impl RecipeTooltipStyle {
90 pub fn new(recipe: TooltipRecipe) -> Self {
91 Self { recipe }
92 }
93
94 pub fn for_tokens(tokens: &InputTokens) -> Self {
101 Self {
102 recipe: TooltipRecipe::for_tokens(tokens),
103 }
104 }
105}
106
107impl TooltipStyle for RecipeTooltipStyle {
108 fn make_body(&self, cfg: &TooltipStyleConfig, ctx: &mut BuildContext) -> WidgetId {
109 let frame = TooltipFrame {
110 child_id: None,
111 pending_child: Some(PendingChild::Id(cfg.content)),
112 recipe: self.recipe,
113 };
114 ctx.add(frame)
115 }
116}
117
118struct TooltipFrame {
122 child_id: Option<WidgetId>,
123 pending_child: Option<PendingChild>,
124 recipe: TooltipRecipe,
125}
126
127impl std::fmt::Debug for TooltipFrame {
128 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
129 f.debug_struct("TooltipFrame").finish()
130 }
131}
132
133impl Widget for TooltipFrame {
134 fn build(&mut self, ctx: &mut BuildContext) -> Vec<WidgetId> {
135 if let Some(pending) = self.pending_child.take() {
136 self.child_id = Some(match pending {
137 PendingChild::Id(id) => id,
138 PendingChild::Deferred(w) => ctx.add_boxed(w),
139 });
140 }
141 self.child_id.into_iter().collect()
142 }
143
144 fn layout_response(&self, proposal: SizeProposal, ctx: &LayoutContext) -> LayoutResponse {
145 let pad_h = self.recipe.padding_horizontal;
146 let pad_v = self.recipe.padding_vertical;
147 let inset_w = pad_h * 2.0;
148 let inset_h = pad_v * 2.0;
149 if let Some(child_id) = self.child_id {
150 let bounded_w = proposal
154 .width
155 .map(|w| w.min(self.recipe.max_width))
156 .unwrap_or(self.recipe.max_width);
157 let inner = SizeProposal {
158 width: Some((bounded_w - inset_w).max(0.0)),
159 height: proposal.height.map(|h| (h - inset_h).max(0.0)),
160 };
161 if let Some(child_size) = ctx.child_size(child_id, inner) {
162 return Size::new(child_size.width + inset_w, child_size.height + inset_h).into();
163 }
164 }
165 proposal.resolve(inset_w, inset_h).into()
166 }
167
168 fn place_children(
169 &self,
170 bounds: Rect,
171 _proposal: SizeProposal,
172 children: &mut [WidgetPlacement],
173 _ctx: &LayoutContext,
174 ) {
175 let pad_h = self.recipe.padding_horizontal;
176 let pad_v = self.recipe.padding_vertical;
177 for child in children.iter_mut() {
178 child.origin = teksilo_canvas::Point::new(bounds.x + pad_h, bounds.y + pad_v);
179 child.size = Size::new(
180 (bounds.width - pad_h * 2.0).max(0.0),
181 (bounds.height - pad_v * 2.0).max(0.0),
182 );
183 }
184 }
185
186 fn paint(&self, bounds: Rect, canvas: &mut Canvas, ctx: &PaintContext) {
187 let radius = CornerRadius::uniform(self.recipe.corner_radius);
188 crate::tooltip::paint_tooltip_shadows(canvas, bounds, radius, ctx);
189 canvas.fill_rounded_rect(bounds, radius, ctx.theme.colors.tooltip_bg);
190 }
191
192 fn accessibility(&self, builder: &mut AccessNodeBuilder) {
193 builder.set_hidden();
195 }
196
197 fn children(&self) -> Vec<WidgetId> {
198 self.child_id.into_iter().collect()
199 }
200}