use std::{
collections::HashMap,
fmt::Debug,
hash::{
DefaultHasher,
Hash,
Hasher,
},
time::Duration,
};
use freya_animation::prelude::*;
use freya_core::{
prelude::*,
scope_id::ScopeId,
};
use torin::{
prelude::{
Area,
Point2D,
Position,
Size2D,
},
size::Size,
};
#[derive(PartialEq)]
pub struct Portal<T> {
key: DiffKey,
children: Vec<Element>,
id: T,
function: Function,
duration: Duration,
ease: Ease,
layout: LayoutData,
show: bool,
dependency: Option<u64>,
}
impl<T> ChildrenExt for Portal<T> {
fn get_children(&mut self) -> &mut Vec<Element> {
&mut self.children
}
}
impl<T> Portal<T> {
pub fn new(id: T) -> Self {
Self {
key: DiffKey::None,
children: vec![],
id,
function: Function::default(),
duration: Duration::from_millis(750),
ease: Ease::default(),
layout: LayoutData::default(),
show: true,
dependency: None,
}
}
pub fn function(mut self, function: Function) -> Self {
self.function = function;
self
}
pub fn duration(mut self, duration: Duration) -> Self {
self.duration = duration;
self
}
pub fn ease(mut self, ease: Ease) -> Self {
self.ease = ease;
self
}
pub fn show(mut self, show: bool) -> Self {
self.show = show;
self
}
pub fn animation_dependency(mut self, dependency: impl Hash) -> Self {
let mut hasher = DefaultHasher::default();
dependency.hash(&mut hasher);
self.dependency = Some(hasher.finish());
self
}
}
impl<T> LayoutExt for Portal<T> {
fn get_layout(&mut self) -> &mut LayoutData {
&mut self.layout
}
}
impl<T> ContainerSizeExt for Portal<T> {}
impl<T> KeyExt for Portal<T> {
fn write_key(&mut self) -> &mut DiffKey {
&mut self.key
}
}
impl<T: Clone + Eq + Hash + Debug + 'static> Component for Portal<T> {
fn render(&self) -> impl IntoElement {
let mut positions = use_hook(|| match try_consume_context::<PortalsMap<T>>() {
Some(ctx) => ctx,
None => {
let ctx = PortalsMap {
ids: State::create_in_scope(HashMap::default(), ScopeId::ROOT),
};
provide_context_for_scope_id(ctx.clone(), ScopeId::ROOT);
ctx
}
});
let id = self.id.clone();
let dependency = self.dependency;
let init_size = use_hook(move || {
positions
.ids
.write()
.remove(&id)
.filter(|(_, last)| dependency.is_none() || *last != dependency)
.map(|(area, _)| area)
});
let mut previous_size = use_state::<Option<Area>>(|| None);
let mut current_size = use_state::<Option<Area>>(|| None);
let mut last_dependency = use_state::<Option<u64>>(|| None);
let mut should_animate = use_state(|| false);
let mut animation = use_animation_with_dependencies(
&(self.function, self.duration, self.ease),
move |conf, (function, duration, ease)| {
conf.on_change(OnChange::Nothing);
let from_size = previous_size
.read()
.unwrap_or(init_size.unwrap_or_default());
let to_size = current_size.read().unwrap_or_default();
(
AnimNum::new(from_size.origin.x, to_size.origin.x)
.duration(*duration)
.ease(*ease)
.function(*function),
AnimNum::new(from_size.origin.y, to_size.origin.y)
.duration(*duration)
.ease(*ease)
.function(*function),
AnimNum::new(from_size.size.width, to_size.size.width)
.duration(*duration)
.ease(*ease)
.function(*function),
AnimNum::new(from_size.size.height, to_size.size.height)
.duration(*duration)
.ease(*ease)
.function(*function),
)
},
);
use_side_effect(move || {
if !*animation.is_running().read() {
should_animate.set_if_modified(false);
}
});
let at_rest = !should_animate() && current_size.read().is_some();
let area = if at_rest {
current_size.read().unwrap_or_default()
} else {
let (x, y, width, height) = animation.get().value();
Area::new(Point2D::new(x, y), Size2D::new(width, height))
};
let is_new = init_size.is_none() && current_size.read().is_none();
let is_stacked = self.dependency.is_some()
&& (is_new || (at_rest && self.dependency == *last_dependency.read()));
let global_area = (!is_stacked).then_some(area);
let id = self.id.clone();
let show = self.show;
rect()
.a11y_focusable(false)
.on_sized(move |e: Event<SizedEventData>| {
if !show || *current_size.peek() == Some(e.area) {
return;
}
positions
.ids
.write()
.insert(id.clone(), (e.area, dependency));
let dependency_changed =
dependency.is_none() || *last_dependency.peek() != dependency;
last_dependency.set_if_modified(dependency);
let animate =
dependency_changed && (init_size.is_some() || current_size.peek().is_some());
previous_size.set(current_size());
current_size.set(Some(e.area));
should_animate.set_if_modified(animate);
spawn(async move {
if animate {
animation.start();
} else {
animation.finish();
}
});
})
.width(self.layout.width.clone())
.height(self.layout.height.clone())
.child(
rect()
.map(global_area, |el, area| {
el.offset_x(area.min_x())
.offset_y(area.min_y())
.position(Position::new_global())
})
.child(
rect()
.width(global_area.map_or(Size::fill(), |area| Size::px(area.width())))
.height(
global_area.map_or(Size::fill(), |area| Size::px(area.height())),
)
.opacity(if is_stacked || !is_new { 1. } else { 0. })
.children(if self.show {
self.children.clone()
} else {
vec![]
}),
),
)
}
fn render_key(&self) -> DiffKey {
self.key.clone().or(self.default_key())
}
}
#[derive(Clone)]
pub struct PortalsMap<T: Clone + PartialEq + 'static> {
pub ids: State<HashMap<T, (Area, Option<u64>)>>,
}