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