Skip to main content

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}