teksilo_widgets/styles/
recipe_tooltip_style.rs1use teksilo_canvas::{Canvas, Rect, Size, SizeProposal};
16use teksilo_core::accessibility::AccessNodeBuilder;
17use teksilo_core::build_context::BuildContext;
18use teksilo_core::styles::{TooltipStyle, TooltipStyleConfig};
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 TOOLTIP_PADDING_HORIZONTAL: f32 = 10.0;
28pub const TOOLTIP_PADDING_VERTICAL: f32 = 6.0;
29pub const TOOLTIP_CORNER_RADIUS: f32 = 8.0;
30pub const TOOLTIP_MAX_WIDTH: f32 = 320.0;
31pub const TOOLTIP_SHADOW_DENSITY: f32 = 1.0;
33
34pub const COMPOSITE_TOOLTIP_PADDING_HORIZONTAL: f32 = 12.0;
35pub const COMPOSITE_TOOLTIP_PADDING_VERTICAL: f32 = 12.0;
36pub const COMPOSITE_TOOLTIP_CORNER_RADIUS: f32 = 8.0;
37pub const COMPOSITE_TOOLTIP_MAX_WIDTH: f32 = 480.0;
38pub const COMPOSITE_TOOLTIP_MAX_HEIGHT: f32 = 480.0;
39pub const COMPOSITE_TOOLTIP_SHADOW_DENSITY: f32 = 0.7;
41
42#[derive(Debug, Clone, Copy, PartialEq)]
48pub struct TooltipRecipe {
49 pub padding_horizontal: f32,
50 pub padding_vertical: f32,
51 pub corner_radius: f32,
52 pub max_width: f32,
53 pub shadow_density: f32,
55}
56
57impl Default for TooltipRecipe {
58 fn default() -> Self {
59 Self {
60 padding_horizontal: TOOLTIP_PADDING_HORIZONTAL,
61 padding_vertical: TOOLTIP_PADDING_VERTICAL,
62 corner_radius: TOOLTIP_CORNER_RADIUS,
63 max_width: TOOLTIP_MAX_WIDTH,
64 shadow_density: TOOLTIP_SHADOW_DENSITY,
65 }
66 }
67}
68
69#[derive(Debug, Default, Clone, Copy)]
72pub struct RecipeTooltipStyle {
73 pub recipe: TooltipRecipe,
74}
75
76impl RecipeTooltipStyle {
77 pub fn new(recipe: TooltipRecipe) -> Self {
78 Self { recipe }
79 }
80}
81
82impl TooltipStyle for RecipeTooltipStyle {
83 fn make_body(&self, cfg: &TooltipStyleConfig, ctx: &mut BuildContext) -> WidgetId {
84 let frame = TooltipFrame {
85 child_id: None,
86 pending_child: Some(PendingChild::Id(cfg.content)),
87 recipe: self.recipe,
88 };
89 ctx.add(frame)
90 }
91}
92
93struct TooltipFrame {
97 child_id: Option<WidgetId>,
98 pending_child: Option<PendingChild>,
99 recipe: TooltipRecipe,
100}
101
102impl std::fmt::Debug for TooltipFrame {
103 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
104 f.debug_struct("TooltipFrame").finish()
105 }
106}
107
108impl Widget for TooltipFrame {
109 fn build(&mut self, ctx: &mut BuildContext) -> Vec<WidgetId> {
110 if let Some(pending) = self.pending_child.take() {
111 self.child_id = Some(match pending {
112 PendingChild::Id(id) => id,
113 PendingChild::Deferred(w) => ctx.add_boxed(w),
114 });
115 }
116 self.child_id.into_iter().collect()
117 }
118
119 fn layout_response(&self, proposal: SizeProposal, ctx: &LayoutContext) -> LayoutResponse {
120 let pad_h = self.recipe.padding_horizontal;
121 let pad_v = self.recipe.padding_vertical;
122 let inset_w = pad_h * 2.0;
123 let inset_h = pad_v * 2.0;
124 if let Some(child_id) = self.child_id {
125 let bounded_w = proposal
129 .width
130 .map(|w| w.min(self.recipe.max_width))
131 .unwrap_or(self.recipe.max_width);
132 let inner = SizeProposal {
133 width: Some((bounded_w - inset_w).max(0.0)),
134 height: proposal.height.map(|h| (h - inset_h).max(0.0)),
135 };
136 if let Some(child_size) = ctx.child_size(child_id, inner) {
137 return Size::new(child_size.width + inset_w, child_size.height + inset_h).into();
138 }
139 }
140 proposal.resolve(inset_w, inset_h).into()
141 }
142
143 fn place_children(
144 &self,
145 bounds: Rect,
146 _proposal: SizeProposal,
147 children: &mut [WidgetPlacement],
148 _ctx: &LayoutContext,
149 ) {
150 let pad_h = self.recipe.padding_horizontal;
151 let pad_v = self.recipe.padding_vertical;
152 for child in children.iter_mut() {
153 child.origin = teksilo_canvas::Point::new(bounds.x + pad_h, bounds.y + pad_v);
154 child.size = Size::new(
155 (bounds.width - pad_h * 2.0).max(0.0),
156 (bounds.height - pad_v * 2.0).max(0.0),
157 );
158 }
159 }
160
161 fn paint(&self, bounds: Rect, canvas: &mut Canvas, ctx: &PaintContext) {
162 let radius = CornerRadius::uniform(self.recipe.corner_radius);
163 crate::tooltip::paint_tooltip_shadows(canvas, bounds, radius, ctx);
164 canvas.fill_rounded_rect(bounds, radius, ctx.theme.colors.tooltip_bg);
165 }
166
167 fn accessibility(&self, builder: &mut AccessNodeBuilder) {
168 builder.set_hidden();
170 }
171
172 fn children(&self) -> Vec<WidgetId> {
173 self.child_id.into_iter().collect()
174 }
175}