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 "chrome/browser/ui/views/status_bubble_views.h"
10 #include "base/i18n/rtl.h"
11 #include "base/message_loop/message_loop.h"
12 #include "base/strings/string_util.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "chrome/browser/themes/theme_properties.h"
15 #include "chrome/browser/ui/elide_url.h"
16 #include "net/base/net_util.h"
17 #include "third_party/skia/include/core/SkPaint.h"
18 #include "third_party/skia/include/core/SkRect.h"
19 #include "ui/base/theme_provider.h"
20 #include "ui/gfx/animation/animation_delegate.h"
21 #include "ui/gfx/animation/linear_animation.h"
22 #include "ui/gfx/canvas.h"
23 #include "ui/gfx/font_list.h"
24 #include "ui/gfx/geometry/point.h"
25 #include "ui/gfx/geometry/rect.h"
26 #include "ui/gfx/screen.h"
27 #include "ui/gfx/skia_util.h"
28 #include "ui/gfx/text_elider.h"
29 #include "ui/gfx/text_utils.h"
30 #include "ui/native_theme/native_theme.h"
31 #include "ui/views/controls/scrollbar/native_scroll_bar.h"
32 #include "ui/views/widget/root_view.h"
33 #include "ui/views/widget/widget.h"
37 #include "ash/wm/window_state.h"
40 // The alpha and color of the bubble's shadow.
41 static const SkColor kShadowColor
= SkColorSetARGB(30, 0, 0, 0);
43 // The roundedness of the edges of our bubble.
44 static const int kBubbleCornerRadius
= 4;
46 // How close the mouse can get to the infobubble before it starts sliding
48 static const int kMousePadding
= 20;
50 // The horizontal offset of the text within the status bubble, not including the
52 static const int kTextPositionX
= 3;
54 // The minimum horizontal space between the (right) end of the text and the edge
55 // of the status bubble, not including the outer shadow ring.
56 static const int kTextHorizPadding
= 1;
58 // Delays before we start hiding or showing the bubble after we receive a
59 // show or hide request.
60 static const int kShowDelay
= 80;
61 static const int kHideDelay
= 250;
63 // How long each fade should last for.
64 static const int kShowFadeDurationMS
= 120;
65 static const int kHideFadeDurationMS
= 200;
66 static const int kFramerate
= 25;
68 // How long each expansion step should take.
69 static const int kMinExpansionStepDurationMS
= 20;
70 static const int kMaxExpansionStepDurationMS
= 150;
73 // StatusBubbleViews::StatusViewAnimation --------------------------------------
74 class StatusBubbleViews::StatusViewAnimation
: public gfx::LinearAnimation
,
75 public gfx::AnimationDelegate
{
77 StatusViewAnimation(StatusView
* status_view
,
80 ~StatusViewAnimation() override
;
82 double GetCurrentOpacity();
85 // gfx::LinearAnimation:
86 void AnimateToState(double state
) override
;
88 // gfx::AnimationDelegate:
89 void AnimationEnded(const Animation
* animation
) override
;
91 StatusView
* status_view_
;
93 // Start and end opacities for the current transition - note that as a
94 // fade-in can easily turn into a fade out, opacity_start_ is sometimes
95 // a value between 0 and 1.
96 double opacity_start_
;
99 DISALLOW_COPY_AND_ASSIGN(StatusViewAnimation
);
103 // StatusBubbleViews::StatusView -----------------------------------------------
105 // StatusView manages the display of the bubble, applying text changes and
106 // fading in or out the bubble as required.
107 class StatusBubbleViews::StatusView
: public views::View
{
109 // The bubble can be in one of many states:
111 BUBBLE_HIDDEN
, // Entirely BUBBLE_HIDDEN.
112 BUBBLE_HIDING_FADE
, // In a fade-out transition.
113 BUBBLE_HIDING_TIMER
, // Waiting before a fade-out.
114 BUBBLE_SHOWING_TIMER
, // Waiting before a fade-in.
115 BUBBLE_SHOWING_FADE
, // In a fade-in transition.
116 BUBBLE_SHOWN
// Fully visible.
126 StatusView(views::Widget
* popup
,
127 ui::ThemeProvider
* theme_provider
);
128 ~StatusView() override
;
130 // Set the bubble text to a certain value, hides the bubble if text is
131 // an empty string. Trigger animation sequence to display if
132 // |should_animate_open|.
133 void SetText(const base::string16
& text
, bool should_animate_open
);
135 BubbleState
state() const { return state_
; }
136 BubbleStyle
style() const { return style_
; }
137 void SetStyle(BubbleStyle style
);
139 // Show the bubble instantly.
142 // Hide the bubble instantly.
145 // Resets any timers we have. Typically called when the user moves a
149 // This call backs the StatusView in order to fade the bubble in and out.
150 void SetOpacity(double opacity
);
152 // Depending on the state of the bubble this will either hide the popup or
154 void OnAnimationEnded();
159 // Manage the timers that control the delay before a fade begins or ends.
160 void StartTimer(base::TimeDelta time
);
163 void RestartTimer(base::TimeDelta delay
);
165 // Manage the fades and starting and stopping the animations correctly.
166 void StartFade(double start
, double end
, int duration
);
171 const char* GetClassName() const override
;
172 void OnPaint(gfx::Canvas
* canvas
) override
;
177 scoped_ptr
<StatusViewAnimation
> animation_
;
179 // Handle to the widget that contains us.
180 views::Widget
* popup_
;
182 // The currently-displayed text.
183 base::string16 text_
;
185 // Holds the theme provider of the frame that created us.
186 ui::ThemeProvider
* theme_service_
;
188 base::WeakPtrFactory
<StatusBubbleViews::StatusView
> timer_factory_
;
190 DISALLOW_COPY_AND_ASSIGN(StatusView
);
193 StatusBubbleViews::StatusView::StatusView(views::Widget
* popup
,
194 ui::ThemeProvider
* theme_provider
)
195 : state_(BUBBLE_HIDDEN
),
196 style_(STYLE_STANDARD
),
197 animation_(new StatusViewAnimation(this, 0, 0)),
199 theme_service_(theme_provider
),
200 timer_factory_(this) {
203 StatusBubbleViews::StatusView::~StatusView() {
208 void StatusBubbleViews::StatusView::SetText(const base::string16
& text
,
209 bool should_animate_open
) {
211 // The string was empty.
214 // We want to show the string.
219 if (should_animate_open
)
224 void StatusBubbleViews::StatusView::Show() {
228 popup_
->ShowInactive();
229 state_
= BUBBLE_SHOWN
;
232 void StatusBubbleViews::StatusView::Hide() {
238 state_
= BUBBLE_HIDDEN
;
241 void StatusBubbleViews::StatusView::StartTimer(base::TimeDelta time
) {
242 if (timer_factory_
.HasWeakPtrs())
243 timer_factory_
.InvalidateWeakPtrs();
245 base::MessageLoop::current()->PostDelayedTask(
247 base::Bind(&StatusBubbleViews::StatusView::OnTimer
,
248 timer_factory_
.GetWeakPtr()),
252 void StatusBubbleViews::StatusView::OnTimer() {
253 if (state_
== BUBBLE_HIDING_TIMER
) {
254 state_
= BUBBLE_HIDING_FADE
;
255 StartFade(1.0, 0.0, kHideFadeDurationMS
);
256 } else if (state_
== BUBBLE_SHOWING_TIMER
) {
257 state_
= BUBBLE_SHOWING_FADE
;
258 StartFade(0.0, 1.0, kShowFadeDurationMS
);
262 void StatusBubbleViews::StatusView::CancelTimer() {
263 if (timer_factory_
.HasWeakPtrs())
264 timer_factory_
.InvalidateWeakPtrs();
267 void StatusBubbleViews::StatusView::RestartTimer(base::TimeDelta delay
) {
272 void StatusBubbleViews::StatusView::ResetTimer() {
273 if (state_
== BUBBLE_SHOWING_TIMER
) {
274 // We hadn't yet begun showing anything when we received a new request
275 // for something to show, so we start from scratch.
276 RestartTimer(base::TimeDelta::FromMilliseconds(kShowDelay
));
280 void StatusBubbleViews::StatusView::StartFade(double start
,
283 animation_
.reset(new StatusViewAnimation(this, start
, end
));
285 // This will also reset the currently-occurring animation.
286 animation_
->SetDuration(duration
);
290 void StatusBubbleViews::StatusView::StartHiding() {
291 if (state_
== BUBBLE_SHOWN
) {
292 state_
= BUBBLE_HIDING_TIMER
;
293 StartTimer(base::TimeDelta::FromMilliseconds(kHideDelay
));
294 } else if (state_
== BUBBLE_SHOWING_TIMER
) {
295 state_
= BUBBLE_HIDDEN
;
298 } else if (state_
== BUBBLE_SHOWING_FADE
) {
299 state_
= BUBBLE_HIDING_FADE
;
300 // Figure out where we are in the current fade.
301 double current_opacity
= animation_
->GetCurrentOpacity();
303 // Start a fade in the opposite direction.
304 StartFade(current_opacity
, 0.0,
305 static_cast<int>(kHideFadeDurationMS
* current_opacity
));
309 void StatusBubbleViews::StatusView::StartShowing() {
310 if (state_
== BUBBLE_HIDDEN
) {
311 popup_
->ShowInactive();
312 state_
= BUBBLE_SHOWING_TIMER
;
313 StartTimer(base::TimeDelta::FromMilliseconds(kShowDelay
));
314 } else if (state_
== BUBBLE_HIDING_TIMER
) {
315 state_
= BUBBLE_SHOWN
;
317 } else if (state_
== BUBBLE_HIDING_FADE
) {
318 // We're partway through a fade.
319 state_
= BUBBLE_SHOWING_FADE
;
321 // Figure out where we are in the current fade.
322 double current_opacity
= animation_
->GetCurrentOpacity();
324 // Start a fade in the opposite direction.
325 StartFade(current_opacity
, 1.0,
326 static_cast<int>(kShowFadeDurationMS
* current_opacity
));
327 } else if (state_
== BUBBLE_SHOWING_TIMER
) {
328 // We hadn't yet begun showing anything when we received a new request
329 // for something to show, so we start from scratch.
334 void StatusBubbleViews::StatusView::SetOpacity(double opacity
) {
335 popup_
->SetOpacity(static_cast<unsigned char>(opacity
* 255));
338 void StatusBubbleViews::StatusView::SetStyle(BubbleStyle style
) {
339 if (style_
!= style
) {
345 void StatusBubbleViews::StatusView::OnAnimationEnded() {
346 if (state_
== BUBBLE_HIDING_FADE
) {
347 state_
= BUBBLE_HIDDEN
;
349 } else if (state_
== BUBBLE_SHOWING_FADE
) {
350 state_
= BUBBLE_SHOWN
;
354 const char* StatusBubbleViews::StatusView::GetClassName() const {
355 return "StatusBubbleViews::StatusView";
358 void StatusBubbleViews::StatusView::OnPaint(gfx::Canvas
* canvas
) {
360 paint
.setStyle(SkPaint::kFill_Style
);
361 paint
.setAntiAlias(true);
362 SkColor toolbar_color
= theme_service_
->GetColor(
363 ThemeProperties::COLOR_TOOLBAR
);
364 paint
.setColor(toolbar_color
);
366 gfx::Rect popup_bounds
= popup_
->GetWindowBoundsInScreen();
368 SkScalar rad
[8] = {};
370 // Top Edges - if the bubble is in its bottom position (sticking downwards),
371 // then we square the top edges. Otherwise, we square the edges based on the
372 // position of the bubble within the window (the bubble is positioned in the
373 // southeast corner in RTL and in the southwest corner in LTR).
374 if (style_
!= STYLE_BOTTOM
) {
375 if (base::i18n::IsRTL() != (style_
== STYLE_STANDARD_RIGHT
)) {
376 // The text is RtL or the bubble is on the right side (but not both).
379 rad
[0] = kBubbleCornerRadius
;
380 rad
[1] = kBubbleCornerRadius
;
383 rad
[2] = kBubbleCornerRadius
;
384 rad
[3] = kBubbleCornerRadius
;
388 // Bottom edges - Keep these squared off if the bubble is in its standard
389 // position (sticking upward).
390 if (style_
!= STYLE_STANDARD
&& style_
!= STYLE_STANDARD_RIGHT
) {
391 // Bottom Right Corner.
392 rad
[4] = kBubbleCornerRadius
;
393 rad
[5] = kBubbleCornerRadius
;
395 // Bottom Left Corner.
396 rad
[6] = kBubbleCornerRadius
;
397 rad
[7] = kBubbleCornerRadius
;
400 // Draw the bubble's shadow.
401 int width
= popup_bounds
.width();
402 int height
= popup_bounds
.height();
403 gfx::Rect
rect(gfx::Rect(popup_bounds
.size()));
404 SkPaint shadow_paint
;
405 shadow_paint
.setAntiAlias(true);
406 shadow_paint
.setColor(kShadowColor
);
409 rrect
.setRectRadii(RectToSkRect(rect
), (const SkVector
*)rad
);
410 canvas
->sk_canvas()->drawRRect(rrect
, shadow_paint
);
412 const int shadow_size
= 2 * kShadowThickness
;
414 rect
.SetRect(SkIntToScalar(kShadowThickness
), SkIntToScalar(kShadowThickness
),
415 SkIntToScalar(width
- shadow_size
),
416 SkIntToScalar(height
- shadow_size
));
417 rrect
.setRectRadii(RectToSkRect(rect
), (const SkVector
*)rad
);
418 canvas
->sk_canvas()->drawRRect(rrect
, paint
);
420 // Draw highlight text and then the text body. In order to make sure the text
421 // is aligned to the right on RTL UIs, we mirror the text bounds if the
423 const gfx::FontList font_list
;
425 std::min(gfx::GetStringWidth(text_
, font_list
),
426 width
- shadow_size
- kTextPositionX
- kTextHorizPadding
);
427 int text_height
= height
- shadow_size
;
428 gfx::Rect
body_bounds(kShadowThickness
+ kTextPositionX
,
430 std::max(0, text_width
),
431 std::max(0, text_height
));
432 body_bounds
.set_x(GetMirroredXForRect(body_bounds
));
434 theme_service_
->GetColor(ThemeProperties::COLOR_STATUS_BAR_TEXT
);
435 canvas
->DrawStringRect(text_
, font_list
, text_color
, body_bounds
);
439 // StatusBubbleViews::StatusViewAnimation --------------------------------------
441 StatusBubbleViews::StatusViewAnimation::StatusViewAnimation(
442 StatusView
* status_view
,
443 double opacity_start
,
445 : gfx::LinearAnimation(kFramerate
, this),
446 status_view_(status_view
),
447 opacity_start_(opacity_start
),
448 opacity_end_(opacity_end
) {
451 StatusBubbleViews::StatusViewAnimation::~StatusViewAnimation() {
452 // Remove ourself as a delegate so that we don't get notified when
453 // animations end as a result of destruction.
457 double StatusBubbleViews::StatusViewAnimation::GetCurrentOpacity() {
458 return opacity_start_
+ (opacity_end_
- opacity_start_
) *
459 gfx::LinearAnimation::GetCurrentValue();
462 void StatusBubbleViews::StatusViewAnimation::AnimateToState(double state
) {
463 status_view_
->SetOpacity(GetCurrentOpacity());
466 void StatusBubbleViews::StatusViewAnimation::AnimationEnded(
467 const gfx::Animation
* animation
) {
468 status_view_
->SetOpacity(opacity_end_
);
469 status_view_
->OnAnimationEnded();
472 // StatusBubbleViews::StatusViewExpander ---------------------------------------
474 // Manages the expansion and contraction of the status bubble as it accommodates
475 // URLs too long to fit in the standard bubble. Changes are passed through the
476 // StatusView to paint.
477 class StatusBubbleViews::StatusViewExpander
: public gfx::LinearAnimation
,
478 public gfx::AnimationDelegate
{
480 StatusViewExpander(StatusBubbleViews
* status_bubble
,
481 StatusView
* status_view
)
482 : gfx::LinearAnimation(kFramerate
, this),
483 status_bubble_(status_bubble
),
484 status_view_(status_view
),
489 // Manage the expansion of the bubble.
490 void StartExpansion(const base::string16
& expanded_text
,
494 // Set width of fully expanded bubble.
495 void SetExpandedWidth(int expanded_width
);
498 // Animation functions.
499 int GetCurrentBubbleWidth();
500 void SetBubbleWidth(int width
);
501 void AnimateToState(double state
) override
;
502 void AnimationEnded(const gfx::Animation
* animation
) override
;
504 // Manager that owns us.
505 StatusBubbleViews
* status_bubble_
;
507 // Change the bounds and text of this view.
508 StatusView
* status_view_
;
510 // Text elided (if needed) to fit maximum status bar width.
511 base::string16 expanded_text_
;
513 // Widths at expansion start and end.
514 int expansion_start_
;
518 void StatusBubbleViews::StatusViewExpander::AnimateToState(double state
) {
519 SetBubbleWidth(GetCurrentBubbleWidth());
522 void StatusBubbleViews::StatusViewExpander::AnimationEnded(
523 const gfx::Animation
* animation
) {
524 SetBubbleWidth(expansion_end_
);
525 status_view_
->SetText(expanded_text_
, false);
528 void StatusBubbleViews::StatusViewExpander::StartExpansion(
529 const base::string16
& expanded_text
,
532 expanded_text_
= expanded_text
;
533 expansion_start_
= expansion_start
;
534 expansion_end_
= expansion_end
;
535 int min_duration
= std::max(kMinExpansionStepDurationMS
,
536 static_cast<int>(kMaxExpansionStepDurationMS
*
537 (expansion_end
- expansion_start
) / 100.0));
538 SetDuration(std::min(kMaxExpansionStepDurationMS
, min_duration
));
542 int StatusBubbleViews::StatusViewExpander::GetCurrentBubbleWidth() {
543 return static_cast<int>(expansion_start_
+
544 (expansion_end_
- expansion_start_
) *
545 gfx::LinearAnimation::GetCurrentValue());
548 void StatusBubbleViews::StatusViewExpander::SetBubbleWidth(int width
) {
549 status_bubble_
->SetBubbleWidth(width
);
550 status_view_
->SchedulePaint();
554 // StatusBubbleViews -----------------------------------------------------------
556 const int StatusBubbleViews::kShadowThickness
= 1;
558 StatusBubbleViews::StatusBubbleViews(views::View
* base_view
)
559 : contains_mouse_(false),
561 base_view_(base_view
),
563 download_shelf_is_visible_(false),
565 expand_timer_factory_(this) {
566 expand_view_
.reset();
569 StatusBubbleViews::~StatusBubbleViews() {
575 void StatusBubbleViews::Init() {
577 popup_
.reset(new views::Widget
);
578 views::Widget
* frame
= base_view_
->GetWidget();
580 view_
= new StatusView(popup_
.get(), frame
->GetThemeProvider());
581 if (!expand_view_
.get())
582 expand_view_
.reset(new StatusViewExpander(this, view_
));
583 views::Widget::InitParams
params(views::Widget::InitParams::TYPE_POPUP
);
584 params
.opacity
= views::Widget::InitParams::TRANSLUCENT_WINDOW
;
585 params
.accept_events
= false;
586 params
.ownership
= views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET
;
587 params
.parent
= frame
->GetNativeView();
588 params
.context
= frame
->GetNativeWindow();
589 popup_
->Init(params
);
590 // We do our own animation and don't want any from the system.
591 popup_
->SetVisibilityChangedAnimationsEnabled(false);
592 popup_
->SetOpacity(0x00);
593 popup_
->SetContentsView(view_
);
595 ash::wm::GetWindowState(popup_
->GetNativeWindow())->
596 set_ignored_by_shelf(true);
602 void StatusBubbleViews::Reposition() {
603 // In restored mode, the client area has a client edge between it and the
605 int overlap
= kShadowThickness
;
606 int height
= GetPreferredSize().height();
607 int base_view_height
= base_view()->bounds().height();
608 gfx::Point
origin(-overlap
, base_view_height
- height
+ overlap
);
609 SetBounds(origin
.x(), origin
.y(), base_view()->bounds().width() / 3, height
);
612 void StatusBubbleViews::RepositionPopup() {
615 views::View::ConvertPointToScreen(base_view_
, &top_left
);
617 popup_
->SetBounds(gfx::Rect(top_left
.x() + position_
.x(),
618 top_left
.y() + position_
.y(),
619 size_
.width(), size_
.height()));
623 gfx::Size
StatusBubbleViews::GetPreferredSize() {
624 return gfx::Size(0, gfx::FontList().GetHeight() + kTotalVerticalPadding
);
627 void StatusBubbleViews::SetBounds(int x
, int y
, int w
, int h
) {
628 original_position_
.SetPoint(x
, y
);
629 position_
.SetPoint(base_view_
->GetMirroredXWithWidthInView(x
, w
), y
);
632 if (popup_
.get() && contains_mouse_
)
633 AvoidMouse(last_mouse_moved_location_
);
636 void StatusBubbleViews::SetStatus(const base::string16
& status_text
) {
638 return; // We have no bounds, don't attempt to show the popup.
640 if (status_text_
== status_text
&& !status_text
.empty())
643 if (!IsFrameVisible())
644 return; // Don't show anything if the parent isn't visible.
647 status_text_
= status_text
;
648 if (!status_text_
.empty()) {
649 view_
->SetText(status_text
, true);
651 } else if (!url_text_
.empty()) {
652 view_
->SetText(url_text_
, true);
654 view_
->SetText(base::string16(), true);
658 void StatusBubbleViews::SetURL(const GURL
& url
, const std::string
& languages
) {
660 languages_
= languages
;
662 return; // We have no bounds, don't attempt to show the popup.
666 // If we want to clear a displayed URL but there is a status still to
667 // display, display that status instead.
668 if (url
.is_empty() && !status_text_
.empty()) {
669 url_text_
= base::string16();
670 if (IsFrameVisible())
671 view_
->SetText(status_text_
, true);
675 // Reset expansion state only when bubble is completely hidden.
676 if (view_
->state() == StatusView::BUBBLE_HIDDEN
) {
677 is_expanded_
= false;
678 SetBubbleWidth(GetStandardStatusBubbleWidth());
681 // Set Elided Text corresponding to the GURL object.
682 gfx::Rect popup_bounds
= popup_
->GetWindowBoundsInScreen();
683 int text_width
= static_cast<int>(popup_bounds
.width() -
684 (kShadowThickness
* 2) - kTextPositionX
- kTextHorizPadding
- 1);
685 url_text_
= ElideUrl(url
, gfx::FontList(), text_width
, languages
);
687 // An URL is always treated as a left-to-right string. On right-to-left UIs
688 // we need to explicitly mark the URL as LTR to make sure it is displayed
690 url_text_
= base::i18n::GetDisplayStringInLTRDirectionality(url_text_
);
692 if (IsFrameVisible()) {
693 view_
->SetText(url_text_
, true);
697 // If bubble is already in expanded state, shift to adjust to new text
698 // size (shrinking or expanding). Otherwise delay.
699 if (is_expanded_
&& !url
.is_empty()) {
701 } else if (net::FormatUrl(url
, languages
).length() > url_text_
.length()) {
702 base::MessageLoop::current()->PostDelayedTask(
704 base::Bind(&StatusBubbleViews::ExpandBubble
,
705 expand_timer_factory_
.GetWeakPtr()),
706 base::TimeDelta::FromMilliseconds(kExpandHoverDelayMS
));
711 void StatusBubbleViews::Hide() {
712 status_text_
= base::string16();
713 url_text_
= base::string16();
718 void StatusBubbleViews::MouseMoved(const gfx::Point
& location
,
720 contains_mouse_
= !left_content
;
725 last_mouse_moved_location_
= location
;
730 if (view_
->state() != StatusView::BUBBLE_HIDDEN
&&
731 view_
->state() != StatusView::BUBBLE_HIDING_FADE
&&
732 view_
->state() != StatusView::BUBBLE_HIDING_TIMER
) {
733 AvoidMouse(location
);
738 void StatusBubbleViews::UpdateDownloadShelfVisibility(bool visible
) {
739 download_shelf_is_visible_
= visible
;
742 void StatusBubbleViews::AvoidMouse(const gfx::Point
& location
) {
743 // Get the position of the frame.
745 views::View::ConvertPointToScreen(base_view_
, &top_left
);
747 int window_width
= base_view_
->GetLocalBounds().width();
749 // Get the cursor position relative to the popup.
750 gfx::Point relative_location
= location
;
751 if (base::i18n::IsRTL()) {
752 int top_right_x
= top_left
.x() + window_width
;
753 relative_location
.set_x(top_right_x
- relative_location
.x());
755 relative_location
.set_x(
756 relative_location
.x() - (top_left
.x() + position_
.x()));
758 relative_location
.set_y(
759 relative_location
.y() - (top_left
.y() + position_
.y()));
761 // If the mouse is in a position where we think it would move the
762 // status bubble, figure out where and how the bubble should be moved.
763 if (relative_location
.y() > -kMousePadding
&&
764 relative_location
.x() < size_
.width() + kMousePadding
) {
765 int offset
= kMousePadding
+ relative_location
.y();
767 // Make the movement non-linear.
768 offset
= offset
* offset
/ kMousePadding
;
770 // When the mouse is entering from the right, we want the offset to be
771 // scaled by how horizontally far away the cursor is from the bubble.
772 if (relative_location
.x() > size_
.width()) {
773 offset
= static_cast<int>(static_cast<float>(offset
) * (
774 static_cast<float>(kMousePadding
-
775 (relative_location
.x() - size_
.width())) /
776 static_cast<float>(kMousePadding
)));
779 // Cap the offset and change the visual presentation of the bubble
780 // depending on where it ends up (so that rounded corners square off
781 // and mate to the edges of the tab content).
782 if (offset
>= size_
.height() - kShadowThickness
* 2) {
783 offset
= size_
.height() - kShadowThickness
* 2;
784 view_
->SetStyle(StatusView::STYLE_BOTTOM
);
785 } else if (offset
> kBubbleCornerRadius
/ 2 - kShadowThickness
) {
786 view_
->SetStyle(StatusView::STYLE_FLOATING
);
788 view_
->SetStyle(StatusView::STYLE_STANDARD
);
791 // Check if the bubble sticks out from the monitor or will obscure
793 gfx::NativeView window
= base_view_
->GetWidget()->GetNativeView();
794 gfx::Rect monitor_rect
= gfx::Screen::GetScreenFor(window
)->
795 GetDisplayNearestWindow(window
).work_area();
796 const int bubble_bottom_y
= top_left
.y() + position_
.y() + size_
.height();
798 if (bubble_bottom_y
+ offset
> monitor_rect
.height() ||
799 (download_shelf_is_visible_
&&
800 (view_
->style() == StatusView::STYLE_FLOATING
||
801 view_
->style() == StatusView::STYLE_BOTTOM
))) {
802 // The offset is still too large. Move the bubble to the right and reset
803 // Y offset_ to zero.
804 view_
->SetStyle(StatusView::STYLE_STANDARD_RIGHT
);
807 // Subtract border width + bubble width.
808 int right_position_x
= window_width
- (position_
.x() + size_
.width());
809 popup_
->SetBounds(gfx::Rect(top_left
.x() + right_position_x
,
810 top_left
.y() + position_
.y(),
811 size_
.width(), size_
.height()));
814 popup_
->SetBounds(gfx::Rect(top_left
.x() + position_
.x(),
815 top_left
.y() + position_
.y() + offset_
,
816 size_
.width(), size_
.height()));
818 } else if (offset_
!= 0 ||
819 view_
->style() == StatusView::STYLE_STANDARD_RIGHT
) {
821 view_
->SetStyle(StatusView::STYLE_STANDARD
);
822 popup_
->SetBounds(gfx::Rect(top_left
.x() + position_
.x(),
823 top_left
.y() + position_
.y(),
824 size_
.width(), size_
.height()));
828 bool StatusBubbleViews::IsFrameVisible() {
829 views::Widget
* frame
= base_view_
->GetWidget();
830 if (!frame
->IsVisible())
833 views::Widget
* window
= frame
->GetTopLevelWidget();
834 return !window
|| !window
->IsMinimized();
837 bool StatusBubbleViews::IsFrameMaximized() {
838 views::Widget
* frame
= base_view_
->GetWidget();
839 views::Widget
* window
= frame
->GetTopLevelWidget();
840 return window
&& window
->IsMaximized();
843 void StatusBubbleViews::ExpandBubble() {
844 // Elide URL to maximum possible size, then check actual length (it may
845 // still be too long to fit) before expanding bubble.
846 gfx::Rect popup_bounds
= popup_
->GetWindowBoundsInScreen();
847 int max_status_bubble_width
= GetMaxStatusBubbleWidth();
848 const gfx::FontList font_list
;
849 url_text_
= ElideUrl(url_
, font_list
, max_status_bubble_width
, languages_
);
850 int expanded_bubble_width
=
851 std::max(GetStandardStatusBubbleWidth(),
852 std::min(gfx::GetStringWidth(url_text_
, font_list
) +
853 (kShadowThickness
* 2) + kTextPositionX
+
854 kTextHorizPadding
+ 1,
855 max_status_bubble_width
));
857 expand_view_
->StartExpansion(url_text_
, popup_bounds
.width(),
858 expanded_bubble_width
);
861 int StatusBubbleViews::GetStandardStatusBubbleWidth() {
862 return base_view_
->bounds().width() / 3;
865 int StatusBubbleViews::GetMaxStatusBubbleWidth() {
866 const ui::NativeTheme
* theme
= base_view_
->GetNativeTheme();
867 return static_cast<int>(std::max(0, base_view_
->bounds().width() -
868 (kShadowThickness
* 2) - kTextPositionX
- kTextHorizPadding
- 1 -
869 views::NativeScrollBar::GetVerticalScrollBarWidth(theme
)));
872 void StatusBubbleViews::SetBubbleWidth(int width
) {
873 size_
.set_width(width
);
874 SetBounds(original_position_
.x(), original_position_
.y(),
875 size_
.width(), size_
.height());
878 void StatusBubbleViews::CancelExpandTimer() {
879 if (expand_timer_factory_
.HasWeakPtrs())
880 expand_timer_factory_
.InvalidateWeakPtrs();