1 /*************************************************************************/
2 /* base_button.cpp */
3 /*************************************************************************/
4 /* This file is part of: */
5 /* GODOT ENGINE */
6 /* https://godotengine.org */
7 /*************************************************************************/
8 /* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
9 /* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
10 /* */
11 /* Permission is hereby granted, free of charge, to any person obtaining */
12 /* a copy of this software and associated documentation files (the */
13 /* "Software"), to deal in the Software without restriction, including */
14 /* without limitation the rights to use, copy, modify, merge, publish, */
15 /* distribute, sublicense, and/or sell copies of the Software, and to */
16 /* permit persons to whom the Software is furnished to do so, subject to */
17 /* the following conditions: */
18 /* */
19 /* The above copyright notice and this permission notice shall be */
20 /* included in all copies or substantial portions of the Software. */
21 /* */
22 /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23 /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24 /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
25 /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26 /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27 /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29 /*************************************************************************/
30 #include "base_button.h"
31 #include "button_group.h"
32 #include "os/keyboard.h"
33 #include "print_string.h"
34 #include "scene/main/viewport.h"
35 #include "scene/scene_string_names.h"
36
_input_event(InputEvent p_event)37 void BaseButton::_input_event(InputEvent p_event) {
38
39 if (status.disabled) // no interaction with disabled button
40 return;
41
42 switch (p_event.type) {
43
44 case InputEvent::MOUSE_BUTTON: {
45
46 const InputEventMouseButton &b = p_event.mouse_button;
47
48 if (status.disabled || b.button_index != 1)
49 return;
50
51 if (status.pressing_button)
52 break;
53
54 if (status.click_on_press) {
55
56 if (b.pressed) {
57
58 emit_signal("button_down");
59
60 if (!toggle_mode) { //mouse press attempt
61
62 status.press_attempt = true;
63 status.pressing_inside = true;
64
65 pressed();
66 if (get_script_instance()) {
67 Variant::CallError ce;
68 get_script_instance()->call(SceneStringNames::get_singleton()->_pressed, NULL, 0, ce);
69 }
70
71 emit_signal("pressed");
72
73 } else {
74
75 status.pressed = !status.pressed;
76 pressed();
77 if (get_script_instance()) {
78 Variant::CallError ce;
79 get_script_instance()->call(SceneStringNames::get_singleton()->_pressed, NULL, 0, ce);
80 }
81 emit_signal("pressed");
82
83 toggled(status.pressed);
84 emit_signal("toggled", status.pressed);
85 }
86
87 } else {
88
89 emit_signal("button_up");
90
91 if (status.press_attempt && status.pressing_inside) {
92 // released();
93 emit_signal("released");
94 }
95 status.press_attempt = false;
96 }
97 update();
98 break;
99 }
100
101 if (b.pressed) {
102
103 status.press_attempt = true;
104 status.pressing_inside = true;
105 emit_signal("button_down");
106
107 } else {
108
109 emit_signal("button_up");
110
111 if (status.press_attempt && status.pressing_inside) {
112
113 if (!toggle_mode) { //mouse press attempt
114
115 pressed();
116 if (get_script_instance()) {
117 Variant::CallError ce;
118 get_script_instance()->call(SceneStringNames::get_singleton()->_pressed, NULL, 0, ce);
119 }
120
121 emit_signal("pressed");
122
123 } else {
124
125 status.pressed = !status.pressed;
126
127 pressed();
128 emit_signal("pressed");
129
130 toggled(status.pressed);
131 emit_signal("toggled", status.pressed);
132 if (get_script_instance()) {
133 get_script_instance()->call(SceneStringNames::get_singleton()->_toggled, status.pressed);
134 }
135 }
136 }
137
138 status.press_attempt = false;
139 }
140
141 update();
142 } break;
143 case InputEvent::MOUSE_MOTION: {
144
145 if (status.press_attempt && status.pressing_button == 0) {
146 bool last_press_inside = status.pressing_inside;
147 status.pressing_inside = has_point(Point2(p_event.mouse_motion.x, p_event.mouse_motion.y));
148 if (last_press_inside != status.pressing_inside)
149 update();
150 }
151 } break;
152 case InputEvent::ACTION:
153 case InputEvent::JOYSTICK_BUTTON:
154 case InputEvent::KEY: {
155
156 if (p_event.is_echo()) {
157 break;
158 }
159
160 if (status.disabled) {
161 break;
162 }
163
164 if (status.press_attempt && status.pressing_button == 0) {
165 break;
166 }
167
168 if (p_event.is_action("ui_accept")) {
169
170 if (p_event.is_pressed()) {
171
172 status.pressing_button++;
173 status.press_attempt = true;
174 status.pressing_inside = true;
175 emit_signal("button_down");
176
177 } else if (status.press_attempt) {
178
179 if (status.pressing_button)
180 status.pressing_button--;
181
182 if (status.pressing_button)
183 break;
184
185 status.press_attempt = false;
186 status.pressing_inside = false;
187
188 emit_signal("button_up");
189
190 if (!toggle_mode) { //mouse press attempt
191
192 pressed();
193 emit_signal("pressed");
194 } else {
195
196 status.pressed = !status.pressed;
197
198 pressed();
199 emit_signal("pressed");
200
201 toggled(status.pressed);
202 if (get_script_instance()) {
203 get_script_instance()->call(SceneStringNames::get_singleton()->_toggled, status.pressed);
204 }
205 emit_signal("toggled", status.pressed);
206 }
207 }
208
209 accept_event();
210 update();
211 }
212 }
213 }
214 }
215
_notification(int p_what)216 void BaseButton::_notification(int p_what) {
217
218 if (p_what == NOTIFICATION_MOUSE_ENTER) {
219
220 status.hovering = true;
221 update();
222 }
223
224 if (p_what == NOTIFICATION_MOUSE_EXIT) {
225 status.hovering = false;
226 update();
227 }
228 if (p_what == NOTIFICATION_DRAG_BEGIN || p_what == NOTIFICATION_SCROLL_BEGIN) {
229
230 if (status.press_attempt) {
231 status.press_attempt = false;
232 status.pressing_button = 0;
233 update();
234 }
235 }
236
237 if (p_what == NOTIFICATION_FOCUS_ENTER) {
238
239 status.hovering = true;
240 update();
241 }
242
243 if (p_what == NOTIFICATION_FOCUS_EXIT) {
244
245 if (status.pressing_button && status.press_attempt) {
246 status.press_attempt = false;
247 status.pressing_button = 0;
248 status.hovering = false;
249 update();
250 } else if (status.hovering) {
251 status.hovering = false;
252 update();
253 }
254 }
255
256 if (p_what == NOTIFICATION_ENTER_TREE) {
257
258 CanvasItem *ci = this;
259 while (ci) {
260
261 ButtonGroup *bg = ci->cast_to<ButtonGroup>();
262 if (bg) {
263
264 group = bg;
265 group->_add_button(this);
266 }
267
268 ci = ci->get_parent_item();
269 }
270 }
271
272 if (p_what == NOTIFICATION_EXIT_TREE) {
273
274 if (group)
275 group->_remove_button(this);
276 }
277
278 if (p_what == NOTIFICATION_VISIBILITY_CHANGED && !is_visible()) {
279
280 if (!toggle_mode) {
281 status.pressed = false;
282 }
283 status.hovering = false;
284 status.press_attempt = false;
285 status.pressing_inside = false;
286 status.pressing_button = 0;
287 }
288 }
289
pressed()290 void BaseButton::pressed() {
291
292 if (get_script_instance())
293 get_script_instance()->call("pressed");
294 }
295
toggled(bool p_pressed)296 void BaseButton::toggled(bool p_pressed) {
297
298 if (get_script_instance())
299 get_script_instance()->call("toggled", p_pressed);
300 }
301
set_disabled(bool p_disabled)302 void BaseButton::set_disabled(bool p_disabled) {
303
304 status.disabled = p_disabled;
305 update();
306 _change_notify("disabled");
307 if (p_disabled)
308 set_focus_mode(FOCUS_NONE);
309 else
310 set_focus_mode(enabled_focus_mode);
311 }
312
is_disabled() const313 bool BaseButton::is_disabled() const {
314
315 return status.disabled;
316 }
317
set_pressed(bool p_pressed)318 void BaseButton::set_pressed(bool p_pressed) {
319
320 if (!toggle_mode)
321 return;
322 if (status.pressed == p_pressed)
323 return;
324 _change_notify("pressed");
325 status.pressed = p_pressed;
326 update();
327 }
328
is_pressing() const329 bool BaseButton::is_pressing() const {
330
331 return status.press_attempt;
332 }
333
is_pressed() const334 bool BaseButton::is_pressed() const {
335
336 return toggle_mode ? status.pressed : status.press_attempt;
337 }
338
is_hovered() const339 bool BaseButton::is_hovered() const {
340
341 return status.hovering;
342 }
343
get_draw_mode() const344 BaseButton::DrawMode BaseButton::get_draw_mode() const {
345
346 if (status.disabled) {
347 return DRAW_DISABLED;
348 };
349
350 //print_line("press attempt: "+itos(status.press_attempt)+" hover: "+itos(status.hovering)+" pressed: "+itos(status.pressed));
351 if (status.press_attempt == false && status.hovering && !status.pressed) {
352
353 return DRAW_HOVER;
354 } else {
355 /* determine if pressed or not */
356
357 bool pressing;
358 if (status.press_attempt) {
359
360 pressing = status.pressing_inside;
361 if (status.pressed)
362 pressing = !pressing;
363 } else {
364
365 pressing = status.pressed;
366 }
367
368 if (pressing)
369 return DRAW_PRESSED;
370 else
371 return DRAW_NORMAL;
372 }
373
374 return DRAW_NORMAL;
375 }
376
set_toggle_mode(bool p_on)377 void BaseButton::set_toggle_mode(bool p_on) {
378
379 toggle_mode = p_on;
380 }
381
is_toggle_mode() const382 bool BaseButton::is_toggle_mode() const {
383
384 return toggle_mode;
385 }
386
set_click_on_press(bool p_click_on_press)387 void BaseButton::set_click_on_press(bool p_click_on_press) {
388
389 status.click_on_press = p_click_on_press;
390 }
391
get_click_on_press() const392 bool BaseButton::get_click_on_press() const {
393
394 return status.click_on_press;
395 }
396
set_enabled_focus_mode(FocusMode p_mode)397 void BaseButton::set_enabled_focus_mode(FocusMode p_mode) {
398
399 enabled_focus_mode = p_mode;
400 if (!status.disabled) {
401 set_focus_mode(p_mode);
402 }
403 }
404
get_enabled_focus_mode() const405 Control::FocusMode BaseButton::get_enabled_focus_mode() const {
406
407 return enabled_focus_mode;
408 }
409
set_shortcut(const Ref<ShortCut> & p_shortcut)410 void BaseButton::set_shortcut(const Ref<ShortCut> &p_shortcut) {
411
412 if (shortcut.is_null() == p_shortcut.is_null())
413 return;
414
415 shortcut = p_shortcut;
416 set_process_unhandled_input(shortcut.is_valid());
417 }
418
get_shortcut() const419 Ref<ShortCut> BaseButton::get_shortcut() const {
420 return shortcut;
421 }
422
_unhandled_input(InputEvent p_event)423 void BaseButton::_unhandled_input(InputEvent p_event) {
424
425 if (!is_disabled() && is_visible() && p_event.is_pressed() && !p_event.is_echo() && shortcut.is_valid() && shortcut->is_shortcut(p_event)) {
426
427 if (get_viewport()->get_modal_stack_top() && !get_viewport()->get_modal_stack_top()->is_a_parent_of(this))
428 return; //ignore because of modal window
429
430 if (is_toggle_mode()) {
431 set_pressed(!is_pressed());
432 emit_signal("toggled", is_pressed());
433 }
434
435 emit_signal("pressed");
436 }
437 }
438
get_tooltip(const Point2 & p_pos) const439 String BaseButton::get_tooltip(const Point2 &p_pos) const {
440
441 String tooltip = Control::get_tooltip(p_pos);
442 if (shortcut.is_valid() && shortcut->is_valid()) {
443 if (tooltip.find("$sc") != -1) {
444 tooltip = tooltip.replace_first("$sc", "(" + shortcut->get_as_text() + ")");
445 } else {
446 tooltip += " (" + shortcut->get_as_text() + ")";
447 }
448 }
449 return tooltip;
450 }
451
_bind_methods()452 void BaseButton::_bind_methods() {
453
454 ObjectTypeDB::bind_method(_MD("_input_event"), &BaseButton::_input_event);
455 ObjectTypeDB::bind_method(_MD("_unhandled_input"), &BaseButton::_unhandled_input);
456 ObjectTypeDB::bind_method(_MD("set_pressed", "pressed"), &BaseButton::set_pressed);
457 ObjectTypeDB::bind_method(_MD("is_pressed"), &BaseButton::is_pressed);
458 ObjectTypeDB::bind_method(_MD("is_hovered"), &BaseButton::is_hovered);
459 ObjectTypeDB::bind_method(_MD("set_toggle_mode", "enabled"), &BaseButton::set_toggle_mode);
460 ObjectTypeDB::bind_method(_MD("is_toggle_mode"), &BaseButton::is_toggle_mode);
461 ObjectTypeDB::bind_method(_MD("set_disabled", "disabled"), &BaseButton::set_disabled);
462 ObjectTypeDB::bind_method(_MD("is_disabled"), &BaseButton::is_disabled);
463 ObjectTypeDB::bind_method(_MD("set_click_on_press", "enable"), &BaseButton::set_click_on_press);
464 ObjectTypeDB::bind_method(_MD("get_click_on_press"), &BaseButton::get_click_on_press);
465 ObjectTypeDB::bind_method(_MD("get_draw_mode"), &BaseButton::get_draw_mode);
466 ObjectTypeDB::bind_method(_MD("set_enabled_focus_mode", "mode"), &BaseButton::set_enabled_focus_mode);
467 ObjectTypeDB::bind_method(_MD("get_enabled_focus_mode"), &BaseButton::get_enabled_focus_mode);
468 ObjectTypeDB::bind_method(_MD("set_shortcut", "shortcut"), &BaseButton::set_shortcut);
469 ObjectTypeDB::bind_method(_MD("get_shortcut"), &BaseButton::get_shortcut);
470
471 BIND_VMETHOD(MethodInfo("_pressed"));
472 BIND_VMETHOD(MethodInfo("_toggled", PropertyInfo(Variant::BOOL, "pressed")));
473
474 ADD_SIGNAL(MethodInfo("pressed"));
475 ADD_SIGNAL(MethodInfo("released"));
476 ADD_SIGNAL(MethodInfo("button_up"));
477 ADD_SIGNAL(MethodInfo("button_down"));
478 ADD_SIGNAL(MethodInfo("toggled", PropertyInfo(Variant::BOOL, "pressed")));
479 ADD_PROPERTYNZ(PropertyInfo(Variant::BOOL, "disabled"), _SCS("set_disabled"), _SCS("is_disabled"));
480 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "toggle_mode"), _SCS("set_toggle_mode"), _SCS("is_toggle_mode"));
481 ADD_PROPERTYNZ(PropertyInfo(Variant::BOOL, "is_pressed"), _SCS("set_pressed"), _SCS("is_pressed"));
482 ADD_PROPERTYNZ(PropertyInfo(Variant::BOOL, "click_on_press"), _SCS("set_click_on_press"), _SCS("get_click_on_press"));
483 ADD_PROPERTY(PropertyInfo(Variant::INT, "enabled_focus_mode", PROPERTY_HINT_ENUM, "None,Click,All"), _SCS("set_enabled_focus_mode"), _SCS("get_enabled_focus_mode"));
484 ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shortcut", PROPERTY_HINT_RESOURCE_TYPE, "ShortCut"), _SCS("set_shortcut"), _SCS("get_shortcut"));
485
486 BIND_CONSTANT(DRAW_NORMAL);
487 BIND_CONSTANT(DRAW_PRESSED);
488 BIND_CONSTANT(DRAW_HOVER);
489 BIND_CONSTANT(DRAW_DISABLED);
490 }
491
BaseButton()492 BaseButton::BaseButton() {
493
494 toggle_mode = false;
495 status.pressed = false;
496 status.press_attempt = false;
497 status.hovering = false;
498 status.pressing_inside = false;
499 status.disabled = false;
500 status.click_on_press = false;
501 status.pressing_button = 0;
502 set_focus_mode(FOCUS_ALL);
503 enabled_focus_mode = FOCUS_ALL;
504 group = NULL;
505 }
506
~BaseButton()507 BaseButton::~BaseButton() {
508 }
509