use std::any::{TypeId, type_name};
use std::rc::Rc;
use super::owner::{nearest_declaration, with_scope_declarations};
use super::signal::{RwSignal, create_signal};
pub fn provide_context<T: 'static>(value: T) {
let type_id = TypeId::of::<T>();
let declared = with_scope_declarations(move |declarations| {
assert!(
!declarations
.iter()
.any(|(declared, _)| *declared == type_id),
"`{}` is already declared in this scope.\n\
A scope below this one shadows it, which is how two of a type \
coexist; a second declaration here would replace the first with \
nothing to say it had.",
type_name::<T>()
);
declarations.push((type_id, Rc::new(value)));
});
assert!(
declared.is_some(),
"`{}` was declared where no scope is current.\n\
A value belongs to a scope — the application's setup, a surface, a \
popup, a dynamic list — and an event handler or a spawned task is \
neither. Declare it where the tree is built.",
type_name::<T>()
);
}
pub fn use_context<T: Clone + 'static>() -> Option<T> {
with_context::<T, T>(Clone::clone)
}
pub fn expect_context<T: Clone + 'static>() -> T {
use_context::<T>().unwrap_or_else(|| {
panic!(
"No value of type `{}` is declared in this scope or any above it.\n\
Either nothing declared it, or the read happened somewhere that \
carries no scope of its own — an event handler or a spawned task, \
which read against the root. Read it in the factory body and \
capture the value into the closure.",
type_name::<T>()
)
})
}
pub fn with_context<T: 'static, R>(f: impl FnOnce(&T) -> R) -> Option<R> {
let value = nearest_declaration(TypeId::of::<T>())?;
let value = value
.downcast_ref::<T>()
.expect("a declaration is keyed by the type it holds");
Some(f(value))
}
pub fn has_context<T: 'static>() -> bool {
nearest_declaration(TypeId::of::<T>()).is_some()
}
pub fn provide_signal_context<T: Clone + PartialEq + Send + 'static>(value: T) -> RwSignal<T> {
let signal = create_signal(value);
provide_context(signal);
signal
}
#[cfg(test)]
mod tests {
use std::cell::Cell;
use std::rc::Rc;
use super::*;
use crate::layout::Constraints;
use crate::reactive::owner::{create_root_owner, dispose_owner_now, under_owner, with_owner};
use crate::reactive::signal::create_signal;
use crate::tree::Tree;
use crate::widgets::container;
use crate::widgets::widget::{Event, MouseButton};
fn in_a_child_scope<T>(f: impl FnOnce() -> T) -> T {
let (value, scope) = with_owner(f);
dispose_owner_now(scope);
value
}
#[test]
fn a_scope_reads_what_an_ancestor_declared() {
create_root_owner();
provide_context(7u32);
assert_eq!(in_a_child_scope(use_context::<u32>), Some(7));
}
#[test]
fn a_sibling_scope_does_not_see_the_declaration() {
create_root_owner();
let ((), declaring) = with_owner(|| provide_context(42u32));
let (seen, reading) = with_owner(use_context::<u32>);
assert_eq!(
seen, None,
"a value declared beside this scope is not above it"
);
dispose_owner_now(declaring);
dispose_owner_now(reading);
}
#[test]
fn an_inner_declaration_shadows_an_outer_one_for_its_own_scope() {
create_root_owner();
provide_context(1u32);
let inner = in_a_child_scope(|| {
provide_context(2u32);
use_context::<u32>()
});
assert_eq!(inner, Some(2), "the nearest declaration wins");
assert_eq!(
use_context::<u32>(),
Some(1),
"and the outer scope is untouched by it"
);
}
#[test]
fn two_scopes_declaring_one_type_each_read_their_own() {
create_root_owner();
let ((), first) = with_owner(|| provide_context(create_signal(1i32)));
let ((), second) = with_owner(|| provide_context(create_signal(2i32)));
let from_first = under_owner(first, || expect_context::<RwSignal<i32>>().get());
let from_second = under_owner(second, || expect_context::<RwSignal<i32>>().get());
assert_eq!((from_first, from_second), (1, 2));
dispose_owner_now(first);
dispose_owner_now(second);
}
#[test]
fn a_declaration_dies_with_the_scope_that_made_it() {
create_root_owner();
let ((), popup) = with_owner(|| provide_context(create_signal(1i32)));
dispose_owner_now(popup);
assert!(
use_context::<RwSignal<i32>>().is_none(),
"the handle went with the scope rather than outliving it"
);
}
#[test]
fn a_memo_reads_its_own_scope_on_every_recomputation() {
create_root_owner();
provide_context("the application's".to_string());
let trigger = create_signal(0u32);
let (memo, row) = with_owner(|| {
provide_context("the row's".to_string());
crate::reactive::create_memo(move || {
trigger.get();
use_context::<String>()
})
});
assert_eq!(
memo.get().as_deref(),
Some("the row's"),
"the first computation runs where the memo was created"
);
trigger.set(1);
assert_eq!(
memo.get().as_deref(),
Some("the row's"),
"and so does every one after it"
);
dispose_owner_now(row);
}
#[test]
fn an_effect_reads_its_own_scope_on_every_run() {
create_root_owner();
provide_context("the application's".to_string());
let trigger = create_signal(0u32);
let seen = Rc::new(Cell::new(None));
let recorder = Rc::clone(&seen);
let ((), row) = with_owner(move || {
provide_context("the row's".to_string());
crate::reactive::create_effect(move || {
trigger.get();
recorder.set(use_context::<String>());
});
});
assert_eq!(seen.take().as_deref(), Some("the row's"), "the first run");
trigger.set(1);
assert_eq!(
seen.take().as_deref(),
Some("the row's"),
"and the run the change scheduled"
);
dispose_owner_now(row);
}
#[test]
fn an_effect_in_a_recycled_slot_reads_its_own_scope_and_not_the_dead_one() {
create_root_owner();
let trigger = create_signal(0u32);
let ((), dead_row) = with_owner(|| {
provide_context("the dead row's".to_string());
crate::reactive::create_effect(move || {
trigger.get();
});
});
dispose_owner_now(dead_row);
let seen = Rc::new(Cell::new(None));
let recorder = Rc::clone(&seen);
let ((), live_row) = with_owner(move || {
provide_context("the live row's".to_string());
crate::reactive::create_effect(move || {
trigger.get();
recorder.set(use_context::<String>());
});
});
assert_eq!(
seen.take().as_deref(),
Some("the live row's"),
"its first run"
);
trigger.set(1);
assert_eq!(
seen.take().as_deref(),
Some("the live row's"),
"and the scheduled one, from a slot the disposed effect used to hold"
);
dispose_owner_now(live_row);
}
#[test]
#[should_panic(expected = "already declared in this scope")]
fn declaring_from_a_property_closure_declares_once_per_read() {
create_root_owner();
let property = crate::reactive::create_derived(|| {
provide_context(1u32);
0u32
});
property.get();
property.get();
}
#[test]
#[should_panic(expected = "no scope is current")]
fn declaring_with_no_scope_current_panics() {
provide_context(1u32);
}
#[test]
#[should_panic(expected = "already declared in this scope")]
fn declaring_one_type_twice_in_a_scope_panics() {
create_root_owner();
provide_context(1u32);
provide_context(2u32);
}
#[test]
fn an_event_handler_reads_against_the_root_and_not_against_the_surface() {
create_root_owner();
provide_context(42u32);
let seen = Rc::new(Cell::new(None));
let from_root = Rc::clone(&seen);
let from_surface = Rc::clone(&seen);
let (widget, surface) = with_owner(move || {
provide_context("the surface's".to_string());
assert_eq!(
use_context::<String>().as_deref(),
Some("the surface's"),
"the factory body is where a read resolves"
);
container().width(80.0).height(20.0).on_click(move || {
from_root.set(Some((use_context::<u32>(), use_context::<String>())));
})
});
let mut tree = Tree::new();
let root = tree.register(Box::new(widget));
tree.with_widget_mut(root, |w, id, tree| w.register_children(tree, id));
tree.layout_widget(root, Constraints::new(0.0, 0.0, 200.0, 100.0));
for event in [
Event::mouse_down(10.0, 10.0, MouseButton::Left),
Event::mouse_up(10.0, 10.0, MouseButton::Left),
] {
tree.dispatch(root, &event);
}
assert_eq!(
seen.take(),
Some((Some(42), None)),
"the handler read against the root: the application's value is \
there, the surface's is not"
);
drop(from_surface);
dispose_owner_now(surface);
}
#[test]
fn a_borrowed_read_walks_the_same_chain() {
create_root_owner();
provide_context(vec![1, 2, 3]);
let sum = in_a_child_scope(|| with_context::<Vec<i32>, _>(|v| v.iter().sum::<i32>()));
assert_eq!(sum, Some(6));
}
#[test]
fn asking_whether_a_type_is_declared_walks_it_too() {
create_root_owner();
assert!(!has_context::<u64>());
provide_context(99u64);
assert!(in_a_child_scope(has_context::<u64>));
}
#[test]
fn a_type_nothing_declared_is_not_found() {
create_root_owner();
assert_eq!(use_context::<String>(), None);
assert_eq!(with_context::<Vec<i32>, _>(|v| v.len()), None);
}
#[test]
#[should_panic(expected = "No value of type")]
fn expecting_a_type_nothing_declared_panics() {
create_root_owner();
expect_context::<f64>();
}
#[test]
fn types_do_not_collide() {
create_root_owner();
provide_context(42u32);
provide_context("hello".to_string());
provide_context(2.72f64);
assert_eq!(use_context::<u32>(), Some(42));
assert_eq!(use_context::<String>(), Some("hello".to_string()));
assert_eq!(use_context::<f64>(), Some(2.72));
}
#[test]
fn a_provided_signal_is_read_back_as_the_read_write_handle() {
create_root_owner();
let signal = provide_signal_context(100i32);
let retrieved = expect_context::<RwSignal<i32>>();
assert_eq!(retrieved.get(), 100);
signal.set(200);
assert_eq!(retrieved.get(), 200);
}
}