use crate::element_tree::{Element, NoEvent, ReconcileCtx, VirtualDom};
use crate::elements::backend::ComponentHolder;
use crate::elements::Component;
use crate::flex;
use crate::glue::{DruidAppData, GlobalEventCx};
use crate::widgets::flex_widget;
use druid::widget::prelude::*;
use druid::{widget, AppLauncher, Point, Widget, WidgetPod, WindowDesc};
use tracing::{debug_span, info, instrument, trace};
pub use druid::PlatformError;
type WidgetSeqOf<RootCpEvent, RootCpState, ReturnedTree> = <<ReturnedTree as Element<
RootCpEvent,
RootCpState,
>>::BuildOutput as VirtualDom<RootCpEvent, RootCpState>>::TargetWidgetSeq;
pub struct RootWidget<RootElem: Element<NoEvent, ()> + Clone + 'static> {
pub root_element: RootElem,
pub root_state: RootElem::AggregateChildrenState,
pub vdom: Option<RootElem::BuildOutput>,
pub default_widget: WidgetPod<DruidAppData, widget::Flex<DruidAppData>>,
pub widget: Option<
WidgetPod<DruidAppData, flex_widget::FlexWidget<WidgetSeqOf<NoEvent, (), RootElem>>>,
>,
}
impl<Comp: Component<Props = ()>> RootWidget<ComponentHolder<Comp, NoEvent, ()>> {
pub fn new(_root_component: Comp) -> Self {
let default_widget = WidgetPod::new(widget::Flex::row());
RootWidget {
root_element: Comp::new(()),
root_state: Default::default(),
vdom: None,
default_widget,
widget: None,
}
}
pub fn with_initial_state(self, comp_local_state: Comp::LocalState) -> Self {
RootWidget {
root_state: (comp_local_state, Default::default()),
..self
}
}
}
impl<RootElem: Element<NoEvent, ()> + Clone + 'static> RootWidget<RootElem> {
#[instrument(level = "debug", skip(self, ctx))]
pub fn init(&mut self, ctx: &mut EventCtx) {
let (new_vdom, state) =
debug_span!("build").in_scope(|| self.root_element.clone().build(Default::default()));
self.root_state = state;
info!("Initial aggregate app state: {:?}", self.root_state);
let widget_seq = debug_span!("init_tree").in_scope(|| new_vdom.init_tree());
let flex_widget = WidgetPod::new(flex_widget::FlexWidget {
direction: flex::Axis::Vertical,
flex_params: flex::FlexContainerParams {
cross_alignment: flex::CrossAxisAlignment::Center,
main_alignment: flex::MainAxisAlignment::Start,
fill_major_axis: false,
},
children_seq: widget_seq,
});
ctx.children_changed();
self.widget = Some(flex_widget);
self.vdom = Some(new_vdom);
ctx.request_paint();
}
#[instrument(level = "debug", skip(self, ctx, data, env))]
pub fn run(&mut self, ctx: &mut EventCtx, data: &mut DruidAppData, env: &Env) {
let prev_root_state = self.root_state.clone();
debug_span!("process_event").in_scope(|| {
let prev_vdom = self.vdom.as_mut().unwrap();
let flex_widget = self.widget.as_mut().unwrap().widget_mut();
let mut cx = GlobalEventCx::new(data);
let _ = prev_vdom.process_local_event(
&mut (),
&mut self.root_state,
&mut flex_widget.children_seq,
&mut cx,
);
});
if self.root_state == prev_root_state {
trace!("State is unchanged. Skipping virtual DOM update.");
return;
}
info!("New aggregate app state: {:?}", self.root_state);
let (new_vdom, state) = debug_span!("build").in_scope(|| {
self.root_element
.clone()
.build(std::mem::take(&mut self.root_state))
});
self.root_state = state;
let flex_widget = self.widget.as_mut().unwrap().widget_mut();
let prev_vdom = self.vdom.as_mut().unwrap();
let mut reconcile_ctx = ReconcileCtx {
event_ctx: ctx,
data,
env,
};
debug_span!("reconcile").in_scope(|| {
new_vdom.reconcile(
&prev_vdom,
&mut flex_widget.children_seq,
&mut reconcile_ctx,
);
});
*prev_vdom = new_vdom;
ctx.request_update();
ctx.request_paint();
}
}
impl<RootElem: Element<NoEvent, ()> + Clone + 'static> Widget<DruidAppData>
for RootWidget<RootElem>
{
fn event(&mut self, ctx: &mut EventCtx, event: &Event, data: &mut DruidAppData, env: &Env) {
if let Some(widget) = &mut self.widget {
widget.event(ctx, event, data, env);
} else {
self.default_widget.event(ctx, event, data, env);
}
if self.vdom.is_none() {
self.init(ctx);
} else {
self.run(ctx, data, env);
}
}
fn lifecycle(
&mut self,
ctx: &mut LifeCycleCtx,
event: &LifeCycle,
data: &DruidAppData,
env: &Env,
) {
if let Some(widget) = &mut self.widget {
widget.lifecycle(ctx, event, data, env);
} else {
self.default_widget.lifecycle(ctx, event, data, env);
}
}
fn update(
&mut self,
ctx: &mut UpdateCtx,
_old_data: &DruidAppData,
data: &DruidAppData,
env: &Env,
) {
if let Some(widget) = &mut self.widget {
widget.update(ctx, data, env);
} else {
self.default_widget.update(ctx, data, env);
}
}
fn layout(
&mut self,
ctx: &mut LayoutCtx,
bc: &BoxConstraints,
data: &DruidAppData,
env: &Env,
) -> Size {
let size;
if let Some(widget) = &mut self.widget {
size = widget.layout(ctx, bc, data, env);
widget.set_origin(ctx, data, env, Point::ZERO);
} else {
size = self.default_widget.layout(ctx, bc, data, env);
self.default_widget.set_origin(ctx, data, env, Point::ZERO);
}
size
}
fn paint(&mut self, ctx: &mut PaintCtx, data: &DruidAppData, env: &Env) {
if let Some(widget) = &mut self.widget {
widget.paint(ctx, data, env);
} else {
self.default_widget.paint(ctx, data, env);
}
}
}
pub struct RootHandler<RootElem: Element<NoEvent, ()> + Clone + 'static> {
pub root_widget: RootWidget<RootElem>,
pub init_tracing: bool,
}
impl<Comp: Component<Props = ()>> RootHandler<ComponentHolder<Comp, NoEvent, ()>> {
pub fn new(root_component: Comp) -> Self {
RootHandler {
root_widget: RootWidget::new(root_component),
init_tracing: false,
}
}
pub fn with_initial_state(self, comp_local_state: Comp::LocalState) -> Self {
RootHandler {
root_widget: self.root_widget.with_initial_state(comp_local_state),
..self
}
}
}
impl<RootElem: Element<NoEvent, ()> + Clone + 'static> RootHandler<RootElem> {
pub fn with_tracing(self, init_tracing: bool) -> Self {
RootHandler {
init_tracing,
..self
}
}
pub fn launch(self) -> Result<(), PlatformError> {
if self.init_tracing {
crate::glue::init_tracing();
}
let widget = self.root_widget;
let main_window = WindowDesc::new(|| widget);
let data = Default::default();
AppLauncher::with_window(main_window).launch(data)
}
}