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 "chrome/browser/ui/views/frame/app_panel_browser_frame_view.h"
7 #include "base/compiler_specific.h"
8 #include "base/strings/utf_string_conversions.h"
9 #include "chrome/browser/ui/views/frame/browser_frame.h"
10 #include "chrome/browser/ui/views/frame/browser_view.h"
11 #include "chrome/browser/ui/views/tab_icon_view.h"
12 #include "content/public/browser/web_contents.h"
13 #include "grit/chromium_strings.h"
14 #include "grit/generated_resources.h"
15 #include "grit/theme_resources.h"
16 #include "grit/ui_resources.h"
17 #include "ui/base/hit_test.h"
18 #include "ui/base/l10n/l10n_util.h"
19 #include "ui/base/resource/resource_bundle.h"
20 #include "ui/gfx/canvas.h"
21 #include "ui/gfx/font.h"
22 #include "ui/gfx/path.h"
23 #include "ui/views/color_constants.h"
24 #include "ui/views/controls/button/image_button.h"
25 #include "ui/views/widget/widget.h"
26 #include "ui/views/widget/widget_delegate.h"
28 using content::WebContents
;
32 // The frame border is only visible in restored mode and is hardcoded to 1 px on
33 // each side regardless of the system window border size.
34 const int kFrameBorderThickness
= 1;
35 // In the window corners, the resize areas don't actually expand bigger, but the
36 // 16 px at the end of each edge triggers diagonal resizing.
37 const int kResizeAreaCornerSize
= 16;
38 // The titlebar never shrinks too short to show the caption button plus some
40 const int kCaptionButtonHeightWithPadding
= 27;
41 // The titlebar has a 2 px 3D edge along the bottom, and we reserve 2 px (1 for
42 // border, 1 for padding) along the top.
43 const int kTitlebarTopAndBottomEdgeThickness
= 2;
44 // The icon is inset 6 px from the left frame border.
45 const int kIconLeftSpacing
= 6;
46 // The icon never shrinks below 16 px on a side.
47 const int kIconMinimumSize
= 16;
48 // There is a 4 px gap between the icon and the title text.
49 const int kIconTitleSpacing
= 4;
50 // There is a 5 px gap between the title text and the close button.
51 const int kTitleCloseButtonSpacing
= 5;
52 // There is a 4 px gap between the close button and the frame border.
53 const int kCloseButtonFrameBorderSpacing
= 4;
55 const SkColor kFrameColorAppPanel
= SK_ColorWHITE
;
56 const SkColor kFrameColorAppPanelInactive
= SK_ColorWHITE
;
60 ///////////////////////////////////////////////////////////////////////////////
61 // AppPanelBrowserFrameView, public:
63 AppPanelBrowserFrameView::AppPanelBrowserFrameView(BrowserFrame
* frame
,
64 BrowserView
* browser_view
)
65 : BrowserNonClientFrameView(frame
, browser_view
),
66 close_button_(new views::ImageButton(this)),
68 DCHECK(browser_view
->ShouldShowWindowIcon());
69 DCHECK(browser_view
->ShouldShowWindowTitle());
71 frame
->set_frame_type(views::Widget::FRAME_TYPE_FORCE_CUSTOM
);
73 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
74 close_button_
->SetImage(views::CustomButton::STATE_NORMAL
,
75 rb
.GetImageSkiaNamed(IDR_CLOSE_2
));
76 close_button_
->SetImage(views::CustomButton::STATE_HOVERED
,
77 rb
.GetImageSkiaNamed(IDR_CLOSE_2_H
));
78 close_button_
->SetImage(views::CustomButton::STATE_PRESSED
,
79 rb
.GetImageSkiaNamed(IDR_CLOSE_2_P
));
80 close_button_
->SetAccessibleName(
81 l10n_util::GetStringUTF16(IDS_ACCNAME_CLOSE
));
82 AddChildView(close_button_
);
84 window_icon_
= new TabIconView(this, NULL
);
85 window_icon_
->set_is_light(true);
86 AddChildView(window_icon_
);
87 window_icon_
->Update();
90 AppPanelBrowserFrameView::~AppPanelBrowserFrameView() {
93 ///////////////////////////////////////////////////////////////////////////////
94 // AppPanelBrowserFrameView, BrowserNonClientFrameView implementation:
96 gfx::Rect
AppPanelBrowserFrameView::GetBoundsForTabStrip(
97 views::View
* tabstrip
) const {
98 // App panels never show a tab strip.
103 int AppPanelBrowserFrameView::GetTopInset() const {
104 return NonClientTopBorderHeight();
107 int AppPanelBrowserFrameView::GetThemeBackgroundXInset() const {
111 void AppPanelBrowserFrameView::UpdateThrobber(bool running
) {
112 window_icon_
->Update();
115 gfx::Size
AppPanelBrowserFrameView::GetMinimumSize() {
116 gfx::Size
min_size(browser_view()->GetMinimumSize());
117 int border_thickness
= NonClientBorderThickness();
118 min_size
.Enlarge(2 * border_thickness
,
119 NonClientTopBorderHeight() + border_thickness
);
121 min_size
.set_width(std::max(min_size
.width(),
122 (2 * FrameBorderThickness()) + kIconLeftSpacing
+ IconSize() +
123 kTitleCloseButtonSpacing
+ kCloseButtonFrameBorderSpacing
));
127 ///////////////////////////////////////////////////////////////////////////////
128 // AppPanelBrowserFrameView, views::NonClientFrameView implementation:
130 gfx::Rect
AppPanelBrowserFrameView::GetBoundsForClientView() const {
131 return client_view_bounds_
;
134 gfx::Rect
AppPanelBrowserFrameView::GetWindowBoundsForClientBounds(
135 const gfx::Rect
& client_bounds
) const {
136 int top_height
= NonClientTopBorderHeight();
137 int border_thickness
= NonClientBorderThickness();
138 return gfx::Rect(std::max(0, client_bounds
.x() - border_thickness
),
139 std::max(0, client_bounds
.y() - top_height
),
140 client_bounds
.width() + (2 * border_thickness
),
141 client_bounds
.height() + top_height
+ border_thickness
);
144 int AppPanelBrowserFrameView::NonClientHitTest(const gfx::Point
& point
) {
145 if (!bounds().Contains(point
))
148 int frame_component
= frame()->client_view()->NonClientHitTest(point
);
150 // See if we're in the sysmenu region. (We check the ClientView first to be
151 // consistent with OpaqueBrowserFrameView; it's not really necessary here.)
152 gfx::Rect
sysmenu_rect(IconBounds());
153 // In maximized mode we extend the rect to the screen corner to take advantage
155 if (frame()->IsMaximized())
156 sysmenu_rect
.SetRect(0, 0, sysmenu_rect
.right(), sysmenu_rect
.bottom());
157 sysmenu_rect
.set_x(GetMirroredXForRect(sysmenu_rect
));
158 if (sysmenu_rect
.Contains(point
))
159 return (frame_component
== HTCLIENT
) ? HTCLIENT
: HTSYSMENU
;
161 if (frame_component
!= HTNOWHERE
)
162 return frame_component
;
164 // Then see if the point is within any of the window controls.
165 if (close_button_
->visible() &&
166 close_button_
->GetMirroredBounds().Contains(point
))
169 int window_component
= GetHTComponentForFrame(point
,
170 NonClientBorderThickness(), NonClientBorderThickness(),
171 kResizeAreaCornerSize
, kResizeAreaCornerSize
,
172 frame()->widget_delegate()->CanResize());
173 // Fall back to the caption if no other component matches.
174 return (window_component
== HTNOWHERE
) ? HTCAPTION
: window_component
;
177 void AppPanelBrowserFrameView::GetWindowMask(const gfx::Size
& size
,
178 gfx::Path
* window_mask
) {
181 if (frame()->IsMaximized())
184 // Redefine the window visible region for the new size.
185 window_mask
->moveTo(0, 3);
186 window_mask
->lineTo(1, 2);
187 window_mask
->lineTo(1, 1);
188 window_mask
->lineTo(2, 1);
189 window_mask
->lineTo(3, 0);
191 window_mask
->lineTo(SkIntToScalar(size
.width() - 3), 0);
192 window_mask
->lineTo(SkIntToScalar(size
.width() - 2), 1);
193 window_mask
->lineTo(SkIntToScalar(size
.width() - 1), 1);
194 window_mask
->lineTo(SkIntToScalar(size
.width() - 1), 2);
195 window_mask
->lineTo(SkIntToScalar(size
.width()), 3);
197 window_mask
->lineTo(SkIntToScalar(size
.width()),
198 SkIntToScalar(size
.height()));
199 window_mask
->lineTo(0, SkIntToScalar(size
.height()));
200 window_mask
->close();
203 void AppPanelBrowserFrameView::ResetWindowControls() {
204 // The close button isn't affected by this constraint.
207 void AppPanelBrowserFrameView::UpdateWindowIcon() {
208 window_icon_
->SchedulePaint();
212 ///////////////////////////////////////////////////////////////////////////////
213 // AppPanelBrowserFrameView, views::View overrides:
215 void AppPanelBrowserFrameView::OnPaint(gfx::Canvas
* canvas
) {
216 if (frame()->IsMaximized())
217 PaintMaximizedFrameBorder(canvas
);
219 PaintRestoredFrameBorder(canvas
);
220 PaintTitleBar(canvas
);
221 if (!frame()->IsMaximized())
222 PaintRestoredClientEdge(canvas
);
225 void AppPanelBrowserFrameView::Layout() {
226 LayoutWindowControls();
228 client_view_bounds_
= CalculateClientAreaBounds(width(), height());
231 ///////////////////////////////////////////////////////////////////////////////
232 // AppPanelBrowserFrameView, views::ButtonListener implementation:
234 void AppPanelBrowserFrameView::ButtonPressed(views::Button
* sender
,
235 const ui::Event
& event
) {
236 if (sender
== close_button_
)
240 ///////////////////////////////////////////////////////////////////////////////
241 // AppPanelBrowserFrameView, TabIconView::TabContentsProvider implementation:
243 bool AppPanelBrowserFrameView::ShouldTabIconViewAnimate() const {
244 // This function is queried during the creation of the window as the
245 // TabIconView we host is initialized, so we need to NULL check the selected
246 // WebContents because in this condition there is not yet a selected tab.
247 WebContents
* current_tab
= browser_view()->GetActiveWebContents();
248 return current_tab
? current_tab
->IsLoading() : false;
251 gfx::ImageSkia
AppPanelBrowserFrameView::GetFaviconForTabIconView() {
252 return frame()->widget_delegate()->GetWindowIcon();
255 ///////////////////////////////////////////////////////////////////////////////
256 // AppPanelBrowserFrameView, private:
258 int AppPanelBrowserFrameView::FrameBorderThickness() const {
259 return frame()->IsMaximized() ? 0 : kFrameBorderThickness
;
262 int AppPanelBrowserFrameView::NonClientBorderThickness() const {
263 return FrameBorderThickness() +
264 (frame()->IsMaximized() ? 0 : kClientEdgeThickness
);
267 int AppPanelBrowserFrameView::NonClientTopBorderHeight() const {
268 return std::max(FrameBorderThickness() + IconSize(),
269 FrameBorderThickness() + kCaptionButtonHeightWithPadding
) +
270 TitlebarBottomThickness();
273 int AppPanelBrowserFrameView::TitlebarBottomThickness() const {
274 return kTitlebarTopAndBottomEdgeThickness
+
275 (frame()->IsMaximized() ? 0 : kClientEdgeThickness
);
278 int AppPanelBrowserFrameView::IconSize() const {
280 // This metric scales up if either the titlebar height or the titlebar font
281 // size are increased.
282 return GetSystemMetrics(SM_CYSMICON
);
284 return std::max(BrowserFrame::GetTitleFont().height(), kIconMinimumSize
);
288 gfx::Rect
AppPanelBrowserFrameView::IconBounds() const {
289 int size
= IconSize();
290 int frame_thickness
= FrameBorderThickness();
291 // Our frame border has a different "3D look" than Windows'. Theirs has a
292 // more complex gradient on the top that they push their icon/title below;
293 // then the maximized window cuts this off and the icon/title are centered
294 // in the remaining space. Because the apparent shape of our border is
295 // simpler, using the same positioning makes things look slightly uncentered
296 // with restored windows, so when the window is restored, instead of
297 // calculating the remaining space from below the frame border, we calculate
298 // from below the top border-plus-padding.
299 int unavailable_px_at_top
= frame()->IsMaximized() ?
300 frame_thickness
: kTitlebarTopAndBottomEdgeThickness
;
301 // When the icon is shorter than the minimum space we reserve for the caption
302 // button, we vertically center it. We want to bias rounding to put extra
303 // space above the icon, since the 3D edge (+ client edge, for restored
304 // windows) below looks (to the eye) more like additional space than does the
305 // border + padding (or nothing at all, for maximized windows) above; hence
307 int y
= unavailable_px_at_top
+ (NonClientTopBorderHeight() -
308 unavailable_px_at_top
- size
- TitlebarBottomThickness() + 1) / 2;
309 return gfx::Rect(frame_thickness
+ kIconLeftSpacing
, y
, size
, size
);
312 void AppPanelBrowserFrameView::PaintRestoredFrameBorder(gfx::Canvas
* canvas
) {
313 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
315 gfx::ImageSkia
* top_left_corner
=
316 rb
.GetImageSkiaNamed(IDR_WINDOW_TOP_LEFT_CORNER
);
317 gfx::ImageSkia
* top_right_corner
=
318 rb
.GetImageSkiaNamed(IDR_WINDOW_TOP_RIGHT_CORNER
);
319 gfx::ImageSkia
* top_edge
= rb
.GetImageSkiaNamed(IDR_WINDOW_TOP_CENTER
);
320 gfx::ImageSkia
* right_edge
= rb
.GetImageSkiaNamed(IDR_WINDOW_RIGHT_SIDE
);
321 gfx::ImageSkia
* left_edge
= rb
.GetImageSkiaNamed(IDR_WINDOW_LEFT_SIDE
);
322 gfx::ImageSkia
* bottom_left_corner
=
323 rb
.GetImageSkiaNamed(IDR_WINDOW_BOTTOM_LEFT_CORNER
);
324 gfx::ImageSkia
* bottom_right_corner
=
325 rb
.GetImageSkiaNamed(IDR_WINDOW_BOTTOM_RIGHT_CORNER
);
326 gfx::ImageSkia
* bottom_edge
= rb
.GetImageSkiaNamed(IDR_WINDOW_BOTTOM_CENTER
);
328 // Window frame mode and color.
329 gfx::ImageSkia
* theme_frame
;
331 if (ShouldPaintAsActive()) {
332 theme_frame
= rb
.GetImageSkiaNamed(IDR_FRAME_APP_PANEL
);
333 frame_color
= kFrameColorAppPanel
;
335 theme_frame
= rb
.GetImageSkiaNamed(IDR_FRAME_APP_PANEL
);
336 frame_color
= kFrameColorAppPanelInactive
;
339 // Fill with the frame color first so we have a constant background for
340 // areas not covered by the theme image.
341 canvas
->FillRect(gfx::Rect(0, 0, width(), theme_frame
->height()),
344 int remaining_height
= height() - theme_frame
->height();
345 if (remaining_height
> 0) {
346 // Now fill down the sides.
347 canvas
->FillRect(gfx::Rect(0, theme_frame
->height(), left_edge
->width(),
348 remaining_height
), frame_color
);
349 canvas
->FillRect(gfx::Rect(width() - right_edge
->width(),
350 theme_frame
->height(), right_edge
->width(),
351 remaining_height
), frame_color
);
352 int center_width
= width() - left_edge
->width() - right_edge
->width();
353 if (center_width
> 0) {
354 // Now fill the bottom area.
355 canvas
->FillRect(gfx::Rect(left_edge
->width(),
356 height() - bottom_edge
->height(), center_width
,
357 bottom_edge
->height()), frame_color
);
361 // Draw the theme frame.
362 canvas
->TileImageInt(*theme_frame
, 0, 0, width(), theme_frame
->height());
365 canvas
->DrawImageInt(*top_left_corner
, 0, 0);
366 canvas
->TileImageInt(*top_edge
, top_left_corner
->width(), 0,
367 width() - top_right_corner
->width(), top_edge
->height());
368 canvas
->DrawImageInt(*top_right_corner
,
369 width() - top_right_corner
->width(), 0);
372 canvas
->TileImageInt(*right_edge
, width() - right_edge
->width(),
373 top_right_corner
->height(), right_edge
->width(),
374 height() - top_right_corner
->height() - bottom_right_corner
->height());
377 canvas
->DrawImageInt(*bottom_right_corner
,
378 width() - bottom_right_corner
->width(),
379 height() - bottom_right_corner
->height());
380 canvas
->TileImageInt(*bottom_edge
, bottom_left_corner
->width(),
381 height() - bottom_edge
->height(),
382 width() - bottom_left_corner
->width() - bottom_right_corner
->width(),
383 bottom_edge
->height());
384 canvas
->DrawImageInt(*bottom_left_corner
, 0,
385 height() - bottom_left_corner
->height());
388 canvas
->TileImageInt(*left_edge
, 0, top_left_corner
->height(),
390 height() - top_left_corner
->height() - bottom_left_corner
->height());
393 void AppPanelBrowserFrameView::PaintMaximizedFrameBorder(gfx::Canvas
* canvas
) {
394 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
396 gfx::ImageSkia
* frame_image
= rb
.GetImageSkiaNamed(IDR_FRAME_APP_PANEL
);
397 canvas
->TileImageInt(*frame_image
, 0, FrameBorderThickness(), width(),
398 frame_image
->height());
400 // The bottom of the titlebar actually comes from the top of the Client Edge
401 // graphic, with the actual client edge clipped off the bottom.
402 gfx::ImageSkia
* titlebar_bottom
= rb
.GetImageSkiaNamed(IDR_APP_TOP_CENTER
);
403 int edge_height
= titlebar_bottom
->height() - kClientEdgeThickness
;
404 canvas
->TileImageInt(*titlebar_bottom
, 0,
405 frame()->client_view()->y() - edge_height
,
406 width(), edge_height
);
409 void AppPanelBrowserFrameView::PaintTitleBar(gfx::Canvas
* canvas
) {
410 // The window icon is painted by the TabIconView.
411 views::WidgetDelegate
* d
= frame()->widget_delegate();
412 canvas
->DrawStringInt(d
->GetWindowTitle(), BrowserFrame::GetTitleFont(),
413 SK_ColorBLACK
, GetMirroredXForRect(title_bounds_
), title_bounds_
.y(),
414 title_bounds_
.width(), title_bounds_
.height());
417 void AppPanelBrowserFrameView::PaintRestoredClientEdge(gfx::Canvas
* canvas
) {
418 gfx::Rect client_area_bounds
= CalculateClientAreaBounds(width(), height());
419 int client_area_top
= client_area_bounds
.y();
421 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
422 gfx::ImageSkia
* top_left
= rb
.GetImageSkiaNamed(IDR_APP_TOP_LEFT
);
423 gfx::ImageSkia
* top
= rb
.GetImageSkiaNamed(IDR_APP_TOP_CENTER
);
424 gfx::ImageSkia
* top_right
= rb
.GetImageSkiaNamed(IDR_APP_TOP_RIGHT
);
425 gfx::ImageSkia
* right
= rb
.GetImageSkiaNamed(IDR_CONTENT_RIGHT_SIDE
);
426 gfx::ImageSkia
* bottom_right
=
427 rb
.GetImageSkiaNamed(IDR_CONTENT_BOTTOM_RIGHT_CORNER
);
428 gfx::ImageSkia
* bottom
= rb
.GetImageSkiaNamed(IDR_CONTENT_BOTTOM_CENTER
);
429 gfx::ImageSkia
* bottom_left
=
430 rb
.GetImageSkiaNamed(IDR_CONTENT_BOTTOM_LEFT_CORNER
);
431 gfx::ImageSkia
* left
= rb
.GetImageSkiaNamed(IDR_CONTENT_LEFT_SIDE
);
434 int top_edge_y
= client_area_top
- top
->height();
435 canvas
->DrawImageInt(*top_left
, client_area_bounds
.x() - top_left
->width(),
437 canvas
->TileImageInt(*top
, client_area_bounds
.x(), top_edge_y
,
438 client_area_bounds
.width(), top
->height());
439 canvas
->DrawImageInt(*top_right
, client_area_bounds
.right(), top_edge_y
);
442 int client_area_bottom
=
443 std::max(client_area_top
, client_area_bounds
.bottom());
444 int client_area_height
= client_area_bottom
- client_area_top
;
445 canvas
->TileImageInt(*right
, client_area_bounds
.right(), client_area_top
,
446 right
->width(), client_area_height
);
449 canvas
->DrawImageInt(*bottom_right
, client_area_bounds
.right(),
451 canvas
->TileImageInt(*bottom
, client_area_bounds
.x(), client_area_bottom
,
452 client_area_bounds
.width(), bottom_right
->height());
453 canvas
->DrawImageInt(*bottom_left
,
454 client_area_bounds
.x() - bottom_left
->width(), client_area_bottom
);
457 canvas
->TileImageInt(*left
, client_area_bounds
.x() - left
->width(),
458 client_area_top
, left
->width(), client_area_height
);
460 // Draw the color to fill in the edges.
461 canvas
->DrawRect(gfx::Rect(
462 client_area_bounds
.x() - kClientEdgeThickness
,
463 client_area_top
- kClientEdgeThickness
,
464 client_area_bounds
.width() + kClientEdgeThickness
,
465 client_area_bottom
- client_area_top
+ kClientEdgeThickness
),
466 views::kClientEdgeColor
);
469 void AppPanelBrowserFrameView::LayoutWindowControls() {
470 close_button_
->SetImageAlignment(views::ImageButton::ALIGN_LEFT
,
471 views::ImageButton::ALIGN_BOTTOM
);
472 bool is_maximized
= frame()->IsMaximized();
473 // There should always be the same number of non-border pixels visible to the
474 // side of the close button. In maximized mode we extend the button to the
475 // screen corner to obey Fitts' Law.
476 int right_extra_width
= is_maximized
? kCloseButtonFrameBorderSpacing
: 0;
477 gfx::Size close_button_size
= close_button_
->GetPreferredSize();
479 (NonClientTopBorderHeight() - close_button_size
.height()) / 2;
480 int top_extra_height
= is_maximized
? close_button_y
: 0;
481 close_button_
->SetBounds(width() - FrameBorderThickness() -
482 kCloseButtonFrameBorderSpacing
- close_button_size
.width(),
483 close_button_y
- top_extra_height
,
484 close_button_size
.width() + right_extra_width
,
485 close_button_size
.height() + top_extra_height
);
488 void AppPanelBrowserFrameView::LayoutTitleBar() {
489 // Size the icon first; the window title is based on the icon position.
490 gfx::Rect
icon_bounds(IconBounds());
491 window_icon_
->SetBoundsRect(icon_bounds
);
494 int title_x
= icon_bounds
.right() + kIconTitleSpacing
;
495 int title_height
= BrowserFrame::GetTitleFont().GetHeight();
496 // We bias the title position so that when the difference between the icon
497 // and title heights is odd, the extra pixel of the title is above the
498 // vertical midline rather than below. This compensates for how the icon is
499 // already biased downwards (see IconBounds()) and helps prevent descenders
500 // on the title from overlapping the 3D edge at the bottom of the titlebar.
501 title_bounds_
.SetRect(title_x
,
502 icon_bounds
.y() + ((icon_bounds
.height() - title_height
- 1) / 2),
503 std::max(0, close_button_
->x() - kTitleCloseButtonSpacing
- title_x
),
507 gfx::Rect
AppPanelBrowserFrameView::CalculateClientAreaBounds(int width
,
509 int top_height
= NonClientTopBorderHeight();
510 int border_thickness
= NonClientBorderThickness();
511 return gfx::Rect(border_thickness
, top_height
,
512 std::max(0, width
- (2 * border_thickness
)),
513 std::max(0, height
- top_height
- border_thickness
));