[Metrics] Make MetricsStateManager take a callback param to check if UMA is enabled.
[chromium-blink-merge.git] / chrome / browser / ui / views / frame / browser_non_client_frame_view_ash.cc
blob90b375e3feafc72347f6f044f21c124631d4a670
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"
45 namespace {
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
49 // user).
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
68 // to hit easily.
69 const int kTabShadowHeight = 4;
71 } // namespace
73 ///////////////////////////////////////////////////////////////////////////////
74 // BrowserNonClientFrameViewAsh, public:
76 // static
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),
84 window_icon_(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.
110 UpdateAvatarInfo();
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_);
118 } else {
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 {
131 if (!tabstrip)
132 return gfx::Rect();
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,
142 GetTopInset(),
143 std::max(0, width() - left_inset - right_inset),
144 tabstrip->GetPreferredSize().height());
147 int BrowserNonClientFrameViewAsh::GetTopInset() const {
148 if (!ShouldPaint() || UseImmersiveLightbarHeaderStyle())
149 return 0;
151 if (browser_view()->IsTabStripVisible()) {
152 if (frame()->IsMaximized() || frame()->IsFullscreen())
153 return kTabstripTopSpacingShort;
154 else
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) {
175 if (window_icon_)
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.
189 return bounds();
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
202 // the avatar label.
203 if (hit_test == HTCAPTION && ((avatar_button() &&
204 avatar_button()->GetMirroredBounds().Contains(point)) ||
205 (avatar_label() && avatar_label()->GetMirroredBounds().Contains(point))))
206 return HTCLIENT;
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;
220 return hit_test;
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() {
239 if (window_icon_)
240 window_icon_->SchedulePaint();
243 void BrowserNonClientFrameViewAsh::UpdateWindowTitle() {
244 if (!frame()->IsFullscreen())
245 header_painter_->SchedulePaintForTitle();
248 ///////////////////////////////////////////////////////////////////////////////
249 // views::View:
251 void BrowserNonClientFrameViewAsh::OnPaint(gfx::Canvas* canvas) {
252 if (!ShouldPaint())
253 return;
255 if (UseImmersiveLightbarHeaderStyle()) {
256 PaintImmersiveLightbarStyleHeader(canvas);
257 return;
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;
285 } else {
286 painted_height = GetTopInset();
288 header_painter_->SetHeaderHeightForPainting(painted_height);
289 if (avatar_button())
290 LayoutAvatar();
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.
301 return false;
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
307 // portion.
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())
314 return false;
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);
349 InvalidateLayout();
350 frame()->client_view()->InvalidateLayout();
351 frame()->GetRootView()->Layout();
354 void BrowserNonClientFrameViewAsh::OnMaximizeModeEnded() {
355 caption_button_container_->UpdateSizeButtonVisibility(false);
356 InvalidateLayout();
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();
374 if (!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());
415 #endif
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();
421 int avatar_y =
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
428 // recognizable.
429 bool avatar_visible = !UseImmersiveLightbarHeaderStyle();
430 int avatar_height = avatar_visible ? avatar_bottom - avatar_y : 0;
432 gfx::Rect avatar_bounds(kAvatarSideSpacing,
433 avatar_y,
434 incognito_icon.width(),
435 avatar_height);
436 avatar_button()->SetBoundsRect(avatar_bounds);
437 avatar_button()->SetVisible(avatar_visible);
440 bool BrowserNonClientFrameViewAsh::ShouldPaint() const {
441 if (!frame()->IsFullscreen())
442 return true;
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.
457 canvas->FillRect(
458 gfx::Rect(width(), header_painter_->GetHeaderHeightForPainting()),
459 SK_ColorBLACK);
462 void BrowserNonClientFrameViewAsh::PaintToolbarBackground(gfx::Canvas* canvas) {
463 gfx::Rect toolbar_bounds(browser_view()->GetToolbarBounds());
464 if (toolbar_bounds.IsEmpty())
465 return;
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(
496 *theme_toolbar,
497 x + GetThemeBackgroundXInset(),
498 bottom_y - GetTopInset(),
499 x, bottom_y,
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,
507 0, 0,
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,
514 0, 0,
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,
521 0, 0,
522 w - toolbar_right->width() - 2 * kClientEdgeThickness,
523 y + kClientEdgeThickness + kContentShadowHeight,
524 toolbar_right->width(), theme_toolbar->height());
526 // Draw the content/toolbar separator.
527 canvas->FillRect(
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));