Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / ui / views / bubble / tray_bubble_view.cc
blob68becc4895e42366996d2f8552055cd313f845b0
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.
5 #include "ui/views/bubble/tray_bubble_view.h"
7 #include <algorithm>
9 #include "third_party/skia/include/core/SkCanvas.h"
10 #include "third_party/skia/include/core/SkColor.h"
11 #include "third_party/skia/include/core/SkPaint.h"
12 #include "third_party/skia/include/core/SkPath.h"
13 #include "third_party/skia/include/effects/SkBlurImageFilter.h"
14 #include "ui/accessibility/ax_view_state.h"
15 #include "ui/aura/window.h"
16 #include "ui/compositor/layer.h"
17 #include "ui/compositor/layer_delegate.h"
18 #include "ui/compositor/paint_recorder.h"
19 #include "ui/events/event.h"
20 #include "ui/gfx/canvas.h"
21 #include "ui/gfx/geometry/insets.h"
22 #include "ui/gfx/geometry/rect.h"
23 #include "ui/gfx/path.h"
24 #include "ui/gfx/skia_util.h"
25 #include "ui/views/bubble/bubble_frame_view.h"
26 #include "ui/views/bubble/bubble_window_targeter.h"
27 #include "ui/views/layout/box_layout.h"
28 #include "ui/views/widget/widget.h"
30 namespace {
32 // Inset the arrow a bit from the edge.
33 const int kArrowMinOffset = 20;
34 const int kBubbleSpacing = 20;
36 // The new theme adjusts the menus / bubbles to be flush with the shelf when
37 // there is no bubble. These are the offsets which need to be applied.
38 const int kArrowOffsetTopBottom = 4;
39 const int kArrowOffsetLeft = 9;
40 const int kArrowOffsetRight = -5;
41 const int kOffsetLeftRightForTopBottomOrientation = 5;
43 // The sampling time for mouse position changes in ms - which is roughly a frame
44 // time.
45 const int kFrameTimeInMS = 30;
46 } // namespace
48 namespace views {
50 namespace internal {
52 // Detects any mouse movement. This is needed to detect mouse movements by the
53 // user over the bubble if the bubble got created underneath the cursor.
54 class MouseMoveDetectorHost : public MouseWatcherHost {
55 public:
56 MouseMoveDetectorHost();
57 ~MouseMoveDetectorHost() override;
59 bool Contains(const gfx::Point& screen_point, MouseEventType type) override;
61 private:
62 DISALLOW_COPY_AND_ASSIGN(MouseMoveDetectorHost);
65 MouseMoveDetectorHost::MouseMoveDetectorHost() {
68 MouseMoveDetectorHost::~MouseMoveDetectorHost() {
71 bool MouseMoveDetectorHost::Contains(const gfx::Point& screen_point,
72 MouseEventType type) {
73 return false;
76 // Custom border for TrayBubbleView. Contains special logic for GetBounds()
77 // to stack bubbles with no arrows correctly. Also calculates the arrow offset.
78 class TrayBubbleBorder : public BubbleBorder {
79 public:
80 TrayBubbleBorder(View* owner,
81 View* anchor,
82 TrayBubbleView::InitParams params)
83 : BubbleBorder(params.arrow, params.shadow, params.arrow_color),
84 owner_(owner),
85 anchor_(anchor),
86 tray_arrow_offset_(params.arrow_offset),
87 first_item_has_no_margin_(params.first_item_has_no_margin) {
88 set_alignment(params.arrow_alignment);
89 set_background_color(params.arrow_color);
90 set_paint_arrow(params.arrow_paint_type);
93 ~TrayBubbleBorder() override {}
95 // Overridden from BubbleBorder.
96 // Sets the bubble on top of the anchor when it has no arrow.
97 gfx::Rect GetBounds(const gfx::Rect& position_relative_to,
98 const gfx::Size& contents_size) const override {
99 if (has_arrow(arrow())) {
100 gfx::Rect rect =
101 BubbleBorder::GetBounds(position_relative_to, contents_size);
102 if (first_item_has_no_margin_) {
103 if (arrow() == BubbleBorder::BOTTOM_RIGHT ||
104 arrow() == BubbleBorder::BOTTOM_LEFT) {
105 rect.set_y(rect.y() + kArrowOffsetTopBottom);
106 int rtl_factor = base::i18n::IsRTL() ? -1 : 1;
107 rect.set_x(rect.x() +
108 rtl_factor * kOffsetLeftRightForTopBottomOrientation);
109 } else if (arrow() == BubbleBorder::LEFT_BOTTOM) {
110 rect.set_x(rect.x() + kArrowOffsetLeft);
111 } else if (arrow() == BubbleBorder::RIGHT_BOTTOM) {
112 rect.set_x(rect.x() + kArrowOffsetRight);
115 return rect;
118 gfx::Size border_size(contents_size);
119 gfx::Insets insets = GetInsets();
120 border_size.Enlarge(insets.width(), insets.height());
121 const int x = position_relative_to.x() +
122 position_relative_to.width() / 2 - border_size.width() / 2;
123 // Position the bubble on top of the anchor.
124 const int y = position_relative_to.y() - border_size.height() +
125 insets.height() - kBubbleSpacing;
126 return gfx::Rect(x, y, border_size.width(), border_size.height());
129 void UpdateArrowOffset() {
130 int arrow_offset = 0;
131 if (arrow() == BubbleBorder::BOTTOM_RIGHT ||
132 arrow() == BubbleBorder::BOTTOM_LEFT) {
133 // Note: tray_arrow_offset_ is relative to the anchor widget.
134 if (tray_arrow_offset_ ==
135 TrayBubbleView::InitParams::kArrowDefaultOffset) {
136 arrow_offset = kArrowMinOffset;
137 } else {
138 const int width = owner_->GetWidget()->GetContentsView()->width();
139 gfx::Point pt(tray_arrow_offset_, 0);
140 View::ConvertPointToScreen(anchor_->GetWidget()->GetRootView(), &pt);
141 View::ConvertPointFromScreen(owner_->GetWidget()->GetRootView(), &pt);
142 arrow_offset = pt.x();
143 if (arrow() == BubbleBorder::BOTTOM_RIGHT)
144 arrow_offset = width - arrow_offset;
145 arrow_offset = std::max(arrow_offset, kArrowMinOffset);
147 } else {
148 if (tray_arrow_offset_ ==
149 TrayBubbleView::InitParams::kArrowDefaultOffset) {
150 arrow_offset = kArrowMinOffset;
151 } else {
152 gfx::Point pt(0, tray_arrow_offset_);
153 View::ConvertPointToScreen(anchor_->GetWidget()->GetRootView(), &pt);
154 View::ConvertPointFromScreen(owner_->GetWidget()->GetRootView(), &pt);
155 arrow_offset = pt.y();
156 arrow_offset = std::max(arrow_offset, kArrowMinOffset);
159 set_arrow_offset(arrow_offset);
162 private:
163 View* owner_;
164 View* anchor_;
165 const int tray_arrow_offset_;
167 // If true the first item should not get any additional spacing against the
168 // anchor (without the bubble tip the bubble should be flush to the shelf).
169 const bool first_item_has_no_margin_;
171 DISALLOW_COPY_AND_ASSIGN(TrayBubbleBorder);
174 // This mask layer clips the bubble's content so that it does not overwrite the
175 // rounded bubble corners.
176 // TODO(miket): This does not work on Windows. Implement layer masking or
177 // alternate solutions if the TrayBubbleView is needed there in the future.
178 class TrayBubbleContentMask : public ui::LayerDelegate {
179 public:
180 explicit TrayBubbleContentMask(int corner_radius);
181 ~TrayBubbleContentMask() override;
183 ui::Layer* layer() { return &layer_; }
185 // Overridden from LayerDelegate.
186 void OnPaintLayer(const ui::PaintContext& context) override;
187 void OnDelegatedFrameDamage(const gfx::Rect& damage_rect_in_dip) override {}
188 void OnDeviceScaleFactorChanged(float device_scale_factor) override;
189 base::Closure PrepareForLayerBoundsChange() override;
191 private:
192 ui::Layer layer_;
193 int corner_radius_;
195 DISALLOW_COPY_AND_ASSIGN(TrayBubbleContentMask);
198 TrayBubbleContentMask::TrayBubbleContentMask(int corner_radius)
199 : layer_(ui::LAYER_TEXTURED),
200 corner_radius_(corner_radius) {
201 layer_.set_delegate(this);
204 TrayBubbleContentMask::~TrayBubbleContentMask() {
205 layer_.set_delegate(NULL);
208 void TrayBubbleContentMask::OnPaintLayer(const ui::PaintContext& context) {
209 ui::PaintRecorder recorder(context, layer()->size());
210 SkPaint paint;
211 paint.setAlpha(255);
212 paint.setStyle(SkPaint::kFill_Style);
213 gfx::Rect rect(layer()->bounds().size());
214 recorder.canvas()->DrawRoundRect(rect, corner_radius_, paint);
217 void TrayBubbleContentMask::OnDeviceScaleFactorChanged(
218 float device_scale_factor) {
219 // Redrawing will take care of scale factor change.
222 base::Closure TrayBubbleContentMask::PrepareForLayerBoundsChange() {
223 return base::Closure();
226 // Custom layout for the bubble-view. Does the default box-layout if there is
227 // enough height. Otherwise, makes sure the bottom rows are visible.
228 class BottomAlignedBoxLayout : public BoxLayout {
229 public:
230 explicit BottomAlignedBoxLayout(TrayBubbleView* bubble_view)
231 : BoxLayout(BoxLayout::kVertical, 0, 0, 0),
232 bubble_view_(bubble_view) {
235 ~BottomAlignedBoxLayout() override {}
237 private:
238 void Layout(View* host) override {
239 if (host->height() >= host->GetPreferredSize().height() ||
240 !bubble_view_->is_gesture_dragging()) {
241 BoxLayout::Layout(host);
242 return;
245 int consumed_height = 0;
246 for (int i = host->child_count() - 1;
247 i >= 0 && consumed_height < host->height(); --i) {
248 View* child = host->child_at(i);
249 if (!child->visible())
250 continue;
251 gfx::Size size = child->GetPreferredSize();
252 child->SetBounds(0, host->height() - consumed_height - size.height(),
253 host->width(), size.height());
254 consumed_height += size.height();
258 TrayBubbleView* bubble_view_;
260 DISALLOW_COPY_AND_ASSIGN(BottomAlignedBoxLayout);
263 } // namespace internal
265 using internal::TrayBubbleBorder;
266 using internal::TrayBubbleContentMask;
267 using internal::BottomAlignedBoxLayout;
269 // static
270 const int TrayBubbleView::InitParams::kArrowDefaultOffset = -1;
272 TrayBubbleView::InitParams::InitParams(AnchorType anchor_type,
273 AnchorAlignment anchor_alignment,
274 int min_width,
275 int max_width)
276 : anchor_type(anchor_type),
277 anchor_alignment(anchor_alignment),
278 min_width(min_width),
279 max_width(max_width),
280 max_height(0),
281 can_activate(false),
282 close_on_deactivate(true),
283 arrow_color(SK_ColorBLACK),
284 first_item_has_no_margin(false),
285 arrow(BubbleBorder::NONE),
286 arrow_offset(kArrowDefaultOffset),
287 arrow_paint_type(BubbleBorder::PAINT_NORMAL),
288 shadow(BubbleBorder::BIG_SHADOW),
289 arrow_alignment(BubbleBorder::ALIGN_EDGE_TO_ANCHOR_EDGE) {
292 // static
293 TrayBubbleView* TrayBubbleView::Create(gfx::NativeView parent_window,
294 View* anchor,
295 Delegate* delegate,
296 InitParams* init_params) {
297 // Set arrow here so that it can be passed to the BubbleView constructor.
298 if (init_params->anchor_type == ANCHOR_TYPE_TRAY) {
299 if (init_params->anchor_alignment == ANCHOR_ALIGNMENT_BOTTOM) {
300 init_params->arrow = base::i18n::IsRTL() ?
301 BubbleBorder::BOTTOM_LEFT : BubbleBorder::BOTTOM_RIGHT;
302 } else if (init_params->anchor_alignment == ANCHOR_ALIGNMENT_TOP) {
303 init_params->arrow = BubbleBorder::TOP_LEFT;
304 } else if (init_params->anchor_alignment == ANCHOR_ALIGNMENT_LEFT) {
305 init_params->arrow = BubbleBorder::LEFT_BOTTOM;
306 } else {
307 init_params->arrow = BubbleBorder::RIGHT_BOTTOM;
309 } else {
310 init_params->arrow = BubbleBorder::NONE;
313 return new TrayBubbleView(parent_window, anchor, delegate, *init_params);
316 TrayBubbleView::TrayBubbleView(gfx::NativeView parent_window,
317 View* anchor,
318 Delegate* delegate,
319 const InitParams& init_params)
320 : BubbleDelegateView(anchor, init_params.arrow),
321 params_(init_params),
322 delegate_(delegate),
323 preferred_width_(init_params.min_width),
324 bubble_border_(NULL),
325 is_gesture_dragging_(false),
326 mouse_actively_entered_(false) {
327 set_parent_window(parent_window);
328 set_notify_enter_exit_on_child(true);
329 set_close_on_deactivate(init_params.close_on_deactivate);
330 set_margins(gfx::Insets());
331 bubble_border_ = new TrayBubbleBorder(this, GetAnchorView(), params_);
332 SetPaintToLayer(true);
333 SetFillsBoundsOpaquely(true);
335 bubble_content_mask_.reset(
336 new TrayBubbleContentMask(bubble_border_->GetBorderCornerRadius()));
339 TrayBubbleView::~TrayBubbleView() {
340 mouse_watcher_.reset();
341 // Inform host items (models) that their views are being destroyed.
342 if (delegate_)
343 delegate_->BubbleViewDestroyed();
346 void TrayBubbleView::InitializeAndShowBubble() {
347 // Must occur after call to BubbleDelegateView::CreateBubble().
348 SetAlignment(params_.arrow_alignment);
349 bubble_border_->UpdateArrowOffset();
351 layer()->parent()->SetMaskLayer(bubble_content_mask_->layer());
353 GetWidget()->Show();
354 GetWidget()->GetNativeWindow()->SetEventTargeter(
355 scoped_ptr<ui::EventTargeter>(new BubbleWindowTargeter(this)));
356 UpdateBubble();
359 void TrayBubbleView::UpdateBubble() {
360 if (GetWidget()) {
361 SizeToContents();
362 bubble_content_mask_->layer()->SetBounds(layer()->bounds());
363 GetWidget()->GetRootView()->SchedulePaint();
367 void TrayBubbleView::SetMaxHeight(int height) {
368 params_.max_height = height;
369 if (GetWidget())
370 SizeToContents();
373 void TrayBubbleView::SetWidth(int width) {
374 width = std::max(std::min(width, params_.max_width), params_.min_width);
375 if (preferred_width_ == width)
376 return;
377 preferred_width_ = width;
378 if (GetWidget())
379 SizeToContents();
382 void TrayBubbleView::SetArrowPaintType(
383 views::BubbleBorder::ArrowPaintType paint_type) {
384 bubble_border_->set_paint_arrow(paint_type);
385 UpdateBubble();
388 gfx::Insets TrayBubbleView::GetBorderInsets() const {
389 return bubble_border_->GetInsets();
392 void TrayBubbleView::Init() {
393 BoxLayout* layout = new BottomAlignedBoxLayout(this);
394 layout->SetDefaultFlex(1);
395 SetLayoutManager(layout);
398 gfx::Rect TrayBubbleView::GetAnchorRect() const {
399 if (!delegate_)
400 return gfx::Rect();
401 return delegate_->GetAnchorRect(anchor_widget(),
402 params_.anchor_type,
403 params_.anchor_alignment);
406 bool TrayBubbleView::CanActivate() const {
407 return params_.can_activate;
410 NonClientFrameView* TrayBubbleView::CreateNonClientFrameView(Widget* widget) {
411 BubbleFrameView* frame = new BubbleFrameView(margins());
412 frame->SetBubbleBorder(scoped_ptr<views::BubbleBorder>(bubble_border_));
413 return frame;
416 bool TrayBubbleView::WidgetHasHitTestMask() const {
417 return true;
420 void TrayBubbleView::GetWidgetHitTestMask(gfx::Path* mask) const {
421 DCHECK(mask);
422 mask->addRect(gfx::RectToSkRect(GetBubbleFrameView()->GetContentsBounds()));
425 gfx::Size TrayBubbleView::GetPreferredSize() const {
426 return gfx::Size(preferred_width_, GetHeightForWidth(preferred_width_));
429 gfx::Size TrayBubbleView::GetMaximumSize() const {
430 gfx::Size size = GetPreferredSize();
431 size.set_width(params_.max_width);
432 return size;
435 int TrayBubbleView::GetHeightForWidth(int width) const {
436 int height = GetInsets().height();
437 width = std::max(width - GetInsets().width(), 0);
438 for (int i = 0; i < child_count(); ++i) {
439 const View* child = child_at(i);
440 if (child->visible())
441 height += child->GetHeightForWidth(width);
444 return (params_.max_height != 0) ?
445 std::min(height, params_.max_height) : height;
448 void TrayBubbleView::OnMouseEntered(const ui::MouseEvent& event) {
449 mouse_watcher_.reset();
450 if (delegate_ && !(event.flags() & ui::EF_IS_SYNTHESIZED)) {
451 // Coming here the user was actively moving the mouse over the bubble and
452 // we inform the delegate that we entered. This will prevent the bubble
453 // to auto close.
454 delegate_->OnMouseEnteredView();
455 mouse_actively_entered_ = true;
456 } else {
457 // Coming here the bubble got shown and the mouse was 'accidentally' over it
458 // which is not a reason to prevent the bubble to auto close. As such we
459 // do not call the delegate, but wait for the first mouse move within the
460 // bubble. The used MouseWatcher will notify use of a movement and call
461 // |MouseMovedOutOfHost|.
462 mouse_watcher_.reset(new MouseWatcher(
463 new views::internal::MouseMoveDetectorHost(),
464 this));
465 // Set the mouse sampling frequency to roughly a frame time so that the user
466 // cannot see a lag.
467 mouse_watcher_->set_notify_on_exit_time(
468 base::TimeDelta::FromMilliseconds(kFrameTimeInMS));
469 mouse_watcher_->Start();
473 void TrayBubbleView::OnMouseExited(const ui::MouseEvent& event) {
474 // If there was a mouse watcher waiting for mouse movements we disable it
475 // immediately since we now leave the bubble.
476 mouse_watcher_.reset();
477 // Do not notify the delegate of an exit if we never told it that we entered.
478 if (delegate_ && mouse_actively_entered_)
479 delegate_->OnMouseExitedView();
482 void TrayBubbleView::GetAccessibleState(ui::AXViewState* state) {
483 if (delegate_ && params_.can_activate) {
484 state->role = ui::AX_ROLE_WINDOW;
485 state->name = delegate_->GetAccessibleNameForBubble();
489 void TrayBubbleView::MouseMovedOutOfHost() {
490 // The mouse was accidentally over the bubble when it opened and the AutoClose
491 // logic was not activated. Now that the user did move the mouse we tell the
492 // delegate to disable AutoClose.
493 delegate_->OnMouseEnteredView();
494 mouse_actively_entered_ = true;
495 mouse_watcher_->Stop();
498 void TrayBubbleView::ChildPreferredSizeChanged(View* child) {
499 SizeToContents();
502 void TrayBubbleView::ViewHierarchyChanged(
503 const ViewHierarchyChangedDetails& details) {
504 if (details.is_add && details.child == this) {
505 details.parent->SetPaintToLayer(true);
506 details.parent->SetFillsBoundsOpaquely(true);
507 details.parent->layer()->SetMasksToBounds(true);
511 } // namespace views