tdf#143209 vcl: track partial scroll deltas

this makes scrolling in calc smoother and makes left/up scrolling
the same speed as right/down scrolling, which was previously not the
case.

prior to this change, Window::HandleScrollCommand only checked each
event for being a large enough scroll to advance one unit. this
happened in lcl_HandleScrollHelper by way of o3tl::saturating_cast,
which meant that nonzero upward/leftward scrolls were always worth at
least one unit, while downward/rightward scrolls needed to be larger
than 1 to count.

now, we accumulate partial scroll offsets in WindowImpl and perform a
saturating cast on the absolute value, so behavior is symmetric and
sensitive to accumulated small scroll values. this feels smoother
and is more correct.

Change-Id: Id3692d14edd45ed26f2952e3418e4d82806e1fc4
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/142948
Tested-by: Jenkins
Tested-by: Caolán McNamara <caolanm@redhat.com>
Reviewed-by: Caolán McNamara <caolanm@redhat.com>
diff --git a/vcl/inc/window.h b/vcl/inc/window.h
index 639bee8..6f10445 100644
--- a/vcl/inc/window.h
+++ b/vcl/inc/window.h
@@ -256,6 +256,8 @@ public:
    vcl::Cursor*        mpCursor;
    PointerStyle        maPointer;
    Fraction            maZoom;
    double              mfPartialScrollX;
    double              mfPartialScrollY;
    OUString            maText;
    std::optional<vcl::Font>
                        mpControlFont;
diff --git a/vcl/source/window/window.cxx b/vcl/source/window/window.cxx
index 4705fe5..5cfc50d 100644
--- a/vcl/source/window/window.cxx
+++ b/vcl/source/window/window.cxx
@@ -590,6 +590,8 @@ WindowImpl::WindowImpl( vcl::Window& rWindow, WindowType nType )
{
    mxOutDev = VclPtr<vcl::WindowOutputDevice>::Create(rWindow);
    maZoom                              = Fraction( 1, 1 );
    mfPartialScrollX                    = 0.0;
    mfPartialScrollY                    = 0.0;
    maWinRegion                         = vcl::Region(true);
    maWinClipRegion                     = vcl::Region(true);
    mpWinData                           = nullptr;                      // Extra Window Data, that we don't need for all windows
diff --git a/vcl/source/window/window2.cxx b/vcl/source/window/window2.cxx
index b6c4ee2..978dc0b 100644
--- a/vcl/source/window/window2.cxx
+++ b/vcl/source/window/window2.cxx
@@ -598,12 +598,14 @@ tools::Long Window::GetDrawPixel( OutputDevice const * pDev, tools::Long nPixels
    return nP;
}

static void lcl_HandleScrollHelper( Scrollable* pScrl, double nN, bool isMultiplyByLineSize )
// returns how much was actually scrolled (so that abs(retval) <= abs(nN))
static double lcl_HandleScrollHelper( Scrollable* pScrl, double nN, bool isMultiplyByLineSize )
{
    if (!pScrl || !nN || pScrl->Inactive())
        return;
        return 0.0;

    tools::Long nNewPos = pScrl->GetThumbPos();
    double scrolled = nN;

    if ( nN == double(-LONG_MAX) )
        nNewPos += pScrl->GetPageSize();
@@ -616,13 +618,22 @@ static void lcl_HandleScrollHelper( Scrollable* pScrl, double nN, bool isMultipl
            nN*=pScrl->GetLineSize();
        }

        const double fVal = nNewPos - nN;
        // compute how many quantized units to scroll
        tools::Long magnitude = o3tl::saturating_cast<tools::Long>(abs(nN));
        tools::Long change = copysign(magnitude, nN);

        nNewPos = o3tl::saturating_cast<tools::Long>(fVal);
        nNewPos = nNewPos - change;

        scrolled = double(change);
        // convert back to chunked/continuous
        if(isMultiplyByLineSize){
            scrolled /= pScrl->GetLineSize();
        }
    }

    pScrl->DoScroll( nNewPos );

    return scrolled;
}

bool Window::HandleScrollCommand( const CommandEvent& rCmd,
@@ -668,6 +679,9 @@ bool Window::HandleScrollCommand( const CommandEvent& rCmd,
                    {
                        double nScrollLines = pData->GetScrollLines();
                        double nLines;
                        double* partialScroll = pData->IsHorz()
                            ? &mpWindowImpl->mfPartialScrollX
                            : &mpWindowImpl->mfPartialScrollY;
                        if ( nScrollLines == COMMAND_WHEEL_PAGESCROLL )
                        {
                            if ( pData->GetDelta() < 0 )
@@ -676,13 +690,12 @@ bool Window::HandleScrollCommand( const CommandEvent& rCmd,
                                nLines = double(LONG_MAX);
                        }
                        else
                            nLines = pData->GetNotchDelta() * nScrollLines;
                            nLines = *partialScroll + pData->GetNotchDelta() * nScrollLines;
                        if ( nLines )
                        {
                            ImplHandleScroll( nullptr,
                                          0L,
                                          pData->IsHorz() ? pHScrl : pVScrl,
                                          nLines );
                            Scrollable* pScrl = pData->IsHorz() ? pHScrl : pVScrl;
                            double scrolled = lcl_HandleScrollHelper( pScrl, nLines, true );
                            *partialScroll = nLines - scrolled;
                            bRet = true;
                        }
                    }
@@ -806,12 +819,6 @@ bool Window::HandleScrollCommand( const CommandEvent& rCmd,
    return bRet;
}

// Note that when called for CommandEventId::Wheel above, despite its name,
// pVScrl isn't necessarily the vertical scroll bar. Depending on
// whether the scroll is horizontal or vertical, it is either the
// horizontal or vertical scroll bar. nY is correspondingly either
// the horizontal or vertical scroll amount.

void Window::ImplHandleScroll( Scrollable* pHScrl, double nX,
                               Scrollable* pVScrl, double nY )
{