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.
5 #include "ash/wm/frame_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 // 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 // In the pre-Ash era the web content area had a frame along the left edge, so
67 // user-generated theme images for the new tab page assume they are shifted
68 // right relative to the header. Now that we have removed the left edge frame
69 // we need to copy the theme image for the window header from a few pixels
70 // inset to preserve alignment with the NTP image, or else we'll break a bunch
71 // of existing themes. We do something similar on OS X for the same reason.
72 const int kThemeFrameImageInsetX
= 5;
73 // Duration of crossfade animation for activating and deactivating frame.
74 const int kActivationCrossfadeDurationMs
= 200;
75 // Alpha/opacity value for fully-opaque headers.
76 const int kFullyOpaque
= 255;
78 // A flag to enable/disable solo window header.
79 bool solo_window_header_enabled
= true;
81 // Tiles an image into an area, rounding the top corners. Samples |image|
82 // starting |image_inset_x| pixels from the left of the image.
83 void TileRoundRect(gfx::Canvas
* canvas
,
84 const gfx::ImageSkia
& image
,
86 const gfx::Rect
& bounds
,
87 int top_left_corner_radius
,
88 int top_right_corner_radius
,
90 SkRect rect
= gfx::RectToSkRect(bounds
);
91 const SkScalar kTopLeftRadius
= SkIntToScalar(top_left_corner_radius
);
92 const SkScalar kTopRightRadius
= SkIntToScalar(top_right_corner_radius
);
94 kTopLeftRadius
, kTopLeftRadius
, // top-left
95 kTopRightRadius
, kTopRightRadius
, // top-right
99 path
.addRoundRect(rect
, radii
, SkPath::kCW_Direction
);
100 canvas
->DrawImageInPath(image
, -image_inset_x
, 0, path
, paint
);
103 // Tiles |frame_image| and |frame_overlay_image| into an area, rounding the top
105 void PaintFrameImagesInRoundRect(gfx::Canvas
* canvas
,
106 const gfx::ImageSkia
* frame_image
,
107 const gfx::ImageSkia
* frame_overlay_image
,
108 const SkPaint
& paint
,
109 const gfx::Rect
& bounds
,
112 SkXfermode::Mode normal_mode
;
113 SkXfermode::AsMode(NULL
, &normal_mode
);
115 // If |paint| is using an unusual SkXfermode::Mode (this is the case while
116 // crossfading), we must create a new canvas to overlay |frame_image| and
117 // |frame_overlay_image| using |normal_mode| and then paint the result
118 // using the unusual mode. We try to avoid this because creating a new
119 // browser-width canvas is expensive.
120 bool fast_path
= (!frame_overlay_image
||
121 SkXfermode::IsMode(paint
.getXfermode(), normal_mode
));
123 TileRoundRect(canvas
, *frame_image
, paint
, bounds
, corner_radius
,
124 corner_radius
, image_inset_x
);
126 if (frame_overlay_image
) {
127 // Adjust |bounds| such that |frame_overlay_image| is not tiled.
128 gfx::Rect overlay_bounds
= bounds
;
129 overlay_bounds
.Intersect(
130 gfx::Rect(bounds
.origin(), frame_overlay_image
->size()));
131 int top_left_corner_radius
= corner_radius
;
132 int top_right_corner_radius
= corner_radius
;
133 if (overlay_bounds
.width() < bounds
.width() - corner_radius
)
134 top_right_corner_radius
= 0;
135 TileRoundRect(canvas
, *frame_overlay_image
, paint
, overlay_bounds
,
136 top_left_corner_radius
, top_right_corner_radius
, 0);
139 gfx::Canvas
temporary_canvas(bounds
.size(), canvas
->image_scale(), false);
140 temporary_canvas
.TileImageInt(*frame_image
,
143 bounds
.width(), bounds
.height());
144 temporary_canvas
.DrawImageInt(*frame_overlay_image
, 0, 0);
145 TileRoundRect(canvas
, gfx::ImageSkia(temporary_canvas
.ExtractImageRep()),
146 paint
, bounds
, corner_radius
, corner_radius
, 0);
150 // Returns true if |child| and all ancestors are visible. Useful to ensure that
151 // a window is individually visible and is not part of a hidden workspace.
152 bool IsVisibleToRoot(Window
* child
) {
153 for (Window
* window
= child
; window
; window
= window
->parent()) {
154 // We must use TargetVisibility() because windows animate in and out and
155 // IsVisible() also tracks the layer visibility state.
156 if (!window
->TargetVisibility())
162 // Returns true if |window| is a "normal" window for purposes of solo window
163 // computations. Returns false for windows that are:
164 // * Not drawn (for example, DragDropTracker uses one for mouse capture)
165 // * Modal alerts (it looks odd for headers to change when an alert opens)
166 // * Constrained windows (ditto)
167 bool IsSoloWindowHeaderCandidate(aura::Window
* window
) {
169 window
->type() == aura::client::WINDOW_TYPE_NORMAL
&&
171 window
->layer()->type() != ui::LAYER_NOT_DRAWN
&&
172 window
->GetProperty(aura::client::kModalKey
) == ui::MODAL_TYPE_NONE
&&
173 !window
->GetProperty(ash::kConstrainedWindowKey
);
176 // Returns a list of windows in |root_window|| that potentially could have
177 // a transparent solo-window header.
178 std::vector
<Window
*> GetWindowsForSoloHeaderUpdate(RootWindow
* root_window
) {
179 std::vector
<Window
*> windows
;
180 // Avoid memory allocations for typical window counts.
182 // Collect windows from the desktop.
183 Window
* desktop
= ash::Shell::GetContainer(
184 root_window
, ash::internal::kShellWindowId_DefaultContainer
);
185 windows
.insert(windows
.end(),
186 desktop
->children().begin(),
187 desktop
->children().end());
188 // Collect "always on top" windows.
189 Window
* top_container
=
190 ash::Shell::GetContainer(
191 root_window
, ash::internal::kShellWindowId_AlwaysOnTopContainer
);
192 windows
.insert(windows
.end(),
193 top_container
->children().begin(),
194 top_container
->children().end());
202 int FramePainter::kActiveWindowOpacity
= 255; // 1.0
203 int FramePainter::kInactiveWindowOpacity
= 255; // 1.0
204 int FramePainter::kSoloWindowOpacity
= 77; // 0.3
206 ///////////////////////////////////////////////////////////////////////////////
207 // FramePainter, public:
209 FramePainter::FramePainter()
212 caption_button_container_(NULL
),
214 top_left_corner_(NULL
),
216 top_right_corner_(NULL
),
217 header_left_edge_(NULL
),
218 header_right_edge_(NULL
),
219 previous_theme_frame_id_(0),
220 previous_theme_frame_overlay_id_(0),
221 previous_opacity_(0),
222 crossfade_theme_frame_id_(0),
223 crossfade_theme_frame_overlay_id_(0),
224 crossfade_opacity_(0) {}
226 FramePainter::~FramePainter() {
227 // Sometimes we are destroyed before the window closes, so ensure we clean up.
229 window_
->RemoveObserver(this);
230 wm::GetWindowState(window_
)->RemoveObserver(this);
234 void FramePainter::Init(
235 views::Widget
* frame
,
236 views::View
* window_icon
,
237 FrameCaptionButtonContainerView
* caption_button_container
) {
239 // window_icon may be NULL.
240 DCHECK(caption_button_container
);
242 window_icon_
= window_icon
;
243 caption_button_container_
= caption_button_container
;
245 // Window frame image parts.
246 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
248 rb
.GetImageNamed(IDR_AURA_WINDOW_HEADER_SHADE_TOP_LEFT
).ToImageSkia();
250 rb
.GetImageNamed(IDR_AURA_WINDOW_HEADER_SHADE_TOP
).ToImageSkia();
252 rb
.GetImageNamed(IDR_AURA_WINDOW_HEADER_SHADE_TOP_RIGHT
).ToImageSkia();
254 rb
.GetImageNamed(IDR_AURA_WINDOW_HEADER_SHADE_LEFT
).ToImageSkia();
256 rb
.GetImageNamed(IDR_AURA_WINDOW_HEADER_SHADE_RIGHT
).ToImageSkia();
258 window_
= frame
->GetNativeWindow();
259 gfx::Insets
mouse_outer_insets(-kResizeOutsideBoundsSize
,
260 -kResizeOutsideBoundsSize
,
261 -kResizeOutsideBoundsSize
,
262 -kResizeOutsideBoundsSize
);
263 gfx::Insets touch_outer_insets
= mouse_outer_insets
.Scale(
264 kResizeOutsideBoundsScaleForTouch
);
265 // Ensure we get resize cursors for a few pixels outside our bounds.
266 window_
->SetHitTestBoundsOverrideOuter(mouse_outer_insets
,
268 // Ensure we get resize cursors just inside our bounds as well.
269 UpdateHitTestBoundsOverrideInner();
271 // Watch for maximize/restore/fullscreen state changes. Observer removes
272 // itself in OnWindowDestroying() below, or in the destructor if we go away
273 // before the window.
274 window_
->AddObserver(this);
275 wm::GetWindowState(window_
)->AddObserver(this);
277 // Solo-window header updates are handled by the workspace controller when
278 // this window is added to the desktop.
282 void FramePainter::SetSoloWindowHeadersEnabled(bool enabled
) {
283 solo_window_header_enabled
= enabled
;
287 void FramePainter::UpdateSoloWindowHeader(RootWindow
* root_window
) {
288 // Use a separate function here so callers outside of FramePainter don't need
289 // to know about "ignorable_window".
290 UpdateSoloWindowInRoot(root_window
, NULL
/* ignorable_window */);
293 gfx::Rect
FramePainter::GetBoundsForClientView(
295 const gfx::Rect
& window_bounds
) const {
299 std::max(0, window_bounds
.width() - (2 * kBorderThickness
)),
300 std::max(0, window_bounds
.height() - top_height
- kBorderThickness
));
303 gfx::Rect
FramePainter::GetWindowBoundsForClientBounds(
305 const gfx::Rect
& client_bounds
) const {
306 return gfx::Rect(std::max(0, client_bounds
.x() - kBorderThickness
),
307 std::max(0, client_bounds
.y() - top_height
),
308 client_bounds
.width() + (2 * kBorderThickness
),
309 client_bounds
.height() + top_height
+ kBorderThickness
);
312 int FramePainter::NonClientHitTest(views::NonClientFrameView
* view
,
313 const gfx::Point
& point
) {
314 gfx::Rect expanded_bounds
= view
->bounds();
315 int outside_bounds
= kResizeOutsideBoundsSize
;
317 if (aura::Env::GetInstance()->is_touch_down())
318 outside_bounds
*= kResizeOutsideBoundsScaleForTouch
;
319 expanded_bounds
.Inset(-outside_bounds
, -outside_bounds
);
321 if (!expanded_bounds
.Contains(point
))
324 // Check the frame first, as we allow a small area overlapping the contents
325 // to be used for resize handles.
326 bool can_ever_resize
= frame_
->widget_delegate() ?
327 frame_
->widget_delegate()->CanResize() :
329 // Don't allow overlapping resize handles when the window is maximized or
330 // fullscreen, as it can't be resized in those states.
332 frame_
->IsMaximized() || frame_
->IsFullscreen() ? 0 :
333 kResizeInsideBoundsSize
;
334 int frame_component
= view
->GetHTComponentForFrame(point
,
337 kResizeAreaCornerSize
,
338 kResizeAreaCornerSize
,
340 if (frame_component
!= HTNOWHERE
)
341 return frame_component
;
343 int client_component
= frame_
->client_view()->NonClientHitTest(point
);
344 if (client_component
!= HTNOWHERE
)
345 return client_component
;
347 if (caption_button_container_
->visible()) {
348 gfx::Point
point_in_caption_button_container(point
);
349 views::View::ConvertPointToTarget(view
, caption_button_container_
,
350 &point_in_caption_button_container
);
351 client_component
= caption_button_container_
->NonClientHitTest(
352 point_in_caption_button_container
);
353 if (client_component
!= HTNOWHERE
)
354 return client_component
;
357 // Caption is a safe default.
361 gfx::Size
FramePainter::GetMinimumSize(views::NonClientFrameView
* view
) {
362 gfx::Size min_size
= frame_
->client_view()->GetMinimumSize();
363 // Ensure we can display the top of the caption area.
364 gfx::Rect client_bounds
= view
->GetBoundsForClientView();
365 min_size
.Enlarge(0, client_bounds
.y());
366 // Ensure we have enough space for the window icon and buttons. We allow
367 // the title string to collapse to zero width.
368 int title_width
= GetTitleOffsetX() +
369 caption_button_container_
->GetMinimumSize().width();
370 if (title_width
> min_size
.width())
371 min_size
.set_width(title_width
);
375 gfx::Size
FramePainter::GetMaximumSize(views::NonClientFrameView
* view
) {
376 return frame_
->client_view()->GetMaximumSize();
379 int FramePainter::GetRightInset() const {
380 return caption_button_container_
->GetPreferredSize().width();
383 int FramePainter::GetThemeBackgroundXInset() const {
384 return kThemeFrameImageInsetX
;
387 bool FramePainter::ShouldUseMinimalHeaderStyle(Themed header_themed
) const {
388 // Use the minimalistic header style whenever |frame_| is maximized or
389 // fullscreen EXCEPT:
390 // - If the user has installed a theme with custom images for the header.
391 // - For windows which are not tracked by the workspace code (which are used
392 // for tab dragging).
393 return (frame_
->IsMaximized() || frame_
->IsFullscreen()) &&
394 header_themed
== THEMED_NO
&&
395 wm::GetWindowState(frame_
->GetNativeWindow())->tracked_by_workspace();
398 void FramePainter::PaintHeader(views::NonClientFrameView
* view
,
400 HeaderMode header_mode
,
402 int theme_frame_overlay_id
) {
403 bool initial_paint
= (previous_theme_frame_id_
== 0);
404 if (!initial_paint
&&
405 (previous_theme_frame_id_
!= theme_frame_id
||
406 previous_theme_frame_overlay_id_
!= theme_frame_overlay_id
)) {
407 aura::Window
* parent
= frame_
->GetNativeWindow()->parent();
408 // Don't animate the header if the parent (a workspace) is already
409 // animating. Doing so results in continually painting during the animation
410 // and gives a slower frame rate.
411 // TODO(sky): expose a better way to determine this rather than assuming
412 // the parent is a workspace.
413 bool parent_animating
= parent
&&
414 (parent
->layer()->GetAnimator()->IsAnimatingProperty(
415 ui::LayerAnimationElement::OPACITY
) ||
416 parent
->layer()->GetAnimator()->IsAnimatingProperty(
417 ui::LayerAnimationElement::VISIBILITY
));
418 if (!parent_animating
) {
419 crossfade_animation_
.reset(new gfx::SlideAnimation(this));
420 crossfade_theme_frame_id_
= previous_theme_frame_id_
;
421 crossfade_theme_frame_overlay_id_
= previous_theme_frame_overlay_id_
;
422 crossfade_opacity_
= previous_opacity_
;
423 crossfade_animation_
->SetSlideDuration(kActivationCrossfadeDurationMs
);
424 crossfade_animation_
->Show();
426 crossfade_animation_
.reset();
431 GetHeaderOpacity(header_mode
, theme_frame_id
, theme_frame_overlay_id
);
432 ui::ThemeProvider
* theme_provider
= frame_
->GetThemeProvider();
433 gfx::ImageSkia
* theme_frame
= theme_provider
->GetImageSkiaNamed(
435 gfx::ImageSkia
* theme_frame_overlay
= NULL
;
436 if (theme_frame_overlay_id
!= 0) {
437 theme_frame_overlay
= theme_provider
->GetImageSkiaNamed(
438 theme_frame_overlay_id
);
440 header_frame_bounds_
= gfx::Rect(0, 0, view
->width(), theme_frame
->height());
442 int corner_radius
= GetHeaderCornerRadius();
445 if (crossfade_animation_
.get() && crossfade_animation_
->is_animating()) {
446 gfx::ImageSkia
* crossfade_theme_frame
=
447 theme_provider
->GetImageSkiaNamed(crossfade_theme_frame_id_
);
448 gfx::ImageSkia
* crossfade_theme_frame_overlay
= NULL
;
449 if (crossfade_theme_frame_overlay_id_
!= 0) {
450 crossfade_theme_frame_overlay
= theme_provider
->GetImageSkiaNamed(
451 crossfade_theme_frame_overlay_id_
);
453 if (!crossfade_theme_frame
||
454 (crossfade_theme_frame_overlay_id_
!= 0 &&
455 !crossfade_theme_frame_overlay
)) {
456 // Reset the animation. This case occurs when the user switches the theme
457 // that they are using.
458 crossfade_animation_
.reset();
459 paint
.setAlpha(opacity
);
461 double current_value
= crossfade_animation_
->GetCurrentValue();
462 int old_alpha
= (1 - current_value
) * crossfade_opacity_
;
463 int new_alpha
= current_value
* opacity
;
465 // Draw the old header background, clipping the corners to be rounded.
466 paint
.setAlpha(old_alpha
);
467 paint
.setXfermodeMode(SkXfermode::kPlus_Mode
);
468 PaintFrameImagesInRoundRect(canvas
,
469 crossfade_theme_frame
,
470 crossfade_theme_frame_overlay
,
472 header_frame_bounds_
,
474 GetThemeBackgroundXInset());
476 paint
.setAlpha(new_alpha
);
479 paint
.setAlpha(opacity
);
482 // Draw the header background, clipping the corners to be rounded.
483 PaintFrameImagesInRoundRect(canvas
,
487 header_frame_bounds_
,
489 GetThemeBackgroundXInset());
491 previous_theme_frame_id_
= theme_frame_id
;
492 previous_theme_frame_overlay_id_
= theme_frame_overlay_id
;
493 previous_opacity_
= opacity
;
495 // We don't need the extra lightness in the edges when we're at the top edge
496 // of the screen or when the header's corners are not rounded.
498 // TODO(sky): this isn't quite right. What we really want is a method that
499 // returns bounds ignoring transforms on certain windows (such as workspaces)
500 // and is relative to the root.
501 if (frame_
->GetNativeWindow()->bounds().y() == 0 || corner_radius
== 0)
504 // Draw the top corners and edge.
505 int top_left_height
= top_left_corner_
->height();
506 canvas
->DrawImageInt(*top_left_corner_
,
507 0, 0, top_left_corner_
->width(), top_left_height
,
508 0, 0, top_left_corner_
->width(), top_left_height
,
510 canvas
->TileImageInt(*top_edge_
,
511 top_left_corner_
->width(),
513 view
->width() - top_left_corner_
->width() - top_right_corner_
->width(),
514 top_edge_
->height());
515 int top_right_height
= top_right_corner_
->height();
516 canvas
->DrawImageInt(*top_right_corner_
,
518 top_right_corner_
->width(), top_right_height
,
519 view
->width() - top_right_corner_
->width(), 0,
520 top_right_corner_
->width(), top_right_height
,
524 int header_left_height
= theme_frame
->height() - top_left_height
;
525 canvas
->TileImageInt(*header_left_edge_
,
527 header_left_edge_
->width(), header_left_height
);
529 // Header right edge.
530 int header_right_height
= theme_frame
->height() - top_right_height
;
531 canvas
->TileImageInt(*header_right_edge_
,
532 view
->width() - header_right_edge_
->width(),
534 header_right_edge_
->width(),
535 header_right_height
);
537 // We don't draw edges around the content area. Web content goes flush
538 // to the edge of the window.
541 void FramePainter::PaintHeaderContentSeparator(views::NonClientFrameView
* view
,
542 gfx::Canvas
* canvas
) {
543 // Paint the line just above the content area.
544 gfx::Rect client_bounds
= view
->GetBoundsForClientView();
545 canvas
->FillRect(gfx::Rect(client_bounds
.x(),
546 client_bounds
.y() - kHeaderContentSeparatorSize
,
547 client_bounds
.width(),
548 kHeaderContentSeparatorSize
),
549 kHeaderContentSeparatorColor
);
552 int FramePainter::HeaderContentSeparatorSize() const {
553 return kHeaderContentSeparatorSize
;
556 void FramePainter::PaintTitleBar(views::NonClientFrameView
* view
,
558 const gfx::Font
& title_font
) {
559 // The window icon is painted by its own views::View.
560 views::WidgetDelegate
* delegate
= frame_
->widget_delegate();
561 if (delegate
&& delegate
->ShouldShowWindowTitle()) {
562 gfx::Rect title_bounds
= GetTitleBounds(title_font
);
563 SkColor title_color
= frame_
->IsMaximized() ?
564 kMaximizedWindowTitleTextColor
: kNonMaximizedWindowTitleTextColor
;
565 canvas
->DrawStringInt(delegate
->GetWindowTitle(),
568 view
->GetMirroredXForRect(title_bounds
),
570 title_bounds
.width(),
571 title_bounds
.height(),
572 gfx::Canvas::NO_SUBPIXEL_RENDERING
);
576 void FramePainter::LayoutHeader(views::NonClientFrameView
* view
,
577 bool shorter_layout
) {
578 caption_button_container_
->set_header_style(shorter_layout
?
579 FrameCaptionButtonContainerView::HEADER_STYLE_SHORT
:
580 FrameCaptionButtonContainerView::HEADER_STYLE_TALL
);
581 caption_button_container_
->Layout();
583 gfx::Size caption_button_container_size
=
584 caption_button_container_
->GetPreferredSize();
585 caption_button_container_
->SetBounds(
586 view
->width() - caption_button_container_size
.width(),
588 caption_button_container_size
.width(),
589 caption_button_container_size
.height());
592 // Vertically center the window icon with respect to the caption button
595 GetCaptionButtonContainerCenterY() - window_icon_
->height() / 2;
596 window_icon_
->SetBounds(kIconOffsetX
, icon_offset_y
, kIconSize
, kIconSize
);
600 void FramePainter::SchedulePaintForTitle(const gfx::Font
& title_font
) {
601 frame_
->non_client_view()->SchedulePaintInRect(GetTitleBounds(title_font
));
604 void FramePainter::OnThemeChanged() {
605 // We do not cache the images for |previous_theme_frame_id_| and
606 // |previous_theme_frame_overlay_id_|. Changing the theme changes the images
607 // returned from ui::ThemeProvider for |previous_theme_frame_id_|
608 // and |previous_theme_frame_overlay_id_|. Reset the image ids to prevent
609 // starting a crossfade animation with these images.
610 previous_theme_frame_id_
= 0;
611 previous_theme_frame_overlay_id_
= 0;
613 if (crossfade_animation_
.get() && crossfade_animation_
->is_animating()) {
614 crossfade_animation_
.reset();
615 frame_
->non_client_view()->SchedulePaintInRect(header_frame_bounds_
);
619 ///////////////////////////////////////////////////////////////////////////////
620 // WindowStateObserver overrides:
621 void FramePainter::OnTrackedByWorkspaceChanged(wm::WindowState
* window_state
,
623 // When 'TrackedByWorkspace' changes, we are going to paint the header
624 // differently. Schedule a paint to ensure everything is updated correctly.
625 if (window_state
->tracked_by_workspace())
626 frame_
->non_client_view()->SchedulePaint();
629 void FramePainter::OnWindowShowTypeChanged(wm::WindowState
* window_state
,
630 wm::WindowShowType old_type
) {
631 UpdateHitTestBoundsOverrideInner();
634 ///////////////////////////////////////////////////////////////////////////////
635 // aura::WindowObserver overrides:
637 void FramePainter::OnWindowVisibilityChanged(aura::Window
* window
,
639 // OnWindowVisibilityChanged can be called for the child windows of |window_|.
640 if (window
!= window_
)
643 // Window visibility change may trigger the change of window solo-ness in a
645 UpdateSoloWindowInRoot(window_
->GetRootWindow(), visible
? NULL
: window_
);
648 void FramePainter::OnWindowDestroying(aura::Window
* destroying
) {
649 DCHECK_EQ(window_
, destroying
);
651 // Must be removed here and not in the destructor, as the aura::Window is
652 // already destroyed when our destructor runs.
653 window_
->RemoveObserver(this);
654 wm::GetWindowState(window_
)->RemoveObserver(this);
656 // If we have two or more windows open and we close this one, we might trigger
657 // the solo window appearance for another window.
658 UpdateSoloWindowInRoot(window_
->GetRootWindow(), window_
);
663 void FramePainter::OnWindowBoundsChanged(aura::Window
* window
,
664 const gfx::Rect
& old_bounds
,
665 const gfx::Rect
& new_bounds
) {
666 // TODO(sky): this isn't quite right. What we really want is a method that
667 // returns bounds ignoring transforms on certain windows (such as workspaces).
668 if ((!frame_
->IsMaximized() && !frame_
->IsFullscreen()) &&
669 ((old_bounds
.y() == 0 && new_bounds
.y() != 0) ||
670 (old_bounds
.y() != 0 && new_bounds
.y() == 0))) {
671 SchedulePaintForHeader();
675 void FramePainter::OnWindowAddedToRootWindow(aura::Window
* window
) {
676 // Needs to trigger the window appearance change if the window moves across
677 // root windows and a solo window is already in the new root.
678 UpdateSoloWindowInRoot(window
->GetRootWindow(), NULL
/* ignore_window */);
681 void FramePainter::OnWindowRemovingFromRootWindow(aura::Window
* window
) {
682 // Needs to trigger the window appearance change if the window moves across
683 // root windows and only one window is left in the previous root. Because
684 // |window| is not yet moved, |window| has to be ignored.
685 UpdateSoloWindowInRoot(window
->GetRootWindow(), window
);
688 ///////////////////////////////////////////////////////////////////////////////
689 // gfx::AnimationDelegate overrides:
691 void FramePainter::AnimationProgressed(const gfx::Animation
* animation
) {
692 frame_
->non_client_view()->SchedulePaintInRect(header_frame_bounds_
);
695 ///////////////////////////////////////////////////////////////////////////////
696 // FramePainter, private:
698 int FramePainter::GetTitleOffsetX() const {
699 return window_icon_
?
700 window_icon_
->bounds().right() + kTitleIconOffsetX
:
704 int FramePainter::GetCaptionButtonContainerCenterY() const {
705 return caption_button_container_
->y() +
706 caption_button_container_
->height() / 2;
709 int FramePainter::GetHeaderCornerRadius() const {
710 // Use square corners for maximized and fullscreen windows when they are
711 // tracked by the workspace code. (Windows which are not tracked by the
712 // workspace code are used for tab dragging.)
713 bool square_corners
= ((frame_
->IsMaximized() || frame_
->IsFullscreen())) &&
714 wm::GetWindowState(frame_
->GetNativeWindow())->tracked_by_workspace();
715 const int kCornerRadius
= 2;
716 return square_corners
? 0 : kCornerRadius
;
719 int FramePainter::GetHeaderOpacity(
720 HeaderMode header_mode
,
722 int theme_frame_overlay_id
) const {
723 // User-provided themes are painted fully opaque.
724 ui::ThemeProvider
* theme_provider
= frame_
->GetThemeProvider();
725 if (theme_provider
->HasCustomImage(theme_frame_id
) ||
726 (theme_frame_overlay_id
!= 0 &&
727 theme_provider
->HasCustomImage(theme_frame_overlay_id
))) {
731 // The header is fully opaque when using the minimalistic header style.
732 if (ShouldUseMinimalHeaderStyle(THEMED_NO
))
735 // Single browser window is very transparent.
736 if (UseSoloWindowHeader())
737 return kSoloWindowOpacity
;
739 // Otherwise, change transparency based on window activation status.
740 if (header_mode
== ACTIVE
)
741 return kActiveWindowOpacity
;
742 return kInactiveWindowOpacity
;
745 bool FramePainter::UseSoloWindowHeader() const {
746 if (!solo_window_header_enabled
)
748 // Don't use transparent headers for panels, pop-ups, etc.
749 if (!IsSoloWindowHeaderCandidate(window_
))
751 aura::RootWindow
* root
= window_
->GetRootWindow();
752 // Don't recompute every time, as it would require many window property
754 return internal::GetRootWindowSettings(root
)->solo_window_header
;
758 bool FramePainter::UseSoloWindowHeaderInRoot(RootWindow
* root_window
,
759 Window
* ignore_window
) {
760 int visible_window_count
= 0;
761 std::vector
<Window
*> windows
= GetWindowsForSoloHeaderUpdate(root_window
);
762 for (std::vector
<Window
*>::const_iterator it
= windows
.begin();
765 Window
* window
= *it
;
766 // Various sorts of windows "don't count" for this computation.
767 if (ignore_window
== window
||
768 !IsSoloWindowHeaderCandidate(window
) ||
769 !IsVisibleToRoot(window
))
771 if (wm::GetWindowState(window
)->IsMaximized())
773 ++visible_window_count
;
774 if (visible_window_count
> 1)
777 // Count must be tested because all windows might be "don't count" windows
778 // in the loop above.
779 return visible_window_count
== 1;
783 void FramePainter::UpdateSoloWindowInRoot(RootWindow
* root
,
784 Window
* ignore_window
) {
786 // Non-Ash Windows doesn't do solo-window counting for transparency effects,
787 // as the desktop background and window frames are managed by the OS.
788 if (!ash::Shell::HasInstance())
793 internal::RootWindowSettings
* root_window_settings
=
794 internal::GetRootWindowSettings(root
);
795 bool old_solo_header
= root_window_settings
->solo_window_header
;
796 bool new_solo_header
= UseSoloWindowHeaderInRoot(root
, ignore_window
);
797 if (old_solo_header
== new_solo_header
)
799 root_window_settings
->solo_window_header
= new_solo_header
;
801 // Invalidate all the window frames in the desktop. There should only be
803 std::vector
<Window
*> windows
= GetWindowsForSoloHeaderUpdate(root
);
804 for (std::vector
<Window
*>::const_iterator it
= windows
.begin();
807 Widget
* widget
= Widget::GetWidgetForNativeWindow(*it
);
808 if (widget
&& widget
->non_client_view())
809 widget
->non_client_view()->SchedulePaint();
813 void FramePainter::UpdateHitTestBoundsOverrideInner() {
814 // Maximized and fullscreen windows don't want resize handles overlapping the
815 // content area, because when the user moves the cursor to the right screen
816 // edge we want them to be able to hit the scroll bar.
817 if (wm::GetWindowState(window_
)->IsMaximizedOrFullscreen()) {
818 window_
->set_hit_test_bounds_override_inner(gfx::Insets());
820 window_
->set_hit_test_bounds_override_inner(
821 gfx::Insets(kResizeInsideBoundsSize
, kResizeInsideBoundsSize
,
822 kResizeInsideBoundsSize
, kResizeInsideBoundsSize
));
826 void FramePainter::SchedulePaintForHeader() {
827 int top_left_height
= top_left_corner_
->height();
828 int top_right_height
= top_right_corner_
->height();
829 frame_
->non_client_view()->SchedulePaintInRect(
830 gfx::Rect(0, 0, frame_
->non_client_view()->width(),
831 std::max(top_left_height
, top_right_height
)));
834 gfx::Rect
FramePainter::GetTitleBounds(const gfx::Font
& title_font
) {
835 int title_x
= GetTitleOffsetX();
836 // Center the text with respect to the caption button container. This way it
837 // adapts to the caption button height and aligns exactly with the window
838 // icon. Don't use |window_icon_| for this computation as it may be NULL.
839 int title_y
= GetCaptionButtonContainerCenterY() - title_font
.GetHeight() / 2;
842 std::max(0, title_y
),
843 std::max(0, caption_button_container_
->x() - kTitleLogoSpacing
- title_x
),
844 title_font
.GetHeight());