aboutsummaryrefslogtreecommitdiffstats
path: root/components/compositing/events.rs
blob: 25779a417affe167269e4a97d8ac18f025bc6808 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
/* 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 compositor_data::{CompositorData, WantsScrollEvents};
use windowing::{MouseWindowEvent, MouseWindowClickEvent, MouseWindowMouseDownEvent};
use windowing::MouseWindowMouseUpEvent;

use geom::length::Length;
use geom::point::TypedPoint2D;
use geom::scale_factor::ScaleFactor;
use geom::size::TypedSize2D;
use layers::geometry::DevicePixel;
use layers::layers::Layer;
use script_traits::{ClickEvent, MouseDownEvent, MouseMoveEvent, MouseUpEvent, SendEventMsg};
use script_traits::{ScriptControlChan};
use servo_msg::compositor_msg::{FixedPosition, LayerId};
use servo_msg::constellation_msg::PipelineId;
use servo_util::geometry::PagePx;
use std::rc::Rc;


use geom::matrix::identity;

trait Clampable {
    fn clamp(&self, mn: &Self, mx: &Self) -> Self;
}

impl Clampable for f32 {
    /// Returns the number constrained within the range `mn <= self <= mx`.
    /// If any of the numbers are `NAN` then `NAN` is returned.
    #[inline]
    fn clamp(&self, mn: &f32, mx: &f32) -> f32 {
        match () {
            _ if self.is_nan()   => *self,
            _ if !(*self <= *mx) => *mx,
            _ if !(*self >= *mn) => *mn,
            _                    => *self,
        }
    }
}

/// Move the layer's descendants that don't want scroll events and scroll by a relative
/// specified amount in page coordinates. 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 handle_scroll_event(layer: Rc<Layer<CompositorData>>,
                           delta: TypedPoint2D<DevicePixel, f32>,
                           cursor: TypedPoint2D<DevicePixel, f32>,
                           window_size: TypedSize2D<DevicePixel, f32>)
                           -> bool {
    // If this layer doesn't want scroll events, neither it nor its children can handle scroll
    // events.
    if layer.extra_data.borrow().wants_scroll_events != WantsScrollEvents {
        return false
    }

    // Allow children to scroll.
    let content_offset = layer.content_offset.borrow().clone();
    let cursor = cursor - content_offset;
    for child in layer.children().iter() {
        let child_bounds = child.bounds.borrow();
        if child_bounds.contains(&cursor) &&
           handle_scroll_event(child.clone(),
                               delta,
                               cursor - child_bounds.origin,
                               child_bounds.size) {
            return true
        }
    }

    clamp_scroll_offset_and_scroll_layer(layer, content_offset + delta, window_size)

}

pub fn clamp_scroll_offset_and_scroll_layer(layer: Rc<Layer<CompositorData>>,
                                            mut new_offset: TypedPoint2D<DevicePixel, f32>,
                                            window_size: TypedSize2D<DevicePixel, f32>)
                                            -> bool {
    let layer_size = layer.bounds.borrow().size;
    let min_x = (window_size.width - layer_size.width).get().min(0.0);
    new_offset.x = Length(new_offset.x.get().clamp(&min_x, &0.0));

    let min_y = (window_size.height - layer_size.height).get().min(0.0);
    new_offset.y = Length(new_offset.y.get().clamp(&min_y, &0.0));

    if *layer.content_offset.borrow() == new_offset {
        return false
    }

    // FIXME: This allows the base layer to record the current content offset without
    // updating its transform. This should be replaced with something less strange.
    *layer.content_offset.borrow_mut() = new_offset;
    scroll_layer_and_all_child_layers(layer.clone(), new_offset)
}

fn scroll_layer_and_all_child_layers(layer: Rc<Layer<CompositorData>>,
                                     new_offset: TypedPoint2D<DevicePixel, f32>)
                                     -> bool {
    let mut result = false;

    // Only scroll this layer if it's not fixed-positioned.
    if layer.extra_data.borrow().scroll_policy != FixedPosition {
        *layer.transform.borrow_mut() = identity().translate(new_offset.x.get(),
                                                             new_offset.y.get(),
                                                             0.0);
        *layer.content_offset.borrow_mut() = new_offset;
        result = true
    }

    for child in layer.children().iter() {
        result |= scroll_layer_and_all_child_layers(child.clone(), new_offset);
    }

    return result;
}

// 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(layer: Rc<Layer<CompositorData>>,
                        event: MouseWindowEvent,
                        cursor: TypedPoint2D<DevicePixel, f32>,
                        device_pixels_per_page_px: ScaleFactor<PagePx, DevicePixel, f32>) {
    let cursor = cursor - *layer.content_offset.borrow();
    for child in layer.children().iter() {
        let child_bounds = child.bounds.borrow();
        if child_bounds.contains(&cursor) {
            send_mouse_event(child.clone(),
                             event,
                             cursor - child_bounds.origin,
                             device_pixels_per_page_px);
            return;
        }
    }

    // This mouse event is mine!
    let cursor = cursor / device_pixels_per_page_px;
    let message = match event {
        MouseWindowClickEvent(button, _) => ClickEvent(button, cursor.to_untyped()),
        MouseWindowMouseDownEvent(button, _) => MouseDownEvent(button, cursor.to_untyped()),
        MouseWindowMouseUpEvent(button, _) => MouseUpEvent(button, cursor.to_untyped()),
    };
    let ScriptControlChan(ref chan) = layer.extra_data.borrow().pipeline.script_chan;
    let _ = chan.send_opt(SendEventMsg(layer.extra_data.borrow().pipeline.id.clone(), message));
}

pub fn send_mouse_move_event(layer: Rc<Layer<CompositorData>>,
                             cursor: TypedPoint2D<PagePx, f32>) {
    let message = MouseMoveEvent(cursor.to_untyped());
    let ScriptControlChan(ref chan) = layer.extra_data.borrow().pipeline.script_chan;
    let _ = chan.send_opt(SendEventMsg(layer.extra_data.borrow().pipeline.id.clone(), message));
}

pub fn move(layer: Rc<Layer<CompositorData>>,
            pipeline_id: PipelineId,
            layer_id: LayerId,
            origin: TypedPoint2D<DevicePixel, f32>,
            window_size: TypedSize2D<DevicePixel, f32>)
            -> bool {
    // Search children for the right layer to move.
    if layer.extra_data.borrow().pipeline.id != pipeline_id ||
       layer.extra_data.borrow().id != layer_id {
        return layer.children().iter().any(|kid| {
            move(kid.clone(),
                 pipeline_id,
                 layer_id,
                 origin,
                 window_size)
        });
    }

    if layer.extra_data.borrow().wants_scroll_events != WantsScrollEvents {
        return false
    }

    clamp_scroll_offset_and_scroll_layer(layer, TypedPoint2D(0f32, 0f32) - origin, window_size)
}