Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(81)

Unified Diff: ios/chrome/browser/ui/contextual_search/contextual_search_panel_view.mm

Issue 2588713002: Upstream Chrome on iOS source code [4/11]. (Closed)
Patch Set: Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: ios/chrome/browser/ui/contextual_search/contextual_search_panel_view.mm
diff --git a/ios/chrome/browser/ui/contextual_search/contextual_search_panel_view.mm b/ios/chrome/browser/ui/contextual_search/contextual_search_panel_view.mm
new file mode 100644
index 0000000000000000000000000000000000000000..1b556aa9945c38874c29a1d9778e1f4f5fe39f12
--- /dev/null
+++ b/ios/chrome/browser/ui/contextual_search/contextual_search_panel_view.mm
@@ -0,0 +1,480 @@
+// Copyright 2014 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.
+
+#import "ios/chrome/browser/ui/contextual_search/contextual_search_panel_view.h"
+
+#import "base/ios/crb_protocol_observers.h"
+#include "base/ios/weak_nsobject.h"
+#include "base/logging.h"
+#include "base/mac/scoped_block.h"
+#include "base/mac/scoped_nsobject.h"
+#import "ios/chrome/browser/procedural_block_types.h"
+#import "ios/chrome/browser/ui/contextual_search/contextual_search_panel_protocols.h"
+#import "ios/chrome/browser/ui/uikit_ui_util.h"
+#import "ios/chrome/common/material_timing.h"
+#import "ios/third_party/material_components_ios/src/components/ShadowElevations/src/MaterialShadowElevations.h"
+#import "ios/third_party/material_components_ios/src/components/ShadowLayer/src/MaterialShadowLayer.h"
+
+namespace {
+
+// Animation timings.
+const NSTimeInterval kPanelAnimationDuration = ios::material::kDuration3;
+const NSTimeInterval kDismissAnimationDuration = ios::material::kDuration1;
+
+// Elevation (in MD vertical space) of the panel when dismissed and peeking.
+const CGFloat kShadowElevation = MDCShadowElevationMenu;
+
+} // namespace
+
+@interface ContextualSearchPanelObservers
+ : CRBProtocolObservers<ContextualSearchPanelMotionObserver>
+@end
+@implementation ContextualSearchPanelObservers
+
+@end
+
+@interface ContextualSearchPanelView ()<UIGestureRecognizerDelegate,
+ ContextualSearchPanelMotionObserver>
+
+// A subview whose content scrolls and whose scrolling is synchronized with
+// panel dragging. This means that if the scrolling subview is being scrolled,
+// that motion will not cause the panel to move, but if the scrolling reaches
+// the end of its possible range, the gesture will then start dragging the
+// panel.
+@property(nonatomic, assign)
+ UIView<ContextualSearchPanelScrollSynchronizer>* scrollSynchronizer;
+
+// Private readonly property to be used by weak pointers to |self| for non-
+// retaining access to the underlying ivar in blocks.
+@property(nonatomic, readonly) ContextualSearchPanelObservers* observers;
+
+// Utility to generate a PanelMotion struct for the panel's current position.
+- (ContextualSearch::PanelMotion)motion;
+@end
+
+@implementation ContextualSearchPanelView {
+ UIStackView* _contents;
+
+ // Constraints that define the size of this view. These will be cleared and
+ // regenerated when the horizontal size class changes.
+ base::scoped_nsobject<NSArray> _sizingConstraints;
+
+ CGPoint _draggingStartPosition;
+ CGPoint _scrolledOffset;
+ base::scoped_nsobject<UIPanGestureRecognizer> _dragRecognizer;
+
+ base::scoped_nsobject<ContextualSearchPanelObservers> _observers;
+
+ base::scoped_nsobject<PanelConfiguration> _configuration;
+
+ base::WeakNSProtocol<id<ContextualSearchPanelScrollSynchronizer>>
+ _scrollSynchronizer;
+
+ // Guide that's used to position this view.
+ base::WeakNSObject<UILayoutGuide> _positioningGuide;
+ // Constraint that sets the size of |_positioningView| so this view is
+ // positioned correctly for its state.
+ base::WeakNSObject<NSLayoutConstraint> _positioningViewConstraint;
+ // Other constraints that determine the position of this view.
+ base::scoped_nsobject<NSArray> _positioningConstraints;
+
+ // Promotion state variables.
+ BOOL _resizingForPromotion;
+ CGFloat _promotionVerticalOffset;
+
+ // YES if dragging started inside the content view and scrolling is possible.
+ BOOL _maybeScrollContent;
+ // YES if the drag is happening along with scrolling the content view.
+ BOOL _isScrollingContent;
+
+ // YES if dragging upwards has occurred.
+ BOOL _hasDraggedUp;
+}
+
+@synthesize state = _state;
+
++ (BOOL)requiresConstraintBasedLayout {
+ return YES;
+}
+
+#pragma mark - Initializers
+
+- (instancetype)initWithConfiguration:(PanelConfiguration*)configuration {
+ if ((self = [super initWithFrame:CGRectZero])) {
+ _configuration.reset([configuration retain]);
+ _state = ContextualSearch::DISMISSED;
+
+ self.translatesAutoresizingMaskIntoConstraints = NO;
+ self.backgroundColor = [UIColor whiteColor];
+ self.accessibilityIdentifier = @"contextualSearchPanel";
+
+ _observers.reset([[ContextualSearchPanelObservers
+ observersWithProtocol:@protocol(ContextualSearchPanelMotionObserver)]
+ retain]);
+ [self addMotionObserver:self];
+
+ // Add gesture recognizer.
+ _dragRecognizer.reset([[UIPanGestureRecognizer alloc]
+ initWithTarget:self
+ action:@selector(handleDragFrom:)]);
+ [self addGestureRecognizer:_dragRecognizer];
+ [_dragRecognizer setDelegate:self];
+
+ // Set up the stack view that holds the panel content
+ _contents = [[[UIStackView alloc] initWithFrame:self.bounds] autorelease];
+ [self addSubview:_contents];
+ _contents.translatesAutoresizingMaskIntoConstraints = NO;
+ _contents.accessibilityIdentifier = @"panelContents";
+ [NSLayoutConstraint activateConstraints:@[
+ [_contents.centerXAnchor constraintEqualToAnchor:self.centerXAnchor],
+ [_contents.centerYAnchor constraintEqualToAnchor:self.centerYAnchor],
+ [_contents.widthAnchor constraintEqualToAnchor:self.widthAnchor],
+ [_contents.heightAnchor constraintEqualToAnchor:self.heightAnchor]
+ ]];
+ _contents.axis = UILayoutConstraintAxisVertical;
+ }
+ return self;
+}
+
+- (instancetype)initWithCoder:(NSCoder*)aDecoder NS_UNAVAILABLE {
+ NOTREACHED();
+ return nil;
+}
+
+- (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE {
+ NOTREACHED();
+ return nil;
+}
+
+#pragma mark - Public content views
+
+- (void)addContentViews:(NSArray*)contentViews {
+ for (UIView* view in contentViews) {
+ if ([view
+ conformsToProtocol:@protocol(
+ ContextualSearchPanelScrollSynchronizer)]) {
+ self.scrollSynchronizer =
+ static_cast<UIView<ContextualSearchPanelScrollSynchronizer>*>(view);
+ }
+ if ([view conformsToProtocol:@protocol(
+ ContextualSearchPanelMotionObserver)]) {
+ [self
+ addMotionObserver:static_cast<
+ id<ContextualSearchPanelMotionObserver>>(view)];
+ }
+ [_contents addArrangedSubview:view];
+ }
+}
+
+#pragma mark - Public observer methods
+
+- (void)addMotionObserver:(id<ContextualSearchPanelMotionObserver>)observer {
+ [_observers addObserver:observer];
+}
+
+- (void)removeMotionObserver:(id<ContextualSearchPanelMotionObserver>)observer {
+ [_observers removeObserver:observer];
+}
+
+- (void)prepareForPromotion {
+ self.scrollSynchronizer = nil;
+ [_observers panelWillPromote:self];
+}
+
+- (void)promoteToMatchSuperviewWithVerticalOffset:(CGFloat)offset {
+ _resizingForPromotion = YES;
+ _promotionVerticalOffset = offset;
+ [NSLayoutConstraint deactivateConstraints:_sizingConstraints];
+ [NSLayoutConstraint deactivateConstraints:_positioningConstraints];
+ [[_positioningGuide owningView] removeLayoutGuide:_positioningGuide];
+ [_observers panelIsPromoting:self];
+ [self setNeedsUpdateConstraints];
+ [self updateConstraintsIfNeeded];
+ [self layoutIfNeeded];
+}
+
+#pragma mark - Public property getters/setters
+
+- (PanelConfiguration*)configuration {
+ return _configuration.get();
+}
+
+- (void)setScrollSynchronizer:
+ (id<ContextualSearchPanelScrollSynchronizer>)scrollSynchronizer {
+ _scrollSynchronizer.reset(scrollSynchronizer);
+}
+
+- (id<ContextualSearchPanelScrollSynchronizer>)scrollSynchronizer {
+ return _scrollSynchronizer;
+}
+
+- (ContextualSearchPanelObservers*)observers {
+ return _observers;
+}
+
+- (void)setState:(ContextualSearch::PanelState)state {
+ if (state == _state)
+ return;
+
+ [_positioningViewConstraint setActive:NO];
+ _positioningViewConstraint.reset();
+ base::WeakNSObject<ContextualSearchPanelView> weakSelf(self);
+ void (^transform)(void) = ^{
+ base::scoped_nsobject<ContextualSearchPanelView> strongSelf(
+ [weakSelf retain]);
+ if (strongSelf) {
+ [strongSelf setNeedsUpdateConstraints];
+ [[strongSelf superview] layoutIfNeeded];
+ [[strongSelf observers] panel:strongSelf
+ didMoveWithMotion:[strongSelf motion]];
+ }
+ };
+
+ base::mac::ScopedBlock<ProceduralBlockWithBool> completion;
+ NSTimeInterval animationDuration;
+ if (state == ContextualSearch::DISMISSED) {
+ animationDuration = kDismissAnimationDuration;
+ completion.reset(
+ ^(BOOL) {
+ [weakSelf setHidden:YES];
+ },
+ base::scoped_policy::RETAIN);
+ } else {
+ self.hidden = NO;
+ animationDuration = kPanelAnimationDuration;
+ }
+
+ // Animations from a dismissed state are EaseOut, others are EaseInOut.
+ ios::material::Curve curve = _state == ContextualSearch::DISMISSED
+ ? ios::material::CurveEaseOut
+ : ios::material::CurveEaseInOut;
+
+ ContextualSearch::PanelState previousState = _state;
+ _state = state;
+ [_observers panel:self didChangeToState:_state fromState:previousState];
+
+ [UIView cr_animateWithDuration:animationDuration
+ delay:0
+ curve:curve
+ options:UIViewAnimationOptionBeginFromCurrentState
+ animations:transform
+ completion:completion];
+}
+
+#pragma mark - UIView methods
+
+- (void)updateConstraints {
+ if (_resizingForPromotion) {
+ [self.widthAnchor constraintEqualToAnchor:self.superview.widthAnchor]
+ .active = YES;
+ [self.centerXAnchor constraintEqualToAnchor:self.superview.centerXAnchor]
+ .active = YES;
+ [self.heightAnchor constraintEqualToAnchor:self.superview.heightAnchor
+ constant:-_promotionVerticalOffset]
+ .active = YES;
+ [self.topAnchor constraintEqualToAnchor:self.superview.topAnchor
+ constant:_promotionVerticalOffset]
+ .active = YES;
+ } else {
+ // Don't update sizing constraints if there isn't a defined horizontal size
+ // yet.
+ if (self.traitCollection.horizontalSizeClass !=
+ UIUserInterfaceSizeClassUnspecified &&
+ !_sizingConstraints) {
+ _sizingConstraints.reset(
+ [[_configuration constraintsForSizingPanel:self] retain]);
+ [NSLayoutConstraint activateConstraints:_sizingConstraints];
+ }
+ // Update positioning constraints if they don't exist.
+ if (!_positioningConstraints) {
+ NSArray* positioningConstraints = @[
+ [[_positioningGuide topAnchor]
+ constraintEqualToAnchor:self.superview.topAnchor],
+ [[_positioningGuide bottomAnchor]
+ constraintEqualToAnchor:self.topAnchor]
+ ];
+ [NSLayoutConstraint activateConstraints:positioningConstraints];
+
+ _positioningConstraints.reset([positioningConstraints retain]);
+ }
+ // Always update the positioning view constraint.
+ _positioningViewConstraint.reset([self.configuration
+ constraintForPositioningGuide:_positioningGuide
+ atState:self.state]);
+ [_positioningViewConstraint setActive:YES];
+ }
+ [super updateConstraints];
+}
+
+- (void)didMoveToSuperview {
+ if (!self.superview)
+ return;
+ // Set up the invisible positioning view used to constrain this view's
+ // position.
+ UILayoutGuide* positioningGuide = [[[UILayoutGuide alloc] init] autorelease];
+ positioningGuide.identifier = @"contextualSearchPosition";
+ [self.superview addLayoutGuide:positioningGuide];
+ _positioningGuide.reset(positioningGuide);
+ [self setNeedsUpdateConstraints];
+}
+
+- (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
+ if (previousTraitCollection.horizontalSizeClass ==
+ self.traitCollection.horizontalSizeClass) {
+ return;
+ }
+
+ [self dismissPanel];
+
+ [_configuration
+ setHorizontalSizeClass:self.traitCollection.horizontalSizeClass];
+ [NSLayoutConstraint deactivateConstraints:_sizingConstraints];
+ _sizingConstraints.reset();
+ [self setNeedsUpdateConstraints];
+}
+
+- (void)layoutSubviews {
+ [super layoutSubviews];
+ self.configuration.containerSize = self.superview.bounds.size;
+ // Update the shadow path for this view.
+ // Consider switching to "full" MDCShadowLayer.
+ MDCShadowMetrics* metrics =
+ [MDCShadowMetrics metricsWithElevation:kShadowElevation];
+ UIBezierPath* shadowPath = [UIBezierPath bezierPathWithRect:self.bounds];
+ self.layer.shadowPath = shadowPath.CGPath;
+ self.layer.shadowOpacity = metrics.topShadowOpacity;
+ self.layer.shadowRadius = metrics.topShadowRadius;
+}
+
+- (void)dismissPanel {
+ ContextualSearch::PanelMotion motion;
+ motion.state = ContextualSearch::DISMISSED;
+ motion.nextState = ContextualSearch::DISMISSED;
+ motion.gradation = 0;
+ motion.position = 0;
+ [_observers panel:self didStopMovingWithMotion:motion];
+}
+
+- (void)dealloc {
+ [self removeMotionObserver:self];
+ [self removeGestureRecognizer:_dragRecognizer];
+ [[_positioningGuide owningView] removeLayoutGuide:_positioningGuide];
+ [super dealloc];
+}
+
+#pragma mark - Gesture recognizer callbacks
+
+- (void)handleDragFrom:(UIGestureRecognizer*)gestureRecognizer {
+ UIPanGestureRecognizer* recognizer =
+ static_cast<UIPanGestureRecognizer*>(gestureRecognizer);
+ if ([recognizer state] == UIGestureRecognizerStateCancelled) {
+ recognizer.enabled = YES;
+ [self dismissPanel];
+ return;
+ }
+
+ CGPoint dragOffset = [recognizer translationInView:[self superview]];
+ BOOL isScrolling = NO;
+ if (_maybeScrollContent && self.scrollSynchronizer.scrolled) {
+ isScrolling = YES;
+ _scrolledOffset = dragOffset;
+ } else {
+ // Adjust drag offset for prior scrolling
+ dragOffset.y -= _scrolledOffset.y;
+ }
+
+ CGPoint newOrigin = _draggingStartPosition;
+ newOrigin.y += dragOffset.y;
+
+ // Clamp the drag to covering height.
+ CGFloat coveringY =
+ [self.configuration positionForPanelState:ContextualSearch::COVERING];
+ if (newOrigin.y < coveringY) {
+ newOrigin.y = coveringY;
+ dragOffset.y = coveringY - _draggingStartPosition.y;
+ [recognizer setTranslation:dragOffset inView:[self superview]];
+ }
+
+ // If the view hasn't moved up yet and it's moving down (dragOffset.y > 0)
+ // and it's moving from a peeking state, clamp the offset y to 0.
+ if (_state == ContextualSearch::PEEKING && !_hasDraggedUp &&
+ dragOffset.y > 0) {
+ dragOffset.y = 0;
+ [recognizer setTranslation:dragOffset inView:[self superview]];
+ }
+
+ switch ([recognizer state]) {
+ case UIGestureRecognizerStateBegan:
+ _draggingStartPosition = self.frame.origin;
+ _scrolledOffset = CGPointZero;
+ _hasDraggedUp = NO;
+ _maybeScrollContent = CGRectContainsPoint(
+ self.scrollSynchronizer.frame, [recognizer locationInView:self]);
+ break;
+ case UIGestureRecognizerStateEnded:
+ if (!CGPointEqualToPoint(self.frame.origin, _draggingStartPosition))
+ [_observers panel:self didStopMovingWithMotion:[self motion]];
+ break;
+ case UIGestureRecognizerStateChanged: {
+ if (!_hasDraggedUp && dragOffset.y < 0)
+ _hasDraggedUp = YES;
+
+ // Don't drag the pane if scrolling is happening.
+ if (isScrolling)
+ break;
+
+ CGRect frame = self.frame;
+ frame.origin.y = _draggingStartPosition.y + dragOffset.y;
+ self.frame = frame;
+ [_observers panel:self didMoveWithMotion:[self motion]];
+ } break;
+ default:
+ break;
+ }
+}
+
+- (ContextualSearch::PanelMotion)motion {
+ ContextualSearch::PanelMotion motion;
+ motion.position = self.frame.origin.y;
+ motion.state = [self.configuration panelStateForPosition:motion.position];
+ motion.nextState = static_cast<ContextualSearch::PanelState>(
+ MIN(motion.state + 1, ContextualSearch::COVERING));
+ motion.gradation = [_configuration gradationToState:motion.nextState
+ fromState:motion.state
+ atPosition:motion.position];
+ return motion;
+}
+
+#pragma mark - ContextualSearchPanelMotionDelegate methods
+
+- (void)panel:(ContextualSearchPanelView*)panel
+ didMoveWithMotion:(ContextualSearch::PanelMotion)motion {
+ if (motion.state == ContextualSearch::PREVIEWING) {
+ MDCShadowMetrics* metrics =
+ [MDCShadowMetrics metricsWithElevation:kShadowElevation];
+ self.layer.shadowOpacity =
+ metrics.topShadowOpacity * (1.0 - motion.gradation);
+ }
+}
+
+#pragma mark - UIGestureRecognizerDelegate methods
+
+- (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
+ shouldRecognizeSimultaneouslyWithGestureRecognizer:
+ (UIGestureRecognizer*)otherGestureRecognizer {
+ // Allow the drag recognizer and the panel content scroll recognizer to
+ // co-recognize.
+ if (gestureRecognizer == _dragRecognizer.get() &&
+ otherGestureRecognizer == self.scrollSynchronizer.scrollRecognizer) {
+ return YES;
+ }
+
+ if (gestureRecognizer == _dragRecognizer.get() &&
+ [_dragRecognizer state] == UIGestureRecognizerStateChanged) {
+ [gestureRecognizer setEnabled:NO];
+ }
+ return NO;
+}
+
+@end

Powered by Google App Engine
This is Rietveld 408576698