auto merge of #675 : eschweic/servo/multi-layer, r=metajack

This refactors the compositor and adds initial support for multiple compositor layers being displayed at once. A new module, `compositor_layer.rs`, does most of the heavy lifting that was once in `mod.rs`.

--- clipped so bors is happy ---
This commit is contained in:
bors-servo 2013-08-08 10:40:14 -07:00
commit 0d46164b43
10 changed files with 741 additions and 290 deletions

View file

@ -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<C: RenderListener + Send> RenderTask<C> {
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<C: RenderListener + Send> RenderTask<C> {
}
}
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) => {
@ -234,7 +234,7 @@ impl<C: RenderListener + Send> RenderTask<C> {
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));

View file

@ -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<Size2D<f32>>,
/// 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<f32>,
/// 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<uint>),
}
impl CompositorLayer {
/// Creates a new CompositorLayer without a page size that is initially hidden.
pub fn new(pipeline: Pipeline, page_size: Option<Size2D<f32>>, tile_size: uint, max_mem: Option<uint>)
-> 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<f32>, cursor: Point2D<f32>, window_size: Size2D<f32>) -> 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<f32>) {
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<f32>, 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<f32>) -> 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<f32>, window_size: Size2D<f32>) -> 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<Size2D<f32>>, tile_size: uint,
max_mem: Option<uint>, clipping_rect: Rect<f32>) {
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,
});
}
}

View file

@ -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<LayerBufferSet>, new_size: Size2D<uint>) {
self.chan.send(Paint(id, layer_buffer_set, new_size))
fn paint(&self, id: PipelineId, layer_buffer_set: arc::ARC<LayerBufferSet>) {
self.chan.send(Paint(id, layer_buffer_set))
}
fn new_layer(&self, page_size: Size2D<uint>, tile_size: uint) {
self.chan.send(NewLayer(page_size, tile_size))
fn new_layer(&self, id: PipelineId, page_size: Size2D<uint>) {
self.chan.send(NewLayer(id, page_size))
}
fn resize_layer(&self, page_size: Size2D<uint>) {
self.chan.send(ResizeLayer(page_size))
fn resize_layer(&self, id: PipelineId, page_size: Size2D<uint>) {
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<AzGLContext>),
// 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>, uint),
/// Alerts the compositor that the current layer has changed size.
ResizeLayer(Size2D<uint>),
/// Alerts the compositor that the current layer has been deleted.
DeleteLayer,
NewLayer(PipelineId, Size2D<uint>),
/// Alerts the compositor that the specified layer has changed size.
ResizeLayer(PipelineId, Size2D<uint>),
/// 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<LayerBufferSet>, Size2D<uint>),
Paint(PipelineId, arc::ARC<LayerBufferSet>),
/// 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<Pipeline> = None;
// Quadtree for this layer
// FIXME: This should be one-per-layer
let mut quadtree: Option<Quadtree<~LayerBuffer>> = 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;
// 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<Pipeline> = None;
// 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);
}
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<CompositorLayer> = 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)));
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));
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();
}
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
}
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<f32>| {
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<f32> = 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,33 +393,18 @@ impl CompositorTask {
// Determine zoom amount
world_zoom = (world_zoom * magnification).max(&1.0);
local_zoom = local_zoom * world_zoom/old_world_zoom;
root_layer.common.set_transform(identity().scale(world_zoom, world_zoom, 1f32));
// 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);
// 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;
}

View file

@ -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<T> {
root: QuadtreeNode<T>,
// The root node of the quadtree
root: ~QuadtreeNode<T>,
// 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<uint>,
// 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<uint>,
}
@ -58,7 +67,7 @@ impl<T: Tile> Quadtree<T> {
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<T: Tile> Quadtree<T> {
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<T: Tile> Quadtree<T> {
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<T> {
pub fn get_tile_pixel<'r>(&'r self, x: uint, y: uint, scale: f32) -> &'r Option<T> {
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<T> {
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<T: Tile> Quadtree<T> {
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<int>, scale: f32) -> (~[BufferRequest], bool) {
pub fn get_tile_rects_pixel(&mut self, window: Rect<int>, 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<f32>, 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 = "<!DOCTYPE html><html>";
@ -248,19 +366,23 @@ impl<T: Tile> QuadtreeNode<T> {
}
/// 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<T: Tile> QuadtreeNode<T> {
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<T: Tile> QuadtreeNode<T> {
/// 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<f32>, scale: f32, tile_size: f32) ->
fn get_tile_rects(&mut self, window: Rect<f32>, clip: Size2D<f32>, scale: f32, tile_size: f32) ->
(~[BufferRequest], bool, int) {
let w_x = window.origin.x;
@ -368,13 +490,17 @@ impl<T: Tile> QuadtreeNode<T> {
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<T: Tile> QuadtreeNode<T> {
}
(~[], 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<T: Tile> QuadtreeNode<T> {
};
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<T: Tile> QuadtreeNode<T> {
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<T: Tile> QuadtreeNode<T> {
(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<T: Tile> QuadtreeNode<T> {
}
#[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);
}

View file

@ -89,15 +89,29 @@ impl WindowMethods<Application> 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<Application> 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 {

View file

@ -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<f32>),
/// Sent when the user scrolls. Includes the current cursor position.
ScrollWindowEvent(Point2D<f32>, Point2D<i32>),
/// 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<A> {
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;
}

View file

@ -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>, uint);
fn resize_layer(&self, Size2D<uint>);
fn delete_layer(&self);
fn paint(&self, id: PipelineId, layer_buffer_set: arc::ARC<LayerBufferSet>, new_size: Size2D<uint>);
fn new_layer(&self, PipelineId, Size2D<uint>);
fn resize_layer(&self, PipelineId, Size2D<uint>);
fn delete_layer(&self, PipelineId);
fn paint(&self, id: PipelineId, layer_buffer_set: arc::ARC<LayerBufferSet>);
fn set_render_state(&self, render_state: RenderState);
}

@ -1 +1 @@
Subproject commit a23cb0bc99a933efa9104c2471ccd14638744ea4
Subproject commit 5b9a447946b736802a0791eb1c24687849ad3984

@ -1 +1 @@
Subproject commit a8843ea084262773c31916e3a52c6dacea135153
Subproject commit 177277d4aaeb56913f2eb04670679cae078cf70b

@ -1 +1 @@
Subproject commit b5cb5593f4938a578eebda56c905bbaff33efe66
Subproject commit e83cca0e287db58a27ee77947a8d4dc3617a6ed0