- add sources.
[platform/framework/web/crosswalk.git] / src / ash / wm / header_painter.cc
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/ash_constants.h"
10 #include "ash/root_window_controller.h"
11 #include "ash/root_window_settings.h"
12 #include "ash/shell.h"
13 #include "ash/shell_window_ids.h"
14 #include "ash/wm/caption_buttons/frame_caption_button_container_view.h"
15 #include "ash/wm/window_state.h"
16 #include "ash/wm/window_util.h"
17 #include "base/logging.h"  // DCHECK
18 #include "grit/ash_resources.h"
19 #include "third_party/skia/include/core/SkCanvas.h"
20 #include "third_party/skia/include/core/SkColor.h"
21 #include "third_party/skia/include/core/SkPaint.h"
22 #include "third_party/skia/include/core/SkPath.h"
23 #include "ui/aura/client/aura_constants.h"
24 #include "ui/aura/env.h"
25 #include "ui/aura/window.h"
26 #include "ui/base/hit_test.h"
27 #include "ui/base/layout.h"
28 #include "ui/base/resource/resource_bundle.h"
29 #include "ui/base/theme_provider.h"
30 #include "ui/gfx/animation/slide_animation.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/widget/widget.h"
37 #include "ui/views/widget/widget_delegate.h"
38
39 using aura::RootWindow;
40 using aura::Window;
41 using views::Widget;
42
43 namespace {
44 // Space between left edge of window and popup window icon.
45 const int kIconOffsetX = 9;
46 // Height and width of window icon.
47 const int kIconSize = 16;
48 // Space between the title text and the caption buttons.
49 const int kTitleLogoSpacing = 5;
50 // Space between window icon and title text.
51 const int kTitleIconOffsetX = 5;
52 // Space between window edge and title text, when there is no icon.
53 const int kTitleNoIconOffsetX = 8;
54 // Color for the non-maximized window title text.
55 const SkColor kNonMaximizedWindowTitleTextColor = SkColorSetRGB(40, 40, 40);
56 // Color for the maximized window title text.
57 const SkColor kMaximizedWindowTitleTextColor = SK_ColorWHITE;
58 // Size of header/content separator line below the header image.
59 const int kHeaderContentSeparatorSize = 1;
60 // Color of header bottom edge line.
61 const SkColor kHeaderContentSeparatorColor = SkColorSetRGB(128, 128, 128);
62 // In the pre-Ash era the web content area had a frame along the left edge, so
63 // user-generated theme images for the new tab page assume they are shifted
64 // right relative to the header.  Now that we have removed the left edge frame
65 // we need to copy the theme image for the window header from a few pixels
66 // inset to preserve alignment with the NTP image, or else we'll break a bunch
67 // of existing themes.  We do something similar on OS X for the same reason.
68 const int kThemeFrameImageInsetX = 5;
69 // Duration of crossfade animation for activating and deactivating frame.
70 const int kActivationCrossfadeDurationMs = 200;
71 // Alpha/opacity value for fully-opaque headers.
72 const int kFullyOpaque = 255;
73
74 // A flag to enable/disable solo window header.
75 bool solo_window_header_enabled = true;
76
77 // Tiles an image into an area, rounding the top corners. Samples |image|
78 // starting |image_inset_x| pixels from the left of the image.
79 void TileRoundRect(gfx::Canvas* canvas,
80                    const gfx::ImageSkia& image,
81                    const SkPaint& paint,
82                    const gfx::Rect& bounds,
83                    int top_left_corner_radius,
84                    int top_right_corner_radius,
85                    int image_inset_x) {
86   SkRect rect = gfx::RectToSkRect(bounds);
87   const SkScalar kTopLeftRadius = SkIntToScalar(top_left_corner_radius);
88   const SkScalar kTopRightRadius = SkIntToScalar(top_right_corner_radius);
89   SkScalar radii[8] = {
90       kTopLeftRadius, kTopLeftRadius,  // top-left
91       kTopRightRadius, kTopRightRadius,  // top-right
92       0, 0,   // bottom-right
93       0, 0};  // bottom-left
94   SkPath path;
95   path.addRoundRect(rect, radii, SkPath::kCW_Direction);
96   canvas->DrawImageInPath(image, -image_inset_x, 0, path, paint);
97 }
98
99 // Tiles |frame_image| and |frame_overlay_image| into an area, rounding the top
100 // corners.
101 void PaintFrameImagesInRoundRect(gfx::Canvas* canvas,
102                                  const gfx::ImageSkia* frame_image,
103                                  const gfx::ImageSkia* frame_overlay_image,
104                                  const SkPaint& paint,
105                                  const gfx::Rect& bounds,
106                                  int corner_radius,
107                                  int image_inset_x) {
108   SkXfermode::Mode normal_mode;
109   SkXfermode::AsMode(NULL, &normal_mode);
110
111   // If |paint| is using an unusual SkXfermode::Mode (this is the case while
112   // crossfading), we must create a new canvas to overlay |frame_image| and
113   // |frame_overlay_image| using |normal_mode| and then paint the result
114   // using the unusual mode. We try to avoid this because creating a new
115   // browser-width canvas is expensive.
116   bool fast_path = (!frame_overlay_image ||
117       SkXfermode::IsMode(paint.getXfermode(), normal_mode));
118   if (fast_path) {
119     TileRoundRect(canvas, *frame_image, paint, bounds, corner_radius,
120         corner_radius, image_inset_x);
121
122     if (frame_overlay_image) {
123       // Adjust |bounds| such that |frame_overlay_image| is not tiled.
124       gfx::Rect overlay_bounds = bounds;
125       overlay_bounds.Intersect(
126           gfx::Rect(bounds.origin(), frame_overlay_image->size()));
127       int top_left_corner_radius = corner_radius;
128       int top_right_corner_radius = corner_radius;
129       if (overlay_bounds.width() < bounds.width() - corner_radius)
130         top_right_corner_radius = 0;
131       TileRoundRect(canvas, *frame_overlay_image, paint, overlay_bounds,
132           top_left_corner_radius, top_right_corner_radius, 0);
133     }
134   } else {
135     gfx::Canvas temporary_canvas(bounds.size(), canvas->image_scale(), false);
136     temporary_canvas.TileImageInt(*frame_image,
137                                   image_inset_x, 0,
138                                   0, 0,
139                                   bounds.width(), bounds.height());
140     temporary_canvas.DrawImageInt(*frame_overlay_image, 0, 0);
141     TileRoundRect(canvas, gfx::ImageSkia(temporary_canvas.ExtractImageRep()),
142         paint, bounds, corner_radius, corner_radius, 0);
143   }
144 }
145
146 // Returns true if |child| and all ancestors are visible. Useful to ensure that
147 // a window is individually visible and is not part of a hidden workspace.
148 bool IsVisibleToRoot(Window* child) {
149   for (Window* window = child; window; window = window->parent()) {
150     // We must use TargetVisibility() because windows animate in and out and
151     // IsVisible() also tracks the layer visibility state.
152     if (!window->TargetVisibility())
153       return false;
154   }
155   return true;
156 }
157
158 // Returns true if |window| is a "normal" window for purposes of solo window
159 // computations. Returns false for windows that are:
160 // * Not drawn (for example, DragDropTracker uses one for mouse capture)
161 // * Modal alerts (it looks odd for headers to change when an alert opens)
162 // * Constrained windows (ditto)
163 bool IsSoloWindowHeaderCandidate(aura::Window* window) {
164   return window &&
165       window->type() == aura::client::WINDOW_TYPE_NORMAL &&
166       window->layer() &&
167       window->layer()->type() != ui::LAYER_NOT_DRAWN &&
168       window->GetProperty(aura::client::kModalKey) == ui::MODAL_TYPE_NONE &&
169       !window->GetProperty(ash::kConstrainedWindowKey);
170 }
171
172 // Returns a list of windows in |root_window|| that potentially could have
173 // a transparent solo-window header.
174 std::vector<Window*> GetWindowsForSoloHeaderUpdate(Window* root_window) {
175   std::vector<Window*> windows;
176   // Avoid memory allocations for typical window counts.
177   windows.reserve(16);
178   // Collect windows from the desktop.
179   Window* desktop = ash::Shell::GetContainer(
180       root_window, ash::internal::kShellWindowId_DefaultContainer);
181   windows.insert(windows.end(),
182                  desktop->children().begin(),
183                  desktop->children().end());
184   // Collect "always on top" windows.
185   Window* top_container =
186       ash::Shell::GetContainer(
187           root_window, ash::internal::kShellWindowId_AlwaysOnTopContainer);
188   windows.insert(windows.end(),
189                  top_container->children().begin(),
190                  top_container->children().end());
191   return windows;
192 }
193 }  // namespace
194
195 namespace ash {
196
197 // static
198 int HeaderPainter::kActiveWindowOpacity = 255;  // 1.0
199 int HeaderPainter::kInactiveWindowOpacity = 255;  // 1.0
200 int HeaderPainter::kSoloWindowOpacity = 77;  // 0.3
201
202 ///////////////////////////////////////////////////////////////////////////////
203 // HeaderPainter, public:
204
205 HeaderPainter::HeaderPainter()
206     : frame_(NULL),
207       header_view_(NULL),
208       window_icon_(NULL),
209       caption_button_container_(NULL),
210       window_(NULL),
211       header_height_(0),
212       top_left_corner_(NULL),
213       top_edge_(NULL),
214       top_right_corner_(NULL),
215       header_left_edge_(NULL),
216       header_right_edge_(NULL),
217       previous_theme_frame_id_(0),
218       previous_theme_frame_overlay_id_(0),
219       previous_opacity_(0),
220       crossfade_theme_frame_id_(0),
221       crossfade_theme_frame_overlay_id_(0),
222       crossfade_opacity_(0) {}
223
224 HeaderPainter::~HeaderPainter() {
225   // Sometimes we are destroyed before the window closes, so ensure we clean up.
226   if (window_) {
227     window_->RemoveObserver(this);
228     wm::GetWindowState(window_)->RemoveObserver(this);
229   }
230 }
231
232 void HeaderPainter::Init(
233     views::Widget* frame,
234     views::View* header_view,
235     views::View* window_icon,
236     FrameCaptionButtonContainerView* caption_button_container) {
237   DCHECK(frame);
238   DCHECK(header_view);
239   // window_icon may be NULL.
240   DCHECK(caption_button_container);
241   frame_ = frame;
242   header_view_ = header_view;
243   window_icon_ = window_icon;
244   caption_button_container_ = caption_button_container;
245
246   // Window frame image parts.
247   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
248   top_left_corner_ =
249       rb.GetImageNamed(IDR_AURA_WINDOW_HEADER_SHADE_TOP_LEFT).ToImageSkia();
250   top_edge_ =
251       rb.GetImageNamed(IDR_AURA_WINDOW_HEADER_SHADE_TOP).ToImageSkia();
252   top_right_corner_ =
253       rb.GetImageNamed(IDR_AURA_WINDOW_HEADER_SHADE_TOP_RIGHT).ToImageSkia();
254   header_left_edge_ =
255       rb.GetImageNamed(IDR_AURA_WINDOW_HEADER_SHADE_LEFT).ToImageSkia();
256   header_right_edge_ =
257       rb.GetImageNamed(IDR_AURA_WINDOW_HEADER_SHADE_RIGHT).ToImageSkia();
258
259   window_ = frame->GetNativeWindow();
260
261   // Observer removes itself in OnWindowDestroying() below, or in the destructor
262   // if we go away before the window.
263   window_->AddObserver(this);
264   wm::GetWindowState(window_)->AddObserver(this);
265
266   // Solo-window header updates are handled by the WorkspaceLayoutManager when
267   // this window is added to the desktop.
268 }
269
270 // static
271 void HeaderPainter::SetSoloWindowHeadersEnabled(bool enabled) {
272   solo_window_header_enabled = enabled;
273 }
274
275 // static
276 void HeaderPainter::UpdateSoloWindowHeader(Window* root_window) {
277   // Use a separate function here so callers outside of HeaderPainter don't need
278   // to know about "ignorable_window".
279   UpdateSoloWindowInRoot(root_window, NULL /* ignorable_window */);
280 }
281
282 // static
283 gfx::Rect HeaderPainter::GetBoundsForClientView(
284     int header_height,
285     const gfx::Rect& window_bounds) {
286   gfx::Rect client_bounds(window_bounds);
287   client_bounds.Inset(0, header_height, 0, 0);
288   return client_bounds;
289 }
290
291 // static
292 gfx::Rect HeaderPainter::GetWindowBoundsForClientBounds(
293       int header_height,
294       const gfx::Rect& client_bounds) {
295   gfx::Rect window_bounds(client_bounds);
296   window_bounds.Inset(0, -header_height, 0, 0);
297   if (window_bounds.y() < 0)
298     window_bounds.set_y(0);
299   return window_bounds;
300 }
301
302 int HeaderPainter::NonClientHitTest(const gfx::Point& point) const {
303   gfx::Point point_in_header_view(point);
304   views::View::ConvertPointFromWidget(header_view_, &point_in_header_view);
305   if (!GetHeaderLocalBounds().Contains(point_in_header_view))
306     return HTNOWHERE;
307   if (caption_button_container_->visible()) {
308     gfx::Point point_in_caption_button_container(point);
309     views::View::ConvertPointFromWidget(caption_button_container_,
310         &point_in_caption_button_container);
311     int component = caption_button_container_->NonClientHitTest(
312         point_in_caption_button_container);
313     if (component != HTNOWHERE)
314       return component;
315   }
316   // Caption is a safe default.
317   return HTCAPTION;
318 }
319
320 int HeaderPainter::GetMinimumHeaderWidth() const {
321   // Ensure we have enough space for the window icon and buttons. We allow
322   // the title string to collapse to zero width.
323   return GetTitleOffsetX() +
324       caption_button_container_->GetMinimumSize().width();
325 }
326
327 int HeaderPainter::GetRightInset() const {
328   return caption_button_container_->GetPreferredSize().width();
329 }
330
331 int HeaderPainter::GetThemeBackgroundXInset() const {
332   return kThemeFrameImageInsetX;
333 }
334
335 bool HeaderPainter::ShouldUseMinimalHeaderStyle(Themed header_themed) const {
336   // Use the minimalistic header style whenever |frame_| is maximized or
337   // fullscreen EXCEPT:
338   // - If the user has installed a theme with custom images for the header.
339   // - For windows which are not tracked by the workspace code (which are used
340   //   for tab dragging).
341   return (frame_->IsMaximized() || frame_->IsFullscreen()) &&
342       header_themed == THEMED_NO &&
343       wm::GetWindowState(frame_->GetNativeWindow())->tracked_by_workspace();
344 }
345
346 void HeaderPainter::PaintHeader(gfx::Canvas* canvas,
347                                 HeaderMode header_mode,
348                                 int theme_frame_id,
349                                 int theme_frame_overlay_id) {
350   bool initial_paint = (previous_theme_frame_id_ == 0);
351   if (!initial_paint &&
352       (previous_theme_frame_id_ != theme_frame_id ||
353        previous_theme_frame_overlay_id_ != theme_frame_overlay_id)) {
354     aura::Window* parent = frame_->GetNativeWindow()->parent();
355     // Don't animate the header if the parent (a workspace) is already
356     // animating. Doing so results in continually painting during the animation
357     // and gives a slower frame rate.
358     // TODO(sky): expose a better way to determine this rather than assuming
359     // the parent is a workspace.
360     bool parent_animating = parent &&
361         (parent->layer()->GetAnimator()->IsAnimatingProperty(
362             ui::LayerAnimationElement::OPACITY) ||
363          parent->layer()->GetAnimator()->IsAnimatingProperty(
364              ui::LayerAnimationElement::VISIBILITY));
365     if (!parent_animating) {
366       crossfade_animation_.reset(new gfx::SlideAnimation(this));
367       crossfade_theme_frame_id_ = previous_theme_frame_id_;
368       crossfade_theme_frame_overlay_id_ = previous_theme_frame_overlay_id_;
369       crossfade_opacity_ = previous_opacity_;
370       crossfade_animation_->SetSlideDuration(kActivationCrossfadeDurationMs);
371       crossfade_animation_->Show();
372     } else {
373       crossfade_animation_.reset();
374     }
375   }
376
377   int opacity =
378       GetHeaderOpacity(header_mode, theme_frame_id, theme_frame_overlay_id);
379   ui::ThemeProvider* theme_provider = frame_->GetThemeProvider();
380   gfx::ImageSkia* theme_frame = theme_provider->GetImageSkiaNamed(
381       theme_frame_id);
382   gfx::ImageSkia* theme_frame_overlay = NULL;
383   if (theme_frame_overlay_id != 0) {
384     theme_frame_overlay = theme_provider->GetImageSkiaNamed(
385         theme_frame_overlay_id);
386   }
387
388   int corner_radius = GetHeaderCornerRadius();
389   SkPaint paint;
390
391   if (crossfade_animation_.get() && crossfade_animation_->is_animating()) {
392     gfx::ImageSkia* crossfade_theme_frame =
393         theme_provider->GetImageSkiaNamed(crossfade_theme_frame_id_);
394     gfx::ImageSkia* crossfade_theme_frame_overlay = NULL;
395     if (crossfade_theme_frame_overlay_id_ != 0) {
396       crossfade_theme_frame_overlay = theme_provider->GetImageSkiaNamed(
397           crossfade_theme_frame_overlay_id_);
398     }
399     if (!crossfade_theme_frame ||
400         (crossfade_theme_frame_overlay_id_ != 0 &&
401          !crossfade_theme_frame_overlay)) {
402       // Reset the animation. This case occurs when the user switches the theme
403       // that they are using.
404       crossfade_animation_.reset();
405       paint.setAlpha(opacity);
406     } else {
407       double current_value = crossfade_animation_->GetCurrentValue();
408       int old_alpha = (1 - current_value) * crossfade_opacity_;
409       int new_alpha = current_value * opacity;
410
411       // Draw the old header background, clipping the corners to be rounded.
412       paint.setAlpha(old_alpha);
413       paint.setXfermodeMode(SkXfermode::kPlus_Mode);
414       PaintFrameImagesInRoundRect(canvas,
415                                   crossfade_theme_frame,
416                                   crossfade_theme_frame_overlay,
417                                   paint,
418                                   GetHeaderLocalBounds(),
419                                   corner_radius,
420                                   GetThemeBackgroundXInset());
421
422       paint.setAlpha(new_alpha);
423     }
424   } else {
425     paint.setAlpha(opacity);
426   }
427
428   // Draw the header background, clipping the corners to be rounded.
429   PaintFrameImagesInRoundRect(canvas,
430                               theme_frame,
431                               theme_frame_overlay,
432                               paint,
433                               GetHeaderLocalBounds(),
434                               corner_radius,
435                               GetThemeBackgroundXInset());
436
437   previous_theme_frame_id_ = theme_frame_id;
438   previous_theme_frame_overlay_id_ = theme_frame_overlay_id;
439   previous_opacity_ = opacity;
440
441   // We don't need the extra lightness in the edges when we're at the top edge
442   // of the screen or when the header's corners are not rounded.
443   //
444   // TODO(sky): this isn't quite right. What we really want is a method that
445   // returns bounds ignoring transforms on certain windows (such as workspaces)
446   // and is relative to the root.
447   if (frame_->GetNativeWindow()->bounds().y() == 0 || corner_radius == 0)
448     return;
449
450   // Draw the top corners and edge.
451   int top_left_width = top_left_corner_->width();
452   int top_left_height = top_left_corner_->height();
453   canvas->DrawImageInt(*top_left_corner_,
454                        0, 0, top_left_width, top_left_height,
455                        0, 0, top_left_width, top_left_height,
456                        false);
457   canvas->TileImageInt(*top_edge_,
458       top_left_width,
459       0,
460       header_view_->width() - top_left_width - top_right_corner_->width(),
461       top_edge_->height());
462   int top_right_height = top_right_corner_->height();
463   canvas->DrawImageInt(*top_right_corner_,
464                        0, 0,
465                        top_right_corner_->width(), top_right_height,
466                        header_view_->width() - top_right_corner_->width(), 0,
467                        top_right_corner_->width(), top_right_height,
468                        false);
469
470   // Header left edge.
471   int header_left_height = theme_frame->height() - top_left_height;
472   canvas->TileImageInt(*header_left_edge_,
473                        0, top_left_height,
474                        header_left_edge_->width(), header_left_height);
475
476   // Header right edge.
477   int header_right_height = theme_frame->height() - top_right_height;
478   canvas->TileImageInt(*header_right_edge_,
479                        header_view_->width() - header_right_edge_->width(),
480                        top_right_height,
481                        header_right_edge_->width(),
482                        header_right_height);
483
484   // We don't draw edges around the content area.  Web content goes flush
485   // to the edge of the window.
486 }
487
488 void HeaderPainter::PaintHeaderContentSeparator(gfx::Canvas* canvas) {
489   canvas->FillRect(gfx::Rect(0,
490                              header_height_ - kHeaderContentSeparatorSize,
491                              header_view_->width(),
492                              kHeaderContentSeparatorSize),
493                    kHeaderContentSeparatorColor);
494 }
495
496 int HeaderPainter::HeaderContentSeparatorSize() const {
497   return kHeaderContentSeparatorSize;
498 }
499
500 void HeaderPainter::PaintTitleBar(gfx::Canvas* canvas,
501                                   const gfx::Font& title_font) {
502   // The window icon is painted by its own views::View.
503   views::WidgetDelegate* delegate = frame_->widget_delegate();
504   if (delegate && delegate->ShouldShowWindowTitle()) {
505     gfx::Rect title_bounds = GetTitleBounds(title_font);
506     SkColor title_color = frame_->IsMaximized() ?
507         kMaximizedWindowTitleTextColor : kNonMaximizedWindowTitleTextColor;
508     canvas->DrawStringInt(delegate->GetWindowTitle(),
509                           title_font,
510                           title_color,
511                           header_view_->GetMirroredXForRect(title_bounds),
512                           title_bounds.y(),
513                           title_bounds.width(),
514                           title_bounds.height(),
515                           gfx::Canvas::NO_SUBPIXEL_RENDERING);
516   }
517 }
518
519 void HeaderPainter::LayoutHeader(bool shorter_layout) {
520   caption_button_container_->set_header_style(shorter_layout ?
521       FrameCaptionButtonContainerView::HEADER_STYLE_SHORT :
522       FrameCaptionButtonContainerView::HEADER_STYLE_TALL);
523   caption_button_container_->Layout();
524
525   gfx::Size caption_button_container_size =
526       caption_button_container_->GetPreferredSize();
527   caption_button_container_->SetBounds(
528       header_view_->width() - caption_button_container_size.width(),
529       0,
530       caption_button_container_size.width(),
531       caption_button_container_size.height());
532
533   if (window_icon_) {
534     // Vertically center the window icon with respect to the caption button
535     // container.
536     int icon_offset_y =
537         GetCaptionButtonContainerCenterY() - window_icon_->height() / 2;
538     window_icon_->SetBounds(kIconOffsetX, icon_offset_y, kIconSize, kIconSize);
539   }
540 }
541
542 void HeaderPainter::SchedulePaintForTitle(const gfx::Font& title_font) {
543   header_view_->SchedulePaintInRect(GetTitleBounds(title_font));
544 }
545
546 void HeaderPainter::OnThemeChanged() {
547   // We do not cache the images for |previous_theme_frame_id_| and
548   // |previous_theme_frame_overlay_id_|. Changing the theme changes the images
549   // returned from ui::ThemeProvider for |previous_theme_frame_id_|
550   // and |previous_theme_frame_overlay_id_|. Reset the image ids to prevent
551   // starting a crossfade animation with these images.
552   previous_theme_frame_id_ = 0;
553   previous_theme_frame_overlay_id_ = 0;
554
555   if (crossfade_animation_.get() && crossfade_animation_->is_animating()) {
556     crossfade_animation_.reset();
557     header_view_->SchedulePaintInRect(GetHeaderLocalBounds());
558   }
559 }
560
561 ///////////////////////////////////////////////////////////////////////////////
562 // WindowStateObserver overrides:
563 void HeaderPainter::OnTrackedByWorkspaceChanged(wm::WindowState* window_state,
564                                                 bool old) {
565   // When 'TrackedByWorkspace' changes, we are going to paint the header
566   // differently. Schedule a paint to ensure everything is updated correctly.
567   if (window_state->tracked_by_workspace())
568     header_view_->SchedulePaint();
569 }
570
571 ///////////////////////////////////////////////////////////////////////////////
572 // aura::WindowObserver overrides:
573
574 void HeaderPainter::OnWindowVisibilityChanged(aura::Window* window,
575                                               bool visible) {
576   // OnWindowVisibilityChanged can be called for the child windows of |window_|.
577   if (window != window_)
578     return;
579
580   // Window visibility change may trigger the change of window solo-ness in a
581   // different window.
582   UpdateSoloWindowInRoot(window_->GetRootWindow(), visible ? NULL : window_);
583 }
584
585 void HeaderPainter::OnWindowDestroying(aura::Window* destroying) {
586   DCHECK_EQ(window_, destroying);
587
588   // Must be removed here and not in the destructor, as the aura::Window is
589   // already destroyed when our destructor runs.
590   window_->RemoveObserver(this);
591   wm::GetWindowState(window_)->RemoveObserver(this);
592
593   // If we have two or more windows open and we close this one, we might trigger
594   // the solo window appearance for another window.
595   UpdateSoloWindowInRoot(window_->GetRootWindow(), window_);
596
597   window_ = NULL;
598 }
599
600 void HeaderPainter::OnWindowBoundsChanged(aura::Window* window,
601                                          const gfx::Rect& old_bounds,
602                                          const gfx::Rect& new_bounds) {
603   // TODO(sky): this isn't quite right. What we really want is a method that
604   // returns bounds ignoring transforms on certain windows (such as workspaces).
605   if ((!frame_->IsMaximized() && !frame_->IsFullscreen()) &&
606       ((old_bounds.y() == 0 && new_bounds.y() != 0) ||
607        (old_bounds.y() != 0 && new_bounds.y() == 0))) {
608     SchedulePaintForHeader();
609   }
610 }
611
612 void HeaderPainter::OnWindowAddedToRootWindow(aura::Window* window) {
613   // Needs to trigger the window appearance change if the window moves across
614   // root windows and a solo window is already in the new root.
615   UpdateSoloWindowInRoot(window->GetRootWindow(), NULL /* ignore_window */);
616 }
617
618 void HeaderPainter::OnWindowRemovingFromRootWindow(aura::Window* window) {
619   // Needs to trigger the window appearance change if the window moves across
620   // root windows and only one window is left in the previous root.  Because
621   // |window| is not yet moved, |window| has to be ignored.
622   UpdateSoloWindowInRoot(window->GetRootWindow(), window);
623 }
624
625 ///////////////////////////////////////////////////////////////////////////////
626 // gfx::AnimationDelegate overrides:
627
628 void HeaderPainter::AnimationProgressed(const gfx::Animation* animation) {
629   header_view_->SchedulePaintInRect(GetHeaderLocalBounds());
630 }
631
632 ///////////////////////////////////////////////////////////////////////////////
633 // HeaderPainter, private:
634
635 gfx::Rect HeaderPainter::GetHeaderLocalBounds() const {
636   return gfx::Rect(header_view_->width(), header_height_);
637 }
638
639 int HeaderPainter::GetTitleOffsetX() const {
640   return window_icon_ ?
641       window_icon_->bounds().right() + kTitleIconOffsetX :
642       kTitleNoIconOffsetX;
643 }
644
645 int HeaderPainter::GetCaptionButtonContainerCenterY() const {
646   return caption_button_container_->y() +
647       caption_button_container_->height() / 2;
648 }
649
650 int HeaderPainter::GetHeaderCornerRadius() const {
651   // Use square corners for maximized and fullscreen windows when they are
652   // tracked by the workspace code. (Windows which are not tracked by the
653   // workspace code are used for tab dragging.)
654   bool square_corners = ((frame_->IsMaximized() || frame_->IsFullscreen())) &&
655       wm::GetWindowState(frame_->GetNativeWindow())->tracked_by_workspace();
656   const int kCornerRadius = 2;
657   return square_corners ? 0 : kCornerRadius;
658 }
659
660 int HeaderPainter::GetHeaderOpacity(
661     HeaderMode header_mode,
662     int theme_frame_id,
663     int theme_frame_overlay_id) const {
664   // User-provided themes are painted fully opaque.
665   ui::ThemeProvider* theme_provider = frame_->GetThemeProvider();
666   if (theme_provider->HasCustomImage(theme_frame_id) ||
667       (theme_frame_overlay_id != 0 &&
668        theme_provider->HasCustomImage(theme_frame_overlay_id))) {
669     return kFullyOpaque;
670   }
671
672   // The header is fully opaque when using the minimalistic header style.
673   if (ShouldUseMinimalHeaderStyle(THEMED_NO))
674     return kFullyOpaque;
675
676   // Single browser window is very transparent.
677   if (UseSoloWindowHeader())
678     return kSoloWindowOpacity;
679
680   // Otherwise, change transparency based on window activation status.
681   if (header_mode == ACTIVE)
682     return kActiveWindowOpacity;
683   return kInactiveWindowOpacity;
684 }
685
686 bool HeaderPainter::UseSoloWindowHeader() const {
687   if (!solo_window_header_enabled)
688     return false;
689   // Don't use transparent headers for panels, pop-ups, etc.
690   if (!IsSoloWindowHeaderCandidate(window_))
691     return false;
692   aura::Window* root = window_->GetRootWindow();
693   // Don't recompute every time, as it would require many window property
694   // lookups.
695   return internal::GetRootWindowSettings(root)->solo_window_header;
696 }
697
698 // static
699 bool HeaderPainter::UseSoloWindowHeaderInRoot(Window* root_window,
700                                               Window* ignore_window) {
701   int visible_window_count = 0;
702   std::vector<Window*> windows = GetWindowsForSoloHeaderUpdate(root_window);
703   for (std::vector<Window*>::const_iterator it = windows.begin();
704        it != windows.end();
705        ++it) {
706     Window* window = *it;
707     // Various sorts of windows "don't count" for this computation.
708     if (ignore_window == window ||
709         !IsSoloWindowHeaderCandidate(window) ||
710         !IsVisibleToRoot(window))
711       continue;
712     if (wm::GetWindowState(window)->IsMaximized())
713       return false;
714     ++visible_window_count;
715     if (visible_window_count > 1)
716       return false;
717   }
718   // Count must be tested because all windows might be "don't count" windows
719   // in the loop above.
720   return visible_window_count == 1;
721 }
722
723 // static
724 void HeaderPainter::UpdateSoloWindowInRoot(Window* root,
725                                            Window* ignore_window) {
726 #if defined(OS_WIN)
727   // Non-Ash Windows doesn't do solo-window counting for transparency effects,
728   // as the desktop background and window frames are managed by the OS.
729   if (!ash::Shell::HasInstance())
730     return;
731 #endif
732   if (!root)
733     return;
734   internal::RootWindowSettings* root_window_settings =
735       internal::GetRootWindowSettings(root);
736   bool old_solo_header = root_window_settings->solo_window_header;
737   bool new_solo_header = UseSoloWindowHeaderInRoot(root, ignore_window);
738   if (old_solo_header == new_solo_header)
739     return;
740   root_window_settings->solo_window_header = new_solo_header;
741
742   // Invalidate all the window frames in the desktop. There should only be
743   // a few.
744   std::vector<Window*> windows = GetWindowsForSoloHeaderUpdate(root);
745   for (std::vector<Window*>::const_iterator it = windows.begin();
746        it != windows.end();
747        ++it) {
748     Widget* widget = Widget::GetWidgetForNativeWindow(*it);
749     if (widget && widget->non_client_view())
750       widget->non_client_view()->SchedulePaint();
751   }
752 }
753
754 void HeaderPainter::SchedulePaintForHeader() {
755   int top_left_height = top_left_corner_->height();
756   int top_right_height = top_right_corner_->height();
757   header_view_->SchedulePaintInRect(
758       gfx::Rect(0, 0, header_view_->width(),
759                 std::max(top_left_height, top_right_height)));
760 }
761
762 gfx::Rect HeaderPainter::GetTitleBounds(const gfx::Font& title_font) {
763   int title_x = GetTitleOffsetX();
764   // Center the text with respect to the caption button container. This way it
765   // adapts to the caption button height and aligns exactly with the window
766   // icon. Don't use |window_icon_| for this computation as it may be NULL.
767   int title_y = GetCaptionButtonContainerCenterY() - title_font.GetHeight() / 2;
768   return gfx::Rect(
769       title_x,
770       std::max(0, title_y),
771       std::max(0, caption_button_container_->x() - kTitleLogoSpacing - title_x),
772       title_font.GetHeight());
773 }
774
775 }  // namespace ash