Browse Source

Scroll behavior improvements

- The actual scroll target is determined before trying to scroll, and will stay the same throughout the current scroll controller mode.
- Mousescroll event no longer performs the actual scroll, and no longer requires a default action. This responsibility has been moved to the scroll controller.
- Mousescroll event is now only submitted once before initiating smooth scroll, with the option to cancel the scroll by stopping its propagation.
- The `overscroll-behavior` property can now be used to stop scroll chaining.
- Cleanup of scroll controller.
Michael Ragazzon 2 năm trước cách đây
mục cha
commit
c8d746192c

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

@@ -583,6 +583,8 @@ public:
 	ElementDecoration* GetElementDecoration() const;
 	ElementDecoration* GetElementDecoration() const;
 	/// Returns the element's scrollbar functionality.
 	/// Returns the element's scrollbar functionality.
 	ElementScroll* GetElementScroll() const;
 	ElementScroll* GetElementScroll() const;
+	/// Returns the element's nearest scroll container that can be scrolled, if any.
+	Element* GetClosestScrollableContainer();
 	/// Returns the element's transform state.
 	/// Returns the element's transform state.
 	const TransformState* GetTransformState() const noexcept;
 	const TransformState* GetTransformState() const noexcept;
 	/// Returns the data model of this element.
 	/// Returns the data model of this element.

+ 45 - 15
Source/Core/Context.cpp

@@ -49,8 +49,9 @@
 
 
 namespace Rml {
 namespace Rml {
 
 
-static constexpr float DOUBLE_CLICK_TIME = 0.5f;     // [s]
-static constexpr float DOUBLE_CLICK_MAX_DIST = 3.f;  // [dp]
+static constexpr float DOUBLE_CLICK_TIME = 0.5f;    // [s]
+static constexpr float DOUBLE_CLICK_MAX_DIST = 3.f; // [dp]
+static constexpr float UNIT_SCROLL_LENGTH = 100.f;  // [dp]
 
 
 Context::Context(const String& name) : name(name), dimensions(0, 0), density_independent_pixel_ratio(1.0f), mouse_position(0, 0), clip_origin(-1, -1), clip_dimensions(-1, -1)
 Context::Context(const String& name) : name(name), dimensions(0, 0), density_independent_pixel_ratio(1.0f), mouse_position(0, 0), clip_origin(-1, -1), clip_dimensions(-1, -1)
 {
 {
@@ -727,21 +728,20 @@ bool Context::ProcessMouseButtonDown(int button_index, int key_modifier_state)
 			propagate = hover->DispatchEvent(EventId::Mousedown, parameters);
 			propagate = hover->DispatchEvent(EventId::Mousedown, parameters);
 	}
 	}
 
 
-	if (*scroll_controller == ScrollController::Mode::Autoscroll)
+	if (scroll_controller->GetMode() == ScrollController::Mode::Autoscroll)
 	{
 	{
 		scroll_controller->Reset();
 		scroll_controller->Reset();
 	}
 	}
 	else if (button_index == 2 && hover && propagate)
 	else if (button_index == 2 && hover && propagate)
 	{
 	{
-		if (*scroll_controller == ScrollController::Mode::Smoothscroll)
-			scroll_controller->Reset();
-
 		Dictionary scroll_parameters;
 		Dictionary scroll_parameters;
 		GenerateMouseEventParameters(scroll_parameters);
 		GenerateMouseEventParameters(scroll_parameters);
+		GenerateKeyModifierEventParameters(scroll_parameters, key_modifier_state);
+		scroll_parameters["autoscroll"] = true;
 
 
-		// Dispatch an event without any scrolling distance, just to see if anyone captures it. If so, we can initiate autoscroll here.
-		if (!hover->DispatchEvent(EventId::Mousescroll, scroll_parameters))
-			scroll_controller->ActivateAutoscroll(hover, mouse_position);
+		// Dispatch a mouse scroll event, this gives elements an opportunity to block autoscroll from being initialized.
+		if (hover->DispatchEvent(EventId::Mousescroll, scroll_parameters))
+			scroll_controller->ActivateAutoscroll(hover->GetClosestScrollableContainer(), mouse_position);
 	}
 	}
 
 
 	return !IsMouseInteracting();
 	return !IsMouseInteracting();
@@ -819,14 +819,43 @@ bool Context::ProcessMouseButtonUp(int button_index, int key_modifier_state)
 			hover->DispatchEvent(EventId::Mouseup, parameters);
 			hover->DispatchEvent(EventId::Mouseup, parameters);
 	}
 	}
 
 
