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

Side by Side 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 unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #import "ios/chrome/browser/ui/contextual_search/contextual_search_panel_view.h"
6
7 #import "base/ios/crb_protocol_observers.h"
8 #include "base/ios/weak_nsobject.h"
9 #include "base/logging.h"
10 #include "base/mac/scoped_block.h"
11 #include "base/mac/scoped_nsobject.h"
12 #import "ios/chrome/browser/procedural_block_types.h"
13 #import "ios/chrome/browser/ui/contextual_search/contextual_search_panel_protoco ls.h"
14 #import "ios/chrome/browser/ui/uikit_ui_util.h"
15 #import "ios/chrome/common/material_timing.h"
16 #import "ios/third_party/material_components_ios/src/components/ShadowElevations /src/MaterialShadowElevations.h"
17 #import "ios/third_party/material_components_ios/src/components/ShadowLayer/src/ MaterialShadowLayer.h"
18
19 namespace {
20
21 // Animation timings.
22 const NSTimeInterval kPanelAnimationDuration = ios::material::kDuration3;
23 const NSTimeInterval kDismissAnimationDuration = ios::material::kDuration1;
24
25 // Elevation (in MD vertical space) of the panel when dismissed and peeking.
26 const CGFloat kShadowElevation = MDCShadowElevationMenu;
27
28 } // namespace
29
30 @interface ContextualSearchPanelObservers
31 : CRBProtocolObservers<ContextualSearchPanelMotionObserver>
32 @end
33 @implementation ContextualSearchPanelObservers
34
35 @end
36
37 @interface ContextualSearchPanelView ()<UIGestureRecognizerDelegate,
38 ContextualSearchPanelMotionObserver>
39
40 // A subview whose content scrolls and whose scrolling is synchronized with
41 // panel dragging. This means that if the scrolling subview is being scrolled,
42 // that motion will not cause the panel to move, but if the scrolling reaches
43 // the end of its possible range, the gesture will then start dragging the
44 // panel.
45 @property(nonatomic, assign)
46 UIView<ContextualSearchPanelScrollSynchronizer>* scrollSynchronizer;
47
48 // Private readonly property to be used by weak pointers to |self| for non-
49 // retaining access to the underlying ivar in blocks.
50 @property(nonatomic, readonly) ContextualSearchPanelObservers* observers;
51
52 // Utility to generate a PanelMotion struct for the panel's current position.
53 - (ContextualSearch::PanelMotion)motion;
54 @end
55
56 @implementation ContextualSearchPanelView {
57 UIStackView* _contents;
58
59 // Constraints that define the size of this view. These will be cleared and
60 // regenerated when the horizontal size class changes.
61 base::scoped_nsobject<NSArray> _sizingConstraints;
62
63 CGPoint _draggingStartPosition;
64 CGPoint _scrolledOffset;
65 base::scoped_nsobject<UIPanGestureRecognizer> _dragRecognizer;
66
67 base::scoped_nsobject<ContextualSearchPanelObservers> _observers;
68
69 base::scoped_nsobject<PanelConfiguration> _configuration;
70
71 base::WeakNSProtocol<id<ContextualSearchPanelScrollSynchronizer>>
72 _scrollSynchronizer;
73
74 // Guide that's used to position this view.
75 base::WeakNSObject<UILayoutGuide> _positioningGuide;
76 // Constraint that sets the size of |_positioningView| so this view is
77 // positioned correctly for its state.
78 base::WeakNSObject<NSLayoutConstraint> _positioningViewConstraint;
79 // Other constraints that determine the position of this view.
80 base::scoped_nsobject<NSArray> _positioningConstraints;
81
82 // Promotion state variables.
83 BOOL _resizingForPromotion;
84 CGFloat _promotionVerticalOffset;
85
86 // YES if dragging started inside the content view and scrolling is possible.
87 BOOL _maybeScrollContent;
88 // YES if the drag is happening along with scrolling the content view.
89 BOOL _isScrollingContent;
90
91 // YES if dragging upwards has occurred.
92 BOOL _hasDraggedUp;
93 }
94
95 @synthesize state = _state;
96
97 + (BOOL)requiresConstraintBasedLayout {
98 return YES;
99 }
100
101 #pragma mark - Initializers
102
103 - (instancetype)initWithConfiguration:(PanelConfiguration*)configuration {
104 if ((self = [super initWithFrame:CGRectZero])) {
105 _configuration.reset([configuration retain]);
106 _state = ContextualSearch::DISMISSED;
107
108 self.translatesAutoresizingMaskIntoConstraints = NO;
109 self.backgroundColor = [UIColor whiteColor];
110 self.accessibilityIdentifier = @"contextualSearchPanel";
111
112 _observers.reset([[ContextualSearchPanelObservers
113 observersWithProtocol:@protocol(ContextualSearchPanelMotionObserver)]
114 retain]);
115 [self addMotionObserver:self];
116
117 // Add gesture recognizer.
118 _dragRecognizer.reset([[UIPanGestureRecognizer alloc]
119 initWithTarget:self
120 action:@selector(handleDragFrom:)]);
121 [self addGestureRecognizer:_dragRecognizer];
122 [_dragRecognizer setDelegate:self];
123
124 // Set up the stack view that holds the panel content
125 _contents = [[[UIStackView alloc] initWithFrame:self.bounds] autorelease];
126 [self addSubview:_contents];
127 _contents.translatesAutoresizingMaskIntoConstraints = NO;
128 _contents.accessibilityIdentifier = @"panelContents";
129 [NSLayoutConstraint activateConstraints:@[
130 [_contents.centerXAnchor constraintEqualToAnchor:self.centerXAnchor],
131 [_contents.centerYAnchor constraintEqualToAnchor:self.centerYAnchor],
132 [_contents.widthAnchor constraintEqualToAnchor:self.widthAnchor],
133 [_contents.heightAnchor constraintEqualToAnchor:self.heightAnchor]
134 ]];
135 _contents.axis = UILayoutConstraintAxisVertical;
136 }
137 return self;
138 }
139
140 - (instancetype)initWithCoder:(NSCoder*)aDecoder NS_UNAVAILABLE {
141 NOTREACHED();
142 return nil;
143 }
144
145 - (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE {
146 NOTREACHED();
147 return nil;
148 }
149
150 #pragma mark - Public content views
151
152 - (void)addContentViews:(NSArray*)contentViews {
153 for (UIView* view in contentViews) {
154 if ([view
155 conformsToProtocol:@protocol(
156 ContextualSearchPanelScrollSynchronizer)]) {
157 self.scrollSynchronizer =
158 static_cast<UIView<ContextualSearchPanelScrollSynchronizer>*>(view);
159 }
160 if ([view conformsToProtocol:@protocol(
161 ContextualSearchPanelMotionObserver)]) {
162 [self
163 addMotionObserver:static_cast<
164 id<ContextualSearchPanelMotionObserver>>(view)];
165 }
166 [_contents addArrangedSubview:view];
167 }
168 }
169
170 #pragma mark - Public observer methods
171
172 - (void)addMotionObserver:(id<ContextualSearchPanelMotionObserver>)observer {
173 [_observers addObserver:observer];
174 }
175
176 - (void)removeMotionObserver:(id<ContextualSearchPanelMotionObserver>)observer {
177 [_observers removeObserver:observer];
178 }
179
180 - (void)prepareForPromotion {
181 self.scrollSynchronizer = nil;
182 [_observers panelWillPromote:self];
183 }
184
185 - (void)promoteToMatchSuperviewWithVerticalOffset:(CGFloat)offset {
186 _resizingForPromotion = YES;
187 _promotionVerticalOffset = offset;
188 [NSLayoutConstraint deactivateConstraints:_sizingConstraints];
189 [NSLayoutConstraint deactivateConstraints:_positioningConstraints];
190 [[_positioningGuide owningView] removeLayoutGuide:_positioningGuide];
191 [_observers panelIsPromoting:self];
192 [self setNeedsUpdateConstraints];
193 [self updateConstraintsIfNeeded];
194 [self layoutIfNeeded];
195 }
196
197 #pragma mark - Public property getters/setters
198
199 - (PanelConfiguration*)configuration {
200 return _configuration.get();
201 }
202
203 - (void)setScrollSynchronizer:
204 (id<ContextualSearchPanelScrollSynchronizer>)scrollSynchronizer {
205 _scrollSynchronizer.reset(scrollSynchronizer);
206 }
207
208 - (id<ContextualSearchPanelScrollSynchronizer>)scrollSynchronizer {
209 return _scrollSynchronizer;
210 }
211
212 - (ContextualSearchPanelObservers*)observers {
213 return _observers;
214 }
215
216 - (void)setState:(ContextualSearch::PanelState)state {
217 if (state == _state)
218 return;
219
220 [_positioningViewConstraint setActive:NO];
221 _positioningViewConstraint.reset();
222 base::WeakNSObject<ContextualSearchPanelView> weakSelf(self);
223 void (^transform)(void) = ^{
224 base::scoped_nsobject<ContextualSearchPanelView> strongSelf(
225 [weakSelf retain]);
226 if (strongSelf) {
227 [strongSelf setNeedsUpdateConstraints];
228 [[strongSelf superview] layoutIfNeeded];
229 [[strongSelf observers] panel:strongSelf
230 didMoveWithMotion:[strongSelf motion]];
231 }
232 };
233
234 base::mac::ScopedBlock<ProceduralBlockWithBool> completion;
235 NSTimeInterval animationDuration;
236 if (state == ContextualSearch::DISMISSED) {
237 animationDuration = kDismissAnimationDuration;
238 completion.reset(
239 ^(BOOL) {
240 [weakSelf setHidden:YES];
241 },
242 base::scoped_policy::RETAIN);
243 } else {
244 self.hidden = NO;
245 animationDuration = kPanelAnimationDuration;
246 }
247
248 // Animations from a dismissed state are EaseOut, others are EaseInOut.
249 ios::material::Curve curve = _state == ContextualSearch::DISMISSED
250 ? ios::material::CurveEaseOut
251 : ios::material::CurveEaseInOut;
252
253 ContextualSearch::PanelState previousState = _state;
254 _state = state;
255 [_observers panel:self didChangeToState:_state fromState:previousState];
256
257 [UIView cr_animateWithDuration:animationDuration
258 delay:0
259 curve:curve
260 options:UIViewAnimationOptionBeginFromCurrentState
261 animations:transform
262 completion:completion];
263 }
264
265 #pragma mark - UIView methods
266
267 - (void)updateConstraints {
268 if (_resizingForPromotion) {
269 [self.widthAnchor constraintEqualToAnchor:self.superview.widthAnchor]
270 .active = YES;
271 [self.centerXAnchor constraintEqualToAnchor:self.superview.centerXAnchor]
272 .active = YES;
273 [self.heightAnchor constraintEqualToAnchor:self.superview.heightAnchor
274 constant:-_promotionVerticalOffset]
275 .active = YES;
276 [self.topAnchor constraintEqualToAnchor:self.superview.topAnchor
277 constant:_promotionVerticalOffset]
278 .active = YES;
279 } else {
280 // Don't update sizing constraints if there isn't a defined horizontal size
281 // yet.
282 if (self.traitCollection.horizontalSizeClass !=
283 UIUserInterfaceSizeClassUnspecified &&
284 !_sizingConstraints) {
285 _sizingConstraints.reset(
286 [[_configuration constraintsForSizingPanel:self] retain]);
287 [NSLayoutConstraint activateConstraints:_sizingConstraints];
288 }
289 // Update positioning constraints if they don't exist.
290 if (!_positioningConstraints) {
291 NSArray* positioningConstraints = @[
292 [[_positioningGuide topAnchor]
293 constraintEqualToAnchor:self.superview.topAnchor],
294 [[_positioningGuide bottomAnchor]
295 constraintEqualToAnchor:self.topAnchor]
296 ];
297 [NSLayoutConstraint activateConstraints:positioningConstraints];
298
299 _positioningConstraints.reset([positioningConstraints retain]);
300 }
301 // Always update the positioning view constraint.
302 _positioningViewConstraint.reset([self.configuration
303 constraintForPositioningGuide:_positioningGuide
304 atState:self.state]);
305 [_positioningViewConstraint setActive:YES];
306 }
307 [super updateConstraints];
308 }
309
310 - (void)didMoveToSuperview {
311 if (!self.superview)
312 return;
313 // Set up the invisible positioning view used to constrain this view's
314 // position.
315 UILayoutGuide* positioningGuide = [[[UILayoutGuide alloc] init] autorelease];
316 positioningGuide.identifier = @"contextualSearchPosition";
317 [self.superview addLayoutGuide:positioningGuide];
318 _positioningGuide.reset(positioningGuide);
319 [self setNeedsUpdateConstraints];
320 }
321
322 - (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
323 if (previousTraitCollection.horizontalSizeClass ==
324 self.traitCollection.horizontalSizeClass) {
325 return;
326 }
327
328 [self dismissPanel];
329
330 [_configuration
331 setHorizontalSizeClass:self.traitCollection.horizontalSizeClass];
332 [NSLayoutConstraint deactivateConstraints:_sizingConstraints];
333 _sizingConstraints.reset();
334 [self setNeedsUpdateConstraints];
335 }
336
337 - (void)layoutSubviews {
338 [super layoutSubviews];
339 self.configuration.containerSize = self.superview.bounds.size;
340 // Update the shadow path for this view.
341 // Consider switching to "full" MDCShadowLayer.
342 MDCShadowMetrics* metrics =
343 [MDCShadowMetrics metricsWithElevation:kShadowElevation];
344 UIBezierPath* shadowPath = [UIBezierPath bezierPathWithRect:self.bounds];
345 self.layer.shadowPath = shadowPath.CGPath;
346 self.layer.shadowOpacity = metrics.topShadowOpacity;
347 self.layer.shadowRadius = metrics.topShadowRadius;
348 }
349
350 - (void)dismissPanel {
351 ContextualSearch::PanelMotion motion;
352 motion.state = ContextualSearch::DISMISSED;
353 motion.nextState = ContextualSearch::DISMISSED;
354 motion.gradation = 0;
355 motion.position = 0;
356 [_observers panel:self didStopMovingWithMotion:motion];
357 }
358
359 - (void)dealloc {
360 [self removeMotionObserver:self];
361 [self removeGestureRecognizer:_dragRecognizer];
362 [[_positioningGuide owningView] removeLayoutGuide:_positioningGuide];
363 [super dealloc];
364 }
365
366 #pragma mark - Gesture recognizer callbacks
367
368 - (void)handleDragFrom:(UIGestureRecognizer*)gestureRecognizer {
369 UIPanGestureRecognizer* recognizer =
370 static_cast<UIPanGestureRecognizer*>(gestureRecognizer);
371 if ([recognizer state] == UIGestureRecognizerStateCancelled) {
372 recognizer.enabled = YES;
373 [self dismissPanel];
374 return;
375 }
376
377 CGPoint dragOffset = [recognizer translationInView:[self superview]];
378 BOOL isScrolling = NO;
379 if (_maybeScrollContent && self.scrollSynchronizer.scrolled) {
380 isScrolling = YES;
381 _scrolledOffset = dragOffset;
382 } else {
383 // Adjust drag offset for prior scrolling
384 dragOffset.y -= _scrolledOffset.y;
385 }
386
387 CGPoint newOrigin = _draggingStartPosition;
388 newOrigin.y += dragOffset.y;
389
390 // Clamp the drag to covering height.
391 CGFloat coveringY =
392 [self.configuration positionForPanelState:ContextualSearch::COVERING];
393 if (newOrigin.y < coveringY) {
394 newOrigin.y = coveringY;
395 dragOffset.y = coveringY - _draggingStartPosition.y;
396 [recognizer setTranslation:dragOffset inView:[self superview]];
397 }
398
399 // If the view hasn't moved up yet and it's moving down (dragOffset.y > 0)
400 // and it's moving from a peeking state, clamp the offset y to 0.
401 if (_state == ContextualSearch::PEEKING && !_hasDraggedUp &&
402 dragOffset.y > 0) {
403 dragOffset.y = 0;
404 [recognizer setTranslation:dragOffset inView:[self superview]];
405 }
406
407 switch ([recognizer state]) {
408 case UIGestureRecognizerStateBegan:
409 _draggingStartPosition = self.frame.origin;
410 _scrolledOffset = CGPointZero;
411 _hasDraggedUp = NO;
412 _maybeScrollContent = CGRectContainsPoint(
413 self.scrollSynchronizer.frame, [recognizer locationInView:self]);
414 break;
415 case UIGestureRecognizerStateEnded:
416 if (!CGPointEqualToPoint(self.frame.origin, _draggingStartPosition))
417 [_observers panel:self didStopMovingWithMotion:[self motion]];
418 break;
419 case UIGestureRecognizerStateChanged: {
420 if (!_hasDraggedUp && dragOffset.y < 0)
421 _hasDraggedUp = YES;
422
423 // Don't drag the pane if scrolling is happening.
424 if (isScrolling)
425 break;
426
427 CGRect frame = self.frame;
428 frame.origin.y = _draggingStartPosition.y + dragOffset.y;
429 self.frame = frame;
430 [_observers panel:self didMoveWithMotion:[self motion]];
431 } break;
432 default:
433 break;
434 }
435 }
436
437 - (ContextualSearch::PanelMotion)motion {
438 ContextualSearch::PanelMotion motion;
439 motion.position = self.frame.origin.y;
440 motion.state = [self.configuration panelStateForPosition:motion.position];
441 motion.nextState = static_cast<ContextualSearch::PanelState>(
442 MIN(motion.state + 1, ContextualSearch::COVERING));
443 motion.gradation = [_configuration gradationToState:motion.nextState
444 fromState:motion.state
445 atPosition:motion.position];
446 return motion;
447 }
448
449 #pragma mark - ContextualSearchPanelMotionDelegate methods
450
451 - (void)panel:(ContextualSearchPanelView*)panel
452 didMoveWithMotion:(ContextualSearch::PanelMotion)motion {
453 if (motion.state == ContextualSearch::PREVIEWING) {
454 MDCShadowMetrics* metrics =
455 [MDCShadowMetrics metricsWithElevation:kShadowElevation];
456 self.layer.shadowOpacity =
457 metrics.topShadowOpacity * (1.0 - motion.gradation);
458 }
459 }
460
461 #pragma mark - UIGestureRecognizerDelegate methods
462
463 - (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
464 shouldRecognizeSimultaneouslyWithGestureRecognizer:
465 (UIGestureRecognizer*)otherGestureRecognizer {
466 // Allow the drag recognizer and the panel content scroll recognizer to
467 // co-recognize.
468 if (gestureRecognizer == _dragRecognizer.get() &&
469 otherGestureRecognizer == self.scrollSynchronizer.scrollRecognizer) {
470 return YES;
471 }
472
473 if (gestureRecognizer == _dragRecognizer.get() &&
474 [_dragRecognizer state] == UIGestureRecognizerStateChanged) {
475 [gestureRecognizer setEnabled:NO];
476 }
477 return NO;
478 }
479
480 @end
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698