waterui_core/env.rs
1//! Environment management module for sharing data across views.
2//!
3//! This module provides functionality for creating and managing environment contexts
4//! that can be passed through the view hierarchy. The environment is a type-based
5//! key-value store where types serve as unique keys.
6//!
7//! The main components are:
8//! - `Environment`: A store for typed values that can be passed between views
9//! - `UseEnv`: A view that allows consuming environment values
10//! - `With`: A view that extends the environment with additional values
11//!
12//! # Example
13//!
14//! ```rust
15//! use waterui_core::{Environment,env::use_env};
16//!
17//! // Create an environment with a string value
18//! let env = Environment::new().with(String::from("Hello, world!"));
19//!
20//! // Access the value in a child view
21//! // let view = use_env(|env: &Environment| {
22//! // if let Some(message) = env.get::<String>() {
23//! // message
24//! // } else {
25//! // "No message found".to_string()
26//! // }
27//! // });
28//! ```
29
30use core::{
31 any::{Any, TypeId},
32 fmt::Debug,
33 marker::PhantomData,
34};
35
36use alloc::{collections::BTreeMap, rc::Rc};
37
38/// An `Environment` stores a map of types to values.
39///
40/// Each type can have at most one value in the environment. The environment
41/// is used to pass contextual information from parent views to child views.
42///
43/// # Examples
44///
45/// ```
46/// use waterui_core::Environment;
47///
48/// let mut env = Environment::new();
49/// env.insert(String::from("hello"));
50///
51/// // Get the value back
52/// assert_eq!(env.get::<String>(), Some(&String::from("hello")));
53///
54/// // Remove the value
55/// env.remove::<String>();
56/// assert_eq!(env.get::<String>(), None);
57/// ```
58#[derive(Debug, Clone, Default)]
59pub struct Environment {
60 map: BTreeMap<TypeId, Rc<dyn Any>>,
61}
62
63impl MetadataKey for Environment {}
64
65use crate::{
66 View,
67 components::Metadata,
68 handler::{HandlerFnOnce, HandlerOnce, IntoHandlerOnce},
69 metadata::MetadataKey,
70 plugin::Plugin,
71 view::{Hook, ViewConfiguration},
72};
73
74/// A type-indexed storage container for values in an environment.
75///
76/// This struct allows storing values of any type `V` indexed by a type key `K`.
77#[derive(Debug)]
78pub struct Store<K, V> {
79 key: PhantomData<K>,
80 value: V,
81}
82
83impl<K, V> Store<K, V> {
84 /// Creates a new store with the given value.
85 #[must_use]
86 pub const fn new(value: V) -> Self {
87 Self {
88 key: PhantomData,
89 value,
90 }
91 }
92
93 /// Returns a reference to the stored value.
94 #[must_use]
95 pub const fn value(&self) -> &V {
96 &self.value
97 }
98}
99
100impl Environment {
101 /// Creates a new empty environment.
102 #[must_use]
103 pub const fn new() -> Self {
104 Self {
105 map: BTreeMap::new(),
106 }
107 }
108
109 /// Stores a value in the environment indexed by type `K`.
110 ///
111 /// # Arguments
112 /// * `value` - The value to store
113 #[must_use]
114 pub fn store<K: 'static, V: 'static>(mut self, value: V) -> Self {
115 self.insert(Store {
116 key: PhantomData::<V>,
117 value,
118 });
119 self
120 }
121
122 /// Queries for a value in the environment indexed by type `K`.
123 ///
124 /// # Returns
125 /// An optional reference to the stored value
126 #[must_use]
127 pub fn query<K: 'static, V: 'static>(&self) -> Option<&V> {
128 self.get::<Store<K, V>>().map(|s| &s.value)
129 }
130
131 /// Installs a plugin into the environment.
132 ///
133 /// Plugins can register values or modifiers that will be available to all views.
134 pub fn install(&mut self, plugin: impl Plugin) -> &mut Self {
135 plugin.install(self);
136 self
137 }
138
139 /// Inserts a value into the environment.
140 ///
141 /// If a value of the same type already exists, it will be replaced.
142 pub fn insert<T: 'static>(&mut self, value: T) {
143 self.map.insert(TypeId::of::<T>(), Rc::new(value));
144 }
145
146 /// Inserts a view configuration hook into the environment.
147 ///
148 /// Hooks allow you to intercept and modify view configurations globally.
149 pub fn insert_hook<T: ViewConfiguration, V: View>(
150 &mut self,
151 hook: impl Fn(&Self, T) -> V + 'static,
152 ) {
153 self.insert(Hook::new(hook));
154 }
155
156 /// Removes a value from the environment by its type.
157 pub fn remove<T: 'static>(&mut self) {
158 self.map.remove(&TypeId::of::<T>());
159 }
160
161 /// Adds a value to the environment and returns the modified environment.
162 ///
163 /// This is a fluent interface for chaining multiple additions.
164 pub fn with<T: 'static>(&mut self, value: T) -> &mut Self {
165 self.insert(value);
166 self
167 }
168
169 /// Retrieves a reference to a value from the environment by its type.
170 ///
171 /// Returns `None` if no value of the requested type exists.
172 ///
173 /// # Panics
174 ///
175 /// This function will panic if a value of the requested type exists in the environment,
176 /// but the stored value cannot be downcast to the requested type. This should never happen
177 /// if only `insert` and `with` are used to add values.
178 #[must_use]
179 #[allow(clippy::coerce_container_to_any)]
180 pub fn get<T: 'static>(&self) -> Option<&T> {
181 self.map
182 .get(&TypeId::of::<T>())
183 .map(|v| v.downcast_ref::<T>().expect("failed to downcast value"))
184 }
185}
186
187/// A view that provides access to the environment.
188///
189/// `UseEnv` allows child views to access values stored in the environment
190/// through a handler function.
191#[derive(Debug, Clone)]
192pub struct UseEnv<V, H> {
193 handler: H,
194 _marker: PhantomData<V>,
195}
196
197impl<V, H> UseEnv<V, H> {
198 /// Creates a new `UseEnv` with the provided handler.
199 #[must_use]
200 pub const fn new(handler: H) -> Self {
201 Self {
202 handler,
203 _marker: PhantomData,
204 }
205 }
206}
207
208/// Creates a view that can access the environment.
209///
210/// This function takes a closure that receives a reference to the environment
211/// and returns a view. It's a convenience wrapper around `UseEnv`.
212#[must_use]
213pub const fn use_env<P, V, F>(f: F) -> UseEnv<V, IntoHandlerOnce<F, P, V>>
214where
215 V: View,
216 F: HandlerFnOnce<P, V>,
217{
218 UseEnv::new(IntoHandlerOnce::new(f))
219}
220
221impl<V, H> View for UseEnv<V, H>
222where
223 V: View,
224 H: HandlerOnce<V>,
225{
226 fn body(self, env: &Environment) -> impl View {
227 self.handler.handle(env)
228 }
229}
230
231/// A view that extends the environment with an additional value.
232///
233/// `With` wraps a child view and provides an extended environment that
234/// includes a new value of type `T`.
235#[derive(Debug, Clone)]
236pub struct With<V, T> {
237 content: V,
238 value: T,
239}
240
241impl<V: View, T: 'static> With<V, T> {
242 /// Creates a new `With` view that wraps the provided content and adds
243 /// the given value to the environment for all child views.
244 pub const fn new(content: V, value: T) -> Self {
245 Self { content, value }
246 }
247}
248
249/// Wraps a view and provides an extended environment value.
250pub const fn with<V: View, T: 'static>(view: V, value: T) -> With<V, T> {
251 With::new(view, value)
252}
253
254impl<V: View, T: 'static> View for With<V, T> {
255 fn body(self, env: &Environment) -> impl View {
256 let mut env = env.clone();
257 env.insert(self.value);
258 Metadata::new(self.content, env)
259 }
260}