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/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::AccessibilityTypes::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     NotifyClick(synthetic_event);
    204   } else {
    205     return false;
    206   }
    207   return true;
    208 }
    209 
    210 bool CustomButton::OnKeyReleased(const ui::KeyEvent& event) {
    211   if ((state_ == STATE_DISABLED) || (event.key_code() != ui::VKEY_SPACE))
    212     return false;
    213 
    214   SetState(STATE_NORMAL);
    215   // TODO(beng): remove once NotifyClick takes ui::Event.
    216   ui::MouseEvent synthetic_event(ui::ET_MOUSE_RELEASED,
    217                                  gfx::Point(),
    218                                  gfx::Point(),
    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   NotifyClick(synthetic_event);
    265   return true;
    266 }
    267 
    268 void CustomButton::ShowContextMenu(const gfx::Point& p,
    269                                    ui::MenuSourceType source_type) {
    270   if (!context_menu_controller())
    271     return;
    272 
    273   // We're about to show the context menu. Showing the context menu likely means
    274   // we won't get a mouse exited and reset state. Reset it now to be sure.
    275   if (state_ != STATE_DISABLED)
    276     SetState(STATE_NORMAL);
    277   View::ShowContextMenu(p, source_type);
    278 }
    279 
    280 void CustomButton::OnDragDone() {
    281   SetState(STATE_NORMAL);
    282 }
    283 
    284 void CustomButton::GetAccessibleState(ui::AccessibleViewState* state) {
    285   Button::GetAccessibleState(state);
    286   switch (state_) {
    287     case STATE_HOVERED:
    288       state->state = ui::AccessibilityTypes::STATE_HOTTRACKED;
    289       break;
    290     case STATE_PRESSED:
    291       state->state = ui::AccessibilityTypes::STATE_PRESSED;
    292       break;
    293     case STATE_DISABLED:
    294       state->state = ui::AccessibilityTypes::STATE_UNAVAILABLE;
    295       break;
    296     case STATE_NORMAL:
    297     case STATE_COUNT:
    298       // No additional accessibility state set for this button state.
    299       break;
    300   }
    301 }
    302 
    303 void CustomButton::VisibilityChanged(View* starting_from, bool visible) {
    304   if (state_ == STATE_DISABLED)
    305     return;
    306   SetState(visible && IsMouseHovered() ? STATE_HOVERED : STATE_NORMAL);
    307 }
    308 
    309 ////////////////////////////////////////////////////////////////////////////////
    310 // CustomButton, gfx::AnimationDelegate implementation:
    311 
    312 void CustomButton::AnimationProgressed(const gfx::Animation* animation) {
    313   SchedulePaint();
    314 }
    315 
    316 ////////////////////////////////////////////////////////////////////////////////
    317 // CustomButton, protected:
    318 
    319 CustomButton::CustomButton(ButtonListener* listener)
    320     : Button(listener),
    321       state_(STATE_NORMAL),
    322       animate_on_state_change_(true),
    323       is_throbbing_(false),
    324       triggerable_event_flags_(ui::EF_LEFT_MOUSE_BUTTON),
    325       request_focus_on_press_(true) {
    326   hover_animation_.reset(new gfx::ThrobAnimation(this));
    327   hover_animation_->SetSlideDuration(kHoverFadeDurationMs);
    328 }
    329 
    330 void CustomButton::StateChanged() {
    331 }
    332 
    333 bool CustomButton::IsTriggerableEvent(const ui::Event& event) {
    334   return event.type() == ui::ET_GESTURE_TAP_DOWN ||
    335          event.type() == ui::ET_GESTURE_TAP ||
    336          (event.IsMouseEvent() &&
    337              (triggerable_event_flags_ & event.flags()) != 0);
    338 }
    339 
    340 bool CustomButton::ShouldEnterPushedState(const ui::Event& event) {
    341   return IsTriggerableEvent(event);
    342 }
    343 
    344 ////////////////////////////////////////////////////////////////////////////////
    345 // CustomButton, View overrides (protected):
    346 
    347 void CustomButton::ViewHierarchyChanged(
    348     const ViewHierarchyChangedDetails& details) {
    349   if (!details.is_add && state_ != STATE_DISABLED)
    350     SetState(STATE_NORMAL);
    351 }
    352 
    353 void CustomButton::OnBlur() {
    354   if (IsHotTracked())
    355     SetState(STATE_NORMAL);
    356 }
    357 
    358 }  // namespace views
    359