-	scroll_controller->ProcessMouseButtonUp();
+	// If we have autoscrolled while holding the middle mouse button, release the autoscroll mode now.
+	if (scroll_controller->HasAutoscrollMoved())
+		scroll_controller->Reset();
 
 
 	return result;
 	return result;
 }
 }
 
 
-bool Context::ProcessMouseWheel(float wheel_delta, int /*key_modifier_state*/)
+bool Context::ProcessMouseWheel(float wheel_delta, int key_modifier_state)
 {
 {
-	return scroll_controller->ProcessMouseWheel(Vector2f{0.f, wheel_delta}, hover, density_independent_pixel_ratio);
+	if (scroll_controller->GetMode() == ScrollController::Mode::Autoscroll)
+	{
+		scroll_controller->Reset();
+		return false;
+	}
+	else if (!hover)
+	{
+		scroll_controller->Reset();
+		return true;
+	}
+
+	Dictionary scroll_parameters;
+	GenerateMouseEventParameters(scroll_parameters);
+	GenerateKeyModifierEventParameters(scroll_parameters, key_modifier_state);
+	scroll_parameters["wheel_delta"] = wheel_delta;
+
+	// Dispatch a mouse scroll event, this gives elements an opportunity to block scrolling from being performed.
+	if (!hover->DispatchEvent(EventId::Mousescroll, scroll_parameters))
+		return false;
+
+	if (scroll_controller->GetMode() != ScrollController::Mode::Smoothscroll)
+		scroll_controller->ActivateSmoothscroll(hover->GetClosestScrollableContainer());
+
+	const float unit_scroll_length = UNIT_SCROLL_LENGTH * density_independent_pixel_ratio;
+	const Vector2f scroll_length = {0.f, wheel_delta * unit_scroll_length};
+
+	scroll_controller->IncrementSmoothscrollTarget(scroll_length);
+	return false;
 }
 }
 
 
 bool Context::ProcessMouseLeave()
 bool Context::ProcessMouseLeave()
@@ -841,7 +870,7 @@ bool Context::ProcessMouseLeave()
 
 
 bool Context::IsMouseInteracting() const
 bool Context::IsMouseInteracting() const
 {
 {
-	return (hover && hover != root.get()) || (active && active != root.get()) || *scroll_controller == ScrollController::Mode::Autoscroll;
+	return (hover && hover != root.get()) || (active && active != root.get()) || scroll_controller->GetMode() == ScrollController::Mode::Autoscroll;
 }
 }
 
 
 // Gets the context's render interface.
 // Gets the context's render interface.
@@ -981,7 +1010,8 @@ void Context::OnElementDetach(Element* element)
 			document_focus_history.erase(it);
 			document_focus_history.erase(it);
 	}
 	}
 
 
-	scroll_controller->OnElementDetach(element);
+	if (scroll_controller->GetTarget() == element)
+		scroll_controller->Reset();
 }
 }
 
 
 // Internal callback for when a new element gains focus
 // Internal callback for when a new element gains focus
