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