Mouse events in several windows

Hi,

I’m doing some tests to integrate panda3d into a wxpython application; my intention is that the main window create one or several panda3d windows managing the 3d and GUI aspects of a chess-match against one or several opponents. In the test (still not integrated with the wx framework), i’m blocking the starting window (by using loadPrcFileData("", “window-type none”)
import direct.directbase.DirectStart
) and then creating a couple of windows on the fly (i create a couple of DirectObject-based objects which use base.openWindow in the init function to set-up the window); i can load models, textures, etc, on both, but i have found a problem when trying to get mouse events on both windows (to pick-up the pieces, by example): i have tried to use base.setupMouse, but only the last window to be created receive mouse events. I’m guessing the correct solution will be creating some MouseWatcher or something so, but i haven’t found clear info on that when quick-searching the forum.

Can anyone give me some hints on how to to that?

On a side note, i’m eagerly awating support for getting panda3d windows embedded on a wx.Panel (i have seen on another thread that something similar is WIP) :wink:

Andreas

I have been researching a bit how the setupMouse in ShowBase.py works; mimetizing that, i have partially solved the problem. Here is a minimal (trimmed) example of what i’m doing now:

from pandac.PandaModules import loadPrcFileData
loadPrcFileData("", "window-type none")
import direct.directbase.DirectStart
from direct.showbase.DirectObject import DirectObject
from pandac.PandaModules import WindowProperties
from pandac.PandaModules import MouseAndKeyboard
from pandac.PandaModules import MouseWatcher
from pandac.PandaModules import ModifierButtons
from pandac.PandaModules import KeyboardButton
from pandac.PandaModules import ButtonThrower
from pandac.PandaModules import DriveInterface


class World(DirectObject):
  def __init__(self, name):
    self.name = name
    wp = WindowProperties()
    wp.setSize(400, 400)
    
    win = base.openWindow(props = wp, aspectRatio = 1)
    
    self.buttonThrowers = []
    for i in range(win.getNumInputDevices()):
        name = win.getInputDeviceName(i)
        mk = base.dataRoot.attachNewNode(MouseAndKeyboard(win, i, name))
        mw = mk.attachNewNode(MouseWatcher(name))
        bt = mw.attachNewNode(ButtonThrower(name))
        if (i != 0):
            bt.node().setPrefix('mousedev'+str(i)+'-')
        mods = ModifierButtons()
        bt.node().setModifierButtons(mods)
        self.buttonThrowers.append(bt)

            
    self.mouseWatcher = self.buttonThrowers[0].getParent()
    self.mouseWatcherNode = self.mouseWatcher.node()
            
    self.drive = self.mouseWatcher.attachNewNode(DriveInterface('drive'))
    

    self.accept('mouse1', self.leftButtonClick)
    self.accept("mouse1-up", self.leftButtonRelease)
    
  def leftButtonClick(self):
       if self.mouseWatcherNode.hasMouse():
           print self.name, "/ leftButtonClick"
  def leftButtonRelease(self):
       if self.mouseWatcherNode.hasMouse():
           print self.name, "/ leftButtonRelease"
    

w = World("first context")
w2 = World("second context")
run()

With that, both windows can receive mouse events, but now some more doubts have arised…

First, i’m still unsure on how to addapt the last three lines in setupMouse:

        # Tell the gui system about our new mouse watcher.
        self.aspect2d.node().setMouseWatcher(mw.node())
        self.aspect2dp.node().setMouseWatcher(mw.node())
        mw.node().addRegion(PGMouseWatcherBackground())

By the comments, i understand what it’s doing, but as said, i’m unsure on how to addapt it to my case (what is and how should i create a ‘aspect2d’ object to avoid breaking anything?); if somebody can give me a tip to save me some days of navigating the library source, i would be grateful. :stuck_out_tongue:

Besides that, there is another issue: when i load models and stretch the windows, the first window maintain the aspect rate of the models, but the second one’s models got deformed (when getting full screen, by example, you see the models stretched over the horizontal direction of the screen). Any thought on this?

You can create an aspect2d node in each window with something like this:
win.aspect2d = win.render2d.attachNewNode(PGTop(“aspect2d”))
Where again, this code is lifted from ShowBase.py.

