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. 4 5 #include "ash/launcher/overflow_bubble.h" 6 7 #include <algorithm> 8 9 #include "ash/launcher/launcher_types.h" 10 #include "ash/launcher/launcher_view.h" 11 #include "ash/root_window_controller.h" 12 #include "ash/shelf/shelf_layout_manager.h" 13 #include "ash/shelf/shelf_widget.h" 14 #include "ash/shell.h" 15 #include "ash/system/tray/system_tray.h" 16 #include "ui/aura/root_window.h" 17 #include "ui/gfx/insets.h" 18 #include "ui/gfx/screen.h" 19 #include "ui/views/bubble/bubble_delegate.h" 20 #include "ui/views/bubble/bubble_frame_view.h" 21 #include "ui/views/widget/widget.h" 22 23 namespace ash { 24 namespace internal { 25 26 namespace { 27 28 // Max bubble size to screen size ratio. 29 const float kMaxBubbleSizeToScreenRatio = 0.5f; 30 31 // Inner padding in pixels for launcher view inside bubble. 32 const int kPadding = 2; 33 34 // Padding space in pixels between LauncherView's left/top edge to its contents. 35 const int kLauncherViewLeadingInset = 8; 36 37 //////////////////////////////////////////////////////////////////////////////// 38 // OverflowBubbleView 39 // OverflowBubbleView hosts a LauncherView to display overflown items. 40 41 class OverflowBubbleView : public views::BubbleDelegateView { 42 public: 43 OverflowBubbleView(); 44 virtual ~OverflowBubbleView(); 45 46 void InitOverflowBubble(views::View* anchor, LauncherView* launcher_view); 47 48 private: 49 bool IsHorizontalAlignment() const { 50 return GetShelfLayoutManagerForLauncher()->IsHorizontalAlignment(); 51 } 52 53 const gfx::Size GetContentsSize() const { 54 return static_cast<views::View*>(launcher_view_)->GetPreferredSize(); 55 } 56 57 // Gets arrow location based on shelf alignment. 58 views::BubbleBorder::Arrow GetBubbleArrow() const { 59 return GetShelfLayoutManagerForLauncher()->SelectValueForShelfAlignment( 60 views::BubbleBorder::BOTTOM_LEFT, 61 views::BubbleBorder::LEFT_TOP, 62 views::BubbleBorder::RIGHT_TOP, 63 views::BubbleBorder::TOP_LEFT); 64 } 65 66 void ScrollByXOffset(int x_offset); 67 void ScrollByYOffset(int y_offset); 68 69 // views::View overrides: 70 virtual gfx::Size GetPreferredSize() OVERRIDE; 71 virtual void Layout() OVERRIDE; 72 virtual void ChildPreferredSizeChanged(views::View* child) OVERRIDE; 73 virtual bool OnMouseWheel(const ui::MouseWheelEvent& event) OVERRIDE; 74 75 // ui::EventHandler overrides: 76 virtual void OnScrollEvent(ui::ScrollEvent* event) OVERRIDE; 77 78 // views::BubbleDelegate overrides: 79 virtual gfx::Rect GetBubbleBounds() OVERRIDE; 80 81 ShelfLayoutManager* GetShelfLayoutManagerForLauncher() const { 82 return ShelfLayoutManager::ForLauncher( 83 anchor_view()->GetWidget()->GetNativeView()); 84 } 85 86 LauncherView* launcher_view_; // Owned by views hierarchy. 87 gfx::Vector2d scroll_offset_; 88 89 DISALLOW_COPY_AND_ASSIGN(OverflowBubbleView); 90 }; 91 92 OverflowBubbleView::OverflowBubbleView() 93 : launcher_view_(NULL) { 94 } 95 96 OverflowBubbleView::~OverflowBubbleView() { 97 } 98 99 void OverflowBubbleView::InitOverflowBubble(views::View* anchor, 100 LauncherView* launcher_view) { 101 // set_anchor_view needs to be called before GetShelfLayoutManagerForLauncher 102 // can be called. 103 set_anchor_view(anchor); 104 set_arrow(GetBubbleArrow()); 105 set_background(NULL); 106 set_color(SkColorSetARGB(kLauncherBackgroundAlpha, 0, 0, 0)); 107 set_margins(gfx::Insets(kPadding, kPadding, kPadding, kPadding)); 108 set_move_with_anchor(true); 109 110 // Makes bubble view has a layer and clip its children layers. 111 SetPaintToLayer(true); 112 SetFillsBoundsOpaquely(false); 113 layer()->SetMasksToBounds(true); 114 115 launcher_view_ = launcher_view; 116 AddChildView(launcher_view_); 117 118 views::BubbleDelegateView::CreateBubble(this); 119 } 120 121 void OverflowBubbleView::ScrollByXOffset(int x_offset) { 122 const gfx::Rect visible_bounds(GetContentsBounds()); 123 const gfx::Size contents_size(GetContentsSize()); 124 125 int x = std::min(contents_size.width() - visible_bounds.width(), 126 std::max(0, scroll_offset_.x() + x_offset)); 127 scroll_offset_.set_x(x); 128 } 129 130 void OverflowBubbleView::ScrollByYOffset(int y_offset) { 131 const gfx::Rect visible_bounds(GetContentsBounds()); 132 const gfx::Size contents_size(GetContentsSize()); 133 134 int y = std::min(contents_size.height() - visible_bounds.height(), 135 std::max(0, scroll_offset_.y() + y_offset)); 136 scroll_offset_.set_y(y); 137 } 138 139 gfx::Size OverflowBubbleView::GetPreferredSize() { 140 gfx::Size preferred_size = GetContentsSize(); 141 142 const gfx::Rect monitor_rect = Shell::GetScreen()->GetDisplayNearestPoint( 143 GetAnchorRect().CenterPoint()).work_area(); 144 if (!monitor_rect.IsEmpty()) { 145 if (IsHorizontalAlignment()) { 146 preferred_size.set_width(std::min( 147 preferred_size.width(), 148 static_cast<int>(monitor_rect.width() * 149 kMaxBubbleSizeToScreenRatio))); 150 } else { 151 preferred_size.set_height(std::min( 152 preferred_size.height(), 153 static_cast<int>(monitor_rect.height() * 154 kMaxBubbleSizeToScreenRatio))); 155 } 156 } 157 158 return preferred_size; 159 } 160 161 void OverflowBubbleView::Layout() { 162 launcher_view_->SetBoundsRect(gfx::Rect( 163 gfx::PointAtOffsetFromOrigin(-scroll_offset_), GetContentsSize())); 164 } 165 166 void OverflowBubbleView::ChildPreferredSizeChanged(views::View* child) { 167 // Ensures |launch_view_| is still visible. 168 ScrollByXOffset(0); 169 ScrollByYOffset(0); 170 Layout(); 171 172 SizeToContents(); 173 } 174 175 bool OverflowBubbleView::OnMouseWheel(const ui::MouseWheelEvent& event) { 176 // The MouseWheelEvent was changed to support both X and Y offsets 177 // recently, but the behavior of this function was retained to continue 178 // using Y offsets only. Might be good to simply scroll in both 179 // directions as in OverflowBubbleView::OnScrollEvent. 180 if (IsHorizontalAlignment()) 181 ScrollByXOffset(-event.y_offset()); 182 else 183 ScrollByYOffset(-event.y_offset()); 184 Layout(); 185 186 return true; 187 } 188 189 void OverflowBubbleView::OnScrollEvent(ui::ScrollEvent* event) { 190 ScrollByXOffset(-event->x_offset()); 191 ScrollByYOffset(-event->y_offset()); 192 Layout(); 193 event->SetHandled(); 194 } 195 196 gfx::Rect OverflowBubbleView::GetBubbleBounds() { 197 views::BubbleBorder* border = GetBubbleFrameView()->bubble_border(); 198 gfx::Insets bubble_insets = border->GetInsets(); 199 200 const int border_size = 201 views::BubbleBorder::is_arrow_on_horizontal(arrow()) ? 202 bubble_insets.left() : bubble_insets.top(); 203 const int arrow_offset = border_size + kPadding + kLauncherViewLeadingInset + 204 ShelfLayoutManager::GetPreferredShelfSize() / 2; 205 206 const gfx::Size content_size = GetPreferredSize(); 207 border->set_arrow_offset(arrow_offset); 208 209 const gfx::Rect anchor_rect = GetAnchorRect(); 210 gfx::Rect bubble_rect = GetBubbleFrameView()->GetUpdatedWindowBounds( 211 anchor_rect, 212 content_size, 213 false); 214 215 gfx::Rect monitor_rect = Shell::GetScreen()->GetDisplayNearestPoint( 216 anchor_rect.CenterPoint()).work_area(); 217 218 int offset = 0; 219 if (views::BubbleBorder::is_arrow_on_horizontal(arrow())) { 220 if (bubble_rect.x() < monitor_rect.x()) 221 offset = monitor_rect.x() - bubble_rect.x(); 222 else if (bubble_rect.right() > monitor_rect.right()) 223 offset = monitor_rect.right() - bubble_rect.right(); 224 225 bubble_rect.Offset(offset, 0); 226 border->set_arrow_offset(anchor_rect.CenterPoint().x() - bubble_rect.x()); 227 } else { 228 if (bubble_rect.y() < monitor_rect.y()) 229 offset = monitor_rect.y() - bubble_rect.y(); 230 else if (bubble_rect.bottom() > monitor_rect.bottom()) 231 offset = monitor_rect.bottom() - bubble_rect.bottom(); 232 233 bubble_rect.Offset(0, offset); 234 border->set_arrow_offset(anchor_rect.CenterPoint().y() - bubble_rect.y()); 235 } 236 237 GetBubbleFrameView()->SchedulePaint(); 238 return bubble_rect; 239 } 240 241 } // namespace 242 243 OverflowBubble::OverflowBubble() 244 : bubble_(NULL), 245 launcher_view_(NULL) { 246 } 247 248 OverflowBubble::~OverflowBubble() { 249 Hide(); 250 } 251 252 void OverflowBubble::Show(views::View* anchor, LauncherView* launcher_view) { 253 Hide(); 254 255 OverflowBubbleView* bubble_view = new OverflowBubbleView(); 256 bubble_view->InitOverflowBubble(anchor, launcher_view); 257 launcher_view_ = launcher_view; 258 259 bubble_ = bubble_view; 260 RootWindowController::ForWindow(anchor->GetWidget()->GetNativeView())-> 261 GetSystemTray()->InitializeBubbleAnimations(bubble_->GetWidget()); 262 bubble_->GetWidget()->AddObserver(this); 263 bubble_->GetWidget()->Show(); 264 } 265 266 void OverflowBubble::Hide() { 267 if (!IsShowing()) 268 return; 269 270 bubble_->GetWidget()->RemoveObserver(this); 271 bubble_->GetWidget()->Close(); 272 bubble_ = NULL; 273 launcher_view_ = NULL; 274 } 275 276 void OverflowBubble::OnWidgetDestroying(views::Widget* widget) { 277 DCHECK(widget == bubble_->GetWidget()); 278 bubble_ = NULL; 279 launcher_view_ = NULL; 280 ShelfLayoutManager::ForLauncher( 281 widget->GetNativeView())->shelf_widget()->launcher()->SchedulePaint(); 282 } 283 284 } // namespace internal 285 } // namespace ash 286