OLD | NEW |
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/ui/views/fullscreen_exit_bubble.h" | 5 #include "chrome/browser/ui/views/fullscreen_exit_bubble_views.h" |
6 | 6 |
7 #include "base/message_loop.h" | 7 #include "base/message_loop.h" |
8 #include "base/utf_string_conversions.h" | 8 #include "base/utf_string_conversions.h" |
9 #include "chrome/app/chrome_command_ids.h" | 9 #include "chrome/app/chrome_command_ids.h" |
10 #include "grit/generated_resources.h" | 10 #include "grit/generated_resources.h" |
11 #include "ui/base/animation/slide_animation.h" | 11 #include "ui/base/animation/slide_animation.h" |
12 #include "ui/base/keycodes/keyboard_codes.h" | 12 #include "ui/base/keycodes/keyboard_codes.h" |
13 #include "ui/base/l10n/l10n_util.h" | 13 #include "ui/base/l10n/l10n_util.h" |
14 #include "ui/base/resource/resource_bundle.h" | 14 #include "ui/base/resource/resource_bundle.h" |
15 #include "ui/gfx/canvas_skia.h" | 15 #include "ui/gfx/canvas_skia.h" |
16 #include "ui/gfx/screen.h" | 16 #include "ui/gfx/screen.h" |
17 #include "views/controls/link.h" | 17 #include "views/controls/link.h" |
18 #include "views/widget/widget.h" | 18 #include "views/widget/widget.h" |
19 | 19 |
20 #if defined(OS_WIN) | 20 #if defined(OS_WIN) |
21 #include "ui/base/l10n/l10n_util_win.h" | 21 #include "ui/base/l10n/l10n_util_win.h" |
22 #endif | 22 #endif |
23 | 23 |
24 // FullscreenExitView ---------------------------------------------------------- | 24 // FullscreenExitView ---------------------------------------------------------- |
25 | 25 |
26 class FullscreenExitBubble::FullscreenExitView : public views::View { | 26 class FullscreenExitBubbleViews::FullscreenExitView : public views::View { |
27 public: | 27 public: |
28 FullscreenExitView(FullscreenExitBubble* bubble, | 28 FullscreenExitView(FullscreenExitBubbleViews* bubble, |
29 const std::wstring& accelerator); | 29 const std::wstring& accelerator); |
30 virtual ~FullscreenExitView(); | 30 virtual ~FullscreenExitView(); |
31 | 31 |
32 // views::View | 32 // views::View |
33 virtual gfx::Size GetPreferredSize(); | 33 virtual gfx::Size GetPreferredSize(); |
34 | 34 |
35 private: | 35 private: |
36 static const int kPaddingPixels; // Number of pixels around all sides of link | |
37 | |
38 // views::View | 36 // views::View |
39 virtual void Layout(); | 37 virtual void Layout(); |
40 virtual void OnPaint(gfx::Canvas* canvas); | 38 virtual void OnPaint(gfx::Canvas* canvas); |
41 | 39 |
42 // Clickable hint text to show in the bubble. | 40 // Clickable hint text to show in the bubble. |
43 views::Link link_; | 41 views::Link link_; |
44 }; | 42 }; |
45 | 43 |
46 const int FullscreenExitBubble::FullscreenExitView::kPaddingPixels = 8; | 44 FullscreenExitBubbleViews::FullscreenExitView::FullscreenExitView( |
47 | 45 FullscreenExitBubbleViews* bubble, |
48 FullscreenExitBubble::FullscreenExitView::FullscreenExitView( | |
49 FullscreenExitBubble* bubble, | |
50 const std::wstring& accelerator) { | 46 const std::wstring& accelerator) { |
51 link_.set_parent_owned(false); | 47 link_.set_parent_owned(false); |
52 #if !defined(OS_CHROMEOS) | 48 #if !defined(OS_CHROMEOS) |
53 link_.SetText( | 49 link_.SetText( |
54 UTF16ToWide(l10n_util::GetStringFUTF16(IDS_EXIT_FULLSCREEN_MODE, | 50 UTF16ToWide(l10n_util::GetStringFUTF16(IDS_EXIT_FULLSCREEN_MODE, |
55 WideToUTF16(accelerator)))); | 51 WideToUTF16(accelerator)))); |
56 #else | 52 #else |
57 link_.SetText( | 53 link_.SetText( |
58 UTF16ToWide(l10n_util::GetStringUTF16(IDS_EXIT_FULLSCREEN_MODE))); | 54 UTF16ToWide(l10n_util::GetStringUTF16(IDS_EXIT_FULLSCREEN_MODE))); |
59 #endif | 55 #endif |
60 link_.set_listener(bubble); | 56 link_.set_listener(bubble); |
61 link_.SetFont(ResourceBundle::GetSharedInstance().GetFont( | 57 link_.SetFont(ResourceBundle::GetSharedInstance().GetFont( |
62 ResourceBundle::LargeFont)); | 58 ResourceBundle::LargeFont)); |
63 link_.SetNormalColor(SK_ColorWHITE); | 59 link_.SetNormalColor(SK_ColorWHITE); |
64 link_.SetHighlightedColor(SK_ColorWHITE); | 60 link_.SetHighlightedColor(SK_ColorWHITE); |
65 AddChildView(&link_); | 61 AddChildView(&link_); |
66 } | 62 } |
67 | 63 |
68 FullscreenExitBubble::FullscreenExitView::~FullscreenExitView() { | 64 FullscreenExitBubbleViews::FullscreenExitView::~FullscreenExitView() { |
69 } | 65 } |
70 | 66 |
71 gfx::Size FullscreenExitBubble::FullscreenExitView::GetPreferredSize() { | 67 gfx::Size FullscreenExitBubbleViews::FullscreenExitView::GetPreferredSize() { |
72 gfx::Size preferred_size(link_.GetPreferredSize()); | 68 gfx::Size preferred_size(link_.GetPreferredSize()); |
73 preferred_size.Enlarge(kPaddingPixels * 2, kPaddingPixels * 2); | 69 preferred_size.Enlarge(kPaddingPx * 2, kPaddingPx * 2); |
74 return preferred_size; | 70 return preferred_size; |
75 } | 71 } |
76 | 72 |
77 void FullscreenExitBubble::FullscreenExitView::Layout() { | 73 void FullscreenExitBubbleViews::FullscreenExitView::Layout() { |
78 gfx::Size link_preferred_size(link_.GetPreferredSize()); | 74 gfx::Size link_preferred_size(link_.GetPreferredSize()); |
79 link_.SetBounds(kPaddingPixels, | 75 link_.SetBounds(kPaddingPx, |
80 height() - kPaddingPixels - link_preferred_size.height(), | 76 height() - kPaddingPx - link_preferred_size.height(), |
81 link_preferred_size.width(), link_preferred_size.height()); | 77 link_preferred_size.width(), link_preferred_size.height()); |
82 } | 78 } |
83 | 79 |
84 void FullscreenExitBubble::FullscreenExitView::OnPaint(gfx::Canvas* canvas) { | 80 void FullscreenExitBubbleViews::FullscreenExitView::OnPaint(gfx::Canvas* canvas)
{ |
85 // Create a round-bottomed rect to fill the whole View. | 81 // Create a round-bottomed rect to fill the whole View. |
86 SkRect rect; | 82 SkRect rect; |
87 SkScalar padding = SkIntToScalar(kPaddingPixels); | 83 SkScalar padding = SkIntToScalar(kPaddingPx); |
88 // The "-padding" top coordinate ensures that the rect is always tall enough | 84 // 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 | 85 // 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 | 86 // popup slides offscreen (in reality, squishes to 0 height), the corners will |
91 // flatten out as the height becomes less than the corner radius. | 87 // flatten out as the height becomes less than the corner radius. |
92 rect.set(0, -padding, SkIntToScalar(width()), SkIntToScalar(height())); | 88 rect.set(0, -padding, SkIntToScalar(width()), SkIntToScalar(height())); |
93 SkScalar rad[8] = { 0, 0, 0, 0, padding, padding, padding, padding }; | 89 SkScalar rad[8] = { 0, 0, 0, 0, padding, padding, padding, padding }; |
94 SkPath path; | 90 SkPath path; |
95 path.addRoundRect(rect, rad, SkPath::kCW_Direction); | 91 path.addRoundRect(rect, rad, SkPath::kCW_Direction); |
96 | 92 |
97 // Fill it black. | 93 // Fill it black. |
98 SkPaint paint; | 94 SkPaint paint; |
99 paint.setStyle(SkPaint::kFill_Style); | 95 paint.setStyle(SkPaint::kFill_Style); |
100 paint.setFlags(SkPaint::kAntiAlias_Flag); | 96 paint.setFlags(SkPaint::kAntiAlias_Flag); |
101 paint.setColor(SK_ColorBLACK); | 97 paint.setColor(SK_ColorBLACK); |
102 canvas->AsCanvasSkia()->drawPath(path, paint); | 98 canvas->AsCanvasSkia()->drawPath(path, paint); |
103 } | 99 } |
104 | 100 |
105 // FullscreenExitBubble -------------------------------------------------------- | 101 // FullscreenExitBubbleViews --------------------------------------------------- |
106 | 102 |
107 const double FullscreenExitBubble::kOpacity = 0.7; | 103 FullscreenExitBubbleViews::FullscreenExitBubbleViews( |
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, | 104 views::Widget* frame, |
117 CommandUpdater::CommandUpdaterDelegate* delegate) | 105 CommandUpdater::CommandUpdaterDelegate* delegate) |
118 : root_view_(frame->GetRootView()), | 106 : FullscreenExitBubble(delegate), |
119 delegate_(delegate), | 107 root_view_(frame->GetRootView()), |
120 popup_(NULL), | 108 popup_(NULL), |
121 size_animation_(new ui::SlideAnimation(this)) { | 109 size_animation_(new ui::SlideAnimation(this)) { |
122 size_animation_->Reset(1); | 110 size_animation_->Reset(1); |
123 | 111 |
124 // Create the contents view. | 112 // Create the contents view. |
125 views::Accelerator accelerator(ui::VKEY_UNKNOWN, false, false, false); | 113 views::Accelerator accelerator(ui::VKEY_UNKNOWN, false, false, false); |
126 bool got_accelerator = frame->GetAccelerator(IDC_FULLSCREEN, &accelerator); | 114 bool got_accelerator = frame->GetAccelerator(IDC_FULLSCREEN, &accelerator); |
127 DCHECK(got_accelerator); | 115 DCHECK(got_accelerator); |
128 view_ = new FullscreenExitView( | 116 view_ = new FullscreenExitView( |
129 this, UTF16ToWideHack(accelerator.GetShortcutText())); | 117 this, UTF16ToWideHack(accelerator.GetShortcutText())); |
130 | 118 |
131 // Initialize the popup. | 119 // Initialize the popup. |
132 popup_ = new views::Widget; | 120 popup_ = new views::Widget; |
133 views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP); | 121 views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP); |
134 params.transparent = true; | 122 params.transparent = true; |
135 params.can_activate = false; | 123 params.can_activate = false; |
136 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; | 124 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; |
137 params.parent = frame->GetNativeView(); | 125 params.parent = frame->GetNativeView(); |
138 params.bounds = GetPopupRect(false); | 126 params.bounds = GetPopupRect(false); |
139 popup_->Init(params); | 127 popup_->Init(params); |
140 popup_->SetContentsView(view_); | 128 popup_->SetContentsView(view_); |
141 popup_->SetOpacity(static_cast<unsigned char>(0xff * kOpacity)); | 129 popup_->SetOpacity(static_cast<unsigned char>(0xff * kOpacity)); |
142 popup_->Show(); // This does not activate the popup. | 130 popup_->Show(); // This does not activate the popup. |
143 | 131 |
144 // Start the initial delay timer and begin watching the mouse. | 132 StartWatchingMouse(); |
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 } | 133 } |
154 | 134 |
155 FullscreenExitBubble::~FullscreenExitBubble() { | 135 FullscreenExitBubbleViews::~FullscreenExitBubbleViews() { |
156 // This is tricky. We may be in an ATL message handler stack, in which case | 136 // 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 | 137 // 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 | 138 // 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 | 139 // 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 | 140 // 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 | 141 // 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 | 142 // another OnFinalMessage() call (if not, as currently). So instead, we tell |
163 // the popup to synchronously hide, and then asynchronously close and delete | 143 // the popup to synchronously hide, and then asynchronously close and delete |
164 // itself. | 144 // itself. |
165 popup_->Close(); | 145 popup_->Close(); |
166 MessageLoop::current()->DeleteSoon(FROM_HERE, popup_); | 146 MessageLoop::current()->DeleteSoon(FROM_HERE, popup_); |
167 } | 147 } |
168 | 148 |
169 void FullscreenExitBubble::LinkClicked(views::Link* source, int event_flags) { | 149 void FullscreenExitBubbleViews::LinkClicked( |
170 delegate_->ExecuteCommand(IDC_FULLSCREEN); | 150 views::Link* source, int event_flags) { |
| 151 ToggleFullscreen(); |
171 } | 152 } |
172 | 153 |
173 void FullscreenExitBubble::AnimationProgressed( | 154 void FullscreenExitBubbleViews::AnimationProgressed( |
174 const ui::Animation* animation) { | 155 const ui::Animation* animation) { |
175 gfx::Rect popup_rect(GetPopupRect(false)); | 156 gfx::Rect popup_rect(GetPopupRect(false)); |
176 if (popup_rect.IsEmpty()) { | 157 if (popup_rect.IsEmpty()) { |
177 popup_->Hide(); | 158 popup_->Hide(); |
178 } else { | 159 } else { |
179 popup_->SetBounds(popup_rect); | 160 popup_->SetBounds(popup_rect); |
180 popup_->Show(); | 161 popup_->Show(); |
181 } | 162 } |
182 } | 163 } |
183 void FullscreenExitBubble::AnimationEnded( | 164 void FullscreenExitBubbleViews::AnimationEnded( |
184 const ui::Animation* animation) { | 165 const ui::Animation* animation) { |
185 AnimationProgressed(animation); | 166 AnimationProgressed(animation); |
186 } | 167 } |
187 | 168 |
188 void FullscreenExitBubble::CheckMousePosition() { | 169 void FullscreenExitBubbleViews::Hide() { |
189 // Desired behavior: | 170 size_animation_->SetSlideDuration(kSlideOutDurationMs); |
190 // | 171 size_animation_->Hide(); |
191 // +------------+-----------------------------+------------+ | 172 } |
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 | 173 |
| 174 void FullscreenExitBubbleViews::Show() { |
| 175 size_animation_->SetSlideDuration(kSlideInDurationMs); |
| 176 size_animation_->Show(); |
| 177 } |
| 178 |
| 179 bool FullscreenExitBubbleViews::IsAnimating() { |
| 180 return size_animation_->GetCurrentValue() != 0; |
| 181 } |
| 182 |
| 183 bool FullscreenExitBubbleViews::IsWindowActive() { |
| 184 return root_view_->GetWidget()->IsActive(); |
| 185 } |
| 186 |
| 187 bool FullscreenExitBubbleViews::WindowContainsPoint(gfx::Point pos) { |
| 188 return root_view_->HitTest(pos); |
| 189 } |
| 190 |
| 191 gfx::Point FullscreenExitBubbleViews::GetCursorScreenPoint() { |
209 gfx::Point cursor_pos = gfx::Screen::GetCursorScreenPoint(); | 192 gfx::Point cursor_pos = gfx::Screen::GetCursorScreenPoint(); |
210 gfx::Point transformed_pos(cursor_pos); | 193 gfx::Point transformed_pos(cursor_pos); |
211 views::View::ConvertPointToView(NULL, root_view_, &transformed_pos); | 194 views::View::ConvertPointToView(NULL, root_view_, &transformed_pos); |
212 | 195 return transformed_pos; |
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 } | 196 } |
236 | 197 |
237 void FullscreenExitBubble::Hide() { | 198 gfx::Rect FullscreenExitBubbleViews::GetPopupRect( |
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 { | 199 bool ignore_animation_state) const { |
248 gfx::Size size(view_->GetPreferredSize()); | 200 gfx::Size size(view_->GetPreferredSize()); |
249 if (!ignore_animation_state) { | 201 if (!ignore_animation_state) { |
250 size.set_height(static_cast<int>(static_cast<double>(size.height()) * | 202 size.set_height(static_cast<int>(static_cast<double>(size.height()) * |
251 size_animation_->GetCurrentValue())); | 203 size_animation_->GetCurrentValue())); |
252 } | 204 } |
253 // NOTE: don't use the bounds of the root_view_. On linux changing window | 205 // 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. | 206 // size is async. Instead we use the size of the screen. |
255 gfx::Rect screen_bounds = gfx::Screen::GetMonitorAreaNearestWindow( | 207 gfx::Rect screen_bounds = gfx::Screen::GetMonitorAreaNearestWindow( |
256 root_view_->GetWidget()->GetNativeView()); | 208 root_view_->GetWidget()->GetNativeView()); |
257 gfx::Point origin(screen_bounds.x() + | 209 gfx::Point origin(screen_bounds.x() + |
258 (screen_bounds.width() - size.width()) / 2, | 210 (screen_bounds.width() - size.width()) / 2, |
259 screen_bounds.y()); | 211 screen_bounds.y()); |
260 return gfx::Rect(origin, size); | 212 return gfx::Rect(origin, size); |
261 } | 213 } |
OLD | NEW |