Home | History | Annotate | Download | only in button
      1 // Copyright (c) 2012 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 "ui/views/controls/button/custom_button.h"
      6 
      7 #include "ui/base/accessibility/accessible_view_state.h"
      8 #include "ui/base/animation/throb_animation.h"
      9 #include "ui/base/events/event.h"
     10 #include "ui/base/keycodes/keyboard_codes.h"
     11 #include "ui/gfx/screen.h"
     12 #include "ui/views/widget/widget.h"
     13 
     14 namespace views {
     15 
     16 // How long the hover animation takes if uninterrupted.
     17 static const int kHoverFadeDurationMs = 150;
     18 
     19 // static
     20 const char CustomButton::kViewClassName[] = "CustomButton";
     21 
     22 ////////////////////////////////////////////////////////////////////////////////
     23 // CustomButton, public:
     24 
     25 CustomButton::~CustomButton() {
     26 }
     27 
     28 void CustomButton::SetState(ButtonState state) {
     29   if (state == state_)
     30     return;
     31 
     32   if (animate_on_state_change_ &&
     33       (!is_throbbing_ || !hover_animation_->is_animating())) {
     34     is_throbbing_ = false;
     35     if (state_ == STATE_NORMAL && state == STATE_HOVERED) {
     36       // Button is hovered from a normal state, start hover animation.
     37       hover_animation_->Show();
     38     } else if ((state_ == STATE_HOVERED || state_ == STATE_PRESSED)
     39           && state == STATE_NORMAL) {
     40       // Button is returning to a normal state from hover, start hover
     41       // fade animation.
     42       hover_animation_->Hide();
     43     } else {
     44       hover_animation_->Stop();
     45     }
     46   }
     47 
     48   state_ = state;
     49   StateChanged();
     50   if (state_changed_delegate_.get())
     51     state_changed_delegate_->StateChanged(state_);
     52   SchedulePaint();
     53 }
     54 
     55 void CustomButton::StartThrobbing(int cycles_til_stop) {
     56   is_throbbing_ = true;
     57   hover_animation_->StartThrobbing(cycles_til_stop);
     58 }
     59 
     60 void CustomButton::StopThrobbing() {
     61   if (hover_animation_->is_animating()) {
     62     hover_animation_->Stop();
     63     SchedulePaint();
     64   }
     65 }
     66 
     67 void CustomButton::SetAnimationDuration(int duration) {
     68   hover_animation_->SetSlideDuration(duration);
     69 }
     70 
     71 void CustomButton::SetHotTracked(bool is_hot_tracked) {
     72   if (state_ != STATE_DISABLED)
     73     SetState(is_hot_tracked ? STATE_HOVERED : STATE_NORMAL);
     74 
     75   if (is_hot_tracked)
     76     NotifyAccessibilityEvent(ui::AccessibilityTypes::EVENT_FOCUS, true);
     77 }
     78 
     79 bool CustomButton::IsHotTracked() const {
     80   return state_ == STATE_HOVERED;
     81 }
     82 
     83 ////////////////////////////////////////////////////////////////////////////////
     84 // CustomButton, View overrides:
     85 
     86 void CustomButton::OnEnabledChanged() {
     87   if (enabled() ? (state_ != STATE_DISABLED) : (state_ == STATE_DISABLED))
     88     return;
     89 
     90   if (enabled())
     91     SetState(IsMouseHovered() ? STATE_HOVERED : STATE_NORMAL);
     92   else
     93     SetState(STATE_DISABLED);
     94 }
     95 
     96 const char* CustomButton::GetClassName() const {
     97   return kViewClassName;
     98 }
     99 
    100 bool CustomButton::OnMousePressed(const ui::MouseEvent& event) {
    101   if (state_ != STATE_DISABLED) {
    102     if (ShouldEnterPushedState(event) && HitTestPoint(event.location()))
    103       SetState(STATE_PRESSED);
    104     if (request_focus_on_press_)
    105       RequestFocus();
    106   }
    107   return true;
    108 }
    109 
    110 bool CustomButton::OnMouseDragged(const ui::MouseEvent& event) {
    111   if (state_ != STATE_DISABLED) {
    112     if (HitTestPoint(event.location()))
    113       SetState(ShouldEnterPushedState(event) ? STATE_PRESSED : STATE_HOVERED);
    114     else
    115       SetState(STATE_NORMAL);
    116   }
    117   return true;
    118 }
    119 
    120 void CustomButton::OnMouseReleased(const ui::MouseEvent& event) {
    121   if (state_ == STATE_DISABLED)
    122     return;
    123 
    124   if (!HitTestPoint(event.location())) {
    125     SetState(STATE_NORMAL);
    126     return;
    127   }
    128 
    129   SetState(STATE_HOVERED);
    130   if (IsTriggerableEvent(event)) {
    131     NotifyClick(event);
    132     // NOTE: We may be deleted at this point (by the listener's notification
    133     // handler).
    134   }
    135 }
    136 
    137 void CustomButton::OnMouseCaptureLost() {
    138   // Starting a drag results in a MouseCaptureLost, we need to ignore it.
    139   if (state_ != STATE_DISABLED && !InDrag())
    140     SetState(STATE_NORMAL);
    141 }
    142 
    143 void CustomButton::OnMouseEntered(const ui::MouseEvent& event) {
    144   if (state_ != STATE_DISABLED)
    145     SetState(STATE_HOVERED);
    146 }
    147 
    148 void CustomButton::OnMouseExited(const ui::MouseEvent& event) {
    149   // Starting a drag results in a MouseExited, we need to ignore it.
    150   if (state_ != STATE_DISABLED && !InDrag())
    151     SetState(STATE_NORMAL);
    152 }
    153 
    154 void CustomButton::OnMouseMoved(const ui::MouseEvent& event) {
    155   if (state_ != STATE_DISABLED)
    156     SetState(HitTestPoint(event.location()) ? STATE_HOVERED : STATE_NORMAL);
    157 }
    158 
    159 bool CustomButton::OnKeyPressed(const ui::KeyEvent& event) {
    160   if (state_ == STATE_DISABLED)
    161     return false;
    162 
    163   // Space sets button state to pushed. Enter clicks the button. This matches
    164   // the Windows native behavior of buttons, where Space clicks the button on
    165   // KeyRelease and Enter clicks the button on KeyPressed.
    166   if (event.key_code() == ui::VKEY_SPACE) {
    167     SetState(STATE_PRESSED);
    168   } else if (event.key_code() == ui::VKEY_RETURN) {
    169     SetState(STATE_NORMAL);
    170     // TODO(beng): remove once NotifyClick takes ui::Event.
    171     ui::MouseEvent synthetic_event(ui::ET_MOUSE_RELEASED,
    172                                    gfx::Point(),
    173                                    gfx::Point(),
    174                                    ui::EF_LEFT_MOUSE_BUTTON);
    175     NotifyClick(synthetic_event);
    176   } else {
    177     return false;
    178   }
    179   return true;
    180 }
    181 
    182 bool CustomButton::OnKeyReleased(const ui::KeyEvent& event) {
    183   if ((state_ == STATE_DISABLED) || (event.key_code() != ui::VKEY_SPACE))
    184     return false;
    185 
    186   SetState(STATE_NORMAL);
    187   // TODO(beng): remove once NotifyClick takes ui::Event.
    188   ui::MouseEvent synthetic_event(ui::ET_MOUSE_RELEASED,
    189                                  gfx::Point(),
    190                                  gfx::Point(),
    191                                  ui::EF_LEFT_MOUSE_BUTTON);
    192   NotifyClick(synthetic_event);
    193   return true;
    194 }
    195 
    196 void CustomButton::OnGestureEvent(ui::GestureEvent* event) {
    197   if (state_ == STATE_DISABLED) {
    198     Button::OnGestureEvent(event);
    199     return;
    200   }
    201 
    202   if (event->type() == ui::ET_GESTURE_TAP && IsTriggerableEvent(*event)) {
    203     // Set the button state to hot and start the animation fully faded in. The
    204     // TAP_UP event issued immediately after will set the state to STATE_NORMAL
    205     // beginning the fade out animation. See http://crbug.com/131184.
    206     SetState(STATE_HOVERED);
    207     hover_animation_->Reset(1.0);
    208     NotifyClick(*event);
    209     event->StopPropagation();
    210   } else if (event->type() == ui::ET_GESTURE_TAP_DOWN &&
    211              ShouldEnterPushedState(*event)) {
    212     SetState(STATE_PRESSED);
    213     if (request_focus_on_press_)
    214       RequestFocus();
    215     event->StopPropagation();
    216   } else {
    217     SetState(STATE_NORMAL);
    218   }
    219   if (!event->handled())
    220     Button::OnGestureEvent(event);
    221 }
    222 
    223 bool CustomButton::AcceleratorPressed(const ui::Accelerator& accelerator) {
    224   SetState(STATE_NORMAL);
    225   /*
    226   ui::KeyEvent key_event(ui::ET_KEY_RELEASED, accelerator.key_code(),
    227                          accelerator.modifiers());
    228                          */
    229   // TODO(beng): remove once NotifyClick takes ui::Event.
    230   ui::MouseEvent synthetic_event(ui::ET_MOUSE_RELEASED,
    231                                  gfx::Point(),
    232                                  gfx::Point(),
    233                                  ui::EF_LEFT_MOUSE_BUTTON);
    234   NotifyClick(synthetic_event);
    235   return true;
    236 }
    237 
    238 void CustomButton::ShowContextMenu(const gfx::Point& p,
    239                                    ui::MenuSourceType source_type) {
    240   if (!context_menu_controller())
    241     return;
    242 
    243   // We're about to show the context menu. Showing the context menu likely means
    244   // we won't get a mouse exited and reset state. Reset it now to be sure.
    245   if (state_ != STATE_DISABLED)
    246     SetState(STATE_NORMAL);
    247   View::ShowContextMenu(p, source_type);
    248 }
    249 
    250 void CustomButton::OnDragDone() {
    251   SetState(STATE_NORMAL);
    252 }
    253 
    254 void CustomButton::GetAccessibleState(ui::AccessibleViewState* state) {
    255   Button::GetAccessibleState(state);
    256   switch (state_) {
    257     case STATE_HOVERED:
    258       state->state = ui::AccessibilityTypes::STATE_HOTTRACKED;
    259       break;
    260     case STATE_PRESSED:
    261       state->state = ui::AccessibilityTypes::STATE_PRESSED;
    262       break;
    263     case STATE_DISABLED:
    264       state->state = ui::AccessibilityTypes::STATE_UNAVAILABLE;
    265       break;
    266     case STATE_NORMAL:
    267     case STATE_COUNT:
    268       // No additional accessibility state set for this button state.
    269       break;
    270   }
    271 }
    272 
    273 void CustomButton::VisibilityChanged(View* starting_from, bool visible) {
    274   if (state_ == STATE_DISABLED)
    275     return;
    276   SetState(visible && IsMouseHovered() ? STATE_HOVERED : STATE_NORMAL);
    277 }
    278 
    279 ////////////////////////////////////////////////////////////////////////////////
    280 // CustomButton, ui::AnimationDelegate implementation:
    281 
    282 void CustomButton::AnimationProgressed(const ui::Animation* animation) {
    283   SchedulePaint();
    284 }
    285 
    286 ////////////////////////////////////////////////////////////////////////////////
    287 // CustomButton, protected:
    288 
    289 CustomButton::CustomButton(ButtonListener* listener)
    290     : Button(listener),
    291       state_(STATE_NORMAL),
    292       animate_on_state_change_(true),
    293       is_throbbing_(false),
    294       triggerable_event_flags_(ui::EF_LEFT_MOUSE_BUTTON),
    295       request_focus_on_press_(true) {
    296   hover_animation_.reset(new ui::ThrobAnimation(this));
    297   hover_animation_->SetSlideDuration(kHoverFadeDurationMs);
    298 }
    299 
    300 void CustomButton::StateChanged() {
    301 }
    302 
    303 bool CustomButton::IsTriggerableEvent(const ui::Event& event) {
    304   return event.type() == ui::ET_GESTURE_TAP_DOWN ||
    305          event.type() == ui::ET_GESTURE_TAP ||
    306          (event.IsMouseEvent() &&
    307              (triggerable_event_flags_ & event.flags()) != 0);
    308 }
    309 
    310 bool CustomButton::ShouldEnterPushedState(const ui::Event& event) {
    311   return IsTriggerableEvent(event);
    312 }
    313 
    314 ////////////////////////////////////////////////////////////////////////////////
    315 // CustomButton, View overrides (protected):
    316 
    317 void CustomButton::ViewHierarchyChanged(
    318     const ViewHierarchyChangedDetails& details) {
    319   if (!details.is_add && state_ != STATE_DISABLED)
    320     SetState(STATE_NORMAL);
    321 }
    322 
    323 void CustomButton::OnBlur() {
    324   if (IsHotTracked())
    325     SetState(STATE_NORMAL);
    326 }
    327 
    328 }  // namespace views
    329