1 // Copyright 2013 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/wm/header_painter.h" 6 7 #include <vector> 8 9 #include "ash/root_window_controller.h" 10 #include "ash/wm/caption_buttons/frame_caption_button_container_view.h" 11 #include "ash/wm/solo_window_tracker.h" 12 #include "base/logging.h" // DCHECK 13 #include "grit/ash_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/aura/window.h" 19 #include "ui/base/hit_test.h" 20 #include "ui/base/resource/resource_bundle.h" 21 #include "ui/base/theme_provider.h" 22 #include "ui/gfx/animation/slide_animation.h" 23 #include "ui/gfx/canvas.h" 24 #include "ui/gfx/font.h" 25 #include "ui/gfx/image/image.h" 26 #include "ui/gfx/screen.h" 27 #include "ui/gfx/skia_util.h" 28 #include "ui/views/widget/widget.h" 29 #include "ui/views/widget/widget_delegate.h" 30 31 using aura::Window; 32 using views::Widget; 33 34 namespace { 35 // Space between left edge of window and popup window icon. 36 const int kIconOffsetX = 9; 37 // Height and width of window icon. 38 const int kIconSize = 16; 39 // Space between the title text and the caption buttons. 40 const int kTitleLogoSpacing = 5; 41 // Space between window icon and title text. 42 const int kTitleIconOffsetX = 5; 43 // Space between window edge and title text, when there is no icon. 44 const int kTitleNoIconOffsetX = 8; 45 // Color for the non-maximized window title text. 46 const SkColor kNonMaximizedWindowTitleTextColor = SkColorSetRGB(40, 40, 40); 47 // Color for the maximized window title text. 48 const SkColor kMaximizedWindowTitleTextColor = SK_ColorWHITE; 49 // Size of header/content separator line below the header image. 50 const int kHeaderContentSeparatorSize = 1; 51 // Color of header bottom edge line. 52 const SkColor kHeaderContentSeparatorColor = SkColorSetRGB(128, 128, 128); 53 // In the pre-Ash era the web content area had a frame along the left edge, so 54 // user-generated theme images for the new tab page assume they are shifted 55 // right relative to the header. Now that we have removed the left edge frame 56 // we need to copy the theme image for the window header from a few pixels 57 // inset to preserve alignment with the NTP image, or else we'll break a bunch 58 // of existing themes. We do something similar on OS X for the same reason. 59 const int kThemeFrameImageInsetX = 5; 60 // Duration of crossfade animation for activating and deactivating frame. 61 const int kActivationCrossfadeDurationMs = 200; 62 // Alpha/opacity value for fully-opaque headers. 63 const int kFullyOpaque = 255; 64 65 // Tiles an image into an area, rounding the top corners. Samples |image| 66 // starting |image_inset_x| pixels from the left of the image. 67 void TileRoundRect(gfx::Canvas* canvas, 68 const gfx::ImageSkia& image, 69 const SkPaint& paint, 70 const gfx::Rect& bounds, 71 int top_left_corner_radius, 72 int top_right_corner_radius, 73 int image_inset_x) { 74 SkRect rect = gfx::RectToSkRect(bounds); 75 const SkScalar kTopLeftRadius = SkIntToScalar(top_left_corner_radius); 76 const SkScalar kTopRightRadius = SkIntToScalar(top_right_corner_radius); 77 SkScalar radii[8] = { 78 kTopLeftRadius, kTopLeftRadius, // top-left 79 kTopRightRadius, kTopRightRadius, // top-right 80 0, 0, // bottom-right 81 0, 0}; // bottom-left 82 SkPath path; 83 path.addRoundRect(rect, radii, SkPath::kCW_Direction); 84 canvas->DrawImageInPath(image, -image_inset_x, 0, path, paint); 85 } 86 87 // Tiles |frame_image| and |frame_overlay_image| into an area, rounding the top 88 // corners. 89 void PaintFrameImagesInRoundRect(gfx::Canvas* canvas, 90 const gfx::ImageSkia* frame_image, 91 const gfx::ImageSkia* frame_overlay_image, 92 const SkPaint& paint, 93 const gfx::Rect& bounds, 94 int corner_radius, 95 int image_inset_x) { 96 SkXfermode::Mode normal_mode; 97 SkXfermode::AsMode(NULL, &normal_mode); 98 99 // If |paint| is using an unusual SkXfermode::Mode (this is the case while 100 // crossfading), we must create a new canvas to overlay |frame_image| and 101 // |frame_overlay_image| using |normal_mode| and then paint the result 102 // using the unusual mode. We try to avoid this because creating a new 103 // browser-width canvas is expensive. 104 bool fast_path = (!frame_overlay_image || 105 SkXfermode::IsMode(paint.getXfermode(), normal_mode)); 106 if (fast_path) { 107 TileRoundRect(canvas, *frame_image, paint, bounds, corner_radius, 108 corner_radius, image_inset_x); 109 110 if (frame_overlay_image) { 111 // Adjust |bounds| such that |frame_overlay_image| is not tiled. 112 gfx::Rect overlay_bounds = bounds; 113 overlay_bounds.Intersect( 114 gfx::Rect(bounds.origin(), frame_overlay_image->size())); 115 int top_left_corner_radius = corner_radius; 116 int top_right_corner_radius = corner_radius; 117 if (overlay_bounds.width() < bounds.width() - corner_radius) 118 top_right_corner_radius = 0; 119 TileRoundRect(canvas, *frame_overlay_image, paint, overlay_bounds, 120 top_left_corner_radius, top_right_corner_radius, 0); 121 } 122 } else { 123 gfx::Canvas temporary_canvas(bounds.size(), canvas->image_scale(), false); 124 temporary_canvas.TileImageInt(*frame_image, 125 image_inset_x, 0, 126 0, 0, 127 bounds.width(), bounds.height()); 128 temporary_canvas.DrawImageInt(*frame_overlay_image, 0, 0); 129 TileRoundRect(canvas, gfx::ImageSkia(temporary_canvas.ExtractImageRep()), 130 paint, bounds, corner_radius, corner_radius, 0); 131 } 132 } 133 134 } // namespace 135 136 namespace ash { 137 138 // static 139 int HeaderPainter::kActiveWindowOpacity = 255; // 1.0 140 int HeaderPainter::kInactiveWindowOpacity = 255; // 1.0 141 int HeaderPainter::kSoloWindowOpacity = 77; // 0.3 142 143 /////////////////////////////////////////////////////////////////////////////// 144 // HeaderPainter, public: 145 146 HeaderPainter::HeaderPainter() 147 : frame_(NULL), 148 header_view_(NULL), 149 window_icon_(NULL), 150 caption_button_container_(NULL), 151 window_(NULL), 152 header_height_(0), 153 top_left_corner_(NULL), 154 top_edge_(NULL), 155 top_right_corner_(NULL), 156 header_left_edge_(NULL), 157 header_right_edge_(NULL), 158 previous_theme_frame_id_(0), 159 previous_theme_frame_overlay_id_(0), 160 previous_opacity_(0), 161 crossfade_theme_frame_id_(0), 162 crossfade_theme_frame_overlay_id_(0), 163 crossfade_opacity_(0) {} 164 165 HeaderPainter::~HeaderPainter() { 166 // Sometimes we are destroyed before the window closes, so ensure we clean up. 167 if (window_) 168 window_->RemoveObserver(this); 169 } 170 171 void HeaderPainter::Init( 172 views::Widget* frame, 173 views::View* header_view, 174 views::View* window_icon, 175 FrameCaptionButtonContainerView* caption_button_container) { 176 DCHECK(frame); 177 DCHECK(header_view); 178 // window_icon may be NULL. 179 DCHECK(caption_button_container); 180 frame_ = frame; 181 header_view_ = header_view; 182 window_icon_ = window_icon; 183 caption_button_container_ = caption_button_container; 184 185 // Window frame image parts. 186 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 187 top_left_corner_ = 188 rb.GetImageNamed(IDR_AURA_WINDOW_HEADER_SHADE_TOP_LEFT).ToImageSkia(); 189 top_edge_ = 190 rb.GetImageNamed(IDR_AURA_WINDOW_HEADER_SHADE_TOP).ToImageSkia(); 191 top_right_corner_ = 192 rb.GetImageNamed(IDR_AURA_WINDOW_HEADER_SHADE_TOP_RIGHT).ToImageSkia(); 193 header_left_edge_ = 194 rb.GetImageNamed(IDR_AURA_WINDOW_HEADER_SHADE_LEFT).ToImageSkia(); 195 header_right_edge_ = 196 rb.GetImageNamed(IDR_AURA_WINDOW_HEADER_SHADE_RIGHT).ToImageSkia(); 197 198 window_ = frame->GetNativeWindow(); 199 200 // Observer removes itself in OnWindowDestroying() below, or in the destructor 201 // if we go away before the window. 202 window_->AddObserver(this); 203 204 // Solo-window header updates are handled by the WorkspaceLayoutManager when 205 // this window is added to the desktop. 206 } 207 208 // static 209 gfx::Rect HeaderPainter::GetBoundsForClientView( 210 int header_height, 211 const gfx::Rect& window_bounds) { 212 gfx::Rect client_bounds(window_bounds); 213 client_bounds.Inset(0, header_height, 0, 0); 214 return client_bounds; 215 } 216 217 // static 218 gfx::Rect HeaderPainter::GetWindowBoundsForClientBounds( 219 int header_height, 220 const gfx::Rect& client_bounds) { 221 gfx::Rect window_bounds(client_bounds); 222 window_bounds.Inset(0, -header_height, 0, 0); 223 if (window_bounds.y() < 0) 224 window_bounds.set_y(0); 225 return window_bounds; 226 } 227 228 int HeaderPainter::NonClientHitTest(const gfx::Point& point) const { 229 gfx::Point point_in_header_view(point); 230 views::View::ConvertPointFromWidget(header_view_, &point_in_header_view); 231 if (!GetHeaderLocalBounds().Contains(point_in_header_view)) 232 return HTNOWHERE; 233 if (caption_button_container_->visible()) { 234 gfx::Point point_in_caption_button_container(point); 235 views::View::ConvertPointFromWidget(caption_button_container_, 236 &point_in_caption_button_container); 237 int component = caption_button_container_->NonClientHitTest( 238 point_in_caption_button_container); 239 if (component != HTNOWHERE) 240 return component; 241 } 242 // Caption is a safe default. 243 return HTCAPTION; 244 } 245 246 int HeaderPainter::GetMinimumHeaderWidth() const { 247 // Ensure we have enough space for the window icon and buttons. We allow 248 // the title string to collapse to zero width. 249 return GetTitleOffsetX() + 250 caption_button_container_->GetMinimumSize().width(); 251 } 252 253 int HeaderPainter::GetRightInset() const { 254 return caption_button_container_->GetPreferredSize().width(); 255 } 256 257 int HeaderPainter::GetThemeBackgroundXInset() const { 258 return kThemeFrameImageInsetX; 259 } 260 261 void HeaderPainter::PaintHeader(gfx::Canvas* canvas, 262 HeaderMode header_mode, 263 int theme_frame_id, 264 int theme_frame_overlay_id) { 265 bool initial_paint = (previous_theme_frame_id_ == 0); 266 if (!initial_paint && 267 (previous_theme_frame_id_ != theme_frame_id || 268 previous_theme_frame_overlay_id_ != theme_frame_overlay_id)) { 269 aura::Window* parent = frame_->GetNativeWindow()->parent(); 270 // Don't animate the header if the parent (a workspace) is already 271 // animating. Doing so results in continually painting during the animation 272 // and gives a slower frame rate. 273 // TODO(sky): expose a better way to determine this rather than assuming 274 // the parent is a workspace. 275 bool parent_animating = parent && 276 (parent->layer()->GetAnimator()->IsAnimatingProperty( 277 ui::LayerAnimationElement::OPACITY) || 278 parent->layer()->GetAnimator()->IsAnimatingProperty( 279 ui::LayerAnimationElement::VISIBILITY)); 280 if (!parent_animating) { 281 crossfade_animation_.reset(new gfx::SlideAnimation(this)); 282 crossfade_theme_frame_id_ = previous_theme_frame_id_; 283 crossfade_theme_frame_overlay_id_ = previous_theme_frame_overlay_id_; 284 crossfade_opacity_ = previous_opacity_; 285 crossfade_animation_->SetSlideDuration(kActivationCrossfadeDurationMs); 286 crossfade_animation_->Show(); 287 } else { 288 crossfade_animation_.reset(); 289 } 290 } 291 292 int opacity = 293 GetHeaderOpacity(header_mode, theme_frame_id, theme_frame_overlay_id); 294 ui::ThemeProvider* theme_provider = frame_->GetThemeProvider(); 295 gfx::ImageSkia* theme_frame = theme_provider->GetImageSkiaNamed( 296 theme_frame_id); 297 gfx::ImageSkia* theme_frame_overlay = NULL; 298 if (theme_frame_overlay_id != 0) { 299 theme_frame_overlay = theme_provider->GetImageSkiaNamed( 300 theme_frame_overlay_id); 301 } 302 303 int corner_radius = GetHeaderCornerRadius(); 304 SkPaint paint; 305 306 if (crossfade_animation_.get() && crossfade_animation_->is_animating()) { 307 gfx::ImageSkia* crossfade_theme_frame = 308 theme_provider->GetImageSkiaNamed(crossfade_theme_frame_id_); 309 gfx::ImageSkia* crossfade_theme_frame_overlay = NULL; 310 if (crossfade_theme_frame_overlay_id_ != 0) { 311 crossfade_theme_frame_overlay = theme_provider->GetImageSkiaNamed( 312 crossfade_theme_frame_overlay_id_); 313 } 314 if (!crossfade_theme_frame || 315 (crossfade_theme_frame_overlay_id_ != 0 && 316 !crossfade_theme_frame_overlay)) { 317 // Reset the animation. This case occurs when the user switches the theme 318 // that they are using. 319 crossfade_animation_.reset(); 320 paint.setAlpha(opacity); 321 } else { 322 double current_value = crossfade_animation_->GetCurrentValue(); 323 int old_alpha = (1 - current_value) * crossfade_opacity_; 324 int new_alpha = current_value * opacity; 325 326 // Draw the old header background, clipping the corners to be rounded. 327 paint.setAlpha(old_alpha); 328 paint.setXfermodeMode(SkXfermode::kPlus_Mode); 329 PaintFrameImagesInRoundRect(canvas, 330 crossfade_theme_frame, 331 crossfade_theme_frame_overlay, 332 paint, 333 GetHeaderLocalBounds(), 334 corner_radius, 335 GetThemeBackgroundXInset()); 336 337 paint.setAlpha(new_alpha); 338 } 339 } else { 340 paint.setAlpha(opacity); 341 } 342 343 // Draw the header background, clipping the corners to be rounded. 344 PaintFrameImagesInRoundRect(canvas, 345 theme_frame, 346 theme_frame_overlay, 347 paint, 348 GetHeaderLocalBounds(), 349 corner_radius, 350 GetThemeBackgroundXInset()); 351 352 previous_theme_frame_id_ = theme_frame_id; 353 previous_theme_frame_overlay_id_ = theme_frame_overlay_id; 354 previous_opacity_ = opacity; 355 356 // We don't need the extra lightness in the edges when we're at the top edge 357 // of the screen or when the header's corners are not rounded. 358 // 359 // TODO(sky): this isn't quite right. What we really want is a method that 360 // returns bounds ignoring transforms on certain windows (such as workspaces) 361 // and is relative to the root. 362 if (frame_->GetNativeWindow()->bounds().y() == 0 || corner_radius == 0) 363 return; 364 365 // Draw the top corners and edge. 366 int top_left_width = top_left_corner_->width(); 367 int top_left_height = top_left_corner_->height(); 368 canvas->DrawImageInt(*top_left_corner_, 369 0, 0, top_left_width, top_left_height, 370 0, 0, top_left_width, top_left_height, 371 false); 372 canvas->TileImageInt(*top_edge_, 373 top_left_width, 374 0, 375 header_view_->width() - top_left_width - top_right_corner_->width(), 376 top_edge_->height()); 377 int top_right_height = top_right_corner_->height(); 378 canvas->DrawImageInt(*top_right_corner_, 379 0, 0, 380 top_right_corner_->width(), top_right_height, 381 header_view_->width() - top_right_corner_->width(), 0, 382 top_right_corner_->width(), top_right_height, 383 false); 384 385 // Header left edge. 386 int header_left_height = theme_frame->height() - top_left_height; 387 canvas->TileImageInt(*header_left_edge_, 388 0, top_left_height, 389 header_left_edge_->width(), header_left_height); 390 391 // Header right edge. 392 int header_right_height = theme_frame->height() - top_right_height; 393 canvas->TileImageInt(*header_right_edge_, 394 header_view_->width() - header_right_edge_->width(), 395 top_right_height, 396 header_right_edge_->width(), 397 header_right_height); 398 399 // We don't draw edges around the content area. Web content goes flush 400 // to the edge of the window. 401 } 402 403 void HeaderPainter::PaintHeaderContentSeparator(gfx::Canvas* canvas) { 404 canvas->FillRect(gfx::Rect(0, 405 header_height_ - kHeaderContentSeparatorSize, 406 header_view_->width(), 407 kHeaderContentSeparatorSize), 408 kHeaderContentSeparatorColor); 409 } 410 411 int HeaderPainter::HeaderContentSeparatorSize() const { 412 return kHeaderContentSeparatorSize; 413 } 414 415 void HeaderPainter::PaintTitleBar(gfx::Canvas* canvas, 416 const gfx::Font& title_font) { 417 // The window icon is painted by its own views::View. 418 views::WidgetDelegate* delegate = frame_->widget_delegate(); 419 if (delegate && delegate->ShouldShowWindowTitle()) { 420 gfx::Rect title_bounds = GetTitleBounds(title_font); 421 SkColor title_color = (frame_->IsMaximized() || frame_->IsFullscreen()) ? 422 kMaximizedWindowTitleTextColor : kNonMaximizedWindowTitleTextColor; 423 canvas->DrawStringInt(delegate->GetWindowTitle(), 424 title_font, 425 title_color, 426 header_view_->GetMirroredXForRect(title_bounds), 427 title_bounds.y(), 428 title_bounds.width(), 429 title_bounds.height(), 430 gfx::Canvas::NO_SUBPIXEL_RENDERING); 431 } 432 } 433 434 void HeaderPainter::LayoutHeader(bool shorter_layout) { 435 caption_button_container_->set_header_style(shorter_layout ? 436 FrameCaptionButtonContainerView::HEADER_STYLE_SHORT : 437 FrameCaptionButtonContainerView::HEADER_STYLE_TALL); 438 caption_button_container_->Layout(); 439 440 gfx::Size caption_button_container_size = 441 caption_button_container_->GetPreferredSize(); 442 caption_button_container_->SetBounds( 443 header_view_->width() - caption_button_container_size.width(), 444 0, 445 caption_button_container_size.width(), 446 caption_button_container_size.height()); 447 448 if (window_icon_) { 449 // Vertically center the window icon with respect to the caption button 450 // container. 451 int icon_offset_y = 452 GetCaptionButtonContainerCenterY() - window_icon_->height() / 2; 453 window_icon_->SetBounds(kIconOffsetX, icon_offset_y, kIconSize, kIconSize); 454 } 455 } 456 457 void HeaderPainter::SchedulePaintForTitle(const gfx::Font& title_font) { 458 header_view_->SchedulePaintInRect(GetTitleBounds(title_font)); 459 } 460 461 void HeaderPainter::OnThemeChanged() { 462 // We do not cache the images for |previous_theme_frame_id_| and 463 // |previous_theme_frame_overlay_id_|. Changing the theme changes the images 464 // returned from ui::ThemeProvider for |previous_theme_frame_id_| 465 // and |previous_theme_frame_overlay_id_|. Reset the image ids to prevent 466 // starting a crossfade animation with these images. 467 previous_theme_frame_id_ = 0; 468 previous_theme_frame_overlay_id_ = 0; 469 470 if (crossfade_animation_.get() && crossfade_animation_->is_animating()) { 471 crossfade_animation_.reset(); 472 header_view_->SchedulePaintInRect(GetHeaderLocalBounds()); 473 } 474 } 475 476 /////////////////////////////////////////////////////////////////////////////// 477 // aura::WindowObserver overrides: 478 479 void HeaderPainter::OnWindowDestroying(aura::Window* destroying) { 480 DCHECK_EQ(window_, destroying); 481 482 // Must be removed here and not in the destructor, as the aura::Window is 483 // already destroyed when our destructor runs. 484 window_->RemoveObserver(this); 485 486 window_ = NULL; 487 } 488 489 void HeaderPainter::OnWindowBoundsChanged(aura::Window* window, 490 const gfx::Rect& old_bounds, 491 const gfx::Rect& new_bounds) { 492 // TODO(sky): this isn't quite right. What we really want is a method that 493 // returns bounds ignoring transforms on certain windows (such as workspaces). 494 if ((!frame_->IsMaximized() && !frame_->IsFullscreen()) && 495 ((old_bounds.y() == 0 && new_bounds.y() != 0) || 496 (old_bounds.y() != 0 && new_bounds.y() == 0))) { 497 SchedulePaintForHeader(); 498 } 499 } 500 501 /////////////////////////////////////////////////////////////////////////////// 502 // gfx::AnimationDelegate overrides: 503 504 void HeaderPainter::AnimationProgressed(const gfx::Animation* animation) { 505 header_view_->SchedulePaintInRect(GetHeaderLocalBounds()); 506 } 507 508 /////////////////////////////////////////////////////////////////////////////// 509 // HeaderPainter, private: 510 511 gfx::Rect HeaderPainter::GetHeaderLocalBounds() const { 512 return gfx::Rect(header_view_->width(), header_height_); 513 } 514 515 int HeaderPainter::GetTitleOffsetX() const { 516 return window_icon_ ? 517 window_icon_->bounds().right() + kTitleIconOffsetX : 518 kTitleNoIconOffsetX; 519 } 520 521 int HeaderPainter::GetCaptionButtonContainerCenterY() const { 522 return caption_button_container_->y() + 523 caption_button_container_->height() / 2; 524 } 525 526 int HeaderPainter::GetHeaderCornerRadius() const { 527 bool square_corners = (frame_->IsMaximized() || frame_->IsFullscreen()); 528 const int kCornerRadius = 2; 529 return square_corners ? 0 : kCornerRadius; 530 } 531 532 int HeaderPainter::GetHeaderOpacity( 533 HeaderMode header_mode, 534 int theme_frame_id, 535 int theme_frame_overlay_id) const { 536 // User-provided themes are painted fully opaque. 537 ui::ThemeProvider* theme_provider = frame_->GetThemeProvider(); 538 if (theme_provider->HasCustomImage(theme_frame_id) || 539 (theme_frame_overlay_id != 0 && 540 theme_provider->HasCustomImage(theme_frame_overlay_id))) { 541 return kFullyOpaque; 542 } 543 544 // Maximized and fullscreen windows are fully opaque. 545 if (frame_->IsMaximized() || frame_->IsFullscreen()) 546 return kFullyOpaque; 547 548 // Solo header is very transparent. 549 ash::SoloWindowTracker* solo_window_tracker = 550 internal::RootWindowController::ForWindow(window_)->solo_window_tracker(); 551 if (solo_window_tracker && 552 solo_window_tracker->GetWindowWithSoloHeader() == window_) { 553 return kSoloWindowOpacity; 554 } 555 556 // Otherwise, change transparency based on window activation status. 557 if (header_mode == ACTIVE) 558 return kActiveWindowOpacity; 559 return kInactiveWindowOpacity; 560 } 561 562 void HeaderPainter::SchedulePaintForHeader() { 563 int top_left_height = top_left_corner_->height(); 564 int top_right_height = top_right_corner_->height(); 565 header_view_->SchedulePaintInRect( 566 gfx::Rect(0, 0, header_view_->width(), 567 std::max(top_left_height, top_right_height))); 568 } 569 570 gfx::Rect HeaderPainter::GetTitleBounds(const gfx::Font& title_font) { 571 int title_x = GetTitleOffsetX(); 572 // Center the text with respect to the caption button container. This way it 573 // adapts to the caption button height and aligns exactly with the window 574 // icon. Don't use |window_icon_| for this computation as it may be NULL. 575 int title_y = GetCaptionButtonContainerCenterY() - title_font.GetHeight() / 2; 576 return gfx::Rect( 577 title_x, 578 std::max(0, title_y), 579 std::max(0, caption_button_container_->x() - kTitleLogoSpacing - title_x), 580 title_font.GetHeight()); 581 } 582 583 } // namespace ash 584