Home | History | Annotate | Download | only in wm
      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