#!/usr/bin/python from __future__ import division,nested_scopes import sys sys.path.append("../../editor/pour") from skim.zooming import Zooming,Pair from math import sqrt,sin,cos from pygame.rect import Rect from xmlnodebase import xmlnodeclass,collectiveelement,xmldocfile import dispatcher import Tkinter as tk def pairdist(pair1,pair2): return sqrt((pair1[0]-pair2[0])**2+(pair1[1]-pair2[1])**2) def canvashighlighter(canvas,obj,attribute,normalval,highlightval): """creates bindings on a canvas obj that make attribute go from normal to highlight when the mouse is over the obj""" canvas.tag_bind(obj,"", lambda ev: canvas.itemconfig(obj,**{attribute:highlightval})) canvas.tag_bind(obj,"", lambda ev: canvas.itemconfig(obj,**{attribute:normalval})) class Field(xmlnodeclass): """one light has a field of influence. for any point on the canvas, you can ask this field how strong it is. """ def name(self,newval=None): """light/sub name""" return self._getorsetattr("name",newval) def center(self,x=None,y=None): """x,y float coords for the center of this light in the field. returns a Pair, although it accepts x,y""" return Pair(self._getorsettypedattr("x",float,x), self._getorsettypedattr("y",float,y)) def falloff(self,dist=None): """linear falloff from 1 at center, to 0 at dist pixels away from center""" return self._getorsettypedattr("falloff",float,dist) def getdistforintensity(self,intens): """returns the distance you'd have to be for the given intensity (0..1)""" return (1-intens)*self.falloff() def calc(self,x,y): """returns field strength at point x,y""" dist=pairdist(Pair(x,y),self.center()) return max(0,(self.falloff()-dist)/self.falloff()) class Fieldset(collectiveelement): """group of fields. persistent.""" def childtype(self): return Field def version(self): """read-only version attribute on fieldset tag""" return self._getorsetattr("version",None) def report(self,x,y): """reports active fields and their intensities""" active=0 for f in self.getall(): name=f.name() intens=f.calc(x,y) if intens>0: print name,intens, active+=1 if active>0: print def getbounds(self): """returns xmin,xmax,ymin,ymax for the non-zero areas of this field""" r=None for f in self.getall(): rad=f.getdistforintensity(0) fx,fy=f.center() fieldrect=Rect(fx-rad,fy-rad,rad*2,rad*2) if r is None: r=fieldrect else: r=r.union(fieldrect) return r.left,r.right,r.top,r.bottom class Fieldsetfile(xmldocfile): def __init__(self,filename): self._openornew(filename,topleveltype=Fieldset) def fieldset(self): return self._gettoplevel() ######################################################################## ######################################################################## class FieldDisplay: """the view for a Field.""" def __init__(self,canvas,field): self.canvas=canvas self.field=field self.tags=[str(id(self))] # canvas tag to id our objects def setcoords(self): """adjust canvas obj coords to match the field""" # this uses the canvas object ids saved by makeobjs f=self.field c=self.canvas w2c=self.canvas.world2canvas # rings for intens,ring in self.rings.items(): rad=f.getdistforintensity(intens) p1=w2c(*(f.center()-Pair(rad,rad))) p2=w2c(*(f.center()+Pair(rad,rad))) c.coords(ring,p1[0],p1[1],p2[0],p2[1]) # text p1=w2c(*f.center()) c.coords(self.txt,*p1) def makeobjs(self): """(re)create the canvas objs (null coords) and make their bindings""" c=self.canvas f=self.field c.delete(self.tags) w2c=self.canvas.world2canvas # make rings self.rings={} # rad,canvasobj for intens,color in (#(1,'white'), (.8,'gray90'),(.6,'gray80'),(.4,'gray60'),(.2,'gray50'), (0,'#000080')): self.rings[intens]=c.create_oval(0,0,0,0, outline=color,width=2,tags=self.tags, outlinestipple='gray50') # make text self.txt=c.create_text(0,0,text=f.name(),font="arial 10 bold", fill='white',anchor='c', tags=self.tags) # highlight text bindings canvashighlighter(c,self.txt,'fill',normalval='white',highlightval='red') # position drag bindings def press(ev): self._lastmouse=ev.x,ev.y def motion(ev): dcan=Pair(*[a-b for a,b in zip((ev.x,ev.y),self._lastmouse)]) dworld=c.canvas2world_vector(*dcan) self.field.center(*(self.field.center()+dworld)) self._lastmouse=ev.x,ev.y dispatcher.send("field coord changed") self.setcoords() # redraw def release(ev): del self._lastmouse c.tag_bind(self.txt,"",press) c.tag_bind(self.txt,"",motion) c.tag_bind(self.txt,"",release) # radius drag bindings outerring=self.rings[0] canvashighlighter(c,outerring, 'outline',normalval='#000080',highlightval='#4040ff') def motion(ev): worldmouse=self.canvas.canvas2world(ev.x,ev.y) currentdist=pairdist(worldmouse,self.field.center()) self.field.falloff(currentdist) dispatcher.send("field coord changed") self.setcoords() c.tag_bind(outerring,"",motion) self.setcoords() class Tracker(tk.Frame): """whole tracker widget, which is mostly a view for a Fieldset. tracker makes its own fieldset""" # world coords of the visible canvas (preserved even in window resizes) xmin=0 xmax=100 ymin=0 ymax=100 fieldsetfile=None displays=None # Field : FieldDisplay. we keep these in sync with the fieldset def __init__(self,master): tk.Frame.__init__(self,master) self.displays={} c=self.canvas=Zooming(self,bg='black',closeenough=5) c.pack(fill='both',exp=1) # preserve edge coords over window resize c.bind("",self.configcoords) c.bind("", lambda ev: self._fieldset().report(*c.canvas2world(ev.x,ev.y))) def save(ev): print "saving" self.fieldsetfile.save() master.bind("",save) dispatcher.connect(self.autobounds,"field coord changed") def _fieldset(self): return self.fieldsetfile.fieldset() def load(self,filename): self.fieldsetfile=Fieldsetfile(filename) self.displays.clear() for f in self.fieldsetfile.fieldset().getall(): self.displays[f]=FieldDisplay(self.canvas,f) self.displays[f].makeobjs() self.autobounds() def configcoords(self,*args): # force our canvas coords to stay at the edges of the window c=self.canvas cornerx,cornery=c.canvas2world(0,0) c.move(cornerx-self.xmin, cornery-self.ymin) c.setscale(0,0, c.winfo_width()/(self.xmax-self.xmin), c.winfo_height()/(self.ymax-self.ymin)) def autobounds(self): """figure out our bounds from the fieldset, and adjust the display zooms. writes the corner coords onto the canvas.""" self.xmin,self.xmax,self.ymin,self.ymax=self._fieldset().getbounds() self.configcoords() c=self.canvas c.delete('cornercoords') for x,anc2 in ((self.xmin,'w'),(self.xmax,'e')): for y,anc1 in ((self.ymin,'n'),(self.ymax,'s')): pos=c.world2canvas(x,y) c.create_text(pos[0],pos[1],text="%s,%s"%(x,y), fill='white',anchor=anc1+anc2, tags='cornercoords') [d.setcoords() for d in self.displays.values()] ######################################################################## ######################################################################## root=tk.Tk() tra=Tracker(root) tra.pack(fill='both',exp=1) tra.load("fieldsets/demo") root.mainloop()