Menu

#749 horizontal scroll with shift key + mouse wheel

Committed
closed
2
2022-10-12
2010-11-25
Chooz'
No

I'm making an intensive use of Florian Balmer's Scintilla based Notepad2 editor and I miss a feature which is available in many design-like [free] software (like Freemind, Gimp, Inkscape, ...) :
the ability to horizontal scroll with the combination of the Shift key and the mouse wheel (when 'Word wrap' is not enabled).

Related

Bugs: #2357
Feature Requests: #1451

Discussion

  • Neil Hodgson

    Neil Hodgson - 2010-11-25

    This is not normal behaviour for text editors so will not be expected by users.

     
  • Neil Hodgson

    Neil Hodgson - 2010-11-25
    • assigned_to: nobody --> nyamatongwe
    • priority: 5 --> 2
    • milestone: --> Won't_Implement
     
  • Zufu Liu

    Zufu Liu - 2022-09-23
    • labels: Scintilla --> Scintilla, win32, scrolling
    • Description has changed:

    Diff:

    --- old
    +++ new
    @@ -1,3 +1,2 @@
    -
     I'm making an intensive use of Florian Balmer's Scintilla based Notepad2 editor and I miss a feature which is available in many design-like \[free\] software \(like Freemind, Gimp, Inkscape, ...\) :
     the ability to horizontal scroll with the combination of the Shift key and the mouse wheel \(when 'Word wrap' is not enabled\).
    
    • Group: Won't_Implement --> Initial
     
  • Zufu Liu

    Zufu Liu - 2022-09-23

    As discussed in [feature-requests:#1451], first patch to encapsulate wheelDelta (may need better method names or merge the last two methods into one).

    class MouseWheelDelta {
        int wheelDelta = 0;
    public:
        bool Accumulate(WPARAM wParam) noexcept {
            wheelDelta -= GET_WHEEL_DELTA_WPARAM(wParam);
            return std::abs(wheelDelta) >= WHEEL_DELTA;
        }
        int Actions() const noexcept {
            return wheelDelta / WHEEL_DELTA;
        }
        void Dispose() noexcept {
            if (wheelDelta >= 0) {
                wheelDelta = wheelDelta % WHEEL_DELTA;
            } else {
                wheelDelta = -(-wheelDelta % WHEEL_DELTA);
            }
        }
    };
    
     

    Related

    Feature Requests: #1451

  • Neil Hodgson

    Neil Hodgson - 2022-09-24

    The modulus code comes from [94b1dc] and doesn't appear to be needed as C % is really remainder so Dispose can be:

    wheelDelta = wheelDelta % WHEEL_DELTA;
    

    Which is simple enough to move back to inline.

    The change was motivated by an email that pointed to the then current C++ standard where "If both operands are nonnegative then the remainder is nonnegative; if not, the sign of the remainder is implementation-defined." but that was fixed in 2008 with
    https://open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3027.html (search for "expr.mul").

     

    Related

    Commit: [94b1dc]

    • Zufu Liu

      Zufu Liu - 2022-09-24

      Updated Dispose().

       
  • Neil Hodgson

    Neil Hodgson - 2022-09-24

    After thinking about this some more, I think the right thing to do is to remove support for high resolution mouse wheels: they just don't exist and its been over 20 years. If such things are developed, we can easily restore the code that supported them. The advantage of removing this feature is that it simplifies the code and makes it OK to handle WM_MOUSEHWHEEL and MK_SHIFT + WM_MOUSEWHEEL uniformly.

    The wheelDelta instance variable move to a local inside MouseMessage:

    const int wheelDelta = -GET_WHEEL_DELTA_WPARAM(wParam);
    

    The modulus code is removed.

     
  • Zufu Liu

    Zufu Liu - 2022-09-24

    Yes, this would simplify the code, per the document for WM_MOUSEWHEEL and WM_MOUSEHWHEEL:

    wParam

    The high-order word indicates the distance the wheel is rotated, expressed in multiples or divisions of WHEEL_DELTA, which is 120. A positive value indicates that the wheel was rotated forward, away from the user; a negative value indicates that the wheel was rotated backward, toward the user.

    so current code with WHEEL_DELTA as the threshold to take action accumulated nothing.

    Edit: I'm wrong on divisions of WHEEL_DELTA.

     

    Last edit: Zufu Liu 2022-09-24
  • Zufu Liu

    Zufu Liu - 2022-09-24

    Full changes with wheelDelta moved local, indentation for original code not changed to make the diff smaller.

     
  • Zufu Liu

    Zufu Liu - 2022-09-24

    Add back std::abs(wheelDelta) >= WHEEL_DELTA.

     
  • Zufu Liu

    Zufu Liu - 2022-09-25

    Improve accuracy for horizontal widthToScroll.

     
    • Neil Hodgson

      Neil Hodgson - 2022-09-26

      That allows horizontally scrolling past the maximum. It should probably stop at the pageWidth calculated in ScintillaWin::ModifyScrollBars. The best place for this check is an issue: placing it in Editor::HorizontalScrollTo may stop overscroll-and-bounce-back which is a feature on some platforms.

       
      • Zufu Liu

        Zufu Liu - 2022-09-26

        Something like following?

        const int charsToScroll = charsPerScroll * wheelDelta.Actions();
        wheelDelta.Dispose();
        int widthToScroll = static_cast<int>(std::lround(charsToScroll * vs.aveCharWidth));
        const int pageWidth = static_cast<int>(GetTextRectangle().Width());
        widthToScroll = std::clamp(widthToScroll, -xOffset, pageWidth - xOffset);
        HorizontalScrollTo(xOffset + widthToScroll);
        
         
        • Neil Hodgson

          Neil Hodgson - 2022-09-26

          Sorry, got it wrong: its horizEndPreferred that is the maximum horizontal scrollbar value.

           
  • Zufu Liu

    Zufu Liu - 2022-09-26

    Changed into

    // see ModifyScrollBars()
    const int pageWidth = static_cast<int>(GetTextRectangle().Width());
    const int horizEndPreferred = std::max(scrollWidth, pageWidth - 1);
    widthToScroll = std::clamp(widthToScroll, -xOffset, horizEndPreferred - xOffset);
    

    case Message::LineScroll in Editor::WndProc() may needs small changes:

    case Message::LineScroll:
        ScrollTo(topLine + lParam);
        //HorizontalScrollTo(xOffset + static_cast<int>(wParam) * static_cast<int>(vs.spaceWidth));
        HorizontalScrollTo(xOffset + static_cast<int>(static_cast<int>(wParam) * vs.spaceWidth));
        // or
        HorizontalScrollTo(xOffset + static_cast<int>(static_cast<int>(wParam) * vs.aveCharWidth));
        return 1;
    
     
    • Zufu Liu

      Zufu Liu - 2022-09-26

      Just tested, limiting widthToScroll seems not required, no behavior difference with/without the clamp.

       
      • Neil Hodgson

        Neil Hodgson - 2022-09-27

        Windows scroll bars don't allow SCROLLINFO::nPos to go to SCROLLINFO::nMax but take the size of the page or thumb as SCROLLINFO::nMax - SCROLLINFO::nPage. With this rough code, the horizontal wheel scroll goes to the same position as moving the thumb.

        if (wheelDelta.Accumulate(wParam)) {
            const int charsToScroll = charsPerScroll * wheelDelta.Actions();
            wheelDelta.Dispose();
            const int widthToScroll = static_cast<int>(std::lround(charsToScroll * vs.aveCharWidth));
            const PRectangle rcText = GetTextRectangle();
            const int pageWidth = static_cast<int>(rcText.Width());
            const int horizEndPreferred = std::max({ scrollWidth, pageWidth - 1, 0 });
            HorizontalScrollTo(std::clamp(xOffset + widthToScroll, 0, horizEndPreferred-pageWidth+1));
        }
        

        To test, enable short adaptive scroll width in SciTE with.

        horizontal.scroll.width=100
        horizontal.scroll.width.tracking=1
        
         
        • Zufu Liu

          Zufu Liu - 2022-09-27

          OK, see the difference.
          Maybe it's better to have a method that returns max horizontal scroll position.

           
          • Neil Hodgson

            Neil Hodgson - 2022-09-27

            There is similar code needed for ModifyScrollBars which wants both the page and document width so proving a common method that returns both:

            struct HorizontalScrollRange {
                int pageWidth;
                int documentWidth;
            };
            
            HorizontalScrollRange ScintillaWin::GetHorizontalScrollRange() const {
                const PRectangle rcText = GetTextRectangle();
                int pageWidth = static_cast<int>(rcText.Width());
                const int horizEndPreferred = std::max({ scrollWidth, pageWidth - 1, 0 });
                if (!horizontalScrollBarVisible || Wrapping())
                    pageWidth = horizEndPreferred + 1;
                return { pageWidth, horizEndPreferred };
            }
            
            //...
            
                if (wheelDelta.Accumulate(wParam)) {
                    const int charsToScroll = charsPerScroll * wheelDelta.Actions();
                    wheelDelta.Dispose();
                    const int widthToScroll = static_cast<int>(std::lround(charsToScroll * vs.aveCharWidth));
                    const HorizontalScrollRange range = GetHorizontalScrollRange();
                    HorizontalScrollTo(std::clamp(xOffset + widthToScroll, 0, range.documentWidth - range.pageWidth + 1));
                }
            
             
            👍
            1
  • Neil Hodgson

    Neil Hodgson - 2022-09-29
    • Group: Initial --> Committed
     
  • Neil Hodgson

    Neil Hodgson - 2022-10-12
    • status: open --> closed
     

Log in to post a comment.