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