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