use teksilo_core::accesskit::Live;
use teksilo_core::build_context::BuildContext;
use teksilo_core::styles::{
DropRegion, DropTargetDragState, DropTargetStyle, DropTargetStyleConfig, DropTargetVariant,
};
use teksilo_core::widget_builder::WidgetBuilder;
use teksilo_core::widget_id::WidgetId;
use teksilo_tokens::{BorderRole, CornerRadius};
use crate::card::Card;
use crate::drop_target::overlay::DropRegionOverlay;
use crate::primitives::{RectWidget, ZStack};
pub const DROP_TARGET_CORNER_RADIUS: f32 = 8.0;
pub const DROP_TARGET_BORDER_WIDTH_DEFAULT: f32 = 2.0;
pub const DROP_TARGET_BORDER_WIDTH_PROMINENT: f32 = 3.0;
pub const DROP_TARGET_BORDER_WIDTH_SUBTLE: f32 = 1.0;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct DropTargetRecipe {
pub corner_radius: f32,
pub border_width_default: f32,
pub border_width_prominent: f32,
pub border_width_subtle: f32,
}
impl Default for DropTargetRecipe {
fn default() -> Self {
Self {
corner_radius: DROP_TARGET_CORNER_RADIUS,
border_width_default: DROP_TARGET_BORDER_WIDTH_DEFAULT,
border_width_prominent: DROP_TARGET_BORDER_WIDTH_PROMINENT,
border_width_subtle: DROP_TARGET_BORDER_WIDTH_SUBTLE,
}
}
}
#[derive(Debug, Default, Clone, Copy)]
pub struct RecipeDropTargetStyle {
pub recipe: DropTargetRecipe,
}
impl RecipeDropTargetStyle {
pub fn new(recipe: DropTargetRecipe) -> Self {
Self { recipe }
}
}
impl DropTargetStyle for RecipeDropTargetStyle {
fn make_body(&self, cfg: &DropTargetStyleConfig, ctx: &mut BuildContext) -> WidgetId {
let border_width = match cfg.variant {
DropTargetVariant::Default => self.recipe.border_width_default,
DropTargetVariant::Prominent => self.recipe.border_width_prominent,
DropTargetVariant::Subtle => self.recipe.border_width_subtle,
DropTargetVariant::None => 0.0,
};
let mut zstack = ZStack::new().add_child(cfg.content_id);
if cfg.variant != DropTargetVariant::None {
let border = cfg
.drag_state
.zip(&cfg.active_region)
.map(|(s, r)| match s {
DropTargetDragState::HoverReject => BorderRole::Error,
DropTargetDragState::HoverAccept if *r == Some(DropRegion::Center) => {
BorderRole::Accent
}
_ => BorderRole::Transparent,
});
let rect = ctx.add(
RectWidget::new()
.border_color(border)
.border_width(border_width)
.corner_radius(CornerRadius::uniform(self.recipe.corner_radius))
.event_pass_through(true)
.access_hidden(true),
);
zstack = zstack.add_child(rect);
}
let mut hint_cards: Vec<(DropRegion, WidgetId)> = Vec::new();
for &(region, hint_id) in &cfg.region_hints {
let card = ctx.add(Card::new().content_id(hint_id).access_live(Live::Polite));
let visible = cfg.active_region.map(move |r| *r == Some(region));
ctx.visible_when(card, visible);
hint_cards.push((region, card));
}
if border_width > 0.0 || !hint_cards.is_empty() {
let overlay = ctx.add(DropRegionOverlay::new(
cfg.active_region.clone(),
cfg.size_factor,
border_width,
hint_cards,
));
zstack = zstack.add_child(overlay);
}
ctx.add(zstack)
}
}