use teksilo_canvas::{Canvas, Rect, Size, SizeProposal};
use teksilo_core::accessibility::AccessNodeBuilder;
use teksilo_core::build_context::BuildContext;
use teksilo_core::styles::{DialogStyle, DialogStyleConfig};
use teksilo_core::widget::{
LayoutContext, LayoutResponse, PaintContext, PendingChild, Widget, WidgetPlacement,
};
use teksilo_core::widget_id::WidgetId;
use teksilo_tokens::{CornerRadius, SurfaceRole};
use crate::primitives::RectWidget;
pub const DIALOG_CONTENT_PADDING: f32 = 24.0;
pub const DIALOG_MIN_WIDTH: f32 = 280.0;
pub const DIALOG_CORNER_RADIUS: f32 = 8.0;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct DialogRecipe {
pub content_padding: f32,
pub min_width: f32,
pub corner_radius: f32,
}
impl Default for DialogRecipe {
fn default() -> Self {
Self {
content_padding: DIALOG_CONTENT_PADDING,
min_width: DIALOG_MIN_WIDTH,
corner_radius: DIALOG_CORNER_RADIUS,
}
}
}
#[derive(Debug, Default, Clone, Copy)]
pub struct RecipeDialogStyle {
pub recipe: DialogRecipe,
}
impl RecipeDialogStyle {
pub fn new(recipe: DialogRecipe) -> Self {
Self { recipe }
}
}
impl DialogStyle for RecipeDialogStyle {
fn make_panel(&self, cfg: &DialogStyleConfig, ctx: &mut BuildContext) -> WidgetId {
ctx.add(DialogPanel {
child_id: None,
pending_child: Some(PendingChild::Id(cfg.content)),
padding: cfg.padding_override.unwrap_or(self.recipe.content_padding),
min_width: cfg.min_width_override.unwrap_or(self.recipe.min_width),
recipe: self.recipe,
})
}
fn make_scrim(&self, ctx: &mut BuildContext) -> WidgetId {
ctx.add(RectWidget::new().background(SurfaceRole::Scrim))
}
}
struct DialogPanel {
child_id: Option<WidgetId>,
pending_child: Option<PendingChild>,
padding: f32,
min_width: f32,
recipe: DialogRecipe,
}
impl std::fmt::Debug for DialogPanel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DialogPanel")
.field("padding", &self.padding)
.field("min_width", &self.min_width)
.finish()
}
}
impl Widget for DialogPanel {
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 inset = self.padding * 2.0;
let content = self
.child_id
.and_then(|id| {
ctx.child_size(
id,
SizeProposal {
width: proposal.width.map(|width| (width - inset).max(0.0)),
height: proposal.height.map(|height| (height - inset).max(0.0)),
},
)
})
.unwrap_or_else(|| proposal.resolve(240.0, 120.0));
Size::new(
(content.width + inset).max(self.min_width),
content.height + inset,
)
.into()
}
fn place_children(
&self,
bounds: Rect,
_proposal: SizeProposal,
children: &mut [WidgetPlacement],
_ctx: &LayoutContext,
) {
let pad = self.padding;
for child in children.iter_mut() {
child.origin = teksilo_canvas::Point::new(bounds.x + pad, bounds.y + pad);
child.size = Size::new(
(bounds.width - pad * 2.0).max(0.0),
(bounds.height - pad * 2.0).max(0.0),
);
}
}
fn paint(&self, bounds: Rect, canvas: &mut Canvas, ctx: &PaintContext) {
let radius = CornerRadius::uniform(self.recipe.corner_radius);
canvas.fill_rounded_rect(bounds, radius, ctx.theme.colors.surface_main);
canvas.stroke_rounded_rect(
bounds,
radius,
ctx.theme.colors.border_strong,
ctx.theme.shape.border_width,
);
}
fn accessibility(&self, builder: &mut AccessNodeBuilder) {
builder.set_hidden();
}
fn children(&self) -> Vec<WidgetId> {
self.child_id.into_iter().collect()
}
}