GUI - Scrolling Listbox (working), improve mouse scrolling

Hello there,

I have created a simple class for displaying a Listbox. It works quite fine, but now I have a small problem. When the Listbox has more than 16 items in it, the user can scroll with his (or her) mouse wheel. That is also working. But in my case, sometimes I will need two Listboxes and here my problem starts. Both Listboxes would scroll when I use the wheel and this is - of course! - not what I want. At this moment, my code is like this:

class PyrListBox():
    def __init__(self, items, pos):
        self.focus = False
        self.max_items = 16
        self.offset = 0
        self.items = items
        self.cards = []
        self.nodes = []
        self.pickedEntry = None

        self.OnCollideIn = None
        self.OnCollideOut = None
        self.OnClick = None
        
        self.cm = CardMaker('cm')
        left,right,bottom,top = 0, 1.0, 0, 0.1
        self.cm.setFrame(left,right,top,bottom)
        self.NP = aspect2d.attachNewNode('listbox')
        x, z = pos
        self.NP.setPos(x, 0, z)
        self.NP.setColor(0.1, 0.1, 0.1, 0.5)
        self.NP.setTransparency(True)
        tex = loader.loadTexture('textures/bg.png')

        for i in range(len(self.items)):
            self.cards.append(self.cm.generate())
            text = TextNode('item name')
            text.setText(self.items[i])
            textNodePath = NodePath(self.cards[i]).attachNewNode(text)
            textNodePath.setScale(0.07)
            textNodePath.setPos(0.025, 0, 0.025)
            textNodePath.setColor(1.0, 1.0, 1.0, 1.0)
            self.nodes.append(self.NP.attachNewNode(self.cards[i]))
            self.nodes[i].setTexture(tex)
            self.nodes[i].setPos(0, 0, - i * 0.1)
            self.nodes[i].setCollideMask(BitMask32.bit(21))
            self.nodes[i].setTag('entry', self.items[i])
            
        self.update()

        self.cTrav = CollisionTraverser()
        self.handler = CollisionHandlerEvent()
        self.handler.addInPattern('ray-into-entry')
        self.handler.addOutPattern('ray-out-entry')
        self.pickerNode = CollisionNode('mouseraycnode')
        self.pickerNP = base.render2d.attachNewNode(self.pickerNode)
        self.pickerNP.show()
        self.pickerRay = CollisionRay(0, -1, 0, 0, 1, 0)
        self.pickerNode.addSolid(self.pickerRay)
        self.pickerNode.setFromCollideMask(BitMask32.bit(21))
        self.pickerNode.setIntoCollideMask(BitMask32.allOff())
        self.cTrav.addCollider(self.pickerNP, self.handler)

        self.DO = DirectObject()

        taskMgr.add(self.rayUpdate, 'updatePicker')
        self.hide()


    def update(self):
        for i in range(len(self.nodes)):
            self.nodes[i].detachNode()
        self.nodes = []
        for i in range(self.offset, self.offset + self.max_items):
            if i > len(self.cards)-1:
                break
            self.nodes.append(self.NP.attachNewNode(self.cards[i]))
            self.nodes[i - self.offset].setPos(0, 0, - (i - self.offset) * 0.1)


    def scroll_down(self):
        if len(self.items) > self.max_items:
            if len(self.items) > self.max_items + self.offset:
                self.offset += 1
                self.update()


    def scroll_up(self):
        if self.offset > 0:
            self.offset -= 1
            self.update()


    def rayUpdate(self, task):
        if base.mouseWatcherNode.hasMouse():
            mpos = base.mouseWatcherNode.getMouse()
            self.pickerNP.setPos(render2d, mpos[0], 0, mpos[1])
            self.cTrav.traverse(render2d)
        return task.cont


    def click(self):
        if self.OnClick <> None:
            self.OnClick(self.pickedEntry)


    def collideIn(self, entry):
        np = entry.getIntoNodePath()
        np.setColor(0.2, 0.2, 0.2, 0.5)
        self.pickedEntry = np.getTag('entry')
        #self.focus = True

        if self.OnCollideIn <> None:
            self.OnCollideIn(self.pickedEntry)


    def collideOut(self, entry):
        np = entry.getIntoNodePath()
        np.setColor(0.1, 0.1, 0.1, 0.5)
        self.pickedEntry = None
        #self.focus = False


    def show(self):
        self.NP.reparentTo(aspect2d)
        self.DO.accept('ray-into-entry', self.collideIn)
        self.DO.accept('ray-out-entry', self.collideOut)
        self.DO.accept('wheel_up', self.scroll_up)
        self.DO.accept('wheel_down', self.scroll_down)
        self.DO.accept('mouse1', self.click)


    def hide(self):
        self.NP.reparentTo(hidden)
        self.DO.ignoreAll()

init() takes the position on the screen and a list of all the items I wanna have. After creation, the variables like self.OnClick can be set to a function, so it is executed when the user clicks (quite simmilar like in Delphi or Lazarus).

I tried it with an variable in which I saved if the Listbox has the “focus” / if the mouse pointer is over an item and checked it before the increasing/decreasing of the self.offset value. This didn’t work well, it hangs mostly and scrolling wasn’t “smooth”. Even setting self.focus = True directly after scrolling and updating wasn’t the clue.

In my very case I COULD deactivate the Event handling of one Listbox, but this not the elegant way.

Has anyone a hint for me? Thanks in advance, I hope my english is good enough for you to understand what I mean, it is not my native language.

Jörn

I already created similar GUI, but without collision.
Just click my sig.

I think, I got the clou know. I’ll calculate an invisible rectangle matching the size of my listbox, and check if the mouse is in this rectangle. That means, overlapping Listboxes both have the ‘focus’, but that doesn’t matter in my case. Thank you! Tomorrow, I post the result - maybe someone can use it, too.