2023-02-09 10:29:46 +00:00
/**************************************************************************/
/* test_viewport.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
# ifndef TEST_VIEWPORT_H
# define TEST_VIEWPORT_H
2024-02-26 06:15:31 +00:00
# include "scene/2d/physics/area_2d.h"
# include "scene/2d/physics/collision_shape_2d.h"
2023-02-09 10:29:46 +00:00
# include "scene/gui/control.h"
2023-07-23 00:14:41 +00:00
# include "scene/gui/subviewport_container.h"
2023-02-10 01:09:07 +00:00
# include "scene/main/canvas_layer.h"
2023-02-09 10:29:46 +00:00
# include "scene/main/window.h"
2021-03-16 06:27:53 +00:00
# include "scene/resources/2d/rectangle_shape_2d.h"
2024-08-07 19:15:17 +00:00
# include "servers/physics_server_2d_dummy.h"
2023-02-09 10:29:46 +00:00
# include "tests/test_macros.h"
namespace TestViewport {
2023-09-04 16:07:16 +00:00
class NotificationControlViewport : public Control {
GDCLASS ( NotificationControlViewport , Control ) ;
2023-02-09 10:29:46 +00:00
protected :
void _notification ( int p_what ) {
switch ( p_what ) {
case NOTIFICATION_MOUSE_ENTER : {
2023-10-31 17:55:34 +00:00
if ( mouse_over ) {
invalid_order = true ;
}
2023-02-09 10:29:46 +00:00
mouse_over = true ;
} break ;
case NOTIFICATION_MOUSE_EXIT : {
2023-10-31 17:55:34 +00:00
if ( ! mouse_over ) {
invalid_order = true ;
}
2023-02-09 10:29:46 +00:00
mouse_over = false ;
} break ;
2023-10-31 17:55:34 +00:00
case NOTIFICATION_MOUSE_ENTER_SELF : {
if ( mouse_over_self ) {
invalid_order = true ;
}
mouse_over_self = true ;
} break ;
case NOTIFICATION_MOUSE_EXIT_SELF : {
if ( ! mouse_over_self ) {
invalid_order = true ;
}
mouse_over_self = false ;
} break ;
2023-02-09 10:29:46 +00:00
}
}
public :
bool mouse_over = false ;
2023-10-31 17:55:34 +00:00
bool mouse_over_self = false ;
bool invalid_order = false ;
2023-02-09 10:29:46 +00:00
} ;
2023-09-04 16:07:16 +00:00
// `NotificationControlViewport`-derived class that additionally
2023-02-09 10:29:46 +00:00
// - allows start Dragging
// - stores mouse information of last event
2023-09-04 16:07:16 +00:00
class DragStart : public NotificationControlViewport {
GDCLASS ( DragStart , NotificationControlViewport ) ;
2023-02-09 10:29:46 +00:00
public :
MouseButton last_mouse_button ;
Point2i last_mouse_move_position ;
StringName drag_data_name = SNAME ( " Drag Data " ) ;
virtual Variant get_drag_data ( const Point2 & p_point ) override {
return drag_data_name ;
}
virtual void gui_input ( const Ref < InputEvent > & p_event ) override {
Ref < InputEventMouseButton > mb = p_event ;
if ( mb . is_valid ( ) ) {
last_mouse_button = mb - > get_button_index ( ) ;
return ;
}
Ref < InputEventMouseMotion > mm = p_event ;
if ( mm . is_valid ( ) ) {
last_mouse_move_position = mm - > get_position ( ) ;
return ;
}
}
} ;
2023-09-04 16:07:16 +00:00
// `NotificationControlViewport`-derived class that acts as a Drag and Drop target.
class DragTarget : public NotificationControlViewport {
GDCLASS ( DragTarget , NotificationControlViewport ) ;
2023-02-09 10:29:46 +00:00
2023-01-19 23:21:11 +00:00
protected :
void _notification ( int p_what ) {
switch ( p_what ) {
case NOTIFICATION_DRAG_BEGIN : {
during_drag = true ;
} break ;
case NOTIFICATION_DRAG_END : {
during_drag = false ;
} break ;
}
}
2023-02-09 10:29:46 +00:00
public :
Variant drag_data ;
2023-01-19 23:21:11 +00:00
bool valid_drop = false ;
bool during_drag = false ;
2023-02-09 10:29:46 +00:00
virtual bool can_drop_data ( const Point2 & p_point , const Variant & p_data ) const override {
StringName string_data = p_data ;
// Verify drag data is compatible.
if ( string_data ! = SNAME ( " Drag Data " ) ) {
return false ;
}
// Only the left half is droppable area.
if ( p_point . x * 2 > get_size ( ) . x ) {
return false ;
}
return true ;
}
virtual void drop_data ( const Point2 & p_point , const Variant & p_data ) override {
drag_data = p_data ;
2023-01-19 23:21:11 +00:00
valid_drop = true ;
2023-02-09 10:29:46 +00:00
}
} ;
TEST_CASE ( " [SceneTree][Viewport] Controls and InputEvent handling " ) {
DragStart * node_a = memnew ( DragStart ) ;
2023-10-31 17:55:34 +00:00
NotificationControlViewport * node_b = memnew ( NotificationControlViewport ) ;
2023-02-09 10:29:46 +00:00
Node2D * node_c = memnew ( Node2D ) ;
DragTarget * node_d = memnew ( DragTarget ) ;
2023-10-31 17:55:34 +00:00
NotificationControlViewport * node_e = memnew ( NotificationControlViewport ) ;
2023-02-09 10:29:46 +00:00
Node * node_f = memnew ( Node ) ;
2023-10-31 17:55:34 +00:00
NotificationControlViewport * node_g = memnew ( NotificationControlViewport ) ;
NotificationControlViewport * node_h = memnew ( NotificationControlViewport ) ;
NotificationControlViewport * node_i = memnew ( NotificationControlViewport ) ;
NotificationControlViewport * node_j = memnew ( NotificationControlViewport ) ;
2023-02-09 10:29:46 +00:00
node_a - > set_name ( SNAME ( " NodeA " ) ) ;
node_b - > set_name ( SNAME ( " NodeB " ) ) ;
node_c - > set_name ( SNAME ( " NodeC " ) ) ;
node_d - > set_name ( SNAME ( " NodeD " ) ) ;
node_e - > set_name ( SNAME ( " NodeE " ) ) ;
node_f - > set_name ( SNAME ( " NodeF " ) ) ;
node_g - > set_name ( SNAME ( " NodeG " ) ) ;
2023-10-31 17:55:34 +00:00
node_h - > set_name ( SNAME ( " NodeH " ) ) ;
node_i - > set_name ( SNAME ( " NodeI " ) ) ;
node_j - > set_name ( SNAME ( " NodeJ " ) ) ;
2023-02-09 10:29:46 +00:00
node_a - > set_position ( Point2i ( 0 , 0 ) ) ;
node_b - > set_position ( Point2i ( 10 , 10 ) ) ;
node_c - > set_position ( Point2i ( 0 , 0 ) ) ;
node_d - > set_position ( Point2i ( 10 , 10 ) ) ;
node_e - > set_position ( Point2i ( 10 , 100 ) ) ;
node_g - > set_position ( Point2i ( 10 , 100 ) ) ;
2023-10-31 17:55:34 +00:00
node_h - > set_position ( Point2i ( 10 , 120 ) ) ;
node_i - > set_position ( Point2i ( 2 , 0 ) ) ;
node_j - > set_position ( Point2i ( 2 , 0 ) ) ;
2023-02-09 10:29:46 +00:00
node_a - > set_size ( Point2i ( 30 , 30 ) ) ;
node_b - > set_size ( Point2i ( 30 , 30 ) ) ;
node_d - > set_size ( Point2i ( 30 , 30 ) ) ;
node_e - > set_size ( Point2i ( 10 , 10 ) ) ;
node_g - > set_size ( Point2i ( 10 , 10 ) ) ;
2023-10-31 17:55:34 +00:00
node_h - > set_size ( Point2i ( 10 , 10 ) ) ;
node_i - > set_size ( Point2i ( 10 , 10 ) ) ;
node_j - > set_size ( Point2i ( 10 , 10 ) ) ;
2023-02-09 10:29:46 +00:00
node_a - > set_focus_mode ( Control : : FOCUS_CLICK ) ;
node_b - > set_focus_mode ( Control : : FOCUS_CLICK ) ;
node_d - > set_focus_mode ( Control : : FOCUS_CLICK ) ;
node_e - > set_focus_mode ( Control : : FOCUS_CLICK ) ;
node_g - > set_focus_mode ( Control : : FOCUS_CLICK ) ;
2023-10-31 17:55:34 +00:00
node_h - > set_focus_mode ( Control : : FOCUS_CLICK ) ;
node_i - > set_focus_mode ( Control : : FOCUS_CLICK ) ;
node_j - > set_focus_mode ( Control : : FOCUS_CLICK ) ;
2023-02-09 10:29:46 +00:00
Window * root = SceneTree : : get_singleton ( ) - > get_root ( ) ;
DisplayServerMock * DS = ( DisplayServerMock * ) ( DisplayServer : : get_singleton ( ) ) ;
// Scene tree:
// - root
// - a (Control)
// - b (Control)
// - c (Node2D)
// - d (Control)
// - e (Control)
// - f (Node)
// - g (Control)
2023-10-31 17:55:34 +00:00
// - h (Control)
// - i (Control)
// - j (Control)
2023-02-09 10:29:46 +00:00
root - > add_child ( node_a ) ;
root - > add_child ( node_b ) ;
node_b - > add_child ( node_c ) ;
node_c - > add_child ( node_d ) ;
root - > add_child ( node_e ) ;
node_e - > add_child ( node_f ) ;
node_f - > add_child ( node_g ) ;
2023-10-31 17:55:34 +00:00
root - > add_child ( node_h ) ;
node_h - > add_child ( node_i ) ;
node_i - > add_child ( node_j ) ;
2023-02-09 10:29:46 +00:00
Point2i on_a = Point2i ( 5 , 5 ) ;
Point2i on_b = Point2i ( 15 , 15 ) ;
Point2i on_d = Point2i ( 25 , 25 ) ;
Point2i on_e = Point2i ( 15 , 105 ) ;
Point2i on_g = Point2i ( 15 , 105 ) ;
2023-10-31 17:55:34 +00:00
Point2i on_i = Point2i ( 13 , 125 ) ;
Point2i on_j = Point2i ( 15 , 125 ) ;
2023-02-09 10:29:46 +00:00
Point2i on_background = Point2i ( 500 , 500 ) ;
Point2i on_outside = Point2i ( - 1 , - 1 ) ;
// Unit tests for Viewport::gui_find_control and Viewport::_gui_find_control_at_pos
SUBCASE ( " [VIEWPORT][GuiFindControl] Finding Controls at a Viewport-position " ) {
// FIXME: It is extremely difficult to create a situation where the Control has a zero determinant.
// Leaving that if-branch untested.
SUBCASE ( " [VIEWPORT][GuiFindControl] Basic position tests " ) {
CHECK ( root - > gui_find_control ( on_a ) = = node_a ) ;
CHECK ( root - > gui_find_control ( on_b ) = = node_b ) ;
CHECK ( root - > gui_find_control ( on_d ) = = node_d ) ;
CHECK ( root - > gui_find_control ( on_e ) = = node_g ) ; // Node F makes G a Root Control at the same position as E
CHECK ( root - > gui_find_control ( on_g ) = = node_g ) ;
CHECK_FALSE ( root - > gui_find_control ( on_background ) ) ;
}
SUBCASE ( " [VIEWPORT][GuiFindControl] Invisible nodes are not considered as results. " ) {
// Non-Root Control
node_d - > hide ( ) ;
CHECK ( root - > gui_find_control ( on_d ) = = node_b ) ;
// Root Control
node_b - > hide ( ) ;
CHECK ( root - > gui_find_control ( on_b ) = = node_a ) ;
}
SUBCASE ( " [VIEWPORT][GuiFindControl] Root Control with CanvasItem as parent is affected by parent's transform. " ) {
node_b - > remove_child ( node_c ) ;
node_c - > set_position ( Point2i ( 50 , 50 ) ) ;
root - > add_child ( node_c ) ;
CHECK ( root - > gui_find_control ( Point2i ( 65 , 65 ) ) = = node_d ) ;
}
SUBCASE ( " [VIEWPORT][GuiFindControl] Control Contents Clipping clips accessible position of children. " ) {
CHECK_FALSE ( node_b - > is_clipping_contents ( ) ) ;
CHECK ( root - > gui_find_control ( on_d + Point2i ( 20 , 20 ) ) = = node_d ) ;
node_b - > set_clip_contents ( true ) ;
CHECK ( root - > gui_find_control ( on_d ) = = node_d ) ;
CHECK_FALSE ( root - > gui_find_control ( on_d + Point2i ( 20 , 20 ) ) ) ;
}
SUBCASE ( " [VIEWPORT][GuiFindControl] Top Level Control as descendant of CanvasItem isn't affected by parent's transform. " ) {
CHECK ( root - > gui_find_control ( on_d + Point2i ( 20 , 20 ) ) = = node_d ) ;
node_d - > set_as_top_level ( true ) ;
CHECK_FALSE ( root - > gui_find_control ( on_d + Point2i ( 20 , 20 ) ) ) ;
CHECK ( root - > gui_find_control ( on_b ) = = node_d ) ;
}
}
SUBCASE ( " [Viewport][GuiInputEvent] nullptr as argument doesn't lead to a crash. " ) {
2023-05-09 13:50:48 +00:00
ERR_PRINT_OFF ;
2023-08-14 11:02:40 +00:00
root - > push_input ( nullptr ) ;
2023-05-09 13:50:48 +00:00
ERR_PRINT_ON ;
2023-02-09 10:29:46 +00:00
}
// Unit tests for Viewport::_gui_input_event (Mouse Buttons)
SUBCASE ( " [Viewport][GuiInputEvent] Mouse Button Down/Up. " ) {
SUBCASE ( " [Viewport][GuiInputEvent] Mouse Button Control Focus Change. " ) {
SUBCASE ( " [Viewport][GuiInputEvent] Grab Focus while no Control has focus. " ) {
CHECK_FALSE ( root - > gui_get_focus_owner ( ) ) ;
// Click on A
SEND_GUI_MOUSE_BUTTON_EVENT ( on_a , MouseButton : : LEFT , MouseButtonMask : : LEFT , Key : : NONE ) ;
CHECK ( node_a - > has_focus ( ) ) ;
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT ( on_a , MouseButton : : LEFT , MouseButtonMask : : NONE , Key : : NONE ) ;
}
SUBCASE ( " [Viewport][GuiInputEvent] Grab Focus from other Control. " ) {
node_a - > grab_focus ( ) ;
CHECK ( node_a - > has_focus ( ) ) ;
// Click on D
SEND_GUI_MOUSE_BUTTON_EVENT ( on_d , MouseButton : : LEFT , MouseButtonMask : : LEFT , Key : : NONE ) ;
CHECK ( node_d - > has_focus ( ) ) ;
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT ( on_d , MouseButton : : LEFT , MouseButtonMask : : NONE , Key : : NONE ) ;
}
SUBCASE ( " [Viewport][GuiInputEvent] Non-CanvasItem breaks Transform hierarchy. " ) {
CHECK_FALSE ( root - > gui_get_focus_owner ( ) ) ;
// Click on G absolute coordinates
SEND_GUI_MOUSE_BUTTON_EVENT ( Point2i ( 15 , 105 ) , MouseButton : : LEFT , MouseButtonMask : : LEFT , Key : : NONE ) ;
CHECK ( node_g - > has_focus ( ) ) ;
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT ( Point2i ( 15 , 105 ) , MouseButton : : LEFT , MouseButtonMask : : NONE , Key : : NONE ) ;
}
SUBCASE ( " [Viewport][GuiInputEvent] No Focus change when clicking in background. " ) {
CHECK_FALSE ( root - > gui_get_focus_owner ( ) ) ;
SEND_GUI_MOUSE_BUTTON_EVENT ( on_background , MouseButton : : LEFT , MouseButtonMask : : LEFT , Key : : NONE ) ;
CHECK_FALSE ( root - > gui_get_focus_owner ( ) ) ;
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT ( on_background , MouseButton : : LEFT , MouseButtonMask : : NONE , Key : : NONE ) ;
node_a - > grab_focus ( ) ;
CHECK ( node_a - > has_focus ( ) ) ;
SEND_GUI_MOUSE_BUTTON_EVENT ( on_background , MouseButton : : LEFT , MouseButtonMask : : LEFT , Key : : NONE ) ;
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT ( on_background , MouseButton : : LEFT , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK ( node_a - > has_focus ( ) ) ;
}
SUBCASE ( " [Viewport][GuiInputEvent] Mouse Button No Focus Steal while other Mouse Button is pressed. " ) {
CHECK_FALSE ( root - > gui_get_focus_owner ( ) ) ;
SEND_GUI_MOUSE_BUTTON_EVENT ( on_a , MouseButton : : LEFT , MouseButtonMask : : LEFT , Key : : NONE ) ;
CHECK ( node_a - > has_focus ( ) ) ;
SEND_GUI_MOUSE_BUTTON_EVENT ( on_b , MouseButton : : RIGHT , ( int ) MouseButtonMask : : LEFT | ( int ) MouseButtonMask : : RIGHT , Key : : NONE ) ;
CHECK ( node_a - > has_focus ( ) ) ;
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT ( on_b , MouseButton : : RIGHT , MouseButtonMask : : LEFT , Key : : NONE ) ;
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT ( on_b , MouseButton : : LEFT , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK ( node_a - > has_focus ( ) ) ;
}
SUBCASE ( " [Viewport][GuiInputEvent] Allow Focus Steal with LMB while other Mouse Button is held down and was initially pressed without being over a Control. " ) {
// TODO: Not sure, if this is intended behavior, but this is an edge case.
CHECK_FALSE ( root - > gui_get_focus_owner ( ) ) ;
SEND_GUI_MOUSE_BUTTON_EVENT ( on_background , MouseButton : : RIGHT , MouseButtonMask : : RIGHT , Key : : NONE ) ;
CHECK_FALSE ( root - > gui_get_focus_owner ( ) ) ;
SEND_GUI_MOUSE_BUTTON_EVENT ( on_a , MouseButton : : LEFT , ( int ) MouseButtonMask : : LEFT | ( int ) MouseButtonMask : : RIGHT , Key : : NONE ) ;
CHECK ( node_a - > has_focus ( ) ) ;
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT ( on_a , MouseButton : : LEFT , MouseButtonMask : : RIGHT , Key : : NONE ) ;
CHECK ( node_a - > has_focus ( ) ) ;
SEND_GUI_MOUSE_BUTTON_EVENT ( on_b , MouseButton : : LEFT , ( int ) MouseButtonMask : : LEFT | ( int ) MouseButtonMask : : RIGHT , Key : : NONE ) ;
CHECK ( node_b - > has_focus ( ) ) ;
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT ( on_d , MouseButton : : LEFT , MouseButtonMask : : RIGHT , Key : : NONE ) ;
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT ( on_d , MouseButton : : RIGHT , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK ( node_b - > has_focus ( ) ) ;
}
SUBCASE ( " [Viewport][GuiInputEvent] Ignore Focus from Mouse Buttons when mouse-filter is set to ignore. " ) {
node_d - > grab_focus ( ) ;
node_d - > set_mouse_filter ( Control : : MOUSE_FILTER_IGNORE ) ;
CHECK ( node_d - > has_focus ( ) ) ;
// Click on overlapping area B&D.
SEND_GUI_MOUSE_BUTTON_EVENT ( on_d , MouseButton : : LEFT , MouseButtonMask : : LEFT , Key : : NONE ) ;
CHECK ( node_b - > has_focus ( ) ) ;
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT ( on_d , MouseButton : : LEFT , MouseButtonMask : : NONE , Key : : NONE ) ;
}
SUBCASE ( " [Viewport][GuiInputEvent] RMB doesn't grab focus. " ) {
node_a - > grab_focus ( ) ;
CHECK ( node_a - > has_focus ( ) ) ;
SEND_GUI_MOUSE_BUTTON_EVENT ( on_d , MouseButton : : RIGHT , MouseButtonMask : : RIGHT , Key : : NONE ) ;
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT ( on_d , MouseButton : : RIGHT , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK ( node_a - > has_focus ( ) ) ;
}
SUBCASE ( " [Viewport][GuiInputEvent] LMB on unfocusable Control doesn't grab focus. " ) {
CHECK_FALSE ( node_g - > has_focus ( ) ) ;
node_g - > set_focus_mode ( Control : : FOCUS_NONE ) ;
SEND_GUI_MOUSE_BUTTON_EVENT ( on_g , MouseButton : : LEFT , MouseButtonMask : : LEFT , Key : : NONE ) ;
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT ( on_g , MouseButton : : LEFT , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK_FALSE ( node_g - > has_focus ( ) ) ;
// Now verify the opposite with FOCUS_CLICK
node_g - > set_focus_mode ( Control : : FOCUS_CLICK ) ;
SEND_GUI_MOUSE_BUTTON_EVENT ( on_g , MouseButton : : LEFT , MouseButtonMask : : LEFT , Key : : NONE ) ;
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT ( on_g , MouseButton : : LEFT , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK ( node_g - > has_focus ( ) ) ;
node_g - > set_focus_mode ( Control : : FOCUS_CLICK ) ;
}
SUBCASE ( " [Viewport][GuiInputEvent] Signal 'gui_focus_changed' is only emitted if a previously unfocused Control grabs focus. " ) {
SIGNAL_WATCH ( root , SNAME ( " gui_focus_changed " ) ) ;
Array node_array ;
node_array . push_back ( node_a ) ;
Array signal_args ;
signal_args . push_back ( node_array ) ;
SEND_GUI_MOUSE_BUTTON_EVENT ( on_a , MouseButton : : LEFT , MouseButtonMask : : LEFT , Key : : NONE ) ;
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT ( on_a , MouseButton : : LEFT , MouseButtonMask : : NONE , Key : : NONE ) ;
SIGNAL_CHECK ( SNAME ( " gui_focus_changed " ) , signal_args ) ;
SEND_GUI_MOUSE_BUTTON_EVENT ( on_a , MouseButton : : LEFT , MouseButtonMask : : LEFT , Key : : NONE ) ;
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT ( on_a , MouseButton : : LEFT , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK ( node_a - > has_focus ( ) ) ;
SIGNAL_CHECK_FALSE ( SNAME ( " gui_focus_changed " ) ) ;
SIGNAL_UNWATCH ( root , SNAME ( " gui_focus_changed " ) ) ;
}
SUBCASE ( " [Viewport][GuiInputEvent] Focus Propagation to parent items. " ) {
SUBCASE ( " [Viewport][GuiInputEvent] Unfocusable Control with MOUSE_FILTER_PASS propagates focus to parent CanvasItem. " ) {
node_d - > set_focus_mode ( Control : : FOCUS_NONE ) ;
node_d - > set_mouse_filter ( Control : : MOUSE_FILTER_PASS ) ;
SEND_GUI_MOUSE_BUTTON_EVENT ( on_d + Point2i ( 20 , 20 ) , MouseButton : : LEFT , MouseButtonMask : : LEFT , Key : : NONE ) ;
CHECK ( node_b - > has_focus ( ) ) ;
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT ( on_d + Point2i ( 20 , 20 ) , MouseButton : : LEFT , MouseButtonMask : : NONE , Key : : NONE ) ;
// Verify break condition for Root Control.
node_a - > set_focus_mode ( Control : : FOCUS_NONE ) ;
node_a - > set_mouse_filter ( Control : : MOUSE_FILTER_PASS ) ;
SEND_GUI_MOUSE_BUTTON_EVENT ( on_a , MouseButton : : LEFT , MouseButtonMask : : LEFT , Key : : NONE ) ;
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT ( on_a , MouseButton : : LEFT , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK ( node_b - > has_focus ( ) ) ;
}
SUBCASE ( " [Viewport][GuiInputEvent] Top Level CanvasItem stops focus propagation. " ) {
node_d - > set_focus_mode ( Control : : FOCUS_NONE ) ;
node_d - > set_mouse_filter ( Control : : MOUSE_FILTER_PASS ) ;
node_c - > set_as_top_level ( true ) ;
SEND_GUI_MOUSE_BUTTON_EVENT ( on_b , MouseButton : : LEFT , MouseButtonMask : : LEFT , Key : : NONE ) ;
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT ( on_b , MouseButton : : LEFT , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK_FALSE ( root - > gui_get_focus_owner ( ) ) ;
node_d - > set_focus_mode ( Control : : FOCUS_CLICK ) ;
SEND_GUI_MOUSE_BUTTON_EVENT ( on_b , MouseButton : : LEFT , MouseButtonMask : : LEFT , Key : : NONE ) ;
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT ( on_b , MouseButton : : LEFT , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK ( node_d - > has_focus ( ) ) ;
}
}
}
SUBCASE ( " [Viewport][GuiInputEvent] Process-Mode affects, if GUI Mouse Button Events are processed. " ) {
node_a - > last_mouse_button = MouseButton : : NONE ;
node_a - > set_process_mode ( Node : : PROCESS_MODE_DISABLED ) ;
SEND_GUI_MOUSE_BUTTON_EVENT ( on_a , MouseButton : : LEFT , MouseButtonMask : : LEFT , Key : : NONE ) ;
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT ( on_a , MouseButton : : LEFT , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK ( node_a - > last_mouse_button = = MouseButton : : NONE ) ;
// Now verify that with allowed processing the event is processed.
node_a - > set_process_mode ( Node : : PROCESS_MODE_ALWAYS ) ;
SEND_GUI_MOUSE_BUTTON_EVENT ( on_a , MouseButton : : LEFT , MouseButtonMask : : LEFT , Key : : NONE ) ;
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT ( on_a , MouseButton : : LEFT , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK ( node_a - > last_mouse_button = = MouseButton : : LEFT ) ;
}
}
// Unit tests for Viewport::_gui_input_event (Mouse Motion)
SUBCASE ( " [Viewport][GuiInputEvent] Mouse Motion " ) {
// FIXME: Tooltips are not yet tested. They likely require an internal clock.
2023-10-31 17:55:34 +00:00
SUBCASE ( " [Viewport][GuiInputEvent] Mouse Motion changes the Control that it is over. " ) {
2023-02-09 10:29:46 +00:00
SEND_GUI_MOUSE_MOTION_EVENT ( on_background , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK_FALSE ( node_a - > mouse_over ) ;
2023-10-31 17:55:34 +00:00
CHECK_FALSE ( node_a - > mouse_over_self ) ;
2023-02-09 10:29:46 +00:00
// Move over Control.
SEND_GUI_MOUSE_MOTION_EVENT ( on_a , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK ( node_a - > mouse_over ) ;
2023-10-31 17:55:34 +00:00
CHECK ( node_a - > mouse_over_self ) ;
2023-02-09 10:29:46 +00:00
// No change.
SEND_GUI_MOUSE_MOTION_EVENT ( on_a + Point2i ( 1 , 1 ) , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK ( node_a - > mouse_over ) ;
2023-10-31 17:55:34 +00:00
CHECK ( node_a - > mouse_over_self ) ;
2023-02-09 10:29:46 +00:00
// Move over other Control.
SEND_GUI_MOUSE_MOTION_EVENT ( on_d , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK_FALSE ( node_a - > mouse_over ) ;
2023-10-31 17:55:34 +00:00
CHECK_FALSE ( node_a - > mouse_over_self ) ;
2023-02-09 10:29:46 +00:00
CHECK ( node_d - > mouse_over ) ;
2023-10-31 17:55:34 +00:00
CHECK ( node_d - > mouse_over_self ) ;
2023-02-09 10:29:46 +00:00
2023-10-31 17:55:34 +00:00
// Move to background.
2023-02-09 10:29:46 +00:00
SEND_GUI_MOUSE_MOTION_EVENT ( on_background , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK_FALSE ( node_d - > mouse_over ) ;
2023-10-31 17:55:34 +00:00
CHECK_FALSE ( node_d - > mouse_over_self ) ;
CHECK_FALSE ( node_a - > invalid_order ) ;
CHECK_FALSE ( node_d - > invalid_order ) ;
}
SUBCASE ( " [Viewport][GuiInputEvent] Mouse Enter/Exit notification propagation. " ) {
node_d - > set_mouse_filter ( Control : : MOUSE_FILTER_PASS ) ;
node_g - > set_mouse_filter ( Control : : MOUSE_FILTER_PASS ) ;
SEND_GUI_MOUSE_MOTION_EVENT ( on_background , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK_FALSE ( node_b - > mouse_over ) ;
CHECK_FALSE ( node_b - > mouse_over_self ) ;
CHECK_FALSE ( node_d - > mouse_over ) ;
CHECK_FALSE ( node_d - > mouse_over_self ) ;
// Move to Control node_d. node_b receives mouse over since it is only separated by a CanvasItem.
SEND_GUI_MOUSE_MOTION_EVENT ( on_d , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK ( node_b - > mouse_over ) ;
CHECK_FALSE ( node_b - > mouse_over_self ) ;
CHECK ( node_d - > mouse_over ) ;
CHECK ( node_d - > mouse_over_self ) ;
// Move to background.
SEND_GUI_MOUSE_MOTION_EVENT ( on_background , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK_FALSE ( node_b - > mouse_over ) ;
CHECK_FALSE ( node_b - > mouse_over_self ) ;
CHECK_FALSE ( node_d - > mouse_over ) ;
CHECK_FALSE ( node_d - > mouse_over_self ) ;
CHECK_FALSE ( node_e - > mouse_over ) ;
CHECK_FALSE ( node_e - > mouse_over_self ) ;
CHECK_FALSE ( node_g - > mouse_over ) ;
CHECK_FALSE ( node_g - > mouse_over_self ) ;
// Move to Control node_g. node_g receives mouse over but node_e does not since it is separated by a non-CanvasItem.
SEND_GUI_MOUSE_MOTION_EVENT ( on_g , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK_FALSE ( node_e - > mouse_over ) ;
CHECK_FALSE ( node_e - > mouse_over_self ) ;
CHECK ( node_g - > mouse_over ) ;
CHECK ( node_g - > mouse_over_self ) ;
// Move to background.
SEND_GUI_MOUSE_MOTION_EVENT ( on_background , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK_FALSE ( node_e - > mouse_over ) ;
CHECK_FALSE ( node_e - > mouse_over_self ) ;
CHECK_FALSE ( node_g - > mouse_over ) ;
CHECK_FALSE ( node_g - > mouse_over_self ) ;
CHECK_FALSE ( node_b - > invalid_order ) ;
CHECK_FALSE ( node_d - > invalid_order ) ;
CHECK_FALSE ( node_e - > invalid_order ) ;
CHECK_FALSE ( node_g - > invalid_order ) ;
node_d - > set_mouse_filter ( Control : : MOUSE_FILTER_STOP ) ;
node_g - > set_mouse_filter ( Control : : MOUSE_FILTER_STOP ) ;
}
SUBCASE ( " [Viewport][GuiInputEvent] Mouse Enter/Exit notification propagation when moving into child. " ) {
2024-05-13 14:56:03 +00:00
SIGNAL_WATCH ( node_i , SceneStringName ( mouse_entered ) ) ;
SIGNAL_WATCH ( node_i , SceneStringName ( mouse_exited ) ) ;
2023-10-31 17:55:34 +00:00
Array signal_args ;
signal_args . push_back ( Array ( ) ) ;
node_j - > set_mouse_filter ( Control : : MOUSE_FILTER_PASS ) ;
// Move to background.
SEND_GUI_MOUSE_MOTION_EVENT ( on_background , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK_FALSE ( node_i - > mouse_over ) ;
CHECK_FALSE ( node_i - > mouse_over_self ) ;
CHECK_FALSE ( node_j - > mouse_over ) ;
CHECK_FALSE ( node_j - > mouse_over_self ) ;
// Move to Control node_i.
SEND_GUI_MOUSE_MOTION_EVENT ( on_i , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK ( node_i - > mouse_over ) ;
CHECK ( node_i - > mouse_over_self ) ;
CHECK_FALSE ( node_j - > mouse_over ) ;
CHECK_FALSE ( node_j - > mouse_over_self ) ;
2024-05-13 14:56:03 +00:00
SIGNAL_CHECK ( SceneStringName ( mouse_entered ) , signal_args ) ;
SIGNAL_CHECK_FALSE ( SceneStringName ( mouse_exited ) ) ;
2023-10-31 17:55:34 +00:00
// Move to child Control node_j. node_i should not receive any new Mouse Enter signals.
SEND_GUI_MOUSE_MOTION_EVENT ( on_j , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK ( node_i - > mouse_over ) ;
CHECK_FALSE ( node_i - > mouse_over_self ) ;
CHECK ( node_j - > mouse_over ) ;
CHECK ( node_j - > mouse_over_self ) ;
2024-05-13 14:56:03 +00:00
SIGNAL_CHECK_FALSE ( SceneStringName ( mouse_entered ) ) ;
SIGNAL_CHECK_FALSE ( SceneStringName ( mouse_exited ) ) ;
2023-10-31 17:55:34 +00:00
// Move to parent Control node_i. node_i should not receive any new Mouse Enter signals.
SEND_GUI_MOUSE_MOTION_EVENT ( on_i , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK ( node_i - > mouse_over ) ;
CHECK ( node_i - > mouse_over_self ) ;
CHECK_FALSE ( node_j - > mouse_over ) ;
CHECK_FALSE ( node_j - > mouse_over_self ) ;
2024-05-13 14:56:03 +00:00
SIGNAL_CHECK_FALSE ( SceneStringName ( mouse_entered ) ) ;
SIGNAL_CHECK_FALSE ( SceneStringName ( mouse_exited ) ) ;
2023-10-31 17:55:34 +00:00
// Move to background.
SEND_GUI_MOUSE_MOTION_EVENT ( on_background , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK_FALSE ( node_i - > mouse_over ) ;
CHECK_FALSE ( node_i - > mouse_over_self ) ;
CHECK_FALSE ( node_j - > mouse_over ) ;
CHECK_FALSE ( node_j - > mouse_over_self ) ;
2024-05-13 14:56:03 +00:00
SIGNAL_CHECK_FALSE ( SceneStringName ( mouse_entered ) ) ;
SIGNAL_CHECK ( SceneStringName ( mouse_exited ) , signal_args ) ;
2023-10-31 17:55:34 +00:00
CHECK_FALSE ( node_i - > invalid_order ) ;
CHECK_FALSE ( node_j - > invalid_order ) ;
node_j - > set_mouse_filter ( Control : : MOUSE_FILTER_STOP ) ;
2024-05-13 14:56:03 +00:00
SIGNAL_UNWATCH ( node_i , SceneStringName ( mouse_entered ) ) ;
SIGNAL_UNWATCH ( node_i , SceneStringName ( mouse_exited ) ) ;
2023-10-31 17:55:34 +00:00
}
SUBCASE ( " [Viewport][GuiInputEvent] Mouse Enter/Exit notification propagation with top level. " ) {
node_c - > set_as_top_level ( true ) ;
node_i - > set_as_top_level ( true ) ;
node_c - > set_position ( node_b - > get_global_position ( ) ) ;
node_i - > set_position ( node_h - > get_global_position ( ) ) ;
node_d - > set_mouse_filter ( Control : : MOUSE_FILTER_PASS ) ;
node_i - > set_mouse_filter ( Control : : MOUSE_FILTER_PASS ) ;
node_j - > set_mouse_filter ( Control : : MOUSE_FILTER_PASS ) ;
SEND_GUI_MOUSE_MOTION_EVENT ( on_background , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK_FALSE ( node_b - > mouse_over ) ;
CHECK_FALSE ( node_b - > mouse_over_self ) ;
CHECK_FALSE ( node_d - > mouse_over ) ;
CHECK_FALSE ( node_d - > mouse_over_self ) ;
// Move to Control node_d. node_b does not receive mouse over since node_c is top level.
SEND_GUI_MOUSE_MOTION_EVENT ( on_d , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK_FALSE ( node_b - > mouse_over ) ;
CHECK_FALSE ( node_b - > mouse_over_self ) ;
CHECK ( node_d - > mouse_over ) ;
CHECK ( node_d - > mouse_over_self ) ;
// Move to background.
SEND_GUI_MOUSE_MOTION_EVENT ( on_background , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK_FALSE ( node_b - > mouse_over ) ;
CHECK_FALSE ( node_b - > mouse_over_self ) ;
CHECK_FALSE ( node_d - > mouse_over ) ;
CHECK_FALSE ( node_d - > mouse_over_self ) ;
CHECK_FALSE ( node_g - > mouse_over ) ;
CHECK_FALSE ( node_g - > mouse_over_self ) ;
CHECK_FALSE ( node_h - > mouse_over ) ;
CHECK_FALSE ( node_h - > mouse_over_self ) ;
CHECK_FALSE ( node_i - > mouse_over ) ;
CHECK_FALSE ( node_i - > mouse_over_self ) ;
// Move to Control node_j. node_h does not receive mouse over since node_i is top level.
SEND_GUI_MOUSE_MOTION_EVENT ( on_j , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK_FALSE ( node_h - > mouse_over ) ;
CHECK_FALSE ( node_h - > mouse_over_self ) ;
CHECK ( node_i - > mouse_over ) ;
CHECK_FALSE ( node_i - > mouse_over_self ) ;
CHECK ( node_j - > mouse_over ) ;
CHECK ( node_j - > mouse_over_self ) ;
// Move to background.
SEND_GUI_MOUSE_MOTION_EVENT ( on_background , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK_FALSE ( node_h - > mouse_over ) ;
CHECK_FALSE ( node_h - > mouse_over_self ) ;
CHECK_FALSE ( node_i - > mouse_over ) ;
CHECK_FALSE ( node_i - > mouse_over_self ) ;
CHECK_FALSE ( node_j - > mouse_over ) ;
CHECK_FALSE ( node_j - > mouse_over_self ) ;
CHECK_FALSE ( node_b - > invalid_order ) ;
CHECK_FALSE ( node_d - > invalid_order ) ;
CHECK_FALSE ( node_e - > invalid_order ) ;
CHECK_FALSE ( node_h - > invalid_order ) ;
CHECK_FALSE ( node_i - > invalid_order ) ;
CHECK_FALSE ( node_j - > invalid_order ) ;
node_c - > set_as_top_level ( false ) ;
node_i - > set_as_top_level ( false ) ;
node_c - > set_position ( Point2i ( 0 , 0 ) ) ;
node_i - > set_position ( Point2i ( 0 , 0 ) ) ;
node_d - > set_mouse_filter ( Control : : MOUSE_FILTER_STOP ) ;
node_i - > set_mouse_filter ( Control : : MOUSE_FILTER_STOP ) ;
node_j - > set_mouse_filter ( Control : : MOUSE_FILTER_STOP ) ;
}
SUBCASE ( " [Viewport][GuiInputEvent] Mouse Enter/Exit notification propagation with mouse filter stop. " ) {
node_i - > set_mouse_filter ( Control : : MOUSE_FILTER_STOP ) ;
node_j - > set_mouse_filter ( Control : : MOUSE_FILTER_PASS ) ;
// Move to background.
SEND_GUI_MOUSE_MOTION_EVENT ( on_background , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK_FALSE ( node_h - > mouse_over ) ;
CHECK_FALSE ( node_h - > mouse_over_self ) ;
CHECK_FALSE ( node_i - > mouse_over ) ;
CHECK_FALSE ( node_i - > mouse_over_self ) ;
CHECK_FALSE ( node_j - > mouse_over ) ;
CHECK_FALSE ( node_j - > mouse_over_self ) ;
// Move to Control node_j. node_h does not receive mouse over since node_i is MOUSE_FILTER_STOP.
SEND_GUI_MOUSE_MOTION_EVENT ( on_j , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK_FALSE ( node_h - > mouse_over ) ;
CHECK_FALSE ( node_h - > mouse_over_self ) ;
CHECK ( node_i - > mouse_over ) ;
CHECK_FALSE ( node_i - > mouse_over_self ) ;
CHECK ( node_j - > mouse_over ) ;
CHECK ( node_j - > mouse_over_self ) ;
// Move to background.
SEND_GUI_MOUSE_MOTION_EVENT ( on_background , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK_FALSE ( node_h - > mouse_over ) ;
CHECK_FALSE ( node_h - > mouse_over_self ) ;
CHECK_FALSE ( node_i - > mouse_over ) ;
CHECK_FALSE ( node_i - > mouse_over_self ) ;
CHECK_FALSE ( node_j - > mouse_over ) ;
CHECK_FALSE ( node_j - > mouse_over_self ) ;
CHECK_FALSE ( node_h - > invalid_order ) ;
CHECK_FALSE ( node_i - > invalid_order ) ;
CHECK_FALSE ( node_j - > invalid_order ) ;
node_i - > set_mouse_filter ( Control : : MOUSE_FILTER_STOP ) ;
node_j - > set_mouse_filter ( Control : : MOUSE_FILTER_STOP ) ;
}
SUBCASE ( " [Viewport][GuiInputEvent] Mouse Enter/Exit notification propagation with mouse filter ignore. " ) {
node_i - > set_mouse_filter ( Control : : MOUSE_FILTER_IGNORE ) ;
node_j - > set_mouse_filter ( Control : : MOUSE_FILTER_PASS ) ;
// Move to background.
SEND_GUI_MOUSE_MOTION_EVENT ( on_background , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK_FALSE ( node_h - > mouse_over ) ;
CHECK_FALSE ( node_h - > mouse_over_self ) ;
CHECK_FALSE ( node_i - > mouse_over ) ;
CHECK_FALSE ( node_i - > mouse_over_self ) ;
CHECK_FALSE ( node_j - > mouse_over ) ;
CHECK_FALSE ( node_j - > mouse_over_self ) ;
// Move to Control node_j. node_i does not receive mouse over since node_i is MOUSE_FILTER_IGNORE.
SEND_GUI_MOUSE_MOTION_EVENT ( on_j , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK ( node_h - > mouse_over ) ;
CHECK_FALSE ( node_h - > mouse_over_self ) ;
CHECK_FALSE ( node_i - > mouse_over ) ;
CHECK_FALSE ( node_i - > mouse_over_self ) ;
CHECK ( node_j - > mouse_over ) ;
CHECK ( node_j - > mouse_over_self ) ;
// Move to background.
SEND_GUI_MOUSE_MOTION_EVENT ( on_background , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK_FALSE ( node_h - > mouse_over ) ;
CHECK_FALSE ( node_h - > mouse_over_self ) ;
CHECK_FALSE ( node_i - > mouse_over ) ;
CHECK_FALSE ( node_i - > mouse_over_self ) ;
CHECK_FALSE ( node_j - > mouse_over ) ;
CHECK_FALSE ( node_j - > mouse_over_self ) ;
CHECK_FALSE ( node_h - > invalid_order ) ;
CHECK_FALSE ( node_i - > invalid_order ) ;
CHECK_FALSE ( node_j - > invalid_order ) ;
node_i - > set_mouse_filter ( Control : : MOUSE_FILTER_STOP ) ;
node_j - > set_mouse_filter ( Control : : MOUSE_FILTER_STOP ) ;
}
SUBCASE ( " [Viewport][GuiInputEvent] Mouse Enter/Exit notification when changing top level. " ) {
2024-05-13 14:56:03 +00:00
SIGNAL_WATCH ( node_i , SceneStringName ( mouse_entered ) ) ;
SIGNAL_WATCH ( node_i , SceneStringName ( mouse_exited ) ) ;
2023-10-31 17:55:34 +00:00
Array signal_args ;
signal_args . push_back ( Array ( ) ) ;
node_d - > set_mouse_filter ( Control : : MOUSE_FILTER_PASS ) ;
node_i - > set_mouse_filter ( Control : : MOUSE_FILTER_PASS ) ;
node_j - > set_mouse_filter ( Control : : MOUSE_FILTER_PASS ) ;
// Move to Control node_d.
SEND_GUI_MOUSE_MOTION_EVENT ( on_d , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK ( node_b - > mouse_over ) ;
CHECK_FALSE ( node_b - > mouse_over_self ) ;
CHECK ( node_d - > mouse_over ) ;
CHECK ( node_d - > mouse_over_self ) ;
// Change node_c to be top level. node_b should receive Mouse Exit.
node_c - > set_as_top_level ( true ) ;
CHECK_FALSE ( node_b - > mouse_over ) ;
CHECK_FALSE ( node_b - > mouse_over_self ) ;
CHECK ( node_d - > mouse_over ) ;
CHECK ( node_d - > mouse_over_self ) ;
// Change node_c to be not top level. node_b should receive Mouse Enter.
node_c - > set_as_top_level ( false ) ;
CHECK ( node_b - > mouse_over ) ;
CHECK_FALSE ( node_b - > mouse_over_self ) ;
CHECK ( node_d - > mouse_over ) ;
CHECK ( node_d - > mouse_over_self ) ;
// Move to Control node_j.
SEND_GUI_MOUSE_MOTION_EVENT ( on_j , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK ( node_h - > mouse_over ) ;
CHECK_FALSE ( node_h - > mouse_over_self ) ;
CHECK ( node_i - > mouse_over ) ;
CHECK_FALSE ( node_i - > mouse_over_self ) ;
CHECK ( node_j - > mouse_over ) ;
CHECK ( node_j - > mouse_over_self ) ;
2024-05-13 14:56:03 +00:00
SIGNAL_CHECK ( SceneStringName ( mouse_entered ) , signal_args ) ;
SIGNAL_CHECK_FALSE ( SceneStringName ( mouse_exited ) ) ;
2023-10-31 17:55:34 +00:00
// Change node_i to top level. node_h should receive Mouse Exit. node_i should not receive any new signals.
node_i - > set_as_top_level ( true ) ;
CHECK_FALSE ( node_h - > mouse_over ) ;
CHECK_FALSE ( node_h - > mouse_over_self ) ;
CHECK ( node_i - > mouse_over ) ;
CHECK_FALSE ( node_i - > mouse_over_self ) ;
CHECK ( node_j - > mouse_over ) ;
CHECK ( node_j - > mouse_over_self ) ;
2024-05-13 14:56:03 +00:00
SIGNAL_CHECK_FALSE ( SceneStringName ( mouse_entered ) ) ;
SIGNAL_CHECK_FALSE ( SceneStringName ( mouse_exited ) ) ;
2023-10-31 17:55:34 +00:00
// Change node_i to not top level. node_h should receive Mouse Enter. node_i should not receive any new signals.
node_i - > set_as_top_level ( false ) ;
CHECK ( node_h - > mouse_over ) ;
CHECK_FALSE ( node_h - > mouse_over_self ) ;
CHECK ( node_i - > mouse_over ) ;
CHECK_FALSE ( node_i - > mouse_over_self ) ;
CHECK ( node_j - > mouse_over ) ;
CHECK ( node_j - > mouse_over_self ) ;
2024-05-13 14:56:03 +00:00
SIGNAL_CHECK_FALSE ( SceneStringName ( mouse_entered ) ) ;
SIGNAL_CHECK_FALSE ( SceneStringName ( mouse_exited ) ) ;
2023-10-31 17:55:34 +00:00
CHECK_FALSE ( node_b - > invalid_order ) ;
CHECK_FALSE ( node_d - > invalid_order ) ;
CHECK_FALSE ( node_e - > invalid_order ) ;
CHECK_FALSE ( node_h - > invalid_order ) ;
CHECK_FALSE ( node_i - > invalid_order ) ;
CHECK_FALSE ( node_j - > invalid_order ) ;
node_d - > set_mouse_filter ( Control : : MOUSE_FILTER_STOP ) ;
node_i - > set_mouse_filter ( Control : : MOUSE_FILTER_STOP ) ;
node_j - > set_mouse_filter ( Control : : MOUSE_FILTER_STOP ) ;
2024-05-13 14:56:03 +00:00
SIGNAL_UNWATCH ( node_i , SceneStringName ( mouse_entered ) ) ;
SIGNAL_UNWATCH ( node_i , SceneStringName ( mouse_exited ) ) ;
2023-10-31 17:55:34 +00:00
}
SUBCASE ( " [Viewport][GuiInputEvent] Mouse Enter/Exit notification when changing the mouse filter to stop. " ) {
2024-05-13 14:56:03 +00:00
SIGNAL_WATCH ( node_i , SceneStringName ( mouse_entered ) ) ;
SIGNAL_WATCH ( node_i , SceneStringName ( mouse_exited ) ) ;
2023-10-31 17:55:34 +00:00
Array signal_args ;
signal_args . push_back ( Array ( ) ) ;
node_i - > set_mouse_filter ( Control : : MOUSE_FILTER_PASS ) ;
node_j - > set_mouse_filter ( Control : : MOUSE_FILTER_PASS ) ;
// Move to Control node_j.
SEND_GUI_MOUSE_MOTION_EVENT ( on_j , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK ( node_h - > mouse_over ) ;
CHECK_FALSE ( node_h - > mouse_over_self ) ;
CHECK ( node_i - > mouse_over ) ;
CHECK_FALSE ( node_i - > mouse_over_self ) ;
CHECK ( node_j - > mouse_over ) ;
CHECK ( node_j - > mouse_over_self ) ;
2024-05-13 14:56:03 +00:00
SIGNAL_CHECK ( SceneStringName ( mouse_entered ) , signal_args ) ;
SIGNAL_CHECK_FALSE ( SceneStringName ( mouse_exited ) ) ;
2023-10-31 17:55:34 +00:00
// Change node_i to MOUSE_FILTER_STOP. node_h should receive Mouse Exit. node_i should not receive any new signals.
node_i - > set_mouse_filter ( Control : : MOUSE_FILTER_STOP ) ;
CHECK_FALSE ( node_h - > mouse_over ) ;
CHECK_FALSE ( node_h - > mouse_over_self ) ;
CHECK ( node_i - > mouse_over ) ;
CHECK_FALSE ( node_i - > mouse_over_self ) ;
CHECK ( node_j - > mouse_over ) ;
CHECK ( node_j - > mouse_over_self ) ;
2024-05-13 14:56:03 +00:00
SIGNAL_CHECK_FALSE ( SceneStringName ( mouse_entered ) ) ;
SIGNAL_CHECK_FALSE ( SceneStringName ( mouse_exited ) ) ;
2023-10-31 17:55:34 +00:00
// Change node_i to MOUSE_FILTER_PASS. node_h should receive Mouse Enter. node_i should not receive any new signals.
node_i - > set_mouse_filter ( Control : : MOUSE_FILTER_PASS ) ;
CHECK ( node_h - > mouse_over ) ;
CHECK_FALSE ( node_h - > mouse_over_self ) ;
CHECK ( node_i - > mouse_over ) ;
CHECK_FALSE ( node_i - > mouse_over_self ) ;
CHECK ( node_j - > mouse_over ) ;
CHECK ( node_j - > mouse_over_self ) ;
2024-05-13 14:56:03 +00:00
SIGNAL_CHECK_FALSE ( SceneStringName ( mouse_entered ) ) ;
SIGNAL_CHECK_FALSE ( SceneStringName ( mouse_exited ) ) ;
2023-10-31 17:55:34 +00:00
CHECK_FALSE ( node_h - > invalid_order ) ;
CHECK_FALSE ( node_i - > invalid_order ) ;
CHECK_FALSE ( node_j - > invalid_order ) ;
node_i - > set_mouse_filter ( Control : : MOUSE_FILTER_STOP ) ;
node_j - > set_mouse_filter ( Control : : MOUSE_FILTER_STOP ) ;
2024-05-13 14:56:03 +00:00
SIGNAL_UNWATCH ( node_i , SceneStringName ( mouse_entered ) ) ;
SIGNAL_UNWATCH ( node_i , SceneStringName ( mouse_exited ) ) ;
2023-10-31 17:55:34 +00:00
}
SUBCASE ( " [Viewport][GuiInputEvent] Mouse Enter/Exit notification when changing the mouse filter to ignore. " ) {
2024-05-13 14:56:03 +00:00
SIGNAL_WATCH ( node_i , SceneStringName ( mouse_entered ) ) ;
SIGNAL_WATCH ( node_i , SceneStringName ( mouse_exited ) ) ;
2023-10-31 17:55:34 +00:00
Array signal_args ;
signal_args . push_back ( Array ( ) ) ;
node_i - > set_mouse_filter ( Control : : MOUSE_FILTER_PASS ) ;
node_j - > set_mouse_filter ( Control : : MOUSE_FILTER_PASS ) ;
// Move to Control node_j.
SEND_GUI_MOUSE_MOTION_EVENT ( on_j , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK ( node_h - > mouse_over ) ;
CHECK_FALSE ( node_h - > mouse_over_self ) ;
CHECK ( node_i - > mouse_over ) ;
CHECK_FALSE ( node_i - > mouse_over_self ) ;
CHECK ( node_j - > mouse_over ) ;
CHECK ( node_j - > mouse_over_self ) ;
2024-05-13 14:56:03 +00:00
SIGNAL_CHECK ( SceneStringName ( mouse_entered ) , signal_args ) ;
SIGNAL_CHECK_FALSE ( SceneStringName ( mouse_exited ) ) ;
2023-10-31 17:55:34 +00:00
// Change node_i to MOUSE_FILTER_IGNORE. node_i should receive Mouse Exit.
node_i - > set_mouse_filter ( Control : : MOUSE_FILTER_IGNORE ) ;
CHECK ( node_h - > mouse_over ) ;
CHECK_FALSE ( node_h - > mouse_over_self ) ;
CHECK_FALSE ( node_i - > mouse_over ) ;
CHECK_FALSE ( node_i - > mouse_over_self ) ;
CHECK ( node_j - > mouse_over ) ;
CHECK ( node_j - > mouse_over_self ) ;
2024-05-13 14:56:03 +00:00
SIGNAL_CHECK_FALSE ( SceneStringName ( mouse_entered ) ) ;
SIGNAL_CHECK ( SceneStringName ( mouse_exited ) , signal_args ) ;
2023-10-31 17:55:34 +00:00
// Change node_i to MOUSE_FILTER_PASS. node_i should receive Mouse Enter.
node_i - > set_mouse_filter ( Control : : MOUSE_FILTER_PASS ) ;
CHECK ( node_h - > mouse_over ) ;
CHECK_FALSE ( node_h - > mouse_over_self ) ;
CHECK ( node_i - > mouse_over ) ;
CHECK_FALSE ( node_i - > mouse_over_self ) ;
CHECK ( node_j - > mouse_over ) ;
CHECK ( node_j - > mouse_over_self ) ;
2024-05-13 14:56:03 +00:00
SIGNAL_CHECK ( SceneStringName ( mouse_entered ) , signal_args ) ;
SIGNAL_CHECK_FALSE ( SceneStringName ( mouse_exited ) ) ;
2023-10-31 17:55:34 +00:00
// Change node_j to MOUSE_FILTER_IGNORE. After updating the mouse motion, node_i should now have mouse_over_self.
node_j - > set_mouse_filter ( Control : : MOUSE_FILTER_IGNORE ) ;
SEND_GUI_MOUSE_MOTION_EVENT ( on_j , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK ( node_h - > mouse_over ) ;
CHECK_FALSE ( node_h - > mouse_over_self ) ;
CHECK ( node_i - > mouse_over ) ;
CHECK ( node_i - > mouse_over_self ) ;
CHECK_FALSE ( node_j - > mouse_over ) ;
CHECK_FALSE ( node_j - > mouse_over_self ) ;
2024-05-13 14:56:03 +00:00
SIGNAL_CHECK_FALSE ( SceneStringName ( mouse_entered ) ) ;
SIGNAL_CHECK_FALSE ( SceneStringName ( mouse_exited ) ) ;
2023-10-31 17:55:34 +00:00
// Change node_j to MOUSE_FILTER_PASS. After updating the mouse motion, node_j should now have mouse_over_self.
node_j - > set_mouse_filter ( Control : : MOUSE_FILTER_PASS ) ;
SEND_GUI_MOUSE_MOTION_EVENT ( on_j , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK ( node_h - > mouse_over ) ;
CHECK_FALSE ( node_h - > mouse_over_self ) ;
CHECK ( node_i - > mouse_over ) ;
CHECK_FALSE ( node_i - > mouse_over_self ) ;
CHECK ( node_j - > mouse_over ) ;
CHECK ( node_j - > mouse_over_self ) ;
2024-05-13 14:56:03 +00:00
SIGNAL_CHECK_FALSE ( SceneStringName ( mouse_entered ) ) ;
SIGNAL_CHECK_FALSE ( SceneStringName ( mouse_exited ) ) ;
2023-10-31 17:55:34 +00:00
CHECK_FALSE ( node_h - > invalid_order ) ;
CHECK_FALSE ( node_i - > invalid_order ) ;
CHECK_FALSE ( node_j - > invalid_order ) ;
node_i - > set_mouse_filter ( Control : : MOUSE_FILTER_STOP ) ;
node_j - > set_mouse_filter ( Control : : MOUSE_FILTER_STOP ) ;
2024-05-13 14:56:03 +00:00
SIGNAL_UNWATCH ( node_i , SceneStringName ( mouse_entered ) ) ;
SIGNAL_UNWATCH ( node_i , SceneStringName ( mouse_exited ) ) ;
2023-10-31 17:55:34 +00:00
}
SUBCASE ( " [Viewport][GuiInputEvent] Mouse Enter/Exit notification when removing the hovered Control. " ) {
2024-05-13 14:56:03 +00:00
SIGNAL_WATCH ( node_h , SceneStringName ( mouse_entered ) ) ;
SIGNAL_WATCH ( node_h , SceneStringName ( mouse_exited ) ) ;
2023-10-31 17:55:34 +00:00
Array signal_args ;
signal_args . push_back ( Array ( ) ) ;
node_i - > set_mouse_filter ( Control : : MOUSE_FILTER_PASS ) ;
node_j - > set_mouse_filter ( Control : : MOUSE_FILTER_PASS ) ;
// Move to Control node_j.
SEND_GUI_MOUSE_MOTION_EVENT ( on_j , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK ( node_h - > mouse_over ) ;
CHECK_FALSE ( node_h - > mouse_over_self ) ;
CHECK ( node_i - > mouse_over ) ;
CHECK_FALSE ( node_i - > mouse_over_self ) ;
CHECK ( node_j - > mouse_over ) ;
CHECK ( node_j - > mouse_over_self ) ;
2024-05-13 14:56:03 +00:00
SIGNAL_CHECK ( SceneStringName ( mouse_entered ) , signal_args ) ;
SIGNAL_CHECK_FALSE ( SceneStringName ( mouse_exited ) ) ;
2023-10-31 17:55:34 +00:00
// Remove node_i from the tree. node_i and node_j should receive Mouse Exit. node_h should not receive any new signals.
node_h - > remove_child ( node_i ) ;
CHECK ( node_h - > mouse_over ) ;
CHECK_FALSE ( node_h - > mouse_over_self ) ;
CHECK_FALSE ( node_i - > mouse_over ) ;
CHECK_FALSE ( node_i - > mouse_over_self ) ;
CHECK_FALSE ( node_j - > mouse_over ) ;
CHECK_FALSE ( node_j - > mouse_over_self ) ;
2024-05-13 14:56:03 +00:00
SIGNAL_CHECK_FALSE ( SceneStringName ( mouse_entered ) ) ;
SIGNAL_CHECK_FALSE ( SceneStringName ( mouse_exited ) ) ;
2023-10-31 17:55:34 +00:00
// Add node_i to the tree and update the mouse. node_i and node_j should receive Mouse Enter. node_h should not receive any new signals.
node_h - > add_child ( node_i ) ;
SEND_GUI_MOUSE_MOTION_EVENT ( on_j , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK ( node_h - > mouse_over ) ;
CHECK_FALSE ( node_h - > mouse_over_self ) ;
CHECK ( node_i - > mouse_over ) ;
CHECK_FALSE ( node_i - > mouse_over_self ) ;
CHECK ( node_j - > mouse_over ) ;
CHECK ( node_j - > mouse_over_self ) ;
2024-05-13 14:56:03 +00:00
SIGNAL_CHECK_FALSE ( SceneStringName ( mouse_entered ) ) ;
SIGNAL_CHECK_FALSE ( SceneStringName ( mouse_exited ) ) ;
2023-10-31 17:55:34 +00:00
CHECK_FALSE ( node_h - > invalid_order ) ;
CHECK_FALSE ( node_i - > invalid_order ) ;
CHECK_FALSE ( node_j - > invalid_order ) ;
node_i - > set_mouse_filter ( Control : : MOUSE_FILTER_STOP ) ;
node_j - > set_mouse_filter ( Control : : MOUSE_FILTER_STOP ) ;
2024-05-13 14:56:03 +00:00
SIGNAL_UNWATCH ( node_h , SceneStringName ( mouse_entered ) ) ;
SIGNAL_UNWATCH ( node_h , SceneStringName ( mouse_exited ) ) ;
2023-10-31 17:55:34 +00:00
}
SUBCASE ( " [Viewport][GuiInputEvent] Mouse Enter/Exit notification when hiding the hovered Control. " ) {
2024-05-13 14:56:03 +00:00
SIGNAL_WATCH ( node_h , SceneStringName ( mouse_entered ) ) ;
SIGNAL_WATCH ( node_h , SceneStringName ( mouse_exited ) ) ;
2023-10-31 17:55:34 +00:00
Array signal_args ;
signal_args . push_back ( Array ( ) ) ;
node_i - > set_mouse_filter ( Control : : MOUSE_FILTER_PASS ) ;
node_j - > set_mouse_filter ( Control : : MOUSE_FILTER_PASS ) ;
// Move to Control node_j.
SEND_GUI_MOUSE_MOTION_EVENT ( on_j , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK ( node_h - > mouse_over ) ;
CHECK_FALSE ( node_h - > mouse_over_self ) ;
CHECK ( node_i - > mouse_over ) ;
CHECK_FALSE ( node_i - > mouse_over_self ) ;
CHECK ( node_j - > mouse_over ) ;
CHECK ( node_j - > mouse_over_self ) ;
2024-05-13 14:56:03 +00:00
SIGNAL_CHECK ( SceneStringName ( mouse_entered ) , signal_args ) ;
SIGNAL_CHECK_FALSE ( SceneStringName ( mouse_exited ) ) ;
2023-10-31 17:55:34 +00:00
// Hide node_i. node_i and node_j should receive Mouse Exit. node_h should not receive any new signals.
node_i - > hide ( ) ;
CHECK ( node_h - > mouse_over ) ;
CHECK_FALSE ( node_h - > mouse_over_self ) ;
CHECK_FALSE ( node_i - > mouse_over ) ;
CHECK_FALSE ( node_i - > mouse_over_self ) ;
CHECK_FALSE ( node_j - > mouse_over ) ;
CHECK_FALSE ( node_j - > mouse_over_self ) ;
2024-05-13 14:56:03 +00:00
SIGNAL_CHECK_FALSE ( SceneStringName ( mouse_entered ) ) ;
SIGNAL_CHECK_FALSE ( SceneStringName ( mouse_exited ) ) ;
2023-10-31 17:55:34 +00:00
// Show node_i and update the mouse. node_i and node_j should receive Mouse Enter. node_h should not receive any new signals.
node_i - > show ( ) ;
SEND_GUI_MOUSE_MOTION_EVENT ( on_j , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK ( node_h - > mouse_over ) ;
CHECK_FALSE ( node_h - > mouse_over_self ) ;
CHECK ( node_i - > mouse_over ) ;
CHECK_FALSE ( node_i - > mouse_over_self ) ;
CHECK ( node_j - > mouse_over ) ;
CHECK ( node_j - > mouse_over_self ) ;
2024-05-13 14:56:03 +00:00
SIGNAL_CHECK_FALSE ( SceneStringName ( mouse_entered ) ) ;
SIGNAL_CHECK_FALSE ( SceneStringName ( mouse_exited ) ) ;
2023-10-31 17:55:34 +00:00
CHECK_FALSE ( node_h - > invalid_order ) ;
CHECK_FALSE ( node_i - > invalid_order ) ;
CHECK_FALSE ( node_j - > invalid_order ) ;
node_i - > set_mouse_filter ( Control : : MOUSE_FILTER_STOP ) ;
node_j - > set_mouse_filter ( Control : : MOUSE_FILTER_STOP ) ;
2024-05-13 14:56:03 +00:00
SIGNAL_UNWATCH ( node_h , SceneStringName ( mouse_entered ) ) ;
SIGNAL_UNWATCH ( node_h , SceneStringName ( mouse_exited ) ) ;
2023-02-09 10:29:46 +00:00
}
SUBCASE ( " [Viewport][GuiInputEvent] Window Mouse Enter/Exit signals. " ) {
2024-05-13 14:56:03 +00:00
SIGNAL_WATCH ( root , SceneStringName ( mouse_entered ) ) ;
SIGNAL_WATCH ( root , SceneStringName ( mouse_exited ) ) ;
2023-02-09 10:29:46 +00:00
Array signal_args ;
signal_args . push_back ( Array ( ) ) ;
SEND_GUI_MOUSE_MOTION_EVENT ( on_outside , MouseButtonMask : : NONE , Key : : NONE ) ;
2024-05-13 14:56:03 +00:00
SIGNAL_CHECK_FALSE ( SceneStringName ( mouse_entered ) ) ;
SIGNAL_CHECK ( SceneStringName ( mouse_exited ) , signal_args ) ;
2023-02-09 10:29:46 +00:00
SEND_GUI_MOUSE_MOTION_EVENT ( on_a , MouseButtonMask : : NONE , Key : : NONE ) ;
2024-05-13 14:56:03 +00:00
SIGNAL_CHECK ( SceneStringName ( mouse_entered ) , signal_args ) ;
SIGNAL_CHECK_FALSE ( SceneStringName ( mouse_exited ) ) ;
2023-02-09 10:29:46 +00:00
2024-05-13 14:56:03 +00:00
SIGNAL_UNWATCH ( root , SceneStringName ( mouse_entered ) ) ;
SIGNAL_UNWATCH ( root , SceneStringName ( mouse_exited ) ) ;
2023-02-09 10:29:46 +00:00
}
SUBCASE ( " [Viewport][GuiInputEvent] Process-Mode affects, if GUI Mouse Motion Events are processed. " ) {
node_a - > last_mouse_move_position = on_outside ;
node_a - > set_process_mode ( Node : : PROCESS_MODE_DISABLED ) ;
SEND_GUI_MOUSE_MOTION_EVENT ( on_a , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK ( node_a - > last_mouse_move_position = = on_outside ) ;
// Now verify that with allowed processing the event is processed.
node_a - > set_process_mode ( Node : : PROCESS_MODE_ALWAYS ) ;
SEND_GUI_MOUSE_MOTION_EVENT ( on_a , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK ( node_a - > last_mouse_move_position = = on_a ) ;
}
}
// Unit tests for Viewport::_gui_input_event (Drag and Drop)
SUBCASE ( " [Viewport][GuiInputEvent] Drag and Drop " ) {
// FIXME: Drag-Preview will likely change. Tests for this part would have to be rewritten anyway.
// See https://github.com/godotengine/godot/pull/67531#issuecomment-1385353430 for details.
2023-01-19 23:21:11 +00:00
// Note: Testing Drag and Drop with non-embedded windows would require DisplayServerMock additions.
2023-02-09 10:29:46 +00:00
int min_grab_movement = 11 ;
2023-01-19 23:21:11 +00:00
SUBCASE ( " [Viewport][GuiInputEvent][DnD] Drag from one Control to another in the same viewport. " ) {
SUBCASE ( " [Viewport][GuiInputEvent][DnD] Perform successful Drag and Drop on a different Control. " ) {
2023-02-09 10:29:46 +00:00
SEND_GUI_MOUSE_BUTTON_EVENT ( on_a , MouseButton : : LEFT , MouseButtonMask : : LEFT , Key : : NONE ) ;
CHECK_FALSE ( root - > gui_is_dragging ( ) ) ;
SEND_GUI_MOUSE_MOTION_EVENT ( on_a + Point2i ( min_grab_movement , 0 ) , MouseButtonMask : : LEFT , Key : : NONE ) ;
CHECK ( root - > gui_is_dragging ( ) ) ;
// Move above a Control, that is a Drop target and allows dropping at this point.
SEND_GUI_MOUSE_MOTION_EVENT ( on_d , MouseButtonMask : : LEFT , Key : : NONE ) ;
CHECK ( DS - > get_cursor_shape ( ) = = DisplayServer : : CURSOR_CAN_DROP ) ;
CHECK ( root - > gui_is_dragging ( ) ) ;
CHECK_FALSE ( root - > gui_is_drag_successful ( ) ) ;
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT ( on_d , MouseButton : : LEFT , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK_FALSE ( root - > gui_is_dragging ( ) ) ;
CHECK ( root - > gui_is_drag_successful ( ) ) ;
CHECK ( ( StringName ) node_d - > drag_data = = SNAME ( " Drag Data " ) ) ;
}
2023-01-19 23:21:11 +00:00
SUBCASE ( " [Viewport][GuiInputEvent][DnD] Perform unsuccessful drop on Control. " ) {
2023-02-09 10:29:46 +00:00
SEND_GUI_MOUSE_BUTTON_EVENT ( on_a , MouseButton : : LEFT , MouseButtonMask : : LEFT , Key : : NONE ) ;
CHECK_FALSE ( root - > gui_is_dragging ( ) ) ;
// Move, but don't trigger DnD yet.
SEND_GUI_MOUSE_MOTION_EVENT ( on_a + Point2i ( 0 , min_grab_movement - 1 ) , MouseButtonMask : : LEFT , Key : : NONE ) ;
CHECK_FALSE ( root - > gui_is_dragging ( ) ) ;
// Move and trigger DnD.
SEND_GUI_MOUSE_MOTION_EVENT ( on_a + Point2i ( 0 , min_grab_movement ) , MouseButtonMask : : LEFT , Key : : NONE ) ;
CHECK ( root - > gui_is_dragging ( ) ) ;
// Move above a Control, that is not a Drop target.
SEND_GUI_MOUSE_MOTION_EVENT ( on_a , MouseButtonMask : : LEFT , Key : : NONE ) ;
CHECK ( DS - > get_cursor_shape ( ) = = DisplayServer : : CURSOR_FORBIDDEN ) ;
// Move above a Control, that is a Drop target, but has disallowed this point.
SEND_GUI_MOUSE_MOTION_EVENT ( on_d + Point2i ( 20 , 0 ) , MouseButtonMask : : LEFT , Key : : NONE ) ;
CHECK ( DS - > get_cursor_shape ( ) = = DisplayServer : : CURSOR_FORBIDDEN ) ;
CHECK ( root - > gui_is_dragging ( ) ) ;
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT ( on_d + Point2i ( 20 , 0 ) , MouseButton : : LEFT , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK_FALSE ( root - > gui_is_dragging ( ) ) ;
CHECK_FALSE ( root - > gui_is_drag_successful ( ) ) ;
}
2023-01-19 23:21:11 +00:00
SUBCASE ( " [Viewport][GuiInputEvent][DnD] Perform unsuccessful drop on No-Control. " ) {
2023-02-09 10:29:46 +00:00
SEND_GUI_MOUSE_BUTTON_EVENT ( on_a , MouseButton : : LEFT , MouseButtonMask : : LEFT , Key : : NONE ) ;
CHECK_FALSE ( root - > gui_is_dragging ( ) ) ;
// Move, but don't trigger DnD yet.
SEND_GUI_MOUSE_MOTION_EVENT ( on_a + Point2i ( min_grab_movement - 1 , 0 ) , MouseButtonMask : : LEFT , Key : : NONE ) ;
CHECK_FALSE ( root - > gui_is_dragging ( ) ) ;
// Move and trigger DnD.
SEND_GUI_MOUSE_MOTION_EVENT ( on_a + Point2i ( min_grab_movement , 0 ) , MouseButtonMask : : LEFT , Key : : NONE ) ;
CHECK ( root - > gui_is_dragging ( ) ) ;
// Move away from Controls.
SEND_GUI_MOUSE_MOTION_EVENT ( on_background , MouseButtonMask : : LEFT , Key : : NONE ) ;
2023-01-19 23:21:11 +00:00
CHECK ( DS - > get_cursor_shape ( ) = = DisplayServer : : CURSOR_ARROW ) ;
2023-02-09 10:29:46 +00:00
CHECK ( root - > gui_is_dragging ( ) ) ;
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT ( on_background , MouseButton : : LEFT , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK_FALSE ( root - > gui_is_dragging ( ) ) ;
CHECK_FALSE ( root - > gui_is_drag_successful ( ) ) ;
}
2023-01-19 23:21:11 +00:00
SUBCASE ( " [Viewport][GuiInputEvent][DnD] Perform unsuccessful drop outside of window. " ) {
2023-02-09 10:29:46 +00:00
SEND_GUI_MOUSE_BUTTON_EVENT ( on_a , MouseButton : : LEFT , MouseButtonMask : : LEFT , Key : : NONE ) ;
CHECK_FALSE ( root - > gui_is_dragging ( ) ) ;
// Move and trigger DnD.
SEND_GUI_MOUSE_MOTION_EVENT ( on_a + Point2i ( min_grab_movement , 0 ) , MouseButtonMask : : LEFT , Key : : NONE ) ;
CHECK ( root - > gui_is_dragging ( ) ) ;
SEND_GUI_MOUSE_MOTION_EVENT ( on_d , MouseButtonMask : : LEFT , Key : : NONE ) ;
CHECK ( DS - > get_cursor_shape ( ) = = DisplayServer : : CURSOR_CAN_DROP ) ;
// Move outside of window.
SEND_GUI_MOUSE_MOTION_EVENT ( on_outside , MouseButtonMask : : LEFT , Key : : NONE ) ;
CHECK ( root - > gui_is_dragging ( ) ) ;
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT ( on_outside , MouseButton : : LEFT , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK_FALSE ( root - > gui_is_dragging ( ) ) ;
CHECK_FALSE ( root - > gui_is_drag_successful ( ) ) ;
}
2023-01-19 23:21:11 +00:00
SUBCASE ( " [Viewport][GuiInputEvent][DnD] Drag and Drop doesn't work with other Mouse Buttons than LMB. " ) {
2023-02-09 10:29:46 +00:00
SEND_GUI_MOUSE_BUTTON_EVENT ( on_a , MouseButton : : MIDDLE , MouseButtonMask : : MIDDLE , Key : : NONE ) ;
CHECK_FALSE ( root - > gui_is_dragging ( ) ) ;
SEND_GUI_MOUSE_MOTION_EVENT ( on_a + Point2i ( min_grab_movement , 0 ) , MouseButtonMask : : MIDDLE , Key : : NONE ) ;
CHECK_FALSE ( root - > gui_is_dragging ( ) ) ;
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT ( on_a , MouseButton : : MIDDLE , MouseButtonMask : : NONE , Key : : NONE ) ;
}
2023-01-19 23:21:11 +00:00
SUBCASE ( " [Viewport][GuiInputEvent][DnD] Drag and Drop parent propagation. " ) {
2023-02-09 10:29:46 +00:00
Node2D * node_aa = memnew ( Node2D ) ;
Control * node_aaa = memnew ( Control ) ;
Node2D * node_dd = memnew ( Node2D ) ;
Control * node_ddd = memnew ( Control ) ;
node_aaa - > set_size ( Size2i ( 10 , 10 ) ) ;
node_aaa - > set_position ( Point2i ( 0 , 5 ) ) ;
node_ddd - > set_size ( Size2i ( 10 , 10 ) ) ;
node_ddd - > set_position ( Point2i ( 0 , 5 ) ) ;
node_a - > add_child ( node_aa ) ;
node_aa - > add_child ( node_aaa ) ;
node_d - > add_child ( node_dd ) ;
node_dd - > add_child ( node_ddd ) ;
Point2i on_aaa = on_a + Point2i ( - 2 , 2 ) ;
Point2i on_ddd = on_d + Point2i ( - 2 , 2 ) ;
SUBCASE ( " [Viewport][GuiInputEvent] Drag and Drop propagation to parent Controls. " ) {
node_aaa - > set_mouse_filter ( Control : : MOUSE_FILTER_PASS ) ;
node_ddd - > set_mouse_filter ( Control : : MOUSE_FILTER_PASS ) ;
SEND_GUI_MOUSE_BUTTON_EVENT ( on_aaa , MouseButton : : LEFT , MouseButtonMask : : LEFT , Key : : NONE ) ;
CHECK_FALSE ( root - > gui_is_dragging ( ) ) ;
SEND_GUI_MOUSE_MOTION_EVENT ( on_aaa + Point2i ( 0 , min_grab_movement ) , MouseButtonMask : : LEFT , Key : : NONE ) ;
CHECK ( root - > gui_is_dragging ( ) ) ;
SEND_GUI_MOUSE_MOTION_EVENT ( on_ddd , MouseButtonMask : : LEFT , Key : : NONE ) ;
CHECK ( root - > gui_is_dragging ( ) ) ;
CHECK_FALSE ( root - > gui_is_drag_successful ( ) ) ;
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT ( on_ddd , MouseButton : : LEFT , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK_FALSE ( root - > gui_is_dragging ( ) ) ;
CHECK ( root - > gui_is_drag_successful ( ) ) ;
node_aaa - > set_mouse_filter ( Control : : MOUSE_FILTER_STOP ) ;
node_ddd - > set_mouse_filter ( Control : : MOUSE_FILTER_STOP ) ;
}
SUBCASE ( " [Viewport][GuiInputEvent] Drag and Drop grab-propagation stopped by Top Level. " ) {
node_aaa - > set_mouse_filter ( Control : : MOUSE_FILTER_PASS ) ;
node_aaa - > set_as_top_level ( true ) ;
SEND_GUI_MOUSE_BUTTON_EVENT ( on_aaa , MouseButton : : LEFT , MouseButtonMask : : LEFT , Key : : NONE ) ;
CHECK_FALSE ( root - > gui_is_dragging ( ) ) ;
SEND_GUI_MOUSE_MOTION_EVENT ( on_aaa + Point2i ( 0 , min_grab_movement ) , MouseButtonMask : : LEFT , Key : : NONE ) ;
CHECK_FALSE ( root - > gui_is_dragging ( ) ) ;
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT ( on_background , MouseButton : : LEFT , MouseButtonMask : : NONE , Key : : NONE ) ;
node_aaa - > set_as_top_level ( false ) ;
node_aaa - > set_mouse_filter ( Control : : MOUSE_FILTER_STOP ) ;
}
SUBCASE ( " [Viewport][GuiInputEvent] Drag and Drop target-propagation stopped by Top Level. " ) {
node_aaa - > set_mouse_filter ( Control : : MOUSE_FILTER_PASS ) ;
node_ddd - > set_mouse_filter ( Control : : MOUSE_FILTER_PASS ) ;
node_ddd - > set_as_top_level ( true ) ;
node_ddd - > set_position ( Point2i ( 30 , 100 ) ) ;
SEND_GUI_MOUSE_BUTTON_EVENT ( on_aaa , MouseButton : : LEFT , MouseButtonMask : : LEFT , Key : : NONE ) ;
CHECK_FALSE ( root - > gui_is_dragging ( ) ) ;
SEND_GUI_MOUSE_MOTION_EVENT ( on_aaa + Point2i ( 0 , min_grab_movement ) , MouseButtonMask : : LEFT , Key : : NONE ) ;
CHECK ( root - > gui_is_dragging ( ) ) ;
SEND_GUI_MOUSE_MOTION_EVENT ( Point2i ( 35 , 105 ) , MouseButtonMask : : LEFT , Key : : NONE ) ;
CHECK ( root - > gui_is_dragging ( ) ) ;
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT ( Point2i ( 35 , 105 ) , MouseButton : : LEFT , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK_FALSE ( root - > gui_is_dragging ( ) ) ;
CHECK_FALSE ( root - > gui_is_drag_successful ( ) ) ;
node_ddd - > set_position ( Point2i ( 0 , 5 ) ) ;
node_ddd - > set_as_top_level ( false ) ;
node_aaa - > set_mouse_filter ( Control : : MOUSE_FILTER_STOP ) ;
node_ddd - > set_mouse_filter ( Control : : MOUSE_FILTER_STOP ) ;
}
SUBCASE ( " [Viewport][GuiInputEvent] Drag and Drop grab-propagation stopped by non-CanvasItem. " ) {
node_g - > set_mouse_filter ( Control : : MOUSE_FILTER_PASS ) ;
SEND_GUI_MOUSE_BUTTON_EVENT ( on_g , MouseButton : : LEFT , MouseButtonMask : : LEFT , Key : : NONE ) ;
SEND_GUI_MOUSE_MOTION_EVENT ( on_g + Point2i ( 0 , min_grab_movement ) , MouseButtonMask : : LEFT , Key : : NONE ) ;
CHECK_FALSE ( root - > gui_is_dragging ( ) ) ;
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT ( on_background , MouseButton : : LEFT , MouseButtonMask : : NONE , Key : : NONE ) ;
node_g - > set_mouse_filter ( Control : : MOUSE_FILTER_STOP ) ;
}
SUBCASE ( " [Viewport][GuiInputEvent] Drag and Drop target-propagation stopped by non-CanvasItem. " ) {
node_g - > set_mouse_filter ( Control : : MOUSE_FILTER_PASS ) ;
SEND_GUI_MOUSE_BUTTON_EVENT ( on_a - Point2i ( 1 , 1 ) , MouseButton : : LEFT , MouseButtonMask : : LEFT , Key : : NONE ) ; // Offset for node_aaa.
SEND_GUI_MOUSE_MOTION_EVENT ( on_a + Point2i ( 0 , min_grab_movement ) , MouseButtonMask : : LEFT , Key : : NONE ) ;
CHECK ( root - > gui_is_dragging ( ) ) ;
SEND_GUI_MOUSE_MOTION_EVENT ( on_g , MouseButtonMask : : LEFT , Key : : NONE ) ;
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT ( on_g , MouseButton : : LEFT , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK_FALSE ( root - > gui_is_dragging ( ) ) ;
node_g - > set_mouse_filter ( Control : : MOUSE_FILTER_STOP ) ;
}
memdelete ( node_ddd ) ;
memdelete ( node_dd ) ;
memdelete ( node_aaa ) ;
memdelete ( node_aa ) ;
}
2023-01-19 23:21:11 +00:00
SUBCASE ( " [Viewport][GuiInputEvent][DnD] Force Drag and Drop. " ) {
2023-02-09 10:29:46 +00:00
SEND_GUI_MOUSE_MOTION_EVENT ( on_background , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK_FALSE ( root - > gui_is_dragging ( ) ) ;
node_a - > force_drag ( SNAME ( " Drag Data " ) , nullptr ) ;
CHECK ( root - > gui_is_dragging ( ) ) ;
SEND_GUI_MOUSE_MOTION_EVENT ( on_d , MouseButtonMask : : NONE , Key : : NONE ) ;
// Force Drop doesn't get triggered by mouse Buttons other than LMB.
SEND_GUI_MOUSE_BUTTON_EVENT ( on_d , MouseButton : : RIGHT , MouseButtonMask : : RIGHT , Key : : NONE ) ;
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT ( on_a , MouseButton : : RIGHT , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK ( root - > gui_is_dragging ( ) ) ;
// Force Drop with LMB-Down.
SEND_GUI_MOUSE_BUTTON_EVENT ( on_d , MouseButton : : LEFT , MouseButtonMask : : LEFT , Key : : NONE ) ;
CHECK_FALSE ( root - > gui_is_dragging ( ) ) ;
CHECK ( root - > gui_is_drag_successful ( ) ) ;
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT ( on_d , MouseButton : : LEFT , MouseButtonMask : : NONE , Key : : NONE ) ;
}
}
2023-01-19 23:21:11 +00:00
SUBCASE ( " [Viewport][GuiInputEvent][DnD] Drag to a different Viewport. " ) {
SubViewportContainer * svc = memnew ( SubViewportContainer ) ;
svc - > set_size ( Size2 ( 100 , 100 ) ) ;
svc - > set_position ( Point2 ( 200 , 50 ) ) ;
root - > add_child ( svc ) ;
SubViewport * sv = memnew ( SubViewport ) ;
sv - > set_embedding_subwindows ( true ) ;
sv - > set_size ( Size2i ( 100 , 100 ) ) ;
svc - > add_child ( sv ) ;
DragStart * sv_a = memnew ( DragStart ) ;
sv_a - > set_position ( Point2 ( 10 , 10 ) ) ;
sv_a - > set_size ( Size2 ( 10 , 10 ) ) ;
sv - > add_child ( sv_a ) ;
Point2i on_sva = Point2i ( 215 , 65 ) ;
DragTarget * sv_b = memnew ( DragTarget ) ;
sv_b - > set_position ( Point2 ( 30 , 30 ) ) ;
sv_b - > set_size ( Size2 ( 20 , 20 ) ) ;
sv - > add_child ( sv_b ) ;
Point2i on_svb = Point2i ( 235 , 85 ) ;
Window * ew = memnew ( Window ) ;
ew - > set_position ( Point2 ( 50 , 200 ) ) ;
ew - > set_size ( Size2 ( 100 , 100 ) ) ;
root - > add_child ( ew ) ;
DragStart * ew_a = memnew ( DragStart ) ;
ew_a - > set_position ( Point2 ( 10 , 10 ) ) ;
ew_a - > set_size ( Size2 ( 10 , 10 ) ) ;
ew - > add_child ( ew_a ) ;
Point2i on_ewa = Point2i ( 65 , 215 ) ;
DragTarget * ew_b = memnew ( DragTarget ) ;
ew_b - > set_position ( Point2 ( 30 , 30 ) ) ;
ew_b - > set_size ( Size2 ( 20 , 20 ) ) ;
ew - > add_child ( ew_b ) ;
Point2i on_ewb = Point2i ( 85 , 235 ) ;
SUBCASE ( " [Viewport][GuiInputEvent][DnD] Drag to SubViewport " ) {
sv_b - > valid_drop = false ;
SEND_GUI_MOUSE_BUTTON_EVENT ( on_a , MouseButton : : LEFT , MouseButtonMask : : LEFT , Key : : NONE ) ;
SEND_GUI_MOUSE_MOTION_EVENT ( on_a + Point2i ( min_grab_movement , 0 ) , MouseButtonMask : : LEFT , Key : : NONE ) ;
CHECK ( root - > gui_is_dragging ( ) ) ;
CHECK ( sv_b - > during_drag ) ;
SEND_GUI_MOUSE_MOTION_EVENT ( on_svb , MouseButtonMask : : LEFT , Key : : NONE ) ;
CHECK ( DS - > get_cursor_shape ( ) = = DisplayServer : : CURSOR_CAN_DROP ) ;
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT ( on_svb , MouseButton : : LEFT , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK ( sv_b - > valid_drop ) ;
CHECK ( ! sv_b - > during_drag ) ;
}
SUBCASE ( " [Viewport][GuiInputEvent][DnD] Drag from SubViewport " ) {
node_d - > valid_drop = false ;
SEND_GUI_MOUSE_BUTTON_EVENT ( on_sva , MouseButton : : LEFT , MouseButtonMask : : LEFT , Key : : NONE ) ;
SEND_GUI_MOUSE_MOTION_EVENT ( on_sva + Point2i ( min_grab_movement , 0 ) , MouseButtonMask : : LEFT , Key : : NONE ) ;
CHECK ( sv - > gui_is_dragging ( ) ) ;
CHECK ( node_d - > during_drag ) ;
SEND_GUI_MOUSE_MOTION_EVENT ( on_d , MouseButtonMask : : LEFT , Key : : NONE ) ;
CHECK ( DS - > get_cursor_shape ( ) = = DisplayServer : : CURSOR_CAN_DROP ) ;
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT ( on_d , MouseButton : : LEFT , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK ( node_d - > valid_drop ) ;
CHECK ( ! node_d - > during_drag ) ;
}
SUBCASE ( " [Viewport][GuiInputEvent][DnD] Drag to embedded Window " ) {
ew_b - > valid_drop = false ;
SEND_GUI_MOUSE_BUTTON_EVENT ( on_a , MouseButton : : LEFT , MouseButtonMask : : LEFT , Key : : NONE ) ;
SEND_GUI_MOUSE_MOTION_EVENT ( on_a + Point2i ( min_grab_movement , 0 ) , MouseButtonMask : : LEFT , Key : : NONE ) ;
CHECK ( root - > gui_is_dragging ( ) ) ;
CHECK ( ew_b - > during_drag ) ;
SEND_GUI_MOUSE_MOTION_EVENT ( on_ewb , MouseButtonMask : : LEFT , Key : : NONE ) ;
CHECK ( DS - > get_cursor_shape ( ) = = DisplayServer : : CURSOR_CAN_DROP ) ;
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT ( on_ewb , MouseButton : : LEFT , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK ( ew_b - > valid_drop ) ;
CHECK ( ! ew_b - > during_drag ) ;
}
SUBCASE ( " [Viewport][GuiInputEvent][DnD] Drag from embedded Window " ) {
node_d - > valid_drop = false ;
SEND_GUI_MOUSE_BUTTON_EVENT ( on_ewa , MouseButton : : LEFT , MouseButtonMask : : LEFT , Key : : NONE ) ;
SEND_GUI_MOUSE_MOTION_EVENT ( on_ewa + Point2i ( min_grab_movement , 0 ) , MouseButtonMask : : LEFT , Key : : NONE ) ;
CHECK ( ew - > gui_is_dragging ( ) ) ;
CHECK ( node_d - > during_drag ) ;
SEND_GUI_MOUSE_MOTION_EVENT ( on_d , MouseButtonMask : : LEFT , Key : : NONE ) ;
CHECK ( DS - > get_cursor_shape ( ) = = DisplayServer : : CURSOR_CAN_DROP ) ;
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT ( on_d , MouseButton : : LEFT , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK ( node_d - > valid_drop ) ;
CHECK ( ! node_d - > during_drag ) ;
}
memdelete ( ew_a ) ;
memdelete ( ew_b ) ;
memdelete ( ew ) ;
memdelete ( sv_a ) ;
memdelete ( sv_b ) ;
memdelete ( sv ) ;
memdelete ( svc ) ;
}
2023-02-09 10:29:46 +00:00
}
2023-10-31 17:55:34 +00:00
memdelete ( node_j ) ;
memdelete ( node_i ) ;
memdelete ( node_h ) ;
2023-02-09 10:29:46 +00:00
memdelete ( node_g ) ;
memdelete ( node_f ) ;
memdelete ( node_e ) ;
memdelete ( node_d ) ;
memdelete ( node_c ) ;
memdelete ( node_b ) ;
memdelete ( node_a ) ;
}
2023-07-23 00:14:41 +00:00
TEST_CASE ( " [SceneTree][Viewport] Control mouse cursor shape " ) {
SUBCASE ( " [Viewport][CursorShape] Mouse cursor is not overridden by SubViewportContainer " ) {
SubViewportContainer * node_a = memnew ( SubViewportContainer ) ;
SubViewport * node_b = memnew ( SubViewport ) ;
Control * node_c = memnew ( Control ) ;
node_a - > set_name ( " SubViewportContainer " ) ;
node_b - > set_name ( " SubViewport " ) ;
node_c - > set_name ( " Control " ) ;
node_a - > set_position ( Point2i ( 0 , 0 ) ) ;
node_c - > set_position ( Point2i ( 0 , 0 ) ) ;
node_a - > set_size ( Point2i ( 100 , 100 ) ) ;
node_b - > set_size ( Point2i ( 100 , 100 ) ) ;
node_c - > set_size ( Point2i ( 100 , 100 ) ) ;
node_a - > set_default_cursor_shape ( Control : : CURSOR_ARROW ) ;
node_c - > set_default_cursor_shape ( Control : : CURSOR_FORBIDDEN ) ;
Window * root = SceneTree : : get_singleton ( ) - > get_root ( ) ;
DisplayServerMock * DS = ( DisplayServerMock * ) ( DisplayServer : : get_singleton ( ) ) ;
// Scene tree:
// - root
// - node_a (SubViewportContainer)
// - node_b (SubViewport)
// - node_c (Control)
root - > add_child ( node_a ) ;
node_a - > add_child ( node_b ) ;
node_b - > add_child ( node_c ) ;
Point2i on_c = Point2i ( 5 , 5 ) ;
SEND_GUI_MOUSE_MOTION_EVENT ( on_c , MouseButtonMask : : NONE , Key : : NONE ) ;
CHECK ( DS - > get_cursor_shape ( ) = = DisplayServer : : CURSOR_FORBIDDEN ) ; // GH-74805
memdelete ( node_c ) ;
memdelete ( node_b ) ;
memdelete ( node_a ) ;
}
}
2023-02-10 01:09:07 +00:00
class TestArea2D : public Area2D {
GDCLASS ( TestArea2D , Area2D ) ;
void _on_mouse_entered ( ) {
enter_id = + + TestArea2D : : counter ; // > 0, if activated.
}
void _on_mouse_exited ( ) {
exit_id = + + TestArea2D : : counter ; // > 0, if activated.
}
void _on_input_event ( Node * p_vp , Ref < InputEvent > p_ev , int p_shape ) {
last_input_event = p_ev ;
}
public :
static int counter ;
int enter_id = 0 ;
int exit_id = 0 ;
Ref < InputEvent > last_input_event ;
void init_signals ( ) {
2024-05-13 14:56:03 +00:00
connect ( SceneStringName ( mouse_entered ) , callable_mp ( this , & TestArea2D : : _on_mouse_entered ) ) ;
connect ( SceneStringName ( mouse_exited ) , callable_mp ( this , & TestArea2D : : _on_mouse_exited ) ) ;
connect ( SceneStringName ( input_event ) , callable_mp ( this , & TestArea2D : : _on_input_event ) ) ;
2023-02-10 01:09:07 +00:00
}
void test_reset ( ) {
enter_id = 0 ;
exit_id = 0 ;
last_input_event . unref ( ) ;
}
} ;
int TestArea2D : : counter = 0 ;
TEST_CASE ( " [SceneTree][Viewport] Physics Picking 2D " ) {
// FIXME: MOUSE_MODE_CAPTURED if-conditions are not testable, because DisplayServerMock doesn't support it.
2024-08-07 19:15:17 +00:00
// NOTE: This test requires a real physics server.
PhysicsServer2DDummy * physics_server_2d_dummy = Object : : cast_to < PhysicsServer2DDummy > ( PhysicsServer2D : : get_singleton ( ) ) ;
if ( physics_server_2d_dummy ) {
return ;
}
2023-02-10 01:09:07 +00:00
struct PickingCollider {
TestArea2D * a ;
CollisionShape2D * c ;
Ref < RectangleShape2D > r ;
} ;
SceneTree * tree = SceneTree : : get_singleton ( ) ;
Window * root = tree - > get_root ( ) ;
root - > set_physics_object_picking ( true ) ;
Point2i on_background = Point2i ( 800 , 800 ) ;
Point2i on_outside = Point2i ( - 1 , - 1 ) ;
SEND_GUI_MOUSE_MOTION_EVENT ( on_background , MouseButtonMask : : NONE , Key : : NONE ) ;
tree - > physics_process ( 1 ) ;
Vector < PickingCollider > v ;
for ( int i = 0 ; i < 4 ; i + + ) {
PickingCollider pc ;
pc . a = memnew ( TestArea2D ) ;
pc . c = memnew ( CollisionShape2D ) ;
pc . r = Ref < RectangleShape2D > ( memnew ( RectangleShape2D ) ) ;
pc . r - > set_size ( Size2 ( 150 , 150 ) ) ;
pc . c - > set_shape ( pc . r ) ;
pc . a - > add_child ( pc . c ) ;
pc . a - > set_name ( " A " + itos ( i ) ) ;
pc . c - > set_name ( " C " + itos ( i ) ) ;
v . push_back ( pc ) ;
2024-05-13 14:56:03 +00:00
SIGNAL_WATCH ( pc . a , SceneStringName ( mouse_entered ) ) ;
SIGNAL_WATCH ( pc . a , SceneStringName ( mouse_exited ) ) ;
2023-02-10 01:09:07 +00:00
}
Node2D * node_a = memnew ( Node2D ) ;
node_a - > set_position ( Point2i ( 0 , 0 ) ) ;
v [ 0 ] . a - > set_position ( Point2i ( 0 , 0 ) ) ;
v [ 1 ] . a - > set_position ( Point2i ( 0 , 100 ) ) ;
node_a - > add_child ( v [ 0 ] . a ) ;
node_a - > add_child ( v [ 1 ] . a ) ;
Node2D * node_b = memnew ( Node2D ) ;
node_b - > set_position ( Point2i ( 100 , 0 ) ) ;
v [ 2 ] . a - > set_position ( Point2i ( 0 , 0 ) ) ;
v [ 3 ] . a - > set_position ( Point2i ( 0 , 100 ) ) ;
node_b - > add_child ( v [ 2 ] . a ) ;
node_b - > add_child ( v [ 3 ] . a ) ;
root - > add_child ( node_a ) ;
root - > add_child ( node_b ) ;
Point2i on_all = Point2i ( 50 , 50 ) ;
Point2i on_0 = Point2i ( 10 , 10 ) ;
Point2i on_01 = Point2i ( 10 , 50 ) ;
Point2i on_02 = Point2i ( 50 , 10 ) ;
Array empty_signal_args_2 ;
empty_signal_args_2 . push_back ( Array ( ) ) ;
empty_signal_args_2 . push_back ( Array ( ) ) ;
Array empty_signal_args_4 ;
empty_signal_args_4 . push_back ( Array ( ) ) ;
empty_signal_args_4 . push_back ( Array ( ) ) ;
empty_signal_args_4 . push_back ( Array ( ) ) ;
empty_signal_args_4 . push_back ( Array ( ) ) ;
for ( PickingCollider E : v ) {
E . a - > init_signals ( ) ;
}
SUBCASE ( " [Viewport][Picking2D] Mouse Motion " ) {
SEND_GUI_MOUSE_MOTION_EVENT ( on_all , MouseButtonMask : : NONE , Key : : NONE ) ;
tree - > physics_process ( 1 ) ;
2024-05-13 14:56:03 +00:00
SIGNAL_CHECK ( SceneStringName ( mouse_entered ) , empty_signal_args_4 ) ;
SIGNAL_CHECK_FALSE ( SceneStringName ( mouse_exited ) ) ;
2023-02-10 01:09:07 +00:00
for ( PickingCollider E : v ) {
CHECK ( E . a - > enter_id ) ;
CHECK_FALSE ( E . a - > exit_id ) ;
E . a - > test_reset ( ) ;
}
SEND_GUI_MOUSE_MOTION_EVENT ( on_01 , MouseButtonMask : : NONE , Key : : NONE ) ;
tree - > physics_process ( 1 ) ;
2024-05-13 14:56:03 +00:00
SIGNAL_CHECK_FALSE ( SceneStringName ( mouse_entered ) ) ;
SIGNAL_CHECK ( SceneStringName ( mouse_exited ) , empty_signal_args_2 ) ;
2023-02-10 01:09:07 +00:00
for ( int i = 0 ; i < v . size ( ) ; i + + ) {
CHECK_FALSE ( v [ i ] . a - > enter_id ) ;
if ( i < 2 ) {
CHECK_FALSE ( v [ i ] . a - > exit_id ) ;
} else {
CHECK ( v [ i ] . a - > exit_id ) ;
}
v [ i ] . a - > test_reset ( ) ;
}
SEND_GUI_MOUSE_MOTION_EVENT ( on_outside , MouseButtonMask : : NONE , Key : : NONE ) ;
tree - > physics_process ( 1 ) ;
2024-05-13 14:56:03 +00:00
SIGNAL_CHECK_FALSE ( SceneStringName ( mouse_entered ) ) ;
SIGNAL_CHECK ( SceneStringName ( mouse_exited ) , empty_signal_args_2 ) ;
2023-02-10 01:09:07 +00:00
for ( int i = 0 ; i < v . size ( ) ; i + + ) {
CHECK_FALSE ( v [ i ] . a - > enter_id ) ;
if ( i < 2 ) {
CHECK ( v [ i ] . a - > exit_id ) ;
} else {
CHECK_FALSE ( v [ i ] . a - > exit_id ) ;
}
v [ i ] . a - > test_reset ( ) ;
}
}
SUBCASE ( " [Viewport][Picking2D] Object moved / passive hovering " ) {
SEND_GUI_MOUSE_MOTION_EVENT ( on_all , MouseButtonMask : : NONE , Key : : NONE ) ;
tree - > physics_process ( 1 ) ;
for ( int i = 0 ; i < v . size ( ) ; i + + ) {
CHECK ( v [ i ] . a - > enter_id ) ;
CHECK_FALSE ( v [ i ] . a - > exit_id ) ;
v [ i ] . a - > test_reset ( ) ;
}
node_b - > set_position ( Point2i ( 200 , 0 ) ) ;
tree - > physics_process ( 1 ) ;
for ( int i = 0 ; i < v . size ( ) ; i + + ) {
CHECK_FALSE ( v [ i ] . a - > enter_id ) ;
if ( i < 2 ) {
CHECK_FALSE ( v [ i ] . a - > exit_id ) ;
} else {
CHECK ( v [ i ] . a - > exit_id ) ;
}
v [ i ] . a - > test_reset ( ) ;
}
node_b - > set_position ( Point2i ( 100 , 0 ) ) ;
tree - > physics_process ( 1 ) ;
for ( int i = 0 ; i < v . size ( ) ; i + + ) {
if ( i < 2 ) {
CHECK_FALSE ( v [ i ] . a - > enter_id ) ;
} else {
CHECK ( v [ i ] . a - > enter_id ) ;
}
CHECK_FALSE ( v [ i ] . a - > exit_id ) ;
v [ i ] . a - > test_reset ( ) ;
}
}
SUBCASE ( " [Viewport][Picking2D] No Processing " ) {
SEND_GUI_MOUSE_MOTION_EVENT ( on_background , MouseButtonMask : : NONE , Key : : NONE ) ;
tree - > physics_process ( 1 ) ;
for ( PickingCollider E : v ) {
E . a - > test_reset ( ) ;
}
v [ 0 ] . a - > set_process_mode ( Node : : PROCESS_MODE_DISABLED ) ;
v [ 0 ] . c - > set_process_mode ( Node : : PROCESS_MODE_DISABLED ) ;
SEND_GUI_MOUSE_MOTION_EVENT ( on_02 , MouseButtonMask : : NONE , Key : : NONE ) ;
tree - > physics_process ( 1 ) ;
CHECK_FALSE ( v [ 0 ] . a - > enter_id ) ;
CHECK_FALSE ( v [ 0 ] . a - > exit_id ) ;
CHECK ( v [ 2 ] . a - > enter_id ) ;
CHECK_FALSE ( v [ 2 ] . a - > exit_id ) ;
for ( PickingCollider E : v ) {
E . a - > test_reset ( ) ;
}
SEND_GUI_MOUSE_MOTION_EVENT ( on_background , MouseButtonMask : : NONE , Key : : NONE ) ;
tree - > physics_process ( 1 ) ;
CHECK_FALSE ( v [ 0 ] . a - > enter_id ) ;
CHECK_FALSE ( v [ 0 ] . a - > exit_id ) ;
CHECK_FALSE ( v [ 2 ] . a - > enter_id ) ;
CHECK ( v [ 2 ] . a - > exit_id ) ;
for ( PickingCollider E : v ) {
E . a - > test_reset ( ) ;
}
v [ 0 ] . a - > set_process_mode ( Node : : PROCESS_MODE_ALWAYS ) ;
v [ 0 ] . c - > set_process_mode ( Node : : PROCESS_MODE_ALWAYS ) ;
}
SUBCASE ( " [Viewport][Picking2D] Multiple events in series " ) {
SEND_GUI_MOUSE_MOTION_EVENT ( on_0 , MouseButtonMask : : NONE , Key : : NONE ) ;
SEND_GUI_MOUSE_MOTION_EVENT ( on_0 + Point2i ( 10 , 0 ) , MouseButtonMask : : NONE , Key : : NONE ) ;
tree - > physics_process ( 1 ) ;
for ( int i = 0 ; i < v . size ( ) ; i + + ) {
if ( i < 1 ) {
CHECK ( v [ i ] . a - > enter_id ) ;
} else {
CHECK_FALSE ( v [ i ] . a - > enter_id ) ;
}
CHECK_FALSE ( v [ i ] . a - > exit_id ) ;
v [ i ] . a - > test_reset ( ) ;
}
SEND_GUI_MOUSE_MOTION_EVENT ( on_background , MouseButtonMask : : NONE , Key : : NONE ) ;
SEND_GUI_MOUSE_MOTION_EVENT ( on_background + Point2i ( 10 , 10 ) , MouseButtonMask : : NONE , Key : : NONE ) ;
tree - > physics_process ( 1 ) ;
for ( int i = 0 ; i < v . size ( ) ; i + + ) {
CHECK_FALSE ( v [ i ] . a - > enter_id ) ;
if ( i < 1 ) {
CHECK ( v [ i ] . a - > exit_id ) ;
} else {
CHECK_FALSE ( v [ i ] . a - > exit_id ) ;
}
v [ i ] . a - > test_reset ( ) ;
}
}
SUBCASE ( " [Viewport][Picking2D] Disable Picking " ) {
SEND_GUI_MOUSE_MOTION_EVENT ( on_02 , MouseButtonMask : : NONE , Key : : NONE ) ;
root - > set_physics_object_picking ( false ) ;
CHECK_FALSE ( root - > get_physics_object_picking ( ) ) ;
tree - > physics_process ( 1 ) ;
for ( int i = 0 ; i < v . size ( ) ; i + + ) {
CHECK_FALSE ( v [ i ] . a - > enter_id ) ;
v [ i ] . a - > test_reset ( ) ;
}
root - > set_physics_object_picking ( true ) ;
CHECK ( root - > get_physics_object_picking ( ) ) ;
}
SUBCASE ( " [Viewport][Picking2D] CollisionObject in CanvasLayer " ) {
CanvasLayer * node_c = memnew ( CanvasLayer ) ;
node_c - > set_rotation ( Math_PI ) ;
node_c - > set_offset ( Point2i ( 100 , 100 ) ) ;
root - > add_child ( node_c ) ;
v [ 2 ] . a - > reparent ( node_c , false ) ;
v [ 3 ] . a - > reparent ( node_c , false ) ;
SEND_GUI_MOUSE_MOTION_EVENT ( on_02 , MouseButtonMask : : NONE , Key : : NONE ) ;
tree - > physics_process ( 1 ) ;
for ( int i = 0 ; i < v . size ( ) ; i + + ) {
if ( i = = 0 | | i = = 3 ) {
CHECK ( v [ i ] . a - > enter_id ) ;
} else {
CHECK_FALSE ( v [ i ] . a - > enter_id ) ;
}
v [ i ] . a - > test_reset ( ) ;
}
v [ 2 ] . a - > reparent ( node_b , false ) ;
v [ 3 ] . a - > reparent ( node_b , false ) ;
root - > remove_child ( node_c ) ;
memdelete ( node_c ) ;
}
SUBCASE ( " [Viewport][Picking2D] Picking Sort " ) {
root - > set_physics_object_picking_sort ( true ) ;
CHECK ( root - > get_physics_object_picking_sort ( ) ) ;
SUBCASE ( " [Viewport][Picking2D] Picking Sort Z-Index " ) {
node_a - > set_z_index ( 10 ) ;
v [ 0 ] . a - > set_z_index ( 0 ) ;
v [ 1 ] . a - > set_z_index ( 2 ) ;
node_b - > set_z_index ( 5 ) ;
v [ 2 ] . a - > set_z_index ( 8 ) ;
v [ 3 ] . a - > set_z_index ( 11 ) ;
v [ 3 ] . a - > set_z_as_relative ( false ) ;
TestArea2D : : counter = 0 ;
SEND_GUI_MOUSE_MOTION_EVENT ( on_all , MouseButtonMask : : NONE , Key : : NONE ) ;
tree - > physics_process ( 1 ) ;
CHECK ( v [ 0 ] . a - > enter_id = = 4 ) ;
CHECK ( v [ 1 ] . a - > enter_id = = 2 ) ;
CHECK ( v [ 2 ] . a - > enter_id = = 1 ) ;
CHECK ( v [ 3 ] . a - > enter_id = = 3 ) ;
for ( int i = 0 ; i < v . size ( ) ; i + + ) {
CHECK_FALSE ( v [ i ] . a - > exit_id ) ;
v [ i ] . a - > test_reset ( ) ;
}
TestArea2D : : counter = 0 ;
SEND_GUI_MOUSE_MOTION_EVENT ( on_background , MouseButtonMask : : NONE , Key : : NONE ) ;
tree - > physics_process ( 1 ) ;
CHECK ( v [ 0 ] . a - > exit_id = = 4 ) ;
CHECK ( v [ 1 ] . a - > exit_id = = 2 ) ;
CHECK ( v [ 2 ] . a - > exit_id = = 1 ) ;
CHECK ( v [ 3 ] . a - > exit_id = = 3 ) ;
for ( int i = 0 ; i < v . size ( ) ; i + + ) {
CHECK_FALSE ( v [ i ] . a - > enter_id ) ;
v [ i ] . a - > set_z_as_relative ( true ) ;
v [ i ] . a - > set_z_index ( 0 ) ;
v [ i ] . a - > test_reset ( ) ;
}
node_a - > set_z_index ( 0 ) ;
node_b - > set_z_index ( 0 ) ;
}
SUBCASE ( " [Viewport][Picking2D] Picking Sort Scene Tree Location " ) {
TestArea2D : : counter = 0 ;
SEND_GUI_MOUSE_MOTION_EVENT ( on_all , MouseButtonMask : : NONE , Key : : NONE ) ;
tree - > physics_process ( 1 ) ;
for ( int i = 0 ; i < v . size ( ) ; i + + ) {
CHECK ( v [ i ] . a - > enter_id = = 4 - i ) ;
CHECK_FALSE ( v [ i ] . a - > exit_id ) ;
v [ i ] . a - > test_reset ( ) ;
}
TestArea2D : : counter = 0 ;
SEND_GUI_MOUSE_MOTION_EVENT ( on_background , MouseButtonMask : : NONE , Key : : NONE ) ;
tree - > physics_process ( 1 ) ;
for ( int i = 0 ; i < v . size ( ) ; i + + ) {
CHECK_FALSE ( v [ i ] . a - > enter_id ) ;
CHECK ( v [ i ] . a - > exit_id = = 4 - i ) ;
v [ i ] . a - > test_reset ( ) ;
}
}
root - > set_physics_object_picking_sort ( false ) ;
CHECK_FALSE ( root - > get_physics_object_picking_sort ( ) ) ;
}
SUBCASE ( " [Viewport][Picking2D] Mouse Button " ) {
SEND_GUI_MOUSE_BUTTON_EVENT ( on_0 , MouseButton : : LEFT , MouseButtonMask : : LEFT , Key : : NONE ) ;
tree - > physics_process ( 1 ) ;
for ( int i = 0 ; i < v . size ( ) ; i + + ) {
if ( i = = 0 ) {
CHECK ( v [ i ] . a - > enter_id ) ;
} else {
CHECK_FALSE ( v [ i ] . a - > enter_id ) ;
}
CHECK_FALSE ( v [ i ] . a - > exit_id ) ;
v [ i ] . a - > test_reset ( ) ;
}
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT ( on_0 , MouseButton : : LEFT , MouseButtonMask : : NONE , Key : : NONE ) ;
tree - > physics_process ( 1 ) ;
for ( int i = 0 ; i < v . size ( ) ; i + + ) {
CHECK_FALSE ( v [ i ] . a - > enter_id ) ;
CHECK_FALSE ( v [ i ] . a - > exit_id ) ;
v [ i ] . a - > test_reset ( ) ;
}
}
SUBCASE ( " [Viewport][Picking2D] Screen Touch " ) {
SEND_GUI_TOUCH_EVENT ( on_01 , true , false ) ;
tree - > physics_process ( 1 ) ;
for ( int i = 0 ; i < v . size ( ) ; i + + ) {
if ( i < 2 ) {
Ref < InputEventScreenTouch > st = v [ i ] . a - > last_input_event ;
CHECK ( st . is_valid ( ) ) ;
} else {
CHECK ( v [ i ] . a - > last_input_event . is_null ( ) ) ;
}
v [ i ] . a - > test_reset ( ) ;
}
}
for ( PickingCollider E : v ) {
2024-05-13 14:56:03 +00:00
SIGNAL_UNWATCH ( E . a , SceneStringName ( mouse_entered ) ) ;
SIGNAL_UNWATCH ( E . a , SceneStringName ( mouse_exited ) ) ;
2023-02-10 01:09:07 +00:00
memdelete ( E . c ) ;
memdelete ( E . a ) ;
}
}
2023-08-01 12:44:45 +00:00
TEST_CASE ( " [SceneTree][Viewport] Embedded Windows " ) {
Window * root = SceneTree : : get_singleton ( ) - > get_root ( ) ;
Window * w = memnew ( Window ) ;
SUBCASE ( " [Viewport] Safe-rect of embedded Window " ) {
root - > add_child ( w ) ;
root - > subwindow_set_popup_safe_rect ( w , Rect2i ( 10 , 10 , 10 , 10 ) ) ;
CHECK_EQ ( root - > subwindow_get_popup_safe_rect ( w ) , Rect2i ( 10 , 10 , 10 , 10 ) ) ;
root - > remove_child ( w ) ;
CHECK_EQ ( root - > subwindow_get_popup_safe_rect ( w ) , Rect2i ( ) ) ;
}
memdelete ( w ) ;
}
2023-02-09 10:29:46 +00:00
} // namespace TestViewport
# endif // TEST_VIEWPORT_H