Home | History | Annotate | Download | only in caption_buttons
      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, &center_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