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/accessibility/ax_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"
19 using ui::NativeTheme
;
25 static const int kBorderPaddingDueToRoundedCorners
= 1;
27 // MenuScrollButton ------------------------------------------------------------
29 // MenuScrollButton is used for the scroll buttons when not all menu items fit
30 // on screen. MenuScrollButton forwards appropriate events to the
33 class MenuScrollButton
: public View
{
35 MenuScrollButton(SubmenuView
* host
, bool is_up
)
38 // Make our height the same as that of other MenuItemViews.
39 pref_height_(MenuItemView::pref_menu_height()) {
42 gfx::Size
GetPreferredSize() const override
{
44 host_
->GetMenuItem()->GetMenuConfig().scroll_arrow_height
* 2 - 1,
48 bool CanDrop(const OSExchangeData
& data
) override
{
49 DCHECK(host_
->GetMenuItem()->GetMenuController());
50 return true; // Always return true so that drop events are targeted to us.
53 void OnDragEntered(const ui::DropTargetEvent
& event
) override
{
54 DCHECK(host_
->GetMenuItem()->GetMenuController());
55 host_
->GetMenuItem()->GetMenuController()->OnDragEnteredScrollButton(
59 int OnDragUpdated(const ui::DropTargetEvent
& event
) override
{
60 return ui::DragDropTypes::DRAG_NONE
;
63 void OnDragExited() override
{
64 DCHECK(host_
->GetMenuItem()->GetMenuController());
65 host_
->GetMenuItem()->GetMenuController()->OnDragExitedScrollButton(host_
);
68 int OnPerformDrop(const ui::DropTargetEvent
& event
) override
{
69 return ui::DragDropTypes::DRAG_NONE
;
72 void OnPaint(gfx::Canvas
* canvas
) override
{
73 const MenuConfig
& config
= host_
->GetMenuItem()->GetMenuConfig();
76 gfx::Rect
item_bounds(0, 0, width(), height());
77 NativeTheme::ExtraParams extra
;
78 extra
.menu_item
.is_selected
= false;
79 GetNativeTheme()->Paint(canvas
->sk_canvas(),
80 NativeTheme::kMenuItemBackground
,
81 NativeTheme::kNormal
, item_bounds
, extra
);
85 int y
= (height() - config
.scroll_arrow_height
) / 2;
87 int x_left
= x
- config
.scroll_arrow_height
;
88 int x_right
= x
+ config
.scroll_arrow_height
;
93 y
= y_bottom
+ config
.scroll_arrow_height
;
95 y_bottom
= y
+ config
.scroll_arrow_height
;
98 path
.setFillType(SkPath::kWinding_FillType
);
99 path
.moveTo(SkIntToScalar(x
), SkIntToScalar(y
));
100 path
.lineTo(SkIntToScalar(x_left
), SkIntToScalar(y_bottom
));
101 path
.lineTo(SkIntToScalar(x_right
), SkIntToScalar(y_bottom
));
102 path
.lineTo(SkIntToScalar(x
), SkIntToScalar(y
));
104 paint
.setStyle(SkPaint::kFill_Style
);
105 paint
.setAntiAlias(true);
106 paint
.setColor(config
.arrow_color
);
107 canvas
->DrawPath(path
, paint
);
111 // SubmenuView we were created for.
114 // Direction of the button.
120 DISALLOW_COPY_AND_ASSIGN(MenuScrollButton
);
125 // MenuScrollView --------------------------------------------------------------
127 // MenuScrollView is a viewport for the SubmenuView. It's reason to exist is so
128 // that ScrollRectToVisible works.
130 // NOTE: It is possible to use ScrollView directly (after making it deal with
131 // null scrollbars), but clicking on a child of ScrollView forces the window to
132 // become active, which we don't want. As we really only need a fraction of
133 // what ScrollView does, so we use a one off variant.
135 class MenuScrollViewContainer::MenuScrollView
: public View
{
137 explicit MenuScrollView(View
* child
) {
141 void ScrollRectToVisible(const gfx::Rect
& rect
) override
{
142 // NOTE: this assumes we only want to scroll in the y direction.
144 // If the rect is already visible, do not scroll.
145 if (GetLocalBounds().Contains(rect
))
148 // Scroll just enough so that the rect is visible.
150 if (rect
.bottom() > GetLocalBounds().bottom())
151 dy
= rect
.bottom() - GetLocalBounds().bottom();
155 // Convert rect.y() to view's coordinates and make sure we don't show past
156 // the bottom of the view.
157 View
* child
= GetContents();
158 child
->SetY(-std::max(0, std::min(
159 child
->GetPreferredSize().height() - this->height(),
163 // Returns the contents, which is the SubmenuView.
164 View
* GetContents() {
169 DISALLOW_COPY_AND_ASSIGN(MenuScrollView
);
172 // MenuScrollViewContainer ----------------------------------------------------
174 MenuScrollViewContainer::MenuScrollViewContainer(SubmenuView
* content_view
)
175 : content_view_(content_view
),
176 arrow_(BubbleBorder::NONE
),
177 bubble_border_(NULL
) {
178 scroll_up_button_
= new MenuScrollButton(content_view
, true);
179 scroll_down_button_
= new MenuScrollButton(content_view
, false);
180 AddChildView(scroll_up_button_
);
181 AddChildView(scroll_down_button_
);
183 scroll_view_
= new MenuScrollView(content_view
);
184 AddChildView(scroll_view_
);
186 arrow_
= BubbleBorderTypeFromAnchor(
187 content_view_
->GetMenuItem()->GetMenuController()->GetAnchorPosition());
189 if (arrow_
!= BubbleBorder::NONE
)
190 CreateBubbleBorder();
192 CreateDefaultBorder();
195 bool MenuScrollViewContainer::HasBubbleBorder() {
196 return arrow_
!= BubbleBorder::NONE
;
199 void MenuScrollViewContainer::SetBubbleArrowOffset(int offset
) {
200 DCHECK(HasBubbleBorder());
201 bubble_border_
->set_arrow_offset(offset
);
204 void MenuScrollViewContainer::OnPaintBackground(gfx::Canvas
* canvas
) {
206 View::OnPaintBackground(canvas
);
210 gfx::Rect
bounds(0, 0, width(), height());
211 NativeTheme::ExtraParams extra
;
212 const MenuConfig
& menu_config
= content_view_
->GetMenuItem()->GetMenuConfig();
213 extra
.menu_background
.corner_radius
= menu_config
.corner_radius
;
214 GetNativeTheme()->Paint(canvas
->sk_canvas(),
215 NativeTheme::kMenuPopupBackground
, NativeTheme::kNormal
, bounds
, extra
);
218 void MenuScrollViewContainer::Layout() {
219 gfx::Insets insets
= GetInsets();
220 int x
= insets
.left();
221 int y
= insets
.top();
222 int width
= View::width() - insets
.width();
223 int content_height
= height() - insets
.height();
224 if (!scroll_up_button_
->visible()) {
225 scroll_view_
->SetBounds(x
, y
, width
, content_height
);
226 scroll_view_
->Layout();
230 gfx::Size pref
= scroll_up_button_
->GetPreferredSize();
231 scroll_up_button_
->SetBounds(x
, y
, width
, pref
.height());
232 content_height
-= pref
.height();
234 const int scroll_view_y
= y
+ pref
.height();
236 pref
= scroll_down_button_
->GetPreferredSize();
237 scroll_down_button_
->SetBounds(x
, height() - pref
.height() - insets
.top(),
238 width
, pref
.height());
239 content_height
-= pref
.height();
241 scroll_view_
->SetBounds(x
, scroll_view_y
, width
, content_height
);
242 scroll_view_
->Layout();
245 gfx::Size
MenuScrollViewContainer::GetPreferredSize() const {
246 gfx::Size prefsize
= scroll_view_
->GetContents()->GetPreferredSize();
247 gfx::Insets insets
= GetInsets();
248 prefsize
.Enlarge(insets
.width(), insets
.height());
252 void MenuScrollViewContainer::GetAccessibleState(
253 ui::AXViewState
* state
) {
254 // Get the name from the submenu view.
255 content_view_
->GetAccessibleState(state
);
257 // Now change the role.
258 state
->role
= ui::AX_ROLE_MENU_BAR
;
259 // Some AT (like NVDA) will not process focus events on menu item children
260 // unless a parent claims to be focused.
261 state
->AddStateFlag(ui::AX_STATE_FOCUSED
);
264 void MenuScrollViewContainer::OnBoundsChanged(
265 const gfx::Rect
& previous_bounds
) {
266 gfx::Size content_pref
= scroll_view_
->GetContents()->GetPreferredSize();
267 scroll_up_button_
->SetVisible(content_pref
.height() > height());
268 scroll_down_button_
->SetVisible(content_pref
.height() > height());
272 void MenuScrollViewContainer::CreateDefaultBorder() {
273 arrow_
= BubbleBorder::NONE
;
274 bubble_border_
= NULL
;
276 const MenuConfig
& menu_config
=
277 content_view_
->GetMenuItem()->GetMenuConfig();
279 int padding
= menu_config
.use_outer_border
&& menu_config
.corner_radius
> 0
280 ? kBorderPaddingDueToRoundedCorners
283 int top
= menu_config
.menu_vertical_border_size
+ padding
;
284 int left
= menu_config
.menu_horizontal_border_size
+ padding
;
285 int bottom
= menu_config
.menu_vertical_border_size
+ padding
;
286 int right
= menu_config
.menu_horizontal_border_size
+ padding
;
288 if (menu_config
.use_outer_border
) {
289 SetBorder(views::Border::CreateBorderPainter(
290 new views::RoundRectPainter(
291 menu_config
.native_theme
->GetSystemColor(
292 ui::NativeTheme::kColorId_MenuBorderColor
),
293 menu_config
.corner_radius
),
294 gfx::Insets(top
, left
, bottom
, right
)));
296 SetBorder(Border::CreateEmptyBorder(top
, left
, bottom
, right
));
300 void MenuScrollViewContainer::CreateBubbleBorder() {
301 bubble_border_
= new BubbleBorder(arrow_
,
302 BubbleBorder::SMALL_SHADOW
,
304 SetBorder(scoped_ptr
<Border
>(bubble_border_
));
305 set_background(new BubbleBackground(bubble_border_
));
308 BubbleBorder::Arrow
MenuScrollViewContainer::BubbleBorderTypeFromAnchor(
309 MenuAnchorPosition anchor
) {
311 case MENU_ANCHOR_BUBBLE_LEFT
:
312 return BubbleBorder::RIGHT_CENTER
;
313 case MENU_ANCHOR_BUBBLE_RIGHT
:
314 return BubbleBorder::LEFT_CENTER
;
315 case MENU_ANCHOR_BUBBLE_ABOVE
:
316 return BubbleBorder::BOTTOM_CENTER
;
317 case MENU_ANCHOR_BUBBLE_BELOW
:
318 return BubbleBorder::TOP_CENTER
;
320 return BubbleBorder::NONE
;