mirror of
https://github.com/Sneed-Group/Poodletooth-iLand
synced 2024-12-26 05:02:31 -06:00
361 lines
14 KiB
Python
361 lines
14 KiB
Python
""" This module implements a Panda3D window that can be embedded
|
|
within a wx.Frame. The window itself is either an embedded window,
|
|
which is a wx.Window with the Panda window attached to it, or it is a
|
|
wxgl.GLCanvas, with Panda directed to draw into it, depending on the
|
|
platform. In either case, you may simply embed this window into a
|
|
wx.Frame of your choosing, using sizers or whatever you like. """
|
|
|
|
import wx
|
|
import platform
|
|
|
|
try:
|
|
import wx.glcanvas as wxgl
|
|
except ImportError:
|
|
wxgl = None
|
|
|
|
from panda3d.core import *
|
|
|
|
__all__ = ['WxPandaWindow']
|
|
|
|
class EmbeddedPandaWindow(wx.Window):
|
|
""" This class implements a Panda3D window that is directly
|
|
embedded within the frame. It is fully supported on Windows,
|
|
partially supported on Linux, and not at all on OSX. """
|
|
|
|
def __init__(self, *args, **kw):
|
|
gsg = None
|
|
if 'gsg' in kw:
|
|
gsg = kw['gsg']
|
|
del kw['gsg']
|
|
|
|
base.startWx()
|
|
wx.Window.__init__(self, *args, **kw)
|
|
|
|
wp = WindowProperties.getDefault()
|
|
if platform.system() != 'Darwin':
|
|
try:
|
|
wp.setParentWindow(self.GetHandle())
|
|
except OverflowError:
|
|
# Sheesh, a negative value from GetHandle(). This can
|
|
# only happen on 32-bit Windows.
|
|
wp.setParentWindow(self.GetHandle() & 0xffffffff)
|
|
|
|
self.win = base.openWindow(props = wp, gsg = gsg, type = 'onscreen',
|
|
unexposedDraw = False)
|
|
self.Bind(wx.EVT_SIZE, self.onSize)
|
|
|
|
# This doesn't actually do anything, since wx won't call
|
|
# EVT_CLOSE on a child window, only on the toplevel window
|
|
# that contains it.
|
|
self.Bind(wx.EVT_CLOSE, self.__closeEvent)
|
|
|
|
def __closeEvent(self, event):
|
|
self.cleanup()
|
|
event.Skip()
|
|
|
|
def cleanup(self):
|
|
""" Parent windows should call cleanup() to clean up the
|
|
wxPandaWindow explicitly (since we can't catch EVT_CLOSE
|
|
directly). """
|
|
if self.win:
|
|
base.closeWindow(self.win)
|
|
self.win = None
|
|
|
|
def onSize(self, event):
|
|
if self.win:
|
|
wp = WindowProperties()
|
|
wp.setOrigin(0, 0)
|
|
wp.setSize(*self.GetClientSize())
|
|
self.win.requestProperties(wp)
|
|
event.Skip()
|
|
|
|
if not hasattr(wxgl, 'GLCanvas'):
|
|
OpenGLPandaWindow = None
|
|
else:
|
|
class OpenGLPandaWindow(wxgl.GLCanvas):
|
|
""" This class implements a Panda3D "window" that actually draws
|
|
within the wx GLCanvas object. It is supported whenever OpenGL is
|
|
Panda's rendering engine, and GLCanvas is available in wx. """
|
|
|
|
removeCallbackWindow = ConfigVariableBool('remove-callback-window', True)
|
|
|
|
Keymap = {
|
|
wx.WXK_BACK : KeyboardButton.backspace(),
|
|
wx.WXK_TAB : KeyboardButton.tab(),
|
|
wx.WXK_RETURN : KeyboardButton.enter(),
|
|
wx.WXK_ESCAPE : KeyboardButton.escape(),
|
|
wx.WXK_DELETE : KeyboardButton._del(), # del is a Python keyword
|
|
wx.WXK_SHIFT : KeyboardButton.shift(),
|
|
wx.WXK_ALT : KeyboardButton.alt(),
|
|
wx.WXK_CONTROL : KeyboardButton.control(),
|
|
wx.WXK_MENU : KeyboardButton.meta(),
|
|
wx.WXK_PAUSE : KeyboardButton.pause(),
|
|
wx.WXK_END : KeyboardButton.end(),
|
|
wx.WXK_HOME : KeyboardButton.home(),
|
|
wx.WXK_LEFT : KeyboardButton.left(),
|
|
wx.WXK_UP : KeyboardButton.up(),
|
|
wx.WXK_RIGHT : KeyboardButton.right(),
|
|
wx.WXK_DOWN : KeyboardButton.down(),
|
|
wx.WXK_PRINT : KeyboardButton.printScreen(),
|
|
wx.WXK_INSERT : KeyboardButton.insert(),
|
|
wx.WXK_F1 : KeyboardButton.f1(),
|
|
wx.WXK_F2 : KeyboardButton.f2(),
|
|
wx.WXK_F3 : KeyboardButton.f3(),
|
|
wx.WXK_F4 : KeyboardButton.f4(),
|
|
wx.WXK_F5 : KeyboardButton.f5(),
|
|
wx.WXK_F6 : KeyboardButton.f6(),
|
|
wx.WXK_F7 : KeyboardButton.f7(),
|
|
wx.WXK_F8 : KeyboardButton.f8(),
|
|
wx.WXK_F9 : KeyboardButton.f9(),
|
|
wx.WXK_F10 : KeyboardButton.f10(),
|
|
wx.WXK_F11 : KeyboardButton.f11(),
|
|
wx.WXK_F12 : KeyboardButton.f12(),
|
|
wx.WXK_NUMLOCK : KeyboardButton.numLock(),
|
|
wx.WXK_SCROLL : KeyboardButton.scrollLock(),
|
|
wx.WXK_PAGEUP : KeyboardButton.pageUp(),
|
|
wx.WXK_PAGEDOWN : KeyboardButton.pageDown(),
|
|
wx.WXK_COMMAND : KeyboardButton.meta(),
|
|
}
|
|
|
|
def __init__(self, *args, **kw):
|
|
gsg = None
|
|
if 'gsg' in kw:
|
|
gsg = kw['gsg']
|
|
del kw['gsg']
|
|
|
|
fbprops = kw.get('fbprops', None)
|
|
if fbprops == None:
|
|
fbprops = FrameBufferProperties.getDefault()
|
|
|
|
attribList = kw.get('attribList', None)
|
|
if attribList is None:
|
|
attribList = [
|
|
wxgl.WX_GL_RGBA, True,
|
|
wxgl.WX_GL_LEVEL, 0,
|
|
]
|
|
if not fbprops.isSingleBuffered():
|
|
attribList.append(wxgl.WX_GL_DOUBLEBUFFER)
|
|
attribList.append(True)
|
|
if fbprops.getDepthBits() > 0:
|
|
attribList.append(wxgl.WX_GL_DEPTH_SIZE)
|
|
if fbprops.getDepthBits() <= 16:
|
|
attribList.append(16)
|
|
elif fbprops.getDepthBits() <= 24:
|
|
attribList.append(24)
|
|
else:
|
|
attribList.append(32)
|
|
|
|
kw['attribList'] = attribList
|
|
|
|
base.startWx()
|
|
wxgl.GLCanvas.__init__(self, *args, **kw)
|
|
self.visible = False
|
|
|
|
# Can't share the GSG when a new wxgl.GLContext is created
|
|
# automatically.
|
|
gsg = None
|
|
|
|
callbackWindowDict = {
|
|
'Events' : self.__eventsCallback,
|
|
'Properties' : self.__propertiesCallback,
|
|
'Render' : self.__renderCallback,
|
|
}
|
|
|
|
# Make sure we have an OpenGL GraphicsPipe.
|
|
if not base.pipe:
|
|
base.makeDefaultPipe()
|
|
pipe = base.pipe
|
|
if pipe.getInterfaceName() != 'OpenGL':
|
|
base.makeAllPipes()
|
|
for pipe in base.pipeList:
|
|
if pipe.getInterfaceName() == 'OpenGL':
|
|
break
|
|
|
|
if pipe.getInterfaceName() != 'OpenGL':
|
|
raise StandardError, "Couldn't get an OpenGL pipe."
|
|
|
|
self.win = base.openWindow(callbackWindowDict = callbackWindowDict, pipe = pipe, gsg = gsg, type = 'onscreen')
|
|
self.hasCapture = False
|
|
self.inputDevice = None
|
|
if hasattr(self.win, 'getInputDevice'):
|
|
self.inputDevice = self.win.getInputDevice(0)
|
|
|
|
self.Bind(wx.EVT_SIZE, self.onSize)
|
|
self.Bind(wx.EVT_PAINT, self.onPaint)
|
|
self.Bind(wx.EVT_IDLE, self.onIdle)
|
|
|
|
if self.inputDevice:
|
|
self.Bind(wx.EVT_LEFT_DOWN, lambda event: self.__buttonDown(MouseButton.one()))
|
|
self.Bind(wx.EVT_LEFT_UP, lambda event: self.__buttonUp(MouseButton.one()))
|
|
self.Bind(wx.EVT_MIDDLE_DOWN, lambda event: self.__buttonDown(MouseButton.two()))
|
|
self.Bind(wx.EVT_MIDDLE_UP, lambda event: self.__buttonUp(MouseButton.two()))
|
|
self.Bind(wx.EVT_RIGHT_DOWN, lambda event: self.__buttonDown(MouseButton.three()))
|
|
self.Bind(wx.EVT_RIGHT_UP, lambda event: self.__buttonUp(MouseButton.three()))
|
|
self.Bind(wx.EVT_MOTION, self.__mouseMotion)
|
|
self.Bind(wx.EVT_MOUSEWHEEL, self.__mouseWheel)
|
|
self.Bind(wx.EVT_LEAVE_WINDOW, self.__mouseLeaveWindow)
|
|
self.Bind(wx.EVT_KEY_DOWN, self.__keyDown)
|
|
self.Bind(wx.EVT_KEY_UP, self.__keyUp)
|
|
self.Bind(wx.EVT_CHAR, self.__keystroke)
|
|
|
|
# This doesn't actually do anything, since wx won't call
|
|
# EVT_CLOSE on a child window, only on the toplevel window
|
|
# that contains it.
|
|
self.Bind(wx.EVT_CLOSE, self.__closeEvent)
|
|
|
|
def __closeEvent(self, event):
|
|
self.cleanup()
|
|
event.Skip()
|
|
|
|
def cleanup(self):
|
|
""" Parent windows should call cleanup() to clean up the
|
|
wxPandaWindow explicitly (since we can't catch EVT_CLOSE
|
|
directly). """
|
|
if self.win:
|
|
self.win.clearEventsCallback()
|
|
self.win.clearPropertiesCallback()
|
|
self.win.clearRenderCallback()
|
|
base.closeWindow(self.win, removeWindow = self.removeCallbackWindow)
|
|
self.win = None
|
|
|
|
def __buttonDown(self, button):
|
|
self.SetFocus()
|
|
if not self.hasCapture:
|
|
self.CaptureMouse()
|
|
self.hasCapture = True
|
|
self.inputDevice.buttonDown(button)
|
|
|
|
def __buttonUp(self, button):
|
|
if self.hasCapture:
|
|
self.ReleaseMouse()
|
|
self.hasCapture = False
|
|
self.inputDevice.buttonUp(button)
|
|
|
|
def __mouseMotion(self, event):
|
|
self.inputDevice.setPointerInWindow(*event.GetPosition())
|
|
|
|
def __mouseWheel(self, event):
|
|
amount = event.GetWheelRotation()
|
|
if amount > 0.0:
|
|
self.inputDevice.buttonDown(MouseButton.wheelUp())
|
|
self.inputDevice.buttonUp(MouseButton.wheelUp())
|
|
elif amount < 0.0:
|
|
self.inputDevice.buttonDown(MouseButton.wheelDown())
|
|
self.inputDevice.buttonUp(MouseButton.wheelDown())
|
|
|
|
def __mouseLeaveWindow(self, event):
|
|
self.inputDevice.setPointerOutOfWindow()
|
|
|
|
def __keyDown(self, event):
|
|
key = self.__getkey(event)
|
|
if key:
|
|
self.inputDevice.buttonDown(key)
|
|
|
|
def __keyUp(self, event):
|
|
key = self.__getkey(event)
|
|
if key:
|
|
self.inputDevice.buttonUp(key)
|
|
|
|
def __getkey(self, event):
|
|
code = event.GetKeyCode()
|
|
key = self.Keymap.get(code, None)
|
|
if key is not None:
|
|
return key
|
|
|
|
if code >= 0x41 and code <= 0x5a:
|
|
# wxWidgets returns uppercase letters, but Panda expects
|
|
# lowercase letters.
|
|
return KeyboardButton.asciiKey(code + 0x20)
|
|
if code >= 0x20 and code <= 0x80:
|
|
# Other ASCII keys are treated the same way in wx and Panda.
|
|
return KeyboardButton.asciiKey(code)
|
|
|
|
return None
|
|
|
|
def __keystroke(self, event):
|
|
if hasattr(event, 'GetUnicodeKey'):
|
|
code = event.GetUnicodeKey()
|
|
else:
|
|
code = event.GetKeyCode()
|
|
if code < 0x20 or code >= 0x80:
|
|
return
|
|
|
|
self.inputDevice.keystroke(code)
|
|
|
|
def __eventsCallback(self, data):
|
|
data.upcall()
|
|
|
|
def __propertiesCallback(self, data):
|
|
data.upcall()
|
|
|
|
def __renderCallback(self, data):
|
|
cbType = data.getCallbackType()
|
|
if cbType == CallbackGraphicsWindow.RCTBeginFrame:
|
|
if not self.visible:
|
|
data.setRenderFlag(False)
|
|
return
|
|
self.SetCurrent()
|
|
|
|
# Don't upcall() in this case.
|
|
return
|
|
|
|
elif cbType == CallbackGraphicsWindow.RCTEndFlip:
|
|
self.SwapBuffers()
|
|
|
|
# Now that we've swapped, ask for a refresh, so we'll
|
|
# get another paint message if the window is still
|
|
# visible onscreen.
|
|
self.Refresh()
|
|
self.visible = False
|
|
|
|
data.upcall()
|
|
|
|
def onSize(self, event):
|
|
wp = WindowProperties()
|
|
wp.setSize(*self.GetClientSize())
|
|
self.win.requestProperties(wp)
|
|
|
|
# Apparently, sometimes on Linux we get the onSize event
|
|
# before the size has actually changed, and the size we
|
|
# report in GetClientSize() is the *previous* size. To
|
|
# work around this unfortunate circumstance, we'll also
|
|
# ensure an idle event comes in later, and check the size
|
|
# again then.
|
|
wx.WakeUpIdle()
|
|
|
|
event.Skip()
|
|
|
|
def onPaint(self, event):
|
|
""" This is called whenever we get the first paint event,
|
|
at which point we can conclude that the window has
|
|
actually been manifested onscreen. (In X11, there appears
|
|
to be no way to know this otherwise.) """
|
|
self.visible = True
|
|
|
|
# Important not to Skip this event, so the window
|
|
# subsystem believes we've drawn the window.
|
|
#event.Skip()
|
|
|
|
def onIdle(self, event):
|
|
if self.win:
|
|
size = None
|
|
properties = self.win.getProperties()
|
|
if properties.hasSize():
|
|
size = (properties.getXSize(), properties.getYSize())
|
|
|
|
if tuple(self.GetClientSize()) != size:
|
|
# The window has changed size during the idle call.
|
|
# This seems to be possible in Linux.
|
|
wp = WindowProperties()
|
|
wp.setSize(*self.GetClientSize())
|
|
self.win.requestProperties(wp)
|
|
|
|
event.Skip()
|
|
|
|
# Choose the best implementation of WxPandaWindow for the platform.
|
|
WxPandaWindow = None
|
|
if platform.system() == 'Darwin' or platform.system() == 'Linux':
|
|
WxPandaWindow = OpenGLPandaWindow
|
|
|
|
if not WxPandaWindow:
|
|
WxPandaWindow = EmbeddedPandaWindow
|