├── .gitignore ├── Cargo.toml ├── README.md ├── examples └── button.rs ├── src ├── application.rs ├── layout_context.rs ├── lib.rs ├── model.rs └── window.rs └── tests ├── common └── utils.rs ├── component.rs └── todomvc.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "weld" 3 | version = "0.1.0" 4 | authors = ["Roy Jacobs "] 5 | 6 | [dependencies] 7 | gleam = "0.4.7" 8 | glutin = "0.9.0" 9 | euclid = "0.15.1" 10 | snowflake = "1.2" 11 | rand = "0.3" 12 | log = "0.3" 13 | futures = "0.1.14" 14 | tokio-core = "0.1.4" 15 | 16 | [dependencies.webrender] 17 | git = "https://github.com/servo/webrender" 18 | 19 | [dependencies.yoga] 20 | git = "https://github.com/bschwind/yoga-rs" 21 | 22 | [dev-dependencies] 23 | pretty_env_logger = "0.1" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Weld 2 | Very alpha GUI library that uses Webrender as a backend. 3 | 4 | The concepts are quite similar to libraries like React, Elm and Flutter: 5 | * Build up your GUI by composing components 6 | * Every interaction from a user with a component will trigger a callback that allows you to return a new component tree 7 | * Weld will (partially) re-render your GUI whenever the tree changes 8 | 9 | # Build 10 | $ cargo build --release 11 | 12 | # Example 13 | $ cargo run --release --example button 14 | -------------------------------------------------------------------------------- /examples/button.rs: -------------------------------------------------------------------------------- 1 | extern crate weld; 2 | extern crate pretty_env_logger; 3 | extern crate webrender; 4 | extern crate rand; 5 | 6 | use weld::application::Application; 7 | use weld::model::*; 8 | use weld::window::Interaction; 9 | use weld::layout::{FlexDirection, Percent, Point, Wrap}; 10 | use weld::layout::FlexStyle::*; 11 | use weld::layout::Align::*; 12 | use webrender::api::*; 13 | use rand::{random, Closed01}; 14 | 15 | #[derive(Debug)] 16 | struct Container {} 17 | 18 | impl Renderer for Container { 19 | fn id(&self) -> &'static str { "Container" } 20 | fn render(&self, context: &mut RenderContext) { 21 | let bounds = context.bounds(); 22 | context.push(RenderElement::Rect(bounds, ColorF::new(1.0, 0.0, 0.0, 1.0))); 23 | context.next(); 24 | } 25 | } 26 | 27 | fn container() -> Component { 28 | Component::new(Container {}) 29 | } 30 | 31 | #[derive(Debug)] 32 | struct Button { 33 | color: ColorF, 34 | } 35 | 36 | impl Renderer for Button { 37 | fn id(&self) -> &'static str { "Button" } 38 | fn render(&self, context: &mut RenderContext) { 39 | let bounds = context.bounds(); 40 | context.push(RenderElement::Rect(bounds, self.color)); 41 | context.next(); 42 | } 43 | } 44 | 45 | fn button(color: &ColorF) -> Component { 46 | Component::new(Button { 47 | color: color.clone() 48 | }) 49 | } 50 | 51 | #[derive(Clone, Debug)] 52 | struct MyAppState { 53 | button_width: i32, 54 | button_color: ColorF, 55 | } 56 | 57 | impl State for MyAppState { 58 | fn build(&self) -> Component { 59 | container() 60 | .styles(vec![ 61 | Width(100.percent()), 62 | Height(100.percent()), 63 | FlexDirection(FlexDirection::Row), 64 | Padding(25.point()), 65 | AlignItems(FlexStart), 66 | FlexWrap(Wrap::Wrap) 67 | ]) 68 | .child( 69 | button(&self.button_color) 70 | .styles(vec![ 71 | Width(self.button_width.point()), 72 | Height(32.point()), 73 | ]) 74 | .name("button") 75 | .on(Box::new(|state: Self, event| { 76 | match *event { 77 | Interaction::Pressed => { 78 | println!("pressed!"); 79 | Ok(Self { 80 | button_width: state.button_width + 5, 81 | ..state 82 | }) 83 | } 84 | Interaction::Released => { 85 | println!("released!"); 86 | 87 | let button_color = ColorF::new(random::>().0, random::>().0, random::>().0, 1.0); 88 | Ok(Self { 89 | button_color, 90 | ..state 91 | }) 92 | } 93 | } 94 | })) 95 | ) 96 | } 97 | } 98 | 99 | fn main() { 100 | pretty_env_logger::init().unwrap(); 101 | 102 | let app = Application::new("Demo", MyAppState { 103 | button_width: 100, 104 | button_color: ColorF::new(0.0, 0.0, 1.0, 1.0), 105 | }); 106 | 107 | app.run(); 108 | } -------------------------------------------------------------------------------- /src/application.rs: -------------------------------------------------------------------------------- 1 | use model::{Component, InvocationError, State}; 2 | use window::{WindowEvent, WebrenderWindow, RendererHandle}; 3 | use layout_context::LayoutContext; 4 | use std::rc::Rc; 5 | use std::cell::RefCell; 6 | use std::sync::{Arc, Mutex}; 7 | use futures::Stream; 8 | use tokio_core::reactor::Core; 9 | 10 | pub struct Application { 11 | title: &'static str, 12 | layout_context: Rc>, 13 | state: S, 14 | } 15 | 16 | impl Application { 17 | pub fn new(title: &'static str, state: S) -> Self { 18 | let layout_context = Rc::new(RefCell::new(LayoutContext::new())); 19 | 20 | Application { 21 | title, 22 | layout_context, 23 | state, 24 | } 25 | } 26 | 27 | pub fn run(mut self) { 28 | let (mut renderer, event_stream) = WebrenderWindow::new(self.title, self.layout_context.clone()); 29 | 30 | let new_state = self.state.clone(); 31 | let mut tree = self.update_tree(&mut renderer, new_state); 32 | 33 | let event_logger = event_stream.for_each(|event| { 34 | //println!("event: {:?}", event); 35 | match event { 36 | WindowEvent::Interaction(_, _) => { 37 | let new_tree = match self.handle_interaction(event, &tree.lock().unwrap()) { 38 | Ok(new_state) => { 39 | Some(self.update_tree(&mut renderer, new_state)) 40 | }, 41 | Err(_) => { 42 | None 43 | } 44 | }; 45 | 46 | if let Some(t) = new_tree { 47 | tree = t; 48 | } 49 | 50 | Ok(()) 51 | } 52 | WindowEvent::NotifyRenderComplete => { 53 | renderer.update(); 54 | Ok(()) 55 | } 56 | WindowEvent::ApplicationClosed => { 57 | //renderer.stop(); 58 | Err(()) 59 | } 60 | _ => Ok(()) 61 | } 62 | }); 63 | 64 | let mut core = Core::new().unwrap(); 65 | let _ = core.run(event_logger); 66 | } 67 | 68 | fn handle_interaction(&self, event: WindowEvent, tree: &Component) -> Result { 69 | match event { 70 | WindowEvent::Interaction(point, interaction) => { 71 | let lc = self.layout_context.borrow(); 72 | if let Some(node) = lc.find_node_at(point, &tree) { 73 | println!("Interaction for: {:?}", node); 74 | node.invoke(&self.state, interaction) 75 | } else { 76 | Err(InvocationError) 77 | } 78 | } 79 | _ => { 80 | Err(InvocationError) 81 | } 82 | } 83 | } 84 | 85 | fn update_tree(&mut self, renderer: &mut RendererHandle, new_state: S) -> Arc> { 86 | let tree = Arc::new(Mutex::new(new_state.build())); 87 | renderer.set_tree(tree.clone()); 88 | renderer.render(); 89 | self.state = new_state; 90 | 91 | tree 92 | } 93 | } -------------------------------------------------------------------------------- /src/layout_context.rs: -------------------------------------------------------------------------------- 1 | use webrender::api::*; 2 | use model::{Component, ComponentId, InspectableComponent, RenderContext, RenderElement}; 3 | use std::collections::HashMap; 4 | use std::cell::{Ref, RefMut, RefCell}; 5 | use layout; 6 | 7 | pub struct LayoutContext { 8 | layout_nodes: HashMap> 9 | } 10 | 11 | impl LayoutContext { 12 | pub fn new() -> LayoutContext { 13 | LayoutContext { 14 | layout_nodes: HashMap::new() 15 | } 16 | } 17 | 18 | pub fn get_layout(&self, node: &InspectableComponent) -> layout::Layout { 19 | self.get_layout_node(node).get_layout() 20 | } 21 | 22 | pub fn update_layout(&mut self, root: &Component, size: &LayoutSize) { 23 | // HACK: Throw away old layout_nodes first 24 | self.layout_nodes = HashMap::new(); 25 | 26 | self.update_layout_recursive(root); 27 | self.get_layout_node_mut(root).calculate_layout(size.width, size.height, layout::Direction::LTR); 28 | } 29 | 30 | fn update_layout_recursive(&mut self, node: &Component) { 31 | { 32 | let layout_node = self.layout_nodes.entry(*node.inspect().id()).or_insert_with(|| RefCell::new(layout::Node::new())); 33 | layout_node.borrow_mut().apply_styles(node.inspect().styles()); 34 | } 35 | 36 | for child in node.inspect().children() { 37 | self.update_layout_recursive(child); 38 | 39 | let mut layout_node = self.get_layout_node_mut(node); 40 | let mut layout_child = self.get_layout_node_mut(child); 41 | let child_count = layout_node.child_count(); 42 | layout_node.insert_child(&mut layout_child, child_count); 43 | } 44 | } 45 | 46 | pub fn build_display_list(&self, builder: &mut DisplayListBuilder, root: &Component) { 47 | let mut elements = Vec::new(); 48 | { 49 | let mut ctx = WebrenderRenderContext::new(&self, root, &mut elements); 50 | root.inspect().renderer().render(&mut ctx); 51 | } 52 | 53 | for element in elements { 54 | match element { 55 | RenderElement::Rect(rect, color) => { 56 | builder.push_rect(rect, None, color); 57 | } 58 | } 59 | } 60 | } 61 | 62 | fn get_layout_node(&self, node: &InspectableComponent) -> Ref { 63 | self.layout_nodes.get(node.id()).unwrap().borrow() 64 | } 65 | 66 | fn get_layout_node_mut(&self, node: &InspectableComponent) -> RefMut { 67 | self.layout_nodes.get(node.id()).unwrap().borrow_mut() 68 | } 69 | 70 | pub fn find_node_at<'a>(&self, point: WorldPoint, root: &'a Component) -> Option<&'a Component> { 71 | self.find_node_at_recursive(point, root) 72 | } 73 | 74 | fn find_node_at_recursive<'a>(&self, point: WorldPoint, node: &'a Component) -> Option<&'a Component> { 75 | let layout = self.get_layout(node); 76 | let rect = WorldRect::new(WorldPoint::new(layout.left, layout.top), WorldSize::new(layout.width, layout.height)); 77 | if !rect.contains(&point) { 78 | None 79 | } else { 80 | for child_id in node.inspect().children() { 81 | if let Some(found_in_child) = self.find_node_at_recursive(point, child_id) { 82 | return Some(found_in_child); 83 | } 84 | } 85 | 86 | Some(node) 87 | } 88 | } 89 | } 90 | 91 | struct WebrenderRenderContext<'a> { 92 | layout_context: &'a LayoutContext, 93 | component: &'a InspectableComponent, 94 | elements: &'a mut Vec, 95 | } 96 | 97 | impl<'a> WebrenderRenderContext<'a> { 98 | pub fn new(layout_context: &'a LayoutContext, component: &'a InspectableComponent, elements: &'a mut Vec) -> WebrenderRenderContext<'a> { 99 | WebrenderRenderContext { 100 | layout_context, 101 | component, 102 | elements, 103 | } 104 | } 105 | } 106 | 107 | impl<'a> RenderContext for WebrenderRenderContext<'a> { 108 | fn render(&mut self) { 109 | self.component.renderer().render(self); 110 | } 111 | 112 | fn push(&mut self, e: RenderElement) { 113 | self.elements.push(e); 114 | } 115 | 116 | fn next(&mut self) { 117 | for child in self.component.children().iter() { 118 | let mut child_context = WebrenderRenderContext::new(self.layout_context, child.inspect(), self.elements); 119 | child_context.render(); 120 | } 121 | } 122 | 123 | fn bounds(&self) -> LayoutRect { 124 | let layout = &self.layout_context.get_layout(self.component); 125 | 126 | LayoutRect::new( 127 | LayoutPoint::new(layout.left, layout.top), 128 | LayoutSize::new(layout.width, layout.height) 129 | ) 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate euclid; 2 | extern crate gleam; 3 | extern crate glutin; 4 | #[macro_use] 5 | extern crate log; 6 | extern crate snowflake; 7 | extern crate webrender; 8 | extern crate rand; 9 | extern crate tokio_core; 10 | extern crate futures; 11 | 12 | pub extern crate yoga; 13 | 14 | pub mod application; 15 | pub mod layout_context; 16 | pub mod model; 17 | pub mod window; 18 | 19 | pub use yoga as layout; 20 | -------------------------------------------------------------------------------- /src/model.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::any::{Any, TypeId}; 3 | use std::result::Result; 4 | use std::marker::PhantomData; 5 | use std::borrow::Borrow; 6 | use std::fmt; 7 | use layout::FlexStyle; 8 | use snowflake::ProcessUniqueId; 9 | use webrender::api::{LayoutRect, ColorF}; 10 | 11 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 12 | 13 | pub trait Event where Self: Sized + 'static {} 14 | 15 | pub trait State where Self: Clone + 'static { 16 | fn build(&self) -> Component; 17 | } 18 | 19 | impl From for Component { 20 | fn from(state: S) -> Component { 21 | state.build() 22 | } 23 | } 24 | 25 | pub trait Renderer { 26 | fn id(&self) -> &'static str; 27 | fn render(&self, context: &mut RenderContext); 28 | } 29 | 30 | pub type ComponentId = ProcessUniqueId; 31 | 32 | pub struct Component { 33 | id: ComponentId, 34 | name: Option, 35 | renderer: Box, 36 | children: Vec, 37 | callbacks: HashMap>, 38 | styles: Vec, 39 | } 40 | 41 | impl fmt::Debug for Component { 42 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 43 | write!(f, "Component {{ id: {} }}", self.id) 44 | } 45 | } 46 | 47 | impl Component { 48 | pub fn new(renderer: R) -> Component { 49 | Component { 50 | id: ProcessUniqueId::new(), 51 | name: None, 52 | renderer: Box::new(renderer), 53 | children: Vec::new(), 54 | callbacks: HashMap::new(), 55 | styles: Vec::new(), 56 | } 57 | } 58 | 59 | pub fn invoke(&self, state: &S, event: E) -> Result { 60 | let callback = self.callbacks.get(&TypeId::of::()).ok_or(InvocationError)?; 61 | callback.invoke(state, &event).and_then(|state_any| { 62 | Ok(state_any.downcast_ref::().unwrap().clone()) 63 | }) 64 | } 65 | 66 | pub fn find_by_name<'a>(&'a self, name: &'static str) -> Option<&'a Component> { 67 | if let Some(ref self_name) = self.name { 68 | if self_name == name { 69 | return Some(self); 70 | } 71 | } 72 | 73 | for child in &self.children { 74 | if let Some(child) = child.find_by_name(name) { 75 | return Some(child); 76 | } 77 | } 78 | 79 | None 80 | } 81 | 82 | pub fn name>(mut self, name: I) -> Self { 83 | self.name = Some(name.into()); 84 | self 85 | } 86 | 87 | pub fn on(mut self, handler: SyncStateHandler) -> Self { 88 | let event_type = TypeId::of::(); 89 | self.callbacks.insert(event_type, Box::new(SyncStateCallback { 90 | state: PhantomData, 91 | handler, 92 | })); 93 | self 94 | } 95 | 96 | pub fn child>(mut self, child: B) -> Self { 97 | self.children.push(child.into()); 98 | self 99 | } 100 | 101 | pub fn children>(mut self, children: Vec) -> Self { 102 | for child in children { 103 | self.children.push(child.into()); 104 | } 105 | self 106 | } 107 | 108 | pub fn style>(mut self, style: B) -> Self { 109 | self.styles.push(style.into()); 110 | self 111 | } 112 | 113 | pub fn styles>(mut self, styles: Vec) -> Self { 114 | for style in styles { 115 | self.styles.push(style.into()); 116 | } 117 | self 118 | } 119 | 120 | pub fn inspect(&self) -> &InspectableComponent { 121 | self 122 | } 123 | } 124 | 125 | pub trait InspectableComponent { 126 | fn id(&self) -> &ComponentId; 127 | fn name(&self) -> &Option; 128 | fn renderer(&self) -> &Renderer; 129 | fn children(&self) -> &Vec; 130 | fn styles(&self) -> &Vec; 131 | } 132 | 133 | impl InspectableComponent for Component { 134 | fn id(&self) -> &ComponentId { 135 | &self.id 136 | } 137 | 138 | fn name(&self) -> &Option { 139 | &self.name 140 | } 141 | 142 | fn renderer(&self) -> &Renderer { 143 | self.renderer.borrow() 144 | } 145 | 146 | fn children(&self) -> &Vec { 147 | &self.children 148 | } 149 | 150 | fn styles(&self) -> &Vec { 151 | &self.styles 152 | } 153 | } 154 | 155 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 156 | 157 | type SyncStateHandler = Box Result>; 158 | 159 | #[derive(Debug)] 160 | pub struct InvocationError; 161 | 162 | trait StateCallback { 163 | fn invoke(&self, state: &Any, event: &Any) -> Result, InvocationError>; 164 | } 165 | 166 | struct SyncStateCallback { 167 | state: PhantomData, 168 | handler: SyncStateHandler 169 | } 170 | 171 | impl StateCallback for SyncStateCallback { 172 | fn invoke(&self, state: &Any, event: &Any) -> Result, InvocationError> { 173 | let e = event.downcast_ref::().ok_or(InvocationError)?; 174 | let s = state.downcast_ref::().ok_or(InvocationError)?; 175 | let new_state = (self.handler)(s.clone(), e)?; 176 | Ok(Box::new(new_state)) 177 | } 178 | } 179 | 180 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 181 | 182 | pub enum RenderElement { 183 | Rect(LayoutRect, ColorF) 184 | } 185 | 186 | pub trait RenderContext { 187 | fn render(&mut self); 188 | fn push(&mut self, e: RenderElement); 189 | fn next(&mut self); 190 | fn bounds(&self) -> LayoutRect; 191 | } 192 | 193 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 194 | -------------------------------------------------------------------------------- /src/window.rs: -------------------------------------------------------------------------------- 1 | use gleam::gl; 2 | use glutin; 3 | use glutin::GlContext; 4 | use webrender; 5 | use webrender::api::*; 6 | use layout_context::LayoutContext; 7 | use futures::{Async, Poll, Stream}; 8 | use futures::task; 9 | use model::{Component, Event}; 10 | use std::rc::Rc; 11 | use std::cell::RefCell; 12 | use std::sync::{Arc, Mutex}; 13 | use std::sync::mpsc; 14 | use std::collections::VecDeque; 15 | 16 | #[derive(Debug)] 17 | pub enum WindowEvent { 18 | ApplicationClosed, 19 | WindowClosed, 20 | NotifyRenderComplete, 21 | Interaction(WorldPoint, Interaction), 22 | GlutinEvent(glutin::Event), 23 | GlutinWindowEvent(glutin::WindowEvent) 24 | } 25 | 26 | #[derive(Debug)] 27 | pub enum Interaction { 28 | Pressed, 29 | Released, 30 | } 31 | 32 | impl Event for Interaction {} 33 | 34 | #[derive(Clone, Copy, PartialEq)] 35 | pub struct Epoch(pub u32); 36 | 37 | impl Epoch { 38 | pub fn next(&mut self) -> Self { 39 | let val = self.0; 40 | self.0 = self.0 + 1; 41 | Epoch(val) 42 | } 43 | } 44 | 45 | struct Notifier { 46 | window_events_tx: mpsc::Sender 47 | } 48 | 49 | impl RenderNotifier for Notifier { 50 | fn new_frame_ready(&mut self) { 51 | info!("new_frame_ready"); 52 | #[cfg(not(target_os = "android"))] 53 | self.window_events_tx.send(WindowEvent::NotifyRenderComplete).unwrap(); 54 | } 55 | 56 | fn new_scroll_frame_ready(&mut self, _composite_needed: bool) { 57 | info!("new_scroll_frame_ready"); 58 | #[cfg(not(target_os = "android"))] 59 | self.window_events_tx.send(WindowEvent::NotifyRenderComplete).unwrap(); 60 | } 61 | } 62 | 63 | pub struct RendererHandle { 64 | epoch: Epoch, 65 | layout_context: Rc>, 66 | tree: Option>>, 67 | window_size: (u32, u32), 68 | gl_window: glutin::GlWindow, 69 | renderer: webrender::renderer::Renderer, 70 | api: webrender::api::RenderApi, 71 | document_id: webrender::api::DocumentId, 72 | } 73 | 74 | impl RendererHandle { 75 | pub fn update(&mut self) { 76 | self.renderer.update(); 77 | self.renderer.render(DeviceUintSize::new(self.window_size.0, self.window_size.1)); 78 | self.gl_window.swap_buffers().unwrap(); 79 | } 80 | 81 | pub fn render(&mut self) { 82 | if let Some(ref tree) = self.tree { 83 | info!("render()"); 84 | let layout_size = LayoutSize::new(self.window_size.0 as f32, self.window_size.1 as f32); 85 | 86 | generate_frame(&self.api, &self.document_id, &layout_size, &self.epoch.next(), &mut self.layout_context.borrow_mut(), &tree.lock().unwrap()); 87 | //context.rendered_epoch = context.epoch; 88 | } 89 | } 90 | 91 | pub fn set_tree(&mut self, tree: Arc>) { 92 | self.tree = Some(tree); 93 | } 94 | } 95 | 96 | pub struct WebrenderWindow; 97 | 98 | impl WebrenderWindow { 99 | pub fn new(title: &'static str, layout_context: Rc>) -> (RendererHandle, EventStream) { 100 | let (window_events_tx, window_events_rx) = mpsc::channel::(); 101 | 102 | let window = glutin::WindowBuilder::new() 103 | .with_title(title) 104 | .with_multitouch(); 105 | 106 | let glutin_events = glutin::EventsLoop::new(); 107 | 108 | let context = glutin::ContextBuilder::new().with_vsync(false); 109 | let gl_window = glutin::GlWindow::new(window, context, &glutin_events).unwrap(); 110 | 111 | unsafe { 112 | let _ = gl_window.make_current().unwrap(); 113 | }; 114 | 115 | let gl = match gl::GlType::default() { 116 | gl::GlType::Gl => unsafe { 117 | gl::GlFns::load_with(|symbol| gl_window.get_proc_address(symbol) as *const _) 118 | }, 119 | gl::GlType::Gles => unsafe { 120 | gl::GlesFns::load_with(|symbol| gl_window.get_proc_address(symbol) as *const _) 121 | } 122 | }; 123 | 124 | //println!("OpenGL version {}", gl.get_string(gl::VERSION)); 125 | //println!("Shader resource path: {:?}", res_path); 126 | 127 | let (width, height) = gl_window.get_inner_size_pixels().unwrap(); 128 | 129 | let opts = webrender::RendererOptions { 130 | //resource_override_path: res_path, 131 | debug: true, 132 | precache_shaders: true, 133 | device_pixel_ratio: 1.0, 134 | //window.hidpi_factor(), 135 | ..webrender::RendererOptions::default() 136 | }; 137 | 138 | let (renderer, sender) = webrender::renderer::Renderer::new(gl, opts).unwrap(); 139 | 140 | let api = sender.create_api(); 141 | let document_id = api.add_document(DeviceUintSize::zero()); 142 | api.set_root_pipeline(document_id, PipelineId(0, 0)); 143 | 144 | let notifier = Box::new(Notifier { 145 | window_events_tx: window_events_tx.clone() 146 | }); 147 | renderer.set_render_notifier(notifier); 148 | 149 | (RendererHandle { 150 | epoch: Epoch(0), 151 | layout_context, 152 | tree: None, 153 | window_size: (width, height), 154 | gl_window, 155 | renderer, 156 | api, 157 | document_id, 158 | }, EventStream { 159 | glutin_events, 160 | window_events: window_events_rx, 161 | events: VecDeque::new(), 162 | mouse: WorldPoint::zero() 163 | }) 164 | } 165 | } 166 | 167 | pub struct EventStream { 168 | glutin_events: glutin::EventsLoop, 169 | window_events: mpsc::Receiver, 170 | events: VecDeque, 171 | mouse: WorldPoint, 172 | } 173 | 174 | impl Stream for EventStream { 175 | type Item = WindowEvent; 176 | type Error = (); 177 | 178 | fn poll(&mut self) -> Poll, Self::Error> { 179 | let mut polled_events = Vec::new(); 180 | 181 | // Grab all Glutin events 182 | let mut mouse: WorldPoint = self.mouse; 183 | self.glutin_events.poll_events(|event| { 184 | let weld_event = match event { 185 | glutin::Event::WindowEvent { event, .. } => match event { 186 | glutin::WindowEvent::Closed => WindowEvent::WindowClosed, 187 | glutin::WindowEvent::MouseMoved { position: (x, y), .. } => { 188 | mouse = WorldPoint::new(x as f32, y as f32); 189 | WindowEvent::GlutinWindowEvent(event) 190 | }, 191 | glutin::WindowEvent::MouseInput { button: glutin::MouseButton::Left, state: glutin::ElementState::Pressed, .. } => { 192 | WindowEvent::Interaction(mouse, Interaction::Pressed) 193 | }, 194 | glutin::WindowEvent::MouseInput { button: glutin::MouseButton::Left, state: glutin::ElementState::Released, .. } => { 195 | WindowEvent::Interaction(mouse, Interaction::Released) 196 | }, 197 | _ => WindowEvent::GlutinWindowEvent(event) 198 | }, 199 | _ => WindowEvent::GlutinEvent(event) 200 | }; 201 | 202 | polled_events.push(weld_event); 203 | }); 204 | self.mouse = mouse; 205 | self.events.extend(polled_events); 206 | 207 | // Grab all events sent by notifier 208 | loop { 209 | match self.window_events.try_recv() { 210 | Ok(event) => self.events.push_back(event), 211 | Err(_) => break 212 | } 213 | } 214 | 215 | // Publish in stream 216 | match self.events.pop_front() { 217 | Some(event) => { 218 | match event { 219 | WindowEvent::WindowClosed => Ok(Async::Ready(None)), 220 | _ => Ok(Async::Ready(Some(event))) 221 | } 222 | }, 223 | None => { 224 | // No messages were polled, notify the task so we will be re-polled in a little while 225 | let t = task::current(); 226 | t.notify(); 227 | 228 | Ok(Async::NotReady) 229 | } 230 | } 231 | } 232 | } 233 | 234 | fn generate_frame(api: &RenderApi, document_id: &DocumentId, layout_size: &LayoutSize, epoch: &Epoch, layout_context: &mut LayoutContext, tree: &Component) { 235 | info!("generate_frame, epoch: {}", epoch.0); 236 | let device_size = DeviceUintSize::new(layout_size.width as u32, layout_size.height as u32); 237 | let root_background_color = ColorF::new(0.0, 0.7, 0.0, 1.0); 238 | api.set_window_parameters(*document_id, device_size, DeviceUintRect::new(DeviceUintPoint::zero(), device_size)); 239 | api.set_display_list(*document_id, 240 | webrender::api::Epoch(epoch.0), 241 | Some(root_background_color), 242 | *layout_size, 243 | build_display_list(&layout_size, layout_context, tree).finalize(), 244 | true); 245 | api.generate_frame(*document_id, None); 246 | } 247 | 248 | fn build_display_list(layout_size: &LayoutSize, layout_context: &mut LayoutContext, tree: &Component) -> DisplayListBuilder { 249 | let mut builder = DisplayListBuilder::new(PipelineId(0, 0), *layout_size); 250 | 251 | layout_context.update_layout(&tree, layout_size); 252 | layout_context.build_display_list(&mut builder, &tree); 253 | 254 | builder 255 | } -------------------------------------------------------------------------------- /tests/common/utils.rs: -------------------------------------------------------------------------------- 1 | use weld::model::*; 2 | use webrender::api::LayoutRect; 3 | 4 | #[macro_export] 5 | macro_rules! impl_dummy_renderer { 6 | ($component_name:ident) => { 7 | impl Renderer for $component_name { 8 | fn id(&self) -> &'static str { 9 | stringify!($component_name) 10 | } 11 | 12 | fn render(&self, ctx: &mut RenderContext) { 13 | println!("Rendering: {:?}", self); 14 | ctx.next(); 15 | } 16 | } 17 | }; 18 | } 19 | 20 | pub struct TestRenderContext<'a> { 21 | component: &'a InspectableComponent, 22 | elements: Vec<(String, String)> 23 | } 24 | 25 | impl<'a> TestRenderContext<'a> { 26 | pub fn new(component: &'a Component) -> TestRenderContext<'a> { 27 | TestRenderContext { 28 | component: component.inspect(), 29 | elements: Vec::new() 30 | } 31 | } 32 | 33 | pub fn elements(&self) -> &Vec<(String, String)> { 34 | &self.elements 35 | } 36 | } 37 | 38 | impl<'a> RenderContext for TestRenderContext<'a> { 39 | fn render(&mut self) { 40 | let component_name: String = self.component.name().clone().unwrap_or_else(|| "Unknown".into()); 41 | let renderer_id: String = self.component.renderer().id().into(); 42 | self.elements.push((component_name, renderer_id)); 43 | self.component.renderer().render(self); 44 | } 45 | 46 | fn push(&mut self, _: RenderElement) { 47 | unimplemented!() 48 | } 49 | 50 | fn next(&mut self) { 51 | for child in self.component.children().iter() { 52 | let mut child_context = TestRenderContext::new(child); 53 | child_context.render(); 54 | self.elements.extend(child_context.elements); 55 | } 56 | } 57 | 58 | fn bounds(&self) -> LayoutRect { 59 | unimplemented!() 60 | } 61 | } 62 | 63 | -------------------------------------------------------------------------------- /tests/component.rs: -------------------------------------------------------------------------------- 1 | extern crate weld; 2 | extern crate webrender; 3 | 4 | use weld::model::*; 5 | 6 | #[path="common/utils.rs"] 7 | #[macro_use] 8 | mod utils; 9 | 10 | use utils::TestRenderContext; 11 | 12 | #[derive(Debug)] 13 | struct ComA {} 14 | 15 | fn com_a() -> Component { 16 | Component::new(ComA {}) 17 | } 18 | 19 | #[derive(Debug)] 20 | struct ComB {} 21 | 22 | fn com_b() -> Component { 23 | Component::new(ComB {}) 24 | } 25 | 26 | impl_dummy_renderer!(ComA); 27 | impl_dummy_renderer!(ComB); 28 | 29 | #[derive(Clone, Debug)] 30 | struct MyAppState { 31 | counter: u32, 32 | child: ChildState, 33 | } 34 | 35 | #[derive(Debug)] 36 | enum MyAppEvent { 37 | Pressed, 38 | } 39 | 40 | impl Event for MyAppEvent {} 41 | 42 | struct MyApp; 43 | 44 | impl MyApp { 45 | fn new() -> MyAppState { 46 | MyAppState { 47 | counter: 0, 48 | child: ChildState { counter: 0 } 49 | } 50 | } 51 | } 52 | 53 | impl State for MyAppState { 54 | fn build(&self) -> Component { 55 | com_a() 56 | .name("parent") 57 | .on(Box::new(|state: Self, event| { 58 | match *event { 59 | MyAppEvent::Pressed => { 60 | Ok(MyAppState { 61 | counter: state.counter + 1, 62 | child: state.child 63 | }) 64 | } 65 | } 66 | })) 67 | .child(com_b().name("child1")) 68 | .child( 69 | self.child.build() 70 | .name("child2") 71 | .on(Box::new(|state: Self, event| { 72 | match *event { 73 | MyAppEvent::Pressed => { 74 | Ok(MyAppState { 75 | counter: state.counter, 76 | child: ChildState { counter: state.child.counter + 1 } 77 | }) 78 | } 79 | } 80 | })) 81 | ) 82 | } 83 | } 84 | 85 | #[derive(Clone, Debug)] 86 | struct ChildState { 87 | counter: u32 88 | } 89 | 90 | impl State for ChildState { 91 | fn build(&self) -> Component { 92 | com_a() 93 | } 94 | } 95 | 96 | #[test] 97 | fn test_invoke() { 98 | let state = MyApp::new(); 99 | let component = state.build(); 100 | let new_state = component.invoke(&state, MyAppEvent::Pressed) 101 | .and_then(|state| state.build().find_by_name("child2").unwrap().invoke(&state, MyAppEvent::Pressed)) 102 | .unwrap(); 103 | assert_eq!(new_state.counter, 1); 104 | assert_eq!(new_state.child.counter, 1); 105 | } 106 | 107 | #[test] 108 | fn test_rendering() { 109 | let state = MyApp::new(); 110 | let component = state.build(); 111 | 112 | let mut context = TestRenderContext::new(&component); 113 | context.render(); 114 | assert_eq!(context.elements(), &vec![ 115 | ("parent".into(), "ComA".into()), 116 | ("child1".into(), "ComB".into()), 117 | ("child2".into(), "ComA".into()), 118 | ]); 119 | } 120 | -------------------------------------------------------------------------------- /tests/todomvc.rs: -------------------------------------------------------------------------------- 1 | extern crate weld; 2 | extern crate webrender; 3 | 4 | use weld::model::*; 5 | 6 | #[path = "common/utils.rs"] 7 | #[macro_use] 8 | mod utils; 9 | 10 | use utils::TestRenderContext; 11 | 12 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 13 | 14 | #[derive(Debug)] 15 | struct Container {} 16 | 17 | fn container() -> Component { 18 | Component::new(Container {}) 19 | } 20 | 21 | #[derive(Debug)] 22 | struct Label { 23 | caption: String 24 | } 25 | 26 | fn label>(caption: S) -> Component { 27 | Component::new(Label { caption: caption.into() }) 28 | } 29 | 30 | #[derive(Debug)] 31 | struct Input {} 32 | 33 | fn input() -> Component { 34 | Component::new(Input {}) 35 | } 36 | 37 | enum InputEvent { 38 | TextChanged(&'static str) 39 | } 40 | 41 | impl Event for InputEvent {} 42 | 43 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 44 | 45 | impl_dummy_renderer!(Container); 46 | impl_dummy_renderer!(Input); 47 | impl_dummy_renderer!(Label); 48 | 49 | #[derive(Clone, Debug)] 50 | struct MyAppState { 51 | todos: Vec, 52 | } 53 | 54 | impl State for MyAppState { 55 | fn build(&self) -> Component { 56 | let items: Vec<_> = self.todos.iter().enumerate().map(|(index, item)| { 57 | TodoItem { message: item.clone() }.build().name(format!("todo-{}", index)) 58 | }).collect(); 59 | 60 | container() 61 | .name("container") 62 | .child( 63 | input() 64 | .name("input") 65 | .on(Box::new(|state: Self, event| { 66 | match *event { 67 | InputEvent::TextChanged(str) => { 68 | let mut new = state.clone(); 69 | new.todos.push(str.into()); 70 | Ok(new) 71 | } 72 | } 73 | })) 74 | ) 75 | .children(items) 76 | } 77 | } 78 | 79 | #[derive(Clone, Debug)] 80 | struct TodoItem { 81 | message: String 82 | } 83 | 84 | impl State for TodoItem { 85 | fn build(&self) -> Component { 86 | label(self.message.clone()) 87 | } 88 | } 89 | 90 | /// 91 | /// 92 | /// 93 | 94 | struct MyApp; 95 | 96 | impl MyApp { 97 | fn new() -> MyAppState { 98 | MyAppState { 99 | todos: Vec::new() 100 | } 101 | } 102 | } 103 | 104 | #[test] 105 | fn test_app() { 106 | let mut state = MyApp::new(); 107 | assert_eq!(state.todos.len(), 0); 108 | 109 | let mut c = state.build(); 110 | assert_eq!(c.inspect().children().len(), 1); 111 | 112 | state = c.find_by_name("input").unwrap().invoke(&state, InputEvent::TextChanged("foo")).unwrap(); 113 | assert_eq!(state.todos.len(), 1); 114 | c = state.build(); 115 | assert_eq!(c.inspect().children().len(), 2); 116 | assert_eq!(c.find_by_name("todo-0").is_some(), true); 117 | assert_eq!(c.find_by_name("todo-1").is_none(), true); 118 | 119 | state = c.find_by_name("input").unwrap().invoke(&state, InputEvent::TextChanged("bar")).unwrap(); 120 | assert_eq!(state.todos.len(), 2); 121 | c = state.build(); 122 | assert_eq!(c.inspect().children().len(), 3); 123 | assert_eq!(c.find_by_name("todo-0").is_some(), true); 124 | assert_eq!(c.find_by_name("todo-1").is_some(), true); 125 | 126 | let mut context = TestRenderContext::new(&c); 127 | context.render(); 128 | } --------------------------------------------------------------------------------