未验证 提交 ebb5b909 编写于 作者: C Chris Yang 提交者: GitHub

IOS Platform view transform/clipping (#9075)

上级 13145e90
......@@ -71,6 +71,7 @@ FILE: ../../../flutter/flow/layers/transform_layer.h
FILE: ../../../flutter/flow/matrix_decomposition.cc
FILE: ../../../flutter/flow/matrix_decomposition.h
FILE: ../../../flutter/flow/matrix_decomposition_unittests.cc
FILE: ../../../flutter/flow/mutators_stack_unittests.cc
FILE: ../../../flutter/flow/paint_utils.cc
FILE: ../../../flutter/flow/paint_utils.h
FILE: ../../../flutter/flow/raster_cache.cc
......
......@@ -114,6 +114,7 @@ executable("flow_unittests") {
"layers/performance_overlay_layer_unittests.cc",
"layers/physical_shape_layer_unittests.cc",
"matrix_decomposition_unittests.cc",
"mutators_stack_unittests.cc",
"raster_cache_unittests.cc",
]
......
......@@ -9,4 +9,34 @@ namespace flutter {
bool ExternalViewEmbedder::SubmitFrame(GrContext* context) {
return false;
};
void MutatorsStack::pushClipRect(const SkRect& rect) {
std::shared_ptr<Mutator> element = std::make_shared<Mutator>(rect);
vector_.push_back(element);
};
void MutatorsStack::pushClipRRect(const SkRRect& rrect) {
std::shared_ptr<Mutator> element = std::make_shared<Mutator>(rrect);
vector_.push_back(element);
};
void MutatorsStack::pushTransform(const SkMatrix& matrix) {
std::shared_ptr<Mutator> element = std::make_shared<Mutator>(matrix);
vector_.push_back(element);
};
void MutatorsStack::pop() {
vector_.pop_back();
};
const std::vector<std::shared_ptr<Mutator>>::const_reverse_iterator
MutatorsStack::top() const {
return vector_.rend();
};
const std::vector<std::shared_ptr<Mutator>>::const_reverse_iterator
MutatorsStack::bottom() const {
return vector_.rbegin();
};
} // namespace flutter
......@@ -9,18 +9,169 @@
#include "flutter/fml/memory/ref_counted.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkPath.h"
#include "third_party/skia/include/core/SkPoint.h"
#include "third_party/skia/include/core/SkRRect.h"
#include "third_party/skia/include/core/SkRect.h"
#include "third_party/skia/include/core/SkSize.h"
namespace flutter {
enum MutatorType { clip_rect, clip_rrect, clip_path, transform };
// Stores mutation information like clipping or transform.
//
// The `type` indicates the type of the mutation: clip_rect, transform and etc.
// Each `type` is paired with an object that supports the mutation. For example,
// if the `type` is clip_rect, `rect()` is used the represent the rect to be
// clipped. One mutation object must only contain one type of mutation.
class Mutator {
public:
Mutator(const Mutator& other) {
type_ = other.type_;
switch (other.type_) {
case clip_rect:
rect_ = other.rect_;
break;
case clip_rrect:
rrect_ = other.rrect_;
break;
case clip_path:
path_ = new SkPath(*other.path_);
break;
case transform:
matrix_ = other.matrix_;
break;
default:
break;
}
}
explicit Mutator(const SkRect& rect) : type_(clip_rect), rect_(rect) {}
explicit Mutator(const SkRRect& rrect) : type_(clip_rrect), rrect_(rrect) {}
explicit Mutator(const SkPath& path)
: type_(clip_path), path_(new SkPath(path)) {}
explicit Mutator(const SkMatrix& matrix)
: type_(transform), matrix_(matrix) {}
const MutatorType& type() const { return type_; }
const SkRect& rect() const { return rect_; }
const SkRRect& rrect() const { return rrect_; }
const SkPath& path() const { return *path_; }
const SkMatrix& matrix() const { return matrix_; }
bool operator==(const Mutator& other) const {
if (type_ != other.type_) {
return false;
}
if (type_ == clip_rect && rect_ == other.rect_) {
return true;
}
if (type_ == clip_rrect && rrect_ == other.rrect_) {
return true;
}
if (type_ == clip_path && *path_ == *other.path_) {
return true;
}
if (type_ == transform && matrix_ == other.matrix_) {
return true;
}
return false;
}
bool operator!=(const Mutator& other) const { return !operator==(other); }
bool isClipType() {
return type_ == clip_rect || type_ == clip_rrect || type_ == clip_path;
}
~Mutator() {
if (type_ == clip_path) {
delete path_;
}
};
private:
MutatorType type_;
union {
SkRect rect_;
SkRRect rrect_;
SkMatrix matrix_;
SkPath* path_;
};
}; // Mutator
// A stack of mutators that can be applied to an embedded platform view.
//
// The stack may include mutators like transforms and clips, each mutator
// applies to all the mutators that are below it in the stack and to the
// embedded view.
//
// For example consider the following stack: [T1, T2, T3], where T1 is the top
// of the stack and T3 is the bottom of the stack. Applying this mutators stack
// to a platform view P1 will result in T1(T2(T2(P1))).
class MutatorsStack {
public:
MutatorsStack() = default;
void pushClipRect(const SkRect& rect);
void pushClipRRect(const SkRRect& rrect);
void pushClipPath(const SkPath& path);
void pushTransform(const SkMatrix& matrix);
// Removes the `Mutator` on the top of the stack
// and destroys it.
void pop();
// Returns an iterator pointing to the top of the stack.
const std::vector<std::shared_ptr<Mutator>>::const_reverse_iterator top()
const;
// Returns an iterator pointing to the bottom of the stack.
const std::vector<std::shared_ptr<Mutator>>::const_reverse_iterator bottom()
const;
bool operator==(const MutatorsStack& other) const {
if (vector_.size() != other.vector_.size()) {
return false;
}
for (size_t i = 0; i < vector_.size(); i++) {
if (*vector_[i] != *other.vector_[i]) {
return false;
}
}
return true;
}
bool operator!=(const MutatorsStack& other) const {
return !operator==(other);
}
private:
std::vector<std::shared_ptr<Mutator>> vector_;
}; // MutatorsStack
class EmbeddedViewParams {
public:
EmbeddedViewParams() = default;
EmbeddedViewParams(const EmbeddedViewParams& other) {
offsetPixels = other.offsetPixels;
sizePoints = other.sizePoints;
mutatorsStack = other.mutatorsStack;
};
SkPoint offsetPixels;
SkSize sizePoints;
MutatorsStack mutatorsStack;
bool operator==(const EmbeddedViewParams& other) const {
return offsetPixels == other.offsetPixels && sizePoints == other.sizePoints;
return offsetPixels == other.offsetPixels &&
sizePoints == other.sizePoints &&
mutatorsStack == other.mutatorsStack;
}
};
......@@ -28,6 +179,8 @@ class EmbeddedViewParams {
// in this case ExternalViewEmbedder is a reference to the
// FlutterPlatformViewsController which is owned by FlutterViewController.
class ExternalViewEmbedder {
// TODO(cyanglaz): Make embedder own the `EmbeddedViewParams`.
public:
ExternalViewEmbedder() = default;
......@@ -46,7 +199,8 @@ class ExternalViewEmbedder {
virtual ~ExternalViewEmbedder() = default;
FML_DISALLOW_COPY_AND_ASSIGN(ExternalViewEmbedder);
};
}; // ExternalViewEmbedder
} // namespace flutter
......
......@@ -50,6 +50,8 @@ void ClipRectLayer::Paint(PaintContext& context) const {
SkAutoCanvasRestore save(context.internal_nodes_canvas, true);
context.internal_nodes_canvas->clipRect(clip_rect_,
clip_behavior_ != Clip::hardEdge);
context.mutators_stack.pushClipRect(clip_rect_);
if (clip_behavior_ == Clip::antiAliasWithSaveLayer) {
context.internal_nodes_canvas->saveLayer(clip_rect_, nullptr);
}
......@@ -57,6 +59,7 @@ void ClipRectLayer::Paint(PaintContext& context) const {
if (clip_behavior_ == Clip::antiAliasWithSaveLayer) {
context.internal_nodes_canvas->restore();
}
context.mutators_stack.pop();
}
} // namespace flutter
......@@ -58,6 +58,8 @@ void ClipRRectLayer::Paint(PaintContext& context) const {
SkAutoCanvasRestore save(context.internal_nodes_canvas, true);
context.internal_nodes_canvas->clipRRect(clip_rrect_,
clip_behavior_ != Clip::hardEdge);
context.mutators_stack.pushClipRRect(clip_rrect_);
if (clip_behavior_ == Clip::antiAliasWithSaveLayer) {
context.internal_nodes_canvas->saveLayer(paint_bounds(), nullptr);
}
......@@ -65,6 +67,7 @@ void ClipRRectLayer::Paint(PaintContext& context) const {
if (clip_behavior_ == Clip::antiAliasWithSaveLayer) {
context.internal_nodes_canvas->restore();
}
context.mutators_stack.pop();
}
} // namespace flutter
......@@ -83,6 +83,7 @@ class Layer {
SkCanvas* leaf_nodes_canvas;
GrContext* gr_context;
ExternalViewEmbedder* view_embedder;
MutatorsStack& mutators_stack;
const Stopwatch& raster_time;
const Stopwatch& ui_time;
TextureRegistry& texture_registry;
......
......@@ -83,11 +83,13 @@ void LayerTree::Paint(CompositorContext::ScopedFrame& frame,
}
}
MutatorsStack stack;
Layer::PaintContext context = {
(SkCanvas*)&internal_nodes_canvas,
frame.canvas(),
frame.gr_context(),
frame.view_embedder(),
stack,
frame.context().raster_time(),
frame.context().ui_time(),
frame.context().texture_registry(),
......@@ -108,6 +110,7 @@ sk_sp<SkPicture> LayerTree::Flatten(const SkRect& bounds) {
return nullptr;
}
MutatorsStack unused_stack;
const Stopwatch unused_stopwatch;
TextureRegistry unused_texture_registry;
SkMatrix root_surface_transformation;
......@@ -135,6 +138,7 @@ sk_sp<SkPicture> LayerTree::Flatten(const SkRect& bounds) {
canvas, // canvas
nullptr,
nullptr,
unused_stack,
unused_stopwatch, // frame time (dont care)
unused_stopwatch, // engine time (dont care)
unused_texture_registry, // texture registry (not supported)
......
......@@ -47,10 +47,13 @@ TEST(PerformanceOverlayLayer, Gold) {
ASSERT_TRUE(surface != nullptr);
flutter::TextureRegistry unused_texture_registry;
flutter::MutatorsStack unused_stack;
flutter::Layer::PaintContext paintContext = {
nullptr, surface->getCanvas(), nullptr, nullptr, mock_stopwatch,
mock_stopwatch, unused_texture_registry, nullptr, false};
nullptr, surface->getCanvas(),
nullptr, nullptr,
unused_stack, mock_stopwatch,
mock_stopwatch, unused_texture_registry,
nullptr, false};
// Specify font file to ensure the same font across different operation
// systems.
......
......@@ -37,6 +37,7 @@ void PlatformViewLayer::Paint(PaintContext& context) const {
params.offsetPixels =
SkPoint::Make(transform.getTranslateX(), transform.getTranslateY());
params.sizePoints = size_;
params.mutatorsStack = context.mutators_stack;
SkCanvas* canvas =
context.view_embedder->CompositeEmbeddedView(view_id_, params);
......
......@@ -66,7 +66,10 @@ void TransformLayer::Paint(PaintContext& context) const {
SkAutoCanvasRestore save(context.internal_nodes_canvas, true);
context.internal_nodes_canvas->concat(transform_);
context.mutators_stack.pushTransform(transform_);
PaintChildren(context);
context.mutators_stack.pop();
}
} // namespace flutter
// Copyright 2013 The Flutter 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 "flutter/flow/embedded_views.h"
#include "gtest/gtest.h"
TEST(MutatorsStack, Initialization) {
flutter::MutatorsStack stack;
ASSERT_TRUE(true);
}
TEST(MutatorsStack, CopyConstructor) {
flutter::MutatorsStack stack;
SkRRect rrect;
SkRect rect;
stack.pushClipRect(rect);
stack.pushClipRRect(rrect);
flutter::MutatorsStack copy = flutter::MutatorsStack(stack);
ASSERT_TRUE(copy == stack);
}
TEST(MutatorsStack, PushClipRect) {
flutter::MutatorsStack stack;
SkRect rect;
stack.pushClipRect(rect);
auto iter = stack.bottom();
ASSERT_TRUE(iter->get()->type() == flutter::MutatorType::clip_rect);
ASSERT_TRUE(iter->get()->rect() == rect);
}
TEST(MutatorsStack, PushClipRRect) {
flutter::MutatorsStack stack;
SkRRect rrect;
stack.pushClipRRect(rrect);
auto iter = stack.bottom();
ASSERT_TRUE(iter->get()->type() == flutter::MutatorType::clip_rrect);
ASSERT_TRUE(iter->get()->rrect() == rrect);
}
TEST(MutatorsStack, PushTransform) {
flutter::MutatorsStack stack;
SkMatrix matrix;
stack.pushTransform(matrix);
auto iter = stack.bottom();
ASSERT_TRUE(iter->get()->type() == flutter::MutatorType::transform);
ASSERT_TRUE(iter->get()->matrix() == matrix);
}
TEST(MutatorsStack, Pop) {
flutter::MutatorsStack stack;
SkMatrix matrix;
stack.pushTransform(matrix);
stack.pop();
auto iter = stack.bottom();
ASSERT_TRUE(iter == stack.top());
}
TEST(MutatorsStack, Traversal) {
flutter::MutatorsStack stack;
SkMatrix matrix;
stack.pushTransform(matrix);
SkRect rect;
stack.pushClipRect(rect);
SkRRect rrect;
stack.pushClipRRect(rrect);
auto iter = stack.bottom();
int index = 0;
while (iter != stack.top()) {
switch (index) {
case 0:
ASSERT_TRUE(iter->get()->type() == flutter::MutatorType::clip_rrect);
ASSERT_TRUE(iter->get()->rrect() == rrect);
break;
case 1:
ASSERT_TRUE(iter->get()->type() == flutter::MutatorType::clip_rect);
ASSERT_TRUE(iter->get()->rect() == rect);
break;
case 2:
ASSERT_TRUE(iter->get()->type() == flutter::MutatorType::transform);
ASSERT_TRUE(iter->get()->matrix() == matrix);
break;
default:
break;
}
++iter;
++index;
}
}
TEST(MutatorsStack, Equality) {
flutter::MutatorsStack stack;
SkMatrix matrix = SkMatrix::MakeScale(1, 1);
stack.pushTransform(matrix);
SkRect rect = SkRect::MakeEmpty();
stack.pushClipRect(rect);
SkRRect rrect = SkRRect::MakeEmpty();
stack.pushClipRRect(rrect);
flutter::MutatorsStack stackOther;
SkMatrix matrixOther = SkMatrix::MakeScale(1, 1);
stackOther.pushTransform(matrixOther);
SkRect rectOther = SkRect::MakeEmpty();
stackOther.pushClipRect(rectOther);
SkRRect rrectOther = SkRRect::MakeEmpty();
stackOther.pushClipRRect(rrectOther);
ASSERT_TRUE(stack == stackOther);
}
TEST(Mutator, Initialization) {
SkRect rect = SkRect::MakeEmpty();
flutter::Mutator mutator = flutter::Mutator(rect);
ASSERT_TRUE(mutator.type() == flutter::MutatorType::clip_rect);
ASSERT_TRUE(mutator.rect() == rect);
SkRRect rrect;
flutter::Mutator mutator2 = flutter::Mutator(rrect);
ASSERT_TRUE(mutator2.type() == flutter::MutatorType::clip_rrect);
ASSERT_TRUE(mutator2.rrect() == rrect);
SkPath path;
flutter::Mutator mutator3 = flutter::Mutator(path);
ASSERT_TRUE(mutator3.type() == flutter::MutatorType::clip_path);
ASSERT_TRUE(mutator3.path() == path);
SkMatrix matrix;
flutter::Mutator mutator4 = flutter::Mutator(matrix);
ASSERT_TRUE(mutator4.type() == flutter::MutatorType::transform);
ASSERT_TRUE(mutator4.matrix() == matrix);
}
TEST(Mutator, CopyConstructor) {
SkRect rect = SkRect::MakeEmpty();
flutter::Mutator mutator = flutter::Mutator(rect);
flutter::Mutator copy = flutter::Mutator(mutator);
ASSERT_TRUE(mutator == copy);
SkRRect rrect;
flutter::Mutator mutator2 = flutter::Mutator(rrect);
flutter::Mutator copy2 = flutter::Mutator(mutator2);
ASSERT_TRUE(mutator2 == copy2);
SkPath path;
flutter::Mutator mutator3 = flutter::Mutator(path);
flutter::Mutator copy3 = flutter::Mutator(mutator3);
ASSERT_TRUE(mutator3 == copy3);
SkMatrix matrix;
flutter::Mutator mutator4 = flutter::Mutator(matrix);
flutter::Mutator copy4 = flutter::Mutator(mutator4);
ASSERT_TRUE(mutator4 == copy4);
}
TEST(Mutator, Equality) {
SkMatrix matrix;
flutter::Mutator mutator = flutter::Mutator(matrix);
flutter::Mutator otherMutator = flutter::Mutator(matrix);
ASSERT_TRUE(mutator == otherMutator);
SkRect rect = SkRect::MakeEmpty();
flutter::Mutator mutator2 = flutter::Mutator(rect);
flutter::Mutator otherMutator2 = flutter::Mutator(rect);
ASSERT_TRUE(mutator2 == otherMutator2);
SkRRect rrect;
flutter::Mutator mutator3 = flutter::Mutator(rrect);
flutter::Mutator otherMutator3 = flutter::Mutator(rrect);
ASSERT_TRUE(mutator3 == otherMutator3);
ASSERT_FALSE(mutator2 == mutator);
}
TEST(Mutator, UnEquality) {
SkRect rect = SkRect::MakeEmpty();
flutter::Mutator mutator = flutter::Mutator(rect);
SkMatrix matrix;
flutter::Mutator notEqualMutator = flutter::Mutator(matrix);
ASSERT_TRUE(notEqualMutator != mutator);
}
......@@ -159,6 +159,7 @@ void RasterCache::Prepare(PrerollContext* context,
entry.image = Rasterize(context->gr_context, ctm, context->dst_color_space,
checkerboard_images_, layer->paint_bounds(),
[layer, context](SkCanvas* canvas) {
MutatorsStack stack;
SkISize canvas_size = canvas->getBaseLayerSize();
SkNWayCanvas internal_nodes_canvas(
canvas_size.width(), canvas_size.height());
......@@ -168,6 +169,7 @@ void RasterCache::Prepare(PrerollContext* context,
canvas,
context->gr_context,
nullptr,
stack,
context->raster_time,
context->ui_time,
context->texture_registry,
......
......@@ -91,6 +91,7 @@ void FlutterPlatformViewsController::OnCreate(FlutterMethodCall* call, FlutterRe
touch_interceptors_[viewId] =
fml::scoped_nsobject<FlutterTouchInterceptingView>([touch_interceptor retain]);
root_views_[viewId] = fml::scoped_nsobject<UIView>([touch_interceptor retain]);
result(nil);
}
......@@ -182,6 +183,117 @@ std::vector<SkCanvas*> FlutterPlatformViewsController::GetCurrentCanvases() {
return canvases;
}
int FlutterPlatformViewsController::CountClips(const MutatorsStack& mutators_stack) {
std::vector<std::shared_ptr<Mutator>>::const_reverse_iterator iter = mutators_stack.bottom();
int clipCount = 0;
while (iter != mutators_stack.top()) {
if ((*iter)->isClipType()) {
clipCount++;
}
++iter;
}
return clipCount;
}
UIView* FlutterPlatformViewsController::ReconstructClipViewsChain(int number_of_clips,
UIView* platform_view,
UIView* head_clip_view) {
NSInteger indexInFlutterView = -1;
if (head_clip_view.superview) {
// TODO(cyanglaz): potentially cache the index of oldPlatformViewRoot to make this a O(1).
// https://github.com/flutter/flutter/issues/35023
indexInFlutterView = [flutter_view_.get().subviews indexOfObject:head_clip_view];
[head_clip_view removeFromSuperview];
}
UIView* head = platform_view;
int clipIndex = 0;
// Re-use as much existing clip views as needed.
while (head != head_clip_view && clipIndex < number_of_clips) {
head = head.superview;
clipIndex++;
}
// If there were not enough existing clip views, add more.
while (clipIndex < number_of_clips) {
ChildClippingView* clippingView = [ChildClippingView new];
[clippingView addSubview:head];
head = clippingView;
clipIndex++;
}
[head removeFromSuperview];
if (indexInFlutterView > -1) {
// The chain was previously attached; attach it to the same position.
[flutter_view_.get() insertSubview:head atIndex:indexInFlutterView];
}
return head;
}
void FlutterPlatformViewsController::ApplyMutators(const MutatorsStack& mutators_stack,
UIView* embedded_view) {
UIView* head = embedded_view;
head.clipsToBounds = YES;
head.layer.transform = CATransform3DIdentity;
ResetAnchor(head.layer);
std::vector<std::shared_ptr<Mutator>>::const_reverse_iterator iter = mutators_stack.bottom();
while (iter != mutators_stack.top()) {
switch ((*iter)->type()) {
case transform: {
CATransform3D transform = GetCATransform3DFromSkMatrix((*iter)->matrix());
head.layer.transform = CATransform3DConcat(head.layer.transform, transform);
break;
}
case clip_rect:
case clip_rrect:
case clip_path: {
ChildClippingView* clipView = (ChildClippingView*)head.superview;
clipView.layer.transform = CATransform3DIdentity;
[clipView setClip:(*iter)->type()
rect:(*iter)->rect()
rrect:(*iter)->rrect()
path:(*iter)->path()];
head.clipsToBounds = YES;
ResetAnchor(clipView.layer);
head = clipView;
break;
}
}
++iter;
}
// Reverse scale based on screen scale.
//
// The UIKit frame is set based on the logical resolution instead of physical.
// (https://developer.apple.com/library/archive/documentation/DeviceInformation/Reference/iOSDeviceCompatibility/Displays/Displays.html).
// However, flow is based on the physical resolution. For eaxmple, 1000 pixels in flow equals
// 500 points in UIKit. And until this point, we did all the calculation based on the flow
// resolution. So we need to scale down to match UIKit's logical resolution.
CGFloat screenScale = [UIScreen mainScreen].scale;
head.layer.transform = CATransform3DConcat(
head.layer.transform, CATransform3DMakeScale(1 / screenScale, 1 / screenScale, 1));
}
void FlutterPlatformViewsController::CompositeWithParams(
int view_id,
const flutter::EmbeddedViewParams& params) {
CGRect frame = CGRectMake(0, 0, params.sizePoints.width(), params.sizePoints.height());
UIView* touchInterceptor = touch_interceptors_[view_id].get();
touchInterceptor.frame = frame;
int currentClippingCount = CountClips(params.mutatorsStack);
int previousClippingCount = clip_count_[view_id];
if (currentClippingCount != previousClippingCount) {
clip_count_[view_id] = currentClippingCount;
// If we have a different clipping count in this frame, we need to reconstruct the
// ClippingChildView chain to prepare for `ApplyMutators`.
UIView* oldPlatformViewRoot = root_views_[view_id].get();
UIView* newPlatformViewRoot =
ReconstructClipViewsChain(currentClippingCount, touchInterceptor, oldPlatformViewRoot);
root_views_[view_id] = fml::scoped_nsobject<UIView>([newPlatformViewRoot retain]);
}
ApplyMutators(params.mutatorsStack, touchInterceptor);
}
SkCanvas* FlutterPlatformViewsController::CompositeEmbeddedView(
int view_id,
const flutter::EmbeddedViewParams& params) {
......@@ -193,15 +305,8 @@ SkCanvas* FlutterPlatformViewsController::CompositeEmbeddedView(
current_composition_params_[view_id] == params) {
return picture_recorders_[view_id]->getRecordingCanvas();
}
current_composition_params_[view_id] = params;
CGFloat screenScale = [[UIScreen mainScreen] scale];
CGRect rect =
CGRectMake(params.offsetPixels.x() / screenScale, params.offsetPixels.y() / screenScale,
params.sizePoints.width(), params.sizePoints.height());
UIView* touch_interceptor = touch_interceptors_[view_id].get();
[touch_interceptor setFrame:rect];
current_composition_params_[view_id] = EmbeddedViewParams(params);
CompositeWithParams(view_id, params);
return picture_recorders_[view_id]->getRecordingCanvas();
}
......@@ -217,6 +322,7 @@ void FlutterPlatformViewsController::Reset() {
active_composition_order_.clear();
picture_recorders_.clear();
current_composition_params_.clear();
clip_count_.clear();
}
bool FlutterPlatformViewsController::SubmitFrame(bool gl_rendering,
......@@ -249,15 +355,18 @@ bool FlutterPlatformViewsController::SubmitFrame(bool gl_rendering,
for (size_t i = 0; i < composition_order_.size(); i++) {
int view_id = composition_order_[i];
UIView* intercepter = touch_interceptors_[view_id].get();
// We added a chain of super views to the platform view to handle clipping.
// The `platform_view_root` is the view at the top of the chain which is a direct subview of the
// `FlutterView`.
UIView* platform_view_root = root_views_[view_id].get();
UIView* overlay = overlays_[view_id]->overlay_view;
FML_CHECK(intercepter.superview == overlay.superview);
FML_CHECK(platform_view_root.superview == overlay.superview);
if (intercepter.superview == flutter_view) {
[flutter_view bringSubviewToFront:intercepter];
if (platform_view_root.superview == flutter_view) {
[flutter_view bringSubviewToFront:platform_view_root];
[flutter_view bringSubviewToFront:overlay];
} else {
[flutter_view addSubview:intercepter];
[flutter_view addSubview:platform_view_root];
[flutter_view addSubview:overlay];
}
......@@ -276,10 +385,14 @@ void FlutterPlatformViewsController::DetachUnusedLayers() {
for (int64_t view_id : active_composition_order_) {
if (composition_order_set.find(view_id) == composition_order_set.end()) {
if (touch_interceptors_.find(view_id) == touch_interceptors_.end()) {
if (root_views_.find(view_id) == root_views_.end()) {
continue;
}
[touch_interceptors_[view_id].get() removeFromSuperview];
// We added a chain of super views to the platform view to handle clipping.
// The `platform_view_root` is the view at the top of the chain which is a direct subview of
// the `FlutterView`.
UIView* platform_view_root = root_views_[view_id].get();
[platform_view_root removeFromSuperview];
[overlays_[view_id]->overlay_view.get() removeFromSuperview];
}
}
......@@ -291,12 +404,14 @@ void FlutterPlatformViewsController::DisposeViews() {
}
for (int64_t viewId : views_to_dispose_) {
UIView* touch_interceptor = touch_interceptors_[viewId].get();
[touch_interceptor removeFromSuperview];
UIView* root_view = root_views_[viewId].get();
[root_view removeFromSuperview];
views_.erase(viewId);
touch_interceptors_.erase(viewId);
root_views_.erase(viewId);
overlays_.erase(viewId);
current_composition_params_.erase(viewId);
clip_count_.erase(viewId);
}
views_to_dispose_.clear();
}
......
......@@ -28,8 +28,29 @@
- (void)blockGesture;
@end
// The parent view handles clipping to its subviews.
@interface ChildClippingView : UIView
// Performs the clipping based on the type.
//
// The `type` must be one of the 3: clip_rect, clip_rrect, clip_path.
- (void)setClip:(flutter::MutatorType)type
rect:(const SkRect&)rect
rrect:(const SkRRect&)rrect
path:(const SkPath&)path;
@end
namespace flutter {
// Converts a SkMatrix to CATransform3D.
// Certain fields are ignored in CATransform3D since SkMatrix is 3x3 and CATransform3D is 4x4.
CATransform3D GetCATransform3DFromSkMatrix(const SkMatrix& matrix);
// Reset the anchor of `layer` to match the tranform operation from flow.
// The position of the `layer` should be unchanged after resetting the anchor.
void ResetAnchor(CALayer* layer);
class IOSGLContext;
class IOSSurface;
......@@ -88,8 +109,17 @@ class FlutterPlatformViewsController {
std::map<std::string, fml::scoped_nsobject<NSObject<FlutterPlatformViewFactory>>> factories_;
std::map<int64_t, fml::scoped_nsobject<NSObject<FlutterPlatformView>>> views_;
std::map<int64_t, fml::scoped_nsobject<FlutterTouchInterceptingView>> touch_interceptors_;
// Mapping a platform view ID to the top most parent view (root_view) who is a direct child to the
// `flutter_view_`.
//
// The platform view with the view ID is a child of the root view; If the platform view is not
// clipped, and no clipping view is added, the root view will be the intercepting view.
std::map<int64_t, fml::scoped_nsobject<UIView>> root_views_;
// Mapping a platform view ID to its latest composition params.
std::map<int64_t, EmbeddedViewParams> current_composition_params_;
// Mapping a platform view ID to the count of the clipping operations that were applied to the
// platform view last time it was composited.
std::map<int64_t, int64_t> clip_count_;
std::map<int64_t, std::unique_ptr<FlutterPlatformViewLayer>> overlays_;
// The GrContext that is currently used by all of the overlay surfaces.
// We track this to know when the GrContext for the Flutter app has changed
......@@ -122,6 +152,40 @@ class FlutterPlatformViewsController {
void EnsureGLOverlayInitialized(int64_t overlay_id,
std::shared_ptr<IOSGLContext> gl_context,
GrContext* gr_context);
// Traverse the `mutators_stack` and return the number of clip operations.
int CountClips(const MutatorsStack& mutators_stack);
// Make sure that platform_view has exactly clip_count ChildClippingView ancestors.
//
// Existing ChildClippingViews are re-used. If there are currently more ChildClippingView
// ancestors than needed, the extra views are detached. If there are less ChildClippingView
// ancestors than needed, new ChildClippingViews will be added.
//
// If head_clip_view was attached as a subview to FlutterView, the head of the newly constructed
// ChildClippingViews chain is attached to FlutterView in the same position.
//
// Returns the new head of the clip views chain.
UIView* ReconstructClipViewsChain(int number_of_clips,
UIView* platform_view,
UIView* head_clip_view);
// Applies the mutators in the mutators_stack to the UIView chain that was constructed by
// `ReconstructClipViewsChain`
//
// Clips are applied to the super view with a CALayer mask. Transforms are applied to the current
// view that's at the head of the chain. For example the following mutators stack [T_1, C_2, T_3,
// T_4, C_5, T_6] where T denotes a transform and C denotes a clip, will result in the following
// UIView tree:
//
// C_2 -> C_5 -> PLATFORM_VIEW
// (PLATFORM_VIEW is a subview of C_5 which is a subview of C_2)
//
// T_1 is applied to C_2, T_3 and T_4 are applied to C_5, and T_6 is applied to PLATFORM_VIEW.
//
// After each clip operation, we update the head to the super view of the current head.
void ApplyMutators(const MutatorsStack& mutators_stack, UIView* embedded_view);
void CompositeWithParams(int view_id, const flutter::EmbeddedViewParams& params);
FML_DISALLOW_COPY_AND_ASSIGN(FlutterPlatformViewsController);
};
......
......@@ -21,4 +21,144 @@ FlutterPlatformViewsController::FlutterPlatformViewsController() = default;
FlutterPlatformViewsController::~FlutterPlatformViewsController() = default;
CATransform3D GetCATransform3DFromSkMatrix(const SkMatrix& matrix) {
// Skia only supports 2D transform so we don't map z.
CATransform3D transform = CATransform3DIdentity;
transform.m11 = matrix.getScaleX();
transform.m21 = matrix.getSkewX();
transform.m41 = matrix.getTranslateX();
transform.m14 = matrix.getPerspX();
transform.m12 = matrix.getSkewY();
transform.m22 = matrix.getScaleY();
transform.m42 = matrix.getTranslateY();
transform.m24 = matrix.getPerspY();
return transform;
}
void ResetAnchor(CALayer* layer) {
// Flow uses (0, 0) to apply transform matrix so we need to match that in Quartz.
layer.anchorPoint = CGPointZero;
layer.position = CGPointZero;
}
} // namespace flutter
@implementation ChildClippingView
+ (CGRect)getCGRectFromSkRect:(const SkRect&)clipSkRect {
return CGRectMake(clipSkRect.fLeft, clipSkRect.fTop, clipSkRect.fRight - clipSkRect.fLeft,
clipSkRect.fBottom - clipSkRect.fTop);
}
- (void)clipRect:(const SkRect&)clipSkRect {
CGRect clipRect = [ChildClippingView getCGRectFromSkRect:clipSkRect];
CGPathRef pathRef = CGPathCreateWithRect(clipRect, nil);
CAShapeLayer* clip = [[CAShapeLayer alloc] init];
clip.path = pathRef;
self.layer.mask = clip;
CGPathRelease(pathRef);
}
- (void)clipRRect:(const SkRRect&)clipSkRRect {
CGPathRef pathRef = nullptr;
switch (clipSkRRect.getType()) {
case SkRRect::kEmpty_Type: {
break;
}
case SkRRect::kRect_Type: {
[self clipRect:clipSkRRect.rect()];
return;
}
case SkRRect::kOval_Type:
case SkRRect::kSimple_Type: {
CGRect clipRect = [ChildClippingView getCGRectFromSkRect:clipSkRRect.rect()];
pathRef = CGPathCreateWithRoundedRect(clipRect, clipSkRRect.getSimpleRadii().x(),
clipSkRRect.getSimpleRadii().y(), nil);
break;
}
case SkRRect::kNinePatch_Type:
case SkRRect::kComplex_Type: {
CGMutablePathRef mutablePathRef = CGPathCreateMutable();
// Complex types, we manually add each corner.
SkRect clipSkRect = clipSkRRect.rect();
SkVector topLeftRadii = clipSkRRect.radii(SkRRect::kUpperLeft_Corner);
SkVector topRightRadii = clipSkRRect.radii(SkRRect::kUpperRight_Corner);
SkVector bottomRightRadii = clipSkRRect.radii(SkRRect::kLowerRight_Corner);
SkVector bottomLeftRadii = clipSkRRect.radii(SkRRect::kLowerLeft_Corner);
// Start drawing RRect
// Move point to the top left corner adding the top left radii's x.
CGPathMoveToPoint(mutablePathRef, nil, clipSkRect.fLeft + topLeftRadii.x(), clipSkRect.fTop);
// Move point horizontally right to the top right corner and add the top right curve.
CGPathAddLineToPoint(mutablePathRef, nil, clipSkRect.fRight - topRightRadii.x(),
clipSkRect.fTop);
CGPathAddCurveToPoint(mutablePathRef, nil, clipSkRect.fRight, clipSkRect.fTop,
clipSkRect.fRight, clipSkRect.fTop + topRightRadii.y(),
clipSkRect.fRight, clipSkRect.fTop + topRightRadii.y());
// Move point vertically down to the bottom right corner and add the bottom right curve.
CGPathAddLineToPoint(mutablePathRef, nil, clipSkRect.fRight,
clipSkRect.fBottom - bottomRightRadii.y());
CGPathAddCurveToPoint(mutablePathRef, nil, clipSkRect.fRight, clipSkRect.fBottom,
clipSkRect.fRight - bottomRightRadii.x(), clipSkRect.fBottom,
clipSkRect.fRight - bottomRightRadii.x(), clipSkRect.fBottom);
// Move point horizontally left to the bottom left corner and add the bottom left curve.
CGPathAddLineToPoint(mutablePathRef, nil, clipSkRect.fLeft + bottomLeftRadii.x(),
clipSkRect.fBottom);
CGPathAddCurveToPoint(mutablePathRef, nil, clipSkRect.fLeft, clipSkRect.fBottom,
clipSkRect.fLeft, clipSkRect.fBottom - bottomLeftRadii.y(),
clipSkRect.fLeft, clipSkRect.fBottom - bottomLeftRadii.y());
// Move point vertically up to the top left corner and add the top left curve.
CGPathAddLineToPoint(mutablePathRef, nil, clipSkRect.fLeft,
clipSkRect.fTop + topLeftRadii.y());
CGPathAddCurveToPoint(mutablePathRef, nil, clipSkRect.fLeft, clipSkRect.fTop,
clipSkRect.fLeft + topLeftRadii.x(), clipSkRect.fTop,
clipSkRect.fLeft + topLeftRadii.x(), clipSkRect.fTop);
CGPathCloseSubpath(mutablePathRef);
pathRef = mutablePathRef;
break;
}
}
// TODO(cyanglaz): iOS does not seem to support hard edge on CAShapeLayer. It clearly stated that
// the CAShaperLayer will be drawn antialiased. Need to figure out a way to do the hard edge
// clipping on iOS.
CAShapeLayer* clip = [[CAShapeLayer alloc] init];
clip.path = pathRef;
self.layer.mask = clip;
CGPathRelease(pathRef);
}
- (void)setClip:(flutter::MutatorType)type
rect:(const SkRect&)rect
rrect:(const SkRRect&)rrect
path:(const SkPath&)path {
FML_CHECK(type == flutter::clip_rect || type == flutter::clip_rrect ||
type == flutter::clip_path);
switch (type) {
case flutter::clip_rect:
[self clipRect:rect];
break;
case flutter::clip_rrect:
[self clipRRect:rrect];
break;
case flutter::clip_path:
// TODO(cyanglaz): Add clip path
break;
default:
break;
}
}
// The ChildClippingView is as big as the FlutterView, we only want touches to be hit tested and
// consumed by this view if they are inside the smaller child view.
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent*)event {
for (UIView* view in self.subviews) {
if ([view pointInside:[self convertPoint:point toView:view] withEvent:event]) {
return YES;
}
}
return NO;
}
@end
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册