diff --git a/src/components/gfx/render_task.rs b/src/components/gfx/render_task.rs index af80d384b69..dab31cdc2f4 100644 --- a/src/components/gfx/render_task.rs +++ b/src/components/gfx/render_task.rs @@ -32,7 +32,7 @@ pub struct RenderLayer { pub enum Msg { RenderMsg(RenderLayer), - ReRenderMsg(~[BufferRequest], f32), + ReRenderMsg(~[BufferRequest], f32, PipelineId), PaintPermissionGranted, PaintPermissionRevoked, ExitMsg(Chan<()>), @@ -135,18 +135,18 @@ impl RenderTask { match self.port.recv() { RenderMsg(render_layer) => { if self.paint_permission { - self.compositor.new_layer(render_layer.size, self.opts.tile_size); + self.compositor.new_layer(self.id, render_layer.size); } self.render_layer = Some(render_layer); } - ReRenderMsg(tiles, scale) => { - self.render(tiles, scale); + ReRenderMsg(tiles, scale, id) => { + self.render(tiles, scale, id); } PaintPermissionGranted => { self.paint_permission = true; match self.render_layer { Some(ref render_layer) => { - self.compositor.new_layer(render_layer.size, self.opts.tile_size); + self.compositor.new_layer(self.id, render_layer.size); } None => {} } @@ -162,7 +162,7 @@ impl RenderTask { } } - fn render(&mut self, tiles: ~[BufferRequest], scale: f32) { + fn render(&mut self, tiles: ~[BufferRequest], scale: f32, id: PipelineId) { let render_layer; match self.render_layer { Some(ref r_layer) => { @@ -202,7 +202,7 @@ impl RenderTask { font_ctx: self.font_ctx, opts: &self.opts }; - + // Apply the translation to render the tile we want. let matrix: Matrix2D = Matrix2D::identity(); let matrix = matrix.scale(scale as AzFloat, scale as AzFloat); @@ -234,7 +234,7 @@ impl RenderTask { debug!("render_task: returning surface"); if self.paint_permission { - self.compositor.paint(self.id, layer_buffer_set.clone(), render_layer.size); + self.compositor.paint(id, layer_buffer_set.clone()); } debug!("caching paint msg"); self.last_paint_msg = Some((layer_buffer_set, render_layer.size)); diff --git a/src/components/main/compositing/compositor_layer.rs b/src/components/main/compositing/compositor_layer.rs new file mode 100644 index 00000000000..083dc5312c5 --- /dev/null +++ b/src/components/main/compositing/compositor_layer.rs @@ -0,0 +1,386 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use geom::point::Point2D; +use geom::size::Size2D; +use geom::rect::Rect; +use geom::matrix::identity; +use gfx::render_task::ReRenderMsg; +use servo_msg::compositor_msg::{LayerBuffer, LayerBufferSet}; +use servo_msg::constellation_msg::PipelineId; +use script::dom::event::{ClickEvent, MouseDownEvent, MouseUpEvent}; +use script::script_task::SendEventMsg; +use windowing::{MouseWindowEvent, MouseWindowClickEvent, MouseWindowMouseDownEvent, MouseWindowMouseUpEvent}; +use compositing::quadtree::Quadtree; +use layers::layers::{ContainerLayerKind, ContainerLayer, TextureLayerKind, TextureLayer, TextureManager}; +use pipeline::Pipeline; + +/// The CompositorLayer represents an element on a page that has a unique scroll +/// or animation behavior. This can include absolute positioned elements, iframes, etc. +/// Each layer can also have child layers. +pub struct CompositorLayer { + /// This layer's pipeline. BufferRequests and mouse events will be sent through this. + pipeline: Pipeline, + /// The size of the underlying page in page coordinates. This is an option + /// because we may not know the size of the page until layout is finished completely. + /// if we have no size yet, the layer is hidden until a size message is recieved. + page_size: Option>, + /// The offset of the page due to scrolling. (0,0) is when the window sees the + /// top left corner of the page. + scroll_offset: Point2D, + /// This layer's children. These could be iframes or any element which + /// differs in scroll behavior from its parent. Each is associated with a + /// ContainerLayer which determines its position relative to its parent and + /// clipping rect. Children are stored in the order in which they are drawn. + children: ~[CompositorLayerChild], + /// This layer's quadtree. This is where all buffers are stored for this layer. + quadtree: MaybeQuadtree, + /// The root layer of this CompositorLayer's layer tree. Buffers are collected + /// from the quadtree and inserted here when the layer is painted to the screen. + root_layer: @mut ContainerLayer, + /// When set to true, this layer is ignored by its parents. This is useful for + /// soft deletion or when waiting on a page size. + hidden: bool, +} + +/// Helper struct for keeping CompositorLayer children organized. +struct CompositorLayerChild { + /// The child itself. + child: ~CompositorLayer, + /// A ContainerLayer managed by the parent node. This deals with clipping and + /// positioning, and is added above the child's layer tree. + container: @mut ContainerLayer, +} + +/// Helper enum for storing quadtrees. Either contains a quadtree, or contains +/// information from which a quadtree can be built. +enum MaybeQuadtree { + Tree(Quadtree<~LayerBuffer>), + NoTree(uint, Option), +} + +impl CompositorLayer { + /// Creates a new CompositorLayer without a page size that is initially hidden. + pub fn new(pipeline: Pipeline, page_size: Option>, tile_size: uint, max_mem: Option) + -> CompositorLayer { + CompositorLayer { + pipeline: pipeline, + page_size: page_size, + scroll_offset: Point2D(0f32, 0f32), + children: ~[], + quadtree: match page_size { + None => NoTree(tile_size, max_mem), + Some(page_size) => Tree(Quadtree::new(page_size.width as uint, + page_size.height as uint, + tile_size, + max_mem)), + }, + root_layer: @mut ContainerLayer(), + hidden: true, + } + } + + // Move the layer by as relative specified amount in page coordinates. Does not change + // the position of the layer relative to its parent. This also takes in a cursor position + // to see if the mouse is over child layers first. If a layer successfully scrolled, returns + // true; otherwise returns false, so a parent layer can scroll instead. + pub fn scroll(&mut self, delta: Point2D, cursor: Point2D, window_size: Size2D) -> bool { + let cursor = cursor - self.scroll_offset; + for self.children.mut_iter().filter(|x| !x.child.hidden).advance |child| { + match child.container.scissor { + None => { + error!("CompositorLayer: unable to perform cursor hit test for layer"); + } + Some(rect) => { + if cursor.x >= rect.origin.x && cursor.x < rect.origin.x + rect.size.width + && cursor.y >= rect.origin.y && cursor.y < rect.origin.y + rect.size.height + && child.child.scroll(delta, cursor - rect.origin, rect.size) { + return true; + } + } + } + } + + let old_origin = self.scroll_offset; + self.scroll_offset = self.scroll_offset + delta; + + // bounds checking + let page_size = match self.page_size { + Some(size) => size, + None => fail!("CompositorLayer: tried to scroll with no page size set"), + }; + let min_x = (window_size.width - page_size.width).min(&0.0); + self.scroll_offset.x = self.scroll_offset.x.clamp(&min_x, &0.0); + let min_y = (window_size.height - page_size.height).min(&0.0); + self.scroll_offset.y = self.scroll_offset.y.clamp(&min_y, &0.0); + + // check to see if we scrolled + if old_origin - self.scroll_offset == Point2D(0f32, 0f32) { + return false; + } + + self.root_layer.common.set_transform(identity().translate(self.scroll_offset.x, + self.scroll_offset.y, + 0.0)); + true + } + + // Takes in a MouseWindowEvent, determines if it should be passed to children, and + // sends the event off to the appropriate pipeline. NB: the cursor position is in + // page coordinates. + pub fn send_mouse_event(&self, event: MouseWindowEvent, cursor: Point2D) { + let cursor = cursor - self.scroll_offset; + for self.children.iter().filter(|&x| !x.child.hidden).advance |child| { + match child.container.scissor { + None => { + error!("CompositorLayer: unable to perform cursor hit test for layer"); + } + Some(rect) => { + if cursor.x >= rect.origin.x && cursor.x < rect.origin.x + rect.size.width + && cursor.y >= rect.origin.y && cursor.y < rect.origin.y + rect.size.height { + child.child.send_mouse_event(event, cursor - rect.origin); + return; + } + } + } + } + + // This mouse event is mine! + let message = match event { + MouseWindowClickEvent(button, _) => ClickEvent(button, cursor), + MouseWindowMouseDownEvent(button, _) => MouseDownEvent(button, cursor), + MouseWindowMouseUpEvent(button, _) => MouseUpEvent(button, cursor), + }; + + self.pipeline.script_chan.send(SendEventMsg(self.pipeline.id.clone(), message)); + } + + // Given the current window size, determine which tiles need to be (re)rendered + // and sends them off the the appropriate renderer. + // Returns a bool that is true if the scene should be repainted. + pub fn get_buffer_request(&mut self, window_rect: Rect, scale: f32) -> bool { + let rect = Rect(Point2D(-self.scroll_offset.x + window_rect.origin.x, + -self.scroll_offset.y + window_rect.origin.y), + window_rect.size); + let mut redisplay: bool; + { // block here to prevent double mutable borrow of self + let quadtree = match self.quadtree { + NoTree(_, _) => fail!("CompositorLayer: cannot get buffer request, no quadtree initialized"), + Tree(ref mut quadtree) => quadtree, + }; + let (request, r) = quadtree.get_tile_rects_page(rect, scale); + redisplay = r; // workaround to make redisplay visible outside block + if !request.is_empty() { + self.pipeline.render_chan.send(ReRenderMsg(request, scale, self.pipeline.id.clone())); + } + } + if redisplay { + self.build_layer_tree(); + } + let transform = |x: &mut CompositorLayerChild| -> bool { + match x.container.scissor { + Some(scissor) => { + let new_rect = window_rect.intersection(&scissor); + match new_rect { + Some(new_rect) => { + x.child.get_buffer_request(new_rect, scale) + } + None => { + false //Layer is offscreen + } + } + } + None => { + fail!("CompositorLayer: Child layer not clipped"); + } + } + }; + self.children.mut_iter().filter(|x| !x.child.hidden) + .transform(transform) + .fold(false, |a, b| a || b) || redisplay + } + + + // Move the sublayer to an absolute position in page coordinates relative to its parent, + // and clip the layer to the specified size in page coordinates. + // This method returns false if the specified layer is not found. + pub fn set_clipping_rect(&mut self, pipeline_id: PipelineId, new_rect: Rect) -> bool { + for self.children.iter().advance |child_node| { + if pipeline_id != child_node.child.pipeline.id { + loop; + } + let con = child_node.container; + con.common.set_transform(identity().translate(new_rect.origin.x, + new_rect.origin.y, + 0.0)); + con.scissor = Some(new_rect); + return true; + } + + // ID does not match any of our immediate children, so recurse on descendents (including hidden children) + self.children.mut_iter().transform(|x| &mut x.child).any(|x| x.set_clipping_rect(pipeline_id, new_rect)) + } + + + // Called when the layer changes size (NOT as a result of a zoom event). + // This method returns false if the specified layer is not found. + pub fn resize(&mut self, pipeline_id: PipelineId, new_size: Size2D, window_size: Size2D) -> bool { + if self.pipeline.id == pipeline_id { + self.page_size = Some(new_size); + // TODO: might get buffers back here + match self.quadtree { + Tree(ref mut quadtree) => quadtree.resize(new_size.width as uint, new_size.height as uint), + NoTree(tile_size, max_mem) => self.quadtree = Tree(Quadtree::new(new_size.width as uint, + new_size.height as uint, + tile_size, + max_mem)), + } + // Call scroll for bounds checking of the page shrunk. Use (-1, -1) as the cursor position + // to make sure the scroll isn't propagated downwards. + self.scroll(Point2D(0f32, 0f32), Point2D(-1f32, -1f32), window_size); + return true; + } + + // ID does not match ours, so recurse on descendents (including hidden children) + let transform = |x: &mut CompositorLayerChild| -> bool { + match x.container.scissor { + Some(scissor) => { + x.child.resize(pipeline_id, new_size, scissor.size) + } + None => { + fail!("CompositorLayer: Child layer not clipped"); + } + } + }; + + self.children.mut_iter().any(transform) + } + + // Collect buffers from the quadtree. This method IS NOT recursive, so child CompositorLayers + // are not rebuilt directly from this method. + pub fn build_layer_tree(&mut self) { + // Iterate over the children of the container layer. + let mut current_layer_child = self.root_layer.first_child; + + // Delete old layer. + while current_layer_child.is_some() { + let trash = current_layer_child.get(); + do current_layer_child.get().with_common |common| { + current_layer_child = common.next_sibling; + } + self.root_layer.remove_child(trash); + } + + // Add child layers. + for self.children.mut_iter().filter(|x| !x.child.hidden).advance |child| { + current_layer_child = match current_layer_child { + None => { + child.container.common.parent = None; + child.container.common.prev_sibling = None; + child.container.common.next_sibling = None; + self.root_layer.add_child(ContainerLayerKind(child.container)); + None + } + Some(_) => { + fail!("CompositorLayer: Layer tree failed to delete"); + } + }; + } + + // Add new tiles. + let quadtree = match self.quadtree { + NoTree(_, _) => fail!("CompositorLayer: cannot get buffer request, no quadtree initialized"), + Tree(ref mut quadtree) => quadtree, + }; + + let all_tiles = quadtree.get_all_tiles(); + for all_tiles.iter().advance |buffer| { + debug!("osmain: compositing buffer rect %?", &buffer.rect); + + // Find or create a texture layer. + let texture_layer; + current_layer_child = match current_layer_child { + None => { + debug!("osmain: adding new texture layer"); + texture_layer = @mut TextureLayer::new(@buffer.draw_target.clone() as @TextureManager, + buffer.screen_pos.size); + self.root_layer.add_child(TextureLayerKind(texture_layer)); + None + } + Some(TextureLayerKind(existing_texture_layer)) => { + texture_layer = existing_texture_layer; + texture_layer.manager = @buffer.draw_target.clone() as @TextureManager; + + // Move on to the next sibling. + do current_layer_child.get().with_common |common| { + common.next_sibling + } + } + Some(_) => fail!(~"found unexpected layer kind"), + }; + + + let rect = buffer.rect; + // Set the layer's transform. + let transform = identity().translate(rect.origin.x, rect.origin.y, 0.0); + let transform = transform.scale(rect.size.width, rect.size.height, 1.0); + texture_layer.common.set_transform(transform); + } + + } + + // Add LayerBuffers to the specified layer. Returns false if the layer is not found. + pub fn add_buffers(&mut self, pipeline_id: PipelineId, new_buffers: &LayerBufferSet) -> bool { + if self.pipeline.id == pipeline_id { + { // block here to prevent double mutable borrow of self + let quadtree = match self.quadtree { + NoTree(_, _) => fail!("CompositorLayer: cannot get buffer request, no quadtree initialized"), + Tree(ref mut quadtree) => quadtree, + }; + + for new_buffers.buffers.iter().advance |buffer| { + // TODO: This may return old buffers, which should be sent back to the renderer. + quadtree.add_tile_pixel(buffer.screen_pos.origin.x, buffer.screen_pos.origin.y, + buffer.resolution, ~buffer.clone()); + } + } + self.build_layer_tree(); + return true; + } + // ID does not match ours, so recurse on descendents (including hidden children). + self.children.mut_iter().transform(|x| &mut x.child).any(|x| x.add_buffers(pipeline_id, new_buffers)) + } + + // Deletes a specified sublayer, including hidden children. Returns false if the layer is not found. + pub fn delete(&mut self, pipeline_id: PipelineId) -> bool { + match self.children.rposition(|x| x.child.pipeline.id == pipeline_id) { + Some(index) => { + // TODO: send buffers back to renderer when layer is deleted + self.children.remove(index); + self.build_layer_tree(); + true + } + None => { + self.children.mut_iter().transform(|x| &mut x.child).any(|x| x.delete(pipeline_id)) + } + } + } + + // Adds a child. + pub fn add_child(&mut self, pipeline: Pipeline, page_size: Option>, tile_size: uint, + max_mem: Option, clipping_rect: Rect) { + let container = @mut ContainerLayer(); + container.scissor = Some(clipping_rect); + container.common.set_transform(identity().translate(clipping_rect.origin.x, + clipping_rect.origin.y, + 0.0)); + let child = ~CompositorLayer::new(pipeline, page_size, tile_size, max_mem); + container.add_child(ContainerLayerKind(child.root_layer)); + self.children.push(CompositorLayerChild { + child: child, + container: container, + }); + + } +} diff --git a/src/components/main/compositing/mod.rs b/src/components/main/compositing/mod.rs index 8bdc8202c6b..b8f4c22ae5d 100644 --- a/src/components/main/compositing/mod.rs +++ b/src/components/main/compositing/mod.rs @@ -3,19 +3,19 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use platform::{Application, Window}; -use script::dom::event::{Event_, ClickEvent, MouseDownEvent, MouseUpEvent, ResizeEvent}; +use script::dom::event::ResizeEvent; use script::script_task::{LoadMsg, NavigateMsg, SendEventMsg}; +pub use windowing; use windowing::{ApplicationMethods, WindowEvent, WindowMethods}; use windowing::{IdleWindowEvent, ResizeWindowEvent, LoadUrlWindowEvent, MouseWindowEventClass}; use windowing::{ScrollWindowEvent, ZoomWindowEvent, NavigationWindowEvent, FinishedWindowEvent}; use windowing::{QuitWindowEvent, MouseWindowClickEvent, MouseWindowMouseDownEvent, MouseWindowMouseUpEvent}; -use servo_msg::compositor_msg::{RenderListener, LayerBuffer, LayerBufferSet, RenderState}; +use servo_msg::compositor_msg::{RenderListener, LayerBufferSet, RenderState}; use servo_msg::compositor_msg::{ReadyState, ScriptListener}; use servo_msg::constellation_msg::PipelineId; use servo_msg::constellation_msg; -use gfx::render_task::ReRenderMsg; use gfx::opts::Opts; use azure::azure_hl::{DataSourceSurface, DrawTarget, SourceSurfaceMethods, current_gl_context}; @@ -35,7 +35,6 @@ use geom::size::Size2D; use geom::rect::Rect; use layers::layers::{ARGB32Format, ContainerLayer, ContainerLayerKind, Format}; use layers::layers::{ImageData, WithDataFn}; -use layers::layers::{TextureLayerKind, TextureLayer, TextureManager}; use layers::rendergl; use layers::scene::Scene; use opengles::gl2; @@ -44,14 +43,16 @@ use servo_util::{time, url}; use servo_util::time::profile; use servo_util::time::ProfilerChan; -use extra::arc; -pub use windowing; - use extra::time::precise_time_s; -use compositing::quadtree::Quadtree; +use extra::arc; + use constellation::SendableFrameTree; use pipeline::Pipeline; +use compositing::compositor_layer::CompositorLayer; + mod quadtree; +mod compositor_layer; + /// The implementation of the layers-based compositor. #[deriving(Clone)] @@ -79,18 +80,18 @@ impl RenderListener for CompositorChan { port.recv() } - fn paint(&self, id: PipelineId, layer_buffer_set: arc::ARC, new_size: Size2D) { - self.chan.send(Paint(id, layer_buffer_set, new_size)) + fn paint(&self, id: PipelineId, layer_buffer_set: arc::ARC) { + self.chan.send(Paint(id, layer_buffer_set)) } - fn new_layer(&self, page_size: Size2D, tile_size: uint) { - self.chan.send(NewLayer(page_size, tile_size)) + fn new_layer(&self, id: PipelineId, page_size: Size2D) { + self.chan.send(NewLayer(id, page_size)) } - fn resize_layer(&self, page_size: Size2D) { - self.chan.send(ResizeLayer(page_size)) + fn resize_layer(&self, id: PipelineId, page_size: Size2D) { + self.chan.send(ResizeLayer(id, page_size)) } - fn delete_layer(&self) { - self.chan.send(DeleteLayer) + fn delete_layer(&self, id: PipelineId) { + self.chan.send(DeleteLayer(id)) } fn set_render_state(&self, render_state: RenderState) { @@ -126,16 +127,16 @@ pub enum Msg { /// Requests the compositors GL context. GetGLContext(Chan), - // TODO: Attach layer ids and epochs to these messages + // TODO: Attach epochs to these messages /// Alerts the compositor that there is a new layer to be rendered. - NewLayer(Size2D, uint), - /// Alerts the compositor that the current layer has changed size. - ResizeLayer(Size2D), - /// Alerts the compositor that the current layer has been deleted. - DeleteLayer, + NewLayer(PipelineId, Size2D), + /// Alerts the compositor that the specified layer has changed size. + ResizeLayer(PipelineId, Size2D), + /// Alerts the compositor that the specified layer has been deleted. + DeleteLayer(PipelineId), /// Requests that the compositor paint the given layer buffer set for the given page size. - Paint(PipelineId, arc::ARC, Size2D), + Paint(PipelineId, arc::ARC), /// Alerts the compositor to the current status of page loading. ChangeReadyState(ReadyState), /// Alerts the compositor to the current status of rendering. @@ -220,119 +221,30 @@ impl CompositorTask { let root_layer = @mut ContainerLayer(); let window_size = window.size(); let mut scene = Scene(ContainerLayerKind(root_layer), window_size, identity()); + let mut window_size = Size2D(window_size.width as int, window_size.height as int); let mut done = false; let mut recomposite = false; - // FIXME: This should not be a separate offset applied after the fact but rather should be - // applied to the layers themselves on a per-layer basis. However, this won't work until scroll - // positions are sent to content. - let mut world_offset = Point2D(0f32, 0f32); - let mut page_size = Size2D(0f32, 0f32); - let mut window_size = Size2D(window_size.width as int, - window_size.height as int); - // Keeps track of the current zoom factor let mut world_zoom = 1f32; - // Keeps track of local zoom factor. Reset to 1 after a rerender event. - let mut local_zoom = 1f32; - // Channel to the outermost frame's pipeline. - // FIXME: Compositor currently only asks for tiles to composite from this pipeline, - // Subframes need to be handled, as well. Additionally, events are only forwarded - // to this pipeline, but they should be routed to the appropriate pipeline via - // the constellation. - let mut pipeline: Option = None; - - // Quadtree for this layer - // FIXME: This should be one-per-layer - let mut quadtree: Option> = None; - - // Keeps track of if we have performed a zoom event and how recently. let mut zoom_action = false; let mut zoom_time = 0f; - // Extract tiles from the given quadtree and build and display the render tree. - let build_layer_tree: &fn(&Quadtree<~LayerBuffer>) = |quad: &Quadtree<~LayerBuffer>| { - // Iterate over the children of the container layer. - let mut current_layer_child = root_layer.first_child; - - // Delete old layer - while current_layer_child.is_some() { - let trash = current_layer_child.get(); - do current_layer_child.get().with_common |common| { - current_layer_child = common.next_sibling; - } - root_layer.remove_child(trash); - } + // Channel to the outermost frame's pipeline. + // FIXME: Events are only forwarded to this pipeline, but they should be + // routed to the appropriate pipeline via the constellation. + let mut pipeline: Option = None; - let all_tiles = quad.get_all_tiles(); - for all_tiles.iter().advance |buffer| { - let width = buffer.screen_pos.size.width as uint; - let height = buffer.screen_pos.size.height as uint; - debug!("osmain: compositing buffer rect %?", &buffer.rect); - - // Find or create a texture layer. - let texture_layer; - current_layer_child = match current_layer_child { - None => { - debug!("osmain: adding new texture layer"); - texture_layer = @mut TextureLayer::new(@buffer.draw_target.clone() as @TextureManager, - buffer.screen_pos.size); - root_layer.add_child(TextureLayerKind(texture_layer)); - None - } - Some(TextureLayerKind(existing_texture_layer)) => { - texture_layer = existing_texture_layer; - texture_layer.manager = @buffer.draw_target.clone() as @TextureManager; - - // Move on to the next sibling. - do current_layer_child.get().with_common |common| { - common.next_sibling - } - } - Some(_) => fail!(~"found unexpected layer kind"), - }; - - let origin = buffer.rect.origin; - let origin = Point2D(origin.x as f32, origin.y as f32); - - // Set the layer's transform. - let transform = identity().translate(origin.x * world_zoom, origin.y * world_zoom, 0.0); - let transform = transform.scale(width as f32 * world_zoom / buffer.resolution, height as f32 * world_zoom / buffer.resolution, 1.0); - texture_layer.common.set_transform(transform); - - } - - // Reset zoom - local_zoom = 1f32; - root_layer.common.set_transform(identity().translate(-world_offset.x, - -world_offset.y, - 0.0)); - recomposite = true; - }; + // The root CompositorLayer + let mut compositor_layer: Option = None; + // Get BufferRequests from each layer. let ask_for_tiles = || { - match quadtree { - Some(ref mut quad) => { - let (tile_request, redisplay) = quad.get_tile_rects(Rect(Point2D(world_offset.x as int, - world_offset.y as int), - window_size), world_zoom); - - if !tile_request.is_empty() { - match pipeline { - Some(ref pipeline) => { - pipeline.render_chan.send(ReRenderMsg(tile_request, world_zoom)); - } - _ => { - println("Warning: Compositor: Cannot send tile request, no render chan initialized"); - } - } - } else if redisplay { - build_layer_tree(quad); - } - } - _ => { - fail!("Compositor: Tried to ask for tiles without an initialized quadtree"); - } + let window_size_page = Size2D(window_size.width as f32 / world_zoom, + window_size.height as f32 / world_zoom); + for compositor_layer.mut_iter().advance |layer| { + recomposite = layer.get_buffer_request(Rect(Point2D(0f32, 0f32), window_size_page), + world_zoom) || recomposite; } }; @@ -357,46 +269,66 @@ impl CompositorTask { GetGLContext(chan) => chan.send(current_gl_context()), - NewLayer(new_size, tile_size) => { - page_size = Size2D(new_size.width as f32, new_size.height as f32); - quadtree = Some(Quadtree::new(new_size.width.max(&(window_size.width as uint)), - new_size.height.max(&(window_size.height as uint)), - tile_size, Some(10000000u))); - ask_for_tiles(); + NewLayer(_id, new_size) => { + // FIXME: This should create an additional layer instead of replacing the current one. + // Once ResizeLayer messages are set up, we can switch to the new functionality. + + let p = match pipeline { + Some(ref pipeline) => pipeline, + None => fail!("Compositor: Received new layer without initialized pipeline"), + }; + let page_size = Size2D(new_size.width as f32, new_size.height as f32); + let new_layer = CompositorLayer::new(p.clone(), Some(page_size), + self.opts.tile_size, Some(10000000u)); - } - ResizeLayer(new_size) => { - page_size = Size2D(new_size.width as f32, new_size.height as f32); - // TODO: update quadtree, ask for tiles - } - DeleteLayer => { - // TODO: create secondary layer tree, keep displaying until new tiles come in + let current_child = root_layer.first_child; + // This assumes there is at most one child, which should be the case. + match current_child { + Some(old_layer) => root_layer.remove_child(old_layer), + None => {} + } + root_layer.add_child(ContainerLayerKind(new_layer.root_layer)); + compositor_layer = Some(new_layer); + + ask_for_tiles(); } - Paint(id, new_layer_buffer_set, new_size) => { - match pipeline { - Some(ref pipeline) => if id != pipeline.id { loop; }, - None => { loop; }, + ResizeLayer(id, new_size) => { + match compositor_layer { + Some(ref mut layer) => { + let page_window = Size2D(window_size.width as f32 / world_zoom, + window_size.height as f32 / world_zoom); + assert!(layer.resize(id, Size2D(new_size.width as f32, + new_size.height as f32), + page_window)); + ask_for_tiles(); + } + None => {} } - + } + + DeleteLayer(id) => { + match compositor_layer { + Some(ref mut layer) => { + assert!(layer.delete(id)); + ask_for_tiles(); + } + None => {} + } + } + + Paint(id, new_layer_buffer_set) => { debug!("osmain: received new frame"); - let quad; - match quadtree { - Some(ref mut q) => quad = q, - None => fail!("Compositor: given paint command with no quadtree initialized"), + match compositor_layer { + Some(ref mut layer) => { + assert!(layer.add_buffers(id, new_layer_buffer_set.get())); + recomposite = true; + } + None => { + fail!("Compositor: given paint command with no CompositorLayer initialized"); + } } - - let new_layer_buffer_set = new_layer_buffer_set.get(); - for new_layer_buffer_set.buffers.iter().advance |buffer| { - // FIXME: Don't copy the buffers here - quad.add_tile(buffer.screen_pos.origin.x, buffer.screen_pos.origin.y, - buffer.resolution, ~buffer.clone()); - } - - page_size = Size2D(new_size.width as f32, new_size.height as f32); - - build_layer_tree(quad); // TODO: Recycle the old buffers; send them back to the renderer to reuse if // it wishes. } @@ -431,56 +363,27 @@ impl CompositorTask { } MouseWindowEventClass(mouse_window_event) => { - let event: Event_; - let world_mouse_point = |layer_mouse_point: Point2D| { - layer_mouse_point + world_offset + let point = match mouse_window_event { + MouseWindowClickEvent(_, p) => Point2D(p.x / world_zoom, p.y / world_zoom), + MouseWindowMouseDownEvent(_, p) => Point2D(p.x / world_zoom, p.y / world_zoom), + MouseWindowMouseUpEvent(_, p) => Point2D(p.x / world_zoom, p.y / world_zoom), }; - match mouse_window_event { - MouseWindowClickEvent(button, layer_mouse_point) => { - event = ClickEvent(button, world_mouse_point(layer_mouse_point)); - } - MouseWindowMouseDownEvent(button, layer_mouse_point) => { - event = MouseDownEvent(button, world_mouse_point(layer_mouse_point)); - } - MouseWindowMouseUpEvent(button, layer_mouse_point) => { - event = MouseUpEvent(button, world_mouse_point(layer_mouse_point)); - } - } - match pipeline { - Some(ref pipeline) => pipeline.script_chan.send(SendEventMsg(pipeline.id.clone(), event)), - None => error!("Compositor: Recieved mouse event without initialized layout chan"), + for compositor_layer.iter().advance |layer| { + layer.send_mouse_event(mouse_window_event, point); } } - ScrollWindowEvent(delta) => { - // FIXME (Rust #2528): Can't use `-=`. - let world_offset_copy = world_offset; - world_offset = world_offset_copy - delta; - - // Clamp the world offset to the screen size. - let max_x = (page_size.width * world_zoom - window_size.width as f32).max(&0.0); - world_offset.x = world_offset.x.clamp(&0.0, &max_x).round(); - let max_y = (page_size.height * world_zoom - window_size.height as f32).max(&0.0); - world_offset.y = world_offset.y.clamp(&0.0, &max_y).round(); - - debug!("compositor: scrolled to %?", world_offset); - - - let mut scroll_transform = identity(); - - scroll_transform = scroll_transform.translate(window_size.width as f32 / 2f32 * local_zoom - world_offset.x, - window_size.height as f32 / 2f32 * local_zoom - world_offset.y, - 0.0); - scroll_transform = scroll_transform.scale(local_zoom, local_zoom, 1f32); - scroll_transform = scroll_transform.translate(window_size.width as f32 / -2f32, - window_size.height as f32 / -2f32, - 0.0); - - root_layer.common.set_transform(scroll_transform); - + ScrollWindowEvent(delta, cursor) => { + // TODO: modify delta to snap scroll to pixels. + let page_delta = Point2D(delta.x as f32 / world_zoom, delta.y as f32 / world_zoom); + let page_cursor: Point2D = Point2D(cursor.x as f32 / world_zoom, + cursor.y as f32 / world_zoom); + let page_window = Size2D(window_size.width as f32 / world_zoom, + window_size.height as f32 / world_zoom); + for compositor_layer.mut_iter().advance |layer| { + recomposite = layer.scroll(page_delta, page_cursor, page_window) || recomposite; + } ask_for_tiles(); - - recomposite = true; } ZoomWindowEvent(magnification) => { @@ -490,34 +393,19 @@ impl CompositorTask { // Determine zoom amount world_zoom = (world_zoom * magnification).max(&1.0); - local_zoom = local_zoom * world_zoom/old_world_zoom; - - // Update world offset - let corner_to_center_x = world_offset.x + window_size.width as f32 / 2f32; - let new_corner_to_center_x = corner_to_center_x * world_zoom / old_world_zoom; - world_offset.x = world_offset.x + new_corner_to_center_x - corner_to_center_x; - - let corner_to_center_y = world_offset.y + window_size.height as f32 / 2f32; - let new_corner_to_center_y = corner_to_center_y * world_zoom / old_world_zoom; - world_offset.y = world_offset.y + new_corner_to_center_y - corner_to_center_y; - - // Clamp to page bounds when zooming out - let max_x = (page_size.width * world_zoom - window_size.width as f32).max(&0.0); - world_offset.x = world_offset.x.clamp(&0.0, &max_x).round(); - let max_y = (page_size.height * world_zoom - window_size.height as f32).max(&0.0); - world_offset.y = world_offset.y.clamp(&0.0, &max_y).round(); - - // Apply transformations - let mut zoom_transform = identity(); - zoom_transform = zoom_transform.translate(window_size.width as f32 / 2f32 * local_zoom - world_offset.x, - window_size.height as f32 / 2f32 * local_zoom - world_offset.y, - 0.0); - zoom_transform = zoom_transform.scale(local_zoom, local_zoom, 1f32); - zoom_transform = zoom_transform.translate(window_size.width as f32 / -2f32, - window_size.height as f32 / -2f32, - 0.0); - root_layer.common.set_transform(zoom_transform); + root_layer.common.set_transform(identity().scale(world_zoom, world_zoom, 1f32)); + // Scroll as needed + let page_delta = Point2D(window_size.width as f32 * (1.0 / world_zoom - 1.0 / old_world_zoom) * 0.5, + window_size.height as f32 * (1.0 / world_zoom - 1.0 / old_world_zoom) * 0.5); + // TODO: modify delta to snap scroll to pixels. + let page_cursor = Point2D(-1f32, -1f32); // Make sure this hits the base layer + let page_window = Size2D(window_size.width as f32 / world_zoom, + window_size.height as f32 / world_zoom); + for compositor_layer.mut_iter().advance |layer| { + layer.scroll(page_delta, page_cursor, page_window); + } + recomposite = true; } diff --git a/src/components/main/compositing/quadtree.rs b/src/components/main/compositing/quadtree.rs index 58eec357a6b..7fc7a72657f 100644 --- a/src/components/main/compositing/quadtree.rs +++ b/src/components/main/compositing/quadtree.rs @@ -8,7 +8,7 @@ use geom::point::Point2D; use geom::size::Size2D; use geom::rect::Rect; -use std::uint::{div_ceil, next_power_of_two}; +use std::uint::{div_ceil, next_power_of_two, range}; use std::vec::build_sized; use std::util::replace; use gfx::render_task::BufferRequest; @@ -17,8 +17,17 @@ use servo_msg::compositor_msg::Tile; /// Parent to all quadtree nodes. Stores variables needed at all levels. All method calls /// at this level are in pixel coordinates. pub struct Quadtree { - root: QuadtreeNode, + // The root node of the quadtree + root: ~QuadtreeNode, + // The size of the layer in pixels. Tiles will be clipped to this size. + // Note that the underlying quadtree has a potentailly larger size, since it is rounded + // to the next highest power of two. + clip_size: Size2D, + // The maximum size of the tiles requested in pixels. Tiles requested will be + // of a size anywhere between half this value and this value. max_tile_size: uint, + // The maximum allowed total memory of tiles in the tree. If this limit is reached, tiles + // will be removed from the tree. Set this to None to prevent this behavior. max_mem: Option, } @@ -58,7 +67,7 @@ impl Quadtree { let size = power_of_two * tile_size; Quadtree { - root: QuadtreeNode { + root: ~QuadtreeNode { tile: None, origin: Point2D(0f32, 0f32), size: size as f32, @@ -66,6 +75,7 @@ impl Quadtree { render_flag: false, tile_mem: 0, }, + clip_size: Size2D(width, height), max_tile_size: tile_size, max_mem: max_mem, } @@ -76,13 +86,19 @@ impl Quadtree { self.max_tile_size } /// Get a tile at a given pixel position and scale. - pub fn get_tile<'r>(&'r self, x: uint, y: uint, scale: f32) -> &'r Option { + pub fn get_tile_pixel<'r>(&'r self, x: uint, y: uint, scale: f32) -> &'r Option { self.root.get_tile(x as f32 / scale, y as f32 / scale) } + + /// Get a tile at a given page position. + pub fn get_tile_page<'r>(&'r self, x: f32, y: f32) -> &'r Option { + self.root.get_tile(x, y) + } + /// Add a tile associtated with a given pixel position and scale. /// If the tile pushes the total memory over its maximum, tiles will be removed /// until total memory is below the maximum again. - pub fn add_tile(&mut self, x: uint, y: uint, scale: f32, tile: T) { + pub fn add_tile_pixel(&mut self, x: uint, y: uint, scale: f32, tile: T) { self.root.add_tile(x as f32 / scale, y as f32 / scale, tile, self.max_tile_size as f32 / scale); match self.max_mem { Some(max) => { @@ -97,36 +113,138 @@ impl Quadtree { None => {} } } - /// Get the tile rect in screen and page coordinates for a given pixel position - pub fn get_tile_rect(&mut self, x: uint, y: uint, scale: f32) -> BufferRequest { - self.root.get_tile_rect(x as f32 / scale, y as f32 / scale, scale, self.max_tile_size as f32 / scale) + + /// Add a tile associtated with a given page position. + /// If the tile pushes the total memory over its maximum, tiles will be removed + /// until total memory is below the maximum again. + pub fn add_tile_page(&mut self, x: f32, y: f32, scale: f32, tile: T) { + self.root.add_tile(x, y, tile, self.max_tile_size as f32 / scale); + match self.max_mem { + Some(max) => { + while self.root.tile_mem > max { + let r = self.root.remove_tile(x, y); + match r { + (Some(_), _, _) => {} + _ => fail!("Quadtree: No valid tiles to remove"), + } + } + } + None => {} + } } + + /// Get the tile rect in screen and page coordinates for a given pixel position + pub fn get_tile_rect_pixel(&mut self, x: uint, y: uint, scale: f32) -> BufferRequest { + self.root.get_tile_rect(x as f32 / scale, y as f32 / scale, + self.clip_size.width as f32, + self.clip_size.height as f32, + scale, self.max_tile_size as f32 / scale) + } + + /// Get the tile rect in screen and page coordinates for a given page position + pub fn get_tile_rect_page(&mut self, x: f32, y: f32, scale: f32) -> BufferRequest { + self.root.get_tile_rect(x, y, + self.clip_size.width as f32, + self.clip_size.height as f32, + scale, self.max_tile_size as f32 / scale) + } + /// Get all the tiles in the tree pub fn get_all_tiles<'r>(&'r self) -> ~[&'r T] { self.root.get_all_tiles() } + /// Ask a tile to be deleted from the quadtree. This tries to delete a tile that is far from the /// given point in pixel coordinates. - pub fn remove_tile(&mut self, x: uint, y: uint, scale: f32) -> T { + pub fn remove_tile_pixel(&mut self, x: uint, y: uint, scale: f32) -> T { let r = self.root.remove_tile(x as f32 / scale, y as f32 / scale); match r { (Some(tile), _, _) => tile, _ => fail!("Quadtree: No valid tiles to remove"), } } + + /// Ask a tile to be deleted from the quadtree. This tries to delete a tile that is far from the + /// given point in page coordinates. + pub fn remove_tile_page(&mut self, x: f32, y: f32) -> T { + let r = self.root.remove_tile(x, y); + match r { + (Some(tile), _, _) => tile, + _ => fail!("Quadtree: No valid tiles to remove"), + } + } + /// Given a window rect in pixel coordinates, this function returns a list of BufferRequests for tiles that /// need to be rendered. It also returns a boolean if the window needs to be redisplayed, i.e. if /// no tiles need to be rendered, but the display tree needs to be rebuilt. This can occur when the /// user zooms out and cached tiles need to be displayed on top of higher resolution tiles. /// When this happens, higher resolution tiles will be removed from the quadtree. - pub fn get_tile_rects(&mut self, window: Rect, scale: f32) -> (~[BufferRequest], bool) { + pub fn get_tile_rects_pixel(&mut self, window: Rect, scale: f32) -> (~[BufferRequest], bool) { let (ret, redisplay, _) = self.root.get_tile_rects( Rect(Point2D(window.origin.x as f32 / scale, window.origin.y as f32 / scale), Size2D(window.size.width as f32 / scale, window.size.height as f32 / scale)), + Size2D(self.clip_size.width as f32, self.clip_size.height as f32), scale, self.max_tile_size as f32 / scale); (ret, redisplay) } + /// Same function as above, using page coordinates for the window + pub fn get_tile_rects_page(&mut self, window: Rect, scale: f32) -> (~[BufferRequest], bool) { + let (ret, redisplay, _) = self.root.get_tile_rects( + window, + Size2D(self.clip_size.width as f32, self.clip_size.height as f32), + scale, self.max_tile_size as f32 / scale); + (ret, redisplay) + } + + + /// Resize the quadtree. This can add more space, changing the root node, or it can shrink, making + /// an internal node the new root. + /// TODO: return tiles after shrinking + pub fn resize(&mut self, width: uint, height: uint) { + self.clip_size = Size2D(width, height); + let longer = width.max(&height); + let new_num_tiles = div_ceil(longer, self.max_tile_size); + let new_size = next_power_of_two(new_num_tiles); + // difference here indicates the number of times the underlying size of the quadtree needs + // to be doubled or halved. It will recursively add a new root if it is positive, or + // recursivly make a child the new root if it is negative. + let difference = (new_size as f32 / self.root.size as f32).log2() as int; + if difference > 0 { // doubling + let difference = difference as uint; + for range(0, difference) |i| { + let new_root = ~QuadtreeNode { + tile: None, + origin: Point2D(0f32, 0f32), + size: new_size as f32 / ((difference - i - 1) as f32).exp2(), + quadrants: [None, None, None, None], + render_flag: false, + tile_mem: self.root.tile_mem, + }; + self.root.quadrants[TL as int] = Some(replace(&mut self.root, new_root)); + } + } else if difference < 0 { // halving + let difference = difference.abs() as uint; + for difference.times { + let remove = replace(&mut self.root.quadrants[TL as int], None); + match remove { + Some(child) => self.root = child, + None => { + self.root = ~QuadtreeNode { + tile: None, + origin: Point2D(0f32, 0f32), + size: new_size as f32, + quadrants: [None, None, None, None], + render_flag: false, + tile_mem: 0, + }; + break; + } + } + } + } + } + /// Generate html to visualize the tree. For debugging purposes only. pub fn get_html(&self) -> ~str { static HEADER: &'static str = ""; @@ -248,19 +366,23 @@ impl QuadtreeNode { } /// Get a tile rect in screen and page coords for a given position in page coords - fn get_tile_rect(&mut self, x: f32, y: f32, scale: f32, tile_size: f32) -> BufferRequest { + fn get_tile_rect(&mut self, x: f32, y: f32, clip_x: f32, clip_y: f32, scale: f32, + tile_size: f32) -> BufferRequest { if x >= self.origin.x + self.size || x < self.origin.x || y >= self.origin.y + self.size || y < self.origin.y { fail!("Quadtree: Tried to query a tile rect outside of range"); } if self.size <= tile_size { - let self_x = (self.origin.x * scale).ceil() as uint; - let self_y = (self.origin.y * scale).ceil() as uint; - let self_size = (self.size * scale).ceil() as uint; + let pix_x = (self.origin.x * scale).ceil() as uint; + let pix_y = (self.origin.y * scale).ceil() as uint; + let page_width = (clip_x - self.origin.x).min(&self.size); + let page_height = (clip_y - self.origin.y).min(&self.size); + let pix_width = (page_width * scale).ceil() as uint; + let pix_height = (page_height * scale).ceil() as uint; self.render_flag = true; - return BufferRequest(Rect(Point2D(self_x, self_y), Size2D(self_size, self_size)), - Rect(Point2D(self.origin.x, self.origin.y), Size2D(self.size, self.size))); + return BufferRequest(Rect(Point2D(pix_x, pix_y), Size2D(pix_width, pix_height)), + Rect(Point2D(self.origin.x, self.origin.y), Size2D(page_width, page_height))); } let quad = self.get_quadrant(x,y); @@ -276,11 +398,11 @@ impl QuadtreeNode { BL | BR => self.origin.y + new_size, }; let mut c = ~QuadtreeNode::new_child(new_x, new_y, new_size); - let result = c.get_tile_rect(x, y, scale, tile_size); + let result = c.get_tile_rect(x, y, clip_x, clip_y, scale, tile_size); self.quadrants[quad as int] = Some(c); result } - Some(ref mut child) => child.get_tile_rect(x, y, scale, tile_size), + Some(ref mut child) => child.get_tile_rect(x, y, clip_x, clip_y, scale, tile_size), } } @@ -357,7 +479,7 @@ impl QuadtreeNode { /// a redisplay boolean, and the difference in tile memory between the new and old quadtree nodes. /// NOTE: this method will sometimes modify the tree by deleting tiles. /// See the QuadTree function description for more details. - fn get_tile_rects(&mut self, window: Rect, scale: f32, tile_size: f32) -> + fn get_tile_rects(&mut self, window: Rect, clip: Size2D, scale: f32, tile_size: f32) -> (~[BufferRequest], bool, int) { let w_x = window.origin.x; @@ -368,13 +490,17 @@ impl QuadtreeNode { let s_y = self.origin.y; let s_size = self.size; - if w_x < s_x || w_x + w_width > s_x + s_size - || w_y < s_y || w_y + w_height > s_y + s_size { - println(fmt!("window: %?, %?, %?, %?; self: %?, %?, %?", - w_x, w_y, w_width, w_height, s_x, s_y, s_size)); - fail!("Quadtree: tried to query an invalid tile rect"); + // if window is outside of visible region, nothing to do + if w_x + w_width < s_x || w_x > s_x + s_size + || w_y + w_height < s_y || w_y > s_y + s_size + || w_x >= clip.width || w_y >= clip.height { + return (~[], false, 0); } + // clip window to visible region + let w_width = (clip.width - w_x).min(&w_width); + let w_height = (clip.height - w_y).min(&w_height); + if s_size <= tile_size { // We are the child return match self.tile { _ if self.render_flag => (~[], false, 0), @@ -397,7 +523,7 @@ impl QuadtreeNode { } (~[], redisplay, delta) } - _ => (~[self.get_tile_rect(s_x, s_y, scale, tile_size)], false, 0), + _ => (~[self.get_tile_rect(s_x, s_y, clip.width, clip.height, scale, tile_size)], false, 0), } } @@ -457,7 +583,7 @@ impl QuadtreeNode { }; let (c_ret, c_redisplay, c_delta) = match self.quadrants[*quad as int] { - Some(ref mut child) => child.get_tile_rects(new_window, scale, tile_size), + Some(ref mut child) => child.get_tile_rects(new_window, clip, scale, tile_size), None => { // Create new child let new_size = self.size / 2.0; @@ -470,9 +596,9 @@ impl QuadtreeNode { BL | BR => self.origin.y + new_size, }; let mut child = ~QuadtreeNode::new_child(new_x, new_y, new_size); - let (a, b, c) = child.get_tile_rects(new_window, scale, tile_size); + let ret = child.get_tile_rects(new_window, clip, scale, tile_size); self.quadrants[*quad as int] = Some(child); - (a, b, c) + ret } }; @@ -484,7 +610,6 @@ impl QuadtreeNode { (ret, redisplay, delta) } - /// Generate html to visualize the tree. /// This is really inefficient, but it's for testing only. fn get_html(&self) -> ~str { @@ -525,6 +650,35 @@ impl QuadtreeNode { } +#[test] +pub fn test_resize() { + struct T { + a: int, + } + + impl Tile for T { + fn get_mem(&self) -> uint { + 1 + } + + fn is_valid(&self, _: f32) -> bool { + true + } + } + + let mut q = Quadtree::new(6, 6, 1, None); + q.add_tile_pixel(0, 0, 1f32, T{a: 0}); + q.add_tile_pixel(5, 5, 1f32, T{a: 1}); + q.resize(8, 1); + assert!(q.root.size == 8.0); + q.resize(18, 1); + assert!(q.root.size == 32.0); + q.resize(8, 1); + assert!(q.root.size == 8.0); + q.resize(3, 1); + assert!(q.root.size == 4.0); + assert!(q.get_all_tiles().len() == 1); +} #[test] pub fn test() { @@ -543,26 +697,26 @@ pub fn test() { } let mut q = Quadtree::new(8, 8, 2, Some(4)); - q.add_tile(0, 0, 1f32, T{a: 0}); - q.add_tile(0, 0, 2f32, T{a: 1}); - q.add_tile(0, 0, 2f32, T{a: 2}); - q.add_tile(2, 0, 2f32, T{a: 3}); + q.add_tile_pixel(0, 0, 1f32, T{a: 0}); + q.add_tile_pixel(0, 0, 2f32, T{a: 1}); + q.add_tile_pixel(0, 0, 2f32, T{a: 2}); + q.add_tile_pixel(2, 0, 2f32, T{a: 3}); assert!(q.root.tile_mem == 3); assert!(q.get_all_tiles().len() == 3); - q.add_tile(0, 2, 2f32, T{a: 4}); - q.add_tile(2, 2, 2f32, T{a: 5}); + q.add_tile_pixel(0, 2, 2f32, T{a: 4}); + q.add_tile_pixel(2, 2, 2f32, T{a: 5}); assert!(q.root.tile_mem == 4); - let (request, _) = q.get_tile_rects(Rect(Point2D(0, 0), Size2D(2, 2)), 2f32); + let (request, _) = q.get_tile_rects_pixel(Rect(Point2D(0, 0), Size2D(2, 2)), 2f32); assert!(request.is_empty()); - let (request, _) = q.get_tile_rects(Rect(Point2D(0, 0), Size2D(2, 2)), 1.9); + let (request, _) = q.get_tile_rects_pixel(Rect(Point2D(0, 0), Size2D(2, 2)), 1.9); assert!(request.is_empty()); - let (request, _) = q.get_tile_rects(Rect(Point2D(0, 0), Size2D(2, 2)), 1f32); + let (request, _) = q.get_tile_rects_pixel(Rect(Point2D(0, 0), Size2D(2, 2)), 1f32); assert!(request.len() == 4); - q.add_tile(0, 0, 0.5, T{a: 6}); - q.add_tile(0, 0, 1f32, T{a: 7}); - let (_, redisplay) = q.get_tile_rects(Rect(Point2D(0, 0), Size2D(2, 2)), 0.5); + q.add_tile_pixel(0, 0, 0.5, T{a: 6}); + q.add_tile_pixel(0, 0, 1f32, T{a: 7}); + let (_, redisplay) = q.get_tile_rects_pixel(Rect(Point2D(0, 0), Size2D(2, 2)), 0.5); assert!(redisplay); assert!(q.root.tile_mem == 1); } diff --git a/src/components/main/platform/common/glfw_windowing.rs b/src/components/main/platform/common/glfw_windowing.rs index 10be6d5b2e8..327d7423540 100644 --- a/src/components/main/platform/common/glfw_windowing.rs +++ b/src/components/main/platform/common/glfw_windowing.rs @@ -89,15 +89,29 @@ impl WindowMethods for Window { } do window.glfw_window.set_mouse_button_callback |win, button, action, _mods| { let (x, y) = win.get_cursor_pos(); + //handle hidpi displays, since GLFW returns non-hi-def coordinates. + let (backing_size, _) = win.get_framebuffer_size(); + let (window_size, _) = win.get_size(); + let hidpi = (backing_size as f32) / (window_size as f32); + let x = x as f32 * hidpi; + let y = y as f32 * hidpi; if button < 3 { window.handle_mouse(button, action, x as i32, y as i32); } } - do window.glfw_window.set_scroll_callback |_win, x_offset, y_offset| { + do window.glfw_window.set_scroll_callback |win, x_offset, y_offset| { let dx = (x_offset as f32) * 30.0; let dy = (y_offset as f32) * 30.0; - event_queue.push(ScrollWindowEvent(Point2D(dx, dy))); + let (x, y) = win.get_cursor_pos(); + //handle hidpi displays, since GLFW returns non-hi-def coordinates. + let (backing_size, _) = win.get_framebuffer_size(); + let (window_size, _) = win.get_size(); + let hidpi = (backing_size as f32) / (window_size as f32); + let x = x as f32 * hidpi; + let y = y as f32 * hidpi; + + event_queue.push(ScrollWindowEvent(Point2D(dx, dy), Point2D(x as i32, y as i32))); } window @@ -148,6 +162,12 @@ impl WindowMethods for Window { self.render_state = render_state; self.update_window_title() } + + fn hidpi_factor(@mut self) -> f32 { + let (backing_size, _) = self.glfw_window.get_framebuffer_size(); + let (window_size, _) = self.glfw_window.get_size(); + (backing_size as f32) / (window_size as f32) + } } impl Window { diff --git a/src/components/main/windowing.rs b/src/components/main/windowing.rs index 00439009d34..f4fbca5d58c 100644 --- a/src/components/main/windowing.rs +++ b/src/components/main/windowing.rs @@ -32,8 +32,8 @@ pub enum WindowEvent { LoadUrlWindowEvent(~str), /// Sent when a mouse hit test is to be performed. MouseWindowEventClass(MouseWindowEvent), - /// Sent when the user scrolls. - ScrollWindowEvent(Point2D), + /// Sent when the user scrolls. Includes the current cursor position. + ScrollWindowEvent(Point2D, Point2D), /// Sent when the user zooms. ZoomWindowEvent(f32), /// Sent when the user uses chrome navigation (i.e. backspace or shift-backspace). @@ -64,5 +64,8 @@ pub trait WindowMethods { pub fn set_ready_state(@mut self, ready_state: ReadyState); /// Sets the render state of the current page. pub fn set_render_state(@mut self, render_state: RenderState); + + /// Returns the hidpi factor of the monitor. + pub fn hidpi_factor(@mut self) -> f32; } diff --git a/src/components/msg/compositor_msg.rs b/src/components/msg/compositor_msg.rs index 5da0ddf204f..80f0c0703d3 100644 --- a/src/components/msg/compositor_msg.rs +++ b/src/components/msg/compositor_msg.rs @@ -58,10 +58,10 @@ pub enum ReadyState { /// submit them to be drawn to the display. pub trait RenderListener { fn get_gl_context(&self) -> AzGLContext; - fn new_layer(&self, Size2D, uint); - fn resize_layer(&self, Size2D); - fn delete_layer(&self); - fn paint(&self, id: PipelineId, layer_buffer_set: arc::ARC, new_size: Size2D); + fn new_layer(&self, PipelineId, Size2D); + fn resize_layer(&self, PipelineId, Size2D); + fn delete_layer(&self, PipelineId); + fn paint(&self, id: PipelineId, layer_buffer_set: arc::ARC); fn set_render_state(&self, render_state: RenderState); } diff --git a/src/support/geom/rust-geom b/src/support/geom/rust-geom index a23cb0bc99a..5b9a447946b 160000 --- a/src/support/geom/rust-geom +++ b/src/support/geom/rust-geom @@ -1 +1 @@ -Subproject commit a23cb0bc99a933efa9104c2471ccd14638744ea4 +Subproject commit 5b9a447946b736802a0791eb1c24687849ad3984 diff --git a/src/support/layers/rust-layers b/src/support/layers/rust-layers index a8843ea0842..177277d4aae 160000 --- a/src/support/layers/rust-layers +++ b/src/support/layers/rust-layers @@ -1 +1 @@ -Subproject commit a8843ea084262773c31916e3a52c6dacea135153 +Subproject commit 177277d4aaeb56913f2eb04670679cae078cf70b diff --git a/src/support/opengles/rust-opengles b/src/support/opengles/rust-opengles index b5cb5593f49..e83cca0e287 160000 --- a/src/support/opengles/rust-opengles +++ b/src/support/opengles/rust-opengles @@ -1 +1 @@ -Subproject commit b5cb5593f4938a578eebda56c905bbaff33efe66 +Subproject commit e83cca0e287db58a27ee77947a8d4dc3617a6ed0