Home | History | Annotate | Download | only in wm
      1 // Copyright (c) 2012 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/frame_painter.h"
      6 
      7 #include <vector>
      8 
      9 #include "ash/ash_constants.h"
     10 #include "ash/root_window_controller.h"
     11 #include "ash/shell.h"
     12 #include "ash/shell_window_ids.h"
     13 #include "ash/wm/property_util.h"
     14 #include "ash/wm/window_properties.h"
     15 #include "ash/wm/window_util.h"
     16 #include "base/logging.h"  // DCHECK
     17 #include "grit/ash_resources.h"
     18 #include "third_party/skia/include/core/SkCanvas.h"
     19 #include "third_party/skia/include/core/SkColor.h"
     20 #include "third_party/skia/include/core/SkPaint.h"
     21 #include "third_party/skia/include/core/SkPath.h"
     22 #include "ui/aura/client/aura_constants.h"
     23 #include "ui/aura/env.h"
     24 #include "ui/aura/root_window.h"
     25 #include "ui/aura/window.h"
     26 #include "ui/base/animation/slide_animation.h"
     27 #include "ui/base/hit_test.h"
     28 #include "ui/base/layout.h"
     29 #include "ui/base/resource/resource_bundle.h"
     30 #include "ui/base/theme_provider.h"
     31 #include "ui/gfx/canvas.h"
     32 #include "ui/gfx/font.h"
     33 #include "ui/gfx/image/image.h"
     34 #include "ui/gfx/screen.h"
     35 #include "ui/gfx/skia_util.h"
     36 #include "ui/views/controls/button/image_button.h"
     37 #include "ui/views/widget/widget.h"
     38 #include "ui/views/widget/widget_delegate.h"
     39 
     40 using aura::RootWindow;
     41 using aura::Window;
     42 using views::Widget;
     43 
     44 namespace {
     45 // TODO(jamescook): Border is specified to be a single pixel overlapping
     46 // the web content and may need to be built into the shadow layers instead.
     47 const int kBorderThickness = 0;
     48 // Space between left edge of window and popup window icon.
     49 const int kIconOffsetX = 9;
     50 // Height and width of window icon.
     51 const int kIconSize = 16;
     52 // Space between the title text and the caption buttons.
     53 const int kTitleLogoSpacing = 5;
     54 // Space between window icon and title text.
     55 const int kTitleIconOffsetX = 5;
     56 // Space between window edge and title text, when there is no icon.
     57 const int kTitleNoIconOffsetX = 8;
     58 // Color for the non-maximized window title text.
     59 const SkColor kNonMaximizedWindowTitleTextColor = SkColorSetRGB(40, 40, 40);
     60 // Color for the maximized window title text.
     61 const SkColor kMaximizedWindowTitleTextColor = SK_ColorWHITE;
     62 // Size of header/content separator line below the header image.
     63 const int kHeaderContentSeparatorSize = 1;
     64 // Color of header bottom edge line.
     65 const SkColor kHeaderContentSeparatorColor = SkColorSetRGB(128, 128, 128);
     66 // Space between close button and right edge of window.
     67 const int kCloseButtonOffsetX = 0;
     68 // Space between close button and top edge of window.
     69 const int kCloseButtonOffsetY = 0;
     70 // The size and close buttons are designed to slightly overlap in order
     71 // to do fancy hover highlighting.
     72 const int kSizeButtonOffsetX = -1;
     73 // In the pre-Ash era the web content area had a frame along the left edge, so
     74 // user-generated theme images for the new tab page assume they are shifted
     75 // right relative to the header.  Now that we have removed the left edge frame
     76 // we need to copy the theme image for the window header from a few pixels
     77 // inset to preserve alignment with the NTP image, or else we'll break a bunch
     78 // of existing themes.  We do something similar on OS X for the same reason.
     79 const int kThemeFrameImageInsetX = 5;
     80 // Duration of crossfade animation for activating and deactivating frame.
     81 const int kActivationCrossfadeDurationMs = 200;
     82 // Alpha/opacity value for fully-opaque headers.
     83 const int kFullyOpaque = 255;
     84 
     85 // Tiles an image into an area, rounding the top corners. Samples |image|
     86 // starting |image_inset_x| pixels from the left of the image.
     87 void TileRoundRect(gfx::Canvas* canvas,
     88                    const gfx::ImageSkia& image,
     89                    const SkPaint& paint,
     90                    const gfx::Rect& bounds,
     91                    int top_left_corner_radius,
     92                    int top_right_corner_radius,
     93                    int image_inset_x) {
     94   SkRect rect = gfx::RectToSkRect(bounds);
     95   const SkScalar kTopLeftRadius = SkIntToScalar(top_left_corner_radius);
     96   const SkScalar kTopRightRadius = SkIntToScalar(top_right_corner_radius);
     97   SkScalar radii[8] = {
     98       kTopLeftRadius, kTopLeftRadius,  // top-left
     99       kTopRightRadius, kTopRightRadius,  // top-right
    100       0, 0,   // bottom-right
    101       0, 0};  // bottom-left
    102   SkPath path;
    103   path.addRoundRect(rect, radii, SkPath::kCW_Direction);
    104   canvas->DrawImageInPath(image, -image_inset_x, 0, path, paint);
    105 }
    106 
    107 // Tiles |frame_image| and |frame_overlay_image| into an area, rounding the top
    108 // corners.
    109 void PaintFrameImagesInRoundRect(gfx::Canvas* canvas,
    110                                  const gfx::ImageSkia* frame_image,
    111                                  const gfx::ImageSkia* frame_overlay_image,
    112                                  const SkPaint& paint,
    113                                  const gfx::Rect& bounds,
    114                                  int corner_radius,
    115                                  int image_inset_x) {
    116   SkXfermode::Mode normal_mode;
    117   SkXfermode::AsMode(NULL, &normal_mode);
    118 
    119   // If |paint| is using an unusual SkXfermode::Mode (this is the case while
    120   // crossfading), we must create a new canvas to overlay |frame_image| and
    121   // |frame_overlay_image| using |normal_mode| and then paint the result
    122   // using the unusual mode. We try to avoid this because creating a new
    123   // browser-width canvas is expensive.
    124   bool fast_path = (!frame_overlay_image ||
    125       SkXfermode::IsMode(paint.getXfermode(), normal_mode));
    126   if (fast_path) {
    127     TileRoundRect(canvas, *frame_image, paint, bounds, corner_radius,
    128         corner_radius, image_inset_x);
    129 
    130     if (frame_overlay_image) {
    131       // Adjust |bounds| such that |frame_overlay_image| is not tiled.
    132       gfx::Rect overlay_bounds = bounds;
    133       overlay_bounds.Intersect(
    134           gfx::Rect(bounds.origin(), frame_overlay_image->size()));
    135       int top_left_corner_radius = corner_radius;
    136       int top_right_corner_radius = corner_radius;
    137       if (overlay_bounds.width() < bounds.width() - corner_radius)
    138         top_right_corner_radius = 0;
    139       TileRoundRect(canvas, *frame_overlay_image, paint, overlay_bounds,
    140           top_left_corner_radius, top_right_corner_radius, 0);
    141     }
    142   } else {
    143     gfx::Canvas temporary_canvas(bounds.size(), canvas->scale_factor(), false);
    144     temporary_canvas.TileImageInt(*frame_image,
    145                                   image_inset_x, 0,
    146                                   0, 0,
    147                                   bounds.width(), bounds.height());
    148     temporary_canvas.DrawImageInt(*frame_overlay_image, 0, 0);
    149     TileRoundRect(canvas, gfx::ImageSkia(temporary_canvas.ExtractImageRep()),
    150         paint, bounds, corner_radius, corner_radius, 0);
    151   }
    152 }
    153 
    154 // Returns true if |child| and all ancestors are visible. Useful to ensure that
    155 // a window is individually visible and is not part of a hidden workspace.
    156 bool IsVisibleToRoot(Window* child) {
    157   for (Window* window = child; window; window = window->parent()) {
    158     // We must use TargetVisibility() because windows animate in and out and
    159     // IsVisible() also tracks the layer visibility state.
    160     if (!window->TargetVisibility())
    161       return false;
    162   }
    163   return true;
    164 }
    165 
    166 // Returns true if |window| is a "normal" window for purposes of solo window
    167 // computations. Returns false for windows that are:
    168 // * Not drawn (for example, DragDropTracker uses one for mouse capture)
    169 // * Modal alerts (it looks odd for headers to change when an alert opens)
    170 // * Constrained windows (ditto)
    171 bool IsSoloWindowHeaderCandidate(aura::Window* window) {
    172   return window &&
    173       window->type() == aura::client::WINDOW_TYPE_NORMAL &&
    174       window->layer() &&
    175       window->layer()->type() != ui::LAYER_NOT_DRAWN &&
    176       window->GetProperty(aura::client::kModalKey) == ui::MODAL_TYPE_NONE &&
    177       !window->GetProperty(ash::kConstrainedWindowKey);
    178 }
    179 
    180 // Returns a list of windows in |root_window|| that potentially could have
    181 // a transparent solo-window header.
    182 std::vector<Window*> GetWindowsForSoloHeaderUpdate(RootWindow* root_window) {
    183   std::vector<Window*> windows;
    184   // During shutdown there may not be a workspace controller. In that case
    185   // we don't care about updating any windows.
    186   // Avoid memory allocations for typical window counts.
    187   windows.reserve(16);
    188   // Collect windows from the desktop.
    189   Window* desktop = ash::Shell::GetContainer(
    190       root_window, ash::internal::kShellWindowId_DefaultContainer);
    191   windows.insert(windows.end(),
    192                  desktop->children().begin(),
    193                  desktop->children().end());
    194   // Collect "always on top" windows.
    195   Window* top_container =
    196       ash::Shell::GetContainer(
    197           root_window, ash::internal::kShellWindowId_AlwaysOnTopContainer);
    198   windows.insert(windows.end(),
    199                  top_container->children().begin(),
    200                  top_container->children().end());
    201   return windows;
    202 }
    203 }  // namespace
    204 
    205 namespace ash {
    206 
    207 // static
    208 int FramePainter::kActiveWindowOpacity = 255;  // 1.0
    209 int FramePainter::kInactiveWindowOpacity = 255;  // 1.0
    210 int FramePainter::kSoloWindowOpacity = 77;  // 0.3
    211 
    212 ///////////////////////////////////////////////////////////////////////////////
    213 // FramePainter, public:
    214 
    215 FramePainter::FramePainter()
    216     : frame_(NULL),
    217       window_icon_(NULL),
    218       size_button_(NULL),
    219       close_button_(NULL),
    220       window_(NULL),
    221       button_separator_(NULL),
    222       top_left_corner_(NULL),
    223       top_edge_(NULL),
    224       top_right_corner_(NULL),
    225       header_left_edge_(NULL),
    226       header_right_edge_(NULL),
    227       previous_theme_frame_id_(0),
    228       previous_theme_frame_overlay_id_(0),
    229       previous_opacity_(0),
    230       crossfade_theme_frame_id_(0),
    231       crossfade_theme_frame_overlay_id_(0),
    232       crossfade_opacity_(0),
    233       size_button_behavior_(SIZE_BUTTON_MAXIMIZES) {}
    234 
    235 FramePainter::~FramePainter() {
    236   // Sometimes we are destroyed before the window closes, so ensure we clean up.
    237   if (window_) {
    238     window_->RemoveObserver(this);
    239   }
    240 }
    241 
    242 void FramePainter::Init(views::Widget* frame,
    243                         views::View* window_icon,
    244                         views::ImageButton* size_button,
    245                         views::ImageButton* close_button,
    246                         SizeButtonBehavior behavior) {
    247   DCHECK(frame);
    248   // window_icon may be NULL.
    249   DCHECK(size_button);
    250   DCHECK(close_button);
    251   frame_ = frame;
    252   window_icon_ = window_icon;
    253   size_button_ = size_button;
    254   close_button_ = close_button;
    255   size_button_behavior_ = behavior;
    256 
    257   // Window frame image parts.
    258   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
    259   button_separator_ =
    260       rb.GetImageNamed(IDR_AURA_WINDOW_BUTTON_SEPARATOR).ToImageSkia();
    261   top_left_corner_ =
    262       rb.GetImageNamed(IDR_AURA_WINDOW_HEADER_SHADE_TOP_LEFT).ToImageSkia();
    263   top_edge_ =
    264       rb.GetImageNamed(IDR_AURA_WINDOW_HEADER_SHADE_TOP).ToImageSkia();
    265   top_right_corner_ =
    266       rb.GetImageNamed(IDR_AURA_WINDOW_HEADER_SHADE_TOP_RIGHT).ToImageSkia();
    267   header_left_edge_ =
    268       rb.GetImageNamed(IDR_AURA_WINDOW_HEADER_SHADE_LEFT).ToImageSkia();
    269   header_right_edge_ =
    270       rb.GetImageNamed(IDR_AURA_WINDOW_HEADER_SHADE_RIGHT).ToImageSkia();
    271 
    272   window_ = frame->GetNativeWindow();
    273   gfx::Insets mouse_insets = gfx::Insets(-kResizeOutsideBoundsSize,
    274                                          -kResizeOutsideBoundsSize,
    275                                          -kResizeOutsideBoundsSize,
    276                                          -kResizeOutsideBoundsSize);
    277   gfx::Insets touch_insets = mouse_insets.Scale(
    278       kResizeOutsideBoundsScaleForTouch);
    279   // Ensure we get resize cursors for a few pixels outside our bounds.
    280   window_->SetHitTestBoundsOverrideOuter(mouse_insets, touch_insets);
    281   // Ensure we get resize cursors just inside our bounds as well.
    282   window_->set_hit_test_bounds_override_inner(mouse_insets);
    283 
    284   // Watch for maximize/restore/fullscreen state changes.  Observer removes
    285   // itself in OnWindowDestroying() below, or in the destructor if we go away
    286   // before the window.
    287   window_->AddObserver(this);
    288 
    289   // Solo-window header updates are handled by the workspace controller when
    290   // this window is added to the desktop.
    291 }
    292 
    293 // static
    294 void FramePainter::UpdateSoloWindowHeader(RootWindow* root_window) {
    295   // Use a separate function here so callers outside of FramePainter don't need
    296   // to know about "ignorable_window".
    297   UpdateSoloWindowInRoot(root_window, NULL /* ignorable_window */);
    298 }
    299 
    300 gfx::Rect FramePainter::GetBoundsForClientView(
    301     int top_height,
    302     const gfx::Rect& window_bounds) const {
    303   return gfx::Rect(
    304       kBorderThickness,
    305       top_height,
    306       std::max(0, window_bounds.width() - (2 * kBorderThickness)),
    307       std::max(0, window_bounds.height() - top_height - kBorderThickness));
    308 }
    309 
    310 gfx::Rect FramePainter::GetWindowBoundsForClientBounds(
    311     int top_height,
    312     const gfx::Rect& client_bounds) const {
    313   return gfx::Rect(std::max(0, client_bounds.x() - kBorderThickness),
    314                    std::max(0, client_bounds.y() - top_height),
    315                    client_bounds.width() + (2 * kBorderThickness),
    316                    client_bounds.height() + top_height + kBorderThickness);
    317 }
    318 
    319 int FramePainter::NonClientHitTest(views::NonClientFrameView* view,
    320                                    const gfx::Point& point) {
    321   gfx::Rect expanded_bounds = view->bounds();
    322   int outside_bounds = kResizeOutsideBoundsSize;
    323 
    324   if (aura::Env::GetInstance()->is_touch_down())
    325     outside_bounds *= kResizeOutsideBoundsScaleForTouch;
    326   expanded_bounds.Inset(-outside_bounds, -outside_bounds);
    327 
    328   if (!expanded_bounds.Contains(point))
    329     return HTNOWHERE;
    330 
    331   // No avatar button.
    332 
    333   // Check the frame first, as we allow a small area overlapping the contents
    334   // to be used for resize handles.
    335   bool can_ever_resize = frame_->widget_delegate() ?
    336       frame_->widget_delegate()->CanResize() :
    337       false;
    338   // Don't allow overlapping resize handles when the window is maximized or
    339   // fullscreen, as it can't be resized in those states.
    340   int resize_border =
    341       frame_->IsMaximized() || frame_->IsFullscreen() ? 0 :
    342       kResizeInsideBoundsSize;
    343   int frame_component = view->GetHTComponentForFrame(point,
    344                                                      resize_border,
    345                                                      resize_border,
    346                                                      kResizeAreaCornerSize,
    347                                                      kResizeAreaCornerSize,
    348                                                      can_ever_resize);
    349   if (frame_component != HTNOWHERE)
    350     return frame_component;
    351 
    352   int client_component = frame_->client_view()->NonClientHitTest(point);
    353   if (client_component != HTNOWHERE)
    354     return client_component;
    355 
    356   // Then see if the point is within any of the window controls.
    357   if (close_button_->visible() &&
    358       close_button_->GetMirroredBounds().Contains(point))
    359     return HTCLOSE;
    360   if (size_button_->visible() &&
    361       size_button_->GetMirroredBounds().Contains(point))
    362     return HTMAXBUTTON;
    363 
    364   // Caption is a safe default.
    365   return HTCAPTION;
    366 }
    367 
    368 gfx::Size FramePainter::GetMinimumSize(views::NonClientFrameView* view) {
    369   gfx::Size min_size = frame_->client_view()->GetMinimumSize();
    370   // Ensure we can display the top of the caption area.
    371   gfx::Rect client_bounds = view->GetBoundsForClientView();
    372   min_size.Enlarge(0, client_bounds.y());
    373   // Ensure we have enough space for the window icon and buttons.  We allow
    374   // the title string to collapse to zero width.
    375   int title_width = GetTitleOffsetX() +
    376       size_button_->width() + kSizeButtonOffsetX +
    377       close_button_->width() + kCloseButtonOffsetX;
    378   if (title_width > min_size.width())
    379     min_size.set_width(title_width);
    380   return min_size;
    381 }
    382 
    383 gfx::Size FramePainter::GetMaximumSize(views::NonClientFrameView* view) {
    384   return frame_->client_view()->GetMaximumSize();
    385 }
    386 
    387 int FramePainter::GetRightInset() const {
    388   gfx::Size close_size = close_button_->GetPreferredSize();
    389   gfx::Size size_button_size = size_button_->GetPreferredSize();
    390   int inset = close_size.width() + kCloseButtonOffsetX +
    391       size_button_size.width() + kSizeButtonOffsetX;
    392   return inset;
    393 }
    394 
    395 int FramePainter::GetThemeBackgroundXInset() const {
    396   return kThemeFrameImageInsetX;
    397 }
    398 
    399 bool FramePainter::ShouldUseMinimalHeaderStyle(Themed header_themed) const {
    400   // Use the minimalistic header style whenever |frame_| is maximized or
    401   // fullscreen EXCEPT:
    402   // - If the user has installed a theme with custom images for the header.
    403   // - For windows which are not tracked by the workspace code (which are used
    404   //   for tab dragging).
    405   return ((frame_->IsMaximized() || frame_->IsFullscreen()) &&
    406       header_themed == THEMED_NO &&
    407       GetTrackedByWorkspace(frame_->GetNativeWindow()));
    408 }
    409 
    410 void FramePainter::PaintHeader(views::NonClientFrameView* view,
    411                                gfx::Canvas* canvas,
    412                                HeaderMode header_mode,
    413                                int theme_frame_id,
    414                                int theme_frame_overlay_id) {
    415   bool initial_paint = (previous_theme_frame_id_ == 0);
    416   if (!initial_paint &&
    417       (previous_theme_frame_id_ != theme_frame_id ||
    418        previous_theme_frame_overlay_id_ != theme_frame_overlay_id)) {
    419     aura::Window* parent = frame_->GetNativeWindow()->parent();
    420     // Don't animate the header if the parent (a workspace) is already
    421     // animating. Doing so results in continually painting during the animation
    422     // and gives a slower frame rate.
    423     // TODO(sky): expose a better way to determine this rather than assuming
    424     // the parent is a workspace.
    425     bool parent_animating = parent &&
    426         (parent->layer()->GetAnimator()->IsAnimatingProperty(
    427             ui::LayerAnimationElement::OPACITY) ||
    428          parent->layer()->GetAnimator()->IsAnimatingProperty(
    429              ui::LayerAnimationElement::VISIBILITY));
    430     if (!parent_animating) {
    431       crossfade_animation_.reset(new ui::SlideAnimation(this));
    432       crossfade_theme_frame_id_ = previous_theme_frame_id_;
    433       crossfade_theme_frame_overlay_id_ = previous_theme_frame_overlay_id_;
    434       crossfade_opacity_ = previous_opacity_;
    435       crossfade_animation_->SetSlideDuration(kActivationCrossfadeDurationMs);
    436       crossfade_animation_->Show();
    437     } else {
    438       crossfade_animation_.reset();
    439     }
    440   }
    441 
    442   int opacity =
    443       GetHeaderOpacity(header_mode, theme_frame_id, theme_frame_overlay_id);
    444   ui::ThemeProvider* theme_provider = frame_->GetThemeProvider();
    445   gfx::ImageSkia* theme_frame = theme_provider->GetImageSkiaNamed(
    446       theme_frame_id);
    447   gfx::ImageSkia* theme_frame_overlay = NULL;
    448   if (theme_frame_overlay_id != 0) {
    449     theme_frame_overlay = theme_provider->GetImageSkiaNamed(
    450         theme_frame_overlay_id);
    451   }
    452   header_frame_bounds_ = gfx::Rect(0, 0, view->width(), theme_frame->height());
    453 
    454   int corner_radius = GetHeaderCornerRadius();
    455   SkPaint paint;
    456 
    457   if (crossfade_animation_.get() && crossfade_animation_->is_animating()) {
    458     gfx::ImageSkia* crossfade_theme_frame =
    459         theme_provider->GetImageSkiaNamed(crossfade_theme_frame_id_);
    460     gfx::ImageSkia* crossfade_theme_frame_overlay = NULL;
    461     if (crossfade_theme_frame_overlay_id_ != 0) {
    462       crossfade_theme_frame_overlay = theme_provider->GetImageSkiaNamed(
    463           crossfade_theme_frame_overlay_id_);
    464     }
    465     if (!crossfade_theme_frame ||
    466         (crossfade_theme_frame_overlay_id_ != 0 &&
    467          !crossfade_theme_frame_overlay)) {
    468       // Reset the animation. This case occurs when the user switches the theme
    469       // that they are using.
    470       crossfade_animation_.reset();
    471       paint.setAlpha(opacity);
    472     } else {
    473       double current_value = crossfade_animation_->GetCurrentValue();
    474       int old_alpha = (1 - current_value) * crossfade_opacity_;
    475       int new_alpha = current_value * opacity;
    476 
    477       // Draw the old header background, clipping the corners to be rounded.
    478       paint.setAlpha(old_alpha);
    479       paint.setXfermodeMode(SkXfermode::kPlus_Mode);
    480       PaintFrameImagesInRoundRect(canvas,
    481                                   crossfade_theme_frame,
    482                                   crossfade_theme_frame_overlay,
    483                                   paint,
    484                                   header_frame_bounds_,
    485                                   corner_radius,
    486                                   GetThemeBackgroundXInset());
    487 
    488       paint.setAlpha(new_alpha);
    489     }
    490   } else {
    491     paint.setAlpha(opacity);
    492   }
    493 
    494   // Draw the header background, clipping the corners to be rounded.
    495   PaintFrameImagesInRoundRect(canvas,
    496                               theme_frame,
    497                               theme_frame_overlay,
    498                               paint,
    499                               header_frame_bounds_,
    500                               corner_radius,
    501                               GetThemeBackgroundXInset());
    502 
    503   previous_theme_frame_id_ = theme_frame_id;
    504   previous_theme_frame_overlay_id_ = theme_frame_overlay_id;
    505   previous_opacity_ = opacity;
    506 
    507   // Separator between the maximize and close buttons.  It overlaps the left
    508   // edge of the close button.
    509   gfx::Rect divider(close_button_->x(), close_button_->y(),
    510                     button_separator_->width(), close_button_->height());
    511   canvas->DrawImageInt(*button_separator_,
    512                        view->GetMirroredXForRect(divider),
    513                        close_button_->y());
    514 
    515   // We don't need the extra lightness in the edges when we're at the top edge
    516   // of the screen or when the header's corners are not rounded.
    517   //
    518   // TODO(sky): this isn't quite right. What we really want is a method that
    519   // returns bounds ignoring transforms on certain windows (such as workspaces)
    520   // and is relative to the root.
    521   if (frame_->GetNativeWindow()->bounds().y() == 0 || corner_radius == 0)
    522     return;
    523 
    524   // Draw the top corners and edge.
    525   int top_left_height = top_left_corner_->height();
    526   canvas->DrawImageInt(*top_left_corner_,
    527                        0, 0, top_left_corner_->width(), top_left_height,
    528                        0, 0, top_left_corner_->width(), top_left_height,
    529                        false);
    530   canvas->TileImageInt(*top_edge_,
    531       top_left_corner_->width(),
    532       0,
    533       view->width() - top_left_corner_->width() - top_right_corner_->width(),
    534       top_edge_->height());
    535   int top_right_height = top_right_corner_->height();
    536   canvas->DrawImageInt(*top_right_corner_,
    537                        0, 0,
    538                        top_right_corner_->width(), top_right_height,
    539                        view->width() - top_right_corner_->width(), 0,
    540                        top_right_corner_->width(), top_right_height,
    541                        false);
    542 
    543   // Header left edge.
    544   int header_left_height = theme_frame->height() - top_left_height;
    545   canvas->TileImageInt(*header_left_edge_,
    546                        0, top_left_height,
    547                        header_left_edge_->width(), header_left_height);
    548 
    549   // Header right edge.
    550   int header_right_height = theme_frame->height() - top_right_height;
    551   canvas->TileImageInt(*header_right_edge_,
    552                        view->width() - header_right_edge_->width(),
    553                        top_right_height,
    554                        header_right_edge_->width(),
    555                        header_right_height);
    556 
    557   // We don't draw edges around the content area.  Web content goes flush
    558   // to the edge of the window.
    559 }
    560 
    561 void FramePainter::PaintHeaderContentSeparator(views::NonClientFrameView* view,
    562                                                gfx::Canvas* canvas) {
    563   // Paint the line just above the content area.
    564   gfx::Rect client_bounds = view->GetBoundsForClientView();
    565   canvas->FillRect(gfx::Rect(client_bounds.x(),
    566                              client_bounds.y() - kHeaderContentSeparatorSize,
    567                              client_bounds.width(),
    568                              kHeaderContentSeparatorSize),
    569                    kHeaderContentSeparatorColor);
    570 }
    571 
    572 int FramePainter::HeaderContentSeparatorSize() const {
    573   return kHeaderContentSeparatorSize;
    574 }
    575 
    576 void FramePainter::PaintTitleBar(views::NonClientFrameView* view,
    577                                  gfx::Canvas* canvas,
    578                                  const gfx::Font& title_font) {
    579   // The window icon is painted by its own views::View.
    580   views::WidgetDelegate* delegate = frame_->widget_delegate();
    581   if (delegate && delegate->ShouldShowWindowTitle()) {
    582     gfx::Rect title_bounds = GetTitleBounds(title_font);
    583     SkColor title_color = frame_->IsMaximized() ?
    584         kMaximizedWindowTitleTextColor : kNonMaximizedWindowTitleTextColor;
    585     canvas->DrawStringInt(delegate->GetWindowTitle(),
    586                           title_font,
    587                           title_color,
    588                           view->GetMirroredXForRect(title_bounds),
    589                           title_bounds.y(),
    590                           title_bounds.width(),
    591                           title_bounds.height(),
    592                           gfx::Canvas::NO_SUBPIXEL_RENDERING);
    593   }
    594 }
    595 
    596 void FramePainter::LayoutHeader(views::NonClientFrameView* view,
    597                                 bool shorter_layout) {
    598   // The new assets only make sense if the window is actually maximized or
    599   // fullscreen.
    600   if (shorter_layout &&
    601       (frame_->IsMaximized() || frame_->IsFullscreen()) &&
    602       GetTrackedByWorkspace(frame_->GetNativeWindow())) {
    603     SetButtonImages(close_button_,
    604                     IDR_AURA_WINDOW_MAXIMIZED_CLOSE2,
    605                     IDR_AURA_WINDOW_MAXIMIZED_CLOSE2_H,
    606                     IDR_AURA_WINDOW_MAXIMIZED_CLOSE2_P);
    607     // The chat window cannot be restored but only minimized.
    608     if (size_button_behavior_ == SIZE_BUTTON_MINIMIZES) {
    609       SetButtonImages(size_button_,
    610                       IDR_AURA_WINDOW_MINIMIZE_SHORT,
    611                       IDR_AURA_WINDOW_MINIMIZE_SHORT_H,
    612                       IDR_AURA_WINDOW_MINIMIZE_SHORT_P);
    613     } else {
    614       SetButtonImages(size_button_,
    615                       IDR_AURA_WINDOW_MAXIMIZED_RESTORE2,
    616                       IDR_AURA_WINDOW_MAXIMIZED_RESTORE2_H,
    617                       IDR_AURA_WINDOW_MAXIMIZED_RESTORE2_P);
    618     }
    619   } else if (shorter_layout) {
    620     SetButtonImages(close_button_,
    621                     IDR_AURA_WINDOW_MAXIMIZED_CLOSE,
    622                     IDR_AURA_WINDOW_MAXIMIZED_CLOSE_H,
    623                     IDR_AURA_WINDOW_MAXIMIZED_CLOSE_P);
    624     // The chat window cannot be restored but only minimized.
    625     if (size_button_behavior_ == SIZE_BUTTON_MINIMIZES) {
    626       SetButtonImages(size_button_,
    627                       IDR_AURA_WINDOW_MINIMIZE_SHORT,
    628                       IDR_AURA_WINDOW_MINIMIZE_SHORT_H,
    629                       IDR_AURA_WINDOW_MINIMIZE_SHORT_P);
    630     } else {
    631       SetButtonImages(size_button_,
    632                       IDR_AURA_WINDOW_MAXIMIZED_RESTORE,
    633                       IDR_AURA_WINDOW_MAXIMIZED_RESTORE_H,
    634                       IDR_AURA_WINDOW_MAXIMIZED_RESTORE_P);
    635     }
    636   } else {
    637     SetButtonImages(close_button_,
    638                     IDR_AURA_WINDOW_CLOSE,
    639                     IDR_AURA_WINDOW_CLOSE_H,
    640                     IDR_AURA_WINDOW_CLOSE_P);
    641     SetButtonImages(size_button_,
    642                     IDR_AURA_WINDOW_MAXIMIZE,
    643                     IDR_AURA_WINDOW_MAXIMIZE_H,
    644                     IDR_AURA_WINDOW_MAXIMIZE_P);
    645   }
    646 
    647   gfx::Size close_size = close_button_->GetPreferredSize();
    648   close_button_->SetBounds(
    649       view->width() - close_size.width() - kCloseButtonOffsetX,
    650       kCloseButtonOffsetY,
    651       close_size.width(),
    652       close_size.height());
    653 
    654   gfx::Size size_button_size = size_button_->GetPreferredSize();
    655   size_button_->SetBounds(
    656       close_button_->x() - size_button_size.width() - kSizeButtonOffsetX,
    657       close_button_->y(),
    658       size_button_size.width(),
    659       size_button_size.height());
    660 
    661   if (window_icon_) {
    662     // Vertically center the window icon with respect to the close button.
    663     int icon_offset_y = GetCloseButtonCenterY() - window_icon_->height() / 2;
    664     window_icon_->SetBounds(kIconOffsetX, icon_offset_y, kIconSize, kIconSize);
    665   }
    666 }
    667 
    668 void FramePainter::SchedulePaintForTitle(const gfx::Font& title_font) {
    669   frame_->non_client_view()->SchedulePaintInRect(GetTitleBounds(title_font));
    670 }
    671 
    672 void FramePainter::OnThemeChanged() {
    673   // We do not cache the images for |previous_theme_frame_id_| and
    674   // |previous_theme_frame_overlay_id_|. Changing the theme changes the images
    675   // returned from ui::ThemeProvider for |previous_theme_frame_id_|
    676   // and |previous_theme_frame_overlay_id_|. Reset the image ids to prevent
    677   // starting a crossfade animation with these images.
    678   previous_theme_frame_id_ = 0;
    679   previous_theme_frame_overlay_id_ = 0;
    680 
    681   if (crossfade_animation_.get() && crossfade_animation_->is_animating()) {
    682     crossfade_animation_.reset();
    683     frame_->non_client_view()->SchedulePaintInRect(header_frame_bounds_);
    684   }
    685 }
    686 
    687 ///////////////////////////////////////////////////////////////////////////////
    688 // aura::WindowObserver overrides:
    689 
    690 void FramePainter::OnWindowPropertyChanged(aura::Window* window,
    691                                            const void* key,
    692                                            intptr_t old) {
    693   // When 'kWindowTrackedByWorkspaceKey' changes, we are going to paint the
    694   // header differently. Schedule a paint to ensure everything is updated
    695   // correctly.
    696   if (key == internal::kWindowTrackedByWorkspaceKey &&
    697       GetTrackedByWorkspace(window)) {
    698     frame_->non_client_view()->SchedulePaint();
    699   }
    700 
    701   if (key != aura::client::kShowStateKey)
    702     return;
    703 
    704   // Maximized and fullscreen windows don't want resize handles overlapping the
    705   // content area, because when the user moves the cursor to the right screen
    706   // edge we want them to be able to hit the scroll bar.
    707   if (ash::wm::IsWindowMaximized(window) ||
    708       ash::wm::IsWindowFullscreen(window)) {
    709     window->set_hit_test_bounds_override_inner(gfx::Insets());
    710   } else {
    711     window->set_hit_test_bounds_override_inner(
    712         gfx::Insets(kResizeInsideBoundsSize, kResizeInsideBoundsSize,
    713                     kResizeInsideBoundsSize, kResizeInsideBoundsSize));
    714   }
    715 }
    716 
    717 void FramePainter::OnWindowVisibilityChanged(aura::Window* window,
    718                                              bool visible) {
    719   // OnWindowVisibilityChanged can be called for the child windows of |window_|.
    720   if (window != window_)
    721     return;
    722 
    723   // Window visibility change may trigger the change of window solo-ness in a
    724   // different window.
    725   UpdateSoloWindowInRoot(window_->GetRootWindow(), visible ? NULL : window_);
    726 }
    727 
    728 void FramePainter::OnWindowDestroying(aura::Window* destroying) {
    729   DCHECK_EQ(window_, destroying);
    730 
    731   // Must be removed here and not in the destructor, as the aura::Window is
    732   // already destroyed when our destructor runs.
    733   window_->RemoveObserver(this);
    734 
    735   // If we have two or more windows open and we close this one, we might trigger
    736   // the solo window appearance for another window.
    737   UpdateSoloWindowInRoot(window_->GetRootWindow(), window_);
    738 
    739   window_ = NULL;
    740 }
    741 
    742 void FramePainter::OnWindowBoundsChanged(aura::Window* window,
    743                                          const gfx::Rect& old_bounds,
    744                                          const gfx::Rect& new_bounds) {
    745   // TODO(sky): this isn't quite right. What we really want is a method that
    746   // returns bounds ignoring transforms on certain windows (such as workspaces).
    747   if ((!frame_->IsMaximized() && !frame_->IsFullscreen()) &&
    748       ((old_bounds.y() == 0 && new_bounds.y() != 0) ||
    749        (old_bounds.y() != 0 && new_bounds.y() == 0))) {
    750     SchedulePaintForHeader();
    751   }
    752 }
    753 
    754 void FramePainter::OnWindowAddedToRootWindow(aura::Window* window) {
    755   // Needs to trigger the window appearance change if the window moves across
    756   // root windows and a solo window is already in the new root.
    757   UpdateSoloWindowInRoot(window->GetRootWindow(), NULL /* ignore_window */);
    758 }
    759 
    760 void FramePainter::OnWindowRemovingFromRootWindow(aura::Window* window) {
    761   // Needs to trigger the window appearance change if the window moves across
    762   // root windows and only one window is left in the previous root.  Because
    763   // |window| is not yet moved, |window| has to be ignored.
    764   UpdateSoloWindowInRoot(window->GetRootWindow(), window);
    765 }
    766 
    767 ///////////////////////////////////////////////////////////////////////////////
    768 // ui::AnimationDelegate overrides:
    769 
    770 void FramePainter::AnimationProgressed(const ui::Animation* animation) {
    771   frame_->non_client_view()->SchedulePaintInRect(header_frame_bounds_);
    772 }
    773 
    774 ///////////////////////////////////////////////////////////////////////////////
    775 // FramePainter, private:
    776 
    777 void FramePainter::SetButtonImages(views::ImageButton* button,
    778                                    int normal_image_id,
    779                                    int hot_image_id,
    780                                    int pushed_image_id) {
    781   ui::ThemeProvider* theme_provider = frame_->GetThemeProvider();
    782   button->SetImage(views::CustomButton::STATE_NORMAL,
    783                    theme_provider->GetImageSkiaNamed(normal_image_id));
    784   button->SetImage(views::CustomButton::STATE_HOVERED,
    785                    theme_provider->GetImageSkiaNamed(hot_image_id));
    786   button->SetImage(views::CustomButton::STATE_PRESSED,
    787                    theme_provider->GetImageSkiaNamed(pushed_image_id));
    788 }
    789 
    790 void FramePainter::SetToggledButtonImages(views::ToggleImageButton* button,
    791                                           int normal_image_id,
    792                                           int hot_image_id,
    793                                           int pushed_image_id) {
    794   ui::ThemeProvider* theme_provider = frame_->GetThemeProvider();
    795   button->SetToggledImage(views::CustomButton::STATE_NORMAL,
    796                           theme_provider->GetImageSkiaNamed(normal_image_id));
    797   button->SetToggledImage(views::CustomButton::STATE_HOVERED,
    798                           theme_provider->GetImageSkiaNamed(hot_image_id));
    799   button->SetToggledImage(views::CustomButton::STATE_PRESSED,
    800                           theme_provider->GetImageSkiaNamed(pushed_image_id));
    801 }
    802 
    803 int FramePainter::GetTitleOffsetX() const {
    804   return window_icon_ ?
    805       window_icon_->bounds().right() + kTitleIconOffsetX :
    806       kTitleNoIconOffsetX;
    807 }
    808 
    809 int FramePainter::GetCloseButtonCenterY() const {
    810   return close_button_->y() + close_button_->height() / 2;
    811 }
    812 
    813 int FramePainter::GetHeaderCornerRadius() const {
    814   // Use square corners for maximized and fullscreen windows when they are
    815   // tracked by the workspace code. (Windows which are not tracked by the
    816   // workspace code are used for tab dragging.)
    817   bool square_corners = ((frame_->IsMaximized() || frame_->IsFullscreen())) &&
    818       GetTrackedByWorkspace(frame_->GetNativeWindow());
    819   const int kCornerRadius = 2;
    820   return square_corners ? 0 : kCornerRadius;
    821 }
    822 
    823 int FramePainter::GetHeaderOpacity(
    824     HeaderMode header_mode,
    825     int theme_frame_id,
    826     int theme_frame_overlay_id) const {
    827   // User-provided themes are painted fully opaque.
    828   ui::ThemeProvider* theme_provider = frame_->GetThemeProvider();
    829   if (theme_provider->HasCustomImage(theme_frame_id) ||
    830       (theme_frame_overlay_id != 0 &&
    831        theme_provider->HasCustomImage(theme_frame_overlay_id))) {
    832     return kFullyOpaque;
    833   }
    834 
    835   // The header is fully opaque when using the minimalistic header style.
    836   if (ShouldUseMinimalHeaderStyle(THEMED_NO))
    837     return kFullyOpaque;
    838 
    839   // Single browser window is very transparent.
    840   if (UseSoloWindowHeader())
    841     return kSoloWindowOpacity;
    842 
    843   // Otherwise, change transparency based on window activation status.
    844   if (header_mode == ACTIVE)
    845     return kActiveWindowOpacity;
    846   return kInactiveWindowOpacity;
    847 }
    848 
    849 bool FramePainter::UseSoloWindowHeader() const {
    850   // Don't use transparent headers for panels, pop-ups, etc.
    851   if (!IsSoloWindowHeaderCandidate(window_))
    852     return false;
    853   aura::RootWindow* root = window_->GetRootWindow();
    854   if (!root || root->GetProperty(internal::kIgnoreSoloWindowFramePainterPolicy))
    855     return false;
    856   // Don't recompute every time, as it would require many window property
    857   // lookups.
    858   return root->GetProperty(internal::kSoloWindowHeaderKey);
    859 }
    860 
    861 // static
    862 bool FramePainter::UseSoloWindowHeaderInRoot(RootWindow* root_window,
    863                                              Window* ignore_window) {
    864   int visible_window_count = 0;
    865   std::vector<Window*> windows = GetWindowsForSoloHeaderUpdate(root_window);
    866   for (std::vector<Window*>::const_iterator it = windows.begin();
    867        it != windows.end();
    868        ++it) {
    869     Window* window = *it;
    870     // Various sorts of windows "don't count" for this computation.
    871     if (ignore_window == window ||
    872         !IsSoloWindowHeaderCandidate(window) ||
    873         !IsVisibleToRoot(window))
    874       continue;
    875     if (wm::IsWindowMaximized(window))
    876       return false;
    877     ++visible_window_count;
    878     if (visible_window_count > 1)
    879       return false;
    880   }
    881   // Count must be tested because all windows might be "don't count" windows
    882   // in the loop above.
    883   return visible_window_count == 1;
    884 }
    885 
    886 // static
    887 void FramePainter::UpdateSoloWindowInRoot(RootWindow* root,
    888                                           Window* ignore_window) {
    889 #if defined(OS_WIN)
    890   // Non-Ash Windows doesn't do solo-window counting for transparency effects,
    891   // as the desktop background and window frames are managed by the OS.
    892   if (!ash::Shell::HasInstance())
    893     return;
    894 #endif
    895   if (!root)
    896     return;
    897   bool old_solo_header = root->GetProperty(internal::kSoloWindowHeaderKey);
    898   bool new_solo_header = UseSoloWindowHeaderInRoot(root, ignore_window);
    899   if (old_solo_header == new_solo_header)
    900     return;
    901   root->SetProperty(internal::kSoloWindowHeaderKey, new_solo_header);
    902   // Invalidate all the window frames in the desktop. There should only be
    903   // a few.
    904   std::vector<Window*> windows = GetWindowsForSoloHeaderUpdate(root);
    905   for (std::vector<Window*>::const_iterator it = windows.begin();
    906        it != windows.end();
    907        ++it) {
    908     Widget* widget = Widget::GetWidgetForNativeWindow(*it);
    909     if (widget && widget->non_client_view())
    910       widget->non_client_view()->SchedulePaint();
    911   }
    912 }
    913 
    914 void FramePainter::SchedulePaintForHeader() {
    915   int top_left_height = top_left_corner_->height();
    916   int top_right_height = top_right_corner_->height();
    917   frame_->non_client_view()->SchedulePaintInRect(
    918       gfx::Rect(0, 0, frame_->non_client_view()->width(),
    919                 std::max(top_left_height, top_right_height)));
    920 }
    921 
    922 gfx::Rect FramePainter::GetTitleBounds(const gfx::Font& title_font) {
    923   int title_x = GetTitleOffsetX();
    924   // Center the text with respect to the close button. This way it adapts to
    925   // the caption height and aligns exactly with the window icon. Don't use
    926   // |window_icon_| for this computation as it may be NULL.
    927   int title_y = GetCloseButtonCenterY() - title_font.GetHeight() / 2;
    928   return gfx::Rect(
    929       title_x,
    930       std::max(0, title_y),
    931       std::max(0, size_button_->x() - kTitleLogoSpacing - title_x),
    932       title_font.GetHeight());
    933 }
    934 
    935 }  // namespace ash
    936