@@ -1104,7 +1134,7 @@ void Context::UpdateHoverChain(Vector2i old_mouse_position, int key_modifier_sta
 	{
 	{
 		String new_cursor_name;
 		String new_cursor_name;
 
 
-		if (*scroll_controller == ScrollController::Mode::Autoscroll)
+		if (scroll_controller->GetMode() == ScrollController::Mode::Autoscroll)
 			new_cursor_name = scroll_controller->GetAutoscrollCursor(mouse_position, density_independent_pixel_ratio);
 			new_cursor_name = scroll_controller->GetAutoscrollCursor(mouse_position, density_independent_pixel_ratio);
 		else if(drag)
 		else if(drag)
 			new_cursor_name = drag->GetComputedValues().cursor();
 			new_cursor_name = drag->GetComputedValues().cursor();

+ 22 - 26
Source/Core/Element.cpp

@@ -1969,6 +1969,28 @@ bool Element::IsLayoutDirty()
 	return false;
 	return false;
 }
 }
 
 
+Element* Element::GetClosestScrollableContainer()
+{
+	using namespace Style;
+
+	Overflow overflow_x = meta->computed_values.overflow_x();
+	Overflow overflow_y = meta->computed_values.overflow_y();
+	bool scrollable_x = (overflow_x == Overflow::Auto || overflow_x == Overflow::Scroll);
+	bool scrollable_y = (overflow_y == Overflow::Auto || overflow_y == Overflow::Scroll);
+
+	scrollable_x = (scrollable_x && GetScrollWidth() > GetClientWidth());
+	scrollable_y = (scrollable_y && GetScrollHeight() > GetClientHeight());
+
+	if (scrollable_x || scrollable_y)
+		return this;
+	else if (meta->computed_values.overscroll_behavior() == OverscrollBehavior::Contain)
+		return nullptr;
+	else if (parent)
+		return parent->GetClosestScrollableContainer();
+
+	return nullptr;
+}
+
 void Element::ProcessDefaultAction(Event& event)
 void Element::ProcessDefaultAction(Event& event)
 {
 {
 	if (event == EventId::Mousedown)
 	if (event == EventId::Mousedown)
@@ -1979,32 +2001,6 @@ void Element::ProcessDefaultAction(Event& event)
 			SetPseudoClass("active", true);
 			SetPseudoClass("active", true);
 	}
 	}
 
 
-	if (event == EventId::Mousescroll)
-	{
-		Style::Overflow overflow_x = meta->computed_values.overflow_x();
-		Style::Overflow overflow_y = meta->computed_values.overflow_y();
-		bool scrollable_x = (overflow_x == Style::Overflow::Auto || overflow_x == Style::Overflow::Scroll);
-		bool scrollable_y = (overflow_y == Style::Overflow::Auto || overflow_y == Style::Overflow::Scroll);
-
-		scrollable_x = (scrollable_x && GetScrollWidth() > GetClientWidth());
-		scrollable_y = (scrollable_y && GetScrollHeight() > GetClientHeight());
-
-		if (scrollable_x || scrollable_y)
-		{
-			// Stop the propagation to prevent scrolling in parent elements.
-			event.StopPropagation();
-
-			const Vector2f scroll_delta = {event.GetParameter("delta_x", 0.f), event.GetParameter("delta_y", 0.f)};
-
-			if (scrollable_x)
-				SetScrollLeft(GetScrollLeft() + scroll_delta.x);
-			if (scrollable_y)
-				SetScrollTop(GetScrollTop() + scroll_delta.y);
-		}
-
-		return;
-	}
-
 	if (event.GetPhase() == EventPhase::Target)
 	if (event.GetPhase() == EventPhase::Target)
 	{
 	{
 		switch (event.GetId())
 		switch (event.GetId())

+ 3 - 16
Source/Core/Elements/WidgetDropDown.cpp

@@ -65,12 +65,13 @@ WidgetDropDown::WidgetDropDown(ElementFormControl* element)
 	selection_element->SetProperty(PropertyId::Clip, Property(Style::Clip::Type::None));
 	selection_element->SetProperty(PropertyId::Clip, Property(Style::Clip::Type::None));
 	selection_element->SetProperty(PropertyId::OverflowY, Property(Style::Overflow::Auto));
 	selection_element->SetProperty(PropertyId::OverflowY, Property(Style::Overflow::Auto));
 
 
+	// Prevent scrolling in the parent document when the mouse is inside the selection box.
+	selection_element->SetProperty(PropertyId::OverscrollBehavior, Property(Style::OverscrollBehavior::Contain));
+
 	parent_element->AddEventListener(EventId::Click, this, true);
 	parent_element->AddEventListener(EventId::Click, this, true);
 	parent_element->AddEventListener(EventId::Blur, this);
 	parent_element->AddEventListener(EventId::Blur, this);
 	parent_element->AddEventListener(EventId::Focus, this);
 	parent_element->AddEventListener(EventId::Focus, this);
 	parent_element->AddEventListener(EventId::Keydown, this, true);
 	parent_element->AddEventListener(EventId::Keydown, this, true);
-
-	selection_element->AddEventListener(EventId::Mousescroll, this);
 }
 }
 
 
 WidgetDropDown::~WidgetDropDown()
 WidgetDropDown::~WidgetDropDown()
@@ -85,8 +86,6 @@ WidgetDropDown::~WidgetDropDown()
 	parent_element->RemoveEventListener(EventId::Blur, this);
 	parent_element->RemoveEventListener(EventId::Blur, this);
 	parent_element->RemoveEventListener(EventId::Focus, this);
 	parent_element->RemoveEventListener(EventId::Focus, this);
 	parent_element->RemoveEventListener(EventId::Keydown, this, true);
 	parent_element->RemoveEventListener(EventId::Keydown, this, true);
-	
-	selection_element->RemoveEventListener(EventId::Mousescroll, this);
 
 
 	DetachScrollEvent();
 	DetachScrollEvent();
 }
 }
