blob: 8e72ba1fbf107ec5d4f9bd6632f5c5c53f2de699 [file] [log] [blame]
// Copyright 2018 The Chromium Authors
// 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/omnibox/popup/omnibox_popup_presenter.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/browser/shared/ui/util/layout_guide_names.h"
#import "ios/chrome/browser/shared/ui/util/uikit_ui_util.h"
#import "ios/chrome/browser/shared/ui/util/util_swift.h"
#import "ios/chrome/browser/ui/omnibox/omnibox_ui_features.h"
#import "ios/chrome/browser/ui/omnibox/popup/content_providing.h"
#import "ios/chrome/browser/ui/omnibox/popup/omnibox_popup_container_view.h"
#import "ios/chrome/browser/ui/toolbar/buttons/toolbar_configuration.h"
#import "ios/chrome/browser/ui/toolbar/public/toolbar_constants.h"
#import "ios/chrome/common/ui/colors/semantic_color_names.h"
#import "ios/chrome/common/ui/util/constraints_ui_util.h"
#import "ios/chrome/grit/ios_theme_resources.h"
#import "ui/base/device_form_factor.h"
#import "ui/gfx/ios/uikit_util.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
namespace {
const CGFloat kVerticalOffset = 6;
const CGFloat kPopupBottomPaddingTablet = 80;
} // namespace
@interface OmniboxPopupPresenter ()
/// Constraint for the bottom anchor of the popup when form factor is phone.
@property(nonatomic, strong) NSLayoutConstraint* bottomConstraintPhone;
/// Constraint for the bottom anchor of the popup when form factor is tablet.
@property(nonatomic, strong) NSLayoutConstraint* bottomConstraintTablet;
@property(nonatomic, weak) id<OmniboxPopupPresenterDelegate> delegate;
@property(nonatomic, weak) UIViewController<ContentProviding>* viewController;
@property(nonatomic, strong) UIView* popupContainerView;
/// Separator for the bottom edge of the popup on iPad.
@property(nonatomic, strong) UIView* bottomSeparator;
// The layout guide center to use to refer to the omnibox.
@property(nonatomic, strong) LayoutGuideCenter* layoutGuideCenter;
@end
@implementation OmniboxPopupPresenter
- (instancetype)
initWithPopupPresenterDelegate:(id<OmniboxPopupPresenterDelegate>)delegate
popupViewController:
(UIViewController<ContentProviding>*)viewController
layoutGuideCenter:(LayoutGuideCenter*)layoutGuideCenter
incognito:(BOOL)incognito {
self = [super init];
if (self) {
_delegate = delegate;
_viewController = viewController;
_layoutGuideCenter = layoutGuideCenter;
// Popup uses same colors as the toolbar, so the ToolbarConfiguration is
// used to get the style.
ToolbarConfiguration* configuration = [[ToolbarConfiguration alloc]
initWithStyle:incognito ? ToolbarStyle::kIncognito
: ToolbarStyle::kNormal];
UIView* containerView = [[OmniboxPopupContainerView alloc] init];
[containerView addSubview:viewController.view];
_popupContainerView = containerView;
UIUserInterfaceStyle userInterfaceStyle =
incognito ? UIUserInterfaceStyleDark : UIUserInterfaceStyleUnspecified;
// Both the container view and the popup view controller need
// overrideUserInterfaceStyle set because the overall popup background
// comes from the container, but overrideUserInterfaceStyle won't
// propagate from a view to any subviews in a different view controller.
_popupContainerView.overrideUserInterfaceStyle = userInterfaceStyle;
viewController.overrideUserInterfaceStyle = userInterfaceStyle;
_popupContainerView.backgroundColor = [configuration backgroundColor];
_popupContainerView.translatesAutoresizingMaskIntoConstraints = NO;
viewController.view.translatesAutoresizingMaskIntoConstraints = NO;
if (IsIpadPopoutOmniboxEnabled()) {
_popupContainerView.clipsToBounds = YES;
_popupContainerView.layer.cornerRadius = 11.0f;
UIColor* borderColor =
incognito ? [UIColor.whiteColor colorWithAlphaComponent:0.12]
: [UIColor.blackColor colorWithAlphaComponent:0.12];
_popupContainerView.layer.borderColor = borderColor.CGColor;
_popupContainerView.layer.borderWidth = 2.0f;
AddSameConstraints(viewController.view, _popupContainerView);
} else {
AddSameConstraints(viewController.view, _popupContainerView);
}
// Add bottom separator. This will only be visible on iPad where
// the omnibox doesn't fill the whole screen.
_bottomSeparator = [[UIView alloc] initWithFrame:CGRectZero];
_bottomSeparator.translatesAutoresizingMaskIntoConstraints = NO;
_bottomSeparator.backgroundColor = [UIColor colorNamed:kToolbarShadowColor];
[_popupContainerView addSubview:self.bottomSeparator];
CGFloat separatorHeight =
ui::AlignValueToUpperPixel(kToolbarSeparatorHeight);
[NSLayoutConstraint activateConstraints:@[
[self.bottomSeparator.heightAnchor
constraintEqualToConstant:separatorHeight],
[self.bottomSeparator.leadingAnchor
constraintEqualToAnchor:_popupContainerView.leadingAnchor],
[self.bottomSeparator.trailingAnchor
constraintEqualToAnchor:_popupContainerView.trailingAnchor],
[self.bottomSeparator.topAnchor
constraintEqualToAnchor:_popupContainerView.bottomAnchor],
]];
}
return self;
}
- (void)updatePopup {
BOOL popupHasContent = self.viewController.hasContent;
BOOL popupIsOnscreen = self.popupContainerView.superview != nil;
if (!popupHasContent && popupIsOnscreen) {
// If intrinsic size is 0 and popup is onscreen, we want to remove the
// popup view.
if (ui::GetDeviceFormFactor() != ui::DEVICE_FORM_FACTOR_TABLET) {
self.bottomConstraintPhone.active = NO;
self.bottomSeparator.hidden = YES;
} else if (!IsIpadPopoutOmniboxEnabled()) {
self.bottomConstraintTablet.active = NO;
}
[self.viewController willMoveToParentViewController:nil];
[self.popupContainerView removeFromSuperview];
[self.viewController removeFromParentViewController];
self.open = NO;
[self.delegate popupDidCloseForPresenter:self];
} else if (popupHasContent && !popupIsOnscreen) {
// If intrinsic size is nonzero and popup is offscreen, we want to add it.
UIViewController* parentVC =
[self.delegate popupParentViewControllerForPresenter:self];
[parentVC addChildViewController:self.viewController];
[[self.delegate popupParentViewForPresenter:self]
addSubview:self.popupContainerView];
[self.viewController didMoveToParentViewController:parentVC];
[self initialLayout];
if (ui::GetDeviceFormFactor() != ui::DEVICE_FORM_FACTOR_TABLET) {
self.bottomConstraintPhone.active = YES;
self.bottomSeparator.hidden = NO;
} else if (!IsIpadPopoutOmniboxEnabled()) {
self.bottomConstraintTablet.active = YES;
}
self.open = YES;
[self.delegate popupDidOpenForPresenter:self];
}
}
/// With popout omnibox, the popup might be in either of two states:
/// a) regular x regular state, where the popup matches OB width
/// b) compact state, where popup takes whole screen width
/// Therefore, on trait collection change, re-add the popup and recreate the
/// constraints to make sure the correct ones are used.
- (void)updatePopupAfterTraitCollectionChange {
DCHECK(IsIpadPopoutOmniboxEnabled());
if (!self.open) {
return;
}
// Re-add the popup container to break any existing constraints.
[self.popupContainerView removeFromSuperview];
[[self.delegate popupParentViewForPresenter:self]
addSubview:self.popupContainerView];
// Re-add necessary constraints.
[self initialLayout];
if (ui::GetDeviceFormFactor() != ui::DEVICE_FORM_FACTOR_TABLET) {
self.bottomConstraintPhone.active = YES;
self.bottomSeparator.hidden = NO;
}
}
#pragma mark - Private
/// Layouts the popup when it is just added to the view hierarchy.
- (void)initialLayout {
UIView* popup = self.popupContainerView;
// Creates the constraints if the view is newly added to the view hierarchy.
// On phone form factor the popup is taking the full height.
self.bottomConstraintPhone =
[popup.bottomAnchor constraintEqualToAnchor:popup.superview.bottomAnchor];
// On tablet form factor the popup is padded on the bottom to allow the user
// to defocus the omnibox.
self.bottomConstraintTablet = [popup.superview.bottomAnchor
constraintGreaterThanOrEqualToAnchor:popup.bottomAnchor
constant:kPopupBottomPaddingTablet];
// Install in the superview the guide tracking the omnibox.
UILayoutGuide* omniboxGuide =
[self.layoutGuideCenter makeLayoutGuideNamed:kOmniboxGuide];
[[popup superview] addLayoutGuide:omniboxGuide];
// Position the top anchor of the popup relatively to that layout guide.
NSLayoutConstraint* topConstraint =
[popup.topAnchor constraintEqualToAnchor:omniboxGuide.bottomAnchor
constant:kVerticalOffset];
NSMutableArray<NSLayoutConstraint*>* constraintsToActivate =
[NSMutableArray arrayWithObject:topConstraint];
if (IsIpadPopoutOmniboxEnabled() &&
IsRegularXRegularSizeClass(self.popupContainerView)) {
[constraintsToActivate addObjectsFromArray:@[
[popup.leadingAnchor constraintEqualToAnchor:omniboxGuide.leadingAnchor],
[popup.trailingAnchor
constraintEqualToAnchor:omniboxGuide.trailingAnchor],
]];
} else {
[constraintsToActivate addObjectsFromArray:@[
[popup.leadingAnchor
constraintEqualToAnchor:popup.superview.leadingAnchor],
[popup.trailingAnchor
constraintEqualToAnchor:popup.superview.trailingAnchor],
]];
}
[NSLayoutConstraint activateConstraints:constraintsToActivate];
[[popup superview] layoutIfNeeded];
}
@end