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