#!/usr/bin/env python # Die object classes for Tk Canvas # author: Matt McCredie import Tkinter as tk import random class Die(object): diecnt = 0 def __init__(self,canvas,side): self.canvas = canvas self.side = side self.tag = "die%d"%Die.diecnt self.pips = "die%d_pips"%Die.diecnt Die.diecnt += 1 self._value = 9 self._fillcolor = 'red' self._outlinecolor = 'black' self._pipfillcolor = 'white' self._pipoutlinecolor = 'black' def draw(self,x,y=None): if not y: x,y = x if self.tag: self.canvas.delete(self.tag) bevel = float(self.side/20) half = self.side / 2 qtr = half / 2 tag = self.canvas.create_polygon( x+bevel, y, x+half, y, # x+self.side-bevel, y, x+self.side, y + bevel, x+self.side, y + half, # x+self.side, y + self.side - bevel, x+self.side-bevel, y + self.side, x+half, y+self.side, # x+bevel, y+self.side, x, y+self.side-bevel, x, y+half, # x, y+bevel, fill=self._fillcolor, outline=self._outlinecolor, smooth=True, width=bevel) self.canvas.addtag_withtag(self.tag,tag) self.die = tag # Draw the pips for i in range(3): for j in range(3): tag = self.canvas.create_oval( x + qtr*(i+1) - 2 * bevel, y + qtr * (1+j) - 2 * bevel, x + qtr*(i+1) + 2 * bevel, y + qtr * (1+j) + 2 * bevel, width = bevel, fill=self._pipfillcolor, outline=self._pipoutlinecolor ) self.canvas.addtag_withtag(self.tag,tag) self.canvas.addtag_withtag(self.pips,tag) self.canvas.addtag_withtag('_'.join([self.tag,str(i),str(j)]),tag) # # o # self.canvas.addtag_withtag(self.tag+"_1",self.tag+"_1_1") # o # # o self.canvas.addtag_withtag(self.tag+"_2",self.tag+"_0_0") self.canvas.addtag_withtag(self.tag+"_2",self.tag+"_2_2") # o # o # o self.canvas.addtag_withtag(self.tag+"_3",self.tag+"_1") self.canvas.addtag_withtag(self.tag+"_3",self.tag+"_2") # o o # # o o self.canvas.addtag_withtag(self.tag+"_4",self.tag+"_2") self.canvas.addtag_withtag(self.tag+"_4",self.tag+"_0_2") self.canvas.addtag_withtag(self.tag+"_4",self.tag+"_2_0") # o o # o # o o self.canvas.addtag_withtag(self.tag+"_5",self.tag+"_1") self.canvas.addtag_withtag(self.tag+"_5",self.tag+"_4") # o o # o o # o o self.canvas.addtag_withtag(self.tag+"_6",self.tag+"_4") self.canvas.addtag_withtag(self.tag+"_6",self.tag+"_0_1") self.canvas.addtag_withtag(self.tag+"_6",self.tag+"_2_1") # o o # ooo # o o self.canvas.addtag_withtag(self.tag+"_7",self.tag+"_6") self.canvas.addtag_withtag(self.tag+"_7",self.tag+"_1") # ooo # o o # ooo self.canvas.addtag_withtag(self.tag+"_8",self.tag+"_6") self.canvas.addtag_withtag(self.tag+"_8",self.tag+"_1_0") self.canvas.addtag_withtag(self.tag+"_8",self.tag+"_1_2") # ooo # ooo # ooo self.canvas.addtag_withtag(self.tag+"_9",self.tag+"_8") self.canvas.addtag_withtag(self.tag+"_9",self.tag+"_1") # force to display the correct value self.value = self._value def _getval(self): return self._value def _setval(self,val): self._value = val self.canvas.lower(self.pips) self.canvas.lift(self.tag+"_%d"%val) value = property(_getval,_setval) # Mix-ins for added functionality class ScaleMixIn(object): # The class that inherits this must have tag and canvas attributes # similar to Die. def __init__(self): self.scale = 1.0 self.csize = float(self.canvas['width']),float(self.canvas['height']) self.canvas.bind("",self.resize,"+") def _getnewscale(self): newsize = float(self.canvas['width']),float(self.canvas['height']) scale = min(newsize)/min(self.csize) self.csize = newsize return scale def resize(self,e): # scale to the smallest side scale = self._getnewscale() # if the dimensions have changed... if scale != 1.0: self.canvas.scale(self.tag,0,0,scale,scale) # scale the line width lnwidth = float(self.canvas.itemcget(self.tag,'width'))*scale self.canvas.itemconfig(self.tag,width=lnwidth) class DragableMixin(object): # The class that inherits this must have tag and canvas attributes # similar to Die. def __init__(self): self.canvas.tag_bind(self.tag, "", self.ondown,"+") self.canvas.tag_bind(self.tag, "", self.onmove) self._moved = False def ondown(self,e): self.mx,self.my = e.x,e.y self.canvas.lift(self.tag) def onmove(self,e): self.canvas.move(self.tag,e.x-self.mx,e.y-self.my) self.ondown(e) class RollableMixin(object): # The class that inherits this must have tag and canvas attributes # similar to Die. def __init__(self,sides=6): self.canvas.tag_bind(self.tag, "", self.roll,"+") self.sides = sides self.value = random.randint(1,self.sides) self.rolling = 0 def roll(self,e=None): if not self.rolling > 0: self.rolling = 10 self.updateRoll() else: self.rolling = 0 def updateRoll(self): self.value = random.randint(1,self.sides) if self.rolling != 0: self.rolling -= 1 self.canvas.after(50,self.updateRoll) class ColorMixin(object): # This class can't really be mixed along side anything but the Die # but it does add a convenient way of changing the color def __init__(self, fill, outline, pipfill, pipoutline): self._fillcolor = fill self._outlinecolor = outline self._pipfillcolor = pipfill self._pipoutlinecolor = pipoutline # Example classes made with different combinations of mixins and overloading class ScalingDie(ScaleMixIn, Die): def __init__(self,canvas,side): Die.__init__(self,canvas,side) ScaleMixIn.__init__(self) class BlackDie(Die,ColorMixin): def __init__(self): Die.__init__(self,canvas,side) ColorMixin.__init__(self,'black','white','red','white') class Rollable(Die,RollableMixin): def __init__(self,canvas,side,nsides=6): Die.__init__(self,canvas,side) RollableMixin.__init__(self,nsides) class BlackRollable(BlackDie,RollableMixin): def __init__(self,canvas,side,nsides=6): BlackDie.__init__(self,canvas,side) RollableMixin.__init__(self,nsides) class Dragable(Die,DragableMixin): def __init__(self,canvas,side,nsides=6): Die.__init__(self,canvas,side) DragableMixin.__init__(self) class ScalingDragable(Die,ScaleMixIn,DragableMixin): def __init__(self,canvas,side): Die.__init__(self,canvas,side) ScaleMixIn.__init__(self) DragableMixin.__init__(self) # a little bit more work here so we don't roll when we intend to drag class ScaleDragAndRollable(Die,ScaleMixIn,DragableMixin,RollableMixin): def __init__(self,canvas,side,nsides=6): Die.__init__(self,canvas,side) ScaleMixIn.__init__(self) DragableMixin.__init__(self) RollableMixin.__init__(self,nsides) self.canvas.tag_bind(self.tag, "", self.onrelease,"+") self._moved = False def onmove(self,e): self._moved = True super(ScaleDragAndRollable,self).onmove(e) def onrelease(self,e): self._moved = False def roll(self,e=None): if not self._moved: super(ScaleDragAndRollable,self).roll(e) class BlackScaleDragAndRollable(ScaleDragAndRollable,ColorMixin): def __init__(self,*args,**kwargs): ScaleDragAndRollable.__init__(self,*args,**kwargs) ColorMixin.__init__(self,'black','white','red','white') # A canvas class that properly resizes itself, like it probably should anyway. # This is needed to properly demonstrate ScaleMixin. class ResizableCanvas(tk.Canvas): def __init__(self,*args,**kwargs): tk.Canvas.__init__(self,*args,**kwargs) self.bind('',self.resize) def resize(self,e): # calculate border width bw = ( int(self['bd']) + int(self['highlightthickness']) ) * 2 # calculate the new width and height newwidth = e.width - bw newheight = e.height - bw # Don't reduce either dimension to 0, it can cause issues. if newwidth < 1: newwidth = 1 if newheight < 1: newheight = 1 # get the old width and height oldwidth = int(self['width']) oldheight = int(self['height']) # if the dimensions have changed... if (newwidth,newheight) != (oldwidth,oldheight): self.configure(width=newwidth,height=newheight) # This is just another mixin example, except that it is for the canvas. It # enables click drag panning of the canvas. class PanningCanvasMixin(object): def __init__(self,gain=1): self._gain = gain self.bind('',self.ondown) self.bind('',self.onmove) def ondown(self,e): self.scan_mark(e.x,e.y) def onmove(self,e): self.scan_dragto(e.x,e.y,self._gain) class PanAndResizeCanvas(ResizableCanvas,PanningCanvasMixin): def __init__(self,*args,**kwargs): ResizableCanvas.__init__(self,*args,**kwargs) PanningCanvasMixin.__init__(self) # TestApp is just a class to encapsulate a small test application. class TestApp(object): def __init__(self,master=None): if not master: self.master = tk.Tk() else: self.master = master canvas = ResizableCanvas(self.master,bg='darkgreen') canvas.pack(fill='both',expand=True) ScaleDragAndRollable(canvas,100).draw(50,50) BlackScaleDragAndRollable(canvas,100).draw(155,50) BlackScaleDragAndRollable(canvas,100).draw(50,155) ScaleDragAndRollable(canvas,100).draw(155,155) def main(self): self.master.mainloop() if __name__ == "__main__": TestApp().main()