custom_button.cc revision a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7
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 21namespace views { 22 23// How long the hover animation takes if uninterrupted. 24static const int kHoverFadeDurationMs = 150; 25 26// static 27const char CustomButton::kViewClassName[] = "CustomButton"; 28 29//////////////////////////////////////////////////////////////////////////////// 30// CustomButton, public: 31 32// static 33const CustomButton* CustomButton::AsCustomButton(const views::View* view) { 34 return AsCustomButton(const_cast<views::View*>(view)); 35} 36 37CustomButton* 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 53CustomButton::~CustomButton() { 54} 55 56void 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 83void CustomButton::StartThrobbing(int cycles_til_stop) { 84 is_throbbing_ = true; 85 hover_animation_->StartThrobbing(cycles_til_stop); 86} 87 88void CustomButton::StopThrobbing() { 89 if (hover_animation_->is_animating()) { 90 hover_animation_->Stop(); 91 SchedulePaint(); 92 } 93} 94 95void CustomButton::SetAnimationDuration(int duration) { 96 hover_animation_->SetSlideDuration(duration); 97} 98 99void 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 107bool CustomButton::IsHotTracked() const { 108 return state_ == STATE_HOVERED; 109} 110 111//////////////////////////////////////////////////////////////////////////////// 112// CustomButton, View overrides: 113 114void 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 124const char* CustomButton::GetClassName() const { 125 return kViewClassName; 126} 127 128bool 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 138bool 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 148void 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 165void 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 171void CustomButton::OnMouseEntered(const ui::MouseEvent& event) { 172 if (state_ != STATE_DISABLED) 173 SetState(STATE_HOVERED); 174} 175 176void 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 182void CustomButton::OnMouseMoved(const ui::MouseEvent& event) { 183 if (state_ != STATE_DISABLED) 184 SetState(HitTestPoint(event.location()) ? STATE_HOVERED : STATE_NORMAL); 185} 186 187bool 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 210bool 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 224void 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 253bool 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 268void 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 280void CustomButton::OnDragDone() { 281 SetState(STATE_NORMAL); 282} 283 284void 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 303void 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 312void CustomButton::AnimationProgressed(const gfx::Animation* animation) { 313 SchedulePaint(); 314} 315 316//////////////////////////////////////////////////////////////////////////////// 317// CustomButton, protected: 318 319CustomButton::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 330void CustomButton::StateChanged() { 331} 332 333bool 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 340bool CustomButton::ShouldEnterPushedState(const ui::Event& event) { 341 return IsTriggerableEvent(event); 342} 343 344//////////////////////////////////////////////////////////////////////////////// 345// CustomButton, View overrides (protected): 346 347void CustomButton::ViewHierarchyChanged( 348 const ViewHierarchyChangedDetails& details) { 349 if (!details.is_add && state_ != STATE_DISABLED) 350 SetState(STATE_NORMAL); 351} 352 353void CustomButton::OnBlur() { 354 if (IsHotTracked()) 355 SetState(STATE_NORMAL); 356} 357 358} // namespace views 359