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 "ui/views/window/custom_frame_view.h" 6 7 #include <algorithm> 8 9 #include "base/strings/utf_string_conversions.h" 10 #include "grit/ui_resources.h" 11 #include "grit/ui_strings.h" 12 #include "ui/base/hit_test.h" 13 #include "ui/base/l10n/l10n_util.h" 14 #include "ui/base/resource/resource_bundle.h" 15 #include "ui/gfx/canvas.h" 16 #include "ui/gfx/font.h" 17 #include "ui/gfx/image/image.h" 18 #include "ui/gfx/path.h" 19 #include "ui/views/color_constants.h" 20 #include "ui/views/controls/button/image_button.h" 21 #include "ui/views/widget/widget.h" 22 #include "ui/views/widget/widget_delegate.h" 23 #include "ui/views/window/client_view.h" 24 #include "ui/views/window/frame_background.h" 25 #include "ui/views/window/window_resources.h" 26 #include "ui/views/window/window_shape.h" 27 28 #if defined(USE_AURA) 29 #include "ui/views/widget/native_widget_aura.h" 30 #elif defined(OS_WIN) 31 #include "ui/views/widget/native_widget_win.h" 32 #endif 33 34 namespace views { 35 36 namespace { 37 38 // The frame border is only visible in restored mode and is hardcoded to 4 px on 39 // each side regardless of the system window border size. 40 const int kFrameBorderThickness = 4; 41 // In the window corners, the resize areas don't actually expand bigger, but the 42 // 16 px at the end of each edge triggers diagonal resizing. 43 const int kResizeAreaCornerSize = 16; 44 // The titlebar never shrinks too short to show the caption button plus some 45 // padding below it. 46 const int kCaptionButtonHeightWithPadding = 19; 47 // The titlebar has a 2 px 3D edge along the top and bottom. 48 const int kTitlebarTopAndBottomEdgeThickness = 2; 49 // The icon is inset 2 px from the left frame border. 50 const int kIconLeftSpacing = 2; 51 // The icon never shrinks below 16 px on a side. 52 const int kIconMinimumSize = 16; 53 // The space between the window icon and the title text. 54 const int kTitleIconOffsetX = 4; 55 // The space between the title text and the caption buttons. 56 const int kTitleCaptionSpacing = 5; 57 58 #if defined(OS_CHROMEOS) 59 // Chrome OS uses a dark gray. 60 const SkColor kDefaultColorFrame = SkColorSetRGB(109, 109, 109); 61 const SkColor kDefaultColorFrameInactive = SkColorSetRGB(176, 176, 176); 62 #else 63 // Windows and Linux use a blue. 64 const SkColor kDefaultColorFrame = SkColorSetRGB(66, 116, 201); 65 const SkColor kDefaultColorFrameInactive = SkColorSetRGB(161, 182, 228); 66 #endif 67 68 const gfx::Font& GetTitleFont() { 69 static gfx::Font* title_font = NULL; 70 if (!title_font) { 71 #if defined(USE_AURA) 72 title_font = new gfx::Font(NativeWidgetAura::GetWindowTitleFont()); 73 #elif defined(OS_WIN) 74 title_font = new gfx::Font(NativeWidgetWin::GetWindowTitleFont()); 75 #elif defined(OS_LINUX) 76 // TODO(ben): need to resolve what font this is. 77 title_font = new gfx::Font(); 78 #endif 79 } 80 return *title_font; 81 } 82 83 } // namespace 84 85 /////////////////////////////////////////////////////////////////////////////// 86 // CustomFrameView, public: 87 88 CustomFrameView::CustomFrameView() 89 : frame_(NULL), 90 window_icon_(NULL), 91 minimize_button_(NULL), 92 maximize_button_(NULL), 93 restore_button_(NULL), 94 close_button_(NULL), 95 should_show_maximize_button_(false), 96 frame_background_(new FrameBackground()) { 97 } 98 99 CustomFrameView::~CustomFrameView() { 100 } 101 102 void CustomFrameView::Init(Widget* frame) { 103 frame_ = frame; 104 105 close_button_ = new ImageButton(this); 106 close_button_->SetAccessibleName( 107 l10n_util::GetStringUTF16(IDS_APP_ACCNAME_CLOSE)); 108 109 // Close button images will be set in LayoutWindowControls(). 110 AddChildView(close_button_); 111 112 minimize_button_ = InitWindowCaptionButton(IDS_APP_ACCNAME_MINIMIZE, 113 IDR_MINIMIZE, IDR_MINIMIZE_H, IDR_MINIMIZE_P); 114 115 maximize_button_ = InitWindowCaptionButton(IDS_APP_ACCNAME_MAXIMIZE, 116 IDR_MAXIMIZE, IDR_MAXIMIZE_H, IDR_MAXIMIZE_P); 117 118 restore_button_ = InitWindowCaptionButton(IDS_APP_ACCNAME_RESTORE, 119 IDR_RESTORE, IDR_RESTORE_H, IDR_RESTORE_P); 120 121 should_show_maximize_button_ = frame_->widget_delegate()->CanMaximize(); 122 123 if (frame_->widget_delegate()->ShouldShowWindowIcon()) { 124 window_icon_ = new ImageButton(this); 125 AddChildView(window_icon_); 126 } 127 } 128 129 /////////////////////////////////////////////////////////////////////////////// 130 // CustomFrameView, NonClientFrameView implementation: 131 132 gfx::Rect CustomFrameView::GetBoundsForClientView() const { 133 return client_view_bounds_; 134 } 135 136 gfx::Rect CustomFrameView::GetWindowBoundsForClientBounds( 137 const gfx::Rect& client_bounds) const { 138 int top_height = NonClientTopBorderHeight(); 139 int border_thickness = NonClientBorderThickness(); 140 return gfx::Rect(std::max(0, client_bounds.x() - border_thickness), 141 std::max(0, client_bounds.y() - top_height), 142 client_bounds.width() + (2 * border_thickness), 143 client_bounds.height() + top_height + border_thickness); 144 } 145 146 int CustomFrameView::NonClientHitTest(const gfx::Point& point) { 147 // Sanity check. 148 if (!bounds().Contains(point)) 149 return HTNOWHERE; 150 151 int frame_component = frame_->client_view()->NonClientHitTest(point); 152 153 // See if we're in the sysmenu region. (We check the ClientView first to be 154 // consistent with OpaqueBrowserFrameView; it's not really necessary here.) 155 gfx::Rect sysmenu_rect(IconBounds()); 156 // In maximized mode we extend the rect to the screen corner to take advantage 157 // of Fitts' Law. 158 if (frame_->IsMaximized()) 159 sysmenu_rect.SetRect(0, 0, sysmenu_rect.right(), sysmenu_rect.bottom()); 160 sysmenu_rect.set_x(GetMirroredXForRect(sysmenu_rect)); 161 if (sysmenu_rect.Contains(point)) 162 return (frame_component == HTCLIENT) ? HTCLIENT : HTSYSMENU; 163 164 if (frame_component != HTNOWHERE) 165 return frame_component; 166 167 // Then see if the point is within any of the window controls. 168 if (close_button_->GetMirroredBounds().Contains(point)) 169 return HTCLOSE; 170 if (restore_button_->GetMirroredBounds().Contains(point)) 171 return HTMAXBUTTON; 172 if (maximize_button_->GetMirroredBounds().Contains(point)) 173 return HTMAXBUTTON; 174 if (minimize_button_->GetMirroredBounds().Contains(point)) 175 return HTMINBUTTON; 176 if (window_icon_ && window_icon_->GetMirroredBounds().Contains(point)) 177 return HTSYSMENU; 178 179 int window_component = GetHTComponentForFrame(point, FrameBorderThickness(), 180 NonClientBorderThickness(), kResizeAreaCornerSize, kResizeAreaCornerSize, 181 frame_->widget_delegate()->CanResize()); 182 // Fall back to the caption if no other component matches. 183 return (window_component == HTNOWHERE) ? HTCAPTION : window_component; 184 } 185 186 void CustomFrameView::GetWindowMask(const gfx::Size& size, 187 gfx::Path* window_mask) { 188 DCHECK(window_mask); 189 if (frame_->IsMaximized()) 190 return; 191 192 GetDefaultWindowMask(size, window_mask); 193 } 194 195 void CustomFrameView::ResetWindowControls() { 196 restore_button_->SetState(CustomButton::STATE_NORMAL); 197 minimize_button_->SetState(CustomButton::STATE_NORMAL); 198 maximize_button_->SetState(CustomButton::STATE_NORMAL); 199 // The close button isn't affected by this constraint. 200 } 201 202 void CustomFrameView::UpdateWindowIcon() { 203 if (window_icon_) 204 window_icon_->SchedulePaint(); 205 } 206 207 void CustomFrameView::UpdateWindowTitle() { 208 SchedulePaintInRect(title_bounds_); 209 } 210 211 /////////////////////////////////////////////////////////////////////////////// 212 // CustomFrameView, View overrides: 213 214 void CustomFrameView::OnPaint(gfx::Canvas* canvas) { 215 if (frame_->IsMaximized()) 216 PaintMaximizedFrameBorder(canvas); 217 else 218 PaintRestoredFrameBorder(canvas); 219 PaintTitleBar(canvas); 220 if (ShouldShowClientEdge()) 221 PaintRestoredClientEdge(canvas); 222 } 223 224 void CustomFrameView::Layout() { 225 LayoutWindowControls(); 226 LayoutTitleBar(); 227 LayoutClientView(); 228 } 229 230 gfx::Size CustomFrameView::GetPreferredSize() { 231 return frame_->non_client_view()->GetWindowBoundsForClientBounds( 232 gfx::Rect(frame_->client_view()->GetPreferredSize())).size(); 233 } 234 235 gfx::Size CustomFrameView::GetMinimumSize() { 236 return frame_->non_client_view()->GetWindowBoundsForClientBounds( 237 gfx::Rect(frame_->client_view()->GetMinimumSize())).size(); 238 } 239 240 gfx::Size CustomFrameView::GetMaximumSize() { 241 gfx::Size max_size = frame_->client_view()->GetMaximumSize(); 242 gfx::Size converted_size = 243 frame_->non_client_view()->GetWindowBoundsForClientBounds( 244 gfx::Rect(max_size)).size(); 245 return gfx::Size(max_size.width() == 0 ? 0 : converted_size.width(), 246 max_size.height() == 0 ? 0 : converted_size.height()); 247 } 248 249 /////////////////////////////////////////////////////////////////////////////// 250 // CustomFrameView, ButtonListener implementation: 251 252 void CustomFrameView::ButtonPressed(Button* sender, const ui::Event& event) { 253 if (sender == close_button_) 254 frame_->Close(); 255 else if (sender == minimize_button_) 256 frame_->Minimize(); 257 else if (sender == maximize_button_) 258 frame_->Maximize(); 259 else if (sender == restore_button_) 260 frame_->Restore(); 261 } 262 263 /////////////////////////////////////////////////////////////////////////////// 264 // CustomFrameView, private: 265 266 int CustomFrameView::FrameBorderThickness() const { 267 return frame_->IsMaximized() ? 0 : kFrameBorderThickness; 268 } 269 270 int CustomFrameView::NonClientBorderThickness() const { 271 // In maximized mode, we don't show a client edge. 272 return FrameBorderThickness() + 273 (ShouldShowClientEdge() ? kClientEdgeThickness : 0); 274 } 275 276 int CustomFrameView::NonClientTopBorderHeight() const { 277 return std::max(FrameBorderThickness() + IconSize(), 278 CaptionButtonY() + kCaptionButtonHeightWithPadding) + 279 TitlebarBottomThickness(); 280 } 281 282 int CustomFrameView::CaptionButtonY() const { 283 // Maximized buttons start at window top so that even if their images aren't 284 // drawn flush with the screen edge, they still obey Fitts' Law. 285 return frame_->IsMaximized() ? FrameBorderThickness() : kFrameShadowThickness; 286 } 287 288 int CustomFrameView::TitlebarBottomThickness() const { 289 return kTitlebarTopAndBottomEdgeThickness + 290 (ShouldShowClientEdge() ? kClientEdgeThickness : 0); 291 } 292 293 int CustomFrameView::IconSize() const { 294 #if defined(OS_WIN) 295 // This metric scales up if either the titlebar height or the titlebar font 296 // size are increased. 297 return GetSystemMetrics(SM_CYSMICON); 298 #else 299 return std::max(GetTitleFont().GetHeight(), kIconMinimumSize); 300 #endif 301 } 302 303 gfx::Rect CustomFrameView::IconBounds() const { 304 int size = IconSize(); 305 int frame_thickness = FrameBorderThickness(); 306 // Our frame border has a different "3D look" than Windows'. Theirs has a 307 // more complex gradient on the top that they push their icon/title below; 308 // then the maximized window cuts this off and the icon/title are centered 309 // in the remaining space. Because the apparent shape of our border is 310 // simpler, using the same positioning makes things look slightly uncentered 311 // with restored windows, so when the window is restored, instead of 312 // calculating the remaining space from below the frame border, we calculate 313 // from below the 3D edge. 314 int unavailable_px_at_top = frame_->IsMaximized() ? 315 frame_thickness : kTitlebarTopAndBottomEdgeThickness; 316 // When the icon is shorter than the minimum space we reserve for the caption 317 // button, we vertically center it. We want to bias rounding to put extra 318 // space above the icon, since the 3D edge (+ client edge, for restored 319 // windows) below looks (to the eye) more like additional space than does the 320 // 3D edge (or nothing at all, for maximized windows) above; hence the +1. 321 int y = unavailable_px_at_top + (NonClientTopBorderHeight() - 322 unavailable_px_at_top - size - TitlebarBottomThickness() + 1) / 2; 323 return gfx::Rect(frame_thickness + kIconLeftSpacing, y, size, size); 324 } 325 326 bool CustomFrameView::ShouldShowClientEdge() const { 327 return !frame_->IsMaximized(); 328 } 329 330 void CustomFrameView::PaintRestoredFrameBorder(gfx::Canvas* canvas) { 331 frame_background_->set_frame_color(GetFrameColor()); 332 const gfx::ImageSkia* frame_image = GetFrameImage(); 333 frame_background_->set_theme_image(frame_image); 334 frame_background_->set_top_area_height(frame_image->height()); 335 336 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 337 338 frame_background_->SetCornerImages( 339 rb.GetImageNamed(IDR_WINDOW_TOP_LEFT_CORNER).ToImageSkia(), 340 rb.GetImageNamed(IDR_WINDOW_TOP_RIGHT_CORNER).ToImageSkia(), 341 rb.GetImageNamed(IDR_WINDOW_BOTTOM_LEFT_CORNER).ToImageSkia(), 342 rb.GetImageNamed(IDR_WINDOW_BOTTOM_RIGHT_CORNER).ToImageSkia()); 343 frame_background_->SetSideImages( 344 rb.GetImageNamed(IDR_WINDOW_LEFT_SIDE).ToImageSkia(), 345 rb.GetImageNamed(IDR_WINDOW_TOP_CENTER).ToImageSkia(), 346 rb.GetImageNamed(IDR_WINDOW_RIGHT_SIDE).ToImageSkia(), 347 rb.GetImageNamed(IDR_WINDOW_BOTTOM_CENTER).ToImageSkia()); 348 349 frame_background_->PaintRestored(canvas, this); 350 } 351 352 void CustomFrameView::PaintMaximizedFrameBorder(gfx::Canvas* canvas) { 353 const gfx::ImageSkia* frame_image = GetFrameImage(); 354 frame_background_->set_theme_image(frame_image); 355 frame_background_->set_top_area_height(frame_image->height()); 356 frame_background_->PaintMaximized(canvas, this); 357 358 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 359 360 // TODO(jamescook): Migrate this into FrameBackground. 361 // The bottom of the titlebar actually comes from the top of the Client Edge 362 // graphic, with the actual client edge clipped off the bottom. 363 const gfx::ImageSkia* titlebar_bottom = rb.GetImageNamed( 364 IDR_APP_TOP_CENTER).ToImageSkia(); 365 int edge_height = titlebar_bottom->height() - 366 (ShouldShowClientEdge() ? kClientEdgeThickness : 0); 367 canvas->TileImageInt(*titlebar_bottom, 0, 368 frame_->client_view()->y() - edge_height, width(), edge_height); 369 } 370 371 void CustomFrameView::PaintTitleBar(gfx::Canvas* canvas) { 372 WidgetDelegate* delegate = frame_->widget_delegate(); 373 374 // It seems like in some conditions we can be asked to paint after the window 375 // that contains us is WM_DESTROYed. At this point, our delegate is NULL. The 376 // correct long term fix may be to shut down the RootView in WM_DESTROY. 377 if (!delegate) 378 return; 379 380 canvas->DrawStringInt(delegate->GetWindowTitle(), GetTitleFont(), 381 SK_ColorWHITE, GetMirroredXForRect(title_bounds_), 382 title_bounds_.y(), title_bounds_.width(), 383 title_bounds_.height()); 384 } 385 386 void CustomFrameView::PaintRestoredClientEdge(gfx::Canvas* canvas) { 387 gfx::Rect client_area_bounds = frame_->client_view()->bounds(); 388 int client_area_top = client_area_bounds.y(); 389 390 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 391 392 // Top: left, center, right sides. 393 const gfx::ImageSkia* top_left = rb.GetImageSkiaNamed(IDR_APP_TOP_LEFT); 394 const gfx::ImageSkia* top_center = rb.GetImageSkiaNamed(IDR_APP_TOP_CENTER); 395 const gfx::ImageSkia* top_right = rb.GetImageSkiaNamed(IDR_APP_TOP_RIGHT); 396 int top_edge_y = client_area_top - top_center->height(); 397 canvas->DrawImageInt(*top_left, 398 client_area_bounds.x() - top_left->width(), 399 top_edge_y); 400 canvas->TileImageInt(*top_center, 401 client_area_bounds.x(), 402 top_edge_y, 403 client_area_bounds.width(), 404 top_center->height()); 405 canvas->DrawImageInt(*top_right, client_area_bounds.right(), top_edge_y); 406 407 // Right side. 408 const gfx::ImageSkia* right = rb.GetImageSkiaNamed(IDR_CONTENT_RIGHT_SIDE); 409 int client_area_bottom = 410 std::max(client_area_top, client_area_bounds.bottom()); 411 int client_area_height = client_area_bottom - client_area_top; 412 canvas->TileImageInt(*right, 413 client_area_bounds.right(), 414 client_area_top, 415 right->width(), 416 client_area_height); 417 418 // Bottom: left, center, right sides. 419 const gfx::ImageSkia* bottom_left = 420 rb.GetImageSkiaNamed(IDR_CONTENT_BOTTOM_LEFT_CORNER); 421 const gfx::ImageSkia* bottom_center = 422 rb.GetImageSkiaNamed(IDR_CONTENT_BOTTOM_CENTER); 423 const gfx::ImageSkia* bottom_right = 424 rb.GetImageSkiaNamed(IDR_CONTENT_BOTTOM_RIGHT_CORNER); 425 426 canvas->DrawImageInt(*bottom_left, 427 client_area_bounds.x() - bottom_left->width(), 428 client_area_bottom); 429 430 canvas->TileImageInt(*bottom_center, 431 client_area_bounds.x(), 432 client_area_bottom, 433 client_area_bounds.width(), 434 bottom_right->height()); 435 436 canvas->DrawImageInt(*bottom_right, 437 client_area_bounds.right(), 438 client_area_bottom); 439 // Left side. 440 const gfx::ImageSkia* left = rb.GetImageSkiaNamed(IDR_CONTENT_LEFT_SIDE); 441 canvas->TileImageInt(*left, 442 client_area_bounds.x() - left->width(), 443 client_area_top, 444 left->width(), 445 client_area_height); 446 447 // Draw the color to fill in the edges. 448 canvas->FillRect(gfx::Rect(client_area_bounds.x() - 1, 449 client_area_top - 1, 450 client_area_bounds.width() + 1, 451 client_area_bottom - client_area_top + 1), 452 kClientEdgeColor); 453 } 454 455 SkColor CustomFrameView::GetFrameColor() const { 456 return frame_->IsActive() ? kDefaultColorFrame : kDefaultColorFrameInactive; 457 } 458 459 const gfx::ImageSkia* CustomFrameView::GetFrameImage() const { 460 return ui::ResourceBundle::GetSharedInstance().GetImageNamed( 461 frame_->IsActive() ? IDR_FRAME : IDR_FRAME_INACTIVE).ToImageSkia(); 462 } 463 464 void CustomFrameView::LayoutWindowControls() { 465 close_button_->SetImageAlignment(ImageButton::ALIGN_LEFT, 466 ImageButton::ALIGN_BOTTOM); 467 int caption_y = CaptionButtonY(); 468 bool is_maximized = frame_->IsMaximized(); 469 // There should always be the same number of non-shadow pixels visible to the 470 // side of the caption buttons. In maximized mode we extend the rightmost 471 // button to the screen corner to obey Fitts' Law. 472 int right_extra_width = is_maximized ? 473 (kFrameBorderThickness - kFrameShadowThickness) : 0; 474 gfx::Size close_button_size = close_button_->GetPreferredSize(); 475 close_button_->SetBounds(width() - FrameBorderThickness() - 476 right_extra_width - close_button_size.width(), caption_y, 477 close_button_size.width() + right_extra_width, 478 close_button_size.height()); 479 480 // When the window is restored, we show a maximized button; otherwise, we show 481 // a restore button. 482 bool is_restored = !is_maximized && !frame_->IsMinimized(); 483 ImageButton* invisible_button = is_restored ? restore_button_ 484 : maximize_button_; 485 invisible_button->SetVisible(false); 486 487 ImageButton* visible_button = is_restored ? maximize_button_ 488 : restore_button_; 489 FramePartImage normal_part, hot_part, pushed_part; 490 int next_button_x; 491 if (should_show_maximize_button_) { 492 visible_button->SetVisible(true); 493 visible_button->SetImageAlignment(ImageButton::ALIGN_LEFT, 494 ImageButton::ALIGN_BOTTOM); 495 gfx::Size visible_button_size = visible_button->GetPreferredSize(); 496 visible_button->SetBounds(close_button_->x() - visible_button_size.width(), 497 caption_y, visible_button_size.width(), 498 visible_button_size.height()); 499 next_button_x = visible_button->x(); 500 } else { 501 visible_button->SetVisible(false); 502 next_button_x = close_button_->x(); 503 } 504 505 minimize_button_->SetVisible(true); 506 minimize_button_->SetImageAlignment(ImageButton::ALIGN_LEFT, 507 ImageButton::ALIGN_BOTTOM); 508 gfx::Size minimize_button_size = minimize_button_->GetPreferredSize(); 509 minimize_button_->SetBounds( 510 next_button_x - minimize_button_size.width(), caption_y, 511 minimize_button_size.width(), 512 minimize_button_size.height()); 513 514 normal_part = IDR_CLOSE; 515 hot_part = IDR_CLOSE_H; 516 pushed_part = IDR_CLOSE_P; 517 518 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 519 520 close_button_->SetImage(CustomButton::STATE_NORMAL, 521 rb.GetImageNamed(normal_part).ToImageSkia()); 522 close_button_->SetImage(CustomButton::STATE_HOVERED, 523 rb.GetImageNamed(hot_part).ToImageSkia()); 524 close_button_->SetImage(CustomButton::STATE_PRESSED, 525 rb.GetImageNamed(pushed_part).ToImageSkia()); 526 } 527 528 void CustomFrameView::LayoutTitleBar() { 529 // The window title position is calculated based on the icon position, even 530 // when there is no icon. 531 gfx::Rect icon_bounds(IconBounds()); 532 bool show_window_icon = window_icon_ != NULL; 533 if (show_window_icon) 534 window_icon_->SetBoundsRect(icon_bounds); 535 536 // The offset between the window left edge and the title text. 537 int title_x = show_window_icon ? icon_bounds.right() + kTitleIconOffsetX 538 : icon_bounds.x(); 539 int title_height = GetTitleFont().GetHeight(); 540 // We bias the title position so that when the difference between the icon and 541 // title heights is odd, the extra pixel of the title is above the vertical 542 // midline rather than below. This compensates for how the icon is already 543 // biased downwards (see IconBounds()) and helps prevent descenders on the 544 // title from overlapping the 3D edge at the bottom of the titlebar. 545 title_bounds_.SetRect(title_x, 546 icon_bounds.y() + ((icon_bounds.height() - title_height - 1) / 2), 547 std::max(0, minimize_button_->x() - kTitleCaptionSpacing - 548 title_x), title_height); 549 } 550 551 void CustomFrameView::LayoutClientView() { 552 int top_height = NonClientTopBorderHeight(); 553 int border_thickness = NonClientBorderThickness(); 554 client_view_bounds_.SetRect(border_thickness, top_height, 555 std::max(0, width() - (2 * border_thickness)), 556 std::max(0, height() - top_height - border_thickness)); 557 } 558 559 ImageButton* CustomFrameView::InitWindowCaptionButton( 560 int accessibility_string_id, 561 int normal_image_id, 562 int hot_image_id, 563 int pushed_image_id) { 564 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 565 ImageButton* button = new ImageButton(this); 566 button->SetAccessibleName(l10n_util::GetStringUTF16(accessibility_string_id)); 567 button->SetImage(CustomButton::STATE_NORMAL, 568 rb.GetImageNamed(normal_image_id).ToImageSkia()); 569 button->SetImage(CustomButton::STATE_HOVERED, 570 rb.GetImageNamed(hot_image_id).ToImageSkia()); 571 button->SetImage(CustomButton::STATE_PRESSED, 572 rb.GetImageNamed(pushed_image_id).ToImageSkia()); 573 AddChildView(button); 574 return button; 575 } 576 577 } // namespace views 578