1 // Copyright 2013 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 "ash/frame/caption_buttons/frame_caption_button_container_view.h"
6
7 #include <cmath>
8 #include <map>
9
10 #include "ash/ash_switches.h"
11 #include "ash/frame/caption_buttons/frame_caption_button.h"
12 #include "ash/frame/caption_buttons/frame_size_button.h"
13 #include "ash/metrics/user_metrics_recorder.h"
14 #include "ash/shell.h"
15 #include "ash/wm/maximize_mode/maximize_mode_controller.h"
16 #include "ui/base/hit_test.h"
17 #include "ui/base/l10n/l10n_util.h"
18 #include "ui/compositor/scoped_animation_duration_scale_mode.h"
19 #include "ui/gfx/animation/slide_animation.h"
20 #include "ui/gfx/animation/tween.h"
21 #include "ui/gfx/canvas.h"
22 #include "ui/gfx/insets.h"
23 #include "ui/gfx/point.h"
24 #include "ui/strings/grit/ui_strings.h" // Accessibility names
25 #include "ui/views/widget/widget.h"
26 #include "ui/views/widget/widget_delegate.h"
27
28 namespace ash {
29
30 namespace {
31
32 // Duration of the animation of the position of |minimize_button_|.
33 const int kPositionAnimationDurationMs = 500;
34
35 // Duration of the animation of the alpha of |size_button_|.
36 const int kAlphaAnimationDurationMs = 250;
37
38 // Delay during |maximize_mode_animation_| hide to wait before beginning to
39 // animate the position of |minimize_button_|.
40 const int kHidePositionDelayMs = 100;
41
42 // Duration of |maximize_mode_animation_| hiding.
43 // Hiding size button 250
44 // |------------------------|
45 // Delay 100 Slide minimize button 500
46 // |---------|-------------------------------------------------|
47 const int kHideAnimationDurationMs =
48 kHidePositionDelayMs + kPositionAnimationDurationMs;
49
50 // Delay during |maximize_mode_animation_| show to wait before beginning to
51 // animate the alpha of |size_button_|.
52 const int kShowAnimationAlphaDelayMs = 100;
53
54 // Duration of |maximize_mode_animation_| showing.
55 // Slide minimize button 500
56 // |-------------------------------------------------|
57 // Delay 100 Show size button 250
58 // |---------|-----------------------|
59 const int kShowAnimationDurationMs = kPositionAnimationDurationMs;
60
61 // Value of |maximize_mode_animation_| showing to begin animating alpha of
62 // |size_button_|.
SizeButtonShowStartValue()63 float SizeButtonShowStartValue() {
64 return static_cast<float>(kShowAnimationAlphaDelayMs)
65 / kShowAnimationDurationMs;
66 }
67
68 // Amount of |maximize_mode_animation_| showing to animate the alpha of
69 // |size_button_|.
SizeButtonShowDuration()70 float SizeButtonShowDuration() {
71 return static_cast<float>(kAlphaAnimationDurationMs)
72 / kShowAnimationDurationMs;
73 }
74
75 // Amount of |maximize_mode_animation_| hiding to animate the alpha of
76 // |size_button_|.
SizeButtonHideDuration()77 float SizeButtonHideDuration() {
78 return static_cast<float>(kAlphaAnimationDurationMs)
79 / kHideAnimationDurationMs;
80 }
81
82 // Value of |maximize_mode_animation_| hiding to begin animating the position of
83 // |minimize_button_|.
HidePositionStartValue()84 float HidePositionStartValue() {
85 return 1.0f - static_cast<float>(kHidePositionDelayMs)
86 / kHideAnimationDurationMs;
87 }
88
89 // Converts |point| from |src| to |dst| and hittests against |dst|.
ConvertPointToViewAndHitTest(const views::View * src,const views::View * dst,const gfx::Point & point)90 bool ConvertPointToViewAndHitTest(const views::View* src,
91 const views::View* dst,
92 const gfx::Point& point) {
93 gfx::Point converted(point);
94 views::View::ConvertPointToTarget(src, dst, &converted);
95 return dst->HitTestPoint(converted);
96 }
97
98 // Bounds animation values to the range 0.0 - 1.0. Allows for mapping of offset
99 // animations to the expected range so that gfx::Tween::CalculateValue() can be
100 // used.
CapAnimationValue(double value)101 double CapAnimationValue(double value) {
102 return std::min(1.0, std::max(0.0, value));
103 }
104
105 } // namespace
106
107 // static
108 const char FrameCaptionButtonContainerView::kViewClassName[] =
109 "FrameCaptionButtonContainerView";
110
FrameCaptionButtonContainerView(views::Widget * frame,MinimizeAllowed minimize_allowed)111 FrameCaptionButtonContainerView::FrameCaptionButtonContainerView(
112 views::Widget* frame,
113 MinimizeAllowed minimize_allowed)
114 : frame_(frame),
115 minimize_button_(NULL),
116 size_button_(NULL),
117 close_button_(NULL) {
118 bool size_button_visibility = ShouldSizeButtonBeVisible();
119 maximize_mode_animation_.reset(new gfx::SlideAnimation(this));
120 maximize_mode_animation_->SetTweenType(gfx::Tween::LINEAR);
121
122 // Ensure animation tracks visibility of size button.
123 if (size_button_visibility)
124 maximize_mode_animation_->Reset(1.0f);
125
126 // Insert the buttons left to right.
127 minimize_button_ = new FrameCaptionButton(this, CAPTION_BUTTON_ICON_MINIMIZE);
128 minimize_button_->SetAccessibleName(
129 l10n_util::GetStringUTF16(IDS_APP_ACCNAME_MINIMIZE));
130 minimize_button_->SetVisible(minimize_allowed == MINIMIZE_ALLOWED);
131 AddChildView(minimize_button_);
132
133 size_button_ = new FrameSizeButton(this, frame, this);
134 size_button_->SetAccessibleName(
135 l10n_util::GetStringUTF16(IDS_APP_ACCNAME_MAXIMIZE));
136 size_button_->SetVisible(size_button_visibility);
137 AddChildView(size_button_);
138
139 close_button_ = new FrameCaptionButton(this, CAPTION_BUTTON_ICON_CLOSE);
140 close_button_->SetAccessibleName(
141 l10n_util::GetStringUTF16(IDS_APP_ACCNAME_CLOSE));
142 AddChildView(close_button_);
143 }
144
~FrameCaptionButtonContainerView()145 FrameCaptionButtonContainerView::~FrameCaptionButtonContainerView() {
146 }
147
EndAnimations()148 void FrameCaptionButtonContainerView::TestApi::EndAnimations() {
149 container_view_->maximize_mode_animation_->End();
150 }
151
SetButtonImages(CaptionButtonIcon icon,int icon_image_id,int inactive_icon_image_id,int hovered_background_image_id,int pressed_background_image_id)152 void FrameCaptionButtonContainerView::SetButtonImages(
153 CaptionButtonIcon icon,
154 int icon_image_id,
155 int inactive_icon_image_id,
156 int hovered_background_image_id,
157 int pressed_background_image_id) {
158 button_icon_id_map_[icon] = ButtonIconIds(icon_image_id,
159 inactive_icon_image_id,
160 hovered_background_image_id,
161 pressed_background_image_id);
162 FrameCaptionButton* buttons[] = {
163 minimize_button_, size_button_, close_button_
164 };
165 for (size_t i = 0; i < arraysize(buttons); ++i) {
166 if (buttons[i]->icon() == icon) {
167 buttons[i]->SetImages(icon,
168 FrameCaptionButton::ANIMATE_NO,
169 icon_image_id,
170 inactive_icon_image_id,
171 hovered_background_image_id,
172 pressed_background_image_id);
173 }
174 }
175 }
176
SetPaintAsActive(bool paint_as_active)177 void FrameCaptionButtonContainerView::SetPaintAsActive(bool paint_as_active) {
178 minimize_button_->set_paint_as_active(paint_as_active);
179 size_button_->set_paint_as_active(paint_as_active);
180 close_button_->set_paint_as_active(paint_as_active);
181 }
182
ResetWindowControls()183 void FrameCaptionButtonContainerView::ResetWindowControls() {
184 SetButtonsToNormal(ANIMATE_NO);
185 }
186
NonClientHitTest(const gfx::Point & point) const187 int FrameCaptionButtonContainerView::NonClientHitTest(
188 const gfx::Point& point) const {
189 if (close_button_->visible() &&
190 ConvertPointToViewAndHitTest(this, close_button_, point)) {
191 return HTCLOSE;
192 } else if (size_button_->visible() &&
193 ConvertPointToViewAndHitTest(this, size_button_, point)) {
194 return HTMAXBUTTON;
195 } else if (minimize_button_->visible() &&
196 ConvertPointToViewAndHitTest(this, minimize_button_, point)) {
197 return HTMINBUTTON;
198 }
199 return HTNOWHERE;
200 }
201
UpdateSizeButtonVisibility()202 void FrameCaptionButtonContainerView::UpdateSizeButtonVisibility() {
203 bool visible = ShouldSizeButtonBeVisible();
204 if (visible) {
205 size_button_->SetVisible(true);
206 maximize_mode_animation_->SetSlideDuration(kShowAnimationDurationMs);
207 maximize_mode_animation_->Show();
208 } else {
209 maximize_mode_animation_->SetSlideDuration(kHideAnimationDurationMs);
210 maximize_mode_animation_->Hide();
211 }
212 }
213
GetPreferredSize() const214 gfx::Size FrameCaptionButtonContainerView::GetPreferredSize() const {
215 int width = 0;
216 for (int i = 0; i < child_count(); ++i) {
217 const views::View* child = child_at(i);
218 if (child->visible())
219 width += child_at(i)->GetPreferredSize().width();
220 }
221 return gfx::Size(width, close_button_->GetPreferredSize().height());
222 }
223
Layout()224 void FrameCaptionButtonContainerView::Layout() {
225 int x = 0;
226 for (int i = 0; i < child_count(); ++i) {
227 views::View* child = child_at(i);
228 if (!child->visible())
229 continue;
230
231 gfx::Size size = child->GetPreferredSize();
232 child->SetBounds(x, 0, size.width(), size.height());
233 x += size.width();
234 }
235 if (maximize_mode_animation_->is_animating()) {
236 AnimationProgressed(maximize_mode_animation_.get());
237 }
238 }
239
GetClassName() const240 const char* FrameCaptionButtonContainerView::GetClassName() const {
241 return kViewClassName;
242 }
243
AnimationEnded(const gfx::Animation * animation)244 void FrameCaptionButtonContainerView::AnimationEnded(
245 const gfx::Animation* animation) {
246 // Ensure that position is calculated at least once.
247 AnimationProgressed(animation);
248
249 double current_value = maximize_mode_animation_->GetCurrentValue();
250 if (current_value == 0.0) {
251 size_button_->SetVisible(false);
252 PreferredSizeChanged();
253 }
254 }
255
AnimationProgressed(const gfx::Animation * animation)256 void FrameCaptionButtonContainerView::AnimationProgressed(
257 const gfx::Animation* animation) {
258 double current_value = animation->GetCurrentValue();
259 int size_alpha = 0;
260 int minimize_x = 0;
261 if (maximize_mode_animation_->IsShowing()) {
262 double scaled_value = CapAnimationValue(
263 (current_value - SizeButtonShowStartValue())
264 / SizeButtonShowDuration());
265 double tweened_value_alpha =
266 gfx::Tween::CalculateValue(gfx::Tween::EASE_OUT,scaled_value);
267 size_alpha = gfx::Tween::LinearIntValueBetween(tweened_value_alpha, 0, 255);
268
269 double tweened_value_slide =
270 gfx::Tween::CalculateValue(gfx::Tween::EASE_OUT, current_value);
271 minimize_x = gfx::Tween::LinearIntValueBetween(tweened_value_slide,
272 size_button_->x(), 0);
273 } else {
274 double scaled_value_alpha = CapAnimationValue(
275 (1.0f - current_value) / SizeButtonHideDuration());
276 double tweened_value_alpha =
277 gfx::Tween::CalculateValue(gfx::Tween::EASE_IN, scaled_value_alpha);
278 size_alpha = gfx::Tween::LinearIntValueBetween(tweened_value_alpha, 255, 0);
279
280 double scaled_value_position = CapAnimationValue(
281 (HidePositionStartValue() - current_value)
282 / HidePositionStartValue());
283 double tweened_value_position =
284 gfx::Tween::CalculateValue(gfx::Tween::EASE_OUT, scaled_value_position);
285 minimize_x = gfx::Tween::LinearIntValueBetween(tweened_value_position, 0,
286 size_button_->x());
287 }
288 size_button_->SetAlpha(size_alpha);
289 minimize_button_->SetX(minimize_x);
290 }
291
SetButtonIcon(FrameCaptionButton * button,CaptionButtonIcon icon,Animate animate)292 void FrameCaptionButtonContainerView::SetButtonIcon(FrameCaptionButton* button,
293 CaptionButtonIcon icon,
294 Animate animate) {
295 // The early return is dependant on |animate| because callers use
296 // SetButtonIcon() with ANIMATE_NO to progress |button|'s crossfade animation
297 // to the end.
298 if (button->icon() == icon &&
299 (animate == ANIMATE_YES || !button->IsAnimatingImageSwap())) {
300 return;
301 }
302
303 FrameCaptionButton::Animate fcb_animate = (animate == ANIMATE_YES) ?
304 FrameCaptionButton::ANIMATE_YES : FrameCaptionButton::ANIMATE_NO;
305 std::map<CaptionButtonIcon, ButtonIconIds>::const_iterator it =
306 button_icon_id_map_.find(icon);
307 if (it != button_icon_id_map_.end()) {
308 button->SetImages(icon,
309 fcb_animate,
310 it->second.icon_image_id,
311 it->second.inactive_icon_image_id,
312 it->second.hovered_background_image_id,
313 it->second.pressed_background_image_id);
314 }
315 }
316
ShouldSizeButtonBeVisible() const317 bool FrameCaptionButtonContainerView::ShouldSizeButtonBeVisible() const {
318 return !Shell::GetInstance()->maximize_mode_controller()->
319 IsMaximizeModeWindowManagerEnabled() &&
320 frame_->widget_delegate()->CanMaximize();
321 }
322
ButtonPressed(views::Button * sender,const ui::Event & event)323 void FrameCaptionButtonContainerView::ButtonPressed(views::Button* sender,
324 const ui::Event& event) {
325 // Abort any animations of the button icons.
326 SetButtonsToNormal(ANIMATE_NO);
327
328 ash::UserMetricsAction action =
329 ash::UMA_WINDOW_MAXIMIZE_BUTTON_CLICK_MINIMIZE;
330 if (sender == minimize_button_) {
331 frame_->Minimize();
332 } else if (sender == size_button_) {
333 if (frame_->IsFullscreen()) { // Can be clicked in immersive fullscreen.
334 frame_->Restore();
335 action = ash::UMA_WINDOW_MAXIMIZE_BUTTON_CLICK_EXIT_FULLSCREEN;
336 } else if (frame_->IsMaximized()) {
337 frame_->Restore();
338 action = ash::UMA_WINDOW_MAXIMIZE_BUTTON_CLICK_RESTORE;
339 } else {
340 frame_->Maximize();
341 action = ash::UMA_WINDOW_MAXIMIZE_BUTTON_CLICK_MAXIMIZE;
342 }
343 } else if (sender == close_button_) {
344 frame_->Close();
345 action = ash::UMA_WINDOW_CLOSE_BUTTON_CLICK;
346 } else {
347 return;
348 }
349 ash::Shell::GetInstance()->metrics()->RecordUserMetricsAction(action);
350 }
351
IsMinimizeButtonVisible() const352 bool FrameCaptionButtonContainerView::IsMinimizeButtonVisible() const {
353 return minimize_button_->visible();
354 }
355
SetButtonsToNormal(Animate animate)356 void FrameCaptionButtonContainerView::SetButtonsToNormal(Animate animate) {
357 SetButtonIcons(CAPTION_BUTTON_ICON_MINIMIZE, CAPTION_BUTTON_ICON_CLOSE,
358 animate);
359 minimize_button_->SetState(views::Button::STATE_NORMAL);
360 size_button_->SetState(views::Button::STATE_NORMAL);
361 close_button_->SetState(views::Button::STATE_NORMAL);
362 }
363
SetButtonIcons(CaptionButtonIcon minimize_button_icon,CaptionButtonIcon close_button_icon,Animate animate)364 void FrameCaptionButtonContainerView::SetButtonIcons(
365 CaptionButtonIcon minimize_button_icon,
366 CaptionButtonIcon close_button_icon,
367 Animate animate) {
368 SetButtonIcon(minimize_button_, minimize_button_icon, animate);
369 SetButtonIcon(close_button_, close_button_icon, animate);
370 }
371
GetButtonClosestTo(const gfx::Point & position_in_screen) const372 const FrameCaptionButton* FrameCaptionButtonContainerView::GetButtonClosestTo(
373 const gfx::Point& position_in_screen) const {
374 // Since the buttons all have the same size, the closest button is the button
375 // with the center point closest to |position_in_screen|.
376 // TODO(pkotwicz): Make the caption buttons not overlap.
377 gfx::Point position(position_in_screen);
378 views::View::ConvertPointFromScreen(this, &position);
379
380 FrameCaptionButton* buttons[] = {
381 minimize_button_, size_button_, close_button_
382 };
383 int min_squared_distance = INT_MAX;
384 FrameCaptionButton* closest_button = NULL;
385 for (size_t i = 0; i < arraysize(buttons); ++i) {
386 FrameCaptionButton* button = buttons[i];
387 if (!button->visible())
388 continue;
389
390 gfx::Point center_point = button->GetLocalBounds().CenterPoint();
391 views::View::ConvertPointToTarget(button, this, ¢er_point);
392 int squared_distance = static_cast<int>(
393 pow(static_cast<double>(position.x() - center_point.x()), 2) +
394 pow(static_cast<double>(position.y() - center_point.y()), 2));
395 if (squared_distance < min_squared_distance) {
396 min_squared_distance = squared_distance;
397 closest_button = button;
398 }
399 }
400 return closest_button;
401 }
402
SetHoveredAndPressedButtons(const FrameCaptionButton * to_hover,const FrameCaptionButton * to_press)403 void FrameCaptionButtonContainerView::SetHoveredAndPressedButtons(
404 const FrameCaptionButton* to_hover,
405 const FrameCaptionButton* to_press) {
406 FrameCaptionButton* buttons[] = {
407 minimize_button_, size_button_, close_button_
408 };
409 for (size_t i = 0; i < arraysize(buttons); ++i) {
410 FrameCaptionButton* button = buttons[i];
411 views::Button::ButtonState new_state = views::Button::STATE_NORMAL;
412 if (button == to_hover)
413 new_state = views::Button::STATE_HOVERED;
414 else if (button == to_press)
415 new_state = views::Button::STATE_PRESSED;
416 button->SetState(new_state);
417 }
418 }
419
ButtonIconIds()420 FrameCaptionButtonContainerView::ButtonIconIds::ButtonIconIds()
421 : icon_image_id(-1),
422 inactive_icon_image_id(-1),
423 hovered_background_image_id(-1),
424 pressed_background_image_id(-1) {
425 }
426
ButtonIconIds(int icon_id,int inactive_icon_id,int hovered_background_id,int pressed_background_id)427 FrameCaptionButtonContainerView::ButtonIconIds::ButtonIconIds(
428 int icon_id,
429 int inactive_icon_id,
430 int hovered_background_id,
431 int pressed_background_id)
432 : icon_image_id(icon_id),
433 inactive_icon_image_id(inactive_icon_id),
434 hovered_background_image_id(hovered_background_id),
435 pressed_background_image_id(pressed_background_id) {
436 }
437
~ButtonIconIds()438 FrameCaptionButtonContainerView::ButtonIconIds::~ButtonIconIds() {
439 }
440
441 } // namespace ash
442