| OLD | NEW |
| (Empty) |
| 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/common/shelf/overflow_bubble_view.h" | |
| 6 | |
| 7 #include <algorithm> | |
| 8 | |
| 9 #include "ash/common/shelf/shelf_constants.h" | |
| 10 #include "ash/common/shelf/wm_shelf.h" | |
| 11 #include "ash/common/wm_window.h" | |
| 12 #include "ash/public/cpp/shell_window_ids.h" | |
| 13 #include "ash/root_window_controller.h" | |
| 14 #include "ui/display/display.h" | |
| 15 #include "ui/display/screen.h" | |
| 16 #include "ui/events/event.h" | |
| 17 #include "ui/gfx/geometry/insets.h" | |
| 18 #include "ui/views/bubble/bubble_frame_view.h" | |
| 19 #include "ui/views/view.h" | |
| 20 #include "ui/views/widget/widget.h" | |
| 21 | |
| 22 namespace ash { | |
| 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(WmShelf* wm_shelf) | |
| 37 : wm_shelf_(wm_shelf), shelf_view_(nullptr) { | |
| 38 DCHECK(wm_shelf_); | |
| 39 } | |
| 40 | |
| 41 OverflowBubbleView::~OverflowBubbleView() {} | |
| 42 | |
| 43 void OverflowBubbleView::InitOverflowBubble(views::View* anchor, | |
| 44 views::View* shelf_view) { | |
| 45 shelf_view_ = shelf_view; | |
| 46 | |
| 47 SetAnchorView(anchor); | |
| 48 set_arrow(GetBubbleArrow()); | |
| 49 set_mirror_arrow_in_rtl(false); | |
| 50 set_background(nullptr); | |
| 51 set_color(kShelfDefaultBaseColor); | |
| 52 set_margins(gfx::Insets(kPadding, kPadding, kPadding, kPadding)); | |
| 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_can_activate(false); | |
| 56 | |
| 57 // Makes bubble view has a layer and clip its children layers. | |
| 58 SetPaintToLayer(); | |
| 59 layer()->SetFillsBoundsOpaquely(false); | |
| 60 layer()->SetMasksToBounds(true); | |
| 61 | |
| 62 // Calls into OnBeforeBubbleWidgetInit to set the window parent container. | |
| 63 views::BubbleDialogDelegateView::CreateBubble(this); | |
| 64 AddChildView(shelf_view_); | |
| 65 } | |
| 66 | |
| 67 const gfx::Size OverflowBubbleView::GetContentsSize() const { | |
| 68 return shelf_view_->GetPreferredSize(); | |
| 69 } | |
| 70 | |
| 71 // Gets arrow location based on shelf alignment. | |
| 72 views::BubbleBorder::Arrow OverflowBubbleView::GetBubbleArrow() const { | |
| 73 switch (wm_shelf_->GetAlignment()) { | |
| 74 case SHELF_ALIGNMENT_BOTTOM: | |
| 75 case SHELF_ALIGNMENT_BOTTOM_LOCKED: | |
| 76 return views::BubbleBorder::BOTTOM_LEFT; | |
| 77 case SHELF_ALIGNMENT_LEFT: | |
| 78 return views::BubbleBorder::LEFT_TOP; | |
| 79 case SHELF_ALIGNMENT_RIGHT: | |
| 80 return views::BubbleBorder::RIGHT_TOP; | |
| 81 } | |
| 82 NOTREACHED(); | |
| 83 return views::BubbleBorder::NONE; | |
| 84 } | |
| 85 | |
| 86 void OverflowBubbleView::ScrollByXOffset(int x_offset) { | |
| 87 const gfx::Rect visible_bounds(GetContentsBounds()); | |
| 88 const gfx::Size contents_size(GetContentsSize()); | |
| 89 | |
| 90 DCHECK_GE(contents_size.width(), visible_bounds.width()); | |
| 91 int x = std::min(contents_size.width() - visible_bounds.width(), | |
| 92 std::max(0, scroll_offset_.x() + x_offset)); | |
| 93 scroll_offset_.set_x(x); | |
| 94 } | |
| 95 | |
| 96 void OverflowBubbleView::ScrollByYOffset(int y_offset) { | |
| 97 const gfx::Rect visible_bounds(GetContentsBounds()); | |
| 98 const gfx::Size contents_size(GetContentsSize()); | |
| 99 | |
| 100 DCHECK_GE(contents_size.width(), visible_bounds.width()); | |
| 101 int y = std::min(contents_size.height() - visible_bounds.height(), | |
| 102 std::max(0, scroll_offset_.y() + y_offset)); | |
| 103 scroll_offset_.set_y(y); | |
| 104 } | |
| 105 | |
| 106 gfx::Size OverflowBubbleView::GetPreferredSize() const { | |
| 107 gfx::Size preferred_size = GetContentsSize(); | |
| 108 | |
| 109 const gfx::Rect monitor_rect = | |
| 110 display::Screen::GetScreen() | |
| 111 ->GetDisplayNearestPoint(GetAnchorRect().CenterPoint()) | |
| 112 .work_area(); | |
| 113 if (!monitor_rect.IsEmpty()) { | |
| 114 if (wm_shelf_->IsHorizontalAlignment()) { | |
| 115 preferred_size.set_width( | |
| 116 std::min(preferred_size.width(), | |
| 117 static_cast<int>(monitor_rect.width() * | |
| 118 kMaxBubbleSizeToScreenRatio))); | |
| 119 } else { | |
| 120 preferred_size.set_height( | |
| 121 std::min(preferred_size.height(), | |
| 122 static_cast<int>(monitor_rect.height() * | |
| 123 kMaxBubbleSizeToScreenRatio))); | |
| 124 } | |
| 125 } | |
| 126 | |
| 127 return preferred_size; | |
| 128 } | |
| 129 | |
| 130 void OverflowBubbleView::Layout() { | |
| 131 shelf_view_->SetBoundsRect(gfx::Rect( | |
| 132 gfx::PointAtOffsetFromOrigin(-scroll_offset_), GetContentsSize())); | |
| 133 } | |
| 134 | |
| 135 void OverflowBubbleView::ChildPreferredSizeChanged(views::View* child) { | |
| 136 // When contents size is changed, ContentsBounds should be updated before | |
| 137 // calculating scroll offset. | |
| 138 SizeToContents(); | |
| 139 | |
| 140 // Ensures |shelf_view_| is still visible. | |
| 141 if (wm_shelf_->IsHorizontalAlignment()) | |
| 142 ScrollByXOffset(0); | |
| 143 else | |
| 144 ScrollByYOffset(0); | |
| 145 Layout(); | |
| 146 } | |
| 147 | |
| 148 bool OverflowBubbleView::OnMouseWheel(const ui::MouseWheelEvent& event) { | |
| 149 // The MouseWheelEvent was changed to support both X and Y offsets | |
| 150 // recently, but the behavior of this function was retained to continue | |
| 151 // using Y offsets only. Might be good to simply scroll in both | |
| 152 // directions as in OverflowBubbleView::OnScrollEvent. | |
| 153 if (wm_shelf_->IsHorizontalAlignment()) | |
| 154 ScrollByXOffset(-event.y_offset()); | |
| 155 else | |
| 156 ScrollByYOffset(-event.y_offset()); | |
| 157 Layout(); | |
| 158 | |
| 159 return true; | |
| 160 } | |
| 161 | |
| 162 void OverflowBubbleView::OnScrollEvent(ui::ScrollEvent* event) { | |
| 163 ScrollByXOffset(-event->x_offset()); | |
| 164 ScrollByYOffset(-event->y_offset()); | |
| 165 Layout(); | |
| 166 event->SetHandled(); | |
| 167 } | |
| 168 | |
| 169 int OverflowBubbleView::GetDialogButtons() const { | |
| 170 return ui::DIALOG_BUTTON_NONE; | |
| 171 } | |
| 172 | |
| 173 void OverflowBubbleView::OnBeforeBubbleWidgetInit( | |
| 174 views::Widget::InitParams* params, | |
| 175 views::Widget* bubble_widget) const { | |
| 176 // Place the bubble in the same root window as the anchor. | |
| 177 WmWindow::Get(anchor_widget()->GetNativeWindow()) | |
| 178 ->GetRootWindowController() | |
| 179 ->ConfigureWidgetInitParamsForContainer( | |
| 180 bubble_widget, kShellWindowId_ShelfBubbleContainer, params); | |
| 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 = views::BubbleBorder::is_arrow_on_horizontal(arrow()) | |
| 188 ? bubble_insets.left() | |
| 189 : bubble_insets.top(); | |
| 190 const int arrow_offset = border_size + kPadding + kShelfViewLeadingInset + | |
| 191 GetShelfConstant(SHELF_SIZE) / 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, content_size, false); | |
| 199 | |
| 200 gfx::Rect monitor_rect = | |
| 201 display::Screen::GetScreen() | |
| 202 ->GetDisplayNearestPoint(anchor_rect.CenterPoint()) | |
| 203 .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 ash | |
| OLD | NEW |