@@ -571,18 +570,6 @@ void WidgetDropDown::ProcessEvent(Event& event)
 		}
 		}
 	}
 	}
 	break;
 	break;
-	case EventId::Mousescroll:
-	{
-		if (event.GetCurrentElement() == selection_element)
-		{
-			// Prevent scrolling in the parent window when mouse is inside the selection box.
-			event.StopPropagation();
-			// Stopping propagation also stops all default scrolling actions. However, we still want to be able
-			// to scroll in the selection box, so call the default action manually.
-			selection_element->ProcessDefaultAction(event);
-		}
-	}
-	break;
 	case EventId::Scroll:
 	case EventId::Scroll:
 	{
 	{
 		if (box_visible)
 		if (box_visible)

+ 1 - 1
Source/Core/EventSpecification.cpp

@@ -48,7 +48,7 @@ void Initialize()
 		//      id                 type      interruptible  bubbles     default_action
 		//      id                 type      interruptible  bubbles     default_action
 		{EventId::Invalid       , "invalid"       , false , false , DefaultActionPhase::None},
 		{EventId::Invalid       , "invalid"       , false , false , DefaultActionPhase::None},
 		{EventId::Mousedown     , "mousedown"     , true  , true  , DefaultActionPhase::TargetAndBubble},
 		{EventId::Mousedown     , "mousedown"     , true  , true  , DefaultActionPhase::TargetAndBubble},
-		{EventId::Mousescroll   , "mousescroll"   , true  , true  , DefaultActionPhase::TargetAndBubble},
+		{EventId::Mousescroll   , "mousescroll"   , true  , true  , DefaultActionPhase::None},
 		{EventId::Mouseover     , "mouseover"     , true  , true  , DefaultActionPhase::Target},
 		{EventId::Mouseover     , "mouseover"     , true  , true  , DefaultActionPhase::Target},
 		{EventId::Mouseout      , "mouseout"      , true  , true  , DefaultActionPhase::Target},
 		{EventId::Mouseout      , "mouseout"      , true  , true  , DefaultActionPhase::Target},
 		{EventId::Focus         , "focus"         , false , false , DefaultActionPhase::Target},
 		{EventId::Focus         , "focus"         , false , false , DefaultActionPhase::Target},

+ 70 - 76
Source/Core/ScrollController.cpp

@@ -27,7 +27,6 @@
  */
  */
 
 
 #include "ScrollController.h"
 #include "ScrollController.h"
-#include "../../Include/RmlUi/Core/ComputedValues.h"
 #include "../../Include/RmlUi/Core/Core.h"
 #include "../../Include/RmlUi/Core/Core.h"
 #include "../../Include/RmlUi/Core/Element.h"
 #include "../../Include/RmlUi/Core/Element.h"
 #include "../../Include/RmlUi/Core/SystemInterface.h"
 #include "../../Include/RmlUi/Core/SystemInterface.h"
@@ -41,6 +40,10 @@ static constexpr float SMOOTHSCROLL_WINDOW_SIZE = 50.f;        // The window whe
 static constexpr float SMOOTHSCROLL_VELOCITY_CONSTANT = 800.f; // The constant velocity, any smoothing is applied on top of this. [dp/s]
 static constexpr float SMOOTHSCROLL_VELOCITY_CONSTANT = 800.f; // The constant velocity, any smoothing is applied on top of this. [dp/s]
 static constexpr float SMOOTHSCROLL_VELOCITY_SQUARE_FACTOR = 0.05f;
 static constexpr float SMOOTHSCROLL_VELOCITY_SQUARE_FACTOR = 0.05f;
 
 
+// Clamp the delta time to some reasonable FPS range, to avoid large steps in case of stuttering or freezing.
+static constexpr float DELTA_TIME_CLAMP_LOW = 1.f / 500.f; // [s]
+static constexpr float DELTA_TIME_CLAMP_HIGH = 1.f / 15.f; // [s]
+
 // Determines the autoscroll velocity based on the distance from the scroll-start mouse position. [px/s]
 // Determines the autoscroll velocity based on the distance from the scroll-start mouse position. [px/s]
 static Vector2f CalculateAutoscrollVelocity(Vector2f target_delta, float dp_ratio)
 static Vector2f CalculateAutoscrollVelocity(Vector2f target_delta, float dp_ratio)
 {
 {
@@ -82,14 +85,33 @@ static Vector2f CalculateSmoothscrollVelocity(Vector2f target_delta, Vector2f sc
 	return dp_ratio * target_delta_signum * (smooth_window * velocity_constant + velocity_square);
 	return dp_ratio * target_delta_signum * (smooth_window * velocity_constant + velocity_square);
 }
 }
 
 
-float ScrollController::UpdateTime()
+void ScrollController::ActivateAutoscroll(Element* in_target, Vector2i start_position)
 {
 {
-	const double previous_tick = previous_update_time;
-	previous_update_time = GetSystemInterface()->GetElapsedTime();
+	Reset();
+	if (!in_target)
+		return;
+	target = in_target;
+	mode = Mode::Autoscroll;
+	autoscroll_start_position = start_position;
+	UpdateTime();
+}
 
 
-	const float dt = float(previous_update_time - previous_tick);
-	// Clamp the delta time to some reasonable FPS range, to avoid large steps in case of stuttering or freezing.
-	return Math::Clamp(dt, 1.f / 500.f, 1.f / 15.f);
+void ScrollController::ActivateSmoothscroll(Element* in_target)
+{
+	Reset();
+	if (!in_target)
+		return;
+	target = in_target;
+	mode = Mode::Smoothscroll;
+	UpdateTime();
+}
+
+void ScrollController::Update(Vector2i mouse_position, float dp_ratio)
+{
+	if (mode == Mode::Autoscroll)
+		UpdateAutoscroll(mouse_position, dp_ratio);
+	else if (mode == Mode::Smoothscroll)
+		UpdateSmoothscroll(dp_ratio);
 }
 }
 
 
 void ScrollController::UpdateAutoscroll(Vector2i mouse_position, float dp_ratio)
 void ScrollController::UpdateAutoscroll(Vector2i mouse_position, float dp_ratio)
@@ -109,21 +131,12 @@ void ScrollController::UpdateAutoscroll(Vector2i mouse_position, float dp_ratio)
 	autoscroll_accumulated_length.y = Math::DecomposeFractionalIntegral(autoscroll_accumulated_length.y, &scroll_length_integral.y);
 	autoscroll_accumulated_length.y = Math::DecomposeFractionalIntegral(autoscroll_accumulated_length.y, &scroll_length_integral.y);
 
 
 	if (scroll_velocity != Vector2f(0.f))
 	if (scroll_velocity != Vector2f(0.f))
-		autoscroll_holding = true;
-
-	if (scroll_length_integral != Vector2f(0.f))
-	{
-		Dictionary scroll_parameters;
-		// GenerateMouseEventParameters(scroll_parameters); // TODO
-		scroll_parameters["delta_x"] = scroll_length_integral.x;
-		scroll_parameters["delta_y"] = scroll_length_integral.y;
+		autoscroll_moved = true;
 
 
-		if (target->DispatchEvent(EventId::Mousescroll, scroll_parameters))
-			Reset(); // Scroll event was not handled by any element, meaning that we don't have anything to scroll.
-	}
+	PerformScrollOnTarget(scroll_length_integral);
 }
 }
 
 
-void ScrollController::UpdateSmoothscroll(Vector2i /*mouse_position*/, float dp_ratio)
+void ScrollController::UpdateSmoothscroll(float dp_ratio)
 {
 {
 	RMLUI_ASSERT(mode == Mode::Smoothscroll && target);
 	RMLUI_ASSERT(mode == Mode::Smoothscroll && target);
 
 
@@ -145,77 +158,43 @@ void ScrollController::UpdateSmoothscroll(Vector2i /*mouse_position*/, float dp_
 			scroll_distance[i] = 0.f;
 			scroll_distance[i] = 0.f;
 	}
 	}
 
 
-	if (scroll_distance != Vector2f(0.f))
-	{
-		smoothscroll_scrolled_distance += scroll_distance;
-
-		Dictionary scroll_parameters;
-		// GenerateMouseEventParameters(scroll_parameters); // TODO
-		scroll_parameters["delta_x"] = scroll_distance.x;
-		scroll_parameters["delta_y"] = scroll_distance.y;
-
-		if (target->DispatchEvent(EventId::Mousescroll, scroll_parameters))
-			Reset(); // Scroll event was not handled by any element, meaning that we don't have anything to scroll.
-	}
+	smoothscroll_scrolled_distance += scroll_distance;
+	PerformScrollOnTarget(scroll_distance);
 
 
 	if (scroll_distance == target_delta)
 	if (scroll_distance == target_delta)
 		Reset();
 		Reset();
 }
 }
 
 
