1 // Copyright (c) 2012 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/browser_non_client_frame_view_ash.h"
7 #include "ash/ash_switches.h"
8 #include "ash/frame/caption_buttons/frame_caption_button_container_view.h"
9 #include "ash/frame/default_header_painter.h"
10 #include "ash/frame/frame_border_hit_test_controller.h"
11 #include "ash/frame/header_painter_util.h"
12 #include "ash/shell.h"
13 #include "base/command_line.h"
14 #include "chrome/browser/themes/theme_properties.h"
15 #include "chrome/browser/ui/browser.h"
16 #include "chrome/browser/ui/views/frame/browser_frame.h"
17 #include "chrome/browser/ui/views/frame/browser_header_painter_ash.h"
18 #include "chrome/browser/ui/views/frame/browser_view.h"
19 #include "chrome/browser/ui/views/frame/immersive_mode_controller.h"
20 #include "chrome/browser/ui/views/profiles/avatar_label.h"
21 #include "chrome/browser/ui/views/profiles/avatar_menu_button.h"
22 #include "chrome/browser/ui/views/tab_icon_view.h"
23 #include "chrome/browser/ui/views/tabs/tab_strip.h"
24 #include "chrome/common/chrome_switches.h"
25 #include "content/public/browser/web_contents.h"
26 #include "grit/ash_resources.h"
27 #include "grit/theme_resources.h"
28 #include "ui/accessibility/ax_view_state.h"
29 #include "ui/aura/client/aura_constants.h"
30 #include "ui/aura/window.h"
31 #include "ui/base/hit_test.h"
32 #include "ui/base/l10n/l10n_util.h"
33 #include "ui/base/layout.h"
34 #include "ui/base/resource/resource_bundle.h"
35 #include "ui/base/theme_provider.h"
36 #include "ui/compositor/layer_animator.h"
37 #include "ui/gfx/canvas.h"
38 #include "ui/gfx/image/image_skia.h"
39 #include "ui/gfx/rect_conversions.h"
40 #include "ui/views/controls/label.h"
41 #include "ui/views/layout/layout_constants.h"
42 #include "ui/views/widget/widget.h"
43 #include "ui/views/widget/widget_delegate.h"
47 // The avatar ends 2 px above the bottom of the tabstrip (which, given the
48 // way the tabstrip draws its bottom edge, will appear like a 1 px gap to the
50 const int kAvatarBottomSpacing
= 2;
51 // There are 2 px on each side of the avatar (between the frame border and
52 // it on the left, and between it and the tabstrip on the right).
53 const int kAvatarSideSpacing
= 2;
54 // Space between left edge of window and tabstrip.
55 const int kTabstripLeftSpacing
= 0;
56 // Space between right edge of tabstrip and maximize button.
57 const int kTabstripRightSpacing
= 10;
58 // Height of the shadow of the content area, at the top of the toolbar.
59 const int kContentShadowHeight
= 1;
60 // Space between top of window and top of tabstrip for tall headers, such as
61 // for restored windows, apps, etc.
62 const int kTabstripTopSpacingTall
= 7;
63 // Space between top of window and top of tabstrip for short headers, such as
64 // for maximized windows, pop-ups, etc.
65 const int kTabstripTopSpacingShort
= 0;
66 // Height of the shadow in the tab image, used to ensure clicks in the shadow
67 // area still drag restored windows. This keeps the clickable area large enough
69 const int kTabShadowHeight
= 4;
73 ///////////////////////////////////////////////////////////////////////////////
74 // BrowserNonClientFrameViewAsh, public:
77 const char BrowserNonClientFrameViewAsh::kViewClassName
[] =
78 "BrowserNonClientFrameViewAsh";
80 BrowserNonClientFrameViewAsh::BrowserNonClientFrameViewAsh(
81 BrowserFrame
* frame
, BrowserView
* browser_view
)
82 : BrowserNonClientFrameView(frame
, browser_view
),
83 caption_button_container_(NULL
),
85 frame_border_hit_test_controller_(
86 new ash::FrameBorderHitTestController(frame
)) {
87 ash::Shell::GetInstance()->AddShellObserver(this);
90 BrowserNonClientFrameViewAsh::~BrowserNonClientFrameViewAsh() {
91 ash::Shell::GetInstance()->RemoveShellObserver(this);
94 void BrowserNonClientFrameViewAsh::Init() {
95 caption_button_container_
= new ash::FrameCaptionButtonContainerView(frame(),
96 ash::FrameCaptionButtonContainerView::MINIMIZE_ALLOWED
);
97 caption_button_container_
->UpdateSizeButtonVisibility(
98 ash::Shell::GetInstance()->IsMaximizeModeWindowManagerEnabled());
99 AddChildView(caption_button_container_
);
101 // Initializing the TabIconView is expensive, so only do it if we need to.
102 if (browser_view()->ShouldShowWindowIcon()) {
103 window_icon_
= new TabIconView(this, NULL
);
104 window_icon_
->set_is_light(true);
105 AddChildView(window_icon_
);
106 window_icon_
->Update();
109 // Create incognito icon if necessary.
112 // HeaderPainter handles layout.
113 if (UsePackagedAppHeaderStyle()) {
114 ash::DefaultHeaderPainter
* header_painter
= new ash::DefaultHeaderPainter
;
115 header_painter_
.reset(header_painter
);
116 header_painter
->Init(frame(), this, window_icon_
,
117 caption_button_container_
);
119 BrowserHeaderPainterAsh
* header_painter
= new BrowserHeaderPainterAsh
;
120 header_painter_
.reset(header_painter
);
121 header_painter
->Init(frame(), browser_view(), this, window_icon_
,
122 caption_button_container_
);
126 ///////////////////////////////////////////////////////////////////////////////
127 // BrowserNonClientFrameView:
129 gfx::Rect
BrowserNonClientFrameViewAsh::GetBoundsForTabStrip(
130 views::View
* tabstrip
) const {
134 // When the tab strip is painted in the immersive fullscreen light bar style,
135 // the caption buttons and the avatar button are not visible. However, their
136 // bounds are still used to compute the tab strip bounds so that the tabs have
137 // the same horizontal position when the tab strip is painted in the immersive
138 // light bar style as when the top-of-window views are revealed.
139 int left_inset
= GetTabStripLeftInset();
140 int right_inset
= GetTabStripRightInset();
141 return gfx::Rect(left_inset
,
143 std::max(0, width() - left_inset
- right_inset
),
144 tabstrip
->GetPreferredSize().height());
147 int BrowserNonClientFrameViewAsh::GetTopInset() const {
148 if (!ShouldPaint() || UseImmersiveLightbarHeaderStyle())
151 if (browser_view()->IsTabStripVisible()) {
152 if (frame()->IsMaximized() || frame()->IsFullscreen())
153 return kTabstripTopSpacingShort
;
155 return kTabstripTopSpacingTall
;
158 if (UsePackagedAppHeaderStyle())
159 return header_painter_
->GetHeaderHeightForPainting();
161 int caption_buttons_bottom
= caption_button_container_
->bounds().bottom();
163 // The toolbar partially overlaps the caption buttons.
164 if (browser_view()->IsToolbarVisible())
165 return caption_buttons_bottom
- kContentShadowHeight
;
167 return caption_buttons_bottom
+ kClientEdgeThickness
;
170 int BrowserNonClientFrameViewAsh::GetThemeBackgroundXInset() const {
171 return ash::HeaderPainterUtil::GetThemeBackgroundXInset();
174 void BrowserNonClientFrameViewAsh::UpdateThrobber(bool running
) {
176 window_icon_
->Update();
179 ///////////////////////////////////////////////////////////////////////////////
180 // views::NonClientFrameView:
182 gfx::Rect
BrowserNonClientFrameViewAsh::GetBoundsForClientView() const {
183 // The ClientView must be flush with the top edge of the widget so that the
184 // web contents can take up the entire screen in immersive fullscreen (with
185 // or without the top-of-window views revealed). When in immersive fullscreen
186 // and the top-of-window views are revealed, the TopContainerView paints the
187 // window header by redirecting paints from its background to
188 // BrowserNonClientFrameViewAsh.
192 gfx::Rect
BrowserNonClientFrameViewAsh::GetWindowBoundsForClientBounds(
193 const gfx::Rect
& client_bounds
) const {
194 return client_bounds
;
197 int BrowserNonClientFrameViewAsh::NonClientHitTest(const gfx::Point
& point
) {
198 int hit_test
= ash::FrameBorderHitTestController::NonClientHitTest(this,
199 caption_button_container_
, point
);
201 // See if the point is actually within the avatar menu button or within
203 if (hit_test
== HTCAPTION
&& ((avatar_button() &&
204 avatar_button()->GetMirroredBounds().Contains(point
)) ||
205 (avatar_label() && avatar_label()->GetMirroredBounds().Contains(point
))))
208 // When the window is restored we want a large click target above the tabs
209 // to drag the window, so redirect clicks in the tab's shadow to caption.
210 if (hit_test
== HTCLIENT
&&
211 !(frame()->IsMaximized() || frame()->IsFullscreen())) {
212 // Convert point to client coordinates.
213 gfx::Point
client_point(point
);
214 View::ConvertPointToTarget(this, frame()->client_view(), &client_point
);
215 // Report hits in shadow at top of tabstrip as caption.
216 gfx::Rect
tabstrip_bounds(browser_view()->tabstrip()->bounds());
217 if (client_point
.y() < tabstrip_bounds
.y() + kTabShadowHeight
)
218 hit_test
= HTCAPTION
;
223 void BrowserNonClientFrameViewAsh::GetWindowMask(const gfx::Size
& size
,
224 gfx::Path
* window_mask
) {
225 // Aura does not use window masks.
228 void BrowserNonClientFrameViewAsh::ResetWindowControls() {
229 // Hide the caption buttons in immersive fullscreen when the tab light bar
230 // is visible because it's confusing when the user hovers or clicks in the
231 // top-right of the screen and hits one.
232 bool button_visibility
= !UseImmersiveLightbarHeaderStyle();
233 caption_button_container_
->SetVisible(button_visibility
);
235 caption_button_container_
->ResetWindowControls();
238 void BrowserNonClientFrameViewAsh::UpdateWindowIcon() {
240 window_icon_
->SchedulePaint();
243 void BrowserNonClientFrameViewAsh::UpdateWindowTitle() {
244 if (!frame()->IsFullscreen())
245 header_painter_
->SchedulePaintForTitle();
248 ///////////////////////////////////////////////////////////////////////////////
251 void BrowserNonClientFrameViewAsh::OnPaint(gfx::Canvas
* canvas
) {
255 if (UseImmersiveLightbarHeaderStyle()) {
256 PaintImmersiveLightbarStyleHeader(canvas
);
260 caption_button_container_
->SetPaintAsActive(ShouldPaintAsActive());
262 ash::HeaderPainter::Mode header_mode
= ShouldPaintAsActive() ?
263 ash::HeaderPainter::MODE_ACTIVE
: ash::HeaderPainter::MODE_INACTIVE
;
264 header_painter_
->PaintHeader(canvas
, header_mode
);
265 if (browser_view()->IsToolbarVisible())
266 PaintToolbarBackground(canvas
);
267 else if (!UsePackagedAppHeaderStyle())
268 PaintContentEdge(canvas
);
271 void BrowserNonClientFrameViewAsh::Layout() {
272 // The header must be laid out before computing |painted_height| because the
273 // computation of |painted_height| for app and popup windows depends on the
274 // position of the window controls.
275 header_painter_
->LayoutHeader();
277 int painted_height
= 0;
278 if (browser_view()->IsTabStripVisible()) {
279 painted_height
= GetTopInset() +
280 browser_view()->tabstrip()->GetPreferredSize().height();
281 } else if (browser_view()->IsToolbarVisible()) {
282 // Paint the header so that it overlaps with the top few pixels of the
283 // toolbar because the top few pixels of the toolbar are not opaque.
284 painted_height
= GetTopInset() + kFrameShadowThickness
* 2;
286 painted_height
= GetTopInset();
288 header_painter_
->SetHeaderHeightForPainting(painted_height
);
291 BrowserNonClientFrameView::Layout();
294 const char* BrowserNonClientFrameViewAsh::GetClassName() const {
295 return kViewClassName
;
298 bool BrowserNonClientFrameViewAsh::HitTestRect(const gfx::Rect
& rect
) const {
299 if (!views::View::HitTestRect(rect
)) {
300 // |rect| is outside BrowserNonClientFrameViewAsh's bounds.
304 TabStrip
* tabstrip
= browser_view()->tabstrip();
305 if (tabstrip
&& browser_view()->IsTabStripVisible()) {
306 // Claim |rect| only if it is above the bottom of the tabstrip in a non-tab
308 gfx::RectF
rect_in_tabstrip_coords_f(rect
);
309 View::ConvertRectToTarget(this, tabstrip
, &rect_in_tabstrip_coords_f
);
310 gfx::Rect rect_in_tabstrip_coords
= gfx::ToEnclosingRect(
311 rect_in_tabstrip_coords_f
);
313 if (rect_in_tabstrip_coords
.y() > tabstrip
->height())
316 return !tabstrip
->HitTestRect(rect_in_tabstrip_coords
) ||
317 tabstrip
->IsRectInWindowCaption(rect_in_tabstrip_coords
);
320 // Claim |rect| if it is above the top of the topmost view in the client area.
321 return rect
.y() < GetTopInset();
324 void BrowserNonClientFrameViewAsh::GetAccessibleState(
325 ui::AXViewState
* state
) {
326 state
->role
= ui::AX_ROLE_TITLE_BAR
;
329 gfx::Size
BrowserNonClientFrameViewAsh::GetMinimumSize() const {
330 gfx::Size
min_client_view_size(frame()->client_view()->GetMinimumSize());
331 int min_width
= std::max(header_painter_
->GetMinimumHeaderWidth(),
332 min_client_view_size
.width());
333 if (browser_view()->IsTabStripVisible()) {
334 // Ensure that the minimum width is enough to hold a minimum width tab strip
335 // at its usual insets.
336 int min_tabstrip_width
=
337 browser_view()->tabstrip()->GetMinimumSize().width();
338 min_width
= std::max(min_width
,
339 min_tabstrip_width
+ GetTabStripLeftInset() + GetTabStripRightInset());
341 return gfx::Size(min_width
, min_client_view_size
.height());
344 ///////////////////////////////////////////////////////////////////////////////
345 // ash::ShellObserver:
347 void BrowserNonClientFrameViewAsh::OnMaximizeModeStarted() {
348 caption_button_container_
->UpdateSizeButtonVisibility(true);
350 frame()->client_view()->InvalidateLayout();
351 frame()->GetRootView()->Layout();
354 void BrowserNonClientFrameViewAsh::OnMaximizeModeEnded() {
355 caption_button_container_
->UpdateSizeButtonVisibility(false);
357 frame()->client_view()->InvalidateLayout();
358 frame()->GetRootView()->Layout();
361 ///////////////////////////////////////////////////////////////////////////////
362 // chrome::TabIconViewModel:
364 bool BrowserNonClientFrameViewAsh::ShouldTabIconViewAnimate() const {
365 // This function is queried during the creation of the window as the
366 // TabIconView we host is initialized, so we need to NULL check the selected
367 // WebContents because in this condition there is not yet a selected tab.
368 content::WebContents
* current_tab
= browser_view()->GetActiveWebContents();
369 return current_tab
? current_tab
->IsLoading() : false;
372 gfx::ImageSkia
BrowserNonClientFrameViewAsh::GetFaviconForTabIconView() {
373 views::WidgetDelegate
* delegate
= frame()->widget_delegate();
375 return gfx::ImageSkia();
376 return delegate
->GetWindowIcon();
379 ///////////////////////////////////////////////////////////////////////////////
380 // BrowserNonClientFrameViewAsh, private:
382 int BrowserNonClientFrameViewAsh::GetTabStripLeftInset() const {
383 return avatar_button() ? kAvatarSideSpacing
+
384 browser_view()->GetOTRAvatarIcon().width() + kAvatarSideSpacing
:
385 kTabstripLeftSpacing
;
388 int BrowserNonClientFrameViewAsh::GetTabStripRightInset() const {
389 return caption_button_container_
->GetPreferredSize().width() +
390 kTabstripRightSpacing
;
393 bool BrowserNonClientFrameViewAsh::UseImmersiveLightbarHeaderStyle() const {
394 ImmersiveModeController
* immersive_controller
=
395 browser_view()->immersive_mode_controller();
396 return immersive_controller
->IsEnabled() &&
397 !immersive_controller
->IsRevealed() &&
398 browser_view()->IsTabStripVisible();
401 bool BrowserNonClientFrameViewAsh::UsePackagedAppHeaderStyle() const {
402 // Non streamlined hosted apps do not have a toolbar or tabstrip. Their header
403 // should look the same as the header for packaged apps. Streamlined hosted
404 // apps have a toolbar so should use the browser header style.
405 return browser_view()->browser()->is_app() &&
406 !CommandLine::ForCurrentProcess()->HasSwitch(
407 switches::kEnableStreamlinedHostedApps
);
410 void BrowserNonClientFrameViewAsh::LayoutAvatar() {
411 DCHECK(avatar_button());
412 #if !defined(OS_CHROMEOS)
413 // ChromeOS shows avatar on V1 app.
414 DCHECK(browser_view()->IsTabStripVisible());
416 gfx::ImageSkia incognito_icon
= browser_view()->GetOTRAvatarIcon();
418 int avatar_bottom
= GetTopInset() +
419 browser_view()->GetTabStripHeight() - kAvatarBottomSpacing
;
420 int avatar_restored_y
= avatar_bottom
- incognito_icon
.height();
422 (browser_view()->IsTabStripVisible() &&
423 (frame()->IsMaximized() || frame()->IsFullscreen())) ?
424 GetTopInset() + kContentShadowHeight
: avatar_restored_y
;
426 // Hide the incognito icon in immersive fullscreen when the tab light bar is
427 // visible because the header is too short for the icognito icon to be
429 bool avatar_visible
= !UseImmersiveLightbarHeaderStyle();
430 int avatar_height
= avatar_visible
? avatar_bottom
- avatar_y
: 0;
432 gfx::Rect
avatar_bounds(kAvatarSideSpacing
,
434 incognito_icon
.width(),
436 avatar_button()->SetBoundsRect(avatar_bounds
);
437 avatar_button()->SetVisible(avatar_visible
);
440 bool BrowserNonClientFrameViewAsh::ShouldPaint() const {
441 if (!frame()->IsFullscreen())
444 // We need to paint when in immersive fullscreen and either:
445 // - The top-of-window views are revealed.
446 // - The lightbar style tabstrip is visible.
447 ImmersiveModeController
* immersive_mode_controller
=
448 browser_view()->immersive_mode_controller();
449 return immersive_mode_controller
->IsEnabled() &&
450 (immersive_mode_controller
->IsRevealed() ||
451 UseImmersiveLightbarHeaderStyle());
454 void BrowserNonClientFrameViewAsh::PaintImmersiveLightbarStyleHeader(
455 gfx::Canvas
* canvas
) {
456 // The light bar header is not themed because theming it does not look good.
458 gfx::Rect(width(), header_painter_
->GetHeaderHeightForPainting()),
462 void BrowserNonClientFrameViewAsh::PaintToolbarBackground(gfx::Canvas
* canvas
) {
463 gfx::Rect
toolbar_bounds(browser_view()->GetToolbarBounds());
464 if (toolbar_bounds
.IsEmpty())
466 gfx::Point
toolbar_origin(toolbar_bounds
.origin());
467 View::ConvertPointToTarget(browser_view(), this, &toolbar_origin
);
468 toolbar_bounds
.set_origin(toolbar_origin
);
470 int x
= toolbar_bounds
.x();
471 int w
= toolbar_bounds
.width();
472 int y
= toolbar_bounds
.y();
473 int h
= toolbar_bounds
.height();
475 // Gross hack: We split the toolbar images into two pieces, since sometimes
476 // (popup mode) the toolbar isn't tall enough to show the whole image. The
477 // split happens between the top shadow section and the bottom gradient
478 // section so that we never break the gradient.
479 // NOTE(pkotwicz): If the computation for |bottom_y| is changed, Layout() must
480 // be changed as well.
481 int split_point
= kFrameShadowThickness
* 2;
482 int bottom_y
= y
+ split_point
;
483 ui::ThemeProvider
* tp
= GetThemeProvider();
484 int bottom_edge_height
= h
- split_point
;
486 canvas
->FillRect(gfx::Rect(x
, bottom_y
, w
, bottom_edge_height
),
487 tp
->GetColor(ThemeProperties::COLOR_TOOLBAR
));
489 // Paint the main toolbar image. Since this image is also used to draw the
490 // tab background, we must use the tab strip offset to compute the image
491 // source y position. If you have to debug this code use an image editor
492 // to paint a diagonal line through the toolbar image and ensure it lines up
493 // across the tab and toolbar.
494 gfx::ImageSkia
* theme_toolbar
= tp
->GetImageSkiaNamed(IDR_THEME_TOOLBAR
);
495 canvas
->TileImageInt(
497 x
+ GetThemeBackgroundXInset(),
498 bottom_y
- GetTopInset(),
500 w
, theme_toolbar
->height());
502 // The content area line has a shadow that extends a couple of pixels above
503 // the toolbar bounds.
504 const int kContentShadowHeight
= 2;
505 gfx::ImageSkia
* toolbar_top
= tp
->GetImageSkiaNamed(IDR_TOOLBAR_SHADE_TOP
);
506 canvas
->TileImageInt(*toolbar_top
,
508 x
, y
- kContentShadowHeight
,
509 w
, split_point
+ kContentShadowHeight
+ 1);
511 // Draw the "lightening" shade line around the edges of the toolbar.
512 gfx::ImageSkia
* toolbar_left
= tp
->GetImageSkiaNamed(IDR_TOOLBAR_SHADE_LEFT
);
513 canvas
->TileImageInt(*toolbar_left
,
515 x
+ kClientEdgeThickness
,
516 y
+ kClientEdgeThickness
+ kContentShadowHeight
,
517 toolbar_left
->width(), theme_toolbar
->height());
518 gfx::ImageSkia
* toolbar_right
=
519 tp
->GetImageSkiaNamed(IDR_TOOLBAR_SHADE_RIGHT
);
520 canvas
->TileImageInt(*toolbar_right
,
522 w
- toolbar_right
->width() - 2 * kClientEdgeThickness
,
523 y
+ kClientEdgeThickness
+ kContentShadowHeight
,
524 toolbar_right
->width(), theme_toolbar
->height());
526 // Draw the content/toolbar separator.
528 gfx::Rect(x
+ kClientEdgeThickness
,
529 toolbar_bounds
.bottom() - kClientEdgeThickness
,
530 w
- (2 * kClientEdgeThickness
),
531 kClientEdgeThickness
),
532 ThemeProperties::GetDefaultColor(
533 ThemeProperties::COLOR_TOOLBAR_SEPARATOR
));
536 void BrowserNonClientFrameViewAsh::PaintContentEdge(gfx::Canvas
* canvas
) {
537 DCHECK(!UsePackagedAppHeaderStyle());
538 canvas
->FillRect(gfx::Rect(0, caption_button_container_
->bounds().bottom(),
539 width(), kClientEdgeThickness
),
540 ThemeProperties::GetDefaultColor(
541 ThemeProperties::COLOR_TOOLBAR_SEPARATOR
));