aspect2d has two purposes. One is to be a child of render2d whose X, Z scale is set to compensate for the aspect ratio of the window. (Hence its name.) For this purpose, any node with a scale will do. But the other purpose is to be a parent of the DirectGUI system, and for this purpose, it has to be a PGTop type node. Other than that, there are no special properties of this node.

As to maintaining the aspect ratio when the window is changed, you can see when ShowBase.__windowEvent() gets called–as it does whenever the window is resized by the user, as well as in other cases–it resets the scale on aspect2d and on base.camLens to compensate for the new window’s aspect ratio. You’ll have to duplicate that code too.

David

Thansk for the info, David. Strangely, i had missed that aspect2d was being created in the same module when looking for it; that’s why i was so puzzled.

I will continue working on this.

Done. I firstly call base.ignore(‘window-event’) before creating the objects and then create a window-event-handler that fit my needs and attach it with self.accept(‘window-event’, self.windowEvent) in the init function; now i’m able to set the right aspect in all the windows and i avoid that closing the first one the engine be shut down; furthermore, i have duplicated and adapted the setupRender2d and setupRender2dp functions (BTW, do i really need the render2dp thingy; what it’s useful for?)

Moving on… now i have hit another problem when trying to split the window in a couple of zones; in more detail: as i cannot use my wxpython controls in the panda3d windows, i’m trying to separate the display zone in two subzones: the 3d view at the left, and a right zone for Panda3d’s GUI controls. But i’m doing something wrong, it seems…

I was trying to do this:

    self.camera.node().getDisplayRegion(0).setDimensions(0,0.8,0,1)
    self.camera.node().getLens().setAspectRatio(0.8)

and the same with self.camera2d with the right part of the window. All seems OK but, when you try to use the mouse, you see that the mouse is still positioned against the old reference frame. By example: if you want to click an object just at the right edge of the 3d view, you must click in the right edge of the full window, and so on. How should i do that?

Andreas

Right, you don’t need render2dp at all. That was added for the sole (very obscure) purpose of creating gui on top of a 3-d scene on top of gui.

The problem with changing the size of render2d’s display region is that the mouse is still sending its coordinate positions in the full range of -1…1 across the full screen. However, I believe you can change this with:

base.mouseWatcherNode.setDisplayRegion(r2dDisplayRegion)

which tells the mouse to send coordinate positions in the range of -1…1 across r2dDisplayRegion (which is no longer the full screen).

Alternatively, instead of changing render2d,you might consider keeping render2d the same range, and just using only positive X coordinates for all of your 2-d elements.

David

Umm, yes i could simply use positive coordinates for my 2-d elements, but that’s not the point; maybe i should explain it better… the important thing for me is not the 2-d region, but the 3-d view; i could live managing the GUI elements with the original range, but i want the 3-d view to be restricted to the left side of the screen. Why? Well, i’m going to have the chess board at position (0,0,0), and some GUI controls are going to allow to move the camera around the board (distance, horizontal angle, etc); if i only show the 3-d view in the window, all is pretty simple, because i only have to set the position of the camera using the GUI values and make it point to (0,0,0). But if i’m going to leave… let’s say 30% of the right of the screen exclusively for GUI controls, making only that wouldn’t serve because the GUI controls would hide the right part of the board.

Of course, i can do the necessary math to simply make the camera point to a center such that i see the board centered in the 70% left of the screen, but i thought this solution would be simpler. Maybe was i wrong?

In relation to this, I was surfing Panda3d’s documentation and i found the “MouseSubregion” class, that, by the look of the comments, would seem to make something similar to what i was looking for. Sadly, i haven’t found any example of use. Could it be useful?

Andreas

No, I think you misunderstood. Go ahead and move render to the left half of the screen, or any fraction of the screen you like; this will restrict the 3-d view to wherever you specify. But keep render2d over the entire screen, since there’s no real reason to restrict it, and it’s just easier that way.

The MouseSubregion could also be used to restrict the mouse. But it’s complicated to use, and you don’t need it. Using base.mouseWatcherNode.setDisplayRegion(), as I describe above, is much simpler, but you don’t need that either, unless I am much mistaken.

David

No, that’s what i had understood. But maybe i didn’t explain enoughly the problem… when i move render to the left of the screen (by example, with the couple of lines 4 post above), i get a problem with mouse events independently of whether or not i touch render2d. So let’s forget render2d… i can simply left it unchanged as you suggest. But render has a similar problem too.

