163 lines
5.4 KiB
Python
163 lines
5.4 KiB
Python
|
from pandac.PandaModules import *
|
||
|
from direct.showbase.DirectObject import DirectObject
|
||
|
from otp.nametag import NametagGlobals
|
||
|
|
||
|
class ClickablePopup(PandaNode, DirectObject):
|
||
|
CS_NORMAL = 0
|
||
|
CS_CLICK = 1
|
||
|
CS_HOVER = 2
|
||
|
CS_DISABLED = 3
|
||
|
|
||
|
def __init__(self, cam=None):
|
||
|
PandaNode.__init__(self, 'popup')
|
||
|
DirectObject.__init__(self)
|
||
|
|
||
|
self.__mwn = NametagGlobals.mouseWatcher
|
||
|
self.__name = 'clickregion-%d' % id(self)
|
||
|
|
||
|
self.__cam = cam
|
||
|
self.__region = MouseWatcherRegion(self.__name, 0, 0, 0, 0)
|
||
|
self.__mwn.addRegion(self.__region)
|
||
|
|
||
|
self.__disabled = False
|
||
|
self.__clicked = False
|
||
|
self.__hovered = False
|
||
|
self.__onscreen = False
|
||
|
self.__clickState = 0
|
||
|
self.__clickArgs = []
|
||
|
|
||
|
self.__clickEvent = ''
|
||
|
|
||
|
self.accept(self.__getEvent(self.__mwn.getEnterPattern()), self.__mouseEnter)
|
||
|
self.accept(self.__getEvent(self.__mwn.getLeavePattern()), self.__mouseLeave)
|
||
|
self.accept(self.__getEvent(self.__mwn.getButtonDownPattern()), self.__buttonDown)
|
||
|
self.accept(self.__getEvent(self.__mwn.getButtonUpPattern()), self.__buttonUp)
|
||
|
|
||
|
def destroy(self):
|
||
|
self.__mwn.removeRegion(self.__region)
|
||
|
self.ignoreAll()
|
||
|
|
||
|
def setClickRegionEvent(self, event, clickArgs=[]):
|
||
|
if event is None:
|
||
|
# The caller is disabling us, so instead:
|
||
|
self.__disabled = True
|
||
|
self.__region.setActive(False)
|
||
|
self.__updateClickState()
|
||
|
else:
|
||
|
self.__clickEvent = event
|
||
|
self.__clickArgs = clickArgs
|
||
|
self.__disabled = False
|
||
|
self.__region.setActive(True)
|
||
|
self.__updateClickState()
|
||
|
|
||
|
def getClickState(self):
|
||
|
return self.__clickState
|
||
|
|
||
|
def clickStateChanged(self):
|
||
|
pass # Intended for subclasses.
|
||
|
|
||
|
def __getEvent(self, pattern):
|
||
|
return pattern.replace('%r', self.__name)
|
||
|
|
||
|
def __mouseEnter(self, region, extra):
|
||
|
self.__hovered = True
|
||
|
self.__updateClickState()
|
||
|
|
||
|
def __mouseLeave(self, region, extra):
|
||
|
self.__hovered = False
|
||
|
self.__updateClickState()
|
||
|
|
||
|
def __buttonDown(self, region, button):
|
||
|
if button == 'mouse1':
|
||
|
self.__clicked = True
|
||
|
self.__updateClickState()
|
||
|
|
||
|
def __buttonUp(self, region, button):
|
||
|
if button == 'mouse1':
|
||
|
self.__clicked = False
|
||
|
self.__updateClickState()
|
||
|
|
||
|
def __updateClickState(self):
|
||
|
if self.__disabled:
|
||
|
state = self.CS_DISABLED
|
||
|
elif self.__clicked:
|
||
|
state = self.CS_CLICK
|
||
|
elif self.__hovered:
|
||
|
state = self.CS_HOVER
|
||
|
else:
|
||
|
state = self.CS_NORMAL
|
||
|
|
||
|
if self.__clickState == state: return
|
||
|
oldState = self.__clickState
|
||
|
self.__clickState = state
|
||
|
|
||
|
if oldState == self.CS_NORMAL and state == self.CS_HOVER:
|
||
|
# Play rollover sound:
|
||
|
base.playSfx(NametagGlobals.rolloverSound)
|
||
|
elif state == self.CS_CLICK:
|
||
|
# Play click sound:
|
||
|
base.playSfx(NametagGlobals.clickSound)
|
||
|
elif oldState == self.CS_CLICK and state == self.CS_HOVER:
|
||
|
# Fire click event:
|
||
|
messenger.send(self.__clickEvent, self.__clickArgs)
|
||
|
|
||
|
self.clickStateChanged()
|
||
|
|
||
|
def updateClickRegion(self, left, right, bottom, top, offset=0):
|
||
|
transform = NodePath.anyPath(self).getNetTransform()
|
||
|
|
||
|
if self.__cam:
|
||
|
# We have a camera, so get its transform and move our net transform
|
||
|
# into the coordinate space of the camera:
|
||
|
camTransform = self.__cam.getNetTransform()
|
||
|
transform = camTransform.invertCompose(transform)
|
||
|
|
||
|
# We must discard the rotational component on our transform, thus:
|
||
|
transform = transform.setQuat(Quat())
|
||
|
|
||
|
# Next, we'll transform the frame into camspace:
|
||
|
mat = transform.getMat()
|
||
|
cTopLeft = mat.xformPoint(Point3(left, 0, top))
|
||
|
cBottomRight = mat.xformPoint(Point3(right, 0, bottom))
|
||
|
|
||
|
# Shift along the offset while in camspace, not worldspace.
|
||
|
if offset:
|
||
|
mid = mat.xformPoint(Point3(0,0,0))
|
||
|
length = mid.length()
|
||
|
shift = mid*(length - offset)/length - mid
|
||
|
cTopLeft += shift
|
||
|
cBottomRight += shift
|
||
|
|
||
|
if self.__cam:
|
||
|
# We must go further and project to screenspace:
|
||
|
lens = self.__cam.node().getLens()
|
||
|
|
||
|
sTopLeft = Point2()
|
||
|
sBottomRight = Point2()
|
||
|
|
||
|
if not (lens.project(Point3(cTopLeft), sTopLeft) and
|
||
|
lens.project(Point3(cBottomRight), sBottomRight)):
|
||
|
# Not on-screen! Disable the click region:
|
||
|
self.__region.setActive(False)
|
||
|
self.__onscreen = False
|
||
|
return
|
||
|
else:
|
||
|
# No cam; the "camspace" (actually just net transform) IS the
|
||
|
# screenspace transform.
|
||
|
sTopLeft = Point2(cTopLeft[0], cTopLeft[2])
|
||
|
sBottomRight = Point2(cBottomRight[0], cBottomRight[2])
|
||
|
|
||
|
sLeft, sTop = sTopLeft
|
||
|
sRight, sBottom = sBottomRight
|
||
|
|
||
|
self.__region.setFrame(sLeft, sRight, sBottom, sTop)
|
||
|
self.__region.setActive(not self.__disabled)
|
||
|
self.__onscreen = True
|
||
|
|
||
|
def stashClickRegion(self):
|
||
|
self.__region.setActive(False)
|
||
|
self.__onscreen = False
|
||
|
|
||
|
def isOnScreen(self):
|
||
|
return self.__onscreen
|