| // 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. |
| |
| #ifndef FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_FLUTTERPLATFORMVIEWS_INTERNAL_H_ |
| #define FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_FLUTTERPLATFORMVIEWS_INTERNAL_H_ |
| |
| #include <Metal/Metal.h> |
| #include "flutter/flow/embedded_views.h" |
| #include "flutter/fml/platform/darwin/scoped_nsobject.h" |
| #include "flutter/shell/common/shell.h" |
| #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterBinaryMessenger.h" |
| #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterChannels.h" |
| #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterPlatformViews.h" |
| #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterPlugin.h" |
| #import "flutter/shell/platform/darwin/ios/framework/Source/SemanticsObject.h" |
| #import "flutter/shell/platform/darwin/ios/ios_context.h" |
| |
| @class FlutterTouchInterceptingView; |
| |
| // A UIView that acts as a clipping mask for the |ChildClippingView|. |
| // |
| // On the [UIView drawRect:] method, this view performs a series of clipping operations and sets the |
| // alpha channel to the final resulting area to be 1; it also sets the "clipped out" area's alpha |
| // channel to be 0. |
| // |
| // When a UIView sets a |FlutterClippingMaskView| as its `maskView`, the alpha channel of the UIView |
| // is replaced with the alpha channel of the |FlutterClippingMaskView|. |
| @interface FlutterClippingMaskView : UIView |
| |
| - (instancetype)initWithFrame:(CGRect)frame screenScale:(CGFloat)screenScale; |
| |
| - (void)reset; |
| |
| // Adds a clip rect operation to the queue. |
| // |
| // The `clipSkRect` is transformed with the `matrix` before adding to the queue. |
| - (void)clipRect:(const SkRect&)clipSkRect matrix:(const SkMatrix&)matrix; |
| |
| // Adds a clip rrect operation to the queue. |
| // |
| // The `clipSkRRect` is transformed with the `matrix` before adding to the queue. |
| - (void)clipRRect:(const SkRRect&)clipSkRRect matrix:(const SkMatrix&)matrix; |
| |
| // Adds a clip path operation to the queue. |
| // |
| // The `path` is transformed with the `matrix` before adding to the queue. |
| - (void)clipPath:(const SkPath&)path matrix:(const SkMatrix&)matrix; |
| |
| @end |
| |
| // A pool that provides |FlutterClippingMaskView|s. |
| // |
| // The pool has a capacity that can be set in the initializer. |
| // When requesting a FlutterClippingMaskView, the pool will first try to reuse an available maskView |
| // in the pool. If there are none available, a new FlutterClippingMaskView is constructed. If the |
| // capacity is reached, the newly constructed FlutterClippingMaskView is not added to the pool. |
| // |
| // Call |insertViewToPoolIfNeeded:| to return a maskView to the pool. |
| @interface FlutterClippingMaskViewPool : NSObject |
| |
| // Initialize the pool with `capacity`. When the `capacity` is reached, a FlutterClippingMaskView is |
| // constructed when requested, and it is not added to the pool. |
| - (instancetype)initWithCapacity:(NSInteger)capacity; |
| |
| // Reuse a maskView from the pool, or allocate a new one. |
| - (FlutterClippingMaskView*)getMaskViewWithFrame:(CGRect)frame; |
| |
| // Insert the `maskView` into the pool. |
| - (void)insertViewToPoolIfNeeded:(FlutterClippingMaskView*)maskView; |
| |
| @end |
| |
| // An object represents a blur filter. |
| // |
| // This object produces a `backdropFilterView`. |
| // To blur a View, add `backdropFilterView` as a subView of the View. |
| @interface PlatformViewFilter : NSObject |
| |
| // Determines the rect of the blur effect in the coordinate system of `backdropFilterView`'s |
| // parentView. |
| @property(assign, nonatomic, readonly) CGRect frame; |
| |
| // Determines the blur intensity. |
| // |
| // It is set as the value of `inputRadius` of the `gaussianFilter` that is internally used. |
| @property(assign, nonatomic, readonly) CGFloat blurRadius; |
| |
| // This is the view to use to blur the PlatformView. |
| // |
| // It is a modified version of UIKit's `UIVisualEffectView`. |
| // The inputRadius can be customized and it doesn't add any color saturation to the blurred view. |
| @property(nonatomic, retain, readonly) UIVisualEffectView* backdropFilterView; |
| |
| // For testing only. |
| + (void)resetPreparation; |
| |
| - (instancetype)init NS_UNAVAILABLE; |
| |
| // Initialize the filter object. |
| // |
| // The `frame` determines the rect of the blur effect in the coordinate system of |
| // `backdropFilterView`'s parentView. The `blurRadius` determines the blur intensity. It is set as |
| // the value of `inputRadius` of the `gaussianFilter` that is internally used. The |
| // `UIVisualEffectView` is the view that is used to add the blur effects. It is modified to become |
| // `backdropFilterView`, which better supports the need of Flutter. |
| // |
| // Note: if the implementation of UIVisualEffectView changes in a way that affects the |
| // implementation in `PlatformViewFilter`, this method will return nil. |
| - (instancetype)initWithFrame:(CGRect)frame |
| blurRadius:(CGFloat)blurRadius |
| visualEffectView:(UIVisualEffectView*)visualEffectView NS_DESIGNATED_INITIALIZER; |
| |
| @end |
| |
| // The parent view handles clipping to its subViews. |
| @interface ChildClippingView : UIView |
| |
| // Applies blur backdrop filters to the ChildClippingView with blur values from |
| // filters. |
| - (void)applyBlurBackdropFilters:(NSArray<PlatformViewFilter*>*)filters; |
| |
| // For testing only. |
| - (NSMutableArray*)backdropFilterSubviews; |
| @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 transform operation from flow. |
| // The position of the `layer` should be unchanged after resetting the anchor. |
| void ResetAnchor(CALayer* layer); |
| |
| CGRect GetCGRectFromSkRect(const SkRect& clipSkRect); |
| BOOL BlurRadiusEqualToBlurRadius(CGFloat radius1, CGFloat radius2); |
| |
| class IOSContextGL; |
| class IOSSurface; |
| |
| struct FlutterPlatformViewLayer { |
| FlutterPlatformViewLayer(const fml::scoped_nsobject<UIView>& overlay_view, |
| const fml::scoped_nsobject<UIView>& overlay_view_wrapper, |
| std::unique_ptr<IOSSurface> ios_surface, |
| std::unique_ptr<Surface> surface); |
| |
| ~FlutterPlatformViewLayer(); |
| |
| fml::scoped_nsobject<UIView> overlay_view; |
| fml::scoped_nsobject<UIView> overlay_view_wrapper; |
| std::unique_ptr<IOSSurface> ios_surface; |
| std::unique_ptr<Surface> surface; |
| |
| // Whether a frame for this layer was submitted. |
| bool did_submit_last_frame; |
| |
| // The GrContext that is currently used by the overlay surfaces. |
| // We track this to know when the GrContext for the Flutter app has changed |
| // so we can update the overlay with the new context. |
| GrDirectContext* gr_context; |
| }; |
| |
| // This class isn't thread safe. |
| class FlutterPlatformViewLayerPool { |
| public: |
| FlutterPlatformViewLayerPool() = default; |
| |
| ~FlutterPlatformViewLayerPool() = default; |
| |
| // Gets a layer from the pool if available, or allocates a new one. |
| // Finally, it marks the layer as used. That is, it increments `available_layer_index_`. |
| std::shared_ptr<FlutterPlatformViewLayer> GetLayer(GrDirectContext* gr_context, |
| const std::shared_ptr<IOSContext>& ios_context, |
| MTLPixelFormat pixel_format); |
| |
| // Gets the layers in the pool that aren't currently used. |
| // This method doesn't mark the layers as unused. |
| std::vector<std::shared_ptr<FlutterPlatformViewLayer>> GetUnusedLayers(); |
| |
| // Marks the layers in the pool as available for reuse. |
| void RecycleLayers(); |
| |
| private: |
| // The index of the entry in the layers_ vector that determines the beginning of the unused |
| // layers. For example, consider the following vector: |
| // _____ |
| // | 0 | |
| /// |---| |
| /// | 1 | <-- available_layer_index_ |
| /// |---| |
| /// | 2 | |
| /// |---| |
| /// |
| /// This indicates that entries starting from 1 can be reused meanwhile the entry at position 0 |
| /// cannot be reused. |
| size_t available_layer_index_ = 0; |
| std::vector<std::shared_ptr<FlutterPlatformViewLayer>> layers_; |
| |
| FML_DISALLOW_COPY_AND_ASSIGN(FlutterPlatformViewLayerPool); |
| }; |
| |
| class FlutterPlatformViewsController { |
| public: |
| FlutterPlatformViewsController(); |
| |
| ~FlutterPlatformViewsController(); |
| |
| fml::WeakPtr<flutter::FlutterPlatformViewsController> GetWeakPtr(); |
| |
| void SetFlutterView(UIView* flutter_view); |
| |
| void SetFlutterViewController(UIViewController* flutter_view_controller); |
| |
| UIViewController* getFlutterViewController(); |
| |
| void RegisterViewFactory( |
| NSObject<FlutterPlatformViewFactory>* factory, |
| NSString* factoryId, |
| FlutterPlatformViewGestureRecognizersBlockingPolicy gestureRecognizerBlockingPolicy); |
| |
| // Called at the beginning of each frame. |
| void BeginFrame(SkISize frame_size); |
| |
| // Indicates that we don't compisite any platform views or overlays during this frame. |
| // Also reverts the composition_order_ to its original state at the beginning of the frame. |
| void CancelFrame(); |
| |
| void PrerollCompositeEmbeddedView(int64_t view_id, |
| std::unique_ptr<flutter::EmbeddedViewParams> params); |
| |
| size_t EmbeddedViewCount(); |
| |
| // Returns the `FlutterPlatformView`'s `view` object associated with the view_id. |
| // |
| // If the `FlutterPlatformViewsController` does not contain any `FlutterPlatformView` object or |
| // a `FlutterPlatformView` object associated with the view_id cannot be found, the method |
| // returns nil. |
| UIView* GetPlatformViewByID(int64_t view_id); |
| |
| // Returns the `FlutterTouchInterceptingView` with the view_id. |
| // |
| // If the `FlutterPlatformViewsController` does not contain any `FlutterPlatformView` object or |
| // a `FlutterPlatformView` object associated with the view_id cannot be found, the method |
| // returns nil. |
| FlutterTouchInterceptingView* GetFlutterTouchInterceptingViewByID(int64_t view_id); |
| |
| PostPrerollResult PostPrerollAction( |
| const fml::RefPtr<fml::RasterThreadMerger>& raster_thread_merger); |
| |
| void EndFrame(bool should_resubmit_frame, |
| const fml::RefPtr<fml::RasterThreadMerger>& raster_thread_merger); |
| |
| DlCanvas* CompositeEmbeddedView(int64_t view_id); |
| |
| // The rect of the platform view at index view_id. This rect has been translated into the |
| // host view coordinate system. Units are device screen pixels. |
| SkRect GetPlatformViewRect(int64_t view_id); |
| |
| // Discards all platform views instances and auxiliary resources. |
| void Reset(); |
| |
| bool SubmitFrame(GrDirectContext* gr_context, |
| const std::shared_ptr<IOSContext>& ios_context, |
| std::unique_ptr<SurfaceFrame> frame); |
| |
| void OnMethodCall(FlutterMethodCall* call, FlutterResult result); |
| |
| // Returns the platform view id if the platform view (or any of its descendant view) is the first |
| // responder. Returns -1 if no such platform view is found. |
| long FindFirstResponderPlatformViewId(); |
| |
| // Pushes backdrop filter mutation to the mutator stack of each visited platform view. |
| void PushFilterToVisitedPlatformViews(const std::shared_ptr<const DlImageFilter>& filter, |
| const SkRect& filter_rect); |
| |
| // Pushes the view id of a visted platform view to the list of visied platform views. |
| void PushVisitedPlatformView(int64_t view_id) { visited_platform_views_.push_back(view_id); } |
| |
| private: |
| static const size_t kMaxLayerAllocations = 2; |
| |
| using LayersMap = std::map<int64_t, std::vector<std::shared_ptr<FlutterPlatformViewLayer>>>; |
| |
| void OnCreate(FlutterMethodCall* call, FlutterResult result); |
| void OnDispose(FlutterMethodCall* call, FlutterResult result); |
| void OnAcceptGesture(FlutterMethodCall* call, FlutterResult result); |
| void OnRejectGesture(FlutterMethodCall* call, FlutterResult result); |
| // Dispose the views in `views_to_dispose_`. |
| void DisposeViews(); |
| |
| // Returns true if there are embedded views in the scene at current frame |
| // Or there will be embedded views in the next frame. |
| // TODO(cyanglaz): https://github.com/flutter/flutter/issues/56474 |
| // Make this method check if there are pending view operations instead. |
| // Also rename it to `HasPendingViewOperations`. |
| bool HasPlatformViewThisOrNextFrame(); |
| |
| // Traverse the `mutators_stack` and return the number of clip operations. |
| int CountClips(const MutatorsStack& mutators_stack); |
| |
| void ClipViewSetMaskView(UIView* clipView); |
| |
| // Applies the mutators in the mutators_stack to the UIView chain that was constructed by |
| // `ReconstructClipViewsChain` |
| // |
| // Clips are applied to the `embedded_view`'s super view(|ChildClippingView|) using a |
| // |FlutterClippingMaskView|. Transforms are applied to `embedded_view` |
| // |
| // The `bounding_rect` is the final bounding rect of the PlatformView |
| // (EmbeddedViewParams::finalBoundingRect). If a clip mutator's rect contains the final bounding |
| // rect of the PlatformView, the clip mutator is not applied for performance optimization. |
| void ApplyMutators(const MutatorsStack& mutators_stack, |
| UIView* embedded_view, |
| const SkRect& bounding_rect); |
| |
| void CompositeWithParams(int64_t view_id, const EmbeddedViewParams& params); |
| |
| // Allocates a new FlutterPlatformViewLayer if needed, draws the pixels within the rect from |
| // the picture on the layer's canvas. |
| std::shared_ptr<FlutterPlatformViewLayer> GetLayer(GrDirectContext* gr_context, |
| const std::shared_ptr<IOSContext>& ios_context, |
| EmbedderViewSlice* slice, |
| SkIRect rect, |
| int64_t view_id, |
| int64_t overlay_id, |
| MTLPixelFormat pixel_format); |
| // Removes overlay views and platform views that aren't needed in the current frame. |
| // Must run on the platform thread. |
| void RemoveUnusedLayers(); |
| // Appends the overlay views and platform view and sets their z index based on the composition |
| // order. |
| void BringLayersIntoView(LayersMap layer_map); |
| |
| // Begin a CATransaction. |
| // This transaction needs to be balanced with |CommitCATransactionIfNeeded|. |
| void BeginCATransaction(); |
| |
| // Commit a CATransaction if |BeginCATransaction| has been called during the frame. |
| void CommitCATransactionIfNeeded(); |
| |
| // Resets the state of the frame. |
| void ResetFrameState(); |
| |
| // The pool of reusable view layers. The pool allows to recycle layer in each frame. |
| std::unique_ptr<FlutterPlatformViewLayerPool> layer_pool_; |
| |
| // The platform view's |EmbedderViewSlice| keyed off the view id, which contains any subsequent |
| // operation until the next platform view or the end of the last leaf node in the layer tree. |
| // |
| // The Slices are deleted by the FlutterPlatformViewsController.reset(). |
| std::map<int64_t, std::unique_ptr<EmbedderViewSlice>> slices_; |
| |
| fml::scoped_nsobject<FlutterMethodChannel> channel_; |
| fml::scoped_nsobject<UIView> flutter_view_; |
| fml::scoped_nsobject<UIViewController> flutter_view_controller_; |
| fml::scoped_nsobject<FlutterClippingMaskViewPool> mask_view_pool_; |
| 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) of a platform view. In |
| // |SubmitFrame|, root_views_ are added to flutter_view_ as child views. |
| // |
| // 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_; |
| SkISize frame_size_; |
| |
| // The number of frames the rasterizer task runner will continue |
| // to run on the platform thread after no platform view is rendered. |
| // |
| // Note: this is an arbitrary number that attempts to account for cases |
| // where the platform view might be momentarily off the screen. |
| static const int kDefaultMergedLeaseDuration = 10; |
| |
| // Method channel `OnDispose` calls adds the views to be disposed to this set to be disposed on |
| // the next frame. |
| std::unordered_set<int64_t> views_to_dispose_; |
| |
| // A vector of embedded view IDs according to their composition order. |
| // The last ID in this vector belond to the that is composited on top of all others. |
| std::vector<int64_t> composition_order_; |
| |
| // A vector of visited platform view IDs. |
| std::vector<int64_t> visited_platform_views_; |
| |
| // The latest composition order that was presented in Present(). |
| std::vector<int64_t> active_composition_order_; |
| |
| // Only compoiste platform views in this set. |
| std::unordered_set<int64_t> views_to_recomposite_; |
| |
| // The FlutterPlatformViewGestureRecognizersBlockingPolicy for each type of platform view. |
| std::map<std::string, FlutterPlatformViewGestureRecognizersBlockingPolicy> |
| gesture_recognizers_blocking_policies_; |
| |
| bool catransaction_added_ = false; |
| |
| // WeakPtrFactory must be the last member. |
| std::unique_ptr<fml::WeakPtrFactory<FlutterPlatformViewsController>> weak_factory_; |
| |
| #if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG |
| // A set to keep track of embedded views that does not have (0, 0) origin. |
| // An insertion triggers a warning message about non-zero origin logged on the debug console. |
| // See https://github.com/flutter/flutter/issues/109700 for details. |
| std::unordered_set<int64_t> non_zero_origin_views_; |
| #endif |
| |
| FML_DISALLOW_COPY_AND_ASSIGN(FlutterPlatformViewsController); |
| }; |
| |
| } // namespace flutter |
| |
| // A UIView that is used as the parent for embedded UIViews. |
| // |
| // This view has 2 roles: |
| // 1. Delay or prevent touch events from arriving the embedded view. |
| // 2. Dispatching all events that are hittested to the embedded view to the FlutterView. |
| @interface FlutterTouchInterceptingView : UIView |
| - (instancetype)initWithEmbeddedView:(UIView*)embeddedView |
| platformViewsController: |
| (fml::WeakPtr<flutter::FlutterPlatformViewsController>)platformViewsController |
| gestureRecognizersBlockingPolicy: |
| (FlutterPlatformViewGestureRecognizersBlockingPolicy)blockingPolicy; |
| |
| // Stop delaying any active touch sequence (and let it arrive the embedded view). |
| - (void)releaseGesture; |
| |
| // Prevent the touch sequence from ever arriving to the embedded view. |
| - (void)blockGesture; |
| |
| // Get embedded view |
| - (UIView*)embeddedView; |
| |
| // Sets flutterAccessibilityContainer as this view's accessibilityContainer. |
| - (void)setFlutterAccessibilityContainer:(NSObject*)flutterAccessibilityContainer; |
| @end |
| |
| @interface UIView (FirstResponder) |
| // Returns YES if a view or any of its descendant view is the first responder. Returns NO otherwise. |
| @property(nonatomic, readonly) BOOL flt_hasFirstResponderInViewHierarchySubtree; |
| @end |
| |
| #endif // FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_FLUTTERPLATFORMVIEWS_INTERNAL_H_ |