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/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"
20 using ui::NativeTheme
;
26 static const int kBorderPaddingDueToRoundedCorners
= 1;
28 // MenuScrollButton ------------------------------------------------------------
30 // MenuScrollButton is used for the scroll buttons when not all menu items fit
31 // on screen. MenuScrollButton forwards appropriate events to the
34 class MenuScrollButton
: public View
{
36 MenuScrollButton(SubmenuView
* host
, bool is_up
)
39 // Make our height the same as that of other MenuItemViews.
40 pref_height_(MenuItemView::pref_menu_height()) {
43 virtual gfx::Size
GetPreferredSize() const OVERRIDE
{
45 host_
->GetMenuItem()->GetMenuConfig().scroll_arrow_height
* 2 - 1,
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.
54 virtual void OnDragEntered(const ui::DropTargetEvent
& event
) OVERRIDE
{
55 DCHECK(host_
->GetMenuItem()->GetMenuController());
56 host_
->GetMenuItem()->GetMenuController()->OnDragEnteredScrollButton(
60 virtual int OnDragUpdated(const ui::DropTargetEvent
& event
) OVERRIDE
{
61 return ui::DragDropTypes::DRAG_NONE
;
64 virtual void OnDragExited() OVERRIDE
{
65 DCHECK(host_
->GetMenuItem()->GetMenuController());
66 host_
->GetMenuItem()->GetMenuController()->OnDragExitedScrollButton(host_
);
69 virtual int OnPerformDrop(const ui::DropTargetEvent
& event
) OVERRIDE
{
70 return ui::DragDropTypes::DRAG_NONE
;
73 virtual void OnPaint(gfx::Canvas
* canvas
) OVERRIDE
{
74 const MenuConfig
& config
= host_
->GetMenuItem()->GetMenuConfig();
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
);
86 int y
= (height() - config
.scroll_arrow_height
) / 2;
88 int x_left
= x
- config
.scroll_arrow_height
;
89 int x_right
= x
+ config
.scroll_arrow_height
;
94 y
= y_bottom
+ config
.scroll_arrow_height
;
96 y_bottom
= y
+ config
.scroll_arrow_height
;
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
));
105 paint
.setStyle(SkPaint::kFill_Style
);
106 paint
.setAntiAlias(true);
107 paint
.setColor(config
.arrow_color
);
108 canvas
->DrawPath(path
, paint
);
112 // SubmenuView we were created for.
115 // Direction of the button.
121 DISALLOW_COPY_AND_ASSIGN(MenuScrollButton
);
126 // MenuScrollView --------------------------------------------------------------
128 // MenuScrollView is a viewport for the SubmenuView. It's reason to exist is so
129 // that ScrollRectToVisible works.
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.
136 class MenuScrollViewContainer::MenuScrollView
: public View
{
138 explicit MenuScrollView(View
* child
) {
142 virtual void ScrollRectToVisible(const gfx::Rect
& rect
) OVERRIDE
{
143 // NOTE: this assumes we only want to scroll in the y direction.
145 // If the rect is already visible, do not scroll.
146 if (GetLocalBounds().Contains(rect
))
149 // Scroll just enough so that the rect is visible.
151 if (rect
.bottom() > GetLocalBounds().bottom())
152 dy
= rect
.bottom() - GetLocalBounds().bottom();
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(),
164 // Returns the contents, which is the SubmenuView.
165 View
* GetContents() {
170 DISALLOW_COPY_AND_ASSIGN(MenuScrollView
);
173 // MenuScrollViewContainer ----------------------------------------------------
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_
);
184 scroll_view_
= new MenuScrollView(content_view
);
185 AddChildView(scroll_view_
);
187 arrow_
= BubbleBorderTypeFromAnchor(
188 content_view_
->GetMenuItem()->GetMenuController()->GetAnchorPosition());
190 if (arrow_
!= BubbleBorder::NONE
)
191 CreateBubbleBorder();
193 CreateDefaultBorder();
196 bool MenuScrollViewContainer::HasBubbleBorder() {
197 return arrow_
!= BubbleBorder::NONE
;
200 void MenuScrollViewContainer::SetBubbleArrowOffset(int offset
) {
201 DCHECK(HasBubbleBorder());
202 bubble_border_
->set_arrow_offset(offset
);
205 void MenuScrollViewContainer::OnPaintBackground(gfx::Canvas
* canvas
) {
207 View::OnPaintBackground(canvas
);
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
);
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();
231 gfx::Size pref
= scroll_up_button_
->GetPreferredSize();
232 scroll_up_button_
->SetBounds(x
, y
, width
, pref
.height());
233 content_height
-= pref
.height();
235 const int scroll_view_y
= y
+ pref
.height();
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();
242 scroll_view_
->SetBounds(x
, scroll_view_y
, width
, content_height
);
243 scroll_view_
->Layout();
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());
253 void MenuScrollViewContainer::GetAccessibleState(
254 ui::AXViewState
* state
) {
255 // Get the name from the submenu view.
256 content_view_
->GetAccessibleState(state
);
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
);
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());
273 void MenuScrollViewContainer::CreateDefaultBorder() {
274 arrow_
= BubbleBorder::NONE
;
275 bubble_border_
= NULL
;
277 const MenuConfig
& menu_config
=
278 content_view_
->GetMenuItem()->GetMenuConfig();
280 bool use_border
= true;
281 int padding
= menu_config
.corner_radius
> 0 ?
282 kBorderPaddingDueToRoundedCorners
: 0;
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.
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
;
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
)));
306 SetBorder(Border::CreateEmptyBorder(top
, left
, bottom
, right
));
310 void MenuScrollViewContainer::CreateBubbleBorder() {
311 bubble_border_
= new BubbleBorder(arrow_
,
312 BubbleBorder::SMALL_SHADOW
,
314 SetBorder(scoped_ptr
<Border
>(bubble_border_
));
315 set_background(new BubbleBackground(bubble_border_
));
318 BubbleBorder::Arrow
MenuScrollViewContainer::BubbleBorderTypeFromAnchor(
319 MenuAnchorPosition 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
;
330 return BubbleBorder::NONE
;