use std::collections::VecDeque;
use log;
use crate::bloom::Bloom;
use crate::kurbo::{Affine, Insets, Rect, Shape, Size};
use crate::piet::RenderContext;
use crate::{
BoxConstraints, Command, Data, Env, Event, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx,
PaintCtx, Target, UpdateCtx, Widget, WidgetId,
};
pub(crate) type CommandQueue = VecDeque<(Target, Command)>;
pub struct WidgetPod<T, W> {
state: BaseState,
old_data: Option<T>,
env: Option<Env>,
inner: W,
}
#[derive(Clone)]
pub(crate) struct BaseState {
pub(crate) id: WidgetId,
pub(crate) layout_rect: Rect,
paint_insets: Insets,
pub(crate) needs_inval: bool,
pub(crate) is_hot: bool,
pub(crate) is_active: bool,
pub(crate) needs_layout: bool,
has_active: bool,
pub(crate) request_anim: bool,
pub(crate) request_timer: bool,
pub(crate) focus_chain: Vec<WidgetId>,
pub(crate) request_focus: Option<FocusChange>,
pub(crate) children: Bloom<WidgetId>,
pub(crate) children_changed: bool,
}
#[derive(Debug, Clone, Copy)]
pub(crate) enum FocusChange {
Resign,
Focus(WidgetId),
Next,
Previous,
}
impl<T, W: Widget<T>> WidgetPod<T, W> {
pub fn new(inner: W) -> WidgetPod<T, W> {
let mut state = BaseState::new(inner.id().unwrap_or_else(WidgetId::next));
state.children_changed = true;
state.needs_layout = true;
WidgetPod {
state,
old_data: None,
env: None,
inner,
}
}
pub(crate) fn state(&self) -> &BaseState {
&self.state
}
pub fn is_active(&self) -> bool {
self.state.is_active
}
pub fn has_active(&self) -> bool {
self.state.has_active
}
pub fn is_hot(&self) -> bool {
self.state.is_hot
}
pub fn widget(&self) -> &W {
&self.inner
}
pub fn widget_mut(&mut self) -> &mut W {
&mut self.inner
}
pub fn id(&self) -> WidgetId {
self.state.id
}
pub fn set_layout_rect(&mut self, layout_rect: Rect) {
self.state.layout_rect = layout_rect;
}
#[deprecated(since = "0.5.0", note = "use layout_rect() instead")]
#[doc(hidden)]
pub fn get_layout_rect(&self) -> Rect {
self.state.layout_rect
}
pub fn layout_rect(&self) -> Rect {
self.state.layout_rect
}
pub fn paint_rect(&self) -> Rect {
self.state.paint_rect()
}
pub fn paint_insets(&self) -> Insets {
self.state.paint_insets
}
pub fn compute_parent_paint_insets(&self, parent_size: Size) -> Insets {
let parent_bounds = Rect::ZERO.with_size(parent_size);
let union_pant_rect = self.paint_rect().union(parent_bounds);
union_pant_rect - parent_bounds
}
}
impl<T: Data, W: Widget<T>> WidgetPod<T, W> {
pub fn paint(&mut self, ctx: &mut PaintCtx, data: &T, env: &Env) {
let mut inner_ctx = PaintCtx {
render_ctx: ctx.render_ctx,
window_id: ctx.window_id,
z_ops: Vec::new(),
region: ctx.region.clone(),
base_state: &self.state,
focus_widget: ctx.focus_widget,
};
self.inner.paint(&mut inner_ctx, data, &env);
ctx.z_ops.append(&mut inner_ctx.z_ops);
if env.get(Env::DEBUG_PAINT) {
const BORDER_WIDTH: f64 = 1.0;
let rect = inner_ctx.size().to_rect().inset(BORDER_WIDTH / -2.0);
let id = self.id().to_raw();
let color = env.get_debug_color(id);
inner_ctx.stroke(rect, &color, BORDER_WIDTH);
}
self.state.needs_inval = false;
}
pub fn paint_with_offset(&mut self, ctx: &mut PaintCtx, data: &T, env: &Env) {
self.paint_with_offset_impl(ctx, data, env, false)
}
pub fn paint_with_offset_always(&mut self, ctx: &mut PaintCtx, data: &T, env: &Env) {
self.paint_with_offset_impl(ctx, data, env, true)
}
fn paint_with_offset_impl(
&mut self,
ctx: &mut PaintCtx,
data: &T,
env: &Env,
paint_if_not_visible: bool,
) {
if !paint_if_not_visible && !ctx.region().intersects(self.state.paint_rect()) {
return;
}
ctx.with_save(|ctx| {
let layout_origin = self.state.layout_rect.origin().to_vec2();
ctx.transform(Affine::translate(layout_origin));
let visible = ctx.region().to_rect() - layout_origin;
ctx.with_child_ctx(visible, |ctx| self.paint(ctx, data, &env));
});
}
pub fn layout(
&mut self,
layout_ctx: &mut LayoutCtx,
bc: &BoxConstraints,
data: &T,
env: &Env,
) -> Size {
layout_ctx.paint_insets = Insets::ZERO;
let size = self.inner.layout(layout_ctx, bc, data, &env);
if size.width.is_infinite() {
let name = self.widget().type_name();
log::warn!("Widget `{}` has an infinite width.", name);
}
if size.height.is_infinite() {
let name = self.widget().type_name();
log::warn!("Widget `{}` has an infinite height.", name);
}
self.state.paint_insets = layout_ctx.paint_insets;
self.state.needs_layout = false;
size
}
pub fn event(&mut self, ctx: &mut EventCtx, event: &Event, data: &mut T, env: &Env) {
if self.old_data.is_none() {
log::error!(
"widget {:?} is receiving an event without having first \
received WidgetAdded.",
ctx.widget_id()
);
}
if ctx.is_handled {
return;
}
let had_active = self.state.has_active;
let mut child_ctx = EventCtx {
cursor: ctx.cursor,
command_queue: ctx.command_queue,
window: &ctx.window,
window_id: ctx.window_id,
base_state: &mut self.state,
had_active,
is_handled: false,
is_root: false,
focus_widget: ctx.focus_widget,
};
let rect = child_ctx.base_state.layout_rect;
let mut recurse = true;
let mut hot_changed = None;
let child_event = match event {
Event::WindowConnected => Event::WindowConnected,
Event::Size(size) => {
child_ctx.request_layout();
recurse = ctx.is_root;
Event::Size(*size)
}
Event::MouseDown(mouse_event) => {
let had_hot = child_ctx.base_state.is_hot;
let now_hot = rect.winding(mouse_event.pos) != 0;
if (!had_hot) && now_hot {
child_ctx.base_state.is_hot = true;
hot_changed = Some(true);
}
recurse = had_active || !ctx.had_active && now_hot;
let mut mouse_event = mouse_event.clone();
mouse_event.pos -= rect.origin().to_vec2();
Event::MouseDown(mouse_event)
}
Event::MouseUp(mouse_event) => {
recurse = had_active || !ctx.had_active && rect.winding(mouse_event.pos) != 0;
let mut mouse_event = mouse_event.clone();
mouse_event.pos -= rect.origin().to_vec2();
Event::MouseUp(mouse_event)
}
Event::MouseMoved(mouse_event) => {
let had_hot = child_ctx.base_state.is_hot;
child_ctx.base_state.is_hot = rect.winding(mouse_event.pos) != 0;
if had_hot != child_ctx.base_state.is_hot {
hot_changed = Some(child_ctx.base_state.is_hot);
}
recurse = had_active || had_hot || child_ctx.base_state.is_hot;
let mut mouse_event = mouse_event.clone();
mouse_event.pos -= rect.origin().to_vec2();
Event::MouseMoved(mouse_event)
}
Event::KeyDown(e) => {
recurse = child_ctx.has_focus();
Event::KeyDown(*e)
}
Event::KeyUp(e) => {
recurse = child_ctx.has_focus();
Event::KeyUp(*e)
}
Event::Paste(e) => {
recurse = child_ctx.has_focus();
Event::Paste(e.clone())
}
Event::Wheel(wheel_event) => {
recurse = had_active || child_ctx.base_state.is_hot;
Event::Wheel(wheel_event.clone())
}
Event::Zoom(zoom) => {
recurse = had_active || child_ctx.base_state.is_hot;
Event::Zoom(*zoom)
}
Event::Timer(id) => {
recurse = child_ctx.base_state.request_timer;
Event::Timer(*id)
}
Event::Command(cmd) => Event::Command(cmd.clone()),
Event::TargetedCommand(target, cmd) => match target {
Target::Window(_) => Event::Command(cmd.clone()),
Target::Widget(id) if *id == child_ctx.widget_id() => Event::Command(cmd.clone()),
Target::Widget(id) => {
recurse = child_ctx.base_state.children.contains(id);
Event::TargetedCommand(*target, cmd.clone())
}
Target::Global => panic!("Target::Global should be converted before WidgetPod"),
},
};
if let Some(is_hot) = hot_changed {
let hot_changed_event = LifeCycle::HotChanged(is_hot);
let mut lc_ctx = child_ctx.make_lifecycle_ctx();
self.inner
.lifecycle(&mut lc_ctx, &hot_changed_event, data, &env);
}
if recurse {
child_ctx.base_state.has_active = false;
self.inner.event(&mut child_ctx, &child_event, data, &env);
child_ctx.base_state.has_active |= child_ctx.base_state.is_active;
};
ctx.base_state.merge_up(&child_ctx.base_state);
ctx.is_handled |= child_ctx.is_handled;
}
pub fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle, data: &T, env: &Env) {
let recurse = match event {
LifeCycle::AnimFrame(_) => {
let r = self.state.request_anim;
self.state.request_anim = false;
r
}
LifeCycle::WidgetAdded => {
assert!(self.old_data.is_none());
self.old_data = Some(data.clone());
self.env = Some(env.clone());
true
}
LifeCycle::RouteWidgetAdded => {
if self.old_data.is_none() {
self.lifecycle(ctx, &LifeCycle::WidgetAdded, data, env);
return;
} else {
if self.state.children_changed {
self.state.children.clear();
self.state.focus_chain.clear();
}
self.state.children_changed
}
}
LifeCycle::HotChanged(_) => false,
LifeCycle::RouteFocusChanged { old, new } => {
self.state.request_focus = None;
let this_changed = if *old == Some(self.state.id) {
Some(false)
} else if *new == Some(self.state.id) {
Some(true)
} else {
None
};
if let Some(change) = this_changed {
let event = LifeCycle::FocusChanged(change);
self.inner.lifecycle(ctx, &event, data, env);
false
} else {
old.map(|id| self.state.children.contains(&id))
.or_else(|| new.map(|id| self.state.children.contains(&id)))
.unwrap_or(false)
}
}
LifeCycle::FocusChanged(_) => {
self.state.request_focus = None;
true
}
#[cfg(test)]
LifeCycle::DebugRequestState { widget, state_cell } => {
if *widget == self.id() {
state_cell.set(self.state.clone());
false
} else {
self.state.children.contains(&widget)
}
}
#[cfg(test)]
LifeCycle::DebugInspectState(f) => {
f.call(&self.state);
true
}
};
let mut child_ctx = LifeCycleCtx {
command_queue: ctx.command_queue,
base_state: &mut self.state,
window_id: ctx.window_id,
};
if recurse {
self.inner.lifecycle(&mut child_ctx, event, data, env);
}
ctx.base_state.merge_up(&self.state);
match event {
LifeCycle::WidgetAdded | LifeCycle::RouteWidgetAdded => {
self.state.children_changed = false;
ctx.base_state.children = ctx.base_state.children.union(self.state.children);
ctx.base_state.focus_chain.extend(&self.state.focus_chain);
ctx.register_child(self.id());
}
_ => (),
}
}
pub fn update(&mut self, ctx: &mut UpdateCtx, data: &T, env: &Env) {
match (self.old_data.as_ref(), self.env.as_ref()) {
(Some(d), Some(e)) if d.same(data) && e.same(env) => return,
(None, _) => {
log::warn!("old_data missing in {:?}, skipping update", self.id());
self.old_data = Some(data.clone());
self.env = Some(env.clone());
return;
}
_ => (),
}
let mut child_ctx = UpdateCtx {
window: ctx.window,
base_state: &mut self.state,
window_id: ctx.window_id,
};
self.inner
.update(&mut child_ctx, self.old_data.as_ref().unwrap(), data, env);
self.old_data = Some(data.clone());
self.env = Some(env.clone());
ctx.base_state.merge_up(&self.state)
}
}
impl<T, W: Widget<T> + 'static> WidgetPod<T, W> {
pub fn boxed(self) -> WidgetPod<T, Box<dyn Widget<T>>> {
WidgetPod::new(Box::new(self.inner))
}
}
impl BaseState {
pub(crate) fn new(id: WidgetId) -> BaseState {
BaseState {
id,
layout_rect: Rect::ZERO,
paint_insets: Insets::ZERO,
needs_inval: false,
is_hot: false,
needs_layout: false,
is_active: false,
has_active: false,
request_anim: false,
request_timer: false,
request_focus: None,
focus_chain: Vec::new(),
children: Bloom::new(),
children_changed: false,
}
}
fn merge_up(&mut self, child_state: &BaseState) {
self.needs_inval |= child_state.needs_inval;
self.needs_layout |= child_state.needs_layout;
self.request_anim |= child_state.request_anim;
self.request_timer |= child_state.request_timer;
self.has_active |= child_state.has_active;
self.children_changed |= child_state.children_changed;
self.request_focus = self.request_focus.or(child_state.request_focus);
}
#[inline]
pub(crate) fn size(&self) -> Size {
self.layout_rect.size()
}
pub(crate) fn paint_rect(&self) -> Rect {
self.layout_rect + self.paint_insets
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::widget::{Flex, Scroll, Split, TextBox};
use crate::{WidgetExt, WindowId};
const ID_1: WidgetId = WidgetId::reserved(0);
const ID_2: WidgetId = WidgetId::reserved(1);
const ID_3: WidgetId = WidgetId::reserved(2);
#[test]
fn register_children() {
fn make_widgets() -> impl Widget<Option<u32>> {
Split::vertical(
Flex::<Option<u32>>::row()
.with_child(TextBox::new().with_id(ID_1).parse())
.with_child(TextBox::new().with_id(ID_2).parse())
.with_child(TextBox::new().with_id(ID_3).parse()),
Scroll::new(TextBox::new().parse()),
)
}
let widget = make_widgets();
let mut widget = WidgetPod::new(widget).boxed();
let mut command_queue: CommandQueue = VecDeque::new();
let mut state = BaseState::new(WidgetId::next());
let mut ctx = LifeCycleCtx {
command_queue: &mut command_queue,
base_state: &mut state,
window_id: WindowId::next(),
};
let env = Env::default();
widget.lifecycle(&mut ctx, &LifeCycle::WidgetAdded, &None, &env);
assert!(ctx.base_state.children.contains(&ID_1));
assert!(ctx.base_state.children.contains(&ID_2));
assert!(ctx.base_state.children.contains(&ID_3));
assert_eq!(ctx.base_state.children.entry_count(), 7);
}
}