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