/**************************************************************************/ /* test_scroll_container.cpp */ /**************************************************************************/ /* 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. */ /**************************************************************************/ #include "tests/test_macros.h" TEST_FORCE_LINK(test_scroll_container) #include "scene/gui/control.h" #include "scene/gui/scroll_container.h" #include "scene/main/scene_tree.h" #include "scene/main/window.h" namespace TestScrollContainer { TEST_CASE("[SceneTree][ScrollContainer] Default behavior") { ScrollContainer *test_container = memnew(ScrollContainer); Window *root = SceneTree::get_singleton()->get_root(); root->add_child(test_container); Control *test_control = memnew(Control); test_control->set_custom_minimum_size(Size2(20, 20)); test_container->add_child(test_control); test_container->set_size(Size2(100, 100)); SceneTree::get_singleton()->process(0); CHECK_MESSAGE( test_container->get_horizontal_scroll_mode() == ScrollContainer::SCROLL_MODE_AUTO, "Horizontal scroll mode is SCROLL_MODE_AUTO by default."); CHECK_MESSAGE( test_container->get_vertical_scroll_mode() == ScrollContainer::SCROLL_MODE_AUTO, "Vertical scroll mode is SCROLL_MODE_AUTO by default."); CHECK_MESSAGE( test_container->get_scroll_hint_mode() == ScrollContainer::SCROLL_HINT_MODE_DISABLED, "Scroll hint mode is SCROLL_HINT_MODE_DISABLED by default."); CHECK_MESSAGE( !test_container->is_scroll_hint_tiled(), "tile_scroll_hint is disabled by default."); CHECK_MESSAGE( !test_container->is_following_focus(), "follow_focus is disabled by default."); CHECK_MESSAGE( !test_container->is_scroll_horizontal_by_default(), "scroll_horizontal_by_default is disabled by default."); CHECK_MESSAGE( !test_container->get_draw_focus_border(), "draw_focus_border is disabled by default."); CHECK_MESSAGE( test_container->is_clipping_contents(), "clip_contents is enabled by default."); CHECK_MESSAGE( test_container->get_h_scroll() == 0, "Horizontal scroll starts at 0 by default."); CHECK_MESSAGE( test_container->get_v_scroll() == 0, "Vertical scroll starts at 0 by default."); CHECK_MESSAGE( test_container->get_h_scroll_bar() != nullptr, "Horizontal scrollbar is created by default."); CHECK_MESSAGE( test_container->get_v_scroll_bar() != nullptr, "Vertical scrollbar is created by default."); CHECK_MESSAGE( !test_container->get_h_scroll_bar()->is_visible(), "Horizontal scrollbar is hidden in auto mode when content fits."); CHECK_MESSAGE( !test_container->get_v_scroll_bar()->is_visible(), "Vertical scrollbar is hidden in auto mode when content fits."); memdelete(test_control); memdelete(test_container); } TEST_CASE("[SceneTree][ScrollContainer] Scroll modes") { ScrollContainer *test_container = memnew(ScrollContainer); Window *root = SceneTree::get_singleton()->get_root(); root->add_child(test_container); Control *test_control = memnew(Control); test_container->add_child(test_control); test_control->set_custom_minimum_size(Size2(100, 100)); Control *target_control = memnew(Control); target_control->set_custom_minimum_size(Size2(20, 20)); target_control->set_position(Point2(80, 80)); target_control->set_size(Size2(20, 20)); test_control->add_child(target_control); test_container->set_size(Size2(50, 50)); SceneTree::get_singleton()->process(0); CHECK_MESSAGE( test_container->get_h_scroll_bar()->is_visible(), "H Scrollbar displays when ScrollContainer width is smaller than contents width with SCROLL_MODE_AUTO."); CHECK_MESSAGE( test_container->get_v_scroll_bar()->is_visible(), "V Scrollbar displays when ScrollContainer height is smaller than contents height with SCROLL_MODE_AUTO."); test_container->set_size(Size2(150, 150)); SceneTree::get_singleton()->process(0); CHECK_MESSAGE( !test_container->get_h_scroll_bar()->is_visible(), "H Scrollbar does not display when ScrollContainer width is larger than contents width with SCROLL_MODE_AUTO."); CHECK_MESSAGE( !test_container->get_v_scroll_bar()->is_visible(), "V Scrollbar does not display when ScrollContainer height is larger than contents height with SCROLL_MODE_AUTO."); test_container->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED); test_container->set_vertical_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED); test_container->set_size(Size2(50, 50)); SceneTree::get_singleton()->process(0); CHECK_MESSAGE( !test_container->get_h_scroll_bar()->is_visible(), "H Scrollbar does not display when ScrollContainer width is smaller than contents width with SCROLL_MODE_DISABLED."); CHECK_MESSAGE( !test_container->get_v_scroll_bar()->is_visible(), "V Scrollbar does not display when ScrollContainer height is smaller than contents height with SCROLL_MODE_DISABLED."); test_container->set_h_scroll(0); test_container->set_v_scroll(0); int disabled_h_scroll_before = test_container->get_h_scroll(); int disabled_v_scroll_before = test_container->get_v_scroll(); test_container->ensure_control_visible(target_control); SceneTree::get_singleton()->process(0); CHECK_MESSAGE( test_container->get_h_scroll() == disabled_h_scroll_before, "SCROLL_MODE_DISABLED does not change horizontal scroll when ensure_control_visible() targets an out-of-view descendant."); CHECK_MESSAGE( test_container->get_v_scroll() == disabled_v_scroll_before, "SCROLL_MODE_DISABLED does not change vertical scroll when ensure_control_visible() targets an out-of-view descendant."); test_container->set_h_scroll(0); test_container->set_v_scroll(0); test_container->set_size(Size2(150, 150)); SceneTree::get_singleton()->process(0); CHECK_MESSAGE( !test_container->get_h_scroll_bar()->is_visible(), "H Scrollbar does not display when ScrollContainer width is larger than contents width with SCROLL_MODE_DISABLED."); CHECK_MESSAGE( !test_container->get_v_scroll_bar()->is_visible(), "V Scrollbar does not display when ScrollContainer height is larger than contents height with SCROLL_MODE_DISABLED."); test_container->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_SHOW_ALWAYS); test_container->set_vertical_scroll_mode(ScrollContainer::SCROLL_MODE_SHOW_ALWAYS); test_container->set_size(Size2(50, 50)); SceneTree::get_singleton()->process(0); CHECK_MESSAGE( test_container->get_h_scroll_bar()->is_visible(), "H Scrollbar displays when ScrollContainer width is smaller than contents width with SCROLL_MODE_SHOW_ALWAYS."); CHECK_MESSAGE( test_container->get_v_scroll_bar()->is_visible(), "V Scrollbar displays when ScrollContainer height is smaller than contents height with SCROLL_MODE_SHOW_ALWAYS."); test_container->set_size(Size2(150, 150)); SceneTree::get_singleton()->process(0); CHECK_MESSAGE( test_container->get_h_scroll_bar()->is_visible(), "H Scrollbar displays when ScrollContainer width is larger than contents width with SCROLL_MODE_SHOW_ALWAYS."); CHECK_MESSAGE( test_container->get_v_scroll_bar()->is_visible(), "V Scrollbar displays when ScrollContainer height is larger than contents height with SCROLL_MODE_SHOW_ALWAYS."); test_container->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_SHOW_NEVER); test_container->set_vertical_scroll_mode(ScrollContainer::SCROLL_MODE_SHOW_NEVER); test_container->set_size(Size2(50, 50)); SceneTree::get_singleton()->process(0); CHECK_MESSAGE( !test_container->get_h_scroll_bar()->is_visible(), "H Scrollbar does not display when ScrollContainer width is smaller than contents width with SCROLL_MODE_SHOW_NEVER."); CHECK_MESSAGE( !test_container->get_v_scroll_bar()->is_visible(), "V Scrollbar does not display when ScrollContainer height is smaller than contents height with SCROLL_MODE_SHOW_NEVER."); test_container->set_h_scroll(0); test_container->set_v_scroll(0); int show_never_h_scroll_before = test_container->get_h_scroll(); int show_never_v_scroll_before = test_container->get_v_scroll(); test_container->ensure_control_visible(target_control); SceneTree::get_singleton()->process(0); CHECK_MESSAGE( test_container->get_h_scroll() > show_never_h_scroll_before, "SCROLL_MODE_SHOW_NEVER keeps horizontal scrollbar hidden while still allowing programmatic scrolling."); CHECK_MESSAGE( test_container->get_v_scroll() > show_never_v_scroll_before, "SCROLL_MODE_SHOW_NEVER keeps vertical scrollbar hidden while still allowing programmatic scrolling."); test_container->set_h_scroll(0); test_container->set_v_scroll(0); test_container->set_size(Size2(150, 150)); SceneTree::get_singleton()->process(0); CHECK_MESSAGE( !test_container->get_h_scroll_bar()->is_visible(), "H Scrollbar does not display when ScrollContainer width is larger than contents width with SCROLL_MODE_SHOW_NEVER."); CHECK_MESSAGE( !test_container->get_v_scroll_bar()->is_visible(), "V Scrollbar does not display when ScrollContainer height is larger than contents height with SCROLL_MODE_SHOW_NEVER."); test_container->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_RESERVE); test_container->set_vertical_scroll_mode(ScrollContainer::SCROLL_MODE_RESERVE); test_container->set_size(Size2(50, 50)); SceneTree::get_singleton()->process(0); CHECK_MESSAGE( test_container->get_h_scroll_bar()->is_visible(), "H Scrollbar displays when ScrollContainer width is smaller than contents width with SCROLL_MODE_RESERVE."); CHECK_MESSAGE( test_container->get_v_scroll_bar()->is_visible(), "V Scrollbar displays when ScrollContainer height is smaller than contents height with SCROLL_MODE_RESERVE."); test_container->set_size(Size2(150, 150)); SceneTree::get_singleton()->process(0); CHECK_MESSAGE( !test_container->get_h_scroll_bar()->is_visible(), "H Scrollbar does not display when ScrollContainer width is larger than contents width with SCROLL_MODE_RESERVE."); CHECK_MESSAGE( !test_container->get_v_scroll_bar()->is_visible(), "V Scrollbar does not display when ScrollContainer height is larger than contents height with SCROLL_MODE_RESERVE."); memdelete(test_control); memdelete(test_container); } TEST_CASE("[SceneTree][ScrollContainer] Scroll values and property setters") { ScrollContainer *test_container = memnew(ScrollContainer); Window *root = SceneTree::get_singleton()->get_root(); root->add_child(test_container); Control *test_control = memnew(Control); test_control->set_custom_minimum_size(Size2(300, 300)); test_container->add_child(test_control); test_container->set_size(Size2(100, 100)); SceneTree::get_singleton()->process(0); test_container->set_h_scroll(25); test_container->set_v_scroll(35); CHECK_MESSAGE( test_container->get_h_scroll() == 25, "Horizontal scroll value is updated by set_h_scroll()."); CHECK_MESSAGE( test_container->get_v_scroll() == 35, "Vertical scroll value is updated by set_v_scroll()."); test_container->set_horizontal_custom_step(7.0f); test_container->set_vertical_custom_step(9.0f); CHECK_MESSAGE( Math::is_equal_approx(test_container->get_horizontal_custom_step(), 7.0f), "Horizontal custom step is updated correctly."); CHECK_MESSAGE( Math::is_equal_approx(test_container->get_vertical_custom_step(), 9.0f), "Vertical custom step is updated correctly."); test_container->set_deadzone(13); CHECK_MESSAGE( test_container->get_deadzone() == 13, "Deadzone value is updated correctly."); test_container->set_scroll_horizontal_by_default(true); CHECK_MESSAGE( test_container->is_scroll_horizontal_by_default(), "scroll_horizontal_by_default is enabled correctly."); test_container->set_follow_focus(true); CHECK_MESSAGE( test_container->is_following_focus(), "follow_focus is enabled correctly."); test_container->set_scroll_hint_mode(ScrollContainer::SCROLL_HINT_MODE_ALL); CHECK_MESSAGE( test_container->get_scroll_hint_mode() == ScrollContainer::SCROLL_HINT_MODE_ALL, "Scroll hint mode is updated correctly."); test_container->set_tile_scroll_hint(true); CHECK_MESSAGE( test_container->is_scroll_hint_tiled(), "Tiled scroll hint mode is enabled correctly."); test_container->set_draw_focus_border(true); CHECK_MESSAGE( test_container->get_draw_focus_border(), "draw_focus_border is enabled correctly."); memdelete(test_control); memdelete(test_container); } TEST_CASE("[SceneTree][ScrollContainer] ensure_control_visible and follow_focus") { const Size2 container_size = Size2(100, 100); ScrollContainer *test_container = memnew(ScrollContainer); test_container->set_size(container_size); Window *root = SceneTree::get_singleton()->get_root(); root->add_child(test_container); Control *content_control = memnew(Control); content_control->set_custom_minimum_size(Size2(1000, 1000)); test_container->add_child(content_control); Control *target_control = memnew(Control); target_control->set_focus_mode(Control::FOCUS_ALL); content_control->add_child(target_control); REQUIRE_EQ(test_container->get_h_scroll(), 0); REQUIRE_EQ(test_container->get_v_scroll(), 0); SceneTree::get_singleton()->process(0); const Point2 initial_scroll = Point2(450, 450); test_container->set_h_scroll(initial_scroll.x); test_container->set_v_scroll(initial_scroll.y); SceneTree::get_singleton()->process(0); REQUIRE_EQ(test_container->get_h_scroll(), initial_scroll.x); REQUIRE_EQ(test_container->get_v_scroll(), initial_scroll.y); REQUIRE_UNARY(test_container->get_h_scroll_bar()->is_visible_in_tree()); REQUIRE_UNARY(test_container->get_v_scroll_bar()->is_visible_in_tree()); REQUIRE_UNARY_FALSE(test_container->is_following_focus()); REQUIRE_UNARY_FALSE(target_control->has_focus()); SUBCASE("No scrolling when focused by default") { target_control->grab_focus(); REQUIRE_UNARY(target_control->has_focus()); CHECK_EQ(test_container->get_h_scroll(), initial_scroll.x); CHECK_EQ(test_container->get_v_scroll(), initial_scroll.y); } SUBCASE("Scrolling when ensure control visible by default") { test_container->ensure_control_visible(target_control); CHECK_EQ(test_container->get_h_scroll(), 0); CHECK_EQ(test_container->get_v_scroll(), 0); } test_container->set_follow_focus(true); REQUIRE_UNARY(test_container->is_following_focus()); const char *size_cases[2] = { "The target control size is 50% of the container size", "The target control size is 150% of the container size", }; const char *location_cases[9] = { "Target control is in the upper left corner", "Target control is directly above", "Target control is in the upper right corner", "Target control is on the left", "Target control is in the center", "Target control is on the right", "Target control is in the lower left corner", "Target control is directly below", "Target control is in the lower right corner", }; const Size2 scroll_bar_space = Size2(test_container->get_v_scroll_bar()->get_size().x, test_container->get_h_scroll_bar()->get_size().y); for (int size_case_idx = 0; size_case_idx < 2; size_case_idx++) { SUBCASE(size_cases[size_case_idx]) { Size2 control_size = container_size / 2 + size_case_idx * container_size; target_control->set_size(control_size); REQUIRE_EQ(target_control->get_size(), control_size); int size_flag = 2 * size_case_idx - 1; // Map 0~1 to -1~1. for (int location_case_idx = 0; location_case_idx < 9; location_case_idx++) { Point2i location_flag = Point2i(location_case_idx % 3 - 1, location_case_idx / 3 - 1); // Ensure that the numeric type and the get_*_scroll value are of the same type. Point2i control_position; Point2i expected_scroll; for (int axis = 0; axis < 2; axis++) { real_t size_offset = (size_flag == -1) ? control_size[axis] / 2 : -control_size[axis] / 4; // The effect of size on offset. control_position[axis] = initial_scroll[axis] + size_offset + location_flag[axis] * container_size[axis] * 3; // The situation is reversed when the size is large versus when it is small. const int com_flag = size_flag * location_flag[axis]; if (com_flag == 1) { expected_scroll[axis] = control_position[axis]; } else if (com_flag == 0) { expected_scroll[axis] = initial_scroll[axis]; } else if (com_flag == -1) { expected_scroll[axis] = control_position[axis] + control_size[axis] - container_size[axis] + scroll_bar_space[axis]; } } SUBCASE(location_cases[location_case_idx]) { target_control->set_position(control_position); REQUIRE_EQ(target_control->get_position(), control_position); SUBCASE("Scrolling when grab focus") { REQUIRE_EQ(test_container->get_h_scroll(), initial_scroll.x); REQUIRE_EQ(test_container->get_v_scroll(), initial_scroll.y); REQUIRE_UNARY_FALSE(target_control->has_focus()); target_control->grab_focus(); REQUIRE_UNARY(target_control->has_focus()); CHECK_EQ(test_container->get_h_scroll(), expected_scroll.x); CHECK_EQ(test_container->get_v_scroll(), expected_scroll.y); } SUBCASE("Scrolling when ensure control visible") { REQUIRE_EQ(test_container->get_h_scroll(), initial_scroll.x); REQUIRE_EQ(test_container->get_v_scroll(), initial_scroll.y); test_container->ensure_control_visible(target_control); CHECK_EQ(test_container->get_h_scroll(), expected_scroll.x); CHECK_EQ(test_container->get_v_scroll(), expected_scroll.y); } } } } } memdelete(target_control); memdelete(content_control); memdelete(test_container); } TEST_CASE("[SceneTree][ScrollContainer] Configuration warnings") { ScrollContainer *test_container = memnew(ScrollContainer); Window *root = SceneTree::get_singleton()->get_root(); root->add_child(test_container); CHECK_MESSAGE( test_container->get_configuration_warnings().size() == 1, "ScrollContainer reports one configuration warning when it has no content child."); Control *child_control_1 = memnew(Control); Control *child_control_2 = memnew(Control); test_container->add_child(child_control_1); CHECK_MESSAGE( test_container->get_configuration_warnings().is_empty(), "ScrollContainer reports no configuration warning when it has exactly one content child."); test_container->add_child(child_control_2); CHECK_MESSAGE( test_container->get_configuration_warnings().size() == 1, "ScrollContainer reports one configuration warning when it has more than one content child."); memdelete(child_control_2); memdelete(child_control_1); memdelete(test_container); } } // namespace TestScrollContainer