diff options
Diffstat (limited to 'pgu')
-rw-r--r-- | pgu/__init__.py | 7 | ||||
-rw-r--r-- | pgu/__init__.pyc | bin | 0 -> 189 bytes | |||
-rw-r--r-- | pgu/algo.py | 135 | ||||
-rw-r--r-- | pgu/ani.py | 90 | ||||
-rw-r--r-- | pgu/engine.py | 154 | ||||
-rw-r--r-- | pgu/engine.pyc | bin | 0 -> 5501 bytes | |||
-rw-r--r-- | pgu/fonts.py | 130 | ||||
-rw-r--r-- | pgu/gui/__init__.py | 32 | ||||
-rw-r--r-- | pgu/gui/__init__.pyc | bin | 0 -> 1856 bytes | |||
-rw-r--r-- | pgu/gui/app.py | 237 | ||||
-rw-r--r-- | pgu/gui/app.pyc | bin | 0 -> 6685 bytes | |||
-rw-r--r-- | pgu/gui/area.py | 434 | ||||
-rw-r--r-- | pgu/gui/area.pyc | bin | 0 -> 13562 bytes | |||
-rw-r--r-- | pgu/gui/basic.py | 124 | ||||
-rw-r--r-- | pgu/gui/basic.pyc | bin | 0 -> 4473 bytes | |||
-rw-r--r-- | pgu/gui/button.py | 351 | ||||
-rw-r--r-- | pgu/gui/button.pyc | bin | 0 -> 11854 bytes | |||
-rw-r--r-- | pgu/gui/const.py | 45 | ||||
-rw-r--r-- | pgu/gui/const.pyc | bin | 0 -> 1133 bytes | |||
-rw-r--r-- | pgu/gui/container.py | 455 | ||||
-rw-r--r-- | pgu/gui/container.pyc | bin | 0 -> 12229 bytes | |||
-rw-r--r-- | pgu/gui/deprecated.py | 76 | ||||
-rw-r--r-- | pgu/gui/deprecated.pyc | bin | 0 -> 3300 bytes | |||
-rw-r--r-- | pgu/gui/dialog.py | 157 | ||||
-rw-r--r-- | pgu/gui/dialog.pyc | bin | 0 -> 4744 bytes | |||
-rw-r--r-- | pgu/gui/document.py | 112 | ||||
-rw-r--r-- | pgu/gui/document.pyc | bin | 0 -> 3731 bytes | |||
-rw-r--r-- | pgu/gui/form.py | 79 | ||||
-rw-r--r-- | pgu/gui/form.pyc | bin | 0 -> 2767 bytes | |||
-rw-r--r-- | pgu/gui/group.py | 43 | ||||
-rw-r--r-- | pgu/gui/group.pyc | bin | 0 -> 1827 bytes | |||
-rw-r--r-- | pgu/gui/input.py | 166 | ||||
-rw-r--r-- | pgu/gui/input.pyc | bin | 0 -> 4938 bytes | |||
-rw-r--r-- | pgu/gui/keysym.py | 72 | ||||
-rw-r--r-- | pgu/gui/keysym.pyc | bin | 0 -> 2638 bytes | |||
-rw-r--r-- | pgu/gui/layout.py | 172 | ||||
-rw-r--r-- | pgu/gui/layout.pyc | bin | 0 -> 5499 bytes | |||
-rw-r--r-- | pgu/gui/menus.py | 121 | ||||
-rw-r--r-- | pgu/gui/menus.pyc | bin | 0 -> 4614 bytes | |||
-rw-r--r-- | pgu/gui/misc.py | 43 | ||||
-rw-r--r-- | pgu/gui/misc.pyc | bin | 0 -> 1797 bytes | |||
-rw-r--r-- | pgu/gui/pguglobals.py | 7 | ||||
-rw-r--r-- | pgu/gui/pguglobals.pyc | bin | 0 -> 134 bytes | |||
-rw-r--r-- | pgu/gui/select.py | 191 | ||||
-rw-r--r-- | pgu/gui/select.pyc | bin | 0 -> 5208 bytes | |||
-rw-r--r-- | pgu/gui/slider.py | 279 | ||||
-rw-r--r-- | pgu/gui/slider.pyc | bin | 0 -> 9981 bytes | |||
-rw-r--r-- | pgu/gui/style.py | 41 | ||||
-rw-r--r-- | pgu/gui/style.pyc | bin | 0 -> 2164 bytes | |||
-rw-r--r-- | pgu/gui/surface.py | 142 | ||||
-rw-r--r-- | pgu/gui/surface.pyc | bin | 0 -> 7067 bytes | |||
-rw-r--r-- | pgu/gui/table.py | 331 | ||||
-rw-r--r-- | pgu/gui/table.pyc | bin | 0 -> 9131 bytes | |||
-rw-r--r-- | pgu/gui/textarea.py | 287 | ||||
-rw-r--r-- | pgu/gui/textarea.pyc | bin | 0 -> 7099 bytes | |||
-rw-r--r-- | pgu/gui/theme.py | 486 | ||||
-rw-r--r-- | pgu/gui/theme.pyc | bin | 0 -> 15188 bytes | |||
-rw-r--r-- | pgu/gui/widget.py | 352 | ||||
-rw-r--r-- | pgu/gui/widget.pyc | bin | 0 -> 11782 bytes | |||
-rw-r--r-- | pgu/hexvid.py | 127 | ||||
-rw-r--r-- | pgu/hexvid.pyc | bin | 0 -> 4090 bytes | |||
-rw-r--r-- | pgu/high.py | 154 | ||||
-rw-r--r-- | pgu/html.py | 571 | ||||
-rw-r--r-- | pgu/html.pyc | bin | 0 -> 22344 bytes | |||
-rw-r--r-- | pgu/isovid.py | 182 | ||||
-rw-r--r-- | pgu/isovid.pyc | bin | 0 -> 6262 bytes | |||
-rw-r--r-- | pgu/layout.py | 4 | ||||
-rw-r--r-- | pgu/layout.pyc | bin | 0 -> 206 bytes | |||
-rw-r--r-- | pgu/text.py | 61 | ||||
-rw-r--r-- | pgu/text.pyc | bin | 0 -> 2535 bytes | |||
-rw-r--r-- | pgu/tilevid.py | 195 | ||||
-rw-r--r-- | pgu/tilevid.pyc | bin | 0 -> 5305 bytes | |||
-rw-r--r-- | pgu/timer.py | 68 | ||||
-rw-r--r-- | pgu/timer.pyc | bin | 0 -> 2173 bytes | |||
-rw-r--r-- | pgu/vid.py | 560 | ||||
-rw-r--r-- | pgu/vid.pyc | bin | 0 -> 17893 bytes |
76 files changed, 7273 insertions, 0 deletions
diff --git a/pgu/__init__.py b/pgu/__init__.py new file mode 100644 index 0000000..d90edbf --- /dev/null +++ b/pgu/__init__.py @@ -0,0 +1,7 @@ +"""Phil's pyGame Utilities + + +""" +__version__ = '0.12.2' + +# vim: set filetype=python sts=4 sw=4 noet si : diff --git a/pgu/__init__.pyc b/pgu/__init__.pyc Binary files differnew file mode 100644 index 0000000..a696dce --- /dev/null +++ b/pgu/__init__.pyc diff --git a/pgu/algo.py b/pgu/algo.py new file mode 100644 index 0000000..dedb020 --- /dev/null +++ b/pgu/algo.py @@ -0,0 +1,135 @@ +"""Some handy algorithms for use in games, etc. + +<p>please note that this file is alpha, and is subject to modification in +future versions of pgu!</p> +""" +print 'pgu.algo','This module is alpha, and is subject to change.' + +#def dist(a,b): +# return abs(a[0]-b[0]) + abs(a[1]-b[1]) + +class node: + def __init__(self,prev,pos,dest): + self.prev,self.pos,self.dest = prev,pos,dest + if self.prev == None: self.g = 0 + else: self.g = self.prev.g + 1 + self.h = dist(pos,dest) + self.f = self.g+self.h + + +def astar(start,end,layer,_dist): + """uses the a* algorithm to find a path + + <pre>astar(start,end,layer,dist): return [list of positions]</pre> + + <dl> + <dt>start<dd>start position + <dt>end<dd>end position + <dt>layer<dd>a grid where zero cells are open and non-zero cells are walls + <dt>dist<dd>a distance function dist(a,b) + </dl> + + <p>returns a list of positions from start to end</p> + """ + global dist + dist = _dist + if layer[start[1]][start[0]]: return [] #start is blocked + if layer[end[1]][end[0]]: return [] #end is blocked + w,h = len(layer[0]),len(layer) + if start[0] < 0 or start[1] < 0 or start[0] >= w or start[1] >= h: return [] #start outside of layer + if end[0] < 0 or end[1] < 0 or end[0] >= w or end[1] >= h: return [] #end outside of layer + + opens = [] + open = {} + closed = {} + cur = node(None,start,end) + open[cur.pos] = cur + opens.append(cur) + while len(open): + cur = opens.pop(0) + if cur.pos not in open: continue + del open[cur.pos] + closed[cur.pos] = cur + if cur.pos == end: break + for dx,dy in [(0,-1),(1,0),(0,1),(-1,0)]:#(-1,-1),(1,-1),(-1,1),(1,1)]: + x,y = pos = cur.pos[0]+dx,cur.pos[1]+dy + if layer[y][x]: continue + #check for blocks of diagonals + if layer[cur.pos[1]+dy][cur.pos[0]]: continue + if layer[cur.pos[1]][cur.pos[0]+dx]: continue + new = node(cur,pos,end) + if pos in open and new.f >= open[pos].f: continue + if pos in closed and new.f >= closed[pos].f: continue + if pos in open: del open[pos] + if pos in closed: del closed[pos] + open[pos] = new + lo = 0 + hi = len(opens) + while lo < hi: + mid = (lo+hi)/2 + if new.f < opens[mid].f: hi = mid + else: lo = mid + 1 + opens.insert(lo,new) + + if cur.pos != end: + return [] + + path = [] + while cur.prev != None: + path.append(cur.pos) + cur = cur.prev + path.reverse() + return path + + +def getline(a,b): + """returns a path of points from a to b + + <pre>getline(a,b): return [list of points]</pre> + + <dl> + <dt>a<dd>starting point + <dt>b<dd>ending point + </dl> + + <p>returns a list of points from a to b</p> + """ + + path = [] + + x1,y1 = a + x2,y2 = b + dx,dy = abs(x2-x1),abs(y2-y1) + + if x2 >= x1: xi1,xi2 = 1,1 + else: xi1,xi2 = -1,-1 + + if y2 >= y1: yi1,yi2 = 1,1 + else: yi1,yi2 = -1,-1 + + if dx >= dy: + xi1,yi2 = 0,0 + d = dx + n = dx/2 + a = dy + p = dx + else: + xi2,yi1 = 0,0 + d = dy + n = dy/2 + a = dx + p = dy + + x,y = x1,y1 + c = 0 + while c <= p: + path.append((x,y)) + n += a + if n > d: + n -= d + x += xi1 + y += yi1 + x += xi2 + y += yi2 + c += 1 + return path diff --git a/pgu/ani.py b/pgu/ani.py new file mode 100644 index 0000000..c33d380 --- /dev/null +++ b/pgu/ani.py @@ -0,0 +1,90 @@ +"""animation loading and manipulating functions. + +<p>please note that this file is alpha, and is subject to modification in +future versions of pgu!</p> +""" + +print 'pgu.ani','This module is alpha, and is subject to change.' + +import math +import pygame + +def _ani_load(tv,name,parts,frames,shape): + l = len(frames) + #print name,parts,l + n = parts.pop() + if len(parts): + s = l/n + for i in xrange(0,n): + _ani_load(tv,name + ".%d"%i,parts[:],frames[s*i:s*(i+1)],shape) + return + + for i in xrange(0,n): + tv.images[name+".%d"%i] = frames[i],shape + +def ani_load(tv,name,img,size,shape,parts): + """load an animation from an image + + <pre>ani_load(tv,name,image,size,shape,parts)</pre> + + <dl> + <dt>tv<dd>vid to load into + <dt>name <dd>prefix name to give the images + <dt>image <dd>image to load anis from + <dt>size <dd>w,h size of image + <dt>shape <dd>shape of image (usually a subset of 0,0,w,h) used for collision detection + <dt>parts <dd>list of parts to divide the animation into + <br>for example parts = [4,5] would yield 4 animations 5 frames long, 20 total + <br>for example parts = [a,b,c] would yield ... images['name.a.b.c'] ..., a*b*c total + </dl> + + """ + parts = parts[:] + parts.reverse() + w,h = size + frames = [] + for y in xrange(0,img.get_height(),h): + for x in xrange(0,img.get_width(),w): + frames.append(img.subsurface(x,y,w,h)) + _ani_load(tv,name,parts,frames,shape) + + +def image_rotate(tv,name,img,shape,angles,diff=0): + """rotate an image and put it into tv.images + + <pre>image_rotate(tv,name,image,shape,angles,diff=0)</pre> + + <dl> + <dt>tv <dd>vid to load into + <dt>name <dd>prefix name to give the images + <dt>image <dd>image to load anis from + <dt>shape <dd>shape fimage (usually a subset of 0,0,w,h) used for collision detection + <dt>angles <dd>a list of angles to render in degrees + <dt>diff <dd>a number to add to the angles, to correct for source image not actually being at 0 degrees + </dl> + """ + w1,h1 = img.get_width(),img.get_height() + shape = pygame.Rect(shape) + ps = shape.topleft,shape.topright,shape.bottomleft,shape.bottomright + for a in angles: + img2 = pygame.transform.rotate(img,a+diff) + w2,h2 = img2.get_width(),img2.get_height() + minx,miny,maxx,maxy = 1024,1024,0,0 + for x,y in ps: + x,y = x-w1/2,y-h1/2 + a2 = math.radians(a+diff) + #NOTE: the + and - are switched from the normal formula because of + #the weird way that pygame does the angle... + x2 = x*math.cos(a2) + y*math.sin(a2) + y2 = y*math.cos(a2) - x*math.sin(a2) + x2,y2 = x2+w2/2,y2+h2/2 + minx = min(minx,x2) + miny = min(miny,y2) + maxx = max(maxx,x2) + maxy = max(maxy,y2) + r = pygame.Rect(minx,miny,maxx-minx,maxy-miny) + #print r + #((ww-w)/2,(hh-h)/2,w,h) + tv.images["%s.%d"%(name,a)] = img2,r + + diff --git a/pgu/engine.py b/pgu/engine.py new file mode 100644 index 0000000..76be583 --- /dev/null +++ b/pgu/engine.py @@ -0,0 +1,154 @@ +"""a state engine. +""" +import pygame +from pygame.locals import * + +class State: + """Template Class -- for a state. + + <pre>State(game,value...)</pre> + + <dl> + <dt>game<dd>The state engine. + <dt>value<dd>I usually pass in a custom value to a state + </dl> + + <p>For all of the template methods, they should return None unless they return + a new State to switch the engine to.</p> + """ + def __init__(self,game,value=None): + self.game,self.value = game,value + def init(self): + """Template Method - Initialize the state, called once the first time a state is selected. + + <pre>State.init()</pre> + """ + return + def paint(self,screen): + """Template Method - Paint the screen. Called once after the state is selected. + + <p>State is responsible for calling <tt>pygame.display.flip()</tt> or whatever.</p> + + <pre>State.paint(screen)</pre> + """ + return + + def repaint(self): + """Template Method - Request a repaint of this state. + + <pre>State.repaint()</pre> + """ + self._paint = 1 + def update(self,screen): + """Template Method - Update the screen. + + <p>State is responsible for calling <tt>pygame.display.update(updates)</tt> or whatever.</p> + + <pre>State.update(screen)</pre> + """ + return + def loop(self): + """Template Method - Run a logic loop, called once per frame. + + <pre>State.loop()</pre> + """ + return + def event(self,e): + """Template Method - Recieve an event. + + <pre>State.event(e)</pre> + """ + return + +class Quit(State): + """A state to quit the state engine. + + <pre>Quit(game,value)</pre> + """ + + def init(self): + self.game.quit = 1 + +class Game: + """Template Class - The state engine. + """ + def fnc(self,f,v=None): + s = self.state + if not hasattr(s,f): return 0 + f = getattr(s,f) + if v != None: r = f(v) + else: r = f() + if r != None: + self.state = r + self.state._paint = 1 + return 1 + return 0 + + def run(self,state,screen=None): + """Run the state engine, this is a infinite loop (until a quit occurs). + + <pre>Game.run(state,screen=None)</pre> + + <dl> + <dt>game<dd>a state engine + <dt>screen<dd>the screen + </dl> + """ + self.quit = 0 + self.state = state + if screen != None: self.screen = screen + + self.init() + + while not self.quit: + self.loop() + + def loop(self): + s = self.state + if not hasattr(s,'_init') or s._init: + s._init = 0 + if self.fnc('init'): return + else: + if self.fnc('loop'): return + if not hasattr(s,'_paint') or s._paint: + s._paint = 0 + if self.fnc('paint',self.screen): return + else: + if self.fnc('update',self.screen): return + + for e in pygame.event.get(): + #NOTE: this might break API? + #if self.event(e): return + if not self.event(e): + if self.fnc('event',e): return + + self.tick() + return + + def init(self): + """Template Method - called at the beginning of State.run() to initialize things. + + <pre>Game.init()</pre> + """ + return + + def tick(self): + """Template Method - called once per frame, usually for timer purposes. + + <pre>Game.tick()</pre> + """ + pygame.time.wait(10) + + def event(self,e): + """Template Method - called with each event, so the engine can capture special events. + + <pre>Game.event(e): return captured</pre> + + <p>return a True value if the event is captured and does not need to be passed onto the current + state</p> + """ + if e.type is QUIT: + self.state = Quit(self) + return 1 + +# vim: set filetype=python sts=4 sw=4 noet si : diff --git a/pgu/engine.pyc b/pgu/engine.pyc Binary files differnew file mode 100644 index 0000000..36c5d0b --- /dev/null +++ b/pgu/engine.pyc diff --git a/pgu/fonts.py b/pgu/fonts.py new file mode 100644 index 0000000..ab6f73d --- /dev/null +++ b/pgu/fonts.py @@ -0,0 +1,130 @@ +"""Some handy font-like objects. + +<p>please note that this file is alpha, and is subject to modification in +future versions of pgu!</p> +""" + +print 'pgu.fonts','This module is alpha, and is subject to change.' + +import pygame +from pygame.locals import * + +class TileFont: + """Creates an instance of the TileFont class. Interface compatible with pygame.Font + + <p>TileFonts are fonts that are stored in a tiled image. Where the image opaque, it assumed that the font is visible. Font color is changed automatically, so it does not work with + fonts with stylized coloring.</p> + + <pre>TileFont(fname,size,hints,scale=None,sensitive=False)</pre> + + <dl> + <dt>size <dd>the dimensions of the characters + <dt>hints <dd>a string of hints "abcdefg..." + <dt>scale <dd>size to scale font to + <dt>sensitive <dd>case sensitivity + </dl> + """ + + def __init__(self,fname,size,hints,scale=None,sensitive=False): + + self.image = pygame.image.load(fname) + + w,h = self.image.get_width(),self.image.get_height() + tw,th = size + if not scale: scale = size + self._size = size + self.scale = scale + + self.chars = {} + x,y = 0,0 + self.sensitive = sensitive + if not self.sensitive: hints = hints.lower() + for c in hints: + if c not in ('\r','\n','\t'): + img = self.image.subsurface(x,y,tw,th) + self.chars[c] = img + x += tw + if x >= w: x,y = 0,y+th + + self.colors = {} + + def size(self,text): + tw,th = self.scale + return len(text)*tw,th + + def render(self,text,antialias=0,color=(255,255,255),background=None): + size = self.size(text) + scale = self.scale + tw,th = self._size + if background == None: + s = pygame.Surface(size).convert_alpha() + s.fill((0,0,0,0)) + else: + s = pygame.Surface(size).convert() + s.fill(background) + + if not self.sensitive: text = text.lower() + + if color not in self.colors: self.colors[color] = {} + colored = self.colors[color] + + x,y = 0,0 + for c in text: + if c in self.chars: + if c not in colored: + img = self.chars[c].convert_alpha() + for yy in xrange(0,th): + for xx in xrange(0,tw): + r,g,b,a = img.get_at((xx,yy)) + if a > 128: + img.set_at((xx,yy),color) + colored[c] = img + img = colored[c] + if scale != (tw,th): img = pygame.transform.scale(img,scale) + s.blit(img,(x,y)) + x += scale[0] + return s + + +class BorderFont: + """a decorator for normal fonts, adds a border. Interface compatible with pygame.Font. + + <pre>BorderFont(font,size=1,color=(0,0,0))</pre> + + <dl> + <dt>size <dd>width of border; defaults 0 + <dt>color <dd>color of border; default (0,0,0) + </dl> + """ + def __init__(self,font,size=1,color=(0,0,0)): + + self.font = font + self._size = size + self.color = color + + def size(self,text): + w,h = self.font.size(text) + s = self._size + return w+s*2,h+s*2 + + def render(self,text,antialias=0,color=(255,255,255),background=None): + size = self.size(text) + + if background == None: + s = pygame.Surface(size).convert_alpha() + s.fill((0,0,0,0)) + else: + s = pygame.Surface(size).convert() + s.fill(background) + + bg = self.font.render(text,antialias,self.color) + fg = self.font.render(text,antialias,color) + + si = self._size + dirs = [(-1,-1),(-1,0),(-1,1),(0,-1),(0,1),(1,-1),(1,0),(1,1)] + for dx,dy in dirs: s.blit(bg,(si+dx*si,si+dy*si)) + s.blit(fg,(si,si)) + + return s + + diff --git a/pgu/gui/__init__.py b/pgu/gui/__init__.py new file mode 100644 index 0000000..256fb63 --- /dev/null +++ b/pgu/gui/__init__.py @@ -0,0 +1,32 @@ +import pygame +from pygame.locals import * + +from theme import Theme +from style import Style +from widget import Widget +from surface import subsurface, ProxySurface +from const import * + +from container import Container +from app import App, Desktop +from table import Table +from document import Document +#html +from area import SlideBox, ScrollArea, List + +from form import Form +from group import Group + +from basic import Spacer, Color, Label, Image, parse_color +from button import Icon, Button, Switch, Checkbox, Radio, Tool, Link +from input import Input, Password +from keysym import Keysym +from slider import VSlider, HSlider, VScrollBar, HScrollBar +from select import Select +from misc import ProgressBar + +from menus import Menus +from dialog import Dialog, FileDialog +from textarea import TextArea + +from deprecated import Toolbox, action_open, action_setvalue, action_quit, action_exec diff --git a/pgu/gui/__init__.pyc b/pgu/gui/__init__.pyc Binary files differnew file mode 100644 index 0000000..2da4bda --- /dev/null +++ b/pgu/gui/__init__.pyc diff --git a/pgu/gui/app.py b/pgu/gui/app.py new file mode 100644 index 0000000..89ce66c --- /dev/null +++ b/pgu/gui/app.py @@ -0,0 +1,237 @@ +""" +""" +import pygame +from pygame.locals import * + +import pguglobals +import container +from const import * + +class App(container.Container): + """The top-level widget for an application. + + <pre>App(theme=None)</pre> + + <dl> + <dt>theme<dd>an instance of a Theme, optional as it will use the default Theme class. + </dl> + + <strong>Basic Example</strong> + <code> + app = gui.App() + app.run(widget=widget,screen=screen) + </code> + + <strong>Integrated Example</strong> + <code> + app = gui.App() + gui.init(widget=widget) + while 1: + for e in pygame.event.get(): + app.event(e) + app.update(screen) + </code> + + + + """ + def __init__(self,theme=None,**params): + self.set_global_app() + + if theme == None: + from theme import Theme + theme = Theme() + self.theme = theme + + params['decorate'] = 'app' + container.Container.__init__(self,**params) + self._quit = False + self.widget = None + self._chsize = False + self._repaint = False + + self.screen = None + self.container = None + self.events = [] + + def set_global_app(self): + # Keep a global reference to this application instance so that PGU + # components can easily find it. + pguglobals.app = self + # For backwards compatibility we keep a reference in the class + # itself too. + App.app = self + + def resize(self): + + screen = self.screen + w = self.widget + wsize = 0 + + #5 cases + + #input screen is already set use its size + if screen: + self.screen = screen + width,height = screen.get_width(),screen.get_height() + + #display.screen + elif pygame.display.get_surface(): + screen = pygame.display.get_surface() + self.screen = screen + width,height = screen.get_width(),screen.get_height() + + #app has width,height + elif self.style.width != 0 and self.style.height != 0: + screen = pygame.display.set_mode((self.style.width,self.style.height),SWSURFACE) + self.screen = screen + width,height = screen.get_width(),screen.get_height() + + #widget has width,height, or its own size.. + else: + wsize = 1 + width,height = w.rect.w,w.rect.h = w.resize() + #w._resize() + screen = pygame.display.set_mode((width,height),SWSURFACE) + self.screen = screen + + #use screen to set up size of this widget + self.style.width,self.style.height = width,height + self.rect.w,self.rect.h = width,height + self.rect.x,self.rect.y = 0,0 + + w.rect.x,w.rect.y = 0,0 + w.rect.w,w.rect.h = w.resize(width,height) + + for w in self.windows: + w.rect.w,w.rect.h = w.resize() + + self._chsize = False + + + def init(self,widget=None,screen=None): #TODO widget= could conflict with module widget + """Initialize the application. + + <pre>App.init(widget=None,screen=None)</pre> + + <dl> + <dt>widget<dd>main widget + <dt>screen<dd>pygame.Surface to render to + </dl> + """ + + self.set_global_app() + + if widget: self.widget = widget + if screen: self.screen = screen + + self.resize() + + w = self.widget + + self.widgets = [] + self.widgets.append(w) + w.container = self + self.focus(w) + + pygame.key.set_repeat(500,30) + + self._repaint = True + self._quit = False + + self.send(INIT) + + def event(self,e): + """Pass an event to the main widget. + + <pre>App.event(e)</pre> + + <dl> + <dt>e<dd>event + </dl> + """ + self.set_global_app() + + #NOTE: might want to deal with ACTIVEEVENT in the future. + self.send(e.type,e) + container.Container.event(self,e) + if e.type == MOUSEBUTTONUP: + if e.button not in (4,5): #ignore mouse wheel + sub = pygame.event.Event(CLICK,{ + 'button':e.button, + 'pos':e.pos}) + self.send(sub.type,sub) + container.Container.event(self,sub) + + + def loop(self): + self.set_global_app() + + s = self.screen + for e in pygame.event.get(): + if not (e.type == QUIT and self.mywindow): + self.event(e) + us = self.update(s) + pygame.display.update(us) + + + def paint(self,screen): + self.screen = screen + if self._chsize: + self.resize() + self._chsize = False + if hasattr(self,'background'): + self.background.paint(screen) + container.Container.paint(self,screen) + + def update(self,screen): + """Update the screen. + + <dl> + <dt>screen<dd>pygame surface + </dl> + """ + self.screen = screen + if self._chsize: + self.resize() + self._chsize = False + if self._repaint: + self.paint(screen) + self._repaint = False + return [pygame.Rect(0,0,screen.get_width(),screen.get_height())] + else: + us = container.Container.update(self,screen) + return us + + def run(self,widget=None,screen=None): + """Run an application. + + <p>Automatically calls <tt>App.init</tt> and then forever loops <tt>App.event</tt> and <tt>App.update</tt></p> + + <dl> + <dt>widget<dd>main widget + <dt>screen<dd>pygame.Surface to render to + </dl> + """ + self.init(widget,screen) + while not self._quit: + self.loop() + pygame.time.wait(10) + + def reupdate(self,w=None): pass + def repaint(self,w=None): self._repaint = True + def repaintall(self): self._repaint = True + def chsize(self): + self._chsize = True + self._repaint = True + + def quit(self,value=None): self._quit = True + +class Desktop(App): + """Create an App using the <tt>desktop</tt> theme class. + + <pre>Desktop()</pre> + """ + def __init__(self,**params): + params.setdefault('cls','desktop') + App.__init__(self,**params) diff --git a/pgu/gui/app.pyc b/pgu/gui/app.pyc Binary files differnew file mode 100644 index 0000000..3199e66 --- /dev/null +++ b/pgu/gui/app.pyc diff --git a/pgu/gui/area.py b/pgu/gui/area.py new file mode 100644 index 0000000..39b8cbd --- /dev/null +++ b/pgu/gui/area.py @@ -0,0 +1,434 @@ +""" +""" +import os + +import pguglobals +from const import * +import surface +import container, table +import group +import basic, button, slider + +class SlideBox(container.Container): + """A scrollable area with no scrollbars. + + <pre>SlideBox(widget,width,height)</pre> + + <dl> + <dt>widget<dd>widget to be able to scroll around + <dt>width, height<dd>size of scrollable area + </dl> + + <strong>Example</strong> + <code> + c = SlideBox(w,100,100) + c.offset = (10,10) + c.repaint() + </code> + + """ + + def __init__(self, widget, width, height, **params): + params.setdefault('width', width) + params.setdefault('height', height) + container.Container.__init__(self, **params) + self.offset = [0, 0] + self.widget = widget + + def __setattr__(self,k,v): + if k == 'widget': + if hasattr(self,'widget'): + self.remove(self.widget) + self.add(v,0,0) + self.__dict__[k] = v + + + def paint(self, s): + #if not hasattr(self,'surface'): + self.surface = pygame.Surface((self.max_rect.w,self.max_rect.h),0,s) + #self.surface.fill((0,0,0,0)) + pguglobals.app.theme.render(self.surface,self.style.background,pygame.Rect(0,0,self.max_rect.w,self.max_rect.h)) + self.bkgr = pygame.Surface((s.get_width(),s.get_height()),0,s) + self.bkgr.blit(s,(0,0)) + container.Container.paint(self,self.surface) + s.blit(self.surface,(-self.offset[0],-self.offset[1])) + self._offset = self.offset[:] + return + + def paint_for_when_pygame_supports_other_tricks(self,s): + #this would be ideal if pygame had support for it! + #and if pgu also had a paint(self,s,rect) method to paint small parts + sr = (self.offset[0],self.offset[1],self.max_rect.w,self.max_rect.h) + cr = (-self.offset[0],-self.offset[1],s.get_width(),s.get_height()) + s2 = s.subsurface(sr) + s2.set_clip(cr) + container.Container.paint(self,s2) + + def proxy_paint(self, s): + container.Container.paint(self, surface.ProxySurface(parent=None, + rect=self.max_rect, + real_surface=s, + offset=self.offset)) + def update(self, s): + rects = container.Container.update(self,self.surface) + + rets = [] + s_rect = pygame.Rect(0,0,s.get_width(),s.get_height()) + + if self.offset == self._offset: + for r in rects: + r2 = r.move((-self.offset[0],-self.offset[1])) + if r2.colliderect(s_rect): + s.blit(self.surface.subsurface(r),r2) + rets.append(r2) + else: + s.blit(self.bkgr,(0,0)) + sub = pygame.Rect(self.offset[0],self.offset[1],min(s.get_width(),self.max_rect.w-self.offset[0]),min(s.get_height(),self.max_rect.h-self.offset[1])) +# print sub +# print self.surface.get_width(),self.surface.get_height() +# print s.get_width(),s.get_height() +# print self.offset +# print self.style.width,self.style.height + s.blit(self.surface.subsurface(sub),(0,0)) + rets.append(s_rect) + self._offset = self.offset[:] + return rets + + def proxy_update(self, s): + rects = container.Container.update(self, surface.ProxySurface(parent=None, + rect=self.max_rect, + real_surface=s, + offset=self.offset)) + result = [] + for r in rects: result.append(pygame.Rect(r).move(self.offset)) + return result + + def resize(self, width=None, height=None): + container.Container.resize(self) + self.max_rect = pygame.Rect(self.widget.rect) + #self.max_rect.w = max(self.max_rect.w,self.style.width) + #self.max_rect.h = max(self.max_rect.h,self.style.height) + return self.style.width,self.style.height + #self.rect = pygame.Rect(self.rect[0], self.rect[1], self.style.width, self.style.height) + + def event(self, e): + if e.type in [MOUSEBUTTONDOWN, MOUSEBUTTONUP, MOUSEMOTION]: + pos = (e.pos[0] + self.offset[0], e.pos[1] + self.offset[1]) + if self.max_rect.collidepoint(pos): + e_params = {'pos': pos } + if e.type == MOUSEMOTION: + e_params['buttons'] = e.buttons + e_params['rel'] = e.rel + else: + e_params['button'] = e.button + e = pygame.event.Event(e.type, e_params) + container.Container.event(self, e) + +#class SlideBox(Area): +# def __init__(self,*args,**params): +# print 'gui.SlideBox','Scheduled to be renamed to Area.' +# Area.__init__(self,*args,**params) + +class ScrollArea(table.Table): + """A scrollable area with scrollbars. + + <pre>ScrollArea(widget,width,height,hscrollbar=True)</pre> + + <dl> + <dt>widget<dd>widget to be able to scroll around + <dt>width, height<dd>size of scrollable area. Set either to 0 to default to size of widget. + <dt>hscrollbar<dd>set to False if you do not wish to have a horizontal scrollbar + <dt>vscrollbar<dd>set to False if you do not wish to have a vertical scrollbar + <dt>step<dd>set to how far clicks on the icons will step + </dl> + """ + def __init__(self, widget, width=0, height=0, hscrollbar=True, vscrollbar=True,step=24, **params): + w= widget + params.setdefault('cls', 'scrollarea') + table.Table.__init__(self, width=width,height=height,**params) + + self.sbox = SlideBox(w, width=width, height=height, cls=self.cls+".content") + self.widget = w + self.vscrollbar = vscrollbar + self.hscrollbar = hscrollbar + + self.step = step + + def __setattr__(self,k,v): + if k == 'widget': + self.sbox.widget = v + self.__dict__[k] = v + + def resize(self,width=None,height=None): + widget = self.widget + box = self.sbox + + #self.clear() + table.Table.clear(self) + #print 'resize',self,self._rows + + self.tr() + self.td(box) + + widget.rect.w, widget.rect.h = widget.resize() + my_width,my_height = self.style.width,self.style.height + if not my_width: + my_width = widget.rect.w + self.hscrollbar = False + if not my_height: + my_height = widget.rect.h + self.vscrollbar = False + + box.style.width,box.style.height = my_width,my_height #self.style.width,self.style.height + + box.rect.w,box.rect.h = box.resize() + + #print widget.rect + #print box.rect + #r = table.Table.resize(self,width,height) + #print r + #return r + + #print box.offset + +# #this old code automatically adds in a scrollbar if needed +# #but it doesn't always work +# self.vscrollbar = None +# if widget.rect.h > box.rect.h: +# self.vscrollbar = slider.VScrollBar(box.offset[1],0, 65535, 0,step=self.step) +# self.td(self.vscrollbar) +# self.vscrollbar.connect(CHANGE, self._vscrollbar_changed, None) +# +# vs = self.vscrollbar +# vs.rect.w,vs.rect.h = vs.resize() +# box.style.width = self.style.width - vs.rect.w +# +# +# self.hscrollbar = None +# if widget.rect.w > box.rect.w: +# self.hscrollbar = slider.HScrollBar(box.offset[0], 0,65535, 0,step=self.step) +# self.hscrollbar.connect(CHANGE, self._hscrollbar_changed, None) +# self.tr() +# self.td(self.hscrollbar) +# +# hs = self.hscrollbar +# hs.rect.w,hs.rect.h = hs.resize() +# box.style.height = self.style.height - hs.rect.h + + xt,xr,xb,xl = pguglobals.app.theme.getspacing(box) + + + if self.vscrollbar: + self.vscrollbar = slider.VScrollBar(box.offset[1],0, 65535, 0,step=self.step) + self.td(self.vscrollbar) + self.vscrollbar.connect(CHANGE, self._vscrollbar_changed, None) + + vs = self.vscrollbar + vs.rect.w,vs.rect.h = vs.resize() + if self.style.width: + box.style.width = self.style.width - (vs.rect.w + xl+xr) + + if self.hscrollbar: + self.hscrollbar = slider.HScrollBar(box.offset[0], 0,65535, 0,step=self.step) + self.hscrollbar.connect(CHANGE, self._hscrollbar_changed, None) + self.tr() + self.td(self.hscrollbar) + + hs = self.hscrollbar + hs.rect.w,hs.rect.h = hs.resize() + if self.style.height: + box.style.height = self.style.height - (hs.rect.h + xt + xb) + + if self.hscrollbar: + hs = self.hscrollbar + hs.min = 0 + hs.max = widget.rect.w - box.style.width + hs.style.width = box.style.width + hs.size = hs.style.width * box.style.width / max(1,widget.rect.w) + else: + box.offset[0] = 0 + + if self.vscrollbar: + vs = self.vscrollbar + vs.min = 0 + vs.max = widget.rect.h - box.style.height + vs.style.height = box.style.height + vs.size = vs.style.height * box.style.height / max(1,widget.rect.h) + else: + box.offset[1] = 0 + + #print self.style.width,box.style.width, hs.style.width + + r = table.Table.resize(self,width,height) + return r + + def x_resize(self, width=None, height=None): + w,h = table.Table.resize(self, width, height) + if self.hscrollbar: + if self.widget.rect.w <= self.sbox.rect.w: + self.hscrollbar.size = self.hscrollbar.style.width + else: + self.hscrollbar.size = max(20,self.hscrollbar.style.width * self.sbox.rect.w / self.widget.rect.w) + self._hscrollbar_changed(None) + if self.widget.rect.h <= self.sbox.rect.h: + self.vscrollbar.size = self.vscrollbar.style.height + else: + self.vscrollbar.size = max(20,self.vscrollbar.style.height * self.sbox.rect.h / self.widget.rect.h) + self._vscrollbar_changed(None) + return w,h + + def _vscrollbar_changed(self, xxx): + #y = (self.widget.rect.h - self.sbox.rect.h) * self.vscrollbar.value / 1000 + #if y >= 0: self.sbox.offset[1] = -y + self.sbox.offset[1] = self.vscrollbar.value + self.sbox.reupdate() + + def _hscrollbar_changed(self, xxx): + #x = (self.widget.rect.w - self.sbox.rect.w) * self.hscrollbar.value / 1000 + #if x >= 0: self.sbox.offset[0] = -x + self.sbox.offset[0] = self.hscrollbar.value + self.sbox.reupdate() + + + def set_vertical_scroll(self, percents): + #if not self.vscrollbar: return + if not hasattr(self.vscrollbar,'value'): return + self.vscrollbar.value = percents #min(max(percents*10, 0), 1000) + self._vscrollbar_changed(None) + + def set_horizontal_scroll(self, percents): + #if not self.hscrollbar: return + if not hasattr(self.hscrollbar,'value'): return + self.hscrollbar.value = percents #min(max(percents*10, 0), 1000) + self._hscrollbar_changed(None) + + + + +class _List_Item(button._button): + def __init__(self,label=None,image=None,value=None,**params): #TODO label= could conflict with the module label + #param image: an imagez.Image object (optional) + #param text: a string object + params.setdefault('cls','list.item') + button._button.__init__(self,**params) + self.group = None + self.value = value #(self, value) + self.widget = None + + if type(label) == str: + label = basic.Label(label, cls=self.cls+".label") + + if image and label: + self.widget = container.Container() + self.widget.add(image, 0, 0) + #HACK: improper use of .resize() + image.rect.w,image.rect.h = image.resize() + self.widget.add(label, image.rect.w, 0) + elif image: self.widget = image + elif label: self.widget = label + + self.pcls = "" + + def resize(self,width=None,height=None): + self.widget.rect.w,self.widget.rect.h = self.widget.resize() + return self.widget.rect.w,self.widget.rect.h +# self.widget._resize() +# self.rect.w,self.rect.h = self.widget.rect_margin.w,self.widget.rect_margin.h + + def event(self,e): + button._button.event(self,e) + if self.group.value == self.value: self.pcls = "down" + + def paint(self,s): + if self.group.value == self.value: self.pcls = "down" + self.widget.paint(surface.subsurface(s,self.widget.rect)) + + def click(self): + self.group.value = self.value + for w in self.group.widgets: + if w != self: w.pcls = "" + + + +class List(ScrollArea): + """A list of items in an area. + + <p>This widget can be a form element, it has a value set to whatever item is selected.</p> + + <pre>List(width,height)</pre> + """ + def _change(self, value): + self.value = self.group.value + self.send(CHANGE) + + def __init__(self, width, height, **params): + params.setdefault('cls', 'list') + self.table = table.Table(width=width) + ScrollArea.__init__(self, self.table, width, height,hscrollbar=False ,**params) + + self.items = [] + + g = group.Group() + self.group = g + g.connect(CHANGE,self._change,None) + self.value = self.group.value = None + + self.add = self._add + self.remove = self._remove + + def clear(self): + """Clear the list. + + <pre>List.clear()</pre> + """ + self.items = [] + self.group = group.Group() + self.group.connect(CHANGE,self._change,None) + self.table.clear() + self.set_vertical_scroll(0) + self.blur(self.myfocus) + + def _docs(self): #HACK: nasty hack to get the docs in "my way" + def add(self, label, image=None, value=None): + """Add an item to the list. + + <pre>List.add(label,image=None,value=None)</pre> + + <dl> + <dt>label<dd>a label for the item + <dt>image<dd>an image for the item + <dt>value<dd>a value for the item + </dl> + """ + + def remove(self,value): + """Remove an item from the list. + + <pre>List.remove(value)</pre> + + <dl> + <dt>value<dd>a value of an item to remove from the list + </dl> + """ + + def _add(self, label, image = None, value=None): + item = _List_Item(label,image=image,value=value) + self.table.tr() + self.table.add(item) + self.items.append(item) + item.group = self.group + item.group.add(item) + + def _remove(self, item): + for i in self.items: + if i.value == item: item = i + if item not in self.items: return + item.blur() + self.items.remove(item) + self.group.widgets.remove(item) + self.table.remove_row(item.style.row) + +#class List(ListArea): +# def __init__(self,*args,**params): +# print 'gui.List','Scheduled to be renamed to ListArea. API may also be changed in the future.' +# ListArea.__init__(self,*args,**params) diff --git a/pgu/gui/area.pyc b/pgu/gui/area.pyc Binary files differnew file mode 100644 index 0000000..f2aa1d1 --- /dev/null +++ b/pgu/gui/area.pyc diff --git a/pgu/gui/basic.py b/pgu/gui/basic.py new file mode 100644 index 0000000..093c6ee --- /dev/null +++ b/pgu/gui/basic.py @@ -0,0 +1,124 @@ +"""These widgets are all grouped together because they are non-interactive widgets. +""" + +import pygame + +from const import * +import widget + +# Turns a descriptive string or a tuple into a pygame color +def parse_color(desc): + if (isinstance(desc, pygame.Color)): + # Already a color + return desc + elif (desc and desc[0] == "#"): + # Because of a bug in pygame 1.8.1 we need to explicitly define the + # alpha value otherwise it will default to transparent. + if (len(desc) == 7): + desc += "FF" + return pygame.Color(desc) + + +class Spacer(widget.Widget): + """A invisible space. + + <pre>Spacer(width,height)</pre> + + """ + def __init__(self,width,height,**params): + params.setdefault('focusable',False) + widget.Widget.__init__(self,width=width,height=height,**params) + + +class Color(widget.Widget): + """A block of color. + + <p>The color can be changed at run-time.</p> + + <pre>Color(value=None)</pre> + + <strong>Example</strong> + <code> + c = Color() + c.value = (255,0,0) + c.value = (0,255,0) + </code> + """ + + + def __init__(self,value=None,**params): + params.setdefault('focusable',False) + if value != None: params['value']=value + widget.Widget.__init__(self,**params) + + def paint(self,s): + if hasattr(self,'value'): s.fill(self.value) + + def __setattr__(self,k,v): + if k == 'value' and type(v) == str: + v = parse_color(v) + _v = self.__dict__.get(k,NOATTR) + self.__dict__[k]=v + if k == 'value' and _v != NOATTR and _v != v: + self.send(CHANGE) + self.repaint() + +class Label(widget.Widget): + """A text label. + + <pre>Label(value)</pre> + + <dl> + <dt>value<dd>text to be displayed + </dl> + + <strong>Example</strong> + <code> + w = Label(value="I own a rubber chicken!") + + w = Label("3 rubber chickens") + </code> + """ + def __init__(self,value,**params): + params.setdefault('focusable',False) + params.setdefault('cls','label') + widget.Widget.__init__(self,**params) + self.value = value + self.font = self.style.font + self.style.width, self.style.height = self.font.size(self.value) + + def paint(self,s): + s.blit(self.font.render(self.value, 1, self.style.color),(0,0)) + +class Image(widget.Widget): + """An image. + + <pre>Image(value)</pre> + + <dl> + <dt>value<dd>a file name or a pygame.Surface + </dl> + + """ + def __init__(self,value,**params): + params.setdefault('focusable',False) + widget.Widget.__init__(self,**params) + if type(value) == str: value = pygame.image.load(value) + + ow,oh = iw,ih = value.get_width(),value.get_height() + sw,sh = self.style.width,self.style.height + + if sw and not sh: + iw,ih = sw,ih*sw/iw + elif sh and not sw: + iw,ih = iw*sh/ih,sh + elif sw and sh: + iw,ih = sw,sh + + if (ow,oh) != (iw,ih): + value = pygame.transform.scale(value,(iw,ih)) + self.style.width,self.style.height = iw,ih + self.value = value + + def paint(self,s): + s.blit(self.value,(0,0)) diff --git a/pgu/gui/basic.pyc b/pgu/gui/basic.pyc Binary files differnew file mode 100644 index 0000000..80e1e93 --- /dev/null +++ b/pgu/gui/basic.pyc diff --git a/pgu/gui/button.py b/pgu/gui/button.py new file mode 100644 index 0000000..90cdd1e --- /dev/null +++ b/pgu/gui/button.py @@ -0,0 +1,351 @@ +""" +""" + +from pygame.locals import * + +from const import * +import widget, surface +import basic + +class _button(widget.Widget): + def __init__(self,**params): + widget.Widget.__init__(self,**params) + self.state = 0 + + def event(self,e): + if e.type == ENTER: self.repaint() + elif e.type == EXIT: self.repaint() + elif e.type == FOCUS: self.repaint() + elif e.type == BLUR: self.repaint() + elif e.type == KEYDOWN: + if e.key == K_SPACE or e.key == K_RETURN: + self.state = 1 + self.repaint() + elif e.type == MOUSEBUTTONDOWN: + self.state = 1 + self.repaint() + elif e.type == KEYUP: + if self.state == 1: + sub = pygame.event.Event(CLICK,{'pos':(0,0),'button':1}) + #self.send(sub.type,sub) + self._event(sub) + + self.state = 0 + self.repaint() + elif e.type == MOUSEBUTTONUP: + self.state = 0 + self.repaint() + elif e.type == CLICK: + self.click() + + self.pcls = "" + if self.state == 0 and self.container.myhover is self: + self.pcls = "hover" + if self.state == 1 and self.container.myhover is self: + self.pcls = "down" + + def click(self): + pass + + +class Button(_button): + """A button, buttons can be clicked, they are usually used to set up callbacks. + + <pre>Button(value=None)</pre> + + <dl> + <dt>value<dd>either a widget or a string + </dl> + + <strong>Example</strong> + <code> + w = gui.Button("Click Me") + w.connect(gui.CLICK,fnc,value) + </code> + """ + def __init__(self,value=None,**params): + params.setdefault('cls','button') + _button.__init__(self,**params) + self.value = value + + def __setattr__(self,k,v): + if k == 'value' and type(v) == str: v = basic.Label(v,cls=self.cls+".label") + _v = self.__dict__.get(k,NOATTR) + self.__dict__[k]=v + if k == 'value' and v != None: + pass + + if k == 'value' and _v != NOATTR and _v != None and _v != v: + self.send(CHANGE) + self.chsize() + + def resize(self,width=None,height=None): + self.value.rect.x,self.value.rect.y = 0,0 + self.value.rect.w,self.value.rect.h = self.value.resize(width,height) + return self.value.rect.w,self.value.rect.h +# +# self.value._resize() +# self.rect.w,self.rect.h = self.value.rect_margin.w,self.value.rect_margin.h +# +# if self.style.width: self.rect.w = max(self.rect.w,self.style.width) +# if self.style.height: self.rect.w = max(self.rect.w,self.style.height) +# +# xt,xr,xb,xl = self.value.getspacing() +# +# self.value._resize(self.rect.w-(xl+xr),self.rect.h-(xt+xb)) +# + def paint(self,s): + self.value.pcls = self.pcls + self.value.paint(surface.subsurface(s,self.value.rect)) + +class Switch(_button): + """A switch can have two states, True or False. + + <pre>Switch(value=False)</pre> + + <dl> + <dt>value<dd>initial value, (True, False) + </dl> + + <strong>Example</strong> + <code> + w = gui.Switch(True) + w.connect(gui.CHANGE,fnc,value) + </code> + """ + def __init__(self,value=False,**params): + params.setdefault('cls','switch') + _button.__init__(self,**params) + self.value = value + + img = self.style.off + self.style.width = img.get_width() + self.style.height = img.get_height() + + def paint(self,s): + #self.pcls = "" + #if self.container.myhover is self: self.pcls = "hover" + if self.value: img = self.style.on + else: img = self.style.off + s.blit(img,(0,0)) + + def __setattr__(self,k,v): + _v = self.__dict__.get(k,NOATTR) + self.__dict__[k]=v + if k == 'value' and _v != NOATTR and _v != v: + self.send(CHANGE) + self.repaint() + + def click(self): + self.value = not self.value + +class Checkbox(_button): + """Within a Group of Checkbox widgets several may be selected at a time. + + <pre>Checkbox(group,value=None)</pre> + + <dl> + <dt>group<dd>a gui.Group for the Checkbox to belong to + <dt>value<dd>the value + </dl> + + <strong>Example</strong> + <code> + g = gui.Group(name='colors',value=['r','b']) + + t = gui.Table() + t.tr() + t.td(gui.Label('Red')) + t.td(gui.Checkbox(g,'r')) + t.tr() + t.td(gui.Label('Green')) + t.td(gui.Checkbox(g,'g')) + t.tr() + t.td(gui.Label('Blue')) + t.td(gui.Checkbox(g,'b')) + </code> + """ + + def __init__(self,group,value=None,**params): + params.setdefault('cls','checkbox') + _button.__init__(self,**params) + self.group = group + self.group.add(self) + if self.group.value == None: + self.group.value = [] + self.value = value + + img = self.style.off + self.style.width = img.get_width() + self.style.height = img.get_height() + + def paint(self,s): + #self.pcls = "" + #if self.container.myhover is self: self.pcls = "hover" + if self.value in self.group.value: img = self.style.on + else: img = self.style.off + + s.blit(img,(0,0)) + + def click(self): + if self.value in self.group.value: + self.group.value.remove(self.value) + else: + self.group.value.append(self.value) + self.group._change() + +class Radio(_button): + """Within a Group of Radio widgets only one may be selected at a time. + + <pre>Radio(group,value=None)</pre> + + <dl> + <dt>group<dd>a gui.Group for the Radio to belong to + <dt>value<dd>the value + </dl> + + <strong>Example</strong> + <code> + g = gui.Group(name='colors',value='g') + + t = gui.Table() + t.tr() + t.td(gui.Label('Red')) + t.td(gui.Radio(g,'r')) + t.tr() + t.td(gui.Label('Green')) + t.td(gui.Radio(g,'g')) + t.tr() + t.td(gui.Label('Blue')) + t.td(gui.Radio(g,'b')) + </code> + """ + + + def __init__(self,group=None,value=None,**params): + params.setdefault('cls','radio') + _button.__init__(self,**params) + self.group = group + self.group.add(self) + self.value = value + + img = self.style.off + self.style.width = img.get_width() + self.style.height = img.get_height() + + def paint(self,s): + #self.pcls = "" + #if self.container.myhover is self: self.pcls = "hover" + if self.group.value == self.value: img = self.style.on + else: img = self.style.off + s.blit(img,(0,0)) + + def click(self): + self.group.value = self.value + +class Tool(_button): + """Within a Group of Tool widgets only one may be selected at a time. + + <pre>Tool(group,widget=None,value=None)</pre> + + <dl> + <dt>group<dd>a gui.Group for the Tool to belong to + <dt>widget<dd>a widget to appear on the Tool (similar to a Button) + <dt>value<dd>the value + </dl> + + <strong>Example</strong> + <code> + g = gui.Group(name='colors',value='g') + + t = gui.Table() + t.tr() + t.td(gui.Tool(g,'Red','r')) + t.tr() + t.td(gui.Tool(g,'Green','g')) + t.tr() + t.td(gui.Tool(g,'Blue','b')) + </code> + """ + + def __init__(self,group,widget=None,value=None,**params): #TODO widget= could conflict with module widget + params.setdefault('cls','tool') + _button.__init__(self,**params) + self.group = group + self.group.add(self) + self.value = value + + if widget: + self.setwidget(widget) + + if self.group.value == self.value: self.pcls = "down" + + def setwidget(self,w): + self.widget = w + + def resize(self,width=None,height=None): + self.widget.rect.w,self.widget.rect.h = self.widget.resize() + #self.widget._resize() + #self.rect.w,self.rect.h = self.widget.rect_margin.w,self.widget.rect_margin.h + + return self.widget.rect.w,self.widget.rect.h + + def event(self,e): + _button.event(self,e) + if self.group.value == self.value: self.pcls = "down" + + def paint(self,s): + if self.group.value == self.value: self.pcls = "down" + self.widget.paint(surface.subsurface(s,self.widget.rect)) + + def click(self): + self.group.value = self.value + for w in self.group.widgets: + if w != self: w.pcls = "" + + +class Icon(_button): + """TODO - might be deprecated + """ + def __init__(self,cls,**params): + params['cls'] = cls + _button.__init__(self,**params) + s = self.style.image + self.style.width = s.get_width() + self.style.height = s.get_height() + self.state = 0 + + def paint(self,s): + #self.pcls = "" + #if self.state == 0 and hasattr(self.container,'myhover') and self.container.myhover is self: self.pcls = "hover" + #if self.state == 1 and hasattr(self.container,'myhover') and self.container.myhover is self: self.pcls = "down" + s.blit(self.style.image,(0,0)) + +class Link(_button): + """A link, links can be clicked, they are usually used to set up callbacks. + Basically the same as the button widget, just text only with a different cls. Made for + convenience. + + <pre>Link(value=None)</pre> + + <dl> + <dt>value<dd>a string + </dl> + + <strong>Example</strong> + <code> + w = gui.Link("Click Me") + w.connect(gui.CLICK,fnc,value) + </code> + """ + def __init__(self,value,**params): + params.setdefault('focusable',True) + params.setdefault('cls','link') + _button.__init__(self,**params) + self.value = value + self.font = self.style.font + self.style.width, self.style.height = self.font.size(self.value) + + def paint(self,s): + s.blit(self.font.render(self.value, 1, self.style.color),(0,0)) + diff --git a/pgu/gui/button.pyc b/pgu/gui/button.pyc Binary files differnew file mode 100644 index 0000000..050236a --- /dev/null +++ b/pgu/gui/button.pyc diff --git a/pgu/gui/const.py b/pgu/gui/const.py new file mode 100644 index 0000000..c865cd8 --- /dev/null +++ b/pgu/gui/const.py @@ -0,0 +1,45 @@ +"""Constants. +<br><br> +<strong>Event Types</strong> + +<p>from pygame</p> +<dl> +<dt>QUIT +<dt>MOUSEBUTTONDOWN +<dt>MOUSEBUTTONUP +<dt>MOUSEMOTION +<dt>KEYDOWN +</dl> + +<p>gui specific</p> +<dl> +<dt>ENTER +<dt>EXIT +<dt>BLUR +<dt>FOCUS +<dt>CLICK +<dt>CHANGE +<dt>OPEN +<dt>CLOSE +<dt>INIT +</dl> + +<strong>Other</strong> +<dl> +<dt>NOATTR +</dl> +""" +import pygame + +from pygame.locals import QUIT, MOUSEBUTTONDOWN, MOUSEBUTTONUP, MOUSEMOTION, KEYDOWN, USEREVENT +ENTER = pygame.locals.USEREVENT + 0 +EXIT = pygame.locals.USEREVENT + 1 +BLUR = pygame.locals.USEREVENT + 2 +FOCUS = pygame.locals.USEREVENT + 3 +CLICK = pygame.locals.USEREVENT + 4 +CHANGE = pygame.locals.USEREVENT + 5 +OPEN = pygame.locals.USEREVENT + 6 +CLOSE = pygame.locals.USEREVENT + 7 +INIT = 'init' + +class NOATTR: pass
\ No newline at end of file diff --git a/pgu/gui/const.pyc b/pgu/gui/const.pyc Binary files differnew file mode 100644 index 0000000..6694c12 --- /dev/null +++ b/pgu/gui/const.pyc diff --git a/pgu/gui/container.py b/pgu/gui/container.py new file mode 100644 index 0000000..b3dd810 --- /dev/null +++ b/pgu/gui/container.py @@ -0,0 +1,455 @@ +""" +""" +import pygame +from pygame.locals import * + +from const import * +import widget, surface +import pguglobals + +class Container(widget.Widget): + """The base container widget, can be used as a template as well as stand alone. + + <pre>Container()</pre> + """ + def __init__(self,**params): + widget.Widget.__init__(self,**params) + self.myfocus = None + self.mywindow = None + self.myhover = None + #self.background = 0 + self.widgets = [] + self.windows = [] + self.toupdate = {} + self.topaint = {} + + def update(self,s): + updates = [] + + if self.myfocus: self.toupdate[self.myfocus] = self.myfocus + + for w in self.topaint: + if w is self.mywindow: + continue + else: + sub = surface.subsurface(s,w.rect) + sub.blit(w._container_bkgr,(0,0)) + w.paint(sub) + updates.append(pygame.rect.Rect(w.rect)) + + for w in self.toupdate: + if w is self.mywindow: + continue + else: + us = w.update(surface.subsurface(s,w.rect)) + if us: + for u in us: + updates.append(pygame.rect.Rect(u.x + w.rect.x,u.y+w.rect.y,u.w,u.h)) + + for w in self.topaint: + if w is self.mywindow: + w.paint(self.top_surface(s,w)) + updates.append(pygame.rect.Rect(w.rect)) + else: + continue + + for w in self.toupdate: + if w is self.mywindow: + us = w.update(self.top_surface(s,w)) + else: + continue + if us: + for u in us: + updates.append(pygame.rect.Rect(u.x + w.rect.x,u.y+w.rect.y,u.w,u.h)) + + self.topaint = {} + self.toupdate = {} + + return updates + + def repaint(self,w=None): + if not w: + return widget.Widget.repaint(self) + self.topaint[w] = w + self.reupdate() + + def reupdate(self,w=None): + if not w: + return widget.Widget.reupdate(self) + self.toupdate[w] = w + self.reupdate() + + def paint(self,s): + self.toupdate = {} + self.topaint = {} + for w in self.widgets: + try: + sub = surface.subsurface(s, w.rect) + except: + print 'container.paint(): %s not inside %s' % ( + w.__class__.__name__,self.__class__.__name__) + print s.get_width(), s.get_height(), w.rect + print "" + else: + if (not (hasattr(w,'_container_bkgr') and + w._container_bkgr.get_width() == sub.get_width() and + w._container_bkgr.get_height() == sub.get_height())): + w._container_bkgr = sub.copy() + w._container_bkgr.fill((0,0,0,0)) + w._container_bkgr.blit(sub,(0,0)) + + w.paint(sub) + + for w in self.windows: + print 'container: windows', len(self.windows), s, w.rect + w.paint(self.top_surface(s,w)) + + def top_surface(self,s,w): + x,y = s.get_abs_offset() + s = s.get_abs_parent() + return surface.subsurface(s,(x+w.rect.x,y+w.rect.y,w.rect.w,w.rect.h)) + + def event(self,e): + used = False + + if self.mywindow and e.type == MOUSEBUTTONDOWN: + w = self.mywindow + if self.myfocus is w: + if not w.rect.collidepoint(e.pos): self.blur(w) + if not self.myfocus: + if w.rect.collidepoint(e.pos): self.focus(w) + + if not self.mywindow: + #### by Gal Koren + ## + ## if e.type == FOCUS: + if e.type == FOCUS and not self.myfocus: + #self.first() + pass + elif e.type == EXIT: + if self.myhover: self.exit(self.myhover) + elif e.type == BLUR: + if self.myfocus: self.blur(self.myfocus) + elif e.type == MOUSEBUTTONDOWN: + h = None + for w in self.widgets: + if not w.disabled: #focusable not considered, since that is only for tabs + if w.rect.collidepoint(e.pos): + h = w + if self.myfocus is not w: self.focus(w) + if not h and self.myfocus: + self.blur(self.myfocus) + elif e.type == MOUSEMOTION: + if 1 in e.buttons: + if self.myfocus: ws = [self.myfocus] + else: ws = [] + else: ws = self.widgets + + h = None + for w in ws: + if w.rect.collidepoint(e.pos): + h = w + if self.myhover is not w: self.enter(w) + if not h and self.myhover: + self.exit(self.myhover) + w = self.myhover + + if w and w is not self.myfocus: + sub = pygame.event.Event(e.type,{ + 'buttons':e.buttons, + 'pos':(e.pos[0]-w.rect.x,e.pos[1]-w.rect.y), + 'rel':e.rel}) + used = w._event(sub) + + w = self.myfocus + if w: + sub = e + + if e.type == MOUSEBUTTONUP or e.type == MOUSEBUTTONDOWN: + sub = pygame.event.Event(e.type,{ + 'button':e.button, + 'pos':(e.pos[0]-w.rect.x,e.pos[1]-w.rect.y)}) + used = w._event(sub) + elif e.type == CLICK and self.myhover is w: + sub = pygame.event.Event(e.type,{ + 'button':e.button, + 'pos':(e.pos[0]-w.rect.x,e.pos[1]-w.rect.y)}) + used = w._event(sub) + elif e.type == CLICK: #a dead click + pass + elif e.type == MOUSEMOTION: + sub = pygame.event.Event(e.type,{ + 'buttons':e.buttons, + 'pos':(e.pos[0]-w.rect.x,e.pos[1]-w.rect.y), + 'rel':e.rel}) + used = w._event(sub) + else: + used = w._event(sub) + + if not used: + if e.type is KEYDOWN: + if e.key is K_TAB and self.myfocus: + if (e.mod&KMOD_SHIFT) == 0: + self.myfocus.next() + else: + self.myfocus.previous() + return True + elif e.key == K_UP: + self._move_focus(0,-1) + return True + elif e.key == K_RIGHT: + self._move_focus(1,0) + return True + elif e.key == K_DOWN: + self._move_focus(0,1) + return True + elif e.key == K_LEFT: + self._move_focus(-1,0) + return True + return used + + def _move_focus(self,dx_,dy_): + myfocus = self.myfocus + if not self.myfocus: return + + from pgu.gui import App + widgets = self._get_widgets(pguglobals.app) + #if myfocus not in widgets: return + #widgets.remove(myfocus) + if myfocus in widgets: + widgets.remove(myfocus) + rect = myfocus.get_abs_rect() + fx,fy = rect.centerx,rect.centery + + def sign(v): + if v < 0: return -1 + if v > 0: return 1 + return 0 + + dist = [] + for w in widgets: + wrect = w.get_abs_rect() + wx,wy = wrect.centerx,wrect.centery + dx,dy = wx-fx,wy-fy + if dx_ > 0 and wrect.left < rect.right: continue + if dx_ < 0 and wrect.right > rect.left: continue + if dy_ > 0 and wrect.top < rect.bottom: continue + if dy_ < 0 and wrect.bottom > rect.top: continue + dist.append((dx*dx+dy*dy,w)) + if not len(dist): return + dist.sort() + d,w = dist.pop(0) + w.focus() + + def _get_widgets(self,c): + widgets = [] + if c.mywindow: + widgets.extend(self._get_widgets(c.mywindow)) + else: + for w in c.widgets: + if isinstance(w,Container): + widgets.extend(self._get_widgets(w)) + elif not w.disabled and w.focusable: + widgets.append(w) + return widgets + + def remove(self,w): + """Remove a widget from the container. + + <pre>Container.remove(w)</pre> + """ + self.blur(w) + self.widgets.remove(w) + #self.repaint() + self.chsize() + + def add(self,w,x,y): + """Add a widget to the container. + + <pre>Container.add(w,x,y)</pre> + + <dl> + <dt>x, y<dd>position of the widget + </dl> + """ + w.style.x = x + w.style.y = y + w.container = self + #NOTE: this might fix it, sort of... + #but the thing is, we don't really want to resize + #something if it is going to get resized again later + #for no reason... + #w.rect.x,w.rect.y = w.style.x,w.style.y + #w.rect.w, w.rect.h = w.resize() + self.widgets.append(w) + self.chsize() + + def open(self,w=None,x=None,y=None): + from app import App #HACK: I import it here to prevent circular importing + if not w: + if (not hasattr(self,'container') or + not self.container) and self is not pguglobals.app: + self.container = pguglobals.app + #print 'top level open' + return widget.Widget.open(self) + + if self.container: + if x != None: return self.container.open(w,self.rect.x+x,self.rect.y+y) + return self.container.open(w) + + w.container = self + + if w.rect.w == 0 or w.rect.h == 0: #this might be okay, not sure if needed. + #_chsize = App.app._chsize #HACK: we don't want this resize to trigger a chsize. + w.rect.w,w.rect.h = w.resize() + #App.app._chsize = _chsize + + if x == None or y == None: #auto center the window + #w.style.x,w.style.y = 0,0 + w.rect.x = (self.rect.w-w.rect.w)/2 + w.rect.y = (self.rect.h-w.rect.h)/2 + #w.resize() + #w._resize(self.rect.w,self.rect.h) + else: #show it where we want it + w.rect.x = x + w.rect.y = y + #w._resize() + + + self.windows.append(w) + self.mywindow = w + self.focus(w) + self.repaint(w) + w.send(OPEN) + + def close(self,w=None): + if not w: + return widget.Widget.close(self) + + if self.container: #make sure we're in the App + return self.container.close(w) + + if self.myfocus is w: self.blur(w) + + if w not in self.windows: return #no need to remove it twice! happens. + + self.windows.remove(w) + + self.mywindow = None + if self.windows: + self.mywindow = self.windows[-1] + self.focus(self.mywindow) + + if not self.mywindow: + self.myfocus = self.widget #HACK: should be done fancier, i think.. + if not self.myhover: + self.enter(self.widget) + + self.repaintall() + w.send(CLOSE) + + def focus(self,w=None): + widget.Widget.focus(self) ### by Gal koren +# if not w: +# return widget.Widget.focus(self) + if not w: return + if self.myfocus: self.blur(self.myfocus) + if self.myhover is not w: self.enter(w) + self.myfocus = w + w._event(pygame.event.Event(FOCUS)) + + #print self.myfocus,self.myfocus.__class__.__name__ + + def blur(self,w=None): + if not w: + return widget.Widget.blur(self) + if self.myfocus is w: + if self.myhover is w: self.exit(w) + self.myfocus = None + w._event(pygame.event.Event(BLUR)) + + def enter(self,w): + if self.myhover: self.exit(self.myhover) + self.myhover = w + w._event(pygame.event.Event(ENTER)) + + def exit(self,w): + if self.myhover and self.myhover is w: + self.myhover = None + w._event(pygame.event.Event(EXIT)) + + +# def first(self): +# for w in self.widgets: +# if w.focusable: +# self.focus(w) +# return +# if self.container: self.container.next(self) + +# def next(self,w): +# if w not in self.widgets: return #HACK: maybe. this happens in windows for some reason... +# +# for w in self.widgets[self.widgets.index(w)+1:]: +# if w.focusable: +# self.focus(w) +# return +# if self.container: return self.container.next(self) + + + def _next(self,orig=None): + start = 0 + if orig in self.widgets: start = self.widgets.index(orig)+1 + for w in self.widgets[start:]: + if not w.disabled and w.focusable: + if isinstance(w,Container): + if w._next(): + return True + else: + self.focus(w) + return True + return False + + def _previous(self,orig=None): + end = len(self.widgets) + if orig in self.widgets: end = self.widgets.index(orig) + ws = self.widgets[:end] + ws.reverse() + for w in ws: + if not w.disabled and w.focusable: + if isinstance(w,Container): + if w._previous(): + return True + else: + self.focus(w) + return True + return False + + def next(self,w=None): + if w != None and w not in self.widgets: return #HACK: maybe. this happens in windows for some reason... + + if self._next(w): return True + if self.container: return self.container.next(self) + + + def previous(self,w=None): + if w != None and w not in self.widgets: return #HACK: maybe. this happens in windows for some reason... + + if self._previous(w): return True + if self.container: return self.container.previous(self) + + def resize(self,width=None,height=None): + #r = self.rect + #r.w,r.h = 0,0 + ww,hh = 0,0 + if self.style.width: ww = self.style.width + if self.style.height: hh = self.style.height + + for w in self.widgets: + #w.rect.w,w.rect.h = 0,0 + w.rect.x,w.rect.y = w.style.x,w.style.y + w.rect.w, w.rect.h = w.resize() + #w._resize() + + ww = max(ww,w.rect.right) + hh = max(hh,w.rect.bottom) + return ww,hh diff --git a/pgu/gui/container.pyc b/pgu/gui/container.pyc Binary files differnew file mode 100644 index 0000000..6379fe2 --- /dev/null +++ b/pgu/gui/container.pyc diff --git a/pgu/gui/deprecated.py b/pgu/gui/deprecated.py new file mode 100644 index 0000000..8d53515 --- /dev/null +++ b/pgu/gui/deprecated.py @@ -0,0 +1,76 @@ +import pygame + +from const import * +import table +import group +import button, basic +import pguglobals + +def action_open(value): + print 'gui.action_open',"Scheduled to be deprecated." + value.setdefault('x',None) + value.setdefault('y',None) + value['container'].open(value['window'],value['x'],value['y']) + +def action_setvalue(value): + print 'gui.action_setvalue',"Scheduled to be deprecated." + a,b = value + b.value = a.value + +def action_quit(value): + print 'gui.action_quit',"Scheduled to be deprecated." + value.quit() + +def action_exec(value): + print 'gui.action_exec',"Scheduled to be deprecated." + exec(value['script'],globals(),value['dict']) + +class Toolbox(table.Table): + def __setattr__(self,k,v): + _v = self.__dict__.get(k,NOATTR) + self.__dict__[k]=v + if k == 'value' and _v != NOATTR and _v != v: + self.group.value = v + for w in self.group.widgets: + if w.value != v: w.pcls = "" + else: w.pcls = "down" + self.repaint() + + def _change(self,value): + self.value = self.group.value + self.send(CHANGE) + + def __init__(self,data,cols=0,rows=0,tool_cls='tool',value=None,**params): + print 'gui.Toolbox','Scheduled to be deprecated.' + params.setdefault('cls','toolbox') + table.Table.__init__(self,**params) + + if cols == 0 and rows == 0: cols = len(data) + if cols != 0 and rows != 0: rows = 0 + + self.tools = {} + + _value = value + + g = group.Group() + self.group = g + g.connect(CHANGE,self._change,None) + self.group.value = _value + + x,y,p,s = 0,0,None,1 + for ico,value in data: + #from __init__ import theme + img = pguglobals.app.theme.get(tool_cls+"."+ico,"","image") + if img: + i = basic.Image(img) + else: i = basic.Label(ico,cls=tool_cls+".label") + p = button.Tool(g,i,value,cls=tool_cls) + self.tools[ico] = p + #p.style.hexpand = 1 + #p.style.vexpand = 1 + self.add(p,x,y) + s = 0 + if cols != 0: x += 1 + if cols != 0 and x == cols: x,y = 0,y+1 + if rows != 0: y += 1 + if rows != 0 and y == rows: x,y = x+1,0 diff --git a/pgu/gui/deprecated.pyc b/pgu/gui/deprecated.pyc Binary files differnew file mode 100644 index 0000000..4e5c7bc --- /dev/null +++ b/pgu/gui/deprecated.pyc diff --git a/pgu/gui/dialog.py b/pgu/gui/dialog.py new file mode 100644 index 0000000..03b48f9 --- /dev/null +++ b/pgu/gui/dialog.py @@ -0,0 +1,157 @@ +""" +""" +import os + +from const import * +import table, area +import basic, input, button +import pguglobals + +class Dialog(table.Table): + """A dialog window with a title bar and an "close" button on the bar. + + <pre>Dialog(title,main)</pre> + + <dl> + <dt>title<dd>title widget, usually a label + <dt>main<dd>main widget, usually a container + </dl> + + <strong>Example</strong> + <code> + title = gui.Label("My Title") + main = gui.Container() + #add stuff to the container... + + d = gui.Dialog(title,main) + d.open() + </code> + """ + def __init__(self,title,main,**params): + params.setdefault('cls','dialog') + table.Table.__init__(self,**params) + + + self.tr() + self.td(title,align=-1,cls=self.cls+'.bar') + clos = button.Icon(self.cls+".bar.close") + clos.connect(CLICK,self.close,None) + self.td(clos,align=1,cls=self.cls+'.bar') + + self.tr() + self.td(main,colspan=2,cls=self.cls+".main") + + +# self.tr() +# +# +# t = table.Table(cls=self.cls+".bar") +# t.tr() +# t.td(title) +# clos = button.Icon(self.cls+".bar.close") +# t.td(clos,align=1) +# clos.connect(CLICK,self.close,None) +# self.add(t,0,0) +# +# main.rect.w,main.rect.h = main.resize() +# clos.rect.w,clos.rect.h = clos.resize() +# title.container.style.width = main.rect.w - clos.rect.w +# +# self.tr() +# self.td(main,cls=self.cls+".main") +# + + +class FileDialog(Dialog): + """A file picker dialog window. + + <pre>FileDialog()</pre> + <p>Some optional parameters:</p> + <dl> + <dt>title_txt<dd>title text + <dt>button_txt<dd>button text + <dt>path<dd>initial path + </dl> + """ + + def __init__(self, title_txt="File Browser", button_txt="Okay", cls="dialog", path=None): + + cls1 = 'filedialog' + if not path: self.curdir = os.getcwd() + else: self.curdir = path + self.dir_img = basic.Image( + pguglobals.app.theme.get(cls1+'.folder', '', 'image')) + td_style = {'padding_left': 4, + 'padding_right': 4, + 'padding_top': 2, + 'padding_bottom': 2} + self.title = basic.Label(title_txt, cls=cls+".title.label") + self.body = table.Table() + self.list = area.List(width=350, height=150) + self.input_dir = input.Input() + self.input_file = input.Input() + self._list_dir_() + self.button_ok = button.Button(button_txt) + self.body.tr() + self.body.td(basic.Label("Folder"), style=td_style, align=-1) + self.body.td(self.input_dir, style=td_style) + self.body.tr() + self.body.td(self.list, colspan=3, style=td_style) + self.list.connect(CHANGE, self._item_select_changed_, None) + self.button_ok.connect(CLICK, self._button_okay_clicked_, None) + self.body.tr() + self.body.td(basic.Label("File"), style=td_style, align=-1) + self.body.td(self.input_file, style=td_style) + self.body.td(self.button_ok, style=td_style) + self.value = None + Dialog.__init__(self, self.title, self.body) + + def _list_dir_(self): + self.input_dir.value = self.curdir + self.input_dir.pos = len(self.curdir) + self.input_dir.vpos = 0 + dirs = [] + files = [] + try: + for i in os.listdir(self.curdir): + if os.path.isdir(os.path.join(self.curdir, i)): dirs.append(i) + else: files.append(i) + except: + self.input_file.value = "Opps! no access" + #if '..' not in dirs: dirs.append('..') + dirs.sort() + dirs = ['..'] + dirs + + files.sort() + for i in dirs: + #item = ListItem(image=self.dir_img, text=i, value=i) + self.list.add(i,image=self.dir_img,value=i) + for i in files: + #item = ListItem(image=None, text=i, value=i) + self.list.add(i,value=i) + #self.list.resize() + self.list.set_vertical_scroll(0) + #self.list.repaintall() + + + def _item_select_changed_(self, arg): + self.input_file.value = self.list.value + fname = os.path.abspath(os.path.join(self.curdir, self.input_file.value)) + if os.path.isdir(fname): + self.input_file.value = "" + self.curdir = fname + self.list.clear() + self._list_dir_() + + + def _button_okay_clicked_(self, arg): + if self.input_dir.value != self.curdir: + if os.path.isdir(self.input_dir.value): + self.input_file.value = "" + self.curdir = os.path.abspath(self.input_dir.value) + self.list.clear() + self._list_dir_() + else: + self.value = os.path.join(self.curdir, self.input_file.value) + self.send(CHANGE) + self.close() diff --git a/pgu/gui/dialog.pyc b/pgu/gui/dialog.pyc Binary files differnew file mode 100644 index 0000000..8837016 --- /dev/null +++ b/pgu/gui/dialog.pyc diff --git a/pgu/gui/document.py b/pgu/gui/document.py new file mode 100644 index 0000000..1bd96df --- /dev/null +++ b/pgu/gui/document.py @@ -0,0 +1,112 @@ +""" +""" +import pygame + +import container +import layout + +class _document_widget: + def __init__(self,w,align=None): + #w.rect.w,w.rect.h = w.resize() + #self.rect = w.rect + self.widget = w + if align != None: self.align = align + +class Document(container.Container): + """A document container contains many widgets strung together in a document format. (How informative!) + + <pre>Document()</pre> + + """ + def __init__(self,**params): + params.setdefault('cls','document') + container.Container.__init__(self,**params) + self.layout = layout.Layout(pygame.Rect(0,0,self.rect.w,self.rect.h)) + + def add(self,e,align=None): + """Add a widget. + + <pre>Document.add(e,align=None)</pre> + + <dl> + <dt>e<dd>widget + <dt>align<dd>alignment (None,-1,0,1) + </dl> + """ + dw = _document_widget(e,align) + self.layout.add(dw) + e.container = self + e._c_dw = dw + self.widgets.append(e) + self.chsize() + + def remove(self,e): + self.layout._widgets.remove(e._c_dw) + self.widgets.remove(e) + self.chsize() + + + def block(self,align): + """Start a new block. + + <pre>Document.block(align)</pre> + + <dl> + <dt>align<dd>alignment of block (-1,0,1) + </dl> + """ + self.layout.add(align) + + def space(self,e): + """Add a spacer. + + <pre>Document.space(e)</pre> + + <dl> + <dt>e<dd>a (w,h) size for the spacer + </dl> + """ + self.layout.add(e) + + def br(self,height): + """Add a line break. + + <pre>Document.br(height)</pre> + + <dl> + <dt>height<dd>height of line break + </dl> + """ + self.layout.add((0,height)) + + def resize(self,width=None,height=None): + if self.style.width: width = self.style.width + if self.style.height: height = self.style.height + + for w in self.widgets: + w.rect.w,w.rect.h = w.resize() + + if (width != None and w.rect.w > width) or (height != None and w.rect.h > height): + w.rect.w,w.rect.h = w.resize(width,height) + + dw = w._c_dw + dw.rect = pygame.Rect(0,0,w.rect.w,w.rect.h) + + if width == None: width = 65535 + self.layout.rect = pygame.Rect(0,0,width,0) + self.layout.resize() + + _max_w = 0 + + for w in self.widgets: + #xt,xl,xb,xr = w.getspacing() + dw = w._c_dw + w.style.x,w.style.y,w.rect.w,w.rect.h = dw.rect.x,dw.rect.y,dw.rect.w,dw.rect.h + #w.resize() + w.rect.x,w.rect.y = w.style.x,w.style.y + _max_w = max(_max_w,w.rect.right) + + #self.rect.w = _max_w #self.layout.rect.w + #self.rect.h = self.layout.rect.h + #print 'document',_max_w,self.layout.rect.h + return _max_w,self.layout.rect.h diff --git a/pgu/gui/document.pyc b/pgu/gui/document.pyc Binary files differnew file mode 100644 index 0000000..1cbdfed --- /dev/null +++ b/pgu/gui/document.pyc diff --git a/pgu/gui/form.py b/pgu/gui/form.py new file mode 100644 index 0000000..9a874f9 --- /dev/null +++ b/pgu/gui/form.py @@ -0,0 +1,79 @@ +""" +""" +import widget + +class Form(widget.Widget): + """A form that automatically will contain all named widgets. + + <p>After a form is created, all named widget that are subsequently created are added + to that form. You may use dict style access to access named widgets.</p> + + <pre>Form()</pre> + + <strong>Example</strong> + <code> + f = gui.Form() + + w = gui.Input("Phil",name="firstname") + w = gui.Input("Hassey",name="lastname") + + print f.results() + print '' + print f.items() + print '' + print f['firstname'].value + print f['lastname'].value + </code> + """ + + def __init__(self): + widget.Widget.__init__(self,decorate=False) + self._elist = [] + self._emap = {} + self._dirty = 0 + Form.form = self + + def add(self,e,name=None,value=None): + if name != None: e.name = name + if value != None: e.value = value + self._elist.append(e) + self._dirty = 1 + + def _clean(self): + for e in self._elist[:]: + if not hasattr(e,'name') or e.name == None: + self._elist.remove(e) + self._emap = {} + for e in self._elist: + self._emap[e.name] = e + self._dirty = 0 + + def __getitem__(self,k): + if self._dirty: self._clean() + return self._emap[k] + + def __contains__(self,k): + if self._dirty: self._clean() + if k in self._emap: return True + return False + + def results(self): + """Return a dict of name => values. + + <pre>Form.results(): return dict</pre> + """ + if self._dirty: self._clean() + r = {} + for e in self._elist: + r[e.name] = e.value + return r + + def items(self): + """Return a list of name, value keys. + + <pre>Form.items(): return list</pre> + """ + return self.results().items() + + #def start(self): + # Object.start(self,-1) diff --git a/pgu/gui/form.pyc b/pgu/gui/form.pyc Binary files differnew file mode 100644 index 0000000..f9e0e44 --- /dev/null +++ b/pgu/gui/form.pyc diff --git a/pgu/gui/group.py b/pgu/gui/group.py new file mode 100644 index 0000000..bcb231a --- /dev/null +++ b/pgu/gui/group.py @@ -0,0 +1,43 @@ +""" +""" +from const import * +import widget + +class Group(widget.Widget): + """An object for grouping together Form elements. + + <pre>Group(name=None,value=None)</pre> + + <dl> + <dt>name<dd>name as used in the Form + <dt>value<dd>values that are currently selected in the group + </dl> + + <p>See [[gui-button]] for several examples.</p> + + <p>When the value changes, an <tt>gui.CHANGE</tt> event is sent. + Although note, that when the value is a list, it may have to be sent by hand via + <tt>g.send(gui.CHANGE)</tt></p> + """ + + def __init__(self,name=None,value=None): + widget.Widget.__init__(self,name=name,value=value) + self.widgets = [] + + def add(self,w): + """Add a widget to this group. + + <pre>Group.add(w)</pre> + """ + self.widgets.append(w) + + def __setattr__(self,k,v): + _v = self.__dict__.get(k,NOATTR) + self.__dict__[k] = v + if k == 'value' and _v != NOATTR and _v != v: + self._change() + + def _change(self): + self.send(CHANGE) + for w in self.widgets: + w.repaint() diff --git a/pgu/gui/group.pyc b/pgu/gui/group.pyc Binary files differnew file mode 100644 index 0000000..1766fc6 --- /dev/null +++ b/pgu/gui/group.pyc diff --git a/pgu/gui/input.py b/pgu/gui/input.py new file mode 100644 index 0000000..74c3c9e --- /dev/null +++ b/pgu/gui/input.py @@ -0,0 +1,166 @@ +""" +""" +import pygame +from pygame.locals import * + +from const import * +import widget + +class Input(widget.Widget): + """A single line text input. + + <pre>Input(value="",size=20)</pre> + + <dl> + <dt>value<dd>initial text + <dt>size<dd>size for the text box, in characters + </dl> + + <strong>Example</strong> + <code> + w = Input(value="Cuzco the Goat",size=20) + + w = Input("Marbles") + </code> + + """ + def __init__(self,value="",size=20,**params): + params.setdefault('cls','input') + widget.Widget.__init__(self,**params) + self.value = value + self.pos = len(str(value)) + self.vpos = 0 + self.font = self.style.font + w,h = self.font.size("e"*size) + if not self.style.height: self.style.height = h + if not self.style.width: self.style.width = w + #self.style.height = max(self.style.height,h) + #self.style.width = max(self.style.width,w) + #self.rect.w=w+self.style.padding_left+self.style.padding_right; + #self.rect.h=h+self.style.padding_top+self.style.padding_bottom; + + def paint(self,s): + + r = pygame.Rect(0,0,self.rect.w,self.rect.h) + + cs = 2 #NOTE: should be in a style + + w,h = self.font.size(self.value[0:self.pos]) + x = w-self.vpos + if x < 0: self.vpos -= -x + if x+cs > s.get_width(): self.vpos += x+cs-s.get_width() + + s.blit(self.font.render(self.value, 1, self.style.color),(-self.vpos,0)) + + if self.container.myfocus is self: + w,h = self.font.size(self.value[0:self.pos]) + r.x = w-self.vpos + r.w = cs + r.h = h + s.fill(self.style.color,r) + + def _setvalue(self,v): + self.__dict__['value'] = v + self.send(CHANGE) + + def event(self,e): + used = None + if e.type == KEYDOWN: + if e.key == K_BACKSPACE: + if self.pos: + self._setvalue(self.value[:self.pos-1] + self.value[self.pos:]) + self.pos -= 1 + elif e.key == K_DELETE: + if len(self.value) > self.pos: + self._setvalue(self.value[:self.pos] + self.value[self.pos+1:]) + elif e.key == K_HOME: + self.pos = 0 + elif e.key == K_END: + self.pos = len(self.value) + elif e.key == K_LEFT: + if self.pos > 0: self.pos -= 1 + used = True + elif e.key == K_RIGHT: + if self.pos < len(self.value): self.pos += 1 + used = True + elif e.key == K_RETURN: + self.next() + elif e.key == K_TAB: + pass + else: + #c = str(e.unicode) + try: + c = (e.unicode).encode('latin-1') + if c: + self._setvalue(self.value[:self.pos] + c + self.value[self.pos:]) + self.pos += 1 + except: #ignore weird characters + pass + self.repaint() + elif e.type == FOCUS: + self.repaint() + elif e.type == BLUR: + self.repaint() + + self.pcls = "" + if self.container.myfocus is self: self.pcls = "focus" + + return used + + def __setattr__(self,k,v): + if k == 'value': + if v == None: v = '' + v = str(v) + self.pos = len(v) + _v = self.__dict__.get(k,NOATTR) + self.__dict__[k]=v + if k == 'value' and _v != NOATTR and _v != v: + self.send(CHANGE) + self.repaint() + +class Password(Input): + """A password input, text is *-ed out. + + <pre>Password(value="",size=20)</pre> + + <dl> + <dt>value<dd>initial text + <dt>size<dd>size for the text box, in characters + </dl> + + <strong>Example</strong> + <code> + w = Password(value="password",size=20) + + w = Password("53[r3+") + </code> + + """ + + def paint(self,s): + hidden="*" + show=len(self.value)*hidden + + #print "self.value:",self.value + + if self.pos == None: self.pos = len(self.value) + + r = pygame.Rect(0,0,self.rect.w,self.rect.h) + + cs = 2 #NOTE: should be in a style + + w,h = self.font.size(show) + x = w-self.vpos + if x < 0: self.vpos -= -x + if x+cs > s.get_width(): self.vpos += x+cs-s.get_width() + + s.blit(self.font.render(show, 1, self.style.color),(-self.vpos,0)) + + if self.container.myfocus is self: + #w,h = self.font.size(self.value[0:self.pos]) + w,h = self.font.size(show[0:self.pos]) + r.x = w-self.vpos + r.w = cs + r.h = h + s.fill(self.style.color,r) + diff --git a/pgu/gui/input.pyc b/pgu/gui/input.pyc Binary files differnew file mode 100644 index 0000000..f6ba8c1 --- /dev/null +++ b/pgu/gui/input.pyc diff --git a/pgu/gui/keysym.py b/pgu/gui/keysym.py new file mode 100644 index 0000000..cc24089 --- /dev/null +++ b/pgu/gui/keysym.py @@ -0,0 +1,72 @@ +""" +""" +import pygame +from pygame.locals import * + +from const import * +import widget + +class Keysym(widget.Widget): + """A keysym input. + + <p>This widget records the keysym of the key pressed while this widget is in focus.</p> + + <pre>Keysym(value=None)</pre> + + <dl> + <dt>value<dd>initial keysym, see <a href="http://www.pygame.org/docs/ref/key.html">pygame keysyms</a> </dl> + + <strong>Example</strong> + <code> + w = Input(value=pygame.locals.K_g) + + w = Input(pygame.locals.K_g) + + w = Input() + </code> + + """ + + def __init__(self,value=None,**params): + params.setdefault('cls','keysym') + widget.Widget.__init__(self,**params) + self.value = value + + self.font = self.style.font + w,h = self.font.size("Right Super") #"Right Shift") + self.style.width,self.style.height = w,h + #self.rect.w=w+self.style.padding_left+self.style.padding_right + #self.rect.h=h+self.style.padding_top+self.style.padding_bottom + + def event(self,e): + used = None + if e.type == FOCUS or e.type == BLUR: self.repaint() + elif e.type == KEYDOWN: + if e.key != K_TAB: + self.value = e.key + self.repaint() + self.send(CHANGE) + used = True + self.next() + self.pcls = "" + if self.container.myfocus is self: self.pcls = "focus" + return used + + def paint(self,s): + r = pygame.rect.Rect(0,0,self.rect.w,self.rect.h) + #render_box(s,self.style.background,r) + if self.value == None: return + name = "" + for p in pygame.key.name(self.value).split(): name += p.capitalize()+" " + #r.x = self.style.padding_left; + #r.y = self.style.padding_bottom; + s.blit(self.style.font.render(name, 1, self.style.color), r) + + def __setattr__(self,k,v): + if k == 'value' and v != None: + v = int(v) + _v = self.__dict__.get(k,NOATTR) + self.__dict__[k]=v + if k == 'value' and _v != NOATTR and _v != v: + self.send(CHANGE) + self.repaint() diff --git a/pgu/gui/keysym.pyc b/pgu/gui/keysym.pyc Binary files differnew file mode 100644 index 0000000..cc4bf2a --- /dev/null +++ b/pgu/gui/keysym.pyc diff --git a/pgu/gui/layout.py b/pgu/gui/layout.py new file mode 100644 index 0000000..01fe0cb --- /dev/null +++ b/pgu/gui/layout.py @@ -0,0 +1,172 @@ +"""document layout engine.""" +class Layout: + """the document layout engine + + .widgets -- elements are kept in this list. read-only, use add to add items to it. + """ + + def __init__(self,rect=None): + """initialize the object with the size of the box.""" + self._widgets = [] + self.rect = rect + + def add(self,e): + """add a document element to the layout. + + a document element may be + - a tuple (w,h) if it is a whitespace element + - a tuple (0,h) if it is a linebreak element + - an integer -1,0,1 if it is a command to start a new block of elements that are aligned either left,center, or right. + - an object with a .rect (for size) -- such as a word element + - an object with a .rect (for size) and .align -- such as an image element + """ + + self._widgets.append(e) + + + def resize(self): + """resize the layout + this method recalculates the position of all document elements + after they have been added to the document. .rect.x,y will be updated for all + objects. + """ + self.init() + self.widgets = [] + for e in self._widgets: + if type(e) is tuple and e[0] != 0: + self.do_space(e) + elif type(e) is tuple and e[0] == 0: + self.do_br(e[1]) + elif type(e) is int: + self.do_block(align=e) + elif hasattr(e,'align'): + self.do_align(e) + else: + self.do_item(e) + self.line() + self.rect.h = max(self.y,self.left_bottom,self.right_bottom) + + def init(self): + self.x,self.y = self.rect.x,self.rect.y + self.left = self.rect.left + self.right = self.rect.right + self.left_bottom = 0 + self.right_bottom = 0 + self.y = self.rect.y + self.x = self.rect.x + self.h = 0 + + self.items = [] + self.align = -1 + + def getleft(self): + if self.y > self.left_bottom: + self.left = self.rect.left + return self.left + + def getright(self): + if self.y > self.right_bottom: + self.right = self.rect.right + return self.right + + def do_br(self,h): + self.line() + self.h = h + + def do_block(self,align=-1): + self.line() + self.align = align + + def do_align(self,e): + align = e.align + ox,oy,oh = self.x,self.y,self.h + w,h = e.rect.w,e.rect.h + + if align == 0: + self.line() + self.x = self.rect.left + (self.rect.width-w)/2 + self.fit = 0 + elif align == -1: + self.line() + self.y = max(self.left_bottom,self.y + self.h) + self.h = 0 + self.x = self.rect.left + elif align == 1: + self.line() + self.y = max(self.right_bottom,self.y + self.h) + self.h = 0 + self.x = self.rect.left + (self.rect.width-w) + + e.rect.x,e.rect.y = self.x,self.y + + self.x = self.x + w + self.y = self.y + + if align == 0: + self.h = max(self.h,h) + self.y = self.y + self.h + self.x = self.getleft() + self.h = 0 + elif align == -1: + self.left = self.x + self.left_bottom = self.y + h + self.x,self.y,self.h = ox + w,oy,oh + elif align == 1: + self.right = self.x - w + self.right_bottom = self.y + h + self.x,self.y,self.h = ox,oy,oh + + self.widgets.append(e) + + def do_space(self,e): + w,h = e + if self.x+w >= self.getright(): + self.line() + else: + self.items.append(e) + self.h = max(self.h,h) + self.x += w + + def do_item(self,e): + w,h = e.rect.w,e.rect.h + if self.x+w >= self.getright(): + self.line() + self.items.append(e) + self.h = max(self.h,h) + self.x += w + + def line(self): + x1 = self.getleft() + x2 = self.getright() + align = self.align + y = self.y + + if len(self.items) != 0 and type(self.items[-1]) == tuple: + del self.items[-1] + + w = 0 + for e in self.items: + if type(e) == tuple: w += e[0] + else: w += e.rect.w + + if align == -1: x = x1 + elif align == 0: + x = x1 + ((x2-x1)-w)/2 + self.fit = 0 + elif align == 1: x = x2 - w + + for e in self.items: + if type(e) == tuple: x += e[0] + else: + e.rect.x,e.rect.y = x,y + self.widgets.append(e) + x += e.rect.w + + self.items = [] + self.y = self.y + self.h + self.x = self.getleft() + self.h = 0 + + + +# vim: set filetype=python sts=4 sw=4 noet si : diff --git a/pgu/gui/layout.pyc b/pgu/gui/layout.pyc Binary files differnew file mode 100644 index 0000000..4e6c10f --- /dev/null +++ b/pgu/gui/layout.pyc diff --git a/pgu/gui/menus.py b/pgu/gui/menus.py new file mode 100644 index 0000000..3f8a08f --- /dev/null +++ b/pgu/gui/menus.py @@ -0,0 +1,121 @@ +""" +""" +from const import * +import table +import basic, button + +class _Menu_Options(table.Table): + def __init__(self,menu,**params): + table.Table.__init__(self,**params) + + self.menu = menu + + def event(self,e): + handled = False + arect = self.get_abs_rect() + + if e.type == MOUSEMOTION: + abspos = e.pos[0]+arect.x,e.pos[1]+arect.y + for w in self.menu.container.widgets: + if not w is self.menu: + mrect = w.get_abs_rect() + if mrect.collidepoint(abspos): + self.menu._close(None) + w._open(None) + handled = True + + if not handled: table.Table.event(self,e) + +class _Menu(button.Button): + def __init__(self,parent,widget=None,**params): #TODO widget= could conflict with module widget + params.setdefault('cls','menu') + button.Button.__init__(self,widget,**params) + + self.parent = parent + + self._cls = self.cls + self.options = _Menu_Options(self, cls=self.cls+".options") + + self.connect(CLICK,self._open,None) + + self.pos = 0 + + def _open(self,value): + self.parent.value = self + self.pcls = 'down' + + self.repaint() + self.container.open(self.options,self.rect.x,self.rect.bottom) + self.options.connect(BLUR,self._pass,None) + self.options.blur(self.options.myfocus) + self.options.connect(BLUR,self._close,None) + self.options.focus() + self.repaint() + + def _pass(self,value): + pass + + def _close(self,value): + self.pcls = '' + self.parent.value = None + self.repaint() + self.options.close() + + def _value(self,value): + self._close(None) + if value['fnc'] != None: + value['fnc'](value['value']) + + def event(self,e): + button.Button.event(self,e) + + if self.parent.value == self: + self.pcls = 'down' + + def add(self,w,fnc=None,value=None): + w.style.align = -1 + b = button.Button(w,cls=self.cls+".option") + b.connect(CLICK,self._value,{'fnc':fnc,'value':value}) + + self.options.tr() + self.options.add(b) + + return b + +class Menus(table.Table): + """A drop down menu bar. + + <pre>Menus(data)</pre> + + <dl> + <dt>data<dd>Menu data, a list of (path,fnc,value), see example below + </dl> + + <strong>Example</strong> + <code> + data = [ + ('File/Save',fnc_save,None), + ('File/New',fnc_new,None), + ('Edit/Copy',fnc_copy,None), + ('Edit/Cut',fnc_cut,None), + ('Help/About',fnc_help,help_about_content), + ('Help/Reference',fnc_help,help_reference_content), + ] + w = Menus(data) + """ + + def __init__(self,data,menu_cls='menu',**params): + params.setdefault('cls','menus') + table.Table.__init__(self,**params) + + self.value = None + + n,m,mt = 0,None,None + for path,cmd,value in data: + parts = path.split("/") + if parts[0] != mt: + mt = parts[0] + m = _Menu(self,basic.Label(mt,cls=menu_cls+".label"),cls=menu_cls) + self.add(m,n,0) + n += 1 + m.add(basic.Label(parts[1],cls=m.cls+".option.label"),cmd,value) diff --git a/pgu/gui/menus.pyc b/pgu/gui/menus.pyc Binary files differnew file mode 100644 index 0000000..911de1b --- /dev/null +++ b/pgu/gui/menus.pyc diff --git a/pgu/gui/misc.py b/pgu/gui/misc.py new file mode 100644 index 0000000..afb10c5 --- /dev/null +++ b/pgu/gui/misc.py @@ -0,0 +1,43 @@ +from const import * +import widget +import pguglobals + +class ProgressBar(widget.Widget): + """A progress bar. + + <pre>ProgressBar(value,min,max)</pre> + + <dl> + <dt>value<dd>starting value + <dt>min<dd>minimum value rendered on the screen (usually 0) + <dt>max<dd>maximum value + </dl> + + <strong>Example</strong> + <code> + w = gui.ProgressBar(0,0,100) + w.value = 25 + </code> + """ + + def __init__(self,value,min,max,**params): + params.setdefault('cls','progressbar') + widget.Widget.__init__(self,**params) + self.min,self.max,self.value = min,max,value + + def paint(self,s): + r = pygame.rect.Rect(0,0,self.rect.w,self.rect.h) + r.w = r.w*(self.value-self.min)/(self.max-self.min) + self.bar = r + pguglobals.app.theme.render(s,self.style.bar,r) + + def __setattr__(self,k,v): + if k == 'value': + v = int(v) + v = max(v,self.min) + v = min(v,self.max) + _v = self.__dict__.get(k,NOATTR) + self.__dict__[k]=v + if k == 'value' and _v != NOATTR and _v != v: + self.send(CHANGE) + self.repaint() diff --git a/pgu/gui/misc.pyc b/pgu/gui/misc.pyc Binary files differnew file mode 100644 index 0000000..fcc796f --- /dev/null +++ b/pgu/gui/misc.pyc diff --git a/pgu/gui/pguglobals.py b/pgu/gui/pguglobals.py new file mode 100644 index 0000000..dc0e673 --- /dev/null +++ b/pgu/gui/pguglobals.py @@ -0,0 +1,7 @@ +# pguglobals.py - A place to stick global variables that need to be accessed +# from other modules. To avoid problems with circular imports +# this module should not import any other PGU module. + +# A global reference to the application instance (App class) +app = None + diff --git a/pgu/gui/pguglobals.pyc b/pgu/gui/pguglobals.pyc Binary files differnew file mode 100644 index 0000000..cd5a648 --- /dev/null +++ b/pgu/gui/pguglobals.pyc diff --git a/pgu/gui/select.py b/pgu/gui/select.py new file mode 100644 index 0000000..90fcfe4 --- /dev/null +++ b/pgu/gui/select.py @@ -0,0 +1,191 @@ +""" +""" +from const import * +import table +import basic, button + +class Select(table.Table): + """A select input. + + <pre>Select(value=None)</pre> + + <dl> + <dt>value<dd>initial value + </dl> + + <strong>Example</strong> + <code> + w = Select(value="goats") + w.add("Cats","cats") + w.add("Goats","goats") + w.add("Dogs","Dogs") + + w.value = 'dogs' #changes the value from goats to dogs + </code> + + """ + + def __init__(self,value=None,**params): + params.setdefault('cls','select') + table.Table.__init__(self,**params) + + self.top_selected = button.Button(cls=self.cls+".selected") + table.Table.add(self,self.top_selected) #,hexpand=1,vexpand=1)#,0,0) + self.top_selected.value = basic.Label(" ",cls=self.cls+".option.label") + + self.top_arrow = button.Button(basic.Image(self.style.arrow),cls=self.cls+".arrow") + table.Table.add(self,self.top_arrow) #,hexpand=1,vexpand=1) #,1,0) + + self.options = table.Table() #style={'border':3}) + self.options_first = None + + self.options.tr() + self.spacer_top = basic.Spacer(0,0) + self.options.add(self.spacer_top) + + self.options.tr() + self._options = table.Table(cls=self.cls+".options") + self.options.add(self._options) + + self.options.tr() + self.spacer_bottom = basic.Spacer(0,0) + self.options.add(self.spacer_bottom) + + + self.options.connect(BLUR,self._close,None) + self.spacer_top.connect(CLICK,self._close,None) + self.spacer_bottom.connect(CLICK,self._close,None) + + self.values = [] + self.value = value + + def resize(self,width=None,height=None): + max_w,max_h = 0,0 + for w in self._options.widgets: + w.rect.w,w.rect.h = w.resize() + max_w,max_h = max(max_w,w.rect.w),max(max_h,w.rect.h) + + #xt,xr,xb,xl = self.top_selected.getspacing() + self.top_selected.style.width = max_w #+ xl + xr + self.top_selected.style.height = max_h #+ xt + xb + + self.top_arrow.connect(CLICK,self._open,None) + self.top_selected.connect(CLICK,self._open,None) + + w,h = table.Table.resize(self,width,height) + + self.spacer_top.style.width, self.spacer_top.style.height = w,h + self.spacer_bottom.style.width, self.spacer_bottom.style.height = w,h + self._options.style.width = w + #HACK: sort of, but not a big one.. + self._options.resize() + + return w,h + + def _open(self,value): + sh = self.rect.h #spacer height + opts = self.options + + self.spacer_top.style.height = 0 + self.spacer_bottom.style.height = 0 + opts.rect.w, opts.rect.h = opts.resize() + h = opts.rect.h + + y = self.rect.y + c = self.container + while hasattr(c,'container'): + y += c.rect.y + if c.container == None: break + c = c.container + + if y + sh + h <= c.rect.h: #down + self.spacer_top.style.height = sh + dy = self.rect.y + else: #up + self.spacer_bottom.style.height = sh + dy = self.rect.y - h + + opts.rect.w, opts.rect.h = opts.resize() + + self.container.open(opts,self.rect.x,dy) + self.options_first.focus() + + def _close(self,value): +# print 'my close!' + self.options.close() + self.top_selected.focus() +# self.blur() +# self.focus() +# print self.container.myfocus == self + + def _setvalue(self,value): + self.value = value._value + if hasattr(self,'container'): + #self.chsize() + #HACK: improper use of resize() + #self.resize() #to recenter the new value, etc. + pass + # #self._resize() + + self._close(None) + #self.repaint() #this will happen anyways + + + + def __setattr__(self,k,v): + mywidget = None + if k == 'value': + for w in self.values: + if w._value == v: + mywidget = w + _v = self.__dict__.get(k,NOATTR) + self.__dict__[k]=v + if k == 'value' and _v != NOATTR and _v != v: + self.send(CHANGE) + self.repaint() + if k == 'value': + if not mywidget: + mywidget = basic.Label(" ",cls=self.cls+".option.label") + self.top_selected.value = mywidget + + def add(self,w,value=None): + """Add a widget, value item to the Select. + + <pre>Select.add(widget,value=None)</pre> + + <dl> + <dt>widget<dd>Widget or string to represent the item + <dt>value<dd>value for this item + </dl> + + <strong>Example</strong> + <code> + w = Select() + w.add("Goat") #adds a Label + w.add("Goat","goat") #adds a Label with the value goat + w.add(gui.Label("Cuzco"),"goat") #adds a Label with value goat + </code> + """ + + if type(w) == str: w = basic.Label(w,cls=self.cls+".option.label") + + w.style.align = -1 + b = button.Button(w,cls=self.cls+".option") + b.connect(CLICK,self._setvalue,w) + #b = w + #w.cls = self.cls+".option" + #w.cls = self.cls+".option" + + self._options.tr() + self._options.add(b) #,align=-1) + + if self.options_first == None: + self.options_first = b + #self._options.td(b, align=-1, cls=self.cls+".option") + #self._options.td(_List_Item(w,value=value),align=-1) + + if value != None: w._value = value + else: w._value = w + if self.value == w._value: + self.top_selected.value = w + self.values.append(w) diff --git a/pgu/gui/select.pyc b/pgu/gui/select.pyc Binary files differnew file mode 100644 index 0000000..152ad9c --- /dev/null +++ b/pgu/gui/select.pyc diff --git a/pgu/gui/slider.py b/pgu/gui/slider.py new file mode 100644 index 0000000..f4fa623 --- /dev/null +++ b/pgu/gui/slider.py @@ -0,0 +1,279 @@ +"""All sliders and scroll bar widgets have the same parameters. + +<pre>Slider(value,min,max,size)</pre> +<dl> +<dt>value<dd>initial value +<dt>min<dd>minimum value +<dt>max<dd>maximum value +<dt>size<dd>size of bar in pixels +</dl> +""" +import pygame +from pygame.locals import * + +from const import * +import widget +import table +import basic +import pguglobals + +_SLIDER_HORIZONTAL = 0 +_SLIDER_VERTICAL = 1 + +class _slider(widget.Widget): + def __init__(self,value,orient,min,max,size,step=1,**params): + params.setdefault('cls','slider') + widget.Widget.__init__(self,**params) + self.min,self.max,self.value,self.orient,self.size,self.step = min,max,value,orient,size,step + + + def paint(self,s): + + self.value = self.value + r = pygame.rect.Rect(0,0,self.style.width,self.style.height) + if self.orient == _SLIDER_HORIZONTAL: + r.x = (self.value-self.min) * (r.w-self.size) / max(1,self.max-self.min); + r.w = self.size; + else: + r.y = (self.value-self.min) * (r.h-self.size) / max(1,self.max-self.min); + r.h = self.size; + + self.bar = r + + pguglobals.app.theme.render(s,self.style.bar,r) + + def event(self,e): + used = None + r = pygame.rect.Rect(0,0,self.style.width,self.style.height) + adj = 0 + if e.type == ENTER: self.repaint() + elif e.type == EXIT: self.repaint() + elif e.type == MOUSEBUTTONDOWN: + if self.bar.collidepoint(e.pos): + self.grab = e.pos[0],e.pos[1] + self.grab_value = self.value + else: + x,y,adj = e.pos[0],e.pos[1],1 + self.grab = None + self.repaint() + elif e.type == MOUSEBUTTONUP: + #x,y,adj = e.pos[0],e.pos[1],1 + self.repaint() + elif e.type == MOUSEMOTION: + if 1 in e.buttons and self.container.myfocus is self: + if self.grab != None: + rel = e.pos[0]-self.grab[0],e.pos[1]-self.grab[1] + if self.orient == _SLIDER_HORIZONTAL: + d = (r.w - self.size) + if d != 0: self.value = self.grab_value + ((self.max-self.min) * rel[0] / d) + else: + d = (r.h - self.size) + if d != 0: self.value = self.grab_value + ((self.max-self.min) * rel[1] / d) + else: + x,y,adj = e.pos[0],e.pos[1],1 + + elif e.type is KEYDOWN: + if self.orient == _SLIDER_HORIZONTAL and e.key == K_LEFT: + self.value -= self.step + used = True + elif self.orient == _SLIDER_HORIZONTAL and e.key == K_RIGHT: + self.value += self.step + used = True + elif self.orient == _SLIDER_VERTICAL and e.key == K_UP: + self.value -= self.step + used = True + elif self.orient == _SLIDER_VERTICAL and e.key == K_DOWN: + self.value += self.step + used = True + + if adj: + if self.orient == _SLIDER_HORIZONTAL: + d = self.size/2 - (r.w/(self.max-self.min+1))/2 + self.value = (x-d) * (self.max-self.min) / (r.w-self.size+1) + self.min + else: + d = self.size/2 - (r.h/(self.max-self.min+1))/2 + self.value = (y-d) * (self.max-self.min) / (r.h-self.size+1) + self.min + + self.pcls = "" + if self.container.myhover is self: self.pcls = "hover" + if (self.container.myfocus is self and 1 in pygame.mouse.get_pressed()): self.pcls = "down" + + return used + + + def __setattr__(self,k,v): + if k == 'value': + v = int(v) + v = max(v,self.min) + v = min(v,self.max) + _v = self.__dict__.get(k,NOATTR) + self.__dict__[k]=v + if k == 'value' and _v != NOATTR and _v != v: + self.send(CHANGE) + self.repaint() + + if hasattr(self,'size'): + sz = min(self.size,max(self.style.width,self.style.height)) + sz = max(sz,min(self.style.width,self.style.height)) + self.__dict__['size'] = sz + + if hasattr(self,'max') and hasattr(self,'min'): + if self.max < self.min: self.max = self.min + +class VSlider(_slider): + """A verticle slider. + + <pre>VSlider(value,min,max,size)</pre> + """ + def __init__(self,value,min,max,size,step=1,**params): + params.setdefault('cls','vslider') + _slider.__init__(self,value,_SLIDER_VERTICAL,min,max,size,step,**params) + +class HSlider(_slider): + """A horizontal slider. + + <pre>HSlider(value,min,max,size)</pre> + """ + def __init__(self,value,min,max,size,step=1,**params): + params.setdefault('cls','hslider') + _slider.__init__(self,value,_SLIDER_HORIZONTAL,min,max,size,step,**params) + +class HScrollBar(table.Table): + """A horizontal scroll bar. + + <pre>HScrollBar(value,min,max,size,step=1)</pre> + """ + def __init__(self,value,min,max,size,step=1,**params): + params.setdefault('cls','hscrollbar') + + table.Table.__init__(self,**params) + + self.slider = _slider(value,_SLIDER_HORIZONTAL,min,max,size,step=step,cls=self.cls+'.slider') + + self.minus = basic.Image(self.style.minus) + self.minus.connect(MOUSEBUTTONDOWN,self._click,-1) + self.slider.connect(CHANGE,self.send,CHANGE) + + self.minus2 = basic.Image(self.style.minus) + self.minus2.connect(MOUSEBUTTONDOWN,self._click,-1) + + self.plus = basic.Image(self.style.plus) + self.plus.connect(MOUSEBUTTONDOWN,self._click,1) + + self.size = size + + def _click(self,value): + self.slider.value += self.slider.step*value + + def resize(self,width=None,height=None): + self.clear() + self.tr() + + w = self.style.width + h = self.slider.style.height + ww = 0 + + if w > (h*2 + self.minus.style.width+self.plus.style.width): + self.td(self.minus) + ww += self.minus.style.width + + self.td(self.slider) + + if w > (h*2 + self.minus.style.width+self.minus2.style.width+self.plus.style.width): + self.td(self.minus2) + ww += self.minus2.style.width + + if w > (h*2 + self.minus.style.width+self.plus.style.width): + self.td(self.plus) + ww += self.plus.style.width + + + #HACK: handle theme sizing properly + xt,xr,xb,xl = pguglobals.app.theme.getspacing(self.slider) + ww += xr+xl + + self.slider.style.width = self.style.width - ww + setattr(self.slider,'size',self.size * self.slider.style.width / max(1,self.style.width)) + return table.Table.resize(self,width,height) + + + def __setattr__(self,k,v): + if k in ('min','max','value','step'): + return setattr(self.slider,k,v) + self.__dict__[k]=v + + def __getattr__(self,k): + if k in ('min','max','value','step'): + return getattr(self.slider,k) + return table.Table.__getattr__(self,k) #self.__dict__[k] + +class VScrollBar(table.Table): + """A vertical scroll bar. + + <pre>VScrollBar(value,min,max,size,step=1)</pre> + """ + def __init__(self,value,min,max,size,step=1,**params): + params.setdefault('cls','vscrollbar') + + table.Table.__init__(self,**params) + + self.minus = basic.Image(self.style.minus) + self.minus.connect(MOUSEBUTTONDOWN,self._click,-1) + + self.minus2 = basic.Image(self.style.minus) + self.minus2.connect(MOUSEBUTTONDOWN,self._click,-1) + + self.plus = basic.Image(self.style.plus) + self.plus.connect(MOUSEBUTTONDOWN,self._click,1) + + self.slider = _slider(value,_SLIDER_VERTICAL,min,max,size,step=step,cls=self.cls+'.slider') + self.slider.connect(CHANGE,self.send,CHANGE) + + self.size = size + + def _click(self,value): + self.slider.value += self.slider.step*value + + def resize(self,width=None,height=None): + self.clear() + + h = self.style.height + w = self.slider.style.width + hh = 0 + + if h > (w*2 + self.minus.style.height+self.plus.style.height): + self.tr() + self.td(self.minus) + hh += self.minus.style.height + + self.tr() + self.td(self.slider) + + if h > (w*2 + self.minus.style.height+self.minus2.style.height+self.plus.style.height): + self.tr() + self.td(self.minus2) + hh += self.minus2.style.height + + if h > (w*2 + self.minus.style.height+self.plus.style.height): + self.tr() + self.td(self.plus) + hh += self.plus.style.height + + + #HACK: handle theme sizing properly + xt,xr,xb,xl = pguglobals.app.theme.getspacing(self.slider) + hh += xt+xb + + self.slider.style.height = self.style.height - hh + setattr(self.slider,'size',self.size * self.slider.style.height / max(1,self.style.height)) + return table.Table.resize(self,width,height) + + def __setattr__(self,k,v): + if k in ('min','max','value','step'): + return setattr(self.slider,k,v) + self.__dict__[k]=v + + def __getattr__(self,k): + if k in ('min','max','value','step'): + return getattr(self.slider,k) + return table.Table.__getattr__(self,k) diff --git a/pgu/gui/slider.pyc b/pgu/gui/slider.pyc Binary files differnew file mode 100644 index 0000000..9ccc719 --- /dev/null +++ b/pgu/gui/slider.pyc diff --git a/pgu/gui/style.py b/pgu/gui/style.py new file mode 100644 index 0000000..3060928 --- /dev/null +++ b/pgu/gui/style.py @@ -0,0 +1,41 @@ +""" +""" + +import pguglobals + +class Style: + """The class used by widget for the widget.style + + <p>This object is used mainly as a dictionary, accessed via <tt>widget.style.attr</tt>, as opposed to + <tt>widget.style['attr']</tt>. It automatically grabs information from the theme via <tt>value = theme.get(widget.cls,widget.pcls,attr)</tt>.</p> + + """ + def __init__(self,o,dict): + self.obj = o + for k,v in dict.items(): self.__dict__[k]=v + self._cache = {} + + def __getattr__(self,k): + key = self.obj.cls,self.obj.pcls,k + if key not in self._cache: + self._cache[key] = Style_get(self.obj.cls,self.obj.pcls,k) + v = self._cache[key] + if k in ( + 'border_top','border_right','border_bottom','border_left', + 'padding_top','padding_right','padding_bottom','padding_left', + 'margin_top','margin_right','margin_bottom','margin_left', + 'align','valign','width','height', + ): self.__dict__[k] = v + return v + + def __setattr__(self,k,v): + self.__dict__[k] = v + + +Style_cache = {} +def Style_get(cls,pcls,k): + key = cls,pcls,k + if key not in Style_cache: + Style_cache[key] = pguglobals.app.theme.get(cls,pcls,k) + return Style_cache[key] + diff --git a/pgu/gui/style.pyc b/pgu/gui/style.pyc Binary files differnew file mode 100644 index 0000000..ae59cf4 --- /dev/null +++ b/pgu/gui/style.pyc diff --git a/pgu/gui/surface.py b/pgu/gui/surface.py new file mode 100644 index 0000000..82d92d1 --- /dev/null +++ b/pgu/gui/surface.py @@ -0,0 +1,142 @@ +""" +""" +import pygame + +def subsurface(s,r): + """Return the subsurface of a surface, with some help, checks. + + <pre>subsurface(s,r): return surface</pre> + """ + r = pygame.Rect(r) + if r.x < 0 or r.y < 0: + raise "gui.subsurface: %d %d %s"%(s.get_width(),s.get_height(),r) + w,h = s.get_width(),s.get_height() + if r.right > w: + r.w -= r.right-w + if r.bottom > h: + r.h -= r.bottom-h + assert(r.w >= 0 and r.h >= 0) + return s.subsurface(r) + +class ProxySurface: + """ + A surface-like object which smartly handle out-of-area blitting. + + <pre>ProxySurface(parent, rect, real_surface=None, offset=(0, 0))</pre> + + <p>only one of parent and real_surface should be supplied (non None)</p> + <dl> + <dt>parent<dd>a ProxySurface object + <dt>real_surface<dd>a pygame Surface object + </dl> + + <strong>Variables</strong> + + <dl> + <dt>mysubsurface<dd>a real and valid pygame.Surface object to be used + for blitting. + <dt>x, y<dd>if the proxy surface is lefter or higher than the parent, + x, y hold the diffs. + <dt>offset<dd>an optional feature which let you scroll the whole blitted + content. + </dl> + """ + def __init__(self, parent, rect, real_surface, offset=(0, 0)): + self.offset = offset + self.x = self.y = 0 + if rect.x < 0: self.x = rect.x + if rect.y < 0: self.y = rect.y + self.real_surface = real_surface + if real_surface == None: + self.mysubsurface = parent.mysubsurface.subsurface( + parent.mysubsurface.get_rect().clip(rect)) + else: + self.mysubsurface = real_surface.subsurface( + real_surface.get_rect().clip(rect)) + rect.topleft = (0, 0) + self.rect = rect + + def blit(self, s, pos, rect=None): + if rect == None: rect = s.get_rect() + pos = (pos[0] + self.offset[0] + self.x, pos[1] + self.offset[1] + self.y) + self.mysubsurface.blit(s, pos, rect) + + def subsurface(self, rect): + r = pygame.Rect(rect).move(self.offset[0] + self.x, + self.offset[1] + self.y) + return ProxySurface(self, r, self.real_surface) + + def fill(self, color, rect=None): + if rect != None: self.mysubsurface.fill(color, rect) + else: self.mysubsurface.fill(color) + def get_rect(self): return self.rect + def get_width(self): return self.rect[2] + def get_height(self): return self.rect[3] + def get_abs_offset(): return self.rect[:2] + def get_abs_parent(): return self.mysubsurface.get_abs_parent() + def set_clip(self, rect=None): + if rect == None: self.mysubsurface.set_clip() + else: + rect = [rect[0] + self.offset[0] + self.x, rect[1] + self.offset[0] + self.y, rect[2], rect[3]] + self.mysubsurface.set_clip(rect) + + + + + + +class xProxySurface: + """ + A surface-like object which smartly handle out-of-area blitting. + + <pre>ProxySurface(parent, rect, real_surface=None, offset=(0, 0))</pre> + + <p>only one of parent and real_surface should be supplied (non None)</p> + <dl> + <dt>parent<dd>a ProxySurface object + <dt>real_surface<dd>a pygame Surface object + </dl> + + <strong>Variables</strong> + + <dl> + <dt>mysubsurface<dd>a real and valid pygame.Surface object to be used + for blitting. + <dt>x, y<dd>if the proxy surface is lefter or higher than the parent, + x, y hold the diffs. + <dt>offset<dd>an optional feature which let you scroll the whole blitted + content. + </dl> + """ + def __init__(self, parent, rect, real_surface, offset=(0, 0)): + self.offset = offset + self.x = self.y = 0 + if rect[0] < 0: self.x = rect[0] + if rect[1] < 0: self.y = rect[1] + self.real_surface = real_surface + if real_surface == None: + self.mysubsurface = parent.mysubsurface.subsurface(parent.mysubsurface.get_rect().clip(rect)) + else: + self.mysubsurface = real_surface.subsurface(real_surface.get_rect().clip(rect)) + rect[0], rect[1] = 0, 0 + self.rect = rect + + def blit(self, s, pos, rect=None): + if rect == None: rect = s.get_rect() + pos = (pos[0] + self.offset[0] + self.x, pos[1] + self.offset[1] + self.y) + self.mysubsurface.blit(s, pos, rect) + + def subsurface(self, rect): return ProxySurface(self, pygame.Rect(rect).move(self.offset[0] + self.x, self.offset[1] + self.y),self.real_surface) + def fill(self, color, rect=None): + if rect != None: self.mysubsurface.fill(color, rect) + else: self.mysubsurface.fill(color) + def get_rect(self): return self.rect + def get_width(self): return self.rect[2] + def get_height(self): return self.rect[3] + def get_abs_offset(): return self.rect[:2] + def get_abs_parent(): return self.mysubsurface.get_abs_parent() + def set_clip(self, rect=None): + if rect == None: self.mysubsurface.set_clip() + else: + rect = [rect[0] + self.offset[0] + self.x, rect[1] + self.offset[0] + self.y, rect[2], rect[3]] + self.mysubsurface.set_clip(rect) diff --git a/pgu/gui/surface.pyc b/pgu/gui/surface.pyc Binary files differnew file mode 100644 index 0000000..13f9f89 --- /dev/null +++ b/pgu/gui/surface.pyc diff --git a/pgu/gui/table.py b/pgu/gui/table.py new file mode 100644 index 0000000..1a92593 --- /dev/null +++ b/pgu/gui/table.py @@ -0,0 +1,331 @@ +""" +""" +from const import * +import container + +class Table(container.Container): + """A table style container. + + <p>If you know HTML, this should all work roughly how you would expect. If you are not + familiar with HTML, please read <a href="http://www.w3.org/TR/REC-html40/struct/tables.html">Tables in HTML Documents</a>. Pay attention to TABLE, TR, TD related parts of the document.</p> + + <pre>Table()</pre> + + <strong>Example</strong> + <code> + t = gui.Table() + + t.tr() + t.td(gui.Label("First Name"), align=-1) + t.td(gui.Input()) + + t.tr() + t.td(gui.Label("Last Name"), align=-1) + t.td(gui.Input()) + </code> + + """ + + + def __init__(self, **params): + params.setdefault('cls','table') + container.Container.__init__(self, **params) + self._rows = [] + self._curRow = 0 + self._trok = False + + def getRows(self): + return len(self._rows) + + def getColumns(self): + if self._rows: + return len(self._rows[0]) + else: + return 0 + + def remove_row(self, n): #NOTE: won't work in all cases. + if n >= self.getRows(): + print "Trying to remove a nonexistant row:", n, "there are only", self.getRows(), "rows" + return + + for cell in self._rows[n]: + if isinstance(cell, dict) and cell["widget"] in self.widgets: + #print 'removing widget' + self.widgets.remove(cell["widget"]) + del self._rows[n] + #print "got here" + + for w in self.widgets: + if w.style.row > n: w.style.row -= 1 + + if self._curRow >= n: + self._curRow -= 1 + + #self.rect.w, self.rect.h = self.resize() + #self.repaint() + + self.chsize() + + def clear(self): + self._rows = [] + self._curRow = 0 + self._trok = False + + self.widgets = [] + + self.chsize() + + #print 'clear',self,self._rows + + def _addRow(self): + self._rows.append([None for x in xrange(self.getColumns())]) + + def tr(self): + """Start on the next row.""" + if not self._trok: + self._trok = True + return + self._curRow += 1 + if self.getRows() <= self._curRow: + self._addRow() + + def _addColumn(self): + if not self._rows: + self._addRow() + for row in self._rows: + row.append(None) + + def _setCell(self, w, col, row, colspan=1, rowspan=1): + #make room for the widget by adding columns and rows + while self.getColumns() < col + colspan: + self._addColumn() + while self.getRows() < row + rowspan: + self._addRow() + + #print w.__class__.__name__,col,row,colspan,rowspan + + #actual widget setting and modification stuff + w.container = self + w.style.row = row #HACK - to work with gal's list + w.style.col = col #HACK - to work with gal's list + self._rows[row][col] = {"widget":w, "colspan":colspan, "rowspan":rowspan} + self.widgets.append(self._rows[row][col]["widget"]) + + #set the spanned columns + #for acell in xrange(col + 1, col + colspan): + # self._rows[row][acell] = True + + #set the spanned rows and the columns on them + #for arow in xrange(row + 1, row + rowspan): + # for acell in xrange(col, col + colspan): #incorrect? + # self._rows[arow][acell] = True + + for arow in xrange(row, row + rowspan): + for acell in xrange(col, col + colspan): #incorrect? + if row != arow or col != acell: + self._rows[arow][acell] = True + + + def td(self, w, col=None, row=None, colspan=1, rowspan=1, **params): + """Add a widget to a table after wrapping it in a TD container. + + <pre>Table.td(w,col=None,row=None,colspan=1,rowspan=1,**params)</pre> + + <dl> + <dt>w<dd>widget + <dt>col<dd>column + <dt>row<dd>row + <dt>colspan<dd>colspan + <dt>rowspan<dd>rowspan + <dt>align<dd>horizontal alignment (-1,0,1) + <dt>valign<dd>vertical alignment (-1,0,1) + <dt>params<dd>other params for the TD container, style information, etc + </dl> + """ + + Table.add(self,_Table_td(w, **params), col=col, row=row, colspan=colspan, rowspan=rowspan) + + def add(self, w, col=None, row=None, colspan=1, rowspan=1): + """Add a widget directly into the table, without wrapping it in a TD container. + + <pre>Table.add(w,col=None,row=None,colspan=1,rowspan=1)</pre> + + <p>See Table.td for an explanation of the parameters.</p> + """ + self._trok = True + #if no row was specifically specified, set it to the current row + if row is None: + row = self._curRow + #print row + + #if its going to be a new row, have it be on the first column + if row >= self.getRows(): + col = 0 + + #try to find an open cell for the widget + if col is None: + for cell in xrange(self.getColumns()): + if col is None and not self._rows[row][cell]: + col = cell + break + + #otherwise put the widget in a new column + if col is None: + col = self.getColumns() + + self._setCell(w, col, row, colspan=colspan, rowspan=rowspan) + + self.chsize() + return + + def remove(self,w): + if hasattr(w,'_table_td'): w = w._table_td + row,col = w.style.row,w.style.col + cell = self._rows[row][col] + colspan,rowspan = cell['colspan'],cell['rowspan'] + + for arow in xrange(row , row + rowspan): + for acell in xrange(col, col + colspan): #incorrect? + self._rows[arow][acell] = False + self.widgets.remove(w) + self.chsize() + + + + def resize(self, width=None, height=None): + #if 1 or self.getRows() == 82: + #print '' + #print 'resize',self.getRows(),self.getColumns(),width,height + #import inspect + #for obj,fname,line,fnc,code,n in inspect.stack()[9:20]: + # print fname,line,':',fnc,code[0].strip() + + + #resize the widgets to their smallest size + for w in self.widgets: + w.rect.w, w.rect.h = w.resize() + + #calculate row heights and column widths + rowsizes = [0 for y in xrange(self.getRows())] + columnsizes = [0 for x in xrange(self.getColumns())] + for row in xrange(self.getRows()): + for cell in xrange(self.getColumns()): + if self._rows[row][cell] and self._rows[row][cell] is not True: + if not self._rows[row][cell]["colspan"] > 1: + columnsizes[cell] = max(columnsizes[cell], self._rows[row][cell]["widget"].rect.w) + if not self._rows[row][cell]["rowspan"] > 1: + rowsizes[row] = max(rowsizes[row], self._rows[row][cell]["widget"].rect.h) + + #distribute extra space if necessary for wide colspanning/rowspanning + for row in xrange(self.getRows()): + for cell in xrange(self.getColumns()): + if self._rows[row][cell] and self._rows[row][cell] is not True: + if self._rows[row][cell]["colspan"] > 1: + columns = xrange(cell, cell + self._rows[row][cell]["colspan"]) + totalwidth = 0 + for acol in columns: + totalwidth += columnsizes[acol] + if totalwidth < self._rows[row][cell]["widget"].rect.w: + for acol in columns: + columnsizes[acol] += _table_div(self._rows[row][cell]["widget"].rect.w - totalwidth, self._rows[row][cell]["colspan"],acol) + if self._rows[row][cell]["rowspan"] > 1: + rows = xrange(row, row + self._rows[row][cell]["rowspan"]) + totalheight = 0 + for arow in rows: + totalheight += rowsizes[arow] + if totalheight < self._rows[row][cell]["widget"].rect.h: + for arow in rows: + rowsizes[arow] += _table_div(self._rows[row][cell]["widget"].rect.h - totalheight, self._rows[row][cell]["rowspan"],arow) + + #make everything fill out to self.style.width, self.style.heigh, not exact, but pretty close... + w, h = sum(columnsizes), sum(rowsizes) + if w > 0 and w < self.style.width and len(columnsizes): + d = (self.style.width - w) + for n in xrange(0, len(columnsizes)): + v = columnsizes[n] + columnsizes[n] += v * d / w + if h > 0 and h < self.style.height and len(rowsizes): + d = (self.style.height - h) / len(rowsizes) + for n in xrange(0, len(rowsizes)): + v = rowsizes[n] + rowsizes[n] += v * d / h + + #set the widget's position by calculating their row/column x/y offset + cellpositions = [[[sum(columnsizes[0:cell]), sum(rowsizes[0:row])] for cell in xrange(self.getColumns())] for row in xrange(self.getRows())] + for row in xrange(self.getRows()): + for cell in xrange(self.getColumns()): + if self._rows[row][cell] and self._rows[row][cell] is not True: + x, y = cellpositions[row][cell] + w = sum(columnsizes[cell:cell+self._rows[row][cell]["colspan"]]) + h = sum(rowsizes[row:row+self._rows[row][cell]["rowspan"]]) + + widget = self._rows[row][cell]["widget"] + widget.rect.x = x + widget.rect.y = y + if 1 and (w,h) != (widget.rect.w,widget.rect.h): +# if h > 20: +# print widget.widget.__class__.__name__, (widget.rect.w,widget.rect.h),'=>',(w,h) + widget.rect.w, widget.rect.h = widget.resize(w, h) + + #print self._rows[row][cell]["widget"].rect + + print 'columnsizes', columnsizes + print sum(columnsizes) + size = sum(columnsizes), sum(rowsizes); print size + + #return the tables final size + return sum(columnsizes),sum(rowsizes) + + +def _table_div(a,b,c): + v,r = a/b, a%b + if r != 0 and (c%b)<r: v += 1 + return v + +class _Table_td(container.Container): + def __init__(self,widget,**params):#hexpand=0,vexpand=0, + container.Container.__init__(self,**params) + self.widget = widget + #self.hexpand=hexpand + #self.vexpand=vexpand + widget._table_td = self + self.add(widget,0,0) + + def resize(self,width=None,height=None): + w = self.widget + + #expansion code, but i didn't like the idea that much.. + #a bit obscure, fairly useless when a user can just + #add a widget to a table instead of td it in. + #ww,hh=None,None + #if self.hexpand: ww = self.style.width + #if self.vexpand: hh = self.style.height + #if self.hexpand and width != None: ww = max(ww,width) + #if self.vexpand and height != None: hh = max(hh,height) + #w.rect.w,w.rect.h = w.resize(ww,hh) + + #why bother, just do the lower mentioned item... + w.rect.w,w.rect.h = w.resize() + + #this should not be needed, widgets should obey their sizing on their own. + +# if (self.style.width!=0 and w.rect.w > self.style.width) or (self.style.height!=0 and w.rect.h > self.style.height): +# ww,hh = None,None +# if self.style.width: ww = self.style.width +# if self.style.height: hh = self.style.height +# w.rect.w,w.rect.h = w.resize(ww,hh) + + + #in the case that the widget is too big, we try to resize it + if (width != None and width < w.rect.w) or (height != None and height < w.rect.h): + w.rect.w,w.rect.h = w.resize(width,height) + + width = max(width,w.rect.w,self.style.width) #,self.style.cell_width) + height = max(height,w.rect.h,self.style.height) #,self.style.cell_height) + + dx = width-w.rect.w + dy = height-w.rect.h + w.rect.x = (self.style.align+1)*dx/2 + w.rect.y = (self.style.valign+1)*dy/2 + + return width,height diff --git a/pgu/gui/table.pyc b/pgu/gui/table.pyc Binary files differnew file mode 100644 index 0000000..cc1e5d8 --- /dev/null +++ b/pgu/gui/table.pyc diff --git a/pgu/gui/textarea.py b/pgu/gui/textarea.py new file mode 100644 index 0000000..667076a --- /dev/null +++ b/pgu/gui/textarea.py @@ -0,0 +1,287 @@ +""" +""" +import pygame +from pygame.locals import * + +from const import * +import widget + +class TextArea(widget.Widget): + """A multi-line text input. + + <pre>TextArea(value="",width = 120, height = 30, size=20)</pre> + + <dl> + <dt>value<dd>initial text + <dt>size<dd>size for the text box, in characters + </dl> + + <strong>Example</strong> + <code> + w = TextArea(value="Cuzco the Goat",size=20) + + w = TextArea("Marbles") + + w = TextArea("Groucho\nHarpo\nChico\nGummo\nZeppo\n\nMarx", 200, 400, 12) + </code> + + """ + def __init__(self,value="",width = 120, height = 30, size=20,**params): + params.setdefault('cls','input') + params.setdefault('width', width) + params.setdefault('height', height) + + widget.Widget.__init__(self,**params) + self.value = value # The value of the TextArea + self.pos = len(str(value)) # The position of the cursor + self.vscroll = 0 # The number of lines that the TextArea is currently scrolled + self.font = self.style.font # The font used for rendering the text + self.cursor_w = 2 # Cursor width (NOTE: should be in a style) + w,h = self.font.size("e"*size) + if not self.style.height: self.style.height = h + if not self.style.width: self.style.width = w + + def resize(self,width=None,height=None): + if (width != None) and (height != None): + self.rect = pygame.Rect(self.rect.x, self.rect.y, width, height) + return self.rect.w, self.rect.h + + def paint(self,s): + + # TODO: What's up with this 20 magic number? It's the margin of the left and right sides, but I'm not sure how this should be gotten other than by trial and error. + max_line_w = self.rect.w - 20 + + # Update the line allocation for the box's value + self.doLines(max_line_w) + + # Make sure that the vpos and hpos of the cursor is set properly + self.updateCursorPos() + + # Make sure that we're scrolled vertically such that the cursor is visible + if (self.vscroll < 0): + self.vscroll = 0 + if (self.vpos < self.vscroll): + self.vscroll = self.vpos + elif ((self.vpos - self.vscroll + 1) * self.line_h > self.rect.h): + self.vscroll = - (self.rect.h / self.line_h - self.vpos - 1) + + # Blit each of the lines in turn + cnt = 0 + for line in self.lines: + line_pos = (0, (cnt - self.vscroll) * self.line_h) + if (line_pos[1] >= 0) and (line_pos[1] < self.rect.h): + s.blit( self.font.render(line, 1, self.style.color), line_pos ) + cnt += 1 + + # If the textarea is focused, then also show the cursor + if self.container.myfocus is self: + r = self.getCursorRect() + s.fill(self.style.color,r) + + # This function updates self.vpos and self.hpos based on self.pos + def updateCursorPos(self): + self.vpos = 0 # Reset the current line that the cursor is on + self.hpos = 0 + + line_cnt = 0 + char_cnt = 0 + + for line in self.lines: + line_char_start = char_cnt # The number of characters at the start of the line + + # Keep track of the character count for words + char_cnt += len(line) + + # If our cursor count is still less than the cursor position, then we can update our cursor line to assume that it's at least on this line + if (char_cnt > self.pos): + self.vpos = line_cnt + self.hpos = self.pos - line_char_start + + break # Now that we know where our cursor is, we exit the loop + + line_cnt += 1 + + if (char_cnt <= self.pos) and (len(self.lines) > 0): + self.vpos = len(self.lines) - 1 + self.hpos = len(self.lines[ self.vpos ] ) + + # Returns a rectangle that is of the size and position of where the cursor is drawn + def getCursorRect(self): + lw = 0 + if (len(self.lines) > 0): + lw, lh = self.font.size( self.lines[ self.vpos ][ 0:self.hpos ] ) + + r = pygame.Rect(lw, (self.vpos - self.vscroll) * self.line_h, self.cursor_w, self.line_h) + return r + + # This function sets the cursor position according to an x/y value (such as by from a mouse click) + def setCursorByXY(self, (x, y)): + self.vpos = ((int) (y / self.line_h)) + self.vscroll + if (self.vpos >= len(self.lines)): + self.vpos = len(self.lines) - 1 + + currentLine = self.lines[ self.vpos ] + + for cnt in range(0, len(currentLine) ): + self.hpos = cnt + lw, lh = self.font.size( currentLine[ 0:self.hpos + 1 ] ) + if (lw > x): + break + + lw, lh = self.font.size( currentLine ) + if (lw < x): + self.hpos = len(currentLine) + + self.setCursorByHVPos() + + # This function sets the cursor position by the horizontal/vertical cursor position. + def setCursorByHVPos(self): + line_cnt = 0 + char_cnt = 0 + + for line in self.lines: + line_char_start = char_cnt # The number of characters at the start of the line + + # Keep track of the character count for words + char_cnt += len(line) + + # If we're on the proper line + if (line_cnt == self.vpos): + # Make sure that we're not trying to go over the edge of the current line + if ( self.hpos >= len(line) ): + self.hpos = len(line) - 1 + # Set the cursor position + self.pos = line_char_start + self.hpos + break # Now that we've set our cursor position, we exit the loop + + line_cnt += 1 + + # Splits up the text found in the control's value, and assigns it into the lines array + def doLines(self, max_line_w): + self.line_h = 10 + self.lines = [] # Create an empty starter list to start things out. + + inx = 0 + line_start = 0 + while inx >= 0: + # Find the next breakable whitespace + # HACK: Find a better way to do this to include tabs and system characters and whatnot. + prev_word_start = inx # Store the previous whitespace + spc_inx = self.value.find(' ', inx+1) + nl_inx = self.value.find('\n', inx+1) + + if (min(spc_inx, nl_inx) == -1): + inx = max(spc_inx, nl_inx) + else: + inx = min(spc_inx, nl_inx) + + # Measure the current line + lw, self.line_h = self.font.size( self.value[ line_start : inx ] ) + + # If we exceeded the max line width, then create a new line + if (lw > max_line_w): + #Fall back to the previous word start + self.lines.append(self.value[ line_start : prev_word_start + 1 ]) + line_start = prev_word_start + 1 + # TODO: Check for extra-long words here that exceed the length of a line, to wrap mid-word + + # If we reached the end of our text + if (inx < 0): + # Then make sure we added the last of the line + if (line_start < len( self.value ) ): + self.lines.append( self.value[ line_start : len( self.value ) ] ) + # If we reached a hard line break + elif (self.value[inx] == "\n"): + # Then make a line break here as well. + newline = self.value[ line_start : inx + 1 ] + newline = newline.replace("\n", " ") # HACK: We know we have a newline character, which doesn't print nicely, so make it into a space. Comment this out to see what I mean. + self.lines.append( newline ) + + line_start = inx + 1 + else: + # Otherwise, we just continue progressing to the next space + pass + + def _setvalue(self,v): + self.__dict__['value'] = v + self.send(CHANGE) + + def event(self,e): + used = None + if e.type == KEYDOWN: + if e.key == K_BACKSPACE: + if self.pos: + self._setvalue(self.value[:self.pos-1] + self.value[self.pos:]) + self.pos -= 1 + elif e.key == K_DELETE: + if len(self.value) > self.pos: + self._setvalue(self.value[:self.pos] + self.value[self.pos+1:]) + elif e.key == K_HOME: + # Find the previous newline + newPos = self.value.rfind('\n', 0, self.pos) + if (newPos >= 0): + self.pos = newPos + elif e.key == K_END: + # Find the previous newline + newPos = self.value.find('\n', self.pos, len(self.value) ) + if (newPos >= 0): + self.pos = newPos + elif e.key == K_LEFT: + if self.pos > 0: self.pos -= 1 + used = True + elif e.key == K_RIGHT: + if self.pos < len(self.value): self.pos += 1 + used = True + elif e.key == K_UP: + self.vpos -= 1 + self.setCursorByHVPos() + elif e.key == K_DOWN: + self.vpos += 1 + self.setCursorByHVPos() + # The following return/tab keys are standard for PGU widgets, but I took them out here to facilitate multi-line text editing +# elif e.key == K_RETURN: +# self.next() +# elif e.key == K_TAB: +# pass + else: + #c = str(e.unicode) + try: + if (e.key == K_RETURN): + c = "\n" + elif (e.key == K_TAB): + c = " " + else: + c = (e.unicode).encode('latin-1') + if c: + self._setvalue(self.value[:self.pos] + c + self.value[self.pos:]) + self.pos += len(c) + except: #ignore weird characters + pass + self.repaint() + elif e.type == MOUSEBUTTONDOWN: + self.setCursorByXY(e.pos) + self.repaint() + + elif e.type == FOCUS: + self.repaint() + elif e.type == BLUR: + self.repaint() + + self.pcls = "" + if self.container.myfocus is self: self.pcls = "focus" + + return used + + def __setattr__(self,k,v): + if k == 'value': + if v == None: v = '' + v = str(v) + self.pos = len(v) + _v = self.__dict__.get(k,NOATTR) + self.__dict__[k]=v + if k == 'value' and _v != NOATTR and _v != v: + self.send(CHANGE) + self.repaint() + +# The first version of this code was done by Clint Herron, and is a modified version of input.py (by Phil Hassey). +# It is under the same license as the rest of the PGU library.
\ No newline at end of file diff --git a/pgu/gui/textarea.pyc b/pgu/gui/textarea.pyc Binary files differnew file mode 100644 index 0000000..8c94884 --- /dev/null +++ b/pgu/gui/textarea.pyc diff --git a/pgu/gui/theme.py b/pgu/gui/theme.py new file mode 100644 index 0000000..8c75329 --- /dev/null +++ b/pgu/gui/theme.py @@ -0,0 +1,486 @@ +""" +""" +import os, re +import pygame + +from const import * +import surface +from basic import parse_color + +__file__ = os.path.abspath(__file__) + +def _list_themes(dir): + d = {} + for entry in os.listdir(dir): + if os.path.exists(os.path.join(dir, entry, 'config.txt')): + d[entry] = os.path.join(dir, entry) + return d + +class Theme: + """Theme interface. + + <p>If you wish to create your own theme, create a class with this interface, and + pass it to gui.App via <tt>gui.App(theme=MyTheme())</tt>.</p> + + <strong>Default Theme</strong> + + <pre>Theme(dirs='default')</pre> + <dl> + <dt>dirs<dd>Name of the theme dir to load a theme from. May be an absolute path to a theme, if pgu is not installed, or if you created your own theme. May include several dirs in a list if data is spread across several themes. + </dl> + + <strong>Example</strong> + + <code> + theme = gui.Theme("default") + theme = gui.Theme(["mytheme","mytheme2"]) + </code> + """ + def __init__(self,dirs='default'): + self.config = {} + self.dict = {} + self._loaded = [] + self.cache = {} + self._preload(dirs) + pygame.font.init() + + def _preload(self,ds): + if not isinstance(ds, list): + ds = [ds] + for d in ds: + if d not in self._loaded: + self._load(d) + self._loaded.append(d) + + def _load(self, name): + #theme_dir = themes[name] + + #try to load the local dir, or absolute path + dnames = [name] + + #if the package isn't installed and people are just + #trying out the scripts or examples + dnames.append(os.path.join(os.path.dirname(__file__),"..","..","data","themes",name)) + + #if the package is installed, and the package is installed + #in /usr/lib/python2.3/site-packages/pgu/ + #or c:\python23\lib\site-packages\pgu\ + #the data is in ... lib/../share/ ... + dnames.append(os.path.join(os.path.dirname(__file__),"..","..","..","..","share","pgu","themes",name)) + dnames.append(os.path.join(os.path.dirname(__file__),"..","..","..","..","..","share","pgu","themes",name)) + dnames.append(os.path.join(os.path.dirname(__file__),"..","..","share","pgu","themes",name)) + for dname in dnames: + if os.path.isdir(dname): break + if not os.path.isdir(dname): + raise 'could not find theme '+name + + fname = os.path.join(dname,"config.txt") + if os.path.isfile(fname): + try: + f = open(fname) + for line in f.readlines(): + vals = line.strip().split() + if len(vals) < 3: continue + cls = vals[0] + del vals[0] + pcls = "" + if cls.find(":")>=0: + cls,pcls = cls.split(":") + attr = vals[0] + del vals[0] + self.config[cls+":"+pcls+" "+attr] = (dname, vals) + finally: + f.close() + fname = os.path.join(dname,"style.ini") + if os.path.isfile(fname): + import ConfigParser + cfg = ConfigParser.ConfigParser() + f = open(fname,'r') + cfg.readfp(f) + for section in cfg.sections(): + cls = section + pcls = '' + if cls.find(":")>=0: + cls,pcls = cls.split(":") + for attr in cfg.options(section): + vals = cfg.get(section,attr).strip().split() + self.config[cls+':'+pcls+' '+attr] = (dname,vals) + + is_image = re.compile('\.(gif|jpg|bmp|png|tga)$', re.I) + def _get(self,key): + if not key in self.config: return + if key in self.dict: return self.dict[key] + dvals = self.config[key] + dname, vals = dvals + #theme_dir = themes[name] + v0 = vals[0] + if v0[0] == '#': + v = parse_color(v0) + #if (len(v0) == 7): + # # Due to a bug in pygame 1.8 (?) we need to explicitly + # # specify the alpha value (otherwise it defaults to zero) + # v0 += "FF" + #v = pygame.color.Color(v0) + elif v0.endswith(".ttf") or v0.endswith(".TTF"): + v = pygame.font.Font(os.path.join(dname, v0),int(vals[1])) + elif self.is_image.search(v0) is not None: + v = pygame.image.load(os.path.join(dname, v0)) + else: + try: v = int(v0) + except: v = pygame.font.SysFont(v0, int(vals[1])) + self.dict[key] = v + return v + + def get(self,cls,pcls,attr): + """Interface method -- get the value of a style attribute. + + <pre>Theme.get(cls,pcls,attr): return value</pre> + + <dl> + <dt>cls<dd>class, for example "checkbox", "button", etc. + <dt>pcls<dd>pseudo class, for example "hover", "down", etc. + <dt>attr<dd>attribute, for example "image", "background", "font", "color", etc. + </dl> + + <p>returns the value of the attribute.</p> + + <p>This method is called from [[gui-style]].</p> + """ + + if not self._loaded: self._preload("default") + + o = cls+":"+pcls+" "+attr + + #if not hasattr(self,'_count'): + # self._count = {} + #if o not in self._count: self._count[o] = 0 + #self._count[o] += 1 + + if o in self.cache: + return self.cache[o] + + v = self._get(cls+":"+pcls+" "+attr) + if v: + self.cache[o] = v + return v + + pcls = "" + v = self._get(cls+":"+pcls+" "+attr) + if v: + self.cache[o] = v + return v + + cls = "default" + v = self._get(cls+":"+pcls+" "+attr) + if v: + self.cache[o] = v + return v + + v = 0 + self.cache[o] = v + return v + + def box(self,w,s): + style = w.style + + c = (0,0,0) + if style.border_color != 0: c = style.border_color + w,h = s.get_width(),s.get_height() + + s.fill(c,(0,0,w,style.border_top)) + s.fill(c,(0,h-style.border_bottom,w,style.border_bottom)) + s.fill(c,(0,0,style.border_left,h)) + s.fill(c,(w-style.border_right,0,style.border_right,h)) + + + def getspacing(self,w): + # return the top, right, bottom, left spacing around the widget + if not hasattr(w,'_spacing'): #HACK: assume spacing doesn't change re pcls + s = w.style + xt = s.margin_top+s.border_top+s.padding_top + xr = s.padding_right+s.border_right+s.margin_right + xb = s.padding_bottom+s.border_bottom+s.margin_bottom + xl = s.margin_left+s.border_left+s.padding_left + w._spacing = xt,xr,xb,xl + return w._spacing + + + def resize(self,w,m): + # Returns the rectangle expanded in each direction + def expand_rect(rect, left, top, right, bottom): + return pygame.Rect(rect.x - left, + rect.y - top, + rect.w + left + right, + rect.h + top + bottom) + + def func(width=None,height=None): + s = w.style + + pt,pr,pb,pl = s.padding_top,s.padding_right,s.padding_bottom,s.padding_left + bt,br,bb,bl = s.border_top,s.border_right,s.border_bottom,s.border_left + mt,mr,mb,ml = s.margin_top,s.margin_right,s.margin_bottom,s.margin_left + # Calculate the total space on each side + top = pt+bt+mt + right = pr+br+mr + bottom = pb+bb+mb + left = pl+bl+ml + ttw = left+right + tth = top+bottom + + ww,hh = None,None + if width != None: ww = width-ttw + if height != None: hh = height-tth + ww,hh = m(ww,hh) + + if width == None: width = ww + if height == None: height = hh + + #if the widget hasn't respected the style.width, + #style height, we'll add in the space for it... + width = max(width-ttw, ww, w.style.width) + height = max(height-tth, hh, w.style.height) + + #width = max(ww,w.style.width-tw) + #height = max(hh,w.style.height-th) + + r = pygame.Rect(left,top,width,height) + + w._rect_padding = expand_rect(r, pl, pt, pr, pb) + w._rect_border = expand_rect(w._rect_padding, bl, bt, br, bb) + w._rect_margin = expand_rect(w._rect_border, ml, mt, mr, mb) + + #w._rect_padding = pygame.Rect(r.x-pl,r.y-pt,r.w+pl+pr,r.h+pt+pb) + #r = w._rect_padding + #w._rect_border = pygame.Rect(r.x-bl,r.y-bt,r.w+bl+br,r.h+bt+bb) + #r = w._rect_border + #w._rect_margin = pygame.Rect(r.x-ml,r.y-mt,r.w+ml+mr,r.h+mt+mb) + + # align it within it's zone of power. + rect = pygame.Rect(left, top, ww, hh) + dx = width-rect.w + dy = height-rect.h + rect.x += (w.style.align+1)*dx/2 + rect.y += (w.style.valign+1)*dy/2 + + w._rect_content = rect + + return (w._rect_margin.w, w._rect_margin.h) + return func + + + def paint(self,w,m): + def func(s): +# if w.disabled: +# if not hasattr(w,'_disabled_bkgr'): +# w._disabled_bkgr = s.convert() +# orig = s +# s = w._disabled_bkgr.convert() + +# if not hasattr(w,'_theme_paint_bkgr'): +# w._theme_paint_bkgr = s.convert() +# else: +# s.blit(w._theme_paint_bkgr,(0,0)) +# +# if w.disabled: +# orig = s +# s = w._theme_paint_bkgr.convert() + + if w.disabled: + if (not (hasattr(w,'_theme_bkgr') and + w._theme_bkgr.get_width() == s.get_width() and + w._theme_bkgr.get_height() == s.get_height())): + w._theme_bkgr = s.copy() + orig = s + s = w._theme_bkgr + s.fill((0,0,0,0)) + s.blit(orig,(0,0)) + + if hasattr(w,'background'): + w.background.paint(surface.subsurface(s,w._rect_border)) + self.box(w,surface.subsurface(s,w._rect_border)) + r = m(surface.subsurface(s,w._rect_content)) + + if w.disabled: + s.set_alpha(128) + orig.blit(s,(0,0)) + +# if w.disabled: +# orig.blit(w._disabled_bkgr,(0,0)) +# s.set_alpha(128) +# orig.blit(s,(0,0)) + + w._painted = True + return r + return func + + def event(self,w,m): + def func(e): + rect = w._rect_content + if e.type == MOUSEBUTTONUP or e.type == MOUSEBUTTONDOWN: + sub = pygame.event.Event(e.type,{ + 'button':e.button, + 'pos':(e.pos[0]-rect.x,e.pos[1]-rect.y)}) + elif e.type == CLICK: + sub = pygame.event.Event(e.type,{ + 'button':e.button, + 'pos':(e.pos[0]-rect.x,e.pos[1]-rect.y)}) + elif e.type == MOUSEMOTION: + sub = pygame.event.Event(e.type,{ + 'buttons':e.buttons, + 'pos':(e.pos[0]-rect.x,e.pos[1]-rect.y), + 'rel':e.rel}) + else: + sub = e + r = m(sub) + return r + return func + + def update(self,w,m): + def func(s): + if w.disabled: return [] + r = m(surface.subsurface(s,w._rect_content)) + if type(r) == list: + dx,dy = w._rect_content.topleft + for rr in r: + rr.x,rr.y = rr.x+dx,rr.y+dy + return r + return func + + def open(self,w,m): + def func(widget=None,x=None,y=None): + if not hasattr(w,'_rect_content'): w.rect.w,w.rect.h = w.resize() #HACK: so that container.open won't resize again! + rect = w._rect_content + ##print w.__class__.__name__, rect + if x != None: x += rect.x + if y != None: y += rect.y + return m(widget,x,y) + return func + + #def open(self,w,m): + # def func(widget=None): + # return m(widget) + # return func + + def decorate(self,widget,level): + """Interface method -- decorate a widget. + + <p>The theme system is given the opportunity to decorate a widget methods at the + end of the Widget initializer.</p> + + <pre>Theme.decorate(widget,level)</pre> + + <dl> + <dt>widget<dd>the widget to be decorated + <dt>level<dd>the amount of decoration to do, False for none, True for normal amount, 'app' for special treatment of App objects. + </dl> + """ + + w = widget + if level == False: return + + if type(w.style.background) != int: + w.background = Background(w,self) + + if level == 'app': return + + for k,v in w.style.__dict__.items(): + if k in ('border','margin','padding'): + for kk in ('top','bottom','left','right'): + setattr(w.style,'%s_%s'%(k,kk),v) + + w.paint = self.paint(w,w.paint) + w.event = self.event(w,w.event) + w.update = self.update(w,w.update) + w.resize = self.resize(w,w.resize) + w.open = self.open(w,w.open) + + def render(self,s,box,r): + """Interface method - render a special widget feature. + + <pre>Theme.render(s,box,r)</pre> + + <dl> + <dt>s<dt>pygame.Surface + <dt>box<dt>box data, a value returned from Theme.get, typically a pygame.Surface + <dt>r<dt>pygame.Rect with the size that the box data should be rendered + </dl> + + """ + + if box == 0: return + + if isinstance(box, pygame.Color) or type(box) == tuple: + s.fill(box,r) + return + + x,y,w,h=r.x,r.y,r.w,r.h + ww,hh=box.get_width()/3,box.get_height()/3 + xx,yy=x+w,y+h + src = pygame.rect.Rect(0,0,ww,hh) + dest = pygame.rect.Rect(0,0,ww,hh) + + s.set_clip(pygame.Rect(x+ww,y+hh,w-ww*2,h-hh*2)) + src.x,src.y = ww,hh + for dest.y in xrange(y+hh,yy-hh,hh): + for dest.x in xrange(x+ww,xx-ww,ww): s.blit(box,dest,src) + + s.set_clip(pygame.Rect(x+ww,y,w-ww*3,hh)) + src.x,src.y,dest.y = ww,0,y + for dest.x in xrange(x+ww,xx-ww*2,ww): s.blit(box,dest,src) + dest.x = xx-ww*2 + s.set_clip(pygame.Rect(x+ww,y,w-ww*2,hh)) + s.blit(box,dest,src) + + s.set_clip(pygame.Rect(x+ww,yy-hh,w-ww*3,hh)) + src.x,src.y,dest.y = ww,hh*2,yy-hh + for dest.x in xrange(x+ww,xx-ww*2,ww): s.blit(box,dest,src) + dest.x = xx-ww*2 + s.set_clip(pygame.Rect(x+ww,yy-hh,w-ww*2,hh)) + s.blit(box,dest,src) + + s.set_clip(pygame.Rect(x,y+hh,xx,h-hh*3)) + src.y,src.x,dest.x = hh,0,x + for dest.y in xrange(y+hh,yy-hh*2,hh): s.blit(box,dest,src) + dest.y = yy-hh*2 + s.set_clip(pygame.Rect(x,y+hh,xx,h-hh*2)) + s.blit(box,dest,src) + + s.set_clip(pygame.Rect(xx-ww,y+hh,xx,h-hh*3)) + src.y,src.x,dest.x=hh,ww*2,xx-ww + for dest.y in xrange(y+hh,yy-hh*2,hh): s.blit(box,dest,src) + dest.y = yy-hh*2 + s.set_clip(pygame.Rect(xx-ww,y+hh,xx,h-hh*2)) + s.blit(box,dest,src) + + s.set_clip() + src.x,src.y,dest.x,dest.y = 0,0,x,y + s.blit(box,dest,src) + + src.x,src.y,dest.x,dest.y = ww*2,0,xx-ww,y + s.blit(box,dest,src) + + src.x,src.y,dest.x,dest.y = 0,hh*2,x,yy-hh + s.blit(box,dest,src) + + src.x,src.y,dest.x,dest.y = ww*2,hh*2,xx-ww,yy-hh + s.blit(box,dest,src) + + + +import pygame +import widget + +class Background(widget.Widget): + def __init__(self,value,theme,**params): + params['decorate'] = False + widget.Widget.__init__(self,**params) + self.value = value + self.theme = theme + + def paint(self,s): + r = pygame.Rect(0,0,s.get_width(),s.get_height()) + v = self.value.style.background + if isinstance(v, pygame.Color) or type(v) == tuple: + s.fill(v) + else: + self.theme.render(s,v,r) diff --git a/pgu/gui/theme.pyc b/pgu/gui/theme.pyc Binary files differnew file mode 100644 index 0000000..ca21e90 --- /dev/null +++ b/pgu/gui/theme.pyc diff --git a/pgu/gui/widget.py b/pgu/gui/widget.py new file mode 100644 index 0000000..eb0a7bc --- /dev/null +++ b/pgu/gui/widget.py @@ -0,0 +1,352 @@ +""" +""" +import pygame + +import pguglobals +import style + +class SignalCallback: + # The function to call + func = None + # The parameters to pass to the function (as a list) + params = None + +class Widget: + """Template object - base for all widgets. + + <pre>Widget(**params)</pre> + + <p>A number of optional params may be passed to the Widget initializer.</p> + + <dl> + <dt>decorate<dd>defaults to True. If true, will call <tt>theme.decorate(self)</tt> to allow the theme a chance to decorate the widget. + <dt>style<dd>a dict of style parameters. + <dt>x, y, width, height<dd>position and size parameters, passed along to style + <dt>align, valign<dd>alignment parameters, passed along to style + <dt>font, color, background<dd>other common parameters that are passed along to style + <dt>cls<dd>class name as used by Theme + <dt>name<dd>name of widget as used by Form. If set, will call <tt>form.add(self,name)</tt> to add the widget to the most recently created Form. + <dt>focusable<dd>True if this widget can receive focus via Tab, etc. Defaults to True. + <dt>disabled<dd>True of this widget is disabled. Defaults to False. + <dt>value<dd>initial value + </dl> + + <strong>Example - Creating your own Widget</strong> + <p>This example shows which methods are template methods.</p> + <code> + class Draw(gui.Widget): + def paint(self,s): + #paint the pygame.Surface + return + + def update(self,s): + #update the pygame.Surface and return the update rects + return [pygame.Rect(0,0,self.rect.w,self.rect.h)] + + def event(self,e): + #handle the pygame.Event + return + + def resize(self,width=None,height=None): + #return the width and height of this widget + return 256,256 + </code> + """ + + def __init__(self,**params): + #object.Object.__init__(self) + self.connects = {} + params.setdefault('decorate',True) + params.setdefault('style',{}) + params.setdefault('focusable',True) + params.setdefault('disabled',False) + + self.focusable = params['focusable'] + self.disabled = params['disabled'] + + self.rect = pygame.Rect(params.get('x',0),params.get('y',0),params.get('width',0),params.get('height',0)) + + s = params['style'] + #some of this is a bit "theme-ish" but it is very handy, so these + #things don't have to be put directly into the style. + for att in ('align','valign','x','y','width','height','color','font','background'): + if att in params: s[att] = params[att] + self.style = style.Style(self,s) + + self.cls = 'default' + if 'cls' in params: self.cls = params['cls'] + if 'name' in params: + import form + self.name = params['name'] + if hasattr(form.Form,'form') and form.Form.form != None: + form.Form.form.add(self) + self.form = form.Form.form + if 'value' in params: self.value = params['value'] + self.pcls = "" + + if params['decorate'] != False: + if (not pguglobals.app): + # TODO - fix this somehow + import app + print 'gui.widget: creating an App' + app.App() + pguglobals.app.theme.decorate(self,params['decorate']) + + def focus(self): + """Focus this Widget. + + <pre>Widget.focus()</pre> + """ + if getattr(self,'container',None) != None: + if self.container.myfocus != self: ## by Gal Koren + self.container.focus(self) + def blur(self): + """Blur this Widget. + + <pre>Widget.blur()</pre> + """ + if getattr(self,'container',None) != None: self.container.blur(self) + def open(self): + """Open this Widget as a modal dialog. + + <pre>Widget.open()</pre> + """ + if getattr(self,'container',None) != None: self.container.open(self) + def close(self): + """Close this Widget (if it is a modal dialog.) + + <pre>Widget.close()</pre> + """ + if getattr(self,'container',None) != None: self.container.close(self) + def resize(self,width=None,height=None): + """Template method - return the size and width of this widget. + + <p>Responsible for also resizing all sub-widgets.</p> + + <pre>Widget.resize(width,height): return width,height</pre> + + <dl> + <dt>width<dd>suggested width + <dt>height<dd>suggested height + </dl> + + <p>If not overridden, will return self.style.width, self.style.height</p> + """ + return self.style.width, self.style.height + def chsize(self): + """Change the size of this widget. + + <p>Calling this method will cause a resize on all the widgets, + including this one.</p> + + <pre>Widget.chsize()</pre> + """ + + if not hasattr(self,'_painted'): return + + if not hasattr(self,'container'): return + + if pguglobals.app: + if pguglobals.app._chsize: + return + pguglobals.app.chsize() + return + + #if hasattr(app.App,'app'): + # w,h = self.rect.w,self.rect.h + # w2,h2 = self.resize() + # if w2 != w or h2 != h: + # app.App.app.chsize() + # else: + # self.repaint() + + + def update(self,s): + """Template method - update the surface + + <pre>Widget.update(s): return list of pygame.Rect(s)</pre> + + <dl> + <dt>s<dd>pygame.Surface to update + </dl> + + <p>return - a list of the updated areas as pygame.Rect(s).</p> + """ + return + + def paint(self,s): + """Template method - paint the surface + + <pre>Widget.paint(s)</pre> + + <dl> + <dt>s<dd>pygame.Surface to paint + </dl> + """ + return + + def repaint(self): + """Request a repaint of this Widget. + + <pre>Widget.repaint()</pre> + """ + if getattr(self,'container',None) != None: self.container.repaint(self) + def repaintall(self): + """Request a repaint of all Widgets. + + <pre>Widget.repaintall()</pre> + """ + if getattr(self,'container',None) != None: self.container.repaintall() + def reupdate(self): + """Request a reupdate of this Widget + + <pre>Widget.reupdate()</pre> + """ + if getattr(self,'container',None) != None: self.container.reupdate(self) + def next(self): + """Pass focus to next Widget. + + <p>Widget order determined by the order they were added to their container.</p> + + <pre>Widget.next()</pre> + """ + if getattr(self,'container',None) != None: self.container.next(self) + def previous(self): + """Pass focus to previous Widget. + + <p>Widget order determined by the order they were added to their container.</p> + + <pre>Widget.previous()</pre> + """ + + if getattr(self,'container',None) != None: self.container.previous(self) + + def get_abs_rect(self): + """Get the absolute rect of this widget on the App screen + + <pre>Widget.get_abs_rect(): return pygame.Rect</pre> + """ + x, y = self.rect.x , self.rect.y + x += self._rect_content.x + y += self._rect_content.y + c = getattr(self,'container',None) + while c: + x += c.rect.x + y += c.rect.y + if hasattr(c,'_rect_content'): + x += c._rect_content.x + y += c._rect_content.y + c = getattr(c,'container',None) + return pygame.Rect(x, y, self.rect.w, self.rect.h) + + def connect(self,code,func,*params): + """Connect a event code to a callback function. + + <p>There may be multiple callbacks per event code.</p> + + <pre>Object.connect(code,fnc,value)</pre> + + <dl> + <dt>code<dd>event type [[gui-const]] + <dt>fnc<dd>callback function + <dt>*values<dd>values to pass to callback. Please note that callbacks may also have "magicaly" parameters. Such as: + <dl> + <dt>_event<dd>receive the event + <dt>_code<dd>receive the event code + <dt>_widget<dd>receive the sending widget + </dl> + </dl> + + <strong>Example</strong> + <code> + def onclick(value): + print 'click',value + + w = Button("PGU!") + w.connect(gui.CLICK,onclick,'PGU Button Clicked') + </code> + """ + # Wrap the callback function and add it to the list + cb = SignalCallback() + cb.func = func + cb.params = params + if (not code in self.connects): + self.connects[code] = [] + self.connects[code].append(cb) + + # Remove signal handlers from the given event code. If func is specified, + # only those handlers will be removed. If func is None, all handlers + # will be removed. + def disconnect(self, code, func=None): + if (not code in self.connects): + return + if (not func): + # Remove all signal handlers + del self.connects[code] + else: + # Remove handlers that call 'func' + n = 0 + callbacks = self.connects[code] + while (n < len(callbacks)): + if (callbacks[n].func == func): + # Remove this callback + del callbacks[n] + else: + n += 1 + + def send(self,code,event=None): + """Send a code, event callback trigger. + + <pre>Object.send(code,event=None)</pre> + + <dl> + <dt>code<dd>event code + <dt>event<dd>event + </dl> + """ + if (not code in self.connects): + return + # Trigger all connected signal handlers + for cb in self.connects[code]: + func = cb.func + values = list(cb.params) + + nargs = func.func_code.co_argcount + names = list(func.func_code.co_varnames)[:nargs] + if hasattr(func,'im_class'): names.pop(0) + + args = [] + magic = {'_event':event,'_code':code,'_widget':self} + for name in names: + if name in magic.keys(): + args.append(magic[name]) + elif len(values): + args.append(values.pop(0)) + else: + break + args.extend(values) + func(*args) + + def _event(self,e): + if self.disabled: return + self.send(e.type,e) + return self.event(e) +# return +# import app +# if hasattr(app.App,'app'): +# app.App.app.events.append((self,e)) + + def event(self,e): + """Template method - called when an event is passed to this object. + + <p>Please note that if you use an event, returning the value True + will stop parent containers from also using the event. (For example, if + your widget handles TABs or arrow keys, and you don't want those to + also alter the focus.)</p> + + <dl> + <dt>e<dd>event + </dl> + """ + + return diff --git a/pgu/gui/widget.pyc b/pgu/gui/widget.pyc Binary files differnew file mode 100644 index 0000000..b053a6c --- /dev/null +++ b/pgu/gui/widget.pyc diff --git a/pgu/hexvid.py b/pgu/hexvid.py new file mode 100644 index 0000000..2d4156d --- /dev/null +++ b/pgu/hexvid.py @@ -0,0 +1,127 @@ +"""Hexagonal tile engine. + +<p>Note -- this engine is not finished. Sprites are not supported. It +can still be useful for using the level editor, and for rendering hex +terrains, however. If you are able to update it and use it in a real game, +help would be greatly appreciated!</p> + +<p>please note that this file is alpha, and is subject to modification in +future versions of pgu!</p> + +""" +print 'pgu.hexvid','This module is alpha, and is subject to change.' + +from pgu.vid import * +import pygame + + +class Hexvid(Vid): + """Create an hex vid engine. See [[vid]]""" + def update(self,screen): + return self.paint(screen) + + def paint(self,screen): + sw,sh = screen.get_width(),screen.get_height() + self.view.w,self.view.h = sw,sh + + tlayer = self.tlayer + blayer = self.blayer + #zlayer = self.zlayer + w,h = len(tlayer[0]),len(tlayer) + + #iso_w,iso_h,iso_z,tile_w,tile_h,base_w,base_h = self.iso_w,self.iso_h,self.iso_z,self.tile_w,self.tile_h,self.base_w,self.base_h + + tile_w,tile_h = self.tile_w,self.tile_h + tile_w2,tile_h2 = tile_w/2,tile_h/2 + + view = self.view + adj = self.adj = pygame.Rect(-self.view.x,-self.view.y,0,0) + + w,h = len(tlayer[0]),len(tlayer) + tiles = self.tiles + + #"" + if self.bounds == None: + tmp,y1 = self.tile_to_view((0,0)) + x1,tmp = self.tile_to_view((0,h+1)) + tmp,y2 = self.tile_to_view((w+1,h+1)) + x2,tmp = self.tile_to_view((w+1,0)) + self.bounds = pygame.Rect(x1,y1,x2-x1,y2-y1) + print self.bounds + #"" + + if self.bounds != None: self.view.clamp_ip(self.bounds) + + ox,oy = self.screen_to_tile((0,0)) + sx,sy = self.tile_to_view((ox,oy)) + dx,dy = sx - self.view.x,sy - self.view.y + + bot = 1 + + tile_wi = tile_w + tile_w/2 + tile_wi2 = tile_wi/2 + + #dx += tile_w/2 + + for i2 in xrange(-bot,self.view.h/tile_h2+bot*3): #NOTE: 3 seems a bit much, but it works. + tx,ty = ox + i2/2 + i2%2,oy + i2/2 + x,y = (i2%2)*tile_wi2 + dx,i2*tile_h2 + dy + + #to adjust for the -1 in i1 + x,tx,ty = x-tile_wi,tx-1,ty+1 + + x -= tile_w/2 + for i1 in xrange(-1,self.view.w/tile_wi+1): + if ty >= 0 and ty < h and tx >= 0 and tx < w: + if blayer != None: + n = blayer[ty][tx] + if n != 0: + t = tiles[n] + if t != None and t.image != None: + screen.blit(t.image,(x,y)) + n = tlayer[ty][tx] + if n != 0: + t = tiles[n] + if t != None and t.image != None: + screen.blit(t.image,(x,y)) + + + tx += 1 + ty -= 1 + x += tile_wi + + return [pygame.Rect(0,0,screen.get_width(),screen.get_height())] + + def view_to_tile(self,pos): + x,y = pos + #x = x + (self.tile_w*1/2) + + x,y = int(x*4/(self.tile_w*3)), y*2/self.tile_h + nx = (x + y) / 2 + ny = (y - x) / 2 + return nx,ny + + def tile_to_view(self,pos): + x,y = pos + nx = x - y + ny = x + y + nx,ny = int(nx*(self.tile_w*3)/4), ny*self.tile_h/2 + + #nx = nx - (self.tile_w*1/2) + return nx,ny + + def screen_to_tile(self,pos): #NOTE HACK : not sure if the 3/8 is right or not, but it is pretty close... + pos = pos[0]+self.view.x + self.tile_w*3/8,pos[1]+self.view.y + pos = self.view_to_tile(pos) + return pos + + def tile_to_screen(self,pos): + pos = self.tile_to_view(pos) + pos = pos[0]-self.view.x,pos[1]-self.view.y + return pos + + + def tga_load_tiles(self,fname,size,tdata={}): + Vid.tga_load_tiles(self,fname,size,tdata) + + self.tile_w,self.tile_h = size
\ No newline at end of file diff --git a/pgu/hexvid.pyc b/pgu/hexvid.pyc Binary files differnew file mode 100644 index 0000000..e29026f --- /dev/null +++ b/pgu/hexvid.pyc diff --git a/pgu/high.py b/pgu/high.py new file mode 100644 index 0000000..e05d22a --- /dev/null +++ b/pgu/high.py @@ -0,0 +1,154 @@ +"""Classes for handling high score tables. +""" + +import os + +def High(fname,limit=10): + """Create a Highs object and returns the default high score table. + + <pre>High(fname,limit=10)</pre> + + <dl> + <dt>fname <dd>filename to store high scores in + <dt>limit <dd>limit of scores to be recorded, defaults to 10 + </dl> + """ + return Highs(fname,limit)['default'] + +class _Score: + def __init__(self,score,name,data=None): + self.score,self.name,self.data=score,name,data + +class _High: + """A high score table. These objects are passed to the user, but should not be created directly. + + <p>You can iterate them:</p> + <code> + for e in myhigh: + print e.score,e.name,e.data + </code> + + <p>You can modify them:</p> + <code> + myhigh[0].name = 'Cuzco' + </code> + + <p>You can find out their length:</p> + <code> + print len(myhigh) + </code> + """ + + def __init__(self,highs,limit=10): + self.highs = highs + self._list = [] + self.limit = limit + + def save(self): + """Save the high scores. + + <pre>_High.save()</pre> + """ + self.highs.save() + + def submit(self,score,name,data=None): + """Submit a high score to this table. + + <pre>_High.submit(score,name,data=None)</pre> + + <p>return -- the position in the table that the score attained. None if the score did not attain a position in the table.</p> + """ + n = 0 + for e in self._list: + if score > e.score: + self._list.insert(n,_Score(score,name,data)) + self._list = self._list[0:self.limit] + return n + n += 1 + if len(self._list) < self.limit: + self._list.append(_Score(score,name,data)) + return len(self._list)-1 + + def check(self,score): + """Check if a score will attain a position in the table. + + <pre>_High.check(score)</pre> + + <p>return -- the position the score will attain, else None</p> + """ + n = 0 + for e in self._list: + if score > e.score: + return n + n += 1 + if len(self._list) < self.limit: + return len(self._list) + + + def __iter__(self): + return self._list.__iter__() + + def __getitem__(self,key): + return self._list[key] + + def __len__(self): + return self._list.__len__() + + +class Highs: + """The high score object. + + <pre>Highs(fname,limit=10)</pre> + <ul> + <dt>fname <dd>filename to store high scores in + <dt>limit <dd>limit of scores to be recorded, defaults to 10 + </ul> + + <p>You may access _High objects through this object:</p> + + <code> + my_easy_hs = highs['easy'] + my_hard_hs = highs['hard'] + </code> + + """ + def __init__(self,fname,limit=10): + self.fname = fname + self.limit = limit + self.load() + + def load(self): + """Re-load the high scores. + + <pre>Highs.load()</pre> + """ + + self._dict = {} + try: + f = open(self.fname) + for line in f.readlines(): + key,score,name,data = line.strip().split("\t") + if key not in self._dict: + self._dict[key] = _High(self,self.limit) + high = self._dict[key] + high.submit(int(score),name,data) + f.close() + except: + pass + + def save(self): + """Save the high scores. + + <pre>Highs.save()</pre> + """ + + f = open(self.fname,"w") + for key,high in self._dict.items(): + for e in high: + f.write("%s\t%d\t%s\t%s\n"%(key,e.score,e.name,str(e.data))) + f.close() + + def __getitem__(self,key): + if key not in self._dict: + self._dict[key] = _High(self,self.limit) + return self._dict[key] diff --git a/pgu/html.py b/pgu/html.py new file mode 100644 index 0000000..817c000 --- /dev/null +++ b/pgu/html.py @@ -0,0 +1,571 @@ +"""a html renderer +""" + +import sys +import htmllib +import re +import pygame +from pygame.locals import * + +from pgu import gui + +_amap = {'left':-1,'right':1,'center':0,None:None,'':None,} +_vamap = {'top':-1,'bottom':1,'center':0,'middle':0,None:None,'':None,} + +# Used by the HTML parser to load external resources (like images). This +# class loads content from the local file system. But you can pass your own +# resource loader to the HTML parser to find images by other means. +class ResourceLoader(object): + # Loads an image and returns it as a pygame image + def load_image(this, path): + return pygame.image.load(path) + +class _dummy: + pass + +class _flush: + def __init__(self): + self.style = _dummy() + self.style.font = None + self.style.color = None + self.cls = None + def add(self,w): pass + def space(self,v): pass + +class _hr(gui.Color): + def __init__(self,**params): + gui.Color.__init__(self,(0,0,0),**params) + def resize(self,width=None,height=None): + w,h = self.style.width,self.style.height + #if width != None: self.rect.w = width + #else: self.rect.w = 1 + + #xt,xr,xb,xl = self.getspacing() + + if width != None: w = max(w,width) + if height != None: h = max(h,height) + w = max(w,1) + h = max(h,1) + + return w,h #self.container.rect.w,h + + #self.rect.w = max(1,width,self.container.rect.w-(xl+xr)) + + #print self.rect + #self.rect.w = 1 + +class _html(htmllib.HTMLParser): + def init(self,doc,font,color,_globals,_locals,loader=None): + self.mystack = [] + self.document = doc + if (loader): + self.loader = loader + else: + # Use the default resource loader + self.loader = ResourceLoader() + self.myopen('document',self.document) + + self.myfont = self.font = font + self.mycolor = self.color = color + + self.form = None + + self._globals = _globals + self._locals = _locals + + def myopen(self,type_,w): + + self.mystack.append((type_,w)) + self.type,self.item = type_,w + + self.font = self.item.style.font + self.color = self.item.style.color + + if not self.font: self.font = self.myfont + if not self.color: self.color = self.mycolor + + def myclose(self,type_): + t = None + self.mydone() + while t != type_: + #if len(self.mystack)==0: return + t,w = self.mystack.pop() + t,w = self.mystack.pop() + self.myopen(t,w) + + def myback(self,type_): + if type(type_) == str: type_ = [type_,] + self.mydone() + #print 'myback',type_ + t = None + while t not in type_: + #if len(self.mystack)==0: return + t,w = self.mystack.pop() + self.myopen(t,w) + + def mydone(self): + #clearing out the last </p> + if not hasattr(self.item,'layout'): return + if len(self.item.layout._widgets) == 0: return + w = self.item.layout._widgets[-1] + if type(w) == tuple: + del self.item.layout._widgets[-1] + + + def start_b(self,attrs): self.font.set_bold(1) + def end_b(self): self.font.set_bold(0) + def start_i(self,attrs): self.font.set_italic(1) + def end_i(self): self.font.set_italic(0) + def start_u(self,attrs): self.font.set_underline(1) + def end_u(self): self.font.set_underline(0) + def start_br(self,attrs): self.do_br(attrs) + def do_br(self,attrs): self.item.br(self.font.size(" ")[1]) + def attrs_to_map(self,attrs): + k = None + r = {} + for k,v in attrs: r[k] = v + return r + + def map_to_params(self,r): + anum = re.compile("\D") + + params = {'style':{}} + style = params['style'] + + if 'bgcolor' in r: + style['background'] = gui.parse_color(r['bgcolor']) + if 'background' in r: + style['background'] = self.loader.load_image(r['background']) + if 'border' in r: style['border'] = int(r['border']) + + for k in ['width','height','colspan','rowspan','size','min','max']: + if k in r: params[k] = int(anum.sub("",r[k])) + + for k in ['name','value']: + if k in r: params[k] = r[k] + + if 'class' in r: params['cls'] = r['class'] + + if 'align' in r: + params['align'] = _amap[r['align']] + if 'valign' in r: + params['valign'] = _vamap[r['valign']] + + if 'style' in r: + for st in r['style'].split(";"): + #print st + if ":" in st: + #print st.split(":") + k,v = st.split(":") + k = k.replace("-","_") + k = k.replace(" ","") + v = v.replace(" ","") + if k == 'color' or k == 'border_color' or k == 'background': + v = gui.parse_color(v) + else: + v = int(anum.sub("",v)) + style[k] = v + return params + + def map_to_connects(self,e,r): + for k,evt in [('onclick',gui.CLICK),('onchange',gui.CHANGE)]: #blah blah blah + + if k in r: + #print k,r[k] + e.connect(evt,self.myexec,(e,r[k])) + + def start_p(self,attrs): + r = self.attrs_to_map(attrs) + align = r.get("align","left") + + self.check_p() + self.item.block(_amap[align]) + + def check_p(self): + if len(self.item.layout._widgets) == 0: return + if type(self.item.layout._widgets[-1]) == tuple: + w,h = self.item.layout._widgets[-1] + if w == 0: return + self.do_br(None) + + def end_p(self): + #print 'end p' + self.check_p() + + + def start_block(self,t,attrs,align=-1): + r = self.attrs_to_map(attrs) + params = self.map_to_params(r) + if 'cls' in params: params['cls'] = t+"."+params['cls'] + else: params['cls'] = t + b = gui.Document(**params) + b.style.font = self.item.style.font + if 'align' in params: + align = params['align'] + self.item.block(align) + self.item.add(b) + self.myopen(t,b) + + + + def end_block(self,t): + self.myclose(t) + self.item.block(-1) + + def start_div(self,attrs): self.start_block('div',attrs) + def end_div(self): self.end_block('div') + def start_center(self,attrs): self.start_block('div',attrs,0) + def end_center(self): self.end_block('div') + + def start_h1(self,attrs): self.start_block('h1',attrs) + def end_h1(self): self.end_block('h1') + def start_h2(self,attrs): self.start_block('h2',attrs) + def end_h2(self): self.end_block('h2') + def start_h3(self,attrs): self.start_block('h3',attrs) + def end_h3(self): self.end_block('h3') + def start_h4(self,attrs): self.start_block('h4',attrs) + def end_h4(self): self.end_block('h4') + def start_h5(self,attrs): self.start_block('h5',attrs) + def end_h5(self): self.end_block('h5') + def start_h6(self,attrs): self.start_block('h6',attrs) + def end_h6(self): self.end_block('h6') + + def start_ul(self,attrs): self.start_block('ul',attrs) + def end_ul(self): self.end_block('ul') + def start_ol(self,attrs): + self.start_block('ol',attrs) + self.item.counter = 0 + def end_ol(self): self.end_block('ol') + def start_li(self,attrs): + self.myback(['ul','ol']) + cur = self.item + self.start_block('li',attrs) + if hasattr(cur,'counter'): + cur.counter += 1 + self.handle_data("%d. "%cur.counter) + else: + self.handle_data("- ") + #def end_li(self): self.end_block('li') #this isn't needed because of how the parser works + + def start_pre(self,attrs): self.start_block('pre',attrs) + def end_pre(self): self.end_block('pre') + def start_code(self,attrs): self.start_block('code',attrs) + def end_code(self): self.end_block('code') + + def start_table(self,attrs): + r = self.attrs_to_map(attrs) + params = self.map_to_params(r) + + align = r.get("align","left") + self.item.block(_amap[align]) + + t = gui.Table(**params) + self.item.add(t) + + self.myopen('table',t) + + def start_tr(self,attrs): + self.myback('table') + self.item.tr() + + def _start_td(self,t,attrs): + r = self.attrs_to_map(attrs) + params = self.map_to_params(r) + if 'cls' in params: params['cls'] = t+"."+params['cls'] + else: params['cls'] = t + b = gui.Document(cls=t) + + self.myback('table') + self.item.td(b,**params) + self.myopen(t,b) + + self.font = self.item.style.font + self.color = self.item.style.color + + def start_td(self,attrs): + self._start_td('td',attrs) + + def start_th(self,attrs): + self._start_td('th',attrs) + + def end_table(self): + self.myclose('table') + self.item.block(-1) + + def start_form(self,attrs): + r = self.attrs_to_map(attrs) + e = self.form = gui.Form() + e.groups = {} + + self._locals[r.get('id',None)] = e + + def start_input(self,attrs): + r = self.attrs_to_map(attrs) + params = self.map_to_params(r) #why bother + #params = {} + + type_,name,value = r.get('type','text'),r.get('name',None),r.get('value',None) + f = self.form + if type_ == 'text': + e = gui.Input(**params) + self.map_to_connects(e,r) + self.item.add(e) + elif type_ == 'radio': + if name not in f.groups: + f.groups[name] = gui.Group(name=name) + g = f.groups[name] + del params['name'] + e = gui.Radio(group=g,**params) + self.map_to_connects(e,r) + self.item.add(e) + if 'checked' in r: g.value = value + elif type_ == 'checkbox': + if name not in f.groups: + f.groups[name] = gui.Group(name=name) + g = f.groups[name] + del params['name'] + e = gui.Checkbox(group=g,**params) + self.map_to_connects(e,r) + self.item.add(e) + if 'checked' in r: g.value = value + + elif type_ == 'button': + e = gui.Button(**params) + self.map_to_connects(e,r) + self.item.add(e) + elif type_ == 'submit': + e = gui.Button(**params) + self.map_to_connects(e,r) + self.item.add(e) + elif type_ == 'file': + e = gui.Input(**params) + self.map_to_connects(e,r) + self.item.add(e) + b = gui.Button(value='Browse...') + self.item.add(b) + def _browse(value): + d = gui.FileDialog(); + d.connect(gui.CHANGE,gui.action_setvalue,(d,e)) + d.open(); + b.connect(gui.CLICK,_browse,None) + + self._locals[r.get('id',None)] = e + + def start_object(self,attrs): + r = self.attrs_to_map(attrs) + params = self.map_to_params(r) + code = "e = %s(**params)"%r['type'] + #print code + #print params + exec(code) + #print e + #print e.style.width,e.style.height + self.map_to_connects(e,r) + self.item.add(e) + + self._locals[r.get('id',None)] = e + + def start_select(self,attrs): + r = self.attrs_to_map(attrs) + params = {} + + name,value = r.get('name',None),r.get('value',None) + e = gui.Select(name=name,value=value,**params) + self.map_to_connects(e,r) + self.item.add(e) + self.myopen('select',e) + + def start_option(self,attrs): + r = self.attrs_to_map(attrs) + params = {} #style = self.map_to_style(r) + + self.myback('select') + e = gui.Document(**params) + self.item.add(e,value=r.get('value',None)) + self.myopen('option',e) + + + def end_select(self): + self.myclose('select') + + def start_hr(self,attrs): + self.do_hr(attrs) + def do_hr(self,attrs): + h = self.font.size(" ")[1]/2 + + r = self.attrs_to_map(attrs) + params = self.map_to_params(r) + params['style']['padding'] = h + print params + + self.item.block(0) + self.item.add(_hr(**params)) + self.item.block(-1) + + def anchor_begin(self,href,name,type_): + pass + + def anchor_end(self): + pass + + def start_title(self,attrs): self.myopen('title',_flush()) + def end_title(self): self.myclose('title') + + def myexec(self,value): + w,code = value + g = self._globals + l = self._locals + l['self'] = w + exec(code,g,l) + + def handle_image(self,src,alt,ismap,align,width,height): + try: + w = gui.Image(self.loader.load_image(src)) + if align != '': + self.item.add(w,_amap[align]) + else: + self.item.add(w) + except: + print 'handle_image: missing %s'%src + + def handle_data(self,txt): + if self.type == 'table': return + elif self.type in ('pre','code'): + txt = txt.replace("\t"," ") + ss = txt.split("\n") + if ss[-1] == "": del ss[-1] + for sentence in ss: + img = self.font.render(sentence,1,self.color) + w = gui.Image(img) + self.item.add(w) + self.item.block(-1) + return + + txt = re.compile("^[\t\r\n]+").sub("",txt) + txt = re.compile("[\t\r\n]+$").sub("",txt) + + tst = re.compile("[\t\r\n]+").sub("",txt) + if tst == "": return + + txt = re.compile("\s+").sub(" ",txt) + if txt == "": return + + if txt == " ": + self.item.space(self.font.size(" ")) + return + + for word in txt.split(" "): + word = word.replace(chr(160)," ") # + #print self.item.cls + w = gui.Image(self.font.render(word,1,self.color)) + self.item.add(w) + self.item.space(self.font.size(" ")) + + +class HTML(gui.Document): + """a gui HTML object + + <pre>HTML(data,globals=None,locals=None)</pre> + + <dl> + <dt>data <dd>html data + <dt>globals <dd>global variables (for scripting) + <dt>locals <dd>local variables (for scripting) + <dt>loader <dd>the resource loader + </dl> + + <p>you may access html elements that have an id via widget[id]</p> + """ + def __init__(self,data,globals=None,locals=None,loader=None,**params): + gui.Document.__init__(self,**params) + # This ensures that the whole HTML document is left-aligned within + # the rendered surface. + self.style.align = -1 + + _globals,_locals = globals,locals + + if _globals == None: _globals = {} + if _locals == None: _locals = {} + self._globals = _globals + self._locals = _locals + + #font = gui.theme.get("label","","font") + p = _html(htmllib.AS_IS,0) + p.init(self,self.style.font,self.style.color,_globals,_locals, + loader=loader) + p.feed(data) + p.close() + p.mydone() + + + def __getitem__(self,k): + return self._locals[k] + + # Returns a box (pygame rectangle) surrounding all widgets in this document + def get_bounding_box(this): + minx = miny = sys.maxint + maxx = maxy = -sys.maxint + for e in this.layout.widgets: + minx = min(minx, e.rect.left) + miny = min(miny, e.rect.top) + maxx = max(maxx, e.rect.right+1) + maxy = max(maxy, e.rect.bottom+1) + return pygame.Rect(minx, miny, maxx-minx, maxy-miny) + + +def render_ext(font, rect, text, aa, color, bgcolor=(0,0,0,0), **params): + """Renders some html and returns the rendered surface, plus the + HTML instance that produced it. + """ + + htm = HTML(text, font=font, color=color, **params) + + if (rect == -1): + # Make the surface large enough to fit the rendered text + htm.resize(width=sys.maxint) + (width, height) = htm.get_bounding_box().size + # Now set the proper document width (computed from the bounding box) + htm.resize(width=width) + elif (type(rect) == int): + # Fix the width of the document, while the height is variable + width = rect + height = htm.resize(width=width)[1] + else: + # Otherwise the width and height of the document is fixed + (width, height) = rect.size + htm.resize(width=width) + + # Now construct a surface and paint to it + surf = pygame.Surface((width, height)).convert_alpha() + surf.fill(bgcolor) + htm.paint(surf) + return (surf, htm) + +def render(font, rect, text, aa, color, bgcolor=(0,0,0,0), **params): + """Renders some html + + <pre>render(font,rect,text,aa,color,bgcolor=(0,0,0,0))</pre> + """ + return render_ext(font, rect, text, aa, color, bgcolor, **params)[0] + +def rendertrim(font,rect,text,aa,color,bgcolor=(0,0,0,0),**params): + """render html, and make sure to trim the size + + rendertrim(font,rect,text,aa,color,bgcolor=(0,0,0,0)) + """ + # Render the HTML + (surf, htm) = render_ext(font, rect, text, aa, color, bgcolor, **params) + return surf.subsurface(htm.get_bounding_box()) + + +def write(s,font,rect,text,aa=0,color=(0,0,0), **params): + """write html to a surface + + write(s,font,rect,text,aa=0,color=(0,0,0)) + """ + htm = HTML(text, font=font, color=color, **params) + htm.resize(width=rect.w) + s = s.subsurface(rect) + htm.paint(s) + +# vim: set filetype=python sts=4 sw=4 noet si : diff --git a/pgu/html.pyc b/pgu/html.pyc Binary files differnew file mode 100644 index 0000000..6a1f3f8 --- /dev/null +++ b/pgu/html.pyc diff --git a/pgu/isovid.py b/pgu/isovid.py new file mode 100644 index 0000000..d5048ec --- /dev/null +++ b/pgu/isovid.py @@ -0,0 +1,182 @@ +"""Isometric tile engine. + +<p>Note -- this engine is not finished, any may not work for your +particular needs. If you are able to update it, help would be +greatly appreciated!</p> + +<p>please note that this file is alpha, and is subject to modification in +future versions of pgu!</p> + +""" +print 'pgu.isovid','This module is alpha, and is subject to change.' + +from pgu.vid import * +import pygame + +class Isovid(Vid): + """Create an iso vid engine. See [[vid]]""" + def update(self,screen): + return self.paint(screen) + + def paint(self,screen): + sw,sh = screen.get_width(),screen.get_height() + + tlayer = self.tlayer + blayer = self.blayer + zlayer = self.zlayer + w,h = len(tlayer[0]),len(tlayer) + + iso_w,iso_h,iso_z,tile_w,tile_h,base_w,base_h = self.iso_w,self.iso_h,self.iso_z,self.tile_w,self.tile_h,self.base_w,self.base_h + + base_h2 = base_h/2 + base_w2 = base_w/2 + + bot = tile_h/base_h2 + todo_max = sh/base_h2+bot + todo = [[] for y in xrange(0,todo_max)] + + self.view.w,self.view.h = sw,sh + view = self.view + adj = self.adj = pygame.Rect(-self.view.x,-self.view.y,0,0) + + for s in self.sprites: + self.sprite_calc_irect(s) + x,y = self.iso_to_view((s.rect.centerx,s.rect.centery)) + v = (y+adj.y)/base_h2 - 1 + if v >= 0 and v < todo_max: + todo[v].append((s.image,s.irect)) + #else: print 'doesnt fit',v + + w,h = len(tlayer[0]),len(tlayer) + tiles = self.tiles + + #"" + if self.bounds == None: + tmp,y1 = self.tile_to_view((0,0)) + x1,tmp = self.tile_to_view((0,h+1)) + tmp,y2 = self.tile_to_view((w+1,h+1)) + x2,tmp = self.tile_to_view((w+1,0)) + self.bounds = pygame.Rect(x1,y1,x2-x1,y2-y1) + #"" + + if self.bounds != None: self.view.clamp_ip(self.bounds) + + ox,oy = self.screen_to_tile((0,0)) + sx,sy = self.iso_to_view((ox*iso_w,oy*iso_h)) + dx,dy = sx - self.view.x,sy - self.view.y + + for i2 in xrange(-bot,self.view.h/base_h2+bot): + tx,ty = ox + i2/2 + i2%2,oy + i2/2 + x,y = (i2%2)*base_w2 + dx,i2*base_h2 + dy + + #to adjust for the -1 in i1 + x,tx,ty = x-base_w,tx-1,ty+1 + for i1 in xrange(-1,self.view.w/base_w+2): #NOTE: not sure why +2 + if ty >= 0 and ty < h and tx >= 0 and tx < w: + z = zlayer[ty][tx]*iso_z + if blayer != None: + n = blayer[ty][tx] + if n != 0: + t = tiles[n] + if t != None and t.image != None: + screen.blit(t.image,(x-base_w2,y+z)) + n = tlayer[ty][tx] + if n != 0: + t = tiles[n] + if t != None and t.image != None: + screen.blit(t.image,(x-base_w2,y-(t.image_h-base_h)+z)) + + tx += 1 + ty -= 1 + x += base_w + for img,irect in todo[y/base_h2]: + screen.blit(img,(irect.x+adj.x,irect.y+adj.y)) + + return [pygame.Rect(0,0,screen.get_width(),screen.get_height())] + + def iso_to_view(self,pos): + tlayer = self.tlayer + w,h = len(tlayer[0]),len(tlayer) + + x,y = pos + + #nx,ny = (h*self.iso_w + x - y)/2, (0 + x + y)/2 + nx,ny = (x - y)/2, (0 + x + y)/2 + + return (nx * self.base_w / self.iso_w), (ny * self.base_h / self.iso_h) + + def view_to_iso(self,pos): + tlayer = self.tlayer + w,h = len(tlayer[0]),len(tlayer) + + x,y = pos + + x,y = x*self.iso_w/self.base_w, y*self.iso_h/self.base_h + + #x -= (self.iso_w/2) * h + #x -= (self.iso_w/2) * h + + nx = (x+y) + ny = y*2-nx + + return nx,ny + + def tile_to_view(self,pos): + return self.iso_to_view((pos[0]*self.iso_w,pos[1]*self.iso_h)) + + def screen_to_tile(self,pos): + x,y = pos + x += self.view.x + y += self.view.y + x,y = self.view_to_iso((x,y)) + return x/self.iso_w,y/self.iso_h + + def tile_to_screen(self,pos): + x,y = self.iso_to_view((pos[0]*self.iso_w,pos[1]*self.iso_h)) + return x-self.view.x,y-self.view.y + + def tga_load_tiles(self,fname,size,tdata={}): + Vid.tga_load_tiles(self,fname,size,tdata) + + self.tile_w,self.tile_h = size + self.iso_w,self.iso_h,self.iso_z = self.tile_w,self.tile_w,1 + self.base_w,self.base_h = self.tile_w,self.tile_w/2 + + + + def resize(self,size,bg=0): + Vid.resize(self,size,bg) + + tlayer = self.tlayer + w,h = len(tlayer[0]),len(tlayer) + + self.zlayer = [[0 for x in xrange(0,w)] for y in xrange(0,h)] + + + + + def sprite_calc_irect(self,s): + tlayer = self.tlayer + w,h = len(tlayer[0]),len(tlayer) + zlayer = self.zlayer + + x,y = self.iso_to_view((s.rect.centerx,s.rect.centery)) + tx,ty = s.rect.centerx/self.iso_w,s.rect.centery/self.iso_h + z = 0 + if ty >= 0 and ty < h and tx >= 0 and tx < w: + z = zlayer[ty][tx]*self.iso_z + + nx,ny = x - s.shape.centerx, y - s.shape.centery + z + + s.irect.x,s.irect.y = nx,ny + + def run_codes(self,cdata,rect): + #HACK to make run_codes work + w,h = self.iso_w,self.iso_h + + img = self.tiles[0].image + + self.tiles[0].image = pygame.Surface((w,h)) + r = Vid.run_codes(self,cdata,rect) + self.tiles[0].image = img + return r diff --git a/pgu/isovid.pyc b/pgu/isovid.pyc Binary files differnew file mode 100644 index 0000000..d4f84bc --- /dev/null +++ b/pgu/isovid.pyc diff --git a/pgu/layout.py b/pgu/layout.py new file mode 100644 index 0000000..75b6c9a --- /dev/null +++ b/pgu/layout.py @@ -0,0 +1,4 @@ +print 'pgu.layout','Scheduled to be deprecated.' + +from pgu.gui.layout import * + diff --git a/pgu/layout.pyc b/pgu/layout.pyc Binary files differnew file mode 100644 index 0000000..fbb09e4 --- /dev/null +++ b/pgu/layout.pyc diff --git a/pgu/text.py b/pgu/text.py new file mode 100644 index 0000000..1010a87 --- /dev/null +++ b/pgu/text.py @@ -0,0 +1,61 @@ +"""a collection of text rendering functions +""" +def write(s,font,pos,color,text,border=1): + """write text to a surface with a black border + + <pre>write(s,font,pos,color,text,border=1)</pre> + """ + i = font.render(text,1,(0,0,0)) + si = border + dirs = [(-1,-1),(-1,0),(-1,1),(0,-1),(0,1),(1,-1),(1,0),(1,1)] + for dx,dy in dirs: s.blit(i,(pos[0]+dx*si,pos[1]+dy*si)) + i = font.render(text,1,color) + s.blit(i,pos) + +def writec(s,font,color,text,border=1): + """write centered text to a surface with a black border + + <pre>writec(s,font,color,text,border=1)</pre> + """ + w,h = font.size(text) + x = (s.get_width()-w)/2 + y = (s.get_height()-h)/2 + write(s,font,(x,y),color,text,border) + +def writepre(s,font,rect,color,text): + """write preformatted text + + <pre>writepre(s,font,rect,color,text)</pre> + """ + r,c,txt = rect,color,text + txt = txt.replace("\t"," ") + i = font.render(" ",1,c) + sw,sh = i.get_width(),i.get_height() + y = r.top + for sentence in txt.split("\n"): + x = r.left + i = font.render(sentence,1,c) + s.blit(i,(x,y)) + y += sh + +def writewrap(s,font,rect,color,text): + """write wrapped text + + <pre>writewrap(s,font,rect,color,text)</pre> + """ + r,c,txt = rect,color,text + txt = txt.replace("\t"," ") + i = font.render(" ",1,c) + sw,sh = i.get_width(),i.get_height() + y = r.top + for sentence in txt.split("\n"): + x = r.left + for word in sentence.split(" "): + i = font.render(word,1,c) + iw,ih = i.get_width(),i.get_height() + if x+iw > r.right: x,y = r.left,y+sh + s.blit(i,(x,y)) + x += iw+sw + y += sh + +# vim: set filetype=python sts=4 sw=4 noet si : diff --git a/pgu/text.pyc b/pgu/text.pyc Binary files differnew file mode 100644 index 0000000..90037e2 --- /dev/null +++ b/pgu/text.pyc diff --git a/pgu/tilevid.py b/pgu/tilevid.py new file mode 100644 index 0000000..00f730d --- /dev/null +++ b/pgu/tilevid.py @@ -0,0 +1,195 @@ +"""Square tile based engine.""" + +from pgu.vid import * +import pygame + +class Tilevid(Vid): + """Based on [[vid]] -- see for reference.""" + def paint(self,s): + sw,sh = s.get_width(),s.get_height() + self.view.w,self.view.h = sw,sh + + tiles = self.tiles + tw,th = tiles[0].image.get_width(),tiles[0].image.get_height() + w,h = self.size + + if self.bounds != None: self.view.clamp_ip(self.bounds) + + ox,oy = self.view.x,self.view.y + tlayer = self.tlayer + blayer = self.blayer + alayer = self.alayer + sprites = self.sprites + + blit = s.blit + yy = - (self.view.y%th) + my = (oy+sh)/th + if (oy+sh)%th: my += 1 + + if blayer != None: + for y in xrange(oy/th,my): + if y >=0 and y < h: + trow = tlayer[y] + brow = blayer[y] + arow = alayer[y] + xx= - (self.view.x%tw) + mx = (ox+sw)/tw + #if (ox+sh)%tw: mx += 1 + for x in xrange(ox/tw,mx+1): + if x >=0and x<w: + blit(tiles[brow[x]].image,(xx,yy)) + blit(tiles[trow[x]].image,(xx,yy)) + arow[x]=0 + xx += tw + yy+=th + else: + for y in xrange(oy/th,my): + if y >=0 and y<h: + trow = tlayer[y] + arow = alayer[y] + xx= - (self.view.x%tw) + mx = (ox+sw)/tw + #if (ox+sh)%tw: mx += 1 + for x in xrange(ox/tw,mx+1): + if x >=0 and x<w: + blit(tiles[trow[x]].image,(xx,yy)) + arow[x]=0 + xx += tw + yy+=th + + for s in sprites: + s.irect.x = s.rect.x-s.shape.x + s.irect.y = s.rect.y-s.shape.y + blit(s.image,(s.irect.x-ox,s.irect.y-oy)) + s.updated=0 + s._irect = Rect(s.irect) + #s._rect = Rect(s.rect) + + self.updates = [] + self._view = pygame.Rect(self.view) + return [Rect(0,0,sw,sh)] + + def update(self,s): + sw,sh = s.get_width(),s.get_height() + self.view.w,self.view.h = sw,sh + + if self.bounds != None: self.view.clamp_ip(self.bounds) + if self.view.x != self._view.x or self.view.y != self._view.y: + return self.paint(s) + + ox,oy = self.view.x,self.view.y + sw,sh = s.get_width(),s.get_height() + w,h = self.size + tlayer = self.tlayer + blayer = self.blayer + alayer = self.alayer + tiles = self.tiles + tw,th = tiles[0].image.get_width(),tiles[0].image.get_height() + sprites = self.sprites + blit = s.blit + + us = [] + + #mark places where sprites have moved, or been removed + + ss = self.sprites.removed + self.sprites.removed = [] + ss.extend(sprites) + for s in ss: + #figure out what has been updated. + s.irect.x = s.rect.x-s.shape.x + s.irect.y = s.rect.y-s.shape.y + if (s.irect.x != s._irect.x or s.irect.y != s._irect.y + or s.image != s._image): + #w,h can be skipped, image covers that... + s.updated = 1 + if s.updated: + r = s._irect + y = max(0,r.y/th) + yy = min(h,r.bottom/th+1) + while y < yy: + x = max(0,r.x/tw) + xx = min(w,r.right/tw+1) + while x < xx: + if alayer[y][x] == 0: + self.updates.append((x,y)) + alayer[y][x]=1 + x += 1 + y += 1 + + r = s.irect + y = max(0,r.y/th) + yy = min(h,r.bottom/th+1) + while y < yy: + x = r.x/tw + xx = min(w,r.right/tw+1) + while x < xx: + if alayer[y][x]==0: + alayer[y][x]=2 + self.updates.append((x,y)) + x += 1 + y += 1 + + + #mark sprites that are not being updated that need to be updated because + #they are being overwritte by sprites / tiles + for s in sprites: + if s.updated==0: + r = s.irect + y = max(0,r.y/th) + yy = min(h,r.bottom/th+1) + while y < yy: + x = max(0,r.x/tw) + xx = min(w,r.right/tw+1) + while x < xx: + if alayer[y][x]==1: + s.updated=1 + x += 1 + y += 1 + + + for u in self.updates: + x,y=u + xx,yy=x*tw-ox,y*th-oy + if alayer[y][x] == 1: + if blayer != None: blit(tiles[blayer[y][x]].image,(xx,yy)) + blit(tiles[tlayer[y][x]].image,(xx,yy)) + alayer[y][x]=0 + us.append(Rect(xx,yy,tw,th)) + + for s in sprites: + if s.updated: + blit(s.image,(s.irect.x-ox, s.irect.y-oy)) + s.updated=0 + s._irect = Rect(s.irect) + s._image = s.image + + self.updates = [] + return us + + def view_to_tile(self,pos): + x,y = pos + tiles = self.tiles + tw,th = tiles[0].image.get_width(),tiles[0].image.get_height() + return x/tw,y/th + + def tile_to_view(self,pos): + x,y = pos + tiles = self.tiles + tw,th = tiles[0].image.get_width(),tiles[0].image.get_height() + x,y = x*tw, y*th + return x,y + + + def screen_to_tile(self,pos): + x,y = pos + x,y = x+self.view.x,y+self.view.y + return self.view_to_tile((x,y)) + + def tile_to_screen(self,pos): + x,y = pos + x,y = self.tile_to_view(pos) + x,y = x - self.view.x, y - self.view.y + return x,y + +# vim: set filetype=python sts=4 sw=4 noet si : diff --git a/pgu/tilevid.pyc b/pgu/tilevid.pyc Binary files differnew file mode 100644 index 0000000..98c5dc4 --- /dev/null +++ b/pgu/tilevid.pyc diff --git a/pgu/timer.py b/pgu/timer.py new file mode 100644 index 0000000..1201b7a --- /dev/null +++ b/pgu/timer.py @@ -0,0 +1,68 @@ +"""A timer for games with set-rate FPS. +""" + +import pygame + +class Timer: + """A timer for games with set-rate FPS. + + <pre>Timer(fps)</pre> + """ + + def __init__(self,fps): + if fps == 0: + self.tick = self._blank + return + self.wait = 1000/fps + self.nt = pygame.time.get_ticks() + pygame.time.wait(0) + + def _blank(self): + pass + + def tick(self): + """Wait correct amount of time each frame. Call this once per frame. + + <pre>Timer.tick()</pre> + """ + self.ct = pygame.time.get_ticks() + if self.ct < self.nt: + pygame.time.wait(self.nt-self.ct) + self.nt+=self.wait + else: + self.nt = pygame.time.get_ticks()+self.wait + + +class Speedometer: + """A timer replacement that returns out FPS once a second. + <pre>Speedometer()</pre> + + <strong>Attributes</strong> + <dl> + <dt>fps <dd>always set to the current FPS + </dl> + """ + def __init__(self): + self.frames = 0 + self.st = pygame.time.get_ticks() + self.fps = 0 + + def tick(self): + """ Call this once per frame. + + <pre>Speedometer.tick()</pre> + """ + r = None + self.frames += 1 + self.ct = pygame.time.get_ticks() + if (self.ct - self.st) >= 1000: + r = self.fps = self.frames + #print "%s: %d fps"%(self.__class__.__name__,self.fps) + self.frames = 0 + self.st += 1000 + pygame.time.wait(0) #NOTE: not sure why, but you gotta call this now and again + return r + + + +# vim: set filetype=python sts=4 sw=4 noet si : diff --git a/pgu/timer.pyc b/pgu/timer.pyc Binary files differnew file mode 100644 index 0000000..3cf833d --- /dev/null +++ b/pgu/timer.pyc diff --git a/pgu/vid.py b/pgu/vid.py new file mode 100644 index 0000000..6890e5d --- /dev/null +++ b/pgu/vid.py @@ -0,0 +1,560 @@ +"""Sprite and tile engine. + +<p>[[tilevid]], [[isovid]], [[hexvid]] are all subclasses of +this interface.</p> + +<p>Includes support for:</p> + +<ul> +<li> Foreground Tiles +<li> Background Tiles +<li> Sprites +<li> Sprite-Sprite Collision handling +<li> Sprite-Tile Collision handling +<li> Scrolling +<li> Loading from PGU tile and sprite formats (optional) +<li> Set rate FPS (optional) +</ul> + +<p>This code was previously known as the King James Version (named after the +Bible of the same name for historical reasons.)</p> +""" + +import pygame +from pygame.rect import Rect +from pygame.locals import * +import math + +class Sprite: + """The object used for Sprites. + + <pre>Sprite(ishape,pos)</pre> + + <dl> + <dt>ishape <dd>an image, or an image, rectstyle. The rectstyle will + describe the shape of the image, used for collision + detection. + <dt>pos <dd>initial (x,y) position of the Sprite. + </dl> + + <strong>Attributes</strong> + <dl> + <dt>rect <dd>the current position of the Sprite + <dt>_rect <dd>the previous position of the Sprite + <dt>groups <dd>the groups the Sprite is in + <dt>agroups <dd>the groups the Sprite can hit in a collision + <dt>hit <dd>the handler for hits -- hit(g,s,a) + <dt>loop <dd>the loop handler, called once a frame + </dl> + """ + def __init__(self,ishape,pos): + if not isinstance(ishape, tuple): + ishape = ishape,None + image,shape = ishape + if shape == None: + shape = pygame.Rect(0,0,image.get_width(),image.get_height()) + if isinstance(shape, tuple): shape = pygame.Rect(shape) + self.image = image + self._image = self.image + self.shape = shape + self.rect = pygame.Rect(pos[0],pos[1],shape.w,shape.h) + self._rect = pygame.Rect(self.rect) + self.irect = pygame.Rect(pos[0]-self.shape.x,pos[1]-self.shape.y, + image.get_width(),image.get_height()) + self._irect = pygame.Rect(self.irect) + self.groups = 0 + self.agroups = 0 + self.updated = 1 + + def setimage(self,ishape): + """Set the image of the Sprite. + + <pre>Sprite.setimage(ishape)</pre> + + <dl> + <dt>ishape <dd>an image, or an image, rectstyle. The rectstyle will + describe the shape of the image, used for collision detection. + </dl> + """ + if not isinstance(ishape, tuple): + ishape = ishape,None + image,shape = ishape + if shape == None: + shape = pygame.Rect(0,0,image.get_width(),image.get_height()) + if isinstance(shape, tuple): + shape = pygame.Rect(shape) + self.image = image + self.shape = shape + self.rect.w,self.rect.h = shape.w,shape.h + self.irect.w,self.irect.h = image.get_width(),image.get_height() + self.updated = 1 + + +class Tile: + """Tile Object used by TileCollide. + + <pre>Tile(image=None)</pre> + <dl> + <dt>image <dd>an image for the Tile. + </dl> + + <strong>Attributes</strong> + <dl> + <dt>agroups <dd>the groups the Tile can hit in a collision + <dt>hit <dd>the handler for hits -- hit(g,t,a) + </dl> + """ + def __init__(self,image=None): + self.image = image + self.agroups = 0 + + def __setattr__(self,k,v): + if k == 'image' and v != None: + self.image_h = v.get_height() + self.image_w = v.get_width() + self.__dict__[k] = v + +class _Sprites(list): + def __init__(self): + list.__init__(self) + self.removed = [] + + def append(self,v): + list.append(self,v) + v.updated = 1 + + def remove(self,v): + list.remove(self,v) + v.updated = 1 + self.removed.append(v) + +class Vid: + """An engine for rendering Sprites and Tiles. + + <pre>Vid()</pre> + + <strong>Attributes</strong> + <dl> + <dt>sprites <dd>a list of the Sprites to be displayed. You may append and + remove Sprites from it. + <dt>images <dd>a dict for images to be put in. + <dt>size <dd>the width, height in Tiles of the layers. Do not modify. + <dt>view <dd>a pygame.Rect of the viewed area. You may change .x, .y, + etc to move the viewed area around. + <dt>bounds <dd>a pygame.Rect (set to None by default) that sets the bounds + of the viewable area. Useful for setting certain borders + as not viewable. + <dt>tlayer <dd>the foreground tiles layer + <dt>clayer <dd>the code layer (optional) + <dt>blayer <dd>the background tiles layer (optional) + <dt>groups <dd>a hash of group names to group values (32 groups max, as a tile/sprites + membership in a group is determined by the bits in an integer) + </dl> + """ + + def __init__(self): + self.tiles = [None for x in xrange(0,256)] + self.sprites = _Sprites() + self.images = {} #just a store for images. + self.layers = None + self.size = None + self.view = pygame.Rect(0,0,0,0) + self._view = pygame.Rect(self.view) + self.bounds = None + self.updates = [] + self.groups = {} + + + def resize(self,size,bg=0): + """Resize the layers. + + <pre>Vid.resize(size,bg=0)</pre> + + <dl> + <dt>size <dd>w,h in Tiles of the layers + <dt>bg <dd>set to 1 if you wish to use both a foreground layer and a + background layer + </dl> + """ + self.size = size + w,h = size + self.layers = [[[0 for x in xrange(0,w)] for y in xrange(0,h)] + for z in xrange(0,4)] + self.tlayer = self.layers[0] + self.blayer = self.layers[1] + if not bg: self.blayer = None + self.clayer = self.layers[2] + self.alayer = self.layers[3] + + self.view.x, self.view.y = 0,0 + self._view.x, self.view.y = 0,0 + self.bounds = None + + self.updates = [] + + def set(self,pos,v): + """Set a tile in the foreground to a value. + + <p>Use this method to set tiles in the foreground, as it will make + sure the screen is updated with the change. Directly changing + the tlayer will not guarantee updates unless you are using .paint() + </p> + + <pre>Vid.set(pos,v)</pre> + + <dl> + <dt>pos <dd>(x,y) of tile + <dt>v <dd>value + </dl> + """ + if self.tlayer[pos[1]][pos[0]] == v: return + self.tlayer[pos[1]][pos[0]] = v + self.alayer[pos[1]][pos[0]] = 1 + self.updates.append(pos) + + def get(self,pos): + """Get the tlayer at pos. + + <pre>Vid.get(pos): return value</pre> + + <dl> + <dt>pos <dd>(x,y) of tile + </dl> + """ + return self.tlayer[pos[1]][pos[0]] + + def paint(self,s): + """Paint the screen. + + <pre>Vid.paint(screen): return [updates]</pre> + + <dl> + <dt>screen <dd>a pygame.Surface to paint to + </dl> + + <p>returns the updated portion of the screen (all of it)</p> + """ + return [] + + def update(self,s): + """Update the screen. + + <pre>Vid.update(screen): return [updates]</pre> + + <dl> + <dt>screen <dd>a pygame.Rect to update + </dl> + + <p>returns a list of updated rectangles.</p> + """ + self.updates = [] + return [] + + def tga_load_level(self,fname,bg=0): + """Load a TGA level. + + <pre>Vid.tga_load_level(fname,bg=0)</pre> + + <dl> + <dt>g <dd>a Tilevid instance + <dt>fname <dd>tga image to load + <dt>bg <dd>set to 1 if you wish to load the background layer + </dl> + """ + if type(fname) == str: img = pygame.image.load(fname) + else: img = fname + w,h = img.get_width(),img.get_height() + self.resize((w,h),bg) + for y in range(0,h): + for x in range(0,w): + t,b,c,_a = img.get_at((x,y)) + self.tlayer[y][x] = t + if bg: self.blayer[y][x] = b + self.clayer[y][x] = c + + def tga_save_level(self,fname): + """Save a TGA level. + + <pre>Vid.tga_save_level(fname)</pre> + + <dl> + <dt>fname <dd>tga image to save to + </dl> + """ + w,h = self.size + img = pygame.Surface((w,h),SWSURFACE,32) + img.fill((0,0,0,0)) + for y in range(0,h): + for x in range(0,w): + t = self.tlayer[y][x] + b = 0 + if self.blayer: + b = self.blayer[y][x] + c = self.clayer[y][x] + _a = 0 + img.set_at((x,y),(t,b,c,_a)) + pygame.image.save(img,fname) + + + + def tga_load_tiles(self,fname,size,tdata={}): + """Load a TGA tileset. + + <pre>Vid.tga_load_tiles(fname,size,tdata={})</pre> + + <dl> + <dt>g <dd>a Tilevid instance + <dt>fname <dd>tga image to load + <dt>size <dd>(w,h) size of tiles in pixels + <dt>tdata <dd>tile data, a dict of tile:(agroups, hit handler, config) + </dl> + """ + TW,TH = size + if type(fname) == str: img = pygame.image.load(fname).convert_alpha() + else: img = fname + w,h = img.get_width(),img.get_height() + + n = 0 + for y in range(0,h,TH): + for x in range(0,w,TW): + i = img.subsurface((x,y,TW,TH)) + tile = Tile(i) + self.tiles[n] = tile + if n in tdata: + agroups,hit,config = tdata[n] + tile.agroups = self.string2groups(agroups) + tile.hit = hit + tile.config = config + n += 1 + + + def load_images(self,idata): + """Load images. + + <pre>Vid.load_images(idata)</pre> + + <dl> + <dt>idata <dd>a list of (name, fname, shape) + </dl> + """ + for name,fname,shape in idata: + self.images[name] = pygame.image.load(fname).convert_alpha(),shape + + def run_codes(self,cdata,rect): + """Run codes. + + <pre>Vid.run_codes(cdata,rect)</pre> + + <dl> + <dt>cdata <dd>a dict of code:(handler function, value) + <dt>rect <dd>a tile rect of the parts of the layer that should have + their codes run + </dl> + """ + tw,th = self.tiles[0].image.get_width(),self.tiles[0].image.get_height() + + x1,y1,w,h = rect + clayer = self.clayer + t = Tile() + for y in range(y1,y1+h): + for x in range(x1,x1+w): + n = clayer[y][x] + if n in cdata: + fnc,value = cdata[n] + t.tx,t.ty = x,y + t.rect = pygame.Rect(x*tw,y*th,tw,th) + fnc(self,t,value) + + + def string2groups(self,str): + """Convert a string to groups. + + <pre>Vid.string2groups(str): return groups</pre> + """ + if str == None: return 0 + return self.list2groups(str.split(",")) + + def list2groups(self,igroups): + """Convert a list to groups. + <pre>Vid.list2groups(igroups): return groups</pre> + """ + for s in igroups: + if not s in self.groups: + self.groups[s] = 2**len(self.groups) + v = 0 + for s,n in self.groups.items(): + if s in igroups: v|=n + return v + + def groups2list(self,groups): + """Convert a groups to a list. + <pre>Vid.groups2list(groups): return list</pre> + """ + v = [] + for s,n in self.groups.items(): + if (n&groups)!=0: v.append(s) + return v + + def hit(self,x,y,t,s): + tiles = self.tiles + tw,th = tiles[0].image.get_width(),tiles[0].image.get_height() + t.tx = x + t.ty = y + t.rect = Rect(x*tw,y*th,tw,th) + t._rect = t.rect + if hasattr(t,'hit'): + t.hit(self,t,s) + + def loop(self): + """Update and hit testing loop. Run this once per frame. + <pre>Vid.loop()</pre> + """ + self.loop_sprites() #sprites may move + self.loop_tilehits() #sprites move + self.loop_spritehits() #no sprites should move + for s in self.sprites: + s._rect = pygame.Rect(s.rect) + + def loop_sprites(self): + as_ = self.sprites[:] + for s in as_: + if hasattr(s,'loop'): + s.loop(self,s) + + def loop_tilehits(self): + tiles = self.tiles + tw,th = tiles[0].image.get_width(),tiles[0].image.get_height() + + layer = self.layers[0] + + as_ = self.sprites[:] + for s in as_: + self._tilehits(s) + + def _tilehits(self,s): + tiles = self.tiles + tw,th = tiles[0].image.get_width(),tiles[0].image.get_height() + layer = self.layers[0] + + for _z in (0,): + if s.groups != 0: + + _rect = s._rect + rect = s.rect + + _rectx = _rect.x + _recty = _rect.y + _rectw = _rect.w + _recth = _rect.h + + rectx = rect.x + recty = rect.y + rectw = rect.w + recth = rect.h + + rect.y = _rect.y + rect.h = _rect.h + + hits = [] + ct,cb,cl,cr = rect.top,rect.bottom,rect.left,rect.right + #nasty ol loops + y = ct/th*th + while y < cb: + x = cl/tw*tw + yy = y/th + while x < cr: + xx = x/tw + t = tiles[layer[yy][xx]] + if (s.groups & t.agroups)!=0: + #self.hit(xx,yy,t,s) + d = math.hypot(rect.centerx-(xx*tw+tw/2), + rect.centery-(yy*th+th/2)) + hits.append((d,t,xx,yy)) + + x += tw + y += th + + hits.sort() + #if len(hits) > 0: print self.frame,hits + for d,t,xx,yy in hits: + self.hit(xx,yy,t,s) + + #switching directions... + _rect.x = rect.x + _rect.w = rect.w + rect.y = recty + rect.h = recth + + hits = [] + ct,cb,cl,cr = rect.top,rect.bottom,rect.left,rect.right + #nasty ol loops + y = ct/th*th + while y < cb: + x = cl/tw*tw + yy = y/th + while x < cr: + xx = x/tw + t = tiles[layer[yy][xx]] + if (s.groups & t.agroups)!=0: + d = math.hypot(rect.centerx-(xx*tw+tw/2), + rect.centery-(yy*th+th/2)) + hits.append((d,t,xx,yy)) + #self.hit(xx,yy,t,s) + x += tw + y += th + + hits.sort() + #if len(hits) > 0: print self.frame,hits + for d,t,xx,yy in hits: + self.hit(xx,yy,t,s) + + #done with loops + _rect.x = _rectx + _rect.y = _recty + + + def loop_spritehits(self): + as_ = self.sprites[:] + + groups = {} + for n in range(0,31): + groups[1<<n] = [] + for s in as_: + g = s.groups + n = 1 + while g: + if (g&1)!=0: groups[n].append(s) + g >>= 1 + n <<= 1 + + for s in as_: + if s.agroups!=0: + rect1,rect2 = s.rect,Rect(s.rect) + #if rect1.centerx < 320: rect2.x += 640 + #else: rect2.x -= 640 + g = s.agroups + n = 1 + while g: + if (g&1)!=0: + for b in groups[n]: + if (s != b and (s.agroups & b.groups)!=0 + and s.rect.colliderect(b.rect)): + s.hit(self,s,b) + + g >>= 1 + n <<= 1 + + + def screen_to_tile(self,pos): + """Convert a screen position to a tile position. + <pre>Vid.screen_to_tile(pos): return pos</pre> + """ + return pos + + def tile_to_screen(self,pos): + """Convert a tile position to a screen position. + <pre>Vid.tile_to_screen(pos): return pos</pre> + """ + return pos + +# vim: set filetype=python sts=4 sw=4 noet si : diff --git a/pgu/vid.pyc b/pgu/vid.pyc Binary files differnew file mode 100644 index 0000000..2d59c98 --- /dev/null +++ b/pgu/vid.pyc |