blob: 6d0ce430d76e481ad47e919dc0907b10db26988d [file] [log] [blame]
// Copyright 2017 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/omnibox/clipping_textfield_container.h"
#include "base/strings/sys_string_conversions.h"
#include "components/omnibox/browser/autocomplete_input.h"
#include "ios/chrome/browser/autocomplete/autocomplete_scheme_classifier_impl.h"
#import "ios/chrome/browser/ui/omnibox/clipping_mask_view.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
@interface ClippingTextFieldContainer ()
@property(nonatomic, strong) NSLayoutConstraint* leftConstraint;
@property(nonatomic, strong) NSLayoutConstraint* rightConstraint;
@property(nonatomic, strong) ClippingMaskView* gradientMaskView;
@property(nonatomic, assign, getter=isClipping) BOOL clipping;
@end
@implementation ClippingTextFieldContainer
@synthesize textField = _textField;
@synthesize leftConstraint = _leftConstraint;
@synthesize rightConstraint = _rightConstraint;
@synthesize gradientMaskView = _gradientMaskView;
@synthesize clipping = _clipping;
- (instancetype)initWithClippingTextField:(ClippingTextField*)textField {
self = [super initWithFrame:CGRectZero];
if (self) {
_textField = textField;
textField.clippingTextfieldDelegate = self;
[self addSubview:textField];
_gradientMaskView = [[ClippingMaskView alloc] init];
self.maskView = _gradientMaskView;
// Configure layout.
// Left and Right are used instead of Leading and Trailing because the
// clipping only ever applies to URLs that are always displayed in LTR.
_leftConstraint =
[self.textField.leftAnchor constraintEqualToAnchor:self.leftAnchor];
_rightConstraint =
[self.textField.rightAnchor constraintEqualToAnchor:self.rightAnchor];
[NSLayoutConstraint activateConstraints:@[
[textField.topAnchor constraintEqualToAnchor:self.topAnchor],
[textField.bottomAnchor constraintEqualToAnchor:self.bottomAnchor],
_leftConstraint,
_rightConstraint,
]];
textField.translatesAutoresizingMaskIntoConstraints = NO;
self.clipsToBounds = YES;
_clipping = NO;
}
return self;
}
- (void)layoutSubviews {
if ([self isClipping]) {
[self applyClipping];
}
[super layoutSubviews];
self.gradientMaskView.frame = self.bounds;
}
- (void)startClipping {
self.clipping = YES;
[self applyClipping];
[self setNeedsLayout];
[self layoutIfNeeded];
}
- (void)applyClipping {
CGFloat suffixWidth = 0;
CGFloat prefixWidth =
-[self leftConstantWithAttributedText:self.textField.attributedText
rightConstant:&suffixWidth];
[self applyGradientsToPrefix:(prefixWidth < 0) suffix:(suffixWidth > 0)];
self.leftConstraint.constant = prefixWidth;
self.rightConstraint.constant = suffixWidth;
}
- (void)stopClipping {
if (![self isClipping]) {
return;
}
self.clipping = NO;
self.leftConstraint.constant = 0;
self.rightConstraint.constant = 0;
[self removeGradient];
}
// Fade the beginning and/or end of the visible string to indicate to the user
// that the URL has been clipped.
- (void)applyGradientsToPrefix:(BOOL)shouldApplyToPrefix
suffix:(BOOL)shouldApplyToSuffix {
self.gradientMaskView.fadeLeft = shouldApplyToPrefix;
self.gradientMaskView.fadeRight = shouldApplyToSuffix;
}
- (void)removeGradient {
[self applyGradientsToPrefix:NO suffix:NO];
}
#pragma mark calculate clipping
// Calculates the length (in pts) of the clipped text on the left and
// right sides of the omnibox in order to show the most significant part of
// the hostname.
- (CGFloat)leftConstantWithAttributedText:(NSAttributedString*)attributedText
rightConstant:(out CGFloat*)right {
// The goal is to always show the most significant part of the hostname
// (i.e. the end of the TLD).
//
// --------------------
// www.somereallyreally|longdomainname.com|/path/gets/clipped
// --------------------
// { clipped prefix } { visible text } { clipped suffix }
// First find how much (if any) of the scheme/host needs to be clipped so that
// the end of the TLD fits in bounds.
CGFloat widthOfClippedPrefix = 0;
url::Component scheme, host;
AutocompleteInput::ParseForEmphasizeComponents(
base::SysNSStringToUTF16(attributedText.string),
AutocompleteSchemeClassifierImpl(), &scheme, &host);
if (host.len < 0) {
return 0;
}
NSRange hostRange = NSMakeRange(0, host.begin + host.len);
NSAttributedString* hostString =
[attributedText attributedSubstringFromRange:hostRange];
CGFloat widthOfHost = ceil(hostString.size.width);
widthOfClippedPrefix = MAX(widthOfHost - self.bounds.size.width, 0);
// Now determine if there is any text that will need to be truncated because
// there's not enough room.
int textWidth = ceil(attributedText.size.width);
CGFloat widthOfClippedSuffix =
MAX(textWidth - self.bounds.size.width - widthOfClippedPrefix, 0);
*right = widthOfClippedSuffix;
return widthOfClippedPrefix;
}
#pragma mark - ClippingTextFieldDelegate
- (void)textFieldTextChanged:(UITextField*)sender {
[self startClipping];
}
- (void)textFieldBecameFirstResponder:(UITextField*)sender {
[self stopClipping];
}
- (void)textFieldResignedFirstResponder:(UITextField*)sender {
[self startClipping];
}
@end