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.
5 #include "ash/frame/caption_buttons/frame_caption_button_container_view.h"
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 "grit/ui_strings.h" // Accessibility names
17 #include "ui/base/hit_test.h"
18 #include "ui/base/l10n/l10n_util.h"
19 #include "ui/compositor/scoped_animation_duration_scale_mode.h"
20 #include "ui/gfx/animation/slide_animation.h"
21 #include "ui/gfx/animation/tween.h"
22 #include "ui/gfx/canvas.h"
23 #include "ui/gfx/insets.h"
24 #include "ui/gfx/point.h"
25 #include "ui/views/widget/widget.h"
26 #include "ui/views/widget/widget_delegate.h"
32 // Duration of the animation of the position of |minimize_button_|.
33 const int kPositionAnimationDurationMs
= 500;
35 // Duration of the animation of the alpha of |size_button_|.
36 const int kAlphaAnimationDurationMs
= 250;
38 // Delay during |maximize_mode_animation_| hide to wait before beginning to
39 // animate the position of |minimize_button_|.
40 const int kHidePositionDelayMs
= 100;
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
;
50 // Delay during |maximize_mode_animation_| show to wait before beginning to
51 // animate the alpha of |size_button_|.
52 const int kShowAnimationAlphaDelayMs
= 100;
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
;
61 // Value of |maximize_mode_animation_| showing to begin animating alpha of
63 float SizeButtonShowStartValue() {
64 return static_cast<float>(kShowAnimationAlphaDelayMs
)
65 / kShowAnimationDurationMs
;
68 // Amount of |maximize_mode_animation_| showing to animate the alpha of
70 float SizeButtonShowDuration() {
71 return static_cast<float>(kAlphaAnimationDurationMs
)
72 / kShowAnimationDurationMs
;
75 // Amount of |maximize_mode_animation_| hiding to animate the alpha of
77 float SizeButtonHideDuration() {
78 return static_cast<float>(kAlphaAnimationDurationMs
)
79 / kHideAnimationDurationMs
;
82 // Value of |maximize_mode_animation_| hiding to begin animating the position of
83 // |minimize_button_|.
84 float HidePositionStartValue() {
85 return 1.0f
- static_cast<float>(kHidePositionDelayMs
)
86 / kHideAnimationDurationMs
;
89 // Converts |point| from |src| to |dst| and hittests against |dst|.
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
);
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
101 double CapAnimationValue(double value
) {
102 return std::min(1.0, std::max(0.0, value
));
108 const char FrameCaptionButtonContainerView::kViewClassName
[] =
109 "FrameCaptionButtonContainerView";
111 FrameCaptionButtonContainerView::FrameCaptionButtonContainerView(
112 views::Widget
* frame
,
113 MinimizeAllowed minimize_allowed
)
115 minimize_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
);
122 // Ensure animation tracks visibility of size button.
123 if (size_button_visibility
)
124 maximize_mode_animation_
->Reset(1.0f
);
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_
);
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_
);
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_
);
145 FrameCaptionButtonContainerView::~FrameCaptionButtonContainerView() {
148 void FrameCaptionButtonContainerView::TestApi::EndAnimations() {
149 container_view_
->maximize_mode_animation_
->End();
152 void FrameCaptionButtonContainerView::SetButtonImages(
153 CaptionButtonIcon icon
,
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_
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
,
170 inactive_icon_image_id
,
171 hovered_background_image_id
,
172 pressed_background_image_id
);
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
);
183 void FrameCaptionButtonContainerView::ResetWindowControls() {
184 SetButtonsToNormal(ANIMATE_NO
);
187 int FrameCaptionButtonContainerView::NonClientHitTest(
188 const gfx::Point
& point
) const {
189 if (close_button_
->visible() &&
190 ConvertPointToViewAndHitTest(this, close_button_
, point
)) {
192 } else if (size_button_
->visible() &&
193 ConvertPointToViewAndHitTest(this, size_button_
, point
)) {
195 } else if (minimize_button_
->visible() &&
196 ConvertPointToViewAndHitTest(this, minimize_button_
, point
)) {
202 void FrameCaptionButtonContainerView::UpdateSizeButtonVisibility() {
203 bool visible
= ShouldSizeButtonBeVisible();
205 size_button_
->SetVisible(true);
206 maximize_mode_animation_
->SetSlideDuration(kShowAnimationDurationMs
);
207 maximize_mode_animation_
->Show();
209 maximize_mode_animation_
->SetSlideDuration(kHideAnimationDurationMs
);
210 maximize_mode_animation_
->Hide();
214 gfx::Size
FrameCaptionButtonContainerView::GetPreferredSize() const {
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();
221 return gfx::Size(width
, close_button_
->GetPreferredSize().height());
224 void FrameCaptionButtonContainerView::Layout() {
226 for (int i
= 0; i
< child_count(); ++i
) {
227 views::View
* child
= child_at(i
);
228 if (!child
->visible())
231 gfx::Size size
= child
->GetPreferredSize();
232 child
->SetBounds(x
, 0, size
.width(), size
.height());
235 if (maximize_mode_animation_
->is_animating()) {
236 AnimationProgressed(maximize_mode_animation_
.get());
240 const char* FrameCaptionButtonContainerView::GetClassName() const {
241 return kViewClassName
;
244 void FrameCaptionButtonContainerView::AnimationEnded(
245 const gfx::Animation
* animation
) {
246 // Ensure that position is calculated at least once.
247 AnimationProgressed(animation
);
249 double current_value
= maximize_mode_animation_
->GetCurrentValue();
250 if (current_value
== 0.0) {
251 size_button_
->SetVisible(false);
252 PreferredSizeChanged();
256 void FrameCaptionButtonContainerView::AnimationProgressed(
257 const gfx::Animation
* animation
) {
258 double current_value
= animation
->GetCurrentValue();
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);
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);
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);
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,
288 size_button_
->SetAlpha(size_alpha
);
289 minimize_button_
->SetX(minimize_x
);
292 void FrameCaptionButtonContainerView::SetButtonIcon(FrameCaptionButton
* button
,
293 CaptionButtonIcon icon
,
295 // The early return is dependant on |animate| because callers use
296 // SetButtonIcon() with ANIMATE_NO to progress |button|'s crossfade animation
298 if (button
->icon() == icon
&&
299 (animate
== ANIMATE_YES
|| !button
->IsAnimatingImageSwap())) {
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
,
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
);
317 bool FrameCaptionButtonContainerView::ShouldSizeButtonBeVisible() const {
318 return !Shell::GetInstance()->maximize_mode_controller()->
319 IsMaximizeModeWindowManagerEnabled() &&
320 frame_
->widget_delegate()->CanMaximize();
323 void FrameCaptionButtonContainerView::ButtonPressed(views::Button
* sender
,
324 const ui::Event
& event
) {
325 // When shift-clicking, slow down animations for visual debugging.
326 // We used to do this via an event filter that looked for the shift key being
327 // pressed but this interfered with several normal keyboard shortcuts.
328 scoped_ptr
<ui::ScopedAnimationDurationScaleMode
> slow_duration_mode
;
329 if (event
.IsShiftDown()) {
330 slow_duration_mode
.reset(new ui::ScopedAnimationDurationScaleMode(
331 ui::ScopedAnimationDurationScaleMode::SLOW_DURATION
));
334 // Abort any animations of the button icons.
335 SetButtonsToNormal(ANIMATE_NO
);
337 ash::UserMetricsAction action
=
338 ash::UMA_WINDOW_MAXIMIZE_BUTTON_CLICK_MINIMIZE
;
339 if (sender
== minimize_button_
) {
341 } else if (sender
== size_button_
) {
342 if (frame_
->IsFullscreen()) { // Can be clicked in immersive fullscreen.
343 frame_
->SetFullscreen(false);
344 action
= ash::UMA_WINDOW_MAXIMIZE_BUTTON_CLICK_EXIT_FULLSCREEN
;
345 } else if (frame_
->IsMaximized()) {
347 action
= ash::UMA_WINDOW_MAXIMIZE_BUTTON_CLICK_RESTORE
;
350 action
= ash::UMA_WINDOW_MAXIMIZE_BUTTON_CLICK_MAXIMIZE
;
352 } else if (sender
== close_button_
) {
354 action
= ash::UMA_WINDOW_CLOSE_BUTTON_CLICK
;
358 ash::Shell::GetInstance()->metrics()->RecordUserMetricsAction(action
);
361 bool FrameCaptionButtonContainerView::IsMinimizeButtonVisible() const {
362 return minimize_button_
->visible();
365 void FrameCaptionButtonContainerView::SetButtonsToNormal(Animate animate
) {
366 SetButtonIcons(CAPTION_BUTTON_ICON_MINIMIZE
, CAPTION_BUTTON_ICON_CLOSE
,
368 minimize_button_
->SetState(views::Button::STATE_NORMAL
);
369 size_button_
->SetState(views::Button::STATE_NORMAL
);
370 close_button_
->SetState(views::Button::STATE_NORMAL
);
373 void FrameCaptionButtonContainerView::SetButtonIcons(
374 CaptionButtonIcon minimize_button_icon
,
375 CaptionButtonIcon close_button_icon
,
377 SetButtonIcon(minimize_button_
, minimize_button_icon
, animate
);
378 SetButtonIcon(close_button_
, close_button_icon
, animate
);
381 const FrameCaptionButton
* FrameCaptionButtonContainerView::GetButtonClosestTo(
382 const gfx::Point
& position_in_screen
) const {
383 // Since the buttons all have the same size, the closest button is the button
384 // with the center point closest to |position_in_screen|.
385 // TODO(pkotwicz): Make the caption buttons not overlap.
386 gfx::Point
position(position_in_screen
);
387 views::View::ConvertPointFromScreen(this, &position
);
389 FrameCaptionButton
* buttons
[] = {
390 minimize_button_
, size_button_
, close_button_
392 int min_squared_distance
= INT_MAX
;
393 FrameCaptionButton
* closest_button
= NULL
;
394 for (size_t i
= 0; i
< arraysize(buttons
); ++i
) {
395 FrameCaptionButton
* button
= buttons
[i
];
396 if (!button
->visible())
399 gfx::Point center_point
= button
->GetLocalBounds().CenterPoint();
400 views::View::ConvertPointToTarget(button
, this, ¢er_point
);
401 int squared_distance
= static_cast<int>(
402 pow(static_cast<double>(position
.x() - center_point
.x()), 2) +
403 pow(static_cast<double>(position
.y() - center_point
.y()), 2));
404 if (squared_distance
< min_squared_distance
) {
405 min_squared_distance
= squared_distance
;
406 closest_button
= button
;
409 return closest_button
;
412 void FrameCaptionButtonContainerView::SetHoveredAndPressedButtons(
413 const FrameCaptionButton
* to_hover
,
414 const FrameCaptionButton
* to_press
) {
415 FrameCaptionButton
* buttons
[] = {
416 minimize_button_
, size_button_
, close_button_
418 for (size_t i
= 0; i
< arraysize(buttons
); ++i
) {
419 FrameCaptionButton
* button
= buttons
[i
];
420 views::Button::ButtonState new_state
= views::Button::STATE_NORMAL
;
421 if (button
== to_hover
)
422 new_state
= views::Button::STATE_HOVERED
;
423 else if (button
== to_press
)
424 new_state
= views::Button::STATE_PRESSED
;
425 button
->SetState(new_state
);
429 FrameCaptionButtonContainerView::ButtonIconIds::ButtonIconIds()
431 inactive_icon_image_id(-1),
432 hovered_background_image_id(-1),
433 pressed_background_image_id(-1) {
436 FrameCaptionButtonContainerView::ButtonIconIds::ButtonIconIds(
438 int inactive_icon_id
,
439 int hovered_background_id
,
440 int pressed_background_id
)
441 : icon_image_id(icon_id
),
442 inactive_icon_image_id(inactive_icon_id
),
443 hovered_background_image_id(hovered_background_id
),
444 pressed_background_image_id(pressed_background_id
) {
447 FrameCaptionButtonContainerView::ButtonIconIds::~ButtonIconIds() {