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