I will be more specific; if i handle the “mouse1” event and use the following code:

       mpos = self.mouseWatcherNode.getMouse()
       print mpos.getX(), mpos.getY()

i can see that, whether i move render to the left or not, clicking the full window is still in the range [-1,1]*[-1,1], so, by example, if i restrict render to the 50% at the left, clicking in the bottom-right corner of the 3d view give (0,-1). This make than, when i implement some collision logic, all get mad; if i put an object just in the center of the 3d view and render is restricted to the half left, i still have to click in the middle of the screen (the right edge of the moved render) to select that piece.

Andreas

Ah, I understand now. My apologies.

All right. Perhaps you do want to use the MouseSubregion object. This is easily done: simply create a MouseSubregion node, set it to the appropriate subregion, and attach it (reparent it) to the MouseAndKeyboard object in the data graph. Then you can parent the MouseWatcherNode to the MouseSubregion, instead of to the MouseAndKeyboard.

You can create multiple MouseSubregions, one for each region you’d like, and each one represents a different virtual mouse that is only active within the indicated region, and whose range is -1…1 over that region.

Is that clear enough? I can try to give a little bit of sample code, but it’s difficult to show an example without writing a lot of code around it. Fortunately, since you’ve already written your own window setup code by duplicating the code in ShowBase, it might be fairly straightforward to you.

David

Perfectly clear, indeed.

However, it doesn’t work as we expected. :wink:

When yo do that, the 3d view behaves as expected, but now… if you put a button in the right part of the screen, do you guess where do you have to click to select it? :wink: Yes, in the rightmost part of the 3d-view. :stuck_out_tongue:

After a number of tests (by example, i created 2 non-overlapping MouseSubregions, but i was limited to put GUI’s controls on one and/or the other zones, but not crossing the frontier between both), i have come to the conclusion that this is the most reasonable choice:

mk = base.dataRoot.attachNewNode(MouseAndKeyboard(self.win, i, name))
mw = mk.attachNewNode(MouseWatcher(name))
msr = mw.attachNewNode(MouseSubregion(name))
msr.node().setDimensions(0,0.8,0,1)            
mw2 = msr.attachNewNode(MouseWatcher("sec"))
bt = mw2.attachNewNode(ButtonThrower(name))
mods = ModifierButtons()
bt.node().setModifierButtons(mods)
self.buttonThrowers.append(bt)

as you can see, i’m using two MouseWatcher; the first one is there to contain the GUI’s control (i have decided to drop aspect2d, as i want the controls stretch over all the right zone):

    self.render2dPG = self.render2d.attachNewNode(PGTop("render2dPG"))
    self.render2dPG.node().setMouseWatcher(mw.node())

The second one, to receive mouse events in the 3d-view (normalized to [-1,1]*[-1,1]):

    self.mouseWatcher = self.buttonThrowers[0].getParent()
    self.mouseWatcherNode = self.mouseWatcher.node()

  def mouseEvent(self):
    if self.mouseWatcherNode.hasMouse():
       mpos = self.mouseWatcherNode.getMouse()
       print mpos.getX(), mpos.getY() 

Of course, i have to touch the camera in a similar way:

    self.camera2.node().getDisplayRegion(0).setDimensions(0,0.8,0,1)
    self.camera2.setScale(0.8,1.0,1.0)

Now i can add some control to the right zone:

    testButton = DirectButton(text = "Extended length\nbutton text", text_pos=(0.8,0.92), text_scale=(0.05,0.05), frameSize=(0.6,1,0.8,1), relief = DGG.RAISED, borderWidth=(0.01,0.01), pressEffect = 1, command=self.buttonPressed)
    testButton.reparentTo(self.render2dPG)

and… all the mouse events work fine. (The button can be pressed, the pieces in the 3d view can be selected when adding collision logic, etc) :slight_smile:

And now… the questions’ turn :stuck_out_tongue:

First, i have seen that i can drop away the line

self.drive = self.mouseWatcher.attachNewNode(DriveInterface('drive'))

Which was the utility of that? Do i need it?

Second, when opening a new window… can i ask that the aspect rate be maintained when the user manually resize the window? (Of course, i would still let the user can maximize the window)

Now, please take a look at this image:

I have a couple of questions…
first, what is the gray zone in the upper-left corner. Is it the limit of an environment attached to render? I firstly supposed it was due to the camera having a small “far distance” property, but it doesn’t seem to be the case, as i have used camera2.node().getLens().setFar with a big value as parameters, and it doesn’t change anything.

