// Copyright (c) 2010 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 "views/controls/menu/menu_scroll_view_container.h" #if defined(OS_WIN) #include #include #include #endif #include "gfx/canvas_skia.h" #include "gfx/color_utils.h" #include "views/border.h" #include "third_party/skia/include/effects/SkGradientShader.h" #include "views/controls/menu/menu_config.h" #include "views/controls/menu/menu_controller.h" #include "views/controls/menu/menu_item_view.h" #include "views/controls/menu/submenu_view.h" #if defined(OS_WIN) #include "gfx/native_theme_win.h" #endif #if defined(OS_WIN) using gfx::NativeTheme; #endif // Height of the scroll arrow. // This goes up to 4 with large fonts, but this is close enough for now. static const int scroll_arrow_height = 3; namespace views { namespace { // MenuScrollButton ------------------------------------------------------------ // MenuScrollButton is used for the scroll buttons when not all menu items fit // on screen. MenuScrollButton forwards appropriate events to the // MenuController. class MenuScrollButton : public View { public: explicit MenuScrollButton(SubmenuView* host, bool is_up) : host_(host), is_up_(is_up), // Make our height the same as that of other MenuItemViews. pref_height_(MenuItemView::pref_menu_height()) { } virtual gfx::Size GetPreferredSize() { return gfx::Size(MenuConfig::instance().scroll_arrow_height * 2 - 1, pref_height_); } virtual bool CanDrop(const OSExchangeData& data) { DCHECK(host_->GetMenuItem()->GetMenuController()); return true; // Always return true so that drop events are targeted to us. } virtual void OnDragEntered(const DropTargetEvent& event) { DCHECK(host_->GetMenuItem()->GetMenuController()); host_->GetMenuItem()->GetMenuController()->OnDragEnteredScrollButton( host_, is_up_); } virtual int OnDragUpdated(const DropTargetEvent& event) { return DragDropTypes::DRAG_NONE; } virtual void OnDragExited() { DCHECK(host_->GetMenuItem()->GetMenuController()); host_->GetMenuItem()->GetMenuController()->OnDragExitedScrollButton(host_); } virtual int OnPerformDrop(const DropTargetEvent& event) { return DragDropTypes::DRAG_NONE; } virtual void Paint(gfx::Canvas* canvas) { const MenuConfig& config = MenuConfig::instance(); #if defined(OS_WIN) HDC dc = canvas->BeginPlatformPaint(); // The background. RECT item_bounds = { 0, 0, width(), height() }; NativeTheme::instance()->PaintMenuItemBackground( NativeTheme::MENU, dc, MENU_POPUPITEM, MPI_NORMAL, false, &item_bounds); canvas->EndPlatformPaint(); SkColor arrow_color = color_utils::GetSysSkColor(COLOR_MENUTEXT); #else SkColor arrow_color = SK_ColorBLACK; #endif // Then the arrow. int x = width() / 2; int y = (height() - config.scroll_arrow_height) / 2; int delta_y = 1; if (!is_up_) { delta_y = -1; y += config.scroll_arrow_height; } for (int i = 0; i < config.scroll_arrow_height; ++i, --x, y += delta_y) canvas->FillRectInt(arrow_color, x, y, (i * 2) + 1, 1); } private: // SubmenuView we were created for. SubmenuView* host_; // Direction of the button. bool is_up_; // Preferred height. int pref_height_; DISALLOW_COPY_AND_ASSIGN(MenuScrollButton); }; } // namespace // MenuScrollView -------------------------------------------------------------- // MenuScrollView is a viewport for the SubmenuView. It's reason to exist is so // that ScrollRectToVisible works. // // NOTE: It is possible to use ScrollView directly (after making it deal with // null scrollbars), but clicking on a child of ScrollView forces the window to // become active, which we don't want. As we really only need a fraction of // what ScrollView does, so we use a one off variant. class MenuScrollViewContainer::MenuScrollView : public View { public: explicit MenuScrollView(View* child) { AddChildView(child); } virtual void ScrollRectToVisible(const gfx::Rect& rect) { // NOTE: this assumes we only want to scroll in the y direction. // Convert rect.y() to view's coordinates and make sure we don't show past // the bottom of the view. View* child = GetContents(); child->SetY(-std::max(0, std::min( child->GetPreferredSize().height() - this->height(), rect.y() - child->y()))); } // Returns the contents, which is the SubmenuView. View* GetContents() { return GetChildViewAt(0); } private: DISALLOW_COPY_AND_ASSIGN(MenuScrollView); }; // MenuScrollViewContainer ---------------------------------------------------- MenuScrollViewContainer::MenuScrollViewContainer(SubmenuView* content_view) { scroll_up_button_ = new MenuScrollButton(content_view, true); scroll_down_button_ = new MenuScrollButton(content_view, false); AddChildView(scroll_up_button_); AddChildView(scroll_down_button_); scroll_view_ = new MenuScrollView(content_view); AddChildView(scroll_view_); set_border(Border::CreateEmptyBorder( SubmenuView::kSubmenuBorderSize, SubmenuView::kSubmenuBorderSize, SubmenuView::kSubmenuBorderSize, SubmenuView::kSubmenuBorderSize)); } void MenuScrollViewContainer::PaintBackground(gfx::Canvas* canvas) { if (background()) { View::PaintBackground(canvas); return; } #if defined(OS_WIN) HDC dc = canvas->BeginPlatformPaint(); RECT bounds = {0, 0, width(), height()}; NativeTheme::instance()->PaintMenuBackground( NativeTheme::MENU, dc, MENU_POPUPBACKGROUND, 0, &bounds); canvas->EndPlatformPaint(); #elif defined(OS_CHROMEOS) static const SkColor kGradientColors[2] = { SK_ColorWHITE, SkColorSetRGB(0xF0, 0xF0, 0xF0) }; static const SkScalar kGradientPoints[2] = { SkIntToScalar(0), SkIntToScalar(1) }; SkPoint points[2]; points[0].set(SkIntToScalar(0), SkIntToScalar(0)); points[1].set(SkIntToScalar(0), SkIntToScalar(height())); SkShader* shader = SkGradientShader::CreateLinear(points, kGradientColors, kGradientPoints, arraysize(kGradientPoints), SkShader::kRepeat_TileMode); DCHECK(shader); SkPaint paint; paint.setShader(shader); shader->unref(); paint.setStyle(SkPaint::kFill_Style); paint.setXfermodeMode(SkXfermode::kSrc_Mode); canvas->DrawRectInt(0, 0, width(), height(), paint); #else // This is the same as COLOR_TOOLBAR. canvas->AsCanvasSkia()->drawColor(SkColorSetRGB(210, 225, 246), SkXfermode::kSrc_Mode); #endif } void MenuScrollViewContainer::Layout() { gfx::Insets insets = GetInsets(); int x = insets.left(); int y = insets.top(); int width = View::width() - insets.width(); int content_height = height() - insets.height(); if (!scroll_up_button_->IsVisible()) { scroll_view_->SetBounds(x, y, width, content_height); scroll_view_->Layout(); return; } gfx::Size pref = scroll_up_button_->GetPreferredSize(); scroll_up_button_->SetBounds(x, y, width, pref.height()); content_height -= pref.height(); const int scroll_view_y = y + pref.height(); pref = scroll_down_button_->GetPreferredSize(); scroll_down_button_->SetBounds(x, height() - pref.height() - insets.top(), width, pref.height()); content_height -= pref.height(); scroll_view_->SetBounds(x, scroll_view_y, width, content_height); scroll_view_->Layout(); } void MenuScrollViewContainer::DidChangeBounds(const gfx::Rect& previous, const gfx::Rect& current) { gfx::Size content_pref = scroll_view_->GetContents()->GetPreferredSize(); scroll_up_button_->SetVisible(content_pref.height() > height()); scroll_down_button_->SetVisible(content_pref.height() > height()); Layout(); } gfx::Size MenuScrollViewContainer::GetPreferredSize() { gfx::Size prefsize = scroll_view_->GetContents()->GetPreferredSize(); gfx::Insets insets = GetInsets(); prefsize.Enlarge(insets.width(), insets.height()); return prefsize; } AccessibilityTypes::Role MenuScrollViewContainer::GetAccessibleRole() { return AccessibilityTypes::ROLE_MENUBAR; } AccessibilityTypes::State MenuScrollViewContainer::GetAccessibleState() { // Some AT (like NVDA) will not process focus events on menu item children // unless a parent claims to be focused. return AccessibilityTypes::STATE_FOCUSED; } } // namespace views