use crate::debug_state::DebugState;
use druid::{
BoxConstraints, Data, Env, Event, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx,
Point, Size, UpdateCtx, Widget, WidgetExt, WidgetPod,
};
use druid::widget::SizedBox;
pub struct Maybe<T> {
some_maker: Box<dyn Fn() -> Box<dyn Widget<T>>>,
none_maker: Box<dyn Fn() -> Box<dyn Widget<()>>>,
widget: MaybeWidget<T>,
}
#[allow(clippy::large_enum_variant)]
enum MaybeWidget<T> {
Some(WidgetPod<T, Box<dyn Widget<T>>>),
None(WidgetPod<(), Box<dyn Widget<()>>>),
}
impl<T: Data> Maybe<T> {
pub fn new<W1, W2>(
some_maker: impl Fn() -> W1 + 'static,
none_maker: impl Fn() -> W2 + 'static,
) -> Maybe<T>
where
W1: Widget<T> + 'static,
W2: Widget<()> + 'static,
{
let widget = MaybeWidget::Some(WidgetPod::new(some_maker().boxed()));
Maybe {
some_maker: Box::new(move || some_maker().boxed()),
none_maker: Box::new(move || none_maker().boxed()),
widget,
}
}
pub fn or_empty<W1: Widget<T> + 'static>(some_maker: impl Fn() -> W1 + 'static) -> Maybe<T> {
Self::new(some_maker, SizedBox::empty)
}
fn rebuild_widget(&mut self, is_some: bool) {
if is_some {
self.widget = MaybeWidget::Some(WidgetPod::new((self.some_maker)()));
} else {
self.widget = MaybeWidget::None(WidgetPod::new((self.none_maker)()));
}
}
}
impl<T: Data> Widget<Option<T>> for Maybe<T> {
fn event(&mut self, ctx: &mut EventCtx, event: &Event, data: &mut Option<T>, env: &Env) {
if data.is_some() == self.widget.is_some() {
match data.as_mut() {
Some(d) => self.widget.with_some(|w| w.event(ctx, event, d, env)),
None => self.widget.with_none(|w| w.event(ctx, event, &mut (), env)),
};
}
}
fn lifecycle(
&mut self,
ctx: &mut LifeCycleCtx,
event: &LifeCycle,
data: &Option<T>,
env: &Env,
) {
if data.is_some() != self.widget.is_some() {
self.rebuild_widget(data.is_some());
}
assert_eq!(data.is_some(), self.widget.is_some(), "{event:?}");
match data.as_ref() {
Some(d) => self.widget.with_some(|w| w.lifecycle(ctx, event, d, env)),
None => self.widget.with_none(|w| w.lifecycle(ctx, event, &(), env)),
};
}
fn update(&mut self, ctx: &mut UpdateCtx, old_data: &Option<T>, data: &Option<T>, env: &Env) {
if old_data.is_some() != data.is_some() {
self.rebuild_widget(data.is_some());
ctx.children_changed();
} else {
match data {
Some(new) => self.widget.with_some(|w| w.update(ctx, new, env)),
None => self.widget.with_none(|w| w.update(ctx, &(), env)),
};
}
}
fn layout(
&mut self,
ctx: &mut LayoutCtx,
bc: &BoxConstraints,
data: &Option<T>,
env: &Env,
) -> Size {
match data.as_ref() {
Some(d) => self.widget.with_some(|w| {
let size = w.layout(ctx, bc, d, env);
w.set_origin(ctx, Point::ORIGIN);
size
}),
None => self.widget.with_none(|w| {
let size = w.layout(ctx, bc, &(), env);
w.set_origin(ctx, Point::ORIGIN);
size
}),
}
.unwrap_or_default()
}
fn paint(&mut self, ctx: &mut PaintCtx, data: &Option<T>, env: &Env) {
match data.as_ref() {
Some(d) => self.widget.with_some(|w| w.paint(ctx, d, env)),
None => self.widget.with_none(|w| w.paint(ctx, &(), env)),
};
}
fn debug_state(&self, data: &Option<T>) -> DebugState {
let child_state = match (&self.widget, data.as_ref()) {
(MaybeWidget::Some(widget_pod), Some(d)) => vec![widget_pod.widget().debug_state(d)],
(MaybeWidget::None(widget_pod), None) => vec![widget_pod.widget().debug_state(&())],
_ => vec![],
};
DebugState {
display_name: self.short_type_name().to_string(),
children: child_state,
..Default::default()
}
}
}
impl<T> MaybeWidget<T> {
fn is_some(&self) -> bool {
match self {
Self::Some(_) => true,
Self::None(_) => false,
}
}
fn with_some<R, F: FnOnce(&mut WidgetPod<T, Box<dyn Widget<T>>>) -> R>(
&mut self,
f: F,
) -> Option<R> {
match self {
Self::Some(widget) => Some(f(widget)),
Self::None(_) => {
tracing::trace!("`MaybeWidget::with_some` called on `None` value");
None
}
}
}
fn with_none<R, F: FnOnce(&mut WidgetPod<(), Box<dyn Widget<()>>>) -> R>(
&mut self,
f: F,
) -> Option<R> {
match self {
Self::None(widget) => Some(f(widget)),
Self::Some(_) => {
tracing::trace!("`MaybeWidget::with_none` called on `Some` value");
None
}
}
}
}