Controls module
Bruce Sherwood <[email protected]> Mon, 23 Jan 2012 10:13:32 -0700
| Newsgroups | gmane.comp.python.visualpython.user |
|---|---|
| Message-ID | <CAFDG03iAd1q2vgx9zRhj4Pp847aaqjDC5oFPgPv6PTPSNt7OaA@mail.gmail.com> |
Attached is a replacement for the file site-packages/vis/controls.py. The attached file fixes bugs in specifying slider min and max different from the default values of 0 and 100. This fix will of course be included in later releases of VPython. I thank Frank Noschese for reporting the bug. Bruce Sherwood ------------------------------------------------------------------------------ Try before you buy = See our experts in action! The most comprehensive online learning library for Microsoft developers is just $99.99! Visual Studio, SharePoint, SQL - plus HTML5, CSS3, MVC3, Metro Style Apps, more. Free future releases when you subscribe now! http://p.sf.net/sfu/learndevnow-dev2 _______________________________________________ Visualpython-users mailing list Visualpython-users-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f@public.gmane.org https://lists.sourceforge.net/lists/listinfo/visualpython-users
controls.py
(application/octet-stream, 16.4 KB)
from .cvisual import vector, mag, norm, dot
from .primitives import (box, cylinder, distant_light, frame, label, sphere)
from .ui import display
from . import crayola
color = crayola
from math import pi, sin, cos
import time
from threading import Thread, RLock
# Bruce Sherwood, begun March 2002
# Import this module to create buttons, toggle switches, sliders, and pull-down menus.
# See controlstest.py in the VPython example programs for an example of how to use controls.
lastcontrols = None # the most recently created controls window
gray = (0.7, 0.7, 0.7)
darkgray = (0.5, 0.5, 0.5)
cdisplays = []
thread_started = False
lock = RLock()
class timer(Thread): # This timer thread watches for mouse events in all gdisplays.
# The only thread problem is that a cdisplay might be removed during this,
# but that shouldn't matter, given what's done here.
# See linuxgazette.net/107/pai.html
def run(self):
while True:
lock.acquire()
if len(cdisplays) > 0:
try:
for c in cdisplays:
if c.display.visible: c.mouse()
except:
pass
lock.release()
time.sleep(0.05)
class controls: # make a special window for buttons, sliders, and pull-down menus
def __init__(self, x=0, y=0, width=300, height=320, range=100,
title=None, foreground=None, background=None):
global lastcontrols
global cdisplays
lastcontrols = self
currentdisplay = display.get_selected()
self.x = x
self.y = y
self.width = width
self.height = height
self.range = range
self.title = title
if title is None:
title = 'Controls'
if foreground is None:
foreground = color.white
if background is None:
background = color.black
self.foreground = foreground
self.background = background
self.display = display(title=title, x=x, y=y, range=range,
width=width, height=height, fov=0.4,
foreground=foreground, background=background,
userzoom=0, userspin=0)
self.display.lights=[distant_light(direction=(0,0,1),color=color.white)]
self.focus = None
self.lastpos = None
self.controllist = []
currentdisplay.select()
cdisplays.append(self)
global thread_started
if not thread_started:
thread_started = True
thr = timer()
thr.start()
def __del__(self):
self.visible = False
try:
cdisplays.remove(self)
except:
pass
def addcontrol(self, control):
self.controllist.append(control)
def interact(self):
# For backward compatibility; no longer necessary to call interact()
pass
def mouse(self):
if self.display.mouse.events:
m = self.display.mouse.getevent()
if m.press == 'left' and m.pick:
picked = m.pick
if self.focus: # have been moving over menu with mouse up
picked = self.focus
for control in self.controllist:
if control.active is picked:
self.focus = control
control.highlight(m.pos)
elif m.release == 'left':
focus = self.focus
self.focus = None # menu may reset self.focus for "sticky" menu
if focus:
focus.unhighlight(m.pos)
elif self.focus: # if dragging a control
pos = self.display.mouse.pos
if pos != self.lastpos:
self.focus.update(pos)
self.lastpos = pos
class ctrl(object): # common aspects of buttons, sliders, and menus
# Note: ctrl is a subclass of "object" in order to be a new-type class which
# permits use of the new "property" feature exploited by buttons and sliders.
def __init__(self, args):
if 'controls' in args:
self.controls = args['controls']
elif lastcontrols is None:
self.controls = controls()
else:
self.controls = lastcontrols
self.controls.addcontrol(self)
self.pos = vector(0,0)
self.action = None
if 'pos' in args:
self.pos = vector(args['pos'])
if 'value' in args:
self.value = args['value']
if 'action' in args:
self.action = args['action']
def highlight(self, pos):
pass
def unhighlight(self, pos):
pass
def update(self, pos):
pass
def execute(self):
if self.action:
self.action()
class button(ctrl):
def __init__(self, **args):
self.type = 'button'
self.value = 0
ctrl.__init__(self, args)
width = height = 40
bcolor = gray
edge = darkgray
self.__text = ''
if 'width' in args:
width = args['width']
if 'height' in args:
height = args['height']
if 'text' in args:
self.__text = args['text']
if 'color' in args:
bcolor = args['color']
disp = self.controls.display
framewidth = width/10.
self.thick = 2.*framewidth
self.box1 = box(display=disp, pos=self.pos+vector(0,height/2.-framewidth/2.,0),
size=(width,framewidth,self.thick), color=edge)
self.box2 = box(display=disp, pos=self.pos+vector(-width/2.+framewidth/2.,0,0),
size=(framewidth,height,self.thick), color=edge)
self.box3 = box(display=disp, pos=self.pos+vector(width/2.-framewidth/2.,0,0),
size=(framewidth,height,self.thick), color=edge)
self.box4 = box(display=disp, pos=self.pos+vector(0,-height/2.+framewidth/2.,0),
size=(width,framewidth,self.thick), color=edge)
self.button = box(display=disp, pos=self.pos+vector(0,0,self.thick/2.+1.),
size=(width-2.*framewidth,height-2.*framewidth,self.thick), color=bcolor)
self.label = label(display=disp, pos=self.button.pos, color=color.black,
text=self.__text, line=0, box=0, opacity=0)
self.active = self.button
def gettext(self):
return self.label.text
def settext(self, text):
self.label.text = text
text = property(gettext, settext) # establishes special getattr/setattr handling
def highlight(self, pos):
self.button.pos.z -= self.thick
self.label.pos.z -= self.thick
self.value = 1
def unhighlight(self, pos):
self.button.pos.z += self.thick
self.label.pos.z += self.thick
self.value = 0
self.execute()
class toggle(ctrl):
def __init__(self, **args):
self.type = 'toggle'
self.__value = 0
ctrl.__init__(self, args)
width = height = 20
self.angle = pi/6. # max rotation of toggle
bcolor = gray
edge = darkgray
self.__text0 = ''
self.__text1 = ''
if 'width' in args:
width = args['width']
if 'height' in args:
height = args['height']
if 'text0' in args:
self.__text0 = args['text0']
if 'text1' in args:
self.__text1 = args['text1']
if 'color' in args:
bcolor = args['color']
if 'value' in args:
self.__value = args['value']
diskthick = width/4.
diskradius = height/2.
ballradius = 0.6*diskradius
self.rodlength = 1.2*diskradius+ballradius
disp = self.controls.display
self.frame = frame(display=disp, pos=self.pos, axis=(1,0,0))
self.back = box(display=disp, frame=self.frame, pos=(0,0,0),
size=(width,height,0.3*diskradius), color=darkgray)
self.disk1 = cylinder(display=disp, frame=self.frame, pos=(-diskthick,0,0),
axis=(-diskthick,0), radius=diskradius, color=gray)
self.disk2 = cylinder(display=disp, frame=self.frame, pos=(diskthick,0,0),
axis=(diskthick,0), radius=diskradius, color=gray)
self.rod = cylinder(display=disp, frame=self.frame, pos=(0,0,0),
axis=(0,0,self.rodlength), radius=width/8., color=gray)
self.ball = sphere(display=disp, frame=self.frame, pos=(0,0,self.rodlength),
radius=ballradius, color=gray)
self.label0 = label(display=disp, frame=self.frame, pos=(0,-1.0*height), text=self.__text0,
line=0, box=0, opacity=0)
self.label1 = label(display=disp, frame=self.frame, pos=(0,1.0*height), text=self.__text1,
line=0, box=0, opacity=0)
self.settoggle(self.__value)
self.active = self.ball
def settoggle(self, val):
self.__value = val
if val == 1:
newpos = self.rodlength*vector(0,sin(self.angle), cos(self.angle))
else:
newpos = self.rodlength*vector(0,-sin(self.angle), cos(self.angle))
self.rod.axis = newpos
self.ball.pos = newpos
def getvalue(self):
return self.__value
def setvalue(self, val):
self.settoggle(val)
self.__value = val
value = property(getvalue, setvalue) # establishes special getattr/setattr handling
def gettext0(self):
return self.label0.text
def settext0(self, text):
self.label0.text = text
text0 = property(gettext0, settext0) # establishes special getattr/setattr handling
def gettext1(self):
return self.label1.text
def settext1(self, text):
self.label1.text = text
text1 = property(gettext1, settext1) # establishes special getattr/setattr handling
def unhighlight(self, pos):
if self.controls.display.mouse.pick is self.active:
self.__value = not(self.__value)
self.settoggle(self.__value)
self.execute()
class slider(ctrl):
def __init__(self, **args):
self.type = 'slider'
self.canupdate = False # cannot update slider position until all attributes initialized
self.__value = 0
self.length = 100.
width = 10.
shaftcolor = darkgray
scolor = gray
self.min = 0.
self.max = 100.
self.axis = vector(1,0,0)
if 'axis' in args:
self.axis = vector(args['axis'])
self.length = mag(self.axis)
self.axis = norm(self.axis)
if 'length' in args:
self.length = args['length']
if 'width' in args:
width = args['width']
if 'min' in args:
self.min = args['min']
if self.__value < self.min:
self.__value = self.min
if 'max' in args:
self.max = args['max']
if self.__value > self.max:
self.__value = self.max
if 'color' in args:
scolor = args['color']
ctrl.__init__(self, args)
disp = self.controls.display
self.shaft = box(display=disp,
pos=self.pos+self.axis*self.length/2., axis=self.axis,
size=(self.length,0.5*width,0.5*width), color=shaftcolor)
self.indicator = box(display=disp,
pos=self.pos+self.axis*(self.__value-self.min)*self.length/(self.max-self.min),
axis=self.axis,
size=(width,width,width), color=scolor)
self.active = self.indicator
self.canupdate = True
def getvalue(self):
return self.__value
def setvalue(self, val):
if self.canupdate: self.update(self.pos+self.axis*(val-self.min)*self.length/(self.max-self.min))
self.__value = val
value = property(getvalue, setvalue) # establishes special getattr/setattr handling
def update(self, pos):
val = self.min+dot((pos-self.pos),self.axis)*(self.max-self.min)/self.length
if val < self.min:
val = self.min
elif val > self.max:
val = self.max
if val != self.__value:
self.indicator.pos = self.pos+self.axis*(val-self.min)*self.length/(self.max-self.min)
self.__value = val
self.execute()
class menu(ctrl):
def __init__(self, **args):
self.type = 'menu'
ctrl.__init__(self, args)
self.items = []
self.width = self.height = 40
self.text = 'Menu'
self.__value = None
self.color = gray
self.nitem = 0
self.open = 0 # true if menu display open in the window
self.action = 1 # dummy placeholder; what is driven is menu.execute()
if 'width' in args:
self.width = args['width']
if 'height' in args:
self.height = args['height']
if 'text' in args:
self.text = args['text']
if 'color' in args:
self.color = args['color']
self.thick = 0.2*self.width
disp = self.controls.display
self.active = box(display=disp, pos=self.pos+vector(0,0,self.thick),
size=(self.width,self.height,self.thick), color=self.color)
self.label = label(display=disp, pos=self.active.pos, color=color.black,
text=self.text, line=0, box=0, opacity=0)
def getvalue(self):
return self.__value
value = property(getvalue, None) # establishes special getattr/setattr handling
def inmenu(self, pos): # return item number (0-N) where mouse is, or -1
# note that item is 0 if mouse is in menu title
if self.pos.x-self.width/2. < pos.x < self.pos.x+self.width/2.:
nitem = int((self.pos.y+self.height/2.-pos.y)/self.height)
if 0 <= nitem <= len(self.items):
return(nitem)
else:
return(-1)
return(-1)
def highlight(self, pos): # mouse down: open the menu, displaying the menu items
self.nitem = self.inmenu(pos)
if self.open: # "sticky" menu already open
if self.nitem > 0:
self.update(pos)
else:
self.unhighlight(pos)
self.open = 0
return
pos = self.pos-vector(0,self.height,0)
self.boxes = []
self.highlightedbox = None
disp = self.controls.display
for item in self.items:
self.boxes.append( (box(display=disp, pos=pos+vector(0,0,self.thick),
size=(self.width,self.height,self.thick), color=self.color),
label(display=disp, pos=pos+vector(0,0,self.thick), color=color.black,
text=item[0], line=0, box=0, opacity=0)) )
pos = pos-vector(0,self.height,0)
def unhighlight(self, pos): # mouse up: close the menu; selected item will be executed
self.nitem = self.inmenu(pos)
if self.nitem == 0 and not self.open: # don't close if mouse up in menu title
self.controls.focus = self # restore menu to be in focus
self.open = 1
return
for box in self.boxes:
box[0].visible = 0
box[1].visible = 0
self.boxes = []
self.open = 0
self.execute()
def update(self, pos): # highlight an individual item during drag
self.nitem = self.inmenu(pos)
if self.nitem > 0:
if self.highlightedbox is not None:
self.highlightedbox.color = gray
if self.items[self.nitem-1][1]: # if there is an associated action
self.highlightedbox = self.boxes[self.nitem-1][0]
self.highlightedbox.color = darkgray
else:
if self.highlightedbox is not None:
self.highlightedbox.color = gray
self.highlightedbox = None
def execute(self):
if self.nitem > 0:
self.__value = self.items[self.nitem-1][0]
action = self.items[self.nitem-1][1]
if action:
action()