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"
9 #include "ash/wm/window_state.h"
10 #include "base/bind.h"
11 #include "base/i18n/rtl.h"
12 #include "base/message_loop/message_loop.h"
13 #include "base/strings/string_util.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "chrome/browser/themes/theme_properties.h"
16 #include "chrome/browser/ui/elide_url.h"
17 #include "net/base/net_util.h"
18 #include "third_party/skia/include/core/SkPaint.h"
19 #include "third_party/skia/include/core/SkPath.h"
20 #include "third_party/skia/include/core/SkRect.h"
21 #include "ui/aura/window.h"
22 #include "ui/base/theme_provider.h"
23 #include "ui/gfx/animation/animation_delegate.h"
24 #include "ui/gfx/animation/linear_animation.h"
25 #include "ui/gfx/canvas.h"
26 #include "ui/gfx/font_list.h"
27 #include "ui/gfx/point.h"
28 #include "ui/gfx/rect.h"
29 #include "ui/gfx/screen.h"
30 #include "ui/gfx/skia_util.h"
31 #include "ui/gfx/text_elider.h"
32 #include "ui/gfx/text_utils.h"
33 #include "ui/native_theme/native_theme.h"
34 #include "ui/views/controls/scrollbar/native_scroll_bar.h"
35 #include "ui/views/widget/root_view.h"
36 #include "ui/views/widget/widget.h"
39 // The alpha and color of the bubble's shadow.
40 static const SkColor kShadowColor = SkColorSetARGB(30, 0, 0, 0);
42 // The roundedness of the edges of our bubble.
43 static const int kBubbleCornerRadius = 4;
45 // How close the mouse can get to the infobubble before it starts sliding
47 static const int kMousePadding = 20;
49 // The horizontal offset of the text within the status bubble, not including the
51 static const int kTextPositionX = 3;
53 // The minimum horizontal space between the (right) end of the text and the edge
54 // of the status bubble, not including the outer shadow ring.
55 static const int kTextHorizPadding = 1;
57 // Delays before we start hiding or showing the bubble after we receive a
58 // show or hide request.
59 static const int kShowDelay = 80;
60 static const int kHideDelay = 250;
62 // How long each fade should last for.
63 static const int kShowFadeDurationMS = 120;
64 static const int kHideFadeDurationMS = 200;
65 static const int kFramerate = 25;
67 // How long each expansion step should take.
68 static const int kMinExpansionStepDurationMS = 20;
69 static const int kMaxExpansionStepDurationMS = 150;
72 // StatusBubbleViews::StatusViewAnimation --------------------------------------
73 class StatusBubbleViews::StatusViewAnimation : public gfx::LinearAnimation,
74 public gfx::AnimationDelegate {
76 StatusViewAnimation(StatusView* status_view,
79 virtual ~StatusViewAnimation();
81 double GetCurrentOpacity();
84 // gfx::LinearAnimation:
85 virtual void AnimateToState(double state) OVERRIDE;
87 // gfx::AnimationDelegate:
88 virtual void AnimationEnded(const Animation* animation) OVERRIDE;
90 StatusView* status_view_;
92 // Start and end opacities for the current transition - note that as a
93 // fade-in can easily turn into a fade out, opacity_start_ is sometimes
94 // a value between 0 and 1.
95 double opacity_start_;
98 DISALLOW_COPY_AND_ASSIGN(StatusViewAnimation);
102 // StatusBubbleViews::StatusView -----------------------------------------------
104 // StatusView manages the display of the bubble, applying text changes and
105 // fading in or out the bubble as required.
106 class StatusBubbleViews::StatusView : public views::View {
108 // The bubble can be in one of many states:
110 BUBBLE_HIDDEN, // Entirely BUBBLE_HIDDEN.
111 BUBBLE_HIDING_FADE, // In a fade-out transition.
112 BUBBLE_HIDING_TIMER, // Waiting before a fade-out.
113 BUBBLE_SHOWING_TIMER, // Waiting before a fade-in.
114 BUBBLE_SHOWING_FADE, // In a fade-in transition.
115 BUBBLE_SHOWN // Fully visible.
125 StatusView(views::Widget* popup,
126 ui::ThemeProvider* theme_provider);
127 virtual ~StatusView();
129 // Set the bubble text to a certain value, hides the bubble if text is
130 // an empty string. Trigger animation sequence to display if
131 // |should_animate_open|.
132 void SetText(const base::string16& text, bool should_animate_open);
134 BubbleState state() const { return state_; }
135 BubbleStyle style() const { return style_; }
136 void SetStyle(BubbleStyle style);
138 // Show the bubble instantly.
141 // Hide the bubble instantly.
144 // Resets any timers we have. Typically called when the user moves a
148 // This call backs the StatusView in order to fade the bubble in and out.
149 void SetOpacity(double opacity);
151 // Depending on the state of the bubble this will either hide the popup or
153 void OnAnimationEnded();
158 // Manage the timers that control the delay before a fade begins or ends.
159 void StartTimer(base::TimeDelta time);
162 void RestartTimer(base::TimeDelta delay);
164 // Manage the fades and starting and stopping the animations correctly.
165 void StartFade(double start, double end, int duration);
170 virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
175 base::WeakPtrFactory<StatusBubbleViews::StatusView> timer_factory_;
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 DISALLOW_COPY_AND_ASSIGN(StatusView);
191 StatusBubbleViews::StatusView::StatusView(views::Widget* popup,
192 ui::ThemeProvider* theme_provider)
193 : state_(BUBBLE_HIDDEN),
194 style_(STYLE_STANDARD),
195 timer_factory_(this),
196 animation_(new StatusViewAnimation(this, 0, 0)),
198 theme_service_(theme_provider) {
201 StatusBubbleViews::StatusView::~StatusView() {
206 void StatusBubbleViews::StatusView::SetText(const base::string16& text,
207 bool should_animate_open) {
209 // The string was empty.
212 // We want to show the string.
217 if (should_animate_open)
222 void StatusBubbleViews::StatusView::Show() {
226 popup_->ShowInactive();
227 state_ = BUBBLE_SHOWN;
230 void StatusBubbleViews::StatusView::Hide() {
236 state_ = BUBBLE_HIDDEN;
239 void StatusBubbleViews::StatusView::StartTimer(base::TimeDelta time) {
240 if (timer_factory_.HasWeakPtrs())
241 timer_factory_.InvalidateWeakPtrs();
243 base::MessageLoop::current()->PostDelayedTask(
245 base::Bind(&StatusBubbleViews::StatusView::OnTimer,
246 timer_factory_.GetWeakPtr()),
250 void StatusBubbleViews::StatusView::OnTimer() {
251 if (state_ == BUBBLE_HIDING_TIMER) {
252 state_ = BUBBLE_HIDING_FADE;
253 StartFade(1.0, 0.0, kHideFadeDurationMS);
254 } else if (state_ == BUBBLE_SHOWING_TIMER) {
255 state_ = BUBBLE_SHOWING_FADE;
256 StartFade(0.0, 1.0, kShowFadeDurationMS);
260 void StatusBubbleViews::StatusView::CancelTimer() {
261 if (timer_factory_.HasWeakPtrs())
262 timer_factory_.InvalidateWeakPtrs();
265 void StatusBubbleViews::StatusView::RestartTimer(base::TimeDelta delay) {
270 void StatusBubbleViews::StatusView::ResetTimer() {
271 if (state_ == BUBBLE_SHOWING_TIMER) {
272 // We hadn't yet begun showing anything when we received a new request
273 // for something to show, so we start from scratch.
274 RestartTimer(base::TimeDelta::FromMilliseconds(kShowDelay));
278 void StatusBubbleViews::StatusView::StartFade(double start,
281 animation_.reset(new StatusViewAnimation(this, start, end));
283 // This will also reset the currently-occurring animation.
284 animation_->SetDuration(duration);
288 void StatusBubbleViews::StatusView::StartHiding() {
289 if (state_ == BUBBLE_SHOWN) {
290 state_ = BUBBLE_HIDING_TIMER;
291 StartTimer(base::TimeDelta::FromMilliseconds(kHideDelay));
292 } else if (state_ == BUBBLE_SHOWING_TIMER) {
293 state_ = BUBBLE_HIDDEN;
296 } else if (state_ == BUBBLE_SHOWING_FADE) {
297 state_ = BUBBLE_HIDING_FADE;
298 // Figure out where we are in the current fade.
299 double current_opacity = animation_->GetCurrentOpacity();
301 // Start a fade in the opposite direction.
302 StartFade(current_opacity, 0.0,
303 static_cast<int>(kHideFadeDurationMS * current_opacity));
307 void StatusBubbleViews::StatusView::StartShowing() {
308 if (state_ == BUBBLE_HIDDEN) {
309 popup_->ShowInactive();
310 state_ = BUBBLE_SHOWING_TIMER;
311 StartTimer(base::TimeDelta::FromMilliseconds(kShowDelay));
312 } else if (state_ == BUBBLE_HIDING_TIMER) {
313 state_ = BUBBLE_SHOWN;
315 } else if (state_ == BUBBLE_HIDING_FADE) {
316 // We're partway through a fade.
317 state_ = BUBBLE_SHOWING_FADE;
319 // Figure out where we are in the current fade.
320 double current_opacity = animation_->GetCurrentOpacity();
322 // Start a fade in the opposite direction.
323 StartFade(current_opacity, 1.0,
324 static_cast<int>(kShowFadeDurationMS * current_opacity));
325 } else if (state_ == BUBBLE_SHOWING_TIMER) {
326 // We hadn't yet begun showing anything when we received a new request
327 // for something to show, so we start from scratch.
332 void StatusBubbleViews::StatusView::SetOpacity(double opacity) {
333 popup_->SetOpacity(static_cast<unsigned char>(opacity * 255));
336 void StatusBubbleViews::StatusView::SetStyle(BubbleStyle style) {
337 if (style_ != style) {
343 void StatusBubbleViews::StatusView::OnAnimationEnded() {
344 if (state_ == BUBBLE_HIDING_FADE) {
345 state_ = BUBBLE_HIDDEN;
347 } else if (state_ == BUBBLE_SHOWING_FADE) {
348 state_ = BUBBLE_SHOWN;
352 void StatusBubbleViews::StatusView::OnPaint(gfx::Canvas* canvas) {
354 paint.setStyle(SkPaint::kFill_Style);
355 paint.setAntiAlias(true);
356 SkColor toolbar_color = theme_service_->GetColor(
357 ThemeProperties::COLOR_TOOLBAR);
358 paint.setColor(toolbar_color);
360 gfx::Rect popup_bounds = popup_->GetWindowBoundsInScreen();
362 // Figure out how to round the bubble's four corners.
365 // Top Edges - if the bubble is in its bottom position (sticking downwards),
366 // then we square the top edges. Otherwise, we square the edges based on the
367 // position of the bubble within the window (the bubble is positioned in the
368 // southeast corner in RTL and in the southwest corner in LTR).
369 if (style_ == STYLE_BOTTOM) {
378 if (base::i18n::IsRTL() != (style_ == STYLE_STANDARD_RIGHT)) {
379 // The text is RtL or the bubble is on the right side (but not both).
382 rad[0] = SkIntToScalar(kBubbleCornerRadius);
383 rad[1] = SkIntToScalar(kBubbleCornerRadius);
394 rad[2] = SkIntToScalar(kBubbleCornerRadius);
395 rad[3] = SkIntToScalar(kBubbleCornerRadius);
399 // Bottom edges - square these off if the bubble is in its standard position
400 // (sticking upward).
401 if (style_ == STYLE_STANDARD || style_ == STYLE_STANDARD_RIGHT) {
402 // Bottom Right Corner.
406 // Bottom Left Corner.
410 // Bottom Right Corner.
411 rad[4] = SkIntToScalar(kBubbleCornerRadius);
412 rad[5] = SkIntToScalar(kBubbleCornerRadius);
414 // Bottom Left Corner.
415 rad[6] = SkIntToScalar(kBubbleCornerRadius);
416 rad[7] = SkIntToScalar(kBubbleCornerRadius);
419 // Draw the bubble's shadow.
420 int width = popup_bounds.width();
421 int height = popup_bounds.height();
422 SkRect rect(gfx::RectToSkRect(gfx::Rect(popup_bounds.size())));
424 shadow_path.addRoundRect(rect, rad, SkPath::kCW_Direction);
425 SkPaint shadow_paint;
426 shadow_paint.setAntiAlias(true);
427 shadow_paint.setColor(kShadowColor);
428 canvas->DrawPath(shadow_path, shadow_paint);
431 rect.set(SkIntToScalar(kShadowThickness),
432 SkIntToScalar(kShadowThickness),
433 SkIntToScalar(width - kShadowThickness),
434 SkIntToScalar(height - kShadowThickness));
436 path.addRoundRect(rect, rad, SkPath::kCW_Direction);
437 canvas->DrawPath(path, paint);
439 // Draw highlight text and then the text body. In order to make sure the text
440 // is aligned to the right on RTL UIs, we mirror the text bounds if the
442 const gfx::FontList font_list;
443 int text_width = std::min(
444 gfx::GetStringWidth(text_, font_list),
445 width - (kShadowThickness * 2) - kTextPositionX - kTextHorizPadding);
446 int text_height = height - (kShadowThickness * 2);
447 gfx::Rect body_bounds(kShadowThickness + kTextPositionX,
449 std::max(0, text_width),
450 std::max(0, text_height));
451 body_bounds.set_x(GetMirroredXForRect(body_bounds));
453 theme_service_->GetColor(ThemeProperties::COLOR_STATUS_BAR_TEXT);
454 canvas->DrawStringRect(text_, font_list, text_color, body_bounds);
458 // StatusBubbleViews::StatusViewAnimation --------------------------------------
460 StatusBubbleViews::StatusViewAnimation::StatusViewAnimation(
461 StatusView* status_view,
462 double opacity_start,
464 : gfx::LinearAnimation(kFramerate, this),
465 status_view_(status_view),
466 opacity_start_(opacity_start),
467 opacity_end_(opacity_end) {
470 StatusBubbleViews::StatusViewAnimation::~StatusViewAnimation() {
471 // Remove ourself as a delegate so that we don't get notified when
472 // animations end as a result of destruction.
476 double StatusBubbleViews::StatusViewAnimation::GetCurrentOpacity() {
477 return opacity_start_ + (opacity_end_ - opacity_start_) *
478 gfx::LinearAnimation::GetCurrentValue();
481 void StatusBubbleViews::StatusViewAnimation::AnimateToState(double state) {
482 status_view_->SetOpacity(GetCurrentOpacity());
485 void StatusBubbleViews::StatusViewAnimation::AnimationEnded(
486 const gfx::Animation* animation) {
487 status_view_->SetOpacity(opacity_end_);
488 status_view_->OnAnimationEnded();
491 // StatusBubbleViews::StatusViewExpander ---------------------------------------
493 // Manages the expansion and contraction of the status bubble as it accommodates
494 // URLs too long to fit in the standard bubble. Changes are passed through the
495 // StatusView to paint.
496 class StatusBubbleViews::StatusViewExpander : public gfx::LinearAnimation,
497 public gfx::AnimationDelegate {
499 StatusViewExpander(StatusBubbleViews* status_bubble,
500 StatusView* status_view)
501 : gfx::LinearAnimation(kFramerate, this),
502 status_bubble_(status_bubble),
503 status_view_(status_view),
508 // Manage the expansion of the bubble.
509 void StartExpansion(const base::string16& expanded_text,
513 // Set width of fully expanded bubble.
514 void SetExpandedWidth(int expanded_width);
517 // Animation functions.
518 int GetCurrentBubbleWidth();
519 void SetBubbleWidth(int width);
520 virtual void AnimateToState(double state) OVERRIDE;
521 virtual void AnimationEnded(const gfx::Animation* animation) OVERRIDE;
523 // Manager that owns us.
524 StatusBubbleViews* status_bubble_;
526 // Change the bounds and text of this view.
527 StatusView* status_view_;
529 // Text elided (if needed) to fit maximum status bar width.
530 base::string16 expanded_text_;
532 // Widths at expansion start and end.
533 int expansion_start_;
537 void StatusBubbleViews::StatusViewExpander::AnimateToState(double state) {
538 SetBubbleWidth(GetCurrentBubbleWidth());
541 void StatusBubbleViews::StatusViewExpander::AnimationEnded(
542 const gfx::Animation* animation) {
543 SetBubbleWidth(expansion_end_);
544 status_view_->SetText(expanded_text_, false);
547 void StatusBubbleViews::StatusViewExpander::StartExpansion(
548 const base::string16& expanded_text,
551 expanded_text_ = expanded_text;
552 expansion_start_ = expansion_start;
553 expansion_end_ = expansion_end;
554 int min_duration = std::max(kMinExpansionStepDurationMS,
555 static_cast<int>(kMaxExpansionStepDurationMS *
556 (expansion_end - expansion_start) / 100.0));
557 SetDuration(std::min(kMaxExpansionStepDurationMS, min_duration));
561 int StatusBubbleViews::StatusViewExpander::GetCurrentBubbleWidth() {
562 return static_cast<int>(expansion_start_ +
563 (expansion_end_ - expansion_start_) *
564 gfx::LinearAnimation::GetCurrentValue());
567 void StatusBubbleViews::StatusViewExpander::SetBubbleWidth(int width) {
568 status_bubble_->SetBubbleWidth(width);
569 status_view_->SchedulePaint();
573 // StatusBubbleViews -----------------------------------------------------------
575 const int StatusBubbleViews::kShadowThickness = 1;
577 StatusBubbleViews::StatusBubbleViews(views::View* base_view)
578 : contains_mouse_(false),
580 base_view_(base_view),
582 download_shelf_is_visible_(false),
584 expand_timer_factory_(this) {
585 expand_view_.reset();
588 StatusBubbleViews::~StatusBubbleViews() {
594 void StatusBubbleViews::Init() {
596 popup_.reset(new views::Widget);
597 views::Widget* frame = base_view_->GetWidget();
599 view_ = new StatusView(popup_.get(), frame->GetThemeProvider());
600 if (!expand_view_.get())
601 expand_view_.reset(new StatusViewExpander(this, view_));
602 views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
603 params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
604 params.accept_events = false;
605 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
606 params.parent = frame->GetNativeView();
607 params.context = frame->GetNativeWindow();
608 popup_->Init(params);
609 popup_->GetNativeView()->SetName("StatusBubbleViews");
610 // We do our own animation and don't want any from the system.
611 popup_->SetVisibilityChangedAnimationsEnabled(false);
612 popup_->SetOpacity(0x00);
613 popup_->SetContentsView(view_);
614 ash::wm::GetWindowState(popup_->GetNativeWindow())->
615 set_ignored_by_shelf(true);
620 void StatusBubbleViews::Reposition() {
621 // In restored mode, the client area has a client edge between it and the
623 int overlap = kShadowThickness;
624 int height = GetPreferredSize().height();
625 int base_view_height = base_view()->bounds().height();
626 gfx::Point origin(-overlap, base_view_height - height + overlap);
627 SetBounds(origin.x(), origin.y(), base_view()->bounds().width() / 3, height);
630 void StatusBubbleViews::RepositionPopup() {
633 views::View::ConvertPointToScreen(base_view_, &top_left);
635 popup_->SetBounds(gfx::Rect(top_left.x() + position_.x(),
636 top_left.y() + position_.y(),
637 size_.width(), size_.height()));
641 gfx::Size StatusBubbleViews::GetPreferredSize() {
642 return gfx::Size(0, gfx::FontList().GetHeight() + kTotalVerticalPadding);
645 void StatusBubbleViews::SetBounds(int x, int y, int w, int h) {
646 original_position_.SetPoint(x, y);
647 position_.SetPoint(base_view_->GetMirroredXWithWidthInView(x, w), y);
650 if (popup_.get() && contains_mouse_)
651 AvoidMouse(last_mouse_moved_location_);
654 void StatusBubbleViews::SetStatus(const base::string16& status_text) {
656 return; // We have no bounds, don't attempt to show the popup.
658 if (status_text_ == status_text && !status_text.empty())
661 if (!IsFrameVisible())
662 return; // Don't show anything if the parent isn't visible.
665 status_text_ = status_text;
666 if (!status_text_.empty()) {
667 view_->SetText(status_text, true);
669 } else if (!url_text_.empty()) {
670 view_->SetText(url_text_, true);
672 view_->SetText(base::string16(), true);
676 void StatusBubbleViews::SetURL(const GURL& url, const std::string& languages) {
678 languages_ = languages;
680 return; // We have no bounds, don't attempt to show the popup.
684 // If we want to clear a displayed URL but there is a status still to
685 // display, display that status instead.
686 if (url.is_empty() && !status_text_.empty()) {
687 url_text_ = base::string16();
688 if (IsFrameVisible())
689 view_->SetText(status_text_, true);
693 // Reset expansion state only when bubble is completely hidden.
694 if (view_->state() == StatusView::BUBBLE_HIDDEN) {
695 is_expanded_ = false;
696 SetBubbleWidth(GetStandardStatusBubbleWidth());
699 // Set Elided Text corresponding to the GURL object.
700 gfx::Rect popup_bounds = popup_->GetWindowBoundsInScreen();
701 int text_width = static_cast<int>(popup_bounds.width() -
702 (kShadowThickness * 2) - kTextPositionX - kTextHorizPadding - 1);
703 url_text_ = ElideUrl(url, gfx::FontList(), text_width, languages);
705 // An URL is always treated as a left-to-right string. On right-to-left UIs
706 // we need to explicitly mark the URL as LTR to make sure it is displayed
708 url_text_ = base::i18n::GetDisplayStringInLTRDirectionality(url_text_);
710 if (IsFrameVisible()) {
711 view_->SetText(url_text_, true);
715 // If bubble is already in expanded state, shift to adjust to new text
716 // size (shrinking or expanding). Otherwise delay.
717 if (is_expanded_ && !url.is_empty()) {
719 } else if (net::FormatUrl(url, languages).length() > url_text_.length()) {
720 base::MessageLoop::current()->PostDelayedTask(
722 base::Bind(&StatusBubbleViews::ExpandBubble,
723 expand_timer_factory_.GetWeakPtr()),
724 base::TimeDelta::FromMilliseconds(kExpandHoverDelayMS));
729 void StatusBubbleViews::Hide() {
730 status_text_ = base::string16();
731 url_text_ = base::string16();
736 void StatusBubbleViews::MouseMoved(const gfx::Point& location,
738 contains_mouse_ = !left_content;
743 last_mouse_moved_location_ = location;
748 if (view_->state() != StatusView::BUBBLE_HIDDEN &&
749 view_->state() != StatusView::BUBBLE_HIDING_FADE &&
750 view_->state() != StatusView::BUBBLE_HIDING_TIMER) {
751 AvoidMouse(location);
756 void StatusBubbleViews::UpdateDownloadShelfVisibility(bool visible) {
757 download_shelf_is_visible_ = visible;
760 void StatusBubbleViews::AvoidMouse(const gfx::Point& location) {
761 // Get the position of the frame.
763 views::View::ConvertPointToScreen(base_view_, &top_left);
765 int window_width = base_view_->GetLocalBounds().width();
767 // Get the cursor position relative to the popup.
768 gfx::Point relative_location = location;
769 if (base::i18n::IsRTL()) {
770 int top_right_x = top_left.x() + window_width;
771 relative_location.set_x(top_right_x - relative_location.x());
773 relative_location.set_x(
774 relative_location.x() - (top_left.x() + position_.x()));
776 relative_location.set_y(
777 relative_location.y() - (top_left.y() + position_.y()));
779 // If the mouse is in a position where we think it would move the
780 // status bubble, figure out where and how the bubble should be moved.
781 if (relative_location.y() > -kMousePadding &&
782 relative_location.x() < size_.width() + kMousePadding) {
783 int offset = kMousePadding + relative_location.y();
785 // Make the movement non-linear.
786 offset = offset * offset / kMousePadding;
788 // When the mouse is entering from the right, we want the offset to be
789 // scaled by how horizontally far away the cursor is from the bubble.
790 if (relative_location.x() > size_.width()) {
791 offset = static_cast<int>(static_cast<float>(offset) * (
792 static_cast<float>(kMousePadding -
793 (relative_location.x() - size_.width())) /
794 static_cast<float>(kMousePadding)));
797 // Cap the offset and change the visual presentation of the bubble
798 // depending on where it ends up (so that rounded corners square off
799 // and mate to the edges of the tab content).
800 if (offset >= size_.height() - kShadowThickness * 2) {
801 offset = size_.height() - kShadowThickness * 2;
802 view_->SetStyle(StatusView::STYLE_BOTTOM);
803 } else if (offset > kBubbleCornerRadius / 2 - kShadowThickness) {
804 view_->SetStyle(StatusView::STYLE_FLOATING);
806 view_->SetStyle(StatusView::STYLE_STANDARD);
809 // Check if the bubble sticks out from the monitor or will obscure
811 gfx::NativeView window = base_view_->GetWidget()->GetNativeView();
812 gfx::Rect monitor_rect = gfx::Screen::GetScreenFor(window)->
813 GetDisplayNearestWindow(window).work_area();
814 const int bubble_bottom_y = top_left.y() + position_.y() + size_.height();
816 if (bubble_bottom_y + offset > monitor_rect.height() ||
817 (download_shelf_is_visible_ &&
818 (view_->style() == StatusView::STYLE_FLOATING ||
819 view_->style() == StatusView::STYLE_BOTTOM))) {
820 // The offset is still too large. Move the bubble to the right and reset
821 // Y offset_ to zero.
822 view_->SetStyle(StatusView::STYLE_STANDARD_RIGHT);
825 // Subtract border width + bubble width.
826 int right_position_x = window_width - (position_.x() + size_.width());
827 popup_->SetBounds(gfx::Rect(top_left.x() + right_position_x,
828 top_left.y() + position_.y(),
829 size_.width(), size_.height()));
832 popup_->SetBounds(gfx::Rect(top_left.x() + position_.x(),
833 top_left.y() + position_.y() + offset_,
834 size_.width(), size_.height()));
836 } else if (offset_ != 0 ||
837 view_->style() == StatusView::STYLE_STANDARD_RIGHT) {
839 view_->SetStyle(StatusView::STYLE_STANDARD);
840 popup_->SetBounds(gfx::Rect(top_left.x() + position_.x(),
841 top_left.y() + position_.y(),
842 size_.width(), size_.height()));
846 bool StatusBubbleViews::IsFrameVisible() {
847 views::Widget* frame = base_view_->GetWidget();
848 if (!frame->IsVisible())
851 views::Widget* window = frame->GetTopLevelWidget();
852 return !window || !window->IsMinimized();
855 bool StatusBubbleViews::IsFrameMaximized() {
856 views::Widget* frame = base_view_->GetWidget();
857 views::Widget* window = frame->GetTopLevelWidget();
858 return window && window->IsMaximized();
861 void StatusBubbleViews::ExpandBubble() {
862 // Elide URL to maximum possible size, then check actual length (it may
863 // still be too long to fit) before expanding bubble.
864 gfx::Rect popup_bounds = popup_->GetWindowBoundsInScreen();
865 int max_status_bubble_width = GetMaxStatusBubbleWidth();
866 const gfx::FontList font_list;
867 url_text_ = ElideUrl(url_, font_list, max_status_bubble_width, languages_);
868 int expanded_bubble_width =
869 std::max(GetStandardStatusBubbleWidth(),
870 std::min(gfx::GetStringWidth(url_text_, font_list) +
871 (kShadowThickness * 2) + kTextPositionX +
872 kTextHorizPadding + 1,
873 max_status_bubble_width));
875 expand_view_->StartExpansion(url_text_, popup_bounds.width(),
876 expanded_bubble_width);
879 int StatusBubbleViews::GetStandardStatusBubbleWidth() {
880 return base_view_->bounds().width() / 3;
883 int StatusBubbleViews::GetMaxStatusBubbleWidth() {
884 const ui::NativeTheme* theme = base_view_->GetNativeTheme();
885 return static_cast<int>(std::max(0, base_view_->bounds().width() -
886 (kShadowThickness * 2) - kTextPositionX - kTextHorizPadding - 1 -
887 views::NativeScrollBar::GetVerticalScrollBarWidth(theme)));
890 void StatusBubbleViews::SetBubbleWidth(int width) {
891 size_.set_width(width);
892 SetBounds(original_position_.x(), original_position_.y(),
893 size_.width(), size_.height());
896 void StatusBubbleViews::CancelExpandTimer() {
897 if (expand_timer_factory_.HasWeakPtrs())
898 expand_timer_factory_.InvalidateWeakPtrs();