The other is… well, with certain sizes of the window, the text of the button can be seen fine, but at others it gets blurred. Of course, i understand there is a limit on what you can expect when scaling the text, but i was wondering if there is any option i should know to make the text “sharper” and avoid (as long as it be possible) the blurring.

Well, sure i have had more doubts, but that’s all i remember now. :stuck_out_tongue:

Andreas

This was just to support the optional DriveInterface, which allows the user to drive around the camera using the mouse. You don’t need it. This is an example of some of the cruft that has accumulated in ShowBase over the years.

Do you mean, you want the aspect ratio of the lens to remain the same, no matter what the aspect ratio of the window changes to; or do you mean you want the aspect ratio of the lens to automatically track the aspect ratio of the window? In either case, the answer is “yes, you can”, since Panda really leaves the aspect ratio up to you. If you want the lens to track the window, you have to hang the callback on window-event (as the code in ShowBase does) to adjust the lens when the window changes; if you don’t want the lens to track the window, you simply don’t do this.

Hmm, I would have guessed it is a far-plane clipping too. Are you sure you are adjusting the far plane of the correct lens? You can also put:

default-far 100000

in your Config.prc to set the default far plane for all lenses. Are you sure the model doesn’t simply end there? If nothing else, if you just want a light-gray background anyway, you can just set the background color of the window to be that light-gray color, instead of relying on a large polygon there.

Try putting one (or more) of:

text-anisotropic-degree 4
text-minfilter linear-mipmap-nearest
text-minfilter linear
text-pixels-per-unit 100

in your Config.prc file. Some of these have more performance implications than others. I believe there are also several forum posts about fiddling with the text rendering properties; try searching the forum for more.

David

Thanks for the info; i will try po implement your suggestions when i get access to my dev PC.

Yes, i know, but that wasn’t what i meant. I will put an example: when you manually resize a window you can, by example, drag with the mouse the right edge of the window to extend it horizontally. What i would like would be that, when doing that, the vertical size of the full window was automatically adjusted to always fit the same width/height ratio than at the beggining of the dragging. The only exception would be making it full window by clicking the “maximize” icon in the title bar.

Andreas

Still not quite following you.

Note that the operating system doesn’t tell the application whether the user is dragging the left edge, the right edge, the bottom edge, the corner, or whatever. It just tells us that the window has changed size, and here’s the new size. As far as that goes, there’s not much difference between resizing a window by dragging it, and resizing it by maximizing it.

When the window changes sizes, there are two sensible ways to react: (1) you can stretch the contents of the window to fit the new size, so that the same contents are visible in the window, but they now have a different shape, or (2) you can change the aspect ratio of the lens to compensate, so that you now see a different set of contents in the window, but a circle is still a circle.

Option (1) means to leave the camera lens alone. Option (2) means to adjust the camera lens as the window changes.

If the window is now wider than it used to be (to take your example), and you go with option (2), you can decide whether you adapt the lens by widening its horizontal field of view, so that you now have more visible on the left and right, or by narrowing its vertical field of view, so that you now have less visible on the top and bottom.

Panda has a method on lens call setMinFov(). This sets the field-of-view of the minimum dimension of the window, given the current aspect ratio. ShowBase uses this method by default to adjust the lens when the window is resized. The result is that if the window is wider than it is tall (as windows usually are), dragging it wider increases the horizontal field of view, but does not affect the vertical field of view. If the window is taller than it is wide, dragging it taller increases the vertical field of view, but does not affect the horizontal field of view. This seems to be intuitively what users expect the window to do.

david

Oh! I finally understand. You’re talking about maintaining the aspect ratio of the window itself, not of the contents of the window! I’m sorry for being thick.

You can do this by responding to a resize event with a new size request. But you’ll have to do it carefully, so you can avoid recursive loops.

David

Ey, it should be I who should apologize, not you! :stuck_out_tongue:

Seriously, thanks for your assistance. I have seen that “text-minfilter linear” is exactly what i wanted. Furthermore, you were right, that grey zone was due to the board model (which is somewhat disturbing, as i haven’t seen it in the modeler; but, whatever the case, it’s a model’s problem, so i will re-check the modeler and/or the exporter at some point).

Now i will do some tests with other control types. All the issues minimally related to mouse events have been solved, so i guess this thread should fade out now… :wink: