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.
5 #include "ash/wm/header_painter.h"
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/root_window.h"
26 #include "ui/aura/window.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/animation/slide_animation.h"
32 #include "ui/gfx/canvas.h"
33 #include "ui/gfx/font.h"
34 #include "ui/gfx/image/image.h"
35 #include "ui/gfx/screen.h"
36 #include "ui/gfx/skia_util.h"
37 #include "ui/views/widget/widget.h"
38 #include "ui/views/widget/widget_delegate.h"
40 using aura::RootWindow
;
45 // Space between left edge of window and popup window icon.
46 const int kIconOffsetX
= 9;
47 // Height and width of window icon.
48 const int kIconSize
= 16;
49 // Space between the title text and the caption buttons.
50 const int kTitleLogoSpacing
= 5;
51 // Space between window icon and title text.
52 const int kTitleIconOffsetX
= 5;
53 // Space between window edge and title text, when there is no icon.
54 const int kTitleNoIconOffsetX
= 8;
55 // Color for the non-maximized window title text.
56 const SkColor kNonMaximizedWindowTitleTextColor
= SkColorSetRGB(40, 40, 40);
57 // Color for the maximized window title text.
58 const SkColor kMaximizedWindowTitleTextColor
= SK_ColorWHITE
;
59 // Size of header/content separator line below the header image.
60 const int kHeaderContentSeparatorSize
= 1;
61 // Color of header bottom edge line.
62 const SkColor kHeaderContentSeparatorColor
= SkColorSetRGB(128, 128, 128);
63 // In the pre-Ash era the web content area had a frame along the left edge, so
64 // user-generated theme images for the new tab page assume they are shifted
65 // right relative to the header. Now that we have removed the left edge frame
66 // we need to copy the theme image for the window header from a few pixels
67 // inset to preserve alignment with the NTP image, or else we'll break a bunch
68 // of existing themes. We do something similar on OS X for the same reason.
69 const int kThemeFrameImageInsetX
= 5;
70 // Duration of crossfade animation for activating and deactivating frame.
71 const int kActivationCrossfadeDurationMs
= 200;
72 // Alpha/opacity value for fully-opaque headers.
73 const int kFullyOpaque
= 255;
75 // A flag to enable/disable solo window header.
76 bool solo_window_header_enabled
= true;
78 // Tiles an image into an area, rounding the top corners. Samples |image|
79 // starting |image_inset_x| pixels from the left of the image.
80 void TileRoundRect(gfx::Canvas
* canvas
,
81 const gfx::ImageSkia
& image
,
83 const gfx::Rect
& bounds
,
84 int top_left_corner_radius
,
85 int top_right_corner_radius
,
87 SkRect rect
= gfx::RectToSkRect(bounds
);
88 const SkScalar kTopLeftRadius
= SkIntToScalar(top_left_corner_radius
);
89 const SkScalar kTopRightRadius
= SkIntToScalar(top_right_corner_radius
);
91 kTopLeftRadius
, kTopLeftRadius
, // top-left
92 kTopRightRadius
, kTopRightRadius
, // top-right
96 path
.addRoundRect(rect
, radii
, SkPath::kCW_Direction
);
97 canvas
->DrawImageInPath(image
, -image_inset_x
, 0, path
, paint
);
100 // Tiles |frame_image| and |frame_overlay_image| into an area, rounding the top
102 void PaintFrameImagesInRoundRect(gfx::Canvas
* canvas
,
103 const gfx::ImageSkia
* frame_image
,
104 const gfx::ImageSkia
* frame_overlay_image
,
105 const SkPaint
& paint
,
106 const gfx::Rect
& bounds
,
109 SkXfermode::Mode normal_mode
;
110 SkXfermode::AsMode(NULL
, &normal_mode
);
112 // If |paint| is using an unusual SkXfermode::Mode (this is the case while
113 // crossfading), we must create a new canvas to overlay |frame_image| and
114 // |frame_overlay_image| using |normal_mode| and then paint the result
115 // using the unusual mode. We try to avoid this because creating a new
116 // browser-width canvas is expensive.
117 bool fast_path
= (!frame_overlay_image
||
118 SkXfermode::IsMode(paint
.getXfermode(), normal_mode
));
120 TileRoundRect(canvas
, *frame_image
, paint
, bounds
, corner_radius
,
121 corner_radius
, image_inset_x
);
123 if (frame_overlay_image
) {
124 // Adjust |bounds| such that |frame_overlay_image| is not tiled.
125 gfx::Rect overlay_bounds
= bounds
;
126 overlay_bounds
.Intersect(
127 gfx::Rect(bounds
.origin(), frame_overlay_image
->size()));
128 int top_left_corner_radius
= corner_radius
;
129 int top_right_corner_radius
= corner_radius
;
130 if (overlay_bounds
.width() < bounds
.width() - corner_radius
)
131 top_right_corner_radius
= 0;
132 TileRoundRect(canvas
, *frame_overlay_image
, paint
, overlay_bounds
,
133 top_left_corner_radius
, top_right_corner_radius
, 0);
136 gfx::Canvas
temporary_canvas(bounds
.size(), canvas
->image_scale(), false);
137 temporary_canvas
.TileImageInt(*frame_image
,
140 bounds
.width(), bounds
.height());
141 temporary_canvas
.DrawImageInt(*frame_overlay_image
, 0, 0);
142 TileRoundRect(canvas
, gfx::ImageSkia(temporary_canvas
.ExtractImageRep()),
143 paint
, bounds
, corner_radius
, corner_radius
, 0);
147 // Returns true if |child| and all ancestors are visible. Useful to ensure that
148 // a window is individually visible and is not part of a hidden workspace.
149 bool IsVisibleToRoot(Window
* child
) {
150 for (Window
* window
= child
; window
; window
= window
->parent()) {
151 // We must use TargetVisibility() because windows animate in and out and
152 // IsVisible() also tracks the layer visibility state.
153 if (!window
->TargetVisibility())
159 // Returns true if |window| is a "normal" window for purposes of solo window
160 // computations. Returns false for windows that are:
161 // * Not drawn (for example, DragDropTracker uses one for mouse capture)
162 // * Modal alerts (it looks odd for headers to change when an alert opens)
163 // * Constrained windows (ditto)
164 bool IsSoloWindowHeaderCandidate(aura::Window
* window
) {
166 window
->type() == aura::client::WINDOW_TYPE_NORMAL
&&
168 window
->layer()->type() != ui::LAYER_NOT_DRAWN
&&
169 window
->GetProperty(aura::client::kModalKey
) == ui::MODAL_TYPE_NONE
&&
170 !window
->GetProperty(ash::kConstrainedWindowKey
);
173 // Returns a list of windows in |root_window|| that potentially could have
174 // a transparent solo-window header.
175 std::vector
<Window
*> GetWindowsForSoloHeaderUpdate(RootWindow
* root_window
) {
176 std::vector
<Window
*> windows
;
177 // Avoid memory allocations for typical window counts.
179 // Collect windows from the desktop.
180 Window
* desktop
= ash::Shell::GetContainer(
181 root_window
, ash::internal::kShellWindowId_DefaultContainer
);
182 windows
.insert(windows
.end(),
183 desktop
->children().begin(),
184 desktop
->children().end());
185 // Collect "always on top" windows.
186 Window
* top_container
=
187 ash::Shell::GetContainer(
188 root_window
, ash::internal::kShellWindowId_AlwaysOnTopContainer
);
189 windows
.insert(windows
.end(),
190 top_container
->children().begin(),
191 top_container
->children().end());
199 int HeaderPainter::kActiveWindowOpacity
= 255; // 1.0
200 int HeaderPainter::kInactiveWindowOpacity
= 255; // 1.0
201 int HeaderPainter::kSoloWindowOpacity
= 77; // 0.3
203 ///////////////////////////////////////////////////////////////////////////////
204 // HeaderPainter, public:
206 HeaderPainter::HeaderPainter()
210 caption_button_container_(NULL
),
213 top_left_corner_(NULL
),
215 top_right_corner_(NULL
),
216 header_left_edge_(NULL
),
217 header_right_edge_(NULL
),
218 previous_theme_frame_id_(0),
219 previous_theme_frame_overlay_id_(0),
220 previous_opacity_(0),
221 crossfade_theme_frame_id_(0),
222 crossfade_theme_frame_overlay_id_(0),
223 crossfade_opacity_(0) {}
225 HeaderPainter::~HeaderPainter() {
226 // Sometimes we are destroyed before the window closes, so ensure we clean up.
228 window_
->RemoveObserver(this);
229 wm::GetWindowState(window_
)->RemoveObserver(this);
233 void HeaderPainter::Init(
234 views::Widget
* frame
,
235 views::View
* header_view
,
236 views::View
* window_icon
,
237 FrameCaptionButtonContainerView
* caption_button_container
) {
240 // window_icon may be NULL.
241 DCHECK(caption_button_container
);
243 header_view_
= header_view
;
244 window_icon_
= window_icon
;
245 caption_button_container_
= caption_button_container
;
247 // Window frame image parts.
248 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
250 rb
.GetImageNamed(IDR_AURA_WINDOW_HEADER_SHADE_TOP_LEFT
).ToImageSkia();
252 rb
.GetImageNamed(IDR_AURA_WINDOW_HEADER_SHADE_TOP
).ToImageSkia();
254 rb
.GetImageNamed(IDR_AURA_WINDOW_HEADER_SHADE_TOP_RIGHT
).ToImageSkia();
256 rb
.GetImageNamed(IDR_AURA_WINDOW_HEADER_SHADE_LEFT
).ToImageSkia();
258 rb
.GetImageNamed(IDR_AURA_WINDOW_HEADER_SHADE_RIGHT
).ToImageSkia();
260 window_
= frame
->GetNativeWindow();
262 // Observer removes itself in OnWindowDestroying() below, or in the destructor
263 // if we go away before the window.
264 window_
->AddObserver(this);
265 wm::GetWindowState(window_
)->AddObserver(this);
267 // Solo-window header updates are handled by the WorkspaceLayoutManager when
268 // this window is added to the desktop.
272 void HeaderPainter::SetSoloWindowHeadersEnabled(bool enabled
) {
273 solo_window_header_enabled
= enabled
;
277 void HeaderPainter::UpdateSoloWindowHeader(RootWindow
* root_window
) {
278 // Use a separate function here so callers outside of HeaderPainter don't need
279 // to know about "ignorable_window".
280 UpdateSoloWindowInRoot(root_window
, NULL
/* ignorable_window */);
284 gfx::Rect
HeaderPainter::GetBoundsForClientView(
286 const gfx::Rect
& window_bounds
) {
287 gfx::Rect
client_bounds(window_bounds
);
288 client_bounds
.Inset(0, header_height
, 0, 0);
289 return client_bounds
;
293 gfx::Rect
HeaderPainter::GetWindowBoundsForClientBounds(
295 const gfx::Rect
& client_bounds
) {
296 gfx::Rect
window_bounds(client_bounds
);
297 window_bounds
.Inset(0, -header_height
, 0, 0);
298 if (window_bounds
.y() < 0)
299 window_bounds
.set_y(0);
300 return window_bounds
;
303 int HeaderPainter::NonClientHitTest(const gfx::Point
& point
) const {
304 gfx::Point
point_in_header_view(point
);
305 views::View::ConvertPointFromWidget(header_view_
, &point_in_header_view
);
306 if (!GetHeaderLocalBounds().Contains(point_in_header_view
))
308 if (caption_button_container_
->visible()) {
309 gfx::Point
point_in_caption_button_container(point
);
310 views::View::ConvertPointFromWidget(caption_button_container_
,
311 &point_in_caption_button_container
);
312 int component
= caption_button_container_
->NonClientHitTest(
313 point_in_caption_button_container
);
314 if (component
!= HTNOWHERE
)
317 // Caption is a safe default.
321 int HeaderPainter::GetMinimumHeaderWidth() const {
322 // Ensure we have enough space for the window icon and buttons. We allow
323 // the title string to collapse to zero width.
324 return GetTitleOffsetX() +
325 caption_button_container_
->GetMinimumSize().width();
328 int HeaderPainter::GetRightInset() const {
329 return caption_button_container_
->GetPreferredSize().width();
332 int HeaderPainter::GetThemeBackgroundXInset() const {
333 return kThemeFrameImageInsetX
;
336 bool HeaderPainter::ShouldUseMinimalHeaderStyle(Themed header_themed
) const {
337 // Use the minimalistic header style whenever |frame_| is maximized or
338 // fullscreen EXCEPT:
339 // - If the user has installed a theme with custom images for the header.
340 // - For windows which are not tracked by the workspace code (which are used
341 // for tab dragging).
342 return (frame_
->IsMaximized() || frame_
->IsFullscreen()) &&
343 header_themed
== THEMED_NO
&&
344 wm::GetWindowState(frame_
->GetNativeWindow())->tracked_by_workspace();
347 void HeaderPainter::PaintHeader(gfx::Canvas
* canvas
,
348 HeaderMode header_mode
,
350 int theme_frame_overlay_id
) {
351 bool initial_paint
= (previous_theme_frame_id_
== 0);
352 if (!initial_paint
&&
353 (previous_theme_frame_id_
!= theme_frame_id
||
354 previous_theme_frame_overlay_id_
!= theme_frame_overlay_id
)) {
355 aura::Window
* parent
= frame_
->GetNativeWindow()->parent();
356 // Don't animate the header if the parent (a workspace) is already
357 // animating. Doing so results in continually painting during the animation
358 // and gives a slower frame rate.
359 // TODO(sky): expose a better way to determine this rather than assuming
360 // the parent is a workspace.
361 bool parent_animating
= parent
&&
362 (parent
->layer()->GetAnimator()->IsAnimatingProperty(
363 ui::LayerAnimationElement::OPACITY
) ||
364 parent
->layer()->GetAnimator()->IsAnimatingProperty(
365 ui::LayerAnimationElement::VISIBILITY
));
366 if (!parent_animating
) {
367 crossfade_animation_
.reset(new gfx::SlideAnimation(this));
368 crossfade_theme_frame_id_
= previous_theme_frame_id_
;
369 crossfade_theme_frame_overlay_id_
= previous_theme_frame_overlay_id_
;
370 crossfade_opacity_
= previous_opacity_
;
371 crossfade_animation_
->SetSlideDuration(kActivationCrossfadeDurationMs
);
372 crossfade_animation_
->Show();
374 crossfade_animation_
.reset();
379 GetHeaderOpacity(header_mode
, theme_frame_id
, theme_frame_overlay_id
);
380 ui::ThemeProvider
* theme_provider
= frame_
->GetThemeProvider();
381 gfx::ImageSkia
* theme_frame
= theme_provider
->GetImageSkiaNamed(
383 gfx::ImageSkia
* theme_frame_overlay
= NULL
;
384 if (theme_frame_overlay_id
!= 0) {
385 theme_frame_overlay
= theme_provider
->GetImageSkiaNamed(
386 theme_frame_overlay_id
);
389 int corner_radius
= GetHeaderCornerRadius();
392 if (crossfade_animation_
.get() && crossfade_animation_
->is_animating()) {
393 gfx::ImageSkia
* crossfade_theme_frame
=
394 theme_provider
->GetImageSkiaNamed(crossfade_theme_frame_id_
);
395 gfx::ImageSkia
* crossfade_theme_frame_overlay
= NULL
;
396 if (crossfade_theme_frame_overlay_id_
!= 0) {
397 crossfade_theme_frame_overlay
= theme_provider
->GetImageSkiaNamed(
398 crossfade_theme_frame_overlay_id_
);
400 if (!crossfade_theme_frame
||
401 (crossfade_theme_frame_overlay_id_
!= 0 &&
402 !crossfade_theme_frame_overlay
)) {
403 // Reset the animation. This case occurs when the user switches the theme
404 // that they are using.
405 crossfade_animation_
.reset();
406 paint
.setAlpha(opacity
);
408 double current_value
= crossfade_animation_
->GetCurrentValue();
409 int old_alpha
= (1 - current_value
) * crossfade_opacity_
;
410 int new_alpha
= current_value
* opacity
;
412 // Draw the old header background, clipping the corners to be rounded.
413 paint
.setAlpha(old_alpha
);
414 paint
.setXfermodeMode(SkXfermode::kPlus_Mode
);
415 PaintFrameImagesInRoundRect(canvas
,
416 crossfade_theme_frame
,
417 crossfade_theme_frame_overlay
,
419 GetHeaderLocalBounds(),
421 GetThemeBackgroundXInset());
423 paint
.setAlpha(new_alpha
);
426 paint
.setAlpha(opacity
);
429 // Draw the header background, clipping the corners to be rounded.
430 PaintFrameImagesInRoundRect(canvas
,
434 GetHeaderLocalBounds(),
436 GetThemeBackgroundXInset());
438 previous_theme_frame_id_
= theme_frame_id
;
439 previous_theme_frame_overlay_id_
= theme_frame_overlay_id
;
440 previous_opacity_
= opacity
;
442 // We don't need the extra lightness in the edges when we're at the top edge
443 // of the screen or when the header's corners are not rounded.
445 // TODO(sky): this isn't quite right. What we really want is a method that
446 // returns bounds ignoring transforms on certain windows (such as workspaces)
447 // and is relative to the root.
448 if (frame_
->GetNativeWindow()->bounds().y() == 0 || corner_radius
== 0)
451 // Draw the top corners and edge.
452 int top_left_width
= top_left_corner_
->width();
453 int top_left_height
= top_left_corner_
->height();
454 canvas
->DrawImageInt(*top_left_corner_
,
455 0, 0, top_left_width
, top_left_height
,
456 0, 0, top_left_width
, top_left_height
,
458 canvas
->TileImageInt(*top_edge_
,
461 header_view_
->width() - top_left_width
- top_right_corner_
->width(),
462 top_edge_
->height());
463 int top_right_height
= top_right_corner_
->height();
464 canvas
->DrawImageInt(*top_right_corner_
,
466 top_right_corner_
->width(), top_right_height
,
467 header_view_
->width() - top_right_corner_
->width(), 0,
468 top_right_corner_
->width(), top_right_height
,
472 int header_left_height
= theme_frame
->height() - top_left_height
;
473 canvas
->TileImageInt(*header_left_edge_
,
475 header_left_edge_
->width(), header_left_height
);
477 // Header right edge.
478 int header_right_height
= theme_frame
->height() - top_right_height
;
479 canvas
->TileImageInt(*header_right_edge_
,
480 header_view_
->width() - header_right_edge_
->width(),
482 header_right_edge_
->width(),
483 header_right_height
);
485 // We don't draw edges around the content area. Web content goes flush
486 // to the edge of the window.
489 void HeaderPainter::PaintHeaderContentSeparator(gfx::Canvas
* canvas
) {
490 canvas
->FillRect(gfx::Rect(0,
491 header_height_
- kHeaderContentSeparatorSize
,
492 header_view_
->width(),
493 kHeaderContentSeparatorSize
),
494 kHeaderContentSeparatorColor
);
497 int HeaderPainter::HeaderContentSeparatorSize() const {
498 return kHeaderContentSeparatorSize
;
501 void HeaderPainter::PaintTitleBar(gfx::Canvas
* canvas
,
502 const gfx::Font
& title_font
) {
503 // The window icon is painted by its own views::View.
504 views::WidgetDelegate
* delegate
= frame_
->widget_delegate();
505 if (delegate
&& delegate
->ShouldShowWindowTitle()) {
506 gfx::Rect title_bounds
= GetTitleBounds(title_font
);
507 SkColor title_color
= frame_
->IsMaximized() ?
508 kMaximizedWindowTitleTextColor
: kNonMaximizedWindowTitleTextColor
;
509 canvas
->DrawStringInt(delegate
->GetWindowTitle(),
512 header_view_
->GetMirroredXForRect(title_bounds
),
514 title_bounds
.width(),
515 title_bounds
.height(),
516 gfx::Canvas::NO_SUBPIXEL_RENDERING
);
520 void HeaderPainter::LayoutHeader(bool shorter_layout
) {
521 caption_button_container_
->set_header_style(shorter_layout
?
522 FrameCaptionButtonContainerView::HEADER_STYLE_SHORT
:
523 FrameCaptionButtonContainerView::HEADER_STYLE_TALL
);
524 caption_button_container_
->Layout();
526 gfx::Size caption_button_container_size
=
527 caption_button_container_
->GetPreferredSize();
528 caption_button_container_
->SetBounds(
529 header_view_
->width() - caption_button_container_size
.width(),
531 caption_button_container_size
.width(),
532 caption_button_container_size
.height());
535 // Vertically center the window icon with respect to the caption button
538 GetCaptionButtonContainerCenterY() - window_icon_
->height() / 2;
539 window_icon_
->SetBounds(kIconOffsetX
, icon_offset_y
, kIconSize
, kIconSize
);
543 void HeaderPainter::SchedulePaintForTitle(const gfx::Font
& title_font
) {
544 header_view_
->SchedulePaintInRect(GetTitleBounds(title_font
));
547 void HeaderPainter::OnThemeChanged() {
548 // We do not cache the images for |previous_theme_frame_id_| and
549 // |previous_theme_frame_overlay_id_|. Changing the theme changes the images
550 // returned from ui::ThemeProvider for |previous_theme_frame_id_|
551 // and |previous_theme_frame_overlay_id_|. Reset the image ids to prevent
552 // starting a crossfade animation with these images.
553 previous_theme_frame_id_
= 0;
554 previous_theme_frame_overlay_id_
= 0;
556 if (crossfade_animation_
.get() && crossfade_animation_
->is_animating()) {
557 crossfade_animation_
.reset();
558 header_view_
->SchedulePaintInRect(GetHeaderLocalBounds());
562 ///////////////////////////////////////////////////////////////////////////////
563 // WindowStateObserver overrides:
564 void HeaderPainter::OnTrackedByWorkspaceChanged(wm::WindowState
* window_state
,
566 // When 'TrackedByWorkspace' changes, we are going to paint the header
567 // differently. Schedule a paint to ensure everything is updated correctly.
568 if (window_state
->tracked_by_workspace())
569 header_view_
->SchedulePaint();
572 ///////////////////////////////////////////////////////////////////////////////
573 // aura::WindowObserver overrides:
575 void HeaderPainter::OnWindowVisibilityChanged(aura::Window
* window
,
577 // OnWindowVisibilityChanged can be called for the child windows of |window_|.
578 if (window
!= window_
)
581 // Window visibility change may trigger the change of window solo-ness in a
583 UpdateSoloWindowInRoot(window_
->GetRootWindow(), visible
? NULL
: window_
);
586 void HeaderPainter::OnWindowDestroying(aura::Window
* destroying
) {
587 DCHECK_EQ(window_
, destroying
);
589 // Must be removed here and not in the destructor, as the aura::Window is
590 // already destroyed when our destructor runs.
591 window_
->RemoveObserver(this);
592 wm::GetWindowState(window_
)->RemoveObserver(this);
594 // If we have two or more windows open and we close this one, we might trigger
595 // the solo window appearance for another window.
596 UpdateSoloWindowInRoot(window_
->GetRootWindow(), window_
);
601 void HeaderPainter::OnWindowBoundsChanged(aura::Window
* window
,
602 const gfx::Rect
& old_bounds
,
603 const gfx::Rect
& new_bounds
) {
604 // TODO(sky): this isn't quite right. What we really want is a method that
605 // returns bounds ignoring transforms on certain windows (such as workspaces).
606 if ((!frame_
->IsMaximized() && !frame_
->IsFullscreen()) &&
607 ((old_bounds
.y() == 0 && new_bounds
.y() != 0) ||
608 (old_bounds
.y() != 0 && new_bounds
.y() == 0))) {
609 SchedulePaintForHeader();
613 void HeaderPainter::OnWindowAddedToRootWindow(aura::Window
* window
) {
614 // Needs to trigger the window appearance change if the window moves across
615 // root windows and a solo window is already in the new root.
616 UpdateSoloWindowInRoot(window
->GetRootWindow(), NULL
/* ignore_window */);
619 void HeaderPainter::OnWindowRemovingFromRootWindow(aura::Window
* window
) {
620 // Needs to trigger the window appearance change if the window moves across
621 // root windows and only one window is left in the previous root. Because
622 // |window| is not yet moved, |window| has to be ignored.
623 UpdateSoloWindowInRoot(window
->GetRootWindow(), window
);
626 ///////////////////////////////////////////////////////////////////////////////
627 // gfx::AnimationDelegate overrides:
629 void HeaderPainter::AnimationProgressed(const gfx::Animation
* animation
) {
630 header_view_
->SchedulePaintInRect(GetHeaderLocalBounds());
633 ///////////////////////////////////////////////////////////////////////////////
634 // HeaderPainter, private:
636 gfx::Rect
HeaderPainter::GetHeaderLocalBounds() const {
637 return gfx::Rect(header_view_
->width(), header_height_
);
640 int HeaderPainter::GetTitleOffsetX() const {
641 return window_icon_
?
642 window_icon_
->bounds().right() + kTitleIconOffsetX
:
646 int HeaderPainter::GetCaptionButtonContainerCenterY() const {
647 return caption_button_container_
->y() +
648 caption_button_container_
->height() / 2;
651 int HeaderPainter::GetHeaderCornerRadius() const {
652 // Use square corners for maximized and fullscreen windows when they are
653 // tracked by the workspace code. (Windows which are not tracked by the
654 // workspace code are used for tab dragging.)
655 bool square_corners
= ((frame_
->IsMaximized() || frame_
->IsFullscreen())) &&
656 wm::GetWindowState(frame_
->GetNativeWindow())->tracked_by_workspace();
657 const int kCornerRadius
= 2;
658 return square_corners
? 0 : kCornerRadius
;
661 int HeaderPainter::GetHeaderOpacity(
662 HeaderMode header_mode
,
664 int theme_frame_overlay_id
) const {
665 // User-provided themes are painted fully opaque.
666 ui::ThemeProvider
* theme_provider
= frame_
->GetThemeProvider();
667 if (theme_provider
->HasCustomImage(theme_frame_id
) ||
668 (theme_frame_overlay_id
!= 0 &&
669 theme_provider
->HasCustomImage(theme_frame_overlay_id
))) {
673 // The header is fully opaque when using the minimalistic header style.
674 if (ShouldUseMinimalHeaderStyle(THEMED_NO
))
677 // Single browser window is very transparent.
678 if (UseSoloWindowHeader())
679 return kSoloWindowOpacity
;
681 // Otherwise, change transparency based on window activation status.
682 if (header_mode
== ACTIVE
)
683 return kActiveWindowOpacity
;
684 return kInactiveWindowOpacity
;
687 bool HeaderPainter::UseSoloWindowHeader() const {
688 if (!solo_window_header_enabled
)
690 // Don't use transparent headers for panels, pop-ups, etc.
691 if (!IsSoloWindowHeaderCandidate(window_
))
693 aura::RootWindow
* root
= window_
->GetRootWindow();
694 // Don't recompute every time, as it would require many window property
696 return internal::GetRootWindowSettings(root
)->solo_window_header
;
700 bool HeaderPainter::UseSoloWindowHeaderInRoot(RootWindow
* root_window
,
701 Window
* ignore_window
) {
702 int visible_window_count
= 0;
703 std::vector
<Window
*> windows
= GetWindowsForSoloHeaderUpdate(root_window
);
704 for (std::vector
<Window
*>::const_iterator it
= windows
.begin();
707 Window
* window
= *it
;
708 // Various sorts of windows "don't count" for this computation.
709 if (ignore_window
== window
||
710 !IsSoloWindowHeaderCandidate(window
) ||
711 !IsVisibleToRoot(window
))
713 if (wm::GetWindowState(window
)->IsMaximized())
715 ++visible_window_count
;
716 if (visible_window_count
> 1)
719 // Count must be tested because all windows might be "don't count" windows
720 // in the loop above.
721 return visible_window_count
== 1;
725 void HeaderPainter::UpdateSoloWindowInRoot(RootWindow
* root
,
726 Window
* ignore_window
) {
728 // Non-Ash Windows doesn't do solo-window counting for transparency effects,
729 // as the desktop background and window frames are managed by the OS.
730 if (!ash::Shell::HasInstance())
735 internal::RootWindowSettings
* root_window_settings
=
736 internal::GetRootWindowSettings(root
);
737 bool old_solo_header
= root_window_settings
->solo_window_header
;
738 bool new_solo_header
= UseSoloWindowHeaderInRoot(root
, ignore_window
);
739 if (old_solo_header
== new_solo_header
)
741 root_window_settings
->solo_window_header
= new_solo_header
;
743 // Invalidate all the window frames in the desktop. There should only be
745 std::vector
<Window
*> windows
= GetWindowsForSoloHeaderUpdate(root
);
746 for (std::vector
<Window
*>::const_iterator it
= windows
.begin();
749 Widget
* widget
= Widget::GetWidgetForNativeWindow(*it
);
750 if (widget
&& widget
->non_client_view())
751 widget
->non_client_view()->SchedulePaint();
755 void HeaderPainter::SchedulePaintForHeader() {
756 int top_left_height
= top_left_corner_
->height();
757 int top_right_height
= top_right_corner_
->height();
758 header_view_
->SchedulePaintInRect(
759 gfx::Rect(0, 0, header_view_
->width(),
760 std::max(top_left_height
, top_right_height
)));
763 gfx::Rect
HeaderPainter::GetTitleBounds(const gfx::Font
& title_font
) {
764 int title_x
= GetTitleOffsetX();
765 // Center the text with respect to the caption button container. This way it
766 // adapts to the caption button height and aligns exactly with the window
767 // icon. Don't use |window_icon_| for this computation as it may be NULL.
768 int title_y
= GetCaptionButtonContainerCenterY() - title_font
.GetHeight() / 2;
771 std::max(0, title_y
),
772 std::max(0, caption_button_container_
->x() - kTitleLogoSpacing
- title_x
),
773 title_font
.GetHeight());