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