Flickering due to TooltipWindow appearing under the mouse cursor

I am in a condition where my tooltips have a good chance of appearing right under the mouse cursor that triggered them.

When that happens, this sequence of events results in a flickering of the tooltip.

Lets suppose for this example that the tooltip client is a Button

  1. The mouse cursor hovers over the Button for the time required for the tooltip to appear

  2. the TooltipWindow appears as expected, under the mouse cursor

  3. a moment later, the timerCallback() in TooltipWindow ticks and detects that the component under the mouse is not the Button anymore, because now there is the TooltipWindow in the way

  4. The TooltipWindow itself is not a TooltipClient (rightly so) and thus the tooltip is made to disappear

  5. I have not moved the mouse cursor all this time, so now that the tooltip has disappeared, the component under the mouse is now once again my Button

  6. The Timer in TooltipWindow ticks again and shows the tooltip for my Button right under the mouse cursor. This replicates the condition of step #2 above, resulting in a loop that makes the tooltip flicker in and out of existence.

Please note that this happens in a project that was made with JUCE 3 and the problem didn’t exist with it.

I have tried to work around this problem by making the TooltipWindow not intercepting mouse clicks (setInterceptMouseClicks(false) ) which should prevent it from being detected as the component under the mouse.

Unfortunately, it seems that the getComponentUnderMouse() used in the tooltip timerCallback() ignores that flag.
In my opinion that is the wrong part: tooltips should take into account the actual hitTest() of Components rather than just their rectangular boundaries in the screen area. This applies not only to my case described here, but also for example to Components representing knobs, that have a round hitArea().

(I am on Mac, but due to the nature of the problem I suppose the same behavior happens on Windows)

Adding the flag ComponentPeer::windowIgnoresMouseClicks to addToDesktop in juce_gui_basics/windows/juce_ToolTipWindow.cpp:93, so

addToDesktop (ComponentPeer::windowHasDropShadow
                    | ComponentPeer::windowIsTemporary
                    | ComponentPeer::windowIgnoresKeyPresses
                    | ComponentPeer::windowIgnoresMouseClicks);

Can you test if this works for you?

This change works perfectly, thanks!
Will you include this in the JUCE codebase?

Done! :slight_smile: (see the develop branch)

1 Like

Sorry for the necromancy, but I seem to having a similar problem in JUCE 6.1.3…

I’ve set all the flags mentioned earlier in this thread, but the tooltip will still flicker when the mouse pointer moves over it.
What I do in the constructor of the BubbleComponent is that I set alwaysOnTop = true (or it won’t be visible at all).
I’ve also tried explicitly setting interceptsMouseClicks to (false, false) and overriding hitTest() in the BubbleComponent to always return false, but it doesn’t seem to make a difference.

Any ideas?

Adding some zombiology to the necropost, I have a weird issue in the same general direction: I’m maintaining some code that uses a tooltip window class derived from TooltipWindow, but wrapped with some background stuff like parsing the tooltips from a JSON object. This class also uses an individual LnF, with the following getTooltipBounds:

juce::Rectangle<int> HelpTooltipWindow::Lnf::getTooltipBounds(const juce::String &tipText,
                                                              juce::Point<int> screenPos,
                                                              juce::Rectangle<int> parentArea) {
	auto layout = tooltipLayout(tipText);
	auto w = (int) (std::max(layout.first.getWidth(), layout.second.getWidth()) + 14);
	auto h = (int) (layout.first.getHeight() + 5 + layout.second.getHeight() + 8);

	return {screenPos.getX(), screenPos.getY(), w, h};
}

Here’s the weird part: On some Retina displays (e.g. M1 Macbook 13"), this leads to the well-known “tooltip window appears underneath mouse cursor” issue. On my FHD external display, that issue never occurs. However, when I use the juce::TooltipWindow instead of this custom window, it also never happens. My understanding of JUCE is still a bit mediocre, but displayTipInternal does the positioning for the JUCE windows itself, if I’m not mistaken? That code is called in both cases (juce::TooltipWindow and ::HelpTooltipWindow), but the LnF then overrides this, correct?

The easy and obvious fix for me was to add an offset to the x and y positions (needs to be 2px, one is not enough) in the above code and call it a day, but I’d still like to better understand why this issue appears without those offsets.

Best,
Andreas

displayTipInternal calls through to updatePosition, which in turn gets the LookAndFeel and then calls getTooltipBounds to set the new position. If a custom LookAndFeel hasn’t been set, the getTooltipBounds implementation from LookAndFeel_V2 will be used, which adds a small offset to the tooltip to prevent it from appearing under the mouse.

The offsets appear to be required; the built-in JUCE LookAndFeels also add offsets to the screenPos.

Thanks @reuk, that confirms my assumptions here. I should have looked into updatePosition in the first place, all makes sense now. The only thing that still weirds me out is that I’m only seeing the errant behaviour on HiDPI displays, but I guess it makes sense.