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 DCHECK_GE(contents_size
.width(), visible_bounds
.width());
135 int x
= std::min(contents_size
.width() - visible_bounds
.width(),
136 std::max(0, scroll_offset_
.x() + x_offset
));
137 scroll_offset_
.set_x(x
);
140 void OverflowBubbleView::ScrollByYOffset(int y_offset
) {
141 const gfx::Rect
visible_bounds(GetContentsBounds());
142 const gfx::Size
contents_size(GetContentsSize());
144 DCHECK_GE(contents_size
.width(), visible_bounds
.width());
145 int y
= std::min(contents_size
.height() - visible_bounds
.height(),
146 std::max(0, scroll_offset_
.y() + y_offset
));
147 scroll_offset_
.set_y(y
);
150 gfx::Size
OverflowBubbleView::GetPreferredSize() {
151 gfx::Size preferred_size
= GetContentsSize();
153 const gfx::Rect monitor_rect
= Shell::GetScreen()->GetDisplayNearestPoint(
154 GetAnchorRect().CenterPoint()).work_area();
155 if (!monitor_rect
.IsEmpty()) {
156 if (IsHorizontalAlignment()) {
157 preferred_size
.set_width(std::min(
158 preferred_size
.width(),
159 static_cast<int>(monitor_rect
.width() *
160 kMaxBubbleSizeToScreenRatio
)));
162 preferred_size
.set_height(std::min(
163 preferred_size
.height(),
164 static_cast<int>(monitor_rect
.height() *
165 kMaxBubbleSizeToScreenRatio
)));
169 return preferred_size
;
172 void OverflowBubbleView::Layout() {
173 shelf_view_
->SetBoundsRect(gfx::Rect(
174 gfx::PointAtOffsetFromOrigin(-scroll_offset_
), GetContentsSize()));
177 void OverflowBubbleView::ChildPreferredSizeChanged(views::View
* child
) {
178 // When contents size is changed, ContentsBounds should be updated before
179 // calculating scroll offset.
182 // Ensures |shelf_view_| is still visible.
183 if (IsHorizontalAlignment())
190 bool OverflowBubbleView::OnMouseWheel(const ui::MouseWheelEvent
& event
) {
191 // The MouseWheelEvent was changed to support both X and Y offsets
192 // recently, but the behavior of this function was retained to continue
193 // using Y offsets only. Might be good to simply scroll in both
194 // directions as in OverflowBubbleView::OnScrollEvent.
195 if (IsHorizontalAlignment())
196 ScrollByXOffset(-event
.y_offset());
198 ScrollByYOffset(-event
.y_offset());
204 void OverflowBubbleView::OnScrollEvent(ui::ScrollEvent
* event
) {
205 ScrollByXOffset(-event
->x_offset());
206 ScrollByYOffset(-event
->y_offset());
211 gfx::Rect
OverflowBubbleView::GetBubbleBounds() {
212 views::BubbleBorder
* border
= GetBubbleFrameView()->bubble_border();
213 gfx::Insets bubble_insets
= border
->GetInsets();
215 const int border_size
=
216 views::BubbleBorder::is_arrow_on_horizontal(arrow()) ?
217 bubble_insets
.left() : bubble_insets
.top();
218 const int arrow_offset
= border_size
+ kPadding
+ kShelfViewLeadingInset
+
219 ShelfLayoutManager::GetPreferredShelfSize() / 2;
221 const gfx::Size content_size
= GetPreferredSize();
222 border
->set_arrow_offset(arrow_offset
);
224 const gfx::Rect anchor_rect
= GetAnchorRect();
225 gfx::Rect bubble_rect
= GetBubbleFrameView()->GetUpdatedWindowBounds(
230 gfx::Rect monitor_rect
= Shell::GetScreen()->GetDisplayNearestPoint(
231 anchor_rect
.CenterPoint()).work_area();
234 if (views::BubbleBorder::is_arrow_on_horizontal(arrow())) {
235 if (bubble_rect
.x() < monitor_rect
.x())
236 offset
= monitor_rect
.x() - bubble_rect
.x();
237 else if (bubble_rect
.right() > monitor_rect
.right())
238 offset
= monitor_rect
.right() - bubble_rect
.right();
240 bubble_rect
.Offset(offset
, 0);
241 border
->set_arrow_offset(anchor_rect
.CenterPoint().x() - bubble_rect
.x());
243 if (bubble_rect
.y() < monitor_rect
.y())
244 offset
= monitor_rect
.y() - bubble_rect
.y();
245 else if (bubble_rect
.bottom() > monitor_rect
.bottom())
246 offset
= monitor_rect
.bottom() - bubble_rect
.bottom();
248 bubble_rect
.Offset(0, offset
);
249 border
->set_arrow_offset(anchor_rect
.CenterPoint().y() - bubble_rect
.y());
252 GetBubbleFrameView()->SchedulePaint();
258 OverflowBubble::OverflowBubble()
264 OverflowBubble::~OverflowBubble() {
268 void OverflowBubble::Show(views::View
* anchor
, ShelfView
* shelf_view
) {
271 OverflowBubbleView
* bubble_view
= new OverflowBubbleView();
272 bubble_view
->InitOverflowBubble(anchor
, shelf_view
);
273 shelf_view_
= shelf_view
;
276 Shell::GetInstance()->AddPreTargetHandler(this);
278 bubble_
= bubble_view
;
279 RootWindowController::ForWindow(anchor
->GetWidget()->GetNativeView())->
280 GetSystemTray()->InitializeBubbleAnimations(bubble_
->GetWidget());
281 bubble_
->GetWidget()->AddObserver(this);
282 bubble_
->GetWidget()->Show();
285 void OverflowBubble::Hide() {
289 Shell::GetInstance()->RemovePreTargetHandler(this);
290 bubble_
->GetWidget()->RemoveObserver(this);
291 bubble_
->GetWidget()->Close();
297 void OverflowBubble::HideBubbleAndRefreshButton() {
301 views::View
* anchor
= anchor_
;
303 // Update overflow button (|anchor|) status when overflow bubble is hidden
304 // by outside event of overflow button.
305 anchor
->SchedulePaint();
308 void OverflowBubble::ProcessPressedEvent(ui::LocatedEvent
* event
) {
309 aura::Window
* target
= static_cast<aura::Window
*>(event
->target());
310 gfx::Point event_location_in_screen
= event
->location();
311 aura::client::GetScreenPositionClient(target
->GetRootWindow())->
312 ConvertPointToScreen(target
, &event_location_in_screen
);
313 if (!shelf_view_
->IsShowingMenu() &&
314 !bubble_
->GetBoundsInScreen().Contains(event_location_in_screen
) &&
315 !anchor_
->GetBoundsInScreen().Contains(event_location_in_screen
)) {
316 HideBubbleAndRefreshButton();
320 void OverflowBubble::OnMouseEvent(ui::MouseEvent
* event
) {
321 if (event
->type() == ui::ET_MOUSE_PRESSED
)
322 ProcessPressedEvent(event
);
325 void OverflowBubble::OnTouchEvent(ui::TouchEvent
* event
) {
326 if (event
->type() == ui::ET_TOUCH_PRESSED
)
327 ProcessPressedEvent(event
);
330 void OverflowBubble::OnWidgetDestroying(views::Widget
* widget
) {
331 DCHECK(widget
== bubble_
->GetWidget());
335 ShelfLayoutManager::ForLauncher(
336 widget
->GetNativeView())->shelf_widget()->launcher()->SchedulePaint();
339 } // namespace internal