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