setInterceptsMouseClicks seemingly ignored in Viewport

Hey all,

I’m hitting a problem working on the ScrollView component over at react-juce:

Specifically, I’m trying to disable mouseDown/mouseUp handlers on all child components when we begin scrolling. As a quick test I’ve attempted the folllowing:

namespace detail
{
        void recursiveClickIntercept(juce::Component *component, bool intercepts)
        {
            auto &children = component->getChildren();

            for (auto *c : children)
                recursiveClickIntercept(c, intercepts);

            component->setInterceptsMouseClicks(intercepts, intercepts);
        }
}

ScrollView::ScrollView()
    {
        // Use a default scrollEvent rate of 30Hz.
        // This is prop configurable
        startTimerHz(30);

        viewport.onAreaChanged([this](const juce::Rectangle<int>& area)
        {
            if (isMouseButtonDown(true) && !isScrolling)
            {
                detail::recursiveClickIntercept(&viewport, false);
                isScrolling = true;
            }

            lastScrollEvent.event = detail::makeScrollEventObject(area.getY(), area.getX());
            lastScrollEvent.dirty = true;
        });

        addAndMakeVisible(viewport);
        viewport.setScrollBarsShown(true, true);

        exportNativeMethods();
    }

However, I’m still seeing child elements of the Viewport getting their mouseUp calls fired:

   void View::mouseUp (const juce::MouseEvent& e)
    {
        dispatchViewEvent("onMouseUp", detail::makeViewEventObject(e, *this));
    }

Under debugger with a breakpoint in View::mouseUp I can see the following:

ignoresMouseClicksFlag = {bool} true
allowChildMouseClicksFlag = {bool} false

Confused as to why I’m still getting mouseUp callbacks fired. Basically this results in our onClick callbacks in React components firing when we stop scrolling the list/viewport which is pretty nasty.

I perhaps need to debug further but am I missing something super obvious in the API here?

Usually it is enough to switch off

setInterceptsMouseClicks (bool component, bool childComponents);

Since the hitTest that is actually affected by the setInterceptMouseClicks setting stops already the traversal into the children.

If you still get mouse events, I would look out for addMouseListener in the code. That will eventually duplicate events, that’s why I always advise against using it with a few exceptions.

1 Like

Thanks Daniel, much appreciated. I’ll dig a little further, something very odd going on here.

Just setting setInterceptsMouseClicks(false, false) at top level
also had 0 effect so I figured I’d try a brute force but no difference.

I had a quick look and it seems like all View send their mouse events to the ReactApplicationRoot which feeds it to the EcmaScriptEngine, so it seems not to obey the JUCE flow of mouse events at all.

But ultimately only @ncthom would know I guess…

1 Like

Yeah no for sure. I added some code a while back which triggers onClick callbacks in response to the dispatched “mouseUp” events in the react-juce event layer.

Basically I want to block dispatching the “mouseUp” event into React at all and this felt like the right route. Maybe it’s not though …

Could potentially handle this in Backend.ts in react-juce and avoid raising “onClick” callbacks based on parent scroll view state. In not convinced that’s the right call just yet!

Decisions, decisions …

I’m almost certainly missing something obvious …

(Will update thread if I find a decent solution)

Cheers Daniel!

The View event dispatching that runs from all Views up into ReactApplicationRoot is orthogonal to a juce::Component::mouseUp or other mouse events. That logic only exists to make sure that when a mouse event does occur on a View, we can dispatch the event into the javascript engine. But before that ever happens we have the business of juce::Component mouse event handling and setInterceptsMouseClicks.

@jucemarler what does the View heirarchy look like inside of the ScrollView? And do you know which View inside that heirarchy is responsible for firing the mouseUp in question?

Yo!

Yeah indeed. I can even see I’m disabling mouse clicks on that View instance under the debugger when disabling mouse clicks on all children recursively.

The items in the ScrollView instance look a little like this:

const ItemImage = ({itemImage}) => (
    <View {...BrowserStyles.browserItemImageContainer}>
        <Image
            {...BrowserStyles.browserItemImage}
            source={itemImage}
        />
    </View>
)

const ItemInfo = ({name, library, category}) => (
    <View {...BrowserStyles.browserItemInfo}>
        <Text {...BrowserStyles.browserItemNameText}>
            {name}
        </Text>
        <Text {...BrowserStyles.browserItemInfoText}>
            {library} &bull; {category}
        </Text>
    </View>
)

export const Item = ({item, ...props}) => {
    const onClick = (_e: SyntheticMouseEvent) => {
        // Do some stuff
    }

    return (
        <View {...props} {...BrowserStyles.browserItem} onClick={onClick}>
            <ItemImage itemImage={getImage(item.photoPath)} />
            <ItemInfo
                name={item.name}
                library={item.library}
                class={item.class}
            />
        </View>
    )
}

The onClick prop is used for some selection state but right now it’s firing when I release mouse/finger to end a scroll.

In this case the mouseUp could vary based on cursor but most often it’ll be the ItemInfo component

EDIT: Might dig into mouse handle code a little further, I wonder if this is something to do with disabling mouse clicks after a mouseDown has already been received? Possible we’ve already set the peer in MouseInputSource and as a result we don’t call hitTest again before firing mouseUp, just a theory, Will test. .

So I have a fix which isn’t entirely awful:

  if (eventType == "onMouseDrag") {
    if (__lastMouseDownViewId != null) {
      if (event.target !== undefined && __containsParentOfType(event.target, "ScrollView"))
        __lastMouseDownViewId = null;
    }
  }

Though I’m not 100% convinced it’s the best solution. This basically stops us bubbling the onClick event up the component tree in the following block:

    if (eventType === "onMouseUp") {
      __bubbleEvent(instance, eventType, event);

      if (__lastMouseDownViewId && viewId === __lastMouseDownViewId) {
        __lastMouseDownViewId = null;
        __bubbleEvent(instance, "onClick", event);
      }
      return;
    }