├── .gitignore ├── res ├── border.png ├── rust-logo-32.png ├── rust-logo-64.png └── font │ ├── FiraSans-Bold.ttf │ ├── FiraSans-Regular.ttf │ └── LICENSE ├── .travis.yml ├── README.md ├── syntax ├── Cargo.toml └── src │ ├── common.rs │ ├── lib.rs │ ├── desc │ └── mod.rs │ └── style │ └── mod.rs ├── Cargo.toml ├── LICENSE-MIT ├── src ├── error.rs ├── tests.rs ├── macros.rs ├── style.rs ├── query.rs ├── expr.rs ├── layout.rs └── lib.rs └── LICENSE-APACHE /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | **/*.rs.bk 3 | Cargo.lock 4 | .vscode 5 | .idea 6 | *.iml -------------------------------------------------------------------------------- /res/border.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thinklibs/fungui/HEAD/res/border.png -------------------------------------------------------------------------------- /res/rust-logo-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thinklibs/fungui/HEAD/res/rust-logo-32.png -------------------------------------------------------------------------------- /res/rust-logo-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thinklibs/fungui/HEAD/res/rust-logo-64.png -------------------------------------------------------------------------------- /res/font/FiraSans-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thinklibs/fungui/HEAD/res/font/FiraSans-Bold.ttf -------------------------------------------------------------------------------- /res/font/FiraSans-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thinklibs/fungui/HEAD/res/font/FiraSans-Regular.ttf -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | sudo: required 3 | language: rust 4 | rust: 5 | - stable 6 | - beta 7 | - nightly 8 | matrix: 9 | allow_failures: 10 | - rust: nightly 11 | 12 | script: 13 | - cargo build --all 14 | - cargo test --all --features=tests -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FunGUI 2 | 3 | A UI layout system that seperates the description of the interface and 4 | the styling/layout. This is designed to be used in games and is currently 5 | used in [UniverCity][univercity]. 6 | 7 | 8 | [univercity]: https://store.steampowered.com/app/808160/UniverCity/ -------------------------------------------------------------------------------- /syntax/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Matthew Collins "] 3 | name = "fungui_syntax" 4 | version = "0.1.1" 5 | 6 | description = "A syntax parsing for fungui" 7 | license = "MIT / Apache-2.0" 8 | repository = "https://github.com/Thinkofname/fungui" 9 | documentation = "https://docs.rs/fungui-syntax" 10 | keywords = ["gui", "user-interface", "gamedev"] 11 | categories = ["gui"] 12 | 13 | [dependencies] 14 | combine = "3.6.1" 15 | fnv = "1.0.6" 16 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Matthew Collins "] 3 | name = "fungui" 4 | version = "0.1.1" 5 | 6 | description = "A user interface layout system" 7 | license = "MIT / Apache-2.0" 8 | repository = "https://github.com/Thinkofname/fungui" 9 | documentation = "https://docs.rs/fungui" 10 | keywords = ["gui", "user-interface", "gamedev"] 11 | categories = ["gui"] 12 | 13 | [dependencies] 14 | fnv = "1.0.6" 15 | ref_filter_map = "1.0.1" 16 | bitflags = "1.0.4" 17 | 18 | [dependencies.fungui_syntax] 19 | path = "./syntax" 20 | version = "0.1.1" 21 | 22 | [workspace] 23 | members = [ 24 | "./syntax" 25 | ] 26 | 27 | [features] 28 | tests = [] -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Matthew Collins 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | 2 | 3 | /// The error type used in FunGUI 4 | #[derive(Debug)] 5 | pub enum Error<'a> { 6 | /// An unknown variable was used 7 | UnknownVariable { 8 | /// The name of the variable 9 | name: &'a str, 10 | }, 11 | /// An incompatible type was used with the given 12 | /// operator 13 | IncompatibleTypeOp { 14 | /// The operator 15 | op: &'static str, 16 | /// The incorrect type 17 | ty: &'static str, 18 | }, 19 | /// An incompatible pair of types was used with the given 20 | /// operator 21 | IncompatibleTypesOp { 22 | /// The operator 23 | op: &'static str, 24 | /// The type of the left hand side 25 | left_ty: &'static str, 26 | /// The type of the right hand side 27 | right_ty: &'static str, 28 | }, 29 | /// A custom reason 30 | Custom { 31 | /// The reason 32 | reason: String, 33 | }, 34 | /// A custom reason without allocating 35 | CustomStatic { 36 | /// The reason 37 | reason: &'static str, 38 | }, 39 | /// The parameter at the given position 40 | /// is missing 41 | MissingParameter { 42 | /// The parameter position 43 | position: i32, 44 | /// The parameter name 45 | name: &'static str, 46 | } 47 | } -------------------------------------------------------------------------------- /syntax/src/common.rs: -------------------------------------------------------------------------------- 1 | 2 | use combine::*; 3 | use combine::parser::char::*; 4 | use combine::parser::range::*; 5 | use combine::error::*; 6 | use combine::Stream; 7 | use combine::stream::state::SourcePosition; 8 | use combine::stream::StreamErrorFor; 9 | use super::Ident; 10 | use std::fmt::Debug; 11 | 12 | pub(crate) fn ident<'a, I>() -> impl Parser> 13 | where 14 | I: Debug + Stream + RangeStream + 'a, 15 | ::Error: combine::ParseError, 16 | { 17 | (position(), take_while1(|c: char| c.is_alphanumeric() || c == '_')) 18 | .map(|(pos, name): (_, &str)| { 19 | Ident { 20 | name: name, 21 | position: SourcePosition::into(pos), 22 | } 23 | }) 24 | } 25 | 26 | pub(crate) fn parse_bool<'a, I>() -> impl Parser 27 | where 28 | I: Debug + Stream + RangeStream + 'a, 29 | ::Error: combine::ParseError, 30 | { 31 | try(string("true").map(|_| true)) 32 | .or(string("false").map(|_| false)) 33 | } 34 | 35 | pub(crate) fn parse_float<'a, I>() -> impl Parser + 'a 36 | where 37 | I: Debug + Stream + RangeStream + 'a, 38 | ::Error: combine::ParseError, 39 | { 40 | from_str(take_while1(|c: char| c.is_digit(10) || c == '.' || c == '-') 41 | .and_then(|v: &str| if v.contains('.') { 42 | Ok(v) 43 | } else { 44 | Err(StreamErrorFor::::expected_static_message("float")) 45 | } )) 46 | } 47 | 48 | pub(crate) fn parse_integer<'a, I>() -> impl Parser 49 | where 50 | I: Debug + Stream + RangeStream + 'a, 51 | ::Error: combine::ParseError, 52 | { 53 | from_str( 54 | Parser::expected(take_while1(|c: char| c.is_digit(10) || c == '-'), "integer") 55 | ) 56 | 57 | } 58 | 59 | pub(crate) fn parse_string<'a, I>() -> impl Parser 60 | where 61 | I: Debug + Stream + RangeStream + 'a, 62 | ::Error: combine::ParseError, 63 | { 64 | ( 65 | token('"'), 66 | recognize(skip_many( 67 | try(string(r#"\""#).map(|_| '"')) 68 | .or(try(string(r#"\t"#).map(|_| '\t'))) 69 | .or(try(string(r#"\n"#).map(|_| '\n'))) 70 | .or(try(string(r#"\r"#).map(|_| '\r'))) 71 | .or(try(string(r#"\\"#).map(|_| '\\'))) 72 | .or(satisfy(|c| c != '"')), 73 | )), 74 | token('"'), 75 | ).map(|v| v.1) 76 | } 77 | 78 | pub(crate) fn skip_comment<'a, I>() -> impl Parser 79 | where 80 | I: Debug + Stream + RangeStream + 'a, 81 | ::Error: combine::ParseError, 82 | { 83 | string("//") 84 | .with(skip_many(satisfy(|c| c != '\n'))) 85 | .with(spaces()) 86 | .map(|_| ()) 87 | } -------------------------------------------------------------------------------- /src/tests.rs: -------------------------------------------------------------------------------- 1 | #![allow(missing_docs)] 2 | use super::*; 3 | 4 | pub enum TestExt{} 5 | 6 | static CHAR: StaticKey = StaticKey("char"); 7 | 8 | impl Extension for TestExt { 9 | type NodeData = TestData; 10 | type Value = (); 11 | fn new_data() -> TestData { 12 | TestData { 13 | render_char: '#', 14 | } 15 | } 16 | 17 | fn style_properties<'a, F>(mut prop: F) 18 | where F: FnMut(StaticKey) + 'a 19 | { 20 | prop(CHAR); 21 | } 22 | 23 | fn update_data(styles: &Styles, nc: &NodeChain, rule: &Rule, data: &mut Self::NodeData) -> DirtyFlags { 24 | eval!(styles, nc, rule.CHAR => val => { 25 | if let Some(c) = val.convert::() { 26 | data.render_char = c.chars().next().unwrap_or('~'); 27 | } else { 28 | data.render_char = '~'; 29 | } 30 | }); 31 | 32 | DirtyFlags::empty() 33 | } 34 | 35 | fn reset_unset_data(used_keys: &FnvHashSet, data: &mut Self::NodeData) -> DirtyFlags { 36 | if !used_keys.contains(&CHAR) { 37 | data.render_char = '~'; 38 | } 39 | DirtyFlags::empty() 40 | } 41 | } 42 | 43 | pub struct TestData { 44 | render_char: char, 45 | } 46 | 47 | pub struct AsciiRender { 48 | width: usize, 49 | height: usize, 50 | data: Vec, 51 | offsets: Vec<(i32, i32)>, 52 | } 53 | 54 | impl AsciiRender { 55 | pub fn new(width: usize, height: usize) -> AsciiRender { 56 | let data = vec!['#'; width * height]; 57 | AsciiRender { 58 | width, 59 | height, 60 | data, 61 | offsets: vec![(0, 0)], 62 | } 63 | } 64 | 65 | pub fn as_string(&self) -> String { 66 | let mut out = String::with_capacity(self.width * self.height); 67 | for line in self.data.chunks(self.width) { 68 | out.extend(line); 69 | out.push('\n'); 70 | } 71 | out.pop(); 72 | out 73 | } 74 | } 75 | 76 | impl RenderVisitor for AsciiRender { 77 | 78 | fn visit(&mut self, node: &mut NodeInner) { 79 | let c = node.ext.render_char; 80 | let (lx, ly) = self.offsets.last().cloned().expect("Missing offset data"); 81 | let ox = node.draw_rect.x + lx; 82 | let oy = node.draw_rect.y + ly; 83 | for y in 0 .. node.draw_rect.height { 84 | for x in 0 .. node.draw_rect.width { 85 | let idx = (ox + x) as usize + (oy + y) as usize * self.width; 86 | self.data[idx] = c; 87 | } 88 | } 89 | self.offsets.push((ox, oy)); 90 | } 91 | fn visit_end(&mut self, _node: &mut NodeInner) { 92 | self.offsets.pop(); 93 | } 94 | } 95 | 96 | 97 | #[test] 98 | fn test() { 99 | let mut manager: Manager = Manager::new(); 100 | manager.add_func_raw("add_two", |args| -> Result<_, _> { 101 | let val: i32 = args.next() 102 | .ok_or(Error::MissingParameter { 103 | position: 0, 104 | name: "value" 105 | }) 106 | .and_then(|v| v)? 107 | .convert() 108 | .ok_or(Error::CustomStatic { 109 | reason: "Expected integer" 110 | })?; 111 | 112 | Ok(Value::Integer(val + 2)) 113 | }); 114 | let src = r#" 115 | basic_abs { 116 | x = 2, 117 | y = 1, 118 | width = 4, 119 | height = 3, 120 | char = "@", 121 | } 122 | basic_abs(offset=ox) { 123 | x = add_two(ox), 124 | } 125 | 126 | inner { 127 | x = 1, 128 | y = 1, 129 | width = 1, 130 | height = 1, 131 | char = "+", 132 | } 133 | "#; 134 | if let Err(err) = manager.load_styles("test", src) { 135 | let mut stdout = std::io::stdout(); 136 | format_parse_error(stdout.lock(), src.lines(), err).unwrap(); 137 | panic!("Styles failed to parse"); 138 | } 139 | manager.add_node(node! { 140 | basic_abs 141 | }); 142 | manager.add_node(node! { 143 | basic_abs(offset = 5) { 144 | inner 145 | } 146 | }); 147 | 148 | manager.layout(20, 8); 149 | 150 | let mut render = AsciiRender::new(20, 8); 151 | manager.render(&mut render); 152 | 153 | let layout = render.as_string(); 154 | println!("Layout: \n{}", layout); 155 | 156 | let expected_output = r##" 157 | #################### 158 | ##@@@@#@@@@######### 159 | ##@@@@#@+@@######### 160 | ##@@@@#@@@@######### 161 | #################### 162 | #################### 163 | #################### 164 | #################### 165 | "##.trim(); 166 | 167 | assert_eq!(layout, expected_output); 168 | } -------------------------------------------------------------------------------- /res/font/LICENSE: -------------------------------------------------------------------------------- 1 | Digitized data copyright (c) 2012-2015, The Mozilla Foundation and Telefonica S.A. 2 | with Reserved Font Name < Fira >, 3 | 4 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 5 | This license is copied below, and is also available with a FAQ at: 6 | http://scripts.sil.org/OFL 7 | 8 | 9 | ----------------------------------------------------------- 10 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 11 | ----------------------------------------------------------- 12 | 13 | PREAMBLE 14 | The goals of the Open Font License (OFL) are to stimulate worldwide 15 | development of collaborative font projects, to support the font creation 16 | efforts of academic and linguistic communities, and to provide a free and 17 | open framework in which fonts may be shared and improved in partnership 18 | with others. 19 | 20 | The OFL allows the licensed fonts to be used, studied, modified and 21 | redistributed freely as long as they are not sold by themselves. The 22 | fonts, including any derivative works, can be bundled, embedded, 23 | redistributed and/or sold with any software provided that any reserved 24 | names are not used by derivative works. The fonts and derivatives, 25 | however, cannot be released under any other type of license. The 26 | requirement for fonts to remain under this license does not apply 27 | to any document created using the fonts or their derivatives. 28 | 29 | DEFINITIONS 30 | "Font Software" refers to the set of files released by the Copyright 31 | Holder(s) under this license and clearly marked as such. This may 32 | include source files, build scripts and documentation. 33 | 34 | "Reserved Font Name" refers to any names specified as such after the 35 | copyright statement(s). 36 | 37 | "Original Version" refers to the collection of Font Software components as 38 | distributed by the Copyright Holder(s). 39 | 40 | "Modified Version" refers to any derivative made by adding to, deleting, 41 | or substituting -- in part or in whole -- any of the components of the 42 | Original Version, by changing formats or by porting the Font Software to a 43 | new environment. 44 | 45 | "Author" refers to any designer, engineer, programmer, technical 46 | writer or other person who contributed to the Font Software. 47 | 48 | PERMISSION & CONDITIONS 49 | Permission is hereby granted, free of charge, to any person obtaining 50 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 51 | redistribute, and sell modified and unmodified copies of the Font 52 | Software, subject to the following conditions: 53 | 54 | 1) Neither the Font Software nor any of its individual components, 55 | in Original or Modified Versions, may be sold by itself. 56 | 57 | 2) Original or Modified Versions of the Font Software may be bundled, 58 | redistributed and/or sold with any software, provided that each copy 59 | contains the above copyright notice and this license. These can be 60 | included either as stand-alone text files, human-readable headers or 61 | in the appropriate machine-readable metadata fields within text or 62 | binary files as long as those fields can be easily viewed by the user. 63 | 64 | 3) No Modified Version of the Font Software may use the Reserved Font 65 | Name(s) unless explicit written permission is granted by the corresponding 66 | Copyright Holder. This restriction only applies to the primary font name as 67 | presented to the users. 68 | 69 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 70 | Software shall not be used to promote, endorse or advertise any 71 | Modified Version, except to acknowledge the contribution(s) of the 72 | Copyright Holder(s) and the Author(s) or with their explicit written 73 | permission. 74 | 75 | 5) The Font Software, modified or unmodified, in part or in whole, 76 | must be distributed entirely under this license, and must not be 77 | distributed under any other license. The requirement for fonts to 78 | remain under this license does not apply to any document created 79 | using the Font Software. 80 | 81 | TERMINATION 82 | This license becomes null and void if any of the above conditions are 83 | not met. 84 | 85 | DISCLAIMER 86 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 87 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 88 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 89 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 90 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 91 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 92 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 93 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 94 | OTHER DEALINGS IN THE FONT SOFTWARE. 95 | -------------------------------------------------------------------------------- /src/macros.rs: -------------------------------------------------------------------------------- 1 | 2 | /// Used to create nodes inline without parsing a document 3 | /// at runtime. 4 | /// 5 | /// The syntax matches the node description format apart from 6 | /// a few differences. 7 | /// 8 | /// * Text can't be inline, it must be wrapped with `@text("hello")` 9 | /// * String attributes currently need to be `.to_owned()` as 10 | /// it expects `String` not `&str`. 11 | /// 12 | /// # Examples 13 | /// 14 | /// ```rust 15 | /// # #[macro_use] extern crate fungui; 16 | /// # fn main() { 17 | /// # let _ : fungui::Node = 18 | /// node!{ 19 | /// panel(x=5, y=16, width=300, height=50) { 20 | /// icon 21 | /// title { 22 | /// @text("Testing") 23 | /// } 24 | /// } 25 | /// }; 26 | /// # } 27 | /// ``` 28 | /// 29 | /// ```rust 30 | /// # #[macro_use] extern crate fungui; 31 | /// # fn main() { 32 | /// # let _ : fungui::Node = 33 | /// node!{ 34 | /// @text("Hello world") 35 | /// }; 36 | /// # } 37 | /// ``` 38 | #[macro_export] 39 | macro_rules! node { 40 | // Helper rules 41 | (@parent($parent:expr)) => {}; 42 | 43 | (@parent($parent:expr) @text($txt:expr)) => ({ 44 | $parent.add_child($crate::Node::new_text($txt)); 45 | }); 46 | (@parent($parent:expr) @text($txt:expr) $($other:tt)*) => ({ 47 | $parent.add_child($crate::Node::new_text($txt)); 48 | node!(@parent($parent) $($other)*); 49 | }); 50 | 51 | (@parent($parent:expr) $name:ident ( 52 | $($key:ident = $val:expr),* 53 | ) { 54 | $($inner:tt)* 55 | }) => ({ 56 | let node = node!($name($($key = $val),*) { 57 | $($inner)* 58 | }); 59 | $parent.add_child(node); 60 | }); 61 | (@parent($parent:expr) $name:ident ( 62 | $($key:ident = $val:expr),* 63 | ) { 64 | $($inner:tt)* 65 | } $($other:tt)*) => ({ 66 | let node = node!($name($($key = $val),*) { 67 | $($inner)* 68 | }); 69 | $parent.add_child(node); 70 | node!(@parent($parent) $($other)*); 71 | }); 72 | 73 | (@parent($parent:expr) $name:ident ( 74 | $($key:ident = $val:expr),* 75 | )) => ({ 76 | let node = node!($name($($key = $val),*)); 77 | $parent.add_child(node); 78 | }); 79 | (@parent($parent:expr) $name:ident ( 80 | $($key:ident = $val:expr),* 81 | ) $($other:tt)*) => ({ 82 | let node = node!($name($($key = $val),*)); 83 | $parent.add_child(node); 84 | node!(@parent($parent) $($other)*); 85 | }); 86 | 87 | (@parent($parent:expr) $name:ident { 88 | $($inner:tt)* 89 | }) => ({ 90 | let node = node!($name { 91 | $($inner)* 92 | }); 93 | $parent.add_child(node); 94 | }); 95 | (@parent($parent:expr) $name:ident { 96 | $($inner:tt)* 97 | } $($other:tt)*) => ({ 98 | let node = node!($name { 99 | $($inner)* 100 | }); 101 | $parent.add_child(node); 102 | node!(@parent($parent) $($other)*); 103 | }); 104 | 105 | (@parent($parent:expr) $name:ident) => ({ 106 | let node = node!($name); 107 | $parent.add_child(node); 108 | }); 109 | (@parent($parent:expr) $name:ident $($other:tt)*) => ({ 110 | let node = node!($name); 111 | $parent.add_child(node); 112 | node!(@parent($parent) $($other)*); 113 | }); 114 | 115 | // Actual rules 116 | (@text($txt:expr)) => ( 117 | $crate::Node::new_text($txt) 118 | ); 119 | 120 | ($name:ident ( 121 | $($key:ident = $val:expr),* 122 | ) { 123 | $($inner:tt)* 124 | }) => ({ 125 | let node = $crate::Node::new(stringify!($name)); 126 | $( 127 | node.set_property(stringify!($key), $val); 128 | )* 129 | node!(@parent(node) $($inner)*); 130 | node 131 | }); 132 | ($name:ident ( 133 | $($key:ident = $val:expr),* 134 | )) => ({ 135 | let node = $crate::Node::new(stringify!($name)); 136 | $( 137 | node.set_property(stringify!($key), $val); 138 | )* 139 | node 140 | }); 141 | ($name:ident { 142 | $($inner:tt)* 143 | }) => ({ 144 | let node = $crate::Node::new(stringify!($name)); 145 | node!(@parent(node) $($inner)*); 146 | node 147 | }); 148 | ($name:ident) => ({ 149 | $crate::Node::new(stringify!($name)) 150 | }); 151 | } 152 | 153 | /// Allows for the creation of queries in a similar format 154 | /// as style rules. 155 | /// 156 | /// # Examples 157 | /// 158 | /// ```rust 159 | /// # #[macro_use] extern crate fungui; 160 | /// # use fungui::Node; 161 | /// # fn main() { 162 | /// # let node : Node = 163 | /// # node!{ 164 | /// # panel(x=5, y=16, width=300, height=50) { 165 | /// # icon 166 | /// # title { 167 | /// # @text("Testing") 168 | /// # } 169 | /// # } 170 | /// # }; 171 | /// let res = query!(node, panel(width=300) > title > @text) 172 | /// .next(); 173 | /// assert_eq!( 174 | /// &*res 175 | /// .as_ref() 176 | /// .and_then(|v| v.text()) 177 | /// .unwrap(), 178 | /// "Testing" 179 | /// ); 180 | /// # } 181 | /// ``` 182 | #[macro_export] 183 | macro_rules! query { 184 | 185 | (@target($query:expr), ) => ( 186 | $query 187 | ); 188 | 189 | (@target($query:expr), @text ( 190 | $($key:ident = $val:expr),* 191 | ) > $($other:tt)*) => ( 192 | query!(@target($query.text() 193 | $( 194 | .property(stringify!($key), $val) 195 | )*.child()), $($other)*) 196 | ); 197 | (@target($query:expr), @text > $($other:tt)*) => ( 198 | query!(@target($query.text().child()), $($other)*) 199 | ); 200 | (@target($query:expr), @text ( 201 | $($key:ident = $val:expr),* 202 | )) => ( 203 | $query.text() 204 | $( 205 | .property(stringify!($key), $val) 206 | )* 207 | ); 208 | (@target($query:expr), @text) => ( 209 | $query.text() 210 | ); 211 | 212 | (@target($query:expr), $name:ident ( 213 | $($key:ident = $val:expr),* 214 | ) > $($other:tt)*) => ( 215 | query!(@target($query.name(stringify!($name)) 216 | $( 217 | .property(stringify!($key), $val) 218 | )*.child()), $($other)*) 219 | ); 220 | (@target($query:expr), $name:ident > $($other:tt)*) => ( 221 | query!(@target($query.name(stringify!($name)).child()), $($other)*) 222 | ); 223 | (@target($query:expr), $name:ident ( 224 | $($key:ident = $val:expr),* 225 | )) => ( 226 | $query.name(stringify!($name)) 227 | $( 228 | .property(stringify!($key), $val) 229 | )* 230 | ); 231 | (@target($query:expr), $name:ident) => ( 232 | $query.name(stringify!($name)) 233 | ); 234 | 235 | ($node:expr, $($other:tt)*) => ({ 236 | let query = $node.query(); 237 | query!(@target(query), $($other)*) 238 | }); 239 | } 240 | 241 | #[test] 242 | fn test_query_macro() { 243 | let node: super::Node<::tests::TestExt> = node!{ 244 | test { 245 | inner { 246 | @text("wrong".to_owned()) 247 | } 248 | inner(a=5) { 249 | @text("hello".to_owned()) 250 | } 251 | } 252 | }; 253 | let res = query!(node, test > inner(a=5) > @text) 254 | .next(); 255 | assert!( 256 | res 257 | .as_ref() 258 | .and_then(|v| v.text()) 259 | .map_or(false, |v| &*v == "hello") 260 | ) 261 | } 262 | -------------------------------------------------------------------------------- /syntax/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![recursion_limit="128"] 2 | extern crate combine; 3 | extern crate fnv; 4 | 5 | pub mod desc; 6 | pub mod style; 7 | pub(crate) mod common; 8 | 9 | 10 | use combine::easy::{ParseError}; 11 | use combine::stream::state::{State, SourcePosition}; 12 | use std::io::{self, Write}; 13 | use std::hash::{Hash, Hasher}; 14 | use std::fmt::{self, Display, Formatter}; 15 | 16 | pub type PError<'a> = ParseError>; 17 | 18 | pub use combine::easy::{Errors, Error, Info}; 19 | 20 | /// An identifier. 21 | /// 22 | /// An identifier is made up of either letters, numbers 23 | /// or `_`. 24 | #[derive(Debug, Default, Clone)] 25 | pub struct Ident<'a> { 26 | /// The identifier's value/name 27 | pub name: &'a str, 28 | /// The position of the identifier within the source. 29 | /// 30 | /// Used for debugging. 31 | pub position: Position, 32 | } 33 | 34 | impl <'a> PartialEq for Ident<'a> { 35 | fn eq(&self, o: &Ident) -> bool { 36 | self.name == o.name 37 | } 38 | } 39 | 40 | impl <'a> Eq for Ident<'a> {} 41 | 42 | impl <'a> Hash for Ident<'a> { 43 | fn hash(&self, state: &mut H) 44 | where 45 | H: Hasher, 46 | { 47 | self.name.hash(state); 48 | } 49 | } 50 | 51 | /// The position in the source file where the 52 | /// the ident/value/etc was defined. 53 | /// 54 | /// This is used to provide better debugging support 55 | /// when an error in encounted. 56 | #[derive(Clone, Copy, Debug, Default)] 57 | pub struct Position { 58 | /// The line this relates to. 59 | /// 60 | /// This starts at line 1 (not 0) 61 | pub line_number: i32, 62 | /// The column this relates to. 63 | /// 64 | /// This starts at line 1 (not 0) 65 | pub column: i32, 66 | } 67 | 68 | impl From for Position { 69 | fn from(v: SourcePosition) -> Position { 70 | Position { 71 | line_number: v.line, 72 | column: v.column, 73 | } 74 | } 75 | } 76 | 77 | impl From for SourcePosition { 78 | fn from(v: Position) -> SourcePosition { 79 | SourcePosition { 80 | line: v.line_number, 81 | column: v.column, 82 | } 83 | } 84 | } 85 | 86 | impl Display for Position { 87 | fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { 88 | write!(fmt, "{}:{}", self.line_number, self.column) 89 | } 90 | } 91 | 92 | /// Formats the error in a user friendly format 93 | pub fn format_error<'a, I, W>( 94 | mut w: W, 95 | source: I, 96 | pos: Position, 97 | len: usize, 98 | msg: &str, 99 | label: &str, 100 | ) -> io::Result<()> 101 | where 102 | W: Write, 103 | I: Iterator, 104 | { 105 | use std::cmp::max; 106 | let number_len = (pos.line_number + 1).to_string().len(); 107 | write!(&mut w, "error: {}\n", msg)?; 108 | write!( 109 | &mut w, 110 | "{:width$}--> {}:{}\n", 111 | "", 112 | pos.line_number, 113 | pos.column, 114 | width = number_len, 115 | )?; 116 | let skip = max(0, pos.line_number - 2) as usize; 117 | let take = if pos.line_number == 1 { 118 | write!(&mut w, "{:width$} |\n", "", width = number_len)?; 119 | 2 120 | } else { 121 | 3 122 | }; 123 | 124 | for (no, line) in source.enumerate().skip(skip).take(take) { 125 | let target_line = no == (pos.line_number - 1) as usize; 126 | if target_line { 127 | write!(&mut w, "{:width$} | {}\n", no + 1, line, width = number_len)?; 128 | write!( 129 | &mut w, 130 | "{:width$} | {:offset$}{:^( 150 | w: W, 151 | source: I, 152 | err: ParseError>, 153 | ) -> Result<(), Box<::std::error::Error>> 154 | where 155 | W: Write, 156 | I: Iterator, 157 | { 158 | use combine::easy::{Error, Info}; 159 | use std::fmt::Write; 160 | let mut msg = String::new(); 161 | let mut label = String::new(); 162 | enum Type { 163 | Unexpected, 164 | Message, 165 | Unknown, 166 | } 167 | let ty = if let Some(first) = err.errors.first() { 168 | match *first { 169 | Error::Unexpected(..) => Type::Unexpected, 170 | _ => Type::Message, 171 | } 172 | } else { 173 | Type::Unknown 174 | }; 175 | 176 | let mut token_len = 1; 177 | 178 | match ty { 179 | Type::Unknown => msg.push_str("Unknown error occurred"), 180 | Type::Message => { 181 | let len = err.errors.len(); 182 | for (idx, err) in err.errors.into_iter().enumerate() { 183 | match err { 184 | Error::Message(ref m) => match *m { 185 | Info::Owned(ref m) => msg.push_str(m), 186 | Info::Borrowed(m) => msg.push_str(m), 187 | _ => unimplemented!(), 188 | }, 189 | Error::Other(ref err) => write!(&mut msg, "{}", err)?, 190 | Error::Expected(ref t) => write!(&mut msg, "Expected: {}", t)?, 191 | Error::Unexpected(ref t) => write!(&mut msg, "Unexpected: {}", t)?, 192 | } 193 | if idx != len - 1 { 194 | msg.push_str(", "); 195 | } 196 | } 197 | }, 198 | Type::Unexpected => { 199 | msg.push_str("Unexpected '"); 200 | label.push_str("Unexpected '"); 201 | if let Some(first) = err.errors.first() { 202 | match *first { 203 | Error::Unexpected(ref m) => match *m { 204 | Info::Owned(ref m) => { 205 | msg.push_str(m); 206 | label.push_str(m); 207 | token_len = m.len(); 208 | } 209 | Info::Borrowed(m) => { 210 | msg.push_str(m); 211 | label.push_str(m); 212 | token_len = m.len(); 213 | } 214 | Info::Token(t) => { 215 | write!(&mut msg, "{}", t)?; 216 | write!(&mut label, "{}", t)?; 217 | } 218 | _ => unimplemented!(), 219 | }, 220 | _ => unimplemented!(), 221 | } 222 | } 223 | label.push_str("'"); 224 | msg.push_str("' expected "); 225 | if err.errors.len() > 2 { 226 | msg.push_str("either "); 227 | } 228 | let len = err.errors[1..].len() as isize; 229 | for (i, err) in err.errors[1..].iter().enumerate() { 230 | msg.push('\''); 231 | match *err { 232 | Error::Expected(ref m) => match *m { 233 | Info::Owned(ref m) => { 234 | msg.push_str(m); 235 | } 236 | Info::Borrowed(m) => { 237 | msg.push_str(m); 238 | } 239 | Info::Token(t) => { 240 | write!(&mut msg, "{}", t)?; 241 | } 242 | _ => unimplemented!(), 243 | }, 244 | _ => {} 245 | } 246 | msg.push('\''); 247 | if (i as isize) < len - 2 { 248 | msg.push_str(", "); 249 | } else if i as isize == len - 2 { 250 | msg.push_str(" or "); 251 | } 252 | } 253 | } 254 | } 255 | 256 | format_error(w, source, err.position.into(), token_len, &msg, &label)?; 257 | Ok(()) 258 | } 259 | -------------------------------------------------------------------------------- /syntax/src/desc/mod.rs: -------------------------------------------------------------------------------- 1 | //! Parser for the UI description format 2 | //! 3 | //! This module contains the AST and parser for the 4 | //! format used to describe the layout of a UI element. 5 | //! 6 | //! The format is as follows: 7 | //! 8 | //! ```text,ignore 9 | //! // Comments (only single line) 10 | //! 11 | //! // Name of an element. Can be made up from any 12 | //! // letter, number or _ 13 | //! root { 14 | //! // Nested elements supported 15 | //! panel { 16 | //! 17 | //! } 18 | //! // Properties can be specified within () 19 | //! // as `key=value` pairs 20 | //! image(src="example.png", width=150, height=150) { 21 | //! 22 | //! } 23 | //! // {} is optional 24 | //! emoji(type="smile") 25 | //! // As is () 26 | //! spacer 27 | //! // Text can be used as well (quoted) 28 | //! "Hello world" 29 | //! } 30 | //! ``` 31 | 32 | use fnv::FnvHashMap; 33 | use common::*; 34 | 35 | use combine::*; 36 | use combine::parser::char::*; 37 | use combine::error::*; 38 | use combine::Stream; 39 | use combine::easy::{ParseError,}; 40 | use combine::stream::state::{State, SourcePosition}; 41 | use super::{Ident, Position}; 42 | use std::fmt::Debug; 43 | 44 | /// A UI description document 45 | /// 46 | /// Currently a document is made up of a single element. 47 | #[derive(Debug)] 48 | pub struct Document<'a> { 49 | /// The root element of the element 50 | pub root: Element<'a>, 51 | } 52 | 53 | impl <'a> Document<'a> { 54 | /// Attempts to parse the given string as a document. 55 | /// 56 | /// This fails when a syntax error occurs. The returned 57 | /// error can be formatted in a user friendly format 58 | /// via the [`format_parse_error`] method. 59 | /// 60 | /// # Example 61 | /// 62 | /// ``` 63 | /// # use fungui_syntax::desc::Document; 64 | /// assert!(Document::parse(r#" 65 | /// root { 66 | /// "hello world" 67 | /// } 68 | /// "#).is_ok()); 69 | /// ``` 70 | /// 71 | /// [`format_parse_error`]: ../fn.format_parse_error.html 72 | pub fn parse(source: &str) -> Result>> { 73 | let (doc, _) = parse_document().easy_parse(State::new(source))?; 74 | Ok(doc) 75 | } 76 | } 77 | 78 | /// An element which can contain other elements and/or 79 | /// have properties attached. 80 | /// 81 | /// An element does nothing by itself (bar special elements 82 | /// as defined by the program, widgets) and must be controlled 83 | /// via a style document. 84 | #[derive(Debug)] 85 | pub struct Element<'a> { 86 | /// The name of this element 87 | pub name: Ident<'a>, 88 | /// Optional map of propreties 89 | pub properties: FnvHashMap, ValueType<'a>>, 90 | /// Optional list of nodes within this element 91 | pub nodes: Vec>, 92 | } 93 | 94 | /// A node that can be contained within an element. 95 | /// 96 | /// This is either another element or raw text. 97 | #[derive(Debug)] 98 | pub enum Node<'a> { 99 | /// A sub element 100 | Element(Element<'a>), 101 | /// Text within an element 102 | /// 103 | /// Position is the position of the text within 104 | /// the source (used for debugging) 105 | Text(&'a str, Position, FnvHashMap, ValueType<'a>>), 106 | } 107 | 108 | /// Contains a value and debugging information 109 | /// for the value. 110 | #[derive(Debug)] 111 | pub struct ValueType<'a> { 112 | /// The parsed value 113 | pub value: Value<'a>, 114 | /// The position of the value within the source. 115 | /// 116 | /// Used for debugging. 117 | pub position: Position, 118 | } 119 | 120 | /// A parsed value for a property 121 | #[derive(Debug)] 122 | pub enum Value<'a> { 123 | /// A boolean value 124 | Boolean(bool), 125 | /// A 32 bit integer 126 | Integer(i32), 127 | /// A 64 bit float (of the form `0.0`) 128 | Float(f64), 129 | /// A quoted string 130 | String(&'a str), 131 | } 132 | 133 | fn parse_document<'a, I>() -> impl Parser> 134 | where 135 | I: Debug + Stream + RangeStream + 'a, 136 | ::Error: combine::ParseError, 137 | { 138 | spaces() 139 | .with(parse_element()) 140 | .map(|e| Document { root: e }) 141 | } 142 | 143 | fn parse_element<'a, I>() -> impl Parser> 144 | where 145 | I: Debug + Stream + RangeStream + 'a, 146 | ::Error: combine::ParseError, 147 | { 148 | let comments = skip_many(skip_comment()); 149 | 150 | let element = ( 151 | ident().skip(look_ahead(char('{').or(char('(')).or(space()).map(|_| ()))), 152 | spaces().with(optional(properties())), 153 | spaces().with(optional(parser(body))), 154 | ); 155 | 156 | spaces() 157 | .with(comments) 158 | .with(element) 159 | .map(|v| { 160 | Element { 161 | name: v.0, 162 | properties: v.1.unwrap_or_default(), 163 | nodes: v.2.unwrap_or_default(), 164 | } 165 | }) 166 | } 167 | 168 | fn body<'a, I>(input: &mut I) -> ParseResult>, I> 169 | where 170 | I: Debug + Stream + RangeStream + 'a, 171 | ::Error: combine::ParseError, 172 | { 173 | let (_, _) = char('{').parse_stream(input)?; 174 | 175 | enum Flow { 176 | Continue(T), 177 | Break, 178 | } 179 | 180 | let mut nodes = Vec::new(); 181 | loop { 182 | let (ret, _) = spaces() 183 | .with(skip_many(skip_comment())) 184 | .with( 185 | try(char('}').map(|_| Flow::Break)) 186 | .or( 187 | ( 188 | position(), 189 | parse_string(), 190 | optional(properties()), 191 | ).map(|v| { 192 | Node::Text(v.1, SourcePosition::into(v.0), v.2.unwrap_or_default()) 193 | }) 194 | .or(parse_element().map(Node::Element)) 195 | .map(|v| Flow::Continue(v)) 196 | ), 197 | ) 198 | .parse_stream(input)?; 199 | if let Flow::Continue(node) = ret { 200 | nodes.push(node); 201 | } else { 202 | break; 203 | } 204 | } 205 | Ok((nodes, Consumed::Consumed(()))) 206 | } 207 | 208 | fn properties<'a, I>() -> impl Parser, ValueType<'a>>> 209 | where 210 | I: Debug + Stream + RangeStream + 'a, 211 | ::Error: combine::ParseError, 212 | { 213 | ( 214 | token('('), 215 | sep_end_by(property(), token(',')), 216 | spaces().with(token(')')), 217 | ).map(|(_, l, _)| l) 218 | } 219 | 220 | fn property<'a, I>() -> impl Parser, ValueType<'a>)> 221 | where 222 | I: Debug + Stream + RangeStream + 'a, 223 | ::Error: combine::ParseError, 224 | { 225 | ( 226 | spaces().with(ident()), 227 | spaces().with(token('=')), 228 | spaces().with(value()), 229 | ).map(|v| (v.0, v.2)) 230 | } 231 | 232 | fn value<'a, I>() -> impl Parser> 233 | where 234 | I: Debug + Stream + RangeStream + 'a, 235 | ::Error: combine::ParseError, 236 | { 237 | let boolean = parse_bool().map(|v| Value::Boolean(v)); 238 | let float = parse_float().map(|v| Value::Float(v)); 239 | let integer = parse_integer().map(|v| Value::Integer(v)); 240 | 241 | let string = parse_string().map(|v| Value::String(v)); 242 | 243 | ( 244 | position(), 245 | try(boolean).or(try(float)).or(try(integer)).or(string), 246 | ).map(|v| { 247 | ValueType { 248 | value: v.1, 249 | position: SourcePosition::into(v.0), 250 | } 251 | }) 252 | } 253 | 254 | #[cfg(test)] 255 | mod tests { 256 | use format_parse_error; 257 | use super::*; 258 | #[test] 259 | fn test() { 260 | let source = r#" 261 | // <- comments 262 | // () are optional 263 | root( 264 | testing=3, hello=4.56, 265 | test="hello world", 266 | quoted="this has \"quotes\" and \\", 267 | negative=-5, negfloat=-634.354 268 | ) { 269 | spacer 270 | // Names can be anything, same for properties. 271 | // Styles control most things, a few special 272 | // widgets tied to certain names. Doesn't 273 | // matter to the parser though 274 | panel(width=500, height=300) { 275 | "Text can be placed within elements" 276 | emoji(name="smile") 277 | "and between them"(text_val=4) 278 | } 279 | 280 | empty_string { 281 | "" 282 | } 283 | } 284 | "#; 285 | let doc = Document::parse(source); 286 | 287 | if let Err(err) = doc { 288 | println!(""); 289 | format_parse_error(::std::io::stdout(), source.lines(), err).unwrap(); 290 | panic!("^^"); 291 | } 292 | } 293 | 294 | #[test] 295 | fn test_print_invalid_ident() { 296 | let source = r#"roo$t { 297 | 298 | } 299 | "#; 300 | let doc = Document::parse(source); 301 | if let Err(err) = doc { 302 | let mut out: Vec = Vec::new(); 303 | format_parse_error(&mut out, source.lines(), err).unwrap(); 304 | assert_eq!( 305 | String::from_utf8_lossy(&out) 306 | .lines() 307 | .map(|v| v.trim_right().to_owned() + "\n") 308 | .collect::(), 309 | r#"error: Unexpected '$' expected either '{', '(' or 'whitespace' 310 | --> 1:4 311 | | 312 | 1 | roo$t { 313 | | ^ Unexpected '$' 314 | | 315 | "# 316 | ); 317 | } else { 318 | panic!("Expected error"); 319 | } 320 | } 321 | } 322 | -------------------------------------------------------------------------------- /src/style.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | use std::hash::{Hash, Hasher}; 4 | 5 | pub(crate) type SFunc = Box Fn(&mut (Iterator, Error<'a>>> + 'a)) -> Result, Error<'a>> + 'static>; 6 | 7 | /// Stores rules, functions and layouts needed for computing styles 8 | pub struct Styles { 9 | pub(crate) _ext: ::std::marker::PhantomData, 10 | pub(crate) static_keys: FnvHashMap<&'static str, StaticKey>, 11 | pub(crate) rules: Rules, 12 | pub(crate) funcs: FnvHashMap>, 13 | pub(crate) layouts: FnvHashMap<&'static str, Box Box>>>, 14 | pub(crate) next_rule_id: u32, 15 | // Stored here for reuse to save on allocations 16 | pub(crate) used_keys: FnvHashSet, 17 | } 18 | 19 | impl Styles { 20 | #[inline] 21 | #[doc(hidden)] 22 | pub fn key_was_used(&self, key: &StaticKey) -> bool { 23 | self.used_keys.contains(key) 24 | } 25 | 26 | pub(crate) fn load_styles<'a>(&mut self, name: &str, doc: syntax::style::Document<'a>) -> Result<(), syntax::PError<'a>>{ 27 | for rule in doc.rules { 28 | let id = self.next_rule_id; 29 | self.next_rule_id = self.next_rule_id.wrapping_add(1); 30 | self.rules.add(id, &mut self.static_keys, name, rule)?; 31 | } 32 | Ok(()) 33 | } 34 | } 35 | 36 | #[derive(Clone, Eq, Debug)] 37 | pub struct RuleKey { 38 | pub inner: RuleKeyBorrow<'static>, 39 | } 40 | 41 | impl Hash for RuleKey { 42 | fn hash(&self, state: &mut H) { 43 | self.inner.hash(state); 44 | } 45 | } 46 | 47 | impl <'a> std::borrow::Borrow> for RuleKey { 48 | fn borrow(&self) -> &RuleKeyBorrow<'a> { 49 | &self.inner 50 | } 51 | } 52 | 53 | impl PartialEq for RuleKey { 54 | fn eq(&self, other: &RuleKey) -> bool { 55 | self.inner.eq(&other.inner) 56 | } 57 | } 58 | 59 | impl <'a> PartialEq> for RuleKey { 60 | fn eq(&self, other: &RuleKeyBorrow<'a>) -> bool { 61 | self.inner.eq(other) 62 | } 63 | } 64 | 65 | #[derive(Clone, Eq, Debug)] 66 | pub enum RuleKeyBorrow<'a> { 67 | Element(String), 68 | ElementBorrow(&'a str), 69 | Text, 70 | } 71 | impl <'a> PartialEq for RuleKeyBorrow<'a> { 72 | fn eq(&self, other: &RuleKeyBorrow<'a>) -> bool { 73 | match (self, other) { 74 | (RuleKeyBorrow::Element(ref a), RuleKeyBorrow::Element(ref b)) => a == b, 75 | (RuleKeyBorrow::ElementBorrow(ref a), RuleKeyBorrow::Element(ref b)) => a == b, 76 | (RuleKeyBorrow::Element(ref a), RuleKeyBorrow::ElementBorrow(ref b)) => a == b, 77 | (RuleKeyBorrow::ElementBorrow(ref a), RuleKeyBorrow::ElementBorrow(ref b)) => a == b, 78 | (RuleKeyBorrow::Text, RuleKeyBorrow::Text) => true, 79 | _ => false, 80 | } 81 | } 82 | } 83 | 84 | impl <'a> PartialEq for RuleKeyBorrow<'a> { 85 | fn eq(&self, other: &RuleKey) -> bool { 86 | self.eq(&other.inner) 87 | } 88 | } 89 | 90 | impl <'a> Hash for RuleKeyBorrow<'a> { 91 | fn hash(&self, state: &mut H) { 92 | match self { 93 | RuleKeyBorrow::Element(ref e) => { 94 | state.write_u8(0); 95 | e.as_str().hash(state); 96 | }, 97 | RuleKeyBorrow::ElementBorrow(ref e) => { 98 | state.write_u8(0); 99 | e.hash(state); 100 | }, 101 | RuleKeyBorrow::Text => { 102 | state.write_u8(1); 103 | }, 104 | } 105 | } 106 | } 107 | 108 | /// Used for quick lookups into possible matches 109 | /// for an element. 110 | /// 111 | /// This wont check properties as its only ment to 112 | /// reduce the search space. 113 | pub struct Rules { 114 | next: FnvHashMap>, 115 | // Set of possible matches 116 | matches: Vec>>, 117 | } 118 | 119 | #[derive(Debug)] 120 | pub enum ValueMatcher { 121 | Boolean(bool), 122 | Integer(i32), 123 | Float(f64), 124 | String(String), 125 | Exists, 126 | } 127 | 128 | impl Rules 129 | where E: Extension 130 | { 131 | pub fn new() -> Rules { 132 | Rules { 133 | next: FnvHashMap::default(), 134 | matches: Vec::new(), 135 | } 136 | } 137 | 138 | fn add<'a>(&mut self, id: u32, keys: &mut FnvHashMap<&'static str, StaticKey>, name: &str, rule: syntax::style::Rule<'a>) -> Result<(), syntax::PError<'a>> { 139 | // Work in reverse to make lookups faster 140 | let mut current = self; 141 | for m in rule.matchers.iter().rev() { 142 | let key = match m.0 { 143 | syntax::style::Matcher::Text => RuleKeyBorrow::Text, 144 | syntax::style::Matcher::Element(ref e) => RuleKeyBorrow::Element(e.name.name.into()), 145 | }; 146 | let tmp = current; 147 | let next = tmp.next.entry(RuleKey{inner: key}).or_insert_with(Rules::new); 148 | current = next; 149 | } 150 | let mut property_replacer = FnvHashMap::default(); 151 | let mut matchers = Vec::with_capacity(rule.matchers.len()); 152 | for (depth, m) in rule.matchers.into_iter().rev().enumerate() { 153 | let key = match m.0 { 154 | syntax::style::Matcher::Text => RuleKeyBorrow::Text, 155 | syntax::style::Matcher::Element(ref e) => RuleKeyBorrow::Element(e.name.name.into()), 156 | }; 157 | let mut properties = Vec::with_capacity(m.1.len()); 158 | for (k, v) in m.1 { 159 | use syntax::style::Value as SVal; 160 | let val = match v.value { 161 | SVal::Boolean(b) => ValueMatcher::Boolean(b), 162 | SVal::Integer(i) => ValueMatcher::Integer(i), 163 | SVal::Float(f) => ValueMatcher::Float(f), 164 | SVal::String(s) => ValueMatcher::String(unescape(s)), 165 | SVal::Variable(n) => { 166 | property_replacer.insert(n.name.to_owned(), (depth, k.name.to_owned())); 167 | ValueMatcher::Exists 168 | } 169 | }; 170 | properties.push((k.name.to_owned(), val)); 171 | } 172 | matchers.push((RuleKey{inner: key}, properties)); 173 | } 174 | 175 | let mut styles = FnvHashMap::with_capacity_and_hasher(rule.styles.len(), Default::default()); 176 | let mut uses_parent_size = false; 177 | for (k, e) in rule.styles { 178 | let key = match keys.get(k.name) { 179 | Some(val) => val, 180 | None => return Err(syntax::Errors::new( 181 | k.position.into(), 182 | syntax::Error::Message(syntax::Info::Borrowed("Unknown style key")), 183 | )), 184 | }; 185 | styles.insert(*key, Expr::from_style(keys, &property_replacer, &mut uses_parent_size, e)?); 186 | } 187 | current.matches.push(Rc::new(Rule { 188 | id, 189 | name: name.into(), 190 | matchers, 191 | styles, 192 | uses_parent_size, 193 | })); 194 | Ok(()) 195 | } 196 | 197 | // Kinda expensive but shouldn't be common 198 | pub fn remove_all_by_name(&mut self, name: &str) { 199 | self.next.values_mut().for_each(|v| { 200 | v.remove_all_by_name(name); 201 | }); 202 | self.matches.retain(|v| v.name != name); 203 | } 204 | 205 | pub(super) fn get_possible_matches(&self, node: &NodeChain, out: &mut Vec>>) { 206 | let mut current = self; 207 | let mut node = Some(node); 208 | while let Some(n) = node.take() { 209 | { 210 | let key = match n.value { 211 | NCValue::Text(_) => RuleKeyBorrow::Text, 212 | NCValue::Element(ref e) => RuleKeyBorrow::ElementBorrow(e), 213 | }; 214 | current = if let Some(v) = current.next.get(&key) { 215 | v 216 | } else { 217 | break 218 | }; 219 | out.extend(current.matches.iter().cloned()); 220 | } 221 | node = n.parent; 222 | } 223 | out.sort_unstable_by_key(|v| v.id); 224 | } 225 | } 226 | 227 | /// A rule which contains a set of matchers to compare against 228 | /// the properties of a node and parents and a set of styles to 229 | /// apply if matched. 230 | pub struct Rule { 231 | id: u32, 232 | name: String, 233 | pub(crate) matchers: Vec<(RuleKey, Vec<(String, ValueMatcher)>)>, 234 | #[doc(hidden)] 235 | // Used by the `eval!` macro 236 | pub styles: FnvHashMap>, 237 | pub(crate) uses_parent_size: bool, 238 | } 239 | 240 | impl Rule 241 | where E: Extension 242 | { 243 | pub(super) fn test(&self, node: &NodeChain) -> bool { 244 | let mut node = Some(node); 245 | for (_rkey, props) in &self.matchers { 246 | if let Some(n) = node.take() { 247 | // Key doesn't need checking because `get_possible_matches` will filter 248 | // that 249 | 250 | for (key, vm) in props { 251 | if let Some(val) = n.properties.get(key) { 252 | let same = match (vm, val) { 253 | (ValueMatcher::Boolean(a), Value::Boolean(b)) => *a == *b, 254 | (ValueMatcher::Integer(a), Value::Integer(b)) => *a == *b, 255 | (ValueMatcher::Integer(a), Value::Float(b)) => *a as f64 == *b, 256 | (ValueMatcher::Float(a), Value::Float(b)) => *a == *b, 257 | (ValueMatcher::Float(a), Value::Integer(b)) => *a == *b as f64, 258 | (ValueMatcher::String(ref a), Value::String(ref b)) => a == b, 259 | (ValueMatcher::Exists, _) => true, 260 | (_, _) => false, 261 | }; 262 | if !same { 263 | return false; 264 | } 265 | } else { 266 | return false; 267 | } 268 | } 269 | node = n.parent; 270 | } else { 271 | return false; 272 | } 273 | } 274 | true 275 | } 276 | } -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /src/query.rs: -------------------------------------------------------------------------------- 1 | 2 | use super::*; 3 | use std::borrow::Cow; 4 | 5 | /// A query on a node that can be used to look up nodes 6 | /// in the same way that styles do. 7 | /// 8 | /// The `query!` macro is a useful wrapper around this 9 | /// to match the syntax used in styles. 10 | pub struct Query<'a, E: Extension + 'a> { 11 | pub(crate) root: Node, 12 | pub(crate) rules: Vec>, 13 | pub(crate) location: Option, 14 | } 15 | 16 | #[derive(Clone, Copy, Debug)] 17 | pub(crate) struct AtLocation { 18 | pub(crate) x: i32, 19 | pub(crate) y: i32, 20 | } 21 | 22 | pub(crate) enum Rule<'a, E: Extension + 'a> { 23 | /// Matches against child nodes 24 | Child, 25 | /// Matches against the element's name 26 | Name(Cow<'a, str>), 27 | /// Matches against a property 28 | Property(Cow<'a, str>, ValueRef<'a, E>), 29 | /// Matches against a text node 30 | Text, 31 | } 32 | 33 | pub enum ValueRef<'a, E: Extension + 'a> { 34 | Boolean(bool), 35 | Integer(i32), 36 | Float(f64), 37 | String(Cow<'a, str>), 38 | ExtValue(&'a E::Value), 39 | } 40 | 41 | impl <'a, E> Clone for ValueRef<'a, E> 42 | where E: Extension + 'a 43 | { 44 | fn clone(&self) -> Self { 45 | match self { 46 | ValueRef::Boolean(v) => ValueRef::Boolean(*v), 47 | ValueRef::Integer(v) => ValueRef::Integer(*v), 48 | ValueRef::Float(v) => ValueRef::Float(*v), 49 | ValueRef::String(v) => ValueRef::String(v.clone()), 50 | ValueRef::ExtValue(v) => ValueRef::ExtValue(*v), 51 | } 52 | } 53 | } 54 | 55 | pub trait AsValueRef<'a, E: Extension> { 56 | fn as_value_ref(self) -> ValueRef<'a, E>; 57 | } 58 | 59 | impl <'a, E> AsValueRef<'a, E> for &'a str 60 | where E: Extension 61 | { 62 | fn as_value_ref(self) -> ValueRef<'a, E> { 63 | ValueRef::String(self.into()) 64 | } 65 | } 66 | impl AsValueRef<'static, E> for String 67 | where E: Extension 68 | { 69 | fn as_value_ref(self) -> ValueRef<'static, E> { 70 | ValueRef::String(self.into()) 71 | } 72 | } 73 | 74 | impl <'a, E> AsValueRef<'a, E> for ValueRef<'a, E> 75 | where E: Extension 76 | { 77 | fn as_value_ref(self) -> ValueRef<'a, E> { 78 | self 79 | } 80 | } 81 | impl <'a, E> AsValueRef<'a, E> for i32 82 | where E: Extension 83 | { 84 | fn as_value_ref(self) -> ValueRef<'a, E> { 85 | ValueRef::Integer(self) 86 | } 87 | } 88 | impl <'a, E> AsValueRef<'a, E> for f64 89 | where E: Extension 90 | { 91 | fn as_value_ref(self) -> ValueRef<'a, E> { 92 | ValueRef::Float(self) 93 | } 94 | } 95 | impl <'a, E> AsValueRef<'a, E> for f32 96 | where E: Extension 97 | { 98 | fn as_value_ref(self) -> ValueRef<'a, E> { 99 | ValueRef::Float(self as f64) 100 | } 101 | } 102 | impl <'a, E> AsValueRef<'a, E> for bool 103 | where E: Extension 104 | { 105 | fn as_value_ref(self) -> ValueRef<'a, E> { 106 | ValueRef::Boolean(self) 107 | } 108 | } 109 | 110 | impl<'a, E> Query<'a, E> 111 | where E: Extension + 'a 112 | { 113 | #[inline] 114 | pub(super) fn new(node: Node) -> Query<'a, E> { 115 | Query { 116 | root: node, 117 | rules: vec![], 118 | location: None, 119 | } 120 | } 121 | 122 | /// Converts an empty query into an owned one 123 | #[inline] 124 | pub fn into_owned(self) -> Query<'static, E> { 125 | assert!(self.rules.is_empty()); 126 | Query { 127 | root: self.root, 128 | rules: vec![], 129 | location: self.location, 130 | } 131 | } 132 | 133 | /// Matches against the name of the current node 134 | /// if it is an element, fails otherwise. 135 | #[inline] 136 | pub fn name(mut self, name: S) -> Query<'a, E> 137 | where S: Into>, 138 | { 139 | self.rules.push(Rule::Name(name.into())); 140 | self 141 | } 142 | 143 | /// Matches against a text node otherwise it fails 144 | #[inline] 145 | pub fn text(mut self) -> Query<'a, E> { 146 | self.rules.push(Rule::Text); 147 | self 148 | } 149 | 150 | /// Matches against a property on the current node compares 151 | /// the value. Fails if the property is missing or the value 152 | /// doesn't match. 153 | #[inline] 154 | pub fn property(mut self, key: S, val: V) -> Query<'a, E> 155 | where 156 | V: AsValueRef<'a, E> + 'a, 157 | S: Into>, 158 | { 159 | self.rules 160 | .push(Rule::Property(key.into(), val.as_value_ref())); 161 | self 162 | } 163 | 164 | /// Moves the matcher to a child node. 165 | /// 166 | /// All other methods (`name`/`text`/`property`) will 167 | /// apply to the child after this 168 | #[inline] 169 | pub fn child(mut self) -> Query<'a, E> { 170 | self.rules.push(Rule::Child); 171 | self 172 | } 173 | 174 | /// Returns a iterator over the possible matches 175 | #[inline] 176 | pub fn matches(self) -> QueryIterator<'a, E> { 177 | let rect = if let Some(loc) = self.location { 178 | let rect = self.root.render_position().unwrap_or(Rect { 179 | x: 0, 180 | y: 0, 181 | width: 0, 182 | height: 0, 183 | }); 184 | 185 | if loc.x < rect.x || loc.x >= rect.x + rect.width || loc.y < rect.y 186 | || loc.y >= rect.y + rect.height 187 | { 188 | return QueryIterator { 189 | nodes: vec![], 190 | rules: self.rules, 191 | location: self.location, 192 | }; 193 | } 194 | rect 195 | } else { 196 | // Dummy out unused data 197 | Rect { 198 | x: 0, 199 | y: 0, 200 | width: 0, 201 | height: 0, 202 | } 203 | }; 204 | let offset = num_children(&self.root) as isize - 1; 205 | QueryIterator { 206 | nodes: vec![(self.root, offset, rect)], 207 | rules: self.rules, 208 | location: self.location, 209 | } 210 | } 211 | 212 | /// Returns a single match if any. 213 | /// 214 | /// Alias for `matches().next()` 215 | #[inline] 216 | pub fn next(self) -> Option> { 217 | self.matches().next() 218 | } 219 | } 220 | 221 | pub struct QueryIterator<'a, E: Extension + 'a> { 222 | nodes: Vec<(Node, isize, Rect)>, 223 | rules: Vec>, 224 | location: Option, 225 | } 226 | 227 | #[inline] 228 | fn num_children(node: &Node) -> usize { 229 | let inner = node.inner.borrow(); 230 | if let NodeValue::Element(ref e) = inner.value { 231 | e.children.len() 232 | } else { 233 | 0 234 | } 235 | } 236 | 237 | impl<'a, E> Iterator for QueryIterator<'a, E> 238 | where E: Extension 239 | { 240 | type Item = Node; 241 | fn next(&mut self) -> Option> { 242 | enum Action { 243 | Nothing, 244 | Pop, 245 | Push(Node, Rect), 246 | Remove(Node), 247 | } 248 | 249 | 'search: loop { 250 | let action = if let Some(cur) = self.nodes.last_mut() { 251 | if cur.1 == -1 { 252 | Action::Remove(cur.0.clone()) 253 | } else { 254 | let inner = cur.0.inner.borrow(); 255 | if let NodeValue::Element(ref e) = inner.value { 256 | cur.1 -= 1; 257 | if let Some(node) = e.children.get((cur.1 + 1) as usize) { 258 | if let Some(loc) = self.location { 259 | let mut rect = cur.2; 260 | let p_rect = cur.2; 261 | let p = node.parent()?.inner; 262 | let inner = p.borrow(); 263 | let self_inner = node.inner.borrow(); 264 | 265 | rect.x += self_inner.draw_rect.x; 266 | rect.y += self_inner.draw_rect.y; 267 | rect.width = self_inner.draw_rect.width; 268 | rect.height = self_inner.draw_rect.height; 269 | 270 | rect.x += inner.scroll_position.0 as i32; 271 | rect.y += inner.scroll_position.1 as i32; 272 | if inner.clip_overflow { 273 | if rect.x < p_rect.x { 274 | rect.width -= p_rect.x - rect.x; 275 | rect.x = p_rect.x; 276 | } 277 | if rect.y < p_rect.y { 278 | rect.height -= p_rect.y - rect.y; 279 | rect.y = p_rect.y; 280 | } 281 | if rect.x + rect.width >= p_rect.x + p_rect.width { 282 | rect.width = (p_rect.x + p_rect.width) - rect.x; 283 | } 284 | if rect.y + rect.height >= p_rect.y + p_rect.height { 285 | rect.height = (p_rect.y + p_rect.height) - rect.y; 286 | } 287 | } 288 | if loc.x < rect.x || loc.x >= rect.x + rect.width || loc.y < rect.y 289 | || loc.y >= rect.y + rect.height 290 | { 291 | Action::Nothing 292 | } else { 293 | Action::Push(node.clone(), rect) 294 | } 295 | } else { 296 | Action::Push( 297 | node.clone(), 298 | Rect { 299 | x: 0, 300 | y: 0, 301 | width: 0, 302 | height: 0, 303 | }, 304 | ) 305 | } 306 | } else { 307 | unreachable!() 308 | } 309 | } else { 310 | Action::Pop 311 | } 312 | } 313 | } else { 314 | return None; 315 | }; 316 | 317 | let node = match action { 318 | Action::Nothing => continue 'search, 319 | Action::Pop => { 320 | self.nodes.pop(); 321 | continue 'search; 322 | } 323 | Action::Push(node, rect) => { 324 | self.nodes 325 | .push((node.clone(), num_children(&node) as isize - 1, rect)); 326 | continue 'search; 327 | } 328 | Action::Remove(node) => { 329 | self.nodes.pop(); 330 | node 331 | } 332 | }; 333 | 334 | let mut cur = node.clone(); 335 | for rule in self.rules.iter().rev() { 336 | match rule { 337 | Rule::Text => if let NodeValue::Element(_) = cur.inner.borrow().value { 338 | continue 'search; 339 | }, 340 | Rule::Name(n) => if let NodeValue::Element(ref e) = cur.inner.borrow().value { 341 | if e.name != *n { 342 | continue 'search; 343 | } 344 | } else { 345 | continue 'search; 346 | }, 347 | Rule::Property(ref k, ref val) => { 348 | let inner = cur.inner.borrow(); 349 | let ok = match (inner.properties.get(&**k), val) { 350 | (Some(Value::Integer(a)), ValueRef::Integer(b)) => a == b, 351 | (Some(Value::Float(a)), ValueRef::Float(b)) => a == b, 352 | (Some(Value::Boolean(a)), ValueRef::Boolean(b)) => a == b, 353 | (Some(Value::String(a)), ValueRef::String(b)) => a == b, 354 | (Some(Value::ExtValue(a)), ValueRef::ExtValue(b)) => a == *b, 355 | _ => false, 356 | }; 357 | if !ok { 358 | continue 'search; 359 | } 360 | } 361 | Rule::Child => { 362 | // Reversed so go up a level instead 363 | let parent = cur.inner.borrow().parent.as_ref().and_then(|v| v.upgrade()); 364 | if let Some(parent) = parent { 365 | cur = Node { inner: parent }; 366 | } 367 | } 368 | } 369 | } 370 | return Some(node); 371 | } 372 | } 373 | } 374 | 375 | #[test] 376 | fn test() { 377 | let doc = syntax::desc::Document::parse( 378 | r#" 379 | panel { 380 | icon(type="warning") 381 | icon(type="warning") 382 | icon(type="cake") 383 | icon(type="warning") 384 | icon(type="test") 385 | } 386 | 387 | "#, 388 | ).unwrap(); 389 | let node = Node::::from_document(doc); 390 | 391 | for n in node.query() 392 | .name("panel") 393 | .child() 394 | .name("icon") 395 | .property("type", "warning") 396 | .matches() 397 | { 398 | assert_eq!(n.name(), Some("icon".to_owned())); 399 | assert_eq!(&*n.get_property_ref::("type").unwrap(), "warning"); 400 | } 401 | } 402 | -------------------------------------------------------------------------------- /src/expr.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use std::fmt::{Formatter, Result as FResult, Display}; 3 | 4 | #[derive(Debug)] 5 | pub enum RectPart { 6 | Width, 7 | Height, 8 | } 9 | 10 | pub enum Expr { 11 | Value(Value), 12 | Variable(String), 13 | ParentRect(RectPart), 14 | VariableParent(usize, String), 15 | 16 | Neg(Box>), 17 | Not(Box>), 18 | And(Box>, Box>), 19 | Or(Box>, Box>), 20 | Xor(Box>, Box>), 21 | 22 | Equal(Box>, Box>), 23 | NotEqual(Box>, Box>), 24 | LessEqual(Box>, Box>), 25 | GreaterEqual(Box>, Box>), 26 | Less(Box>, Box>), 27 | Greater(Box>, Box>), 28 | 29 | Add(Box>, Box>), 30 | Sub(Box>, Box>), 31 | Mul(Box>, Box>), 32 | Div(Box>, Box>), 33 | Rem(Box>, Box>), 34 | 35 | IntToFloat(Box>), 36 | FloatToInt(Box>), 37 | 38 | Call(StaticKey, Vec>), 39 | } 40 | 41 | impl Display for Expr 42 | where E: Extension 43 | { 44 | fn fmt(&self, f: &mut Formatter) -> FResult { 45 | match self { 46 | Expr::Value(Value::Boolean(v)) => write!(f, "{}", v), 47 | Expr::Value(Value::Integer(v)) => write!(f, "{}", v), 48 | Expr::Value(Value::Float(v)) => write!(f, "{}", v), 49 | Expr::Value(Value::String(v)) => write!(f, "{:?}", v), 50 | Expr::Value(Value::ExtValue(_)) => write!(f, "EXT"), 51 | Expr::Variable(var) => write!(f, "{}", var), 52 | Expr::VariableParent(d, var) => write!(f, "{}({})", var, d), 53 | Expr::ParentRect(part) => write!(f, "parent({:?})", part), 54 | 55 | Expr::Neg(e) => write!(f, "-({})", e), 56 | Expr::Not(e) => write!(f, "!({})", e), 57 | Expr::And(a, b) => write!(f, "({} && {})", a, b), 58 | Expr::Or(a, b) => write!(f, "({} || {})", a, b), 59 | Expr::Xor(a, b) => write!(f, "({} ^ {})", a, b), 60 | 61 | Expr::Equal(a, b) => write!(f, "({} == {})", a, b), 62 | Expr::NotEqual(a, b) => write!(f, "({} != {})", a, b), 63 | Expr::LessEqual(a, b) => write!(f, "({} <= {})", a, b), 64 | Expr::GreaterEqual(a, b) => write!(f, "({} >= {})", a, b), 65 | Expr::Less(a, b) => write!(f, "({} < {})", a, b), 66 | Expr::Greater(a, b) => write!(f, "({} > {})", a, b), 67 | 68 | Expr::Add(a, b) => write!(f, "({} + {})", a, b), 69 | Expr::Sub(a, b) => write!(f, "({} - {})", a, b), 70 | Expr::Mul(a, b) => write!(f, "({} * {})", a, b), 71 | Expr::Div(a, b) => write!(f, "({} / {})", a, b), 72 | Expr::Rem(a, b) => write!(f, "({} % {})", a, b), 73 | 74 | Expr::IntToFloat(e) => write!(f, "float({})", e), 75 | Expr::FloatToInt(e) => write!(f, "int({})", e), 76 | 77 | Expr::Call(name, exprs) => { 78 | write!(f, "{}(", name.0)?; 79 | for e in exprs { 80 | write!(f, "{}, ", e)?; 81 | } 82 | write!(f, ")") 83 | }, 84 | } 85 | } 86 | } 87 | 88 | fn get_ty(v: &Value) -> &'static str { 89 | match v { 90 | Value::Integer(_) => "integer", 91 | Value::Float(_) => "float", 92 | Value::Boolean(_) => "boolean", 93 | Value::String(_) => "string", 94 | Value::ExtValue(_) => "extension value", 95 | } 96 | } 97 | 98 | impl Expr 99 | where E: Extension 100 | { 101 | pub fn eval<'a>(&'a self, styles: &'a Styles, node: &'a NodeChain) -> Result, Error<'a>> { 102 | Ok(match *self { 103 | Expr::Value(ref v) => v.clone(), 104 | Expr::Variable(ref n) => return node.properties.get(n).cloned().ok_or(Error::UnknownVariable{name: n}), 105 | Expr::VariableParent(depth, ref n) => { 106 | let mut node = node; 107 | for _ in 0 .. depth { 108 | node = node.parent.expect("Missing parent, shouldn't happen"); 109 | } 110 | return node.properties.get(n).cloned().ok_or(Error::UnknownVariable{name: n}); 111 | }, 112 | Expr::ParentRect(RectPart::Width) => return node.parent 113 | .ok_or(Error::CustomStatic{reason: "No parent"}) 114 | .map(|v| v.draw_rect.width) 115 | .map(Value::Integer), 116 | Expr::ParentRect(RectPart::Height) => return node.parent 117 | .ok_or(Error::CustomStatic{reason: "No parent"}) 118 | .map(|v| v.draw_rect.height) 119 | .map(Value::Integer), 120 | Expr::Neg(ref e) => match e.eval(styles, node)? { 121 | Value::Integer(a) => Value::Integer(-a), 122 | Value::Float(a) => Value::Float(-a), 123 | v => return Err(Error::IncompatibleTypeOp{op: "-", ty: get_ty(&v)}), 124 | }, 125 | Expr::Not(ref e) => match e.eval(styles, node)? { 126 | Value::Boolean(a) => Value::Boolean(!a), 127 | v => return Err(Error::IncompatibleTypeOp{op: "-", ty: get_ty(&v)}), 128 | }, 129 | Expr::IntToFloat(ref e) => match e.eval(styles, node)? { 130 | Value::Integer(a) => Value::Float(a as f64), 131 | v => return Err(Error::IncompatibleTypeOp{op: "-", ty: get_ty(&v)}), 132 | }, 133 | Expr::FloatToInt(ref e) => match e.eval(styles, node)? { 134 | Value::Float(a) => Value::Integer(a as i32), 135 | v => return Err(Error::IncompatibleTypeOp{op: "-", ty: get_ty(&v)}), 136 | }, 137 | 138 | Expr::And(ref a, ref b) => match (a.eval(styles, node)?, b.eval(styles, node)?) { 139 | (Value::Boolean(a), Value::Boolean(b)) => Value::Boolean(a && b), 140 | (a,b) => return Err(Error::IncompatibleTypesOp{op: "&&", left_ty: get_ty(&a), right_ty: get_ty(&b)}), 141 | }, 142 | Expr::Or(ref a, ref b) => match (a.eval(styles, node)?, b.eval(styles, node)?) { 143 | (Value::Boolean(a), Value::Boolean(b)) => Value::Boolean(a || b), 144 | (a,b) => return Err(Error::IncompatibleTypesOp{op: "||", left_ty: get_ty(&a), right_ty: get_ty(&b)}), 145 | }, 146 | Expr::Xor(ref a, ref b) => match (a.eval(styles, node)?, b.eval(styles, node)?) { 147 | (Value::Boolean(a), Value::Boolean(b)) => Value::Boolean(a ^ b), 148 | (a,b) => return Err(Error::IncompatibleTypesOp{op: "^", left_ty: get_ty(&a), right_ty: get_ty(&b)}), 149 | }, 150 | 151 | Expr::Equal(ref a, ref b) => match (a.eval(styles, node)?, b.eval(styles, node)?) { 152 | (Value::Boolean(a), Value::Boolean(b)) => Value::Boolean(a == b), 153 | (a,b) => return Err(Error::IncompatibleTypesOp{op: "==", left_ty: get_ty(&a), right_ty: get_ty(&b)}), 154 | }, 155 | Expr::NotEqual(ref a, ref b) => match (a.eval(styles, node)?, b.eval(styles, node)?) { 156 | (Value::Boolean(a), Value::Boolean(b)) => Value::Boolean(a != b), 157 | (a,b) => return Err(Error::IncompatibleTypesOp{op: "!=", left_ty: get_ty(&a), right_ty: get_ty(&b)}), 158 | }, 159 | Expr::LessEqual(ref a, ref b) => match (a.eval(styles, node)?, b.eval(styles, node)?) { 160 | (Value::Boolean(a), Value::Boolean(b)) => Value::Boolean(a <= b), 161 | (a,b) => return Err(Error::IncompatibleTypesOp{op: "<=", left_ty: get_ty(&a), right_ty: get_ty(&b)}), 162 | }, 163 | Expr::GreaterEqual(ref a, ref b) => match (a.eval(styles, node)?, b.eval(styles, node)?) { 164 | (Value::Boolean(a), Value::Boolean(b)) => Value::Boolean(a >= b), 165 | (a,b) => return Err(Error::IncompatibleTypesOp{op: ">=", left_ty: get_ty(&a), right_ty: get_ty(&b)}), 166 | }, 167 | Expr::Less(ref a, ref b) => match (a.eval(styles, node)?, b.eval(styles, node)?) { 168 | (Value::Boolean(a), Value::Boolean(b)) => Value::Boolean(a < b), 169 | (a,b) => return Err(Error::IncompatibleTypesOp{op: "<", left_ty: get_ty(&a), right_ty: get_ty(&b)}), 170 | }, 171 | Expr::Greater(ref a, ref b) => match (a.eval(styles, node)?, b.eval(styles, node)?) { 172 | (Value::Boolean(a), Value::Boolean(b)) => Value::Boolean(a > b), 173 | (a,b) => return Err(Error::IncompatibleTypesOp{op: ">", left_ty: get_ty(&a), right_ty: get_ty(&b)}), 174 | }, 175 | 176 | Expr::Add(ref a, ref b) => match (a.eval(styles, node)?, b.eval(styles, node)?) { 177 | (Value::Integer(a), Value::Integer(b)) => Value::Integer(a + b), 178 | (Value::Float(a), Value::Float(b)) => Value::Float(a + b), 179 | (a,b) => return Err(Error::IncompatibleTypesOp{op: "+", left_ty: get_ty(&a), right_ty: get_ty(&b)}), 180 | }, 181 | Expr::Sub(ref a, ref b) => match (a.eval(styles, node)?, b.eval(styles, node)?) { 182 | (Value::Integer(a), Value::Integer(b)) => Value::Integer(a - b), 183 | (Value::Float(a), Value::Float(b)) => Value::Float(a - b), 184 | (a,b) => return Err(Error::IncompatibleTypesOp{op: "-", left_ty: get_ty(&a), right_ty: get_ty(&b)}), 185 | }, 186 | Expr::Mul(ref a, ref b) => match (a.eval(styles, node)?, b.eval(styles, node)?) { 187 | (Value::Integer(a), Value::Integer(b)) => Value::Integer(a * b), 188 | (Value::Float(a), Value::Float(b)) => Value::Float(a * b), 189 | (a,b) => return Err(Error::IncompatibleTypesOp{op: "*", left_ty: get_ty(&a), right_ty: get_ty(&b)}), 190 | }, 191 | Expr::Div(ref a, ref b) => match (a.eval(styles, node)?, b.eval(styles, node)?) { 192 | (Value::Integer(a), Value::Integer(b)) => Value::Integer(a / b), 193 | (Value::Float(a), Value::Float(b)) => Value::Float(a / b), 194 | (a,b) => return Err(Error::IncompatibleTypesOp{op: "/", left_ty: get_ty(&a), right_ty: get_ty(&b)}), 195 | }, 196 | Expr::Rem(ref a, ref b) => match (a.eval(styles, node)?, b.eval(styles, node)?) { 197 | (Value::Integer(a), Value::Integer(b)) => Value::Integer(a % b), 198 | (Value::Float(a), Value::Float(b)) => Value::Float(a % b), 199 | (a,b) => return Err(Error::IncompatibleTypesOp{op: "%", left_ty: get_ty(&a), right_ty: get_ty(&b)}), 200 | }, 201 | Expr::Call(ref name, ref args) => { 202 | let func = styles.funcs.get(name).expect("Missing func"); 203 | 204 | let mut args = args.iter() 205 | .map(move |v| v.eval(styles, node)); 206 | return func(&mut args) 207 | } 208 | }) 209 | } 210 | 211 | pub fn from_style<'a>( 212 | static_keys: &FnvHashMap<&'static str, StaticKey>, 213 | replacements: &FnvHashMap, 214 | uses_parent_size: &mut bool, 215 | e: syntax::style::ExprType<'a> 216 | ) -> Result, syntax::PError<'a>> { 217 | use syntax::style::Expr as SExpr; 218 | use syntax::style::Value as SVal; 219 | Ok(match e.expr { 220 | SExpr::Value(v) => match v { 221 | SVal::Boolean(b) => Expr::Value(Value::Boolean(b)), 222 | SVal::Integer(i) => Expr::Value(Value::Integer(i)), 223 | SVal::Float(f) => Expr::Value(Value::Float(f)), 224 | SVal::String(s) => Expr::Value(Value::String(unescape(s))), 225 | SVal::Variable(v) => if let Some(r) = replacements.get(v.name) { 226 | if r.0 == 0 { 227 | Expr::Variable(r.1.clone()) 228 | } else { 229 | Expr::VariableParent(r.0, r.1.clone()) 230 | } 231 | } else { 232 | *uses_parent_size = true; 233 | match v.name { 234 | "parent_width" => Expr::ParentRect(RectPart::Width), 235 | "parent_height" => Expr::ParentRect(RectPart::Height), 236 | _ => return Err(syntax::Errors::new( 237 | v.position.into(), 238 | syntax::Error::Message(syntax::Info::Borrowed("Unknown variable")), 239 | )) 240 | } 241 | }, 242 | }, 243 | SExpr::Neg(e) => Expr::Neg(Box::new(Expr::from_style(static_keys, replacements, uses_parent_size, *e)?)), 244 | 245 | SExpr::Not(e) => Expr::Not(Box::new(Expr::from_style(static_keys, replacements, uses_parent_size, *e)?)), 246 | SExpr::And(l, r) => Expr::And( 247 | Box::new(Expr::from_style(static_keys, replacements, uses_parent_size, *l)?), 248 | Box::new(Expr::from_style(static_keys, replacements, uses_parent_size, *r)?), 249 | ), 250 | SExpr::Or(l, r) => Expr::Or( 251 | Box::new(Expr::from_style(static_keys, replacements, uses_parent_size, *l)?), 252 | Box::new(Expr::from_style(static_keys, replacements, uses_parent_size, *r)?), 253 | ), 254 | SExpr::Xor(l, r) => Expr::Xor( 255 | Box::new(Expr::from_style(static_keys, replacements, uses_parent_size, *l)?), 256 | Box::new(Expr::from_style(static_keys, replacements, uses_parent_size, *r)?), 257 | ), 258 | 259 | SExpr::Add(l, r) => Expr::Add( 260 | Box::new(Expr::from_style(static_keys, replacements, uses_parent_size, *l)?), 261 | Box::new(Expr::from_style(static_keys, replacements, uses_parent_size, *r)?), 262 | ), 263 | SExpr::Sub(l, r) => Expr::Sub( 264 | Box::new(Expr::from_style(static_keys, replacements, uses_parent_size, *l)?), 265 | Box::new(Expr::from_style(static_keys, replacements, uses_parent_size, *r)?), 266 | ), 267 | SExpr::Mul(l, r) => Expr::Mul( 268 | Box::new(Expr::from_style(static_keys, replacements, uses_parent_size, *l)?), 269 | Box::new(Expr::from_style(static_keys, replacements, uses_parent_size, *r)?), 270 | ), 271 | SExpr::Div(l, r) => Expr::Div( 272 | Box::new(Expr::from_style(static_keys, replacements, uses_parent_size, *l)?), 273 | Box::new(Expr::from_style(static_keys, replacements, uses_parent_size, *r)?), 274 | ), 275 | SExpr::Rem(l, r) => Expr::Rem( 276 | Box::new(Expr::from_style(static_keys, replacements, uses_parent_size, *l)?), 277 | Box::new(Expr::from_style(static_keys, replacements, uses_parent_size, *r)?), 278 | ), 279 | 280 | SExpr::Equal(l, r) => Expr::Equal( 281 | Box::new(Expr::from_style(static_keys, replacements, uses_parent_size, *l)?), 282 | Box::new(Expr::from_style(static_keys, replacements, uses_parent_size, *r)?), 283 | ), 284 | SExpr::NotEqual(l, r) => Expr::NotEqual( 285 | Box::new(Expr::from_style(static_keys, replacements, uses_parent_size, *l)?), 286 | Box::new(Expr::from_style(static_keys, replacements, uses_parent_size, *r)?), 287 | ), 288 | SExpr::LessEqual(l, r) => Expr::LessEqual( 289 | Box::new(Expr::from_style(static_keys, replacements, uses_parent_size, *l)?), 290 | Box::new(Expr::from_style(static_keys, replacements, uses_parent_size, *r)?), 291 | ), 292 | SExpr::GreaterEqual(l, r) => Expr::GreaterEqual( 293 | Box::new(Expr::from_style(static_keys, replacements, uses_parent_size, *l)?), 294 | Box::new(Expr::from_style(static_keys, replacements, uses_parent_size, *r)?), 295 | ), 296 | SExpr::Less(l, r) => Expr::Less( 297 | Box::new(Expr::from_style(static_keys, replacements, uses_parent_size, *l)?), 298 | Box::new(Expr::from_style(static_keys, replacements, uses_parent_size, *r)?), 299 | ), 300 | SExpr::Greater(l, r) => Expr::Greater( 301 | Box::new(Expr::from_style(static_keys, replacements, uses_parent_size, *l)?), 302 | Box::new(Expr::from_style(static_keys, replacements, uses_parent_size, *r)?), 303 | ), 304 | 305 | SExpr::IntToFloat(e) => Expr::IntToFloat(Box::new(Expr::from_style(static_keys, replacements, uses_parent_size, *e)?)), 306 | SExpr::FloatToInt(e) => Expr::FloatToInt(Box::new(Expr::from_style(static_keys, replacements, uses_parent_size, *e)?)), 307 | 308 | SExpr::Call(name, params) => { 309 | let key = static_keys.get(name.name).ok_or_else(|| { 310 | syntax::Errors::new( 311 | name.position.into(), 312 | syntax::Error::Message(syntax::Info::Borrowed("Unknown function")), 313 | ) 314 | })?; 315 | Expr::Call(*key, params.into_iter() 316 | .map(|v| Expr::from_style(static_keys, replacements, uses_parent_size, v)) 317 | .collect::, _>>()? 318 | ) 319 | }, 320 | 321 | }) 322 | } 323 | } -------------------------------------------------------------------------------- /src/layout.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | use std::marker::PhantomData; 4 | use std::cell::RefMut; 5 | 6 | /// Used to position an element within another element. 7 | /// 8 | /// The order of method calls during layout is as followed 9 | /// 10 | /// ```ignore 11 | /// 12 | /// parent_layout.do_layout(...); 13 | /// current_layout.start_layout(...); 14 | /// 15 | /// for child in children { 16 | /// current_layout.do_layout(...); 17 | /// child_layout.start_layout(...); 18 | /// 19 | /// // repeat for children 20 | /// 21 | /// child_layout.finish_layout(...); 22 | /// current_layout.do_layout_end(...); 23 | /// } 24 | /// 25 | /// current_layout.finish_layout(...); 26 | /// parent_layout.do_layout_end(...); 27 | /// ``` 28 | pub trait LayoutEngine 29 | where E: Extension 30 | { 31 | /// The type of the data that will be stored on child nodes 32 | type ChildData: 'static; 33 | 34 | /// The name of this layout as it will be referenced in style rules 35 | fn name() -> &'static str; 36 | 37 | /// Called to register the properties used by the layout. 38 | /// 39 | /// # Example 40 | /// ```ignore 41 | /// static MY_PROP: StaticKey = StaticKey("my_prop"); 42 | /// 43 | /// // ... 44 | /// 45 | /// fn style_properties<'a, F>(mut prop: F) 46 | /// where F: FnMut(StaticKey) + 'a 47 | /// { 48 | /// prop(MY_PROP); 49 | /// } 50 | /// 51 | /// ``` 52 | fn style_properties<'a, F>(prop: F) 53 | where F: FnMut(StaticKey) + 'a; 54 | 55 | /// Creates a new child data to be stored on a node 56 | fn new_child_data() -> Self::ChildData; 57 | 58 | /// Called to apply a given style rule on a node 59 | /// 60 | /// Its recomended to use the `eval!` macro to check for relevant properties 61 | /// as it also skips ones that have already been set by a another rule. 62 | /// 63 | /// # Example 64 | /// ```ignore 65 | /// 66 | /// fn update_data(&mut self, styles: &Styles, nc: &NodeChain, rule: &Rule) -> DirtyFlags { 67 | /// let mut flags = DirtyFlags::empty(); 68 | /// eval!(styles, nc, rule.X => val => { 69 | /// let new = val.convert(); 70 | /// if self.x != new { 71 | /// self.x = new; 72 | /// flags |= DirtyFlags::POSITION; 73 | /// } 74 | /// }); 75 | /// flags 76 | /// } 77 | /// ``` 78 | fn update_data(&mut self, _styles: &Styles, _nc: &NodeChain, _rule: &Rule) -> DirtyFlags { 79 | DirtyFlags::empty() 80 | } 81 | 82 | /// Called to apply a given style rule on a child node 83 | /// 84 | /// Its recomended to use the `eval!` macro to check for relevant properties 85 | /// as it also skips ones that have already been set by a another rule. 86 | /// 87 | /// # Example 88 | /// ```ignore 89 | /// 90 | /// fn update_child_data(&mut self, styles: &Styles, nc: &NodeChain, rule: &Rule, data: &mut Self::ChildData) -> DirtyFlags { 91 | /// let mut flags = DirtyFlags::empty(); 92 | /// eval!(styles, nc, rule.X => val => { 93 | /// let new = val.convert(); 94 | /// if data.x != new { 95 | /// data.x = new; 96 | /// flags |= DirtyFlags::POSITION; 97 | /// } 98 | /// }); 99 | /// flags 100 | /// } 101 | /// ``` 102 | fn update_child_data(&mut self, _styles: &Styles, _nc: &NodeChain, _rule: &Rule, _data: &mut Self::ChildData) -> DirtyFlags { 103 | DirtyFlags::empty() 104 | } 105 | 106 | /// Called after applying all relevant rules to reset any properties that 107 | /// weren't set. 108 | /// 109 | /// This is needed because a node could have a property set previously and 110 | /// then later (e.g. when a property is changed) no longer have it set. 111 | /// Due to it no longer being set `update_data` would not be called for 112 | /// that property leaving it stuck with its previous value. 113 | /// 114 | /// `used_keys` will contain every property key that was used by rules 115 | /// in this update, if the key isn't in this set it should be reset. 116 | fn reset_unset_data(&mut self, _used_keys: &FnvHashSet) -> DirtyFlags { 117 | DirtyFlags::empty() 118 | } 119 | /// Called after applying all relevant rules to a child reset any properties 120 | /// that weren't set. 121 | /// 122 | /// This is needed because a node could have a property set previously and 123 | /// then later (e.g. when a property is changed) no longer have it set. 124 | /// Due to it no longer being set `update_data` would not be called for 125 | /// that property leaving it stuck with its previous value. 126 | /// 127 | /// `used_keys` will contain every property key that was used by rules 128 | /// in this update, if the key isn't in this set it should be reset. 129 | fn reset_unset_child_data(&mut self, _used_keys: &FnvHashSet, _data: &mut Self::ChildData) -> DirtyFlags { 130 | DirtyFlags::empty() 131 | } 132 | 133 | /// Called to check the parent node's dirty flags to update its own dirty flags 134 | fn check_parent_flags(&mut self, _flags: DirtyFlags) -> DirtyFlags { 135 | DirtyFlags::empty() 136 | } 137 | /// Called to check the child nodes' dirty flags to update its own dirty flags 138 | fn check_child_flags(&mut self, _flags: DirtyFlags) -> DirtyFlags { 139 | DirtyFlags::empty() 140 | } 141 | 142 | /// Begins the layout for this node 143 | /// 144 | /// Called after the parent node's layout has called its `do_layout` method 145 | fn start_layout(&mut self, _ext: &mut E::NodeData, current: Rect, _flags: DirtyFlags, _children: ChildAccess) -> Rect { 146 | current 147 | } 148 | 149 | /// Begins the layout for a child node of this layout 150 | /// 151 | /// Called before the child node's layout's `start_layout` method 152 | fn do_layout(&mut self, _value: &NodeValue, _ext: &mut E::NodeData, _data: &mut Self::ChildData, current: Rect, _flags: DirtyFlags) -> Rect { 153 | current 154 | } 155 | 156 | /// Ends the layout for this child node 157 | /// 158 | /// Called after all the child nodes have had their layout called 159 | fn do_layout_end(&mut self, _value: &NodeValue, _ext: &mut E::NodeData, _data: &mut Self::ChildData, current: Rect, _flags: DirtyFlags) -> Rect { 160 | current 161 | } 162 | /// Ends the layout for this node 163 | /// 164 | /// Called after all the child nodes have had `do_layout(_end)` called 165 | fn finish_layout(&mut self, _ext: &mut E::NodeData, current: Rect, _flags: DirtyFlags, _children: ChildAccess) -> Rect { 166 | current 167 | } 168 | } 169 | 170 | /// Provides access to a child node and its stored layout data 171 | pub struct ChildAccess<'a, L: LayoutEngine + ?Sized, E: Extension + 'a> { 172 | _l: PhantomData, 173 | nodes: &'a [Node], 174 | } 175 | 176 | /// Helper struct to split a `RefMut` on a `NodeInner` whilst 177 | /// `RefMut::split` is unstable. 178 | pub struct NodeAccess<'a, L: LayoutEngine + ?Sized, E: Extension + 'a> { 179 | node: RefMut<'a, NodeInner>, 180 | _l: PhantomData, 181 | } 182 | 183 | impl <'a, L, E> NodeAccess<'a, L, E> 184 | where L: LayoutEngine, 185 | E: Extension 186 | { 187 | /// Splits this node access into its value and the data stored 188 | /// on it for this layout. 189 | #[inline] 190 | pub fn split(&mut self) -> (&mut NodeValue, &mut L::ChildData) { 191 | let node: &mut _ = &mut *self.node; 192 | ( 193 | &mut node.value, 194 | node.parent_data.downcast_mut::() 195 | .expect("Child has incorrect data") 196 | ) 197 | } 198 | } 199 | 200 | impl <'a, L, E> ChildAccess<'a, L, E> 201 | where L: LayoutEngine, 202 | E: Extension 203 | { 204 | /// Returns the number of child nodes 205 | #[inline] 206 | pub fn len(&self) -> usize { 207 | self.nodes.len() 208 | } 209 | 210 | /// Returns the child's size, flags and data for the given 211 | /// index if any. 212 | #[inline] 213 | pub fn get(&self, idx: usize) -> Option<(Rect, DirtyFlags, NodeAccess)> { 214 | let n = self.nodes.get(idx)?; 215 | let nr = n.inner.borrow_mut(); 216 | let draw_rect = nr.draw_rect; 217 | let flags = nr.dirty_flags; 218 | 219 | Some((draw_rect, flags, NodeAccess { 220 | node: nr, 221 | _l: PhantomData, 222 | })) 223 | } 224 | } 225 | 226 | 227 | pub(crate) trait BoxLayoutEngine 228 | where E: Extension 229 | { 230 | fn name(&self) -> &'static str; 231 | fn update_data(&mut self, styles: &Styles, nc: &NodeChain, rule: &Rule) -> DirtyFlags; 232 | fn update_child_data(&mut self, styles: &Styles, nc: &NodeChain, rule: &Rule, data: &mut Box) -> DirtyFlags; 233 | fn reset_unset_data(&mut self, used_keys: &FnvHashSet) -> DirtyFlags; 234 | fn reset_unset_child_data(&mut self, used_keys: &FnvHashSet, data: &mut Box) -> DirtyFlags; 235 | fn check_parent_flags(&mut self, flags: DirtyFlags) -> DirtyFlags; 236 | fn check_child_flags(&mut self, flags: DirtyFlags) -> DirtyFlags; 237 | 238 | fn start_layout(&mut self, _ext: &mut E::NodeData, current: Rect, flags: DirtyFlags, children: &[Node]) -> Rect; 239 | fn do_layout(&mut self, value: &NodeValue, _ext: &mut E::NodeData, data: &mut Box, current: Rect, flags: DirtyFlags) -> Rect; 240 | fn do_layout_end(&mut self, value: &NodeValue, _ext: &mut E::NodeData, data: &mut Box, current: Rect, flags: DirtyFlags) -> Rect; 241 | fn finish_layout(&mut self, _ext: &mut E::NodeData, current: Rect, flags: DirtyFlags, children: &[Node]) -> Rect; 242 | } 243 | 244 | impl BoxLayoutEngine for T 245 | where E: Extension, 246 | T: LayoutEngine 247 | { 248 | fn name(&self) -> &'static str { 249 | T::name() 250 | } 251 | fn update_data(&mut self, styles: &Styles, nc: &NodeChain, rule: &Rule) -> DirtyFlags { 252 | LayoutEngine::update_data(self, styles, nc, rule) 253 | } 254 | 255 | fn update_child_data(&mut self, styles: &Styles, nc: &NodeChain, rule: &Rule, data: &mut Box) -> DirtyFlags { 256 | if !data.is::<>::ChildData>() { 257 | *data = Box::new(Self::new_child_data()); 258 | } 259 | let data = data.downcast_mut::<>::ChildData>().expect("Failed to access child data"); 260 | LayoutEngine::update_child_data(self, styles, nc, rule, data) 261 | } 262 | 263 | fn reset_unset_data(&mut self, used_keys: &FnvHashSet) -> DirtyFlags { 264 | LayoutEngine::reset_unset_data(self, used_keys) 265 | } 266 | fn reset_unset_child_data(&mut self, used_keys: &FnvHashSet, data: &mut Box) -> DirtyFlags { 267 | if !data.is::<>::ChildData>() { 268 | *data = Box::new(Self::new_child_data()); 269 | } 270 | let data = data.downcast_mut::<>::ChildData>().expect("Failed to access child data"); 271 | LayoutEngine::reset_unset_child_data(self, used_keys, data) 272 | } 273 | 274 | fn check_parent_flags(&mut self, flags: DirtyFlags) -> DirtyFlags { 275 | LayoutEngine::check_parent_flags(self, flags) 276 | } 277 | fn check_child_flags(&mut self, flags: DirtyFlags) -> DirtyFlags { 278 | LayoutEngine::check_child_flags(self, flags) 279 | } 280 | 281 | fn start_layout(&mut self, ext: &mut E::NodeData, current: Rect, flags: DirtyFlags, children: &[Node]) -> Rect { 282 | LayoutEngine::start_layout(self, ext, current, flags, ChildAccess{_l: PhantomData, nodes: children}) 283 | } 284 | fn do_layout(&mut self, value: &NodeValue, ext: &mut E::NodeData, data: &mut Box, current: Rect, flags: DirtyFlags) -> Rect { 285 | let data = data.downcast_mut::<>::ChildData>().expect("Failed to access child data"); 286 | LayoutEngine::do_layout(self, value, ext, data, current, flags) 287 | } 288 | fn do_layout_end(&mut self, value: &NodeValue, ext: &mut E::NodeData, data: &mut Box, current: Rect, flags: DirtyFlags) -> Rect { 289 | let data = data.downcast_mut::<>::ChildData>().expect("Failed to access child data"); 290 | LayoutEngine::do_layout_end(self, value, ext, data, current, flags) 291 | } 292 | fn finish_layout(&mut self, ext: &mut E::NodeData, current: Rect, flags: DirtyFlags, children: &[Node]) -> Rect { 293 | LayoutEngine::finish_layout(self, ext, current, flags, ChildAccess{_l: PhantomData, nodes: children}) 294 | } 295 | } 296 | 297 | #[derive(Default)] 298 | pub(crate) struct AbsoluteLayout { 299 | } 300 | #[derive(Default)] 301 | pub(crate) struct AbsoluteLayoutChild { 302 | x: Option, 303 | y: Option, 304 | width: Option, 305 | height: Option, 306 | } 307 | 308 | /// The "x" static key used by the absolute layout 309 | /// 310 | /// This should be used if you wish to use "x" in your 311 | /// own layouts due to the fact that two static strings 312 | /// across crates/modules don't always point to the same 313 | /// value which is a requirement for static keys. 314 | pub static X: StaticKey = StaticKey("x"); 315 | /// The "y" static key used by the absolute layout 316 | /// 317 | /// This should be used if you wish to use "x" in your 318 | /// own layouts due to the fact that two static strings 319 | /// across crates/modules don't always point to the same 320 | /// value which is a requirement for static keys. 321 | pub static Y: StaticKey = StaticKey("y"); 322 | /// The "width" static key used by the absolute layout 323 | /// 324 | /// This should be used if you wish to use "x" in your 325 | /// own layouts due to the fact that two static strings 326 | /// across crates/modules don't always point to the same 327 | /// value which is a requirement for static keys. 328 | pub static WIDTH: StaticKey = StaticKey("width"); 329 | /// The "height" static key used by the absolute layout 330 | /// 331 | /// This should be used if you wish to use "x" in your 332 | /// own layouts due to the fact that two static strings 333 | /// across crates/modules don't always point to the same 334 | /// value which is a requirement for static keys. 335 | pub static HEIGHT: StaticKey = StaticKey("height"); 336 | 337 | impl LayoutEngine for AbsoluteLayout 338 | where E: Extension 339 | { 340 | type ChildData = AbsoluteLayoutChild; 341 | 342 | fn name() -> &'static str { "absolute" } 343 | fn style_properties<'a, F>(mut prop: F) 344 | where F: FnMut(StaticKey) + 'a 345 | { 346 | prop(X); 347 | prop(Y); 348 | prop(WIDTH); 349 | prop(HEIGHT); 350 | } 351 | 352 | fn new_child_data() -> AbsoluteLayoutChild { 353 | AbsoluteLayoutChild::default() 354 | } 355 | 356 | fn update_data(&mut self, _styles: &Styles, _nc: &NodeChain, _rule: &Rule) -> DirtyFlags { 357 | DirtyFlags::empty() 358 | } 359 | fn update_child_data(&mut self, styles: &Styles, nc: &NodeChain, rule: &Rule, data: &mut Self::ChildData) -> DirtyFlags { 360 | let mut flags = DirtyFlags::empty(); 361 | eval!(styles, nc, rule.X => val => { 362 | let new = val.convert(); 363 | if data.x != new { 364 | data.x = new; 365 | flags |= DirtyFlags::POSITION; 366 | } 367 | }); 368 | eval!(styles, nc, rule.Y => val => { 369 | let new = val.convert(); 370 | if data.y != new { 371 | data.y = new; 372 | flags |= DirtyFlags::POSITION; 373 | } 374 | }); 375 | eval!(styles, nc, rule.WIDTH => val => { 376 | let new = val.convert(); 377 | if data.width != new { 378 | data.width = new; 379 | flags |= DirtyFlags::SIZE; 380 | } 381 | }); 382 | eval!(styles, nc, rule.HEIGHT => val => { 383 | let new = val.convert(); 384 | if data.height != new { 385 | data.height = new; 386 | flags |= DirtyFlags::SIZE; 387 | } 388 | }); 389 | flags 390 | } 391 | 392 | fn reset_unset_data(&mut self, _used_keys: &FnvHashSet) -> DirtyFlags { 393 | DirtyFlags::empty() 394 | } 395 | fn reset_unset_child_data(&mut self, used_keys: &FnvHashSet, data: &mut Self::ChildData) -> DirtyFlags { 396 | let mut flags = DirtyFlags::empty(); 397 | if !used_keys.contains(&X) && data.x.is_some() { 398 | data.x = None; 399 | flags |= DirtyFlags::POSITION; 400 | } 401 | if !used_keys.contains(&Y) && data.y.is_some() { 402 | data.y = None; 403 | flags |= DirtyFlags::POSITION; 404 | } 405 | if !used_keys.contains(&WIDTH) && data.width.is_some() { 406 | data.width = None; 407 | flags |= DirtyFlags::SIZE; 408 | } 409 | if !used_keys.contains(&HEIGHT) && data.height.is_some() { 410 | data.height = None; 411 | flags |= DirtyFlags::SIZE; 412 | } 413 | 414 | flags 415 | } 416 | 417 | fn do_layout(&mut self, _value: &NodeValue, _ext: &mut E::NodeData, data: &mut Self::ChildData, mut current: Rect, _flags: DirtyFlags) -> Rect { 418 | data.x.map(|v| current.x = v); 419 | data.y.map(|v| current.y = v); 420 | data.width.map(|v| current.width = v); 421 | data.height.map(|v| current.height = v); 422 | current 423 | } 424 | } -------------------------------------------------------------------------------- /syntax/src/style/mod.rs: -------------------------------------------------------------------------------- 1 | //! Parser for the UI style format 2 | //! 3 | //! This module contains the AST and parser for the 4 | //! format used to style and position a UI element. 5 | //! 6 | //! The format is as follows: 7 | //! 8 | //! ```text,ignore 9 | //! // Comments (only single line) 10 | //! 11 | //! // Name of an element. Can be made up from any 12 | //! // letter, number or _ 13 | //! root > panel > image(width=width, height=height) { 14 | //! width = width, 15 | //! height = height, 16 | //! } 17 | //! emoji(type="smile") { 18 | //! image = "icons/smile.png", 19 | //! } 20 | //! panel > @text { 21 | //! color = "#0050AA", 22 | //! } 23 | //! ``` 24 | 25 | use fnv::FnvHashMap; 26 | use common::*; 27 | 28 | use combine::*; 29 | use combine::parser::char::*; 30 | use combine::error::*; 31 | use combine::Stream; 32 | use combine::easy::{ParseError,}; 33 | use combine::stream::state::{State, SourcePosition}; 34 | use super::{Ident, Position}; 35 | use std::fmt::Debug; 36 | 37 | /// A UI style document 38 | #[derive(Debug)] 39 | pub struct Document<'a> { 40 | /// A list of rules in this document 41 | pub rules: Vec>, 42 | } 43 | 44 | impl <'a> Document<'a> { 45 | /// Attempts to parse the given string as a document. 46 | /// 47 | /// This fails when a syntax error occurs. The returned 48 | /// error can be formatted in a user friendly format 49 | /// via the [`format_parse_error`] method. 50 | /// 51 | /// # Example 52 | /// 53 | /// ``` 54 | /// # use fungui_syntax::style::Document; 55 | /// assert!(Document::parse(r##" 56 | /// panel { 57 | /// background = "#ff0000", 58 | /// } 59 | /// "##).is_ok()); 60 | /// ``` 61 | /// 62 | /// [`format_parse_error`]: ../fn.format_parse_error.html 63 | pub fn parse(source: &str) -> Result>> { 64 | let (doc, _) = parse_document().easy_parse(State::new(source))?; 65 | Ok(doc) 66 | } 67 | } 68 | 69 | #[derive(Debug, Clone)] 70 | pub struct Rule<'a> { 71 | pub matchers: Vec<(Matcher<'a>, FnvHashMap, ValueType<'a>>)>, 72 | pub styles: FnvHashMap, ExprType<'a>>, 73 | } 74 | 75 | #[derive(Debug, Clone)] 76 | pub enum Matcher<'a> { 77 | Element(Element<'a>), 78 | Text, 79 | } 80 | 81 | /// An element which can contain other elements and/or 82 | /// have properties attached. 83 | /// 84 | /// An element does nothing by itself (bar special elements 85 | /// as defined by the program, widgets) and must be controlled 86 | /// via a style document. 87 | #[derive(Debug, Clone)] 88 | pub struct Element<'a> { 89 | /// The name of this element 90 | pub name: Ident<'a>, 91 | } 92 | 93 | /// Contains a value and debugging information 94 | /// for the value. 95 | #[derive(Debug, Clone)] 96 | pub struct ValueType<'a> { 97 | /// The parsed value 98 | pub value: Value<'a>, 99 | /// The position of the value within the source. 100 | /// 101 | /// Used for debugging. 102 | pub position: Position, 103 | } 104 | 105 | /// A parsed value for a property 106 | #[derive(Debug, Clone)] 107 | pub enum Value<'a> { 108 | /// A boolean value 109 | Boolean(bool), 110 | /// A 32 bit integer 111 | Integer(i32), 112 | /// A 64 bit float (of the form `0.0`) 113 | Float(f64), 114 | /// A quoted string 115 | String(&'a str), 116 | /// A variable name 117 | Variable(Ident<'a>), 118 | } 119 | 120 | #[derive(Debug, Clone)] 121 | pub struct ExprType<'a> { 122 | /// The parsed value 123 | pub expr: Expr<'a>, 124 | /// The position of the value within the source. 125 | /// 126 | /// Used for debugging. 127 | pub position: Position, 128 | } 129 | 130 | #[derive(Debug, Clone)] 131 | pub enum Expr<'a> { 132 | Value(Value<'a>), 133 | Neg(Box>), 134 | 135 | Not(Box>), 136 | And(Box>, Box>), 137 | Or(Box>, Box>), 138 | Xor(Box>, Box>), 139 | 140 | Add(Box>, Box>), 141 | Sub(Box>, Box>), 142 | Mul(Box>, Box>), 143 | Div(Box>, Box>), 144 | Rem(Box>, Box>), 145 | 146 | Equal(Box>, Box>), 147 | NotEqual(Box>, Box>), 148 | LessEqual(Box>, Box>), 149 | GreaterEqual(Box>, Box>), 150 | Less(Box>, Box>), 151 | Greater(Box>, Box>), 152 | 153 | IntToFloat(Box>), 154 | FloatToInt(Box>), 155 | 156 | Call(Ident<'a>, Vec>), 157 | } 158 | 159 | fn parse_document<'a, I>() -> impl Parser> 160 | where 161 | I: Debug + Stream + RangeStream + 'a, 162 | ::Error: combine::ParseError, 163 | { 164 | let rule = (parse_rule(), spaces()).map(|v| v.0); 165 | spaces() 166 | .with(many1(rule)) 167 | .map(|e| Document { rules: e }) 168 | } 169 | 170 | fn parse_rule<'a, I>() -> impl Parser> 171 | where 172 | I: Debug + Stream + RangeStream + 'a, 173 | ::Error: combine::ParseError, 174 | { 175 | let comments = skip_many(skip_comment()); 176 | 177 | let matcher = ( 178 | try(spaces().with(string("@text").map(|_| Matcher::Text))) 179 | .or(parse_element().map(|v| Matcher::Element(v))), 180 | optional(properties()).map(|v| v.unwrap_or_default()), 181 | ); 182 | 183 | let rule = ( 184 | sep_by1(try(matcher), try(spaces().with(token('>')))), 185 | spaces().with(parser(styles)), 186 | ); 187 | 188 | spaces() 189 | .with(comments) 190 | .with(rule) 191 | .map(|v| { 192 | Rule { 193 | matchers: v.0, 194 | styles: v.1, 195 | } 196 | }) 197 | } 198 | 199 | fn parse_element<'a, I>() -> impl Parser> 200 | where 201 | I: Debug + Stream + RangeStream + 'a, 202 | ::Error: combine::ParseError, 203 | { 204 | let comments = skip_many(skip_comment()); 205 | 206 | let element = ident().skip(look_ahead(char('{').or(char('(')).or(space()).map(|_| ()))); 207 | 208 | spaces() 209 | .with(comments) 210 | .with(element) 211 | .map(|v| Element { name: v }) 212 | } 213 | 214 | fn styles<'a, I>(input: &mut I) -> ParseResult, ExprType<'a>>, I> 215 | where 216 | I: Debug + Stream + RangeStream + 'a, 217 | ::Error: combine::ParseError, 218 | { 219 | let (_, _) = char('{').parse_stream(input)?; 220 | 221 | enum Flow { 222 | Continue(T), 223 | Break, 224 | } 225 | 226 | let mut styles = FnvHashMap::default(); 227 | loop { 228 | let prop = (style_property(), optional(token(','))); 229 | let (ret, _) = spaces() 230 | .with(skip_many(skip_comment())) 231 | .with( 232 | try(char('}').map(|_| Flow::Break)) 233 | .or( 234 | prop 235 | .map(|v| Flow::Continue(v.0)) 236 | ), 237 | ) 238 | .parse_stream(input)?; 239 | if let Flow::Continue(s) = ret { 240 | styles.insert(s.0, s.1); 241 | } else { 242 | break; 243 | } 244 | } 245 | Ok((styles, Consumed::Consumed(()))) 246 | } 247 | 248 | fn style_property<'a, I>() -> impl Parser, ExprType<'a>)> 249 | where 250 | I: Debug + Stream + RangeStream + 'a, 251 | ::Error: combine::ParseError, 252 | { 253 | ( 254 | spaces().with(ident()), 255 | spaces().with(token('=')), 256 | spaces().with(parser(expr)), 257 | ).map(|v| (v.0, v.2)) 258 | } 259 | 260 | fn expr<'a, I>(input: &mut I) -> ParseResult, I> 261 | where 262 | I: Debug + Stream + RangeStream + 'a, 263 | ::Error: combine::ParseError, 264 | { 265 | let skip_spaces = || spaces().silent(); 266 | 267 | let (mut current, _) = 268 | skip_spaces() 269 | .with(parser(bool_ops)) 270 | .skip(skip_spaces()) 271 | .parse_stream(input)?; 272 | 273 | loop { 274 | let (op, _) = match (position(), choice(( 275 | attempt(string("==")), 276 | attempt(string("!=")), 277 | attempt(string("<=")), 278 | attempt(string(">=")), 279 | string("<"), 280 | string(">"), 281 | ))) 282 | .skip(skip_spaces()) 283 | .parse_stream(input) 284 | { 285 | Ok(v) => v, 286 | Err(_) => break, 287 | }; 288 | let (other, _) = parser(bool_ops) 289 | .skip(skip_spaces()) 290 | .parse_stream(input)?; 291 | current = ExprType { 292 | position: SourcePosition::into(op.0), 293 | expr: match op.1 { 294 | "==" => Expr::Equal(Box::new(current), Box::new(other)), 295 | "!=" => Expr::NotEqual(Box::new(current), Box::new(other)), 296 | "<=" => Expr::LessEqual(Box::new(current), Box::new(other)), 297 | ">=" => Expr::GreaterEqual(Box::new(current), Box::new(other)), 298 | "<" => Expr::Less(Box::new(current), Box::new(other)), 299 | ">" => Expr::Greater(Box::new(current), Box::new(other)), 300 | _ => unreachable!(), 301 | }, 302 | }; 303 | } 304 | Ok((current, Consumed::Consumed(()))) 305 | } 306 | 307 | fn bool_ops<'a, I>(input: &mut I) -> ParseResult, I> 308 | where 309 | I: Debug + Stream + RangeStream + 'a, 310 | ::Error: combine::ParseError, 311 | { 312 | let skip_spaces = || spaces().silent(); 313 | 314 | let (mut current, _) = parser(term1) 315 | .skip(skip_spaces()) 316 | .parse_stream(input)?; 317 | 318 | loop { 319 | let (op, _) = match (position(), choice(( 320 | attempt(string("&&")), 321 | attempt(string("||")), 322 | string("^"), 323 | ))) 324 | .skip(skip_spaces()) 325 | .parse_stream(input) 326 | { 327 | Ok(v) => v, 328 | Err(_) => break, 329 | }; 330 | let (other, _) = parser(term1) 331 | .skip(skip_spaces()) 332 | .parse_stream(input)?; 333 | current = ExprType { 334 | position: SourcePosition::into(op.0), 335 | expr: match op.1 { 336 | "&&" => Expr::And(Box::new(current), Box::new(other)), 337 | "||" => Expr::Or(Box::new(current), Box::new(other)), 338 | "^" => Expr::Xor(Box::new(current), Box::new(other)), 339 | _ => unreachable!(), 340 | }, 341 | }; 342 | } 343 | 344 | Ok((current, Consumed::Consumed(()))) 345 | } 346 | 347 | fn term1<'a, I>(input: &mut I) -> ParseResult, I> 348 | where 349 | I: Debug + Stream + RangeStream + 'a, 350 | ::Error: combine::ParseError, 351 | { 352 | let skip_spaces = || spaces().silent(); 353 | 354 | let (mut current, _) = parser(term2) 355 | .skip(skip_spaces()) 356 | .parse_stream(input)?; 357 | 358 | loop { 359 | let (op, _) = match (position(), choice((char('+'), char('-')))) 360 | .skip(skip_spaces()) 361 | .parse_stream(input) 362 | { 363 | Ok(v) => v, 364 | Err(_) => break, 365 | }; 366 | let (other, _) = parser(term2) 367 | .skip(skip_spaces()) 368 | .parse_stream(input)?; 369 | current = ExprType { 370 | position: SourcePosition::into(op.0), 371 | expr: match op.1 { 372 | '+' => Expr::Add(Box::new(current), Box::new(other)), 373 | '-' => Expr::Sub(Box::new(current), Box::new(other)), 374 | _ => unreachable!(), 375 | }, 376 | }; 377 | } 378 | 379 | Ok((current, Consumed::Consumed(()))) 380 | } 381 | 382 | fn term2<'a, I>(input: &mut I) -> ParseResult, I> 383 | where 384 | I: Debug + Stream + RangeStream + 'a, 385 | ::Error: combine::ParseError, 386 | { 387 | let skip_spaces = || spaces().silent(); 388 | 389 | let (mut current, _) = factor() 390 | .skip(skip_spaces()) 391 | .parse_stream(input)?; 392 | 393 | loop { 394 | let (op, _) = match (position(), choice((char('*'), char('/'), char('%')))) 395 | .skip(skip_spaces()) 396 | .parse_stream(input) 397 | { 398 | Ok(v) => v, 399 | Err(_) => break, 400 | }; 401 | let (other, _) = factor() 402 | .skip(skip_spaces()) 403 | .parse_stream(input)?; 404 | current = ExprType { 405 | position: SourcePosition::into(op.0), 406 | expr: match op.1 { 407 | '*' => Expr::Mul(Box::new(current), Box::new(other)), 408 | '/' => Expr::Div(Box::new(current), Box::new(other)), 409 | '%' => Expr::Rem(Box::new(current), Box::new(other)), 410 | _ => unreachable!(), 411 | }, 412 | }; 413 | } 414 | Ok((current, Consumed::Consumed(()))) 415 | } 416 | 417 | fn factor<'a, I>() -> impl Parser> 418 | where 419 | I: Debug + Stream + RangeStream + 'a, 420 | ::Error: combine::ParseError, 421 | { 422 | let skip_spaces = || spaces().silent(); 423 | 424 | let brackets = char('(') 425 | .skip(skip_spaces()) 426 | .with(parser(expr)) 427 | .skip(skip_spaces()) 428 | .skip(char(')')); 429 | 430 | 431 | let call = (ident(), char('(') 432 | .skip(skip_spaces()) 433 | .with(sep_end_by(parser(expr).skip(skip_spaces()), char(','))) 434 | .skip(skip_spaces()) 435 | .skip(char(')')) 436 | ).map(|v| Expr::Call(v.0, v.1)); 437 | 438 | let float_to_int = string("int") 439 | .expected("int cast") 440 | .skip(string("(")) 441 | .skip(skip_spaces()) 442 | .with(parser(expr)) 443 | .map(|v| Expr::FloatToInt(Box::new(v))) 444 | .skip(skip_spaces()) 445 | .skip(char(')')); 446 | let int_to_float = string("float") 447 | .expected("float cast") 448 | .skip(string("(")) 449 | .skip(skip_spaces()) 450 | .with(parser(expr)) 451 | .map(|v| Expr::IntToFloat(Box::new(v))) 452 | .skip(skip_spaces()) 453 | .skip(char(')')); 454 | 455 | let not = char('!') 456 | .skip(skip_spaces()) 457 | .with(parser(expr)) 458 | .map(|v| Expr::Not(Box::new(v))); 459 | 460 | let neg = char('-') 461 | .skip(skip_spaces()) 462 | .with(parser(expr)) 463 | .map(|v| Expr::Neg(Box::new(v))); 464 | 465 | ( 466 | position(), 467 | choice(( 468 | attempt(float_to_int), 469 | attempt(int_to_float), 470 | attempt(brackets.map(|v| v.expr)), 471 | attempt(call), 472 | attempt(value().map(|v| Expr::Value(v.value))), 473 | attempt(not), 474 | attempt(neg), 475 | )) 476 | ).map(|v| ExprType { 477 | position: SourcePosition::into(v.0), 478 | expr: v.1, 479 | }) 480 | } 481 | 482 | fn properties<'a, I>() -> impl Parser, ValueType<'a>>> 483 | where 484 | I: Debug + Stream + RangeStream + 'a, 485 | ::Error: combine::ParseError, 486 | { 487 | ( 488 | token('('), 489 | sep_end_by(property(), token(',')), 490 | spaces().with(token(')')), 491 | ).map(|(_, l, _)| l) 492 | } 493 | 494 | fn property<'a, I>() -> impl Parser, ValueType<'a>)> 495 | where 496 | I: Debug + Stream + RangeStream + 'a, 497 | ::Error: combine::ParseError, 498 | { 499 | ( 500 | spaces().with(ident()), 501 | spaces().with(token('=')), 502 | spaces().with(value()), 503 | ).map(|v| (v.0, v.2)) 504 | } 505 | 506 | fn value<'a, I>() -> impl Parser> 507 | where 508 | I: Debug + Stream + RangeStream + 'a, 509 | ::Error: combine::ParseError, 510 | { 511 | let boolean = parse_bool().map(|v| Value::Boolean(v)); 512 | let float = parse_float().map(|v| Value::Float(v)); 513 | let integer = parse_integer().map(|v| Value::Integer(v)); 514 | 515 | let string = parse_string().map(|v| Value::String(v)); 516 | 517 | let variable = ident().map(|v| Value::Variable(v)); 518 | 519 | ( 520 | position(), 521 | try(boolean) 522 | .or(try(float)) 523 | .or(try(integer)) 524 | .or(try(variable)) 525 | .or(string), 526 | ).map(|v| { 527 | ValueType { 528 | value: v.1, 529 | position: SourcePosition::into(v.0), 530 | } 531 | }) 532 | } 533 | 534 | #[cfg(test)] 535 | mod tests { 536 | use format_parse_error; 537 | use super::*; 538 | #[test] 539 | fn test() { 540 | let source = r##" 541 | // Comments (only single line) 542 | root > panel > image(width=width, height=height) { 543 | width = width, 544 | height = height, 545 | test_expr = width + 6, 546 | test_expr2 = -5 + -3, 547 | test_expr3 = height - 6, 548 | test_expr4 = -3--4, 549 | test_expr5 = 6 * 3, 550 | 551 | p_test = 5 * (1 + 2) - 3/5, 552 | 553 | call_test = do_thing(5, 3, 4 * 7) / pi(), 554 | hard_test = -banana() / -(5--4), 555 | } 556 | emoji(type="smile") { 557 | image = "icons/smile.png", 558 | } 559 | 560 | panel > @text { 561 | color = "#0050AA", 562 | } 563 | "##; 564 | let doc = Document::parse(source); 565 | if let Err(err) = doc { 566 | println!(""); 567 | format_parse_error(::std::io::stdout(), source.lines(), err).unwrap(); 568 | panic!("^^"); 569 | } 570 | } 571 | } 572 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! FunGUI is a UI layout system that seperates the description of the interface and 2 | //! the styling/layout. 3 | //! 4 | //! In FunGUI there are two main systems that come into play that the 5 | //! rest is built around: Nodes and Styles. 6 | //! 7 | //! # Nodes 8 | //! 9 | //! Nodes are used to describe the user interface without any information 10 | //! about how it should look, only the structure. There are two types of 11 | //! nodes: Elements and Text. 12 | //! 13 | //! Elements are simply a name which could be anything, there are no special 14 | //! names as everything is controled by the style rules. Elements may contain 15 | //! child nodes. 16 | //! 17 | //! Text as the name implies is just text. Unlike elements, text may not 18 | //! have any child nodes. 19 | //! 20 | //! Any node may have properties on it. Properties are used to provide 21 | //! configuration to a node which is useful if you use the same node type 22 | //! multiple times. For example an `url` property may be used on a text 23 | //! node to allow the style rules to color it differently or make it clickable. 24 | //! 25 | //! ## Example 26 | //! 27 | //! An example of the node format: 28 | //! 29 | //! ```rust 30 | //! # extern crate fungui_syntax; 31 | //! # fungui_syntax::desc::Document::parse(r##" 32 | //! alert(level="warning") { 33 | //! title { 34 | //! "This is an alert" 35 | //! } 36 | //! content { 37 | //! "If you would like more info click " 38 | //! "here"(url="http://....") 39 | //! "." 40 | //! } 41 | //! buttons { 42 | //! button(focused=true) { 43 | //! "Accept" 44 | //! } 45 | //! button { 46 | //! "Ignore" 47 | //! } 48 | //! } 49 | //! } 50 | //! # "##).unwrap(); 51 | //! ``` 52 | //! 53 | //! # Styles 54 | //! 55 | //! Styles are used to define the behaviour of a node. This can be something like 56 | //! how the node will render or how the node will react to events. 57 | //! 58 | //! Styles apply using matching rules to find what nodes they will apply too. Rules 59 | //! can specific a hierarchy of nodes and what properties the node should have and 60 | //! their values. This allows for a `title` inside an `alert` to act differently to 61 | //! a `title` inside an `window` for example. 62 | //! 63 | //! Once a match is found the style rules are applied to the node. Rules can be a 64 | //! simple constant value or an expression. Expressions perform basic math (`+-/*%`) 65 | //! and boolean operations (`|| && <= ` etc), reference properties that were matched 66 | //! and execute functions. Functions can be used for complex properties instead of 67 | //! spliting them across multiple rules. 68 | //! 69 | //! ## Variables and types 70 | //! 71 | //! Variables are typed and floats/integers are treated as seperate and not casted 72 | //! automatically, this includes constants in style rules as well. For constants 73 | //! defining a number as `5` will be an integer whilst `5.0` will be a float. For 74 | //! variables you can cast using `int(val)` or `float(val)`. 75 | //! 76 | //! ### Special variables 77 | //! 78 | //! There are two special variables that can be used without using them in a matching 79 | //! rule: `parent_width` and `parent_height`. These allow you to size things relative 80 | //! to the parent's size without needing a custom layout to handle it. Whilst these 81 | //! are useful in some cases they do come with a larger cost. In order to handle this 82 | //! the interface may have to re-run the layout system multiple to resolve the variables 83 | //! causing a slowdown however this will generally only happen the first time the 84 | //! node has its layout computed. 85 | //! 86 | //! ## Example 87 | //! 88 | //! An example of the style format: 89 | //! 90 | //! ```rust 91 | //! # extern crate fungui_syntax; 92 | //! # fungui_syntax::style::Document::parse(r##" 93 | //! alert { 94 | //! center = true, 95 | //! layout = "rows", 96 | //! width = 500, 97 | //! height = 400, 98 | //! } 99 | //! alert(level="warning") { 100 | //! background_color = rgb(255, 255, 0), 101 | //! } 102 | //! 103 | //! alert > title { 104 | //! layout = "lined", 105 | //! width = parent_width, 106 | //! } 107 | //! alert > title > @text { 108 | //! font_color = rgb(0, 0, 0), 109 | //! font_size = 24, 110 | //! } 111 | //! 112 | //! alert > content { 113 | //! layout = "lined", 114 | //! width = parent_width, 115 | //! } 116 | //! alert > content > @text { 117 | //! font_color = rgb(0, 0, 0), 118 | //! font_size = 16, 119 | //! } 120 | //! alert > content > @text(url=url) { 121 | //! font_color = rgb(0, 120, 0), 122 | //! on_mouse_up = action("visit", url), 123 | //! } 124 | //! # "##).unwrap(); 125 | //! ``` 126 | //! 127 | //! # Layouts 128 | //! 129 | //! Layouts take some of the style rules and use that to position and size a node. 130 | //! These can be added via `add_layout_engine` and selected using the `layout` style 131 | //! property. 132 | //! 133 | //! # Extension 134 | //! 135 | //! The `Extension` trait paired with the `RenderVisitor` trait is the main way that 136 | //! is used to actually make the user interface do something. By itself FunGUI only 137 | //! does layout, the extension trait can be used to add events and rendering by adding 138 | //! its own properties to use in style rules. In UniverCity these are things like 139 | //! `image` and `background_color` for rendering and `on_mouse_down` for events where 140 | //! events are lua code defined inline with the styles: 141 | //! 142 | //! ```ignore 143 | //! button { 144 | //! border_width = border_width(15.0), 145 | //! border = border_image("ui/button", 15, 15, true), 146 | //! shadow = shadow(2.0, 2.0, rgba(0, 0, 0, 0.3), 3.0, 0.0, "outset"), 147 | //! layout = "center", 148 | //! can_hover = true, 149 | //! } 150 | //! 151 | //! button(theme="blueprint") { 152 | //! border_width = border_width(15.0), 153 | //! border = border_image("ui/button", 15, 15, true), 154 | //! tint = rgba(0, 255, 255, 0.4), 155 | //! } 156 | //! button(on_click=click) { 157 | //! on_mouse_up = list(click, "init# 158 | //! audio.play_sound('click') 159 | //! return true 160 | //! "), 161 | //! } 162 | //! ``` 163 | 164 | 165 | #![warn(missing_docs)] 166 | 167 | extern crate fnv; 168 | extern crate fungui_syntax as syntax; 169 | extern crate ref_filter_map; 170 | extern crate bitflags; 171 | 172 | mod query; 173 | pub use query::Query; 174 | mod error; 175 | pub use error::Error; 176 | #[macro_use] 177 | mod macros; 178 | #[cfg(any(test, feature="tests"))] 179 | pub mod tests; 180 | mod style; 181 | use style::*; 182 | mod expr; 183 | use expr::*; 184 | mod layout; 185 | use layout::*; 186 | 187 | pub use layout::{ 188 | LayoutEngine, ChildAccess, 189 | NodeAccess, 190 | X, Y, WIDTH, HEIGHT 191 | }; 192 | 193 | pub use style::{Rule, Styles}; 194 | // TODO: Really shouldn't need this 195 | pub use fnv::FnvHashSet; 196 | 197 | use fnv::FnvHashMap; 198 | use std::rc::{Rc, Weak}; 199 | use std::cell::{Ref, RefMut, RefCell}; 200 | use std::any::Any; 201 | use std::hash::{Hash, Hasher}; 202 | use bitflags::bitflags; 203 | pub use syntax::{format_error, format_parse_error}; 204 | 205 | /// An alias for a common return type used in FunGUI 206 | pub type FResult<'a, T> = Result>; 207 | 208 | /// An unchanging key 209 | /// 210 | /// `Hash` and `Eq` are based on the pointer instead of 211 | /// the value of the string. Its recomended to create this 212 | /// via `static` to make sure that it always points to the 213 | /// same thing. 214 | #[derive(Clone, Copy, Debug, Eq)] 215 | pub struct StaticKey(pub &'static str); 216 | 217 | impl PartialEq for StaticKey { 218 | fn eq(&self, other: &StaticKey) -> bool { 219 | use std::ptr; 220 | ptr::eq(self.0, other.0) 221 | } 222 | } 223 | 224 | impl Hash for StaticKey { 225 | fn hash(&self, state: &mut H) { 226 | (self.0 as *const str).hash(state) 227 | } 228 | } 229 | 230 | bitflags! { 231 | /// Flags used to mark certain properties as dirty/changed 232 | pub struct DirtyFlags: u32 { 233 | /// Marks the node's position as changed 234 | const POSITION = 0b0000_0001; 235 | /// Marks the node's size as changed 236 | const SIZE = 0b0000_0010; 237 | /// Marks the node's scroll position as changed 238 | const SCROLL = 0b0000_0100; 239 | /// Marks the node's layout as changed 240 | const LAYOUT = 0b0000_1000; 241 | /// Marks the node's text as changed 242 | const TEXT = 0b0001_0000; 243 | /// Marks the node's children as changed 244 | const CHILDREN = 0b0010_0000; 245 | 246 | // Extra ones for layouts to use 247 | /// Extra flag for layouts to use 248 | const LAYOUT_1 = 0b0000_1000_0000_0000_0000_0000_0000_0000; 249 | /// Extra flag for layouts to use 250 | const LAYOUT_2 = 0b0000_0100_0000_0000_0000_0000_0000_0000; 251 | /// Extra flag for layouts to use 252 | const LAYOUT_3 = 0b0000_0010_0000_0000_0000_0000_0000_0000; 253 | /// Extra flag for layouts to use 254 | const LAYOUT_4 = 0b0000_0001_0000_0000_0000_0000_0000_0000; 255 | /// All extra flags for layouts 256 | const LAYOUT_ALL = Self::LAYOUT_1.bits | Self::LAYOUT_2.bits | Self::LAYOUT_3.bits | Self::LAYOUT_4.bits; 257 | // Extra ones for extensions to use 258 | /// Extra flag for extensions to use 259 | const EXT_1 = 0b1000_0000_0000_0000_0000_0000_0000_0000; 260 | /// Extra flag for extensions to use 261 | const EXT_2 = 0b0100_0000_0000_0000_0000_0000_0000_0000; 262 | /// Extra flag for extensions to use 263 | const EXT_3 = 0b0010_0000_0000_0000_0000_0000_0000_0000; 264 | /// Extra flag for extensions to use 265 | const EXT_4 = 0b0001_0000_0000_0000_0000_0000_0000_0000; 266 | /// All extra flags for extensions 267 | const EXT_ALL = Self::EXT_1.bits | Self::EXT_2.bits | Self::EXT_3.bits | Self::EXT_4.bits; 268 | } 269 | } 270 | 271 | /// Extensions extend stylish to allow custom style properties to be added 272 | pub trait Extension { 273 | /// The type of the data that will be stored on every node 274 | /// 275 | /// Can be acccessed via the `.ext` field on `NodeInner` 276 | type NodeData: Sized; 277 | /// The type of the extra Values that will be used to extend 278 | /// the fungui `Value` in `ExtValue` type. 279 | /// 280 | /// This is normally an enum 281 | type Value: Clone + PartialEq + Sized; 282 | 283 | /// Creates a new empty `NodeData` to be stored on a Node. 284 | fn new_data() -> Self::NodeData; 285 | 286 | /// Called to add new style keys that can be used by style rules 287 | /// 288 | /// # Example 289 | /// ```ignore 290 | /// static MY_PROP: StaticKey = StaticKey("my_prop"); 291 | /// 292 | /// // ... 293 | /// 294 | /// fn style_properties<'a, F>(mut prop: F) 295 | /// where F: FnMut(StaticKey) + 'a 296 | /// { 297 | /// prop(MY_PROP); 298 | /// } 299 | /// 300 | /// ``` 301 | fn style_properties<'a, F>(prop: F) 302 | where F: FnMut(StaticKey) + 'a; 303 | 304 | /// Called to apply a given style rule on a node 305 | /// 306 | /// Its recomended to use the `eval!` macro to check for relevant properties 307 | /// as it also skips ones that have already been set by a another rule. 308 | /// 309 | /// # Example 310 | /// ```ignore 311 | /// 312 | /// fn update_child_data(&mut self, styles: &Styles, nc: &NodeChain, rule: &Rule, data: &mut Self::ChildData) -> DirtyFlags { 313 | /// let mut flags = DirtyFlags::empty(); 314 | /// eval!(styles, nc, rule.X => val => { 315 | /// let new = val.convert(); 316 | /// if data.x != new { 317 | /// data.x = new; 318 | /// flags |= DirtyFlags::POSITION; 319 | /// } 320 | /// }); 321 | /// flags 322 | /// } 323 | /// ``` 324 | fn update_data(styles: &Styles, nc: &NodeChain, rule: &Rule, data: &mut Self::NodeData) -> DirtyFlags 325 | where Self: Sized; 326 | 327 | /// Called after applying all relevant rules to reset any properties that 328 | /// weren't set. 329 | /// 330 | /// This is needed because a node could have a property set previously and 331 | /// then later (e.g. when a property is changed) no longer have it set. 332 | /// Due to it no longer being set `update_data` would not be called for 333 | /// that property leaving it stuck with its previous value. 334 | /// 335 | /// `used_keys` will contain every property key that was used by rules 336 | /// in this update, if the key isn't in this set it should be reset. 337 | fn reset_unset_data(used_keys: &FnvHashSet, data: &mut Self::NodeData) -> DirtyFlags; 338 | 339 | /// Called with the flags of a node to allow the data to be updated 340 | /// based on the dirty state of the node. 341 | /// 342 | /// This is useful to marking a node as needing a redraw when it 343 | /// moves. 344 | fn check_flags(_data: &mut Self::NodeData, _flags: DirtyFlags) { } 345 | } 346 | 347 | /// Stores loaded nodes and manages the layout. 348 | pub struct Manager { 349 | // Has no parent, is the parent for all base nodes 350 | // in the system 351 | root: Node, 352 | styles: Styles, 353 | last_size: (i32, i32), 354 | dirty: bool, 355 | } 356 | 357 | static CLIP_OVERFLOW: StaticKey = StaticKey("clip_overflow"); 358 | static SCROLL_X: StaticKey = StaticKey("scroll_x"); 359 | static SCROLL_Y: StaticKey = StaticKey("scroll_y"); 360 | static LAYOUT: StaticKey = StaticKey("layout"); 361 | 362 | impl Manager { 363 | /// Creates a new manager with an empty root node. 364 | pub fn new() -> Manager { 365 | let mut static_keys = FnvHashMap::default(); 366 | { 367 | let mut prop = |key: StaticKey| {static_keys.insert(key.0, key);}; 368 | prop(CLIP_OVERFLOW); 369 | prop(SCROLL_X); 370 | prop(SCROLL_Y); 371 | prop(LAYOUT); 372 | E::style_properties(prop); 373 | } 374 | let mut m = Manager { 375 | root: Node::root(), 376 | styles: Styles { 377 | _ext: ::std::marker::PhantomData, 378 | static_keys, 379 | rules: Rules::new(), 380 | funcs: FnvHashMap::default(), 381 | layouts: FnvHashMap::default(), 382 | next_rule_id: 0, 383 | used_keys: FnvHashSet::default(), 384 | }, 385 | last_size: (0, 0), 386 | dirty: true, 387 | }; 388 | m.add_layout_engine(AbsoluteLayout::default); 389 | 390 | m 391 | } 392 | 393 | /// Adds a new function that can be used to create a layout engine. 394 | /// 395 | /// A layout engine is used to position elements within an element. 396 | /// 397 | /// The layout engine can be selected by using the `layout` attribute. 398 | pub fn add_layout_engine(&mut self, creator: F) 399 | where 400 | F: Fn() -> L + 'static, 401 | L: LayoutEngine + 'static, 402 | { 403 | L::style_properties(|key| {self.styles.static_keys.insert(key.0, key);}); 404 | self.styles.layouts.insert(L::name(), Box::new(move || Box::new(creator()))); 405 | } 406 | 407 | /// Add a function that can be called by style rules 408 | /// 409 | /// Arguments are only parsed when obtained from the iterator 410 | /// making unused parameters cheap. 411 | pub fn add_func_raw(&mut self, name: &'static str, func: F) 412 | where 413 | F: for<'a> Fn(&mut (Iterator>> + 'a)) -> FResult<'a, Value> + 'static, 414 | { 415 | let key = self.styles.static_keys.entry(name).or_insert(StaticKey(name)); 416 | self.styles.funcs.insert(*key, Box::new(func)); 417 | } 418 | 419 | /// Adds the node to the root node of this manager. 420 | /// 421 | /// The node is created from the passed string. 422 | /// See [`from_str`](struct.Node.html#from_str) 423 | pub fn add_node_str<'a>(&mut self, node: &'a str) -> Result<(), syntax::PError<'a>> { 424 | self.add_node(Node::from_str(node)?); 425 | Ok(()) 426 | } 427 | 428 | /// Adds the node to the root node of this manager 429 | pub fn add_node(&mut self, node: Node) { 430 | self.root.add_child(node); 431 | } 432 | 433 | /// Removes the node from the root node of this manager 434 | pub fn remove_node(&mut self, node: Node) { 435 | self.root.remove_child(node); 436 | } 437 | 438 | /// Starts a query from the root of this manager 439 | pub fn query(&self) -> query::Query { 440 | query::Query::new(self.root.clone()) 441 | } 442 | 443 | /// Starts a query looking for elements at the target 444 | /// location. 445 | pub fn query_at(&self, x: i32, y: i32) -> query::Query<'static, E> { 446 | query::Query { 447 | root: self.root.clone(), 448 | rules: Vec::new(), 449 | location: Some(query::AtLocation { x: x, y: y }), 450 | } 451 | } 452 | 453 | /// Loads a set of styles from the given string. 454 | /// 455 | /// The name can be used to remove the loaded styles later 456 | pub fn load_styles<'a>( 457 | &mut self, 458 | name: &str, 459 | style_rules: &'a str, 460 | ) -> Result<(), syntax::PError<'a>> { 461 | let styles = syntax::style::Document::parse(style_rules)?; 462 | self.styles.load_styles(name, styles)?; 463 | self.dirty = true; 464 | Ok(()) 465 | } 466 | 467 | /// Removes the set of styles with the given name 468 | pub fn remove_styles(&mut self, name: &str) { 469 | self.styles.rules.remove_all_by_name(name); 470 | self.dirty = true; 471 | } 472 | 473 | /// Positions the nodes in this manager. 474 | /// 475 | /// This will update nodes based on their properties and then 476 | /// position them based on their selected layout. 477 | pub fn layout(&mut self, width: i32, height: i32) { 478 | let size = (width, height); 479 | let flags = if self.last_size != size { 480 | self.last_size = size; 481 | DirtyFlags::SIZE 482 | } else { 483 | DirtyFlags::empty() 484 | }; 485 | 486 | let mut inner = self.root.inner.borrow_mut(); 487 | inner.draw_rect = Rect{x: 0, y: 0, width, height}; 488 | 489 | let p = NodeChain { 490 | parent: None, 491 | value: NCValue::Element("root"), 492 | draw_rect: inner.draw_rect, 493 | properties: &FnvHashMap::default(), 494 | }; 495 | 496 | let mut layout = AbsoluteLayout::default(); 497 | 498 | // This is a loop due to the `parent_X` support requiring 499 | // the layout to be computed so it can be used in style rules 500 | // creating a chicken/egg problem. If they aren't used then 501 | // this will only execute once. 502 | loop { 503 | let mut properties_changed = false; 504 | 505 | if let NodeValue::Element(ref v) = inner.value { 506 | for c in &v.children { 507 | c.do_update(&mut self.styles, &p, &mut layout, self.dirty, flags == DirtyFlags::SIZE, flags); 508 | } 509 | 510 | for c in &v.children { 511 | properties_changed |= c.layout(&self.styles, &mut layout); 512 | } 513 | } 514 | 515 | self.dirty = false; 516 | if !properties_changed { 517 | break; 518 | } 519 | } 520 | } 521 | 522 | /// Renders the nodes in this manager by passing the draw position/size 523 | /// and style properties to the visitor 524 | pub fn render(&mut self, visitor: &mut V) 525 | where 526 | V: RenderVisitor, 527 | { 528 | self.root.render(visitor); 529 | } 530 | } 531 | 532 | /// The position and size of an node 533 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] 534 | pub struct Rect { 535 | /// The x position of the node 536 | pub x: i32, 537 | /// The y position of the node 538 | pub y: i32, 539 | /// The width of the node 540 | pub width: i32, 541 | /// The height of the node 542 | pub height: i32, 543 | } 544 | 545 | /// Called for every node in a manager to allow them to 546 | /// be rendered. 547 | pub trait RenderVisitor { 548 | /// Called per node before visiting their children 549 | fn visit(&mut self, node: &mut NodeInner); 550 | /// Called per node after visiting their children 551 | fn visit_end(&mut self, node: &mut NodeInner); 552 | } 553 | 554 | /// A node representing an element or text. 555 | /// 556 | /// Can be cloned to duplicate the reference to the node. 557 | pub struct Node { 558 | inner: Rc>>, 559 | } 560 | 561 | impl Clone for Node { 562 | fn clone(&self) -> Self { 563 | Node { 564 | inner: self.inner.clone(), 565 | } 566 | } 567 | } 568 | 569 | /// Tries to find and evalulate a given style property in a rule. 570 | /// 571 | /// This will skip properties that have already been set previously 572 | /// in the update. Should only be used during an `update_(child_)data` 573 | /// call. 574 | /// 575 | /// ```ignore 576 | /// eval!(styles, nc, rule.MY_PROP => val => { 577 | /// // This will only run if MY_PROP was set in the rule 578 | /// // val will be a `Value` containing what the property 579 | /// // was set too 580 | /// }); 581 | /// ``` 582 | #[macro_export] 583 | macro_rules! eval { 584 | ($styles:expr, $n:expr, $rule:ident.$key:expr => $ret:ident => $ok:block) => { 585 | if !$styles.key_was_used(&$key) { 586 | if let Some(e) = $rule.styles.get(&$key) { 587 | match e.eval($styles, &$n) { 588 | Ok($ret) => $ok, 589 | Err(err) => { 590 | // TODO: Collect errors for the user to display 591 | // instead of printing 592 | println!("Failed to evalulate expression ({}): {:?}", e, err); 593 | } 594 | } 595 | } 596 | } 597 | }; 598 | } 599 | 600 | impl Node { 601 | 602 | fn do_update( 603 | &self, 604 | styles: &mut Styles, 605 | parent: &NodeChain, 606 | parent_layout: &mut dyn BoxLayoutEngine, 607 | mut styles_updated: bool, mut parent_dirty: bool, 608 | parent_flags: DirtyFlags, 609 | ) -> DirtyFlags 610 | { 611 | use std::mem::replace; 612 | 613 | let inner: &mut _ = &mut *self.inner.borrow_mut(); 614 | let props_dirty = replace(&mut inner.properties_changed, false); 615 | let rules_dirty = replace(&mut inner.rules_dirty, false); 616 | inner.dirty_flags = DirtyFlags::empty(); 617 | if inner.text_changed { 618 | inner.dirty_flags |= DirtyFlags::TEXT; 619 | } 620 | if rules_dirty { 621 | inner.dirty_flags |= DirtyFlags::CHILDREN; 622 | } 623 | 624 | if styles_updated || rules_dirty { 625 | styles_updated = true; 626 | parent_dirty = true; 627 | inner.possible_rules.clear(); 628 | let c = NodeChain { 629 | parent: Some(parent), 630 | value: inner.value.as_chain(), 631 | draw_rect: inner.draw_rect, 632 | properties: &inner.properties, 633 | }; 634 | styles.rules.get_possible_matches(&c, &mut inner.possible_rules); 635 | } 636 | if parent_dirty || props_dirty { 637 | parent_dirty = true; 638 | let c = NodeChain { 639 | parent: Some(parent), 640 | value: inner.value.as_chain(), 641 | draw_rect: inner.draw_rect, 642 | properties: &inner.properties, 643 | }; 644 | styles.used_keys.clear(); 645 | inner.uses_parent_size = false; 646 | for rule in inner.possible_rules.iter().rev() { 647 | if rule.test(&c) { 648 | inner.uses_parent_size |= rule.uses_parent_size; 649 | eval!(styles, c, rule.LAYOUT => val => { 650 | let new = val.convert::(); 651 | let new = new.as_ref().map(|v| v.as_str()) 652 | .unwrap_or("absolute"); 653 | if new != inner.layout.name() { 654 | if let Some(nl) = styles.layouts.get(new) { 655 | inner.layout = nl(); 656 | inner.dirty_flags |= DirtyFlags::POSITION | DirtyFlags::SIZE | DirtyFlags::LAYOUT; 657 | } 658 | } 659 | }); 660 | // TODO: Error/warn on incorrect types? 661 | eval!(styles, c, rule.SCROLL_X => val => { 662 | let new = val.convert().unwrap_or(0.0); 663 | if inner.scroll_position.0 != new { 664 | inner.scroll_position.0 = new; 665 | inner.dirty_flags |= DirtyFlags::SCROLL; 666 | } 667 | }); 668 | eval!(styles, c, rule.SCROLL_Y => val => { 669 | let new = val.convert().unwrap_or(0.0); 670 | if inner.scroll_position.1 != new { 671 | inner.scroll_position.1 = new; 672 | inner.dirty_flags |= DirtyFlags::SCROLL; 673 | } 674 | }); 675 | eval!(styles, c, rule.CLIP_OVERFLOW => val => { 676 | inner.clip_overflow = val.convert().unwrap_or(false); 677 | }); 678 | inner.dirty_flags |= E::update_data(styles, &c, rule, &mut inner.ext); 679 | inner.dirty_flags |= inner.layout.update_data(styles, &c, rule); 680 | inner.dirty_flags |= parent_layout.update_child_data(styles, &c, rule, &mut inner.parent_data); 681 | 682 | styles.used_keys.extend(rule.styles.keys()); 683 | } 684 | } 685 | if !styles.used_keys.contains(&CLIP_OVERFLOW) { 686 | inner.clip_overflow = false; 687 | } 688 | if !styles.used_keys.contains(&SCROLL_X) { 689 | inner.scroll_position.0 = 0.0; 690 | inner.dirty_flags |= DirtyFlags::SCROLL; 691 | } 692 | if !styles.used_keys.contains(&SCROLL_Y) { 693 | inner.scroll_position.1 = 0.0; 694 | inner.dirty_flags |= DirtyFlags::SCROLL; 695 | } 696 | inner.dirty_flags |= E::reset_unset_data(&styles.used_keys, &mut inner.ext); 697 | inner.dirty_flags |= inner.layout.reset_unset_data(&styles.used_keys); 698 | inner.dirty_flags |= parent_layout.reset_unset_child_data(&styles.used_keys, &mut inner.parent_data); 699 | 700 | } 701 | inner.dirty_flags |= inner.layout.check_parent_flags(parent_flags); 702 | let mut child_flags = DirtyFlags::empty(); 703 | let p = NodeChain { 704 | parent: Some(parent), 705 | value: inner.value.as_chain(), 706 | draw_rect: inner.draw_rect, 707 | properties: &inner.properties, 708 | }; 709 | if let NodeValue::Element(ref v) = inner.value { 710 | for c in &v.children { 711 | child_flags |= c.do_update(styles, &p, &mut *inner.layout, styles_updated, parent_dirty, inner.dirty_flags); 712 | } 713 | } 714 | inner.dirty_flags |= inner.layout.check_child_flags(child_flags); 715 | 716 | E::check_flags(&mut inner.ext, inner.dirty_flags); 717 | 718 | inner.dirty_flags 719 | } 720 | 721 | fn layout( 722 | &self, 723 | styles: &Styles, 724 | parent_layout: &mut dyn BoxLayoutEngine, 725 | ) -> bool { 726 | let inner: &mut _ = &mut *self.inner.borrow_mut(); 727 | inner.done_layout = true; 728 | let nodes = if let NodeValue::Element(ref v) = inner.value { 729 | v.children.as_slice() 730 | } else { 731 | &[] 732 | }; 733 | inner.draw_rect = parent_layout.do_layout(&inner.value, &mut inner.ext, &mut inner.parent_data, inner.draw_rect, inner.dirty_flags); 734 | inner.draw_rect = inner.layout.start_layout(&mut inner.ext, inner.draw_rect, inner.dirty_flags, nodes); 735 | 736 | let mut properties_changed = false; 737 | for c in nodes { 738 | properties_changed |= c.layout(styles, &mut *inner.layout); 739 | } 740 | inner.draw_rect = inner.layout.finish_layout(&mut inner.ext, inner.draw_rect, inner.dirty_flags, nodes); 741 | inner.draw_rect = parent_layout.do_layout_end(&inner.value, &mut inner.ext, &mut inner.parent_data, inner.draw_rect, inner.dirty_flags); 742 | 743 | if inner.draw_rect != inner.prev_rect { 744 | for c in nodes { 745 | let mut c = c.inner.borrow_mut(); 746 | if c.uses_parent_size { 747 | c.properties_changed = true; 748 | properties_changed = true; 749 | } 750 | } 751 | } 752 | inner.prev_rect = inner.draw_rect; 753 | properties_changed 754 | } 755 | 756 | fn render(&self, visitor: &mut V) 757 | where 758 | V: RenderVisitor, 759 | { 760 | let inner: &mut _ = &mut *self.inner.borrow_mut(); 761 | visitor.visit(inner); 762 | if let NodeValue::Element(ref v) = inner.value { 763 | for c in &v.children { 764 | c.render(visitor); 765 | } 766 | } 767 | visitor.visit_end(inner); 768 | } 769 | 770 | /// Creates a new element with the given name. 771 | pub fn new(name: S) -> Node 772 | where 773 | S: Into, 774 | { 775 | Node { 776 | inner: Rc::new(RefCell::new(NodeInner { 777 | value: NodeValue::Element(Element { 778 | name: name.into(), 779 | children: Vec::new(), 780 | }), 781 | .. Default::default() 782 | })), 783 | } 784 | } 785 | 786 | /// Creates a new text node with the given text. 787 | pub fn new_text(text: S) -> Node 788 | where 789 | S: Into, 790 | { 791 | Node { 792 | inner: Rc::new(RefCell::new(NodeInner { 793 | value: NodeValue::Text(text.into()), 794 | .. Default::default() 795 | })), 796 | } 797 | } 798 | 799 | /// Returns an immutable reference to the 800 | /// node's inner value 801 | #[inline] 802 | pub fn borrow(&self) -> Ref> { 803 | self.inner.borrow() 804 | } 805 | 806 | /// Returns an mutable reference to the 807 | /// node's inner value 808 | #[inline] 809 | pub fn borrow_mut(&self) -> RefMut> { 810 | self.inner.borrow_mut() 811 | } 812 | 813 | /// Adds the passed node as a child to this node 814 | /// before other child nodes. 815 | /// 816 | /// Returns true if the node was added 817 | pub fn add_child_first(&self, node: Node) -> bool { 818 | if node.inner.borrow().parent.is_some() { 819 | return false; 820 | } 821 | if let NodeValue::Element(ref mut e) = self.inner.borrow_mut().value { 822 | { 823 | let mut inner = node.inner.borrow_mut(); 824 | inner.parent = Some(Rc::downgrade(&self.inner)); 825 | inner.rules_dirty = true; 826 | } 827 | e.children.insert(0, node); 828 | true 829 | } else { 830 | false 831 | } 832 | } 833 | 834 | /// Adds the passed node as a child to this node. 835 | /// 836 | /// Returns true if the node was added 837 | pub fn add_child(&self, node: Node) -> bool { 838 | if node.inner.borrow().parent.is_some() { 839 | return false; 840 | } 841 | if let NodeValue::Element(ref mut e) = self.inner.borrow_mut().value { 842 | { 843 | let mut inner = node.inner.borrow_mut(); 844 | inner.parent = Some(Rc::downgrade(&self.inner)); 845 | inner.rules_dirty = true; 846 | } 847 | e.children.push(node); 848 | true 849 | } else { 850 | false 851 | } 852 | } 853 | 854 | /// Removes the passed node as a child from this node. 855 | /// 856 | /// Returns true if the node was removed 857 | pub fn remove_child(&self, node: Node) -> bool { 858 | if !node.inner 859 | .borrow() 860 | .parent 861 | .as_ref() 862 | .and_then(|v| v.upgrade()) 863 | .map_or(false, |v| Rc::ptr_eq(&v, &self.inner)) { 864 | return false; 865 | } 866 | let inner: &mut NodeInner<_> = &mut *self.inner.borrow_mut(); 867 | if let NodeValue::Element(ref mut e) = inner.value { 868 | e.children.retain(|v| !Rc::ptr_eq(&v.inner, &node.inner)); 869 | { 870 | let mut inner = node.inner.borrow_mut(); 871 | inner.parent = None; 872 | inner.rules_dirty = true; 873 | } 874 | true 875 | } else { 876 | false 877 | } 878 | } 879 | 880 | /// Returns a vector containing the child nodes of this 881 | /// node. 882 | #[inline] 883 | pub fn children(&self) -> Vec> { 884 | if let NodeValue::Element(ref e) = self.inner.borrow().value { 885 | Clone::clone(&e.children) 886 | } else { 887 | Vec::new() 888 | } 889 | } 890 | 891 | /// Returns the parent node of this node. 892 | pub fn parent(&self) -> Option> { 893 | let inner = self.inner.borrow(); 894 | inner 895 | .parent 896 | .as_ref() 897 | .and_then(|v| v.upgrade()) 898 | .map(|v| Node { inner: v }) 899 | } 900 | 901 | /// Returns the name of the node if it has one 902 | #[inline] 903 | pub fn name(&self) -> Option { 904 | let inner = self.inner.borrow(); 905 | match inner.value { 906 | NodeValue::Element(ref e) => Some(e.name.clone()), 907 | NodeValue::Text(_) => None, 908 | } 909 | } 910 | 911 | /// Returns whether the passed node points to the same node 912 | /// as this one 913 | #[inline] 914 | pub fn is_same(&self, other: &Node) -> bool { 915 | Rc::ptr_eq(&self.inner, &other.inner) 916 | } 917 | 918 | /// Returns the text of the node if it is a text node. 919 | #[inline] 920 | pub fn text(&self) -> Option> { 921 | let inner = self.inner.borrow(); 922 | ref_filter_map::ref_filter_map(inner, |v| 923 | if let NodeValue::Text(ref t) = v.value { 924 | Some(t.as_str()) 925 | } else { 926 | None 927 | } 928 | ) 929 | } 930 | 931 | /// Sets the text of the node if it is a text node. 932 | pub fn set_text(&self, txt: S) 933 | where 934 | S: Into, 935 | String: PartialEq, 936 | { 937 | let inner: &mut NodeInner<_> = &mut *self.inner.borrow_mut(); 938 | if let NodeValue::Text(ref mut t) = inner.value { 939 | if *t != txt{ 940 | *t = txt.into(); 941 | inner.text_changed = true; 942 | } 943 | } 944 | } 945 | 946 | /// Returns whether this node has had its layout computed 947 | /// at least once 948 | pub fn has_layout(&self) -> bool { 949 | self.inner.borrow().done_layout 950 | } 951 | 952 | /// Returns the raw position of the node. 953 | /// 954 | /// This position isn't transformed and is relative 955 | /// to the parent instead of absolute like `render_position` 956 | pub fn raw_position(&self) -> Rect { 957 | self.inner.borrow().draw_rect 958 | } 959 | 960 | /// Returns the rendering position of the node. 961 | /// 962 | /// Useful for IME handling. 963 | /// Must be called after a `layout` call. 964 | pub fn render_position(&self) -> Option { 965 | let inner = self.inner.borrow(); 966 | let mut rect = inner.draw_rect; 967 | let mut cur = inner.parent.as_ref().and_then(|v| v.upgrade()); 968 | while let Some(p) = cur { 969 | let inner = p.borrow(); 970 | rect.x += inner.scroll_position.0 as i32; 971 | rect.y += inner.scroll_position.1 as i32; 972 | if inner.clip_overflow { 973 | if rect.x < 0 { 974 | rect.width += rect.x; 975 | rect.x = 0; 976 | } 977 | if rect.y < 0 { 978 | rect.height += rect.y; 979 | rect.y = 0; 980 | } 981 | if rect.x + rect.width >= inner.draw_rect.width { 982 | rect.width -= (rect.x + rect.width) - inner.draw_rect.width; 983 | } 984 | if rect.y + rect.height >= inner.draw_rect.height { 985 | rect.height -= (rect.y + rect.height) - inner.draw_rect.height; 986 | } 987 | } 988 | if rect.width <= 0 || rect.height <= 0 { 989 | return None; 990 | } 991 | 992 | rect.x += inner.draw_rect.x; 993 | rect.y += inner.draw_rect.y; 994 | cur = inner.parent.as_ref().and_then(|v| v.upgrade()); 995 | } 996 | Some(rect) 997 | } 998 | 999 | /// Removes the property on the node. 1000 | pub fn remove_property(&self, key: &str) { 1001 | let mut inner = self.inner.borrow_mut(); 1002 | inner.properties.remove(key); 1003 | } 1004 | 1005 | /// Returns a copy of the value for the given property 1006 | /// if it exists. 1007 | #[inline] 1008 | pub fn get_property(&self, key: &str) -> Option 1009 | where V: ConvertValue 1010 | { 1011 | let inner = self.inner.borrow(); 1012 | inner.get_property::(key) 1013 | } 1014 | 1015 | /// Returns a reference to the value for the given property 1016 | /// if it exists. 1017 | #[inline] 1018 | pub fn get_property_ref(&self, key: &str) -> Option> 1019 | where V: ConvertValue 1020 | { 1021 | let inner = self.inner.borrow(); 1022 | ref_filter_map::ref_filter_map( 1023 | inner, 1024 | |v| v.get_property_ref::(key) 1025 | ) 1026 | } 1027 | 1028 | /// Sets the value of a given property 1029 | #[inline] 1030 | pub fn set_property(&self, key: &str, v: V) 1031 | where V: ConvertValue 1032 | { 1033 | let mut inner = self.inner.borrow_mut(); 1034 | inner.properties_changed = true; 1035 | inner.properties.insert(key.into(), V::to_value(v)); 1036 | } 1037 | 1038 | /// Sets the value of a given property without flagging 1039 | /// the node as changed. 1040 | /// 1041 | /// This is useful for when properties are use as storage 1042 | /// and not used in style rules. 1043 | /// 1044 | /// As a general convention this properties should use keys 1045 | /// begining with `$` (e.g. `$cycle`) as these are not accepted 1046 | /// by the style parser. 1047 | #[inline] 1048 | pub fn raw_set_property(&self, key: &str, v: V) 1049 | where V: ConvertValue 1050 | { 1051 | let mut inner = self.inner.borrow_mut(); 1052 | inner.properties.insert(key.into(), V::to_value(v)); 1053 | } 1054 | 1055 | /// Creates a weak reference to this node. 1056 | pub fn weak(&self) -> WeakNode { 1057 | WeakNode { 1058 | inner: Rc::downgrade(&self.inner), 1059 | } 1060 | } 1061 | 1062 | /// Begins a query on this node 1063 | pub fn query(&self) -> query::Query { 1064 | query::Query::new(self.clone()) 1065 | } 1066 | 1067 | /// Creates a node from a string 1068 | pub fn from_str(s: &str) -> Result, syntax::PError> { 1069 | syntax::desc::Document::parse(s).map(|v| Node::from_document(v)) 1070 | } 1071 | 1072 | /// Creates a node from a parsed document. 1073 | pub fn from_document(desc: syntax::desc::Document) -> Node { 1074 | Node::from_doc_element(desc.root) 1075 | } 1076 | 1077 | fn from_doc_text( 1078 | desc: &str, 1079 | properties: FnvHashMap, 1080 | ) -> Node { 1081 | let text = unescape(desc); 1082 | Node { 1083 | inner: Rc::new(RefCell::new(NodeInner { 1084 | value: NodeValue::Text(text), 1085 | properties: properties 1086 | .into_iter() 1087 | .map(|(n, v)| (n.name.into(), Value::from(v))) 1088 | .collect(), 1089 | .. Default::default() 1090 | })), 1091 | } 1092 | } 1093 | 1094 | fn from_doc_element(desc: syntax::desc::Element) -> Node { 1095 | let node = Node { 1096 | inner: Rc::new(RefCell::new(NodeInner { 1097 | value: NodeValue::Element(Element { 1098 | name: desc.name.name.into(), 1099 | children: Vec::with_capacity(desc.nodes.len()), 1100 | }), 1101 | properties: desc.properties 1102 | .into_iter() 1103 | .map(|(n, v)| (n.name.into(), Value::from(v))) 1104 | .collect(), 1105 | .. Default::default() 1106 | })), 1107 | }; 1108 | 1109 | for c in desc.nodes.into_iter().map(|n| match n { 1110 | syntax::desc::Node::Element(e) => Node::from_doc_element(e), 1111 | syntax::desc::Node::Text(t, _, props) => Node::from_doc_text(t, props), 1112 | }) { 1113 | node.add_child(c); 1114 | } 1115 | 1116 | node 1117 | } 1118 | 1119 | fn root() -> Node { 1120 | Node { 1121 | inner: Rc::new(RefCell::new(NodeInner { 1122 | value: NodeValue::Element(Element { 1123 | name: "root".into(), 1124 | children: Vec::new(), 1125 | }), 1126 | .. Default::default() 1127 | })), 1128 | } 1129 | } 1130 | } 1131 | 1132 | fn unescape(v: &str) -> String { 1133 | let mut text = String::new(); 1134 | let mut special = false; 1135 | for c in v.chars() { 1136 | if special { 1137 | match c { 1138 | 't' => text.push('\t'), 1139 | 'n' => text.push('\n'), 1140 | 'r' => text.push('\r'), 1141 | _ => text.push(c), 1142 | } 1143 | special = false; 1144 | continue; 1145 | } 1146 | if c == '\\' { 1147 | special = true; 1148 | } else { 1149 | text.push(c); 1150 | } 1151 | } 1152 | text 1153 | } 1154 | 1155 | /// A weak reference to a node. 1156 | pub struct WeakNode { 1157 | inner: Weak>>, 1158 | } 1159 | impl WeakNode { 1160 | /// Tries to upgrade this weak reference into a strong one. 1161 | /// 1162 | /// Fails if there isn't any strong references to the node. 1163 | pub fn upgrade(&self) -> Option> { 1164 | self.inner.upgrade().map(|v| Node { inner: v }) 1165 | } 1166 | } 1167 | 1168 | impl Clone for WeakNode { 1169 | fn clone(&self) -> Self { 1170 | WeakNode { 1171 | inner: self.inner.clone(), 1172 | } 1173 | } 1174 | } 1175 | 1176 | /// The inner data of a single node. 1177 | /// 1178 | /// `Node` is a wrapper around this to allow it to be passed 1179 | /// around easily via reference counting. 1180 | pub struct NodeInner { 1181 | parent: Option>>>, 1182 | properties: FnvHashMap>, 1183 | properties_changed: bool, 1184 | possible_rules: Vec>>, 1185 | done_layout: bool, 1186 | // Set when added/removed from a node 1187 | rules_dirty: bool, 1188 | dirty_flags: DirtyFlags, 1189 | /// The value of the node. 1190 | /// 1191 | /// The value is either the name and children of 1192 | /// the node or the text of the node. 1193 | pub value: NodeValue, 1194 | /// Whether the text of this node has changed since 1195 | /// last viewed. 1196 | /// 1197 | /// The render visitor should reset this flag after viewing it 1198 | pub text_changed: bool, 1199 | layout: Box>, 1200 | parent_data: Box, 1201 | uses_parent_size: bool, 1202 | prev_rect: Rect, 1203 | /// The current draw position of this node 1204 | pub draw_rect: Rect, 1205 | /// The scroll offset of all elements inside this one 1206 | pub scroll_position: (f32, f32), 1207 | /// Whether this element clips child elements that overflow 1208 | /// its bounds 1209 | pub clip_overflow: bool, 1210 | /// The location that this element should be drawn at as 1211 | /// decided by the layout engine 1212 | pub draw_position: Rect, 1213 | /// Extension provided data 1214 | pub ext: E::NodeData, 1215 | } 1216 | 1217 | impl Default for NodeInner 1218 | where E: Extension 1219 | { 1220 | fn default() -> NodeInner { 1221 | NodeInner { 1222 | parent: None, 1223 | layout: Box::new(AbsoluteLayout::default()), 1224 | parent_data: Box::new(AbsoluteLayoutChild::default()), 1225 | value: NodeValue::Text(String::new()), 1226 | properties: FnvHashMap::default(), 1227 | properties_changed: true, 1228 | possible_rules: Vec::new(), 1229 | done_layout: false, 1230 | rules_dirty: true, 1231 | text_changed: false, 1232 | dirty_flags: DirtyFlags::empty(), 1233 | uses_parent_size: false, 1234 | prev_rect: Rect{x: 0, y: 0, width: 0, height: 0}, 1235 | draw_rect: Rect{x: 0, y: 0, width: 0, height: 0}, 1236 | scroll_position: (0.0, 0.0), 1237 | clip_overflow: false, 1238 | draw_position: Rect{x: 0, y: 0, width: 0, height: 0}, 1239 | ext: E::new_data(), 1240 | } 1241 | } 1242 | } 1243 | 1244 | impl NodeInner 1245 | where E: Extension 1246 | { 1247 | #[inline] 1248 | fn get_property_impl(props: &FnvHashMap>, key: &str) -> Option 1249 | where V: ConvertValue 1250 | { 1251 | props.get(key) 1252 | .cloned() 1253 | .and_then(|v| V::from_value(v)) 1254 | } 1255 | 1256 | /// Returns a copy of the value for the given property 1257 | /// if it exists. 1258 | #[inline] 1259 | pub fn get_property(&self, key: &str) -> Option 1260 | where V: ConvertValue 1261 | { 1262 | Self::get_property_impl::(&self.properties, key) 1263 | } 1264 | 1265 | #[inline] 1266 | fn get_property_ref_impl<'a, V>(props: &'a FnvHashMap>, key: &str) -> Option<&'a V::RefType> 1267 | where V: ConvertValue 1268 | { 1269 | props.get(key) 1270 | .and_then(|v| V::from_value_ref(v)) 1271 | } 1272 | 1273 | /// Returns a reference to the value for the given property 1274 | /// if it exists. 1275 | #[inline] 1276 | pub fn get_property_ref(&self, key: &str) -> Option<&V::RefType> 1277 | where V: ConvertValue 1278 | { 1279 | Self::get_property_ref_impl::(&self.properties, key) 1280 | } 1281 | 1282 | /// Returns the text of the node if it is a text node. 1283 | pub fn text(&self) -> Option<&str> { 1284 | match self.value { 1285 | NodeValue::Element(_) => None, 1286 | NodeValue::Text(ref t) => Some(t.as_str()), 1287 | } 1288 | } 1289 | } 1290 | 1291 | /// The value of a node. 1292 | /// 1293 | /// Either an element with children or 1294 | /// text node. 1295 | pub enum NodeValue { 1296 | /// An element node, with a name and children 1297 | Element(Element), 1298 | /// A text node 1299 | Text(String), 1300 | } 1301 | 1302 | impl NodeValue { 1303 | 1304 | /// Returns the text of the node if it is a text node. 1305 | pub fn text(&self) -> Option<&str> { 1306 | match self { 1307 | NodeValue::Element(_) => None, 1308 | NodeValue::Text(ref t) => Some(t.as_str()), 1309 | } 1310 | } 1311 | } 1312 | 1313 | /// An element node 1314 | pub struct Element { 1315 | name: String, 1316 | children: Vec>, 1317 | } 1318 | 1319 | /// A chain of nodes and their parents 1320 | /// 1321 | /// Used during applying rules for quick traversal. 1322 | pub struct NodeChain<'a, E: Extension + 'a> { 1323 | parent: Option<&'a NodeChain<'a, E>>, 1324 | value: NCValue<'a>, 1325 | draw_rect: Rect, 1326 | properties: &'a FnvHashMap>, 1327 | } 1328 | 1329 | impl <'a, E> NodeChain<'a, E> 1330 | where E: Extension 1331 | { 1332 | /// Returns the text of the node if it is a text node. 1333 | pub fn text(&self) -> Option<&'a str> { 1334 | match self.value { 1335 | NCValue::Text(v) => Some(v), 1336 | _ => None, 1337 | } 1338 | } 1339 | } 1340 | 1341 | #[derive(Debug)] 1342 | enum NCValue<'a> { 1343 | Text(&'a str), 1344 | Element(&'a str), 1345 | } 1346 | impl NodeValue { 1347 | fn as_chain(&self) -> NCValue { 1348 | match *self { 1349 | NodeValue::Text(ref t) => NCValue::Text(t.as_str()), 1350 | NodeValue::Element(ref e) => NCValue::Element(e.name.as_str()), 1351 | } 1352 | } 1353 | } 1354 | 1355 | /// A value that can be used as a style property 1356 | #[derive(Debug)] 1357 | pub enum Value { 1358 | /// A boolean value 1359 | Boolean(bool), 1360 | /// An integer value 1361 | Integer(i32), 1362 | /// A floating point value 1363 | Float(f64), 1364 | /// A string value 1365 | String(String), 1366 | /// An extension defined value 1367 | ExtValue(E::Value), 1368 | } 1369 | 1370 | impl Value 1371 | where E: Extension 1372 | { 1373 | /// Attemps to convert this value into the given 1374 | /// type 1375 | pub fn convert(self) -> Option 1376 | where V: ConvertValue 1377 | { 1378 | V::from_value(self) 1379 | } 1380 | 1381 | /// Attemps to convert a reference to this value into 1382 | /// the given reference type 1383 | pub fn convert_ref(&self) -> Option<&V::RefType> 1384 | where V: ConvertValue 1385 | { 1386 | V::from_value_ref(self) 1387 | } 1388 | } 1389 | 1390 | impl Clone for Value 1391 | where E: Extension 1392 | { 1393 | fn clone(&self) -> Value { 1394 | match *self { 1395 | Value::Boolean(v) => Value::Boolean(v), 1396 | Value::Integer(v) => Value::Integer(v), 1397 | Value::Float(v) => Value::Float(v), 1398 | Value::String(ref v) => Value::String(v.clone()), 1399 | Value::ExtValue(ref v) => Value::ExtValue(v.clone()), 1400 | } 1401 | } 1402 | } 1403 | 1404 | impl PartialEq for Value 1405 | where E: Extension 1406 | { 1407 | fn eq(&self, rhs: &Value) -> bool { 1408 | use Value::*; 1409 | match (self, rhs) { 1410 | (&Boolean(a), &Boolean(b)) => a == b, 1411 | (&Integer(a), &Integer(b)) => a == b, 1412 | (&Float(a), &Float(b)) => a == b, 1413 | (&String(ref a), &String(ref b)) => a == b, 1414 | (&ExtValue(ref a), &ExtValue(ref b)) => a == b, 1415 | _ => false, 1416 | } 1417 | } 1418 | } 1419 | 1420 | impl <'a, E> From> for Value 1421 | where E: Extension 1422 | { 1423 | fn from(v: syntax::desc::ValueType<'a>) -> Value { 1424 | match v.value { 1425 | syntax::desc::Value::Boolean(val) => Value::Boolean(val), 1426 | syntax::desc::Value::Integer(val) => Value::Integer(val), 1427 | syntax::desc::Value::Float(val) => Value::Float(val), 1428 | syntax::desc::Value::String(val) => Value::String(unescape(val)), 1429 | } 1430 | } 1431 | } 1432 | 1433 | /// Types that can be converted to and from a value 1434 | pub trait ConvertValue: Sized { 1435 | /// The reference type of this value. 1436 | /// 1437 | /// Useful for types like `String` where the reference 1438 | /// type is `str` 1439 | type RefType: ?Sized; 1440 | 1441 | /// Tries to convert from the passed value to this type 1442 | fn from_value(v: Value) -> Option; 1443 | /// Tries to convert from the passed value to the reference 1444 | /// type. 1445 | fn from_value_ref(v: &Value) -> Option<&Self::RefType>; 1446 | /// Converts the value into a `Value` 1447 | fn to_value(v: Self) -> Value; 1448 | } 1449 | 1450 | impl ConvertValue for i32 1451 | where E: Extension 1452 | { 1453 | type RefType = i32; 1454 | fn from_value(v: Value) -> Option { 1455 | match v { 1456 | Value::Integer(i) => Some(i), 1457 | Value::Float(f) => Some(f as i32), 1458 | _ => None, 1459 | } 1460 | } 1461 | fn from_value_ref(v: &Value) -> Option<&Self::RefType> { 1462 | match v { 1463 | Value::Integer(i) => Some(i), 1464 | _ => None, 1465 | } 1466 | } 1467 | fn to_value(v: Self) -> Value { 1468 | Value::Integer(v) 1469 | } 1470 | } 1471 | 1472 | impl ConvertValue for f64 1473 | where E: Extension 1474 | { 1475 | type RefType = f64; 1476 | fn from_value(v: Value) -> Option { 1477 | match v { 1478 | Value::Integer(i) => Some(i as f64), 1479 | Value::Float(f) => Some(f), 1480 | _ => None, 1481 | } 1482 | } 1483 | fn from_value_ref(v: &Value) -> Option<&Self::RefType> { 1484 | match v { 1485 | Value::Float(f) => Some(f), 1486 | _ => None, 1487 | } 1488 | } 1489 | fn to_value(v: Self) -> Value { 1490 | Value::Float(v) 1491 | } 1492 | } 1493 | 1494 | impl ConvertValue for f32 1495 | where E: Extension 1496 | { 1497 | type RefType = f64; 1498 | fn from_value(v: Value) -> Option { 1499 | match v { 1500 | Value::Integer(i) => Some(i as f32), 1501 | Value::Float(f) => Some(f as f32), 1502 | _ => None, 1503 | } 1504 | } 1505 | fn from_value_ref(v: &Value) -> Option<&Self::RefType> { 1506 | match v { 1507 | Value::Float(f) => Some(f), 1508 | _ => None, 1509 | } 1510 | } 1511 | fn to_value(v: Self) -> Value { 1512 | Value::Float(v as f64) 1513 | } 1514 | } 1515 | 1516 | impl ConvertValue for bool 1517 | where E: Extension 1518 | { 1519 | type RefType = bool; 1520 | fn from_value(v: Value) -> Option { 1521 | match v { 1522 | Value::Boolean(b) => Some(b), 1523 | _ => None, 1524 | } 1525 | } 1526 | fn from_value_ref(v: &Value) -> Option<&Self::RefType> { 1527 | match v { 1528 | Value::Boolean(b) => Some(b), 1529 | _ => None, 1530 | } 1531 | } 1532 | fn to_value(v: Self) -> Value { 1533 | Value::Boolean(v) 1534 | } 1535 | } 1536 | 1537 | impl ConvertValue for String 1538 | where E: Extension 1539 | { 1540 | type RefType = str; 1541 | fn from_value(v: Value) -> Option { 1542 | match v { 1543 | Value::String(s) => Some(s.clone()), 1544 | _ => None, 1545 | } 1546 | } 1547 | fn from_value_ref(v: &Value) -> Option<&Self::RefType> { 1548 | match v { 1549 | Value::String(s) => Some(s.as_str()), 1550 | _ => None, 1551 | } 1552 | } 1553 | fn to_value(v: Self) -> Value { 1554 | Value::String(v) 1555 | } 1556 | } 1557 | impl ConvertValue for Value 1558 | where E: Extension 1559 | { 1560 | type RefType = Value; 1561 | fn from_value(v: Value) -> Option> { 1562 | Some(v) 1563 | } 1564 | fn from_value_ref(v: &Value) -> Option<&Self::RefType> { 1565 | Some(v) 1566 | } 1567 | fn to_value(v: Self) -> Value { 1568 | v 1569 | } 1570 | } --------------------------------------------------------------------------------