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