use std::collections::VecDeque;
use std::ops::{Deref, DerefMut};
use std::time::Instant;
use log;
use crate::kurbo::{Affine, Rect, Shape, Size};
use crate::piet::{Piet, RenderContext};
use crate::{
BoxConstraints, Command, Cursor, Data, Env, Event, Text, TimerToken, Widget, WinCtx,
WindowHandle, WindowId,
};
pub type BoxedWidget<T> = WidgetPod<T, Box<dyn Widget<T>>>;
pub struct WidgetPod<T: Data, W: Widget<T>> {
state: BaseState,
old_data: Option<T>,
env: Option<Env>,
inner: W,
}
#[derive(Default)]
pub(crate) struct BaseState {
layout_rect: Rect,
pub(crate) needs_inval: bool,
is_hot: bool,
is_active: bool,
has_active: bool,
pub(crate) request_anim: bool,
request_timer: bool,
has_focus: bool,
pub(crate) request_focus: bool,
}
impl<T: Data, W: Widget<T>> WidgetPod<T, W> {
pub fn new(inner: W) -> WidgetPod<T, W> {
WidgetPod {
state: Default::default(),
old_data: None,
env: None,
inner,
}
}
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 set_layout_rect(&mut self, layout_rect: Rect) {
self.state.layout_rect = layout_rect;
}
pub fn get_layout_rect(&self) -> Rect {
self.state.layout_rect
}
pub fn paint(&mut self, paint_ctx: &mut PaintCtx, data: &T, env: &Env) {
let mut ctx = PaintCtx {
render_ctx: paint_ctx.render_ctx,
window_id: paint_ctx.window_id,
region: paint_ctx.region.clone(),
base_state: &self.state,
};
self.inner.paint(&mut ctx, data, &env);
}
pub fn paint_with_offset(&mut self, paint_ctx: &mut PaintCtx, data: &T, env: &Env) {
self.paint_with_offset_impl(paint_ctx, data, env, false)
}
pub fn paint_with_offset_always(&mut self, paint_ctx: &mut PaintCtx, data: &T, env: &Env) {
self.paint_with_offset_impl(paint_ctx, data, env, true)
}
fn paint_with_offset_impl(
&mut self,
paint_ctx: &mut PaintCtx,
data: &T,
env: &Env,
paint_if_not_visible: bool,
) {
if !paint_if_not_visible && !paint_ctx.region().intersects(self.state.layout_rect) {
return;
}
if let Err(e) = paint_ctx.save() {
log::error!("saving render context failed: {:?}", e);
return;
}
let layout_origin = self.state.layout_rect.origin().to_vec2();
paint_ctx.transform(Affine::translate(layout_origin));
let visible = paint_ctx.region().to_rect() - layout_origin;
paint_ctx.with_child_ctx(visible, |ctx| self.paint(ctx, data, &env));
if let Err(e) = paint_ctx.restore() {
log::error!("restoring render context failed: {:?}", e);
}
}
pub fn layout(
&mut self,
layout_ctx: &mut LayoutCtx,
bc: &BoxConstraints,
data: &T,
env: &Env,
) -> Size {
self.inner.layout(layout_ctx, bc, data, &env)
}
pub fn event(&mut self, ctx: &mut EventCtx, event: &Event, data: &mut T, env: &Env) {
if ctx.is_handled || !event.recurse() {
return;
}
let had_active = self.state.has_active;
let mut child_ctx = EventCtx {
win_ctx: ctx.win_ctx,
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,
};
let rect = child_ctx.base_state.layout_rect;
let mut recurse = true;
let mut hot_changed = None;
let child_event = match event {
Event::LifeCycle(event) => Event::LifeCycle(*event),
Event::Size(size) => {
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.base_state.has_focus;
Event::KeyDown(*e)
}
Event::KeyUp(e) => {
recurse = child_ctx.base_state.has_focus;
Event::KeyUp(*e)
}
Event::Paste(e) => {
recurse = child_ctx.base_state.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::HotChanged(is_hot) => Event::HotChanged(*is_hot),
Event::FocusChanged(_is_focused) => {
let had_focus = child_ctx.base_state.has_focus;
let focus = child_ctx.base_state.request_focus;
child_ctx.base_state.request_focus = false;
child_ctx.base_state.has_focus = focus;
recurse = focus || had_focus;
Event::FocusChanged(focus)
}
Event::AnimFrame(interval) => {
recurse = child_ctx.base_state.request_anim;
child_ctx.base_state.request_anim = false;
Event::AnimFrame(*interval)
}
Event::Timer(id) => {
recurse = child_ctx.base_state.request_timer;
Event::Timer(*id)
}
Event::Command(cmd) => Event::Command(cmd.clone()),
};
child_ctx.base_state.needs_inval = false;
if let Some(is_hot) = hot_changed {
let hot_changed_event = Event::HotChanged(is_hot);
self.inner
.event(&mut child_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.needs_inval |= child_ctx.base_state.needs_inval;
ctx.base_state.request_anim |= child_ctx.base_state.request_anim;
ctx.base_state.request_timer |= child_ctx.base_state.request_timer;
ctx.base_state.is_hot |= child_ctx.base_state.is_hot;
ctx.base_state.has_active |= child_ctx.base_state.has_active;
ctx.base_state.request_focus |= child_ctx.base_state.request_focus;
ctx.is_handled |= child_ctx.is_handled;
}
pub fn update(&mut self, ctx: &mut UpdateCtx, data: &T, env: &Env) {
let data_same = if let Some(ref old_data) = self.old_data {
old_data.same(data)
} else {
false
};
let env_same = if let Some(ref old_env) = self.env {
old_env.same(env)
} else {
false
};
if data_same && env_same {
return;
}
self.inner.update(ctx, self.old_data.as_ref(), data, env);
self.old_data = Some(data.clone());
self.env = Some(env.clone());
}
}
impl<T: Data, W: Widget<T> + 'static> WidgetPod<T, W> {
pub fn boxed(self) -> BoxedWidget<T> {
WidgetPod {
state: self.state,
old_data: self.old_data,
env: self.env,
inner: Box::new(self.inner),
}
}
}
impl BaseState {
#[inline]
fn size(&self) -> Size {
self.layout_rect.size()
}
}
pub struct PaintCtx<'a, 'b: 'a> {
pub render_ctx: &'a mut Piet<'b>,
pub window_id: WindowId,
pub(crate) region: Region,
pub(crate) base_state: &'a BaseState,
}
#[derive(Debug, Clone)]
pub struct Region(Rect);
impl Region {
pub fn to_rect(&self) -> Rect {
self.0
}
#[inline]
pub fn intersects(&self, other: Rect) -> bool {
self.0.intersect(other).area() > 0.
}
}
impl From<Rect> for Region {
fn from(src: Rect) -> Region {
Region(src)
}
}
impl<'a, 'b: 'a> Deref for PaintCtx<'a, 'b> {
type Target = Piet<'b>;
fn deref(&self) -> &Self::Target {
self.render_ctx
}
}
impl<'a, 'b: 'a> DerefMut for PaintCtx<'a, 'b> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.render_ctx
}
}
impl<'a, 'b: 'a> PaintCtx<'a, 'b> {
pub fn is_hot(&self) -> bool {
self.base_state.is_hot
}
pub fn is_active(&self) -> bool {
self.base_state.is_active
}
pub fn size(&self) -> Size {
self.base_state.size()
}
pub fn has_focus(&self) -> bool {
self.base_state.has_focus
}
#[inline]
pub fn region(&self) -> &Region {
&self.region
}
pub fn with_child_ctx(&mut self, region: impl Into<Region>, f: impl FnOnce(&mut PaintCtx)) {
let PaintCtx {
render_ctx,
window_id,
base_state,
..
} = self;
let mut child_ctx = PaintCtx {
render_ctx,
base_state,
window_id: *window_id,
region: region.into(),
};
f(&mut child_ctx)
}
}
pub struct LayoutCtx<'a, 'b: 'a> {
pub(crate) text_factory: &'a mut Text<'b>,
pub(crate) window_id: WindowId,
}
pub struct EventCtx<'a, 'b> {
pub(crate) win_ctx: &'a mut dyn WinCtx<'b>,
pub(crate) cursor: &'a mut Option<Cursor>,
pub(crate) command_queue: &'a mut VecDeque<(WindowId, Command)>,
pub(crate) window_id: WindowId,
pub(crate) window: &'a WindowHandle,
pub(crate) base_state: &'a mut BaseState,
pub(crate) had_active: bool,
pub(crate) is_handled: bool,
pub(crate) is_root: bool,
}
pub struct UpdateCtx<'a, 'b: 'a> {
pub(crate) text_factory: &'a mut Text<'b>,
pub(crate) window: &'a WindowHandle,
pub(crate) needs_inval: bool,
pub(crate) window_id: WindowId,
}
impl<'a, 'b> EventCtx<'a, 'b> {
pub fn invalidate(&mut self) {
self.base_state.needs_inval = true;
}
pub fn text(&mut self) -> &mut Text<'b> {
self.win_ctx.text_factory()
}
pub fn set_cursor(&mut self, cursor: &Cursor) {
*self.cursor = Some(cursor.clone());
}
pub fn set_active(&mut self, active: bool) {
self.base_state.is_active = active;
}
pub fn is_hot(&self) -> bool {
self.base_state.is_hot
}
pub fn is_active(&self) -> bool {
self.base_state.is_active
}
pub fn window(&self) -> &WindowHandle {
&self.window
}
pub fn set_handled(&mut self) {
self.is_handled = true;
}
pub fn is_handled(&self) -> bool {
self.is_handled
}
pub fn has_focus(&self) -> bool {
self.base_state.has_focus
}
pub fn request_focus(&mut self) {
self.base_state.request_focus = true;
}
pub fn request_anim_frame(&mut self) {
self.base_state.request_anim = true;
}
pub fn request_timer(&mut self, deadline: Instant) -> TimerToken {
self.base_state.request_timer = true;
self.win_ctx.request_timer(deadline)
}
pub fn size(&self) -> Size {
self.base_state.size()
}
pub fn submit_command(
&mut self,
command: impl Into<Command>,
window_id: impl Into<Option<WindowId>>,
) {
let window_id = window_id.into().unwrap_or(self.window_id);
self.command_queue.push_back((window_id, command.into()))
}
pub fn window_id(&self) -> WindowId {
self.window_id
}
}
impl<'a, 'b> LayoutCtx<'a, 'b> {
pub fn text(&mut self) -> &mut Text<'b> {
&mut self.text_factory
}
pub fn window_id(&self) -> WindowId {
self.window_id
}
}
impl<'a, 'b> UpdateCtx<'a, 'b> {
pub fn invalidate(&mut self) {
self.needs_inval = true;
}
pub fn text(&mut self) -> &mut Text<'b> {
self.text_factory
}
pub fn window(&self) -> &WindowHandle {
&self.window
}
pub fn window_id(&self) -> WindowId {
self.window_id
}
}