Skip to content

Commit

Permalink
Make Environment a Model to allow for environment bindings (#159)
Browse files Browse the repository at this point in the history
* Make Environment a model

* Only remove one model during event handling rather than the entire model list

This allows for a model to query other models attached to the same entity.

This in turn allows for the AppData in the l10n example to query the environment model to toggle locale.

* Remove `needs_rebuild`

* Re-add textbox in l10n example

Also remove debug printing

* Remove unused import
  • Loading branch information
geom3trik authored Jul 18, 2022
1 parent 2e731b7 commit aefe2ca
Show file tree
Hide file tree
Showing 12 changed files with 112 additions and 41 deletions.
2 changes: 0 additions & 2 deletions baseview/src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,8 +189,6 @@ impl WindowHandler for ViziaWindow {
fn on_frame(&mut self, window: &mut Window) {
let context = window.gl_context().expect("Window was created without OpenGL support");

//self.application.rebuild(&self.builder);

self.application.on_frame_update();

unsafe { context.make_current() };
Expand Down
23 changes: 19 additions & 4 deletions core/src/context/draw.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
use femtovg::TextContext;
use morphorm::Units;

use crate::cache::CachedData;
use crate::cache::{BoundingBox, CachedData};
use crate::input::{Modifiers, MouseState};
use crate::prelude::*;
use crate::resource::{ImageOrId, ResourceManager};
use crate::style::LinearGradient;
use crate::text::Selection;

/// A restricted context used when drawing.
///
/// This type is part of the prelude.
/// A context used when drawing.
pub struct DrawContext<'a>(&'a mut Context);

macro_rules! style_getter_units {
Expand Down Expand Up @@ -39,30 +37,37 @@ impl<'a> DrawContext<'a> {
Self(cx)
}

/// Returns the current entity
pub fn current(&self) -> Entity {
self.0.current
}

/// Returns a reference to the cache
pub fn cache(&mut self) -> &mut CachedData {
&mut self.0.cache
}

/// Returns a reference to the tree
pub fn tree(&self) -> &Tree {
&self.0.tree
}

/// Returns a reference to the resource manager
pub(crate) fn resource_manager(&self) -> &ResourceManager {
&self.0.resource_manager
}

/// Returns a reference to the text context
pub fn text_context(&self) -> &TextContext {
&self.0.text_context
}

/// Returns a reference to the mouse state
pub fn mouse(&self) -> &MouseState {
&self.0.mouse
}

/// Returns a reference to the modifiers state
pub fn modifiers(&self) -> &Modifiers {
&self.0.modifiers
}
Expand All @@ -87,6 +92,16 @@ impl<'a> DrawContext<'a> {
logical * self.0.style.dpi_factor as f32
}

/// Returns the current application scale factor
pub fn scale_factor(&self) -> f32 {
self.0.style_ref().dpi_factor as f32
}

/// Returns the bounds of the current entity
pub fn bounds(&self) -> BoundingBox {
self.0.cache_ref().get_bounds(self.current())
}

style_getter_units!(border_width);
style_getter_units!(border_radius_top_right);
style_getter_units!(border_radius_top_left);
Expand Down
20 changes: 11 additions & 9 deletions core/src/context/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ const DOUBLE_CLICK_INTERVAL: Duration = Duration::from_millis(500);
/// This type is part of the prelude.
pub struct Context {
pub(crate) entity_manager: IdManager<Entity>,
tree: Tree,
pub(crate) tree: Tree,
current: Entity,
/// TODO make this private when there's no longer a need to mutate views after building
pub views: FnvHashMap<Entity, Box<dyn ViewHandler>>,
Expand All @@ -59,8 +59,7 @@ pub struct Context {
style: Style,
cache: CachedData,

environment: Environment,

//environment: Environment,
mouse: MouseState,
modifiers: Modifiers,

Expand Down Expand Up @@ -96,7 +95,7 @@ impl Context {
data: SparseSet::new(),
style: Style::default(),
cache,
environment: Environment::new(),
// environment: Environment::new(),
event_queue: VecDeque::new(),
listeners: HashMap::default(),
mouse: MouseState::default(),
Expand All @@ -121,6 +120,8 @@ impl Context {
click_pos: (0.0, 0.0),
};

Environment::new().build(&mut result);

result.entity_manager.create();

result
Expand Down Expand Up @@ -176,8 +177,9 @@ impl Context {
&self.cache
}

pub fn environment(&mut self) -> &mut Environment {
&mut self.environment
pub fn environment(&self) -> &Environment {
//&mut self.environment
self.data::<Environment>().unwrap()
}

/// The current femtovg text context. Useful when measuring or rendering fonts.
Expand Down Expand Up @@ -672,7 +674,7 @@ impl Context {

// Reload the stored themes
for (index, theme) in self.resource_manager.themes.iter().enumerate() {
if !self.environment.include_default_theme && index == 1 {
if !self.environment().include_default_theme && index == 1 {
continue;
}

Expand Down Expand Up @@ -780,7 +782,8 @@ impl Context {
}

pub fn add_translation(&mut self, lang: LanguageIdentifier, ftl: String) {
self.resource_manager.add_translation(lang, ftl)
self.resource_manager.add_translation(lang, ftl);
self.emit(EnvironmentEvent::SetLocale(self.resource_manager.language.clone()));
}

pub fn spawn<F>(&self, target: F)
Expand Down Expand Up @@ -1194,7 +1197,6 @@ impl DataContext for Context {
}

for entity in self.current.parent_iter(&self.tree) {
//println!("Current: {} {:?}", entity, entity.parent(&self.tree));
if let Some(data_list) = self.data.get(entity) {
for (_, model) in data_list.data.iter() {
if let Some(data) = model.downcast_ref::<T>() {
Expand Down
31 changes: 28 additions & 3 deletions core/src/environment.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
use unic_langid::LanguageIdentifier;
use vizia_derive::Lens;

use crate::{context::Context, events::Event, state::Lens, state::Model};

#[derive(Lens)]
pub struct Environment {
pub locale: LanguageIdentifier,
// Signifies whether the app should be rebuilt.
pub include_default_theme: bool,
}
Expand All @@ -11,14 +18,32 @@ impl Default for Environment {

impl Environment {
pub fn new() -> Self {
Self { include_default_theme: true }
Self { locale: LanguageIdentifier::default(), include_default_theme: true }
}
}

pub enum EnvironmentEvent {
IncludeDefaultTheme(bool),
SetLocale(LanguageIdentifier),
//UseSystemLocale,
}

impl Model for Environment {
fn event(&mut self, _: &mut Context, event: &mut Event) {
event.map(|event, _| match event {
EnvironmentEvent::IncludeDefaultTheme(flag) => {
self.include_default_theme = *flag;
}

EnvironmentEvent::SetLocale(locale) => {
self.locale = locale.clone();
}
});
}
}

/// Methods which control the environment the application will run in. This trait is implemented for
/// Application.
///
/// This trait is part of the prelude.
pub trait Env {
fn ignore_default_styles(self) -> Self;
}
20 changes: 10 additions & 10 deletions core/src/events/event_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,16 +136,16 @@ fn visit_entity(context: &mut Context, entity: Entity, event: &mut Event) {
context.views.insert(entity, view);
}

if let Some(mut model_list) = context.data.remove(entity) {
for (_, model) in model_list.data.iter_mut() {
// if event.trace {
// println!("Event: {:?} -> Model {:?}", event, ty);
// }
context.with_current(entity, |cx| {
model.event(cx, event);
});
}
let mut type_ids = Vec::new();
if let Some(model_list) = context.data.get(entity) {
type_ids = model_list.data.iter().map(|(id, _)| id.clone()).collect();
}

context.data.insert(entity, model_list).expect("Failed to insert data");
for type_id in type_ids.iter() {
let mut model = context.data.get_mut(entity).unwrap().data.remove(type_id).unwrap();
context.with_current(entity, |cx| {
model.event(cx, event);
});
context.data.get_mut(entity).unwrap().data.insert(*type_id, model);
}
}
3 changes: 2 additions & 1 deletion core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ pub mod prelude {
pub use super::animation::{AnimExt, Animation, AnimationBuilder};
pub use super::context::{Context, ContextProxy, DataContext, DrawContext, ProxyEmitError};
pub use super::entity::Entity;
pub use super::environment::Env;
pub use super::environment::{Env, Environment, EnvironmentEvent};
pub use super::events::{Event, Message, Propagation};
pub use super::handle::Handle;
pub use super::input::{
Expand All @@ -69,6 +69,7 @@ pub mod prelude {
pub use keyboard_types::{Code, Key};
pub use morphorm::Units::*;
pub use morphorm::{GeometryChanged, LayoutType, PositionType, Units};
pub use unic_langid::LanguageIdentifier;
}

/// One very small function for abstracting debugging between web and desktop programming.
Expand Down
17 changes: 11 additions & 6 deletions core/src/localization/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,8 @@ impl Localized {

impl Res<String> for Localized {
fn get_val(&self, cx: &Context) -> String {
let bundle = cx.resource_manager_ref().current_translation();
let locale = &cx.environment().locale;
let bundle = cx.resource_manager_ref().current_translation(locale);
let message = if let Some(msg) = bundle.get_message(&self.key) {
msg
} else {
Expand Down Expand Up @@ -140,11 +141,15 @@ impl Res<String> for Localized {
where
F: 'static + Clone + Fn(&mut Context, Entity, String),
{
cx.with_current(entity, |cx| {
let lenses = self.args.values().map(|x| x.make_clone()).collect();
let self2 = self.clone();
bind_recursive(cx, &lenses, move |cx| {
closure(cx, entity, self2.get_val(cx));
let self2 = self.clone();
cx.with_current(entity, move |cx| {
Binding::new(cx, Environment::locale, move |cx, _| {
let lenses = self2.args.values().map(|x| x.make_clone()).collect();
let self3 = self2.clone();
let closure = closure.clone();
bind_recursive(cx, &lenses, move |cx| {
closure(cx, entity, self3.get_val(cx));
});
});
});
}
Expand Down
7 changes: 5 additions & 2 deletions core/src/resource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,11 @@ impl ResourceManager {
self.renegotiate_language();
}

pub fn current_translation(&self) -> &FluentBundle<FluentResource> {
self.translations.get(&self.language).unwrap()
pub fn current_translation(
&self,
locale: &LanguageIdentifier,
) -> &FluentBundle<FluentResource> {
self.translations.get(locale).unwrap()
}

pub(crate) fn add_font(&mut self, _name: &str, _path: &str) {}
Expand Down
3 changes: 3 additions & 0 deletions core/src/state/data.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use std::{ptr, rc::Rc, sync::Arc};

use unic_langid::LanguageIdentifier;

use crate::prelude::*;

/// A trait for any type which can be bound to, i.e. can be cached and compared against previous
Expand Down Expand Up @@ -58,6 +60,7 @@ impl_data_simple!(std::net::IpAddr);
impl_data_simple!(std::net::SocketAddr);
impl_data_simple!(std::ops::RangeFull);
impl_data_simple!(std::path::PathBuf);
impl_data_simple!(LanguageIdentifier);

impl_data_simple!(String);

Expand Down
20 changes: 19 additions & 1 deletion examples/localization/l10n.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,21 @@ pub struct AppData {
pub enum AppEvent {
SetName(String),
ReceiveEmail,
ToggleLanguage,
}

impl Model for AppData {
fn event(&mut self, _cx: &mut Context, event: &mut Event) {
fn event(&mut self, cx: &mut Context, event: &mut Event) {
event.map(|app_event, _| match app_event {
AppEvent::SetName(s) => self.name = s.clone(),
AppEvent::ReceiveEmail => self.emails += 1,
AppEvent::ToggleLanguage => {
if cx.environment().locale.to_string() == "en-US" {
cx.emit(EnvironmentEvent::SetLocale("fr".parse().unwrap()));
} else {
cx.emit(EnvironmentEvent::SetLocale("en-US".parse().unwrap()));
}
}
});
}
}
Expand All @@ -34,6 +42,16 @@ fn main() {
AppData { name: "Audrey".to_owned(), emails: 1 }.build(cx);

VStack::new(cx, |cx| {
HStack::new(cx, |cx| {
Checkbox::new(cx, Environment::locale.map(|locale| locale.to_string() == "en-US"))
.on_toggle(|cx| cx.emit(AppEvent::ToggleLanguage));
Label::new(cx, "Toggle Language");
})
.child_top(Stretch(1.0))
.child_bottom(Stretch(1.0))
.col_between(Pixels(20.0))
.height(Auto);

Label::new(cx, Localized::new("hello-world"));
HStack::new(cx, |cx| {
Label::new(cx, Localized::new("enter-name"));
Expand Down
4 changes: 2 additions & 2 deletions examples/resources/fr/hello.ftl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
hello-world = Bonjour, monde!
intro = Bienvenue, { $name }
enter-name = Veuillez saisir votre nom
intro = Bienvenue, { $name }.
enter-name = Veuillez saisir votre nom:
emails =
{ $unread_emails ->
[one] Vous avez un e-mail non lu.
Expand Down
3 changes: 2 additions & 1 deletion winit/src/application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -594,7 +594,8 @@ impl WindowModifiers for Application {
impl Env for Application {
fn ignore_default_styles(mut self) -> Self {
if self.context.environment().include_default_theme {
self.context.environment().include_default_theme = false;
//self.context.environment().include_default_theme = false;
self.context.emit(EnvironmentEvent::IncludeDefaultTheme(false));
self.context.reload_styles().expect("Failed to reload styles");
}

Expand Down

0 comments on commit aefe2ca

Please sign in to comment.