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