├── .gitattributes ├── .gitignore ├── .travis.yml ├── Cargo.toml ├── LICENSE ├── README.md ├── data_bindings ├── Cargo.toml └── src │ ├── context │ ├── manager.rs │ ├── mod.rs │ └── proxies.rs │ ├── lib.rs │ ├── lookup.rs │ ├── macros.rs │ └── store │ ├── cast.rs │ ├── impls.rs │ ├── mod.rs │ └── value.rs ├── docs ├── action.md ├── index.md └── languages_reference │ ├── intro.md │ ├── properties_list.md │ └── tag_list.md ├── examples ├── btn.png ├── list.deps ├── list.markup ├── list.rs ├── list.style ├── menu.deps ├── menu.markup ├── menu.rs ├── menu.style ├── shop_inventory.deps ├── shop_inventory.markup ├── shop_inventory.rs ├── shop_inventory.style └── util │ └── mod.rs ├── mkdocs.yml ├── parsers ├── Cargo.toml └── src │ ├── deps │ ├── mod.rs │ └── parser.rs │ ├── lib.rs │ ├── markup │ ├── lib.rs │ ├── mod.rs │ ├── parser.rs │ └── tags.rs │ ├── parsing │ ├── bufferconsumer.rs │ ├── error.rs │ └── mod.rs │ ├── report.rs │ └── style │ ├── mod.rs │ └── parser.rs ├── shared ├── Cargo.toml └── src │ ├── asset.rs │ ├── deps.rs │ ├── lib.rs │ ├── markup.rs │ ├── properties.rs │ ├── resource.rs │ └── style.rs └── src ├── data_bindings ├── buffer.rs └── mod.rs ├── deps.rs ├── focus ├── direction │ ├── down.rs │ ├── left.rs │ ├── mod.rs │ ├── right.rs │ └── up.rs ├── mod.rs └── tagged_tree.rs ├── layout ├── boxes │ ├── buffer │ │ ├── mod.rs │ │ ├── repeat_node.rs │ │ └── simple_node.rs │ └── mod.rs ├── dim.rs ├── mod.rs └── rect.rs ├── lib.rs ├── markup └── mod.rs ├── rendering ├── backend │ ├── glutinglium.rs │ └── mod.rs ├── mod.rs ├── render.rs └── view.rs ├── resource.rs ├── router.rs ├── state └── mod.rs ├── style └── mod.rs └── util ├── buffer.rs ├── flat_tree ├── buffer.rs └── mod.rs └── mod.rs /.gitattributes: -------------------------------------------------------------------------------- 1 | *.png -delta 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled files 2 | *.o 3 | *.so 4 | *.rlib 5 | *.dll 6 | 7 | # Executables 8 | *.exe 9 | 10 | # Generated by Cargo 11 | target/ 12 | Cargo.lock 13 | 14 | # Project files 15 | .settings -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: nightly 3 | 4 | addons: 5 | apt: 6 | packages: 7 | - libxxf86vm-dev 8 | - libosmesa6-dev 9 | 10 | script: 11 | - cargo build -v 12 | - cd ./shared/ 13 | - cargo test -v 14 | - cd ../parsers/ 15 | - cargo test -v 16 | - cd ../data_bindings 17 | - cargo test -v 18 | - cd .. 19 | - cargo test -v 20 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | 2 | [package] 3 | name = "oil" 4 | version = "0.1.0" 5 | authors = ["Nemikolh ", "Vaelden "] 6 | homepage = "http://oil-lang.github.io/" 7 | repository = "https://github.com/oil-lang/oil-rs" 8 | readme = "README.md" 9 | license = "MIT" 10 | keywords = ["ui", "opengl", "gamedev"] 11 | description = "User interface library oriented for games. It includes user interface languages." 12 | exclude = [ 13 | ".travis.yml", 14 | "examples/*", 15 | "docs/*", 16 | "mkdocs.yml" 17 | ] 18 | 19 | [profile.release] 20 | opt-level = 3 21 | debug = true 22 | rpath = false 23 | lto = false 24 | debug-assertions = false 25 | 26 | [dependencies] 27 | bitflags = "0.1" 28 | phf = "*" 29 | phf_macros = "*" 30 | image = "0.3.9" 31 | cgmath = "0.2.0" # Needs to be the same version than the one used by glium. 32 | num = "*" 33 | 34 | [dependencies.oil_parsers] 35 | path = "./parsers" 36 | version = "=0.1.0" 37 | 38 | [dependencies.oil_shared] 39 | path = "./shared" 40 | version = "=0.1.0" 41 | 42 | [dependencies.oil_databindings] 43 | path = "./data_bindings" 44 | version= "=0.2.0" 45 | 46 | [dependencies.glium] 47 | version = "=0.4.0" 48 | features = ["image", "cgmath", "gl_read_buffer", "gl_depth_textures"] 49 | default-features = false 50 | 51 | [dev-dependencies.glutin] 52 | version = "0.1.4" 53 | 54 | [dev-dependencies] 55 | clock_ticks = "*" 56 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 uil-lang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # oil-rs [![Build Status](https://travis-ci.org/oil-lang/oil-rs.svg?branch=master)](https://travis-ci.org/oil-lang/oil-rs) 2 | 3 | Oil is a graphical user interface library for Rust with video games in mind. 4 | It is designed around three languages to describe your user interface: 5 | 6 | * A markup language 7 | * A style language 8 | * A dependency description language 9 | 10 | Now you've got it right? It definitely looks pretty similar to HTML and CSS. 11 | Of course there's a non goal of redoing a web browser here. That's not the point. 12 | 13 | This library's goals are completely different from a web browser engine such as [servo](https://github.com/servo/servo). 14 | The key idea behind familiarity is the ease of learning while bringing *(trying)* the good part 15 | from web development for game development with Rust. 16 | 17 | Okay, now a few more things to keep in mind before getting started: 18 | 19 | * The library is young and still in its early development stage. Don't expect speed yet. 20 | * A video game in development is currently using Uil, leading the design decisions for Uil. 21 | It essentially means some feature might be set as lower/higher priority because of the main project. 22 | * Contributions are welcomed ! 23 | 24 | ## [Getting-started](http://oil-lang.github.io/#getting-started) 25 | 26 | ```toml 27 | [dependencies] 28 | oil = "*" 29 | ``` 30 | 31 | For a concrete example, you should have a look at the examples in the `examples/` folder. 32 | 33 | ## Roadmap 34 | 35 | This library does not allow to do many things right now. In the future, you'll have: 36 | 37 | * fonts support 38 | * User events such as mouse/key 39 | * Data-bindings 40 | * Animations 41 | -------------------------------------------------------------------------------- /data_bindings/Cargo.toml: -------------------------------------------------------------------------------- 1 | 2 | [package] 3 | name = "oil_databindings" 4 | version = "0.2.0" 5 | authors = ["Nemikolh ", "Vaelden "] 6 | homepage = "http://oil-lang.github.io/" 7 | repository = "https://github.com/oil-lang/oil-rs" 8 | license = "MIT" 9 | keywords = ["ui", "data-bindings"] 10 | description = "Data-bindings crate for oil." 11 | 12 | [dependencies] 13 | mopa = "0.2.0" 14 | num = "*" -------------------------------------------------------------------------------- /data_bindings/src/context/manager.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use { 4 | StoreValue, 5 | Store, 6 | PropertyAccessor, 7 | AttributeSetResult, 8 | AttributeGetResult, 9 | AttributeMutResult 10 | }; 11 | use store::StoreValueStatic; 12 | use super::Context; 13 | 14 | use store::AssignFromCast; 15 | use DataBindingsContext; 16 | 17 | /// The `ContextManager` is templated by a class implementing 18 | /// the `Store` trait. It will then be your only way to store 19 | /// values that can be bind and used in views. 20 | #[derive(Default)] 21 | pub struct ContextManager { 22 | global: G, 23 | views: HashMap, 24 | } 25 | 26 | impl ContextManager 27 | where G: Store + Default, 28 | V: Store 29 | { 30 | 31 | /// Create a new context manager using the specified model. 32 | /// 33 | pub fn new(store: G) -> ContextManager { 34 | ContextManager { 35 | global: store, 36 | views: HashMap::new() 37 | } 38 | } 39 | 40 | /// Insert a root `Store` for the given view. 41 | /// **Note:** 42 | /// With the `ContextManager`, all views have a store of the same type. 43 | /// But if this is not what you want, you can implement 44 | /// the trait `...` TODO(Nemikolh) 45 | pub fn insert_view_level_store(&mut self, view_name: String, store: V) { 46 | self.views.insert(view_name, store); 47 | } 48 | 49 | /// Returns the global store with mutable access. 50 | pub fn get_global_store_mut(&mut self) -> &mut G { 51 | &mut self.global 52 | } 53 | 54 | /// Returns the store associated with the given view name. 55 | pub fn get_view_store<'a>(&'a self, view_name: &String) -> Option<&'a V> { 56 | self.views.get(view_name) 57 | } 58 | 59 | /// Returns the store associated with the given view name with mutable access. 60 | pub fn get_view_store_mut<'a>(&'a mut self, view_name: &String) -> Option<&'a mut V> { 61 | self.views.get_mut(view_name) 62 | } 63 | 64 | /// Equivalent to the get_attribute of the `Store` trait. 65 | pub fn get_attribute<'a>(&'a self, view: &str, key: &str) -> Option> { 66 | match self.views.get(view) { 67 | Some(store) => 68 | if let AttributeGetResult::PrimitiveType(sv) = store 69 | .get_attribute(PropertyAccessor::new(key)) { 70 | return Some(sv); 71 | }, 72 | _ => () 73 | } 74 | if let AttributeGetResult::PrimitiveType(sv) = self.global 75 | .get_attribute(PropertyAccessor::new(key)) { 76 | Some(sv) 77 | } else { 78 | None 79 | } 80 | } 81 | 82 | /// Equivalent to the `set_attribute` of the `Store` trait. 83 | /// TODO(Nemikolh): Move that comment for the trait `...` 84 | /// The view argument can be ignored. It offers the possibility 85 | /// To have different root stores per view. 86 | pub fn set_attribute<'a>(&mut self, view: &str, key: &str, value: StoreValue<'a>) { 87 | if let AttributeSetResult::NoSuchProperty(sv) = self.views.get_mut(view) 88 | .unwrap() 89 | .set_attribute(PropertyAccessor::new(key), value) { 90 | if let AttributeSetResult::NoSuchProperty(_) = self.global 91 | .set_attribute(PropertyAccessor::new(key), sv) { 92 | // Insertion failed. 93 | } 94 | } 95 | } 96 | } 97 | 98 | /// Implement `Context` equivalent method per view, 99 | /// if the type `V` implement `Context`. 100 | impl ContextManager 101 | where V: Context, 102 | G: Default 103 | { 104 | pub fn register_store_for_view( 105 | &mut self, 106 | view_name: String, 107 | store_name: String, 108 | store: S) { 109 | self.views.get_mut(&view_name).unwrap().register_store(store_name, store); 110 | } 111 | 112 | pub fn register_value_for_view>( 113 | &mut self, 114 | view_name: String, 115 | store_name: String, 116 | value: M) { 117 | self.views.get_mut(&view_name).unwrap().register_value(store_name, value); 118 | } 119 | } 120 | 121 | /// Implement `Context` equivalent method for the global data, 122 | /// if the type `G` implement `Context`. 123 | impl ContextManager 124 | where G: Context + Default 125 | { 126 | pub fn register_global_store( 127 | &mut self, 128 | store_name: String, 129 | store: S) 130 | { 131 | self.global.register_store(store_name, store); 132 | } 133 | 134 | 135 | pub fn register_global_value>( 136 | &mut self, 137 | store_name: String, 138 | value: M) 139 | { 140 | self.global.register_value(store_name, value); 141 | } 142 | } 143 | 144 | // ======================================== // 145 | // IMPLS // 146 | // ======================================== // 147 | 148 | impl DataBindingsContext for ContextManager 149 | where G: Store + Default, 150 | V: Store 151 | { 152 | fn get_view_context<'a>(&'a self, view: &String) -> ViewContext<'a> { 153 | ViewContext { 154 | view_context: self.get_view_store(view).map(|vs| vs as &Store), 155 | global: &self.global as &Store 156 | } 157 | } 158 | 159 | fn get_view_context_mut<'a>(&'a mut self, view: &String) -> ViewContextMut<'a> { 160 | ViewContextMut { 161 | view_context: self.views.get_mut(view).map(|vs| vs as &mut Store), 162 | global: &mut self.global as &mut Store 163 | } 164 | } 165 | } 166 | 167 | pub struct ViewContext<'a> { 168 | view_context: Option<&'a Store>, 169 | global: &'a Store, 170 | } 171 | 172 | pub struct ViewContextMut<'a> { 173 | view_context: Option<&'a mut Store>, 174 | global: &'a mut Store, 175 | } 176 | 177 | impl<'a> ViewContext<'a> { 178 | pub fn get_attribute(&'a self, property_path: &str) -> Option> { 179 | match self.view_context { 180 | Some(ref store) => 181 | if let AttributeGetResult::PrimitiveType(sv) = store 182 | .get_attribute(PropertyAccessor::new(property_path)) { 183 | return Some(sv); 184 | }, 185 | _ => () 186 | } 187 | if let AttributeGetResult::PrimitiveType(sv) = self.global 188 | .get_attribute(PropertyAccessor::new(property_path)) { 189 | Some(sv) 190 | } else { 191 | None 192 | } 193 | } 194 | } 195 | 196 | impl<'a> ViewContextMut<'a> { 197 | pub fn get_attribute(&'a mut self, property_path: &str) -> Option<&'a mut AssignFromCast> { 198 | match self.view_context { 199 | Some(ref mut store) => 200 | if let AttributeMutResult::PrimitiveType(sv) = store 201 | .get_attribute_mut(PropertyAccessor::new(property_path)) { 202 | return Some(sv); 203 | }, 204 | _ => () 205 | } 206 | if let AttributeMutResult::PrimitiveType(sv) = self.global 207 | .get_attribute_mut(PropertyAccessor::new(property_path)) { 208 | Some(sv) 209 | } else { 210 | None 211 | } 212 | } 213 | } 214 | 215 | 216 | // ======================================== // 217 | // TESTS // 218 | // ======================================== // 219 | 220 | #[cfg(test)] 221 | mod test { 222 | } 223 | -------------------------------------------------------------------------------- /data_bindings/src/context/mod.rs: -------------------------------------------------------------------------------- 1 | pub use self::manager::ContextManager; 2 | pub use self::manager::ViewContext; 3 | pub use self::manager::ViewContextMut; 4 | 5 | mod manager; 6 | //mod proxies; 7 | 8 | //use mopa; 9 | use std::collections::HashMap; 10 | use store::StoreValueStatic; 11 | use { 12 | Store, 13 | StoreValue, 14 | AttributeGetResult, 15 | AttributeMutResult, 16 | AttributeSetResult 17 | }; 18 | use lookup::PropertyAccessor; 19 | 20 | /// This trait is used to provide a possible interface for Context 21 | /// objects managed by the `ContextManager`. It is implemented by 22 | /// the `AmbientModel` to give an example of such a `Context`. 23 | /// **Note:** 24 | /// If the "Context" type for the `ContextManager` implement this trait, 25 | /// then those function can be used also on the `ContextManager`. 26 | pub trait Context { 27 | 28 | /// Register a single value at the key 29 | fn register_value>(&mut self, key: String, value: V); 30 | 31 | /// Register a store: 32 | fn register_store(&mut self, key: String, store: S); 33 | 34 | /// Return a previously registered store: 35 | /// This can be useful when you want to modify an existing store but without 36 | /// retaining a reference to it. 37 | fn get_store_mut(&mut self, key: String) -> Option<&mut Box>; 38 | } 39 | 40 | /// Default version of the `ContextManager` where the template 41 | /// parameter is set to `AmbientModel`. 42 | pub type DefaultContextManager = ContextManager; 43 | 44 | /// An `AmbientModel` instance is a root object that is used 45 | /// by the `DefaultContextManager`. 46 | /// Internally it use a HashMap for single `StoreValue`s 47 | /// and an other HashMap for boxed type implementing the trait `Store`. 48 | #[derive(Default)] 49 | pub struct AmbientModel { 50 | values: HashMap, 51 | stores: HashMap>, 52 | } 53 | 54 | /// Minimal contraint to be used in a `ContextManager`: 55 | /// implement the trait `Store`. 56 | impl Store for AmbientModel { 57 | 58 | fn get_attribute<'a>(&'a self, k: PropertyAccessor) -> AttributeGetResult<'a> { 59 | let value = self.stores.get_attribute(k.clone()); 60 | if value.is_found() { 61 | value 62 | } else { 63 | self.values.get_attribute(k) 64 | } 65 | } 66 | 67 | fn get_attribute_mut<'a>(&'a mut self, k: PropertyAccessor) -> AttributeMutResult<'a> { 68 | let value = self.stores.get_attribute_mut(k.clone()); 69 | if value.is_found() { 70 | value 71 | } else { 72 | self.values.get_attribute_mut(k) 73 | } 74 | } 75 | 76 | fn set_attribute<'a>(&mut self, k: PropertyAccessor, value: StoreValue<'a>) -> AttributeSetResult<'a> { 77 | match self.stores.set_attribute(k.clone(), value) { 78 | AttributeSetResult::NoSuchProperty(v) => { 79 | self.values.set_attribute(k, v) 80 | } 81 | _ => AttributeSetResult::Stored 82 | } 83 | } 84 | } 85 | 86 | // Context implementation 87 | impl Context for AmbientModel { 88 | 89 | fn register_value>(&mut self, key: String, value: V) { 90 | self.values.insert(key, value.into()); 91 | } 92 | 93 | fn register_store(&mut self, key: String, store: S) { 94 | self.stores.insert(key, Box::new(store) as Box); 95 | } 96 | 97 | fn get_store_mut(&mut self, key: String) -> Option<&mut Box> { 98 | self.stores.get_mut(&key) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /data_bindings/src/context/proxies.rs: -------------------------------------------------------------------------------- 1 | use std::rc::{Rc,Weak}; 2 | use std::cell::RefCell; 3 | use std::ops::Deref; 4 | use data_bindings::{ 5 | BindingResult, 6 | DataBindingError, 7 | StoreValue, 8 | IsRepeatable, 9 | Store, 10 | ArrStore, 11 | IteratingClosure, 12 | PropertyAccessor, 13 | AttributeSetResult 14 | }; 15 | 16 | 17 | #[derive(Debug)] 18 | pub struct Proxy { 19 | data: Weak>, 20 | } 21 | 22 | impl Store for Proxy 23 | where T: Store { 24 | fn get_attribute(&self, k: PropertyAccessor) -> AttributeGetResult { 25 | match self.data.upgrade() { 26 | None => NoSuchProperty, 27 | Some(p) => p.borrow().get_attribute(k), 28 | } 29 | } 30 | 31 | fn set_attribute(&mut self, k: PropertyAccessor, value: StoreValue) -> AttributeSetResult { 32 | match self.data.upgrade() { 33 | None => NoSuchProperty(value), 34 | Some(p) => p.borrow_mut().set_attribute(k, value), 35 | } 36 | } 37 | } 38 | 39 | impl Proxy { 40 | pub fn new(value: &Rc>) -> Proxy { 41 | Proxy { 42 | data: value.downgrade(), 43 | } 44 | } 45 | } 46 | 47 | 48 | pub struct RepeatProxy { 49 | weak_values: Weak>>, 50 | } 51 | 52 | impl RepeatProxy { 53 | pub fn new(values: &Rc>>) -> RepeatProxy { 54 | RepeatProxy { 55 | weak_values: values.downgrade(), 56 | } 57 | } 58 | } 59 | 60 | impl IsRepeatable for RepeatProxy 61 | where T: Store + ArrStore + 'static { 62 | 63 | fn iter(&self, closure: &mut IteratingClosure) -> bool { 64 | let ref_values = match self.weak_values.upgrade() { 65 | Some(r) => r, 66 | None => return false, 67 | }; 68 | let mut values = ref_values.borrow_mut(); 69 | let mut iter = values.iter_mut().map(|item| item as &mut Store); 70 | closure(&mut iter); 71 | true 72 | } 73 | 74 | fn compare_and_update(&self, k: &str, output: &mut Vec) -> BindingResult { 75 | let ref_values = match self.weak_values.upgrade() { 76 | Some(r) => r, 77 | None => return Err(DataBindingError::DanglingReference(format!(": {}", k))), 78 | }; 79 | let values = ref_values.borrow_mut(); 80 | ArrStore::compare_and_update((*values).deref(), k, output) 81 | } 82 | 83 | fn len(&self) -> BindingResult { 84 | let ref_values = match self.weak_values.upgrade() { 85 | Some(r) => r, 86 | None => return Err(DataBindingError::DanglingReference("".to_string())), 87 | }; 88 | let values = ref_values.borrow(); 89 | Ok((*values).len() as u32) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /data_bindings/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(hashmap_hasher)] 2 | #![feature(reflect_marker)] 3 | #![feature(test)] 4 | extern crate test as bench; 5 | 6 | #[macro_use] 7 | extern crate mopa; 8 | extern crate num; 9 | 10 | // Re-export 11 | #[macro_use] 12 | mod macros; 13 | 14 | pub use self::store::StoreValue; 15 | pub use self::store::Cast; 16 | pub use self::context::DefaultContextManager; 17 | pub use self::lookup::PropertyAccessor; 18 | 19 | //mod error; 20 | pub mod context; 21 | pub mod store; 22 | mod lookup; 23 | 24 | /// Key trait to create a model that support two-ways databindings 25 | /// with oil. 26 | /// 27 | /// The simplest way to implement it, is by using 28 | /// the `declare_data_binding!` macro like this: 29 | /// 30 | /// ```rust 31 | /// # #[macro_use] 32 | /// # extern crate oil_databindings; 33 | /// struct Player { 34 | /// name: String, 35 | /// pv: i64, 36 | /// xp: i64, 37 | /// non_relevant_stuff: usize, 38 | /// } 39 | /// 40 | /// declare_data_binding! { 41 | /// Player { 42 | /// name, 43 | /// pv, 44 | /// xp 45 | /// } 46 | /// } 47 | /// # fn main() { 48 | /// # } 49 | /// ``` 50 | /// 51 | /// The trait `Store` is [mopafied](https://github.com/chris-morgan/mopa) 52 | /// to allow cast(s) to the original type: 53 | /// 54 | /// ```rust 55 | /// # #[macro_use] 56 | /// # extern crate oil_databindings; 57 | /// # use oil_databindings::{PropertyAccessor, StoreValue, Store}; 58 | /// # use oil_databindings::context::AmbientModel; 59 | /// # use oil_databindings::context::Context; 60 | /// # struct Player { 61 | /// # name: String, 62 | /// # pv: i64, 63 | /// # xp: i64, 64 | /// # non_relevant_stuff: usize, 65 | /// # } 66 | /// # 67 | /// # declare_data_binding! { 68 | /// # Player { 69 | /// # name, 70 | /// # pv, 71 | /// # xp 72 | /// # } 73 | /// # } 74 | /// # fn main() { 75 | /// // Creation of an AmbientModel and an instance of the previously defined store 76 | /// let p = Box::new(Player { 77 | /// name: "Bob".to_string(), 78 | /// pv: 10, 79 | /// xp: 0, 80 | /// non_relevant_stuff: 0 81 | /// }); 82 | /// let mut a = AmbientModel::default(); 83 | /// 84 | /// // Registering the player object (giving ownership here) 85 | /// a.register_store("player".to_string(), p); 86 | /// 87 | /// // Checking that the player is correctly registered: 88 | /// { 89 | /// let attribute = a.get_attribute(PropertyAccessor::new("player.name")).unwrap(); 90 | /// match attribute { 91 | /// StoreValue::String(s) => println!("Hello {}!", s), 92 | /// _ => unreachable!(), 93 | /// } 94 | /// } 95 | /// 96 | /// // Get back a mutable reference to the player instance 97 | /// let iam_bob = a.get_store_mut("player".to_string()).unwrap() 98 | /// .downcast_ref::>().unwrap(); 99 | /// assert_eq!(iam_bob.name, "Bob"); 100 | /// assert_eq!(iam_bob.pv, 10); 101 | /// # } 102 | /// ``` 103 | /// 104 | pub trait Store: mopa::Any { 105 | 106 | /// Return the value corresponding to the key 'k'. 107 | /// If no value is found with such a name, the trait implementer 108 | /// will returns `AttributeGetResult`. 109 | fn get_attribute(&self, k: PropertyAccessor) -> AttributeGetResult; 110 | 111 | /// Return the value corresponding to the key 'k' with a mutable access. 112 | /// If no value is found with such a name, the trait implementer 113 | /// will return `AttributeMutResult::NoSuchProperty`. 114 | fn get_attribute_mut(&mut self, k: PropertyAccessor) -> AttributeMutResult; 115 | 116 | /// This method set the value for the attribute named 'k'. 117 | /// For consistency the lookup algorithm should be the 118 | /// same as with `get_attribute`. 119 | fn set_attribute<'a>(&mut self, k: PropertyAccessor, value: StoreValue<'a>) -> AttributeSetResult<'a>; 120 | } 121 | 122 | mopafy!(Store); 123 | 124 | /// Result type when calling `get_attribute` on a `Store` 125 | /// object. 126 | pub enum AttributeGetResult<'a> { 127 | /// This value is returned when the get has succeeded 128 | /// and the value is a `PrimitiveType`. 129 | PrimitiveType(StoreValue<'a>), 130 | /// This value is returned when the get has succeeded 131 | /// and the value is a `IterableType`. 132 | IterableType(Box + 'a>), 133 | /// This value is returned to indicate that there's no such property 134 | /// accessible for the given PropertyAccessor. 135 | NoSuchProperty, 136 | } 137 | 138 | /// Result type when calling `get_attribute_mut` on a `Store` 139 | /// object. 140 | pub enum AttributeMutResult<'a> { 141 | /// This value should be returned when the get has succeeded 142 | /// and the value is a `PrimitiveType`. 143 | PrimitiveType(&'a mut store::AssignFromCast), 144 | /// This value should be returned when the get has succeeded 145 | /// and the value is an `IterableType`. 146 | IterableType(Box + 'a>), 147 | /// Access didn't provide any result. 148 | NoSuchProperty 149 | } 150 | 151 | /// Result type when calling `set_attribute` on a `Store` 152 | /// object. 153 | pub enum AttributeSetResult<'a> { 154 | /// This value should be returned in case of success 155 | /// (the value has been successfully stored) 156 | Stored, 157 | /// In case of failure due to a cast error (see trait `Cast`), return that 158 | /// value instead of `NoSuchProperty`, it means that the PropertyAccessor has 159 | /// managed to find an existing property but there was a type error. 160 | /// It is different from NoSuchProperty because as with Stored it stops 161 | /// the lookup. 162 | /// The main difference with `Stored` is that a warning log will be reported. 163 | /// 164 | /// TODO(Nemikolh): It could be nice if at the oil API level 165 | /// there was a way to configure whether or not the application 166 | /// should panic, log or do nothing. 167 | WrongType, 168 | /// If the lookup failed, the value argument of `set_attribute` must 169 | /// be returned unchanged via this enum value. 170 | NoSuchProperty(StoreValue<'a>) 171 | } 172 | 173 | /// The context that is going to be used by oil. This trait is only here 174 | /// to reduce the burden of using the `ContextManager` and also reduce the amount 175 | /// of changes to performs when refactoring it. 176 | /// This trait only really matters to **oil** developpers. 177 | pub trait DataBindingsContext { 178 | /// Returns a context for the given view that support lookup on `Store` registered in 179 | /// the `ContextManager`. 180 | fn get_view_context<'a>(&'a self, view_name: &String) -> self::context::ViewContext<'a>; 181 | /// Identical to `get_view_context` except that you have a mutable access to the context. 182 | fn get_view_context_mut<'a>(&'a mut self, view_name: &String) -> self::context::ViewContextMut<'a>; 183 | } 184 | 185 | 186 | // ======================================== // 187 | // IMPLS // 188 | // ======================================== // 189 | 190 | impl<'a> AttributeGetResult<'a> { 191 | 192 | pub fn unwrap(self) -> StoreValue<'a> { 193 | match self { 194 | AttributeGetResult::PrimitiveType(s) => s, 195 | _ => panic!(), 196 | } 197 | } 198 | 199 | pub fn unwrap_iter(self) -> Box + 'a> { 200 | match self { 201 | AttributeGetResult::IterableType(it) => it, 202 | _ => panic!(), 203 | } 204 | } 205 | 206 | pub fn is_found(&self) -> bool { 207 | match self { 208 | &AttributeGetResult::PrimitiveType(_) => true, 209 | &AttributeGetResult::IterableType(_) => true, 210 | _ => false 211 | } 212 | } 213 | } 214 | 215 | impl<'a> AttributeMutResult<'a> { 216 | 217 | pub fn unwrap_iter(self) -> Box + 'a> { 218 | match self { 219 | AttributeMutResult::IterableType(it) => it, 220 | _ => panic!(), 221 | } 222 | } 223 | 224 | pub fn is_found(&self) -> bool { 225 | match self { 226 | &AttributeMutResult::PrimitiveType(_) => true, 227 | &AttributeMutResult::IterableType(_) => true, 228 | _ => false 229 | } 230 | } 231 | } 232 | 233 | // ======================================== // 234 | // TESTS // 235 | // ======================================== // 236 | 237 | #[cfg(test)] 238 | mod test { 239 | use super::*; 240 | 241 | #[derive(Debug)] 242 | struct Player { 243 | name: String, 244 | test: Vec, 245 | pv: i64, 246 | xp: i64, 247 | non_relevant_stuff: usize, 248 | } 249 | 250 | impl Player { 251 | fn new(name: T, pv: i64, xp: i64) -> Player { 252 | Player { 253 | name: name.to_string(), 254 | test: Vec::new(), 255 | pv: pv, 256 | xp: xp, 257 | non_relevant_stuff: 0, 258 | } 259 | } 260 | } 261 | 262 | declare_data_binding! { 263 | Player { 264 | name, 265 | test, 266 | pv, 267 | xp 268 | } 269 | } 270 | 271 | #[test] 272 | fn register_global_player() { 273 | let mut context = DefaultContextManager::default(); 274 | let player = Player::new("Grub", 42, 100); 275 | context.register_global_store("player".to_string(), player); 276 | assert_eq!(context.get_attribute("main", "player.pv").unwrap(), StoreValue::Integer(42)); 277 | assert_eq!(context.get_attribute("main", "player.xp").unwrap(), StoreValue::Integer(100)); 278 | } 279 | 280 | #[test] 281 | fn register_global_value() { 282 | let mut context = DefaultContextManager::default(); 283 | context.register_global_value("option.width".to_string(), 42); 284 | assert_eq!(context.get_attribute("main", "option.width").unwrap(), StoreValue::Integer(42)); 285 | } 286 | 287 | // TODO(Nemikolh): Move this test in manager.rs 288 | #[test] 289 | fn lookup_should_pick_store_before_global() { 290 | let mut context = DefaultContextManager::default(); 291 | context.register_global_value("player.pv".to_string(), 12); 292 | assert_eq!(context.get_attribute("main", "player.pv").unwrap(), StoreValue::Integer(12)); 293 | let player = Player::new("Grub", 42, 100); 294 | context.register_global_store("player".to_string(), player); 295 | assert_eq!(context.get_attribute("main", "player.pv").unwrap(), StoreValue::Integer(42)); 296 | } 297 | 298 | } 299 | -------------------------------------------------------------------------------- /data_bindings/src/lookup.rs: -------------------------------------------------------------------------------- 1 | /// A property accessor represent 2 | /// 3 | #[derive(Clone, Debug)] 4 | pub struct PropertyAccessor<'a> { 5 | path: &'a str, 6 | } 7 | 8 | 9 | impl<'a> PropertyAccessor<'a> { 10 | 11 | /// A property accessor can only be created from a path 12 | /// of the form: 13 | /// ```txt 14 | /// . [...] . 15 | /// ``` 16 | /// 17 | pub fn new(path: &'a str) -> PropertyAccessor<'a> { 18 | PropertyAccessor { 19 | path: path, 20 | } 21 | } 22 | 23 | /// Returns the name associated with that property accessor 24 | /// or `None` if the end of the path has been reached. 25 | pub fn name(&self) -> &'a str { 26 | self.path.find('.').map(|i| &self.path[..i]).unwrap_or(self.path) 27 | } 28 | 29 | /// Returns the next property accessor in the path. 30 | /// If the end is reached, then calling name on the property 31 | /// accessor created with that function will return `None`. 32 | pub fn next(&self) -> PropertyAccessor<'a> { 33 | let next = self.path.find('.').unwrap_or(self.path.len() - 1) + 1; 34 | PropertyAccessor { 35 | path: &self.path[next..self.path.len()], 36 | } 37 | } 38 | } 39 | 40 | /// This iterator generate from a PropertyAccessor 41 | /// a sequence of 42 | /// ```txt 43 | /// (String, PropertyAccessor) 44 | /// ``` 45 | /// where the first String is called the "prefix". It is a convenient 46 | /// iterator that works well with a `Map`-like container where keys can 47 | /// contain the separator `.` char. 48 | /// 49 | /// # Example: 50 | /// 51 | /// Given the PropertyAccessor `"settings.gui.window.scale"`, 52 | /// when trying in a Hashmap to access the object behind the property, 53 | /// we're going to try to reach in order: 54 | /// ```txt 55 | /// LocalProperty("settings"), PropertyAccessor("gui.window.scale") 56 | /// LocalProperty("settings.gui"), PropertyAccessor("window.scale") 57 | /// LocalProperty("settings.gui.window"), PropertyAccessor("scale") 58 | /// LocalProperty("settings.gui.window.scale"), PropertyAccessor("") 59 | /// ``` 60 | /// This check only make sense at the Hashmap level because Rust type won't 61 | /// contains in their name a `.`. 62 | pub struct PrefixKeyIter<'a> { 63 | property_full_path: &'a str, 64 | position: usize, 65 | } 66 | 67 | impl <'a> PrefixKeyIter<'a> { 68 | pub fn new(property: PropertyAccessor<'a>) -> PrefixKeyIter<'a> { 69 | PrefixKeyIter { 70 | property_full_path: property.path, 71 | position: 0, 72 | } 73 | } 74 | } 75 | 76 | impl <'a> Iterator for PrefixKeyIter<'a> { 77 | type Item = (&'a str, PropertyAccessor<'a>); 78 | 79 | fn next(&mut self) -> Option<::Item> { 80 | if self.position == usize::max_value() { 81 | None 82 | } else { 83 | let offset = self.property_full_path[self.position..].find('.'); 84 | match offset { 85 | Some(i) => { 86 | let prefix = &self.property_full_path[..self.position+i]; 87 | let property = &self.property_full_path[self.position+i+1..]; 88 | self.position += i + 1; 89 | Some((prefix, PropertyAccessor::new(property))) 90 | } 91 | _ => { 92 | self.position = usize::max_value(); 93 | Some((self.property_full_path, PropertyAccessor::new(""))) 94 | } 95 | } 96 | } 97 | } 98 | } 99 | 100 | 101 | #[cfg(test)] 102 | mod test { 103 | 104 | use super::*; 105 | 106 | #[test] 107 | fn new_should_create_a_property_accessor_with_name_eq_to_some() { 108 | let a = PropertyAccessor::new("test"); 109 | let b = PropertyAccessor::new(""); 110 | assert_eq!(a.name(), "test"); 111 | assert_eq!(b.name(), ""); 112 | } 113 | 114 | #[test] 115 | fn next_should_create_a_property_accessor_returning_the_next_name_in_the_path() { 116 | let a = PropertyAccessor::new("foo.bar"); 117 | let b = a.next(); 118 | assert_eq!(a.name(), "foo"); 119 | assert_eq!(b.name(), "bar"); 120 | assert_eq!(a.name(), "foo"); 121 | } 122 | 123 | #[test] 124 | fn next_with_a_path_of_length_three() { 125 | let a = PropertyAccessor::new("foo.bar.bazz"); 126 | let b = a.next(); 127 | let c = b.next(); 128 | assert_eq!(a.name(), "foo"); 129 | assert_eq!(b.name(), "bar"); 130 | assert_eq!(c.name(), "bazz"); 131 | } 132 | 133 | #[test] 134 | fn prefixkeyiter_should_generate_property_accessor_with_correct_order() { 135 | let a = PrefixKeyIter::new(PropertyAccessor::new("foo.bar.bazz")); 136 | let mut prefixes = Vec::with_capacity(3); 137 | let mut properties = Vec::with_capacity(3); 138 | for (i, p) in a { prefixes.push(i); properties.push(p); } 139 | assert_eq!(prefixes, vec!["foo", "foo.bar", "foo.bar.bazz"]); 140 | let mut it = properties.iter(); 141 | assert_eq!(it.next().unwrap().name(), "bar"); 142 | assert_eq!(it.next().unwrap().name(), "bazz"); 143 | assert_eq!(it.next().unwrap().name(), ""); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /data_bindings/src/macros.rs: -------------------------------------------------------------------------------- 1 | 2 | #[macro_export] 3 | macro_rules! declare_data_binding { 4 | ($name:ident { 5 | $($field:ident),* 6 | }) => ( 7 | impl $crate::Store for $name { 8 | fn get_attribute<'a>(&'a self, k: $crate::PropertyAccessor) 9 | -> $crate::AttributeGetResult<'a> 10 | { 11 | match k.name() { 12 | $(stringify!($field) => self.$field.get_attribute(k.next()),)* 13 | _ => $crate::AttributeGetResult::NoSuchProperty, 14 | } 15 | } 16 | 17 | fn get_attribute_mut<'a>(&'a mut self, k: $crate::PropertyAccessor) 18 | -> $crate::AttributeMutResult<'a> 19 | { 20 | match k.name() { 21 | $(stringify!($field) => self.$field.get_attribute_mut(k.next()),)* 22 | _ => $crate::AttributeMutResult::NoSuchProperty, 23 | } 24 | } 25 | 26 | fn set_attribute<'a>(&mut self, k: $crate::PropertyAccessor, value: $crate::StoreValue<'a>) 27 | -> $crate::AttributeSetResult<'a> 28 | { 29 | match k.name() { 30 | $(stringify!($field) => self.$field.set_attribute(k.next(), value),)* 31 | _ => $crate::AttributeSetResult::NoSuchProperty(value), 32 | } 33 | } 34 | } 35 | ) 36 | } 37 | 38 | /// To apply this macro to your own type, you need to verify 39 | /// the following trait: 40 | /// ```ignore 41 | /// T where T: AsStoreValue + Cast + Reflect + 'static 42 | /// ``` 43 | /// The `impl` couldn't be made generic because of a conflict 44 | /// between this implementation and the generic one for `Box`. 45 | /// TODO(Nemikolh): Was it a compiler bug ? 46 | #[macro_export] 47 | macro_rules! impl_store_for_value_type_like { 48 | ($type_ident:ident) => ( 49 | impl Store for $type_ident 50 | { 51 | 52 | fn get_attribute<'b>(&'b self, k: $crate::PropertyAccessor) -> $crate::AttributeGetResult<'b> { 53 | match k.name() { 54 | "" => { 55 | let attribute: $crate::StoreValue<'b> = self.as_store_value(); 56 | $crate::AttributeGetResult::PrimitiveType(attribute) 57 | } 58 | _ => $crate::AttributeGetResult::NoSuchProperty 59 | } 60 | } 61 | 62 | fn get_attribute_mut<'b>(&'b mut self, k: $crate::PropertyAccessor) -> $crate::AttributeMutResult<'b> { 63 | match k.name() { 64 | "" => { 65 | $crate::AttributeMutResult::PrimitiveType(self as &'b mut $crate::store::AssignFromCast) 66 | } 67 | _ => $crate::AttributeMutResult::NoSuchProperty 68 | } 69 | } 70 | 71 | fn set_attribute<'b>(&mut self, k: $crate::PropertyAccessor, value: $crate::StoreValue<'b>) -> $crate::AttributeSetResult<'b> { 72 | match k.name() { 73 | "" => match ::cast(value) { 74 | Some(c) => { *self = c; $crate::AttributeSetResult::Stored }, 75 | None => $crate::AttributeSetResult::WrongType 76 | }, 77 | _ => $crate::AttributeSetResult::NoSuchProperty(value) 78 | } 79 | } 80 | } 81 | ) 82 | } 83 | -------------------------------------------------------------------------------- /data_bindings/src/store/cast.rs: -------------------------------------------------------------------------------- 1 | use num::ToPrimitive; 2 | use StoreValue; 3 | use store::StoreValueStatic; 4 | use std::ops::Deref; 5 | 6 | /// A type that want to be converted into from a `StoreValue` 7 | /// must implement this trait. 8 | pub trait Cast { 9 | 10 | /// Try to cast the given StoreValue into Self. 11 | /// If the StoreValue has an appropriate type, return 12 | /// `None`. 13 | fn cast(this: StoreValue) -> Option; 14 | } 15 | 16 | /// A type to convert a type into a store value 17 | /// but without moving it. 18 | pub trait AsStoreValue { 19 | 20 | /// Should convert the type into a StoreValue. 21 | /// Note that the lifetime of the StoreValue is bounded 22 | /// by `self`. 23 | fn as_store_value(&self) -> StoreValue; 24 | } 25 | 26 | /// The friend trait of the `Cast` trait. It is automatically implemented 27 | /// and shouldn't be implemented directly. Rely on the `Cast` trait instead. 28 | pub trait AssignFromCast { 29 | 30 | fn assign(&mut self, this: StoreValue); 31 | } 32 | 33 | impl AssignFromCast for T 34 | where T: Cast 35 | { 36 | fn assign(&mut self, this: StoreValue) { 37 | match ::cast(this) { 38 | Some(v) => *self = v, 39 | None => (), 40 | } 41 | } 42 | } 43 | 44 | impl Cast for StoreValueStatic { 45 | fn cast(this: StoreValue) -> Option { 46 | match this { 47 | StoreValue::String(s) => Some(StoreValueStatic::String(s.to_string())), 48 | StoreValue::Integer(i) => Some(StoreValueStatic::Integer(i)), 49 | StoreValue::Boolean(b) => Some(StoreValueStatic::Boolean(b)), 50 | } 51 | } 52 | } 53 | 54 | impl AsStoreValue for StoreValueStatic { 55 | fn as_store_value(&self) -> StoreValue { 56 | match self { 57 | &StoreValueStatic::String(ref s) => StoreValue::String(s.deref()), 58 | &StoreValueStatic::Integer(i) => StoreValue::Integer(i), 59 | &StoreValueStatic::Boolean(b) => StoreValue::Boolean(b), 60 | } 61 | } 62 | } 63 | 64 | /// Implementation for integers 65 | macro_rules! impl_for_integer { 66 | ($type_ident:ident, $to_value:ident) => ( 67 | impl Cast for $type_ident { 68 | fn cast(this: StoreValue) -> Option { 69 | match this { 70 | StoreValue::Integer(i) => { 71 | i.$to_value() 72 | } 73 | _ => None 74 | } 75 | } 76 | } 77 | impl AsStoreValue for $type_ident { 78 | fn as_store_value(&self) -> StoreValue { 79 | StoreValue::Integer(*self as i64) 80 | } 81 | } 82 | ) 83 | } 84 | 85 | impl_for_integer!(i64, to_i64); 86 | impl_for_integer!(i32, to_i32); 87 | impl_for_integer!(i16, to_i16); 88 | impl_for_integer!(i8, to_i8); 89 | 90 | impl_for_integer!(u32, to_u32); 91 | impl_for_integer!(u16, to_u16); 92 | impl_for_integer!(u8, to_u8); 93 | 94 | impl Cast for String { 95 | fn cast(this: StoreValue) -> Option { 96 | match this { 97 | StoreValue::String(s) => { 98 | Some(s.to_string()) 99 | } 100 | StoreValue::Integer(i) => { 101 | Some(i.to_string()) 102 | } 103 | StoreValue::Boolean(b) => { 104 | Some(b.to_string()) 105 | } 106 | } 107 | } 108 | } 109 | 110 | impl AsStoreValue for String { 111 | fn as_store_value(&self) -> StoreValue { 112 | StoreValue::String(self.deref()) 113 | } 114 | } 115 | 116 | impl Cast for bool { 117 | fn cast(this: StoreValue) -> Option { 118 | match this { 119 | StoreValue::Boolean(b) => { 120 | Some(b) 121 | }, 122 | StoreValue::String(s) => { 123 | Some(s.is_empty()) 124 | }, 125 | StoreValue::Integer(i) => { 126 | Some(i != 0) 127 | } 128 | } 129 | } 130 | } 131 | 132 | impl AsStoreValue for bool { 133 | fn as_store_value(&self) -> StoreValue { 134 | StoreValue::Boolean(*self) 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /data_bindings/src/store/mod.rs: -------------------------------------------------------------------------------- 1 | 2 | pub use self::cast::Cast; 3 | pub use self::cast::AssignFromCast; 4 | pub use self::value::StoreValue; 5 | pub use self::value::StoreValueStatic; 6 | 7 | // This is where Store default implementation lives 8 | mod impls; 9 | 10 | // Those two modules must be implemented 11 | // in sync. 12 | mod value; 13 | mod cast; 14 | -------------------------------------------------------------------------------- /data_bindings/src/store/value.rs: -------------------------------------------------------------------------------- 1 | /// `StoreValue` is the type that encapsulate 2 | /// a value extracted from a Store 3 | #[derive(Debug, Clone, Eq, PartialEq)] 4 | pub enum StoreValue<'a> { 5 | String(&'a str), 6 | Integer(i64), 7 | Boolean(bool), 8 | } 9 | 10 | /// Equivalent to of `StoreValue<'static>` to allow the implementation 11 | /// of a : 12 | /// ```ignore 13 | /// impl<'a> From> for StoreValue<'a> { 14 | /// ... 15 | /// } 16 | /// ``` 17 | #[derive(Clone)] 18 | pub enum StoreValueStatic { 19 | String(String), 20 | Integer(i64), 21 | Boolean(bool), 22 | } 23 | 24 | macro_rules! impl_for_integer { 25 | ($int_type:ident) => ( 26 | impl From<$int_type> for StoreValueStatic { 27 | fn from(i: $int_type) -> StoreValueStatic { 28 | StoreValueStatic::Integer(i as i64) 29 | } 30 | } 31 | ) 32 | } 33 | 34 | impl_for_integer!(i64); 35 | impl_for_integer!(i32); 36 | impl_for_integer!(i16); 37 | impl_for_integer!(i8); 38 | 39 | impl_for_integer!(u32); 40 | impl_for_integer!(u16); 41 | impl_for_integer!(u8); 42 | 43 | impl From for StoreValueStatic { 44 | fn from(s: String) -> StoreValueStatic { 45 | StoreValueStatic::String(s) 46 | } 47 | } 48 | 49 | impl From for StoreValueStatic { 50 | fn from(b: bool) -> StoreValueStatic { 51 | StoreValueStatic::Boolean(b) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /docs/action.md: -------------------------------------------------------------------------------- 1 | # Actions 2 | 3 | > TODO 4 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Welcome to Oil ! 2 | 3 | Oil is a [GUI](https://en.wikipedia.org/wiki/GUI) library for Game developers. 4 | It is written in Rust. To learn more about Rust, please visit the [official web site](http://rust-lang.org). 5 | 6 | Oil abstracts the user interface related code from your core game application. 7 | In that respect, it works similarly to [QML](http://en.wikipedia.org/wiki/QML). 8 | So how it is different ? 9 | 10 | Instead of using a scripting language, Oil provides you: 11 | 12 | * A markup language which use two-ways property bindings to attach values from your models to each view's 13 | elements. Those bindings are explicit and use a [mustache](https://mustache.github.io/)-like syntax. 14 | 15 | * A style language similar to CSS with additional states that cover both animation needs 16 | and classic interaction changes. 17 | 18 | * A dependency description language to load your assets: image, fonts, shaders and so on. 19 | 20 | With that in mind, we hope Oil will help you have a clear separation between your game logic, 21 | and the user interface. 22 | 23 | 24 | 25 | > Note: 26 | > 27 | > If you think that some of the above statements are currently wrong, you're probably right. 28 | > What are you waiting for to contribute to the project then ? :) 29 | 30 | ## Getting started 31 | 32 | To use oil in your code, you must add it to your Cargo.toml file in 33 | the dependency section: 34 | 35 | ```toml 36 | [dependencies] 37 | oil = "*" 38 | ``` 39 | 40 | Oil currently relies on [glium](http://tomaka.github.io/glium/) and 41 | [glutin](http://tomaka.github.io/glutin/glutin/index.html) to do the rendering. 42 | 43 | While this will be more flexible in the future, both these library are pretty good 44 | to start with and have a decent wrapper around OpenGL. 45 | 46 | The only thing needed to know about glutin/glium to use the Oil is how to create 47 | a window, which as simple as doing writing such as: 48 | 49 | ```rust 50 | extern crate glutin; 51 | extern crate glium; 52 | 53 | use glium::DisplayBuild; 54 | 55 | // ... 56 | 57 | let display = glutin::WindowBuilder::new() 58 | .build_glium() 59 | .unwrap(); 60 | ``` 61 | 62 | ## Writing your first interface 63 | 64 | > TODO 65 | -------------------------------------------------------------------------------- /docs/languages_reference/intro.md: -------------------------------------------------------------------------------- 1 | 2 | # Oil - Design - 3 | 4 | Oil is designed around the following components: 5 | 6 | * `markup` 7 | * `data-bindings` 8 | * `style` 9 | * `external dependencies` 10 | 11 | Every components has its own logic and can be considered separately from the rest. 12 | 13 | ## Markup 14 | 15 | > TODO 16 | 17 | ## Style 18 | 19 | > TODO 20 | -------------------------------------------------------------------------------- /docs/languages_reference/properties_list.md: -------------------------------------------------------------------------------- 1 | # Style properties 2 | 3 | Properties can hold different kind of values: 4 | 5 | * Length is a number followed by a unit such as `40px`. 6 | * Reserved keywords such as `auto`, `expand`, `fit` and `repeat`. 7 | * A dep declared by the dependency description language such as `$btn.img`. 8 | 9 | Here is the full list of valid properties: 10 | 11 | | Property name | Accepted values | Meaning | 12 | | ----------------------- | ------------------------ | ------- | 13 | | `left` | Length | | 14 | | `right` | Length | | 15 | | `top` | Length | | 16 | | `bottom` | Length | | 17 | | `height` | Length | | 18 | | `width` | Length, `auto`, `expand` | | 19 | | `margin` | Length, `auto`, `expand` | | 20 | | `margin-left` | Length, `auto`, `expand` | | 21 | | `margin-right` | Length, `auto`, `expand` | | 22 | | `margin-top` | Length, `auto` | | 23 | | `margin-bottom` | Length, `auto` | | 24 | | `padding` | Length | | 25 | | `padding-left` | Length | | 26 | | `padding-right` | Length | | 27 | | `padding-top` | Length | | 28 | | `padding-bottom` | Length | | 29 | | `border` | Length | | 30 | | `border-left` | Length | | 31 | | `border-right` | Length | | 32 | | `border-top` | Length | | 33 | | `border-bottom` | Length | | 34 | | `layout` | *TBD* | | 35 | | `background-image` | **dep** only | | 36 | | `background-image-rule` | `fit` or `repeat` | | 37 | -------------------------------------------------------------------------------- /docs/languages_reference/tag_list.md: -------------------------------------------------------------------------------- 1 | # Markup tags 2 | 3 | Each tag has a specific semantic and should be use only for its purpose. If you need 4 | a generic tag, you probably want a [group](tag_list.md#group) tag. 5 | 6 | All tags accept a `class` attribute that stores the class list 7 | as follow: 8 | ```xml 9 | 10 | ``` 11 | 12 | #### view 13 | 14 | **Example:** 15 | 16 | ```xml 17 | 18 | 19 | 20 | ``` 21 | 22 | **Context:** 23 | 24 | Need to be declared at the root of the markup file. Otherwise, it will be ignored 25 | and the parser will emit a warning. 26 | 27 | **Attributes:** 28 | 29 | - `name` contains the name of the view. If the value provided is `main` then this 30 | view will be the first one to be loaded. The value can't be a data binding. 31 | 32 | 33 | #### template declaration 34 | 35 | **Example:** 36 | 37 | ```xml 38 | 41 | ``` 42 | 43 | **Context:** 44 | 45 | As for the tag `view`, this one need to be at the root level. Otherwise, it will 46 | be interpreted differently. 47 | 48 | **Attributes:** 49 | 50 | - `name` contains the name of the template. It can't be a data binding. 51 | 52 | 53 | #### text-input 54 | 55 | **Example:** 56 | 57 | ```xml 58 | 59 | ``` 60 | 61 | **Context:** None 62 | 63 | **Attributes:** 64 | 65 | - `value` represent the editable content of the input. This **has** to be a 66 | data binding. If not, then the tag will be ignored and a warning will be emited. 67 | - `key` represent the KeyCode that will trigger the user event `UserEvent::Submit`. 68 | By default, this is the enter key. A data binding is valid here. 69 | 70 | #### progress-bar 71 | 72 | **Example:** 73 | 74 | ```xml 75 | 76 | ``` 77 | 78 | **Context:** None 79 | 80 | **Attributes:** 81 | 82 | - `value` contains the current percentage for the progress bar. 83 | It can be a floating point value between `0.0` and `100.0`. 84 | 85 | #### group 86 | 87 | **Example:** 88 | 89 | ```xml 90 | 91 | ``` 92 | 93 | **Context:** None 94 | 95 | **Attributes:** None 96 | 97 | #### button 98 | 99 | **Example:** 100 | 101 | ```xml 102 |