Source code for direct.tkwidgets.Floater
"""
Floater Class: Velocity style controller for floating point values with
a label, entry (validated), and scale
"""
__all__ = ['Floater', 'FloaterWidget', 'FloaterGroup']
from .Valuator import Valuator, VALUATOR_MINI, VALUATOR_FULL
from direct.task import Task
from direct.task.TaskManagerGlobal import taskMgr
from panda3d.core import ClockObject
import math
import Pmw
import tkinter as tk
FLOATER_WIDTH = 22
FLOATER_HEIGHT = 18
[docs]class Floater(Valuator):
[docs] def __init__(self, parent = None, **kw):
INITOPT = Pmw.INITOPT
optiondefs = (
('style', VALUATOR_MINI, INITOPT),
)
self.defineoptions(kw, optiondefs)
# Initialize the superclass
Valuator.__init__(self, parent)
self.initialiseoptions(Floater)
[docs] def createValuator(self):
self._valuator = self.createcomponent('valuator',
(('floater', 'valuator'),),
None,
FloaterWidget,
(self.interior(),),
command = self.setEntry,
value = self['value'])
self._valuator._widget.bind('<Double-ButtonPress-1>', self.mouseReset)
[docs] def packValuator(self):
# Position components
if self._label:
self._label.grid(row=0, column=0, sticky = tk.EW)
self._entry.grid(row=0, column=1, sticky = tk.EW)
self._valuator.grid(row=0, column=2, padx = 2, pady = 2)
self.interior().columnconfigure(0, weight = 1)
[docs]class FloaterWidget(Pmw.MegaWidget):
[docs] def __init__(self, parent = None, **kw):
#define the megawidget options
INITOPT = Pmw.INITOPT
optiondefs = (
# Appearance
('width', FLOATER_WIDTH, INITOPT),
('height', FLOATER_HEIGHT, INITOPT),
('relief', tk.RAISED, self.setRelief),
('borderwidth', 2, self.setBorderwidth),
('background', 'grey75', self.setBackground),
# Behavior
# Initial value of floater, use self.set to change value
('value', 0.0, INITOPT),
('numDigits', 2, self.setNumDigits),
# Command to execute on floater updates
('command', None, None),
# Extra data to be passed to command function
('commandData', [], None),
# Callback's to execute during mouse interaction
('preCallback', None, None),
('postCallback', None, None),
# Extra data to be passed to callback function, needs to be a list
('callbackData', [], None),
)
self.defineoptions(kw, optiondefs)
# Initialize the superclass
Pmw.MegaWidget.__init__(self, parent)
# Set up some local and instance variables
# Create the components
interior = self.interior()
# Current value
self.value = self['value']
# The canvas
width = self['width']
height = self['height']
self._widget = self.createcomponent('canvas', (), None,
tk.Canvas, (interior,),
width = width,
height = height,
background = self['background'],
highlightthickness = 0,
scrollregion = (-width/2.0,
-height/2.0,
width/2.0,
height/2.0))
self._widget.pack(expand = 1, fill = tk.BOTH)
# The floater icon
self._widget.create_polygon(-width/2.0, 0, -2.0, -height/2.0,
-2.0, height/2.0,
fill = 'grey50',
tags = ('floater',))
self._widget.create_polygon(width/2.0, 0, 2.0, height/2.0,
2.0, -height/2.0,
fill = 'grey50',
tags = ('floater',))
# Add event bindings
self._widget.bind('<ButtonPress-1>', self.mouseDown)
self._widget.bind('<B1-Motion>', self.updateFloaterSF)
self._widget.bind('<ButtonRelease-1>', self.mouseUp)
self._widget.bind('<Enter>', self.highlightWidget)
self._widget.bind('<Leave>', self.restoreWidget)
# Make sure input variables processed
self.initialiseoptions(FloaterWidget)
[docs] def set(self, value, fCommand = 1):
"""
self.set(value, fCommand = 1)
Set floater to new value, execute command if fCommand == 1
"""
# Send command if any
if fCommand and (self['command'] is not None):
self['command'](*[value] + self['commandData'])
# Record value
self.value = value
## Canvas callback functions
# Floater velocity controller
[docs] def mouseDown(self, event):
""" Begin mouse interaction """
# Exectute user redefinable callback function (if any)
self['relief'] = tk.SUNKEN
if self['preCallback']:
self['preCallback'](*self['callbackData'])
self.velocitySF = 0.0
self.updateTask = taskMgr.add(self.updateFloaterTask,
'updateFloater')
self.updateTask.lastTime = ClockObject.getGlobalClock().getFrameTime()
[docs] def updateFloaterTask(self, state):
"""
Update floaterWidget value based on current scaleFactor
Adjust for time to compensate for fluctuating frame rates
"""
currT = ClockObject.getGlobalClock().getFrameTime()
dt = currT - state.lastTime
self.set(self.value + self.velocitySF * dt)
state.lastTime = currT
return Task.cont
[docs] def updateFloaterSF(self, event):
"""
Update velocity scale factor based of mouse distance from origin
"""
x = self._widget.canvasx(event.x)
y = self._widget.canvasy(event.y)
offset = max(0, abs(x) - Valuator.deadband)
if offset == 0:
return 0
sf = math.pow(Valuator.sfBase,
self.minExp + offset/Valuator.sfDist)
if x > 0:
self.velocitySF = sf
else:
self.velocitySF = -sf
[docs] def mouseUp(self, event):
taskMgr.remove(self.updateTask)
self.velocitySF = 0.0
# Execute user redefinable callback function (if any)
if self['postCallback']:
self['postCallback'](*self['callbackData'])
self['relief'] = tk.RAISED
[docs] def setNumDigits(self):
"""
Adjust minimum exponent to use in velocity task based
upon the number of digits to be displayed in the result
"""
self.minExp = math.floor(-self['numDigits']/
math.log10(Valuator.sfBase))
# Methods to modify floater characteristics
[docs]class FloaterGroup(Pmw.MegaToplevel):
[docs] def __init__(self, parent = None, **kw):
# Default group size
DEFAULT_DIM = 1
# Default value depends on *actual* group size, test for user input
DEFAULT_VALUE = [0.0] * kw.get('dim', DEFAULT_DIM)
DEFAULT_LABELS = ['v[%d]' % x for x in range(kw.get('dim', DEFAULT_DIM))]
#define the megawidget options
INITOPT = Pmw.INITOPT
optiondefs = (
('dim', DEFAULT_DIM, INITOPT),
('side', tk.TOP, INITOPT),
('title', 'Floater Group', None),
# A tuple of initial values, one for each floater
('value', DEFAULT_VALUE, INITOPT),
# The command to be executed any time one of the floaters is updated
('command', None, None),
# A tuple of labels, one for each floater
('labels', DEFAULT_LABELS, self._updateLabels),
)
self.defineoptions(kw, optiondefs)
# Initialize the toplevel widget
Pmw.MegaToplevel.__init__(self, parent)
# Create the components
interior = self.interior()
# Get a copy of the initial value (making sure its a list)
self._value = list(self['value'])
# The Menu Bar
self.balloon = Pmw.Balloon()
menubar = self.createcomponent('menubar', (), None,
Pmw.MenuBar, (interior,),
balloon = self.balloon)
menubar.pack(fill=tk.X)
# FloaterGroup Menu
menubar.addmenu('Floater Group', 'Floater Group Operations')
menubar.addmenuitem(
'Floater Group', 'command', 'Reset the Floater Group panel',
label = 'Reset',
command = lambda s = self: s.reset())
menubar.addmenuitem(
'Floater Group', 'command', 'Dismiss Floater Group panel',
label = 'Dismiss', command = self.withdraw)
menubar.addmenu('Help', 'Floater Group Help Operations')
self.toggleBalloonVar = tk.IntVar()
self.toggleBalloonVar.set(0)
menubar.addmenuitem('Help', 'checkbutton',
'Toggle balloon help',
label = 'Balloon Help',
variable = self.toggleBalloonVar,
command = self.toggleBalloon)
self.floaterList = []
for index in range(self['dim']):
# Add a group alias so you can configure the floaters via:
# fg.configure(Valuator_XXX = YYY)
f = self.createcomponent(
'floater%d' % index, (), 'Valuator', Floater,
(interior,), value = self._value[index],
text = self['labels'][index])
# Do this separately so command doesn't get executed during construction
f['command'] = lambda val, s=self, i=index: s._floaterSetAt(i, val)
f.pack(side = self['side'], expand = 1, fill = tk.X)
self.floaterList.append(f)
# Make sure floaters are initialized
self.set(self['value'])
# Make sure input variables processed
self.initialiseoptions(FloaterGroup)
def _updateLabels(self):
if self['labels']:
for index in range(self['dim']):
self.floaterList[index]['text'] = self['labels'][index]
[docs] def toggleBalloon(self):
if self.toggleBalloonVar.get():
self.balloon.configure(state = 'balloon')
else:
self.balloon.configure(state = 'none')
# This is the command is used to set the groups value
[docs] def set(self, value, fCommand = 1):
for i in range(self['dim']):
self._value[i] = value[i]
# Update floater, but don't execute its command
self.floaterList[i].set(value[i], 0)
if fCommand and (self['command'] is not None):
self['command'](self._value)
[docs] def setAt(self, index, value):
# Update floater and execute its command
self.floaterList[index].set(value)
# This is the command used by the floater
def _floaterSetAt(self, index, value):
self._value[index] = value
if self['command']:
self['command'](self._value)