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.
5 #include "ui/views/controls/menu/menu_scroll_view_container.h"
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"
20 #include "ui/native_theme/native_theme_aura.h"
23 using ui::NativeTheme;
29 static const int kBorderPaddingDueToRoundedCorners = 1;
31 // MenuScrollButton ------------------------------------------------------------
33 // MenuScrollButton is used for the scroll buttons when not all menu items fit
34 // on screen. MenuScrollButton forwards appropriate events to the
37 class MenuScrollButton : public View {
39 MenuScrollButton(SubmenuView* host, bool is_up)
42 // Make our height the same as that of other MenuItemViews.
43 pref_height_(MenuItemView::pref_menu_height()) {
46 virtual gfx::Size GetPreferredSize() OVERRIDE {
48 host_->GetMenuItem()->GetMenuConfig().scroll_arrow_height * 2 - 1,
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.
57 virtual void OnDragEntered(const ui::DropTargetEvent& event) OVERRIDE {
58 DCHECK(host_->GetMenuItem()->GetMenuController());
59 host_->GetMenuItem()->GetMenuController()->OnDragEnteredScrollButton(
63 virtual int OnDragUpdated(const ui::DropTargetEvent& event) OVERRIDE {
64 return ui::DragDropTypes::DRAG_NONE;
67 virtual void OnDragExited() OVERRIDE {
68 DCHECK(host_->GetMenuItem()->GetMenuController());
69 host_->GetMenuItem()->GetMenuController()->OnDragExitedScrollButton(host_);
72 virtual int OnPerformDrop(const ui::DropTargetEvent& event) OVERRIDE {
73 return ui::DragDropTypes::DRAG_NONE;
76 virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE {
77 const MenuConfig& config = host_->GetMenuItem()->GetMenuConfig();
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);
89 int y = (height() - config.scroll_arrow_height) / 2;
91 int x_left = x - config.scroll_arrow_height;
92 int x_right = x + config.scroll_arrow_height;
97 y = y_bottom + config.scroll_arrow_height;
99 y_bottom = y + config.scroll_arrow_height;
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));
108 paint.setStyle(SkPaint::kFill_Style);
109 paint.setAntiAlias(true);
110 paint.setColor(config.arrow_color);
111 canvas->DrawPath(path, paint);
115 // SubmenuView we were created for.
118 // Direction of the button.
124 DISALLOW_COPY_AND_ASSIGN(MenuScrollButton);
129 // MenuScrollView --------------------------------------------------------------
131 // MenuScrollView is a viewport for the SubmenuView. It's reason to exist is so
132 // that ScrollRectToVisible works.
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.
139 class MenuScrollViewContainer::MenuScrollView : public View {
141 explicit MenuScrollView(View* child) {
145 virtual void ScrollRectToVisible(const gfx::Rect& rect) OVERRIDE {
146 // NOTE: this assumes we only want to scroll in the y direction.
148 // If the rect is already visible, do not scroll.
149 if (GetLocalBounds().Contains(rect))
152 // Scroll just enough so that the rect is visible.
154 if (rect.bottom() > GetLocalBounds().bottom())
155 dy = rect.bottom() - GetLocalBounds().bottom();
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(),
167 // Returns the contents, which is the SubmenuView.
168 View* GetContents() {
173 DISALLOW_COPY_AND_ASSIGN(MenuScrollView);
176 // MenuScrollViewContainer ----------------------------------------------------
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_);
187 scroll_view_ = new MenuScrollView(content_view);
188 AddChildView(scroll_view_);
190 arrow_ = BubbleBorderTypeFromAnchor(
191 content_view_->GetMenuItem()->GetMenuController()->GetAnchorPosition());
193 if (arrow_ != BubbleBorder::NONE)
194 CreateBubbleBorder();
196 CreateDefaultBorder();
199 bool MenuScrollViewContainer::HasBubbleBorder() {
200 return arrow_ != BubbleBorder::NONE;
203 void MenuScrollViewContainer::SetBubbleArrowOffset(int offset) {
204 DCHECK(HasBubbleBorder());
205 bubble_border_->set_arrow_offset(offset);
208 void MenuScrollViewContainer::OnPaintBackground(gfx::Canvas* canvas) {
210 View::OnPaintBackground(canvas);
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);
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();
234 gfx::Size pref = scroll_up_button_->GetPreferredSize();
235 scroll_up_button_->SetBounds(x, y, width, pref.height());
236 content_height -= pref.height();
238 const int scroll_view_y = y + pref.height();
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();
245 scroll_view_->SetBounds(x, scroll_view_y, width, content_height);
246 scroll_view_->Layout();
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());
256 void MenuScrollViewContainer::GetAccessibleState(
257 ui::AccessibleViewState* state) {
258 // Get the name from the submenu view.
259 content_view_->GetAccessibleState(state);
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;
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());
276 void MenuScrollViewContainer::CreateDefaultBorder() {
277 arrow_ = BubbleBorder::NONE;
278 bubble_border_ = NULL;
280 const MenuConfig& menu_config =
281 content_view_->GetMenuItem()->GetMenuConfig();
283 bool use_border = true;
284 int padding = menu_config.corner_radius > 0 ?
285 kBorderPaddingDueToRoundedCorners : 0;
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.
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;
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)));
308 set_border(Border::CreateEmptyBorder(top, left, bottom, right));
312 void MenuScrollViewContainer::CreateBubbleBorder() {
313 bubble_border_ = new BubbleBorder(arrow_,
314 BubbleBorder::SMALL_SHADOW,
316 set_border(bubble_border_);
317 set_background(new BubbleBackground(bubble_border_));
321 MenuScrollViewContainer::BubbleBorderTypeFromAnchor(
322 MenuItemView::AnchorPosition 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;
333 return BubbleBorder::NONE;