Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/pgu
diff options
context:
space:
mode:
Diffstat (limited to 'pgu')
-rw-r--r--pgu/__init__.py7
-rw-r--r--pgu/__init__.pycbin0 -> 189 bytes
-rw-r--r--pgu/algo.py135
-rw-r--r--pgu/ani.py90
-rw-r--r--pgu/engine.py154
-rw-r--r--pgu/engine.pycbin0 -> 5501 bytes
-rw-r--r--pgu/fonts.py130
-rw-r--r--pgu/gui/__init__.py32
-rw-r--r--pgu/gui/__init__.pycbin0 -> 1856 bytes
-rw-r--r--pgu/gui/app.py237
-rw-r--r--pgu/gui/app.pycbin0 -> 6685 bytes
-rw-r--r--pgu/gui/area.py434
-rw-r--r--pgu/gui/area.pycbin0 -> 13562 bytes
-rw-r--r--pgu/gui/basic.py124
-rw-r--r--pgu/gui/basic.pycbin0 -> 4473 bytes
-rw-r--r--pgu/gui/button.py351
-rw-r--r--pgu/gui/button.pycbin0 -> 11854 bytes
-rw-r--r--pgu/gui/const.py45
-rw-r--r--pgu/gui/const.pycbin0 -> 1133 bytes
-rw-r--r--pgu/gui/container.py455
-rw-r--r--pgu/gui/container.pycbin0 -> 12229 bytes
-rw-r--r--pgu/gui/deprecated.py76
-rw-r--r--pgu/gui/deprecated.pycbin0 -> 3300 bytes
-rw-r--r--pgu/gui/dialog.py157
-rw-r--r--pgu/gui/dialog.pycbin0 -> 4744 bytes
-rw-r--r--pgu/gui/document.py112
-rw-r--r--pgu/gui/document.pycbin0 -> 3731 bytes
-rw-r--r--pgu/gui/form.py79
-rw-r--r--pgu/gui/form.pycbin0 -> 2767 bytes
-rw-r--r--pgu/gui/group.py43
-rw-r--r--pgu/gui/group.pycbin0 -> 1827 bytes
-rw-r--r--pgu/gui/input.py166
-rw-r--r--pgu/gui/input.pycbin0 -> 4938 bytes
-rw-r--r--pgu/gui/keysym.py72
-rw-r--r--pgu/gui/keysym.pycbin0 -> 2638 bytes
-rw-r--r--pgu/gui/layout.py172
-rw-r--r--pgu/gui/layout.pycbin0 -> 5499 bytes
-rw-r--r--pgu/gui/menus.py121
-rw-r--r--pgu/gui/menus.pycbin0 -> 4614 bytes
-rw-r--r--pgu/gui/misc.py43
-rw-r--r--pgu/gui/misc.pycbin0 -> 1797 bytes
-rw-r--r--pgu/gui/pguglobals.py7
-rw-r--r--pgu/gui/pguglobals.pycbin0 -> 134 bytes
-rw-r--r--pgu/gui/select.py191
-rw-r--r--pgu/gui/select.pycbin0 -> 5208 bytes
-rw-r--r--pgu/gui/slider.py279
-rw-r--r--pgu/gui/slider.pycbin0 -> 9981 bytes
-rw-r--r--pgu/gui/style.py41
-rw-r--r--pgu/gui/style.pycbin0 -> 2164 bytes
-rw-r--r--pgu/gui/surface.py142
-rw-r--r--pgu/gui/surface.pycbin0 -> 7067 bytes
-rw-r--r--pgu/gui/table.py331
-rw-r--r--pgu/gui/table.pycbin0 -> 9131 bytes
-rw-r--r--pgu/gui/textarea.py287
-rw-r--r--pgu/gui/textarea.pycbin0 -> 7099 bytes
-rw-r--r--pgu/gui/theme.py486
-rw-r--r--pgu/gui/theme.pycbin0 -> 15188 bytes
-rw-r--r--pgu/gui/widget.py352
-rw-r--r--pgu/gui/widget.pycbin0 -> 11782 bytes
-rw-r--r--pgu/hexvid.py127
-rw-r--r--pgu/hexvid.pycbin0 -> 4090 bytes
-rw-r--r--pgu/high.py154
-rw-r--r--pgu/html.py571
-rw-r--r--pgu/html.pycbin0 -> 22344 bytes
-rw-r--r--pgu/isovid.py182
-rw-r--r--pgu/isovid.pycbin0 -> 6262 bytes
-rw-r--r--pgu/layout.py4
-rw-r--r--pgu/layout.pycbin0 -> 206 bytes
-rw-r--r--pgu/text.py61
-rw-r--r--pgu/text.pycbin0 -> 2535 bytes
-rw-r--r--pgu/tilevid.py195
-rw-r--r--pgu/tilevid.pycbin0 -> 5305 bytes
-rw-r--r--pgu/timer.py68
-rw-r--r--pgu/timer.pycbin0 -> 2173 bytes
-rw-r--r--pgu/vid.py560
-rw-r--r--pgu/vid.pycbin0 -> 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
new file mode 100644
index 0000000..a696dce
--- /dev/null
+++ b/pgu/__init__.pyc
Binary files differ
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
new file mode 100644
index 0000000..36c5d0b
--- /dev/null
+++ b/pgu/engine.pyc
Binary files differ
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
new file mode 100644
index 0000000..2da4bda
--- /dev/null
+++ b/pgu/gui/__init__.pyc
Binary files differ
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
new file mode 100644
index 0000000..3199e66
--- /dev/null
+++ b/pgu/gui/app.pyc
Binary files differ
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
new file mode 100644
index 0000000..f2aa1d1
--- /dev/null
+++ b/pgu/gui/area.pyc
Binary files differ
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
new file mode 100644
index 0000000..80e1e93
--- /dev/null
+++ b/pgu/gui/basic.pyc
Binary files differ
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
new file mode 100644
index 0000000..050236a
--- /dev/null
+++ b/pgu/gui/button.pyc
Binary files differ
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
new file mode 100644
index 0000000..6694c12
--- /dev/null
+++ b/pgu/gui/const.pyc
Binary files differ
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
new file mode 100644
index 0000000..6379fe2
--- /dev/null
+++ b/pgu/gui/container.pyc
Binary files differ
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
new file mode 100644
index 0000000..4e5c7bc
--- /dev/null
+++ b/pgu/gui/deprecated.pyc
Binary files differ
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
new file mode 100644
index 0000000..8837016
--- /dev/null
+++ b/pgu/gui/dialog.pyc
Binary files differ
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
new file mode 100644
index 0000000..1cbdfed
--- /dev/null
+++ b/pgu/gui/document.pyc
Binary files differ
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
new file mode 100644
index 0000000..f9e0e44
--- /dev/null
+++ b/pgu/gui/form.pyc
Binary files differ
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
new file mode 100644
index 0000000..1766fc6
--- /dev/null
+++ b/pgu/gui/group.pyc
Binary files differ
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
new file mode 100644
index 0000000..f6ba8c1
--- /dev/null
+++ b/pgu/gui/input.pyc
Binary files differ
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
new file mode 100644
index 0000000..cc4bf2a
--- /dev/null
+++ b/pgu/gui/keysym.pyc
Binary files differ
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
new file mode 100644
index 0000000..4e6c10f
--- /dev/null
+++ b/pgu/gui/layout.pyc
Binary files differ
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
new file mode 100644
index 0000000..911de1b
--- /dev/null
+++ b/pgu/gui/menus.pyc
Binary files differ
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
new file mode 100644
index 0000000..fcc796f
--- /dev/null
+++ b/pgu/gui/misc.pyc
Binary files differ
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
new file mode 100644
index 0000000..cd5a648
--- /dev/null
+++ b/pgu/gui/pguglobals.pyc
Binary files differ
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
new file mode 100644
index 0000000..152ad9c
--- /dev/null
+++ b/pgu/gui/select.pyc
Binary files differ
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
new file mode 100644
index 0000000..9ccc719
--- /dev/null
+++ b/pgu/gui/slider.pyc
Binary files differ
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
new file mode 100644
index 0000000..ae59cf4
--- /dev/null
+++ b/pgu/gui/style.pyc
Binary files differ
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
new file mode 100644
index 0000000..13f9f89
--- /dev/null
+++ b/pgu/gui/surface.pyc
Binary files differ
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
new file mode 100644
index 0000000..cc1e5d8
--- /dev/null
+++ b/pgu/gui/table.pyc
Binary files differ
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
new file mode 100644
index 0000000..8c94884
--- /dev/null
+++ b/pgu/gui/textarea.pyc
Binary files differ
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
new file mode 100644
index 0000000..ca21e90
--- /dev/null
+++ b/pgu/gui/theme.pyc
Binary files differ
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
new file mode 100644
index 0000000..b053a6c
--- /dev/null
+++ b/pgu/gui/widget.pyc
Binary files differ
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
new file mode 100644
index 0000000..e29026f
--- /dev/null
+++ b/pgu/hexvid.pyc
Binary files differ
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)," ") #&nbsp;
+ #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
new file mode 100644
index 0000000..6a1f3f8
--- /dev/null
+++ b/pgu/html.pyc
Binary files differ
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
new file mode 100644
index 0000000..d4f84bc
--- /dev/null
+++ b/pgu/isovid.pyc
Binary files differ
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
new file mode 100644
index 0000000..fbb09e4
--- /dev/null
+++ b/pgu/layout.pyc
Binary files differ
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
new file mode 100644
index 0000000..90037e2
--- /dev/null
+++ b/pgu/text.pyc
Binary files differ
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
new file mode 100644
index 0000000..98c5dc4
--- /dev/null
+++ b/pgu/tilevid.pyc
Binary files differ
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
new file mode 100644
index 0000000..3cf833d
--- /dev/null
+++ b/pgu/timer.pyc
Binary files differ
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
new file mode 100644
index 0000000..2d59c98
--- /dev/null
+++ b/pgu/vid.pyc
Binary files differ