-void ScrollController::ActivateAutoscroll(Element* in_target, Vector2i start_position)
+void ScrollController::PerformScrollOnTarget(Vector2f delta_distance)
 {
 {
-	Reset();
-	mode = Mode::Autoscroll;
-	target = in_target;
-	autoscroll_start_position = start_position;
-	// TODO: Determine the element to scroll first. Only target that directly, don't do scroll event.
-	UpdateTime();
+	RMLUI_ASSERT(target);
+	if (delta_distance.x != 0.f)
+		target->SetScrollLeft(target->GetScrollLeft() + delta_distance.x);
+	if (delta_distance.y != 0.f)
+		target->SetScrollTop(target->GetScrollTop() + delta_distance.y);
 }
 }
 
 
-bool ScrollController::ProcessMouseWheel(Vector2f wheel_delta, Element* hover, float dp_ratio)
+void ScrollController::IncrementSmoothscrollTarget(Vector2f delta_distance)
 {
 {
-	if (mode == Mode::Autoscroll)
-	{
-		Reset();
-		return false;
-	}
-	else if (!hover)
-	{
-		Reset();
-	}
-	else
-	{
-		RMLUI_ASSERT(hover);
-
-		if (mode != Mode::Smoothscroll)
-			ActivateSmoothscroll(hover);
-
-		auto OppositeDirection = [](float a, float b) { return (a < 0.f && b > 0.f) || (a > 0.f && b < 0.f); };
+	auto OppositeDirection = [](float a, float b) { return (a < 0.f && b > 0.f) || (a > 0.f && b < 0.f); };
+	Vector2f delta = smoothscroll_target_distance - smoothscroll_scrolled_distance;
 
 
-		// The scroll length for a single unit of wheel delta is defined as three default sized lines.
-		const float default_scroll_length = 100.f * dp_ratio;
-
-		Vector2f delta = smoothscroll_target_distance - smoothscroll_scrolled_distance;
-
-		if (OppositeDirection(wheel_delta.x, delta.x))
-		{
-			smoothscroll_target_distance.x = 0.f;
-			smoothscroll_scrolled_distance.x = 0.f;
-		}
-		if (OppositeDirection(wheel_delta.y, delta.y))
+	// Reset movement state if we start scrolling in the opposite direction.
+	for (int i = 0; i < 2; i++)
+	{
+		if (OppositeDirection(delta_distance[i], delta[i]))
 		{
 		{
-			smoothscroll_target_distance.y = 0.f;
-			smoothscroll_scrolled_distance.y = 0.f;
+			smoothscroll_target_distance[i] = 0.f;
+			smoothscroll_scrolled_distance[i] = 0.f;
 		}
 		}
-
-		smoothscroll_target_distance += wheel_delta * default_scroll_length;
-
-		// TODO test if we can scroll here first!
-
-		return false;
 	}
 	}
 
 
-	return true;
+	smoothscroll_target_distance += delta_distance;
+}
+
+void ScrollController::Reset()
+{
+	*this = ScrollController{};
 }
 }
 
 
 String ScrollController::GetAutoscrollCursor(Vector2i mouse_position, float dp_ratio) const
 String ScrollController::GetAutoscrollCursor(Vector2i mouse_position, float dp_ratio) const
