| OLD | NEW |
| (Empty) |
| 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 | |
| 20 using ui::NativeTheme; | |
| 21 | |
| 22 namespace views { | |
| 23 | |
| 24 namespace { | |
| 25 | |
| 26 static 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 | |
| 34 class 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 | |
| 136 class 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 | |
| 175 MenuScrollViewContainer::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 | |
| 196 bool MenuScrollViewContainer::HasBubbleBorder() { | |
| 197 return arrow_ != BubbleBorder::NONE; | |
| 198 } | |
| 199 | |
| 200 void MenuScrollViewContainer::SetBubbleArrowOffset(int offset) { | |
| 201 DCHECK(HasBubbleBorder()); | |
| 202 bubble_border_->set_arrow_offset(offset); | |
| 203 } | |
| 204 | |
| 205 void 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 | |
| 219 void 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 | |
| 246 gfx::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 | |
| 253 void 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 | |
| 265 void 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 | |
| 273 void 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 | |
| 310 void 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 | |
| 318 BubbleBorder::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 | |
| OLD | NEW |