@@ -846,6 +846,297 @@ TEST_CASE("[SceneTree][TabBar] layout and offset") {
846846 memdelete (tab_bar);
847847}
848848
849- // FIXME: Add tests for mouse click, keyboard navigation, and drag and drop.
849+ TEST_CASE (" [SceneTree][TabBar] Mouse interaction" ) {
850+ TabBar *tab_bar = memnew (TabBar);
851+ SceneTree::get_singleton ()->get_root ()->add_child (tab_bar);
852+
853+ tab_bar->set_clip_tabs (false );
854+ tab_bar->add_tab (" tab0" );
855+ tab_bar->add_tab (" tab1 " );
856+ tab_bar->add_tab (" tab2 " );
857+ MessageQueue::get_singleton ()->flush ();
858+
859+ Vector<Rect2> tab_rects = {
860+ tab_bar->get_tab_rect (0 ),
861+ tab_bar->get_tab_rect (1 ),
862+ tab_bar->get_tab_rect (2 )
863+ };
864+
865+ SIGNAL_WATCH (tab_bar, " active_tab_rearranged" );
866+ SIGNAL_WATCH (tab_bar, " tab_button_pressed" );
867+ SIGNAL_WATCH (tab_bar, " tab_changed" );
868+ SIGNAL_WATCH (tab_bar, " tab_clicked" );
869+ SIGNAL_WATCH (tab_bar, " tab_close_pressed" );
870+ SIGNAL_WATCH (tab_bar, " tab_hovered" );
871+ SIGNAL_WATCH (tab_bar, " tab_selected" );
872+
873+ SUBCASE (" [TabBar] Hover over tabs" ) {
874+ // Default is -1.
875+ CHECK (tab_bar->get_hovered_tab () == -1 );
876+
877+ // Hover over the first tab.
878+ SEND_GUI_MOUSE_MOTION_EVENT (tab_rects[0 ].position , MouseButtonMask::NONE, Key::NONE);
879+ SIGNAL_CHECK (" tab_hovered" , { { 0 } });
880+ CHECK (tab_bar->get_hovered_tab () == 0 );
881+
882+ // Hover over same tab won't send signal again.
883+ SEND_GUI_MOUSE_MOTION_EVENT (tab_rects[0 ].position + Point2 (2 , 2 ), MouseButtonMask::NONE, Key::NONE);
884+ SIGNAL_CHECK_FALSE (" tab_hovered" );
885+ CHECK (tab_bar->get_hovered_tab () == 0 );
886+
887+ // Hover over different tab.
888+ SEND_GUI_MOUSE_MOTION_EVENT (tab_rects[1 ].position , MouseButtonMask::NONE, Key::NONE);
889+ SIGNAL_CHECK (" tab_hovered" , { { 1 } });
890+ CHECK (tab_bar->get_hovered_tab () == 1 );
891+
892+ // Exit area.
893+ SEND_GUI_MOUSE_MOTION_EVENT (Point2 (-1 , -1 ), MouseButtonMask::NONE, Key::NONE);
894+ SIGNAL_CHECK_FALSE (" tab_hovered" );
895+ CHECK (tab_bar->get_hovered_tab () == -1 );
896+ }
897+
898+ SUBCASE (" [TabBar] Click to change current" ) {
899+ CHECK (tab_bar->get_current_tab () == 0 );
900+ CHECK (tab_bar->get_previous_tab () == -1 );
901+ SIGNAL_DISCARD (" tab_selected" );
902+ SIGNAL_DISCARD (" tab_changed" );
903+
904+ // Click to set the current tab.
905+ SEND_GUI_MOUSE_BUTTON_EVENT (tab_rects[1 ].position , MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
906+ CHECK (tab_bar->get_current_tab () == 1 );
907+ CHECK (tab_bar->get_previous_tab () == 0 );
908+ SIGNAL_CHECK (" tab_selected" , { { 1 } });
909+ SIGNAL_CHECK (" tab_changed" , { { 1 } });
910+ SIGNAL_CHECK (" tab_clicked" , { { 1 } });
911+
912+ // Click on the same tab.
913+ SEND_GUI_MOUSE_BUTTON_EVENT (tab_rects[1 ].position , MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
914+ CHECK (tab_bar->get_current_tab () == 1 );
915+ CHECK (tab_bar->get_previous_tab () == 1 );
916+ SIGNAL_CHECK (" tab_selected" , { { 1 } });
917+ SIGNAL_CHECK_FALSE (" tab_changed" );
918+ SIGNAL_CHECK (" tab_clicked" , { { 1 } });
919+ }
920+
921+ SUBCASE (" [TabBar] Click on close button" ) {
922+ const Size2 close_button_size = tab_bar->get_theme_icon (" close_icon" )->get_size ();
923+ int h_separation = tab_bar->get_theme_constant (" h_separation" );
924+ float margin = tab_bar->get_theme_stylebox (" tab_hovered_style" )->get_margin (SIDE_RIGHT);
925+
926+ tab_bar->set_tab_close_display_policy (TabBar::CLOSE_BUTTON_SHOW_ALWAYS);
927+ MessageQueue::get_singleton ()->flush ();
928+ tab_rects = { tab_bar->get_tab_rect (0 ), tab_bar->get_tab_rect (1 ), tab_bar->get_tab_rect (2 ) };
929+
930+ Point2 cb_pos = Size2 (tab_rects[0 ].get_end ().x - close_button_size.x - h_separation - margin + 1 , tab_rects[0 ].position .y + (tab_rects[0 ].size .y - close_button_size.y ) / 2 + 1 );
931+ SEND_GUI_MOUSE_MOTION_EVENT (cb_pos, MouseButtonMask::LEFT, Key::NONE);
932+ SEND_GUI_MOUSE_BUTTON_EVENT (cb_pos, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
933+ SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT (cb_pos, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
934+ SIGNAL_CHECK (" tab_close_pressed" , { { 0 } });
935+ SIGNAL_CHECK_FALSE (" tab_clicked" );
936+ SIGNAL_CHECK_FALSE (" tab_selected" );
937+ SIGNAL_CHECK_FALSE (" tab_changed" );
938+ SIGNAL_CHECK_FALSE (" tab_button_pressed" );
939+ // It does not remove the tab.
940+ CHECK (tab_bar->get_tab_count () == 3 );
941+ }
942+
943+ SUBCASE (" [TabBar] Drag and drop internally" ) {
944+ // Cannot drag if not enabled.
945+ CHECK_FALSE (tab_bar->get_drag_to_rearrange_enabled ());
946+ SEND_GUI_MOUSE_BUTTON_EVENT (tab_rects[0 ].position , MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
947+ SEND_GUI_MOUSE_MOTION_EVENT (tab_rects[1 ].position , MouseButtonMask::LEFT, Key::NONE);
948+ SIGNAL_CHECK (" tab_selected" , { { 0 } });
949+ SIGNAL_CHECK_FALSE (" tab_changed" );
950+ CHECK_FALSE (tab_bar->get_viewport ()->gui_is_dragging ());
951+ SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT (tab_rects[1 ].position , MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
952+ CHECK_FALSE (tab_bar->get_viewport ()->gui_is_dragging ());
953+ CHECK (tab_bar->get_tab_title (0 ) == " tab0" );
954+ CHECK (tab_bar->get_tab_title (1 ) == " tab1 " );
955+ CHECK (tab_bar->get_tab_title (2 ) == " tab2 " );
956+ SIGNAL_CHECK_FALSE (" active_tab_rearranged" );
957+ SIGNAL_CHECK_FALSE (" tab_selected" );
958+ SIGNAL_CHECK_FALSE (" tab_changed" );
959+
960+ tab_bar->set_drag_to_rearrange_enabled (true );
961+ CHECK (tab_bar->get_drag_to_rearrange_enabled ());
962+
963+ // Release over the same tab to not move.
964+ SEND_GUI_MOUSE_BUTTON_EVENT (tab_rects[0 ].position , MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
965+ SEND_GUI_MOUSE_MOTION_EVENT (tab_rects[1 ].position , MouseButtonMask::LEFT, Key::NONE);
966+ SEND_GUI_MOUSE_MOTION_EVENT (tab_rects[0 ].position , MouseButtonMask::LEFT, Key::NONE);
967+ SIGNAL_CHECK (" tab_selected" , { { 0 } });
968+ SIGNAL_CHECK_FALSE (" tab_changed" );
969+ CHECK (tab_bar->get_viewport ()->gui_is_dragging ());
970+ SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT (tab_rects[0 ].position , MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
971+ CHECK_FALSE (tab_bar->get_viewport ()->gui_is_dragging ());
972+ CHECK (tab_bar->get_tab_title (0 ) == " tab0" );
973+ CHECK (tab_bar->get_tab_title (1 ) == " tab1 " );
974+ CHECK (tab_bar->get_tab_title (2 ) == " tab2 " );
975+ SIGNAL_CHECK_FALSE (" active_tab_rearranged" );
976+ SIGNAL_CHECK_FALSE (" tab_selected" );
977+ SIGNAL_CHECK_FALSE (" tab_changed" );
978+
979+ // Move the first tab after the second.
980+ SEND_GUI_MOUSE_BUTTON_EVENT (tab_rects[0 ].position , MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
981+ SEND_GUI_MOUSE_MOTION_EVENT (tab_rects[1 ].position , MouseButtonMask::LEFT, Key::NONE);
982+ SIGNAL_CHECK (" tab_selected" , { { 0 } });
983+ SIGNAL_CHECK_FALSE (" tab_changed" );
984+ CHECK (tab_bar->get_viewport ()->gui_is_dragging ());
985+ SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT (tab_rects[1 ].position + Point2 (tab_rects[1 ].size .x / 2 + 1 , 0 ), MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
986+ CHECK_FALSE (tab_bar->get_viewport ()->gui_is_dragging ());
987+ CHECK (tab_bar->get_tab_title (0 ) == " tab1 " );
988+ CHECK (tab_bar->get_tab_title (1 ) == " tab0" );
989+ CHECK (tab_bar->get_tab_title (2 ) == " tab2 " );
990+ CHECK (tab_bar->get_current_tab () == 1 );
991+ SIGNAL_CHECK (" active_tab_rearranged" , { { 1 } });
992+ SIGNAL_CHECK (" tab_selected" , { { 1 } });
993+ SIGNAL_CHECK_FALSE (" tab_changed" );
994+
995+ tab_rects = { tab_bar->get_tab_rect (0 ), tab_bar->get_tab_rect (1 ), tab_bar->get_tab_rect (2 ) };
996+
997+ // Move the last tab to be the first.
998+ SEND_GUI_MOUSE_BUTTON_EVENT (tab_rects[2 ].position , MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
999+ SEND_GUI_MOUSE_MOTION_EVENT (tab_rects[0 ].position , MouseButtonMask::LEFT, Key::NONE);
1000+ SIGNAL_CHECK (" tab_selected" , { { 2 } });
1001+ SIGNAL_CHECK (" tab_changed" , { { 2 } });
1002+ CHECK (tab_bar->get_viewport ()->gui_is_dragging ());
1003+ SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT (tab_rects[0 ].position , MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
1004+ CHECK_FALSE (tab_bar->get_viewport ()->gui_is_dragging ());
1005+ CHECK (tab_bar->get_tab_title (0 ) == " tab2 " );
1006+ CHECK (tab_bar->get_tab_title (1 ) == " tab1 " );
1007+ CHECK (tab_bar->get_tab_title (2 ) == " tab0" );
1008+ CHECK (tab_bar->get_current_tab () == 0 );
1009+ SIGNAL_CHECK (" active_tab_rearranged" , { { 0 } });
1010+ SIGNAL_CHECK (" tab_selected" , { { 0 } });
1011+ SIGNAL_CHECK_FALSE (" tab_changed" );
1012+ }
1013+
1014+ SUBCASE (" [TabBar] Drag and drop to different TabBar" ) {
1015+ TabBar *target_tab_bar = memnew (TabBar);
1016+ SceneTree::get_singleton ()->get_root ()->add_child (target_tab_bar);
1017+
1018+ target_tab_bar->set_clip_tabs (false );
1019+ target_tab_bar->add_tab (" other_tab0" );
1020+ MessageQueue::get_singleton ()->flush ();
1021+ target_tab_bar->set_position (tab_bar->get_size ());
1022+ MessageQueue::get_singleton ()->flush ();
1023+
1024+ Vector<Rect2> target_tab_rects = { target_tab_bar->get_tab_rect (0 ) };
1025+ tab_bar->set_drag_to_rearrange_enabled (true );
1026+ tab_bar->set_tabs_rearrange_group (1 );
1027+
1028+ Point2 target_tab_after_first = target_tab_bar->get_position () + target_tab_rects[0 ].position + Point2 (target_tab_rects[0 ].size .x / 2 + 1 , 0 );
1029+
1030+ // Cannot drag to another TabBar that does not have drag to rearrange enabled.
1031+ CHECK_FALSE (target_tab_bar->get_drag_to_rearrange_enabled ());
1032+ SEND_GUI_MOUSE_BUTTON_EVENT (tab_rects[0 ].position , MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
1033+ SEND_GUI_MOUSE_MOTION_EVENT (tab_rects[0 ].position + Point2 (20 , 0 ), MouseButtonMask::LEFT, Key::NONE);
1034+ SEND_GUI_MOUSE_MOTION_EVENT (target_tab_after_first, MouseButtonMask::LEFT, Key::NONE);
1035+ SIGNAL_CHECK (" tab_selected" , { { 0 } });
1036+ SIGNAL_CHECK_FALSE (" tab_changed" );
1037+ CHECK (tab_bar->get_viewport ()->gui_is_dragging ());
1038+ SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT (target_tab_after_first, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
1039+ CHECK_FALSE (tab_bar->get_viewport ()->gui_is_dragging ());
1040+ CHECK (tab_bar->get_tab_count () == 3 );
1041+ CHECK (target_tab_bar->get_tab_count () == 1 );
1042+ SIGNAL_CHECK_FALSE (" active_tab_rearranged" );
1043+ SIGNAL_CHECK_FALSE (" tab_selected" );
1044+ SIGNAL_CHECK_FALSE (" tab_changed" );
1045+
1046+ // Cannot drag to another TabBar that has a tabs rearrange group of -1.
1047+ target_tab_bar->set_drag_to_rearrange_enabled (true );
1048+ tab_bar->set_tabs_rearrange_group (-1 );
1049+ SEND_GUI_MOUSE_BUTTON_EVENT (tab_rects[0 ].position , MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
1050+ SEND_GUI_MOUSE_MOTION_EVENT (tab_rects[0 ].position + Point2 (20 , 0 ), MouseButtonMask::LEFT, Key::NONE);
1051+ SEND_GUI_MOUSE_MOTION_EVENT (target_tab_after_first, MouseButtonMask::LEFT, Key::NONE);
1052+ SIGNAL_CHECK (" tab_selected" , { { 0 } });
1053+ SIGNAL_CHECK_FALSE (" tab_changed" );
1054+ CHECK (tab_bar->get_viewport ()->gui_is_dragging ());
1055+ SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT (target_tab_after_first, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
1056+ CHECK_FALSE (tab_bar->get_viewport ()->gui_is_dragging ());
1057+ CHECK (tab_bar->get_tab_count () == 3 );
1058+ CHECK (target_tab_bar->get_tab_count () == 1 );
1059+ SIGNAL_CHECK_FALSE (" active_tab_rearranged" );
1060+ SIGNAL_CHECK_FALSE (" tab_selected" );
1061+ SIGNAL_CHECK_FALSE (" tab_changed" );
1062+
1063+ // Cannot drag to another TabBar that has a different tabs rearrange group.
1064+ tab_bar->set_tabs_rearrange_group (1 );
1065+ target_tab_bar->set_tabs_rearrange_group (2 );
1066+ SEND_GUI_MOUSE_BUTTON_EVENT (tab_rects[0 ].position , MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
1067+ SEND_GUI_MOUSE_MOTION_EVENT (tab_rects[0 ].position + Point2 (20 , 0 ), MouseButtonMask::LEFT, Key::NONE);
1068+ SEND_GUI_MOUSE_MOTION_EVENT (target_tab_after_first, MouseButtonMask::LEFT, Key::NONE);
1069+ SIGNAL_CHECK (" tab_selected" , { { 0 } });
1070+ SIGNAL_CHECK_FALSE (" tab_changed" );
1071+ CHECK (tab_bar->get_viewport ()->gui_is_dragging ());
1072+ SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT (target_tab_after_first, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
1073+ CHECK_FALSE (tab_bar->get_viewport ()->gui_is_dragging ());
1074+ CHECK (tab_bar->get_tab_count () == 3 );
1075+ CHECK (target_tab_bar->get_tab_count () == 1 );
1076+ SIGNAL_CHECK_FALSE (" active_tab_rearranged" );
1077+ SIGNAL_CHECK_FALSE (" tab_selected" );
1078+ SIGNAL_CHECK_FALSE (" tab_changed" );
1079+
1080+ // Drag to target container.
1081+ target_tab_bar->set_tabs_rearrange_group (1 );
1082+ SEND_GUI_MOUSE_BUTTON_EVENT (tab_rects[0 ].position , MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
1083+ SEND_GUI_MOUSE_MOTION_EVENT (tab_rects[0 ].position + Point2 (20 , 0 ), MouseButtonMask::LEFT, Key::NONE);
1084+ SEND_GUI_MOUSE_MOTION_EVENT (target_tab_after_first, MouseButtonMask::LEFT, Key::NONE);
1085+ SIGNAL_CHECK (" tab_selected" , { { 0 } });
1086+ SIGNAL_CHECK_FALSE (" tab_changed" );
1087+ CHECK (tab_bar->get_viewport ()->gui_is_dragging ());
1088+ SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT (target_tab_after_first, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
1089+ CHECK_FALSE (tab_bar->get_viewport ()->gui_is_dragging ());
1090+ CHECK (tab_bar->get_tab_count () == 2 );
1091+ CHECK (target_tab_bar->get_tab_count () == 2 );
1092+ CHECK (tab_bar->get_tab_title (0 ) == " tab1 " );
1093+ CHECK (tab_bar->get_tab_title (1 ) == " tab2 " );
1094+ CHECK (target_tab_bar->get_tab_title (0 ) == " other_tab0" );
1095+ CHECK (target_tab_bar->get_tab_title (1 ) == " tab0" );
1096+ CHECK (tab_bar->get_current_tab () == 0 );
1097+ CHECK (target_tab_bar->get_current_tab () == 1 );
1098+ SIGNAL_CHECK_FALSE (" active_tab_rearranged" );
1099+ SIGNAL_CHECK_FALSE (" tab_selected" ); // Does not send since tab was removed.
1100+ SIGNAL_CHECK (" tab_changed" , { { 0 } });
1101+
1102+ Point2 target_tab = target_tab_bar->get_position ();
1103+
1104+ // Drag to target container at first index.
1105+ target_tab_bar->set_tabs_rearrange_group (1 );
1106+ SEND_GUI_MOUSE_BUTTON_EVENT (tab_rects[0 ].position , MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
1107+ SEND_GUI_MOUSE_MOTION_EVENT (tab_rects[0 ].position + Point2 (20 , 0 ), MouseButtonMask::LEFT, Key::NONE);
1108+ SEND_GUI_MOUSE_MOTION_EVENT (target_tab, MouseButtonMask::LEFT, Key::NONE);
1109+ SIGNAL_CHECK (" tab_selected" , { { 0 } });
1110+ SIGNAL_CHECK_FALSE (" tab_changed" );
1111+ CHECK (tab_bar->get_viewport ()->gui_is_dragging ());
1112+ SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT (target_tab, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
1113+ CHECK_FALSE (tab_bar->get_viewport ()->gui_is_dragging ());
1114+ CHECK (tab_bar->get_tab_count () == 1 );
1115+ CHECK (target_tab_bar->get_tab_count () == 3 );
1116+ CHECK (tab_bar->get_tab_title (0 ) == " tab2 " );
1117+ CHECK (target_tab_bar->get_tab_title (0 ) == " tab1 " );
1118+ CHECK (target_tab_bar->get_tab_title (1 ) == " other_tab0" );
1119+ CHECK (target_tab_bar->get_tab_title (2 ) == " tab0" );
1120+ CHECK (tab_bar->get_current_tab () == 0 );
1121+ CHECK (target_tab_bar->get_current_tab () == 0 );
1122+ SIGNAL_CHECK_FALSE (" active_tab_rearranged" );
1123+ SIGNAL_CHECK_FALSE (" tab_selected" ); // Does not send since tab was removed.
1124+ SIGNAL_CHECK (" tab_changed" , { { 0 } });
1125+
1126+ memdelete (target_tab_bar);
1127+ }
1128+
1129+ SIGNAL_UNWATCH (tab_bar, " active_tab_rearranged" );
1130+ SIGNAL_UNWATCH (tab_bar, " tab_button_pressed" );
1131+ SIGNAL_UNWATCH (tab_bar, " tab_changed" );
1132+ SIGNAL_UNWATCH (tab_bar, " tab_clicked" );
1133+ SIGNAL_UNWATCH (tab_bar, " tab_close_pressed" );
1134+ SIGNAL_UNWATCH (tab_bar, " tab_hovered" );
1135+ SIGNAL_UNWATCH (tab_bar, " tab_selected" );
1136+
1137+ memdelete (tab_bar);
1138+ }
1139+
1140+ // FIXME: Add tests for keyboard navigation and other methods.
8501141
8511142} // namespace TestTabBar
0 commit comments