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. 4 5 #include "ash/wm/frame_painter.h" 6 7 #include <vector> 8 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" 39 40 using aura::RootWindow; 41 using aura::Window; 42 using views::Widget; 43 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; 84 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); 105 } 106 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); 118 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); 129 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); 141 } 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); 151 } 152 } 153 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; 162 } 163 return true; 164 } 165 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); 178 } 179 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; 202 } 203 } // namespace 204 205 namespace ash { 206 207 // static 208 int FramePainter::kActiveWindowOpacity = 255; // 1.0 209 int FramePainter::kInactiveWindowOpacity = 255; // 1.0 210 int FramePainter::kSoloWindowOpacity = 77; // 0.3 211 212 /////////////////////////////////////////////////////////////////////////////// 213 // FramePainter, public: 214 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) {} 234 235 FramePainter::~FramePainter() { 236 // Sometimes we are destroyed before the window closes, so ensure we clean up. 237 if (window_) { 238 window_->RemoveObserver(this); 239 } 240 } 241 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; 256 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(); 271 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); 283 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); 288 289 // Solo-window header updates are handled by the workspace controller when 290 // this window is added to the desktop. 291 } 292 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 */); 298 } 299 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)); 308 } 309 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); 317 } 318 319 int FramePainter::NonClientHitTest(views::NonClientFrameView* view, 320 const gfx::Point& point) { 321 gfx::Rect expanded_bounds = view->bounds(); 322 int outside_bounds = kResizeOutsideBoundsSize; 323 324 if (aura::Env::GetInstance()->is_touch_down()) 325 outside_bounds *= kResizeOutsideBoundsScaleForTouch; 326 expanded_bounds.Inset(-outside_bounds, -outside_bounds); 327 328 if (!expanded_bounds.Contains(point)) 329 return HTNOWHERE; 330 331 // No avatar button. 332 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; 351 352 int client_component = frame_->client_view()->NonClientHitTest(point); 353 if (client_component != HTNOWHERE) 354 return client_component; 355 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; 363 364 // Caption is a safe default. 365 return HTCAPTION; 366 } 367 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; 381 } 382 383 gfx::Size FramePainter::GetMaximumSize(views::NonClientFrameView* view) { 384 return frame_->client_view()->GetMaximumSize(); 385 } 386 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; 393 } 394 395 int FramePainter::GetThemeBackgroundXInset() const { 396 return kThemeFrameImageInsetX; 397 } 398 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())); 408 } 409 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(); 439 } 440 } 441 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); 451 } 452 header_frame_bounds_ = gfx::Rect(0, 0, view->width(), theme_frame->height()); 453 454 int corner_radius = GetHeaderCornerRadius(); 455 SkPaint paint; 456 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_); 464 } 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; 476 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()); 487 488 paint.setAlpha(new_alpha); 489 } 490 } else { 491 paint.setAlpha(opacity); 492 } 493 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()); 502 503 previous_theme_frame_id_ = theme_frame_id; 504 previous_theme_frame_overlay_id_ = theme_frame_overlay_id; 505 previous_opacity_ = opacity; 506 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()); 514 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. 517 // 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; 523 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(), 532 0, 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); 542 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); 548 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); 556 557 // We don't draw edges around the content area. Web content goes flush 558 // to the edge of the window. 559 } 560 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); 570 } 571 572 int FramePainter::HeaderContentSeparatorSize() const { 573 return kHeaderContentSeparatorSize; 574 } 575 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); 593 } 594 } 595 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); 618 } 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); 635 } 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); 645 } 646 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()); 653 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()); 660 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); 665 } 666 } 667 668 void FramePainter::SchedulePaintForTitle(const gfx::Font& title_font) { 669 frame_->non_client_view()->SchedulePaintInRect(GetTitleBounds(title_font)); 670 } 671 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; 680 681 if (crossfade_animation_.get() && crossfade_animation_->is_animating()) { 682 crossfade_animation_.reset(); 683 frame_->non_client_view()->SchedulePaintInRect(header_frame_bounds_); 684 } 685 } 686 687 /////////////////////////////////////////////////////////////////////////////// 688 // aura::WindowObserver overrides: 689 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(); 699 } 700 701 if (key != aura::client::kShowStateKey) 702 return; 703 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)); 714 } 715 } 716 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; 722 723 // Window visibility change may trigger the change of window solo-ness in a 724 // different window. 725 UpdateSoloWindowInRoot(window_->GetRootWindow(), visible ? NULL : window_); 726 } 727 728 void FramePainter::OnWindowDestroying(aura::Window* destroying) { 729 DCHECK_EQ(window_, destroying); 730 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); 734 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_); 738 739 window_ = NULL; 740 } 741 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(); 751 } 752 } 753 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 */); 758 } 759 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); 765 } 766 767 /////////////////////////////////////////////////////////////////////////////// 768 // ui::AnimationDelegate overrides: 769 770 void FramePainter::AnimationProgressed(const ui::Animation* animation) { 771 frame_->non_client_view()->SchedulePaintInRect(header_frame_bounds_); 772 } 773 774 /////////////////////////////////////////////////////////////////////////////// 775 // FramePainter, private: 776 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)); 788 } 789 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)); 801 } 802 803 int FramePainter::GetTitleOffsetX() const { 804 return window_icon_ ? 805 window_icon_->bounds().right() + kTitleIconOffsetX : 806 kTitleNoIconOffsetX; 807 } 808 809 int FramePainter::GetCloseButtonCenterY() const { 810 return close_button_->y() + close_button_->height() / 2; 811 } 812 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; 821 } 822 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; 833 } 834 835 // The header is fully opaque when using the minimalistic header style. 836 if (ShouldUseMinimalHeaderStyle(THEMED_NO)) 837 return kFullyOpaque; 838 839 // Single browser window is very transparent. 840 if (UseSoloWindowHeader()) 841 return kSoloWindowOpacity; 842 843 // Otherwise, change transparency based on window activation status. 844 if (header_mode == ACTIVE) 845 return kActiveWindowOpacity; 846 return kInactiveWindowOpacity; 847 } 848 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); 859 } 860 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; 880 } 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; 884 } 885 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(); 911 } 912 } 913 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))); 920 } 921 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()); 933 } 934 935 } // namespace ash 936