@@ -242,4 +221,19 @@ String ScrollController::GetAutoscrollCursor(Vector2i mouse_position, float dp_r
 
 
 	return result;
 	return result;
 }
 }
+
+bool ScrollController::HasAutoscrollMoved() const
+{
+	return mode == Mode::Autoscroll && autoscroll_moved;
+}
+
+float ScrollController::UpdateTime()
+{
+	const double previous_tick = previous_update_time;
+	previous_update_time = GetSystemInterface()->GetElapsedTime();
+
+	const float dt = float(previous_update_time - previous_tick);
+	return Math::Clamp(dt, DELTA_TIME_CLAMP_LOW, DELTA_TIME_CLAMP_HIGH);
+}
+
 } // namespace Rml
 } // namespace Rml

+ 25 - 38
Source/Core/ScrollController.h

@@ -34,60 +34,47 @@
 
 
 namespace Rml {
 namespace Rml {
 
 
+/**
+    Implements scrolling behavior that occurs over time.
+
+    Scrolling modes are activated externally, targeting a given element. The actual scrolling takes place during update calls.
+ */
+
 class ScrollController {
 class ScrollController {
 public:
 public:
-	enum class Mode { None, Smoothscroll, Autoscroll };
-
-	void ActivateAutoscroll(Element* in_target, Vector2i start_position);
+	enum class Mode {
+		None,
+		Smoothscroll, // Smooth scrolling to target distance.
+		Autoscroll,   // Scrolling with middle mouse button.
+	};
 
 
-	void Update(Vector2i mouse_position, float dp_ratio)
-	{
-		if (mode == Mode::Autoscroll && target)
-			UpdateAutoscroll(mouse_position, dp_ratio);
-		else if (mode == Mode::Smoothscroll && target)
-			UpdateSmoothscroll(mouse_position, dp_ratio);
-	}
+	void ActivateAutoscroll(Element* target, Vector2i start_position);
 
 
-	bool ProcessMouseWheel(Vector2f wheel_delta, Element* hover, float dp_ratio);
+	void ActivateSmoothscroll(Element* target);
 
 
-	void ProcessMouseButtonUp()
-	{
-		if (mode == Mode::Autoscroll && autoscroll_holding)
-			Reset();
-	}
+	void Update(Vector2i mouse_position, float dp_ratio);
 
 
-	void OnElementDetach(Element* element)
-	{
-		if (element == target)
-			Reset();
-	}
+	void IncrementSmoothscrollTarget(Vector2f delta_distance);
 
 
-	// Reset autoscroll state, disabling the mode.
-	void Reset() { *this = ScrollController{}; }
+	void Reset();
 
 
-	// Returns the autoscroll cursor based on the scroll direction.
+	// Returns the autoscroll cursor based on the active scroll velocity.
 	String GetAutoscrollCursor(Vector2i mouse_position, float dp_ratio) const;
 	String GetAutoscrollCursor(Vector2i mouse_position, float dp_ratio) const;
+	// Returns true if autoscroll mode is active and the cursor has been moved outside the idle scroll area.
+	bool HasAutoscrollMoved() const;
 
 
-	bool operator==(Mode in_mode) const { return mode == in_mode; }
-
-	explicit operator bool() const { return mode != Mode::None; }
+	Mode GetMode() const { return mode; }
+	Element* GetTarget() const { return target; }
 
 
 private:
 private:
-	void ActivateSmoothscroll(Element* in_target)
-	{
-		Reset();
-		mode = Mode::Smoothscroll;
-		target = in_target;
-		UpdateTime();
-	}
-
 	// Updates time to now, and returns the delta time since the previous time update.
 	// Updates time to now, and returns the delta time since the previous time update.
 	float UpdateTime();
 	float UpdateTime();
 
 
-	// Update autoscroll state (scrolling with middle mouse button), and submit scroll events as necessary.
 	void UpdateAutoscroll(Vector2i mouse_position, float dp_ratio);
 	void UpdateAutoscroll(Vector2i mouse_position, float dp_ratio);
 
 
-	void UpdateSmoothscroll(Vector2i mouse_position, float dp_ratio);
+	void UpdateSmoothscroll(float dp_ratio);
+
+	void PerformScrollOnTarget(Vector2f delta_distance);
 
 
 	Mode mode = Mode::None;
 	Mode mode = Mode::None;
 
 
@@ -96,7 +83,7 @@ private:
 
 
 	Vector2i autoscroll_start_position;
 	Vector2i autoscroll_start_position;
 	Vector2f autoscroll_accumulated_length;
 	Vector2f autoscroll_accumulated_length;
-	bool autoscroll_holding = false;
+	bool autoscroll_moved = false;
 
 
 	Vector2f smoothscroll_target_distance;
 	Vector2f smoothscroll_target_distance;
 	Vector2f smoothscroll_scrolled_distance;
 	Vector2f smoothscroll_scrolled_distance;