teksilo-widgets 0.9.0

Widget library for Teksilo — over a hundred widgets and layout primitives, from Button to TreeTableView.
Documentation
// SPDX-License-Identifier: MPL-2.0
// SPDX-FileCopyrightText: 2026 FernTech

//! Default `PopoverStyle` impl driven by paint-recipe data.
//!
//! `RecipePopoverStyle` constructs the IntUI `PopoverSurface`
//! (`crates/teksilo-widgets/src/popover.rs`) — an elevated panel with
//! `surface_main` background, accent-aware shadow whose attached side
//! is suppressed (so the panel reads as connected to its trigger),
//! and an optional directional caret that points at the trigger.
//!
//! Apps wanting a different chrome (frosted-glass popover, brutalist
//! flat panel, custom caret) write their own `impl PopoverStyle`
//! block and install per-call (`Popover::style(...)`) or theme-wide
//! (`theme.style_slots.popover = Some(Rc::new(MyPopover))`).

use teksilo_canvas::EdgeInsets;
use teksilo_core::build_context::BuildContext;
use teksilo_core::styles::{PopoverStyle, PopoverStyleConfig, PopoverVariant};
use teksilo_core::widget::PendingChild;
use teksilo_core::widget_id::WidgetId;
use teksilo_tokens::SurfaceRole;

use crate::popover_surface::PopoverSurface;

// IntUI design tokens for Popover. The recipe and surface own their
// own dimensions.
//
// `POPOVER_PADDING` is the content inset for Default/Tooltip-flavoured
// popovers. The `Menu` variant uses zero content padding so menu rows
// (which carry their own row padding) reach the surface edge.
pub const POPOVER_PADDING: f32 = 16.0;
pub const POPOVER_CORNER_RADIUS: f32 = 8.0;
pub const POPOVER_BORDER_WIDTH: f32 = 1.0;
/// Corner radius for the `Menu`-flavoured popup surface (menu lists,
/// combo-box dropdowns, search-field suggestion panels).
pub const MENU_POPUP_CORNER_RADIUS: f32 = 8.0;
/// 0..=1 multiplier on `shape.shadow_inner_sm.color.a` at paint time.
pub const POPOVER_SHADOW_DENSITY: f32 = 0.5;

/// Configurable dimensions for [`RecipePopoverStyle`].
///
/// [`Default`] fills every field from the corresponding `POPOVER_*` /
/// `MENU_POPUP_*` constant, so existing code that relies on the defaults
/// is unaffected.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct PopoverRecipe {
    pub padding: f32,
    pub corner_radius: f32,
    pub border_width: f32,
    pub menu_popup_corner_radius: f32,
    pub shadow_density: f32,
}

impl Default for PopoverRecipe {
    fn default() -> Self {
        Self {
            padding: POPOVER_PADDING,
            corner_radius: POPOVER_CORNER_RADIUS,
            border_width: POPOVER_BORDER_WIDTH,
            menu_popup_corner_radius: MENU_POPUP_CORNER_RADIUS,
            shadow_density: POPOVER_SHADOW_DENSITY,
        }
    }
}

/// Default `PopoverStyle` shipped with Teksilo. The `Default` and
/// `Tooltip` variants produce the elevated `surface_main` panel with
/// 16 px content padding; the `Menu` variant produces a `surface_raised`
/// panel with zero content padding (so menu rows reach the edge) and a
/// presentational a11y node (the caller owns the container role).
#[derive(Debug, Default, Clone, Copy)]
pub struct RecipePopoverStyle {
    pub recipe: PopoverRecipe,
}

impl RecipePopoverStyle {
    pub fn new(recipe: PopoverRecipe) -> Self {
        Self { recipe }
    }
}

impl PopoverStyle for RecipePopoverStyle {
    fn make_body(&self, cfg: &PopoverStyleConfig, ctx: &mut BuildContext) -> WidgetId {
        let (content_padding, background, corner_radius, presentational) = match cfg.variant {
            PopoverVariant::Menu => (
                EdgeInsets::ZERO,
                SurfaceRole::Raised,
                self.recipe.menu_popup_corner_radius,
                true,
            ),
            PopoverVariant::Default | PopoverVariant::Tooltip => (
                EdgeInsets::uniform(self.recipe.padding),
                SurfaceRole::Main,
                self.recipe.corner_radius,
                false,
            ),
        };

        ctx.add(PopoverSurface::new(
            PendingChild::Id(cfg.content),
            cfg.placement.clone(),
            cfg.show_caret,
            cfg.caret_size,
            cfg.name.clone(),
            content_padding,
            background,
            corner_radius,
            presentational,
        ))
    }
}