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. 4 5 #include "ash/shelf/overflow_bubble_view.h" 6 7 #include <algorithm> 8 9 #include "ash/root_window_controller.h" 10 #include "ash/shelf/shelf_layout_manager.h" 11 #include "ash/shelf/shelf_view.h" 12 #include "ash/shell.h" 13 #include "ash/shell_window_ids.h" 14 #include "ui/events/event.h" 15 #include "ui/gfx/insets.h" 16 #include "ui/gfx/screen.h" 17 #include "ui/views/bubble/bubble_frame_view.h" 18 #include "ui/views/widget/widget.h" 19 20 namespace ash { 21 namespace internal { 22 23 namespace { 24 25 // Max bubble size to screen size ratio. 26 const float kMaxBubbleSizeToScreenRatio = 0.5f; 27 28 // Inner padding in pixels for shelf view inside bubble. 29 const int kPadding = 2; 30 31 // Padding space in pixels between ShelfView's left/top edge to its contents. 32 const int kShelfViewLeadingInset = 8; 33 34 } // namespace 35 36 OverflowBubbleView::OverflowBubbleView() 37 : shelf_view_(NULL) { 38 } 39 40 OverflowBubbleView::~OverflowBubbleView() { 41 } 42 43 void OverflowBubbleView::InitOverflowBubble(views::View* anchor, 44 ShelfView* shelf_view) { 45 // set_anchor_view needs to be called before GetShelfLayoutManagerForLauncher 46 // can be called. 47 SetAnchorView(anchor); 48 set_arrow(GetBubbleArrow()); 49 set_background(NULL); 50 set_color(SkColorSetARGB(kLauncherBackgroundAlpha, 0, 0, 0)); 51 set_margins(gfx::Insets(kPadding, kPadding, kPadding, kPadding)); 52 set_move_with_anchor(true); 53 // Overflow bubble should not get focus. If it get focus when it is shown, 54 // active state item is changed to running state. 55 set_use_focusless(true); 56 57 // Makes bubble view has a layer and clip its children layers. 58 SetPaintToLayer(true); 59 SetFillsBoundsOpaquely(false); 60 layer()->SetMasksToBounds(true); 61 62 shelf_view_ = shelf_view; 63 AddChildView(shelf_view_); 64 65 set_parent_window(Shell::GetContainer( 66 anchor->GetWidget()->GetNativeWindow()->GetRootWindow(), 67 internal::kShellWindowId_ShelfBubbleContainer)); 68 views::BubbleDelegateView::CreateBubble(this); 69 } 70 71 bool OverflowBubbleView::IsHorizontalAlignment() const { 72 ShelfLayoutManager* shelf_layout_manager = GetShelfLayoutManagerForLauncher(); 73 return shelf_layout_manager ? 74 shelf_layout_manager->IsHorizontalAlignment() : 75 false; 76 } 77 78 const gfx::Size OverflowBubbleView::GetContentsSize() const { 79 return static_cast<views::View*>(shelf_view_)->GetPreferredSize(); 80 } 81 82 // Gets arrow location based on shelf alignment. 83 views::BubbleBorder::Arrow OverflowBubbleView::GetBubbleArrow() const { 84 ShelfLayoutManager* shelf_layout_manager = GetShelfLayoutManagerForLauncher(); 85 return shelf_layout_manager ? 86 shelf_layout_manager->SelectValueForShelfAlignment( 87 views::BubbleBorder::BOTTOM_LEFT, 88 views::BubbleBorder::LEFT_TOP, 89 views::BubbleBorder::RIGHT_TOP, 90 views::BubbleBorder::TOP_LEFT) : 91 views::BubbleBorder::NONE; 92 } 93 94 void OverflowBubbleView::ScrollByXOffset(int x_offset) { 95 const gfx::Rect visible_bounds(GetContentsBounds()); 96 const gfx::Size contents_size(GetContentsSize()); 97 98 DCHECK_GE(contents_size.width(), visible_bounds.width()); 99 int x = std::min(contents_size.width() - visible_bounds.width(), 100 std::max(0, scroll_offset_.x() + x_offset)); 101 scroll_offset_.set_x(x); 102 } 103 104 void OverflowBubbleView::ScrollByYOffset(int y_offset) { 105 const gfx::Rect visible_bounds(GetContentsBounds()); 106 const gfx::Size contents_size(GetContentsSize()); 107 108 DCHECK_GE(contents_size.width(), visible_bounds.width()); 109 int y = std::min(contents_size.height() - visible_bounds.height(), 110 std::max(0, scroll_offset_.y() + y_offset)); 111 scroll_offset_.set_y(y); 112 } 113 114 gfx::Size OverflowBubbleView::GetPreferredSize() { 115 gfx::Size preferred_size = GetContentsSize(); 116 117 const gfx::Rect monitor_rect = Shell::GetScreen()->GetDisplayNearestPoint( 118 GetAnchorRect().CenterPoint()).work_area(); 119 if (!monitor_rect.IsEmpty()) { 120 if (IsHorizontalAlignment()) { 121 preferred_size.set_width(std::min( 122 preferred_size.width(), 123 static_cast<int>(monitor_rect.width() * 124 kMaxBubbleSizeToScreenRatio))); 125 } else { 126 preferred_size.set_height(std::min( 127 preferred_size.height(), 128 static_cast<int>(monitor_rect.height() * 129 kMaxBubbleSizeToScreenRatio))); 130 } 131 } 132 133 return preferred_size; 134 } 135 136 void OverflowBubbleView::Layout() { 137 shelf_view_->SetBoundsRect(gfx::Rect( 138 gfx::PointAtOffsetFromOrigin(-scroll_offset_), GetContentsSize())); 139 } 140 141 void OverflowBubbleView::ChildPreferredSizeChanged(views::View* child) { 142 // When contents size is changed, ContentsBounds should be updated before 143 // calculating scroll offset. 144 SizeToContents(); 145 146 // Ensures |shelf_view_| is still visible. 147 if (IsHorizontalAlignment()) 148 ScrollByXOffset(0); 149 else 150 ScrollByYOffset(0); 151 Layout(); 152 } 153 154 bool OverflowBubbleView::OnMouseWheel(const ui::MouseWheelEvent& event) { 155 // The MouseWheelEvent was changed to support both X and Y offsets 156 // recently, but the behavior of this function was retained to continue 157 // using Y offsets only. Might be good to simply scroll in both 158 // directions as in OverflowBubbleView::OnScrollEvent. 159 if (IsHorizontalAlignment()) 160 ScrollByXOffset(-event.y_offset()); 161 else 162 ScrollByYOffset(-event.y_offset()); 163 Layout(); 164 165 return true; 166 } 167 168 ShelfLayoutManager* 169 OverflowBubbleView::GetShelfLayoutManagerForLauncher() const { 170 return GetAnchorView() ? 171 ShelfLayoutManager::ForLauncher( 172 GetAnchorView()->GetWidget()->GetNativeView()) : 173 NULL; 174 } 175 176 void OverflowBubbleView::OnScrollEvent(ui::ScrollEvent* event) { 177 ScrollByXOffset(-event->x_offset()); 178 ScrollByYOffset(-event->y_offset()); 179 Layout(); 180 event->SetHandled(); 181 } 182 183 gfx::Rect OverflowBubbleView::GetBubbleBounds() { 184 views::BubbleBorder* border = GetBubbleFrameView()->bubble_border(); 185 gfx::Insets bubble_insets = border->GetInsets(); 186 187 const int border_size = 188 views::BubbleBorder::is_arrow_on_horizontal(arrow()) ? 189 bubble_insets.left() : bubble_insets.top(); 190 const int arrow_offset = border_size + kPadding + kShelfViewLeadingInset + 191 ShelfLayoutManager::GetPreferredShelfSize() / 2; 192 193 const gfx::Size content_size = GetPreferredSize(); 194 border->set_arrow_offset(arrow_offset); 195 196 const gfx::Rect anchor_rect = GetAnchorRect(); 197 gfx::Rect bubble_rect = GetBubbleFrameView()->GetUpdatedWindowBounds( 198 anchor_rect, 199 content_size, 200 false); 201 202 gfx::Rect monitor_rect = Shell::GetScreen()->GetDisplayNearestPoint( 203 anchor_rect.CenterPoint()).work_area(); 204 205 int offset = 0; 206 if (views::BubbleBorder::is_arrow_on_horizontal(arrow())) { 207 if (bubble_rect.x() < monitor_rect.x()) 208 offset = monitor_rect.x() - bubble_rect.x(); 209 else if (bubble_rect.right() > monitor_rect.right()) 210 offset = monitor_rect.right() - bubble_rect.right(); 211 212 bubble_rect.Offset(offset, 0); 213 border->set_arrow_offset(anchor_rect.CenterPoint().x() - bubble_rect.x()); 214 } else { 215 if (bubble_rect.y() < monitor_rect.y()) 216 offset = monitor_rect.y() - bubble_rect.y(); 217 else if (bubble_rect.bottom() > monitor_rect.bottom()) 218 offset = monitor_rect.bottom() - bubble_rect.bottom(); 219 220 bubble_rect.Offset(0, offset); 221 border->set_arrow_offset(anchor_rect.CenterPoint().y() - bubble_rect.y()); 222 } 223 224 GetBubbleFrameView()->SchedulePaint(); 225 return bubble_rect; 226 } 227 228 } // namespace internal 229 } // namespace ash 230