use crate::core::{Action, Animation, Easing, EntityContext, FocusHandle, Handle, Lerp, Size};
use crate::view::any_element::AnyElement;
use crate::view::div::div;
use crate::view::element::IntoElement;
use crate::view::interactivity::{ClickEvent, Effects, EventContext};
use crate::view::theme::Theme;
use std::any::Any;
use std::ops::{Deref, DerefMut};
use std::time::{Duration, Instant};
const PHASE_SECONDS: f32 = 86_400.0;
pub trait Render: Sized + 'static {
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement;
}
pub trait View: Sized + 'static {
fn view(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement;
}
impl<T: Render> View for T {
fn view(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
self.render(cx)
}
}
pub struct ViewContext<'a, 'b, T> {
pub theme: &'a Theme,
pub viewport: Size,
entity: &'a mut EntityContext<'b, T>,
}
impl<'a, 'b, T: 'static> ViewContext<'a, 'b, T> {
pub fn new(theme: &'a Theme, viewport: Size, entity: &'a mut EntityContext<'b, T>) -> Self {
Self { theme, viewport, entity }
}
pub fn listener<E>(
&self,
callback: impl Fn(&mut T, &E, &mut ListenerContext<T>) + 'static,
) -> impl Fn(&E, &mut EventContext) + 'static {
let handle = self.entity.handle();
move |event, context| {
let effects = &mut context.effects;
context.entities.update(handle, |this, entity| {
let mut listener = ListenerContext { entity, effects };
callback(this, event, &mut listener);
});
}
}
pub fn click(
&self,
action: impl Fn(&mut T, &mut ListenerContext<T>) + 'static,
) -> impl Fn(&ClickEvent, &mut EventContext) + 'static {
self.listener(move |this: &mut T, _: &ClickEvent, cx| {
action(this, cx);
cx.notify();
})
}
pub fn toggle(&self, field: fn(&mut T) -> &mut bool) -> impl Fn(&ClickEvent, &mut EventContext) + 'static {
self.click(move |this, _| {
let flag = field(this);
*flag = !*flag;
})
}
pub fn set<V: Clone + 'static>(
&self,
field: fn(&mut T) -> &mut V,
value: V,
) -> impl Fn(&ClickEvent, &mut EventContext) + 'static {
self.click(move |this, _| *field(this) = value.clone())
}
pub fn fire<E: Any + Clone>(&self, event: E) -> impl Fn(&ClickEvent, &mut EventContext) + 'static {
self.click(move |_, cx| cx.emit(event.clone()))
}
pub fn dragging<Payload: std::any::Any>(&self) -> bool {
self.entity.drag_type == Some(std::any::TypeId::of::<Payload>())
}
pub fn render_child<V: View>(&mut self, child: Handle<V>) -> AnyElement {
let (theme, viewport) = (self.theme, self.viewport);
self.entity
.update(child, |view, entity| view.view(&mut ViewContext::new(theme, viewport, entity)).into_element())
.unwrap_or_else(|| div().into_element())
}
pub fn animate(&mut self, id: &str, animation: Animation) -> f32 {
let (progress, running) = self.entity.animations.progress(id, animation, Instant::now());
if running {
self.entity.timers.request_animation_frame();
}
progress
}
pub fn animate_between<L: Lerp>(&mut self, id: &str, animation: Animation, from: L, to: L) -> L {
from.lerp(to, self.animate(id, animation))
}
pub fn phase(&mut self, id: &str, enabled: bool) -> f32 {
if !enabled {
return 0.0;
}
let day = Animation::new(Duration::from_secs_f32(PHASE_SECONDS)).easing(Easing::Linear);
self.animate(id, day) * PHASE_SECONDS
}
pub fn fade_in(&mut self, id: &str, millis: u64, enabled: bool) -> f32 {
if !enabled {
return 1.0;
}
self.animate(id, Animation::new(Duration::from_millis(millis)))
}
pub fn transition(&mut self, id: &str, target: f32, animation: Animation) -> f32 {
let (value, running) = self.entity.animations.transition(id, target, animation, Instant::now());
if running {
self.entity.timers.request_animation_frame();
}
value
}
pub fn new_focus_handle(&mut self) -> FocusHandle {
self.entity.focus.new_handle()
}
pub fn is_focused(&self, handle: &FocusHandle) -> bool {
self.entity.focus.is_focused(handle)
}
}
impl<'b, T> Deref for ViewContext<'_, 'b, T> {
type Target = EntityContext<'b, T>;
fn deref(&self) -> &Self::Target {
self.entity
}
}
impl<T> DerefMut for ViewContext<'_, '_, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.entity
}
}
pub struct ListenerContext<'a, 'b, T> {
entity: &'a mut EntityContext<'b, T>,
effects: &'a mut Effects,
}
impl<T: 'static> ListenerContext<'_, '_, T> {
pub fn notify(&mut self) {
self.effects.notify();
self.entity.notify();
}
pub fn dispatch_action(&mut self, action: Action) {
self.effects.dispatch_action(action);
}
pub fn propagate(&mut self) {
self.effects.propagate();
}
pub fn pointer(&self) -> Option<crate::core::Point> {
self.effects.pointer
}
pub fn focus(&mut self, handle: &FocusHandle) {
self.entity.focus.focus(handle);
self.effects.notify();
}
}
impl<'b, T> Deref for ListenerContext<'_, 'b, T> {
type Target = EntityContext<'b, T>;
fn deref(&self) -> &Self::Target {
self.entity
}
}
impl<T> DerefMut for ListenerContext<'_, '_, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.entity
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::{EntityMap, MouseButton};
struct Panel {
open: bool,
tab: u8,
clicks: u32,
}
#[derive(Clone)]
struct Closed;
type Handler = Box<dyn Fn(&ClickEvent, &mut EventContext)>;
fn click(entities: &mut EntityMap, handler: &Handler) -> bool {
let event = ClickEvent {
position: Default::default(),
button: MouseButton::Left,
modifiers: Default::default(),
click_count: 1,
};
let mut context = EventContext { entities, effects: Default::default() };
handler(&event, &mut context);
context.effects.redraw
}
#[test]
fn click_toggle_set_and_fire_mutate_redraw_and_emit() {
let mut entities = EntityMap::new();
let handle = entities.insert(Panel { open: false, tab: 0, clicks: 0 });
let seen = std::rc::Rc::new(std::cell::Cell::new(0));
let counter = seen.clone();
entities.update(handle, |_, cx| {
let me = cx.handle();
cx.subscribe(me, me, move |_: &mut Panel, _: &Closed, _| counter.set(counter.get() + 1));
});
let theme = Theme::default();
let (toggle, set, count, fire) = entities
.update(handle, |_, entity| {
let cx = ViewContext::new(&theme, Default::default(), entity);
let handlers: [Handler; 4] = [
Box::new(cx.toggle(|t| &mut t.open)),
Box::new(cx.set(|t| &mut t.tab, 3)),
Box::new(cx.click(|t, _| t.clicks += 1)),
Box::new(cx.fire(Closed)),
];
handlers
})
.map(|[a, b, c, d]| (a, b, c, d))
.unwrap();
assert!(click(&mut entities, &toggle) && click(&mut entities, &set) && click(&mut entities, &count));
click(&mut entities, &toggle);
click(&mut entities, &fire);
let panel = entities.read(handle).unwrap();
assert!(!panel.open, "dos toggles vuelven al inicio");
assert_eq!((panel.tab, panel.clicks), (3, 1));
assert_eq!(seen.get(), 1, "fire emitió el evento una vez");
}
#[test]
fn phase_and_fade_in_do_not_animate_when_disabled() {
let mut entities = EntityMap::new();
let handle = entities.insert(Panel { open: false, tab: 0, clicks: 0 });
let theme = Theme::default();
entities.update(handle, |_, entity| {
let mut cx = ViewContext::new(&theme, Default::default(), entity);
assert_eq!((cx.phase("a", false), cx.fade_in("b", 200, false)), (0.0, 1.0));
assert!(cx.fade_in("c", 200, true) < 1.0, "recién empezada, aún no llegó al final");
});
}
}