blob: fdfe4c7bb2bd2312f0776c828327fca3d61c657b [file] [log] [blame]
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001// 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 "chrome/browser/ui/views/frame/opaque_browser_frame_view.h"
6
7#include <algorithm>
8#include <string>
9
10#include "base/command_line.h"
11#include "base/compiler_specific.h"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000012#include "base/prefs/pref_service.h"
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010013#include "base/strings/utf_string_conversions.h"
Ben Murdoch7dbb3d52013-07-17 14:55:54 +010014#include "chrome/browser/chrome_notification_types.h"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000015#include "chrome/browser/themes/theme_properties.h"
Ben Murdocheb525c52013-07-10 11:40:50 +010016#include "chrome/browser/ui/views/avatar_label.h"
Torne (Richard Coles)58218062012-11-14 11:43:16 +000017#include "chrome/browser/ui/views/avatar_menu_button.h"
18#include "chrome/browser/ui/views/frame/browser_frame.h"
19#include "chrome/browser/ui/views/frame/browser_view.h"
Torne (Richard Coles)58537e22013-09-12 12:10:22 +010020#include "chrome/browser/ui/views/frame/opaque_browser_frame_view_layout.h"
Torne (Richard Coles)d0247b12013-09-19 22:36:51 +010021#include "chrome/browser/ui/views/frame/opaque_browser_frame_view_platform_specific.h"
Torne (Richard Coles)58218062012-11-14 11:43:16 +000022#include "chrome/browser/ui/views/tab_icon_view.h"
23#include "chrome/browser/ui/views/tabs/tab_strip.h"
24#include "chrome/browser/ui/views/toolbar_view.h"
Torne (Richard Coles)58218062012-11-14 11:43:16 +000025#include "chrome/common/chrome_switches.h"
26#include "chrome/common/pref_names.h"
27#include "content/public/browser/notification_service.h"
28#include "content/public/browser/web_contents.h"
29#include "grit/chromium_strings.h"
30#include "grit/generated_resources.h"
31#include "grit/theme_resources.h"
32#include "grit/ui_resources.h"
33#include "ui/base/accessibility/accessible_view_state.h"
34#include "ui/base/hit_test.h"
35#include "ui/base/l10n/l10n_util.h"
36#include "ui/base/resource/resource_bundle.h"
37#include "ui/base/theme_provider.h"
38#include "ui/gfx/canvas.h"
39#include "ui/gfx/font.h"
40#include "ui/gfx/image/image.h"
41#include "ui/gfx/image/image_skia.h"
42#include "ui/gfx/path.h"
43#include "ui/views/controls/button/image_button.h"
44#include "ui/views/controls/image_view.h"
45#include "ui/views/controls/label.h"
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010046#include "ui/views/layout/layout_constants.h"
Torne (Richard Coles)58218062012-11-14 11:43:16 +000047#include "ui/views/widget/root_view.h"
48#include "ui/views/window/frame_background.h"
49#include "ui/views/window/window_shape.h"
50
Torne (Richard Coles)58218062012-11-14 11:43:16 +000051using content::WebContents;
52
53namespace {
54
Torne (Richard Coles)58218062012-11-14 11:43:16 +000055// While resize areas on Windows are normally the same size as the window
56// borders, our top area is shrunk by 1 px to make it easier to move the window
57// around with our thinner top grabbable strip. (Incidentally, our side and
58// bottom resize areas don't match the frame border thickness either -- they
59// span the whole nonclient area, so there's no "dead zone" for the mouse.)
60const int kTopResizeAdjust = 1;
Torne (Richard Coles)58537e22013-09-12 12:10:22 +010061
Torne (Richard Coles)58218062012-11-14 11:43:16 +000062// In the window corners, the resize areas don't actually expand bigger, but the
63// 16 px at the end of each edge triggers diagonal resizing.
64const int kResizeAreaCornerSize = 16;
Torne (Richard Coles)58537e22013-09-12 12:10:22 +010065
Torne (Richard Coles)58218062012-11-14 11:43:16 +000066// The content left/right images have a shadow built into them.
67const int kContentEdgeShadowThickness = 2;
Torne (Richard Coles)58537e22013-09-12 12:10:22 +010068
Torne (Richard Coles)58218062012-11-14 11:43:16 +000069// The icon never shrinks below 16 px on a side.
70const int kIconMinimumSize = 16;
Torne (Richard Coles)58537e22013-09-12 12:10:22 +010071
Torne (Richard Coles)58218062012-11-14 11:43:16 +000072// The top 3 px of the tabstrip is shadow; in maximized mode we push this off
73// the top of the screen so the tabs appear flush against the screen edge.
74const int kTabstripTopShadowThickness = 3;
Torne (Richard Coles)58218062012-11-14 11:43:16 +000075
76// Converts |bounds| from |src|'s coordinate system to |dst|, and checks if
77// |pt| is contained within.
78bool ConvertedContainsCheck(gfx::Rect bounds, const views::View* src,
79 const views::View* dst, const gfx::Point& pt) {
80 DCHECK(src);
81 DCHECK(dst);
82 gfx::Point origin(bounds.origin());
83 views::View::ConvertPointToTarget(src, dst, &origin);
84 bounds.set_origin(origin);
85 return bounds.Contains(pt);
86}
87
Torne (Richard Coles)58218062012-11-14 11:43:16 +000088} // namespace
89
90///////////////////////////////////////////////////////////////////////////////
91// OpaqueBrowserFrameView, public:
92
93OpaqueBrowserFrameView::OpaqueBrowserFrameView(BrowserFrame* frame,
94 BrowserView* browser_view)
95 : BrowserNonClientFrameView(frame, browser_view),
Torne (Richard Coles)58537e22013-09-12 12:10:22 +010096 layout_(new OpaqueBrowserFrameViewLayout(this)),
Torne (Richard Coles)58218062012-11-14 11:43:16 +000097 minimize_button_(NULL),
98 maximize_button_(NULL),
99 restore_button_(NULL),
100 close_button_(NULL),
101 window_icon_(NULL),
102 window_title_(NULL),
103 frame_background_(new views::FrameBackground()) {
Torne (Richard Coles)58537e22013-09-12 12:10:22 +0100104 SetLayoutManager(layout_);
105
106 if (OpaqueBrowserFrameViewLayout::ShouldAddDefaultCaptionButtons()) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000107 minimize_button_ = InitWindowCaptionButton(IDR_MINIMIZE,
108 IDR_MINIMIZE_H,
109 IDR_MINIMIZE_P,
110 IDR_MINIMIZE_BUTTON_MASK,
Torne (Richard Coles)58537e22013-09-12 12:10:22 +0100111 IDS_ACCNAME_MINIMIZE,
112 VIEW_ID_MINIMIZE_BUTTON);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000113 maximize_button_ = InitWindowCaptionButton(IDR_MAXIMIZE,
114 IDR_MAXIMIZE_H,
115 IDR_MAXIMIZE_P,
116 IDR_MAXIMIZE_BUTTON_MASK,
Torne (Richard Coles)58537e22013-09-12 12:10:22 +0100117 IDS_ACCNAME_MAXIMIZE,
118 VIEW_ID_MAXIMIZE_BUTTON);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000119 restore_button_ = InitWindowCaptionButton(IDR_RESTORE,
120 IDR_RESTORE_H,
121 IDR_RESTORE_P,
122 IDR_RESTORE_BUTTON_MASK,
Torne (Richard Coles)58537e22013-09-12 12:10:22 +0100123 IDS_ACCNAME_RESTORE,
124 VIEW_ID_RESTORE_BUTTON);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000125 close_button_ = InitWindowCaptionButton(IDR_CLOSE,
126 IDR_CLOSE_H,
127 IDR_CLOSE_P,
128 IDR_CLOSE_BUTTON_MASK,
Torne (Richard Coles)58537e22013-09-12 12:10:22 +0100129 IDS_ACCNAME_CLOSE,
130 VIEW_ID_CLOSE_BUTTON);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000131 }
132
133 // Initializing the TabIconView is expensive, so only do it if we need to.
134 if (browser_view->ShouldShowWindowIcon()) {
135 window_icon_ = new TabIconView(this);
136 window_icon_->set_is_light(true);
Torne (Richard Coles)58537e22013-09-12 12:10:22 +0100137 window_icon_->set_id(VIEW_ID_WINDOW_ICON);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000138 AddChildView(window_icon_);
139 window_icon_->Update();
140 }
141
142 window_title_ = new views::Label(browser_view->GetWindowTitle(),
143 BrowserFrame::GetTitleFont());
144 window_title_->SetVisible(browser_view->ShouldShowWindowTitle());
145 window_title_->SetEnabledColor(SK_ColorWHITE);
146 // TODO(msw): Use a transparent background color as a workaround to use the
147 // gfx::Canvas::NO_SUBPIXEL_RENDERING flag and avoid some visual artifacts.
148 window_title_->SetBackgroundColor(0x00000000);
149 window_title_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
Torne (Richard Coles)58537e22013-09-12 12:10:22 +0100150 window_title_->set_id(VIEW_ID_WINDOW_TITLE);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000151 AddChildView(window_title_);
152
153 UpdateAvatarInfo();
154 if (!browser_view->IsOffTheRecord()) {
155 registrar_.Add(this, chrome::NOTIFICATION_PROFILE_CACHED_INFO_CHANGED,
156 content::NotificationService::AllSources());
157 }
Torne (Richard Coles)d0247b12013-09-19 22:36:51 +0100158
159 platform_observer_.reset(
160 OpaqueBrowserFrameViewPlatformSpecific::Create(this, layout_));
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000161}
162
163OpaqueBrowserFrameView::~OpaqueBrowserFrameView() {
164}
165
166///////////////////////////////////////////////////////////////////////////////
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000167// OpaqueBrowserFrameView, BrowserNonClientFrameView implementation:
168
169gfx::Rect OpaqueBrowserFrameView::GetBoundsForTabStrip(
170 views::View* tabstrip) const {
171 if (!tabstrip)
172 return gfx::Rect();
173
Torne (Richard Coles)58537e22013-09-12 12:10:22 +0100174 return layout_->GetBoundsForTabStrip(tabstrip->GetPreferredSize(), width());
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000175}
176
177BrowserNonClientFrameView::TabStripInsets
178OpaqueBrowserFrameView::GetTabStripInsets(bool restored) const {
Torne (Richard Coles)4e180b62013-10-18 15:46:22 +0100179 if (!browser_view()->IsTabStripVisible())
180 return TabStripInsets();
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000181 // TODO: include OTR and caption.
Torne (Richard Coles)58537e22013-09-12 12:10:22 +0100182 return TabStripInsets(layout_->GetTabStripInsetsTop(restored), 0, 0);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000183}
184
185int OpaqueBrowserFrameView::GetThemeBackgroundXInset() const {
186 return 0;
187}
188
189void OpaqueBrowserFrameView::UpdateThrobber(bool running) {
190 if (window_icon_)
191 window_icon_->Update();
192}
193
194gfx::Size OpaqueBrowserFrameView::GetMinimumSize() {
Torne (Richard Coles)58537e22013-09-12 12:10:22 +0100195 return layout_->GetMinimumSize(width());
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000196}
197
198///////////////////////////////////////////////////////////////////////////////
199// OpaqueBrowserFrameView, views::NonClientFrameView implementation:
200
201gfx::Rect OpaqueBrowserFrameView::GetBoundsForClientView() const {
Torne (Richard Coles)58537e22013-09-12 12:10:22 +0100202 return layout_->client_view_bounds();
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000203}
204
205gfx::Rect OpaqueBrowserFrameView::GetWindowBoundsForClientBounds(
206 const gfx::Rect& client_bounds) const {
Torne (Richard Coles)58537e22013-09-12 12:10:22 +0100207 return layout_->GetWindowBoundsForClientBounds(client_bounds);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000208}
209
210int OpaqueBrowserFrameView::NonClientHitTest(const gfx::Point& point) {
211 if (!bounds().Contains(point))
212 return HTNOWHERE;
213
Ben Murdocheb525c52013-07-10 11:40:50 +0100214 // See if the point is within the avatar menu button or within the avatar
215 // label.
216 if ((avatar_button() &&
217 avatar_button()->GetMirroredBounds().Contains(point)) ||
218 (avatar_label() && avatar_label()->GetMirroredBounds().Contains(point)))
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000219 return HTCLIENT;
220
221 int frame_component = frame()->client_view()->NonClientHitTest(point);
222
223 // See if we're in the sysmenu region. We still have to check the tabstrip
224 // first so that clicks in a tab don't get treated as sysmenu clicks.
225 gfx::Rect sysmenu_rect(IconBounds());
226 // In maximized mode we extend the rect to the screen corner to take advantage
227 // of Fitts' Law.
228 if (frame()->IsMaximized())
229 sysmenu_rect.SetRect(0, 0, sysmenu_rect.right(), sysmenu_rect.bottom());
230 sysmenu_rect.set_x(GetMirroredXForRect(sysmenu_rect));
231 if (sysmenu_rect.Contains(point))
232 return (frame_component == HTCLIENT) ? HTCLIENT : HTSYSMENU;
233
234 if (frame_component != HTNOWHERE)
235 return frame_component;
236
237 // Then see if the point is within any of the window controls.
238 if (close_button_ && close_button_->visible() &&
239 close_button_->GetMirroredBounds().Contains(point))
240 return HTCLOSE;
241 if (restore_button_ && restore_button_->visible() &&
242 restore_button_->GetMirroredBounds().Contains(point))
243 return HTMAXBUTTON;
244 if (maximize_button_ && maximize_button_->visible() &&
245 maximize_button_->GetMirroredBounds().Contains(point))
246 return HTMAXBUTTON;
247 if (minimize_button_ && minimize_button_->visible() &&
248 minimize_button_->GetMirroredBounds().Contains(point))
249 return HTMINBUTTON;
250
251 views::WidgetDelegate* delegate = frame()->widget_delegate();
252 if (!delegate) {
253 LOG(WARNING) << "delegate is NULL, returning safe default.";
254 return HTCAPTION;
255 }
256 int window_component = GetHTComponentForFrame(point, TopResizeHeight(),
257 NonClientBorderThickness(), kResizeAreaCornerSize, kResizeAreaCornerSize,
258 delegate->CanResize());
259 // Fall back to the caption if no other component matches.
260 return (window_component == HTNOWHERE) ? HTCAPTION : window_component;
261}
262
263void OpaqueBrowserFrameView::GetWindowMask(const gfx::Size& size,
264 gfx::Path* window_mask) {
265 DCHECK(window_mask);
266
267 if (frame()->IsMaximized() || frame()->IsFullscreen())
268 return;
269
270 views::GetDefaultWindowMask(size, window_mask);
271}
272
273void OpaqueBrowserFrameView::ResetWindowControls() {
Torne (Richard Coles)58537e22013-09-12 12:10:22 +0100274 if (!OpaqueBrowserFrameViewLayout::ShouldAddDefaultCaptionButtons())
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000275 return;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000276 restore_button_->SetState(views::CustomButton::STATE_NORMAL);
277 minimize_button_->SetState(views::CustomButton::STATE_NORMAL);
278 maximize_button_->SetState(views::CustomButton::STATE_NORMAL);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000279 // The close button isn't affected by this constraint.
280}
281
282void OpaqueBrowserFrameView::UpdateWindowIcon() {
283 window_icon_->SchedulePaint();
284}
285
286void OpaqueBrowserFrameView::UpdateWindowTitle() {
287 if (!frame()->IsFullscreen())
288 window_title_->SchedulePaint();
289}
290
291///////////////////////////////////////////////////////////////////////////////
292// OpaqueBrowserFrameView, views::View overrides:
293
294void OpaqueBrowserFrameView::OnPaint(gfx::Canvas* canvas) {
295 if (frame()->IsFullscreen())
296 return; // Nothing is visible, so don't bother to paint.
297
298 if (frame()->IsMaximized())
299 PaintMaximizedFrameBorder(canvas);
300 else
301 PaintRestoredFrameBorder(canvas);
302
303 // The window icon and title are painted by their respective views.
304 /* TODO(pkasting): If this window is active, we should also draw a drop
305 * shadow on the title. This is tricky, because we don't want to hardcode a
306 * shadow color (since we want to work with various themes), but we can't
307 * alpha-blend either (since the Windows text APIs don't really do this).
308 * So we'd need to sample the background color at the right location and
309 * synthesize a good shadow color. */
310
311 if (browser_view()->IsToolbarVisible())
312 PaintToolbarBackground(canvas);
313 if (!frame()->IsMaximized())
314 PaintRestoredClientEdge(canvas);
315}
316
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000317bool OpaqueBrowserFrameView::HitTestRect(const gfx::Rect& rect) const {
Ben Murdochbbcdd452013-07-25 10:06:34 +0100318 if (!views::View::HitTestRect(rect)) {
319 // |rect| is outside OpaqueBrowserFrameView's bounds.
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000320 return false;
Ben Murdochbbcdd452013-07-25 10:06:34 +0100321 }
322
323 // If the rect is outside the bounds of the client area, claim it.
324 // TODO(tdanderson): Implement View::ConvertRectToTarget().
325 gfx::Point rect_in_client_view_coords_origin(rect.origin());
326 View::ConvertPointToTarget(this, frame()->client_view(),
327 &rect_in_client_view_coords_origin);
328 gfx::Rect rect_in_client_view_coords(
329 rect_in_client_view_coords_origin, rect.size());
330 if (!frame()->client_view()->HitTestRect(rect_in_client_view_coords))
331 return true;
332
333 // Otherwise, claim |rect| only if it is above the bottom of the tabstrip in
334 // a non-tab portion.
335 TabStrip* tabstrip = browser_view()->tabstrip();
336 if (!tabstrip || !browser_view()->IsTabStripVisible())
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000337 return false;
338
Ben Murdochbbcdd452013-07-25 10:06:34 +0100339 gfx::Point rect_in_tabstrip_coords_origin(rect.origin());
340 View::ConvertPointToTarget(this, tabstrip,
341 &rect_in_tabstrip_coords_origin);
342 gfx::Rect rect_in_tabstrip_coords(
343 rect_in_tabstrip_coords_origin, rect.size());
344
345 if (rect_in_tabstrip_coords.bottom() > tabstrip->GetLocalBounds().bottom()) {
346 // |rect| is below the tabstrip.
347 return false;
348 }
349
350 if (tabstrip->HitTestRect(rect_in_tabstrip_coords)) {
351 // Claim |rect| if it is in a non-tab portion of the tabstrip.
352 // TODO(tdanderson): Pass |rect_in_tabstrip_coords| instead of its center
353 // point to TabStrip::IsPositionInWindowCaption() once
354 // GetEventHandlerForRect() is implemented.
355 return tabstrip->IsPositionInWindowCaption(
356 rect_in_tabstrip_coords.CenterPoint());
357 }
358
359 // The window switcher button is to the right of the tabstrip but is
360 // part of the client view.
361 views::View* window_switcher_button =
362 browser_view()->window_switcher_button();
363 if (window_switcher_button && window_switcher_button->visible()) {
364 gfx::Point rect_in_window_switcher_coords_origin(rect.origin());
365 View::ConvertPointToTarget(this, window_switcher_button,
366 &rect_in_window_switcher_coords_origin);
367 gfx::Rect rect_in_window_switcher_coords(
368 rect_in_window_switcher_coords_origin, rect.size());
369
370 if (window_switcher_button->HitTestRect(rect_in_window_switcher_coords))
371 return false;
372 }
373
374 // We claim |rect| because it is above the bottom of the tabstrip, but
375 // neither in the tabstrip nor in the window switcher button. In particular,
376 // the avatar label/button is left of the tabstrip and the window controls
377 // are right of the tabstrip.
378 return true;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000379}
380
381void OpaqueBrowserFrameView::GetAccessibleState(
382 ui::AccessibleViewState* state) {
383 state->role = ui::AccessibilityTypes::ROLE_TITLEBAR;
384}
385
386///////////////////////////////////////////////////////////////////////////////
387// OpaqueBrowserFrameView, views::ButtonListener implementation:
388
389void OpaqueBrowserFrameView::ButtonPressed(views::Button* sender,
390 const ui::Event& event) {
391 if (sender == minimize_button_)
392 frame()->Minimize();
393 else if (sender == maximize_button_)
394 frame()->Maximize();
395 else if (sender == restore_button_)
396 frame()->Restore();
397 else if (sender == close_button_)
398 frame()->Close();
399}
400
401///////////////////////////////////////////////////////////////////////////////
402// OpaqueBrowserFrameView, TabIconView::TabContentsProvider implementation:
403
404bool OpaqueBrowserFrameView::ShouldTabIconViewAnimate() const {
405 // This function is queried during the creation of the window as the
406 // TabIconView we host is initialized, so we need to NULL check the selected
407 // WebContents because in this condition there is not yet a selected tab.
408 WebContents* current_tab = browser_view()->GetActiveWebContents();
409 return current_tab ? current_tab->IsLoading() : false;
410}
411
412gfx::ImageSkia OpaqueBrowserFrameView::GetFaviconForTabIconView() {
413 views::WidgetDelegate* delegate = frame()->widget_delegate();
414 if (!delegate) {
415 LOG(WARNING) << "delegate is NULL, returning safe default.";
416 return gfx::ImageSkia();
417 }
418 return delegate->GetWindowIcon();
419}
420
421///////////////////////////////////////////////////////////////////////////////
422// OpaqueBrowserFrameView, protected:
423
424void OpaqueBrowserFrameView::Observe(
425 int type,
426 const content::NotificationSource& source,
427 const content::NotificationDetails& details) {
428 switch (type) {
429 case chrome::NOTIFICATION_PROFILE_CACHED_INFO_CHANGED:
430 UpdateAvatarInfo();
431 break;
432 default:
433 NOTREACHED() << "Got a notification we didn't register for!";
434 break;
435 }
436}
437
438///////////////////////////////////////////////////////////////////////////////
Torne (Richard Coles)58537e22013-09-12 12:10:22 +0100439// OpaqueBrowserFrameView, OpaqueBrowserFrameViewLayoutDelegate implementation:
440
441bool OpaqueBrowserFrameView::ShouldShowWindowIcon() const {
442 views::WidgetDelegate* delegate = frame()->widget_delegate();
443 return delegate && delegate->ShouldShowWindowIcon();
444}
445
446bool OpaqueBrowserFrameView::ShouldShowWindowTitle() const {
447 // |delegate| may be NULL if called from callback of InputMethodChanged while
448 // a window is being destroyed.
449 // See more discussion at http://crosbug.com/8958
450 views::WidgetDelegate* delegate = frame()->widget_delegate();
451 return delegate && delegate->ShouldShowWindowTitle();
452}
453
454string16 OpaqueBrowserFrameView::GetWindowTitle() const {
455 return frame()->widget_delegate()->GetWindowTitle();
456}
457
458int OpaqueBrowserFrameView::GetIconSize() const {
459#if defined(OS_WIN)
460 // This metric scales up if either the titlebar height or the titlebar font
461 // size are increased.
462 return GetSystemMetrics(SM_CYSMICON);
463#else
464 return std::max(BrowserFrame::GetTitleFont().GetHeight(), kIconMinimumSize);
465#endif
466}
467
468bool OpaqueBrowserFrameView::ShouldLeaveOffsetNearTopBorder() const {
469 return frame()->ShouldLeaveOffsetNearTopBorder();
470}
471
472gfx::Size OpaqueBrowserFrameView::GetBrowserViewMinimumSize() const {
473 return browser_view()->GetMinimumSize();
474}
475
476bool OpaqueBrowserFrameView::ShouldShowAvatar() const {
477 return browser_view()->ShouldShowAvatar();
478}
479
480gfx::ImageSkia OpaqueBrowserFrameView::GetOTRAvatarIcon() const {
481 return browser_view()->GetOTRAvatarIcon();
482}
483
484bool OpaqueBrowserFrameView::IsMaximized() const {
485 return frame()->IsMaximized();
486}
487
488bool OpaqueBrowserFrameView::IsMinimized() const {
489 return frame()->IsMinimized();
490}
491
492bool OpaqueBrowserFrameView::IsFullscreen() const {
493 return frame()->IsFullscreen();
494}
495
496bool OpaqueBrowserFrameView::IsTabStripVisible() const {
497 return browser_view()->IsTabStripVisible();
498}
499
500int OpaqueBrowserFrameView::GetTabStripHeight() const {
501 return browser_view()->GetTabStripHeight();
502}
503
504int OpaqueBrowserFrameView::GetAdditionalReservedSpaceInTabStrip() const {
505 // We don't have the sysmenu buttons in Windows 8 metro mode. However there
506 // are buttons like the window switcher which are drawn in the non client
507 // are in the BrowserView. We need to ensure that the tab strip does not
508 // draw on the window switcher button.
509 views::View* button = browser_view()->window_switcher_button();
510 return button ? button->width() : 0;
511}
512
513gfx::Size OpaqueBrowserFrameView::GetTabstripPreferredSize() const {
514 gfx::Size s = browser_view()->tabstrip()->GetPreferredSize();
515 return s;
516}
517
518///////////////////////////////////////////////////////////////////////////////
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000519// OpaqueBrowserFrameView, private:
520
521views::ImageButton* OpaqueBrowserFrameView::InitWindowCaptionButton(
522 int normal_image_id,
523 int hot_image_id,
524 int pushed_image_id,
525 int mask_image_id,
Torne (Richard Coles)58537e22013-09-12 12:10:22 +0100526 int accessibility_string_id,
527 ViewID view_id) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000528 views::ImageButton* button = new views::ImageButton(this);
529 ui::ThemeProvider* tp = frame()->GetThemeProvider();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000530 button->SetImage(views::CustomButton::STATE_NORMAL,
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000531 tp->GetImageSkiaNamed(normal_image_id));
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000532 button->SetImage(views::CustomButton::STATE_HOVERED,
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000533 tp->GetImageSkiaNamed(hot_image_id));
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000534 button->SetImage(views::CustomButton::STATE_PRESSED,
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000535 tp->GetImageSkiaNamed(pushed_image_id));
536 if (browser_view()->IsBrowserTypeNormal()) {
537 button->SetBackground(
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000538 tp->GetColor(ThemeProperties::COLOR_BUTTON_BACKGROUND),
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000539 tp->GetImageSkiaNamed(IDR_THEME_WINDOW_CONTROL_BACKGROUND),
540 tp->GetImageSkiaNamed(mask_image_id));
541 }
542 button->SetAccessibleName(
543 l10n_util::GetStringUTF16(accessibility_string_id));
Torne (Richard Coles)58537e22013-09-12 12:10:22 +0100544 button->set_id(view_id);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000545 AddChildView(button);
546 return button;
547}
548
549int OpaqueBrowserFrameView::FrameBorderThickness(bool restored) const {
Torne (Richard Coles)58537e22013-09-12 12:10:22 +0100550 return layout_->FrameBorderThickness(restored);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000551}
552
553int OpaqueBrowserFrameView::TopResizeHeight() const {
554 return FrameBorderThickness(false) - kTopResizeAdjust;
555}
556
557int OpaqueBrowserFrameView::NonClientBorderThickness() const {
Torne (Richard Coles)58537e22013-09-12 12:10:22 +0100558 return layout_->NonClientBorderThickness();
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000559}
560
561gfx::Rect OpaqueBrowserFrameView::IconBounds() const {
Torne (Richard Coles)58537e22013-09-12 12:10:22 +0100562 return layout_->IconBounds();
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000563}
564
565void OpaqueBrowserFrameView::PaintRestoredFrameBorder(gfx::Canvas* canvas) {
566 frame_background_->set_frame_color(GetFrameColor());
567 frame_background_->set_theme_image(GetFrameImage());
568 frame_background_->set_theme_overlay_image(GetFrameOverlayImage());
569 frame_background_->set_top_area_height(GetTopAreaHeight());
570
571 ui::ThemeProvider* tp = GetThemeProvider();
572 frame_background_->SetSideImages(
573 tp->GetImageSkiaNamed(IDR_WINDOW_LEFT_SIDE),
574 tp->GetImageSkiaNamed(IDR_WINDOW_TOP_CENTER),
575 tp->GetImageSkiaNamed(IDR_WINDOW_RIGHT_SIDE),
576 tp->GetImageSkiaNamed(IDR_WINDOW_BOTTOM_CENTER));
577 frame_background_->SetCornerImages(
578 tp->GetImageSkiaNamed(IDR_WINDOW_TOP_LEFT_CORNER),
579 tp->GetImageSkiaNamed(IDR_WINDOW_TOP_RIGHT_CORNER),
580 tp->GetImageSkiaNamed(IDR_WINDOW_BOTTOM_LEFT_CORNER),
581 tp->GetImageSkiaNamed(IDR_WINDOW_BOTTOM_RIGHT_CORNER));
582 frame_background_->PaintRestored(canvas, this);
583
584 // Note: When we don't have a toolbar, we need to draw some kind of bottom
585 // edge here. Because the App Window graphics we use for this have an
586 // attached client edge and their sizing algorithm is a little involved, we do
587 // all this in PaintRestoredClientEdge().
588}
589
590void OpaqueBrowserFrameView::PaintMaximizedFrameBorder(gfx::Canvas* canvas) {
591 frame_background_->set_frame_color(GetFrameColor());
592 frame_background_->set_theme_image(GetFrameImage());
593 frame_background_->set_theme_overlay_image(GetFrameOverlayImage());
594 frame_background_->set_top_area_height(GetTopAreaHeight());
595
596 // Theme frame must be aligned with the tabstrip as if we were
597 // in restored mode. Note that the top of the tabstrip is
598 // kTabstripTopShadowThickness px off the top of the screen.
599 int theme_background_y = -(GetTabStripInsets(true).top +
600 kTabstripTopShadowThickness);
601 frame_background_->set_theme_background_y(theme_background_y);
602
603 frame_background_->PaintMaximized(canvas, this);
604
605 // TODO(jamescook): Migrate this into FrameBackground.
606 if (!browser_view()->IsToolbarVisible()) {
607 // There's no toolbar to edge the frame border, so we need to draw a bottom
608 // edge. The graphic we use for this has a built in client edge, so we clip
609 // it off the bottom.
610 gfx::ImageSkia* top_center =
611 GetThemeProvider()->GetImageSkiaNamed(IDR_APP_TOP_CENTER);
612 int edge_height = top_center->height() - kClientEdgeThickness;
613 canvas->TileImageInt(*top_center, 0,
614 frame()->client_view()->y() - edge_height, width(), edge_height);
615 }
616}
617
618void OpaqueBrowserFrameView::PaintToolbarBackground(gfx::Canvas* canvas) {
619 gfx::Rect toolbar_bounds(browser_view()->GetToolbarBounds());
620 if (toolbar_bounds.IsEmpty())
621 return;
622 gfx::Point toolbar_origin(toolbar_bounds.origin());
623 ConvertPointToTarget(browser_view(), this, &toolbar_origin);
624 toolbar_bounds.set_origin(toolbar_origin);
625
626 int x = toolbar_bounds.x();
627 int w = toolbar_bounds.width();
628 int y = toolbar_bounds.y();
629 int h = toolbar_bounds.height();
630
631 // Gross hack: We split the toolbar images into two pieces, since sometimes
632 // (popup mode) the toolbar isn't tall enough to show the whole image. The
633 // split happens between the top shadow section and the bottom gradient
634 // section so that we never break the gradient.
635 int split_point = kFrameShadowThickness * 2;
636 int bottom_y = y + split_point;
637 ui::ThemeProvider* tp = GetThemeProvider();
638 gfx::ImageSkia* toolbar_left = tp->GetImageSkiaNamed(
639 IDR_CONTENT_TOP_LEFT_CORNER);
640 int bottom_edge_height = std::min(toolbar_left->height(), h) - split_point;
641
642 // Split our canvas out so we can mask out the corners of the toolbar
643 // without masking out the frame.
644 canvas->SaveLayerAlpha(
645 255, gfx::Rect(x - kClientEdgeThickness, y, w + kClientEdgeThickness * 3,
646 h));
647
648 // Paint the bottom rect.
649 canvas->FillRect(gfx::Rect(x, bottom_y, w, bottom_edge_height),
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000650 tp->GetColor(ThemeProperties::COLOR_TOOLBAR));
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000651
652 // Tile the toolbar image starting at the frame edge on the left and where the
653 // horizontal tabstrip is (or would be) on the top.
654 gfx::ImageSkia* theme_toolbar = tp->GetImageSkiaNamed(IDR_THEME_TOOLBAR);
655 canvas->TileImageInt(*theme_toolbar,
656 x + GetThemeBackgroundXInset(),
657 bottom_y - GetTabStripInsets(false).top,
658 x, bottom_y, w, theme_toolbar->height());
659
660 // Draw rounded corners for the tab.
661 gfx::ImageSkia* toolbar_left_mask =
662 tp->GetImageSkiaNamed(IDR_CONTENT_TOP_LEFT_CORNER_MASK);
663 gfx::ImageSkia* toolbar_right_mask =
664 tp->GetImageSkiaNamed(IDR_CONTENT_TOP_RIGHT_CORNER_MASK);
665
666 // We mask out the corners by using the DestinationIn transfer mode,
667 // which keeps the RGB pixels from the destination and the alpha from
668 // the source.
669 SkPaint paint;
670 paint.setXfermodeMode(SkXfermode::kDstIn_Mode);
671
672 // Mask the left edge.
673 int left_x = x - kContentEdgeShadowThickness;
674 canvas->DrawImageInt(*toolbar_left_mask, 0, 0, toolbar_left_mask->width(),
675 split_point, left_x, y, toolbar_left_mask->width(),
676 split_point, false, paint);
677 canvas->DrawImageInt(*toolbar_left_mask, 0,
678 toolbar_left_mask->height() - bottom_edge_height,
679 toolbar_left_mask->width(), bottom_edge_height, left_x, bottom_y,
680 toolbar_left_mask->width(), bottom_edge_height, false, paint);
681
682 // Mask the right edge.
683 int right_x =
684 x + w - toolbar_right_mask->width() + kContentEdgeShadowThickness;
685 canvas->DrawImageInt(*toolbar_right_mask, 0, 0, toolbar_right_mask->width(),
686 split_point, right_x, y, toolbar_right_mask->width(),
687 split_point, false, paint);
688 canvas->DrawImageInt(*toolbar_right_mask, 0,
689 toolbar_right_mask->height() - bottom_edge_height,
690 toolbar_right_mask->width(), bottom_edge_height, right_x, bottom_y,
691 toolbar_right_mask->width(), bottom_edge_height, false, paint);
692 canvas->Restore();
693
694 canvas->DrawImageInt(*toolbar_left, 0, 0, toolbar_left->width(), split_point,
695 left_x, y, toolbar_left->width(), split_point, false);
696 canvas->DrawImageInt(*toolbar_left, 0,
697 toolbar_left->height() - bottom_edge_height, toolbar_left->width(),
698 bottom_edge_height, left_x, bottom_y, toolbar_left->width(),
699 bottom_edge_height, false);
700
701 gfx::ImageSkia* toolbar_center =
702 tp->GetImageSkiaNamed(IDR_CONTENT_TOP_CENTER);
703 canvas->TileImageInt(*toolbar_center, 0, 0, left_x + toolbar_left->width(),
704 y, right_x - (left_x + toolbar_left->width()),
705 split_point);
706
707 gfx::ImageSkia* toolbar_right = tp->GetImageSkiaNamed(
708 IDR_CONTENT_TOP_RIGHT_CORNER);
709 canvas->DrawImageInt(*toolbar_right, 0, 0, toolbar_right->width(),
710 split_point, right_x, y, toolbar_right->width(), split_point, false);
711 canvas->DrawImageInt(*toolbar_right, 0,
712 toolbar_right->height() - bottom_edge_height, toolbar_right->width(),
713 bottom_edge_height, right_x, bottom_y, toolbar_right->width(),
714 bottom_edge_height, false);
715
716 // Draw the content/toolbar separator.
717 canvas->FillRect(
718 gfx::Rect(x + kClientEdgeThickness,
719 toolbar_bounds.bottom() - kClientEdgeThickness,
720 w - (2 * kClientEdgeThickness),
721 kClientEdgeThickness),
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000722 ThemeProperties::GetDefaultColor(
723 ThemeProperties::COLOR_TOOLBAR_SEPARATOR));
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000724}
725
726void OpaqueBrowserFrameView::PaintRestoredClientEdge(gfx::Canvas* canvas) {
727 ui::ThemeProvider* tp = GetThemeProvider();
728 int client_area_top = frame()->client_view()->y();
729 int image_top = client_area_top;
730
Torne (Richard Coles)58537e22013-09-12 12:10:22 +0100731 gfx::Rect client_area_bounds =
732 layout_->CalculateClientAreaBounds(width(), height());
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000733 SkColor toolbar_color = tp->GetColor(ThemeProperties::COLOR_TOOLBAR);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000734
735 if (browser_view()->IsToolbarVisible()) {
736 // The client edge images always start below the toolbar corner images. The
737 // client edge filled rects start there or at the bottom of the toolbar,
738 // whichever is shorter.
739 gfx::Rect toolbar_bounds(browser_view()->GetToolbarBounds());
740 image_top += toolbar_bounds.y() +
741 tp->GetImageSkiaNamed(IDR_CONTENT_TOP_LEFT_CORNER)->height();
742 client_area_top = std::min(image_top,
743 client_area_top + toolbar_bounds.bottom() - kClientEdgeThickness);
744 } else if (!browser_view()->IsTabStripVisible()) {
745 // The toolbar isn't going to draw a client edge for us, so draw one
746 // ourselves.
747 gfx::ImageSkia* top_left = tp->GetImageSkiaNamed(IDR_APP_TOP_LEFT);
748 gfx::ImageSkia* top_center = tp->GetImageSkiaNamed(IDR_APP_TOP_CENTER);
749 gfx::ImageSkia* top_right = tp->GetImageSkiaNamed(IDR_APP_TOP_RIGHT);
750 int top_edge_y = client_area_top - top_center->height();
751 int height = client_area_top - top_edge_y;
752
753 canvas->DrawImageInt(*top_left, 0, 0, top_left->width(), height,
754 client_area_bounds.x() - top_left->width(), top_edge_y,
755 top_left->width(), height, false);
756 canvas->TileImageInt(*top_center, 0, 0, client_area_bounds.x(), top_edge_y,
757 client_area_bounds.width(), std::min(height, top_center->height()));
758 canvas->DrawImageInt(*top_right, 0, 0, top_right->width(), height,
759 client_area_bounds.right(), top_edge_y,
760 top_right->width(), height, false);
761
762 // Draw the toolbar color across the top edge.
763 canvas->FillRect(gfx::Rect(client_area_bounds.x() - kClientEdgeThickness,
764 client_area_top - kClientEdgeThickness,
765 client_area_bounds.width() + (2 * kClientEdgeThickness),
766 kClientEdgeThickness), toolbar_color);
767 }
768
769 int client_area_bottom =
770 std::max(client_area_top, height() - NonClientBorderThickness());
771 int image_height = client_area_bottom - image_top;
772
773 // Draw the client edge images.
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000774 gfx::ImageSkia* right = tp->GetImageSkiaNamed(IDR_CONTENT_RIGHT_SIDE);
775 canvas->TileImageInt(*right, client_area_bounds.right(), image_top,
776 right->width(), image_height);
777 canvas->DrawImageInt(
778 *tp->GetImageSkiaNamed(IDR_CONTENT_BOTTOM_RIGHT_CORNER),
779 client_area_bounds.right(), client_area_bottom);
780 gfx::ImageSkia* bottom = tp->GetImageSkiaNamed(IDR_CONTENT_BOTTOM_CENTER);
781 canvas->TileImageInt(*bottom, client_area_bounds.x(),
782 client_area_bottom, client_area_bounds.width(),
783 bottom->height());
784 gfx::ImageSkia* bottom_left =
785 tp->GetImageSkiaNamed(IDR_CONTENT_BOTTOM_LEFT_CORNER);
786 canvas->DrawImageInt(*bottom_left,
787 client_area_bounds.x() - bottom_left->width(), client_area_bottom);
788 gfx::ImageSkia* left = tp->GetImageSkiaNamed(IDR_CONTENT_LEFT_SIDE);
789 canvas->TileImageInt(*left, client_area_bounds.x() - left->width(),
790 image_top, left->width(), image_height);
791
792 // Draw the toolbar color so that the client edges show the right color even
793 // where not covered by the toolbar image. NOTE: We do this after drawing the
794 // images because the images are meant to alpha-blend atop the frame whereas
795 // these rects are meant to be fully opaque, without anything overlaid.
796 canvas->FillRect(gfx::Rect(client_area_bounds.x() - kClientEdgeThickness,
797 client_area_top, kClientEdgeThickness,
798 client_area_bottom + kClientEdgeThickness - client_area_top),
799 toolbar_color);
800 canvas->FillRect(gfx::Rect(client_area_bounds.x(), client_area_bottom,
801 client_area_bounds.width(), kClientEdgeThickness),
802 toolbar_color);
803 canvas->FillRect(gfx::Rect(client_area_bounds.right(), client_area_top,
804 kClientEdgeThickness,
805 client_area_bottom + kClientEdgeThickness - client_area_top),
806 toolbar_color);
807}
808
809SkColor OpaqueBrowserFrameView::GetFrameColor() const {
810 bool is_incognito = browser_view()->IsOffTheRecord();
811 if (browser_view()->IsBrowserTypeNormal()) {
812 if (ShouldPaintAsActive()) {
813 return GetThemeProvider()->GetColor(is_incognito ?
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000814 ThemeProperties::COLOR_FRAME_INCOGNITO :
815 ThemeProperties::COLOR_FRAME);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000816 }
817 return GetThemeProvider()->GetColor(is_incognito ?
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000818 ThemeProperties::COLOR_FRAME_INCOGNITO_INACTIVE :
819 ThemeProperties::COLOR_FRAME_INACTIVE);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000820 }
821 // Never theme app and popup windows.
822 if (ShouldPaintAsActive()) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000823 return ThemeProperties::GetDefaultColor(is_incognito ?
824 ThemeProperties::COLOR_FRAME_INCOGNITO : ThemeProperties::COLOR_FRAME);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000825 }
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000826 return ThemeProperties::GetDefaultColor(is_incognito ?
827 ThemeProperties::COLOR_FRAME_INCOGNITO_INACTIVE :
828 ThemeProperties::COLOR_FRAME_INACTIVE);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000829}
830
831gfx::ImageSkia* OpaqueBrowserFrameView::GetFrameImage() const {
832 bool is_incognito = browser_view()->IsOffTheRecord();
833 int resource_id;
834 if (browser_view()->IsBrowserTypeNormal()) {
835 if (ShouldPaintAsActive()) {
836 resource_id = is_incognito ?
837 IDR_THEME_FRAME_INCOGNITO : IDR_THEME_FRAME;
838 } else {
839 resource_id = is_incognito ?
840 IDR_THEME_FRAME_INCOGNITO_INACTIVE : IDR_THEME_FRAME_INACTIVE;
841 }
842 return GetThemeProvider()->GetImageSkiaNamed(resource_id);
843 }
844 // Never theme app and popup windows.
845 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
846 if (ShouldPaintAsActive()) {
847 resource_id = is_incognito ?
848 IDR_THEME_FRAME_INCOGNITO : IDR_FRAME;
849 } else {
850 resource_id = is_incognito ?
851 IDR_THEME_FRAME_INCOGNITO_INACTIVE : IDR_THEME_FRAME_INACTIVE;
852 }
853 return rb.GetImageSkiaNamed(resource_id);
854}
855
856gfx::ImageSkia* OpaqueBrowserFrameView::GetFrameOverlayImage() const {
857 ui::ThemeProvider* tp = GetThemeProvider();
858 if (tp->HasCustomImage(IDR_THEME_FRAME_OVERLAY) &&
859 browser_view()->IsBrowserTypeNormal() &&
860 !browser_view()->IsOffTheRecord()) {
861 return tp->GetImageSkiaNamed(ShouldPaintAsActive() ?
862 IDR_THEME_FRAME_OVERLAY : IDR_THEME_FRAME_OVERLAY_INACTIVE);
863 }
864 return NULL;
865}
866
867int OpaqueBrowserFrameView::GetTopAreaHeight() const {
868 gfx::ImageSkia* frame_image = GetFrameImage();
869 int top_area_height = frame_image->height();
870 if (browser_view()->IsTabStripVisible()) {
871 top_area_height = std::max(top_area_height,
872 GetBoundsForTabStrip(browser_view()->tabstrip()).bottom());
873 }
874 return top_area_height;
875}