Scroll behaviour


#1

hi,

I'm reposting this question since it got deleted in the SQL blackhole yesterday.

When using knobs within a viewport (or any widget that doesn't forward mouseWheelMove() to its parent), as soon the component gets behing the mouse cursor, the widget gets the events instead of the the viewport. I understand the rationale behind it, but it can be quite frustrating and is also an easy way to quickly make unwanted alterations to a document without even noticing.

On iOS on the other hand,  as soon a scroll gesture starts, the initial component continues to receive the scroll events until the end of the gesture. which makes it less error-prone.

The question is, Can we and how could we simulate this behaviour with juce? 

 


#2

Good question. I think it'd need to be done at a very low level in the mouse handling logic.. Seems like a good idea on the surface, but is it something that would be universally good, or would there be situations where it'd mess things up?


#3

Not sure if that should be the default behaviour, but at least that would be good to have the choice to implement it that way in some cases.

What I think is missing, is some kind of lowlevel startMouseWheelMove() and endMouseWheelMove().

If we had that information the knobs could ignore mouseWheelMove if they weren't behind the mouseCursor when the mouseWheel gesture started.

I don't know the underlying OS APIs much. But at least on some devices like trackpads, using 2 finger drag for scrolling, we should be able to get the info. For regular three buttons mouses that's more difficult, we'd need to recreate the information using some timer-based clustering of mouse scroll events.

 

 


#4

Keeping track of the component where a mousewheel events sequence have began would be very handy, for sure. Especially with scroll momentum on macos ( see http://www.juce.com/forum/topic/scroll-inertia )


#5

Here's a simple hack I've done to improve the situation with basic scroll gesture detection. I'm not asking to make it the default behaviour, but in our case it's helping a lot.

Ideally having an equivalent of setInterceptsMouseClicks() like setInterceptsMouseWheel() would be perfect.

For now I'm emulating it using setEnabled(false) on the viewed component.

BTW if there are nested viewports within the component, they will catch the events even if their state is disabled because the implementation of Viewport::useMouseWheelMoveIfNeeded() doesn't check isEnabled()

 

class CustomViewport : public juce::Viewport, public juce::Timer
{
  bool mScrolling;
  bool mGotEventsWhileScrolling;
public:
  CustomViewport()
  : mScrolling(false)
  , mGotEventsWhileScrolling(false)
  {
  }
  
  void mouseWheelMove (const juce::MouseEvent &event, const juce::MouseWheelDetails &wheel) override
  {
    if(useMouseWheelMoveIfNeeded(event, wheel))
    {
      if(!mScrolling) // it's the first mouseScroll gesture
      {
        mScrolling = true;
        if(getViewedComponent())
        {
          // we prevent mouse events from reaching our child components
          // ideally we would like to be able to call getViewedComponent()->setInterceptsMouseWheelEvents(false, false)
          getViewedComponent()->setEnabled(false);
        }
        startTimer(300); // the gesture will end in 300ms unless there are new events in the meantime
      }
      else
      {
        mGotEventsWhileScrolling = true; // stop pending update, we'll start a new one
      }
      
      juce::Viewport::mouseWheelMove(event, wheel);
    }
  }
  
  void timerCallback() override
  {
    if(mScrolling)
    {
      if(mGotEventsWhileScrolling)
      {
        mGotEventsWhileScrolling = false;
        startTimer(300);
        return;
      }
      
      mScrolling = false;
      if(getViewedComponent()) // stop the gesture
      {
        getViewedComponent()->setEnabled(true);
      }
    }
  }
};

#6

Just bumping the thread so you get a chance to see it once you finish the Meetup European tour.

That was great meeting you today


#7

bump


#8

sorry to have to insist, but I didn't get any answer...

 

BTW I've browsed trough the way events reach components and it's not obvious what the best implementation would be.

Right now, the top down search for the target component is the same for all kind of events (respecting hitTest() and setInterceptMouseClicks()) and if the target component isn't finally interrested by mouseWheel events it forward them to its parent recursively in a bottom-up fashion.

I can see why having to find a target component for different kind of events isn't necesarilly the best implementation. (but that would be the best place to insert a test to know if components are interrested by wheel events because it's a top-down search).

Adding that kind of test in the default implementation of Component::mouseWheelMove() in a bottom-up manner seems fragile and inefficient at best since it would require to ask parent components recursively to know if their children are allowed to receive mouseWheel events

 

thoughts?


#9

Jules, no answer?


#10

I started work on this, but it's proving a bit trickier than expected. I was trying to do it by turning off mouse events for the viewport, but that didn't work too well and will probably need some hackery in the low-level mouse handling code. I agree it needs to be done, just need to figure out the best approach.


#11

I'm glad to hear that, thanks!