diff options
Diffstat (limited to 'chrome/browser/ui/views/fullscreen_exit_bubble.cc')
-rw-r--r-- | chrome/browser/ui/views/fullscreen_exit_bubble.cc | 288 |
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); +} |