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/controls/menu/menu_scroll_view_container.h" 6 7#include "third_party/skia/include/core/SkPaint.h" 8#include "third_party/skia/include/core/SkPath.h" 9#include "ui/accessibility/ax_view_state.h" 10#include "ui/gfx/canvas.h" 11#include "ui/native_theme/native_theme_aura.h" 12#include "ui/views/border.h" 13#include "ui/views/bubble/bubble_border.h" 14#include "ui/views/controls/menu/menu_config.h" 15#include "ui/views/controls/menu/menu_controller.h" 16#include "ui/views/controls/menu/menu_item_view.h" 17#include "ui/views/controls/menu/submenu_view.h" 18#include "ui/views/round_rect_painter.h" 19 20using ui::NativeTheme; 21 22namespace views { 23 24namespace { 25 26static const int kBorderPaddingDueToRoundedCorners = 1; 27 28// MenuScrollButton ------------------------------------------------------------ 29 30// MenuScrollButton is used for the scroll buttons when not all menu items fit 31// on screen. MenuScrollButton forwards appropriate events to the 32// MenuController. 33 34class MenuScrollButton : public View { 35 public: 36 MenuScrollButton(SubmenuView* host, bool is_up) 37 : host_(host), 38 is_up_(is_up), 39 // Make our height the same as that of other MenuItemViews. 40 pref_height_(MenuItemView::pref_menu_height()) { 41 } 42 43 virtual gfx::Size GetPreferredSize() const OVERRIDE { 44 return gfx::Size( 45 host_->GetMenuItem()->GetMenuConfig().scroll_arrow_height * 2 - 1, 46 pref_height_); 47 } 48 49 virtual bool CanDrop(const OSExchangeData& data) OVERRIDE { 50 DCHECK(host_->GetMenuItem()->GetMenuController()); 51 return true; // Always return true so that drop events are targeted to us. 52 } 53 54 virtual void OnDragEntered(const ui::DropTargetEvent& event) OVERRIDE { 55 DCHECK(host_->GetMenuItem()->GetMenuController()); 56 host_->GetMenuItem()->GetMenuController()->OnDragEnteredScrollButton( 57 host_, is_up_); 58 } 59 60 virtual int OnDragUpdated(const ui::DropTargetEvent& event) OVERRIDE { 61 return ui::DragDropTypes::DRAG_NONE; 62 } 63 64 virtual void OnDragExited() OVERRIDE { 65 DCHECK(host_->GetMenuItem()->GetMenuController()); 66 host_->GetMenuItem()->GetMenuController()->OnDragExitedScrollButton(host_); 67 } 68 69 virtual int OnPerformDrop(const ui::DropTargetEvent& event) OVERRIDE { 70 return ui::DragDropTypes::DRAG_NONE; 71 } 72 73 virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE { 74 const MenuConfig& config = host_->GetMenuItem()->GetMenuConfig(); 75 76 // The background. 77 gfx::Rect item_bounds(0, 0, width(), height()); 78 NativeTheme::ExtraParams extra; 79 extra.menu_item.is_selected = false; 80 GetNativeTheme()->Paint(canvas->sk_canvas(), 81 NativeTheme::kMenuItemBackground, 82 NativeTheme::kNormal, item_bounds, extra); 83 84 // Then the arrow. 85 int x = width() / 2; 86 int y = (height() - config.scroll_arrow_height) / 2; 87 88 int x_left = x - config.scroll_arrow_height; 89 int x_right = x + config.scroll_arrow_height; 90 int y_bottom; 91 92 if (!is_up_) { 93 y_bottom = y; 94 y = y_bottom + config.scroll_arrow_height; 95 } else { 96 y_bottom = y + config.scroll_arrow_height; 97 } 98 SkPath path; 99 path.setFillType(SkPath::kWinding_FillType); 100 path.moveTo(SkIntToScalar(x), SkIntToScalar(y)); 101 path.lineTo(SkIntToScalar(x_left), SkIntToScalar(y_bottom)); 102 path.lineTo(SkIntToScalar(x_right), SkIntToScalar(y_bottom)); 103 path.lineTo(SkIntToScalar(x), SkIntToScalar(y)); 104 SkPaint paint; 105 paint.setStyle(SkPaint::kFill_Style); 106 paint.setAntiAlias(true); 107 paint.setColor(config.arrow_color); 108 canvas->DrawPath(path, paint); 109 } 110 111 private: 112 // SubmenuView we were created for. 113 SubmenuView* host_; 114 115 // Direction of the button. 116 bool is_up_; 117 118 // Preferred height. 119 int pref_height_; 120 121 DISALLOW_COPY_AND_ASSIGN(MenuScrollButton); 122}; 123 124} // namespace 125 126// MenuScrollView -------------------------------------------------------------- 127 128// MenuScrollView is a viewport for the SubmenuView. It's reason to exist is so 129// that ScrollRectToVisible works. 130// 131// NOTE: It is possible to use ScrollView directly (after making it deal with 132// null scrollbars), but clicking on a child of ScrollView forces the window to 133// become active, which we don't want. As we really only need a fraction of 134// what ScrollView does, so we use a one off variant. 135 136class MenuScrollViewContainer::MenuScrollView : public View { 137 public: 138 explicit MenuScrollView(View* child) { 139 AddChildView(child); 140 } 141 142 virtual void ScrollRectToVisible(const gfx::Rect& rect) OVERRIDE { 143 // NOTE: this assumes we only want to scroll in the y direction. 144 145 // If the rect is already visible, do not scroll. 146 if (GetLocalBounds().Contains(rect)) 147 return; 148 149 // Scroll just enough so that the rect is visible. 150 int dy = 0; 151 if (rect.bottom() > GetLocalBounds().bottom()) 152 dy = rect.bottom() - GetLocalBounds().bottom(); 153 else 154 dy = rect.y(); 155 156 // Convert rect.y() to view's coordinates and make sure we don't show past 157 // the bottom of the view. 158 View* child = GetContents(); 159 child->SetY(-std::max(0, std::min( 160 child->GetPreferredSize().height() - this->height(), 161 dy - child->y()))); 162 } 163 164 // Returns the contents, which is the SubmenuView. 165 View* GetContents() { 166 return child_at(0); 167 } 168 169 private: 170 DISALLOW_COPY_AND_ASSIGN(MenuScrollView); 171}; 172 173// MenuScrollViewContainer ---------------------------------------------------- 174 175MenuScrollViewContainer::MenuScrollViewContainer(SubmenuView* content_view) 176 : content_view_(content_view), 177 arrow_(BubbleBorder::NONE), 178 bubble_border_(NULL) { 179 scroll_up_button_ = new MenuScrollButton(content_view, true); 180 scroll_down_button_ = new MenuScrollButton(content_view, false); 181 AddChildView(scroll_up_button_); 182 AddChildView(scroll_down_button_); 183 184 scroll_view_ = new MenuScrollView(content_view); 185 AddChildView(scroll_view_); 186 187 arrow_ = BubbleBorderTypeFromAnchor( 188 content_view_->GetMenuItem()->GetMenuController()->GetAnchorPosition()); 189 190 if (arrow_ != BubbleBorder::NONE) 191 CreateBubbleBorder(); 192 else 193 CreateDefaultBorder(); 194} 195 196bool MenuScrollViewContainer::HasBubbleBorder() { 197 return arrow_ != BubbleBorder::NONE; 198} 199 200void MenuScrollViewContainer::SetBubbleArrowOffset(int offset) { 201 DCHECK(HasBubbleBorder()); 202 bubble_border_->set_arrow_offset(offset); 203} 204 205void MenuScrollViewContainer::OnPaintBackground(gfx::Canvas* canvas) { 206 if (background()) { 207 View::OnPaintBackground(canvas); 208 return; 209 } 210 211 gfx::Rect bounds(0, 0, width(), height()); 212 NativeTheme::ExtraParams extra; 213 const MenuConfig& menu_config = content_view_->GetMenuItem()->GetMenuConfig(); 214 extra.menu_background.corner_radius = menu_config.corner_radius; 215 GetNativeTheme()->Paint(canvas->sk_canvas(), 216 NativeTheme::kMenuPopupBackground, NativeTheme::kNormal, bounds, extra); 217} 218 219void MenuScrollViewContainer::Layout() { 220 gfx::Insets insets = GetInsets(); 221 int x = insets.left(); 222 int y = insets.top(); 223 int width = View::width() - insets.width(); 224 int content_height = height() - insets.height(); 225 if (!scroll_up_button_->visible()) { 226 scroll_view_->SetBounds(x, y, width, content_height); 227 scroll_view_->Layout(); 228 return; 229 } 230 231 gfx::Size pref = scroll_up_button_->GetPreferredSize(); 232 scroll_up_button_->SetBounds(x, y, width, pref.height()); 233 content_height -= pref.height(); 234 235 const int scroll_view_y = y + pref.height(); 236 237 pref = scroll_down_button_->GetPreferredSize(); 238 scroll_down_button_->SetBounds(x, height() - pref.height() - insets.top(), 239 width, pref.height()); 240 content_height -= pref.height(); 241 242 scroll_view_->SetBounds(x, scroll_view_y, width, content_height); 243 scroll_view_->Layout(); 244} 245 246gfx::Size MenuScrollViewContainer::GetPreferredSize() const { 247 gfx::Size prefsize = scroll_view_->GetContents()->GetPreferredSize(); 248 gfx::Insets insets = GetInsets(); 249 prefsize.Enlarge(insets.width(), insets.height()); 250 return prefsize; 251} 252 253void MenuScrollViewContainer::GetAccessibleState( 254 ui::AXViewState* state) { 255 // Get the name from the submenu view. 256 content_view_->GetAccessibleState(state); 257 258 // Now change the role. 259 state->role = ui::AX_ROLE_MENU_BAR; 260 // Some AT (like NVDA) will not process focus events on menu item children 261 // unless a parent claims to be focused. 262 state->AddStateFlag(ui::AX_STATE_FOCUSED); 263} 264 265void MenuScrollViewContainer::OnBoundsChanged( 266 const gfx::Rect& previous_bounds) { 267 gfx::Size content_pref = scroll_view_->GetContents()->GetPreferredSize(); 268 scroll_up_button_->SetVisible(content_pref.height() > height()); 269 scroll_down_button_->SetVisible(content_pref.height() > height()); 270 Layout(); 271} 272 273void MenuScrollViewContainer::CreateDefaultBorder() { 274 arrow_ = BubbleBorder::NONE; 275 bubble_border_ = NULL; 276 277 const MenuConfig& menu_config = 278 content_view_->GetMenuItem()->GetMenuConfig(); 279 280 bool use_border = true; 281 int padding = menu_config.corner_radius > 0 ? 282 kBorderPaddingDueToRoundedCorners : 0; 283 284#if defined(USE_AURA) && !(defined(OS_LINUX) && !defined(OS_CHROMEOS)) 285 if (menu_config.native_theme == ui::NativeThemeAura::instance()) { 286 // In case of NativeThemeAura the border gets drawn with the shadow. 287 // Furthermore no additional padding is wanted. 288 use_border = false; 289 padding = 0; 290 } 291#endif 292 293 int top = menu_config.menu_vertical_border_size + padding; 294 int left = menu_config.menu_horizontal_border_size + padding; 295 int bottom = menu_config.menu_vertical_border_size + padding; 296 int right = menu_config.menu_horizontal_border_size + padding; 297 298 if (use_border) { 299 SetBorder(views::Border::CreateBorderPainter( 300 new views::RoundRectPainter( 301 menu_config.native_theme->GetSystemColor( 302 ui::NativeTheme::kColorId_MenuBorderColor), 303 menu_config.corner_radius), 304 gfx::Insets(top, left, bottom, right))); 305 } else { 306 SetBorder(Border::CreateEmptyBorder(top, left, bottom, right)); 307 } 308} 309 310void MenuScrollViewContainer::CreateBubbleBorder() { 311 bubble_border_ = new BubbleBorder(arrow_, 312 BubbleBorder::SMALL_SHADOW, 313 SK_ColorWHITE); 314 SetBorder(scoped_ptr<Border>(bubble_border_)); 315 set_background(new BubbleBackground(bubble_border_)); 316} 317 318BubbleBorder::Arrow MenuScrollViewContainer::BubbleBorderTypeFromAnchor( 319 MenuAnchorPosition anchor) { 320 switch (anchor) { 321 case MENU_ANCHOR_BUBBLE_LEFT: 322 return BubbleBorder::RIGHT_CENTER; 323 case MENU_ANCHOR_BUBBLE_RIGHT: 324 return BubbleBorder::LEFT_CENTER; 325 case MENU_ANCHOR_BUBBLE_ABOVE: 326 return BubbleBorder::BOTTOM_CENTER; 327 case MENU_ANCHOR_BUBBLE_BELOW: 328 return BubbleBorder::TOP_CENTER; 329 default: 330 return BubbleBorder::NONE; 331 } 332} 333 334} // namespace views 335