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