| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/ui/views/frame/browser_non_client_frame_view.h" |
| |
| #include "base/metrics/histogram_macros.h" |
| #include "build/build_config.h" |
| #include "chrome/app/vector_icons/vector_icons.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/profiles/avatar_menu.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/profiles/profile_attributes_entry.h" |
| #include "chrome/browser/profiles/profile_manager.h" |
| #include "chrome/browser/themes/theme_properties.h" |
| #include "chrome/browser/ui/layout_constants.h" |
| #include "chrome/browser/ui/view_ids.h" |
| #include "chrome/browser/ui/views/frame/browser_view.h" |
| #include "chrome/browser/ui/views/tabs/tab_strip.h" |
| #include "chrome/grit/theme_resources.h" |
| #include "components/signin/core/common/profile_management_switches.h" |
| #include "third_party/skia/include/core/SkColor.h" |
| #include "ui/base/theme_provider.h" |
| #include "ui/gfx/canvas.h" |
| #include "ui/gfx/color_palette.h" |
| #include "ui/gfx/image/image.h" |
| #include "ui/gfx/paint_vector_icon.h" |
| #include "ui/gfx/scoped_canvas.h" |
| #include "ui/views/background.h" |
| |
| #if defined(OS_WIN) |
| #include "chrome/browser/ui/views/frame/taskbar_decorator_win.h" |
| #endif |
| |
| BrowserNonClientFrameView::BrowserNonClientFrameView(BrowserFrame* frame, |
| BrowserView* browser_view) |
| : frame_(frame), |
| browser_view_(browser_view), |
| profile_indicator_icon_(nullptr) { |
| // The profile manager may by null in tests. |
| if (g_browser_process->profile_manager()) { |
| g_browser_process->profile_manager()-> |
| GetProfileAttributesStorage().AddObserver(this); |
| } |
| } |
| |
| BrowserNonClientFrameView::~BrowserNonClientFrameView() { |
| // The profile manager may by null in tests. |
| if (g_browser_process->profile_manager()) { |
| g_browser_process->profile_manager()-> |
| GetProfileAttributesStorage().RemoveObserver(this); |
| } |
| } |
| |
| void BrowserNonClientFrameView::OnBrowserViewInitViewsComplete() { |
| UpdateMinimumSize(); |
| } |
| |
| void BrowserNonClientFrameView::OnMaximizedStateChanged() {} |
| |
| gfx::ImageSkia BrowserNonClientFrameView::GetIncognitoAvatarIcon() const { |
| const SkColor icon_color = color_utils::PickContrastingColor( |
| SK_ColorWHITE, gfx::kChromeIconGrey, GetFrameColor()); |
| return gfx::CreateVectorIcon(kIncognitoIcon, icon_color); |
| } |
| |
| SkColor BrowserNonClientFrameView::GetToolbarTopSeparatorColor() const { |
| const auto color_id = |
| ShouldPaintAsActive() |
| ? ThemeProperties::COLOR_TOOLBAR_TOP_SEPARATOR |
| : ThemeProperties::COLOR_TOOLBAR_TOP_SEPARATOR_INACTIVE; |
| return ShouldPaintAsThemed() ? GetThemeProvider()->GetColor(color_id) |
| : ThemeProperties::GetDefaultColor( |
| color_id, browser_view_->IsIncognito()); |
| } |
| |
| views::View* BrowserNonClientFrameView::GetProfileSwitcherView() const { |
| return nullptr; |
| } |
| |
| void BrowserNonClientFrameView::UpdateClientArea() {} |
| |
| void BrowserNonClientFrameView::UpdateMinimumSize() {} |
| |
| void BrowserNonClientFrameView::ChildPreferredSizeChanged(views::View* child) { |
| if (child == GetProfileSwitcherView()) { |
| // Perform a re-layout if the avatar button has changed, since that can |
| // affect the size of the tabs. |
| frame()->GetRootView()->Layout(); |
| } |
| } |
| |
| void BrowserNonClientFrameView::VisibilityChanged(views::View* starting_from, |
| bool is_visible) { |
| // UpdateTaskbarDecoration() calls DrawTaskbarDecoration(), but that does |
| // nothing if the window is not visible. So even if we've already gotten the |
| // up-to-date decoration, we need to run the update procedure again here when |
| // the window becomes visible. |
| if (is_visible) |
| OnProfileAvatarChanged(base::FilePath()); |
| } |
| |
| bool BrowserNonClientFrameView::ShouldPaintAsThemed() const { |
| return browser_view_->IsBrowserTypeNormal(); |
| } |
| |
| SkColor BrowserNonClientFrameView::GetFrameColor(bool active) const { |
| ThemeProperties::OverwritableByUserThemeProperty color_id = |
| active ? ThemeProperties::COLOR_FRAME |
| : ThemeProperties::COLOR_FRAME_INACTIVE; |
| return ShouldPaintAsThemed() |
| ? GetThemeProviderForProfile()->GetColor(color_id) |
| : ThemeProperties::GetDefaultColor(color_id, |
| browser_view_->IsIncognito()); |
| } |
| |
| gfx::ImageSkia BrowserNonClientFrameView::GetFrameImage(bool active) const { |
| const ui::ThemeProvider* tp = frame_->GetThemeProvider(); |
| int frame_image_id = active ? IDR_THEME_FRAME : IDR_THEME_FRAME_INACTIVE; |
| return ShouldPaintAsThemed() && (tp->HasCustomImage(frame_image_id) || |
| tp->HasCustomImage(IDR_THEME_FRAME)) |
| ? *tp->GetImageSkiaNamed(frame_image_id) |
| : gfx::ImageSkia(); |
| } |
| |
| gfx::ImageSkia BrowserNonClientFrameView::GetFrameOverlayImage( |
| bool active) const { |
| if (browser_view_->IsIncognito() || !browser_view_->IsBrowserTypeNormal()) |
| return gfx::ImageSkia(); |
| |
| const ui::ThemeProvider* tp = frame_->GetThemeProvider(); |
| int frame_overlay_image_id = |
| active ? IDR_THEME_FRAME_OVERLAY : IDR_THEME_FRAME_OVERLAY_INACTIVE; |
| return tp->HasCustomImage(frame_overlay_image_id) |
| ? *tp->GetImageSkiaNamed(frame_overlay_image_id) |
| : gfx::ImageSkia(); |
| } |
| |
| SkColor BrowserNonClientFrameView::GetFrameColor() const { |
| return GetFrameColor(ShouldPaintAsActive()); |
| } |
| |
| gfx::ImageSkia BrowserNonClientFrameView::GetFrameImage() const { |
| return GetFrameImage(ShouldPaintAsActive()); |
| } |
| |
| gfx::ImageSkia BrowserNonClientFrameView::GetFrameOverlayImage() const { |
| return GetFrameOverlayImage(ShouldPaintAsActive()); |
| } |
| |
| void BrowserNonClientFrameView::UpdateProfileIndicatorIcon() { |
| if (!profile_indicator_icon_) { |
| profile_indicator_icon_ = new ProfileIndicatorIcon(); |
| profile_indicator_icon_->set_id(VIEW_ID_PROFILE_INDICATOR_ICON); |
| AddChildView(profile_indicator_icon_); |
| // Invalidate here because adding a child does not invalidate the layout. |
| InvalidateLayout(); |
| frame_->GetRootView()->Layout(); |
| } |
| |
| gfx::Image icon; |
| const Profile* profile = browser_view()->browser()->profile(); |
| if (profile->GetProfileType() == Profile::INCOGNITO_PROFILE) { |
| icon = gfx::Image(GetIncognitoAvatarIcon()); |
| } else { |
| #if defined(OS_CHROMEOS) |
| AvatarMenu::GetImageForMenuButton(profile->GetPath(), &icon); |
| #else |
| NOTREACHED(); |
| #endif |
| } |
| |
| profile_indicator_icon_->SetIcon(icon); |
| } |
| |
| void BrowserNonClientFrameView::PaintToolbarBackground( |
| gfx::Canvas* canvas) const { |
| gfx::Rect toolbar_bounds(browser_view()->GetToolbarBounds()); |
| if (toolbar_bounds.IsEmpty()) |
| return; |
| gfx::Point toolbar_origin(toolbar_bounds.origin()); |
| ConvertPointToTarget(browser_view(), this, &toolbar_origin); |
| toolbar_bounds.set_origin(toolbar_origin); |
| |
| const ui::ThemeProvider* tp = GetThemeProvider(); |
| const int x = toolbar_bounds.x(); |
| const int y = toolbar_bounds.y(); |
| const int w = toolbar_bounds.width(); |
| |
| // Background. |
| if (tp->HasCustomImage(IDR_THEME_TOOLBAR)) { |
| canvas->TileImageInt(*tp->GetImageSkiaNamed(IDR_THEME_TOOLBAR), |
| x + GetThemeBackgroundXInset(), |
| y - GetTopInset(false) - GetLayoutInsets(TAB).top(), x, |
| y, w, toolbar_bounds.height()); |
| } else { |
| canvas->FillRect(toolbar_bounds, |
| tp->GetColor(ThemeProperties::COLOR_TOOLBAR)); |
| } |
| |
| // Top stroke. |
| gfx::ScopedCanvas scoped_canvas(canvas); |
| gfx::Rect tabstrip_bounds = |
| GetMirroredRect(GetBoundsForTabStrip(browser_view()->tabstrip())); |
| canvas->ClipRect(tabstrip_bounds, SkClipOp::kDifference); |
| gfx::Rect separator_rect(x, y, w, 0); |
| separator_rect.set_y(tabstrip_bounds.bottom()); |
| BrowserView::Paint1pxHorizontalLine(canvas, GetToolbarTopSeparatorColor(), |
| separator_rect, true); |
| |
| // Toolbar/content separator. |
| BrowserView::Paint1pxHorizontalLine( |
| canvas, tp->GetColor(ThemeProperties::COLOR_TOOLBAR_BOTTOM_SEPARATOR), |
| toolbar_bounds, true); |
| } |
| |
| void BrowserNonClientFrameView::ViewHierarchyChanged( |
| const ViewHierarchyChangedDetails& details) { |
| if (details.is_add && details.child == this) |
| UpdateProfileIcons(); |
| } |
| |
| void BrowserNonClientFrameView::ActivationChanged(bool active) { |
| // On Windows, while deactivating the widget, this is called before the |
| // active HWND has actually been changed. Since we want the avatar state to |
| // reflect that the window is inactive, we force NonClientFrameView to see the |
| // "correct" state as an override. |
| set_active_state_override(&active); |
| UpdateProfileIcons(); |
| set_active_state_override(nullptr); |
| |
| // Changing the activation state may change the toolbar top separator color |
| // that's used as the stroke around tabs/the new tab button. |
| browser_view_->tabstrip()->SchedulePaint(); |
| |
| // Changing the activation state may change the visible frame color. |
| SchedulePaint(); |
| } |
| |
| bool BrowserNonClientFrameView::DoesIntersectRect(const views::View* target, |
| const gfx::Rect& rect) const { |
| DCHECK_EQ(target, this); |
| if (!views::ViewTargeterDelegate::DoesIntersectRect(this, rect)) { |
| // |rect| is outside the frame's bounds. |
| return false; |
| } |
| |
| if (!browser_view()->IsTabStripVisible()) { |
| // Claim |rect| if it is above the top of the topmost client area view. |
| return rect.y() < GetTopInset(false); |
| } |
| |
| // If the rect is outside the bounds of the client area, claim it. |
| gfx::RectF rect_in_client_view_coords_f(rect); |
| View::ConvertRectToTarget(this, frame()->client_view(), |
| &rect_in_client_view_coords_f); |
| gfx::Rect rect_in_client_view_coords = |
| gfx::ToEnclosingRect(rect_in_client_view_coords_f); |
| if (!frame()->client_view()->HitTestRect(rect_in_client_view_coords)) |
| return true; |
| |
| // Otherwise, claim |rect| only if it is above the bottom of the tabstrip in |
| // a non-tab portion. |
| TabStrip* tabstrip = browser_view()->tabstrip(); |
| if (!tabstrip || !browser_view()->IsTabStripVisible()) |
| return false; |
| |
| gfx::RectF rect_in_tabstrip_coords_f(rect); |
| View::ConvertRectToTarget(this, tabstrip, &rect_in_tabstrip_coords_f); |
| gfx::Rect rect_in_tabstrip_coords = |
| gfx::ToEnclosingRect(rect_in_tabstrip_coords_f); |
| if (rect_in_tabstrip_coords.y() >= tabstrip->GetLocalBounds().bottom()) { |
| // |rect| is below the tabstrip. |
| return false; |
| } |
| |
| if (tabstrip->HitTestRect(rect_in_tabstrip_coords)) { |
| // Claim |rect| if it is in a non-tab portion of the tabstrip. |
| return tabstrip->IsRectInWindowCaption(rect_in_tabstrip_coords); |
| } |
| |
| // We claim |rect| because it is above the bottom of the tabstrip, but |
| // not in the tabstrip itself. In particular, the avatar label/button is left |
| // of the tabstrip and the window controls are right of the tabstrip. |
| return true; |
| } |
| |
| void BrowserNonClientFrameView::OnProfileAdded( |
| const base::FilePath& profile_path) { |
| OnProfileAvatarChanged(profile_path); |
| } |
| |
| void BrowserNonClientFrameView::OnProfileWasRemoved( |
| const base::FilePath& profile_path, |
| const base::string16& profile_name) { |
| OnProfileAvatarChanged(profile_path); |
| } |
| |
| void BrowserNonClientFrameView::OnProfileAvatarChanged( |
| const base::FilePath& profile_path) { |
| UpdateTaskbarDecoration(); |
| UpdateProfileIcons(); |
| } |
| |
| void BrowserNonClientFrameView::OnProfileHighResAvatarLoaded( |
| const base::FilePath& profile_path) { |
| UpdateTaskbarDecoration(); |
| } |
| |
| const ui::ThemeProvider* |
| BrowserNonClientFrameView::GetThemeProviderForProfile() const { |
| // Because the frame's accessor reads the ThemeProvider from the profile and |
| // not the widget, it can be called even before we're in a view hierarchy. |
| return frame_->GetThemeProvider(); |
| } |
| |
| void BrowserNonClientFrameView::UpdateTaskbarDecoration() { |
| #if defined(OS_WIN) |
| if (browser_view()->browser()->profile()->IsGuestSession() || |
| // Browser process and profile manager may be null in tests. |
| (g_browser_process && g_browser_process->profile_manager() && |
| g_browser_process->profile_manager() |
| ->GetProfileAttributesStorage() |
| .GetNumberOfProfiles() <= 1)) { |
| chrome::DrawTaskbarDecoration(frame_->GetNativeWindow(), nullptr); |
| return; |
| } |
| |
| // For popups and panels which don't have the avatar button, we still |
| // need to draw the taskbar decoration. Even though we have an icon on the |
| // window's relaunch details, we draw over it because the user may have |
| // pinned the badge-less Chrome shortcut which will cause Windows to ignore |
| // the relaunch details. |
| // TODO(calamity): ideally this should not be necessary but due to issues |
| // with the default shortcut being pinned, we add the runtime badge for |
| // safety. See crbug.com/313800. |
| gfx::Image decoration; |
| AvatarMenu::ImageLoadStatus status = AvatarMenu::GetImageForMenuButton( |
| browser_view()->browser()->profile()->GetPath(), &decoration); |
| |
| UMA_HISTOGRAM_ENUMERATION( |
| "Profile.AvatarLoadStatus", status, |
| static_cast<int>(AvatarMenu::ImageLoadStatus::MAX) + 1); |
| |
| // If the user is using a Gaia picture and the picture is still being loaded, |
| // wait until the load finishes. This taskbar decoration will be triggered |
| // again upon the finish of the picture load. |
| if (status == AvatarMenu::ImageLoadStatus::LOADING || |
| status == AvatarMenu::ImageLoadStatus::PROFILE_DELETED) { |
| return; |
| } |
| |
| chrome::DrawTaskbarDecoration(frame_->GetNativeWindow(), &decoration); |
| #endif |
| } |