teksilo_widgets/styles/recipe_popover_style.rs
1// SPDX-License-Identifier: MPL-2.0
2// SPDX-FileCopyrightText: 2026 FernTech
3
4//! Default `PopoverStyle` impl driven by paint-recipe data.
5//!
6//! `RecipePopoverStyle` constructs the IntUI `PopoverSurface`
7//! (`crates/teksilo-widgets/src/popover.rs`) — an elevated panel with
8//! `surface_main` background, accent-aware shadow whose attached side
9//! is suppressed (so the panel reads as connected to its trigger),
10//! and an optional directional caret that points at the trigger.
11//!
12//! Apps wanting a different chrome (frosted-glass popover, brutalist
13//! flat panel, custom caret) write their own `impl PopoverStyle`
14//! block and install per-call (`Popover::style(...)`) or theme-wide
15//! (`theme.style_slots.popover = Some(Rc::new(MyPopover))`).
16
17use teksilo_canvas::EdgeInsets;
18use teksilo_core::build_context::BuildContext;
19use teksilo_core::styles::{PopoverStyle, PopoverStyleConfig, PopoverVariant};
20use teksilo_core::widget::PendingChild;
21use teksilo_core::widget_id::WidgetId;
22use teksilo_tokens::SurfaceRole;
23
24use crate::popover_surface::PopoverSurface;
25
26// IntUI design tokens for Popover. The recipe and surface own their
27// own dimensions.
28//
29// `POPOVER_PADDING` is the content inset for Default/Tooltip-flavoured
30// popovers. The `Menu` variant uses zero content padding so menu rows
31// (which carry their own row padding) reach the surface edge.
32pub const POPOVER_PADDING: f32 = 16.0;
33pub const POPOVER_CORNER_RADIUS: f32 = 8.0;
34pub const POPOVER_BORDER_WIDTH: f32 = 1.0;
35/// Corner radius for the `Menu`-flavoured popup surface (menu lists,
36/// combo-box dropdowns, search-field suggestion panels).
37pub const MENU_POPUP_CORNER_RADIUS: f32 = 8.0;
38/// 0..=1 multiplier on `shape.shadow_inner_sm.color.a` at paint time.
39pub const POPOVER_SHADOW_DENSITY: f32 = 0.5;
40
41/// Configurable dimensions for [`RecipePopoverStyle`].
42///
43/// [`Default`] fills every field from the corresponding `POPOVER_*` /
44/// `MENU_POPUP_*` constant, so existing code that relies on the defaults
45/// is unaffected.
46#[derive(Debug, Clone, Copy, PartialEq)]
47pub struct PopoverRecipe {
48 pub padding: f32,
49 pub corner_radius: f32,
50 pub border_width: f32,
51 pub menu_popup_corner_radius: f32,
52 pub shadow_density: f32,
53}
54
55impl Default for PopoverRecipe {
56 fn default() -> Self {
57 Self {
58 padding: POPOVER_PADDING,
59 corner_radius: POPOVER_CORNER_RADIUS,
60 border_width: POPOVER_BORDER_WIDTH,
61 menu_popup_corner_radius: MENU_POPUP_CORNER_RADIUS,
62 shadow_density: POPOVER_SHADOW_DENSITY,
63 }
64 }
65}
66
67/// Default `PopoverStyle` shipped with Teksilo. The `Default` and
68/// `Tooltip` variants produce the elevated `surface_main` panel with
69/// 16 px content padding; the `Menu` variant produces a `surface_raised`
70/// panel with zero content padding (so menu rows reach the edge) and a
71/// presentational a11y node (the caller owns the container role).
72#[derive(Debug, Default, Clone, Copy)]
73pub struct RecipePopoverStyle {
74 pub recipe: PopoverRecipe,
75}
76
77impl RecipePopoverStyle {
78 pub fn new(recipe: PopoverRecipe) -> Self {
79 Self { recipe }
80 }
81}
82
83impl PopoverStyle for RecipePopoverStyle {
84 fn make_body(&self, cfg: &PopoverStyleConfig, ctx: &mut BuildContext) -> WidgetId {
85 let (content_padding, background, corner_radius, presentational) = match cfg.variant {
86 PopoverVariant::Menu => (
87 EdgeInsets::ZERO,
88 SurfaceRole::Raised,
89 self.recipe.menu_popup_corner_radius,
90 true,
91 ),
92 PopoverVariant::Default | PopoverVariant::Tooltip => (
93 EdgeInsets::uniform(self.recipe.padding),
94 SurfaceRole::Main,
95 self.recipe.corner_radius,
96 false,
97 ),
98 };
99
100 ctx.add(PopoverSurface::new(
101 PendingChild::Id(cfg.content),
102 cfg.placement.clone(),
103 cfg.show_caret,
104 cfg.caret_size,
105 cfg.name.clone(),
106 content_padding,
107 background,
108 corner_radius,
109 presentational,
110 ))
111 }
112}