DirectRadioButton.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. """A DirectRadioButton is a type of button that, similar to a
  2. DirectCheckButton, has a separate indicator and can be toggled between
  3. two states. However, only one DirectRadioButton in a group can be enabled
  4. at a particular time.
  5. See the :ref:`directradiobutton` page in the programming manual for a more
  6. in-depth explanation and an example of how to use this class.
  7. """
  8. __all__ = ['DirectRadioButton']
  9. from panda3d.core import PGFrameStyle, VBase4
  10. from . import DirectGuiGlobals as DGG
  11. from .DirectButton import DirectButton
  12. from .DirectLabel import DirectLabel
  13. class DirectRadioButton(DirectButton):
  14. """
  15. DirectRadioButton(parent) - Create a DirectGuiWidget which responds
  16. to mouse clicks by setting given value to given variable and
  17. execute a callback function (passing that state through) if defined
  18. """
  19. def __init__(self, parent = None, **kw):
  20. # Inherits from DirectButton
  21. # A Direct Frame can have:
  22. # - A background texture (pass in path to image, or Texture Card)
  23. # - A midground geometry item (pass in geometry)
  24. # - A foreground text Node (pass in text string or Onscreen Text)
  25. # For a direct button:
  26. # Each button has 4 states (ready, press, rollover, disabled)
  27. # The same image/geom/text can be used for all four states or each
  28. # state can have a different text/geom/image
  29. # State transitions happen automatically based upon mouse interaction
  30. # Responds to click event and calls command if None
  31. self.colors = None
  32. optiondefs = (
  33. ('indicatorValue', 0, self.setIndicatorValue),
  34. # variable is a list whose value will be set by this radio button
  35. ('variable', [], None),
  36. # value is the value to be set when this radio button is selected
  37. ('value', [], None),
  38. # others is a list of other radio buttons sharing same variable
  39. ('others', [], None),
  40. # boxBorder defines the space created around the check box
  41. ('boxBorder', 0, None),
  42. # boxPlacement maps left, above, right, below
  43. ('boxPlacement', 'left', None),
  44. # boxGeom defines geom to indicate current radio button is selected or not
  45. ('boxGeom', None, None),
  46. ('boxGeomColor', None, None),
  47. ('boxGeomScale', 1.0, None),
  48. ('boxImage', None, None),
  49. ('boxImageScale', 1.0, None),
  50. ('boxImageColor', VBase4(1, 1, 1, 1), None),
  51. ('boxRelief', None, None),
  52. )
  53. # Merge keyword options with default options
  54. self.defineoptions(kw, optiondefs)
  55. # Initialize superclasses
  56. DirectButton.__init__(self, parent)
  57. self.indicator = self.createcomponent("indicator", (), None,
  58. DirectLabel, (self,),
  59. numStates = 2,
  60. image = self['boxImage'],
  61. image_scale = self['boxImageScale'],
  62. image_color = self['boxImageColor'],
  63. geom = self['boxGeom'],
  64. geom_scale = self['boxGeomScale'],
  65. geom_color = self['boxGeomColor'],
  66. state = 'disabled',
  67. text = ('X', 'X'),
  68. relief = self['boxRelief'],
  69. )
  70. # Call option initialization functions
  71. self.initialiseoptions(DirectRadioButton)
  72. # After initialization with X giving it the correct size, put back space
  73. if self['boxGeom'] is None:
  74. if 'boxRelief' not in kw and self['boxImage'] is None:
  75. self.indicator['relief'] = DGG.SUNKEN
  76. self.indicator['text'] = (' ', '*')
  77. self.indicator['text_pos'] = (0, -.25)
  78. else:
  79. self.indicator['text'] = (' ', ' ')
  80. if self['boxGeomColor'] is not None and self['boxGeom'] is not None:
  81. self.colors = [VBase4(1, 1, 1, 0), self['boxGeomColor']]
  82. self.component('indicator')['geom_color'] = VBase4(1, 1, 1, 0)
  83. needToCheck = True
  84. if len(self['value']) == len(self['variable']) != 0:
  85. for i in range(len(self['value'])):
  86. if self['variable'][i] != self['value'][i]:
  87. needToCheck = False
  88. break
  89. if needToCheck:
  90. self.check()
  91. # Override the resetFrameSize of DirectGuiWidget inorder to provide space for label
  92. def resetFrameSize(self):
  93. self.setFrameSize(fClearFrame = 1)
  94. def setFrameSize(self, fClearFrame = 0):
  95. if self['frameSize']:
  96. # Use user specified bounds
  97. self.bounds = self['frameSize']
  98. frameType = self.frameStyle[0].getType()
  99. ibw = self.indicator['borderWidth']
  100. else:
  101. # Use ready state to compute bounds
  102. frameType = self.frameStyle[0].getType()
  103. if fClearFrame and (frameType != PGFrameStyle.TNone):
  104. self.frameStyle[0].setType(PGFrameStyle.TNone)
  105. self.guiItem.setFrameStyle(0, self.frameStyle[0])
  106. # To force an update of the button
  107. self.guiItem.getStateDef(0)
  108. # Clear out frame before computing bounds
  109. self.getBounds()
  110. # Restore frame style if necessary
  111. if frameType != PGFrameStyle.TNone:
  112. self.frameStyle[0].setType(frameType)
  113. self.guiItem.setFrameStyle(0, self.frameStyle[0])
  114. # Ok, they didn't set specific bounds,
  115. # let's add room for the label indicator
  116. # get the difference in height
  117. ibw = self.indicator['borderWidth']
  118. indicatorWidth = (self.indicator.getWidth() + (2*ibw[0]))
  119. indicatorHeight = (self.indicator.getHeight() + (2*ibw[1]))
  120. diff = (indicatorHeight + (2*self['boxBorder']) -
  121. (self.bounds[3] - self.bounds[2]))
  122. # If background is smaller then indicator, enlarge background
  123. if diff > 0:
  124. if self['boxPlacement'] == 'left': #left
  125. self.bounds[0] += -(indicatorWidth + (2*self['boxBorder']))
  126. self.bounds[3] += diff/2
  127. self.bounds[2] -= diff/2
  128. elif self['boxPlacement'] == 'below': #below
  129. self.bounds[2] += -(indicatorHeight+(2*self['boxBorder']))
  130. elif self['boxPlacement'] == 'right': #right
  131. self.bounds[1] += indicatorWidth + (2*self['boxBorder'])
  132. self.bounds[3] += diff/2
  133. self.bounds[2] -= diff/2
  134. else: #above
  135. self.bounds[3] += indicatorHeight + (2*self['boxBorder'])
  136. # Else make space on correct side for indicator
  137. else:
  138. if self['boxPlacement'] == 'left': #left
  139. self.bounds[0] += -(indicatorWidth + (2*self['boxBorder']))
  140. elif self['boxPlacement'] == 'below': #below
  141. self.bounds[2] += -(indicatorHeight + (2*self['boxBorder']))
  142. elif self['boxPlacement'] == 'right': #right
  143. self.bounds[1] += indicatorWidth + (2*self['boxBorder'])
  144. else: #above
  145. self.bounds[3] += indicatorHeight + (2*self['boxBorder'])
  146. # Set frame to new dimensions
  147. if frameType != PGFrameStyle.TNone and frameType != PGFrameStyle.TFlat:
  148. bw = self['borderWidth']
  149. else:
  150. bw = (0, 0)
  151. # Set frame to new dimensions
  152. self.guiItem.setFrame(
  153. self.bounds[0] - bw[0],
  154. self.bounds[1] + bw[0],
  155. self.bounds[2] - bw[1],
  156. self.bounds[3] + bw[1])
  157. # If they didn't specify a position, put it in the center of new area
  158. if not self.indicator['pos']:
  159. bbounds = self.bounds
  160. lbounds = self.indicator.bounds
  161. newpos = [0, 0, 0]
  162. if self['boxPlacement'] == 'left': #left
  163. newpos[0] += bbounds[0]-lbounds[0] + self['boxBorder'] + ibw[0]
  164. dropValue = (bbounds[3]-bbounds[2]-lbounds[3]+lbounds[2])/2 + self['boxBorder']
  165. newpos[2] += (bbounds[3]-lbounds[3] + self['boxBorder'] -
  166. dropValue)
  167. elif self['boxPlacement'] == 'right': #right
  168. newpos[0] += bbounds[1]-lbounds[1] - self['boxBorder'] - ibw[0]
  169. dropValue = (bbounds[3]-bbounds[2]-lbounds[3]+lbounds[2])/2 + self['boxBorder']
  170. newpos[2] += (bbounds[3]-lbounds[3] + self['boxBorder']
  171. - dropValue)
  172. elif self['boxPlacement'] == 'above': #above
  173. newpos[2] += bbounds[3]-lbounds[3] - self['boxBorder'] - ibw[1]
  174. else: #below
  175. newpos[2] += bbounds[2]-lbounds[2] + self['boxBorder'] + ibw[1]
  176. self.indicator.setPos(newpos[0], newpos[1], newpos[2])
  177. def commandFunc(self, event):
  178. if len(self['value']) == len(self['variable']) != 0:
  179. for i in range(len(self['value'])):
  180. self['variable'][i] = self['value'][i]
  181. self.check()
  182. def check(self):
  183. self['indicatorValue'] = 1
  184. self.setIndicatorValue()
  185. for other in self['others']:
  186. if other != self:
  187. other.uncheck()
  188. if self['command']:
  189. # Pass any extra args to command
  190. self['command'](*self['extraArgs'])
  191. def setOthers(self, others):
  192. self['others'] = others
  193. def uncheck(self):
  194. self['indicatorValue'] = 0
  195. if self.colors is not None:
  196. self.component('indicator')['geom_color'] = self.colors[self['indicatorValue']]
  197. def setIndicatorValue(self):
  198. self.component('indicator').guiItem.setState(self['indicatorValue'])
  199. if self.colors is not None:
  200. self.component('indicator')['geom_color'] = self.colors[self['indicatorValue']]