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