Enable snappy for IndexedDB.
[chromium-blink-merge.git] / ash / wm / frame_painter.cc
blob42c6b3f5577b4a3e75d01774246838f59fe99caa
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/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;
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 // 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,
85 const SkPaint& paint,
86 const gfx::Rect& bounds,
87 int top_left_corner_radius,
88 int top_right_corner_radius,
89 int image_inset_x) {
90 SkRect rect = gfx::RectToSkRect(bounds);
91 const SkScalar kTopLeftRadius = SkIntToScalar(top_left_corner_radius);
92 const SkScalar kTopRightRadius = SkIntToScalar(top_right_corner_radius);
93 SkScalar radii[8] = {
94 kTopLeftRadius, kTopLeftRadius, // top-left
95 kTopRightRadius, kTopRightRadius, // top-right
96 0, 0, // bottom-right
97 0, 0}; // bottom-left
98 SkPath path;
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
104 // corners.
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,
110 int corner_radius,
111 int image_inset_x) {
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));
122 if (fast_path) {
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);
138 } else {
139 gfx::Canvas temporary_canvas(bounds.size(), canvas->image_scale(), false);
140 temporary_canvas.TileImageInt(*frame_image,
141 image_inset_x, 0,
142 0, 0,
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())
157 return false;
159 return true;
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) {
168 return window &&
169 window->type() == aura::client::WINDOW_TYPE_NORMAL &&
170 window->layer() &&
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.
181 windows.reserve(16);
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());
195 return windows;
197 } // namespace
199 namespace ash {
201 // static
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()
210 : frame_(NULL),
211 window_icon_(NULL),
212 caption_button_container_(NULL),
213 window_(NULL),
214 top_left_corner_(NULL),
215 top_edge_(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.
228 if (window_) {
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) {
238 DCHECK(frame);
239 // window_icon may be NULL.
240 DCHECK(caption_button_container);
241 frame_ = frame;
242 window_icon_ = window_icon;
243 caption_button_container_ = caption_button_container;
245 // Window frame image parts.
246 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
247 top_left_corner_ =
248 rb.GetImageNamed(IDR_AURA_WINDOW_HEADER_SHADE_TOP_LEFT).ToImageSkia();
249 top_edge_ =
250 rb.GetImageNamed(IDR_AURA_WINDOW_HEADER_SHADE_TOP).ToImageSkia();
251 top_right_corner_ =
252 rb.GetImageNamed(IDR_AURA_WINDOW_HEADER_SHADE_TOP_RIGHT).ToImageSkia();
253 header_left_edge_ =
254 rb.GetImageNamed(IDR_AURA_WINDOW_HEADER_SHADE_LEFT).ToImageSkia();
255 header_right_edge_ =
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,
267 touch_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.
281 // static
282 void FramePainter::SetSoloWindowHeadersEnabled(bool enabled) {
283 solo_window_header_enabled = enabled;
286 // static
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(
294 int top_height,
295 const gfx::Rect& window_bounds) const {
296 return gfx::Rect(
297 kBorderThickness,
298 top_height,
299 std::max(0, window_bounds.width() - (2 * kBorderThickness)),
300 std::max(0, window_bounds.height() - top_height - kBorderThickness));
303 gfx::Rect FramePainter::GetWindowBoundsForClientBounds(
304 int top_height,
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))
322 return HTNOWHERE;
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() :
328 false;
329 // Don't allow overlapping resize handles when the window is maximized or
330 // fullscreen, as it can't be resized in those states.
331 int resize_border =
332 frame_->IsMaximized() || frame_->IsFullscreen() ? 0 :
333 kResizeInsideBoundsSize;
334 int frame_component = view->GetHTComponentForFrame(point,
335 resize_border,
336 resize_border,
337 kResizeAreaCornerSize,
338 kResizeAreaCornerSize,
339 can_ever_resize);
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.
358 return HTCAPTION;
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);
372 return min_size;
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,
399 gfx::Canvas* canvas,
400 HeaderMode header_mode,
401 int theme_frame_id,
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();
425 } else {
426 crossfade_animation_.reset();
430 int opacity =
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(
434 theme_frame_id);
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();
443 SkPaint paint;
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);
460 } else {
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,
471 paint,
472 header_frame_bounds_,
473 corner_radius,
474 GetThemeBackgroundXInset());
476 paint.setAlpha(new_alpha);
478 } else {
479 paint.setAlpha(opacity);
482 // Draw the header background, clipping the corners to be rounded.
483 PaintFrameImagesInRoundRect(canvas,
484 theme_frame,
485 theme_frame_overlay,
486 paint,
487 header_frame_bounds_,
488 corner_radius,
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)
502 return;
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,
509 false);
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_,
517 0, 0,
518 top_right_corner_->width(), top_right_height,
519 view->width() - top_right_corner_->width(), 0,
520 top_right_corner_->width(), top_right_height,
521 false);
523 // Header left edge.
524 int header_left_height = theme_frame->height() - top_left_height;
525 canvas->TileImageInt(*header_left_edge_,
526 0, top_left_height,
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(),
533 top_right_height,
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,
557 gfx::Canvas* canvas,
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(),
566 title_font,
567 title_color,
568 view->GetMirroredXForRect(title_bounds),
569 title_bounds.y(),
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());
591 if (window_icon_) {
592 // Vertically center the window icon with respect to the caption button
593 // container.
594 int icon_offset_y =
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,
622 bool old) {
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,
638 bool visible) {
639 // OnWindowVisibilityChanged can be called for the child windows of |window_|.
640 if (window != window_)
641 return;
643 // Window visibility change may trigger the change of window solo-ness in a
644 // different window.
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_);
660 window_ = NULL;
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 :
701 kTitleNoIconOffsetX;
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,
721 int theme_frame_id,
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))) {
728 return kFullyOpaque;
731 // The header is fully opaque when using the minimalistic header style.
732 if (ShouldUseMinimalHeaderStyle(THEMED_NO))
733 return kFullyOpaque;
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)
747 return false;
748 // Don't use transparent headers for panels, pop-ups, etc.
749 if (!IsSoloWindowHeaderCandidate(window_))
750 return false;
751 aura::RootWindow* root = window_->GetRootWindow();
752 // Don't recompute every time, as it would require many window property
753 // lookups.
754 return internal::GetRootWindowSettings(root)->solo_window_header;
757 // static
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();
763 it != windows.end();
764 ++it) {
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))
770 continue;
771 if (wm::GetWindowState(window)->IsMaximized())
772 return false;
773 ++visible_window_count;
774 if (visible_window_count > 1)
775 return false;
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;
782 // static
783 void FramePainter::UpdateSoloWindowInRoot(RootWindow* root,
784 Window* ignore_window) {
785 #if defined(OS_WIN)
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())
789 return;
790 #endif
791 if (!root)
792 return;
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)
798 return;
799 root_window_settings->solo_window_header = new_solo_header;
801 // Invalidate all the window frames in the desktop. There should only be
802 // a few.
803 std::vector<Window*> windows = GetWindowsForSoloHeaderUpdate(root);
804 for (std::vector<Window*>::const_iterator it = windows.begin();
805 it != windows.end();
806 ++it) {
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());
819 } else {
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;
840 return gfx::Rect(
841 title_x,
842 std::max(0, title_y),
843 std::max(0, caption_button_container_->x() - kTitleLogoSpacing - title_x),
844 title_font.GetHeight());
847 } // namespace ash