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_|. 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_|. 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_|. 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_|. 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|. 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. 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 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 145 FrameCaptionButtonContainerView::~FrameCaptionButtonContainerView() { 146 } 147 148 void FrameCaptionButtonContainerView::TestApi::EndAnimations() { 149 container_view_->maximize_mode_animation_->End(); 150 } 151 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 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 183 void FrameCaptionButtonContainerView::ResetWindowControls() { 184 SetButtonsToNormal(ANIMATE_NO); 185 } 186 187 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 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 214 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 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 240 const char* FrameCaptionButtonContainerView::GetClassName() const { 241 return kViewClassName; 242 } 243 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 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 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 317 bool FrameCaptionButtonContainerView::ShouldSizeButtonBeVisible() const { 318 return !Shell::GetInstance()->maximize_mode_controller()-> 319 IsMaximizeModeWindowManagerEnabled() && 320 frame_->widget_delegate()->CanMaximize(); 321 } 322 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 352 bool FrameCaptionButtonContainerView::IsMinimizeButtonVisible() const { 353 return minimize_button_->visible(); 354 } 355 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 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 372 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 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 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 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 438 FrameCaptionButtonContainerView::ButtonIconIds::~ButtonIconIds() { 439 } 440 441 } // namespace ash 442