Commit aa274831 authored by bokan's avatar bokan Committed by Commit bot

Move viewport scrolling logic into separate class

The viewport is made up of two scrolling layers now, the inner
and outer viewport scroll layers. These layers do not follow the
usual scroll bubbling logic since they're supposed to appear as
one viewport to the user (i.e. we should rail). Forcing it to
be scrolled via LayerTreeHostImpl::ScrollBy caused that method to
become increasingly complex.

This patch creates a Viewport class that allows LTHI::ScrollBy to
call Viewport::ScrollBy and scroll the viewport as a unit. I've
tried to simplify the logic in both these methods as much as I
could without changing behavior.

BUG=443724

Review URL: https://codereview.chromium.org/986443003

Cr-Commit-Position: refs/heads/master@{#322268}
parent 170b95cf
...@@ -196,6 +196,8 @@ component("cc") { ...@@ -196,6 +196,8 @@ component("cc") {
"layers/video_layer.h", "layers/video_layer.h",
"layers/video_layer_impl.cc", "layers/video_layer_impl.cc",
"layers/video_layer_impl.h", "layers/video_layer_impl.h",
"layers/viewport.cc",
"layers/viewport.h",
"output/begin_frame_args.cc", "output/begin_frame_args.cc",
"output/begin_frame_args.h", "output/begin_frame_args.h",
"output/bsp_tree.cc", "output/bsp_tree.cc",
......
...@@ -248,6 +248,8 @@ ...@@ -248,6 +248,8 @@
'layers/video_layer.h', 'layers/video_layer.h',
'layers/video_layer_impl.cc', 'layers/video_layer_impl.cc',
'layers/video_layer_impl.h', 'layers/video_layer_impl.h',
'layers/viewport.cc',
'layers/viewport.h',
'output/begin_frame_args.cc', 'output/begin_frame_args.cc',
'output/begin_frame_args.h', 'output/begin_frame_args.h',
'output/bsp_tree.cc', 'output/bsp_tree.cc',
......
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "cc/layers/viewport.h"
#include "base/logging.h"
#include "cc/input/top_controls_manager.h"
#include "cc/trees/layer_tree_host_impl.h"
#include "cc/trees/layer_tree_impl.h"
#include "ui/gfx/geometry/vector2d_conversions.h"
#include "ui/gfx/geometry/vector2d_f.h"
namespace cc {
// static
scoped_ptr<Viewport> Viewport::Create(
LayerTreeHostImpl* host_impl) {
return make_scoped_ptr(new Viewport(host_impl));
}
Viewport::Viewport(LayerTreeHostImpl* host_impl)
: host_impl_(host_impl) {
DCHECK(host_impl_);
}
Viewport::ScrollResult Viewport::ScrollBy(const gfx::Vector2dF& delta,
const gfx::Point& viewport_point,
bool is_wheel_scroll) {
gfx::Vector2dF content_delta = delta;
ScrollResult result;
if (ShouldTopControlsConsumeScroll(delta)) {
result.top_controls_applied_delta = ScrollTopControls(delta);
content_delta -= result.top_controls_applied_delta;
}
gfx::Vector2dF pending_content_delta = content_delta;
if (OuterScrollLayer()) {
pending_content_delta -= host_impl_->ScrollLayer(OuterScrollLayer(),
pending_content_delta,
viewport_point,
is_wheel_scroll);
}
// TODO(bokan): This shouldn't be needed but removing it causes subtle
// viewport movement during top controls manipulation.
if (!gfx::ToRoundedVector2d(pending_content_delta).IsZero()) {
pending_content_delta -= host_impl_->ScrollLayer(InnerScrollLayer(),
pending_content_delta,
viewport_point,
is_wheel_scroll);
result.unused_scroll_delta = AdjustOverscroll(pending_content_delta);
}
result.applied_delta = content_delta - pending_content_delta;
return result;
}
gfx::Vector2dF Viewport::ScrollTopControls(const gfx::Vector2dF& delta) {
gfx::Vector2dF excess_delta =
host_impl_->top_controls_manager()->ScrollBy(delta);
return delta - excess_delta;
}
bool Viewport::ShouldTopControlsConsumeScroll(
const gfx::Vector2dF& scroll_delta) const {
// Always consume if it's in the direction to show the top controls.
if (scroll_delta.y() < 0)
return true;
if (TotalScrollOffset().y() < MaxTotalScrollOffset().y())
return true;
return false;
}
gfx::Vector2dF Viewport::AdjustOverscroll(const gfx::Vector2dF& delta) const {
const float kEpsilon = 0.1f;
gfx::Vector2dF adjusted = delta;
if (std::abs(adjusted.x()) < kEpsilon)
adjusted.set_x(0.0f);
if (std::abs(adjusted.y()) < kEpsilon)
adjusted.set_y(0.0f);
// Disable overscroll on axes which are impossible to scroll.
if (host_impl_->settings().report_overscroll_only_for_scrollable_axes) {
if (std::abs(MaxTotalScrollOffset().x()) <= kEpsilon ||
!InnerScrollLayer()->user_scrollable_horizontal())
adjusted.set_x(0.0f);
if (std::abs(MaxTotalScrollOffset().y()) <= kEpsilon ||
!InnerScrollLayer()->user_scrollable_vertical())
adjusted.set_y(0.0f);
}
return adjusted;
}
gfx::ScrollOffset Viewport::MaxTotalScrollOffset() const {
gfx::ScrollOffset offset;
offset += InnerScrollLayer()->MaxScrollOffset();
if (OuterScrollLayer())
offset += OuterScrollLayer()->MaxScrollOffset();
return offset;
}
gfx::ScrollOffset Viewport::TotalScrollOffset() const {
gfx::ScrollOffset offset;
offset += InnerScrollLayer()->CurrentScrollOffset();
if (OuterScrollLayer())
offset += OuterScrollLayer()->CurrentScrollOffset();
return offset;
}
LayerImpl* Viewport::InnerScrollLayer() const {
return host_impl_->InnerViewportScrollLayer();
}
LayerImpl* Viewport::OuterScrollLayer() const {
return host_impl_->OuterViewportScrollLayer();
}
} // namespace cc
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CC_LAYERS_VIEWPORT_H_
#define CC_LAYERS_VIEWPORT_H_
#include "base/memory/scoped_ptr.h"
#include "cc/layers/layer_impl.h"
#include "ui/gfx/geometry/vector2d_f.h"
namespace cc {
class LayerTreeHostImpl;
// Encapsulates gesture handling logic on the viewport layers. The "viewport"
// is made up of two scrolling layers, the inner viewport (visual) and the
// outer viewport (layout) scroll layers. These layers have different scroll
// bubbling behavior from the rest of the layer tree which is encoded in this
// class.
class CC_EXPORT Viewport {
public:
struct ScrollResult {
gfx::Vector2dF applied_delta;
gfx::Vector2dF unused_scroll_delta;
gfx::Vector2dF top_controls_applied_delta;
};
static scoped_ptr<Viewport> Create(LayerTreeHostImpl* host_impl);
// Scrolls the viewport, applying the unique bubbling between the inner and
// outer viewport. Scrolls can be consumed by top controls.
ScrollResult ScrollBy(const gfx::Vector2dF& delta,
const gfx::Point& viewport_point,
bool is_wheel_scroll);
private:
explicit Viewport(LayerTreeHostImpl* host_impl);
bool ShouldTopControlsConsumeScroll(const gfx::Vector2dF& scroll_delta) const;
gfx::Vector2dF AdjustOverscroll(const gfx::Vector2dF& delta) const;
// Sends the delta to the top controls, returns the amount applied.
gfx::Vector2dF ScrollTopControls(const gfx::Vector2dF& delta);
gfx::ScrollOffset MaxTotalScrollOffset() const;
gfx::ScrollOffset TotalScrollOffset() const;
LayerImpl* InnerScrollLayer() const;
LayerImpl* OuterScrollLayer() const;
LayerTreeHostImpl* host_impl_;
DISALLOW_COPY_AND_ASSIGN(Viewport);
};
} // namespace cc
#endif // CC_LAYERS_VIEWPORT_H_
...@@ -39,6 +39,7 @@ ...@@ -39,6 +39,7 @@
#include "cc/layers/painted_scrollbar_layer_impl.h" #include "cc/layers/painted_scrollbar_layer_impl.h"
#include "cc/layers/render_surface_impl.h" #include "cc/layers/render_surface_impl.h"
#include "cc/layers/scrollbar_layer_impl_base.h" #include "cc/layers/scrollbar_layer_impl_base.h"
#include "cc/layers/viewport.h"
#include "cc/output/compositor_frame_metadata.h" #include "cc/output/compositor_frame_metadata.h"
#include "cc/output/copy_output_request.h" #include "cc/output/copy_output_request.h"
#include "cc/output/delegating_renderer.h" #include "cc/output/delegating_renderer.h"
...@@ -240,6 +241,8 @@ LayerTreeHostImpl::LayerTreeHostImpl( ...@@ -240,6 +241,8 @@ LayerTreeHostImpl::LayerTreeHostImpl(
LayerTreeImpl::create(this, new SyncedProperty<ScaleGroup>(), LayerTreeImpl::create(this, new SyncedProperty<ScaleGroup>(),
new SyncedTopControls, new SyncedElasticOverscroll); new SyncedTopControls, new SyncedElasticOverscroll);
viewport_ = Viewport::Create(this);
TRACE_EVENT_OBJECT_CREATED_WITH_ID( TRACE_EVENT_OBJECT_CREATED_WITH_ID(
TRACE_DISABLED_BY_DEFAULT("cc.debug"), "cc::LayerTreeHostImpl", id_); TRACE_DISABLED_BY_DEFAULT("cc.debug"), "cc::LayerTreeHostImpl", id_);
...@@ -2477,7 +2480,6 @@ InputHandler::ScrollStatus LayerTreeHostImpl::ScrollAnimated( ...@@ -2477,7 +2480,6 @@ InputHandler::ScrollStatus LayerTreeHostImpl::ScrollAnimated(
gfx::Vector2dF LayerTreeHostImpl::ScrollLayerWithViewportSpaceDelta( gfx::Vector2dF LayerTreeHostImpl::ScrollLayerWithViewportSpaceDelta(
LayerImpl* layer_impl, LayerImpl* layer_impl,
float scale_from_viewport_to_screen_space,
const gfx::PointF& viewport_point, const gfx::PointF& viewport_point,
const gfx::Vector2dF& viewport_delta) { const gfx::Vector2dF& viewport_delta) {
// Layers with non-invertible screen space transforms should not have passed // Layers with non-invertible screen space transforms should not have passed
...@@ -2491,6 +2493,7 @@ gfx::Vector2dF LayerTreeHostImpl::ScrollLayerWithViewportSpaceDelta( ...@@ -2491,6 +2493,7 @@ gfx::Vector2dF LayerTreeHostImpl::ScrollLayerWithViewportSpaceDelta(
// layers, we may need to explicitly handle uninvertible transforms here. // layers, we may need to explicitly handle uninvertible transforms here.
DCHECK(did_invert); DCHECK(did_invert);
float scale_from_viewport_to_screen_space = device_scale_factor_;
gfx::PointF screen_space_point = gfx::PointF screen_space_point =
gfx::ScalePoint(viewport_point, scale_from_viewport_to_screen_space); gfx::ScalePoint(viewport_point, scale_from_viewport_to_screen_space);
...@@ -2565,19 +2568,25 @@ static gfx::Vector2dF ScrollLayerWithLocalDelta( ...@@ -2565,19 +2568,25 @@ static gfx::Vector2dF ScrollLayerWithLocalDelta(
return gfx::Vector2dF(scrolled.x(), scrolled.y()); return gfx::Vector2dF(scrolled.x(), scrolled.y());
} }
bool LayerTreeHostImpl::ShouldTopControlsConsumeScroll( gfx::Vector2dF LayerTreeHostImpl::ScrollLayer(LayerImpl* layer_impl,
const gfx::Vector2dF& scroll_delta) const { const gfx::Vector2dF& delta,
DCHECK(CurrentlyScrollingLayer()); const gfx::Point& viewport_point,
bool is_wheel_scroll) {
// Always consume if it's in the direction to show the top controls. // Gesture events need to be transformed from viewport coordinates to
if (scroll_delta.y() < 0) // local layer coordinates so that the scrolling contents exactly follow
return true; // the user's finger. In contrast, wheel events represent a fixed amount
// of scrolling so we can just apply them directly, but the page scale
if (active_tree()->TotalScrollOffset().y() < // factor is applied to the scroll delta.
active_tree()->TotalMaxScrollOffset().y()) if (is_wheel_scroll) {
return true; float scale_factor = active_tree()->current_page_scale_factor();
return ScrollLayerWithLocalDelta(layer_impl,
delta,
scale_factor);
}
return false; return ScrollLayerWithViewportSpaceDelta(layer_impl,
viewport_point,
delta);
} }
InputHandlerScrollResult LayerTreeHostImpl::ScrollBy( InputHandlerScrollResult LayerTreeHostImpl::ScrollBy(
...@@ -2593,130 +2602,71 @@ InputHandlerScrollResult LayerTreeHostImpl::ScrollBy( ...@@ -2593,130 +2602,71 @@ InputHandlerScrollResult LayerTreeHostImpl::ScrollBy(
bool did_scroll_y = false; bool did_scroll_y = false;
bool did_scroll_top_controls = false; bool did_scroll_top_controls = false;
bool consume_by_top_controls = ShouldTopControlsConsumeScroll(scroll_delta); for (LayerImpl* layer_impl = CurrentlyScrollingLayer();
// There's an edge case where the outer viewport isn't scrollable when the
// scroll starts, however, as the top controls show the outer viewport becomes
// scrollable. Therefore, always try scrolling the outer viewport before the
// inner.
// TODO(bokan): Move the top controls logic out of the loop since the scroll
// that causes the outer viewport to become scrollable will still be applied
// to the inner viewport.
LayerImpl* start_layer = CurrentlyScrollingLayer();
if (start_layer == InnerViewportScrollLayer() && OuterViewportScrollLayer())
start_layer = OuterViewportScrollLayer();
for (LayerImpl* layer_impl = start_layer;
layer_impl; layer_impl;
layer_impl = layer_impl->parent()) { layer_impl = layer_impl->parent()) {
if (!layer_impl->scrollable()) // Skip the outer viewport scroll layer so that we try to scroll the
// viewport only once. i.e. The inner viewport layer represents the
// viewport.
if (!layer_impl->scrollable() || layer_impl == OuterViewportScrollLayer())
continue; continue;
if (layer_impl == InnerViewportScrollLayer() ||
layer_impl == OuterViewportScrollLayer()) {
if (consume_by_top_controls) {
gfx::Vector2dF excess_delta =
top_controls_manager_->ScrollBy(pending_delta);
gfx::Vector2dF applied_delta = pending_delta - excess_delta;
pending_delta = excess_delta;
// Force updating of vertical adjust values if needed.
if (applied_delta.y() != 0)
did_scroll_top_controls = true;
}
// Track root layer deltas for reporting overscroll.
if (layer_impl == InnerViewportScrollLayer())
unused_root_delta = pending_delta;
}
gfx::Vector2dF applied_delta; gfx::Vector2dF applied_delta;
// Gesture events need to be transformed from viewport coordinates to local if (layer_impl == InnerViewportScrollLayer()) {
// layer coordinates so that the scrolling contents exactly follow the Viewport::ScrollResult result = viewport()->ScrollBy(pending_delta,
// user's finger. In contrast, wheel events represent a fixed amount of viewport_point,
// scrolling so we can just apply them directly, but the page scale factor wheel_scrolling_);
// is applied to the scroll delta. applied_delta = result.applied_delta;
if (!wheel_scrolling_) { unused_root_delta = result.unused_scroll_delta;
float scale_from_viewport_to_screen_space = device_scale_factor_; did_scroll_top_controls = result.top_controls_applied_delta.y() != 0;
applied_delta =
ScrollLayerWithViewportSpaceDelta(layer_impl,
scale_from_viewport_to_screen_space,
viewport_point, pending_delta);
} else { } else {
applied_delta = ScrollLayerWithLocalDelta( applied_delta = ScrollLayer(layer_impl,
layer_impl, pending_delta, active_tree_->current_page_scale_factor()); pending_delta,
viewport_point,
wheel_scrolling_);
} }
const float kEpsilon = 0.1f;
if (layer_impl == InnerViewportScrollLayer()) {
unused_root_delta.Subtract(applied_delta);
if (std::abs(unused_root_delta.x()) < kEpsilon)
unused_root_delta.set_x(0.0f);
if (std::abs(unused_root_delta.y()) < kEpsilon)
unused_root_delta.set_y(0.0f);
// Disable overscroll on axes which is impossible to scroll.
if (settings_.report_overscroll_only_for_scrollable_axes) {
if (std::abs(active_tree_->TotalMaxScrollOffset().x()) <= kEpsilon ||
!layer_impl->user_scrollable_horizontal())
unused_root_delta.set_x(0.0f);
if (std::abs(active_tree_->TotalMaxScrollOffset().y()) <= kEpsilon ||
!layer_impl->user_scrollable_vertical())
unused_root_delta.set_y(0.0f);
}
}
// Scrolls should bubble perfectly between the outer and inner viewports.
bool allow_unrestricted_bubbling_for_current_layer =
layer_impl == OuterViewportScrollLayer();
bool allow_bubbling_for_current_layer =
allow_unrestricted_bubbling_for_current_layer || should_bubble_scrolls_;
// If the layer wasn't able to move, try the next one in the hierarchy. // If the layer wasn't able to move, try the next one in the hierarchy.
const float kEpsilon = 0.1f;
bool did_move_layer_x = std::abs(applied_delta.x()) > kEpsilon; bool did_move_layer_x = std::abs(applied_delta.x()) > kEpsilon;
bool did_move_layer_y = std::abs(applied_delta.y()) > kEpsilon; bool did_move_layer_y = std::abs(applied_delta.y()) > kEpsilon;
did_scroll_x |= did_move_layer_x; did_scroll_x |= did_move_layer_x;
did_scroll_y |= did_move_layer_y; did_scroll_y |= did_move_layer_y;
if (!did_move_layer_x && !did_move_layer_y) {
if (allow_bubbling_for_current_layer || !did_lock_scrolling_layer_)
continue;
else
break;
}
if (did_move_layer_x || did_move_layer_y) {
did_lock_scrolling_layer_ = true; did_lock_scrolling_layer_ = true;
// When scrolls are allowed to bubble, it's important that the original // When scrolls are allowed to bubble, it's important that the original
// scrolling layer be preserved. This ensures that, after a scroll bubbles, // scrolling layer be preserved. This ensures that, after a scroll
// the user can reverse scroll directions and immediately resume scrolling // bubbles, the user can reverse scroll directions and immediately resume
// the original layer that scrolled. // scrolling the original layer that scrolled.
if (!should_bubble_scrolls_) if (!should_bubble_scrolls_) {
active_tree_->SetCurrentlyScrollingLayer(layer_impl); active_tree_->SetCurrentlyScrollingLayer(layer_impl);
if (!allow_bubbling_for_current_layer)
break; break;
}
if (allow_unrestricted_bubbling_for_current_layer) {
pending_delta -= applied_delta;
} else {
// If the applied delta is within 45 degrees of the input delta, bail out // If the applied delta is within 45 degrees of the input delta, bail out
// to make it easier to scroll just one layer in one direction without // to make it easier to scroll just one layer in one direction without
// affecting any of its parents. // affecting any of its parents.
float angle_threshold = 45; float angle_threshold = 45;
if (MathUtil::SmallestAngleBetweenVectors(applied_delta, pending_delta) < if (MathUtil::SmallestAngleBetweenVectors(applied_delta, pending_delta) <
angle_threshold) { angle_threshold)
pending_delta = gfx::Vector2dF();
break; break;
}
// Allow further movement only on an axis perpendicular to the direction // Allow further movement only on an axis perpendicular to the direction
// in which the layer moved. // in which the layer moved.
gfx::Vector2dF perpendicular_axis(-applied_delta.y(), applied_delta.x()); gfx::Vector2dF perpendicular_axis(-applied_delta.y(), applied_delta.x());
pending_delta = pending_delta =
MathUtil::ProjectVector(pending_delta, perpendicular_axis); MathUtil::ProjectVector(pending_delta, perpendicular_axis);
}
if (gfx::ToRoundedVector2d(pending_delta).IsZero()) if (gfx::ToRoundedVector2d(pending_delta).IsZero())
break; break;
} }
if (!should_bubble_scrolls_ && did_lock_scrolling_layer_)
break;
}
bool did_scroll_content = did_scroll_x || did_scroll_y; bool did_scroll_content = did_scroll_x || did_scroll_y;
if (did_scroll_content) { if (did_scroll_content) {
// If we are scrolling with an active scroll handler, forward latency // If we are scrolling with an active scroll handler, forward latency
......
...@@ -73,6 +73,7 @@ class TopControlsManager; ...@@ -73,6 +73,7 @@ class TopControlsManager;
class UIResourceBitmap; class UIResourceBitmap;
class UIResourceRequest; class UIResourceRequest;
struct ScrollAndScaleSet; struct ScrollAndScaleSet;
class Viewport;
enum class GpuRasterizationStatus { enum class GpuRasterizationStatus {
ON, ON,
...@@ -524,6 +525,11 @@ class CC_EXPORT LayerTreeHostImpl ...@@ -524,6 +525,11 @@ class CC_EXPORT LayerTreeHostImpl
return frame_timing_tracker_.get(); return frame_timing_tracker_.get();
} }
gfx::Vector2dF ScrollLayer(LayerImpl* layer_impl,
const gfx::Vector2dF& delta,
const gfx::Point& viewport_point,
bool is_wheel_scroll);
protected: protected:
LayerTreeHostImpl( LayerTreeHostImpl(
const LayerTreeSettings& settings, const LayerTreeSettings& settings,
...@@ -546,6 +552,11 @@ class CC_EXPORT LayerTreeHostImpl ...@@ -546,6 +552,11 @@ class CC_EXPORT LayerTreeHostImpl
Proxy* proxy_; Proxy* proxy_;
private: private:
gfx::Vector2dF ScrollLayerWithViewportSpaceDelta(
LayerImpl* layer_impl,
const gfx::PointF& viewport_point,
const gfx::Vector2dF& viewport_delta);
void CreateAndSetRenderer(); void CreateAndSetRenderer();
void CreateAndSetTileManager(); void CreateAndSetTileManager();
void DestroyTileManager(); void DestroyTileManager();
...@@ -555,6 +566,8 @@ class CC_EXPORT LayerTreeHostImpl ...@@ -555,6 +566,8 @@ class CC_EXPORT LayerTreeHostImpl
bool IsSynchronousSingleThreaded() const; bool IsSynchronousSingleThreaded() const;
Viewport* viewport() { return viewport_.get(); }
// Scroll by preferring to move the outer viewport first, only moving the // Scroll by preferring to move the outer viewport first, only moving the
// inner if the outer is at its scroll extents. // inner if the outer is at its scroll extents.
void ScrollViewportBy(gfx::Vector2dF scroll_delta); void ScrollViewportBy(gfx::Vector2dF scroll_delta);
...@@ -565,14 +578,6 @@ class CC_EXPORT LayerTreeHostImpl ...@@ -565,14 +578,6 @@ class CC_EXPORT LayerTreeHostImpl
void AnimateScrollbars(base::TimeTicks monotonic_time); void AnimateScrollbars(base::TimeTicks monotonic_time);
void AnimateTopControls(base::TimeTicks monotonic_time); void AnimateTopControls(base::TimeTicks monotonic_time);
bool ShouldTopControlsConsumeScroll(const gfx::Vector2dF& scroll_delta) const;
gfx::Vector2dF ScrollLayerWithViewportSpaceDelta(
LayerImpl* layer_impl,
float scale_from_viewport_to_screen_space,
const gfx::PointF& viewport_point,
const gfx::Vector2dF& viewport_delta);
void TrackDamageForAllSurfaces( void TrackDamageForAllSurfaces(
LayerImpl* root_draw_layer, LayerImpl* root_draw_layer,
const LayerImplList& render_surface_layer_list); const LayerImplList& render_surface_layer_list);
...@@ -747,6 +752,8 @@ class CC_EXPORT LayerTreeHostImpl ...@@ -747,6 +752,8 @@ class CC_EXPORT LayerTreeHostImpl
scoped_ptr<FrameTimingTracker> frame_timing_tracker_; scoped_ptr<FrameTimingTracker> frame_timing_tracker_;
scoped_ptr<Viewport> viewport_;
DISALLOW_COPY_AND_ASSIGN(LayerTreeHostImpl); DISALLOW_COPY_AND_ASSIGN(LayerTreeHostImpl);
}; };
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment