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 "chrome/browser/ui/views/frame/opaque_browser_frame_view_layout.h"
7 #include "base/basictypes.h"
8 #include "base/command_line.h"
9 #include "base/strings/utf_string_conversions.h"
10 #include "chrome/browser/ui/views/profiles/avatar_label.h"
11 #include "chrome/browser/ui/views/profiles/avatar_menu_button.h"
12 #include "chrome/browser/ui/views/tab_icon_view.h"
13 #include "chrome/browser/ui/views/tabs/tab.h"
14 #include "chrome/common/chrome_switches.h"
15 #include "components/signin/core/common/profile_management_switches.h"
16 #include "ui/gfx/image/image_skia.h"
17 #include "ui/gfx/image/image_skia_rep.h"
18 #include "ui/gfx/text_constants.h"
19 #include "ui/views/controls/button/image_button.h"
20 #include "ui/views/controls/button/menu_button.h"
21 #include "ui/views/controls/label.h"
22 #include "ui/views/test/views_test_base.h"
28 const int kWidth = 500;
30 class TestLayoutDelegate : public OpaqueBrowserFrameViewLayoutDelegate {
40 : show_avatar_(false),
41 show_caption_buttons_(true),
42 window_state_(STATE_NORMAL) {
45 virtual ~TestLayoutDelegate() {}
47 void SetWindowTitle(const base::string16& title) {
48 window_title_ = title;
51 void SetShouldShowAvatar(bool show_avatar) {
52 show_avatar_ = show_avatar;
55 void SetShouldShowCaptionButtons(bool show_caption_buttons) {
56 show_caption_buttons_ = show_caption_buttons;
59 void SetWindowState(WindowState state) {
60 window_state_ = state;
63 // OpaqueBrowserFrameViewLayoutDelegate overrides:
65 virtual bool ShouldShowWindowIcon() const OVERRIDE {
66 return !window_title_.empty();
69 virtual bool ShouldShowWindowTitle() const OVERRIDE {
70 return !window_title_.empty();
73 virtual base::string16 GetWindowTitle() const OVERRIDE {
77 virtual int GetIconSize() const OVERRIDE {
78 // The value on linux_aura and non-aura windows.
82 virtual bool ShouldLeaveOffsetNearTopBorder() const OVERRIDE {
83 return !IsMaximized();
86 virtual gfx::Size GetBrowserViewMinimumSize() const OVERRIDE {
87 // Taken from a calculation in BrowserViewLayout.
88 return gfx::Size(168, 64);
91 virtual bool ShouldShowCaptionButtons() const OVERRIDE {
92 return show_caption_buttons_;
95 virtual bool ShouldShowAvatar() const OVERRIDE {
99 virtual bool IsRegularOrGuestSession() const OVERRIDE {
103 virtual gfx::ImageSkia GetOTRAvatarIcon() const OVERRIDE {
104 // The calculations depend on the size of the OTR resource, and chromeos
105 // uses a different sized image, so hard code the size of the current
106 // windows/linux one.
107 gfx::ImageSkiaRep rep(gfx::Size(40, 29), 1.0f);
108 gfx::ImageSkia image(rep);
112 virtual bool IsMaximized() const OVERRIDE {
113 return window_state_ == STATE_MAXIMIZED;
116 virtual bool IsMinimized() const OVERRIDE {
117 return window_state_ == STATE_MINIMIZED;
120 virtual bool IsFullscreen() const OVERRIDE {
121 return window_state_ == STATE_FULLSCREEN;
124 virtual bool IsTabStripVisible() const OVERRIDE {
125 return window_title_.empty();
128 virtual int GetTabStripHeight() const OVERRIDE {
129 return IsTabStripVisible() ? Tab::GetMinimumUnselectedSize().height() : 0;
132 virtual gfx::Size GetTabstripPreferredSize() const OVERRIDE {
133 // Measured from Tabstrip::GetPreferredSize().
134 return IsTabStripVisible() ? gfx::Size(78, 29) : gfx::Size(0, 0);
138 base::string16 window_title_;
140 bool show_caption_buttons_;
141 WindowState window_state_;
143 DISALLOW_COPY_AND_ASSIGN(TestLayoutDelegate);
148 class OpaqueBrowserFrameViewLayoutTest : public views::ViewsTestBase {
150 OpaqueBrowserFrameViewLayoutTest() {}
151 virtual ~OpaqueBrowserFrameViewLayoutTest() {}
153 virtual void SetUp() OVERRIDE {
154 views::ViewsTestBase::SetUp();
156 delegate_.reset(new TestLayoutDelegate);
157 layout_manager_ = new OpaqueBrowserFrameViewLayout(delegate_.get());
158 layout_manager_->set_extra_caption_y(0);
159 layout_manager_->set_window_caption_spacing(0);
160 widget_ = new Widget;
161 widget_->Init(CreateParams(Widget::InitParams::TYPE_POPUP));
162 root_view_ = widget_->GetRootView();
163 root_view_->SetSize(gfx::Size(kWidth, kWidth));
164 root_view_->SetLayoutManager(layout_manager_);
166 // Add the caption buttons. We use fake images because we're modeling the
167 // Windows assets here, while the linux version uses differently sized
170 // TODO(erg): In a follow up patch, separate these sizes out into virtual
171 // accessors so we can test both the windows and linux behaviours once we
172 // start modifying the code.
173 minimize_button_ = InitWindowCaptionButton(
174 VIEW_ID_MINIMIZE_BUTTON, gfx::Size(26, 18));
175 maximize_button_ = InitWindowCaptionButton(
176 VIEW_ID_MAXIMIZE_BUTTON, gfx::Size(25, 18));
177 restore_button_ = InitWindowCaptionButton(
178 VIEW_ID_RESTORE_BUTTON, gfx::Size(25, 18));
179 close_button_ = InitWindowCaptionButton(
180 VIEW_ID_CLOSE_BUTTON, gfx::Size(43, 18));
183 virtual void TearDown() OVERRIDE {
186 views::ViewsTestBase::TearDown();
190 views::ImageButton* InitWindowCaptionButton(ViewID view_id,
191 const gfx::Size& size) {
192 views::ImageButton* button = new views::ImageButton(NULL);
193 gfx::ImageSkiaRep rep(size, 1.0f);
194 gfx::ImageSkia image(rep);
195 button->SetImage(views::CustomButton::STATE_NORMAL, &image);
196 button->set_id(view_id);
197 root_view_->AddChildView(button);
201 void AddWindowTitleIcons() {
202 tab_icon_view_ = new TabIconView(NULL, NULL);
203 tab_icon_view_->set_is_light(true);
204 tab_icon_view_->set_id(VIEW_ID_WINDOW_ICON);
205 root_view_->AddChildView(tab_icon_view_);
207 window_title_ = new views::Label(delegate_->GetWindowTitle());
208 window_title_->SetVisible(delegate_->ShouldShowWindowTitle());
209 window_title_->SetEnabledColor(SK_ColorWHITE);
210 window_title_->SetSubpixelRenderingEnabled(false);
211 window_title_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
212 window_title_->set_id(VIEW_ID_WINDOW_TITLE);
213 root_view_->AddChildView(window_title_);
216 void AddAvatarButton() {
217 // Disable the New Avatar Menu.
218 switches::DisableNewAvatarMenuForTesting(CommandLine::ForCurrentProcess());
220 menu_button_ = new AvatarMenuButton(NULL, false);
221 menu_button_->set_id(VIEW_ID_AVATAR_BUTTON);
222 delegate_->SetShouldShowAvatar(true);
223 root_view_->AddChildView(menu_button_);
226 void AddAvatarLabel() {
227 avatar_label_ = new AvatarLabel(NULL);
228 avatar_label_->set_id(VIEW_ID_AVATAR_LABEL);
229 root_view_->AddChildView(avatar_label_);
231 // The avatar label should only be used together with the avatar button.
235 void AddNewAvatarButton() {
236 // Enable the New Avatar Menu.
237 switches::EnableNewAvatarMenuForTesting(CommandLine::ForCurrentProcess());
240 new views::MenuButton(NULL, base::string16(), NULL, false);
241 new_avatar_button_->set_id(VIEW_ID_NEW_AVATAR_BUTTON);
242 root_view_->AddChildView(new_avatar_button_);
245 void ExpectBasicWindowBounds() {
246 EXPECT_EQ("428,1 25x18", maximize_button_->bounds().ToString());
247 EXPECT_EQ("402,1 26x18", minimize_button_->bounds().ToString());
248 EXPECT_EQ("0,0 0x0", restore_button_->bounds().ToString());
249 EXPECT_EQ("453,1 43x18", close_button_->bounds().ToString());
253 views::View* root_view_;
254 OpaqueBrowserFrameViewLayout* layout_manager_;
255 scoped_ptr<TestLayoutDelegate> delegate_;
258 views::ImageButton* minimize_button_;
259 views::ImageButton* maximize_button_;
260 views::ImageButton* restore_button_;
261 views::ImageButton* close_button_;
263 TabIconView* tab_icon_view_;
264 views::Label* window_title_;
266 AvatarLabel* avatar_label_;
267 AvatarMenuButton* menu_button_;
268 views::MenuButton* new_avatar_button_;
270 DISALLOW_COPY_AND_ASSIGN(OpaqueBrowserFrameViewLayoutTest);
273 TEST_F(OpaqueBrowserFrameViewLayoutTest, BasicWindow) {
274 // Tests the layout of a default chrome window with no avatars, no window
275 // titles, and a tabstrip.
276 root_view_->Layout();
278 ExpectBasicWindowBounds();
280 // After some visual inspection, it really does look like the tabstrip is
281 // initally positioned out of our view.
282 EXPECT_EQ("-1,13 398x29",
283 layout_manager_->GetBoundsForTabStrip(
284 delegate_->GetTabstripPreferredSize(), kWidth).ToString());
285 EXPECT_EQ("261x73", layout_manager_->GetMinimumSize(kWidth).ToString());
287 // A normal window with no window icon still produces icon bounds for
288 // Windows, which has a hidden icon that a user can double click on to close
290 EXPECT_EQ("6,4 17x17", layout_manager_->IconBounds().ToString());
293 TEST_F(OpaqueBrowserFrameViewLayoutTest, BasicWindowMaximized) {
294 // Tests the layout of a default chrome window with no avatars, no window
295 // titles, and a tabstrip, but maximized this time.
296 delegate_->SetWindowState(TestLayoutDelegate::STATE_MAXIMIZED);
297 root_view_->Layout();
299 // Note how the bounds start at the exact top of the window while maximized
300 // while they start 1 pixel below when unmaximized.
301 EXPECT_EQ("0,0 0x0", maximize_button_->bounds().ToString());
302 EXPECT_EQ("403,0 26x18", minimize_button_->bounds().ToString());
303 EXPECT_EQ("429,0 25x18", restore_button_->bounds().ToString());
304 EXPECT_EQ("454,0 46x18", close_button_->bounds().ToString());
306 EXPECT_EQ("-5,-3 392x29",
307 layout_manager_->GetBoundsForTabStrip(
308 delegate_->GetTabstripPreferredSize(), kWidth).ToString());
309 EXPECT_EQ("262x61", layout_manager_->GetMinimumSize(kWidth).ToString());
311 // In the maximized case, OpaqueBrowserFrameView::NonClientHitTest() uses
312 // this rect, extended to the top left corner of the window.
313 EXPECT_EQ("2,0 17x17", layout_manager_->IconBounds().ToString());
316 TEST_F(OpaqueBrowserFrameViewLayoutTest, MaximizedWithYOffset) {
317 // Tests the layout of a basic chrome window with the caption buttons slightly
318 // offset from the top of the screen (as they are on Linux).
319 layout_manager_->set_extra_caption_y(2);
320 delegate_->SetWindowState(TestLayoutDelegate::STATE_MAXIMIZED);
321 root_view_->Layout();
323 // Note how the bounds start at the exact top of the window, DESPITE the
324 // caption Y offset of 2. This ensures that we obey Fitts' Law (the buttons
325 // are clickable on the top edge of the screen). However, the buttons are 2
326 // pixels taller, so the images appear to be offset by 2 pixels.
327 EXPECT_EQ("0,0 0x0", maximize_button_->bounds().ToString());
328 EXPECT_EQ("403,0 26x20", minimize_button_->bounds().ToString());
329 EXPECT_EQ("429,0 25x20", restore_button_->bounds().ToString());
330 EXPECT_EQ("454,0 46x20", close_button_->bounds().ToString());
332 EXPECT_EQ("-5,-3 392x29",
333 layout_manager_->GetBoundsForTabStrip(
334 delegate_->GetTabstripPreferredSize(), kWidth).ToString());
335 EXPECT_EQ("262x61", layout_manager_->GetMinimumSize(kWidth).ToString());
337 // In the maximized case, OpaqueBrowserFrameView::NonClientHitTest() uses
338 // this rect, extended to the top left corner of the window.
339 EXPECT_EQ("2,0 17x17", layout_manager_->IconBounds().ToString());
342 TEST_F(OpaqueBrowserFrameViewLayoutTest, WindowButtonsOnLeft) {
343 // Tests the layout of a chrome window with caption buttons on the left.
344 std::vector<views::FrameButton> leading_buttons;
345 std::vector<views::FrameButton> trailing_buttons;
346 leading_buttons.push_back(views::FRAME_BUTTON_CLOSE);
347 leading_buttons.push_back(views::FRAME_BUTTON_MINIMIZE);
348 leading_buttons.push_back(views::FRAME_BUTTON_MAXIMIZE);
349 layout_manager_->SetButtonOrdering(leading_buttons, trailing_buttons);
350 root_view_->Layout();
352 EXPECT_EQ("73,1 25x18", maximize_button_->bounds().ToString());
353 EXPECT_EQ("47,1 26x18", minimize_button_->bounds().ToString());
354 EXPECT_EQ("0,0 0x0", restore_button_->bounds().ToString());
355 EXPECT_EQ("4,1 43x18", close_button_->bounds().ToString());
357 EXPECT_EQ("93,13 398x29",
358 layout_manager_->GetBoundsForTabStrip(
359 delegate_->GetTabstripPreferredSize(), kWidth).ToString());
360 EXPECT_EQ("261x73", layout_manager_->GetMinimumSize(kWidth).ToString());
362 // If the buttons are on the left, there should be no hidden icon for the user
364 EXPECT_EQ("0,0 0x0", layout_manager_->IconBounds().ToString());
367 TEST_F(OpaqueBrowserFrameViewLayoutTest, WithoutCaptionButtons) {
368 // Tests the layout of a default chrome window with no caption buttons (which
369 // should force the tab strip to be condensed).
370 delegate_->SetShouldShowCaptionButtons(false);
371 root_view_->Layout();
373 EXPECT_EQ("0,0 0x0", maximize_button_->bounds().ToString());
374 EXPECT_EQ("0,0 0x0", minimize_button_->bounds().ToString());
375 EXPECT_EQ("0,0 0x0", restore_button_->bounds().ToString());
376 EXPECT_EQ("0,0 0x0", close_button_->bounds().ToString());
378 EXPECT_EQ("-5,-3 500x29",
379 layout_manager_->GetBoundsForTabStrip(
380 delegate_->GetTabstripPreferredSize(), kWidth).ToString());
381 EXPECT_EQ("251x61", layout_manager_->GetMinimumSize(kWidth).ToString());
383 // A normal window with no window icon still produces icon bounds for
384 // Windows, which has a hidden icon that a user can double click on to close
386 EXPECT_EQ("2,0 17x17", layout_manager_->IconBounds().ToString());
389 TEST_F(OpaqueBrowserFrameViewLayoutTest, MaximizedWithoutCaptionButtons) {
390 // Tests the layout of a maximized chrome window with no caption buttons.
391 delegate_->SetWindowState(TestLayoutDelegate::STATE_MAXIMIZED);
392 delegate_->SetShouldShowCaptionButtons(false);
393 root_view_->Layout();
395 EXPECT_EQ("0,0 0x0", maximize_button_->bounds().ToString());
396 EXPECT_EQ("0,0 0x0", minimize_button_->bounds().ToString());
397 EXPECT_EQ("0,0 0x0", restore_button_->bounds().ToString());
398 EXPECT_EQ("0,0 0x0", close_button_->bounds().ToString());
400 EXPECT_EQ("-5,-3 500x29",
401 layout_manager_->GetBoundsForTabStrip(
402 delegate_->GetTabstripPreferredSize(), kWidth).ToString());
403 EXPECT_EQ("251x61", layout_manager_->GetMinimumSize(kWidth).ToString());
405 // In the maximized case, OpaqueBrowserFrameView::NonClientHitTest() uses
406 // this rect, extended to the top left corner of the window.
407 EXPECT_EQ("2,0 17x17", layout_manager_->IconBounds().ToString());
410 TEST_F(OpaqueBrowserFrameViewLayoutTest, WithWindowTitleAndIcon) {
411 // Tests the layout of pop up windows.
412 delegate_->SetWindowTitle(base::ASCIIToUTF16("Window Title"));
413 AddWindowTitleIcons();
414 root_view_->Layout();
416 // We should have the right hand side should match the BasicWindow case.
417 ExpectBasicWindowBounds();
419 // Check the location of the tab icon and window title.
420 EXPECT_EQ("6,3 17x17", tab_icon_view_->bounds().ToString());
421 EXPECT_EQ("27,3 370x17", window_title_->bounds().ToString());
424 TEST_F(OpaqueBrowserFrameViewLayoutTest, WindowWithAvatar) {
425 // Tests a normal tabstrip window with an avatar icon.
427 root_view_->Layout();
429 ExpectBasicWindowBounds();
431 // Check the location of the avatar
432 EXPECT_EQ("7,11 40x29", menu_button_->bounds().ToString());
433 EXPECT_EQ("45,13 352x29",
434 layout_manager_->GetBoundsForTabStrip(
435 delegate_->GetTabstripPreferredSize(), kWidth).ToString());
436 EXPECT_EQ("261x73", layout_manager_->GetMinimumSize(kWidth).ToString());
439 TEST_F(OpaqueBrowserFrameViewLayoutTest, WindowWithAvatarWithButtonsOnLeft) {
440 // Tests the layout of a chrome window with an avatar icon and caption buttons
441 // on the left. The avatar icon should therefore be on the right.
442 // AddAvatarLabel() also adds the avatar button.
444 std::vector<views::FrameButton> leading_buttons;
445 std::vector<views::FrameButton> trailing_buttons;
446 leading_buttons.push_back(views::FRAME_BUTTON_CLOSE);
447 leading_buttons.push_back(views::FRAME_BUTTON_MINIMIZE);
448 leading_buttons.push_back(views::FRAME_BUTTON_MAXIMIZE);
449 layout_manager_->SetButtonOrdering(leading_buttons, trailing_buttons);
450 root_view_->Layout();
452 EXPECT_EQ("73,1 25x18", maximize_button_->bounds().ToString());
453 EXPECT_EQ("47,1 26x18", minimize_button_->bounds().ToString());
454 EXPECT_EQ("0,0 0x0", restore_button_->bounds().ToString());
455 EXPECT_EQ("4,1 43x18", close_button_->bounds().ToString());
457 // Check the location of the avatar
458 EXPECT_EQ("454,11 40x29", menu_button_->bounds().ToString());
460 // Check the tab strip bounds.
461 gfx::Rect tab_strip_bounds = layout_manager_->GetBoundsForTabStrip(
462 delegate_->GetTabstripPreferredSize(), kWidth);
463 EXPECT_GT(tab_strip_bounds.x(), maximize_button_->bounds().x());
464 EXPECT_GT(maximize_button_->bounds().right(), tab_strip_bounds.x());
465 EXPECT_EQ(13, tab_strip_bounds.y());
466 EXPECT_EQ(29, tab_strip_bounds.height());
467 EXPECT_GT(avatar_label_->bounds().x(), tab_strip_bounds.right());
468 EXPECT_EQ("261x73", layout_manager_->GetMinimumSize(kWidth).ToString());
470 // Check the relative location of the avatar label to the avatar. The right
471 // end of the avatar label should be slightly to the right of the right end of
473 EXPECT_GT(avatar_label_->bounds().right(), menu_button_->bounds().right());
474 EXPECT_GT(menu_button_->bounds().x(), avatar_label_->bounds().x());
475 EXPECT_GT(menu_button_->bounds().bottom(),
476 avatar_label_->bounds().bottom());
477 EXPECT_GT(avatar_label_->bounds().y(), menu_button_->bounds().y());
479 // This means that the menu will pop out facing the left (if it were to face
480 // the right, it would go outside the window frame and be clipped).
481 EXPECT_TRUE(menu_button_->button_on_right());
483 // If the buttons are on the left, there should be no hidden icon for the user
485 EXPECT_EQ("0,0 0x0", layout_manager_->IconBounds().ToString());
488 TEST_F(OpaqueBrowserFrameViewLayoutTest,
489 WindowWithAvatarWithoutCaptionButtonsOnLeft) {
490 // Tests the layout of a chrome window with an avatar icon and no caption
491 // buttons. However, the caption buttons *would* be on the left if they
492 // weren't hidden, and therefore, the avatar icon should be on the right.
493 // The lack of caption buttons should force the tab strip to be condensed.
495 std::vector<views::FrameButton> leading_buttons;
496 std::vector<views::FrameButton> trailing_buttons;
497 leading_buttons.push_back(views::FRAME_BUTTON_CLOSE);
498 leading_buttons.push_back(views::FRAME_BUTTON_MINIMIZE);
499 leading_buttons.push_back(views::FRAME_BUTTON_MAXIMIZE);
500 layout_manager_->SetButtonOrdering(leading_buttons, trailing_buttons);
501 delegate_->SetShouldShowCaptionButtons(false);
502 root_view_->Layout();
504 EXPECT_EQ("0,0 0x0", maximize_button_->bounds().ToString());
505 EXPECT_EQ("0,0 0x0", minimize_button_->bounds().ToString());
506 EXPECT_EQ("0,0 0x0", restore_button_->bounds().ToString());
507 EXPECT_EQ("0,0 0x0", close_button_->bounds().ToString());
509 // Check the location of the avatar
510 EXPECT_EQ("458,0 40x24", menu_button_->bounds().ToString());
511 EXPECT_EQ("-5,-3 458x29",
512 layout_manager_->GetBoundsForTabStrip(
513 delegate_->GetTabstripPreferredSize(), kWidth).ToString());
514 EXPECT_EQ("251x61", layout_manager_->GetMinimumSize(kWidth).ToString());
516 // A normal window with no window icon still produces icon bounds for
517 // Windows, which has a hidden icon that a user can double click on to close
519 EXPECT_EQ("2,0 17x17", layout_manager_->IconBounds().ToString());
522 TEST_F(OpaqueBrowserFrameViewLayoutTest, WindowWithNewAvatar) {
523 // Tests a normal tabstrip window with the new style avatar icon.
524 AddNewAvatarButton();
525 root_view_->Layout();
527 ExpectBasicWindowBounds();
529 // Check the location of the avatar button.
530 EXPECT_EQ("385,1 12x18", new_avatar_button_->bounds().ToString());
531 // The basic window bounds are (-1, 13 398x29). There should not be an icon
532 // avatar in the left, and the new avatar button has an offset of 5 to its
534 EXPECT_EQ("-1,13 381x29",
535 layout_manager_->GetBoundsForTabStrip(
536 delegate_->GetTabstripPreferredSize(), kWidth).ToString());
537 EXPECT_EQ("261x73", layout_manager_->GetMinimumSize(kWidth).ToString());
540 TEST_F(OpaqueBrowserFrameViewLayoutTest, WindowWithAvatarLabelAndButtonOnLeft) {
542 root_view_->Layout();
544 ExpectBasicWindowBounds();
546 // Check the location of the avatar label relative to the avatar button if
547 // both are displayed on the left side.
548 // The label height and width depends on the font size and the text displayed.
549 // This may possibly change, so we don't test it here.
550 EXPECT_EQ(menu_button_->bounds().x() - 2, avatar_label_->bounds().x());
552 menu_button_->bounds().bottom() - 3 - avatar_label_->bounds().height(),
553 avatar_label_->bounds().y());