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.
4
5 #include "ash/frame/default_header_painter.h"
6
7 #include "ash/frame/caption_buttons/frame_caption_button_container_view.h"
8 #include "ash/frame/header_painter_util.h"
9 #include "base/debug/leak_annotations.h"
10 #include "base/logging.h" // DCHECK
11 #include "grit/ash_resources.h"
12 #include "third_party/skia/include/core/SkColor.h"
13 #include "third_party/skia/include/core/SkPaint.h"
14 #include "third_party/skia/include/core/SkPath.h"
15 #include "ui/base/resource/resource_bundle.h"
16 #include "ui/gfx/animation/slide_animation.h"
17 #include "ui/gfx/canvas.h"
18 #include "ui/gfx/font_list.h"
19 #include "ui/gfx/image/image.h"
20 #include "ui/gfx/rect.h"
21 #include "ui/gfx/skia_util.h"
22 #include "ui/views/view.h"
23 #include "ui/views/widget/native_widget_aura.h"
24 #include "ui/views/widget/widget.h"
25 #include "ui/views/widget/widget_delegate.h"
26
27 using views::Widget;
28
29 namespace {
30
31 // Color for the window title text.
32 const SkColor kTitleTextColor = SkColorSetRGB(40, 40, 40);
33 // Color of the active window header/content separator line.
34 const SkColor kHeaderContentSeparatorColor = SkColorSetRGB(150, 150, 152);
35 // Color of the inactive window header/content separator line.
36 const SkColor kHeaderContentSeparatorInactiveColor =
37 SkColorSetRGB(180, 180, 182);
38 // Duration of crossfade animation for activating and deactivating frame.
39 const int kActivationCrossfadeDurationMs = 200;
40
41 // Tiles an image into an area, rounding the top corners.
TileRoundRect(gfx::Canvas * canvas,const gfx::ImageSkia & image,const SkPaint & paint,const gfx::Rect & bounds,int corner_radius)42 void TileRoundRect(gfx::Canvas* canvas,
43 const gfx::ImageSkia& image,
44 const SkPaint& paint,
45 const gfx::Rect& bounds,
46 int corner_radius) {
47 SkRect rect = gfx::RectToSkRect(bounds);
48 const SkScalar corner_radius_scalar = SkIntToScalar(corner_radius);
49 SkScalar radii[8] = {
50 corner_radius_scalar, corner_radius_scalar, // top-left
51 corner_radius_scalar, corner_radius_scalar, // top-right
52 0, 0, // bottom-right
53 0, 0}; // bottom-left
54 SkPath path;
55 path.addRoundRect(rect, radii, SkPath::kCW_Direction);
56 canvas->DrawImageInPath(image, 0, 0, path, paint);
57 }
58
59 // Returns the FontList to use for the title.
GetTitleFontList()60 const gfx::FontList& GetTitleFontList() {
61 static const gfx::FontList* title_font_list =
62 new gfx::FontList(views::NativeWidgetAura::GetWindowTitleFontList());
63 ANNOTATE_LEAKING_OBJECT_PTR(title_font_list);
64 return *title_font_list;
65 }
66
67 } // namespace
68
69 namespace ash {
70
71 ///////////////////////////////////////////////////////////////////////////////
72 // DefaultHeaderPainter, public:
73
DefaultHeaderPainter()74 DefaultHeaderPainter::DefaultHeaderPainter()
75 : frame_(NULL),
76 view_(NULL),
77 window_icon_(NULL),
78 window_icon_size_(HeaderPainterUtil::GetDefaultIconSize()),
79 caption_button_container_(NULL),
80 height_(0),
81 mode_(MODE_INACTIVE),
82 initial_paint_(true),
83 activation_animation_(new gfx::SlideAnimation(this)) {
84 }
85
~DefaultHeaderPainter()86 DefaultHeaderPainter::~DefaultHeaderPainter() {
87 }
88
Init(views::Widget * frame,views::View * header_view,views::View * window_icon,FrameCaptionButtonContainerView * caption_button_container)89 void DefaultHeaderPainter::Init(
90 views::Widget* frame,
91 views::View* header_view,
92 views::View* window_icon,
93 FrameCaptionButtonContainerView* caption_button_container) {
94 DCHECK(frame);
95 DCHECK(header_view);
96 // window_icon may be NULL.
97 DCHECK(caption_button_container);
98 frame_ = frame;
99 view_ = header_view;
100 window_icon_ = window_icon;
101 caption_button_container_ = caption_button_container;
102
103 caption_button_container_->SetButtonImages(
104 CAPTION_BUTTON_ICON_MINIMIZE,
105 IDR_AURA_WINDOW_CONTROL_ICON_MINIMIZE,
106 IDR_AURA_WINDOW_CONTROL_ICON_MINIMIZE_I,
107 IDR_AURA_WINDOW_CONTROL_BACKGROUND_H,
108 IDR_AURA_WINDOW_CONTROL_BACKGROUND_P);
109 caption_button_container_->SetButtonImages(
110 CAPTION_BUTTON_ICON_MAXIMIZE_RESTORE,
111 IDR_AURA_WINDOW_CONTROL_ICON_SIZE,
112 IDR_AURA_WINDOW_CONTROL_ICON_SIZE_I,
113 IDR_AURA_WINDOW_CONTROL_BACKGROUND_H,
114 IDR_AURA_WINDOW_CONTROL_BACKGROUND_P);
115 caption_button_container_->SetButtonImages(
116 CAPTION_BUTTON_ICON_CLOSE,
117 IDR_AURA_WINDOW_CONTROL_ICON_CLOSE,
118 IDR_AURA_WINDOW_CONTROL_ICON_CLOSE_I,
119 IDR_AURA_WINDOW_CONTROL_BACKGROUND_H,
120 IDR_AURA_WINDOW_CONTROL_BACKGROUND_P);
121
122 // There is no dedicated icon for the snap-left and snap-right buttons
123 // when |frame_| is inactive because they should never be visible while
124 // |frame_| is inactive.
125 caption_button_container_->SetButtonImages(
126 CAPTION_BUTTON_ICON_LEFT_SNAPPED,
127 IDR_AURA_WINDOW_CONTROL_ICON_LEFT_SNAPPED,
128 IDR_AURA_WINDOW_CONTROL_ICON_LEFT_SNAPPED,
129 IDR_AURA_WINDOW_CONTROL_BACKGROUND_H,
130 IDR_AURA_WINDOW_CONTROL_BACKGROUND_P);
131 caption_button_container_->SetButtonImages(
132 CAPTION_BUTTON_ICON_RIGHT_SNAPPED,
133 IDR_AURA_WINDOW_CONTROL_ICON_RIGHT_SNAPPED,
134 IDR_AURA_WINDOW_CONTROL_ICON_RIGHT_SNAPPED,
135 IDR_AURA_WINDOW_CONTROL_BACKGROUND_H,
136 IDR_AURA_WINDOW_CONTROL_BACKGROUND_P);
137 }
138
GetMinimumHeaderWidth() const139 int DefaultHeaderPainter::GetMinimumHeaderWidth() const {
140 // Ensure we have enough space for the window icon and buttons. We allow
141 // the title string to collapse to zero width.
142 return GetTitleBounds().x() +
143 caption_button_container_->GetMinimumSize().width();
144 }
145
PaintHeader(gfx::Canvas * canvas,Mode mode)146 void DefaultHeaderPainter::PaintHeader(gfx::Canvas* canvas, Mode mode) {
147 Mode old_mode = mode_;
148 mode_ = mode;
149
150 if (mode_ != old_mode) {
151 if (!initial_paint_ && HeaderPainterUtil::CanAnimateActivation(frame_)) {
152 activation_animation_->SetSlideDuration(kActivationCrossfadeDurationMs);
153 if (mode_ == MODE_ACTIVE)
154 activation_animation_->Show();
155 else
156 activation_animation_->Hide();
157 } else {
158 if (mode_ == MODE_ACTIVE)
159 activation_animation_->Reset(1);
160 else
161 activation_animation_->Reset(0);
162 }
163 initial_paint_ = false;
164 }
165
166 int corner_radius = (frame_->IsMaximized() || frame_->IsFullscreen()) ?
167 0 : HeaderPainterUtil::GetTopCornerRadiusWhenRestored();
168
169 int active_alpha = activation_animation_->CurrentValueBetween(0, 255);
170 int inactive_alpha = 255 - active_alpha;
171
172 SkPaint paint;
173 if (inactive_alpha > 0) {
174 if (active_alpha > 0)
175 paint.setXfermodeMode(SkXfermode::kPlus_Mode);
176
177 paint.setAlpha(inactive_alpha);
178 gfx::ImageSkia inactive_frame = *GetInactiveFrameImage();
179 TileRoundRect(canvas, inactive_frame, paint, GetLocalBounds(),
180 corner_radius);
181 }
182
183 if (active_alpha > 0) {
184 paint.setAlpha(active_alpha);
185 gfx::ImageSkia active_frame = *GetActiveFrameImage();
186 TileRoundRect(canvas, active_frame, paint, GetLocalBounds(),
187 corner_radius);
188 }
189
190 if (!frame_->IsMaximized() &&
191 !frame_->IsFullscreen() &&
192 mode_ == MODE_INACTIVE) {
193 PaintHighlightForInactiveRestoredWindow(canvas);
194 }
195 if (frame_->widget_delegate() &&
196 frame_->widget_delegate()->ShouldShowWindowTitle()) {
197 PaintTitleBar(canvas);
198 }
199 PaintHeaderContentSeparator(canvas);
200 }
201
LayoutHeader()202 void DefaultHeaderPainter::LayoutHeader() {
203 caption_button_container_->Layout();
204
205 gfx::Size caption_button_container_size =
206 caption_button_container_->GetPreferredSize();
207 caption_button_container_->SetBounds(
208 view_->width() - caption_button_container_size.width(),
209 0,
210 caption_button_container_size.width(),
211 caption_button_container_size.height());
212
213 if (window_icon_) {
214 // Vertically center the window icon with respect to the caption button
215 // container.
216 // Floor when computing the center of |caption_button_container_|.
217 int icon_offset_y =
218 caption_button_container_->height() / 2 - window_icon_size_ / 2;
219 window_icon_->SetBounds(HeaderPainterUtil::GetIconXOffset(), icon_offset_y,
220 window_icon_size_, window_icon_size_);
221 }
222
223 // The header/content separator line overlays the caption buttons.
224 SetHeaderHeightForPainting(caption_button_container_->height());
225 }
226
GetHeaderHeightForPainting() const227 int DefaultHeaderPainter::GetHeaderHeightForPainting() const {
228 return height_;
229 }
230
SetHeaderHeightForPainting(int height)231 void DefaultHeaderPainter::SetHeaderHeightForPainting(int height) {
232 height_ = height;
233 }
234
SchedulePaintForTitle()235 void DefaultHeaderPainter::SchedulePaintForTitle() {
236 view_->SchedulePaintInRect(GetTitleBounds());
237 }
238
UpdateWindowIcon(views::View * window_icon,int window_icon_size)239 void DefaultHeaderPainter::UpdateWindowIcon(views::View* window_icon,
240 int window_icon_size) {
241 window_icon_ = window_icon;
242 window_icon_size_ = window_icon_size;
243 }
244
245 ///////////////////////////////////////////////////////////////////////////////
246 // gfx::AnimationDelegate overrides:
247
AnimationProgressed(const gfx::Animation * animation)248 void DefaultHeaderPainter::AnimationProgressed(
249 const gfx::Animation* animation) {
250 view_->SchedulePaintInRect(GetLocalBounds());
251 }
252
253 ///////////////////////////////////////////////////////////////////////////////
254 // DefaultHeaderPainter, private:
255
PaintHighlightForInactiveRestoredWindow(gfx::Canvas * canvas)256 void DefaultHeaderPainter::PaintHighlightForInactiveRestoredWindow(
257 gfx::Canvas* canvas) {
258 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
259 gfx::ImageSkia top_edge = *rb.GetImageSkiaNamed(
260 IDR_AURA_WINDOW_HEADER_SHADE_INACTIVE_TOP);
261 gfx::ImageSkia left_edge = *rb.GetImageSkiaNamed(
262 IDR_AURA_WINDOW_HEADER_SHADE_INACTIVE_LEFT);
263 gfx::ImageSkia right_edge = *rb.GetImageSkiaNamed(
264 IDR_AURA_WINDOW_HEADER_SHADE_INACTIVE_RIGHT);
265 gfx::ImageSkia bottom_edge = *rb.GetImageSkiaNamed(
266 IDR_AURA_WINDOW_HEADER_SHADE_INACTIVE_BOTTOM);
267
268 int left_edge_width = left_edge.width();
269 int right_edge_width = right_edge.width();
270 canvas->DrawImageInt(left_edge, 0, 0);
271 canvas->DrawImageInt(right_edge, view_->width() - right_edge_width, 0);
272 canvas->TileImageInt(
273 top_edge,
274 left_edge_width,
275 0,
276 view_->width() - left_edge_width - right_edge_width,
277 top_edge.height());
278
279 DCHECK_EQ(left_edge.height(), right_edge.height());
280 int bottom = left_edge.height();
281 int bottom_height = bottom_edge.height();
282 canvas->TileImageInt(
283 bottom_edge,
284 left_edge_width,
285 bottom - bottom_height,
286 view_->width() - left_edge_width - right_edge_width,
287 bottom_height);
288 }
289
PaintTitleBar(gfx::Canvas * canvas)290 void DefaultHeaderPainter::PaintTitleBar(gfx::Canvas* canvas) {
291 // The window icon is painted by its own views::View.
292 gfx::Rect title_bounds = GetTitleBounds();
293 title_bounds.set_x(view_->GetMirroredXForRect(title_bounds));
294 canvas->DrawStringRectWithFlags(frame_->widget_delegate()->GetWindowTitle(),
295 GetTitleFontList(),
296 kTitleTextColor,
297 title_bounds,
298 gfx::Canvas::NO_SUBPIXEL_RENDERING);
299 }
300
PaintHeaderContentSeparator(gfx::Canvas * canvas)301 void DefaultHeaderPainter::PaintHeaderContentSeparator(gfx::Canvas* canvas) {
302 SkColor color = (mode_ == MODE_ACTIVE) ?
303 kHeaderContentSeparatorColor :
304 kHeaderContentSeparatorInactiveColor;
305
306 SkPaint paint;
307 paint.setColor(color);
308 // Draw the line as 1px thick regardless of scale factor.
309 paint.setStrokeWidth(0);
310
311 float thickness = 1 / canvas->image_scale();
312 SkScalar y = SkIntToScalar(height_) - SkFloatToScalar(thickness);
313 canvas->sk_canvas()->drawLine(0, y, SkIntToScalar(view_->width()), y, paint);
314 }
315
GetLocalBounds() const316 gfx::Rect DefaultHeaderPainter::GetLocalBounds() const {
317 return gfx::Rect(view_->width(), height_);
318 }
319
GetTitleBounds() const320 gfx::Rect DefaultHeaderPainter::GetTitleBounds() const {
321 return HeaderPainterUtil::GetTitleBounds(
322 window_icon_, caption_button_container_, GetTitleFontList());
323 }
324
GetActiveFrameImage() const325 gfx::ImageSkia* DefaultHeaderPainter::GetActiveFrameImage() const {
326 return ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
327 IDR_AURA_WINDOW_HEADER_BASE);
328 }
329
GetInactiveFrameImage() const330 gfx::ImageSkia* DefaultHeaderPainter::GetInactiveFrameImage() const {
331 int frame_image_id = (frame_->IsMaximized() || frame_->IsFullscreen()) ?
332 IDR_AURA_WINDOW_HEADER_BASE :
333 IDR_AURA_WINDOW_HEADER_BASE_RESTORED_INACTIVE;
334 return ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
335 frame_image_id);
336 }
337
338 } // namespace ash
339