use teksilo_canvas::{Canvas, Point, Rect, SizeProposal};
use crate::accessibility::AccessNodeBuilder;
use crate::build_context::BuildContext;
use crate::widget::{
LayoutContext, PaintContext, PendingChild, Widget, WidgetPlacement, WidgetTreeView,
};
use crate::widget_builder::HandlerSet;
use crate::widget_id::WidgetId;
macro_rules! impl_widget_for_branch {
($branch:ident, $($arm:ident),+) => {
#[deny(clippy::missing_trait_methods)]
impl<$($arm: Widget),+> Widget for $branch<$($arm),+> {
fn type_name(&self) -> &'static str {
match self { $($branch::$arm(w) => w.type_name()),+ }
}
fn build(&mut self, ctx: &mut BuildContext) -> Vec<WidgetId> {
match self { $($branch::$arm(w) => w.build(ctx)),+ }
}
fn layout_response(
&self,
proposal: SizeProposal,
ctx: &LayoutContext,
) -> crate::widget::LayoutResponse {
match self { $($branch::$arm(w) => w.layout_response(proposal, ctx)),+ }
}
fn cacheable_layout(&self) -> bool {
match self { $($branch::$arm(w) => w.cacheable_layout()),+ }
}
fn place_children(
&self,
bounds: Rect,
proposal: SizeProposal,
children: &mut [WidgetPlacement],
ctx: &LayoutContext,
) {
match self {
$($branch::$arm(w) => w.place_children(bounds, proposal, children, ctx)),+
}
}
fn paint(&self, bounds: Rect, canvas: &mut Canvas, ctx: &PaintContext) {
match self { $($branch::$arm(w) => w.paint(bounds, canvas, ctx)),+ }
}
fn wants_after_paint(&self) -> bool {
match self { $($branch::$arm(w) => w.wants_after_paint()),+ }
}
fn after_paint(&self, view: &WidgetTreeView<'_>, ctx: &PaintContext) {
match self { $($branch::$arm(w) => w.after_paint(view, ctx)),+ }
}
fn wants_post_paint(&self) -> bool {
match self { $($branch::$arm(w) => w.wants_post_paint()),+ }
}
fn post_paint(&self, bounds: Rect, canvas: &mut Canvas, ctx: &PaintContext) {
match self { $($branch::$arm(w) => w.post_paint(bounds, canvas, ctx)),+ }
}
fn accessibility(&self, builder: &mut AccessNodeBuilder) {
match self { $($branch::$arm(w) => w.accessibility(builder)),+ }
}
fn culls_children(&self) -> bool {
match self { $($branch::$arm(w) => w.culls_children()),+ }
}
fn wants_descendant_redirects(&self) -> bool {
match self { $($branch::$arm(w) => w.wants_descendant_redirects()),+ }
}
fn a11y_redirect_descendant(
&self,
self_id: WidgetId,
descendant: WidgetId,
) -> Option<accesskit::NodeId> {
match self {
$($branch::$arm(w) => w.a11y_redirect_descendant(self_id, descendant)),+
}
}
fn accessible_title_hint(&self) -> Option<String> {
match self { $($branch::$arm(w) => w.accessible_title_hint()),+ }
}
fn accessible_title_node(&self) -> Option<crate::widget_id::WidgetId> {
match self { $($branch::$arm(w) => w.accessible_title_node()),+ }
}
fn initial_focus_hint(&self) -> Option<WidgetId> {
match self { $($branch::$arm(w) => w.initial_focus_hint()),+ }
}
fn context_menu_key_target(&self) -> Option<WidgetId> {
match self { $($branch::$arm(w) => w.context_menu_key_target()),+ }
}
fn children(&self) -> Vec<WidgetId> {
match self { $($branch::$arm(w) => w.children()),+ }
}
fn accessibility_children(&self) -> Option<Vec<WidgetId>> {
match self { $($branch::$arm(w) => w.accessibility_children()),+ }
}
fn as_any(&self) -> Option<&dyn std::any::Any> {
match self { $($branch::$arm(w) => w.as_any()),+ }
}
fn as_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
match self { $($branch::$arm(w) => w.as_any_mut()),+ }
}
fn clips_children(&self) -> bool {
match self { $($branch::$arm(w) => w.clips_children()),+ }
}
fn focus_reveal_rect(&self, bounds: Rect) -> Option<Rect> {
match self { $($branch::$arm(w) => w.focus_reveal_rect(bounds)),+ }
}
fn hit_shape(&self, local_point: Point, bounds: Rect) -> bool {
match self { $($branch::$arm(w) => w.hit_shape(local_point, bounds)),+ }
}
fn accepts_child_hit(&self, child: WidgetId, point: Point) -> bool {
match self { $($branch::$arm(w) => w.accepts_child_hit(child, point)),+ }
}
fn hit_outset(
&self,
kind: teksilo_tokens::PointerKind,
tokens: &teksilo_tokens::InputTokens,
) -> teksilo_canvas::EdgeInsets {
match self { $($branch::$arm(w) => w.hit_outset(kind, tokens)),+ }
}
fn hit_slop(
&self,
kind: teksilo_tokens::PointerKind,
tokens: &teksilo_tokens::InputTokens,
) -> Option<crate::pointer::hit_slop::HitSlop> {
match self { $($branch::$arm(w) => Widget::hit_slop(w, kind, tokens)),+ }
}
fn hit_distance(&self, local_point: Point, bounds: Rect) -> Option<f32> {
match self { $($branch::$arm(w) => w.hit_distance(local_point, bounds)),+ }
}
fn target_regions(&self, bounds: Rect) -> Vec<crate::partition::TargetRegion> {
match self { $($branch::$arm(w) => w.target_regions(bounds)),+ }
}
fn preserves_children_on_rebuild(&self) -> bool {
match self { $($branch::$arm(w) => w.preserves_children_on_rebuild()),+ }
}
fn tooltip_has_content(&self) -> bool {
match self { $($branch::$arm(w) => w.tooltip_has_content()),+ }
}
fn declare_shortcuts(&self) -> Vec<crate::shortcut::Shortcut> {
match self { $($branch::$arm(w) => w.declare_shortcuts()),+ }
}
fn take_handler_set(&mut self) -> Option<HandlerSet> {
match self { $($branch::$arm(w) => w.take_handler_set()),+ }
}
}
};
}
#[derive(Debug)]
pub enum TeksiBranch<L: Widget, R: Widget> {
L(L),
R(R),
}
impl_widget_for_branch!(TeksiBranch, L, R);
#[derive(Debug)]
pub enum TeksiBranch3<A: Widget, B: Widget, C: Widget> {
A(A),
B(B),
C(C),
}
impl_widget_for_branch!(TeksiBranch3, A, B, C);
#[derive(Debug)]
pub enum TeksiBranch4<A: Widget, B: Widget, C: Widget, D: Widget> {
A(A),
B(B),
C(C),
D(D),
}
impl_widget_for_branch!(TeksiBranch4, A, B, C, D);
pub trait IntoTeksiChild {
fn into_pending(self) -> PendingChild;
}
impl<W: Widget + 'static> IntoTeksiChild for W {
fn into_pending(self) -> PendingChild {
PendingChild::Deferred(Box::new(self))
}
}
impl IntoTeksiChild for WidgetId {
fn into_pending(self) -> PendingChild {
PendingChild::Id(self)
}
}
pub trait IntoTeksiCondition {
fn teksilo_into_conditional_child<W: crate::widget::Widget + 'static>(
self,
child: W,
ctx: &mut crate::build_context::BuildContext,
) -> Option<WidgetId>;
}
impl IntoTeksiCondition for bool {
fn teksilo_into_conditional_child<W: crate::widget::Widget + 'static>(
self,
child: W,
ctx: &mut crate::build_context::BuildContext,
) -> Option<WidgetId> {
if self { Some(ctx.add(child)) } else { None }
}
}
impl IntoTeksiCondition for crate::signal::Signal<bool> {
fn teksilo_into_conditional_child<W: crate::widget::Widget + 'static>(
self,
child: W,
ctx: &mut crate::build_context::BuildContext,
) -> Option<WidgetId> {
let id = ctx.add(child);
ctx.visible_when(id, self);
Some(id)
}
}
impl IntoTeksiCondition for crate::signal::Prop<bool> {
fn teksilo_into_conditional_child<W: crate::widget::Widget + 'static>(
self,
child: W,
ctx: &mut crate::build_context::BuildContext,
) -> Option<WidgetId> {
let id = ctx.add(child);
ctx.visible_when(id, self);
Some(id)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_widgets::FillWidget;
use crate::widget_tree::WidgetTree;
use teksilo_canvas::SizeProposal;
use teksilo_tokens::Color;
#[test]
fn teksilo_branch_dispatches_to_active_variant() {
let mut tree_l = WidgetTree::new();
let id_l = tree_l.add(TeksiBranch::<FillWidget, FillWidget>::L(
FillWidget::new().background(Color::RED),
));
tree_l.layout(SizeProposal::exact(100.0, 50.0));
assert!((tree_l.bounds(id_l).width - 100.0).abs() < 0.01);
let mut tree_r = WidgetTree::new();
let id_r = tree_r.add(TeksiBranch::<FillWidget, FillWidget>::R(
FillWidget::new().background(Color::BLUE),
));
tree_r.layout(SizeProposal::exact(80.0, 40.0));
assert!((tree_r.bounds(id_r).width - 80.0).abs() < 0.01);
}
#[test]
fn teksilo_branch3_dispatches_to_active_variant() {
let mut tree = WidgetTree::new();
let id = tree.add(TeksiBranch3::<FillWidget, FillWidget, FillWidget>::B(
FillWidget::new(),
));
tree.layout(SizeProposal::exact(120.0, 60.0));
assert!((tree.bounds(id).width - 120.0).abs() < 0.01);
}
#[test]
fn into_teksilo_child_routes_widget_to_deferred() {
let pending = FillWidget::new().into_pending();
assert!(matches!(pending, PendingChild::Deferred(_)));
}
#[test]
fn into_teksilo_child_routes_widget_id_to_id() {
let mut tree = WidgetTree::new();
let leaf = tree.add(FillWidget::new());
let pending = leaf.into_pending();
match pending {
PendingChild::Id(id) => assert_eq!(id, leaf),
_ => panic!("expected PendingChild::Id"),
}
}
}