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 "chrome/browser/themes/theme_properties.h"
12 #include "chrome/browser/ui/browser.h"
13 #include "chrome/browser/ui/views/avatar_label.h"
14 #include "chrome/browser/ui/views/avatar_menu_button.h"
15 #include "chrome/browser/ui/views/frame/browser_frame.h"
16 #include "chrome/browser/ui/views/frame/browser_view.h"
17 #include "chrome/browser/ui/views/frame/immersive_mode_controller.h"
18 #include "chrome/browser/ui/views/tab_icon_view.h"
19 #include "chrome/browser/ui/views/tabs/tab_strip.h"
20 #include "content/public/browser/web_contents.h"
21 #include "grit/ash_resources.h"
22 #include "grit/theme_resources.h"
23 #include "ui/aura/client/aura_constants.h"
24 #include "ui/aura/window.h"
25 #include "ui/base/accessibility/accessible_view_state.h"
26 #include "ui/base/hit_test.h"
27 #include "ui/base/l10n/l10n_util.h"
28 #include "ui/base/layout.h"
29 #include "ui/base/resource/resource_bundle.h"
30 #include "ui/base/theme_provider.h"
31 #include "ui/compositor/layer_animator.h"
32 #include "ui/gfx/canvas.h"
33 #include "ui/gfx/image/image_skia.h"
34 #include "ui/views/controls/label.h"
35 #include "ui/views/layout/layout_constants.h"
36 #include "ui/views/widget/widget.h"
37 #include "ui/views/widget/widget_delegate.h"
41 // The avatar ends 2 px above the bottom of the tabstrip (which, given the
42 // way the tabstrip draws its bottom edge, will appear like a 1 px gap to the
44 const int kAvatarBottomSpacing = 2;
45 // There are 2 px on each side of the avatar (between the frame border and
46 // it on the left, and between it and the tabstrip on the right).
47 const int kAvatarSideSpacing = 2;
48 // Space between left edge of window and tabstrip.
49 const int kTabstripLeftSpacing = 0;
50 // Space between right edge of tabstrip and maximize button.
51 const int kTabstripRightSpacing = 10;
52 // Height of the shadow of the content area, at the top of the toolbar.
53 const int kContentShadowHeight = 1;
54 // Space between top of window and top of tabstrip for tall headers, such as
55 // for restored windows, apps, etc.
56 const int kTabstripTopSpacingTall = 7;
57 // Space between top of window and top of tabstrip for short headers, such as
58 // for maximized windows, pop-ups, etc.
59 const int kTabstripTopSpacingShort = 0;
60 // Height of the shadow in the tab image, used to ensure clicks in the shadow
61 // area still drag restored windows. This keeps the clickable area large enough
63 const int kTabShadowHeight = 4;
65 // Space between right edge of tabstrip and caption buttons when using the
66 // alternate caption button style.
67 const int kTabstripRightSpacingAlternateCaptionButtonStyle = 0;
68 // Space between top of window and top of tabstrip for short headers when using
69 // the alternate caption button style.
70 const int kTabstripTopSpacingShortAlternateCaptionButtonStyle = 4;
74 ///////////////////////////////////////////////////////////////////////////////
75 // BrowserNonClientFrameViewAsh, public:
78 const char BrowserNonClientFrameViewAsh::kViewClassName[] =
79 "BrowserNonClientFrameViewAsh";
81 BrowserNonClientFrameViewAsh::BrowserNonClientFrameViewAsh(
82 BrowserFrame* frame, BrowserView* browser_view)
83 : BrowserNonClientFrameView(frame, browser_view),
84 caption_button_container_(NULL),
86 header_painter_(new ash::HeaderPainter),
87 frame_border_hit_test_controller_(
88 new ash::FrameBorderHitTestController(frame)) {
91 BrowserNonClientFrameViewAsh::~BrowserNonClientFrameViewAsh() {
94 void BrowserNonClientFrameViewAsh::Init() {
95 caption_button_container_ = new ash::FrameCaptionButtonContainerView(frame(),
96 ash::FrameCaptionButtonContainerView::MINIMIZE_ALLOWED);
97 AddChildView(caption_button_container_);
99 // Initializing the TabIconView is expensive, so only do it if we need to.
100 if (browser_view()->ShouldShowWindowIcon()) {
101 window_icon_ = new TabIconView(this);
102 window_icon_->set_is_light(true);
103 AddChildView(window_icon_);
104 window_icon_->Update();
107 // Create incognito icon if necessary.
110 // Frame painter handles layout.
111 header_painter_->Init(frame(), this, window_icon_, caption_button_container_);
114 ///////////////////////////////////////////////////////////////////////////////
115 // BrowserNonClientFrameView overrides:
117 gfx::Rect BrowserNonClientFrameViewAsh::GetBoundsForTabStrip(
118 views::View* tabstrip) const {
122 // When the tab strip is painted in the immersive fullscreen light bar style,
123 // the caption buttons and the avatar button are not visible. However, their
124 // bounds are still used to compute the tab strip bounds so that the tabs have
125 // the same horizontal position when the tab strip is painted in the immersive
126 // light bar style as when the top-of-window views are revealed.
127 TabStripInsets insets(GetTabStripInsets(false));
128 return gfx::Rect(insets.left, insets.top,
129 std::max(0, width() - insets.left - insets.right),
130 tabstrip->GetPreferredSize().height());
133 BrowserNonClientFrameView::TabStripInsets
134 BrowserNonClientFrameViewAsh::GetTabStripInsets(bool force_restored) const {
135 int left = avatar_button() ? kAvatarSideSpacing +
136 browser_view()->GetOTRAvatarIcon().width() + kAvatarSideSpacing :
137 kTabstripLeftSpacing;
138 int extra_right = ash::switches::UseAlternateFrameCaptionButtonStyle() ?
139 kTabstripRightSpacingAlternateCaptionButtonStyle :
140 kTabstripRightSpacing;
141 int right = header_painter_->GetRightInset() + extra_right;
143 int top = NonClientTopBorderHeight();
145 top = kTabstripTopSpacingTall;
147 if (browser_view()->IsTabStripVisible() &&
148 !UseImmersiveLightbarHeaderStyle() &&
150 if (UseShortHeader()) {
151 if (ash::switches::UseAlternateFrameCaptionButtonStyle())
152 top += kTabstripTopSpacingShortAlternateCaptionButtonStyle;
154 top += kTabstripTopSpacingShort;
156 top += kTabstripTopSpacingTall;
160 return TabStripInsets(top, left, right);
163 int BrowserNonClientFrameViewAsh::GetThemeBackgroundXInset() const {
164 return header_painter_->GetThemeBackgroundXInset();
167 void BrowserNonClientFrameViewAsh::UpdateThrobber(bool running) {
169 window_icon_->Update();
172 ///////////////////////////////////////////////////////////////////////////////
173 // views::NonClientFrameView overrides:
175 gfx::Rect BrowserNonClientFrameViewAsh::GetBoundsForClientView() const {
176 int top_height = NonClientTopBorderHeight();
177 return ash::HeaderPainter::GetBoundsForClientView(top_height, bounds());
180 gfx::Rect BrowserNonClientFrameViewAsh::GetWindowBoundsForClientBounds(
181 const gfx::Rect& client_bounds) const {
182 int top_height = NonClientTopBorderHeight();
183 return ash::HeaderPainter::GetWindowBoundsForClientBounds(top_height,
187 int BrowserNonClientFrameViewAsh::NonClientHitTest(const gfx::Point& point) {
188 int hit_test = ash::FrameBorderHitTestController::NonClientHitTest(this,
189 header_painter_.get(), point);
191 // See if the point is actually within the avatar menu button or within
193 if (hit_test == HTCAPTION && ((avatar_button() &&
194 avatar_button()->GetMirroredBounds().Contains(point)) ||
195 (avatar_label() && avatar_label()->GetMirroredBounds().Contains(point))))
198 // When the window is restored we want a large click target above the tabs
199 // to drag the window, so redirect clicks in the tab's shadow to caption.
200 if (hit_test == HTCLIENT &&
201 !(frame()->IsMaximized() || frame()->IsFullscreen())) {
202 // Convert point to client coordinates.
203 gfx::Point client_point(point);
204 View::ConvertPointToTarget(this, frame()->client_view(), &client_point);
205 // Report hits in shadow at top of tabstrip as caption.
206 gfx::Rect tabstrip_bounds(browser_view()->tabstrip()->bounds());
207 if (client_point.y() < tabstrip_bounds.y() + kTabShadowHeight)
208 hit_test = HTCAPTION;
213 void BrowserNonClientFrameViewAsh::GetWindowMask(const gfx::Size& size,
214 gfx::Path* window_mask) {
215 // Aura does not use window masks.
218 void BrowserNonClientFrameViewAsh::ResetWindowControls() {
219 // Hide the caption buttons in immersive fullscreen when the tab light bar
220 // is visible because it's confusing when the user hovers or clicks in the
221 // top-right of the screen and hits one.
222 bool button_visibility = !UseImmersiveLightbarHeaderStyle();
223 caption_button_container_->SetVisible(button_visibility);
225 caption_button_container_->ResetWindowControls();
228 void BrowserNonClientFrameViewAsh::UpdateWindowIcon() {
230 window_icon_->SchedulePaint();
233 void BrowserNonClientFrameViewAsh::UpdateWindowTitle() {
234 if (!frame()->IsFullscreen())
235 header_painter_->SchedulePaintForTitle(BrowserFrame::GetTitleFont());
238 ///////////////////////////////////////////////////////////////////////////////
239 // views::View overrides:
241 void BrowserNonClientFrameViewAsh::OnPaint(gfx::Canvas* canvas) {
245 if (UseImmersiveLightbarHeaderStyle()) {
246 PaintImmersiveLightbarStyleHeader(canvas);
250 // The primary header image changes based on window activation state and
251 // theme, so we look it up for each paint.
252 int theme_frame_image_id = GetThemeFrameImageId();
253 int theme_frame_overlay_image_id = GetThemeFrameOverlayImageId();
255 ui::ThemeProvider* theme_provider = GetThemeProvider();
256 ash::HeaderPainter::Themed header_themed = ash::HeaderPainter::THEMED_NO;
257 if (theme_provider->HasCustomImage(theme_frame_image_id) ||
258 (theme_frame_overlay_image_id != 0 &&
259 theme_provider->HasCustomImage(theme_frame_overlay_image_id))) {
260 header_themed = ash::HeaderPainter::THEMED_YES;
263 if (header_painter_->ShouldUseMinimalHeaderStyle(header_themed))
264 theme_frame_image_id = IDR_AURA_WINDOW_HEADER_BASE_MINIMAL;
266 header_painter_->PaintHeader(
268 ShouldPaintAsActive() ?
269 ash::HeaderPainter::ACTIVE : ash::HeaderPainter::INACTIVE,
270 theme_frame_image_id,
271 theme_frame_overlay_image_id);
272 if (browser_view()->ShouldShowWindowTitle())
273 header_painter_->PaintTitleBar(canvas, BrowserFrame::GetTitleFont());
274 if (browser_view()->IsToolbarVisible())
275 PaintToolbarBackground(canvas);
277 PaintContentEdge(canvas);
280 void BrowserNonClientFrameViewAsh::Layout() {
281 header_painter_->LayoutHeader(UseShortHeader());
282 int header_height = 0;
283 if (browser_view()->IsTabStripVisible()) {
284 header_height = GetTabStripInsets(false).top +
285 browser_view()->GetTabStripHeight();
287 header_height = NonClientTopBorderHeight();
289 header_painter_->set_header_height(header_height);
292 BrowserNonClientFrameView::Layout();
295 const char* BrowserNonClientFrameViewAsh::GetClassName() const {
296 return kViewClassName;
299 bool BrowserNonClientFrameViewAsh::HitTestRect(const gfx::Rect& rect) const {
300 if (!views::View::HitTestRect(rect)) {
301 // |rect| is outside BrowserNonClientFrameViewAsh's bounds.
304 // If the rect is outside the bounds of the client area, claim it.
305 // TODO(tdanderson): Implement View::ConvertRectToTarget().
306 gfx::Point rect_in_client_view_coords_origin(rect.origin());
307 View::ConvertPointToTarget(this, frame()->client_view(),
308 &rect_in_client_view_coords_origin);
309 gfx::Rect rect_in_client_view_coords(
310 rect_in_client_view_coords_origin, rect.size());
311 if (!frame()->client_view()->HitTestRect(rect_in_client_view_coords))
314 // Otherwise, claim |rect| only if it is above the bottom of the tabstrip in
315 // a non-tab portion.
316 TabStrip* tabstrip = browser_view()->tabstrip();
317 if (!tabstrip || !browser_view()->IsTabStripVisible())
320 gfx::Point rect_in_tabstrip_coords_origin(rect.origin());
321 View::ConvertPointToTarget(this, tabstrip,
322 &rect_in_tabstrip_coords_origin);
323 gfx::Rect rect_in_tabstrip_coords(rect_in_tabstrip_coords_origin,
326 if (rect_in_tabstrip_coords.bottom() > tabstrip->GetLocalBounds().bottom()) {
327 // |rect| is below the tabstrip.
331 if (tabstrip->HitTestRect(rect_in_tabstrip_coords)) {
332 // Claim |rect| if it is in a non-tab portion of the tabstrip.
333 // TODO(tdanderson): Pass |rect_in_tabstrip_coords| instead of its center
334 // point to TabStrip::IsPositionInWindowCaption() once
335 // GetEventHandlerForRect() is implemented.
336 return tabstrip->IsPositionInWindowCaption(
337 rect_in_tabstrip_coords.CenterPoint());
340 // We claim |rect| because it is above the bottom of the tabstrip, but
341 // not in the tabstrip. In particular, the window controls are right of
346 void BrowserNonClientFrameViewAsh::GetAccessibleState(
347 ui::AccessibleViewState* state) {
348 state->role = ui::AccessibilityTypes::ROLE_TITLEBAR;
351 gfx::Size BrowserNonClientFrameViewAsh::GetMinimumSize() {
352 gfx::Size min_client_view_size(frame()->client_view()->GetMinimumSize());
354 std::max(header_painter_->GetMinimumHeaderWidth(),
355 min_client_view_size.width()),
356 NonClientTopBorderHeight() + min_client_view_size.height());
359 void BrowserNonClientFrameViewAsh::OnThemeChanged() {
360 BrowserNonClientFrameView::OnThemeChanged();
361 header_painter_->OnThemeChanged();
364 ///////////////////////////////////////////////////////////////////////////////
365 // chrome::TabIconViewModel overrides:
367 bool BrowserNonClientFrameViewAsh::ShouldTabIconViewAnimate() const {
368 // This function is queried during the creation of the window as the
369 // TabIconView we host is initialized, so we need to NULL check the selected
370 // WebContents because in this condition there is not yet a selected tab.
371 content::WebContents* current_tab = browser_view()->GetActiveWebContents();
372 return current_tab ? current_tab->IsLoading() : false;
375 gfx::ImageSkia BrowserNonClientFrameViewAsh::GetFaviconForTabIconView() {
376 views::WidgetDelegate* delegate = frame()->widget_delegate();
378 return gfx::ImageSkia();
379 return delegate->GetWindowIcon();
382 ///////////////////////////////////////////////////////////////////////////////
383 // BrowserNonClientFrameViewAsh, private:
385 int BrowserNonClientFrameViewAsh::NonClientTopBorderHeight() const {
386 if (!ShouldPaint() || browser_view()->IsTabStripVisible())
389 int caption_buttons_bottom = caption_button_container_->bounds().bottom();
390 if (browser_view()->IsToolbarVisible())
391 return caption_buttons_bottom - kContentShadowHeight;
392 return caption_buttons_bottom + kClientEdgeThickness;
395 bool BrowserNonClientFrameViewAsh::UseShortHeader() const {
396 // Restored browser -> tall header
397 // Maximized browser -> short header
398 // Fullscreen browser, no immersive reveal -> hidden or super short light bar
399 // Fullscreen browser, immersive reveal -> short header
400 // Popup&App window -> tall header
401 // Panels use short header and are handled via ash::PanelFrameView.
402 // Dialogs use short header and are handled via ash::CustomFrameViewAsh.
403 Browser* browser = browser_view()->browser();
404 switch (browser->type()) {
405 case Browser::TYPE_TABBED:
406 return frame()->IsMaximized() || frame()->IsFullscreen();
407 case Browser::TYPE_POPUP:
415 bool BrowserNonClientFrameViewAsh::UseImmersiveLightbarHeaderStyle() const {
416 ImmersiveModeController* immersive_controller =
417 browser_view()->immersive_mode_controller();
418 return immersive_controller->IsEnabled() &&
419 !immersive_controller->IsRevealed() &&
420 browser_view()->IsTabStripVisible();
423 void BrowserNonClientFrameViewAsh::LayoutAvatar() {
424 DCHECK(avatar_button());
425 gfx::ImageSkia incognito_icon = browser_view()->GetOTRAvatarIcon();
427 int avatar_bottom = GetTabStripInsets(false).top +
428 browser_view()->GetTabStripHeight() - kAvatarBottomSpacing;
429 int avatar_restored_y = avatar_bottom - incognito_icon.height();
430 int avatar_y = (frame()->IsMaximized() || frame()->IsFullscreen()) ?
431 GetTabStripInsets(false).top + kContentShadowHeight :
434 // Hide the incognito icon in immersive fullscreen when the tab light bar is
435 // visible because the header is too short for the icognito icon to be
437 bool avatar_visible = !UseImmersiveLightbarHeaderStyle();
438 int avatar_height = avatar_visible ? avatar_bottom - avatar_y : 0;
440 gfx::Rect avatar_bounds(kAvatarSideSpacing,
442 incognito_icon.width(),
444 avatar_button()->SetBoundsRect(avatar_bounds);
445 avatar_button()->SetVisible(avatar_visible);
448 bool BrowserNonClientFrameViewAsh::ShouldPaint() const {
449 if (!frame()->IsFullscreen())
452 // We need to paint when in immersive fullscreen and either:
453 // - The top-of-window views are revealed.
454 // - The lightbar style tabstrip is visible.
455 // Because immersive fullscreen is only supported for tabbed browser windows,
456 // checking whether the tab strip is visible is sufficient.
457 return browser_view()->IsTabStripVisible();
460 void BrowserNonClientFrameViewAsh::PaintImmersiveLightbarStyleHeader(
461 gfx::Canvas* canvas) {
462 // The light bar header is not themed because theming it does not look good.
463 gfx::ImageSkia* frame_image = GetThemeProvider()->GetImageSkiaNamed(
464 IDR_AURA_WINDOW_HEADER_BASE_MINIMAL);
465 canvas->TileImageInt(*frame_image, 0, 0, width(), frame_image->height());
468 void BrowserNonClientFrameViewAsh::PaintToolbarBackground(gfx::Canvas* canvas) {
469 gfx::Rect toolbar_bounds(browser_view()->GetToolbarBounds());
470 if (toolbar_bounds.IsEmpty())
472 gfx::Point toolbar_origin(toolbar_bounds.origin());
473 View::ConvertPointToTarget(browser_view(), this, &toolbar_origin);
474 toolbar_bounds.set_origin(toolbar_origin);
476 int x = toolbar_bounds.x();
477 int w = toolbar_bounds.width();
478 int y = toolbar_bounds.y();
479 int h = toolbar_bounds.height();
481 // Gross hack: We split the toolbar images into two pieces, since sometimes
482 // (popup mode) the toolbar isn't tall enough to show the whole image. The
483 // split happens between the top shadow section and the bottom gradient
484 // section so that we never break the gradient.
485 int split_point = kFrameShadowThickness * 2;
486 int bottom_y = y + split_point;
487 ui::ThemeProvider* tp = GetThemeProvider();
488 int bottom_edge_height = h - split_point;
490 canvas->FillRect(gfx::Rect(x, bottom_y, w, bottom_edge_height),
491 tp->GetColor(ThemeProperties::COLOR_TOOLBAR));
493 // Paint the main toolbar image. Since this image is also used to draw the
494 // tab background, we must use the tab strip offset to compute the image
495 // source y position. If you have to debug this code use an image editor
496 // to paint a diagonal line through the toolbar image and ensure it lines up
497 // across the tab and toolbar.
498 gfx::ImageSkia* theme_toolbar = tp->GetImageSkiaNamed(IDR_THEME_TOOLBAR);
499 canvas->TileImageInt(
501 x + GetThemeBackgroundXInset(),
502 bottom_y - GetTabStripInsets(false).top,
504 w, theme_toolbar->height());
506 // The content area line has a shadow that extends a couple of pixels above
507 // the toolbar bounds.
508 const int kContentShadowHeight = 2;
509 gfx::ImageSkia* toolbar_top = tp->GetImageSkiaNamed(IDR_TOOLBAR_SHADE_TOP);
510 canvas->TileImageInt(*toolbar_top,
512 x, y - kContentShadowHeight,
513 w, split_point + kContentShadowHeight + 1);
515 // Draw the "lightening" shade line around the edges of the toolbar.
516 gfx::ImageSkia* toolbar_left = tp->GetImageSkiaNamed(IDR_TOOLBAR_SHADE_LEFT);
517 canvas->TileImageInt(*toolbar_left,
519 x + kClientEdgeThickness,
520 y + kClientEdgeThickness + kContentShadowHeight,
521 toolbar_left->width(), theme_toolbar->height());
522 gfx::ImageSkia* toolbar_right =
523 tp->GetImageSkiaNamed(IDR_TOOLBAR_SHADE_RIGHT);
524 canvas->TileImageInt(*toolbar_right,
526 w - toolbar_right->width() - 2 * kClientEdgeThickness,
527 y + kClientEdgeThickness + kContentShadowHeight,
528 toolbar_right->width(), theme_toolbar->height());
530 // Draw the content/toolbar separator.
532 gfx::Rect(x + kClientEdgeThickness,
533 toolbar_bounds.bottom() - kClientEdgeThickness,
534 w - (2 * kClientEdgeThickness),
535 kClientEdgeThickness),
536 ThemeProperties::GetDefaultColor(
537 ThemeProperties::COLOR_TOOLBAR_SEPARATOR));
540 void BrowserNonClientFrameViewAsh::PaintContentEdge(gfx::Canvas* canvas) {
541 canvas->FillRect(gfx::Rect(0, caption_button_container_->bounds().bottom(),
542 width(), kClientEdgeThickness),
543 ThemeProperties::GetDefaultColor(
544 ThemeProperties::COLOR_TOOLBAR_SEPARATOR));
547 int BrowserNonClientFrameViewAsh::GetThemeFrameImageId() const {
548 bool is_incognito = !browser_view()->IsRegularOrGuestSession();
549 if (browser_view()->IsBrowserTypeNormal()) {
550 // Use the standard resource ids to allow users to theme the frames.
551 if (ShouldPaintAsActive()) {
552 return is_incognito ?
553 IDR_THEME_FRAME_INCOGNITO : IDR_THEME_FRAME;
555 return is_incognito ?
556 IDR_THEME_FRAME_INCOGNITO_INACTIVE : IDR_THEME_FRAME_INACTIVE;
558 // Never theme app and popup windows.
559 if (ShouldPaintAsActive()) {
560 return is_incognito ?
561 IDR_AURA_WINDOW_HEADER_BASE_INCOGNITO_ACTIVE :
562 IDR_AURA_WINDOW_HEADER_BASE_ACTIVE;
564 return is_incognito ?
565 IDR_AURA_WINDOW_HEADER_BASE_INCOGNITO_INACTIVE :
566 IDR_AURA_WINDOW_HEADER_BASE_INACTIVE;
569 int BrowserNonClientFrameViewAsh::GetThemeFrameOverlayImageId() const {
570 ui::ThemeProvider* tp = GetThemeProvider();
571 if (tp->HasCustomImage(IDR_THEME_FRAME_OVERLAY) &&
572 browser_view()->IsBrowserTypeNormal() &&
573 !browser_view()->IsOffTheRecord()) {
574 return ShouldPaintAsActive() ?
575 IDR_THEME_FRAME_OVERLAY : IDR_THEME_FRAME_OVERLAY_INACTIVE;