Environment
First, we note that most projects can get away with using the standard environment entirely and that modification is only necessary in some niche scenarios.
Nevertheless, environments are another method for passing information. They come in two flavors: the constant environment and variable environment.
As a comment, for a given window all IVPs and VPs must be implemented with respect to the window's environment. Theoretically separate windows can have separate environment types, but this is uncommon.
Constant Environment
The constant environment is context that is passed to the entire view hierarchy
of a window. This can be used to pass references to bindings, menu channels, portals,
color schemes, and anything else you find relevant. Note that the value of the
constant environment is specified in the root_environment()
function of your
Environment
implementation.
The main way the constant environment is used is in the into_view_provider
function.
Namely, whenever a struct is converted into a view provider, it can use the constant
environment freely. This way, you do not have to have the caller of the IVP explicitly
provide you with e.g. the MenuChannel, but can instead query it during conversion time.
A final note is that despite being called the constant environment, it's theoretically possible to change it over time (but it will still be observed the same by all views for any given timestamp). However, this is recommended against and may cause logical errors.
Here's a (snipped) section of Quarve's library code where we take advantage of the constant environment to gain access to the appropriate channels that we need. In particular, for each page of a TextView, we need to be able to respond to events such as select all.
#![allow(unused)] fn main() { fn into_view_provider(self, env: &E::Const, _s: MSlock) -> impl ViewProvider<...> { // use the standard env to find the appropriate menu channel // that a page needs to respond to let env = env.as_ref(); PageVP { /- snip -/ select_all_menu: env.channels.select_all_menu.clone(), cut_menu: env.channels.cut_menu.clone(), copy_menu: env.channels.copy_menu.clone(), paste_menu: env.channels.paste_menu.clone(), } } }
Variable Envrionment
While the constant environment is uniform throughout the view tree, the variable environment can be changed for certain subtrees. You can think about it as each view in the tree has its own copy of the variable environment personalized to itself (of course, this is not how it is actually implemented for efficiency reasons).
This is useful because it allows you to configure settings for only portions
of the view hierarchy. For instance, the .text_size
modifier is actually an environment
modifier that sets a special variable in the environment to the specified size.
Then, text related views in this subtree read this value from the environment
and consequently render their text appropriately. This highlights how variable
environment modifiers allow you to give information to many views at once,
which is much nicer than manually specifying the text size for every single
label, in this example.
#![allow(unused)] fn main() { fn var_env_example() -> impl IVP { vstack() .push(text("rabbit")) .push(text("bunny")) .push( text("strawberry") // explicitly override the parent environment modifier .text_size(20) ) // set the text size for this entire subtree // note that vstack is not even text related, // but we can still apply the modifier here! .text_size(14) } }
Advanced: You can write your own environment modifier by implementing the
EnvironmentModifier
trait. These work by having methods to apply the changes
of this modifier to the environment, and a corresponding one to undo the changes
(thereby restoring the environment to its original state). To actually use the modifier,
call .env_modifier(<your modifier>)
on the target IVP. Of course, feel free to
create a convenience modifier to simplify the syntax.