Refactor compositor to use messages rather than callbacks.

This commit is contained in:
eschweic 2013-07-16 17:29:56 -07:00
parent c17ede3716
commit d695b2d2bc
3 changed files with 285 additions and 357 deletions

View file

@ -6,9 +6,11 @@ use platform::{Application, Window};
use script::dom::event::{Event, ClickEvent, MouseDownEvent, MouseUpEvent, ResizeEvent};
use script::script_task::{LoadMsg, NavigateMsg, SendEventMsg};
use script::layout_interface::{LayoutChan, RouteScriptMsg};
use windowing::{ApplicationMethods, WindowMethods, WindowMouseEvent, WindowClickEvent};
use windowing::{WindowMouseDownEvent, WindowMouseUpEvent};
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::{ReadyState, ScriptListener};
@ -216,38 +218,39 @@ impl CompositorTask {
let context = rendergl::init_render_context();
let root_layer = @mut ContainerLayer();
let window_size = window.size();
let scene = @mut Scene(ContainerLayerKind(root_layer), window_size, identity());
let done = @mut false;
let recomposite = @mut false;
let mut scene = Scene(ContainerLayerKind(root_layer), window_size, identity());
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 world_offset = @mut Point2D(0f32, 0f32);
let page_size = @mut Size2D(0f32, 0f32);
let window_size = @mut Size2D(window_size.width as int,
window_size.height as int);
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 world_zoom = @mut 1f32;
let mut world_zoom = 1f32;
// Keeps track of local zoom factor. Reset to 1 after a rerender event.
let local_zoom = @mut 1f32;
let mut local_zoom = 1f32;
// Channel to the current renderer.
// FIXME: This probably shouldn't be stored like this.
let render_chan: @mut Option<RenderChan> = @mut None;
let pipeline_id: @mut Option<uint> = @mut None;
let mut render_chan: Option<RenderChan> = None;
let mut pipeline_id: Option<uint> = None;
let mut layout_chan: Option<LayoutChan> = None;
// Quadtree for this layer
// FIXME: This should be one-per-layer
let quadtree: @mut Option<Quadtree<~LayerBuffer>> = @mut None;
let mut quadtree: Option<Quadtree<~LayerBuffer>> = None;
// Keeps track of if we have performed a zoom event and how recently.
let zoom_action = @mut false;
let zoom_time = @mut 0f;
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>| {
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;
@ -292,31 +295,31 @@ impl CompositorTask {
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);
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;
local_zoom = 1f32;
root_layer.common.set_transform(identity().translate(-world_offset.x,
-world_offset.y,
0.0));
*recomposite = true;
recomposite = true;
};
let ask_for_tiles: @fn() = || {
match *quadtree {
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);
window_size), world_zoom);
if !tile_request.is_empty() {
match *render_chan {
match render_chan {
Some(ref chan) => {
chan.send(ReRenderMsg(tile_request, *world_zoom));
chan.send(ReRenderMsg(tile_request, world_zoom));
}
_ => {
println("Warning: Compositor: Cannot send tile request, no render chan initialized");
@ -331,68 +334,12 @@ impl CompositorTask {
}
}
};
let update_layout_callbacks: @fn(LayoutChan) = |layout_chan: LayoutChan| {
let layout_chan_clone = layout_chan.clone();
do window.set_navigation_callback |direction| {
let direction = match direction {
windowing::Forward => constellation_msg::Forward,
windowing::Back => constellation_msg::Back,
};
layout_chan_clone.send(RouteScriptMsg(NavigateMsg(direction)));
}
let layout_chan_clone = layout_chan.clone();
// Hook the windowing system's resize callback up to the resize rate limiter.
do window.set_resize_callback |width, height| {
let new_size = Size2D(width as int, height as int);
if *window_size != new_size {
debug!("osmain: window resized to %ux%u", width, height);
*window_size = new_size;
layout_chan_clone.send(RouteScriptMsg(SendEventMsg(ResizeEvent(width, height))));
} else {
debug!("osmain: dropping window resize since size is still %ux%u", width, height);
}
}
let layout_chan_clone = layout_chan.clone();
// When the user enters a new URL, load it.
do window.set_load_url_callback |url_string| {
debug!("osmain: loading URL `%s`", url_string);
layout_chan_clone.send(RouteScriptMsg(LoadMsg(url::make_url(url_string.to_str(), None))));
}
let layout_chan_clone = layout_chan.clone();
// When the user triggers a mouse event, perform appropriate hit testing
do window.set_mouse_callback |window_mouse_event: WindowMouseEvent| {
let event: Event;
let world_mouse_point = |layer_mouse_point: Point2D<f32>| {
layer_mouse_point + *world_offset
};
match window_mouse_event {
WindowClickEvent(button, layer_mouse_point) => {
event = ClickEvent(button, world_mouse_point(layer_mouse_point));
}
WindowMouseDownEvent(button, layer_mouse_point) => {
event = MouseDownEvent(button, world_mouse_point(layer_mouse_point));
}
WindowMouseUpEvent(button, layer_mouse_point) => {
event = MouseUpEvent(button, world_mouse_point(layer_mouse_point));
}
}
layout_chan_clone.send(RouteScriptMsg(SendEventMsg(event)));
}
};
let check_for_messages: @fn(&Port<Msg>) = |port: &Port<Msg>| {
let check_for_messages: &fn(&Port<Msg>) = |port: &Port<Msg>| {
// Handle messages
while port.peek() {
match port.recv() {
Exit => *done = true,
Exit => done = true,
ChangeReadyState(ready_state) => window.set_ready_state(ready_state),
ChangeRenderState(render_state) => window.set_render_state(render_state),
@ -401,9 +348,9 @@ impl CompositorTask {
new_render_chan,
new_pipeline_id,
response_chan) => {
update_layout_callbacks(new_layout_chan);
*render_chan = Some(new_render_chan);
*pipeline_id = Some(new_pipeline_id);
layout_chan = Some(new_layout_chan);
render_chan = Some(new_render_chan);
pipeline_id = Some(new_pipeline_id);
response_chan.send(CompositorAck(new_pipeline_id));
}
@ -413,17 +360,17 @@ 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)),
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();
}
ResizeLayer(new_size) => {
*page_size = Size2D(new_size.width as f32, new_size.height as f32);
page_size = Size2D(new_size.width as f32, new_size.height as f32);
// TODO: update quadtree, ask for tiles
}
DeleteLayer => {
@ -431,16 +378,15 @@ impl CompositorTask {
}
Paint(id, new_layer_buffer_set, new_size) => {
match *pipeline_id {
match pipeline_id {
Some(pipeline_id) => if id != pipeline_id { loop; },
None => { loop; },
}
debug!("osmain: received new frame");
let quad;
match *quadtree {
match quadtree {
Some(ref mut q) => quad = q,
None => fail!("Compositor: given paint command with no quadtree initialized"),
}
@ -452,10 +398,9 @@ impl CompositorTask {
buffer.resolution, ~buffer.clone());
}
*page_size = Size2D(new_size.width as f32, new_size.height as f32);
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.
}
@ -463,6 +408,147 @@ impl CompositorTask {
}
};
let check_for_window_messages: &fn(WindowEvent) = |event| {
match event {
IdleWindowEvent => {}
ResizeWindowEvent(width, height) => {
let new_size = Size2D(width as int, height as int);
if window_size != new_size {
debug!("osmain: window resized to %ux%u", width, height);
window_size = new_size;
match layout_chan {
Some(ref chan) => chan.send(RouteScriptMsg(SendEventMsg(ResizeEvent(width, height)))),
None => error!("Compositor: Recieved resize event without initialized layout chan"),
}
} else {
debug!("osmain: dropping window resize since size is still %ux%u", width, height);
}
}
LoadUrlWindowEvent(url_string) => {
debug!("osmain: loading URL `%s`", url_string);
match layout_chan {
Some(ref chan) => chan.send(RouteScriptMsg(LoadMsg(url::make_url(url_string.to_str(), None)))),
None => error!("Compositor: Recieved loadurl event without initialized layout chan"),
}
}
MouseWindowEventClass(mouse_window_event) => {
let event: Event;
let world_mouse_point = |layer_mouse_point: Point2D<f32>| {
layer_mouse_point + world_offset
};
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 layout_chan {
Some(ref chan) => chan.send(RouteScriptMsg(SendEventMsg(event))),
None => error!("Compositor: Recieved mouse event without initialized layout chan"),
}
}
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);
ask_for_tiles();
recomposite = true;
}
ZoomWindowEvent(magnification) => {
zoom_action = true;
zoom_time = precise_time_s();
let old_world_zoom = world_zoom;
// 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);
recomposite = true;
}
NavigationWindowEvent(direction) => {
let direction = match direction {
windowing::Forward => constellation_msg::Forward,
windowing::Back => constellation_msg::Back,
};
match layout_chan {
Some(ref chan) => chan.send(RouteScriptMsg(NavigateMsg(direction))),
None => error!("Compositor: Recieved navigation event without initialized layout chan"),
}
}
FinishedWindowEvent => {
if self.opts.exit_after_load {
done = true;
}
}
QuitWindowEvent => {
done = true;
}
}
};
let profiler_chan = self.profiler_chan.clone();
let write_png = self.opts.output_file.is_some();
let exit = self.opts.exit_after_load;
@ -473,7 +559,7 @@ impl CompositorTask {
scene.size = window.size();
// Render the scene.
rendergl::render_scene(context, scene);
rendergl::render_scene(context, &scene);
}
// Render to PNG. We must read from the back buffer (ie, before
@ -504,105 +590,32 @@ impl CompositorTask {
let res = png::store_png(&img, &path);
assert!(res.is_ok());
*done = true;
done = true;
}
window.present();
if exit { *done = true; }
if exit { done = true; }
};
// When the user scrolls, move the layer around.
do window.set_scroll_callback |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);
ask_for_tiles();
*recomposite = true;
}
// When the user pinch-zooms, scale the layer
do window.set_zoom_callback |magnification| {
*zoom_action = true;
*zoom_time = precise_time_s();
let old_world_zoom = *world_zoom;
// 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);
*recomposite = true;
}
// Enter the main event loop.
while !*done {
while !done {
// Check for new messages coming from the rendering task.
check_for_messages(&self.port);
// Check for messages coming from the windowing system.
if window.check_loop() {
*done = true;
}
check_for_window_messages(window.recv());
if *recomposite {
*recomposite = false;
if recomposite {
recomposite = false;
composite();
}
timer::sleep(&uv_global_loop::get(), 10);
// If a pinch-zoom happened recently, ask for tiles at the new resolution
if *zoom_action && precise_time_s() - *zoom_time > 0.3 {
*zoom_action = false;
if zoom_action && precise_time_s() - zoom_time > 0.3 {
zoom_action = false;
ask_for_tiles();
}

View file

@ -4,9 +4,11 @@
//! A windowing implementation using GLFW.
use windowing::{ApplicationMethods, LoadUrlCallback, MouseCallback, FinishedCallback};
use windowing::{ResizeCallback, ScrollCallback, WindowMethods, WindowMouseEvent, WindowClickEvent};
use windowing::{WindowMouseDownEvent, WindowMouseUpEvent, ZoomCallback, Forward, Back, NavigationCallback};
use windowing::{ApplicationMethods, WindowEvent, WindowMethods};
use windowing::{IdleWindowEvent, ResizeWindowEvent, LoadUrlWindowEvent, MouseWindowEventClass};
use windowing::{ScrollWindowEvent, ZoomWindowEvent, NavigationWindowEvent, FinishedWindowEvent};
use windowing::{QuitWindowEvent, MouseWindowClickEvent, MouseWindowMouseDownEvent, MouseWindowMouseUpEvent};
use windowing::{Forward, Back};
use alert::{Alert, AlertMethods};
use std::libc::c_int;
@ -39,13 +41,7 @@ impl Drop for Application {
pub struct Window {
glfw_window: glfw::Window,
resize_callback: Option<ResizeCallback>,
load_url_callback: Option<LoadUrlCallback>,
mouse_callback: Option<MouseCallback>,
scroll_callback: Option<ScrollCallback>,
zoom_callback: Option<ZoomCallback>,
navigation_callback: Option<NavigationCallback>,
finished_callback: Option<FinishedCallback>,
event_queue: @mut ~[WindowEvent],
drag_origin: Point2D<c_int>,
@ -59,7 +55,7 @@ pub struct Window {
impl WindowMethods<Application> for Window {
/// Creates a new window.
pub fn new(_: &Application) -> @mut Window {
fn new(_: &Application) -> @mut Window {
// Create the GLFW window.
let glfw_window = glfw::Window::create(800, 600, "Servo", glfw::Windowed).unwrap();
glfw_window.make_context_current();
@ -68,13 +64,7 @@ impl WindowMethods<Application> for Window {
let window = @mut Window {
glfw_window: glfw_window,
resize_callback: None,
load_url_callback: None,
mouse_callback: None,
scroll_callback: None,
zoom_callback: None,
navigation_callback: None,
finished_callback: None,
event_queue: @mut ~[],
drag_origin: Point2D(0 as c_int, 0),
@ -86,12 +76,11 @@ impl WindowMethods<Application> for Window {
throbber_frame: 0,
};
let event_queue = window.event_queue;
// Register event handlers.
do window.glfw_window.set_framebuffer_size_callback |_win, width, height| {
match window.resize_callback {
None => {}
Some(callback) => callback(width as uint, height as uint),
}
event_queue.push(ResizeWindowEvent(width as uint, height as uint))
}
do window.glfw_window.set_key_callback |_win, key, _scancode, action, mods| {
if action == glfw::PRESS {
@ -108,81 +97,52 @@ impl WindowMethods<Application> for Window {
let dx = (x_offset as f32) * 30.0;
let dy = (y_offset as f32) * 30.0;
window.handle_scroll(Point2D(dx, dy));
event_queue.push(ScrollWindowEvent(Point2D(dx, dy)));
}
window
}
/// Returns the size of the window.
pub fn size(&self) -> Size2D<f32> {
fn size(&self) -> Size2D<f32> {
let (width, height) = self.glfw_window.get_framebuffer_size();
Size2D(width as f32, height as f32)
}
/// Presents the window to the screen (perhaps by page flipping).
pub fn present(&mut self) {
fn present(&mut self) {
self.glfw_window.swap_buffers();
}
/// Registers a callback to run when a resize event occurs.
pub fn set_resize_callback(&mut self, new_resize_callback: ResizeCallback) {
self.resize_callback = Some(new_resize_callback)
}
/// Registers a callback to be run when a new URL is to be loaded.
pub fn set_load_url_callback(&mut self, new_load_url_callback: LoadUrlCallback) {
self.load_url_callback = Some(new_load_url_callback)
}
/// Registers a callback to be run when a mouse event occurs.
pub fn set_mouse_callback(&mut self, new_mouse_callback: MouseCallback) {
self.mouse_callback = Some(new_mouse_callback)
}
/// Registers a callback to be run when the user scrolls.
pub fn set_scroll_callback(&mut self, new_scroll_callback: ScrollCallback) {
self.scroll_callback = Some(new_scroll_callback)
}
/// Registers a zoom to be run when the user zooms.
pub fn set_zoom_callback(&mut self, new_zoom_callback: ZoomCallback) {
self.zoom_callback = Some(new_zoom_callback)
}
/// Registers a callback to be run when backspace or shift-backspace is pressed.
pub fn set_navigation_callback(&mut self, new_navigation_callback: NavigationCallback) {
self.navigation_callback = Some(new_navigation_callback)
}
pub fn set_finished_callback(&mut self, new_finished_callback: FinishedCallback) {
self.finished_callback = Some(new_finished_callback)
}
/// Spins the event loop.
pub fn check_loop(@mut self) -> bool {
fn recv(@mut self) -> WindowEvent {
if !self.event_queue.is_empty() {
return self.event_queue.shift()
}
glfw::poll_events();
self.throbber_frame = (self.throbber_frame + 1) % (THROBBER.len() as u8);
self.update_window_title();
self.glfw_window.should_close()
if self.glfw_window.should_close() {
QuitWindowEvent
} else if !self.event_queue.is_empty() {
self.event_queue.shift()
} else {
IdleWindowEvent
}
}
/// Sets the ready state.
pub fn set_ready_state(@mut self, ready_state: ReadyState) {
fn set_ready_state(@mut self, ready_state: ReadyState) {
self.ready_state = ready_state;
self.update_window_title()
}
/// Sets the render state.
pub fn set_render_state(@mut self, render_state: RenderState) {
fn set_render_state(@mut self, render_state: RenderState) {
if self.ready_state == FinishedLoading &&
self.render_state == RenderingRenderState &&
render_state == IdleRenderState {
// page loaded
for self.finished_callback.iter().advance |&callback| {
callback();
}
self.event_queue.push(FinishedWindowEvent);
}
self.render_state = render_state;
@ -221,24 +181,16 @@ impl Window {
glfw::KEY_ESCAPE => self.glfw_window.set_should_close(true),
glfw::KEY_L if mods & glfw::MOD_CONTROL != 0 => self.load_url(), // Ctrl+L
glfw::KEY_EQUAL if mods & glfw::MOD_CONTROL != 0 => { // Ctrl-+
for self.zoom_callback.iter().advance |&callback| {
callback(1.1);
}
self.event_queue.push(ZoomWindowEvent(1.1));
}
glfw::KEY_MINUS if mods & glfw::MOD_CONTROL != 0 => { // Ctrl--
for self.zoom_callback.iter().advance |&callback| {
callback(0.90909090909);
}
self.event_queue.push(ZoomWindowEvent(0.90909090909));
}
glfw::KEY_BACKSPACE if mods & glfw::MOD_SHIFT != 0 => { // Shift-Backspace
for self.navigation_callback.iter().advance |&callback| {
callback(Forward);
}
self.event_queue.push(NavigationWindowEvent(Forward));
}
glfw::KEY_BACKSPACE => { // Backspace
for self.navigation_callback.iter().advance |&callback| {
callback(Back);
}
self.event_queue.push(NavigationWindowEvent(Back));
}
_ => {}
}
@ -248,67 +200,40 @@ impl Window {
fn handle_mouse(&self, button: c_int, action: c_int, x: c_int, y: c_int) {
// FIXME(tkuehn): max pixel dist should be based on pixel density
let max_pixel_dist = 10f;
match self.mouse_callback {
None => {}
Some(callback) => {
let event: WindowMouseEvent;
match action {
glfw::PRESS => {
event = WindowMouseDownEvent(button as uint, Point2D(x as f32, y as f32));
*self.mouse_down_point = Point2D(x, y);
*self.mouse_down_button = button;
}
glfw::RELEASE => {
event = WindowMouseUpEvent(button as uint, Point2D(x as f32, y as f32));
if *self.mouse_down_button == button {
let pixel_dist = *self.mouse_down_point - Point2D(x, y);
let pixel_dist = ((pixel_dist.x * pixel_dist.x +
pixel_dist.y * pixel_dist.y) as float).sqrt();
if pixel_dist < max_pixel_dist {
let click_event = WindowClickEvent(button as uint,
Point2D(x as f32, y as f32));
callback(click_event);
}
}
}
_ => fail!("I cannot recognize the type of mouse action that occured. :-(")
};
callback(event);
let event = match action {
glfw::PRESS => {
*self.mouse_down_point = Point2D(x, y);
*self.mouse_down_button = button;
MouseWindowMouseDownEvent(button as uint, Point2D(x as f32, y as f32))
}
}
}
/// Helper function to handle a scroll.
fn handle_scroll(&mut self, delta: Point2D<f32>) {
match self.scroll_callback {
None => {}
Some(callback) => callback(delta),
}
}
/// Helper function to handle a zoom.
fn handle_zoom(&mut self, magnification: f32) {
match self.zoom_callback {
None => {}
Some(callback) => callback(magnification),
}
glfw::RELEASE => {
if *self.mouse_down_button == button {
let pixel_dist = *self.mouse_down_point - Point2D(x, y);
let pixel_dist = ((pixel_dist.x * pixel_dist.x +
pixel_dist.y * pixel_dist.y) as float).sqrt();
if pixel_dist < max_pixel_dist {
let click_event = MouseWindowClickEvent(button as uint,
Point2D(x as f32, y as f32));
self.event_queue.push(MouseWindowEventClass(click_event));
}
}
MouseWindowMouseUpEvent(button as uint, Point2D(x as f32, y as f32))
}
_ => fail!("I cannot recognize the type of mouse action that occured. :-(")
};
self.event_queue.push(MouseWindowEventClass(event));
}
/// Helper function to pop up an alert box prompting the user to load a URL.
fn load_url(&self) {
match self.load_url_callback {
None => error!("no URL callback registered, doing nothing"),
Some(callback) => {
let mut alert: Alert = AlertMethods::new("Navigate to:");
alert.add_prompt();
alert.run();
let value = alert.prompt_value();
if "" == value { // To avoid crashing on Linux.
callback("http://purple.com/")
} else {
callback(value)
}
}
let mut alert: Alert = AlertMethods::new("Navigate to:");
alert.add_prompt();
alert.run();
let value = alert.prompt_value();
if "" == value { // To avoid crashing on Linux.
self.event_queue.push(LoadUrlWindowEvent(~"http://purple.com/"))
} else {
self.event_queue.push(LoadUrlWindowEvent(value))
}
}
}

View file

@ -8,10 +8,10 @@ use geom::point::Point2D;
use geom::size::Size2D;
use servo_msg::compositor_msg::{ReadyState, RenderState};
pub enum WindowMouseEvent {
WindowClickEvent(uint, Point2D<f32>),
WindowMouseDownEvent(uint, Point2D<f32>),
WindowMouseUpEvent(uint, Point2D<f32>),
pub enum MouseWindowEvent {
MouseWindowClickEvent(uint, Point2D<f32>),
MouseWindowMouseDownEvent(uint, Point2D<f32>),
MouseWindowMouseUpEvent(uint, Point2D<f32>),
}
pub enum WindowNavigateMsg {
@ -19,26 +19,30 @@ pub enum WindowNavigateMsg {
Back,
}
/// Type of the function that is called when the window is resized.
pub type ResizeCallback = @fn(uint, uint);
/// Type of the function that is called when a new URL is to be loaded.
pub type LoadUrlCallback = @fn(&str);
/// Type of the function that is called when a mouse hit test is to be performed.
pub type MouseCallback = @fn(WindowMouseEvent);
/// Type of the function that is called when the user scrolls.
pub type ScrollCallback = @fn(Point2D<f32>);
/// Type of the function that is called when the user zooms.
pub type ZoomCallback = @fn(f32);
/// Type of the function that is called when the user clicks backspace or shift-backspace
pub type NavigationCallback = @fn(WindowNavigateMsg);
/// Type of the function that is called when the rendering is finished
pub type FinishedCallback = @fn();
/// Events that the windowing system sends to Servo.
pub enum WindowEvent {
/// Sent when no message has arrived.
///
/// FIXME: This is a bogus event and is only used because we don't have the new
/// scheduler integrated with the platform event loop.
IdleWindowEvent,
/// Sent when the window is resized.
ResizeWindowEvent(uint, uint),
/// Sent when a new URL is to be loaded.
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 zooms.
ZoomWindowEvent(f32),
/// Sent when the user uses chrome navigation (i.e. backspace or shift-backspace).
NavigationWindowEvent(WindowNavigateMsg),
/// Sent when rendering is finished.
FinishedWindowEvent,
/// Sent when the user quits the application
QuitWindowEvent,
}
/// Methods for an abstract Application.
pub trait ApplicationMethods {
@ -52,24 +56,10 @@ pub trait WindowMethods<A> {
pub fn size(&self) -> Size2D<f32>;
/// Presents the window to the screen (perhaps by page flipping).
pub fn present(&mut self);
/// Spins the event loop and returns the next event.
pub fn recv(@mut self) -> WindowEvent;
/// Registers a callback to run when a resize event occurs.
pub fn set_resize_callback(&mut self, new_resize_callback: ResizeCallback);
/// Registers a callback to run when a new URL is to be loaded.
pub fn set_load_url_callback(&mut self, new_load_url_callback: LoadUrlCallback);
/// Registers a callback to run when the user clicks.
pub fn set_mouse_callback(&mut self, new_mouse_callback: MouseCallback);
/// Registers a callback to run when the user scrolls.
pub fn set_scroll_callback(&mut self, new_scroll_callback: ScrollCallback);
/// Registers a callback to run when the user zooms.
pub fn set_zoom_callback(&mut self, new_zoom_callback: ZoomCallback);
/// Registers a callback to run when the user presses backspace or shift-backspace.
pub fn set_navigation_callback(&mut self, new_navigation_callback: NavigationCallback);
/// Registers a callback to run when rendering is finished.
pub fn set_finished_callback(&mut self, new_finish_callback: FinishedCallback);
/// Spins the event loop. Returns whether the window should close.
pub fn check_loop(@mut self) -> bool;
/// Sets the ready state of the current page.
pub fn set_ready_state(@mut self, ready_state: ReadyState);
/// Sets the render state of the current page.