diff options
-rw-r--r-- | ui/keyboard/keyboard_controller.cc | 50 | ||||
-rw-r--r-- | ui/keyboard/keyboard_controller.h | 10 | ||||
-rw-r--r-- | ui/keyboard/keyboard_controller_unittest.cc | 105 |
3 files changed, 118 insertions, 47 deletions
diff --git a/ui/keyboard/keyboard_controller.cc b/ui/keyboard/keyboard_controller.cc index 1a3d88d..0977f89 100644 --- a/ui/keyboard/keyboard_controller.cc +++ b/ui/keyboard/keyboard_controller.cc @@ -4,6 +4,7 @@ #include "ui/keyboard/keyboard_controller.h" +#include "base/bind.h" #include "ui/aura/layout_manager.h" #include "ui/aura/window.h" #include "ui/aura/window_delegate.h" @@ -19,6 +20,8 @@ namespace { +const int kHideKeyboardDelayMs = 100; + gfx::Rect KeyboardBoundsFromWindowBounds(const gfx::Rect& window_bounds) { const float kKeyboardHeightRatio = 0.3f; return gfx::Rect( @@ -118,7 +121,9 @@ class KeyboardLayoutManager : public aura::LayoutManager { KeyboardController::KeyboardController(KeyboardControllerProxy* proxy) : proxy_(proxy), container_(NULL), - input_method_(NULL) { + input_method_(NULL), + keyboard_visible_(false), + weak_factory_(this) { CHECK(proxy); input_method_ = proxy_->GetInputMethod(); input_method_->AddObserver(this); @@ -166,7 +171,7 @@ void KeyboardController::OnTextInputStateChanged( if (!container_) return; - bool was_showing = container_->IsVisible(); + bool was_showing = keyboard_visible_; bool should_show = was_showing; if (!client || client->GetTextInputType() == ui::TEXT_INPUT_TYPE_NONE) { should_show = false; @@ -182,20 +187,26 @@ void KeyboardController::OnTextInputStateChanged( } if (was_showing != should_show) { - gfx::Rect new_bounds( - should_show ? container_->children()[0]->bounds() : gfx::Rect()); - - FOR_EACH_OBSERVER( - KeyboardControllerObserver, - observer_list_, - OnKeyboardBoundsChanging(new_bounds)); - - if (should_show) + if (should_show) { + keyboard_visible_ = true; + weak_factory_.InvalidateWeakPtrs(); + if (container_->IsVisible()) + return; + + FOR_EACH_OBSERVER( + KeyboardControllerObserver, + observer_list_, + OnKeyboardBoundsChanging(container_->children()[0]->bounds())); proxy_->ShowKeyboardContainer(container_); - else - proxy_->HideKeyboardContainer(container_); + } else { + keyboard_visible_ = false; + base::MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&KeyboardController::HideKeyboard, + weak_factory_.GetWeakPtr()), + base::TimeDelta::FromMilliseconds(kHideKeyboardDelayMs)); + } } - // TODO(bryeung): whenever the TextInputClient changes we need to notify the // keyboard (with the TextInputType) so that it can reset it's state (e.g. // abandon compositions in progress) @@ -207,4 +218,15 @@ void KeyboardController::OnInputMethodDestroyed( input_method_ = NULL; } +void KeyboardController::HideKeyboard() { + FOR_EACH_OBSERVER(KeyboardControllerObserver, + observer_list_, + OnKeyboardBoundsChanging(gfx::Rect())); + proxy_->HideKeyboardContainer(container_); +} + +bool KeyboardController::WillHideKeyboard() const { + return weak_factory_.HasWeakPtrs(); +} + } // namespace keyboard diff --git a/ui/keyboard/keyboard_controller.h b/ui/keyboard/keyboard_controller.h index 6022339..e1574f2 100644 --- a/ui/keyboard/keyboard_controller.h +++ b/ui/keyboard/keyboard_controller.h @@ -61,9 +61,19 @@ class KEYBOARD_EXPORT KeyboardController : public ui::InputMethodObserver, virtual void OnInputMethodDestroyed( const ui::InputMethod* input_method) OVERRIDE; + // Hides virtual keyboard and notifies observer bounds change. + // This functions should be called with a delay to avoid layout flicker + // when the focus of input field quickly change. + void HideKeyboard(); + + // Returns true if keyboard is scheduled to hide. + bool WillHideKeyboard() const; + scoped_ptr<KeyboardControllerProxy> proxy_; aura::Window* container_; ui::InputMethod* input_method_; + bool keyboard_visible_; + base::WeakPtrFactory<KeyboardController> weak_factory_; ObserverList<KeyboardControllerObserver> observer_list_; diff --git a/ui/keyboard/keyboard_controller_unittest.cc b/ui/keyboard/keyboard_controller_unittest.cc index 49c695a..107600f 100644 --- a/ui/keyboard/keyboard_controller_unittest.cc +++ b/ui/keyboard/keyboard_controller_unittest.cc @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "base/bind.h" #include "base/memory/scoped_ptr.h" #include "base/message_loop/message_loop.h" #include "testing/gtest/include/gtest/gtest.h" @@ -158,6 +159,27 @@ class TestTextInputClient : public ui::TextInputClient { DISALLOW_COPY_AND_ASSIGN(TestTextInputClient); }; +class KeyboardContainerObserver : public aura::WindowObserver { + public: + explicit KeyboardContainerObserver(aura::Window* window) : window_(window) { + window_->AddObserver(this); + } + virtual ~KeyboardContainerObserver() { + window_->RemoveObserver(this); + } + + private: + virtual void OnWindowVisibilityChanged(aura::Window* window, + bool visible) OVERRIDE { + if (!visible) + base::MessageLoop::current()->Quit(); + } + + aura::Window* window_; + + DISALLOW_COPY_AND_ASSIGN(KeyboardContainerObserver); +}; + } // namespace class KeyboardControllerTest : public testing::Test { @@ -170,6 +192,8 @@ class KeyboardControllerTest : public testing::Test { aura_test_helper_->SetUp(); ui::SetUpInputMethodFactoryForTesting(); focus_controller_.reset(new TestFocusController(root_window())); + proxy_ = new TestKeyboardControllerProxy(); + controller_.reset(new KeyboardController(proxy_)); } virtual void TearDown() OVERRIDE { @@ -178,37 +202,43 @@ class KeyboardControllerTest : public testing::Test { } aura::RootWindow* root_window() { return aura_test_helper_->root_window(); } + KeyboardControllerProxy* proxy() { return proxy_; } + KeyboardController* controller() { return controller_.get(); } - void ShowKeyboard(KeyboardController* controller) { + void ShowKeyboard() { TestTextInputClient test_text_input_client(ui::TEXT_INPUT_TYPE_TEXT); - controller->OnTextInputStateChanged(&test_text_input_client); + controller_->OnTextInputStateChanged(&test_text_input_client); } protected: + bool WillHideKeyboard() { + return controller_->WillHideKeyboard(); + } + base::MessageLoopForUI message_loop_; scoped_ptr<aura::test::AuraTestHelper> aura_test_helper_; scoped_ptr<TestFocusController> focus_controller_; private: + KeyboardControllerProxy* proxy_; + scoped_ptr<KeyboardController> controller_; + DISALLOW_COPY_AND_ASSIGN(KeyboardControllerTest); }; TEST_F(KeyboardControllerTest, KeyboardSize) { - KeyboardControllerProxy* proxy = new TestKeyboardControllerProxy(); - KeyboardController controller(proxy); - - scoped_ptr<aura::Window> container(controller.GetContainerWindow()); + scoped_ptr<aura::Window> container(controller()->GetContainerWindow()); gfx::Rect bounds(0, 0, 100, 100); container->SetBounds(bounds); - const gfx::Rect& before_bounds = proxy->GetKeyboardWindow()->bounds(); + const gfx::Rect& before_bounds = proxy()->GetKeyboardWindow()->bounds(); gfx::Rect new_bounds( before_bounds.x(), before_bounds.y(), before_bounds.width() / 2, before_bounds.height() / 2); // The KeyboardController's LayoutManager shouldn't let this happen - proxy->GetKeyboardWindow()->SetBounds(new_bounds); - ASSERT_EQ(before_bounds, proxy->GetKeyboardWindow()->bounds()); + proxy()->GetKeyboardWindow()->SetBounds(new_bounds); + ASSERT_EQ(before_bounds, proxy()->GetKeyboardWindow()->bounds()); } // Tests that tapping/clicking inside the keyboard does not give it focus. @@ -222,16 +252,14 @@ TEST_F(KeyboardControllerTest, ClickDoesNotFocusKeyboard) { window->Show(); window->Focus(); - KeyboardControllerProxy* proxy = new TestKeyboardControllerProxy(); - KeyboardController controller(proxy); - - scoped_ptr<aura::Window> keyboard_container(controller.GetContainerWindow()); + scoped_ptr<aura::Window> keyboard_container( + controller()->GetContainerWindow()); keyboard_container->SetBounds(root_bounds); root_window()->AddChild(keyboard_container.get()); keyboard_container->Show(); - ShowKeyboard(&controller); + ShowKeyboard(); EXPECT_TRUE(window->IsVisible()); EXPECT_TRUE(keyboard_container->IsVisible()); @@ -244,7 +272,7 @@ TEST_F(KeyboardControllerTest, ClickDoesNotFocusKeyboard) { keyboard_container->AddPreTargetHandler(&observer); aura::test::EventGenerator generator(root_window()); - generator.MoveMouseTo(proxy->GetKeyboardWindow()->bounds().CenterPoint()); + generator.MoveMouseTo(proxy()->GetKeyboardWindow()->bounds().CenterPoint()); generator.ClickLeftButton(); EXPECT_TRUE(window->HasFocus()); EXPECT_FALSE(keyboard_container->HasFocus()); @@ -260,32 +288,43 @@ TEST_F(KeyboardControllerTest, ClickDoesNotFocusKeyboard) { TEST_F(KeyboardControllerTest, VisibilityChangeWithTextInputTypeChange) { const gfx::Rect& root_bounds = root_window()->bounds(); - aura::test::EventCountDelegate delegate; - scoped_ptr<aura::Window> window(new aura::Window(&delegate)); - window->Init(ui::LAYER_NOT_DRAWN); - window->SetBounds(root_bounds); - root_window()->AddChild(window.get()); - window->Show(); - window->Focus(); - - KeyboardControllerProxy* proxy = new TestKeyboardControllerProxy(); - ui::InputMethod* input_method = proxy->GetInputMethod(); - TestTextInputClient input_client(ui::TEXT_INPUT_TYPE_TEXT); - TestTextInputClient no_input_client(ui::TEXT_INPUT_TYPE_NONE); - input_method->SetFocusedTextInputClient(&input_client); - KeyboardController controller(proxy); - - scoped_ptr<aura::Window> keyboard_container(controller.GetContainerWindow()); + ui::InputMethod* input_method = proxy()->GetInputMethod(); + TestTextInputClient input_client_0(ui::TEXT_INPUT_TYPE_TEXT); + TestTextInputClient input_client_1(ui::TEXT_INPUT_TYPE_TEXT); + TestTextInputClient input_client_2(ui::TEXT_INPUT_TYPE_TEXT); + TestTextInputClient no_input_client_0(ui::TEXT_INPUT_TYPE_NONE); + TestTextInputClient no_input_client_1(ui::TEXT_INPUT_TYPE_NONE); + input_method->SetFocusedTextInputClient(&input_client_0); + + scoped_ptr<aura::Window> keyboard_container( + controller()->GetContainerWindow()); + scoped_ptr<KeyboardContainerObserver> keyboard_container_observer( + new KeyboardContainerObserver(keyboard_container.get())); keyboard_container->SetBounds(root_bounds); root_window()->AddChild(keyboard_container.get()); EXPECT_TRUE(keyboard_container->IsVisible()); - input_method->SetFocusedTextInputClient(&no_input_client); + input_method->SetFocusedTextInputClient(&no_input_client_0); + // Keyboard should not immediately hide itself. It is delayed to avoid layout + // flicker when the focus of input field quickly change. + EXPECT_TRUE(keyboard_container->IsVisible()); + EXPECT_TRUE(WillHideKeyboard()); + // Wait for hide keyboard to finish. + base::MessageLoop::current()->Run(); EXPECT_FALSE(keyboard_container->IsVisible()); - input_method->SetFocusedTextInputClient(&input_client); + input_method->SetFocusedTextInputClient(&input_client_1); + EXPECT_TRUE(keyboard_container->IsVisible()); + + // Schedule to hide keyboard. + input_method->SetFocusedTextInputClient(&no_input_client_1); + EXPECT_TRUE(WillHideKeyboard()); + // Cancel keyboard hide. + input_method->SetFocusedTextInputClient(&input_client_2); + + EXPECT_FALSE(WillHideKeyboard()); EXPECT_TRUE(keyboard_container->IsVisible()); } |