Windows should animate when they are about to get docked at screen edges.
[chromium-blink-merge.git] / ash / wm / frame_painter.cc
blobadf9dd7b7a27c13a6f0a2683e85c73b09bf98439
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"
7 #include <vector>
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;
41 using aura::Window;
42 using views::Widget;
44 namespace {
45 // TODO(jamescook): Border is specified to be a single pixel overlapping
46 // the web content and may need to be built into the shadow layers instead.
47 const int kBorderThickness = 0;
48 // Space between left edge of window and popup window icon.
49 const int kIconOffsetX = 9;
50 // Height and width of window icon.
51 const int kIconSize = 16;
52 // Space between the title text and the caption buttons.
53 const int kTitleLogoSpacing = 5;
54 // Space between window icon and title text.
55 const int kTitleIconOffsetX = 5;
56 // Space between window edge and title text, when there is no icon.
57 const int kTitleNoIconOffsetX = 8;
58 // Color for the non-maximized window title text.
59 const SkColor kNonMaximizedWindowTitleTextColor = SkColorSetRGB(40, 40, 40);
60 // Color for the maximized window title text.
61 const SkColor kMaximizedWindowTitleTextColor = SK_ColorWHITE;
62 // Size of header/content separator line below the header image.
63 const int kHeaderContentSeparatorSize = 1;
64 // Color of header bottom edge line.
65 const SkColor kHeaderContentSeparatorColor = SkColorSetRGB(128, 128, 128);
66 // Space between close button and right edge of window.
67 const int kCloseButtonOffsetX = 0;
68 // Space between close button and top edge of window.
69 const int kCloseButtonOffsetY = 0;
70 // The size and close buttons are designed to slightly overlap in order
71 // to do fancy hover highlighting.
72 const int kSizeButtonOffsetX = -1;
73 // In the pre-Ash era the web content area had a frame along the left edge, so
74 // user-generated theme images for the new tab page assume they are shifted
75 // right relative to the header. Now that we have removed the left edge frame
76 // we need to copy the theme image for the window header from a few pixels
77 // inset to preserve alignment with the NTP image, or else we'll break a bunch
78 // of existing themes. We do something similar on OS X for the same reason.
79 const int kThemeFrameImageInsetX = 5;
80 // Duration of crossfade animation for activating and deactivating frame.
81 const int kActivationCrossfadeDurationMs = 200;
82 // Alpha/opacity value for fully-opaque headers.
83 const int kFullyOpaque = 255;
85 // Tiles an image into an area, rounding the top corners. Samples |image|
86 // starting |image_inset_x| pixels from the left of the image.
87 void TileRoundRect(gfx::Canvas* canvas,
88 const gfx::ImageSkia& image,
89 const SkPaint& paint,
90 const gfx::Rect& bounds,
91 int top_left_corner_radius,
92 int top_right_corner_radius,
93 int image_inset_x) {
94 SkRect rect = gfx::RectToSkRect(bounds);
95 const SkScalar kTopLeftRadius = SkIntToScalar(top_left_corner_radius);
96 const SkScalar kTopRightRadius = SkIntToScalar(top_right_corner_radius);
97 SkScalar radii[8] = {
98 kTopLeftRadius, kTopLeftRadius, // top-left
99 kTopRightRadius, kTopRightRadius, // top-right
100 0, 0, // bottom-right
101 0, 0}; // bottom-left
102 SkPath path;
103 path.addRoundRect(rect, radii, SkPath::kCW_Direction);
104 canvas->DrawImageInPath(image, -image_inset_x, 0, path, paint);
107 // Tiles |frame_image| and |frame_overlay_image| into an area, rounding the top
108 // corners.
109 void PaintFrameImagesInRoundRect(gfx::Canvas* canvas,
110 const gfx::ImageSkia* frame_image,
111 const gfx::ImageSkia* frame_overlay_image,
112 const SkPaint& paint,
113 const gfx::Rect& bounds,
114 int corner_radius,
115 int image_inset_x) {
116 SkXfermode::Mode normal_mode;
117 SkXfermode::AsMode(NULL, &normal_mode);
119 // If |paint| is using an unusual SkXfermode::Mode (this is the case while
120 // crossfading), we must create a new canvas to overlay |frame_image| and
121 // |frame_overlay_image| using |normal_mode| and then paint the result
122 // using the unusual mode. We try to avoid this because creating a new
123 // browser-width canvas is expensive.
124 bool fast_path = (!frame_overlay_image ||
125 SkXfermode::IsMode(paint.getXfermode(), normal_mode));
126 if (fast_path) {
127 TileRoundRect(canvas, *frame_image, paint, bounds, corner_radius,
128 corner_radius, image_inset_x);
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);
142 } else {
143 gfx::Canvas temporary_canvas(bounds.size(), canvas->scale_factor(), false);
144 temporary_canvas.TileImageInt(*frame_image,
145 image_inset_x, 0,
146 0, 0,
147 bounds.width(), bounds.height());
148 temporary_canvas.DrawImageInt(*frame_overlay_image, 0, 0);
149 TileRoundRect(canvas, gfx::ImageSkia(temporary_canvas.ExtractImageRep()),
150 paint, bounds, corner_radius, corner_radius, 0);
154 // Returns true if |child| and all ancestors are visible. Useful to ensure that
155 // a window is individually visible and is not part of a hidden workspace.
156 bool IsVisibleToRoot(Window* child) {
157 for (Window* window = child; window; window = window->parent()) {
158 // We must use TargetVisibility() because windows animate in and out and
159 // IsVisible() also tracks the layer visibility state.
160 if (!window->TargetVisibility())
161 return false;
163 return true;
166 // Returns true if |window| is a "normal" window for purposes of solo window
167 // computations. Returns false for windows that are:
168 // * Not drawn (for example, DragDropTracker uses one for mouse capture)
169 // * Modal alerts (it looks odd for headers to change when an alert opens)
170 // * Constrained windows (ditto)
171 bool IsSoloWindowHeaderCandidate(aura::Window* window) {
172 return window &&
173 window->type() == aura::client::WINDOW_TYPE_NORMAL &&
174 window->layer() &&
175 window->layer()->type() != ui::LAYER_NOT_DRAWN &&
176 window->GetProperty(aura::client::kModalKey) == ui::MODAL_TYPE_NONE &&
177 !window->GetProperty(ash::kConstrainedWindowKey);
180 // Returns a list of windows in |root_window|| that potentially could have
181 // a transparent solo-window header.
182 std::vector<Window*> GetWindowsForSoloHeaderUpdate(RootWindow* root_window) {
183 std::vector<Window*> windows;
184 // During shutdown there may not be a workspace controller. In that case
185 // we don't care about updating any windows.
186 // Avoid memory allocations for typical window counts.
187 windows.reserve(16);
188 // Collect windows from the desktop.
189 Window* desktop = ash::Shell::GetContainer(
190 root_window, ash::internal::kShellWindowId_DefaultContainer);
191 windows.insert(windows.end(),
192 desktop->children().begin(),
193 desktop->children().end());
194 // Collect "always on top" windows.
195 Window* top_container =
196 ash::Shell::GetContainer(
197 root_window, ash::internal::kShellWindowId_AlwaysOnTopContainer);
198 windows.insert(windows.end(),
199 top_container->children().begin(),
200 top_container->children().end());
201 return windows;
203 } // namespace
205 namespace ash {
207 // static
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()
216 : frame_(NULL),
217 window_icon_(NULL),
218 size_button_(NULL),
219 close_button_(NULL),
220 window_(NULL),
221 button_separator_(NULL),
222 top_left_corner_(NULL),
223 top_edge_(NULL),
224 top_right_corner_(NULL),
225 header_left_edge_(NULL),
226 header_right_edge_(NULL),
227 previous_theme_frame_id_(0),
228 previous_theme_frame_overlay_id_(0),
229 previous_opacity_(0),
230 crossfade_theme_frame_id_(0),
231 crossfade_theme_frame_overlay_id_(0),
232 crossfade_opacity_(0),
233 size_button_behavior_(SIZE_BUTTON_MAXIMIZES) {}
235 FramePainter::~FramePainter() {
236 // Sometimes we are destroyed before the window closes, so ensure we clean up.
237 if (window_) {
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) {
247 DCHECK(frame);
248 // window_icon may be NULL.
249 DCHECK(size_button);
250 DCHECK(close_button);
251 frame_ = frame;
252 window_icon_ = window_icon;
253 size_button_ = size_button;
254 close_button_ = close_button;
255 size_button_behavior_ = behavior;
257 // Window frame image parts.
258 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
259 button_separator_ =
260 rb.GetImageNamed(IDR_AURA_WINDOW_BUTTON_SEPARATOR).ToImageSkia();
261 top_left_corner_ =
262 rb.GetImageNamed(IDR_AURA_WINDOW_HEADER_SHADE_TOP_LEFT).ToImageSkia();
263 top_edge_ =
264 rb.GetImageNamed(IDR_AURA_WINDOW_HEADER_SHADE_TOP).ToImageSkia();
265 top_right_corner_ =
266 rb.GetImageNamed(IDR_AURA_WINDOW_HEADER_SHADE_TOP_RIGHT).ToImageSkia();
267 header_left_edge_ =
268 rb.GetImageNamed(IDR_AURA_WINDOW_HEADER_SHADE_LEFT).ToImageSkia();
269 header_right_edge_ =
270 rb.GetImageNamed(IDR_AURA_WINDOW_HEADER_SHADE_RIGHT).ToImageSkia();
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.
293 // static
294 void FramePainter::UpdateSoloWindowHeader(RootWindow* root_window) {
295 // Use a separate function here so callers outside of FramePainter don't need
296 // to know about "ignorable_window".
297 UpdateSoloWindowInRoot(root_window, NULL /* ignorable_window */);
300 gfx::Rect FramePainter::GetBoundsForClientView(
301 int top_height,
302 const gfx::Rect& window_bounds) const {
303 return gfx::Rect(
304 kBorderThickness,
305 top_height,
306 std::max(0, window_bounds.width() - (2 * kBorderThickness)),
307 std::max(0, window_bounds.height() - top_height - kBorderThickness));
310 gfx::Rect FramePainter::GetWindowBoundsForClientBounds(
311 int top_height,
312 const gfx::Rect& client_bounds) const {
313 return gfx::Rect(std::max(0, client_bounds.x() - kBorderThickness),
314 std::max(0, client_bounds.y() - top_height),
315 client_bounds.width() + (2 * kBorderThickness),
316 client_bounds.height() + top_height + kBorderThickness);
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))
329 return HTNOWHERE;
331 // No avatar button.
333 // Check the frame first, as we allow a small area overlapping the contents
334 // to be used for resize handles.
335 bool can_ever_resize = frame_->widget_delegate() ?
336 frame_->widget_delegate()->CanResize() :
337 false;
338 // Don't allow overlapping resize handles when the window is maximized or
339 // fullscreen, as it can't be resized in those states.
340 int resize_border =
341 frame_->IsMaximized() || frame_->IsFullscreen() ? 0 :
342 kResizeInsideBoundsSize;
343 int frame_component = view->GetHTComponentForFrame(point,
344 resize_border,
345 resize_border,
346 kResizeAreaCornerSize,
347 kResizeAreaCornerSize,
348 can_ever_resize);
349 if (frame_component != HTNOWHERE)
350 return frame_component;
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))
359 return HTCLOSE;
360 if (size_button_->visible() &&
361 size_button_->GetMirroredBounds().Contains(point))
362 return HTMAXBUTTON;
364 // Caption is a safe default.
365 return HTCAPTION;
368 gfx::Size FramePainter::GetMinimumSize(views::NonClientFrameView* view) {
369 gfx::Size min_size = frame_->client_view()->GetMinimumSize();
370 // Ensure we can display the top of the caption area.
371 gfx::Rect client_bounds = view->GetBoundsForClientView();
372 min_size.Enlarge(0, client_bounds.y());
373 // Ensure we have enough space for the window icon and buttons. We allow
374 // the title string to collapse to zero width.
375 int title_width = GetTitleOffsetX() +
376 size_button_->width() + kSizeButtonOffsetX +
377 close_button_->width() + kCloseButtonOffsetX;
378 if (title_width > min_size.width())
379 min_size.set_width(title_width);
380 return min_size;
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;
392 return inset;
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,
411 gfx::Canvas* canvas,
412 HeaderMode header_mode,
413 int theme_frame_id,
414 int theme_frame_overlay_id) {
415 bool initial_paint = (previous_theme_frame_id_ == 0);
416 if (!initial_paint &&
417 (previous_theme_frame_id_ != theme_frame_id ||
418 previous_theme_frame_overlay_id_ != theme_frame_overlay_id)) {
419 aura::Window* parent = frame_->GetNativeWindow()->parent();
420 // Don't animate the header if the parent (a workspace) is already
421 // animating. Doing so results in continually painting during the animation
422 // and gives a slower frame rate.
423 // TODO(sky): expose a better way to determine this rather than assuming
424 // the parent is a workspace.
425 bool parent_animating = parent &&
426 (parent->layer()->GetAnimator()->IsAnimatingProperty(
427 ui::LayerAnimationElement::OPACITY) ||
428 parent->layer()->GetAnimator()->IsAnimatingProperty(
429 ui::LayerAnimationElement::VISIBILITY));
430 if (!parent_animating) {
431 crossfade_animation_.reset(new ui::SlideAnimation(this));
432 crossfade_theme_frame_id_ = previous_theme_frame_id_;
433 crossfade_theme_frame_overlay_id_ = previous_theme_frame_overlay_id_;
434 crossfade_opacity_ = previous_opacity_;
435 crossfade_animation_->SetSlideDuration(kActivationCrossfadeDurationMs);
436 crossfade_animation_->Show();
437 } else {
438 crossfade_animation_.reset();
442 int opacity =
443 GetHeaderOpacity(header_mode, theme_frame_id, theme_frame_overlay_id);
444 ui::ThemeProvider* theme_provider = frame_->GetThemeProvider();
445 gfx::ImageSkia* theme_frame = theme_provider->GetImageSkiaNamed(
446 theme_frame_id);
447 gfx::ImageSkia* theme_frame_overlay = NULL;
448 if (theme_frame_overlay_id != 0) {
449 theme_frame_overlay = theme_provider->GetImageSkiaNamed(
450 theme_frame_overlay_id);
452 header_frame_bounds_ = gfx::Rect(0, 0, view->width(), theme_frame->height());
454 int corner_radius = GetHeaderCornerRadius();
455 SkPaint paint;
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);
472 } else {
473 double current_value = crossfade_animation_->GetCurrentValue();
474 int old_alpha = (1 - current_value) * crossfade_opacity_;
475 int new_alpha = current_value * opacity;
477 // Draw the old header background, clipping the corners to be rounded.
478 paint.setAlpha(old_alpha);
479 paint.setXfermodeMode(SkXfermode::kPlus_Mode);
480 PaintFrameImagesInRoundRect(canvas,
481 crossfade_theme_frame,
482 crossfade_theme_frame_overlay,
483 paint,
484 header_frame_bounds_,
485 corner_radius,
486 GetThemeBackgroundXInset());
488 paint.setAlpha(new_alpha);
490 } else {
491 paint.setAlpha(opacity);
494 // Draw the header background, clipping the corners to be rounded.
495 PaintFrameImagesInRoundRect(canvas,
496 theme_frame,
497 theme_frame_overlay,
498 paint,
499 header_frame_bounds_,
500 corner_radius,
501 GetThemeBackgroundXInset());
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),
513 close_button_->y());
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)
522 return;
524 // Draw the top corners and edge.
525 int top_left_height = top_left_corner_->height();
526 canvas->DrawImageInt(*top_left_corner_,
527 0, 0, top_left_corner_->width(), top_left_height,
528 0, 0, top_left_corner_->width(), top_left_height,
529 false);
530 canvas->TileImageInt(*top_edge_,
531 top_left_corner_->width(),
533 view->width() - top_left_corner_->width() - top_right_corner_->width(),
534 top_edge_->height());
535 int top_right_height = top_right_corner_->height();
536 canvas->DrawImageInt(*top_right_corner_,
537 0, 0,
538 top_right_corner_->width(), top_right_height,
539 view->width() - top_right_corner_->width(), 0,
540 top_right_corner_->width(), top_right_height,
541 false);
543 // Header left edge.
544 int header_left_height = theme_frame->height() - top_left_height;
545 canvas->TileImageInt(*header_left_edge_,
546 0, top_left_height,
547 header_left_edge_->width(), header_left_height);
549 // Header right edge.
550 int header_right_height = theme_frame->height() - top_right_height;
551 canvas->TileImageInt(*header_right_edge_,
552 view->width() - header_right_edge_->width(),
553 top_right_height,
554 header_right_edge_->width(),
555 header_right_height);
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,
577 gfx::Canvas* canvas,
578 const gfx::Font& title_font) {
579 // The window icon is painted by its own views::View.
580 views::WidgetDelegate* delegate = frame_->widget_delegate();
581 if (delegate && delegate->ShouldShowWindowTitle()) {
582 gfx::Rect title_bounds = GetTitleBounds(title_font);
583 SkColor title_color = frame_->IsMaximized() ?
584 kMaximizedWindowTitleTextColor : kNonMaximizedWindowTitleTextColor;
585 canvas->DrawStringInt(delegate->GetWindowTitle(),
586 title_font,
587 title_color,
588 view->GetMirroredXForRect(title_bounds),
589 title_bounds.y(),
590 title_bounds.width(),
591 title_bounds.height(),
592 gfx::Canvas::NO_SUBPIXEL_RENDERING);
596 void FramePainter::LayoutHeader(views::NonClientFrameView* view,
597 bool shorter_layout) {
598 // The new assets only make sense if the window is actually maximized or
599 // fullscreen.
600 if (shorter_layout &&
601 (frame_->IsMaximized() || frame_->IsFullscreen()) &&
602 GetTrackedByWorkspace(frame_->GetNativeWindow())) {
603 SetButtonImages(close_button_,
604 IDR_AURA_WINDOW_MAXIMIZED_CLOSE2,
605 IDR_AURA_WINDOW_MAXIMIZED_CLOSE2_H,
606 IDR_AURA_WINDOW_MAXIMIZED_CLOSE2_P);
607 // The chat window cannot be restored but only minimized.
608 if (size_button_behavior_ == SIZE_BUTTON_MINIMIZES) {
609 SetButtonImages(size_button_,
610 IDR_AURA_WINDOW_MINIMIZE_SHORT,
611 IDR_AURA_WINDOW_MINIMIZE_SHORT_H,
612 IDR_AURA_WINDOW_MINIMIZE_SHORT_P);
613 } else {
614 SetButtonImages(size_button_,
615 IDR_AURA_WINDOW_MAXIMIZED_RESTORE2,
616 IDR_AURA_WINDOW_MAXIMIZED_RESTORE2_H,
617 IDR_AURA_WINDOW_MAXIMIZED_RESTORE2_P);
619 } else if (shorter_layout) {
620 SetButtonImages(close_button_,
621 IDR_AURA_WINDOW_MAXIMIZED_CLOSE,
622 IDR_AURA_WINDOW_MAXIMIZED_CLOSE_H,
623 IDR_AURA_WINDOW_MAXIMIZED_CLOSE_P);
624 // The chat window cannot be restored but only minimized.
625 if (size_button_behavior_ == SIZE_BUTTON_MINIMIZES) {
626 SetButtonImages(size_button_,
627 IDR_AURA_WINDOW_MINIMIZE_SHORT,
628 IDR_AURA_WINDOW_MINIMIZE_SHORT_H,
629 IDR_AURA_WINDOW_MINIMIZE_SHORT_P);
630 } else {
631 SetButtonImages(size_button_,
632 IDR_AURA_WINDOW_MAXIMIZED_RESTORE,
633 IDR_AURA_WINDOW_MAXIMIZED_RESTORE_H,
634 IDR_AURA_WINDOW_MAXIMIZED_RESTORE_P);
636 } else {
637 SetButtonImages(close_button_,
638 IDR_AURA_WINDOW_CLOSE,
639 IDR_AURA_WINDOW_CLOSE_H,
640 IDR_AURA_WINDOW_CLOSE_P);
641 SetButtonImages(size_button_,
642 IDR_AURA_WINDOW_MAXIMIZE,
643 IDR_AURA_WINDOW_MAXIMIZE_H,
644 IDR_AURA_WINDOW_MAXIMIZE_P);
647 gfx::Size close_size = close_button_->GetPreferredSize();
648 close_button_->SetBounds(
649 view->width() - close_size.width() - kCloseButtonOffsetX,
650 kCloseButtonOffsetY,
651 close_size.width(),
652 close_size.height());
654 gfx::Size size_button_size = size_button_->GetPreferredSize();
655 size_button_->SetBounds(
656 close_button_->x() - size_button_size.width() - kSizeButtonOffsetX,
657 close_button_->y(),
658 size_button_size.width(),
659 size_button_size.height());
661 if (window_icon_) {
662 // Vertically center the window icon with respect to the close button.
663 int icon_offset_y = GetCloseButtonCenterY() - window_icon_->height() / 2;
664 window_icon_->SetBounds(kIconOffsetX, icon_offset_y, kIconSize, kIconSize);
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,
691 const void* key,
692 intptr_t old) {
693 // When 'kWindowTrackedByWorkspaceKey' changes, we are going to paint the
694 // header differently. Schedule a paint to ensure everything is updated
695 // correctly.
696 if (key == internal::kWindowTrackedByWorkspaceKey &&
697 GetTrackedByWorkspace(window)) {
698 frame_->non_client_view()->SchedulePaint();
701 if (key != aura::client::kShowStateKey)
702 return;
704 // Maximized and fullscreen windows don't want resize handles overlapping the
705 // content area, because when the user moves the cursor to the right screen
706 // edge we want them to be able to hit the scroll bar.
707 if (ash::wm::IsWindowMaximized(window) ||
708 ash::wm::IsWindowFullscreen(window)) {
709 window->set_hit_test_bounds_override_inner(gfx::Insets());
710 } else {
711 window->set_hit_test_bounds_override_inner(
712 gfx::Insets(kResizeInsideBoundsSize, kResizeInsideBoundsSize,
713 kResizeInsideBoundsSize, kResizeInsideBoundsSize));
717 void FramePainter::OnWindowVisibilityChanged(aura::Window* window,
718 bool visible) {
719 // OnWindowVisibilityChanged can be called for the child windows of |window_|.
720 if (window != window_)
721 return;
723 // Window visibility change may trigger the change of window solo-ness in a
724 // different window.
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_);
739 window_ = NULL;
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,
778 int normal_image_id,
779 int hot_image_id,
780 int pushed_image_id) {
781 ui::ThemeProvider* theme_provider = frame_->GetThemeProvider();
782 button->SetImage(views::CustomButton::STATE_NORMAL,
783 theme_provider->GetImageSkiaNamed(normal_image_id));
784 button->SetImage(views::CustomButton::STATE_HOVERED,
785 theme_provider->GetImageSkiaNamed(hot_image_id));
786 button->SetImage(views::CustomButton::STATE_PRESSED,
787 theme_provider->GetImageSkiaNamed(pushed_image_id));
790 void FramePainter::SetToggledButtonImages(views::ToggleImageButton* button,
791 int normal_image_id,
792 int hot_image_id,
793 int pushed_image_id) {
794 ui::ThemeProvider* theme_provider = frame_->GetThemeProvider();
795 button->SetToggledImage(views::CustomButton::STATE_NORMAL,
796 theme_provider->GetImageSkiaNamed(normal_image_id));
797 button->SetToggledImage(views::CustomButton::STATE_HOVERED,
798 theme_provider->GetImageSkiaNamed(hot_image_id));
799 button->SetToggledImage(views::CustomButton::STATE_PRESSED,
800 theme_provider->GetImageSkiaNamed(pushed_image_id));
803 int FramePainter::GetTitleOffsetX() const {
804 return window_icon_ ?
805 window_icon_->bounds().right() + kTitleIconOffsetX :
806 kTitleNoIconOffsetX;
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,
825 int theme_frame_id,
826 int theme_frame_overlay_id) const {
827 // User-provided themes are painted fully opaque.
828 ui::ThemeProvider* theme_provider = frame_->GetThemeProvider();
829 if (theme_provider->HasCustomImage(theme_frame_id) ||
830 (theme_frame_overlay_id != 0 &&
831 theme_provider->HasCustomImage(theme_frame_overlay_id))) {
832 return kFullyOpaque;
835 // The header is fully opaque when using the minimalistic header style.
836 if (ShouldUseMinimalHeaderStyle(THEMED_NO))
837 return kFullyOpaque;
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_))
852 return false;
853 aura::RootWindow* root = window_->GetRootWindow();
854 if (!root || root->GetProperty(internal::kIgnoreSoloWindowFramePainterPolicy))
855 return false;
856 // Don't recompute every time, as it would require many window property
857 // lookups.
858 return root->GetProperty(internal::kSoloWindowHeaderKey);
861 // static
862 bool FramePainter::UseSoloWindowHeaderInRoot(RootWindow* root_window,
863 Window* ignore_window) {
864 int visible_window_count = 0;
865 std::vector<Window*> windows = GetWindowsForSoloHeaderUpdate(root_window);
866 for (std::vector<Window*>::const_iterator it = windows.begin();
867 it != windows.end();
868 ++it) {
869 Window* window = *it;
870 // Various sorts of windows "don't count" for this computation.
871 if (ignore_window == window ||
872 !IsSoloWindowHeaderCandidate(window) ||
873 !IsVisibleToRoot(window))
874 continue;
875 if (wm::IsWindowMaximized(window))
876 return false;
877 ++visible_window_count;
878 if (visible_window_count > 1)
879 return false;
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;
886 // static
887 void FramePainter::UpdateSoloWindowInRoot(RootWindow* root,
888 Window* ignore_window) {
889 #if defined(OS_WIN)
890 // Non-Ash Windows doesn't do solo-window counting for transparency effects,
891 // as the desktop background and window frames are managed by the OS.
892 if (!ash::Shell::HasInstance())
893 return;
894 #endif
895 if (!root)
896 return;
897 bool old_solo_header = root->GetProperty(internal::kSoloWindowHeaderKey);
898 bool new_solo_header = UseSoloWindowHeaderInRoot(root, ignore_window);
899 if (old_solo_header == new_solo_header)
900 return;
901 root->SetProperty(internal::kSoloWindowHeaderKey, new_solo_header);
902 // Invalidate all the window frames in the desktop. There should only be
903 // a few.
904 std::vector<Window*> windows = GetWindowsForSoloHeaderUpdate(root);
905 for (std::vector<Window*>::const_iterator it = windows.begin();
906 it != windows.end();
907 ++it) {
908 Widget* widget = Widget::GetWidgetForNativeWindow(*it);
909 if (widget && widget->non_client_view())
910 widget->non_client_view()->SchedulePaint();
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;
928 return gfx::Rect(
929 title_x,
930 std::max(0, title_y),
931 std::max(0, size_button_->x() - kTitleLogoSpacing - title_x),
932 title_font.GetHeight());
935 } // namespace ash