summaryrefslogtreecommitdiffstats
path: root/chrome/browser/ui/views/fullscreen_exit_bubble.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/ui/views/fullscreen_exit_bubble.cc')
-rw-r--r--chrome/browser/ui/views/fullscreen_exit_bubble.cc288
1 files changed, 288 insertions, 0 deletions
diff --git a/chrome/browser/ui/views/fullscreen_exit_bubble.cc b/chrome/browser/ui/views/fullscreen_exit_bubble.cc
new file mode 100644
index 0000000..635e9d2
--- /dev/null
+++ b/chrome/browser/ui/views/fullscreen_exit_bubble.cc
@@ -0,0 +1,288 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/views/fullscreen_exit_bubble.h"
+
+#include "app/keyboard_codes.h"
+#include "app/l10n_util.h"
+#include "app/resource_bundle.h"
+#include "chrome/app/chrome_command_ids.h"
+#include "gfx/canvas_skia.h"
+#include "grit/generated_resources.h"
+#include "views/screen.h"
+#include "views/widget/root_view.h"
+#include "views/window/window.h"
+
+#if defined(OS_WIN)
+#include "app/l10n_util_win.h"
+#include "views/widget/widget_win.h"
+#elif defined(OS_LINUX)
+#include "views/widget/widget_gtk.h"
+#endif
+
+// FullscreenExitView ----------------------------------------------------------
+
+class FullscreenExitBubble::FullscreenExitView : public views::View {
+ public:
+ FullscreenExitView(FullscreenExitBubble* bubble,
+ const std::wstring& accelerator);
+ virtual ~FullscreenExitView();
+
+ // views::View
+ virtual gfx::Size GetPreferredSize();
+
+ private:
+ static const int kPaddingPixels; // Number of pixels around all sides of link
+
+ // views::View
+ virtual void Layout();
+ virtual void Paint(gfx::Canvas* canvas);
+
+ // Clickable hint text to show in the bubble.
+ views::Link link_;
+};
+
+const int FullscreenExitBubble::FullscreenExitView::kPaddingPixels = 8;
+
+FullscreenExitBubble::FullscreenExitView::FullscreenExitView(
+ FullscreenExitBubble* bubble,
+ const std::wstring& accelerator) {
+ link_.set_parent_owned(false);
+#if !defined(OS_CHROMEOS)
+ link_.SetText(l10n_util::GetStringF(IDS_EXIT_FULLSCREEN_MODE, accelerator));
+#else
+ link_.SetText(l10n_util::GetString(IDS_EXIT_FULLSCREEN_MODE));
+#endif
+ link_.SetController(bubble);
+ link_.SetFont(ResourceBundle::GetSharedInstance().GetFont(
+ ResourceBundle::LargeFont));
+ link_.SetNormalColor(SK_ColorWHITE);
+ link_.SetHighlightedColor(SK_ColorWHITE);
+ AddChildView(&link_);
+}
+
+FullscreenExitBubble::FullscreenExitView::~FullscreenExitView() {
+}
+
+gfx::Size FullscreenExitBubble::FullscreenExitView::GetPreferredSize() {
+ gfx::Size preferred_size(link_.GetPreferredSize());
+ preferred_size.Enlarge(kPaddingPixels * 2, kPaddingPixels * 2);
+ return preferred_size;
+}
+
+void FullscreenExitBubble::FullscreenExitView::Layout() {
+ gfx::Size link_preferred_size(link_.GetPreferredSize());
+ link_.SetBounds(kPaddingPixels,
+ height() - kPaddingPixels - link_preferred_size.height(),
+ link_preferred_size.width(), link_preferred_size.height());
+}
+
+void FullscreenExitBubble::FullscreenExitView::Paint(gfx::Canvas* canvas) {
+ // Create a round-bottomed rect to fill the whole View.
+ SkRect rect;
+ SkScalar padding = SkIntToScalar(kPaddingPixels);
+ // The "-padding" top coordinate ensures that the rect is always tall enough
+ // to contain the complete rounded corner radius. If we set this to 0, as the
+ // popup slides offscreen (in reality, squishes to 0 height), the corners will
+ // flatten out as the height becomes less than the corner radius.
+ rect.set(0, -padding, SkIntToScalar(width()), SkIntToScalar(height()));
+ SkScalar rad[8] = { 0, 0, 0, 0, padding, padding, padding, padding };
+ SkPath path;
+ path.addRoundRect(rect, rad, SkPath::kCW_Direction);
+
+ // Fill it black.
+ SkPaint paint;
+ paint.setStyle(SkPaint::kFill_Style);
+ paint.setFlags(SkPaint::kAntiAlias_Flag);
+ paint.setColor(SK_ColorBLACK);
+ canvas->AsCanvasSkia()->drawPath(path, paint);
+}
+
+
+// FullscreenExitPopup ---------------------------------------------------------
+
+#if defined(OS_WIN)
+class FullscreenExitBubble::FullscreenExitPopup : public views::WidgetWin {
+ public:
+ FullscreenExitPopup() : views::WidgetWin() {}
+ virtual ~FullscreenExitPopup() {}
+
+ // views::WidgetWin:
+ virtual LRESULT OnMouseActivate(HWND window,
+ UINT hittest_code,
+ UINT message) {
+ // Prevent the popup from being activated, so it won't steal focus from the
+ // rest of the browser, and doesn't cause problems with the FocusManager's
+ // "RestoreFocusedView()" functionality.
+ return MA_NOACTIVATE;
+ }
+};
+#elif defined(OS_LINUX)
+// TODO: figure out the equivalent of MA_NOACTIVATE for gtk.
+#endif
+
+// FullscreenExitBubble --------------------------------------------------------
+
+const double FullscreenExitBubble::kOpacity = 0.7;
+const int FullscreenExitBubble::kInitialDelayMs = 2300;
+const int FullscreenExitBubble::kIdleTimeMs = 2300;
+const int FullscreenExitBubble::kPositionCheckHz = 10;
+const int FullscreenExitBubble::kSlideInRegionHeightPx = 4;
+const int FullscreenExitBubble::kSlideInDurationMs = 350;
+const int FullscreenExitBubble::kSlideOutDurationMs = 700;
+
+FullscreenExitBubble::FullscreenExitBubble(
+ views::Widget* frame,
+ CommandUpdater::CommandUpdaterDelegate* delegate)
+ : root_view_(frame->GetRootView()),
+ delegate_(delegate),
+ popup_(NULL),
+ size_animation_(new SlideAnimation(this)) {
+ size_animation_->Reset(1);
+
+ // Create the contents view.
+ views::Accelerator accelerator(app::VKEY_UNKNOWN, false, false, false);
+ bool got_accelerator = frame->GetAccelerator(IDC_FULLSCREEN, &accelerator);
+ DCHECK(got_accelerator);
+ view_ = new FullscreenExitView(this, accelerator.GetShortcutText());
+
+ // Initialize the popup.
+#if defined(OS_WIN)
+ popup_ = new FullscreenExitPopup();
+ popup_->set_window_style(WS_POPUP);
+ popup_->set_window_ex_style(WS_EX_LAYERED | WS_EX_TOOLWINDOW |
+ l10n_util::GetExtendedTooltipStyles());
+#elif defined(OS_LINUX)
+ popup_ = new views::WidgetGtk(views::WidgetGtk::TYPE_POPUP);
+ popup_->MakeTransparent();
+#endif
+ popup_->SetOpacity(static_cast<unsigned char>(0xff * kOpacity));
+ popup_->Init(frame->GetNativeView(), GetPopupRect(false));
+ popup_->set_delete_on_destroy(false);
+ popup_->SetContentsView(view_);
+ popup_->Show(); // This does not activate the popup.
+
+ // Start the initial delay timer and begin watching the mouse.
+ initial_delay_.Start(base::TimeDelta::FromMilliseconds(kInitialDelayMs), this,
+ &FullscreenExitBubble::CheckMousePosition);
+ gfx::Point cursor_pos = views::Screen::GetCursorScreenPoint();
+ last_mouse_pos_ = cursor_pos;
+ views::View::ConvertPointToView(NULL, root_view_, &last_mouse_pos_);
+ mouse_position_checker_.Start(
+ base::TimeDelta::FromMilliseconds(1000 / kPositionCheckHz), this,
+ &FullscreenExitBubble::CheckMousePosition);
+}
+
+FullscreenExitBubble::~FullscreenExitBubble() {
+ // This is tricky. We may be in an ATL message handler stack, in which case
+ // the popup cannot be deleted yet. We also can't blindly use
+ // set_delete_on_destroy(true) on the popup to delete it when it closes,
+ // because if the user closed the last tab while in fullscreen mode, Windows
+ // has already destroyed the popup HWND by the time we get here, and thus
+ // either the popup will already have been deleted (if we set this in our
+ // constructor) or the popup will never get another OnFinalMessage() call (if
+ // not, as currently). So instead, we tell the popup to synchronously hide,
+ // and then asynchronously close and delete itself.
+ popup_->Close();
+ MessageLoop::current()->DeleteSoon(FROM_HERE, popup_);
+}
+
+void FullscreenExitBubble::LinkActivated(views::Link* source, int event_flags) {
+ delegate_->ExecuteCommand(IDC_FULLSCREEN);
+}
+
+void FullscreenExitBubble::AnimationProgressed(
+ const Animation* animation) {
+ gfx::Rect popup_rect(GetPopupRect(false));
+ if (popup_rect.IsEmpty()) {
+ popup_->Hide();
+ } else {
+#if defined(OS_WIN)
+ popup_->MoveWindow(popup_rect.x(), popup_rect.y(), popup_rect.width(),
+ popup_rect.height());
+#elif defined(OS_LINUX)
+ popup_->SetBounds(popup_rect);
+#endif
+ popup_->Show();
+ }
+}
+void FullscreenExitBubble::AnimationEnded(
+ const Animation* animation) {
+ AnimationProgressed(animation);
+}
+
+void FullscreenExitBubble::CheckMousePosition() {
+ // Desired behavior:
+ //
+ // +------------+-----------------------------+------------+
+ // | _ _ _ _ | Exit full screen mode (F11) | _ _ _ _ | Slide-in region
+ // | _ _ _ _ \_____________________________/ _ _ _ _ | Neutral region
+ // | | Slide-out region
+ // : :
+ //
+ // * If app is not active, we hide the popup.
+ // * If the mouse is offscreen or in the slide-out region, we hide the popup.
+ // * If the mouse goes idle, we hide the popup.
+ // * If the mouse is in the slide-in-region and not idle, we show the popup.
+ // * If the mouse is in the neutral region and not idle, and the popup is
+ // currently sliding out, we show it again. This facilitates users
+ // correcting us if they try to mouse horizontally towards the popup and
+ // unintentionally drop too low.
+ // * Otherwise, we do nothing, because the mouse is in the neutral region and
+ // either the popup is hidden or the mouse is not idle, so we don't want to
+ // change anything's state.
+
+ gfx::Point cursor_pos = views::Screen::GetCursorScreenPoint();
+ gfx::Point transformed_pos(cursor_pos);
+ views::View::ConvertPointToView(NULL, root_view_, &transformed_pos);
+
+ // Check to see whether the mouse is idle.
+ if (transformed_pos != last_mouse_pos_) {
+ // The mouse moved; reset the idle timer.
+ idle_timeout_.Stop(); // If the timer isn't running, this is a no-op.
+ idle_timeout_.Start(base::TimeDelta::FromMilliseconds(kIdleTimeMs), this,
+ &FullscreenExitBubble::CheckMousePosition);
+ }
+ last_mouse_pos_ = transformed_pos;
+
+ if ((!root_view_->GetWidget()->IsActive()) ||
+ !root_view_->HitTest(transformed_pos) ||
+ (cursor_pos.y() >= GetPopupRect(true).bottom()) ||
+ !idle_timeout_.IsRunning()) {
+ // The cursor is offscreen, in the slide-out region, or idle.
+ Hide();
+ } else if ((cursor_pos.y() < kSlideInRegionHeightPx) ||
+ (size_animation_->GetCurrentValue() != 0)) {
+ // The cursor is not idle, and either it's in the slide-in region or it's in
+ // the neutral region and we're sliding out.
+ size_animation_->SetSlideDuration(kSlideInDurationMs);
+ size_animation_->Show();
+ }
+}
+
+void FullscreenExitBubble::Hide() {
+ // Allow the bubble to hide if the window is deactivated or our initial delay
+ // finishes.
+ if ((!root_view_->GetWidget()->IsActive()) || !initial_delay_.IsRunning()) {
+ size_animation_->SetSlideDuration(kSlideOutDurationMs);
+ size_animation_->Hide();
+ }
+}
+
+gfx::Rect FullscreenExitBubble::GetPopupRect(
+ bool ignore_animation_state) const {
+ gfx::Size size(view_->GetPreferredSize());
+ if (!ignore_animation_state) {
+ size.set_height(static_cast<int>(static_cast<double>(size.height()) *
+ size_animation_->GetCurrentValue()));
+ }
+ // NOTE: don't use the bounds of the root_view_. On linux changing window
+ // size is async. Instead we use the size of the screen.
+ gfx::Rect screen_bounds = views::Screen::GetMonitorAreaNearestWindow(
+ root_view_->GetWidget()->GetNativeView());
+ gfx::Point origin(screen_bounds.x() +
+ (screen_bounds.width() - size.width()) / 2,
+ screen_bounds.y());
+ return gfx::Rect(origin, size);
+}