mirror of
https://github.com/servo/servo.git
synced 2025-06-23 16:44:33 +01:00
auto merge of #3537 : mrobinson/servo/scale, r=zwarich
When interacting with Layers it is simpler to use LayerPixels, which are unscaled pixels in the Layer coordinate system. This removes a lot of room for error and makes things simpler.
This commit is contained in:
commit
6358b7d94e
5 changed files with 64 additions and 96 deletions
10
Cargo.lock
generated
10
Cargo.lock
generated
|
@ -33,7 +33,7 @@ dependencies = [
|
||||||
"freetype 0.1.0 (git+https://github.com/servo/rust-freetype#0b03da276e4bdeae2300596dabc4ccb16733ad70)",
|
"freetype 0.1.0 (git+https://github.com/servo/rust-freetype#0b03da276e4bdeae2300596dabc4ccb16733ad70)",
|
||||||
"geom 0.1.0 (git+https://github.com/servo/rust-geom#90add8d65273c8a46aa16d73959e29a51d0c282d)",
|
"geom 0.1.0 (git+https://github.com/servo/rust-geom#90add8d65273c8a46aa16d73959e29a51d0c282d)",
|
||||||
"glfw 0.0.1 (git+https://github.com/servo/glfw-rs?ref=servo#7ccfaca315a43d97914e1601c90ad348ef190edf)",
|
"glfw 0.0.1 (git+https://github.com/servo/glfw-rs?ref=servo#7ccfaca315a43d97914e1601c90ad348ef190edf)",
|
||||||
"layers 0.1.0 (git+https://github.com/servo/rust-layers#9c2848823e24af586899947743704b1238f374c8)",
|
"layers 0.1.0 (git+https://github.com/servo/rust-layers#180d3ff2f28d239e32d01982c76be5c97d5763a8)",
|
||||||
"opengles 0.1.0 (git+https://github.com/servo/rust-opengles#6776e9c07feb149d34b087039ecf6b2c143e3afc)",
|
"opengles 0.1.0 (git+https://github.com/servo/rust-opengles#6776e9c07feb149d34b087039ecf6b2c143e3afc)",
|
||||||
"skia-sys 0.0.20130412 (git+https://github.com/servo/skia#6d696712962fd0d41120b7a414a48417da8e6a92)",
|
"skia-sys 0.0.20130412 (git+https://github.com/servo/skia#6d696712962fd0d41120b7a414a48417da8e6a92)",
|
||||||
"xlib 0.1.0 (git+https://github.com/servo/rust-xlib#581d4faddec5188d3c3ae5307dbea28aab90644c)",
|
"xlib 0.1.0 (git+https://github.com/servo/rust-xlib#581d4faddec5188d3c3ae5307dbea28aab90644c)",
|
||||||
|
@ -66,7 +66,7 @@ dependencies = [
|
||||||
"gfx 0.0.1",
|
"gfx 0.0.1",
|
||||||
"glfw 0.0.1 (git+https://github.com/servo/glfw-rs?ref=servo#7ccfaca315a43d97914e1601c90ad348ef190edf)",
|
"glfw 0.0.1 (git+https://github.com/servo/glfw-rs?ref=servo#7ccfaca315a43d97914e1601c90ad348ef190edf)",
|
||||||
"glut 0.0.1 (git+https://github.com/servo/rust-glut#01af0162ea0322ad1a40d6adb023a39813605949)",
|
"glut 0.0.1 (git+https://github.com/servo/rust-glut#01af0162ea0322ad1a40d6adb023a39813605949)",
|
||||||
"layers 0.1.0 (git+https://github.com/servo/rust-layers#9c2848823e24af586899947743704b1238f374c8)",
|
"layers 0.1.0 (git+https://github.com/servo/rust-layers#180d3ff2f28d239e32d01982c76be5c97d5763a8)",
|
||||||
"layout_traits 0.0.1",
|
"layout_traits 0.0.1",
|
||||||
"msg 0.0.1",
|
"msg 0.0.1",
|
||||||
"net 0.0.1",
|
"net 0.0.1",
|
||||||
|
@ -181,7 +181,7 @@ dependencies = [
|
||||||
"freetype 0.1.0 (git+https://github.com/servo/rust-freetype#0b03da276e4bdeae2300596dabc4ccb16733ad70)",
|
"freetype 0.1.0 (git+https://github.com/servo/rust-freetype#0b03da276e4bdeae2300596dabc4ccb16733ad70)",
|
||||||
"geom 0.1.0 (git+https://github.com/servo/rust-geom#90add8d65273c8a46aa16d73959e29a51d0c282d)",
|
"geom 0.1.0 (git+https://github.com/servo/rust-geom#90add8d65273c8a46aa16d73959e29a51d0c282d)",
|
||||||
"harfbuzz 0.1.0 (git+https://github.com/servo/rust-harfbuzz#ad520942cc17232e1a40cdd8a99c2905623d35f6)",
|
"harfbuzz 0.1.0 (git+https://github.com/servo/rust-harfbuzz#ad520942cc17232e1a40cdd8a99c2905623d35f6)",
|
||||||
"layers 0.1.0 (git+https://github.com/servo/rust-layers#9c2848823e24af586899947743704b1238f374c8)",
|
"layers 0.1.0 (git+https://github.com/servo/rust-layers#180d3ff2f28d239e32d01982c76be5c97d5763a8)",
|
||||||
"msg 0.0.1",
|
"msg 0.0.1",
|
||||||
"net 0.0.1",
|
"net 0.0.1",
|
||||||
"plugins 0.0.1",
|
"plugins 0.0.1",
|
||||||
|
@ -265,7 +265,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "layers"
|
name = "layers"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/servo/rust-layers#9c2848823e24af586899947743704b1238f374c8"
|
source = "git+https://github.com/servo/rust-layers#180d3ff2f28d239e32d01982c76be5c97d5763a8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"core_foundation 0.1.0 (git+https://github.com/servo/rust-core-foundation#166a601ff3e0fc3a64ca1a9090d02c8d4f22b61a)",
|
"core_foundation 0.1.0 (git+https://github.com/servo/rust-core-foundation#166a601ff3e0fc3a64ca1a9090d02c8d4f22b61a)",
|
||||||
"egl 0.1.0 (git+https://github.com/servo/rust-egl#88f2a13812ddbce2bf2317221663a61c31b3e220)",
|
"egl 0.1.0 (git+https://github.com/servo/rust-egl#88f2a13812ddbce2bf2317221663a61c31b3e220)",
|
||||||
|
@ -323,7 +323,7 @@ dependencies = [
|
||||||
"core_foundation 0.1.0 (git+https://github.com/servo/rust-core-foundation#166a601ff3e0fc3a64ca1a9090d02c8d4f22b61a)",
|
"core_foundation 0.1.0 (git+https://github.com/servo/rust-core-foundation#166a601ff3e0fc3a64ca1a9090d02c8d4f22b61a)",
|
||||||
"geom 0.1.0 (git+https://github.com/servo/rust-geom#90add8d65273c8a46aa16d73959e29a51d0c282d)",
|
"geom 0.1.0 (git+https://github.com/servo/rust-geom#90add8d65273c8a46aa16d73959e29a51d0c282d)",
|
||||||
"io_surface 0.1.0 (git+https://github.com/servo/rust-io-surface#7038341220bd7e86e21118fac2cbc6bd50890e47)",
|
"io_surface 0.1.0 (git+https://github.com/servo/rust-io-surface#7038341220bd7e86e21118fac2cbc6bd50890e47)",
|
||||||
"layers 0.1.0 (git+https://github.com/servo/rust-layers#9c2848823e24af586899947743704b1238f374c8)",
|
"layers 0.1.0 (git+https://github.com/servo/rust-layers#180d3ff2f28d239e32d01982c76be5c97d5763a8)",
|
||||||
"url 0.1.0 (git+https://github.com/servo/rust-url#29f70a47230c2aa736e263977247c786e0b2c243)",
|
"url 0.1.0 (git+https://github.com/servo/rust-url#29f70a47230c2aa736e263977247c786e0b2c243)",
|
||||||
"util 0.0.1",
|
"util 0.0.1",
|
||||||
]
|
]
|
||||||
|
|
|
@ -29,7 +29,7 @@ use geom::rect::{Rect, TypedRect};
|
||||||
use geom::size::TypedSize2D;
|
use geom::size::TypedSize2D;
|
||||||
use geom::scale_factor::ScaleFactor;
|
use geom::scale_factor::ScaleFactor;
|
||||||
use gfx::render_task::{RenderChan, RenderMsg, RenderRequest, UnusedBufferMsg};
|
use gfx::render_task::{RenderChan, RenderMsg, RenderRequest, UnusedBufferMsg};
|
||||||
use layers::geometry::DevicePixel;
|
use layers::geometry::{DevicePixel, LayerPixel};
|
||||||
use layers::layers::{BufferRequest, Layer, LayerBufferSet};
|
use layers::layers::{BufferRequest, Layer, LayerBufferSet};
|
||||||
use layers::rendergl;
|
use layers::rendergl;
|
||||||
use layers::rendergl::RenderContext;
|
use layers::rendergl::RenderContext;
|
||||||
|
@ -457,7 +457,9 @@ impl IOCompositor {
|
||||||
match frame_rect {
|
match frame_rect {
|
||||||
Some(ref frame_rect) => {
|
Some(ref frame_rect) => {
|
||||||
*root_layer.masks_to_bounds.borrow_mut() = true;
|
*root_layer.masks_to_bounds.borrow_mut() = true;
|
||||||
*root_layer.bounds.borrow_mut() = frame_rect * self.device_pixels_per_page_px();
|
|
||||||
|
let frame_rect = frame_rect.to_untyped();
|
||||||
|
*root_layer.bounds.borrow_mut() = Rect::from_untyped(&frame_rect);
|
||||||
}
|
}
|
||||||
None => {}
|
None => {}
|
||||||
}
|
}
|
||||||
|
@ -500,15 +502,7 @@ impl IOCompositor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// rust-layers keeps everything in layer coordinates, so we must convert all rectangles
|
fn create_or_update_root_layer(&mut self, layer_properties: LayerProperties) {
|
||||||
// from page coordinates into layer coordinates based on our current scale.
|
|
||||||
fn convert_page_rect_to_layer_coordinates(&self, page_rect: Rect<f32>) -> Rect<f32> {
|
|
||||||
page_rect * self.device_pixels_per_page_px().get()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_or_update_root_layer(&mut self, mut layer_properties: LayerProperties) {
|
|
||||||
layer_properties.rect = self.convert_page_rect_to_layer_coordinates(layer_properties.rect);
|
|
||||||
|
|
||||||
let need_new_root_layer = !self.update_layer_if_exists(layer_properties);
|
let need_new_root_layer = !self.update_layer_if_exists(layer_properties);
|
||||||
if need_new_root_layer {
|
if need_new_root_layer {
|
||||||
let root_layer = self.find_pipeline_root_layer(layer_properties.pipeline_id);
|
let root_layer = self.find_pipeline_root_layer(layer_properties.pipeline_id);
|
||||||
|
@ -532,8 +526,7 @@ impl IOCompositor {
|
||||||
self.send_buffer_requests_for_all_layers();
|
self.send_buffer_requests_for_all_layers();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_or_update_descendant_layer(&mut self, mut layer_properties: LayerProperties) {
|
fn create_or_update_descendant_layer(&mut self, layer_properties: LayerProperties) {
|
||||||
layer_properties.rect = self.convert_page_rect_to_layer_coordinates(layer_properties.rect);
|
|
||||||
if !self.update_layer_if_exists(layer_properties) {
|
if !self.update_layer_if_exists(layer_properties) {
|
||||||
self.create_descendant_layer(layer_properties);
|
self.create_descendant_layer(layer_properties);
|
||||||
}
|
}
|
||||||
|
@ -569,15 +562,15 @@ impl IOCompositor {
|
||||||
pub fn move_layer(&self,
|
pub fn move_layer(&self,
|
||||||
pipeline_id: PipelineId,
|
pipeline_id: PipelineId,
|
||||||
layer_id: LayerId,
|
layer_id: LayerId,
|
||||||
origin: TypedPoint2D<DevicePixel, f32>)
|
origin: TypedPoint2D<LayerPixel, f32>)
|
||||||
-> bool {
|
-> bool {
|
||||||
|
let window_size = self.window_size.as_f32() / self.scene.scale;
|
||||||
match self.find_layer_with_pipeline_and_layer_id(pipeline_id, layer_id) {
|
match self.find_layer_with_pipeline_and_layer_id(pipeline_id, layer_id) {
|
||||||
Some(ref layer) => {
|
Some(ref layer) => {
|
||||||
if layer.extra_data.borrow().wants_scroll_events == WantsScrollEvents {
|
if layer.extra_data.borrow().wants_scroll_events == WantsScrollEvents {
|
||||||
events::clamp_scroll_offset_and_scroll_layer(layer.clone(),
|
events::clamp_scroll_offset_and_scroll_layer(layer.clone(),
|
||||||
TypedPoint2D(0f32, 0f32) - origin,
|
TypedPoint2D(0f32, 0f32) - origin,
|
||||||
self.window_size.as_f32(),
|
window_size);
|
||||||
self.device_pixels_per_page_px());
|
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
@ -588,10 +581,8 @@ impl IOCompositor {
|
||||||
fn scroll_layer_to_fragment_point_if_necessary(&mut self,
|
fn scroll_layer_to_fragment_point_if_necessary(&mut self,
|
||||||
pipeline_id: PipelineId,
|
pipeline_id: PipelineId,
|
||||||
layer_id: LayerId) {
|
layer_id: LayerId) {
|
||||||
let device_pixels_per_page_px = self.device_pixels_per_page_px();
|
|
||||||
match self.fragment_point.take() {
|
match self.fragment_point.take() {
|
||||||
Some(point) => {
|
Some(point) => {
|
||||||
let point = point * device_pixels_per_page_px.get();
|
|
||||||
if !self.move_layer(pipeline_id, layer_id, Point2D::from_untyped(&point)) {
|
if !self.move_layer(pipeline_id, layer_id, Point2D::from_untyped(&point)) {
|
||||||
fail!("Compositor: Tried to scroll to fragment with unknown layer.");
|
fail!("Compositor: Tried to scroll to fragment with unknown layer.");
|
||||||
}
|
}
|
||||||
|
@ -606,11 +597,9 @@ impl IOCompositor {
|
||||||
pipeline_id: PipelineId,
|
pipeline_id: PipelineId,
|
||||||
layer_id: LayerId,
|
layer_id: LayerId,
|
||||||
new_origin: Point2D<f32>) {
|
new_origin: Point2D<f32>) {
|
||||||
let new_origin_in_device_coordinates = new_origin * self.device_pixels_per_page_px().get();
|
|
||||||
match self.find_layer_with_pipeline_and_layer_id(pipeline_id, layer_id) {
|
match self.find_layer_with_pipeline_and_layer_id(pipeline_id, layer_id) {
|
||||||
Some(ref layer) => {
|
Some(ref layer) => {
|
||||||
layer.bounds.borrow_mut().origin =
|
layer.bounds.borrow_mut().origin = Point2D::from_untyped(&new_origin)
|
||||||
Point2D::from_untyped(&new_origin_in_device_coordinates)
|
|
||||||
}
|
}
|
||||||
None => fail!("Compositor received SetLayerOrigin for nonexistent layer"),
|
None => fail!("Compositor received SetLayerOrigin for nonexistent layer"),
|
||||||
};
|
};
|
||||||
|
@ -647,9 +636,7 @@ impl IOCompositor {
|
||||||
pipeline_id: PipelineId,
|
pipeline_id: PipelineId,
|
||||||
layer_id: LayerId,
|
layer_id: LayerId,
|
||||||
point: Point2D<f32>) {
|
point: Point2D<f32>) {
|
||||||
let device_pixels_per_page_px = self.device_pixels_per_page_px();
|
if self.move_layer(pipeline_id, layer_id, Point2D::from_untyped(&point)) {
|
||||||
let device_point = point * device_pixels_per_page_px.get();
|
|
||||||
if self.move_layer(pipeline_id, layer_id, Point2D::from_untyped(&device_point)) {
|
|
||||||
self.recomposite = true;
|
self.recomposite = true;
|
||||||
self.send_buffer_requests_for_all_layers();
|
self.send_buffer_requests_for_all_layers();
|
||||||
} else {
|
} else {
|
||||||
|
@ -749,37 +736,36 @@ impl IOCompositor {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_mouse_window_event_class(&self, mouse_window_event: MouseWindowEvent) {
|
fn on_mouse_window_event_class(&self, mouse_window_event: MouseWindowEvent) {
|
||||||
let scale = self.device_pixels_per_page_px();
|
|
||||||
let point = match mouse_window_event {
|
let point = match mouse_window_event {
|
||||||
MouseWindowClickEvent(_, p) => p,
|
MouseWindowClickEvent(_, p) => p,
|
||||||
MouseWindowMouseDownEvent(_, p) => p,
|
MouseWindowMouseDownEvent(_, p) => p,
|
||||||
MouseWindowMouseUpEvent(_, p) => p,
|
MouseWindowMouseUpEvent(_, p) => p,
|
||||||
};
|
};
|
||||||
for layer in self.scene.root.iter() {
|
for layer in self.scene.root.iter() {
|
||||||
events::send_mouse_event(layer.clone(), mouse_window_event, point, scale);
|
events::send_mouse_event(layer.clone(), mouse_window_event, point / self.scene.scale);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_mouse_window_move_event_class(&self, cursor: TypedPoint2D<DevicePixel, f32>) {
|
fn on_mouse_window_move_event_class(&self, cursor: TypedPoint2D<DevicePixel, f32>) {
|
||||||
let scale = self.device_pixels_per_page_px();
|
|
||||||
for layer in self.scene.root.iter() {
|
for layer in self.scene.root.iter() {
|
||||||
events::send_mouse_move_event(layer.clone(), cursor / scale);
|
events::send_mouse_move_event(layer.clone(), cursor / self.scene.scale);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_scroll_window_event(&mut self,
|
fn on_scroll_window_event(&mut self,
|
||||||
delta: TypedPoint2D<DevicePixel, f32>,
|
delta: TypedPoint2D<DevicePixel, f32>,
|
||||||
cursor: TypedPoint2D<DevicePixel, i32>) {
|
cursor: TypedPoint2D<DevicePixel, i32>) {
|
||||||
|
let delta = delta / self.scene.scale;
|
||||||
|
let cursor = cursor.as_f32() / self.scene.scale;
|
||||||
|
let window_size = self.window_size.as_f32() / self.scene.scale;
|
||||||
|
|
||||||
let mut scroll = false;
|
let mut scroll = false;
|
||||||
let window_size = self.window_size.as_f32();
|
|
||||||
let scene_scale = self.device_pixels_per_page_px();
|
|
||||||
match self.scene.root {
|
match self.scene.root {
|
||||||
Some(ref mut layer) => {
|
Some(ref mut layer) => {
|
||||||
scroll = events::handle_scroll_event(layer.clone(),
|
scroll = events::handle_scroll_event(layer.clone(),
|
||||||
delta,
|
delta,
|
||||||
cursor.as_f32(),
|
cursor,
|
||||||
window_size,
|
window_size) == ScrollPositionChanged;
|
||||||
scene_scale) == ScrollPositionChanged;
|
|
||||||
}
|
}
|
||||||
None => { }
|
None => { }
|
||||||
}
|
}
|
||||||
|
@ -803,7 +789,11 @@ impl IOCompositor {
|
||||||
|
|
||||||
fn update_zoom_transform(&mut self) {
|
fn update_zoom_transform(&mut self) {
|
||||||
let scale = self.device_pixels_per_page_px();
|
let scale = self.device_pixels_per_page_px();
|
||||||
self.scene.scale = scale.get();
|
self.scene.scale = ScaleFactor(scale.get());
|
||||||
|
|
||||||
|
// We need to set the size of the root layer again, since the window size
|
||||||
|
// has changed in unscaled layer pixels.
|
||||||
|
self.scene.set_root_layer_size(self.window_size.as_f32());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_zoom_window_event(&mut self, magnification: f32) {
|
fn on_zoom_window_event(&mut self, magnification: f32) {
|
||||||
|
@ -824,20 +814,18 @@ impl IOCompositor {
|
||||||
|
|
||||||
// Scroll as needed
|
// Scroll as needed
|
||||||
let window_size = self.window_size.as_f32();
|
let window_size = self.window_size.as_f32();
|
||||||
let page_delta: TypedPoint2D<PagePx, f32> = TypedPoint2D(
|
let page_delta: TypedPoint2D<LayerPixel, f32> = TypedPoint2D(
|
||||||
window_size.width.get() * (viewport_zoom.inv() - old_viewport_zoom.inv()).get() * 0.5,
|
window_size.width.get() * (viewport_zoom.inv() - old_viewport_zoom.inv()).get() * 0.5,
|
||||||
window_size.height.get() * (viewport_zoom.inv() - old_viewport_zoom.inv()).get() * 0.5);
|
window_size.height.get() * (viewport_zoom.inv() - old_viewport_zoom.inv()).get() * 0.5);
|
||||||
|
|
||||||
let delta = page_delta * self.device_pixels_per_page_px();
|
|
||||||
let cursor = TypedPoint2D(-1f32, -1f32); // Make sure this hits the base layer.
|
let cursor = TypedPoint2D(-1f32, -1f32); // Make sure this hits the base layer.
|
||||||
let scene_scale = self.device_pixels_per_page_px();
|
let window_size = self.window_size.as_f32() / self.scene.scale;
|
||||||
match self.scene.root {
|
match self.scene.root {
|
||||||
Some(ref mut layer) => {
|
Some(ref mut layer) => {
|
||||||
events::handle_scroll_event(layer.clone(),
|
events::handle_scroll_event(layer.clone(),
|
||||||
delta,
|
page_delta,
|
||||||
cursor,
|
cursor,
|
||||||
window_size,
|
window_size);
|
||||||
scene_scale);
|
|
||||||
}
|
}
|
||||||
None => { }
|
None => { }
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,17 +8,15 @@ use pipeline::CompositionPipeline;
|
||||||
|
|
||||||
use azure::azure_hl::Color;
|
use azure::azure_hl::Color;
|
||||||
use geom::point::TypedPoint2D;
|
use geom::point::TypedPoint2D;
|
||||||
use geom::scale_factor::ScaleFactor;
|
|
||||||
use geom::size::{Size2D, TypedSize2D};
|
use geom::size::{Size2D, TypedSize2D};
|
||||||
use geom::rect::Rect;
|
use geom::rect::Rect;
|
||||||
use gfx::render_task::UnusedBufferMsg;
|
use gfx::render_task::UnusedBufferMsg;
|
||||||
use layers::geometry::DevicePixel;
|
use layers::geometry::LayerPixel;
|
||||||
use layers::layers::{Layer, LayerBufferSet};
|
use layers::layers::{Layer, LayerBufferSet};
|
||||||
use layers::platform::surface::NativeSurfaceMethods;
|
use layers::platform::surface::NativeSurfaceMethods;
|
||||||
use servo_msg::compositor_msg::{Epoch, LayerId};
|
use servo_msg::compositor_msg::{Epoch, LayerId};
|
||||||
use servo_msg::compositor_msg::ScrollPolicy;
|
use servo_msg::compositor_msg::ScrollPolicy;
|
||||||
use servo_msg::constellation_msg::PipelineId;
|
use servo_msg::constellation_msg::PipelineId;
|
||||||
use servo_util::geometry::PagePx;
|
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
pub struct CompositorData {
|
pub struct CompositorData {
|
||||||
|
@ -43,7 +41,7 @@ pub struct CompositorData {
|
||||||
|
|
||||||
/// The scroll offset originating from this scrolling root. This allows scrolling roots
|
/// The scroll offset originating from this scrolling root. This allows scrolling roots
|
||||||
/// to track their current scroll position even while their content_offset does not change.
|
/// to track their current scroll position even while their content_offset does not change.
|
||||||
pub scroll_offset: TypedPoint2D<PagePx, f32>,
|
pub scroll_offset: TypedPoint2D<LayerPixel, f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[deriving(PartialEq, Clone)]
|
#[deriving(PartialEq, Clone)]
|
||||||
|
@ -81,17 +79,15 @@ impl CompositorData {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_layer(layer: Rc<Layer<CompositorData>>, layer_properties: LayerProperties) {
|
pub fn update_layer(layer: Rc<Layer<CompositorData>>, layer_properties: LayerProperties) {
|
||||||
let size: TypedSize2D<DevicePixel, f32> = Size2D::from_untyped(&layer_properties.rect.size);
|
let size: TypedSize2D<LayerPixel, f32> = Size2D::from_untyped(&layer_properties.rect.size);
|
||||||
layer.resize(size);
|
layer.resize(size);
|
||||||
|
|
||||||
// Call scroll for bounds checking if the page shrunk. Use (-1, -1) as the
|
// Call scroll for bounds checking if the page shrunk. Use (-1, -1) as the
|
||||||
// cursor position to make sure the scroll isn't propagated downwards. The
|
// cursor position to make sure the scroll isn't propagated downwards.
|
||||||
// scale doesn't matter here since 0, 0 is 0, 0 no matter the scene scale.
|
|
||||||
events::handle_scroll_event(layer.clone(),
|
events::handle_scroll_event(layer.clone(),
|
||||||
TypedPoint2D(0f32, 0f32),
|
TypedPoint2D(0f32, 0f32),
|
||||||
TypedPoint2D(-1f32, -1f32),
|
TypedPoint2D(-1f32, -1f32),
|
||||||
size,
|
size);
|
||||||
ScaleFactor(1.0) /* scene_scale */);
|
|
||||||
CompositorData::update_layer_except_size(layer, layer_properties);
|
CompositorData::update_layer_except_size(layer, layer_properties);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,14 +9,12 @@ use windowing::MouseWindowMouseUpEvent;
|
||||||
use geom::length::Length;
|
use geom::length::Length;
|
||||||
use geom::point::{Point2D, TypedPoint2D};
|
use geom::point::{Point2D, TypedPoint2D};
|
||||||
use geom::rect::Rect;
|
use geom::rect::Rect;
|
||||||
use geom::scale_factor::ScaleFactor;
|
|
||||||
use geom::size::TypedSize2D;
|
use geom::size::TypedSize2D;
|
||||||
use layers::geometry::DevicePixel;
|
use layers::geometry::LayerPixel;
|
||||||
use layers::layers::Layer;
|
use layers::layers::Layer;
|
||||||
use script_traits::{ClickEvent, MouseDownEvent, MouseMoveEvent, MouseUpEvent, SendEventMsg};
|
use script_traits::{ClickEvent, MouseDownEvent, MouseMoveEvent, MouseUpEvent, SendEventMsg};
|
||||||
use script_traits::{ScriptControlChan};
|
use script_traits::{ScriptControlChan};
|
||||||
use servo_msg::compositor_msg::FixedPosition;
|
use servo_msg::compositor_msg::FixedPosition;
|
||||||
use servo_util::geometry::PagePx;
|
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
|
||||||
|
@ -52,10 +50,9 @@ pub enum ScrollEventResult {
|
||||||
/// mouse is over child layers first. If a layer successfully scrolled, returns true; otherwise
|
/// mouse is over child layers first. If a layer successfully scrolled, returns true; otherwise
|
||||||
/// returns false, so a parent layer can scroll instead.
|
/// returns false, so a parent layer can scroll instead.
|
||||||
pub fn handle_scroll_event(layer: Rc<Layer<CompositorData>>,
|
pub fn handle_scroll_event(layer: Rc<Layer<CompositorData>>,
|
||||||
delta: TypedPoint2D<DevicePixel, f32>,
|
delta: TypedPoint2D<LayerPixel, f32>,
|
||||||
cursor: TypedPoint2D<DevicePixel, f32>,
|
cursor: TypedPoint2D<LayerPixel, f32>,
|
||||||
window_size: TypedSize2D<DevicePixel, f32>,
|
window_size: TypedSize2D<LayerPixel, f32>)
|
||||||
scale: ScaleFactor<PagePx, DevicePixel, f32>)
|
|
||||||
-> ScrollEventResult {
|
-> ScrollEventResult {
|
||||||
// If this layer doesn't want scroll events, neither it nor its children can handle scroll
|
// If this layer doesn't want scroll events, neither it nor its children can handle scroll
|
||||||
// events.
|
// events.
|
||||||
|
@ -65,30 +62,25 @@ pub fn handle_scroll_event(layer: Rc<Layer<CompositorData>>,
|
||||||
|
|
||||||
// Allow children to scroll.
|
// Allow children to scroll.
|
||||||
let scroll_offset = layer.extra_data.borrow().scroll_offset;
|
let scroll_offset = layer.extra_data.borrow().scroll_offset;
|
||||||
let scroll_offset_in_device_pixels = scroll_offset * scale;
|
let new_cursor = cursor - scroll_offset;
|
||||||
let new_cursor = cursor - scroll_offset_in_device_pixels;
|
|
||||||
for child in layer.children().iter() {
|
for child in layer.children().iter() {
|
||||||
let child_bounds = child.bounds.borrow();
|
let child_bounds = child.bounds.borrow();
|
||||||
if child_bounds.contains(&new_cursor) {
|
if child_bounds.contains(&new_cursor) {
|
||||||
let result = handle_scroll_event(child.clone(),
|
let result = handle_scroll_event(child.clone(),
|
||||||
delta,
|
delta,
|
||||||
new_cursor - child_bounds.origin,
|
new_cursor - child_bounds.origin,
|
||||||
child_bounds.size,
|
child_bounds.size);
|
||||||
scale);
|
|
||||||
if result != ScrollEventUnhandled {
|
if result != ScrollEventUnhandled {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
clamp_scroll_offset_and_scroll_layer(layer,
|
clamp_scroll_offset_and_scroll_layer(layer, scroll_offset + delta, window_size)
|
||||||
scroll_offset_in_device_pixels + delta,
|
|
||||||
window_size,
|
|
||||||
scale)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn calculate_content_size_for_layer(layer: Rc<Layer<CompositorData>>)
|
pub fn calculate_content_size_for_layer(layer: Rc<Layer<CompositorData>>)
|
||||||
-> TypedSize2D<DevicePixel, f32> {
|
-> TypedSize2D<LayerPixel, f32> {
|
||||||
layer.children().iter().fold(Rect::zero(),
|
layer.children().iter().fold(Rect::zero(),
|
||||||
|unioned_rect, child_rect| {
|
|unioned_rect, child_rect| {
|
||||||
unioned_rect.union(&*child_rect.bounds.borrow())
|
unioned_rect.union(&*child_rect.bounds.borrow())
|
||||||
|
@ -96,29 +88,27 @@ pub fn calculate_content_size_for_layer(layer: Rc<Layer<CompositorData>>)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clamp_scroll_offset_and_scroll_layer(layer: Rc<Layer<CompositorData>>,
|
pub fn clamp_scroll_offset_and_scroll_layer(layer: Rc<Layer<CompositorData>>,
|
||||||
new_offset: TypedPoint2D<DevicePixel, f32>,
|
new_offset: TypedPoint2D<LayerPixel, f32>,
|
||||||
window_size: TypedSize2D<DevicePixel, f32>,
|
window_size: TypedSize2D<LayerPixel, f32>)
|
||||||
scale: ScaleFactor<PagePx, DevicePixel, f32>)
|
|
||||||
-> ScrollEventResult {
|
-> ScrollEventResult {
|
||||||
let layer_size = calculate_content_size_for_layer(layer.clone());
|
let layer_size = calculate_content_size_for_layer(layer.clone());
|
||||||
let min_x = (window_size.width - layer_size.width).get().min(0.0);
|
let min_x = (window_size.width - layer_size.width).get().min(0.0);
|
||||||
let min_y = (window_size.height - layer_size.height).get().min(0.0);
|
let min_y = (window_size.height - layer_size.height).get().min(0.0);
|
||||||
let new_offset : TypedPoint2D<DevicePixel, f32> =
|
let new_offset : TypedPoint2D<LayerPixel, f32> =
|
||||||
Point2D(Length(new_offset.x.get().clamp(&min_x, &0.0)),
|
Point2D(Length(new_offset.x.get().clamp(&min_x, &0.0)),
|
||||||
Length(new_offset.y.get().clamp(&min_y, &0.0)));
|
Length(new_offset.y.get().clamp(&min_y, &0.0)));
|
||||||
|
|
||||||
let new_offset_in_page_px = new_offset / scale;
|
if layer.extra_data.borrow().scroll_offset == new_offset {
|
||||||
if layer.extra_data.borrow().scroll_offset == new_offset_in_page_px {
|
|
||||||
return ScrollPositionUnchanged;
|
return ScrollPositionUnchanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The scroll offset is just a record of the scroll position of this scrolling root,
|
// The scroll offset is just a record of the scroll position of this scrolling root,
|
||||||
// but scroll_layer_and_all_child_layers actually moves the child layers.
|
// but scroll_layer_and_all_child_layers actually moves the child layers.
|
||||||
layer.extra_data.borrow_mut().scroll_offset = new_offset_in_page_px;
|
layer.extra_data.borrow_mut().scroll_offset = new_offset;
|
||||||
|
|
||||||
let mut result = false;
|
let mut result = false;
|
||||||
for child in layer.children().iter() {
|
for child in layer.children().iter() {
|
||||||
result |= scroll_layer_and_all_child_layers(child.clone(), new_offset_in_page_px);
|
result |= scroll_layer_and_all_child_layers(child.clone(), new_offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
if result {
|
if result {
|
||||||
|
@ -129,7 +119,7 @@ pub fn clamp_scroll_offset_and_scroll_layer(layer: Rc<Layer<CompositorData>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn scroll_layer_and_all_child_layers(layer: Rc<Layer<CompositorData>>,
|
fn scroll_layer_and_all_child_layers(layer: Rc<Layer<CompositorData>>,
|
||||||
new_offset: TypedPoint2D<PagePx, f32>)
|
new_offset: TypedPoint2D<LayerPixel, f32>)
|
||||||
-> bool {
|
-> bool {
|
||||||
let mut result = false;
|
let mut result = false;
|
||||||
|
|
||||||
|
@ -139,7 +129,7 @@ fn scroll_layer_and_all_child_layers(layer: Rc<Layer<CompositorData>>,
|
||||||
*layer.transform.borrow_mut() = identity().translate(new_offset.x,
|
*layer.transform.borrow_mut() = identity().translate(new_offset.x,
|
||||||
new_offset.y,
|
new_offset.y,
|
||||||
0.0);
|
0.0);
|
||||||
*layer.content_offset.borrow_mut() = new_offset;
|
*layer.content_offset.borrow_mut() = Point2D::from_untyped(&new_offset);
|
||||||
result = true
|
result = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,24 +146,18 @@ fn scroll_layer_and_all_child_layers(layer: Rc<Layer<CompositorData>>,
|
||||||
// page coordinates.
|
// page coordinates.
|
||||||
pub fn send_mouse_event(layer: Rc<Layer<CompositorData>>,
|
pub fn send_mouse_event(layer: Rc<Layer<CompositorData>>,
|
||||||
event: MouseWindowEvent,
|
event: MouseWindowEvent,
|
||||||
cursor: TypedPoint2D<DevicePixel, f32>,
|
cursor: TypedPoint2D<LayerPixel, f32>) {
|
||||||
device_pixels_per_page_px: ScaleFactor<PagePx, DevicePixel, f32>) {
|
let content_offset = *layer.content_offset.borrow();
|
||||||
let content_offset = *layer.content_offset.borrow() * device_pixels_per_page_px.get();
|
|
||||||
let content_offset : TypedPoint2D<DevicePixel, f32> = Point2D::from_untyped(&content_offset);
|
|
||||||
let cursor = cursor - content_offset;
|
let cursor = cursor - content_offset;
|
||||||
for child in layer.children().iter() {
|
for child in layer.children().iter() {
|
||||||
let child_bounds = child.bounds.borrow();
|
let child_bounds = child.bounds.borrow();
|
||||||
if child_bounds.contains(&cursor) {
|
if child_bounds.contains(&cursor) {
|
||||||
send_mouse_event(child.clone(),
|
send_mouse_event(child.clone(), event, cursor - child_bounds.origin);
|
||||||
event,
|
|
||||||
cursor - child_bounds.origin,
|
|
||||||
device_pixels_per_page_px);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This mouse event is mine!
|
// This mouse event is mine!
|
||||||
let cursor = cursor / device_pixels_per_page_px;
|
|
||||||
let message = match event {
|
let message = match event {
|
||||||
MouseWindowClickEvent(button, _) => ClickEvent(button, cursor.to_untyped()),
|
MouseWindowClickEvent(button, _) => ClickEvent(button, cursor.to_untyped()),
|
||||||
MouseWindowMouseDownEvent(button, _) => MouseDownEvent(button, cursor.to_untyped()),
|
MouseWindowMouseDownEvent(button, _) => MouseDownEvent(button, cursor.to_untyped()),
|
||||||
|
@ -184,7 +168,7 @@ pub fn send_mouse_event(layer: Rc<Layer<CompositorData>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_mouse_move_event(layer: Rc<Layer<CompositorData>>,
|
pub fn send_mouse_move_event(layer: Rc<Layer<CompositorData>>,
|
||||||
cursor: TypedPoint2D<PagePx, f32>) {
|
cursor: TypedPoint2D<LayerPixel, f32>) {
|
||||||
let message = MouseMoveEvent(cursor.to_untyped());
|
let message = MouseMoveEvent(cursor.to_untyped());
|
||||||
let ScriptControlChan(ref chan) = layer.extra_data.borrow().pipeline.script_chan;
|
let ScriptControlChan(ref chan) = layer.extra_data.borrow().pipeline.script_chan;
|
||||||
let _ = chan.send_opt(SendEventMsg(layer.extra_data.borrow().pipeline.id.clone(), message));
|
let _ = chan.send_opt(SendEventMsg(layer.extra_data.borrow().pipeline.id.clone(), message));
|
||||||
|
|
12
ports/cef/Cargo.lock
generated
12
ports/cef/Cargo.lock
generated
|
@ -11,7 +11,7 @@ dependencies = [
|
||||||
"glfw 0.0.1 (git+https://github.com/servo/glfw-rs?ref=servo#955dbe919870b0536f79123232d87c0efe3c552e)",
|
"glfw 0.0.1 (git+https://github.com/servo/glfw-rs?ref=servo#955dbe919870b0536f79123232d87c0efe3c552e)",
|
||||||
"glut 0.0.1 (git+https://github.com/servo/rust-glut#01af0162ea0322ad1a40d6adb023a39813605949)",
|
"glut 0.0.1 (git+https://github.com/servo/rust-glut#01af0162ea0322ad1a40d6adb023a39813605949)",
|
||||||
"js 0.1.0 (git+https://github.com/servo/rust-mozjs#41fb0d80a5ed5614ca13a120cdb3281e599d4e04)",
|
"js 0.1.0 (git+https://github.com/servo/rust-mozjs#41fb0d80a5ed5614ca13a120cdb3281e599d4e04)",
|
||||||
"layers 0.1.0 (git+https://github.com/servo/rust-layers#9c2848823e24af586899947743704b1238f374c8)",
|
"layers 0.1.0 (git+https://github.com/servo/rust-layers#180d3ff2f28d239e32d01982c76be5c97d5763a8)",
|
||||||
"msg 0.0.1",
|
"msg 0.0.1",
|
||||||
"net 0.0.1",
|
"net 0.0.1",
|
||||||
"opengles 0.1.0 (git+https://github.com/servo/rust-opengles#6776e9c07feb149d34b087039ecf6b2c143e3afc)",
|
"opengles 0.1.0 (git+https://github.com/servo/rust-opengles#6776e9c07feb149d34b087039ecf6b2c143e3afc)",
|
||||||
|
@ -45,7 +45,7 @@ dependencies = [
|
||||||
"freetype 0.1.0 (git+https://github.com/servo/rust-freetype#0b03da276e4bdeae2300596dabc4ccb16733ad70)",
|
"freetype 0.1.0 (git+https://github.com/servo/rust-freetype#0b03da276e4bdeae2300596dabc4ccb16733ad70)",
|
||||||
"geom 0.1.0 (git+https://github.com/servo/rust-geom#90add8d65273c8a46aa16d73959e29a51d0c282d)",
|
"geom 0.1.0 (git+https://github.com/servo/rust-geom#90add8d65273c8a46aa16d73959e29a51d0c282d)",
|
||||||
"glfw 0.0.1 (git+https://github.com/servo/glfw-rs?ref=servo#955dbe919870b0536f79123232d87c0efe3c552e)",
|
"glfw 0.0.1 (git+https://github.com/servo/glfw-rs?ref=servo#955dbe919870b0536f79123232d87c0efe3c552e)",
|
||||||
"layers 0.1.0 (git+https://github.com/servo/rust-layers#9c2848823e24af586899947743704b1238f374c8)",
|
"layers 0.1.0 (git+https://github.com/servo/rust-layers#180d3ff2f28d239e32d01982c76be5c97d5763a8)",
|
||||||
"opengles 0.1.0 (git+https://github.com/servo/rust-opengles#6776e9c07feb149d34b087039ecf6b2c143e3afc)",
|
"opengles 0.1.0 (git+https://github.com/servo/rust-opengles#6776e9c07feb149d34b087039ecf6b2c143e3afc)",
|
||||||
"skia-sys 0.0.20130412 (git+https://github.com/servo/skia#6d696712962fd0d41120b7a414a48417da8e6a92)",
|
"skia-sys 0.0.20130412 (git+https://github.com/servo/skia#6d696712962fd0d41120b7a414a48417da8e6a92)",
|
||||||
"xlib 0.1.0 (git+https://github.com/servo/rust-xlib#581d4faddec5188d3c3ae5307dbea28aab90644c)",
|
"xlib 0.1.0 (git+https://github.com/servo/rust-xlib#581d4faddec5188d3c3ae5307dbea28aab90644c)",
|
||||||
|
@ -78,7 +78,7 @@ dependencies = [
|
||||||
"gfx 0.0.1",
|
"gfx 0.0.1",
|
||||||
"glfw 0.0.1 (git+https://github.com/servo/glfw-rs?ref=servo#955dbe919870b0536f79123232d87c0efe3c552e)",
|
"glfw 0.0.1 (git+https://github.com/servo/glfw-rs?ref=servo#955dbe919870b0536f79123232d87c0efe3c552e)",
|
||||||
"glut 0.0.1 (git+https://github.com/servo/rust-glut#01af0162ea0322ad1a40d6adb023a39813605949)",
|
"glut 0.0.1 (git+https://github.com/servo/rust-glut#01af0162ea0322ad1a40d6adb023a39813605949)",
|
||||||
"layers 0.1.0 (git+https://github.com/servo/rust-layers#9c2848823e24af586899947743704b1238f374c8)",
|
"layers 0.1.0 (git+https://github.com/servo/rust-layers#180d3ff2f28d239e32d01982c76be5c97d5763a8)",
|
||||||
"layout_traits 0.0.1",
|
"layout_traits 0.0.1",
|
||||||
"msg 0.0.1",
|
"msg 0.0.1",
|
||||||
"net 0.0.1",
|
"net 0.0.1",
|
||||||
|
@ -193,7 +193,7 @@ dependencies = [
|
||||||
"freetype 0.1.0 (git+https://github.com/servo/rust-freetype#0b03da276e4bdeae2300596dabc4ccb16733ad70)",
|
"freetype 0.1.0 (git+https://github.com/servo/rust-freetype#0b03da276e4bdeae2300596dabc4ccb16733ad70)",
|
||||||
"geom 0.1.0 (git+https://github.com/servo/rust-geom#90add8d65273c8a46aa16d73959e29a51d0c282d)",
|
"geom 0.1.0 (git+https://github.com/servo/rust-geom#90add8d65273c8a46aa16d73959e29a51d0c282d)",
|
||||||
"harfbuzz 0.1.0 (git+https://github.com/servo/rust-harfbuzz#ad520942cc17232e1a40cdd8a99c2905623d35f6)",
|
"harfbuzz 0.1.0 (git+https://github.com/servo/rust-harfbuzz#ad520942cc17232e1a40cdd8a99c2905623d35f6)",
|
||||||
"layers 0.1.0 (git+https://github.com/servo/rust-layers#9c2848823e24af586899947743704b1238f374c8)",
|
"layers 0.1.0 (git+https://github.com/servo/rust-layers#180d3ff2f28d239e32d01982c76be5c97d5763a8)",
|
||||||
"msg 0.0.1",
|
"msg 0.0.1",
|
||||||
"net 0.0.1",
|
"net 0.0.1",
|
||||||
"plugins 0.0.1",
|
"plugins 0.0.1",
|
||||||
|
@ -277,7 +277,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "layers"
|
name = "layers"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/servo/rust-layers#9c2848823e24af586899947743704b1238f374c8"
|
source = "git+https://github.com/servo/rust-layers#180d3ff2f28d239e32d01982c76be5c97d5763a8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"core_foundation 0.1.0 (git+https://github.com/servo/rust-core-foundation#166a601ff3e0fc3a64ca1a9090d02c8d4f22b61a)",
|
"core_foundation 0.1.0 (git+https://github.com/servo/rust-core-foundation#166a601ff3e0fc3a64ca1a9090d02c8d4f22b61a)",
|
||||||
"egl 0.1.0 (git+https://github.com/servo/rust-egl#88f2a13812ddbce2bf2317221663a61c31b3e220)",
|
"egl 0.1.0 (git+https://github.com/servo/rust-egl#88f2a13812ddbce2bf2317221663a61c31b3e220)",
|
||||||
|
@ -335,7 +335,7 @@ dependencies = [
|
||||||
"core_foundation 0.1.0 (git+https://github.com/servo/rust-core-foundation#166a601ff3e0fc3a64ca1a9090d02c8d4f22b61a)",
|
"core_foundation 0.1.0 (git+https://github.com/servo/rust-core-foundation#166a601ff3e0fc3a64ca1a9090d02c8d4f22b61a)",
|
||||||
"geom 0.1.0 (git+https://github.com/servo/rust-geom#90add8d65273c8a46aa16d73959e29a51d0c282d)",
|
"geom 0.1.0 (git+https://github.com/servo/rust-geom#90add8d65273c8a46aa16d73959e29a51d0c282d)",
|
||||||
"io_surface 0.1.0 (git+https://github.com/servo/rust-io-surface#7038341220bd7e86e21118fac2cbc6bd50890e47)",
|
"io_surface 0.1.0 (git+https://github.com/servo/rust-io-surface#7038341220bd7e86e21118fac2cbc6bd50890e47)",
|
||||||
"layers 0.1.0 (git+https://github.com/servo/rust-layers#9c2848823e24af586899947743704b1238f374c8)",
|
"layers 0.1.0 (git+https://github.com/servo/rust-layers#180d3ff2f28d239e32d01982c76be5c97d5763a8)",
|
||||||
"url 0.1.0 (git+https://github.com/servo/rust-url#29f70a47230c2aa736e263977247c786e0b2c243)",
|
"url 0.1.0 (git+https://github.com/servo/rust-url#29f70a47230c2aa736e263977247c786e0b2c243)",
|
||||||
"util 0.0.1",
|
"util 0.0.1",
|
||||||
]
|
]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue