use std::cell::RefCell;
use day_reactive::{Scope, Signal};
use day_spec::SizeClass;
use crate::build::Piece;
use crate::{AnyPiece, piece_fn};
use crate::tree::{RNode, with_tree};
#[derive(Clone, Copy)]
struct WindowAmbient {
size_class: Signal<Option<SizeClass>>,
reported_class: Signal<Option<SizeClass>>,
overridden: Signal<bool>,
safe_area: Signal<day_geometry::Insets>,
}
day_reactive::tls_slots! {
ambient;
static AMBIENT: RefCell<Vec<(RNode, WindowAmbient)>> = const { RefCell::new(Vec::new()) };
static SEED: std::cell::Cell<Option<day_geometry::Insets>> = const { std::cell::Cell::new(None) };
}
fn ambient_of(root: RNode) -> WindowAmbient {
AMBIENT.with(|m| {
let mut m = m.borrow_mut();
if let Some((_, a)) = m.iter().find(|(r, _)| *r == root) {
return *a;
}
let ambient = day_reactive::Scope::root().enter(|| WindowAmbient {
size_class: Signal::global(None),
reported_class: Signal::global(None),
overridden: Signal::global(false),
safe_area: Signal::global(initial_safe_area()),
});
m.push((root, ambient));
ambient
})
}
fn initial_safe_area() -> day_geometry::Insets {
if let Some(seed) = SEED.with(|s| s.take()) {
return seed;
}
std::env::var("DAY_SAFE_AREA_TOP")
.ok()
.and_then(|v| v.parse::<f64>().ok())
.map(|top| day_geometry::Insets {
top,
..Default::default()
})
.unwrap_or_default()
}
pub fn seed_safe_area(insets: day_geometry::Insets) {
SEED.with(|s| s.set(Some(insets)));
}
fn target_window() -> RNode {
crate::toolbar::window_being_built()
}
pub fn size_class() -> Option<SizeClass> {
if !crate::tree::has_tree() {
return None;
}
window_size_class(target_window())
}
pub fn window_size_class(root: RNode) -> Option<SizeClass> {
ambient_of(root).size_class.get()
}
pub fn window_size_class_untracked(root: RNode) -> Option<SizeClass> {
ambient_of(root).size_class.get_untracked()
}
pub fn set_window_size_class(root: RNode, class: SizeClass) {
ambient_of(root).reported_class.set(Some(class));
if ambient_of(root).overridden.get_untracked() {
return;
}
let signal = ambient_of(root).size_class;
if signal.get_untracked() == Some(class) {
return;
}
signal.set(Some(class));
with_tree(|t| t.layout_if_needed());
}
pub fn set_size_class(class: SizeClass) {
set_window_size_class(primary_root(), class);
}
pub fn override_size_class(class: SizeClass) {
let root = primary_root();
ambient_of(root).overridden.set(true);
let signal = ambient_of(root).size_class;
if signal.get_untracked() == Some(class) {
return;
}
signal.set(Some(class));
with_tree(|t| t.layout_if_needed());
}
pub fn restore_reported_size_class() {
let root = primary_root();
ambient_of(root).overridden.set(false);
let Some(class) = ambient_of(root).reported_class.get_untracked() else {
return;
};
let signal = ambient_of(root).size_class;
if signal.get_untracked() == Some(class) {
return;
}
signal.set(Some(class));
with_tree(|t| t.layout_if_needed());
}
pub fn safe_area() -> day_geometry::Insets {
window_safe_area(target_window())
}
pub fn window_safe_area(root: RNode) -> day_geometry::Insets {
ambient_of(root).safe_area.get()
}
pub fn set_window_safe_area(root: RNode, insets: day_geometry::Insets) {
ambient_of(root).safe_area.set(insets);
}
pub fn set_safe_area(insets: day_geometry::Insets) {
set_window_safe_area(primary_root(), insets);
}
fn primary_root() -> RNode {
with_tree(|t| t.root_node())
}
pub(crate) fn forget_window(root: RNode) {
AMBIENT.with(|m| m.borrow_mut().retain(|(r, _)| *r != root));
}
pub fn reset_ambient() {
AMBIENT.with(|m| m.borrow_mut().clear());
}
pub fn with_environment<T: Clone + 'static, P: Piece>(
value: T,
content: impl FnOnce() -> P + 'static,
) -> impl Piece {
piece_fn(move |cx| {
let scope = Scope::child();
scope.provide(value);
scope.enter(|| content().build(cx))
})
}
pub fn environment<T: Clone + 'static>() -> Option<T> {
Scope::current().use_context::<T>()
}
pub fn focused_environment<T: Clone + 'static>() -> Option<T> {
crate::windows::focused_scope_chain()
.into_iter()
.find_map(|scope| scope.use_context::<T>())
}
pub fn app_environment<T: Clone + 'static>(make: impl FnOnce() -> T) -> T {
let root = Scope::root();
if let Some(existing) = root.use_context::<T>() {
return existing;
}
let value = root.enter(make);
root.provide(value.clone());
value
}
pub trait Ambient: Clone + 'static {
fn create() -> Self;
fn scoped<P: Piece>(content: impl FnOnce(Self) -> P + 'static) -> AnyPiece
where
Self: Sized,
{
AnyPiece::new(piece_fn(move |cx| {
let scope = Scope::current();
let value = Self::create();
scope.provide(value.clone());
content(value).build(cx)
}))
}
fn app() -> Self
where
Self: Sized,
{
app_environment(Self::create)
}
fn ambient() -> Self
where
Self: Sized,
{
Self::try_ambient().unwrap_or_else(|| {
let t = std::any::type_name::<Self>();
panic!(
"day: no ambient `{t}` in scope. Provide one above this piece with \
`{t}::scoped(…)` (per window) or `{t}::app()` (app-wide). If there IS one, \
this read is running too late: `ambient()` resolves while a piece BUILDS, so \
read it in the piece's body and capture the value rather than calling it \
inside a reactive closure (docs/state.md)."
)
})
}
fn try_ambient() -> Option<Self>
where
Self: Sized,
{
environment::<Self>()
}
fn focused() -> Option<Self>
where
Self: Sized,
{
focused_environment::<Self>()
}
}