Browse Source

Add smooth scrolling option to Element::ScrollIntoView

Michael Ragazzon 2 years ago
parent
commit
7868be1a3b
3 changed files with 40 additions and 13 deletions
  1. 3 2
      Include/RmlUi/Core/Element.h
  2. 10 11
      Source/Core/Element.cpp
  3. 27 0
      Tests/Source/UnitTests/Element.cpp

+ 3 - 2
Include/RmlUi/Core/Element.h

@@ -81,11 +81,12 @@ enum class ScrollAlignment {
 	Defines behavior of Element::ScrollIntoView.
  */
 struct ScrollIntoViewOptions {
-	ScrollIntoViewOptions(ScrollAlignment vertical = ScrollAlignment::Start, ScrollAlignment horizontal = ScrollAlignment::Nearest) :
-		vertical(vertical), horizontal(horizontal)
+	ScrollIntoViewOptions(ScrollAlignment vertical = ScrollAlignment::Start, ScrollAlignment horizontal = ScrollAlignment::Nearest,
+		ScrollBehavior behavior = ScrollBehavior::Auto) : vertical(vertical), horizontal(horizontal), behavior(behavior)
 	{}
 	ScrollAlignment vertical;
 	ScrollAlignment horizontal;
+	ScrollBehavior behavior;
 };
 
 /**

+ 10 - 11
Source/Core/Element.cpp

@@ -1280,9 +1280,9 @@ bool Element::DispatchEvent(EventId id, const Dictionary& parameters)
 void Element::ScrollIntoView(const ScrollIntoViewOptions options)
 {
 	const Vector2f size = main_box.GetSize(Box::BORDER);
+	ScrollBehavior scroll_behavior = options.behavior;
 
-	Element* scroll_parent = parent;
-	while (scroll_parent != nullptr)
+	for (Element* scroll_parent = parent; scroll_parent; scroll_parent = scroll_parent->GetParentNode())
 	{
 		using Style::Overflow;
 		const ComputedValues& computed = scroll_parent->GetComputedValues();
@@ -1302,17 +1302,16 @@ void Element::ScrollIntoView(const ScrollIntoViewOptions options)
 			const Vector2f delta_scroll_offset_start = parent_client_offset - relative_offset;
 			const Vector2f delta_scroll_offset_end = delta_scroll_offset_start + size - parent_client_size;
 
-			Vector2f new_scroll_offset = old_scroll_offset;
-			new_scroll_offset.x += GetScrollOffsetDelta(options.horizontal, delta_scroll_offset_start.x, delta_scroll_offset_end.x);
-			new_scroll_offset.y += GetScrollOffsetDelta(options.vertical, delta_scroll_offset_start.y, delta_scroll_offset_end.y);
+			Vector2f scroll_delta = {
+				scrollable_box_x ? GetScrollOffsetDelta(options.horizontal, delta_scroll_offset_start.x, delta_scroll_offset_end.x) : 0.f,
+				scrollable_box_y ? GetScrollOffsetDelta(options.vertical, delta_scroll_offset_start.y, delta_scroll_offset_end.y) : 0.f,
+			};
 
-			if (scrollable_box_x)
-				scroll_parent->SetScrollLeft(new_scroll_offset.x);
-			if (scrollable_box_y)
-				scroll_parent->SetScrollTop(new_scroll_offset.y);
-		}
+			scroll_parent->ScrollTo(old_scroll_offset + scroll_delta, scroll_behavior);
 
-		scroll_parent = scroll_parent->GetParentNode();
+			// Currently, only a single scrollable parent can be smooth scrolled at a time, so any other parents must be instant scrolled.
+			scroll_behavior = ScrollBehavior::Instant;
+		}
 	}
 }
 

+ 27 - 0
Tests/Source/UnitTests/Element.cpp

@@ -27,6 +27,7 @@
  */
 
 #include "../Common/Mocks.h"
+#include "../Common/TestsInterface.h"
 #include "../Common/TestsShell.h"
 #include "../Common/TypesToString.h"
 #include <RmlUi/Core/Context.h>
@@ -392,6 +393,32 @@ TEST_CASE("Element.ScrollIntoView")
 			CHECK(cells[2][2]->GetAbsoluteOffset(Rml::Box::Area::BORDER) == Vector2f(0, 0));
 			CHECK(cells[3][3]->GetAbsoluteOffset(Rml::Box::Area::BORDER) == Vector2f(50, 50));
 		}
+
+		SUBCASE("Smoothscroll")
+		{
+			TestsSystemInterface* system_interface = TestsShell::GetTestsSystemInterface();
+			system_interface->SetTime(0);
+			cells[3][3]->ScrollIntoView({ScrollAlignment::Nearest, ScrollAlignment::Nearest, ScrollBehavior::Smooth});
+
+			constexpr double dt = 1.0 / 15.0;
+			system_interface->SetTime(dt);
+			Run(context);
+
+			// We don't define the exact offset at this time step, but it should be somewhere between the start and end offsets.
+			Vector2f offset = cells[3][3]->GetAbsoluteOffset(Rml::Box::Area::BORDER);
+			CHECK(offset.x > 50.f);
+			CHECK(offset.y > 50.f);
+			CHECK(offset.x < 75.f);
+			CHECK(offset.y < 75.f);
+
+			// After one second it should be at the destination offset.
+			for (double t = 2.0 * dt; t < 1.0; t += dt)
+			{
+				system_interface->SetTime(t);
+				Run(context);
+			}
+			CHECK(cells[3][3]->GetAbsoluteOffset(Rml::Box::Area::BORDER) == Vector2f(50, 50));
+		}
 	}
 
 	document->Close();