DirectGUI: Unexpected "Without"/"Exit" Events

[edit] I’ve managed to produce a minimal test-program for this issue; please see the end of the post for it. [/edit]

I’ve encountered a bit of an odd issue in DirectGUI, and I’m not sure of how best to proceed in discovering its ultimate cause, and then (hopefully) fixing it.

Simply put, in my project I have a series of DirectButtons, each with “rollover” sounds. (They also have “press” sounds, but those aren’t important here, I believe.) When these buttons are clicked, specific DirectFrames are reparented into and out of a nearby DirectScrolledFrame.

This all works–save for one thing: Sometimes, after clicking on one of those buttons, the rollover sound plays again. Without, mind you, my moving the mouse outside of the button.

Having investigated, I see in the event-spam that, in the same frame that includes the click-related events, I’m also getting a “without” and an “exit” event, and then on the next frame a “within” and an “enter” event. Like so:

:event(debug): Throwing event click-mouse1-pg149(PGMouseWatcherParameter)
:event(debug): Throwing event release-mouse1-pg149(PGMouseWatcherParameter)
:event(debug): Throwing event mouse1-up()
:event(debug): Throwing event time-mouse1-up(7.74108)
:event(debug): Throwing event without-pg149(PGMouseWatcherParameter)
:event(debug): Throwing event exit-pg149(PGMouseWatcherParameter)
:event(spam): Throwing event NewFrame()
:event(debug): Throwing event within-pg149(PGMouseWatcherParameter)
:event(debug): Throwing event enter-pg149(PGMouseWatcherParameter)

Investigating within my code, the issue seems to only occur when I reparent the above-mentioned frame to the DirectScrolledFrame; if that doesn’t happen, the issue doesn’t present.

It does look like the frame might be overlapping the button before reparenting–but it is (if I recall correctly) initially detached and hidden, and is being shown and reparented within the same frame. As such, it shouldn’t affect the button.

And indeed, attempts to reproduce the issue along those lines within a simple test-program have thus far failed. :/

And so I find myself unsure of how to proceed in my investigation. Does anyone have any suggestions…? (e.g. Things that I might try to reproduce the issue in the test-program.)

[edit]
Okay, I believe that I’ve found the culprit–and it’s not, actually, the reparenting of the abovementioned frame. Rather, it’s a change that’s later made in response to that reparenting: the hiding of the vertical scroll-bar.

Here, then, is a short test-program that (on my machine) demonstrates the issue.

To test:

  • Replace “hit.ogg” with whatever (short) sound-file you may have on hand
  • Run the program
  • Move the mouse to hover over the DirectButton that should appear.
    • Note that the rollover sound (correctly) plays
  • Click the button.
    • Note that the vertical scroll-bar disappears (as the code calls its “hide” method)–and the rollover sound (incorrectly) plays again!
from direct.showbase.ShowBase import ShowBase

from direct.gui.DirectGui import DGG, DirectButton, DirectFrame, DirectScrolledFrame


class Game(ShowBase):
	def __init__(self):
		ShowBase.__init__(self)
		
		self.scroller = DirectScrolledFrame(frameSize = (-1, 1, -1, 0),
											canvasSize = (-0.7, 0.7, -2, 2),
											pos = (0, 0, -0),
											relief = None,
											autoHideScrollBars = False
											)
		self.canvas = self.scroller.getCanvas()
		
		btn = DirectButton(text = "",
						   scale = 0.1,
						   frameSize = (-1, 1, -0.5, 0.5),
						   pos = (0, 0, 0),
						   rolloverSound = loader.loadSfx("hit.ogg"),
						   command = self.mew
						   )
		
						   
	def mew(self):
		self.scroller.verticalScroll.hide()
	
app = Game()
app.run()

Okay, new post as the previous was getting long, if I may.

So, I’ve done a bit more investigating, and it looks like the problem may lie in “mouseWatcher.cxx”.

Specifically, I’ve added some debug print-outs to the “set_current_regions” method in that file and to the “without_region” method of “pgMouseWatcherRegion.cxx”.

With those in place, I’m seeing that “without_region” is being called for the button–and seems to be called as a result of “set_current_regions” determining that the button’s region is a “leftover”, a region found in the “old” group of regions, but not present in the “new” group of regions.

Here are the relevant code-changes:
“mouseWatcher.cxx”, ~line 663:

  while (old_ri != _current_regions.end()) {
    // Here's a region we don't have any more.
    MouseWatcherRegion *old_region = (*old_ri);
    tform_cat.info() << "leftover ri!\n";      // <---
    tform_cat.info() << "WITHOUTING!\n";       // <---
    without_region(old_region, param);
    any_changes = true;
    ++old_ri;
  }

“pgMouseWatcherRegion.cxx”, ~line86:

void PGMouseWatcherRegion::
without_region(const MouseWatcherParameter &param) {
  if (_item != nullptr) {
    _item->without_region(param);
    pgui_cat.info() << "withouting: " << *_item <<"\n";
  }
}

(And with “notify-level info” and “default-directnotify-level info” set in my PRC file.)

And here’s the relevant output that I see:

:tform: leftover ri!
:tform: WITHOUTING!
:pgui: withouting: PGButton DirectButton-pg9

Note that this happens without my leaving the button, but with the scroll-bar being hidden in response to the button. (And I have checked that this does seem to be happening within the same frame.)

At this point, I think that I’m going to file this as a bug over on GitHub.

[edit] And here’s the issue on GitHub: