1 // Copyright 2014 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_header_painter_ash.h"
7 #include "ash/frame/caption_buttons/frame_caption_button_container_view.h"
8 #include "ash/frame/header_painter_util.h"
9 #include "base/logging.h" // DCHECK
10 #include "chrome/browser/ui/browser.h"
11 #include "chrome/browser/ui/views/frame/browser_frame.h"
12 #include "chrome/browser/ui/views/frame/browser_view.h"
13 #include "grit/theme_resources.h"
14 #include "third_party/skia/include/core/SkCanvas.h"
15 #include "third_party/skia/include/core/SkColor.h"
16 #include "third_party/skia/include/core/SkPaint.h"
17 #include "third_party/skia/include/core/SkPath.h"
18 #include "ui/base/resource/resource_bundle.h"
19 #include "ui/base/theme_provider.h"
20 #include "ui/gfx/animation/slide_animation.h"
21 #include "ui/gfx/canvas.h"
22 #include "ui/gfx/geometry/rect.h"
23 #include "ui/gfx/image/image_skia.h"
24 #include "ui/gfx/skia_util.h"
25 #include "ui/views/view.h"
26 #include "ui/views/widget/widget.h"
27 #include "ui/views/widget/widget_delegate.h"
32 // Color for the window title text.
33 const SkColor kWindowTitleTextColor
= SkColorSetRGB(40, 40, 40);
34 // Duration of crossfade animation for activating and deactivating frame.
35 const int kActivationCrossfadeDurationMs
= 200;
37 // Tiles an image into an area, rounding the top corners. Samples |image|
38 // starting |image_inset_x| pixels from the left of the image.
39 void TileRoundRect(gfx::Canvas
* canvas
,
40 const gfx::ImageSkia
& image
,
42 const gfx::Rect
& bounds
,
43 int top_left_corner_radius
,
44 int top_right_corner_radius
,
46 SkRect rect
= gfx::RectToSkRect(bounds
);
47 const SkScalar kTopLeftRadius
= SkIntToScalar(top_left_corner_radius
);
48 const SkScalar kTopRightRadius
= SkIntToScalar(top_right_corner_radius
);
50 kTopLeftRadius
, kTopLeftRadius
, // top-left
51 kTopRightRadius
, kTopRightRadius
, // top-right
55 path
.addRoundRect(rect
, radii
, SkPath::kCW_Direction
);
56 canvas
->DrawImageInPath(image
, -image_inset_x
, 0, path
, paint
);
59 // Tiles |frame_image| and |frame_overlay_image| into an area, rounding the top
61 void PaintFrameImagesInRoundRect(gfx::Canvas
* canvas
,
62 const gfx::ImageSkia
& frame_image
,
63 const gfx::ImageSkia
& frame_overlay_image
,
65 const gfx::Rect
& bounds
,
68 SkXfermode::Mode normal_mode
;
69 SkXfermode::AsMode(nullptr, &normal_mode
);
71 // If |paint| is using an unusual SkXfermode::Mode (this is the case while
72 // crossfading), we must create a new canvas to overlay |frame_image| and
73 // |frame_overlay_image| using |normal_mode| and then paint the result
74 // using the unusual mode. We try to avoid this because creating a new
75 // browser-width canvas is expensive.
76 bool fast_path
= (frame_overlay_image
.isNull() ||
77 SkXfermode::IsMode(paint
.getXfermode(), normal_mode
));
79 TileRoundRect(canvas
, frame_image
, paint
, bounds
, corner_radius
,
80 corner_radius
, image_inset_x
);
82 if (!frame_overlay_image
.isNull()) {
83 // Adjust |bounds| such that |frame_overlay_image| is not tiled.
84 gfx::Rect overlay_bounds
= bounds
;
85 overlay_bounds
.Intersect(
86 gfx::Rect(bounds
.origin(), frame_overlay_image
.size()));
87 int top_left_corner_radius
= corner_radius
;
88 int top_right_corner_radius
= corner_radius
;
89 if (overlay_bounds
.width() < bounds
.width() - corner_radius
)
90 top_right_corner_radius
= 0;
91 TileRoundRect(canvas
, frame_overlay_image
, paint
, overlay_bounds
,
92 top_left_corner_radius
, top_right_corner_radius
, 0);
95 gfx::Canvas
temporary_canvas(bounds
.size(), canvas
->image_scale(), false);
96 temporary_canvas
.TileImageInt(frame_image
,
99 bounds
.width(), bounds
.height());
100 temporary_canvas
.DrawImageInt(frame_overlay_image
, 0, 0);
101 TileRoundRect(canvas
, gfx::ImageSkia(temporary_canvas
.ExtractImageRep()),
102 paint
, bounds
, corner_radius
, corner_radius
, 0);
108 ///////////////////////////////////////////////////////////////////////////////
109 // BrowserHeaderPainterAsh, public:
111 BrowserHeaderPainterAsh::BrowserHeaderPainterAsh()
114 is_incognito_(false),
116 window_icon_(nullptr),
117 window_icon_x_inset_(ash::HeaderPainterUtil::GetDefaultLeftViewXInset()),
118 caption_button_container_(nullptr),
120 initial_paint_(true),
121 mode_(MODE_INACTIVE
),
122 activation_animation_(new gfx::SlideAnimation(this)) {
125 BrowserHeaderPainterAsh::~BrowserHeaderPainterAsh() {
128 void BrowserHeaderPainterAsh::Init(
129 views::Widget
* frame
,
130 BrowserView
* browser_view
,
131 views::View
* header_view
,
132 views::View
* window_icon
,
133 ash::FrameCaptionButtonContainerView
* caption_button_container
) {
135 DCHECK(browser_view
);
137 // window_icon may be null.
138 DCHECK(caption_button_container
);
141 is_tabbed_
= browser_view
->browser()->is_type_tabbed();
142 is_incognito_
= !browser_view
->IsRegularOrGuestSession();
145 window_icon_
= window_icon
;
146 caption_button_container_
= caption_button_container
;
149 int BrowserHeaderPainterAsh::GetMinimumHeaderWidth() const {
150 // Ensure we have enough space for the window icon and buttons. We allow
151 // the title string to collapse to zero width.
152 return GetTitleBounds().x() +
153 caption_button_container_
->GetMinimumSize().width();
156 void BrowserHeaderPainterAsh::PaintHeader(gfx::Canvas
* canvas
, Mode mode
) {
157 Mode old_mode
= mode_
;
160 if (mode_
!= old_mode
) {
161 if (!initial_paint_
&&
162 ash::HeaderPainterUtil::CanAnimateActivation(frame_
)) {
163 activation_animation_
->SetSlideDuration(kActivationCrossfadeDurationMs
);
164 if (mode_
== MODE_ACTIVE
)
165 activation_animation_
->Show();
167 activation_animation_
->Hide();
169 if (mode_
== MODE_ACTIVE
)
170 activation_animation_
->Reset(1);
172 activation_animation_
->Reset(0);
174 initial_paint_
= false;
177 int corner_radius
= (frame_
->IsMaximized() || frame_
->IsFullscreen()) ?
178 0 : ash::HeaderPainterUtil::GetTopCornerRadiusWhenRestored();
180 int active_alpha
= activation_animation_
->CurrentValueBetween(0, 255);
181 int inactive_alpha
= 255 - active_alpha
;
184 if (inactive_alpha
> 0) {
185 if (active_alpha
> 0)
186 paint
.setXfermodeMode(SkXfermode::kPlus_Mode
);
188 gfx::ImageSkia inactive_frame_image
;
189 gfx::ImageSkia inactive_frame_overlay_image
;
190 GetFrameImages(MODE_INACTIVE
, &inactive_frame_image
,
191 &inactive_frame_overlay_image
);
193 paint
.setAlpha(inactive_alpha
);
194 PaintFrameImagesInRoundRect(
196 inactive_frame_image
,
197 inactive_frame_overlay_image
,
201 ash::HeaderPainterUtil::GetThemeBackgroundXInset());
204 if (active_alpha
> 0) {
205 gfx::ImageSkia active_frame_image
;
206 gfx::ImageSkia active_frame_overlay_image
;
207 GetFrameImages(MODE_ACTIVE
, &active_frame_image
,
208 &active_frame_overlay_image
);
210 paint
.setAlpha(active_alpha
);
211 PaintFrameImagesInRoundRect(
214 active_frame_overlay_image
,
218 ash::HeaderPainterUtil::GetThemeBackgroundXInset());
221 if (!frame_
->IsMaximized() && !frame_
->IsFullscreen())
222 PaintHighlightForRestoredWindow(canvas
);
223 if (frame_
->widget_delegate() &&
224 frame_
->widget_delegate()->ShouldShowWindowTitle()) {
225 PaintTitleBar(canvas
);
229 void BrowserHeaderPainterAsh::LayoutHeader() {
230 // Purposefully set |painted_height_| to an invalid value. We cannot use
231 // |painted_height_| because the computation of |painted_height_| may depend
232 // on having laid out the window controls.
233 painted_height_
= -1;
235 UpdateCaptionButtonImages();
236 caption_button_container_
->Layout();
238 gfx::Size caption_button_container_size
=
239 caption_button_container_
->GetPreferredSize();
240 caption_button_container_
->SetBounds(
241 view_
->width() - caption_button_container_size
.width(),
243 caption_button_container_size
.width(),
244 caption_button_container_size
.height());
247 // Vertically center the window icon with respect to the caption button
249 gfx::Size
icon_size(window_icon_
->GetPreferredSize());
250 int icon_offset_y
= (caption_button_container_
->height() -
251 icon_size
.height()) / 2;
252 window_icon_
->SetBounds(window_icon_x_inset_
,
259 int BrowserHeaderPainterAsh::GetHeaderHeight() const {
260 return caption_button_container_
->height();
263 int BrowserHeaderPainterAsh::GetHeaderHeightForPainting() const {
264 return painted_height_
;
267 void BrowserHeaderPainterAsh::SetHeaderHeightForPainting(int height
) {
268 painted_height_
= height
;
271 void BrowserHeaderPainterAsh::SchedulePaintForTitle() {
272 view_
->SchedulePaintInRect(GetTitleBounds());
275 void BrowserHeaderPainterAsh::UpdateLeftViewXInset(int left_view_x_inset
) {
276 window_icon_x_inset_
= left_view_x_inset
;
279 ///////////////////////////////////////////////////////////////////////////////
280 // gfx::AnimationDelegate overrides:
282 void BrowserHeaderPainterAsh::AnimationProgressed(
283 const gfx::Animation
* animation
) {
284 view_
->SchedulePaintInRect(GetPaintedBounds());
287 ///////////////////////////////////////////////////////////////////////////////
288 // BrowserHeaderPainterAsh, private:
290 void BrowserHeaderPainterAsh::PaintHighlightForRestoredWindow(
291 gfx::Canvas
* canvas
) {
292 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
293 gfx::ImageSkia top_left_corner
= *rb
.GetImageSkiaNamed(
294 IDR_ASH_BROWSER_WINDOW_HEADER_SHADE_TOP_LEFT
);
295 gfx::ImageSkia top_right_corner
= *rb
.GetImageSkiaNamed(
296 IDR_ASH_BROWSER_WINDOW_HEADER_SHADE_TOP_RIGHT
);
297 gfx::ImageSkia top_edge
= *rb
.GetImageSkiaNamed(
298 IDR_ASH_BROWSER_WINDOW_HEADER_SHADE_TOP
);
299 gfx::ImageSkia left_edge
= *rb
.GetImageSkiaNamed(
300 IDR_ASH_BROWSER_WINDOW_HEADER_SHADE_LEFT
);
301 gfx::ImageSkia right_edge
= *rb
.GetImageSkiaNamed(
302 IDR_ASH_BROWSER_WINDOW_HEADER_SHADE_RIGHT
);
304 int top_left_width
= top_left_corner
.width();
305 int top_left_height
= top_left_corner
.height();
306 canvas
->DrawImageInt(top_left_corner
, 0, 0);
308 int top_right_width
= top_right_corner
.width();
309 int top_right_height
= top_right_corner
.height();
310 canvas
->DrawImageInt(top_right_corner
,
311 view_
->width() - top_right_width
,
314 canvas
->TileImageInt(
318 view_
->width() - top_left_width
- top_right_width
,
321 canvas
->TileImageInt(left_edge
,
325 painted_height_
- top_left_height
);
327 canvas
->TileImageInt(right_edge
,
328 view_
->width() - right_edge
.width(),
331 painted_height_
- top_right_height
);
334 void BrowserHeaderPainterAsh::PaintTitleBar(gfx::Canvas
* canvas
) {
335 // The window icon is painted by its own views::View.
336 gfx::Rect title_bounds
= GetTitleBounds();
337 title_bounds
.set_x(view_
->GetMirroredXForRect(title_bounds
));
338 canvas
->DrawStringRectWithFlags(frame_
->widget_delegate()->GetWindowTitle(),
339 BrowserFrame::GetTitleFontList(),
340 kWindowTitleTextColor
,
342 gfx::Canvas::NO_SUBPIXEL_RENDERING
);
345 void BrowserHeaderPainterAsh::GetFrameImages(
347 gfx::ImageSkia
* frame_image
,
348 gfx::ImageSkia
* frame_overlay_image
) const {
350 GetFrameImagesForTabbedBrowser(mode
, frame_image
, frame_overlay_image
);
352 *frame_image
= GetFrameImageForNonTabbedBrowser(mode
);
353 *frame_overlay_image
= gfx::ImageSkia();
357 void BrowserHeaderPainterAsh::GetFrameImagesForTabbedBrowser(
359 gfx::ImageSkia
* frame_image
,
360 gfx::ImageSkia
* frame_overlay_image
) const {
361 int frame_image_id
= 0;
362 int frame_overlay_image_id
= 0;
364 ui::ThemeProvider
* tp
= frame_
->GetThemeProvider();
365 if (tp
->HasCustomImage(IDR_THEME_FRAME_OVERLAY
) && !is_incognito_
) {
366 frame_overlay_image_id
= (mode
== MODE_ACTIVE
) ?
367 IDR_THEME_FRAME_OVERLAY
: IDR_THEME_FRAME_OVERLAY_INACTIVE
;
370 if (mode
== MODE_ACTIVE
) {
371 frame_image_id
= is_incognito_
?
372 IDR_THEME_FRAME_INCOGNITO
: IDR_THEME_FRAME
;
374 frame_image_id
= is_incognito_
?
375 IDR_THEME_FRAME_INCOGNITO_INACTIVE
: IDR_THEME_FRAME_INACTIVE
;
378 *frame_image
= *tp
->GetImageSkiaNamed(frame_image_id
);
379 *frame_overlay_image
= (frame_overlay_image_id
== 0) ?
380 gfx::ImageSkia() : *tp
->GetImageSkiaNamed(frame_overlay_image_id
);
383 gfx::ImageSkia
BrowserHeaderPainterAsh::GetFrameImageForNonTabbedBrowser(
385 // Request the images from the ResourceBundle (and not from the ThemeProvider)
386 // in order to get the default non-themed assets.
387 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
388 if (mode
== MODE_ACTIVE
) {
389 return *rb
.GetImageSkiaNamed(is_incognito_
?
390 IDR_THEME_FRAME_INCOGNITO
: IDR_THEME_FRAME
);
392 return *rb
.GetImageSkiaNamed(is_incognito_
?
393 IDR_THEME_FRAME_INCOGNITO_INACTIVE
: IDR_THEME_FRAME_INACTIVE
);
396 void BrowserHeaderPainterAsh::UpdateCaptionButtonImages() {
397 int hover_background_id
= 0;
398 int pressed_background_id
= 0;
399 if (frame_
->IsMaximized() || frame_
->IsFullscreen()) {
400 hover_background_id
=
401 IDR_ASH_BROWSER_WINDOW_CONTROL_BACKGROUND_MAXIMIZED_H
;
402 pressed_background_id
=
403 IDR_ASH_BROWSER_WINDOW_CONTROL_BACKGROUND_MAXIMIZED_P
;
405 hover_background_id
=
406 IDR_ASH_BROWSER_WINDOW_CONTROL_BACKGROUND_RESTORED_H
;
407 pressed_background_id
=
408 IDR_ASH_BROWSER_WINDOW_CONTROL_BACKGROUND_RESTORED_P
;
410 caption_button_container_
->SetButtonImages(
411 ash::CAPTION_BUTTON_ICON_MINIMIZE
,
412 IDR_ASH_BROWSER_WINDOW_CONTROL_ICON_MINIMIZE
,
414 pressed_background_id
);
416 int size_icon_id
= 0;
417 if (frame_
->IsMaximized() || frame_
->IsFullscreen())
418 size_icon_id
= IDR_ASH_BROWSER_WINDOW_CONTROL_ICON_RESTORE
;
420 size_icon_id
= IDR_ASH_BROWSER_WINDOW_CONTROL_ICON_MAXIMIZE
;
421 caption_button_container_
->SetButtonImages(
422 ash::CAPTION_BUTTON_ICON_MAXIMIZE_RESTORE
,
425 pressed_background_id
);
427 caption_button_container_
->SetButtonImages(
428 ash::CAPTION_BUTTON_ICON_CLOSE
,
429 IDR_ASH_BROWSER_WINDOW_CONTROL_ICON_CLOSE
,
431 pressed_background_id
);
432 caption_button_container_
->SetButtonImages(
433 ash::CAPTION_BUTTON_ICON_LEFT_SNAPPED
,
434 IDR_ASH_BROWSER_WINDOW_CONTROL_ICON_LEFT_SNAPPED
,
436 pressed_background_id
);
437 caption_button_container_
->SetButtonImages(
438 ash::CAPTION_BUTTON_ICON_RIGHT_SNAPPED
,
439 IDR_ASH_BROWSER_WINDOW_CONTROL_ICON_RIGHT_SNAPPED
,
441 pressed_background_id
);
444 gfx::Rect
BrowserHeaderPainterAsh::GetPaintedBounds() const {
445 return gfx::Rect(view_
->width(), painted_height_
);
448 gfx::Rect
BrowserHeaderPainterAsh::GetTitleBounds() const {
449 return ash::HeaderPainterUtil::GetTitleBounds(window_icon_
,
450 caption_button_container_
, BrowserFrame::GetTitleFontList());