| OLD | NEW |
| (Empty) |
| 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/message_loop.h" | |
| 8 #include "base/utf_string_conversions.h" | |
| 9 #include "chrome/app/chrome_command_ids.h" | |
| 10 #include "grit/generated_resources.h" | |
| 11 #include "ui/base/animation/slide_animation.h" | |
| 12 #include "ui/base/keycodes/keyboard_codes.h" | |
| 13 #include "ui/base/l10n/l10n_util.h" | |
| 14 #include "ui/base/resource/resource_bundle.h" | |
| 15 #include "ui/gfx/canvas_skia.h" | |
| 16 #include "ui/gfx/screen.h" | |
| 17 #include "views/controls/link.h" | |
| 18 #include "views/widget/widget.h" | |
| 19 | |
| 20 #if defined(OS_WIN) | |
| 21 #include "ui/base/l10n/l10n_util_win.h" | |
| 22 #endif | |
| 23 | |
| 24 // FullscreenExitView ---------------------------------------------------------- | |
| 25 | |
| 26 class FullscreenExitBubble::FullscreenExitView : public views::View { | |
| 27 public: | |
| 28 FullscreenExitView(FullscreenExitBubble* bubble, | |
| 29 const std::wstring& accelerator); | |
| 30 virtual ~FullscreenExitView(); | |
| 31 | |
| 32 // views::View | |
| 33 virtual gfx::Size GetPreferredSize(); | |
| 34 | |
| 35 private: | |
| 36 static const int kPaddingPixels; // Number of pixels around all sides of link | |
| 37 | |
| 38 // views::View | |
| 39 virtual void Layout(); | |
| 40 virtual void OnPaint(gfx::Canvas* canvas); | |
| 41 | |
| 42 // Clickable hint text to show in the bubble. | |
| 43 views::Link link_; | |
| 44 }; | |
| 45 | |
| 46 const int FullscreenExitBubble::FullscreenExitView::kPaddingPixels = 8; | |
| 47 | |
| 48 FullscreenExitBubble::FullscreenExitView::FullscreenExitView( | |
| 49 FullscreenExitBubble* bubble, | |
| 50 const std::wstring& accelerator) { | |
| 51 link_.set_parent_owned(false); | |
| 52 #if !defined(OS_CHROMEOS) | |
| 53 link_.SetText( | |
| 54 UTF16ToWide(l10n_util::GetStringFUTF16(IDS_EXIT_FULLSCREEN_MODE, | |
| 55 WideToUTF16(accelerator)))); | |
| 56 #else | |
| 57 link_.SetText( | |
| 58 UTF16ToWide(l10n_util::GetStringUTF16(IDS_EXIT_FULLSCREEN_MODE))); | |
| 59 #endif | |
| 60 link_.set_listener(bubble); | |
| 61 link_.SetFont(ResourceBundle::GetSharedInstance().GetFont( | |
| 62 ResourceBundle::LargeFont)); | |
| 63 link_.SetNormalColor(SK_ColorWHITE); | |
| 64 link_.SetHighlightedColor(SK_ColorWHITE); | |
| 65 AddChildView(&link_); | |
| 66 } | |
| 67 | |
| 68 FullscreenExitBubble::FullscreenExitView::~FullscreenExitView() { | |
| 69 } | |
| 70 | |
| 71 gfx::Size FullscreenExitBubble::FullscreenExitView::GetPreferredSize() { | |
| 72 gfx::Size preferred_size(link_.GetPreferredSize()); | |
| 73 preferred_size.Enlarge(kPaddingPixels * 2, kPaddingPixels * 2); | |
| 74 return preferred_size; | |
| 75 } | |
| 76 | |
| 77 void FullscreenExitBubble::FullscreenExitView::Layout() { | |
| 78 gfx::Size link_preferred_size(link_.GetPreferredSize()); | |
| 79 link_.SetBounds(kPaddingPixels, | |
| 80 height() - kPaddingPixels - link_preferred_size.height(), | |
| 81 link_preferred_size.width(), link_preferred_size.height()); | |
| 82 } | |
| 83 | |
| 84 void FullscreenExitBubble::FullscreenExitView::OnPaint(gfx::Canvas* canvas) { | |
| 85 // Create a round-bottomed rect to fill the whole View. | |
| 86 SkRect rect; | |
| 87 SkScalar padding = SkIntToScalar(kPaddingPixels); | |
| 88 // The "-padding" top coordinate ensures that the rect is always tall enough | |
| 89 // to contain the complete rounded corner radius. If we set this to 0, as the | |
| 90 // popup slides offscreen (in reality, squishes to 0 height), the corners will | |
| 91 // flatten out as the height becomes less than the corner radius. | |
| 92 rect.set(0, -padding, SkIntToScalar(width()), SkIntToScalar(height())); | |
| 93 SkScalar rad[8] = { 0, 0, 0, 0, padding, padding, padding, padding }; | |
| 94 SkPath path; | |
| 95 path.addRoundRect(rect, rad, SkPath::kCW_Direction); | |
| 96 | |
| 97 // Fill it black. | |
| 98 SkPaint paint; | |
| 99 paint.setStyle(SkPaint::kFill_Style); | |
| 100 paint.setFlags(SkPaint::kAntiAlias_Flag); | |
| 101 paint.setColor(SK_ColorBLACK); | |
| 102 canvas->AsCanvasSkia()->drawPath(path, paint); | |
| 103 } | |
| 104 | |
| 105 // FullscreenExitBubble -------------------------------------------------------- | |
| 106 | |
| 107 const double FullscreenExitBubble::kOpacity = 0.7; | |
| 108 const int FullscreenExitBubble::kInitialDelayMs = 2300; | |
| 109 const int FullscreenExitBubble::kIdleTimeMs = 2300; | |
| 110 const int FullscreenExitBubble::kPositionCheckHz = 10; | |
| 111 const int FullscreenExitBubble::kSlideInRegionHeightPx = 4; | |
| 112 const int FullscreenExitBubble::kSlideInDurationMs = 350; | |
| 113 const int FullscreenExitBubble::kSlideOutDurationMs = 700; | |
| 114 | |
| 115 FullscreenExitBubble::FullscreenExitBubble( | |
| 116 views::Widget* frame, | |
| 117 CommandUpdater::CommandUpdaterDelegate* delegate) | |
| 118 : root_view_(frame->GetRootView()), | |
| 119 delegate_(delegate), | |
| 120 popup_(NULL), | |
| 121 size_animation_(new ui::SlideAnimation(this)) { | |
| 122 size_animation_->Reset(1); | |
| 123 | |
| 124 // Create the contents view. | |
| 125 views::Accelerator accelerator(ui::VKEY_UNKNOWN, false, false, false); | |
| 126 bool got_accelerator = frame->GetAccelerator(IDC_FULLSCREEN, &accelerator); | |
| 127 DCHECK(got_accelerator); | |
| 128 view_ = new FullscreenExitView( | |
| 129 this, UTF16ToWideHack(accelerator.GetShortcutText())); | |
| 130 | |
| 131 // Initialize the popup. | |
| 132 popup_ = new views::Widget; | |
| 133 views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP); | |
| 134 params.transparent = true; | |
| 135 params.can_activate = false; | |
| 136 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; | |
| 137 params.parent = frame->GetNativeView(); | |
| 138 params.bounds = GetPopupRect(false); | |
| 139 popup_->Init(params); | |
| 140 popup_->SetContentsView(view_); | |
| 141 popup_->SetOpacity(static_cast<unsigned char>(0xff * kOpacity)); | |
| 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 = gfx::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 set the popup's ownership | |
| 158 // model to NATIVE_WIDGET_OWNS_WIDGET because if the user closed the last tab | |
| 159 // while in fullscreen mode, Windows has already destroyed the popup HWND by | |
| 160 // the time we get here, and thus either the popup will already have been | |
| 161 // deleted (if we set this in our constructor) or the popup will never get | |
| 162 // another OnFinalMessage() call (if not, as currently). So instead, we tell | |
| 163 // the popup to synchronously hide, and then asynchronously close and delete | |
| 164 // itself. | |
| 165 popup_->Close(); | |
| 166 MessageLoop::current()->DeleteSoon(FROM_HERE, popup_); | |
| 167 } | |
| 168 | |
| 169 void FullscreenExitBubble::LinkClicked(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 = gfx::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 = gfx::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 } | |
| OLD | NEW |