use internal::immediate_effects_box;
use raui_core::prelude::*;
use serde::{Deserialize, Serialize};
use std::{cell::RefCell, collections::HashMap, rc::Rc, sync::Arc};
thread_local! {
pub(crate) static STACK: RefCell<Vec<Vec<WidgetNode>>> = Default::default();
pub(crate) static STATES: RefCell<Option<Rc<RefCell<ImmediateStates>>>> = Default::default();
pub(crate) static ACCESS_POINTS: RefCell<Option<Rc<RefCell<ImmediateAccessPoints>>>> = Default::default();
pub(crate) static PROPS_STACK: RefCell<Option<Rc<RefCell<Vec<Props>>>>> = Default::default();
}
#[derive(Default)]
pub struct ImmediateContext {
states: Rc<RefCell<ImmediateStates>>,
access_points: Rc<RefCell<ImmediateAccessPoints>>,
props_stack: Rc<RefCell<Vec<Props>>>,
}
impl ImmediateContext {
pub fn activate(context: &Self) {
STATES.with(|states| {
context.states.borrow_mut().reset();
*states.borrow_mut() = Some(context.states.clone());
});
ACCESS_POINTS.with(|access_points| {
*access_points.borrow_mut() = Some(context.access_points.clone());
});
PROPS_STACK.with(|props_stack| {
*props_stack.borrow_mut() = Some(context.props_stack.clone());
});
}
pub fn deactivate() {
STATES.with(|states| {
*states.borrow_mut() = None;
});
ACCESS_POINTS.with(|access_points| {
if let Some(access_points) = access_points.borrow_mut().as_mut() {
access_points.borrow_mut().reset();
}
*access_points.borrow_mut() = None;
});
PROPS_STACK.with(|props_stack| {
if let Some(props_stack) = props_stack.borrow_mut().as_mut() {
props_stack.borrow_mut().clear();
}
*props_stack.borrow_mut() = None;
});
}
}
#[derive(Default)]
struct ImmediateStates {
data: Vec<DynamicManaged>,
position: usize,
}
impl ImmediateStates {
fn reset(&mut self) {
self.data.truncate(self.position);
self.position = 0;
}
fn alloc<T>(&mut self, mut init: impl FnMut() -> T) -> ManagedLazy<T> {
let index = self.position;
self.position += 1;
if let Some(managed) = self.data.get_mut(index) {
if managed.type_hash() != &TypeHash::of::<T>() {
*managed = DynamicManaged::new(init()).ok().unwrap();
}
} else {
self.data.push(DynamicManaged::new(init()).ok().unwrap());
}
self.data
.get(index)
.unwrap()
.lazy()
.into_typed()
.ok()
.unwrap()
}
}
#[derive(Default)]
struct ImmediateAccessPoints {
data: HashMap<String, DynamicManagedLazy>,
}
impl ImmediateAccessPoints {
fn register<T>(&mut self, id: impl ToString, data: &mut T) -> Lifetime {
let result = Lifetime::default();
self.data
.insert(id.to_string(), DynamicManagedLazy::new(data, result.lazy()));
result
}
fn reset(&mut self) {
self.data.clear();
}
fn access<T>(&self, id: &str) -> ManagedLazy<T> {
self.data
.get(id)
.unwrap()
.clone()
.into_typed()
.ok()
.unwrap()
}
}
#[derive(PropsData, Default, Clone, Serialize, Deserialize)]
#[props_data(raui_core::props::PropsData)]
pub struct ImmediateHooks {
#[serde(default, skip)]
hooks: Vec<fn(&mut WidgetContext)>,
}
impl ImmediateHooks {
pub fn with(mut self, pointer: fn(&mut WidgetContext)) -> Self {
self.hooks.push(pointer);
self
}
}
impl std::fmt::Debug for ImmediateHooks {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct(stringify!(ImmediateHooks))
.finish_non_exhaustive()
}
}
macro_rules! impl_lifecycle_props {
($($id:ident),+ $(,)?) => {
$(
#[derive(PropsData, Default, Clone, Serialize, Deserialize)]
#[props_data(raui_core::props::PropsData)]
pub struct $id {
#[serde(default, skip)]
callback: Option<Arc<dyn Fn() + Send + Sync>>,
}
impl $id {
pub fn new(callback: impl Fn() + Send + Sync + 'static) -> Self {
Self {
callback: Some(Arc::new(callback)),
}
}
}
impl std::fmt::Debug for $id {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct(stringify!($id)).finish_non_exhaustive()
}
}
)+
};
}
impl_lifecycle_props! {
ImmediateOnMount,
ImmediateOnChange,
ImmediateOnUnmount
}
pub fn use_state<T>(init: impl FnMut() -> T) -> ManagedLazy<T> {
STATES.with(|states| {
let states = states.borrow();
let mut states = states
.as_ref()
.unwrap_or_else(|| panic!("You must activate context first for `use_state` to work!"))
.borrow_mut();
states.alloc(init)
})
}
pub fn use_access<T>(id: &str) -> ManagedLazy<T> {
ACCESS_POINTS.with(|access_points| {
let access_points = access_points.borrow();
let access_points = access_points
.as_ref()
.unwrap_or_else(|| panic!("You must activate context first for `use_access` to work!"))
.borrow();
access_points.access(id)
})
}
pub fn use_stack_props<T: PropsData + Clone + 'static>() -> Option<T> {
PROPS_STACK.with(|props_stack| {
if let Some(props_stack) = props_stack.borrow().as_ref() {
for props in props_stack.borrow().iter().rev() {
if let Ok(props) = props.read_cloned::<T>() {
return Some(props);
}
}
}
None
})
}
pub fn use_effects<R>(props: impl Into<Props>, mut f: impl FnMut() -> R) -> R {
begin();
let result = f();
let node = end().pop().unwrap_or_default();
push(
make_widget!(immediate_effects_box)
.merge_props(props.into())
.named_slot("content", node),
);
result
}
pub fn register_access<T>(id: &str, data: &mut T) -> Lifetime {
ACCESS_POINTS.with(|access_points| {
let access_points = access_points.borrow();
let mut access_points = access_points
.as_ref()
.unwrap_or_else(|| panic!("You must activate context first for `use_access` to work!"))
.borrow_mut();
access_points.register(id, data)
})
}
pub fn begin() {
STACK.with(|stack| stack.borrow_mut().push(Default::default()));
}
pub fn end() -> Vec<WidgetNode> {
STACK.with(|stack| stack.borrow_mut().pop().unwrap_or_default())
}
pub fn push(widget: impl Into<WidgetNode>) {
STACK.with(|stack| {
if let Some(widgets) = stack.borrow_mut().last_mut() {
widgets.push(widget.into());
}
});
}
pub fn extend(iter: impl IntoIterator<Item = WidgetNode>) {
STACK.with(|stack| {
if let Some(widgets) = stack.borrow_mut().last_mut() {
widgets.extend(iter);
}
});
}
pub fn pop() -> WidgetNode {
STACK.with(|stack| {
stack
.borrow_mut()
.last_mut()
.and_then(|widgets| widgets.pop())
.unwrap_or_default()
})
}
pub fn reset() {
STACK.with(|stack| {
stack.borrow_mut().clear();
});
PROPS_STACK.with(|props_stack| {
if let Some(props_stack) = props_stack.borrow_mut().as_mut() {
props_stack.borrow_mut().clear();
}
});
}
pub fn apply_key<R>(key: impl ToString, mut f: impl FnMut() -> R) -> R {
let key = key.to_string();
begin();
let result = f();
let mut widgets = end();
match widgets.len() {
0 => {}
1 => {
let mut widget = widgets.pop().unwrap();
if let WidgetNode::Component(widget) = &mut widget {
widget.key = Some(key);
}
push(widget);
}
_ => {
for (index, widget) in widgets.iter_mut().enumerate() {
if let WidgetNode::Component(widget) = widget {
widget.key = Some(format!("{}-{}", key, index));
}
}
extend(widgets);
}
}
result
}
pub fn apply_props<R>(props: impl Into<Props>, mut f: impl FnMut() -> R) -> R {
let props = props.into();
begin();
let result = f();
let mut widgets = end();
for widget in &mut widgets {
if let Some(widget) = widget.props_mut() {
widget.merge_from(props.clone());
}
}
extend(widgets);
result
}
pub fn apply_shared_props<R>(props: impl Into<Props>, mut f: impl FnMut() -> R) -> R {
let props = props.into();
begin();
let result = f();
let mut widgets = end();
for widget in &mut widgets {
if let Some(widget) = widget.shared_props_mut() {
widget.merge_from(props.clone());
}
}
extend(widgets);
result
}
pub fn stack_props<R>(props: impl Into<Props>, mut f: impl FnMut() -> R) -> R {
PROPS_STACK.with(|props_stack| {
if let Some(props_stack) = props_stack.borrow_mut().as_mut() {
props_stack.borrow_mut().push(props.into());
}
});
begin();
let result = f();
extend(end());
PROPS_STACK.with(|props_stack| {
if let Some(props_stack) = props_stack.borrow_mut().as_mut() {
props_stack.borrow_mut().pop();
}
});
result
}
pub fn list_component<R>(
widget: impl Into<WidgetComponent>,
props: impl Into<Props>,
mut f: impl FnMut() -> R,
) -> R {
begin();
let result = f();
let widgets = end();
push(
widget
.into()
.merge_props(props.into())
.listed_slots(widgets),
);
result
}
pub fn content_component<R>(
widget: impl Into<WidgetComponent>,
content_name: &str,
props: impl Into<Props>,
mut f: impl FnMut() -> R,
) -> R {
begin();
let result = f();
let node = end().pop().unwrap_or_default();
push(
widget
.into()
.merge_props(props.into())
.named_slot(content_name, node),
);
result
}
pub fn tuple<R>(mut f: impl FnMut() -> R) -> R {
begin();
let result = f();
let widgets = end();
push(WidgetNode::Tuple(widgets));
result
}
pub fn component(widget: impl Into<WidgetComponent>, props: impl Into<Props>) {
push(widget.into().merge_props(props.into()));
}
pub fn unit(widget: impl Into<WidgetUnitNode>) {
push(widget.into());
}
pub fn make_widgets(context: &ImmediateContext, mut f: impl FnMut()) -> Vec<WidgetNode> {
ImmediateContext::activate(context);
begin();
f();
let result = end();
ImmediateContext::deactivate();
result
}
mod internal {
use super::*;
pub(crate) fn immediate_effects_box(mut ctx: WidgetContext) -> WidgetNode {
for hook in ctx.props.read_cloned_or_default::<ImmediateHooks>().hooks {
hook(&mut ctx);
}
if let Ok(event) = ctx.props.read::<ImmediateOnMount>() {
if let Some(callback) = event.callback.as_ref() {
let callback = callback.clone();
ctx.life_cycle.mount(move |_| {
callback();
});
}
}
if let Ok(event) = ctx.props.read::<ImmediateOnChange>() {
if let Some(callback) = event.callback.as_ref() {
let callback = callback.clone();
ctx.life_cycle.change(move |_| {
callback();
});
}
}
if let Ok(event) = ctx.props.read::<ImmediateOnUnmount>() {
if let Some(callback) = event.callback.as_ref() {
let callback = callback.clone();
ctx.life_cycle.unmount(move |_| {
callback();
});
}
}
unpack_named_slots!(ctx.named_slots => content);
AreaBoxNode {
id: ctx.id.to_owned(),
slot: Box::new(content),
}
.into()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn run(frame: usize) {
let show_slider = use_state(|| false);
let mut show_slider = show_slider.write().unwrap();
let show_text_field = use_state(|| false);
let mut show_text_field = show_text_field.write().unwrap();
if frame == 1 {
*show_slider = true;
} else if frame == 3 {
*show_text_field = true;
} else if frame == 5 {
*show_slider = false;
} else if frame == 7 {
*show_text_field = false;
} else if frame == 9 {
*show_slider = true;
*show_text_field = true;
}
println!(
"* #{} | HOVERED: {} | CLICKED: {}",
frame, *show_slider, *show_text_field
);
if *show_slider {
slider();
}
if *show_text_field {
text_field();
}
}
fn slider() {
let value = use_state(|| 0.0);
let mut state = value.write().unwrap();
*state += 0.1;
println!("* SLIDER VALUE: {}", *state);
}
fn text_field() {
let text = use_state(|| String::default());
let mut text = text.write().unwrap();
text.push('z');
println!("* TEXT FIELD: {}", text.as_str());
}
#[test]
fn test_use_state() {
let context = ImmediateContext::default();
for frame in 0..12 {
ImmediateContext::activate(&context);
run(frame);
ImmediateContext::deactivate();
}
}
}