Commit 93a0598a authored by stkhapugin@chromium.org's avatar stkhapugin@chromium.org Committed by Commit Bot

[iOS] Support up/down arrows in omnibox.

Adds support for up/down arrows in the iOS omnibox. Allows highlighting
suggestions from the list and choosing them with Enter.

Bug: 267089
Cq-Include-Trybots: master.tryserver.chromium.mac:ios-simulator-cronet;master.tryserver.chromium.mac:ios-simulator-full-configs
Change-Id: Iedf4ad116b9033fcc2cfbab7bc69a9868db0fc1d
Reviewed-on: https://chromium-review.googlesource.com/951487Reviewed-by: default avatarGauthier Ambard <gambard@chromium.org>
Reviewed-by: default avatarJustin Cohen <justincohen@chromium.org>
Reviewed-by: default avatarMark Cogan <marq@chromium.org>
Commit-Queue: Mark Cogan <marq@chromium.org>
Cr-Commit-Position: refs/heads/master@{#543094}
parent c7cb2270
......@@ -13,6 +13,7 @@ source_set("commands") {
"command_dispatcher.mm",
"external_search_commands.h",
"history_popup_commands.h",
"omnibox_suggestion_commands.h",
"open_new_tab_command.h",
"open_new_tab_command.mm",
"open_url_command.h",
......
// Copyright 2018 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.
#ifndef IOS_CHROME_BROWSER_UI_COMMANDS_OMNIBOX_SUGGESTION_COMMANDS_H_
#define IOS_CHROME_BROWSER_UI_COMMANDS_OMNIBOX_SUGGESTION_COMMANDS_H_
#import <UIKit/UIKit.h>
// Commands to advance the suggestion highlight in the suggestions popup of the
// omnibox.
@protocol OmniboxSuggestionCommands<NSObject>
// Moves the highlight up.
- (void)highlightPreviousSuggestion;
// Moves the highlight down.
- (void)highlightNextSuggestion;
@end
#endif // IOS_CHROME_BROWSER_UI_COMMANDS_OMNIBOX_SUGGESTION_COMMANDS_H_
......@@ -17,6 +17,7 @@ namespace ios {
class ChromeBrowserState;
}
class WebStateList;
@class CommandDispatcher;
@protocol ApplicationCommands;
@protocol BrowserCommands;
@protocol OmniboxPopupPositioner;
......@@ -33,7 +34,7 @@ class WebStateList;
// Weak reference to ChromeBrowserState;
@property(nonatomic, assign) ios::ChromeBrowserState* browserState;
// The dispatcher for this view controller.
@property(nonatomic, weak) id<ApplicationCommands, BrowserCommands> dispatcher;
@property(nonatomic, weak) CommandDispatcher* dispatcher;
// URL loader for the location bar.
@property(nonatomic, weak) id<UrlLoader> URLLoader;
// Delegate for this coordinator.
......
......@@ -112,17 +112,23 @@ const int kLocationAuthorizationStatusCount = 4;
}
self.keyboardDelegate = [[ToolbarAssistiveKeyboardDelegateImpl alloc] init];
self.keyboardDelegate.dispatcher = self.dispatcher;
self.keyboardDelegate.dispatcher =
static_cast<id<ApplicationCommands, BrowserCommands>>(self.dispatcher);
self.keyboardDelegate.omniboxTextField = self.locationBarView.textField;
ConfigureAssistiveKeyboardViews(self.locationBarView.textField, kDotComTLD,
self.keyboardDelegate);
_locationBarController = std::make_unique<LocationBarControllerImpl>(
self.locationBarView, self.browserState, self, self.dispatcher);
self.locationBarView, self.browserState, self,
static_cast<id<BrowserCommands>>(self.dispatcher));
_locationBarController->SetURLLoader(self);
self.omniboxPopupCoordinator =
_locationBarController->CreatePopupCoordinator(self.popupPositioner);
self.omniboxPopupCoordinator.dispatcher = self.dispatcher;
[self.omniboxPopupCoordinator start];
self.locationBarView.textField.suggestionCommandsEndpoint =
static_cast<id<OmniboxSuggestionCommands>>(self.dispatcher);
self.mediator = [[LocationBarMediator alloc] init];
self.mediator.webStateList = self.webStateList;
self.mediator.consumer = self;
......
......@@ -12,6 +12,10 @@
// Delegate for AutocompleteResultConsumer.
@protocol AutocompleteResultConsumerDelegate<NSObject>
// Tells the delegate when a row containing a suggestion is highlighted (i.e.
// with arrow keys).
- (void)autocompleteResultConsumer:(id<AutocompleteResultConsumer>)sender
didHighlightRow:(NSUInteger)row;
// Tells the delegate when a row containing a suggestion is clicked.
- (void)autocompleteResultConsumer:(id<AutocompleteResultConsumer>)sender
didSelectRow:(NSUInteger)row;
......
......@@ -8,6 +8,7 @@
#import "ios/chrome/browser/ui/omnibox/clipping_textfield.h"
#include "base/strings/string16.h"
#import "ios/chrome/browser/ui/commands/omnibox_suggestion_commands.h"
#import "ios/chrome/browser/ui/omnibox/omnibox_text_field_delegate.h"
// Enum type specifying the direction of fade animations.
......@@ -99,6 +100,10 @@ typedef enum {
// instead of UITextFieldDelegate.
@property(nonatomic, weak) id<OmniboxTextFieldDelegate> delegate;
// The object handling suggestion commands.
@property(nonatomic, weak) id<OmniboxSuggestionCommands>
suggestionCommandsEndpoint;
// Text displayed when in pre-edit state.
@property(nonatomic, strong) NSString* preEditText;
......
......@@ -93,6 +93,7 @@ NSString* const kOmniboxFadeAnimationKey = @"OmniboxFadeAnimation";
@synthesize selectedTextBackgroundColor = _selectedTextBackgroundColor;
@synthesize placeholderTextColor = _placeholderTextColor;
@synthesize incognito = _incognito;
@synthesize suggestionCommandsEndpoint = _suggestionCommandsEndpoint;
#pragma mark - Public methods
// Overload to allow for code-based initialization.
......@@ -665,6 +666,32 @@ NSString* const kOmniboxFadeAnimationKey = @"OmniboxFadeAnimation";
[super deleteBackward];
}
#pragma mark Key Commands
- (NSArray<UIKeyCommand*>*)keyCommands {
// These up/down arrow key commands override the standard UITextInput handling
// of up/down arrow key. The standard behavior is to go to the beginning/end
// of the text. Instead, the omnibox popup needs to highlight suggestions.
UIKeyCommand* commandUp =
[UIKeyCommand keyCommandWithInput:UIKeyInputUpArrow
modifierFlags:0
action:@selector(keyCommandUp)];
UIKeyCommand* commandDown =
[UIKeyCommand keyCommandWithInput:UIKeyInputDownArrow
modifierFlags:0
action:@selector(keyCommandDown)];
return @[ commandUp, commandDown ];
}
- (void)keyCommandUp {
[self.suggestionCommandsEndpoint highlightNextSuggestion];
}
- (void)keyCommandDown {
[self.suggestionCommandsEndpoint highlightPreviousSuggestion];
}
#pragma mark - helpers
// Gets the bounds of the rect covering the URL.
......
......@@ -28,6 +28,7 @@ source_set("popup") {
"//ios/chrome/browser/browser_state",
"//ios/chrome/browser/ui:ui",
"//ios/chrome/browser/ui:ui_util",
"//ios/chrome/browser/ui/commands",
"//ios/chrome/browser/ui/omnibox:omnibox_popup_shared",
"//ios/chrome/browser/ui/omnibox:omnibox_util",
"//ios/chrome/browser/ui/toolbar/public:public",
......
......@@ -9,6 +9,7 @@
#include <memory>
@class CommandDispatcher;
@protocol OmniboxPopupPositioner;
class OmniboxPopupViewIOS;
......@@ -29,6 +30,8 @@ class ChromeBrowserState;
@property(nonatomic, weak) id<OmniboxPopupPositioner> positioner;
// Whether this coordinator has results to show.
@property(nonatomic, assign, readonly) BOOL hasResults;
// The dispatcher for this view controller.
@property(nonatomic, readwrite, weak) CommandDispatcher* dispatcher;
- (void)start;
- (void)stop;
......
......@@ -7,6 +7,7 @@
#import "components/image_fetcher/ios/ios_image_data_fetcher_wrapper.h"
#include "components/omnibox/browser/autocomplete_result.h"
#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
#import "ios/chrome/browser/ui/commands/command_dispatcher.h"
#import "ios/chrome/browser/ui/omnibox/popup/omnibox_popup_mediator.h"
#import "ios/chrome/browser/ui/omnibox/popup/omnibox_popup_presenter.h"
#import "ios/chrome/browser/ui/omnibox/popup/omnibox_popup_view_controller.h"
......@@ -31,6 +32,7 @@
@synthesize mediator = _mediator;
@synthesize popupViewController = _popupViewController;
@synthesize positioner = _positioner;
@synthesize dispatcher = _dispatcher;
#pragma mark - Public
......@@ -62,12 +64,17 @@
self.popupViewController.imageRetriever = self.mediator;
self.popupViewController.delegate = self.mediator;
[self.dispatcher
startDispatchingToTarget:self.popupViewController
forProtocol:@protocol(OmniboxSuggestionCommands)];
_popupView->SetMediator(self.mediator);
}
- (void)stop {
_popupView.reset();
[self.dispatcher
stopDispatchingForProtocol:@protocol(OmniboxSuggestionCommands)];
}
#pragma mark - Property accessor
......
......@@ -24,6 +24,7 @@ class OmniboxPopupMediatorDelegate {
virtual void OnMatchSelectedForAppending(const AutocompleteMatch& match) = 0;
virtual void OnMatchSelectedForDeletion(const AutocompleteMatch& match) = 0;
virtual void OnScroll() = 0;
virtual void OnMatchHighlighted(size_t row) = 0;
};
@interface OmniboxPopupMediator
......
......@@ -98,6 +98,11 @@
#pragma mark - AutocompleteResultConsumerDelegate
- (void)autocompleteResultConsumer:(id<AutocompleteResultConsumer>)sender
didHighlightRow:(NSUInteger)row {
_delegate->OnMatchHighlighted(row);
}
- (void)autocompleteResultConsumer:(id<AutocompleteResultConsumer>)sender
didSelectRow:(NSUInteger)row {
// OpenMatch() may close the popup, which will clear the result set and, by
......
......@@ -139,6 +139,7 @@ NS_INLINE CGFloat BottomPadding() {
self.bottomConstraint.active = NO;
self.heightConstraint.active = YES;
}
[UIView animateWithDuration:kCollapseAnimationDuration
delay:0
options:UIViewAnimationOptionCurveEaseInOut
......
......@@ -6,6 +6,7 @@
#define IOS_CHROME_BROWSER_UI_OMNIBOX_OMNIBOX_POPUP_VIEW_CONTROLLER_H_
#import <UIKit/UIKit.h>
#import "ios/chrome/browser/ui/commands/omnibox_suggestion_commands.h"
#import "ios/chrome/browser/ui/omnibox/autocomplete_result_consumer.h"
#import "ios/chrome/browser/ui/omnibox/image_retriever.h"
......@@ -13,8 +14,14 @@
// View controller used to display a list of omnibox autocomplete matches in the
// omnibox popup.
// It implements up/down arrow handling to highlight autocomplete results.
// Ideally, that should be implemented as key commands in this view controller,
// but UITextField has standard handlers for up/down arrows, so when the omnibox
// is the first responder, this view controller cannot receive these events.
// Hence the delegation.
@interface OmniboxPopupViewController
: UITableViewController<AutocompleteResultConsumer>
: UITableViewController<AutocompleteResultConsumer,
OmniboxSuggestionCommands>
@property(nonatomic, assign) BOOL incognito;
@property(nonatomic, weak) id<AutocompleteResultConsumerDelegate> delegate;
......
......@@ -57,12 +57,17 @@ UIColor* BackgroundColorIncognito() {
CGFloat _keyboardHeight;
}
// Index path of currently highlighted row. The rows can be highlighted by
// tapping and holding on them or by using arrow keys on a hardware keyboard.
@property(nonatomic, strong) NSIndexPath* highlightedIndexPath;
@end
@implementation OmniboxPopupViewController
@synthesize delegate = _delegate;
@synthesize incognito = _incognito;
@synthesize imageRetriever = _imageRetriever;
@synthesize highlightedIndexPath = _highlightedIndexPath;
#pragma mark -
#pragma mark Initialization
......@@ -163,6 +168,12 @@ UIColor* BackgroundColorIncognito() {
- (void)updateMatches:(NSArray<id<AutocompleteSuggestion>>*)result
withAnimation:(BOOL)animation {
// Reset highlight state.
if (self.highlightedIndexPath) {
[self unhighlightRowAtIndexPath:self.highlightedIndexPath];
self.highlightedIndexPath = nil;
}
_currentResult = result;
[self layoutRows];
......@@ -402,6 +413,16 @@ UIColor* BackgroundColorIncognito() {
[CATransaction commit];
}
- (void)highlightRowAtIndexPath:(NSIndexPath*)indexPath {
UITableViewCell* cell = [self.tableView cellForRowAtIndexPath:indexPath];
[cell setHighlighted:YES animated:NO];
}
- (void)unhighlightRowAtIndexPath:(NSIndexPath*)indexPath {
UITableViewCell* cell = [self.tableView cellForRowAtIndexPath:indexPath];
[cell setHighlighted:NO animated:NO];
}
#pragma mark -
#pragma mark Action for append UIButton
......@@ -438,6 +459,70 @@ UIColor* BackgroundColorIncognito() {
_alignment = alignment;
}
#pragma mark -
#pragma mark OmniboxSuggestionCommands
- (void)highlightNextSuggestion {
NSIndexPath* path = self.highlightedIndexPath;
if (path == nil) {
// When nothing is highlighted, pressing Up Arrow doesn't do anything.
return;
}
if (path.row == 0) {
// Can't move up from first row. Call the delegate again so that the inline
// autocomplete text is set again (in case the user exited the inline
// autocomplete).
[self.delegate autocompleteResultConsumer:self
didHighlightRow:self.highlightedIndexPath.row];
return;
}
[self unhighlightRowAtIndexPath:self.highlightedIndexPath];
self.highlightedIndexPath =
[NSIndexPath indexPathForRow:self.highlightedIndexPath.row - 1
inSection:0];
[self highlightRowAtIndexPath:self.highlightedIndexPath];
[self.delegate autocompleteResultConsumer:self
didHighlightRow:self.highlightedIndexPath.row];
}
- (void)highlightPreviousSuggestion {
if (!self.highlightedIndexPath) {
// Initialize the highlighted row to -1, so that pressing down when nothing
// is highlighted highlights the first row (at index 0).
self.highlightedIndexPath = [NSIndexPath indexPathForRow:-1 inSection:0];
}
NSIndexPath* path = self.highlightedIndexPath;
if (path.row == [self.tableView numberOfRowsInSection:0] - 1) {
// Can't go below last row. Call the delegate again so that the inline
// autocomplete text is set again (in case the user exited the inline
// autocomplete).
[self.delegate autocompleteResultConsumer:self
didHighlightRow:self.highlightedIndexPath.row];
return;
}
// There is a row below, move highlight there.
[self unhighlightRowAtIndexPath:self.highlightedIndexPath];
self.highlightedIndexPath =
[NSIndexPath indexPathForRow:self.highlightedIndexPath.row + 1
inSection:0];
[self highlightRowAtIndexPath:self.highlightedIndexPath];
[self.delegate autocompleteResultConsumer:self
didHighlightRow:self.highlightedIndexPath.row];
}
- (void)keyCommandReturn {
[self.tableView selectRowAtIndexPath:self.highlightedIndexPath
animated:YES
scrollPosition:UITableViewScrollPositionNone];
}
#pragma mark -
#pragma mark Table view delegate
......
......@@ -50,6 +50,7 @@ class OmniboxPopupViewIOS : public OmniboxPopupView,
// OmniboxPopupViewControllerDelegate implementation.
bool IsStarredMatch(const AutocompleteMatch& match) const override;
void OnMatchHighlighted(size_t row) override;
void OnMatchSelected(const AutocompleteMatch& match, size_t row) override;
void OnMatchSelectedForAppending(const AutocompleteMatch& match) override;
void OnMatchSelectedForDeletion(const AutocompleteMatch& match) override;
......
......@@ -92,6 +92,10 @@ bool OmniboxPopupViewIOS::IsStarredMatch(const AutocompleteMatch& match) const {
return model_->IsStarredMatch(match);
}
void OmniboxPopupViewIOS::OnMatchHighlighted(size_t row) {
model_->SetSelectedLine(row, false, true);
}
void OmniboxPopupViewIOS::OnMatchSelected(
const AutocompleteMatch& selectedMatch,
size_t row) {
......
......@@ -8,8 +8,10 @@
#include <memory>
#include "base/mac/foundation_util.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/sys_string_conversions.h"
#import "ios/chrome/browser/ui/commands/command_dispatcher.h"
#import "ios/chrome/browser/ui/fullscreen/fullscreen_controller.h"
#import "ios/chrome/browser/ui/fullscreen/fullscreen_controller_factory.h"
#import "ios/chrome/browser/ui/fullscreen/fullscreen_ui_updater.h"
......@@ -188,7 +190,8 @@
- (void)setUpLocationBar {
self.locationBarCoordinator = [[LocationBarCoordinator alloc] init];
self.locationBarCoordinator.browserState = self.browserState;
self.locationBarCoordinator.dispatcher = self.dispatcher;
self.locationBarCoordinator.dispatcher =
base::mac::ObjCCastStrict<CommandDispatcher>(self.dispatcher);
self.locationBarCoordinator.URLLoader = self.URLLoader;
self.locationBarCoordinator.delegate = self.delegate;
self.locationBarCoordinator.webStateList = self.webStateList;
......
......@@ -70,9 +70,11 @@
// Weak reference to ChromeBrowserState;
@property(nonatomic, assign) ios::ChromeBrowserState* browserState;
// The dispatcher for this view controller.
@property(nonatomic, weak) CommandDispatcher* dispatcher;
// The commands endpoint for this view controller.
@property(nonatomic, weak)
id<ApplicationCommands, BrowserCommands, OmniboxFocuser>
dispatcher;
commandsEndpoint;
// Coordinator for the tools menu UI.
@property(nonatomic, strong) ToolsMenuCoordinator* toolsMenuCoordinator;
// Button updater for the toolbar.
......@@ -93,6 +95,7 @@
@synthesize URLLoader = _URLLoader;
@synthesize webStateList = _webStateList;
@synthesize locationBarCoordinator = _locationBarCoordinator;
@synthesize commandsEndpoint = _commandsEndpoint;
- (instancetype)
initWithToolsMenuConfigurationProvider:
......@@ -103,9 +106,10 @@ initWithToolsMenuConfigurationProvider:
if (self) {
DCHECK(browserState);
_mediator = [[ToolbarMediator alloc] init];
_dispatcher =
static_cast<id<ApplicationCommands, BrowserCommands, OmniboxFocuser>>(
dispatcher);
_dispatcher = dispatcher;
_commandsEndpoint =
static_cast<CommandDispatcher<ApplicationCommands, BrowserCommands,
OmniboxFocuser>*>(dispatcher);
_browserState = browserState;
_toolsMenuCoordinator = [[ToolsMenuCoordinator alloc] init];
......@@ -175,19 +179,19 @@ initWithToolsMenuConfigurationProvider:
ToolbarStyle style = isIncognito ? INCOGNITO : NORMAL;
ToolbarButtonFactory* factory =
[[ToolbarButtonFactory alloc] initWithStyle:style];
factory.dispatcher = self.dispatcher;
factory.dispatcher = self.commandsEndpoint;
factory.visibilityConfiguration =
[[ToolbarButtonVisibilityConfiguration alloc] initWithType:LEGACY];
self.buttonUpdater = [[ToolbarButtonUpdater alloc] init];
self.buttonUpdater.factory = factory;
self.toolbarViewController = [[ToolbarViewController alloc]
initWithDispatcher:self.dispatcher
initWithDispatcher:self.commandsEndpoint
buttonFactory:factory
buttonUpdater:self.buttonUpdater
omniboxFocuser:self.locationBarCoordinator];
self.toolbarViewController.locationBarView = self.locationBarCoordinator.view;
self.toolbarViewController.dispatcher = self.dispatcher;
self.toolbarViewController.dispatcher = self.commandsEndpoint;
_fullscreenObserver =
std::make_unique<FullscreenUIUpdater>(self.toolbarViewController);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment