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