1 // Copyright 2013 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 "ash/shelf/overflow_bubble.h"
9 #include "ash/launcher/launcher_types.h"
10 #include "ash/root_window_controller.h"
11 #include "ash/shelf/shelf_layout_manager.h"
12 #include "ash/shelf/shelf_view.h"
13 #include "ash/shelf/shelf_widget.h"
14 #include "ash/shell.h"
15 #include "ash/shell_window_ids.h"
16 #include "ash/system/tray/system_tray.h"
17 #include "ui/aura/client/screen_position_client.h"
18 #include "ui/aura/root_window.h"
19 #include "ui/events/event.h"
20 #include "ui/gfx/insets.h"
21 #include "ui/gfx/screen.h"
22 #include "ui/views/bubble/bubble_delegate.h"
23 #include "ui/views/bubble/bubble_frame_view.h"
24 #include "ui/views/widget/widget.h"
31 // Max bubble size to screen size ratio.
32 const float kMaxBubbleSizeToScreenRatio = 0.5f;
34 // Inner padding in pixels for shelf view inside bubble.
35 const int kPadding = 2;
37 // Padding space in pixels between ShelfView's left/top edge to its contents.
38 const int kShelfViewLeadingInset = 8;
40 ////////////////////////////////////////////////////////////////////////////////
42 // OverflowBubbleView hosts a ShelfView to display overflown items.
44 class OverflowBubbleView : public views::BubbleDelegateView {
47 virtual ~OverflowBubbleView();
49 void InitOverflowBubble(views::View* anchor, ShelfView* shelf_view);
52 bool IsHorizontalAlignment() const {
53 return GetShelfLayoutManagerForLauncher()->IsHorizontalAlignment();
56 const gfx::Size GetContentsSize() const {
57 return static_cast<views::View*>(shelf_view_)->GetPreferredSize();
60 // Gets arrow location based on shelf alignment.
61 views::BubbleBorder::Arrow GetBubbleArrow() const {
62 return GetShelfLayoutManagerForLauncher()->SelectValueForShelfAlignment(
63 views::BubbleBorder::BOTTOM_LEFT,
64 views::BubbleBorder::LEFT_TOP,
65 views::BubbleBorder::RIGHT_TOP,
66 views::BubbleBorder::TOP_LEFT);
69 void ScrollByXOffset(int x_offset);
70 void ScrollByYOffset(int y_offset);
72 // views::View overrides:
73 virtual gfx::Size GetPreferredSize() OVERRIDE;
74 virtual void Layout() OVERRIDE;
75 virtual void ChildPreferredSizeChanged(views::View* child) OVERRIDE;
76 virtual bool OnMouseWheel(const ui::MouseWheelEvent& event) OVERRIDE;
78 // ui::EventHandler overrides:
79 virtual void OnScrollEvent(ui::ScrollEvent* event) OVERRIDE;
81 // views::BubbleDelegate overrides:
82 virtual gfx::Rect GetBubbleBounds() OVERRIDE;
84 ShelfLayoutManager* GetShelfLayoutManagerForLauncher() const {
85 return ShelfLayoutManager::ForLauncher(
86 GetAnchorView()->GetWidget()->GetNativeView());
89 ShelfView* shelf_view_; // Owned by views hierarchy.
90 gfx::Vector2d scroll_offset_;
92 DISALLOW_COPY_AND_ASSIGN(OverflowBubbleView);
95 OverflowBubbleView::OverflowBubbleView()
99 OverflowBubbleView::~OverflowBubbleView() {
102 void OverflowBubbleView::InitOverflowBubble(views::View* anchor,
103 ShelfView* shelf_view) {
104 // set_anchor_view needs to be called before GetShelfLayoutManagerForLauncher
106 SetAnchorView(anchor);
107 set_arrow(GetBubbleArrow());
108 set_background(NULL);
109 set_color(SkColorSetARGB(kLauncherBackgroundAlpha, 0, 0, 0));
110 set_margins(gfx::Insets(kPadding, kPadding, kPadding, kPadding));
111 set_move_with_anchor(true);
112 // Overflow bubble should not get focus. If it get focus when it is shown,
113 // active state item is changed to running state.
114 set_use_focusless(true);
116 // Makes bubble view has a layer and clip its children layers.
117 SetPaintToLayer(true);
118 SetFillsBoundsOpaquely(false);
119 layer()->SetMasksToBounds(true);
121 shelf_view_ = shelf_view;
122 AddChildView(shelf_view_);
124 set_parent_window(Shell::GetContainer(
125 anchor->GetWidget()->GetNativeWindow()->GetRootWindow(),
126 internal::kShellWindowId_ShelfBubbleContainer));
127 views::BubbleDelegateView::CreateBubble(this);
130 void OverflowBubbleView::ScrollByXOffset(int x_offset) {
131 const gfx::Rect visible_bounds(GetContentsBounds());
132 const gfx::Size contents_size(GetContentsSize());
134 int x = std::min(contents_size.width() - visible_bounds.width(),
135 std::max(0, scroll_offset_.x() + x_offset));
136 scroll_offset_.set_x(x);
139 void OverflowBubbleView::ScrollByYOffset(int y_offset) {
140 const gfx::Rect visible_bounds(GetContentsBounds());
141 const gfx::Size contents_size(GetContentsSize());
143 int y = std::min(contents_size.height() - visible_bounds.height(),
144 std::max(0, scroll_offset_.y() + y_offset));
145 scroll_offset_.set_y(y);
148 gfx::Size OverflowBubbleView::GetPreferredSize() {
149 gfx::Size preferred_size = GetContentsSize();
151 const gfx::Rect monitor_rect = Shell::GetScreen()->GetDisplayNearestPoint(
152 GetAnchorRect().CenterPoint()).work_area();
153 if (!monitor_rect.IsEmpty()) {
154 if (IsHorizontalAlignment()) {
155 preferred_size.set_width(std::min(
156 preferred_size.width(),
157 static_cast<int>(monitor_rect.width() *
158 kMaxBubbleSizeToScreenRatio)));
160 preferred_size.set_height(std::min(
161 preferred_size.height(),
162 static_cast<int>(monitor_rect.height() *
163 kMaxBubbleSizeToScreenRatio)));
167 return preferred_size;
170 void OverflowBubbleView::Layout() {
171 shelf_view_->SetBoundsRect(gfx::Rect(
172 gfx::PointAtOffsetFromOrigin(-scroll_offset_), GetContentsSize()));
175 void OverflowBubbleView::ChildPreferredSizeChanged(views::View* child) {
176 // Ensures |launch_view_| is still visible.
184 bool OverflowBubbleView::OnMouseWheel(const ui::MouseWheelEvent& event) {
185 // The MouseWheelEvent was changed to support both X and Y offsets
186 // recently, but the behavior of this function was retained to continue
187 // using Y offsets only. Might be good to simply scroll in both
188 // directions as in OverflowBubbleView::OnScrollEvent.
189 if (IsHorizontalAlignment())
190 ScrollByXOffset(-event.y_offset());
192 ScrollByYOffset(-event.y_offset());
198 void OverflowBubbleView::OnScrollEvent(ui::ScrollEvent* event) {
199 ScrollByXOffset(-event->x_offset());
200 ScrollByYOffset(-event->y_offset());
205 gfx::Rect OverflowBubbleView::GetBubbleBounds() {
206 views::BubbleBorder* border = GetBubbleFrameView()->bubble_border();
207 gfx::Insets bubble_insets = border->GetInsets();
209 const int border_size =
210 views::BubbleBorder::is_arrow_on_horizontal(arrow()) ?
211 bubble_insets.left() : bubble_insets.top();
212 const int arrow_offset = border_size + kPadding + kShelfViewLeadingInset +
213 ShelfLayoutManager::GetPreferredShelfSize() / 2;
215 const gfx::Size content_size = GetPreferredSize();
216 border->set_arrow_offset(arrow_offset);
218 const gfx::Rect anchor_rect = GetAnchorRect();
219 gfx::Rect bubble_rect = GetBubbleFrameView()->GetUpdatedWindowBounds(
224 gfx::Rect monitor_rect = Shell::GetScreen()->GetDisplayNearestPoint(
225 anchor_rect.CenterPoint()).work_area();
228 if (views::BubbleBorder::is_arrow_on_horizontal(arrow())) {
229 if (bubble_rect.x() < monitor_rect.x())
230 offset = monitor_rect.x() - bubble_rect.x();
231 else if (bubble_rect.right() > monitor_rect.right())
232 offset = monitor_rect.right() - bubble_rect.right();
234 bubble_rect.Offset(offset, 0);
235 border->set_arrow_offset(anchor_rect.CenterPoint().x() - bubble_rect.x());
237 if (bubble_rect.y() < monitor_rect.y())
238 offset = monitor_rect.y() - bubble_rect.y();
239 else if (bubble_rect.bottom() > monitor_rect.bottom())
240 offset = monitor_rect.bottom() - bubble_rect.bottom();
242 bubble_rect.Offset(0, offset);
243 border->set_arrow_offset(anchor_rect.CenterPoint().y() - bubble_rect.y());
246 GetBubbleFrameView()->SchedulePaint();
252 OverflowBubble::OverflowBubble()
258 OverflowBubble::~OverflowBubble() {
262 void OverflowBubble::Show(views::View* anchor, ShelfView* shelf_view) {
265 OverflowBubbleView* bubble_view = new OverflowBubbleView();
266 bubble_view->InitOverflowBubble(anchor, shelf_view);
267 shelf_view_ = shelf_view;
270 Shell::GetInstance()->AddPreTargetHandler(this);
272 bubble_ = bubble_view;
273 RootWindowController::ForWindow(anchor->GetWidget()->GetNativeView())->
274 GetSystemTray()->InitializeBubbleAnimations(bubble_->GetWidget());
275 bubble_->GetWidget()->AddObserver(this);
276 bubble_->GetWidget()->Show();
279 void OverflowBubble::Hide() {
283 Shell::GetInstance()->RemovePreTargetHandler(this);
284 bubble_->GetWidget()->RemoveObserver(this);
285 bubble_->GetWidget()->Close();
291 void OverflowBubble::HideBubbleAndRefreshButton() {
295 views::View* anchor = anchor_;
297 // Update overflow button (|anchor|) status when overflow bubble is hidden
298 // by outside event of overflow button.
299 anchor->SchedulePaint();
302 void OverflowBubble::ProcessPressedEvent(ui::LocatedEvent* event) {
303 aura::Window* target = static_cast<aura::Window*>(event->target());
304 gfx::Point event_location_in_screen = event->location();
305 aura::client::GetScreenPositionClient(target->GetRootWindow())->
306 ConvertPointToScreen(target, &event_location_in_screen);
307 if (!shelf_view_->IsShowingMenu() &&
308 !bubble_->GetBoundsInScreen().Contains(event_location_in_screen) &&
309 !anchor_->GetBoundsInScreen().Contains(event_location_in_screen)) {
310 HideBubbleAndRefreshButton();
314 void OverflowBubble::OnMouseEvent(ui::MouseEvent* event) {
315 if (event->type() == ui::ET_MOUSE_PRESSED)
316 ProcessPressedEvent(event);
319 void OverflowBubble::OnTouchEvent(ui::TouchEvent* event) {
320 if (event->type() == ui::ET_TOUCH_PRESSED)
321 ProcessPressedEvent(event);
324 void OverflowBubble::OnWidgetDestroying(views::Widget* widget) {
325 DCHECK(widget == bubble_->GetWidget());
329 ShelfLayoutManager::ForLauncher(
330 widget->GetNativeView())->shelf_widget()->launcher()->SchedulePaint();
333 } // namespace internal