Commit 1b4014e2 authored by Alexander Mikhaylenko's avatar Alexander Mikhaylenko
Browse files

Make the scrolling patch adaptive

Only change the deceleration for touchscreens.
parent 8fdd93dc
Pipeline #63267 failed with stages
in 155 minutes and 39 seconds
From: Alexander Mikhaylenko <alexander.mikhaylenko@puri.sm>
Date: Fri, 8 Jan 2021 15:07:23 +0500
Subject: Drop the scroll deceleration friction for touchscreens
Make it more comfortable to scroll.
---
.../nicosia/ScrollingTreeFrameScrollingNodeNicosia.cpp | 4 ++--
.../ScrollingTreeOverflowScrollingNodeNicosia.cpp | 4 ++--
Source/WebCore/platform/PlatformWheelEvent.h | 3 +++
Source/WebCore/platform/ScrollAnimationKinetic.cpp | 18 +++++++++++-------
Source/WebCore/platform/ScrollAnimationKinetic.h | 6 ++++--
.../WebCore/platform/generic/ScrollAnimatorGeneric.cpp | 4 ++--
Source/WebKit/Shared/WebEvent.h | 4 +++-
Source/WebKit/Shared/WebEventConversion.cpp | 1 +
Source/WebKit/Shared/WebWheelEvent.cpp | 6 +++++-
Source/WebKit/Shared/gtk/NativeWebWheelEventGtk.cpp | 4 ++--
Source/WebKit/Shared/gtk/WebEventFactory.cpp | 7 ++++++-
Source/WebKit/UIProcess/WebPageProxy.cpp | 2 +-
12 files changed, 42 insertions(+), 21 deletions(-)
diff --git a/Source/WebCore/page/scrolling/nicosia/ScrollingTreeFrameScrollingNodeNicosia.cpp b/Source/WebCore/page/scrolling/nicosia/ScrollingTreeFrameScrollingNodeNicosia.cpp
index 562cd86..3ff362c 100644
--- a/Source/WebCore/page/scrolling/nicosia/ScrollingTreeFrameScrollingNodeNicosia.cpp
+++ b/Source/WebCore/page/scrolling/nicosia/ScrollingTreeFrameScrollingNodeNicosia.cpp
@@ -168,12 +168,12 @@ WheelEventHandlingResult ScrollingTreeFrameScrollingNodeNicosia::handleWheelEven
m_kineticAnimation->appendToScrollHistory(wheelEvent);
m_kineticAnimation->stop();
if (wheelEvent.isEndOfNonMomentumScroll()) {
- m_kineticAnimation->start(currentScrollPosition(), m_kineticAnimation->computeVelocity(), canHaveHorizontalScrollbar(), canHaveVerticalScrollbar());
+ m_kineticAnimation->start(wheelEvent, currentScrollPosition(), m_kineticAnimation->computeVelocity(), canHaveHorizontalScrollbar(), canHaveVerticalScrollbar());
m_kineticAnimation->clearScrollHistory();
return WheelEventHandlingResult::handled();
}
if (wheelEvent.isTransitioningToMomentumScroll()) {
- m_kineticAnimation->start(currentScrollPosition(), wheelEvent.swipeVelocity(), canHaveHorizontalScrollbar(), canHaveVerticalScrollbar());
+ m_kineticAnimation->start(wheelEvent, currentScrollPosition(), wheelEvent.swipeVelocity(), canHaveHorizontalScrollbar(), canHaveVerticalScrollbar());
m_kineticAnimation->clearScrollHistory();
return WheelEventHandlingResult::handled();
}
diff --git a/Source/WebCore/page/scrolling/nicosia/ScrollingTreeOverflowScrollingNodeNicosia.cpp b/Source/WebCore/page/scrolling/nicosia/ScrollingTreeOverflowScrollingNodeNicosia.cpp
index 64d8a58..713f399 100644
--- a/Source/WebCore/page/scrolling/nicosia/ScrollingTreeOverflowScrollingNodeNicosia.cpp
+++ b/Source/WebCore/page/scrolling/nicosia/ScrollingTreeOverflowScrollingNodeNicosia.cpp
@@ -158,12 +158,12 @@ WheelEventHandlingResult ScrollingTreeOverflowScrollingNodeNicosia::handleWheelE
m_kineticAnimation->appendToScrollHistory(wheelEvent);
m_kineticAnimation->stop();
if (wheelEvent.isEndOfNonMomentumScroll()) {
- m_kineticAnimation->start(currentScrollPosition(), m_kineticAnimation->computeVelocity(), canHaveHorizontalScrollbar(), canHaveVerticalScrollbar());
+ m_kineticAnimation->start(wheelEvent, currentScrollPosition(), m_kineticAnimation->computeVelocity(), canHaveHorizontalScrollbar(), canHaveVerticalScrollbar());
m_kineticAnimation->clearScrollHistory();
return WheelEventHandlingResult::handled();
}
if (wheelEvent.isTransitioningToMomentumScroll()) {
- m_kineticAnimation->start(currentScrollPosition(), wheelEvent.swipeVelocity(), canHaveHorizontalScrollbar(), canHaveVerticalScrollbar());
+ m_kineticAnimation->start(wheelEvent, currentScrollPosition(), wheelEvent.swipeVelocity(), canHaveHorizontalScrollbar(), canHaveVerticalScrollbar());
m_kineticAnimation->clearScrollHistory();
return WheelEventHandlingResult::handled();
}
diff --git a/Source/WebCore/platform/PlatformWheelEvent.h b/Source/WebCore/platform/PlatformWheelEvent.h
index c8bedbf..4b02ab0 100644
--- a/Source/WebCore/platform/PlatformWheelEvent.h
+++ b/Source/WebCore/platform/PlatformWheelEvent.h
@@ -173,6 +173,8 @@ public:
FloatPoint swipeVelocity() const;
#endif
+ bool isTouch() const { return m_isTouch; }
+
#if PLATFORM(WIN)
PlatformWheelEvent(HWND, WPARAM, LPARAM, bool isMouseHWheel);
#endif
@@ -202,6 +204,7 @@ protected:
float m_unacceleratedScrollingDeltaX { 0 };
float m_unacceleratedScrollingDeltaY { 0 };
#endif
+ bool m_isTouch { false };
};
#if ENABLE(ASYNC_SCROLLING)
diff --git a/Source/WebCore/platform/ScrollAnimationKinetic.cpp b/Source/WebCore/platform/ScrollAnimationKinetic.cpp
index 9a5a89e..9551a3f 100644
--- a/Source/WebCore/platform/ScrollAnimationKinetic.cpp
+++ b/Source/WebCore/platform/ScrollAnimationKinetic.cpp
@@ -65,7 +65,8 @@
* f2(x) = t exp(-mx / 2)
*/
-static const double decelFriction = 4;
+static const double decelFrictionTouchpad = 4;
+static const double decelFrictionTouch = 1;
static const double frameRate = 60;
static const Seconds tickTime = 1_s / frameRate;
static const Seconds minimumTimerInterval { 1_ms };
@@ -73,13 +74,14 @@ static const Seconds scrollCaptureThreshold { 150_ms };
namespace WebCore {
-ScrollAnimationKinetic::PerAxisData::PerAxisData(double lower, double upper, double initialPosition, double initialVelocity)
+ScrollAnimationKinetic::PerAxisData::PerAxisData(double lower, double upper, double initialPosition, double initialVelocity, double decelFriction)
: m_lower(lower)
, m_upper(upper)
, m_coef1(initialVelocity / decelFriction + initialPosition)
, m_coef2(-initialVelocity / decelFriction)
, m_position(clampTo(initialPosition, lower, upper))
, m_velocity(initialPosition < lower || initialPosition > upper ? 0 : initialVelocity)
+ , m_decelFriction(decelFriction)
{
}
@@ -89,9 +91,9 @@ bool ScrollAnimationKinetic::PerAxisData::animateScroll(Seconds timeDelta)
auto lastTime = m_elapsedTime;
m_elapsedTime += timeDelta;
- double exponentialPart = exp(-decelFriction * m_elapsedTime.value());
+ double exponentialPart = exp(-m_decelFriction * m_elapsedTime.value());
m_position = m_coef1 + m_coef2 * exponentialPart;
- m_velocity = -decelFriction * m_coef2 * exponentialPart;
+ m_velocity = -m_decelFriction * m_coef2 * exponentialPart;
if (m_position < m_lower) {
m_velocity = m_lower - m_position;
@@ -167,7 +169,7 @@ FloatPoint ScrollAnimationKinetic::computeVelocity()
return FloatPoint(accumDelta.x() * -1 / (last - first).value(), accumDelta.y() * -1 / (last - first).value());
}
-void ScrollAnimationKinetic::start(const FloatPoint& initialPosition, const FloatPoint& velocity, bool mayHScroll, bool mayVScroll)
+void ScrollAnimationKinetic::start(const PlatformWheelEvent& event, const FloatPoint& initialPosition, const FloatPoint& velocity, bool mayHScroll, bool mayVScroll)
{
stop();
@@ -180,12 +182,14 @@ void ScrollAnimationKinetic::start(const FloatPoint& initialPosition, const Floa
if (mayHScroll) {
m_horizontalData = PerAxisData(extents.minimumScrollPosition.x(),
extents.maximumScrollPosition.x(),
- initialPosition.x(), velocity.x());
+ initialPosition.x(), velocity.x(),
+ event.isTouch() ? decelFrictionTouch : decelFrictionTouchpad);
}
if (mayVScroll) {
m_verticalData = PerAxisData(extents.minimumScrollPosition.y(),
extents.maximumScrollPosition.y(),
- initialPosition.y(), velocity.y());
+ initialPosition.y(), velocity.y(),
+ event.isTouch() ? decelFrictionTouch : decelFrictionTouchpad);
}
m_startTime = MonotonicTime::now() - tickTime / 2.;
diff --git a/Source/WebCore/platform/ScrollAnimationKinetic.h b/Source/WebCore/platform/ScrollAnimationKinetic.h
index 6abe79e..52542e5 100644
--- a/Source/WebCore/platform/ScrollAnimationKinetic.h
+++ b/Source/WebCore/platform/ScrollAnimationKinetic.h
@@ -39,7 +39,7 @@ class ScrollAnimationKinetic final : public ScrollAnimation {
private:
class PerAxisData {
public:
- PerAxisData(double lower, double upper, double initialPosition, double initialVelocity);
+ PerAxisData(double lower, double upper, double initialPosition, double initialVelocity, double decelFriction);
double position() { return m_position; }
@@ -55,6 +55,8 @@ private:
Seconds m_elapsedTime;
double m_position { 0 };
double m_velocity { 0 };
+
+ double m_decelFriction;
};
public:
@@ -68,7 +70,7 @@ public:
void clearScrollHistory();
FloatPoint computeVelocity();
- void start(const FloatPoint& initialPosition, const FloatPoint& velocity, bool mayHScroll, bool mayVScroll);
+ void start(const PlatformWheelEvent& event, const FloatPoint& initialPosition, const FloatPoint& velocity, bool mayHScroll, bool mayVScroll);
void stop() override;
bool isActive() const override;
diff --git a/Source/WebCore/platform/generic/ScrollAnimatorGeneric.cpp b/Source/WebCore/platform/generic/ScrollAnimatorGeneric.cpp
index cb1955a..8777052 100644
--- a/Source/WebCore/platform/generic/ScrollAnimatorGeneric.cpp
+++ b/Source/WebCore/platform/generic/ScrollAnimatorGeneric.cpp
@@ -126,12 +126,12 @@ bool ScrollAnimatorGeneric::handleWheelEvent(const PlatformWheelEvent& event)
m_kineticAnimation->appendToScrollHistory(event);
if (event.isEndOfNonMomentumScroll()) {
- m_kineticAnimation->start(m_currentPosition, m_kineticAnimation->computeVelocity(), m_scrollableArea.horizontalScrollbar(), m_scrollableArea.verticalScrollbar());
+ m_kineticAnimation->start(event, m_currentPosition, m_kineticAnimation->computeVelocity(), m_scrollableArea.horizontalScrollbar(), m_scrollableArea.verticalScrollbar());
return true;
}
if (event.isTransitioningToMomentumScroll()) {
m_kineticAnimation->clearScrollHistory();
- m_kineticAnimation->start(m_currentPosition, event.swipeVelocity(), m_scrollableArea.horizontalScrollbar(), m_scrollableArea.verticalScrollbar());
+ m_kineticAnimation->start(event, m_currentPosition, event.swipeVelocity(), m_scrollableArea.horizontalScrollbar(), m_scrollableArea.verticalScrollbar());
return true;
}
#endif
diff --git a/Source/WebKit/Shared/WebEvent.h b/Source/WebKit/Shared/WebEvent.h
index 02f799a..aaf6cd6 100644
--- a/Source/WebKit/Shared/WebEvent.h
+++ b/Source/WebKit/Shared/WebEvent.h
@@ -207,7 +207,7 @@ public:
#if PLATFORM(COCOA)
WebWheelEvent(Type, const WebCore::IntPoint& position, const WebCore::IntPoint& globalPosition, const WebCore::FloatSize& delta, const WebCore::FloatSize& wheelTicks, Granularity, bool directionInvertedFromDevice, Phase, Phase momentumPhase, bool hasPreciseScrollingDeltas, uint32_t scrollCount, const WebCore::FloatSize& unacceleratedScrollingDelta, OptionSet<Modifier>, WallTime timestamp);
#elif PLATFORM(GTK) || USE(LIBWPE)
- WebWheelEvent(Type, const WebCore::IntPoint& position, const WebCore::IntPoint& globalPosition, const WebCore::FloatSize& delta, const WebCore::FloatSize& wheelTicks, Phase, Phase momentumPhase, Granularity, bool hasPreciseScrollingDeltas, OptionSet<Modifier>, WallTime timestamp);
+ WebWheelEvent(Type, const WebCore::IntPoint& position, const WebCore::IntPoint& globalPosition, const WebCore::FloatSize& delta, const WebCore::FloatSize& wheelTicks, Phase, Phase momentumPhase, Granularity, bool hasPreciseScrollingDeltas, OptionSet<Modifier>, WallTime timestamp, bool isTouch);
#endif
const WebCore::IntPoint position() const { return m_position; }
@@ -225,6 +225,7 @@ public:
uint32_t scrollCount() const { return m_scrollCount; }
const WebCore::FloatSize& unacceleratedScrollingDelta() const { return m_unacceleratedScrollingDelta; }
#endif
+ bool isTouch() const { return m_isTouch; }
void encode(IPC::Encoder&) const;
static WARN_UNUSED_RETURN bool decode(IPC::Decoder&, WebWheelEvent&);
@@ -247,6 +248,7 @@ private:
uint32_t m_scrollCount { 0 };
WebCore::FloatSize m_unacceleratedScrollingDelta;
#endif
+ bool m_isTouch { false };
};
// FIXME: Move this class to its own header file.
diff --git a/Source/WebKit/Shared/WebEventConversion.cpp b/Source/WebKit/Shared/WebEventConversion.cpp
index e410387..16e0f7e 100644
--- a/Source/WebKit/Shared/WebEventConversion.cpp
+++ b/Source/WebKit/Shared/WebEventConversion.cpp
@@ -169,6 +169,7 @@ public:
m_unacceleratedScrollingDeltaX = webEvent.unacceleratedScrollingDelta().width();
m_unacceleratedScrollingDeltaY = webEvent.unacceleratedScrollingDelta().height();
#endif
+ m_isTouch = webEvent.isTouch();
}
};
diff --git a/Source/WebKit/Shared/WebWheelEvent.cpp b/Source/WebKit/Shared/WebWheelEvent.cpp
index e283ec7..a438368 100644
--- a/Source/WebKit/Shared/WebWheelEvent.cpp
+++ b/Source/WebKit/Shared/WebWheelEvent.cpp
@@ -66,7 +66,7 @@ WebWheelEvent::WebWheelEvent(Type type, const IntPoint& position, const IntPoint
ASSERT(isWheelEventType(type));
}
#elif PLATFORM(GTK) || USE(LIBWPE)
-WebWheelEvent::WebWheelEvent(Type type, const IntPoint& position, const IntPoint& globalPosition, const FloatSize& delta, const FloatSize& wheelTicks, Phase phase, Phase momentumPhase, Granularity granularity, bool hasPreciseScrollingDeltas, OptionSet<Modifier> modifiers, WallTime timestamp)
+WebWheelEvent::WebWheelEvent(Type type, const IntPoint& position, const IntPoint& globalPosition, const FloatSize& delta, const FloatSize& wheelTicks, Phase phase, Phase momentumPhase, Granularity granularity, bool hasPreciseScrollingDeltas, OptionSet<Modifier> modifiers, WallTime timestamp, bool isTouch)
: WebEvent(type, modifiers, timestamp)
, m_position(position)
, m_globalPosition(globalPosition)
@@ -77,6 +77,7 @@ WebWheelEvent::WebWheelEvent(Type type, const IntPoint& position, const IntPoint
, m_phase(phase)
, m_momentumPhase(momentumPhase)
, m_hasPreciseScrollingDeltas(hasPreciseScrollingDeltas)
+ , m_isTouch(isTouch)
{
ASSERT(isWheelEventType(type));
}
@@ -101,6 +102,7 @@ void WebWheelEvent::encode(IPC::Encoder& encoder) const
encoder << m_scrollCount;
encoder << m_unacceleratedScrollingDelta;
#endif
+ encoder << m_isTouch;
}
bool WebWheelEvent::decode(IPC::Decoder& decoder, WebWheelEvent& t)
@@ -133,6 +135,8 @@ bool WebWheelEvent::decode(IPC::Decoder& decoder, WebWheelEvent& t)
if (!decoder.decode(t.m_unacceleratedScrollingDelta))
return false;
#endif
+ if (!decoder.decode(t.m_isTouch))
+ return false;
return true;
}
diff --git a/Source/WebKit/Shared/gtk/NativeWebWheelEventGtk.cpp b/Source/WebKit/Shared/gtk/NativeWebWheelEventGtk.cpp
index 488fbec..5dc363b 100644
--- a/Source/WebKit/Shared/gtk/NativeWebWheelEventGtk.cpp
+++ b/Source/WebKit/Shared/gtk/NativeWebWheelEventGtk.cpp
@@ -50,12 +50,12 @@ NativeWebWheelEvent::NativeWebWheelEvent(GdkEvent* event, const WebCore::IntPoin
}
NativeWebWheelEvent::NativeWebWheelEvent(const WebCore::IntPoint& position, const WebCore::IntPoint& globalPosition, const WebCore::FloatSize& delta, const WebCore::FloatSize& wheelTicks, WebWheelEvent::Phase phase, WebWheelEvent::Phase momentumPhase)
- : WebWheelEvent(WebEvent::Wheel, position, globalPosition, delta, wheelTicks, phase, momentumPhase, WebWheelEvent::ScrollByPixelWheelEvent, false, { }, WallTime::now())
+ : WebWheelEvent(WebEvent::Wheel, position, globalPosition, delta, wheelTicks, phase, momentumPhase, WebWheelEvent::ScrollByPixelWheelEvent, false, { }, WallTime::now(), false)
{
}
NativeWebWheelEvent::NativeWebWheelEvent(const NativeWebWheelEvent& event)
- : WebWheelEvent(event.type(), event.position(), event.globalPosition(), event.delta(), event.wheelTicks(), event.phase(), event.momentumPhase(), event.granularity(), event.hasPreciseScrollingDeltas(), event.modifiers(), event.timestamp())
+ : WebWheelEvent(event.type(), event.position(), event.globalPosition(), event.delta(), event.wheelTicks(), event.phase(), event.momentumPhase(), event.granularity(), event.hasPreciseScrollingDeltas(), event.modifiers(), event.timestamp(), event.isTouch())
, m_nativeEvent(event.nativeEvent() ? gdk_event_copy(event.nativeEvent()) : nullptr)
{
}
diff --git a/Source/WebKit/Shared/gtk/WebEventFactory.cpp b/Source/WebKit/Shared/gtk/WebEventFactory.cpp
index ca5ce48..c4b621a 100644
--- a/Source/WebKit/Shared/gtk/WebEventFactory.cpp
+++ b/Source/WebKit/Shared/gtk/WebEventFactory.cpp
@@ -280,6 +280,10 @@ WebWheelEvent WebEventFactory::createWebWheelEvent(const GdkEvent* event, const
float step = static_cast<float>(Scrollbar::pixelsPerLineStep());
FloatSize delta(wheelTicks.width() * step, wheelTicks.height() * step);
+ GdkDevice* device = gdk_event_get_source_device(event);
+ GdkInputSource source = gdk_device_get_source(device);
+ bool isTouch = source == GDK_SOURCE_TOUCHSCREEN;
+
return WebWheelEvent(WebEvent::Wheel,
position,
globalPosition,
@@ -290,7 +294,8 @@ WebWheelEvent WebEventFactory::createWebWheelEvent(const GdkEvent* event, const
WebWheelEvent::ScrollByPixelWheelEvent,
false,
modifiersForEvent(event),
- wallTimeForEvent(event));
+ wallTimeForEvent(event),
+ isTouch);
}
WebKeyboardEvent WebEventFactory::createWebKeyboardEvent(const GdkEvent* event, const String& text, bool handledByInputMethod, Optional<Vector<CompositionUnderline>>&& preeditUnderlines, Optional<EditingRange>&& preeditSelectionRange, Vector<String>&& commands)
diff --git a/Source/WebKit/UIProcess/WebPageProxy.cpp b/Source/WebKit/UIProcess/WebPageProxy.cpp
index 0939b66..79d97b2 100644
--- a/Source/WebKit/UIProcess/WebPageProxy.cpp
+++ b/Source/WebKit/UIProcess/WebPageProxy.cpp
@@ -2632,7 +2632,7 @@ static WebWheelEvent coalesce(const WebWheelEvent& a, const WebWheelEvent& b)
return WebWheelEvent(WebEvent::Wheel, b.position(), b.globalPosition(), mergedDelta, mergedWheelTicks, b.granularity(), b.directionInvertedFromDevice(), b.phase(), b.momentumPhase(), b.hasPreciseScrollingDeltas(), b.scrollCount(), mergedUnacceleratedScrollingDelta, b.modifiers(), b.timestamp());
#elif PLATFORM(GTK) || USE(LIBWPE)
- return WebWheelEvent(WebEvent::Wheel, b.position(), b.globalPosition(), mergedDelta, mergedWheelTicks, b.phase(), b.momentumPhase(), b.granularity(), b.hasPreciseScrollingDeltas(), b.modifiers(), b.timestamp());
+ return WebWheelEvent(WebEvent::Wheel, b.position(), b.globalPosition(), mergedDelta, mergedWheelTicks, b.phase(), b.momentumPhase(), b.granularity(), b.hasPreciseScrollingDeltas(), b.modifiers(), b.timestamp(), b.isTouch());
#else
return WebWheelEvent(WebEvent::Wheel, b.position(), b.globalPosition(), mergedDelta, mergedWheelTicks, b.granularity(), b.modifiers(), b.timestamp());
#endif
From: Adrien Plazas <kekun.plazas@laposte.net>
Date: Thu, 4 Jul 2019 09:45:26 +0200
Subject: ScrollAnimationKinetic: Drop the deceleration friction
This makes WebKitGTK more comfortable on touch screens as it avoids to
scroll too much.
---
Source/WebCore/platform/ScrollAnimationKinetic.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Source/WebCore/platform/ScrollAnimationKinetic.cpp b/Source/WebCore/platform/ScrollAnimationKinetic.cpp
index 9a5a89e..7275f12 100644
--- a/Source/WebCore/platform/ScrollAnimationKinetic.cpp
+++ b/Source/WebCore/platform/ScrollAnimationKinetic.cpp
@@ -65,7 +65,7 @@
* f2(x) = t exp(-mx / 2)
*/
-static const double decelFriction = 4;
+static const double decelFriction = 1;
static const double frameRate = 60;
static const Seconds tickTime = 1_s / frameRate;
static const Seconds minimumTimerInterval { 1_ms };
......@@ -14,7 +14,7 @@ This fixes a FTBFS in riscv64
2 files changed, 3 insertions(+)
diff --git a/Source/cmake/OptionsGTK.cmake b/Source/cmake/OptionsGTK.cmake
index 4e26d40..265976f 100644
index dc40653..8918130 100644
--- a/Source/cmake/OptionsGTK.cmake
+++ b/Source/cmake/OptionsGTK.cmake
@@ -33,6 +33,8 @@ if (USER_AGENT_BRANDING)
......
......@@ -6,4 +6,4 @@ prefer-pthread.patch
dont-detect-sse2.patch
reduce-memory-overheads.patch
force-single-process.patch
0009-ScrollAnimationKinetic-Drop-the-deceleration-frictio.patch
0009-Drop-the-scroll-deceleration-friction-for-touchscree.patch
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment