Roll src/third_party/skia d32087a:1052f51
[chromium-blink-merge.git] / ui / views / controls / menu / menu_scroll_view_container.cc
blobf7490d8e1674695fbf9520fb708bd96dd25e30d8
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;
21 namespace views {
23 namespace {
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
31 // MenuController.
33 class MenuScrollButton : public View {
34 public:
35 MenuScrollButton(SubmenuView* host, bool is_up)
36 : host_(host),
37 is_up_(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 {
43 return gfx::Size(
44 host_->GetMenuItem()->GetMenuConfig().scroll_arrow_height * 2 - 1,
45 pref_height_);
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(
56 host_, is_up_);
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();
75 // The background.
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);
83 // Then the arrow.
84 int x = width() / 2;
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;
89 int y_bottom;
91 if (!is_up_) {
92 y_bottom = y;
93 y = y_bottom + config.scroll_arrow_height;
94 } else {
95 y_bottom = y + config.scroll_arrow_height;
97 SkPath path;
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));
103 SkPaint paint;
104 paint.setStyle(SkPaint::kFill_Style);
105 paint.setAntiAlias(true);
106 paint.setColor(config.arrow_color);
107 canvas->DrawPath(path, paint);
110 private:
111 // SubmenuView we were created for.
112 SubmenuView* host_;
114 // Direction of the button.
115 bool is_up_;
117 // Preferred height.
118 int pref_height_;
120 DISALLOW_COPY_AND_ASSIGN(MenuScrollButton);
123 } // namespace
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 {
136 public:
137 explicit MenuScrollView(View* child) {
138 AddChildView(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))
146 return;
148 // Scroll just enough so that the rect is visible.
149 int dy = 0;
150 if (rect.bottom() > GetLocalBounds().bottom())
151 dy = rect.bottom() - GetLocalBounds().bottom();
152 else
153 dy = rect.y();
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(),
160 dy - child->y())));
163 // Returns the contents, which is the SubmenuView.
164 View* GetContents() {
165 return child_at(0);
168 private:
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();
191 else
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) {
205 if (background()) {
206 View::OnPaintBackground(canvas);
207 return;
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();
227 return;
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());
249 return prefsize;
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());
269 Layout();
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
281 : 0;
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)));
295 } else {
296 SetBorder(Border::CreateEmptyBorder(top, left, bottom, right));
300 void MenuScrollViewContainer::CreateBubbleBorder() {
301 bubble_border_ = new BubbleBorder(arrow_,
302 BubbleBorder::SMALL_SHADOW,
303 SK_ColorWHITE);
304 SetBorder(scoped_ptr<Border>(bubble_border_));
305 set_background(new BubbleBackground(bubble_border_));
308 BubbleBorder::Arrow MenuScrollViewContainer::BubbleBorderTypeFromAnchor(
309 MenuAnchorPosition anchor) {
310 switch (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;
319 default:
320 return BubbleBorder::NONE;
324 } // namespace views