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/wm/caption_buttons/frame_caption_button_container_view.h"
9 #include "ash/wm/frame_border_hit_test_controller.h"
10 #include "ash/wm/header_painter.h"
11 #include "base/command_line.h"
12 #include "chrome/browser/themes/theme_properties.h"
13 #include "chrome/browser/ui/browser.h"
14 #include "chrome/browser/ui/views/avatar_label.h"
15 #include "chrome/browser/ui/views/avatar_menu_button.h"
16 #include "chrome/browser/ui/views/frame/browser_frame.h"
17 #include "chrome/browser/ui/views/frame/browser_view.h"
18 #include "chrome/browser/ui/views/frame/immersive_mode_controller.h"
19 #include "chrome/browser/ui/views/tab_icon_view.h"
20 #include "chrome/browser/ui/views/tabs/tab_strip.h"
21 #include "chrome/common/chrome_switches.h"
22 #include "content/public/browser/web_contents.h"
23 #include "grit/ash_resources.h"
24 #include "grit/theme_resources.h"
25 #include "ui/aura/client/aura_constants.h"
26 #include "ui/aura/window.h"
27 #include "ui/base/accessibility/accessible_view_state.h"
28 #include "ui/base/hit_test.h"
29 #include "ui/base/l10n/l10n_util.h"
30 #include "ui/base/layout.h"
31 #include "ui/base/resource/resource_bundle.h"
32 #include "ui/base/theme_provider.h"
33 #include "ui/compositor/layer_animator.h"
34 #include "ui/gfx/canvas.h"
35 #include "ui/gfx/image/image_skia.h"
36 #include "ui/gfx/rect_conversions.h"
37 #include "ui/views/controls/label.h"
38 #include "ui/views/layout/layout_constants.h"
39 #include "ui/views/widget/widget.h"
40 #include "ui/views/widget/widget_delegate.h"
44 // The avatar ends 2 px above the bottom of the tabstrip (which, given the
45 // way the tabstrip draws its bottom edge, will appear like a 1 px gap to the
47 const int kAvatarBottomSpacing = 2;
48 // There are 2 px on each side of the avatar (between the frame border and
49 // it on the left, and between it and the tabstrip on the right).
50 const int kAvatarSideSpacing = 2;
51 // Space between left edge of window and tabstrip.
52 const int kTabstripLeftSpacing = 0;
53 // Space between right edge of tabstrip and maximize button.
54 const int kTabstripRightSpacing = 10;
55 // Height of the shadow of the content area, at the top of the toolbar.
56 const int kContentShadowHeight = 1;
57 // Space between top of window and top of tabstrip for tall headers, such as
58 // for restored windows, apps, etc.
59 const int kTabstripTopSpacingTall = 7;
60 // Space between top of window and top of tabstrip for short headers, such as
61 // for maximized windows, pop-ups, etc.
62 const int kTabstripTopSpacingShort = 0;
63 // Height of the shadow in the tab image, used to ensure clicks in the shadow
64 // area still drag restored windows. This keeps the clickable area large enough
66 const int kTabShadowHeight = 4;
70 ///////////////////////////////////////////////////////////////////////////////
71 // BrowserNonClientFrameViewAsh, public:
74 const char BrowserNonClientFrameViewAsh::kViewClassName[] =
75 "BrowserNonClientFrameViewAsh";
77 BrowserNonClientFrameViewAsh::BrowserNonClientFrameViewAsh(
78 BrowserFrame* frame, BrowserView* browser_view)
79 : BrowserNonClientFrameView(frame, browser_view),
80 caption_button_container_(NULL),
82 header_painter_(new ash::HeaderPainter),
83 frame_border_hit_test_controller_(
84 new ash::FrameBorderHitTestController(frame)) {
87 BrowserNonClientFrameViewAsh::~BrowserNonClientFrameViewAsh() {
90 void BrowserNonClientFrameViewAsh::Init() {
91 caption_button_container_ = new ash::FrameCaptionButtonContainerView(frame(),
92 ash::FrameCaptionButtonContainerView::MINIMIZE_ALLOWED);
93 AddChildView(caption_button_container_);
95 // Initializing the TabIconView is expensive, so only do it if we need to.
96 if (browser_view()->ShouldShowWindowIcon()) {
97 window_icon_ = new TabIconView(this, NULL);
98 window_icon_->set_is_light(true);
99 AddChildView(window_icon_);
100 window_icon_->Update();
103 // Create incognito icon if necessary.
106 // HeaderPainter handles layout.
107 header_painter_->Init(frame(), this, window_icon_, caption_button_container_);
110 ///////////////////////////////////////////////////////////////////////////////
111 // BrowserNonClientFrameView overrides:
113 gfx::Rect BrowserNonClientFrameViewAsh::GetBoundsForTabStrip(
114 views::View* tabstrip) const {
118 // When the tab strip is painted in the immersive fullscreen light bar style,
119 // the caption buttons and the avatar button are not visible. However, their
120 // bounds are still used to compute the tab strip bounds so that the tabs have
121 // the same horizontal position when the tab strip is painted in the immersive
122 // light bar style as when the top-of-window views are revealed.
123 int left_inset = GetTabStripLeftInset();
124 int right_inset = GetTabStripRightInset();
125 return gfx::Rect(left_inset,
127 std::max(0, width() - left_inset - right_inset),
128 tabstrip->GetPreferredSize().height());
131 int BrowserNonClientFrameViewAsh::GetTopInset() const {
132 if (!ShouldPaint() || UseImmersiveLightbarHeaderStyle())
135 if (browser_view()->IsTabStripVisible()) {
136 if (frame()->IsMaximized() || frame()->IsFullscreen())
137 return kTabstripTopSpacingShort;
139 return kTabstripTopSpacingTall;
142 int caption_buttons_bottom = caption_button_container_->bounds().bottom();
144 // The toolbar partially overlaps the caption buttons.
145 if (browser_view()->IsToolbarVisible())
146 return caption_buttons_bottom - kContentShadowHeight;
148 int separator_thickness = UsePackagedAppHeaderStyle() ?
149 header_painter_->HeaderContentSeparatorSize() : kClientEdgeThickness;
150 return caption_buttons_bottom + separator_thickness;
153 int BrowserNonClientFrameViewAsh::GetThemeBackgroundXInset() const {
154 return header_painter_->GetThemeBackgroundXInset();
157 void BrowserNonClientFrameViewAsh::UpdateThrobber(bool running) {
159 window_icon_->Update();
162 ///////////////////////////////////////////////////////////////////////////////
163 // views::NonClientFrameView overrides:
165 gfx::Rect BrowserNonClientFrameViewAsh::GetBoundsForClientView() const {
166 // The ClientView must be flush with the top edge of the widget so that the
167 // web contents can take up the entire screen in immersive fullscreen (with
168 // or without the top-of-window views revealed). When in immersive fullscreen
169 // and the top-of-window views are revealed, the TopContainerView paints the
170 // window header by redirecting paints from its background to
171 // BrowserNonClientFrameViewAsh.
172 return ash::HeaderPainter::GetBoundsForClientView(0, bounds());
175 gfx::Rect BrowserNonClientFrameViewAsh::GetWindowBoundsForClientBounds(
176 const gfx::Rect& client_bounds) const {
177 return ash::HeaderPainter::GetWindowBoundsForClientBounds(0, client_bounds);
180 int BrowserNonClientFrameViewAsh::NonClientHitTest(const gfx::Point& point) {
181 int hit_test = ash::FrameBorderHitTestController::NonClientHitTest(this,
182 header_painter_.get(), point);
184 // See if the point is actually within the avatar menu button or within
186 if (hit_test == HTCAPTION && ((avatar_button() &&
187 avatar_button()->GetMirroredBounds().Contains(point)) ||
188 (avatar_label() && avatar_label()->GetMirroredBounds().Contains(point))))
191 // When the window is restored we want a large click target above the tabs
192 // to drag the window, so redirect clicks in the tab's shadow to caption.
193 if (hit_test == HTCLIENT &&
194 !(frame()->IsMaximized() || frame()->IsFullscreen())) {
195 // Convert point to client coordinates.
196 gfx::Point client_point(point);
197 View::ConvertPointToTarget(this, frame()->client_view(), &client_point);
198 // Report hits in shadow at top of tabstrip as caption.
199 gfx::Rect tabstrip_bounds(browser_view()->tabstrip()->bounds());
200 if (client_point.y() < tabstrip_bounds.y() + kTabShadowHeight)
201 hit_test = HTCAPTION;
206 void BrowserNonClientFrameViewAsh::GetWindowMask(const gfx::Size& size,
207 gfx::Path* window_mask) {
208 // Aura does not use window masks.
211 void BrowserNonClientFrameViewAsh::ResetWindowControls() {
212 // Hide the caption buttons in immersive fullscreen when the tab light bar
213 // is visible because it's confusing when the user hovers or clicks in the
214 // top-right of the screen and hits one.
215 bool button_visibility = !UseImmersiveLightbarHeaderStyle();
216 caption_button_container_->SetVisible(button_visibility);
218 caption_button_container_->ResetWindowControls();
221 void BrowserNonClientFrameViewAsh::UpdateWindowIcon() {
223 window_icon_->SchedulePaint();
226 void BrowserNonClientFrameViewAsh::UpdateWindowTitle() {
227 if (!frame()->IsFullscreen())
228 header_painter_->SchedulePaintForTitle(BrowserFrame::GetTitleFontList());
231 ///////////////////////////////////////////////////////////////////////////////
232 // views::View overrides:
234 void BrowserNonClientFrameViewAsh::OnPaint(gfx::Canvas* canvas) {
238 if (UseImmersiveLightbarHeaderStyle()) {
239 PaintImmersiveLightbarStyleHeader(canvas);
243 // The primary header image changes based on window activation state and
244 // theme, so we look it up for each paint.
245 int theme_frame_image_id = GetThemeFrameImageId();
246 int theme_frame_overlay_image_id = GetThemeFrameOverlayImageId();
248 ui::ThemeProvider* theme_provider = GetThemeProvider();
249 if (!theme_provider->HasCustomImage(theme_frame_image_id) &&
250 (theme_frame_overlay_image_id == 0 ||
251 !theme_provider->HasCustomImage(theme_frame_overlay_image_id))) {
252 if (frame()->IsMaximized() || frame()->IsFullscreen())
253 theme_frame_image_id = IDR_AURA_WINDOW_HEADER_BASE_MINIMAL;
255 header_painter_->PaintHeader(
257 theme_frame_image_id,
258 theme_frame_overlay_image_id);
259 if (browser_view()->ShouldShowWindowTitle())
260 header_painter_->PaintTitleBar(canvas, BrowserFrame::GetTitleFontList());
261 if (browser_view()->IsToolbarVisible())
262 PaintToolbarBackground(canvas);
264 PaintContentEdge(canvas);
267 void BrowserNonClientFrameViewAsh::Layout() {
268 // The header must be laid out before computing |header_height| because the
269 // computation of |header_height| for app and popup windows depends on the
270 // position of the window controls.
271 header_painter_->LayoutHeader(UsePackagedAppHeaderStyle() ||
272 frame()->IsMaximized() ||
273 frame()->IsFullscreen());
275 int header_height = 0;
276 if (browser_view()->IsTabStripVisible()) {
277 header_height = GetTopInset() +
278 browser_view()->tabstrip()->GetPreferredSize().height();
279 } else if (browser_view()->IsToolbarVisible()) {
280 // Set the header's height so that it overlaps with the toolbar because the
281 // top few pixels of the toolbar are not opaque.
282 header_height = GetTopInset() + kFrameShadowThickness * 2;
284 header_height = GetTopInset();
286 header_painter_->set_header_height(header_height);
289 BrowserNonClientFrameView::Layout();
292 const char* BrowserNonClientFrameViewAsh::GetClassName() const {
293 return kViewClassName;
296 bool BrowserNonClientFrameViewAsh::HitTestRect(const gfx::Rect& rect) const {
297 if (!views::View::HitTestRect(rect)) {
298 // |rect| is outside BrowserNonClientFrameViewAsh's bounds.
302 TabStrip* tabstrip = browser_view()->tabstrip();
303 if (tabstrip && browser_view()->IsTabStripVisible()) {
304 // Claim |rect| only if it is above the bottom of the tabstrip in a non-tab
306 gfx::RectF rect_in_tabstrip_coords_f(rect);
307 View::ConvertRectToTarget(this, tabstrip, &rect_in_tabstrip_coords_f);
308 gfx::Rect rect_in_tabstrip_coords = gfx::ToEnclosingRect(
309 rect_in_tabstrip_coords_f);
311 if (rect_in_tabstrip_coords.y() > tabstrip->height())
314 return !tabstrip->HitTestRect(rect_in_tabstrip_coords) ||
315 tabstrip->IsRectInWindowCaption(rect_in_tabstrip_coords);
318 // Claim |rect| if it is above the top of the topmost view in the client area.
319 return rect.y() < GetTopInset();
322 void BrowserNonClientFrameViewAsh::GetAccessibleState(
323 ui::AccessibleViewState* state) {
324 state->role = ui::AccessibilityTypes::ROLE_TITLEBAR;
327 gfx::Size BrowserNonClientFrameViewAsh::GetMinimumSize() {
328 gfx::Size min_client_view_size(frame()->client_view()->GetMinimumSize());
329 int min_width = std::max(header_painter_->GetMinimumHeaderWidth(),
330 min_client_view_size.width());
331 if (browser_view()->IsTabStripVisible()) {
332 // Ensure that the minimum width is enough to hold a minimum width tab strip
333 // at its usual insets.
334 int min_tabstrip_width =
335 browser_view()->tabstrip()->GetMinimumSize().width();
336 min_width = std::max(min_width,
337 min_tabstrip_width + GetTabStripLeftInset() + GetTabStripRightInset());
339 return gfx::Size(min_width, min_client_view_size.height());
342 void BrowserNonClientFrameViewAsh::OnThemeChanged() {
343 BrowserNonClientFrameView::OnThemeChanged();
344 header_painter_->OnThemeChanged();
347 ///////////////////////////////////////////////////////////////////////////////
348 // chrome::TabIconViewModel overrides:
350 bool BrowserNonClientFrameViewAsh::ShouldTabIconViewAnimate() const {
351 // This function is queried during the creation of the window as the
352 // TabIconView we host is initialized, so we need to NULL check the selected
353 // WebContents because in this condition there is not yet a selected tab.
354 content::WebContents* current_tab = browser_view()->GetActiveWebContents();
355 return current_tab ? current_tab->IsLoading() : false;
358 gfx::ImageSkia BrowserNonClientFrameViewAsh::GetFaviconForTabIconView() {
359 views::WidgetDelegate* delegate = frame()->widget_delegate();
361 return gfx::ImageSkia();
362 return delegate->GetWindowIcon();
365 ///////////////////////////////////////////////////////////////////////////////
366 // BrowserNonClientFrameViewAsh, private:
368 int BrowserNonClientFrameViewAsh::GetTabStripLeftInset() const {
369 return avatar_button() ? kAvatarSideSpacing +
370 browser_view()->GetOTRAvatarIcon().width() + kAvatarSideSpacing :
371 kTabstripLeftSpacing;
374 int BrowserNonClientFrameViewAsh::GetTabStripRightInset() const {
375 return header_painter_->GetRightInset() + kTabstripRightSpacing;
378 bool BrowserNonClientFrameViewAsh::UseImmersiveLightbarHeaderStyle() const {
379 ImmersiveModeController* immersive_controller =
380 browser_view()->immersive_mode_controller();
381 return immersive_controller->IsEnabled() &&
382 !immersive_controller->IsRevealed() &&
383 browser_view()->IsTabStripVisible();
386 bool BrowserNonClientFrameViewAsh::UsePackagedAppHeaderStyle() const {
387 // Non streamlined hosted apps do not have a toolbar or tabstrip. Their header
388 // should look the same as the header for packaged apps. Streamlined hosted
389 // apps have a toolbar so should use the browser header style.
390 return browser_view()->browser()->is_app() &&
391 !CommandLine::ForCurrentProcess()->HasSwitch(
392 switches::kEnableStreamlinedHostedApps);
395 void BrowserNonClientFrameViewAsh::LayoutAvatar() {
396 DCHECK(avatar_button());
397 DCHECK(browser_view()->IsTabStripVisible());
398 gfx::ImageSkia incognito_icon = browser_view()->GetOTRAvatarIcon();
400 int avatar_bottom = GetTopInset() +
401 browser_view()->GetTabStripHeight() - kAvatarBottomSpacing;
402 int avatar_restored_y = avatar_bottom - incognito_icon.height();
403 int avatar_y = (frame()->IsMaximized() || frame()->IsFullscreen()) ?
404 GetTopInset() + kContentShadowHeight : avatar_restored_y;
406 // Hide the incognito icon in immersive fullscreen when the tab light bar is
407 // visible because the header is too short for the icognito icon to be
409 bool avatar_visible = !UseImmersiveLightbarHeaderStyle();
410 int avatar_height = avatar_visible ? avatar_bottom - avatar_y : 0;
412 gfx::Rect avatar_bounds(kAvatarSideSpacing,
414 incognito_icon.width(),
416 avatar_button()->SetBoundsRect(avatar_bounds);
417 avatar_button()->SetVisible(avatar_visible);
420 bool BrowserNonClientFrameViewAsh::ShouldPaint() const {
421 if (!frame()->IsFullscreen())
424 // We need to paint when in immersive fullscreen and either:
425 // - The top-of-window views are revealed.
426 // - The lightbar style tabstrip is visible.
427 ImmersiveModeController* immersive_mode_controller =
428 browser_view()->immersive_mode_controller();
429 return immersive_mode_controller->IsEnabled() &&
430 (immersive_mode_controller->IsRevealed() ||
431 UseImmersiveLightbarHeaderStyle());
434 void BrowserNonClientFrameViewAsh::PaintImmersiveLightbarStyleHeader(
435 gfx::Canvas* canvas) {
436 // The light bar header is not themed because theming it does not look good.
437 gfx::ImageSkia* frame_image = GetThemeProvider()->GetImageSkiaNamed(
438 IDR_AURA_WINDOW_HEADER_BASE_MINIMAL);
439 canvas->TileImageInt(*frame_image, 0, 0, width(), frame_image->height());
442 void BrowserNonClientFrameViewAsh::PaintToolbarBackground(gfx::Canvas* canvas) {
443 gfx::Rect toolbar_bounds(browser_view()->GetToolbarBounds());
444 if (toolbar_bounds.IsEmpty())
446 gfx::Point toolbar_origin(toolbar_bounds.origin());
447 View::ConvertPointToTarget(browser_view(), this, &toolbar_origin);
448 toolbar_bounds.set_origin(toolbar_origin);
450 int x = toolbar_bounds.x();
451 int w = toolbar_bounds.width();
452 int y = toolbar_bounds.y();
453 int h = toolbar_bounds.height();
455 // Gross hack: We split the toolbar images into two pieces, since sometimes
456 // (popup mode) the toolbar isn't tall enough to show the whole image. The
457 // split happens between the top shadow section and the bottom gradient
458 // section so that we never break the gradient.
459 // NOTE(pkotwicz): If the computation for |bottom_y| is changed, Layout() must
460 // be changed as well.
461 int split_point = kFrameShadowThickness * 2;
462 int bottom_y = y + split_point;
463 ui::ThemeProvider* tp = GetThemeProvider();
464 int bottom_edge_height = h - split_point;
466 canvas->FillRect(gfx::Rect(x, bottom_y, w, bottom_edge_height),
467 tp->GetColor(ThemeProperties::COLOR_TOOLBAR));
469 // Paint the main toolbar image. Since this image is also used to draw the
470 // tab background, we must use the tab strip offset to compute the image
471 // source y position. If you have to debug this code use an image editor
472 // to paint a diagonal line through the toolbar image and ensure it lines up
473 // across the tab and toolbar.
474 gfx::ImageSkia* theme_toolbar = tp->GetImageSkiaNamed(IDR_THEME_TOOLBAR);
475 canvas->TileImageInt(
477 x + GetThemeBackgroundXInset(),
478 bottom_y - GetTopInset(),
480 w, theme_toolbar->height());
482 // The content area line has a shadow that extends a couple of pixels above
483 // the toolbar bounds.
484 const int kContentShadowHeight = 2;
485 gfx::ImageSkia* toolbar_top = tp->GetImageSkiaNamed(IDR_TOOLBAR_SHADE_TOP);
486 canvas->TileImageInt(*toolbar_top,
488 x, y - kContentShadowHeight,
489 w, split_point + kContentShadowHeight + 1);
491 // Draw the "lightening" shade line around the edges of the toolbar.
492 gfx::ImageSkia* toolbar_left = tp->GetImageSkiaNamed(IDR_TOOLBAR_SHADE_LEFT);
493 canvas->TileImageInt(*toolbar_left,
495 x + kClientEdgeThickness,
496 y + kClientEdgeThickness + kContentShadowHeight,
497 toolbar_left->width(), theme_toolbar->height());
498 gfx::ImageSkia* toolbar_right =
499 tp->GetImageSkiaNamed(IDR_TOOLBAR_SHADE_RIGHT);
500 canvas->TileImageInt(*toolbar_right,
502 w - toolbar_right->width() - 2 * kClientEdgeThickness,
503 y + kClientEdgeThickness + kContentShadowHeight,
504 toolbar_right->width(), theme_toolbar->height());
506 // Draw the content/toolbar separator.
508 gfx::Rect(x + kClientEdgeThickness,
509 toolbar_bounds.bottom() - kClientEdgeThickness,
510 w - (2 * kClientEdgeThickness),
511 kClientEdgeThickness),
512 ThemeProperties::GetDefaultColor(
513 ThemeProperties::COLOR_TOOLBAR_SEPARATOR));
516 void BrowserNonClientFrameViewAsh::PaintContentEdge(gfx::Canvas* canvas) {
517 if (UsePackagedAppHeaderStyle()) {
518 header_painter_->PaintHeaderContentSeparator(canvas);
520 canvas->FillRect(gfx::Rect(0, caption_button_container_->bounds().bottom(),
521 width(), kClientEdgeThickness),
522 ThemeProperties::GetDefaultColor(
523 ThemeProperties::COLOR_TOOLBAR_SEPARATOR));
527 int BrowserNonClientFrameViewAsh::GetThemeFrameImageId() const {
528 bool is_incognito = !browser_view()->IsRegularOrGuestSession();
529 if (browser_view()->IsBrowserTypeNormal()) {
530 // Use the standard resource ids to allow users to theme the frames.
531 if (ShouldPaintAsActive()) {
532 return is_incognito ?
533 IDR_THEME_FRAME_INCOGNITO : IDR_THEME_FRAME;
535 return is_incognito ?
536 IDR_THEME_FRAME_INCOGNITO_INACTIVE : IDR_THEME_FRAME_INACTIVE;
538 // Never theme app and popup windows.
539 if (ShouldPaintAsActive()) {
540 return is_incognito ?
541 IDR_AURA_WINDOW_HEADER_BASE_INCOGNITO_ACTIVE :
542 IDR_AURA_WINDOW_HEADER_BASE_ACTIVE;
544 return is_incognito ?
545 IDR_AURA_WINDOW_HEADER_BASE_INCOGNITO_INACTIVE :
546 IDR_AURA_WINDOW_HEADER_BASE_INACTIVE;
549 int BrowserNonClientFrameViewAsh::GetThemeFrameOverlayImageId() const {
550 ui::ThemeProvider* tp = GetThemeProvider();
551 if (tp->HasCustomImage(IDR_THEME_FRAME_OVERLAY) &&
552 browser_view()->IsBrowserTypeNormal() &&
553 !browser_view()->IsOffTheRecord()) {
554 return ShouldPaintAsActive() ?
555 IDR_THEME_FRAME_OVERLAY : IDR_THEME_FRAME_OVERLAY_INACTIVE;