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 #include "ash/frame/caption_buttons/frame_size_button.h" 6 7 #include "ash/frame/caption_buttons/frame_caption_button.h" 8 #include "ash/frame/caption_buttons/frame_caption_button_container_view.h" 9 #include "ash/shell.h" 10 #include "ash/test/ash_test_base.h" 11 #include "ash/wm/window_state.h" 12 #include "base/i18n/rtl.h" 13 #include "grit/ash_resources.h" 14 #include "ui/aura/test/event_generator.h" 15 #include "ui/aura/window.h" 16 #include "ui/base/l10n/l10n_util.h" 17 #include "ui/events/gestures/gesture_configuration.h" 18 #include "ui/gfx/display.h" 19 #include "ui/gfx/screen.h" 20 #include "ui/views/widget/widget.h" 21 #include "ui/views/widget/widget_delegate.h" 22 23 namespace ash { 24 namespace test { 25 26 namespace { 27 28 class TestWidgetDelegate : public views::WidgetDelegateView { 29 public: 30 TestWidgetDelegate() {} 31 virtual ~TestWidgetDelegate() {} 32 33 // Overridden from views::WidgetDelegate: 34 virtual views::View* GetContentsView() OVERRIDE { 35 return this; 36 } 37 virtual bool CanResize() const OVERRIDE { 38 return true; 39 } 40 virtual bool CanMaximize() const OVERRIDE { 41 return true; 42 } 43 44 ash::FrameCaptionButtonContainerView* caption_button_container() { 45 return caption_button_container_; 46 } 47 48 private: 49 // Overridden from views::View: 50 virtual void Layout() OVERRIDE { 51 caption_button_container_->Layout(); 52 53 // Right align the caption button container. 54 gfx::Size preferred_size = caption_button_container_->GetPreferredSize(); 55 caption_button_container_->SetBounds(width() - preferred_size.width(), 0, 56 preferred_size.width(), preferred_size.height()); 57 } 58 59 virtual void ViewHierarchyChanged( 60 const ViewHierarchyChangedDetails& details) OVERRIDE { 61 if (details.is_add && details.child == this) { 62 caption_button_container_ = new FrameCaptionButtonContainerView( 63 GetWidget(), FrameCaptionButtonContainerView::MINIMIZE_ALLOWED); 64 65 // Set arbitrary images for the container's buttons so that the buttons 66 // have non-empty sizes. 67 for (int icon = 0; icon < CAPTION_BUTTON_ICON_COUNT; ++icon) { 68 caption_button_container_->SetButtonImages( 69 static_cast<CaptionButtonIcon>(icon), 70 IDR_AURA_WINDOW_CONTROL_ICON_CLOSE, 71 IDR_AURA_WINDOW_CONTROL_ICON_CLOSE_I, 72 IDR_AURA_WINDOW_CONTROL_BACKGROUND_H, 73 IDR_AURA_WINDOW_CONTROL_BACKGROUND_P); 74 } 75 76 AddChildView(caption_button_container_); 77 } 78 } 79 80 // Not owned. 81 ash::FrameCaptionButtonContainerView* caption_button_container_; 82 83 DISALLOW_COPY_AND_ASSIGN(TestWidgetDelegate); 84 }; 85 86 } // namespace 87 88 class FrameSizeButtonTest : public AshTestBase { 89 public: 90 FrameSizeButtonTest() {} 91 virtual ~FrameSizeButtonTest() {} 92 93 // Returns the center point of |view| in screen coordinates. 94 gfx::Point CenterPointInScreen(views::View* view) { 95 return view->GetBoundsInScreen().CenterPoint(); 96 } 97 98 // Returns true if the window has |state_type|. 99 bool HasStateType(wm::WindowStateType state_type) const { 100 return window_state()->GetStateType() == state_type; 101 } 102 103 // Returns true if all three buttons are in the normal state. 104 bool AllButtonsInNormalState() const { 105 return minimize_button_->state() == views::Button::STATE_NORMAL && 106 size_button_->state() == views::Button::STATE_NORMAL && 107 close_button_->state() == views::Button::STATE_NORMAL; 108 } 109 110 // Creates a widget with |delegate|. The returned widget takes ownership of 111 // |delegate|. 112 views::Widget* CreateWidget(views::WidgetDelegate* delegate) { 113 views::Widget* widget = new views::Widget; 114 views::Widget::InitParams params( 115 views::Widget::InitParams::TYPE_WINDOW_FRAMELESS); 116 params.context = CurrentContext(); 117 params.delegate = delegate; 118 params.bounds = gfx::Rect(10, 10, 100, 100); 119 widget->Init(params); 120 widget->Show(); 121 return widget; 122 } 123 124 // AshTestBase overrides: 125 virtual void SetUp() OVERRIDE { 126 AshTestBase::SetUp(); 127 128 TestWidgetDelegate* delegate = new TestWidgetDelegate(); 129 window_state_ = ash::wm::GetWindowState( 130 CreateWidget(delegate)->GetNativeWindow()); 131 132 FrameCaptionButtonContainerView::TestApi test( 133 delegate->caption_button_container()); 134 135 minimize_button_ = test.minimize_button(); 136 size_button_ = test.size_button(); 137 static_cast<FrameSizeButton*>( 138 size_button_)->set_delay_to_set_buttons_to_snap_mode(0); 139 close_button_ = test.close_button(); 140 } 141 142 ash::wm::WindowState* window_state() { return window_state_; } 143 const ash::wm::WindowState* window_state() const { return window_state_; } 144 145 FrameCaptionButton* minimize_button() { return minimize_button_; } 146 FrameCaptionButton* size_button() { return size_button_; } 147 FrameCaptionButton* close_button() { return close_button_; } 148 149 private: 150 // Not owned. 151 ash::wm::WindowState* window_state_; 152 FrameCaptionButton* minimize_button_; 153 FrameCaptionButton* size_button_; 154 FrameCaptionButton* close_button_; 155 156 DISALLOW_COPY_AND_ASSIGN(FrameSizeButtonTest); 157 }; 158 159 // Tests that pressing the left mouse button or tapping down on the size button 160 // puts the button into the pressed state. 161 TEST_F(FrameSizeButtonTest, PressedState) { 162 aura::test::EventGenerator& generator = GetEventGenerator(); 163 generator.MoveMouseTo(CenterPointInScreen(size_button())); 164 generator.PressLeftButton(); 165 EXPECT_EQ(views::Button::STATE_PRESSED, size_button()->state()); 166 generator.ReleaseLeftButton(); 167 RunAllPendingInMessageLoop(); 168 EXPECT_EQ(views::Button::STATE_NORMAL, size_button()->state()); 169 170 generator.MoveMouseTo(CenterPointInScreen(size_button())); 171 generator.PressTouchId(3); 172 EXPECT_EQ(views::Button::STATE_PRESSED, size_button()->state()); 173 generator.ReleaseTouchId(3); 174 RunAllPendingInMessageLoop(); 175 EXPECT_EQ(views::Button::STATE_NORMAL, size_button()->state()); 176 } 177 178 // Tests that clicking on the size button toggles between the maximized and 179 // normal state. 180 TEST_F(FrameSizeButtonTest, ClickSizeButtonTogglesMaximize) { 181 EXPECT_FALSE(window_state()->IsMaximized()); 182 183 aura::test::EventGenerator& generator = GetEventGenerator(); 184 generator.MoveMouseTo(CenterPointInScreen(size_button())); 185 generator.ClickLeftButton(); 186 RunAllPendingInMessageLoop(); 187 EXPECT_TRUE(window_state()->IsMaximized()); 188 189 generator.MoveMouseTo(CenterPointInScreen(size_button())); 190 generator.ClickLeftButton(); 191 RunAllPendingInMessageLoop(); 192 EXPECT_FALSE(window_state()->IsMaximized()); 193 194 generator.GestureTapAt(CenterPointInScreen(size_button())); 195 RunAllPendingInMessageLoop(); 196 EXPECT_TRUE(window_state()->IsMaximized()); 197 198 generator.GestureTapAt(CenterPointInScreen(size_button())); 199 RunAllPendingInMessageLoop(); 200 EXPECT_FALSE(window_state()->IsMaximized()); 201 } 202 203 // Test that clicking + dragging to a button adjacent to the size button snaps 204 // the window left or right. 205 TEST_F(FrameSizeButtonTest, ButtonDrag) { 206 EXPECT_TRUE(window_state()->IsNormalStateType()); 207 208 // 1) Test by dragging the mouse. 209 // Snap right. 210 aura::test::EventGenerator& generator = GetEventGenerator(); 211 generator.MoveMouseTo(CenterPointInScreen(size_button())); 212 generator.PressLeftButton(); 213 generator.MoveMouseTo(CenterPointInScreen(close_button())); 214 generator.ReleaseLeftButton(); 215 RunAllPendingInMessageLoop(); 216 EXPECT_TRUE(HasStateType(wm::WINDOW_STATE_TYPE_RIGHT_SNAPPED)); 217 218 // Snap left. 219 generator.MoveMouseTo(CenterPointInScreen(size_button())); 220 generator.PressLeftButton(); 221 generator.MoveMouseTo(CenterPointInScreen(minimize_button())); 222 generator.ReleaseLeftButton(); 223 RunAllPendingInMessageLoop(); 224 EXPECT_TRUE(HasStateType(wm::WINDOW_STATE_TYPE_LEFT_SNAPPED)); 225 226 // 2) Test with scroll gestures. 227 // Snap right. 228 generator.GestureScrollSequence( 229 CenterPointInScreen(size_button()), 230 CenterPointInScreen(close_button()), 231 base::TimeDelta::FromMilliseconds(100), 232 3); 233 RunAllPendingInMessageLoop(); 234 EXPECT_TRUE(HasStateType(wm::WINDOW_STATE_TYPE_RIGHT_SNAPPED)); 235 236 // Snap left. 237 generator.GestureScrollSequence( 238 CenterPointInScreen(size_button()), 239 CenterPointInScreen(minimize_button()), 240 base::TimeDelta::FromMilliseconds(100), 241 3); 242 RunAllPendingInMessageLoop(); 243 EXPECT_TRUE(HasStateType(wm::WINDOW_STATE_TYPE_LEFT_SNAPPED)); 244 245 // 3) Test with tap gestures. 246 const int touch_default_radius = 247 ui::GestureConfiguration::default_radius(); 248 ui::GestureConfiguration::set_default_radius(0); 249 // Snap right. 250 generator.MoveMouseTo(CenterPointInScreen(size_button())); 251 generator.PressMoveAndReleaseTouchTo(CenterPointInScreen(close_button())); 252 RunAllPendingInMessageLoop(); 253 EXPECT_TRUE(HasStateType(wm::WINDOW_STATE_TYPE_RIGHT_SNAPPED)); 254 // Snap left. 255 generator.MoveMouseTo(CenterPointInScreen(size_button())); 256 generator.PressMoveAndReleaseTouchTo(CenterPointInScreen(minimize_button())); 257 RunAllPendingInMessageLoop(); 258 EXPECT_TRUE(HasStateType(wm::WINDOW_STATE_TYPE_LEFT_SNAPPED)); 259 ui::GestureConfiguration::set_default_radius(touch_default_radius); 260 } 261 262 // Test that clicking, dragging, and overshooting the minimize button a bit 263 // horizontally still snaps the window left. 264 TEST_F(FrameSizeButtonTest, SnapLeftOvershootMinimize) { 265 EXPECT_TRUE(window_state()->IsNormalStateType()); 266 267 aura::test::EventGenerator& generator = GetEventGenerator(); 268 generator.MoveMouseTo(CenterPointInScreen(size_button())); 269 270 generator.PressLeftButton(); 271 // Move to the minimize button. 272 generator.MoveMouseTo(CenterPointInScreen(minimize_button())); 273 // Overshoot the minimize button. 274 generator.MoveMouseBy(-minimize_button()->width(), 0); 275 generator.ReleaseLeftButton(); 276 RunAllPendingInMessageLoop(); 277 EXPECT_TRUE(HasStateType(wm::WINDOW_STATE_TYPE_LEFT_SNAPPED)); 278 } 279 280 // Test that right clicking the size button has no effect. 281 TEST_F(FrameSizeButtonTest, RightMouseButton) { 282 EXPECT_TRUE(window_state()->IsNormalStateType()); 283 284 aura::test::EventGenerator& generator = GetEventGenerator(); 285 generator.MoveMouseTo(CenterPointInScreen(size_button())); 286 generator.PressRightButton(); 287 generator.ReleaseRightButton(); 288 RunAllPendingInMessageLoop(); 289 EXPECT_TRUE(window_state()->IsNormalStateType()); 290 } 291 292 // Test that upon releasing the mouse button after having pressed the size 293 // button 294 // - The state of all the caption buttons is reset. 295 // - The icon displayed by all of the caption buttons is reset. 296 TEST_F(FrameSizeButtonTest, ResetButtonsAfterClick) { 297 EXPECT_EQ(CAPTION_BUTTON_ICON_MINIMIZE, minimize_button()->icon()); 298 EXPECT_EQ(CAPTION_BUTTON_ICON_CLOSE, close_button()->icon()); 299 EXPECT_TRUE(AllButtonsInNormalState()); 300 301 // Pressing the size button should result in the size button being pressed and 302 // the minimize and close button icons changing. 303 aura::test::EventGenerator& generator = GetEventGenerator(); 304 generator.MoveMouseTo(CenterPointInScreen(size_button())); 305 generator.PressLeftButton(); 306 EXPECT_EQ(views::Button::STATE_NORMAL, minimize_button()->state()); 307 EXPECT_EQ(views::Button::STATE_PRESSED, size_button()->state()); 308 EXPECT_EQ(views::Button::STATE_NORMAL, close_button()->state()); 309 EXPECT_EQ(CAPTION_BUTTON_ICON_LEFT_SNAPPED, minimize_button()->icon()); 310 EXPECT_EQ(CAPTION_BUTTON_ICON_RIGHT_SNAPPED, close_button()->icon()); 311 312 // Dragging the mouse over the minimize button should hover the minimize 313 // button and the minimize and close button icons should stay changed. 314 generator.MoveMouseTo(CenterPointInScreen(minimize_button())); 315 EXPECT_EQ(views::Button::STATE_HOVERED, minimize_button()->state()); 316 EXPECT_EQ(views::Button::STATE_PRESSED, size_button()->state()); 317 EXPECT_EQ(views::Button::STATE_NORMAL, close_button()->state()); 318 EXPECT_EQ(CAPTION_BUTTON_ICON_LEFT_SNAPPED, minimize_button()->icon()); 319 EXPECT_EQ(CAPTION_BUTTON_ICON_RIGHT_SNAPPED, close_button()->icon()); 320 321 // Release the mouse, snapping the window left. 322 generator.ReleaseLeftButton(); 323 RunAllPendingInMessageLoop(); 324 EXPECT_TRUE(HasStateType(wm::WINDOW_STATE_TYPE_LEFT_SNAPPED)); 325 326 // None of the buttons should stay pressed and the buttons should have their 327 // regular icons. 328 EXPECT_TRUE(AllButtonsInNormalState()); 329 EXPECT_EQ(CAPTION_BUTTON_ICON_MINIMIZE, minimize_button()->icon()); 330 EXPECT_EQ(CAPTION_BUTTON_ICON_CLOSE, close_button()->icon()); 331 332 // Repeat test but release button where it does not affect the window's state 333 // because the code path is different. 334 generator.MoveMouseTo(CenterPointInScreen(size_button())); 335 generator.PressLeftButton(); 336 EXPECT_EQ(views::Button::STATE_NORMAL, minimize_button()->state()); 337 EXPECT_EQ(views::Button::STATE_PRESSED, size_button()->state()); 338 EXPECT_EQ(views::Button::STATE_NORMAL, close_button()->state()); 339 EXPECT_EQ(CAPTION_BUTTON_ICON_LEFT_SNAPPED, minimize_button()->icon()); 340 EXPECT_EQ(CAPTION_BUTTON_ICON_RIGHT_SNAPPED, close_button()->icon()); 341 342 const gfx::Rect& kWorkAreaBoundsInScreen = 343 ash::Shell::GetScreen()->GetPrimaryDisplay().work_area(); 344 generator.MoveMouseTo(kWorkAreaBoundsInScreen.bottom_left()); 345 346 // None of the buttons should be pressed because we are really far away from 347 // any of the caption buttons. The minimize and close button icons should 348 // be changed because the mouse is pressed. 349 EXPECT_TRUE(AllButtonsInNormalState()); 350 EXPECT_EQ(CAPTION_BUTTON_ICON_LEFT_SNAPPED, minimize_button()->icon()); 351 EXPECT_EQ(CAPTION_BUTTON_ICON_RIGHT_SNAPPED, close_button()->icon()); 352 353 // Release the mouse. The window should stay snapped left. 354 generator.ReleaseLeftButton(); 355 RunAllPendingInMessageLoop(); 356 EXPECT_TRUE(HasStateType(wm::WINDOW_STATE_TYPE_LEFT_SNAPPED)); 357 358 // The buttons should stay unpressed and the buttons should now have their 359 // regular icons. 360 EXPECT_TRUE(AllButtonsInNormalState()); 361 EXPECT_EQ(CAPTION_BUTTON_ICON_MINIMIZE, minimize_button()->icon()); 362 EXPECT_EQ(CAPTION_BUTTON_ICON_CLOSE, close_button()->icon()); 363 } 364 365 // Test that the size button is pressed whenever the snap left/right buttons 366 // are hovered. 367 TEST_F(FrameSizeButtonTest, SizeButtonPressedWhenSnapButtonHovered) { 368 EXPECT_EQ(CAPTION_BUTTON_ICON_MINIMIZE, minimize_button()->icon()); 369 EXPECT_EQ(CAPTION_BUTTON_ICON_CLOSE, close_button()->icon()); 370 EXPECT_TRUE(AllButtonsInNormalState()); 371 372 // Pressing the size button should result in the size button being pressed and 373 // the minimize and close button icons changing. 374 aura::test::EventGenerator& generator = GetEventGenerator(); 375 generator.MoveMouseTo(CenterPointInScreen(size_button())); 376 generator.PressLeftButton(); 377 EXPECT_EQ(views::Button::STATE_NORMAL, minimize_button()->state()); 378 EXPECT_EQ(views::Button::STATE_PRESSED, size_button()->state()); 379 EXPECT_EQ(views::Button::STATE_NORMAL, close_button()->state()); 380 EXPECT_EQ(CAPTION_BUTTON_ICON_LEFT_SNAPPED, minimize_button()->icon()); 381 EXPECT_EQ(CAPTION_BUTTON_ICON_RIGHT_SNAPPED, close_button()->icon()); 382 383 // Dragging the mouse over the minimize button (snap left button) should hover 384 // the minimize button and keep the size button pressed. 385 generator.MoveMouseTo(CenterPointInScreen(minimize_button())); 386 EXPECT_EQ(views::Button::STATE_HOVERED, minimize_button()->state()); 387 EXPECT_EQ(views::Button::STATE_PRESSED, size_button()->state()); 388 EXPECT_EQ(views::Button::STATE_NORMAL, close_button()->state()); 389 390 // Moving the mouse far away from the caption buttons and then moving it over 391 // the close button (snap right button) should hover the close button and 392 // keep the size button pressed. 393 const gfx::Rect& kWorkAreaBoundsInScreen = 394 ash::Shell::GetScreen()->GetPrimaryDisplay().work_area(); 395 generator.MoveMouseTo(kWorkAreaBoundsInScreen.bottom_left()); 396 EXPECT_TRUE(AllButtonsInNormalState()); 397 generator.MoveMouseTo(CenterPointInScreen(close_button())); 398 EXPECT_EQ(views::Button::STATE_NORMAL, minimize_button()->state()); 399 EXPECT_EQ(views::Button::STATE_PRESSED, size_button()->state()); 400 EXPECT_EQ(views::Button::STATE_HOVERED, close_button()->state()); 401 } 402 403 class FrameSizeButtonTestRTL : public FrameSizeButtonTest { 404 public: 405 FrameSizeButtonTestRTL() {} 406 virtual ~FrameSizeButtonTestRTL() {} 407 408 virtual void SetUp() OVERRIDE { 409 original_locale_ = l10n_util::GetApplicationLocale(std::string()); 410 base::i18n::SetICUDefaultLocale("he"); 411 412 FrameSizeButtonTest::SetUp(); 413 } 414 415 virtual void TearDown() OVERRIDE { 416 FrameSizeButtonTest::TearDown(); 417 base::i18n::SetICUDefaultLocale(original_locale_); 418 } 419 420 private: 421 std::string original_locale_; 422 423 DISALLOW_COPY_AND_ASSIGN(FrameSizeButtonTestRTL); 424 }; 425 426 // Test that clicking + dragging to a button adjacent to the size button presses 427 // the correct button and snaps the window to the correct side. 428 TEST_F(FrameSizeButtonTestRTL, ButtonDrag) { 429 // In RTL the close button should be left of the size button and the minimize 430 // button should be right of the size button. 431 ASSERT_LT(close_button()->GetBoundsInScreen().x(), 432 size_button()->GetBoundsInScreen().x()); 433 ASSERT_LT(size_button()->GetBoundsInScreen().x(), 434 minimize_button()->GetBoundsInScreen().x()); 435 436 // Test initial state. 437 EXPECT_TRUE(window_state()->IsNormalStateType()); 438 EXPECT_TRUE(AllButtonsInNormalState()); 439 EXPECT_EQ(CAPTION_BUTTON_ICON_MINIMIZE, minimize_button()->icon()); 440 EXPECT_EQ(CAPTION_BUTTON_ICON_CLOSE, close_button()->icon()); 441 442 // Pressing the size button should swap the icons of the minimize and close 443 // buttons to icons for snapping right and for snapping left respectively. 444 aura::test::EventGenerator& generator = GetEventGenerator(); 445 generator.MoveMouseTo(CenterPointInScreen(size_button())); 446 generator.PressLeftButton(); 447 EXPECT_EQ(views::Button::STATE_NORMAL, minimize_button()->state()); 448 EXPECT_EQ(views::Button::STATE_PRESSED, size_button()->state()); 449 EXPECT_EQ(views::Button::STATE_NORMAL, close_button()->state()); 450 EXPECT_EQ(CAPTION_BUTTON_ICON_RIGHT_SNAPPED, minimize_button()->icon()); 451 EXPECT_EQ(CAPTION_BUTTON_ICON_LEFT_SNAPPED, close_button()->icon()); 452 453 // Dragging over to the minimize button should press it. 454 generator.MoveMouseTo(CenterPointInScreen(minimize_button())); 455 EXPECT_EQ(views::Button::STATE_HOVERED, minimize_button()->state()); 456 EXPECT_EQ(views::Button::STATE_PRESSED, size_button()->state()); 457 EXPECT_EQ(views::Button::STATE_NORMAL, close_button()->state()); 458 459 // Releasing should snap the window right. 460 generator.ReleaseLeftButton(); 461 RunAllPendingInMessageLoop(); 462 EXPECT_TRUE(HasStateType(wm::WINDOW_STATE_TYPE_RIGHT_SNAPPED)); 463 464 // None of the buttons should stay pressed and the buttons should have their 465 // regular icons. 466 EXPECT_TRUE(AllButtonsInNormalState()); 467 EXPECT_EQ(CAPTION_BUTTON_ICON_MINIMIZE, minimize_button()->icon()); 468 EXPECT_EQ(CAPTION_BUTTON_ICON_CLOSE, close_button()->icon()); 469 } 470 471 } // namespace test 472 } // namespace ash 473