use teksilo_canvas::{Canvas, Rect, Size, SizeProposal};
use teksilo_core::accessibility::AccessNodeBuilder;
use teksilo_core::build_context::BuildContext;
use teksilo_core::styles::{TooltipStyle, TooltipStyleConfig};
use teksilo_core::widget::{
LayoutContext, LayoutResponse, PaintContext, PendingChild, Widget, WidgetPlacement,
};
use teksilo_core::widget_id::WidgetId;
use teksilo_tokens::CornerRadius;
pub const TOOLTIP_PADDING_HORIZONTAL: f32 = 10.0;
pub const TOOLTIP_PADDING_VERTICAL: f32 = 6.0;
pub const TOOLTIP_CORNER_RADIUS: f32 = 8.0;
pub const TOOLTIP_MAX_WIDTH: f32 = 320.0;
pub const TOOLTIP_SHADOW_DENSITY: f32 = 1.0;
pub const COMPOSITE_TOOLTIP_PADDING_HORIZONTAL: f32 = 12.0;
pub const COMPOSITE_TOOLTIP_PADDING_VERTICAL: f32 = 12.0;
pub const COMPOSITE_TOOLTIP_CORNER_RADIUS: f32 = 8.0;
pub const COMPOSITE_TOOLTIP_MAX_WIDTH: f32 = 480.0;
pub const COMPOSITE_TOOLTIP_MAX_HEIGHT: f32 = 480.0;
pub const COMPOSITE_TOOLTIP_SHADOW_DENSITY: f32 = 0.7;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct TooltipRecipe {
pub padding_horizontal: f32,
pub padding_vertical: f32,
pub corner_radius: f32,
pub max_width: f32,
pub shadow_density: f32,
}
impl Default for TooltipRecipe {
fn default() -> Self {
Self {
padding_horizontal: TOOLTIP_PADDING_HORIZONTAL,
padding_vertical: TOOLTIP_PADDING_VERTICAL,
corner_radius: TOOLTIP_CORNER_RADIUS,
max_width: TOOLTIP_MAX_WIDTH,
shadow_density: TOOLTIP_SHADOW_DENSITY,
}
}
}
#[derive(Debug, Default, Clone, Copy)]
pub struct RecipeTooltipStyle {
pub recipe: TooltipRecipe,
}
impl RecipeTooltipStyle {
pub fn new(recipe: TooltipRecipe) -> Self {
Self { recipe }
}
}
impl TooltipStyle for RecipeTooltipStyle {
fn make_body(&self, cfg: &TooltipStyleConfig, ctx: &mut BuildContext) -> WidgetId {
let frame = TooltipFrame {
child_id: None,
pending_child: Some(PendingChild::Id(cfg.content)),
recipe: self.recipe,
};
ctx.add(frame)
}
}
struct TooltipFrame {
child_id: Option<WidgetId>,
pending_child: Option<PendingChild>,
recipe: TooltipRecipe,
}
impl std::fmt::Debug for TooltipFrame {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TooltipFrame").finish()
}
}
impl Widget for TooltipFrame {
fn build(&mut self, ctx: &mut BuildContext) -> Vec<WidgetId> {
if let Some(pending) = self.pending_child.take() {
self.child_id = Some(match pending {
PendingChild::Id(id) => id,
PendingChild::Deferred(w) => ctx.add_boxed(w),
});
}
self.child_id.into_iter().collect()
}
fn layout_response(&self, proposal: SizeProposal, ctx: &LayoutContext) -> LayoutResponse {
let pad_h = self.recipe.padding_horizontal;
let pad_v = self.recipe.padding_vertical;
let inset_w = pad_h * 2.0;
let inset_h = pad_v * 2.0;
if let Some(child_id) = self.child_id {
let bounded_w = proposal
.width
.map(|w| w.min(self.recipe.max_width))
.unwrap_or(self.recipe.max_width);
let inner = SizeProposal {
width: Some((bounded_w - inset_w).max(0.0)),
height: proposal.height.map(|h| (h - inset_h).max(0.0)),
};
if let Some(child_size) = ctx.child_size(child_id, inner) {
return Size::new(child_size.width + inset_w, child_size.height + inset_h).into();
}
}
proposal.resolve(inset_w, inset_h).into()
}
fn place_children(
&self,
bounds: Rect,
_proposal: SizeProposal,
children: &mut [WidgetPlacement],
_ctx: &LayoutContext,
) {
let pad_h = self.recipe.padding_horizontal;
let pad_v = self.recipe.padding_vertical;
for child in children.iter_mut() {
child.origin = teksilo_canvas::Point::new(bounds.x + pad_h, bounds.y + pad_v);
child.size = Size::new(
(bounds.width - pad_h * 2.0).max(0.0),
(bounds.height - pad_v * 2.0).max(0.0),
);
}
}
fn paint(&self, bounds: Rect, canvas: &mut Canvas, ctx: &PaintContext) {
let radius = CornerRadius::uniform(self.recipe.corner_radius);
crate::tooltip::paint_tooltip_shadows(canvas, bounds, radius, ctx);
canvas.fill_rounded_rect(bounds, radius, ctx.theme.colors.tooltip_bg);
}
fn accessibility(&self, builder: &mut AccessNodeBuilder) {
builder.set_hidden();
}
fn children(&self) -> Vec<WidgetId> {
self.child_id.into_iter().collect()
}
}