Attachment 'pyginput.py'

Download

   1 #!/usr/bin/env python
   2 # coding: utf
   3 '''
   4 Simple input box for pygame
   5 
   6 Usage:
   7     box=Input(…)                                    # declare an input box
   8     while <main loop>:
   9 
  10         if box.is_active() and <event goes to box>: # currently in input state
  11             box.edit(event)                         # pass event to the box
  12         else:
  13             <parse eventsd normally>
  14 
  15         if box.is_done():                           # input is finished …
  16             if box.is_cancelled():                  # …with cancel
  17                 <"no input is needed" action>
  18             elif box.is_failed():                   # …unsuccessfully
  19                 <unsuccessfull input action>
  20             else:                                   # …succsessfuly
  21                 <succsessful input action>
  22             box.deactivate()                        # hide box and resore state
  23 
  24         if <input is needed>:                       # we need to input something in program
  25             box.activate(…)                         # store old state and turn on the box
  26 
  27         if box.is_active(): box.draw(surface,pos)   # show box after all surface changes
  28         pygame.display.flip()                       # display the picture
  29         if box.is_active(): box.undraw()            # hide box before further surface changes
  30 '''
  31 
  32 import pygame
  33 __VERSION__=0.06
  34 
  35 ACTIVE,DONE,FAILED,CANCEL,SHOWN,FIRST=(1<<i for i in range(6))
  36 
  37 _unicode=type(u"")
  38 
  39 class Input:
  40     # Default values for some properties
  41     BorderWidth=3
  42     Status=0
  43     CursorWidth=2
  44     Margin=0
  45     TextColor=pygame.Color("Black")
  46     PromptColor=pygame.Color("grey50")
  47     CursorColor=pygame.Color("grey75")
  48     OverColor=pygame.Color("tomato")
  49     PaperColor=pygame.Color("ivory")
  50     Prompt=u""
  51     DefaultText=u""
  52     SetType=_unicode
  53     RetryIncorrect=True
  54     TextLength=8
  55     FontID=None
  56     Font=None
  57     FontSize=24
  58     PromptGap=None
  59     Size=None
  60     FixedSize=False
  61     BackingStore=None
  62     RepeatStore=None
  63     RepeatDefault=(500,100)
  64 
  65     def __update__(self, *pargs, **nargs):
  66         '''Update box properties.
  67         Positional parameters: [Prompt, [DefaultText]].
  68         Named parameters: all class data fields.
  69         
  70         - Size supercedes FontSize, default FontSize is 24
  71         - setting DefaultText also sets SetType (so use 0., not "0" for float input)
  72         '''
  73         if len(pargs)>0:
  74             self.Prompt=pargs[0]
  75         if len(pargs)>1:
  76             self.DefaultText=_unicode(pargs[1])
  77             self.SetType=type(pargs[1])
  78         for prop in nargs:
  79             if hasattr(self,prop):
  80                 setattr(self,prop,nargs[prop])
  81         if not self.FontID:
  82             self.FontID=pygame.font.match_font("sans")
  83         if self.PromptGap is None and self.Prompt:
  84             self.PromptGap=" "
  85         if "SetType" not in nargs and "DefaultText" in nargs:
  86             self.SetType=type(nargs["DefaultText"])
  87         if "TextLength" not in nargs and self.DefaultText:
  88             self.TextLength=0
  89         if "Size" in nargs:
  90             self.FixedSize=True
  91             self.FontSize=self.Size[1]-2*self.BorderWidth
  92             self.Font=pygame.font.Font(self.FontID, self.FontSize)
  93         elif not self.Size or not self.FixedSize:
  94             self.Font=pygame.font.Font(self.FontID, self.FontSize)
  95             self.Size=self.Font.size(self.Prompt+self.PromptGap+"W"*max(self.TextLength,len(self.DefaultText)+1,2))
  96             self.Size=self.Size[0]+2*self.BorderWidth,self.Size[1]+2*self.BorderWidth
  97         self.Paper=pygame.Surface(self.Size)
  98         # TODO background image/transparency for Paper
  99         self.Paper.fill(self.PaperColor)
 100         pr=self.Font.render(self.Prompt, True, self.PromptColor)
 101         self.Paper.blit(pr, (self.BorderWidth,(self.Size[1]-pr.get_height())//2))
 102         self.Text=_unicode(self.DefaultText)
 103         self.Cursor=len(self.Text)
 104 
 105     def __init__(self, *pargs, **nargs):
 106         '''Create a text input entity. Call __update__() next.'''
 107         self.__update__(*pargs, **nargs)
 108 
 109     def __sawtoothed__(self, block, side, mult=3):
 110         '''Create a sawtoothed mark for left (False) or right (True) side'''
 111         w,h=block.get_size()
 112         nw=mult*self.BorderWidth
 113         n=(h//nw)|1
 114         x,d=side and (w-1,-nw) or (0,nw)
 115         return [(x+d*(i%2),h*i//n) for i in range(n)]
 116 
 117     def value(self):
 118         '''Check if input is correct and return it, return None if it is not'''
 119         try:
 120             return self.SetType(self.Text)
 121         except:
 122             try:
 123                 return self.SetType(str(self.Text))
 124             except:
 125                 return None
 126 
 127     def render(self):
 128         '''Return paper surface with current prompt and text printed on'''
 129         ret=self.Paper.copy()
 130         wl=self.Font.size(self.Prompt+self.PromptGap)[0]
 131         ib=ret.subsurface((wl,0,ret.get_width()-wl,ret.get_height()))
 132         ia=ret.subsurface((wl+self.BorderWidth,self.BorderWidth,ret.get_width()-wl-2*self.BorderWidth,ret.get_height()-2*self.BorderWidth))
 133         pr=self.Font.render(self.Text, True, self.TextColor)
 134         w=self.Font.size(self.Text[:self.Cursor])[0]
 135         while self.Margin and w-self.Font.size(self.Text[:self.Margin])[0]<self.CursorWidth and self.Margin>0:
 136             self.Margin-=1
 137         while w-self.Font.size(self.Text[:self.Margin])[0]>ia.get_width()-self.CursorWidth and self.Margin<len(self.Text):
 138             self.Margin+=1
 139         Offset=-self.Font.size(self.Text[:self.Margin])[0]
 140         ia.blit(pr,(Offset,(ia.get_height()-pr.get_height())//2))
 141         pygame.draw.line(ia, self.CursorColor, (w+Offset,2), (w+Offset,ia.get_height()-2),self.CursorWidth)
 142         if Offset<0:
 143             pygame.draw.polygon(ib, self.OverColor, self.__sawtoothed__(ib, False))
 144         if Offset+pr.get_width()>ia.get_width()-self.CursorWidth:
 145             pygame.draw.polygon(ib, self.OverColor, self.__sawtoothed__(ib, True))
 146         return ret
 147 
 148     def draw(self,scr,pos):
 149         '''Draw input box on surface scr at position pos
 150         backuping an underlying part of surface'''
 151         self.BackingStore=(self.Paper.copy(),scr,pos)
 152         self.BackingStore[0].blit(self.BackingStore[1],(0,0),(self.BackingStore[2],self.Size))
 153         self.BackingStore[1].blit(self.render(),self.BackingStore[2])
 154         self.Status|=SHOWN
 155 
 156     def undraw(self):
 157         '''Remove the box from the surface it was drawn
 158         restoring underlying part of surface'''
 159         if self.BackingStore:
 160             self.BackingStore[1].blit(self.BackingStore[0],self.BackingStore[2])
 161         self.Status&=~SHOWN
 162 
 163     def activate(self, *pargs, **nargs):
 164         '''Enable input from the box.
 165         If either pargs or nargs is given, call __update__().
 166         Return False if no activation was needed, True otherwise.
 167 
 168         Calling __update__() means resetting every field,
 169         so use `inputbox.activate("<any prompt>")' to replace
 170         last entered value with the default one.
 171         '''
 172         if self.Status&ACTIVE and not pargs and not nargs:
 173             return False
 174         if pargs or nargs:
 175             self.__update__(*pargs, **nargs)
 176         self.Cursor=len(self.Text)
 177         self.Margin=0
 178         self.Status=ACTIVE|FIRST
 179         self.RepeatStore=pygame.key.get_repeat()
 180         pygame.key.set_repeat(*self.RepeatDefault)
 181         return True
 182 
 183     def deactivate(self):
 184         '''Disable input from the box'''
 185         if self.Status:
 186             self.Status=0
 187             pygame.key.set_repeat(*self.RepeatStore)
 188 
 189     def is_active(self): return self.Status&ACTIVE
 190     def is_done(self): return self.Status&DONE
 191     def is_failed(self): return self.Status&FAILED
 192     def is_cancelled(self): return self.Status&CANCEL
 193     def is_shown(self): return self.Status&SHOWN
 194 
 195     def is_success(self):
 196         return self.is_done() and not (self.is_failed() or self.is_cancelled())
 197 
 198     def edit(self,ev):
 199         '''Proceed event for editing input box.
 200         Supported keys:
 201             <any unicode symbol>:   input character
 202             <Return>/<Enter>:       finish input
 203             <Backspace>/<Del>:      delete character under/after the cursor
 204             <Esc>:                  restore default value
 205             <Home>/<End>/→/←:       move cursor
 206             <Esc><Esc>:             cancel input'''
 207         if ev.type is pygame.KEYDOWN:
 208             if ev.key == pygame.K_BACKSPACE:
 209                 if self.Cursor>0:
 210                     self.Text=self.Text[:self.Cursor-1]+self.Text[self.Cursor:]
 211                     self.Cursor-=1
 212             elif ev.key == pygame.K_DELETE:
 213                 if self.Cursor<len(self.Text):
 214                     self.Text=self.Text[:self.Cursor]+self.Text[self.Cursor+1:]
 215             elif ev.unicode >= u' ':
 216                 if self.Status&FIRST:
 217                     self.Text=ev.unicode
 218                     self.Cursor=1
 219                 else:
 220                     self.Text=self.Text[:self.Cursor]+ev.unicode+self.Text[self.Cursor:]
 221                     self.Cursor+=1
 222             elif ev.key == pygame.K_ESCAPE:
 223                 if self.Text==self.DefaultText:
 224                      self.Status|=CANCEL|DONE
 225                 self.Text=self.DefaultText
 226                 self.Margin=0
 227             elif ev.key in (pygame.K_RETURN, pygame.K_KP_ENTER):
 228                 if self.value() is None:
 229                     if self.RetryIncorrect:
 230                         # TODO signal an error
 231                         self.Text=self.DefaultText
 232                         self.Margin=0
 233                     else:
 234                         self.Status|=DONE|FAILED
 235                 else:
 236                     self.Status|=DONE
 237             elif ev.key == pygame.K_HOME:
 238                 self.Cursor=0
 239             elif ev.key == pygame.K_END:
 240                 self.Cursor=len(self.Text)
 241             elif ev.key == pygame.K_RIGHT:
 242                 self.Cursor=min(self.Cursor+1,len(self.Text))
 243             elif ev.key == pygame.K_LEFT:
 244                 self.Cursor=max(self.Cursor-1,0)
 245             self.Status&=~FIRST
 246 
 247     def input(self, scr, pos, *pargs, **nargs):
 248         '''Perform synchronous input on surface scr at position pos.
 249         All unused events are ignored.'''
 250         self.activate(*pargs, **nargs)
 251         while not self.is_done():
 252             self.edit(pygame.event.wait())
 253             self.draw(scr,pos)
 254             pygame.display.flip()
 255             self.undraw()
 256         self.deactivate()
 257         return self.value()
 258 
 259 def Print(scr, text, pos=None, font=None, size=24, antialias=True, color=None, background=None):
 260     '''Print text on surface screen using as many defaults as possible'''
 261     # TODO font is registered every time you print, fix this
 262     f=pygame.font.Font(font, size)
 263     if pos == None:
 264         pos=scr.get_size()
 265         sz=f.size(text)
 266         pos=(pos[0]-sz[0])//2,(pos[1]-sz[1])//2
 267     if color == None:
 268         color=scr.get_at(pos)
 269         color.r, color.g, color.b = 255^color.r, 255^color.g, 255^color.b
 270     if background:
 271         pr=f.render(text, antialias, color, background=background)
 272     else:
 273         pr=f.render(text, antialias, color)
 274     scr.blit(pr, pos)
 275 
 276 def __main():
 277     import random
 278     _=random.randint
 279     pygame.init()
 280     Size=(760,180)
 281     Scr=pygame.display.set_mode(Size)
 282     Scr.fill(pygame.Color("Black"))
 283     r=20
 284     x,y=Size[0]//12,Size[1]//5
 285     inp=Input("Input",Size=(Size[0]-2*x,Size[1]-2*y))
 286     cont,verbose=True,False
 287     defaults,defcnt=(("String",u""),("Int",0),("Float",0.)),0
 288     while cont:
 289         pos=_(r,Size[0]-r),_(r,Size[1]-r)
 290         col=_(10,255),_(10,255),_(10,255)
 291         pygame.draw.circle(Scr,col,pos,r)
 292         for ev in pygame.event.get():
 293             if verbose:
 294                 print(ev)
 295             if ev.type is pygame.QUIT:
 296                 cont=False
 297             if ev.type is pygame.KEYDOWN and inp.is_active():
 298                 inp.edit(ev)
 299             elif ev.type is pygame.KEYDOWN:
 300                 if ev.key in (pygame.K_KP_ENTER, pygame.K_RETURN, 13):
 301                     if not inp.is_active():
 302                         inp.activate(*defaults[defcnt])
 303                         defcnt=(defcnt+1)%len(defaults)
 304                 elif ev.key == pygame.K_F1:
 305                     verbose=True
 306                 elif ev.key is pygame.K_ESCAPE:
 307                     if not inp.is_active():
 308                         cont=False
 309 
 310         if inp.is_done():
 311             if inp.is_failed():
 312                 print("Data incorrect")
 313             elif inp.is_cancelled():
 314                 print("Input is cancelled")
 315             else:
 316                 print(u"Result: '{0}'".format(inp.value()))
 317             inp.deactivate()
 318 
 319         if inp.is_active(): inp.draw(Scr,(x,y))
 320         pygame.display.flip()
 321         if inp.is_active(): inp.undraw()
 322     quit()
 323 
 324 if __name__ == "__main__":
 325     __main()

Attached Files

To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.

You are not allowed to attach a file to this page.