Home | History | Annotate | Download | only in views
      1 // Copyright (c) 2011 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 "chrome/browser/ui/views/fullscreen_exit_bubble.h"
      6 
      7 #include "base/utf_string_conversions.h"
      8 #include "chrome/app/chrome_command_ids.h"
      9 #include "grit/generated_resources.h"
     10 #include "ui/base/animation/slide_animation.h"
     11 #include "ui/base/keycodes/keyboard_codes.h"
     12 #include "ui/base/l10n/l10n_util.h"
     13 #include "ui/base/resource/resource_bundle.h"
     14 #include "ui/gfx/canvas_skia.h"
     15 #include "views/screen.h"
     16 #include "views/widget/root_view.h"
     17 #include "views/window/window.h"
     18 
     19 #if defined(OS_WIN)
     20 #include "ui/base/l10n/l10n_util_win.h"
     21 #include "views/widget/widget_win.h"
     22 #elif defined(OS_LINUX)
     23 #include "views/widget/widget_gtk.h"
     24 #endif
     25 
     26 // FullscreenExitView ----------------------------------------------------------
     27 
     28 class FullscreenExitBubble::FullscreenExitView : public views::View {
     29  public:
     30   FullscreenExitView(FullscreenExitBubble* bubble,
     31                      const std::wstring& accelerator);
     32   virtual ~FullscreenExitView();
     33 
     34   // views::View
     35   virtual gfx::Size GetPreferredSize();
     36 
     37  private:
     38   static const int kPaddingPixels;  // Number of pixels around all sides of link
     39 
     40   // views::View
     41   virtual void Layout();
     42   virtual void OnPaint(gfx::Canvas* canvas);
     43 
     44   // Clickable hint text to show in the bubble.
     45   views::Link link_;
     46 };
     47 
     48 const int FullscreenExitBubble::FullscreenExitView::kPaddingPixels = 8;
     49 
     50 FullscreenExitBubble::FullscreenExitView::FullscreenExitView(
     51     FullscreenExitBubble* bubble,
     52     const std::wstring& accelerator) {
     53   link_.set_parent_owned(false);
     54 #if !defined(OS_CHROMEOS)
     55   link_.SetText(
     56       UTF16ToWide(l10n_util::GetStringFUTF16(IDS_EXIT_FULLSCREEN_MODE,
     57                                              WideToUTF16(accelerator))));
     58 #else
     59   link_.SetText(
     60       UTF16ToWide(l10n_util::GetStringUTF16(IDS_EXIT_FULLSCREEN_MODE)));
     61 #endif
     62   link_.SetController(bubble);
     63   link_.SetFont(ResourceBundle::GetSharedInstance().GetFont(
     64       ResourceBundle::LargeFont));
     65   link_.SetNormalColor(SK_ColorWHITE);
     66   link_.SetHighlightedColor(SK_ColorWHITE);
     67   AddChildView(&link_);
     68 }
     69 
     70 FullscreenExitBubble::FullscreenExitView::~FullscreenExitView() {
     71 }
     72 
     73 gfx::Size FullscreenExitBubble::FullscreenExitView::GetPreferredSize() {
     74   gfx::Size preferred_size(link_.GetPreferredSize());
     75   preferred_size.Enlarge(kPaddingPixels * 2, kPaddingPixels * 2);
     76   return preferred_size;
     77 }
     78 
     79 void FullscreenExitBubble::FullscreenExitView::Layout() {
     80   gfx::Size link_preferred_size(link_.GetPreferredSize());
     81   link_.SetBounds(kPaddingPixels,
     82                   height() - kPaddingPixels - link_preferred_size.height(),
     83                   link_preferred_size.width(), link_preferred_size.height());
     84 }
     85 
     86 void FullscreenExitBubble::FullscreenExitView::OnPaint(gfx::Canvas* canvas) {
     87   // Create a round-bottomed rect to fill the whole View.
     88   SkRect rect;
     89   SkScalar padding = SkIntToScalar(kPaddingPixels);
     90   // The "-padding" top coordinate ensures that the rect is always tall enough
     91   // to contain the complete rounded corner radius.  If we set this to 0, as the
     92   // popup slides offscreen (in reality, squishes to 0 height), the corners will
     93   // flatten out as the height becomes less than the corner radius.
     94   rect.set(0, -padding, SkIntToScalar(width()), SkIntToScalar(height()));
     95   SkScalar rad[8] = { 0, 0, 0, 0, padding, padding, padding, padding };
     96   SkPath path;
     97   path.addRoundRect(rect, rad, SkPath::kCW_Direction);
     98 
     99   // Fill it black.
    100   SkPaint paint;
    101   paint.setStyle(SkPaint::kFill_Style);
    102   paint.setFlags(SkPaint::kAntiAlias_Flag);
    103   paint.setColor(SK_ColorBLACK);
    104   canvas->AsCanvasSkia()->drawPath(path, paint);
    105 }
    106 
    107 // FullscreenExitBubble --------------------------------------------------------
    108 
    109 const double FullscreenExitBubble::kOpacity = 0.7;
    110 const int FullscreenExitBubble::kInitialDelayMs = 2300;
    111 const int FullscreenExitBubble::kIdleTimeMs = 2300;
    112 const int FullscreenExitBubble::kPositionCheckHz = 10;
    113 const int FullscreenExitBubble::kSlideInRegionHeightPx = 4;
    114 const int FullscreenExitBubble::kSlideInDurationMs = 350;
    115 const int FullscreenExitBubble::kSlideOutDurationMs = 700;
    116 
    117 FullscreenExitBubble::FullscreenExitBubble(
    118     views::Widget* frame,
    119     CommandUpdater::CommandUpdaterDelegate* delegate)
    120     : root_view_(frame->GetRootView()),
    121       delegate_(delegate),
    122       popup_(NULL),
    123       size_animation_(new ui::SlideAnimation(this)) {
    124   size_animation_->Reset(1);
    125 
    126   // Create the contents view.
    127   views::Accelerator accelerator(ui::VKEY_UNKNOWN, false, false, false);
    128   bool got_accelerator = frame->GetAccelerator(IDC_FULLSCREEN, &accelerator);
    129   DCHECK(got_accelerator);
    130   view_ = new FullscreenExitView(
    131       this, UTF16ToWideHack(accelerator.GetShortcutText()));
    132 
    133   // Initialize the popup.
    134   views::Widget::CreateParams params(views::Widget::CreateParams::TYPE_POPUP);
    135   params.transparent = true;
    136   params.can_activate = false;
    137   params.delete_on_destroy = false;
    138   popup_ = views::Widget::CreateWidget(params);
    139   popup_->SetOpacity(static_cast<unsigned char>(0xff * kOpacity));
    140   popup_->Init(frame->GetNativeView(), GetPopupRect(false));
    141   popup_->SetContentsView(view_);
    142   popup_->Show();  // This does not activate the popup.
    143 
    144   // Start the initial delay timer and begin watching the mouse.
    145   initial_delay_.Start(base::TimeDelta::FromMilliseconds(kInitialDelayMs), this,
    146                        &FullscreenExitBubble::CheckMousePosition);
    147   gfx::Point cursor_pos = views::Screen::GetCursorScreenPoint();
    148   last_mouse_pos_ = cursor_pos;
    149   views::View::ConvertPointToView(NULL, root_view_, &last_mouse_pos_);
    150   mouse_position_checker_.Start(
    151       base::TimeDelta::FromMilliseconds(1000 / kPositionCheckHz), this,
    152       &FullscreenExitBubble::CheckMousePosition);
    153 }
    154 
    155 FullscreenExitBubble::~FullscreenExitBubble() {
    156   // This is tricky.  We may be in an ATL message handler stack, in which case
    157   // the popup cannot be deleted yet.  We also can't blindly use
    158   // set_delete_on_destroy(true) on the popup to delete it when it closes,
    159   // because if the user closed the last tab while in fullscreen mode, Windows
    160   // has already destroyed the popup HWND by the time we get here, and thus
    161   // either the popup will already have been deleted (if we set this in our
    162   // constructor) or the popup will never get another OnFinalMessage() call (if
    163   // not, as currently).  So instead, we tell the popup to synchronously hide,
    164   // and then asynchronously close and delete itself.
    165   popup_->Close();
    166   MessageLoop::current()->DeleteSoon(FROM_HERE, popup_);
    167 }
    168 
    169 void FullscreenExitBubble::LinkActivated(views::Link* source, int event_flags) {
    170   delegate_->ExecuteCommand(IDC_FULLSCREEN);
    171 }
    172 
    173 void FullscreenExitBubble::AnimationProgressed(
    174     const ui::Animation* animation) {
    175   gfx::Rect popup_rect(GetPopupRect(false));
    176   if (popup_rect.IsEmpty()) {
    177     popup_->Hide();
    178   } else {
    179     popup_->SetBounds(popup_rect);
    180     popup_->Show();
    181   }
    182 }
    183 void FullscreenExitBubble::AnimationEnded(
    184     const ui::Animation* animation) {
    185   AnimationProgressed(animation);
    186 }
    187 
    188 void FullscreenExitBubble::CheckMousePosition() {
    189   // Desired behavior:
    190   //
    191   // +------------+-----------------------------+------------+
    192   // | _  _  _  _ | Exit full screen mode (F11) | _  _  _  _ |  Slide-in region
    193   // | _  _  _  _ \_____________________________/ _  _  _  _ |  Neutral region
    194   // |                                                       |  Slide-out region
    195   // :                                                       :
    196   //
    197   // * If app is not active, we hide the popup.
    198   // * If the mouse is offscreen or in the slide-out region, we hide the popup.
    199   // * If the mouse goes idle, we hide the popup.
    200   // * If the mouse is in the slide-in-region and not idle, we show the popup.
    201   // * If the mouse is in the neutral region and not idle, and the popup is
    202   //   currently sliding out, we show it again.  This facilitates users
    203   //   correcting us if they try to mouse horizontally towards the popup and
    204   //   unintentionally drop too low.
    205   // * Otherwise, we do nothing, because the mouse is in the neutral region and
    206   //   either the popup is hidden or the mouse is not idle, so we don't want to
    207   //   change anything's state.
    208 
    209   gfx::Point cursor_pos = views::Screen::GetCursorScreenPoint();
    210   gfx::Point transformed_pos(cursor_pos);
    211   views::View::ConvertPointToView(NULL, root_view_, &transformed_pos);
    212 
    213   // Check to see whether the mouse is idle.
    214   if (transformed_pos != last_mouse_pos_) {
    215     // The mouse moved; reset the idle timer.
    216     idle_timeout_.Stop();  // If the timer isn't running, this is a no-op.
    217     idle_timeout_.Start(base::TimeDelta::FromMilliseconds(kIdleTimeMs), this,
    218                         &FullscreenExitBubble::CheckMousePosition);
    219   }
    220   last_mouse_pos_ = transformed_pos;
    221 
    222   if ((!root_view_->GetWidget()->IsActive()) ||
    223       !root_view_->HitTest(transformed_pos) ||
    224       (cursor_pos.y() >= GetPopupRect(true).bottom()) ||
    225       !idle_timeout_.IsRunning()) {
    226     // The cursor is offscreen, in the slide-out region, or idle.
    227     Hide();
    228   } else if ((cursor_pos.y() < kSlideInRegionHeightPx) ||
    229              (size_animation_->GetCurrentValue() != 0)) {
    230     // The cursor is not idle, and either it's in the slide-in region or it's in
    231     // the neutral region and we're sliding out.
    232     size_animation_->SetSlideDuration(kSlideInDurationMs);
    233     size_animation_->Show();
    234   }
    235 }
    236 
    237 void FullscreenExitBubble::Hide() {
    238   // Allow the bubble to hide if the window is deactivated or our initial delay
    239   // finishes.
    240   if ((!root_view_->GetWidget()->IsActive()) || !initial_delay_.IsRunning()) {
    241     size_animation_->SetSlideDuration(kSlideOutDurationMs);
    242     size_animation_->Hide();
    243   }
    244 }
    245 
    246 gfx::Rect FullscreenExitBubble::GetPopupRect(
    247     bool ignore_animation_state) const {
    248   gfx::Size size(view_->GetPreferredSize());
    249   if (!ignore_animation_state) {
    250     size.set_height(static_cast<int>(static_cast<double>(size.height()) *
    251         size_animation_->GetCurrentValue()));
    252   }
    253   // NOTE: don't use the bounds of the root_view_. On linux changing window
    254   // size is async. Instead we use the size of the screen.
    255   gfx::Rect screen_bounds = views::Screen::GetMonitorAreaNearestWindow(
    256       root_view_->GetWidget()->GetNativeView());
    257   gfx::Point origin(screen_bounds.x() +
    258                     (screen_bounds.width() - size.width()) / 2,
    259                     screen_bounds.y());
    260   return gfx::Rect(origin, size);
    261 }
    262