diff --git a/src/frame.rs b/src/frame.rs index 7bb21a6676..7a543302e3 100644 --- a/src/frame.rs +++ b/src/frame.rs @@ -1,8 +1,9 @@ use app_units::Au; use batch::{MAX_MATRICES_PER_BATCH, OffsetParams}; use device::{TextureId, TextureFilter}; -use euclid::{Rect, Point2D, Size2D, Matrix4}; +use euclid::{Rect, Point2D, Point3D, Point4D, Size2D, Matrix4}; use fnv::FnvHasher; +use geometry::ray_intersects_rect; use internal_types::{AxisDirection, LowLevelFilterOp, CompositionOp, DrawListItemIndex}; use internal_types::{BatchUpdateList, RenderTargetIndex, DrawListId}; use internal_types::{CompositeBatchInfo, CompositeBatchJob}; @@ -575,27 +576,72 @@ impl Frame { mem::replace(&mut self.pending_updates, BatchUpdateList::new()) } - pub fn scroll(&mut self, delta: &Point2D) { - // TODO: Select correct layer for scrolling! - for (layer_id, layer) in &mut self.layers { - let layer_size = layer.layer_size; + pub fn get_scroll_layer(&self, + cursor: &Point2D, + scroll_layer_id: ScrollLayerId, + parent_transform: &Matrix4) -> Option { + self.layers.get(&scroll_layer_id).and_then(|layer| { + let transform = parent_transform.mul(&layer.local_transform); + + for child_layer_id in &layer.children { + if let Some(layer_id) = self.get_scroll_layer(cursor, + *child_layer_id, + &transform) { + return Some(layer_id); + } + } - match layer_id { - &ScrollLayerId::Fixed => { - // Can't scroll a fixed layer! + match scroll_layer_id { + ScrollLayerId::Fixed => { + None } - &ScrollLayerId::Normal(..) => { - if layer_size.width > layer.viewport_size.width { - layer.scroll_offset.x = layer.scroll_offset.x + delta.x; - layer.scroll_offset.x = layer.scroll_offset.x.min(0.0); - layer.scroll_offset.x = layer.scroll_offset.x.max(-layer_size.width + layer.viewport_size.width); + ScrollLayerId::Normal(..) => { + let inv = transform.invert(); + let z0 = -10000.0; + let z1 = 10000.0; + + let p0 = inv.transform_point4d(&Point4D::new(cursor.x, cursor.y, z0, 1.0)); + let p0 = Point3D::new(p0.x / p0.w, + p0.y / p0.w, + p0.z / p0.w); + let p1 = inv.transform_point4d(&Point4D::new(cursor.x, cursor.y, z1, 1.0)); + let p1 = Point3D::new(p1.x / p1.w, + p1.y / p1.w, + p1.z / p1.w); + + let layer_rect = Rect::new(layer.world_origin, layer.viewport_size); + + if ray_intersects_rect(p0, p1, layer_rect) { + Some(scroll_layer_id) + } else { + None } + } + } + }) + } - if layer_size.height > layer.viewport_size.height { - layer.scroll_offset.y = layer.scroll_offset.y + delta.y; - layer.scroll_offset.y = layer.scroll_offset.y.min(0.0); - layer.scroll_offset.y = layer.scroll_offset.y.max(-layer_size.height + layer.viewport_size.height); - } + pub fn scroll(&mut self, + delta: Point2D, + cursor: Point2D) { + if let Some(root_scroll_layer_id) = self.root_scroll_layer_id { + let scroll_layer_id = self.get_scroll_layer(&cursor, + root_scroll_layer_id, + &Matrix4::identity()); + + if let Some(scroll_layer_id) = scroll_layer_id { + let layer = self.layers.get_mut(&scroll_layer_id).unwrap(); + + if layer.layer_size.width > layer.viewport_size.width { + layer.scroll_offset.x = layer.scroll_offset.x + delta.x; + layer.scroll_offset.x = layer.scroll_offset.x.min(0.0); + layer.scroll_offset.x = layer.scroll_offset.x.max(-layer.layer_size.width + layer.viewport_size.width); + } + + if layer.layer_size.height > layer.viewport_size.height { + layer.scroll_offset.y = layer.scroll_offset.y + delta.y; + layer.scroll_offset.y = layer.scroll_offset.y.min(0.0); + layer.scroll_offset.y = layer.scroll_offset.y.max(-layer.layer_size.height + layer.viewport_size.height); } } } diff --git a/src/geometry.rs b/src/geometry.rs new file mode 100644 index 0000000000..7e316b4c38 --- /dev/null +++ b/src/geometry.rs @@ -0,0 +1,81 @@ +use euclid::{Rect, Point3D}; + +/* + A naive port of "An Efficient and Robust Ray–Box Intersection Algorithm" + from https://www.cs.utah.edu/~awilliam/box/box.pdf + + This should be cleaned up and extracted into more useful types! + */ + +// Assumes rect is in the z=0 plane! +pub fn ray_intersects_rect(ray_origin: Point3D, + ray_end: Point3D, + rect: Rect) -> bool { + let mut dir = ray_end - ray_origin; + let len = ((dir.x*dir.x) + (dir.y*dir.y) + (dir.z*dir.z)).sqrt(); + dir.x = dir.x / len; + dir.y = dir.y / len; + dir.z = dir.z / len; + let inv_direction = Point3D::new(1.0/dir.x, 1.0/dir.y, 1.0/dir.z); + + let sign = [ + if inv_direction.x < 0.0 { + 1 + } else { + 0 + }, + if inv_direction.y < 0.0 { + 1 + } else { + 0 + }, + if inv_direction.z < 0.0 { + 1 + } else { + 0 + }, + ]; + + let parameters = [ + Point3D::new(rect.origin.x, rect.origin.y, 0.0), + Point3D::new(rect.origin.x + rect.size.width, + rect.origin.y + rect.size.height, + 0.0), + ]; + + let mut tmin = (parameters[sign[0]].x - ray_origin.x) * inv_direction.x; + let mut tmax = (parameters[1-sign[0]].x - ray_origin.x) * inv_direction.x; + let tymin = (parameters[sign[1]].y - ray_origin.y) * inv_direction.y; + let tymax = (parameters[1-sign[1]].y - ray_origin.y) * inv_direction.y; + if (tmin > tymax) || (tymin > tmax) { + return false; + } + if tymin > tmin { + tmin = tymin; + } + if tymax < tmax { + tmax = tymax; + } + let tzmin = (parameters[sign[2]].z - ray_origin.z) * inv_direction.z; + let tzmax = (parameters[1-sign[2]].z - ray_origin.z) * inv_direction.z; + if (tmin > tzmax) || (tzmin > tmax) { + return false; + } + + // Don't care about where on the ray it hits... + true + + /* + if tzmin > tmin { + tmin = tzmin; + } + if tzmax < tmax { + tmax = tzmax; + } + + let t0 = 0.0; + let t1 = len; + + (tmin < t1) && (tmax > t0) + */ +} diff --git a/src/lib.rs b/src/lib.rs index 027fea688f..3358ad5565 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,6 +12,7 @@ mod debug_render; mod device; mod frame; mod freelist; +mod geometry; mod internal_types; mod layer; mod node_compiler; diff --git a/src/render_backend.rs b/src/render_backend.rs index 91ff4d035a..fadc5eb03f 100644 --- a/src/render_backend.rs +++ b/src/render_backend.rs @@ -162,9 +162,9 @@ impl RenderBackend { self.publish_frame(frame, &mut profile_counters); } - ApiMsg::Scroll(delta) => { + ApiMsg::Scroll(delta, cursor) => { let frame = profile_counters.total_time.profile(|| { - self.frame.scroll(&delta); + self.frame.scroll(delta, cursor); self.render() });