astar(start,end,layer,dist): return [list of positions]
+
+
+
start
start position
+
end
end position
+
layer
a grid where zero cells are open and non-zero cells are walls
+
dist
a distance function dist(a,b) - manhattan distance is used by default
+
+
+
returns a list of positions from start to end
+ """
+
+ 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
+
+ if layer[start[1]][start[0]]:
+ return [] #start is blocked
+ if layer[end[1]][end[0]]:
+ return [] #end is blocked
+
+ opens = []
+ open = {}
+ closed = {}
+ cur = node(None, start, end, dist)
+ 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)]:
+ pos = cur.pos[0]+dx,cur.pos[1]+dy
+ # Check if the point lies in the grid
+ if (pos[0] < 0 or pos[1] < 0 or
+ pos[0] >= w or pos[1] >= h or
+ layer[pos[0]][pos[1]]):
+ 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, dist)
+ 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
+
+
getline(a,b): return [list of points]
+
+
+
a
starting point
+
b
ending point
+
+
+
returns a list of points from a to b
+ """
+
+ 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/src/pgu/ani.py b/src/pgu/ani.py
new file mode 100644
index 0000000..c33d380
--- /dev/null
+++ b/src/pgu/ani.py
@@ -0,0 +1,90 @@
+"""animation loading and manipulating functions.
+
+
please note that this file is alpha, and is subject to modification in
+future versions of pgu!
+"""
+
+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
+
+
ani_load(tv,name,image,size,shape,parts)
+
+
+
tv
vid to load into
+
name
prefix name to give the images
+
image
image to load anis from
+
size
w,h size of image
+
shape
shape of image (usually a subset of 0,0,w,h) used for collision detection
+
parts
list of parts to divide the animation into
+ for example parts = [4,5] would yield 4 animations 5 frames long, 20 total
+ for example parts = [a,b,c] would yield ... images['name.a.b.c'] ..., a*b*c total
+
+
+ """
+ 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
+
+
image_rotate(tv,name,image,shape,angles,diff=0)
+
+
+
tv
vid to load into
+
name
prefix name to give the images
+
image
image to load anis from
+
shape
shape fimage (usually a subset of 0,0,w,h) used for collision detection
+
angles
a list of angles to render in degrees
+
diff
a number to add to the angles, to correct for source image not actually being at 0 degrees
+
+ """
+ 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/src/pgu/engine.py b/src/pgu/engine.py
new file mode 100644
index 0000000..76be583
--- /dev/null
+++ b/src/pgu/engine.py
@@ -0,0 +1,154 @@
+"""a state engine.
+"""
+import pygame
+from pygame.locals import *
+
+class State:
+ """Template Class -- for a state.
+
+
State(game,value...)
+
+
+
game
The state engine.
+
value
I usually pass in a custom value to a state
+
+
+
For all of the template methods, they should return None unless they return
+ a new State to switch the engine to.
+ """
+ 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.
+
+
State.init()
+ """
+ return
+ def paint(self,screen):
+ """Template Method - Paint the screen. Called once after the state is selected.
+
+
State is responsible for calling pygame.display.flip() or whatever.
+
+
State.paint(screen)
+ """
+ return
+
+ def repaint(self):
+ """Template Method - Request a repaint of this state.
+
+
+ """
+ return
+
+class Quit(State):
+ """A state to quit the state engine.
+
+
Quit(game,value)
+ """
+
+ 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).
+
+
Game.run(state,screen=None)
+
+
+
game
a state engine
+
screen
the screen
+
+ """
+ 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.
+
+
Game.init()
+ """
+ return
+
+ def tick(self):
+ """Template Method - called once per frame, usually for timer purposes.
+
+
Game.tick()
+ """
+ pygame.time.wait(10)
+
+ def event(self,e):
+ """Template Method - called with each event, so the engine can capture special events.
+
+
Game.event(e): return captured
+
+
return a True value if the event is captured and does not need to be passed onto the current
+ state
+ """
+ if e.type is QUIT:
+ self.state = Quit(self)
+ return 1
+
+# vim: set filetype=python sts=4 sw=4 noet si :
diff --git a/src/pgu/fonts.py b/src/pgu/fonts.py
new file mode 100644
index 0000000..ab6f73d
--- /dev/null
+++ b/src/pgu/fonts.py
@@ -0,0 +1,130 @@
+"""Some handy font-like objects.
+
+
please note that this file is alpha, and is subject to modification in
+future versions of pgu!
+"""
+
+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
+
+
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.
+ """
+ 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.
+
+
List.add(label,image=None,value=None)
+
+
+
label
a label for the item
+
image
an image for the item
+
value
a value for the item
+
+ """
+
+ def remove(self,value):
+ """Remove an item from the list.
+
+
List.remove(value)
+
+
+
value
a value of an item to remove from the list
+
+ """
+
+ 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/src/pgu/gui/basic.py b/src/pgu/gui/basic.py
new file mode 100644
index 0000000..3800440
--- /dev/null
+++ b/src/pgu/gui/basic.py
@@ -0,0 +1,136 @@
+"""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 (is_color(desc)):
+ # 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)
+
+# Determines if the given object is a pygame-compatible color or not
+def is_color(col):
+ # In every version of pygame (up to 1.8.1 so far) will interpret
+ # a tuple as a color.
+ if (type(col) == tuple):
+ return col
+ if (hasattr(pygame, "Color") and type(pygame.Color) == type):
+ # This is a recent version of pygame that uses a proper type
+ # instance for colors.
+ return (isinstance(col, pygame.Color))
+ # Otherwise, this version of pygame only supports tuple colors
+ return False
+
+class Spacer(widget.Widget):
+ """A invisible space.
+
+
+"""
+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/src/pgu/gui/container.py b/src/pgu/gui/container.py
new file mode 100644
index 0000000..73a8e68
--- /dev/null
+++ b/src/pgu/gui/container.py
@@ -0,0 +1,414 @@
+"""
+"""
+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.
+
+
Container()
+ """
+ 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)
+ #if (hasattr(w, "_container_bkgr")):
+ # 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') or
+# w._container_bkgr.get_size() != sub.get_size()):
+# #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:
+ 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
+
+ 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.
+
+
Container.remove(w)
+ """
+ self.blur(w)
+ self.widgets.remove(w)
+ #self.repaint()
+ self.chsize()
+
+ def add(self,w,x,y):
+ """Add a widget to the container.
+
+
Container.add(w,x,y)
+
+
+
x, y
position of the widget
+
+ """
+ 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):
+ if (not w):
+ w = self
+
+ if (x != None):
+ # The position is relative to this container
+ rect = self.get_abs_rect()
+ pos = (rect.x + x, rect.y + y)
+ else:
+ pos = None
+ # Have the application open the window
+ pguglobals.app.open(w, pos)
+
+ 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
+
+ # Returns the widget with the given name
+ def find(self, name):
+ for w in self.widgets:
+ if (w.name == name):
+ return w
+ elif (isinstance(w, Container)):
+ tmp = w.find(name)
+ if (tmp): return tmp
+ return None
+
diff --git a/src/pgu/gui/deprecated.py b/src/pgu/gui/deprecated.py
new file mode 100644
index 0000000..8d53515
--- /dev/null
+++ b/src/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/src/pgu/gui/dialog.py b/src/pgu/gui/dialog.py
new file mode 100644
index 0000000..0d30b34
--- /dev/null
+++ b/src/pgu/gui/dialog.py
@@ -0,0 +1,168 @@
+"""
+"""
+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.
+
+
+ """
+ self.layout.add(e)
+
+ def br(self,height):
+ """Add a line break.
+
+
Document.br(height)
+
+
+
height
height of line break
+
+ """
+ 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/src/pgu/gui/form.py b/src/pgu/gui/form.py
new file mode 100644
index 0000000..9a874f9
--- /dev/null
+++ b/src/pgu/gui/form.py
@@ -0,0 +1,79 @@
+"""
+"""
+import widget
+
+class Form(widget.Widget):
+ """A form that automatically will contain all named widgets.
+
+
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.
+
+
Form()
+
+ Example
+
+ 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
+
+ """
+
+ 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.
+
+
Form.results(): return dict
+ """
+ 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.
+
+
When the value changes, an gui.CHANGE event is sent.
+ Although note, that when the value is a list, it may have to be sent by hand via
+ g.send(gui.CHANGE)
+ """
+
+ 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.
+
+
Group.add(w)
+ """
+ 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/src/pgu/gui/input.py b/src/pgu/gui/input.py
new file mode 100644
index 0000000..3f3f653
--- /dev/null
+++ b/src/pgu/gui/input.py
@@ -0,0 +1,169 @@
+"""
+"""
+import pygame
+from pygame.locals import *
+
+from const import *
+import widget
+
+class Input(widget.Widget):
+ """A single line text input.
+
+
Input(value="",size=20)
+
+
+
value
initial text
+
size
size for the text box, in characters
+
+
+ Example
+
+ w = Input(value="Cuzco the Goat",size=20)
+
+ w = Input("Marbles")
+
+
+ """
+ def __init__(self,value="",size=20,customFont = None,**params):
+ params.setdefault('cls','input')
+ widget.Widget.__init__(self,**params)
+ self.value = value
+ self.pos = len(str(value))
+ self.vpos = 0
+ if customFont != None:
+ self.font = customFont
+ else:
+ 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.
+
+
Password(value="",size=20)
+
+
+
value
initial text
+
size
size for the text box, in characters
+
+
+ Example
+
+ w = Password(value="password",size=20)
+
+ w = Password("53[r3+")
+
+
+ """
+
+ 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/src/pgu/gui/keysym.py b/src/pgu/gui/keysym.py
new file mode 100644
index 0000000..cc24089
--- /dev/null
+++ b/src/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.
+
+
This widget records the keysym of the key pressed while this widget is in focus.
+
+ Example
+
+ w = Input(value=pygame.locals.K_g)
+
+ w = Input(pygame.locals.K_g)
+
+ w = Input()
+
+
+ """
+
+ 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/src/pgu/gui/layout.py b/src/pgu/gui/layout.py
new file mode 100644
index 0000000..01fe0cb
--- /dev/null
+++ b/src/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/src/pgu/gui/menus.py b/src/pgu/gui/menus.py
new file mode 100644
index 0000000..b850b6c
--- /dev/null
+++ b/src/pgu/gui/menus.py
@@ -0,0 +1,119 @@
+"""
+"""
+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._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.
+
+
Menus(data)
+
+
+
data
Menu data, a list of (path,fnc,value), see example below
+
+
+ Example
+
+ 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/src/pgu/gui/misc.py b/src/pgu/gui/misc.py
new file mode 100644
index 0000000..afb10c5
--- /dev/null
+++ b/src/pgu/gui/misc.py
@@ -0,0 +1,43 @@
+from const import *
+import widget
+import pguglobals
+
+class ProgressBar(widget.Widget):
+ """A progress bar.
+
+
ProgressBar(value,min,max)
+
+
+
value
starting value
+
min
minimum value rendered on the screen (usually 0)
+
max
maximum value
+
+
+ Example
+
+ w = gui.ProgressBar(0,0,100)
+ w.value = 25
+
+ """
+
+ 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/src/pgu/gui/pguglobals.py b/src/pgu/gui/pguglobals.py
new file mode 100644
index 0000000..dc0e673
--- /dev/null
+++ b/src/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/src/pgu/gui/select.py b/src/pgu/gui/select.py
new file mode 100644
index 0000000..0ee39e9
--- /dev/null
+++ b/src/pgu/gui/select.py
@@ -0,0 +1,180 @@
+"""
+"""
+
+import traceback
+
+from const import *
+from button import Button
+from basic import Label, Image
+from table import Table
+
+class Select(Table):
+ """A select input.
+
+
Select(value=None)
+
+
+
value
initial value
+
+
+ Example
+
+ 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
+
+
+ """
+
+ # The drop-down arrow button for the selection widget
+ top_arrow = None
+ # A button displaying the currently selected item
+ top_selection = None
+ # The first option added to the selector
+ firstOption = None
+ # The PGU table of options
+ options = None
+
+ def __init__(self,value=None,**params):
+ params.setdefault('cls','select')
+ Table.__init__(self,**params)
+
+ label = Label(" ",cls=self.cls+".option.label")
+ self.top_selected = Button(label, cls=self.cls+".selected")
+ Table.add(self,self.top_selected) #,hexpand=1,vexpand=1)#,0,0)
+
+ self.top_arrow = Button(Image(self.style.arrow), cls=self.cls+".arrow")
+ Table.add(self,self.top_arrow) #,hexpand=1,vexpand=1) #,1,0)
+
+ self.options = Table(cls=self.cls+".options")
+ self.options.connect(BLUR,self._close,None)
+ self.options.name = "pulldown-table"
+
+ 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.resize(self,width,height)
+
+ self.options.style.width = w
+ #HACK: sort of, but not a big one..
+ self.options.resize()
+
+ return w,h
+
+ def _open(self,value):
+ opts = self.options
+
+ opts.rect.w, opts.rect.h = opts.resize()
+
+# y = self.rect.y
+# c = self.container
+# while hasattr(c, 'container'):
+# y += c.rect.y
+# if (not c.container):
+# break
+# c = c.container
+
+# if y + self.rect.h + opts.rect.h <= c.rect.h: #down
+# dy = self.rect.y + self.rect.h
+# else: #up
+# dy = self.rect.y - self.rect.h
+
+ opts.rect.w, opts.rect.h = opts.resize()
+
+ # TODO - make sure there is enough space to open down
+ # ...
+ yp = self.rect.bottom-1
+
+ self.container.open(opts, self.rect.x, yp)
+ self.firstOption.focus()
+
+ # TODO - this is a hack
+ for opt in self.options.widgets:
+ opt.repaint()
+
+ def _close(self,value):
+ self.options.close()
+ self.top_selected.focus()
+
+ 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 = 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.
+
+
Select.add(widget,value=None)
+
+
+
widget
Widget or string to represent the item
+
value
value for this item
+
+
+ Example
+
+ 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
+
+ """
+
+ if type(w) == str: w = Label(w,cls=self.cls+".option.label")
+
+ w.style.align = -1
+ btn = Button(w,cls=self.cls+".option")
+ btn.connect(CLICK,self._setvalue,w)
+
+ self.options.tr()
+ self.options.add(btn)
+
+ if (not self.firstOption):
+ self.firstOption = btn
+
+ 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/src/pgu/gui/slider.py b/src/pgu/gui/slider.py
new file mode 100644
index 0000000..f4fa623
--- /dev/null
+++ b/src/pgu/gui/slider.py
@@ -0,0 +1,279 @@
+"""All sliders and scroll bar widgets have the same parameters.
+
+
Slider(value,min,max,size)
+
+
value
initial value
+
min
minimum value
+
max
maximum value
+
size
size of bar in pixels
+
+"""
+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.
+
+
This object is used mainly as a dictionary, accessed via widget.style.attr, as opposed to
+ widget.style['attr']. It automatically grabs information from the theme via value = theme.get(widget.cls,widget.pcls,attr).
+
+ """
+ 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/src/pgu/gui/surface.py b/src/pgu/gui/surface.py
new file mode 100644
index 0000000..9bd064d
--- /dev/null
+++ b/src/pgu/gui/surface.py
@@ -0,0 +1,143 @@
+"""
+"""
+import pygame
+
+def subsurface(s,r):
+ """Return the subsurface of a surface, with some help, checks.
+
+
subsurface(s,r): return surface
+ """
+ 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.
+
+
If you know HTML, this should all work roughly how you would expect. If you are not
+ familiar with HTML, please read Tables in HTML Documents. Pay attention to TABLE, TR, TD related parts of the document.
+
+
Table()
+
+ Example
+
+ 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())
+
+
+ """
+
+
+ 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.
+
+
See Table.td for an explanation of the parameters.
+ """
+ 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
+ #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) 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/src/pgu/gui/textarea.py b/src/pgu/gui/textarea.py
new file mode 100644
index 0000000..667076a
--- /dev/null
+++ b/src/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.
+
+
+
+ Example
+
+ w = TextArea(value="Cuzco the Goat",size=20)
+
+ w = TextArea("Marbles")
+
+ w = TextArea("Groucho\nHarpo\nChico\nGummo\nZeppo\n\nMarx", 200, 400, 12)
+
+
+ """
+ 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/src/pgu/gui/theme.py b/src/pgu/gui/theme.py
new file mode 100644
index 0000000..283c287
--- /dev/null
+++ b/src/pgu/gui/theme.py
@@ -0,0 +1,485 @@
+# theme.py
+
+"""
+"""
+import os, re
+import pygame
+
+from const import *
+import widget
+import surface
+from basic import parse_color, is_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.
+
+
If you wish to create your own theme, create a class with this interface, and
+ pass it to gui.App via gui.App(theme=MyTheme()).
+
+ Default Theme
+
+
Theme(dirs='default')
+
+
dirs
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.
+
+
+ Example
+
+
+ theme = gui.Theme("default")
+ theme = gui.Theme(["mytheme","mytheme2"])
+
+ """
+ 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.
+
+
Theme.get(cls,pcls,attr): return value
+
+
+
cls
class, for example "checkbox", "button", etc.
+
pcls
pseudo class, for example "hover", "down", etc.
+
attr
attribute, for example "image", "background", "font", "color", etc.
+
+
+
returns the value of the attribute.
+
+
This method is called from [[gui-style]].
+ """
+
+ 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.
+
+
The theme system is given the opportunity to decorate a widget methods at the
+ end of the Widget initializer.
+
+
Theme.decorate(widget,level)
+
+
+
widget
the widget to be decorated
+
level
the amount of decoration to do, False for none, True for normal amount, 'app' for special treatment of App objects.
+
+ """
+
+ 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.
+
+
Theme.render(s,box,r)
+
+
+
s
pygame.Surface
+
box
box data, a value returned from Theme.get, typically a pygame.Surface
+
r
pygame.Rect with the size that the box data should be rendered
+
+
+ """
+
+ if box == 0: return
+
+ if is_color(box):
+ 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)
+
+
+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 is_color(v):
+ s.fill(v)
+ else:
+ self.theme.render(s,v,r)
diff --git a/src/pgu/gui/widget.py b/src/pgu/gui/widget.py
new file mode 100644
index 0000000..f0f4b13
--- /dev/null
+++ b/src/pgu/gui/widget.py
@@ -0,0 +1,377 @@
+"""
+"""
+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.
+
+
Widget(**params)
+
+
A number of optional params may be passed to the Widget initializer.
+
+
+
decorate
defaults to True. If true, will call theme.decorate(self) to allow the theme a chance to decorate the widget.
+
style
a dict of style parameters.
+
x, y, width, height
position and size parameters, passed along to style
+
align, valign
alignment parameters, passed along to style
+
font, color, background
other common parameters that are passed along to style
+
cls
class name as used by Theme
+
name
name of widget as used by Form. If set, will call form.add(self,name) to add the widget to the most recently created Form.
+
focusable
True if this widget can receive focus via Tab, etc. Defaults to True.
+
disabled
True of this widget is disabled. Defaults to False.
+
value
initial value
+
+
+ Example - Creating your own Widget
+
This example shows which methods are template methods.
+
+ 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
+
+ """
+
+ # The name of the widget (or None if not defined)
+ name = None
+
+ 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.
+
+
Widget.focus()
+ """
+ if getattr(self,'container',None) != None:
+ if self.container.myfocus != self: ## by Gal Koren
+ self.container.focus(self)
+
+ def blur(self):
+ """Blur this Widget.
+
+
Widget.blur()
+ """
+ if getattr(self,'container',None) != None: self.container.blur(self)
+
+ def open(self):
+ """Open this Widget as a modal dialog.
+
+
Widget.open()
+ """
+ #if getattr(self,'container',None) != None: self.container.open(self)
+ pguglobals.app.open(self)
+
+ def close(self, w=None):
+ """Close this Widget (if it is a modal dialog.)
+
+
Widget.close()
+ """
+ #if getattr(self,'container',None) != None: self.container.close(self)
+ if (not w):
+ w = self
+ pguglobals.app.close(w)
+
+ def resize(self,width=None,height=None):
+ """Template method - return the size and width of this widget.
+
+
Responsible for also resizing all sub-widgets.
+
+
Widget.resize(width,height): return width,height
+
+
+
width
suggested width
+
height
suggested height
+
+
+
If not overridden, will return self.style.width, self.style.height
+ """
+ return self.style.width, self.style.height
+
+ def chsize(self):
+ """Change the size of this widget.
+
+
Calling this method will cause a resize on all the widgets,
+ including this one.
+
+
Widget.chsize()
+ """
+
+ 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
+
+
Widget.update(s): return list of pygame.Rect(s)
+
+
+
s
pygame.Surface to update
+
+
+
return - a list of the updated areas as pygame.Rect(s).
+ """
+ return
+
+ def repaint(self):
+ """Request a repaint of this Widget.
+
+
Widget.repaint()
+ """
+ if getattr(self,'container',None) != None: self.container.repaint(self)
+ def repaintall(self):
+ """Request a repaint of all Widgets.
+
+
Widget.repaintall()
+ """
+ if getattr(self,'container',None) != None: self.container.repaintall()
+ def reupdate(self):
+ """Request a reupdate of this Widget
+
+
Widget.reupdate()
+ """
+ if getattr(self,'container',None) != None: self.container.reupdate(self)
+ def next(self):
+ """Pass focus to next Widget.
+
+
Widget order determined by the order they were added to their container.
+
+
Widget.next()
+ """
+ if getattr(self,'container',None) != None: self.container.next(self)
+ def previous(self):
+ """Pass focus to previous Widget.
+
+
Widget order determined by the order they were added to their container.
+
+
Widget.previous()
+ """
+
+ 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
+
+
Widget.get_abs_rect(): return pygame.Rect
+ """
+ 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.
+
+
There may be multiple callbacks per event code.
+
+
Object.connect(code,fnc,value)
+
+
+
code
event type [[gui-const]]
+
fnc
callback function
+
*values
values to pass to callback. Please note that callbacks may also have "magicaly" parameters. Such as:
+
+
_event
receive the event
+
_code
receive the event code
+
_widget
receive the sending widget
+
+
+
+ Example
+
+ def onclick(value):
+ print 'click',value
+
+ w = Button("PGU!")
+ w.connect(gui.CLICK,onclick,'PGU Button Clicked')
+
+ """
+ if (not code in self.connects):
+ self.connects[code] = []
+ for cb in self.connects[code]:
+ if (cb.func == func):
+ # Already connected to this callback function
+ return
+ # Wrap the callback function and add it to the list
+ cb = SignalCallback()
+ cb.func = func
+ cb.params = params
+ 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.
+
+
Object.send(code,event=None)
+
+
+
code
event code
+
event
event
+
+ """
+ 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.
+
+
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.)
+
+
+
e
event
+
+ """
+
+ return
+
+ # Returns the top-level widget (usually the Desktop) by following the
+ # chain of 'container' references.
+ def get_toplevel(self):
+ top = self
+ while (getattr(top, "container", None)):
+ top = top.container
+ return top
+
diff --git a/src/pgu/hexvid.py b/src/pgu/hexvid.py
new file mode 100644
index 0000000..2d4156d
--- /dev/null
+++ b/src/pgu/hexvid.py
@@ -0,0 +1,127 @@
+"""Hexagonal tile engine.
+
+
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!
+
+
please note that this file is alpha, and is subject to modification in
+future versions of pgu!
+
+"""
+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/src/pgu/high.py b/src/pgu/high.py
new file mode 100644
index 0000000..e05d22a
--- /dev/null
+++ b/src/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.
+
+
High(fname,limit=10)
+
+
+
fname
filename to store high scores in
+
limit
limit of scores to be recorded, defaults to 10
+
+ """
+ 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.
+
+
You can iterate them:
+
+ for e in myhigh:
+ print e.score,e.name,e.data
+
+
+
+ """
+ self.highs.save()
+
+ def submit(self,score,name,data=None):
+ """Submit a high score to this table.
+
+
_High.submit(score,name,data=None)
+
+
return -- the position in the table that the score attained. None if the score did not attain a position in the table.
+ """
+ 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.
+
+
_High.check(score)
+
+
return -- the position the score will attain, else None
+ """
+ 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.
+
+
+ """
+
+ 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.
+
+
Highs.save()
+ """
+
+ 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/src/pgu/html.py b/src/pgu/html.py
new file mode 100644
index 0000000..817c000
--- /dev/null
+++ b/src/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
+ if not hasattr(self.item,'layout'): return
+ if len(self.item.layout._widgets) == 0: return
+ w = self.item.layout._widgets[-1]
+ if type(w) == tuple:
+ del self.item.layout._widgets[-1]
+
+
+ def start_b(self,attrs): self.font.set_bold(1)
+ def end_b(self): self.font.set_bold(0)
+ def start_i(self,attrs): self.font.set_italic(1)
+ def end_i(self): self.font.set_italic(0)
+ def start_u(self,attrs): self.font.set_underline(1)
+ def end_u(self): self.font.set_underline(0)
+ def start_br(self,attrs): self.do_br(attrs)
+ def do_br(self,attrs): self.item.br(self.font.size(" ")[1])
+ def attrs_to_map(self,attrs):
+ k = None
+ r = {}
+ for k,v in attrs: r[k] = v
+ return r
+
+ def map_to_params(self,r):
+ anum = re.compile("\D")
+
+ params = {'style':{}}
+ style = params['style']
+
+ if 'bgcolor' in r:
+ style['background'] = gui.parse_color(r['bgcolor'])
+ if 'background' in r:
+ style['background'] = self.loader.load_image(r['background'])
+ if 'border' in r: style['border'] = int(r['border'])
+
+ for k in ['width','height','colspan','rowspan','size','min','max']:
+ if k in r: params[k] = int(anum.sub("",r[k]))
+
+ for k in ['name','value']:
+ if k in r: params[k] = r[k]
+
+ if 'class' in r: params['cls'] = r['class']
+
+ if 'align' in r:
+ params['align'] = _amap[r['align']]
+ if 'valign' in r:
+ params['valign'] = _vamap[r['valign']]
+
+ if 'style' in r:
+ for st in r['style'].split(";"):
+ #print st
+ if ":" in st:
+ #print st.split(":")
+ k,v = st.split(":")
+ k = k.replace("-","_")
+ k = k.replace(" ","")
+ v = v.replace(" ","")
+ if k == 'color' or k == 'border_color' or k == 'background':
+ v = gui.parse_color(v)
+ else:
+ v = int(anum.sub("",v))
+ style[k] = v
+ return params
+
+ def map_to_connects(self,e,r):
+ for k,evt in [('onclick',gui.CLICK),('onchange',gui.CHANGE)]: #blah blah blah
+
+ if k in r:
+ #print k,r[k]
+ e.connect(evt,self.myexec,(e,r[k]))
+
+ def start_p(self,attrs):
+ r = self.attrs_to_map(attrs)
+ align = r.get("align","left")
+
+ self.check_p()
+ self.item.block(_amap[align])
+
+ def check_p(self):
+ if len(self.item.layout._widgets) == 0: return
+ if type(self.item.layout._widgets[-1]) == tuple:
+ w,h = self.item.layout._widgets[-1]
+ if w == 0: return
+ self.do_br(None)
+
+ def end_p(self):
+ #print 'end p'
+ self.check_p()
+
+
+ def start_block(self,t,attrs,align=-1):
+ r = self.attrs_to_map(attrs)
+ params = self.map_to_params(r)
+ if 'cls' in params: params['cls'] = t+"."+params['cls']
+ else: params['cls'] = t
+ b = gui.Document(**params)
+ b.style.font = self.item.style.font
+ if 'align' in params:
+ align = params['align']
+ self.item.block(align)
+ self.item.add(b)
+ self.myopen(t,b)
+
+
+
+ def end_block(self,t):
+ self.myclose(t)
+ self.item.block(-1)
+
+ def start_div(self,attrs): self.start_block('div',attrs)
+ def end_div(self): self.end_block('div')
+ def start_center(self,attrs): self.start_block('div',attrs,0)
+ def end_center(self): self.end_block('div')
+
+ def start_h1(self,attrs): self.start_block('h1',attrs)
+ def end_h1(self): self.end_block('h1')
+ def start_h2(self,attrs): self.start_block('h2',attrs)
+ def end_h2(self): self.end_block('h2')
+ def start_h3(self,attrs): self.start_block('h3',attrs)
+ def end_h3(self): self.end_block('h3')
+ def start_h4(self,attrs): self.start_block('h4',attrs)
+ def end_h4(self): self.end_block('h4')
+ def start_h5(self,attrs): self.start_block('h5',attrs)
+ def end_h5(self): self.end_block('h5')
+ def start_h6(self,attrs): self.start_block('h6',attrs)
+ def end_h6(self): self.end_block('h6')
+
+ def start_ul(self,attrs): self.start_block('ul',attrs)
+ def end_ul(self): self.end_block('ul')
+ def start_ol(self,attrs):
+ self.start_block('ol',attrs)
+ self.item.counter = 0
+ def end_ol(self): self.end_block('ol')
+ def start_li(self,attrs):
+ self.myback(['ul','ol'])
+ cur = self.item
+ self.start_block('li',attrs)
+ if hasattr(cur,'counter'):
+ cur.counter += 1
+ self.handle_data("%d. "%cur.counter)
+ else:
+ self.handle_data("- ")
+ #def end_li(self): self.end_block('li') #this isn't needed because of how the parser works
+
+ def start_pre(self,attrs): self.start_block('pre',attrs)
+ def end_pre(self): self.end_block('pre')
+ def start_code(self,attrs): self.start_block('code',attrs)
+ def end_code(self): self.end_block('code')
+
+ def start_table(self,attrs):
+ r = self.attrs_to_map(attrs)
+ params = self.map_to_params(r)
+
+ align = r.get("align","left")
+ self.item.block(_amap[align])
+
+ t = gui.Table(**params)
+ self.item.add(t)
+
+ self.myopen('table',t)
+
+ def start_tr(self,attrs):
+ self.myback('table')
+ self.item.tr()
+
+ def _start_td(self,t,attrs):
+ r = self.attrs_to_map(attrs)
+ params = self.map_to_params(r)
+ if 'cls' in params: params['cls'] = t+"."+params['cls']
+ else: params['cls'] = t
+ b = gui.Document(cls=t)
+
+ self.myback('table')
+ self.item.td(b,**params)
+ self.myopen(t,b)
+
+ self.font = self.item.style.font
+ self.color = self.item.style.color
+
+ def start_td(self,attrs):
+ self._start_td('td',attrs)
+
+ def start_th(self,attrs):
+ self._start_td('th',attrs)
+
+ def end_table(self):
+ self.myclose('table')
+ self.item.block(-1)
+
+ def start_form(self,attrs):
+ r = self.attrs_to_map(attrs)
+ e = self.form = gui.Form()
+ e.groups = {}
+
+ self._locals[r.get('id',None)] = e
+
+ def start_input(self,attrs):
+ r = self.attrs_to_map(attrs)
+ params = self.map_to_params(r) #why bother
+ #params = {}
+
+ type_,name,value = r.get('type','text'),r.get('name',None),r.get('value',None)
+ f = self.form
+ if type_ == 'text':
+ e = gui.Input(**params)
+ self.map_to_connects(e,r)
+ self.item.add(e)
+ elif type_ == 'radio':
+ if name not in f.groups:
+ f.groups[name] = gui.Group(name=name)
+ g = f.groups[name]
+ del params['name']
+ e = gui.Radio(group=g,**params)
+ self.map_to_connects(e,r)
+ self.item.add(e)
+ if 'checked' in r: g.value = value
+ elif type_ == 'checkbox':
+ if name not in f.groups:
+ f.groups[name] = gui.Group(name=name)
+ g = f.groups[name]
+ del params['name']
+ e = gui.Checkbox(group=g,**params)
+ self.map_to_connects(e,r)
+ self.item.add(e)
+ if 'checked' in r: g.value = value
+
+ elif type_ == 'button':
+ e = gui.Button(**params)
+ self.map_to_connects(e,r)
+ self.item.add(e)
+ elif type_ == 'submit':
+ e = gui.Button(**params)
+ self.map_to_connects(e,r)
+ self.item.add(e)
+ elif type_ == 'file':
+ e = gui.Input(**params)
+ self.map_to_connects(e,r)
+ self.item.add(e)
+ b = gui.Button(value='Browse...')
+ self.item.add(b)
+ def _browse(value):
+ d = gui.FileDialog();
+ d.connect(gui.CHANGE,gui.action_setvalue,(d,e))
+ d.open();
+ b.connect(gui.CLICK,_browse,None)
+
+ self._locals[r.get('id',None)] = e
+
+ def start_object(self,attrs):
+ r = self.attrs_to_map(attrs)
+ params = self.map_to_params(r)
+ code = "e = %s(**params)"%r['type']
+ #print code
+ #print params
+ exec(code)
+ #print e
+ #print e.style.width,e.style.height
+ self.map_to_connects(e,r)
+ self.item.add(e)
+
+ self._locals[r.get('id',None)] = e
+
+ def start_select(self,attrs):
+ r = self.attrs_to_map(attrs)
+ params = {}
+
+ name,value = r.get('name',None),r.get('value',None)
+ e = gui.Select(name=name,value=value,**params)
+ self.map_to_connects(e,r)
+ self.item.add(e)
+ self.myopen('select',e)
+
+ def start_option(self,attrs):
+ r = self.attrs_to_map(attrs)
+ params = {} #style = self.map_to_style(r)
+
+ self.myback('select')
+ e = gui.Document(**params)
+ self.item.add(e,value=r.get('value',None))
+ self.myopen('option',e)
+
+
+ def end_select(self):
+ self.myclose('select')
+
+ def start_hr(self,attrs):
+ self.do_hr(attrs)
+ def do_hr(self,attrs):
+ h = self.font.size(" ")[1]/2
+
+ r = self.attrs_to_map(attrs)
+ params = self.map_to_params(r)
+ params['style']['padding'] = h
+ print params
+
+ self.item.block(0)
+ self.item.add(_hr(**params))
+ self.item.block(-1)
+
+ def anchor_begin(self,href,name,type_):
+ pass
+
+ def anchor_end(self):
+ pass
+
+ def start_title(self,attrs): self.myopen('title',_flush())
+ def end_title(self): self.myclose('title')
+
+ def myexec(self,value):
+ w,code = value
+ g = self._globals
+ l = self._locals
+ l['self'] = w
+ exec(code,g,l)
+
+ def handle_image(self,src,alt,ismap,align,width,height):
+ try:
+ w = gui.Image(self.loader.load_image(src))
+ if align != '':
+ self.item.add(w,_amap[align])
+ else:
+ self.item.add(w)
+ except:
+ print 'handle_image: missing %s'%src
+
+ def handle_data(self,txt):
+ if self.type == 'table': return
+ elif self.type in ('pre','code'):
+ txt = txt.replace("\t"," ")
+ ss = txt.split("\n")
+ if ss[-1] == "": del ss[-1]
+ for sentence in ss:
+ img = self.font.render(sentence,1,self.color)
+ w = gui.Image(img)
+ self.item.add(w)
+ self.item.block(-1)
+ return
+
+ txt = re.compile("^[\t\r\n]+").sub("",txt)
+ txt = re.compile("[\t\r\n]+$").sub("",txt)
+
+ tst = re.compile("[\t\r\n]+").sub("",txt)
+ if tst == "": return
+
+ txt = re.compile("\s+").sub(" ",txt)
+ if txt == "": return
+
+ if txt == " ":
+ self.item.space(self.font.size(" "))
+ return
+
+ for word in txt.split(" "):
+ word = word.replace(chr(160)," ") #
+ #print self.item.cls
+ w = gui.Image(self.font.render(word,1,self.color))
+ self.item.add(w)
+ self.item.space(self.font.size(" "))
+
+
+class HTML(gui.Document):
+ """a gui HTML object
+
+
HTML(data,globals=None,locals=None)
+
+
+
data
html data
+
globals
global variables (for scripting)
+
locals
local variables (for scripting)
+
loader
the resource loader
+
+
+
you may access html elements that have an id via widget[id]
+ """
+ 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
+
+
render(font,rect,text,aa,color,bgcolor=(0,0,0,0))
+ """
+ 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/src/pgu/isovid.py b/src/pgu/isovid.py
new file mode 100644
index 0000000..d5048ec
--- /dev/null
+++ b/src/pgu/isovid.py
@@ -0,0 +1,182 @@
+"""Isometric tile engine.
+
+
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!
+
+
please note that this file is alpha, and is subject to modification in
+future versions of pgu!
+
+"""
+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/src/pgu/layout.py b/src/pgu/layout.py
new file mode 100644
index 0000000..75b6c9a
--- /dev/null
+++ b/src/pgu/layout.py
@@ -0,0 +1,4 @@
+print 'pgu.layout','Scheduled to be deprecated.'
+
+from pgu.gui.layout import *
+
diff --git a/src/pgu/text.py b/src/pgu/text.py
new file mode 100644
index 0000000..1010a87
--- /dev/null
+++ b/src/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
+
+
write(s,font,pos,color,text,border=1)
+ """
+ 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
+
+
writec(s,font,color,text,border=1)
+ """
+ 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
+
+
writepre(s,font,rect,color,text)
+ """
+ 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
+
+
writewrap(s,font,rect,color,text)
+ """
+ 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/src/pgu/tilevid.py b/src/pgu/tilevid.py
new file mode 100644
index 0000000..00f730d
--- /dev/null
+++ b/src/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=0 and y=0 and xTimer(fps)
+ """
+
+ 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.
+
+
Timer.tick()
+ """
+ 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.
+
Speedometer()
+
+ Attributes
+
+
fps
always set to the current FPS
+
+ """
+ def __init__(self):
+ self.frames = 0
+ self.st = pygame.time.get_ticks()
+ self.fps = 0
+
+ def tick(self):
+ """ Call this once per frame.
+
+
Speedometer.tick()
+ """
+ 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/src/pgu/vid.py b/src/pgu/vid.py
new file mode 100644
index 0000000..6890e5d
--- /dev/null
+++ b/src/pgu/vid.py
@@ -0,0 +1,560 @@
+"""Sprite and tile engine.
+
+
[[tilevid]], [[isovid]], [[hexvid]] are all subclasses of
+this interface.
+
+
Includes support for:
+
+
+
Foreground Tiles
+
Background Tiles
+
Sprites
+
Sprite-Sprite Collision handling
+
Sprite-Tile Collision handling
+
Scrolling
+
Loading from PGU tile and sprite formats (optional)
+
Set rate FPS (optional)
+
+
+
This code was previously known as the King James Version (named after the
+Bible of the same name for historical reasons.)
+"""
+
+import pygame
+from pygame.rect import Rect
+from pygame.locals import *
+import math
+
+class Sprite:
+ """The object used for Sprites.
+
+
Sprite(ishape,pos)
+
+
+
ishape
an image, or an image, rectstyle. The rectstyle will
+ describe the shape of the image, used for collision
+ detection.
+
a list of the Sprites to be displayed. You may append and
+ remove Sprites from it.
+
images
a dict for images to be put in.
+
size
the width, height in Tiles of the layers. Do not modify.
+
view
a pygame.Rect of the viewed area. You may change .x, .y,
+ etc to move the viewed area around.
+
bounds
a pygame.Rect (set to None by default) that sets the bounds
+ of the viewable area. Useful for setting certain borders
+ as not viewable.
+
tlayer
the foreground tiles layer
+
clayer
the code layer (optional)
+
blayer
the background tiles layer (optional)
+
groups
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)
+
+ """
+
+ 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.
+
+
Vid.resize(size,bg=0)
+
+
+
size
w,h in Tiles of the layers
+
bg
set to 1 if you wish to use both a foreground layer and a
+ background layer
+
+ """
+ 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.
+
+
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()
+
+
+
Vid.set(pos,v)
+
+
+
pos
(x,y) of tile
+
v
value
+
+ """
+ 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.
+
+
set to 1 if you wish to load the background layer
+
+ """
+ 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.
+
+
Vid.tga_save_level(fname)
+
+
+
fname
tga image to save to
+
+ """
+ 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.
+
+
Vid.tga_load_tiles(fname,size,tdata={})
+
+
+
g
a Tilevid instance
+
fname
tga image to load
+
size
(w,h) size of tiles in pixels
+
tdata
tile data, a dict of tile:(agroups, hit handler, config)
+
+ """
+ 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.
+
+
Vid.load_images(idata)
+
+
+
idata
a list of (name, fname, shape)
+
+ """
+ for name,fname,shape in idata:
+ self.images[name] = pygame.image.load(fname).convert_alpha(),shape
+
+ def run_codes(self,cdata,rect):
+ """Run codes.
+
+
Vid.run_codes(cdata,rect)
+
+
+
cdata
a dict of code:(handler function, value)
+
rect
a tile rect of the parts of the layer that should have
+ their codes run
+
+ """
+ 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.
+
+
Vid.string2groups(str): return groups
+ """
+ if str == None: return 0
+ return self.list2groups(str.split(","))
+
+ def list2groups(self,igroups):
+ """Convert a list to groups.
+
Vid.list2groups(igroups): return groups
+ """
+ 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.
+
Vid.groups2list(groups): return list
+ """
+ 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.
+
Vid.loop()
+ """
+ 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<>= 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.
+
Vid.screen_to_tile(pos): return pos
+ """
+ return pos
+
+ def tile_to_screen(self,pos):
+ """Convert a tile position to a screen position.
+
Vid.tile_to_screen(pos): return pos
+ """
+ return pos
+
+# vim: set filetype=python sts=4 sw=4 noet si :
diff --git a/src/songs/MidiToSong.py b/src/songs/MidiToSong.py
new file mode 100644
index 0000000..f85b06c
--- /dev/null
+++ b/src/songs/MidiToSong.py
@@ -0,0 +1,60 @@
+'''
+Created on 8 dec. 2009
+
+@author: Samuel Benveniste
+'''
+
+from mxmMidi.MidiOutStream import MidiOutStream
+
+class MidiToSong(MidiOutStream):
+ '''
+ Creates songs from midi files using mxmMidi package
+ '''
+ def __init__(self):
+ self.midiNoteNumbers = []
+ self.noteLengths = []
+ self.quarterNoteLength = 500
+ self.firstNoteOn = True
+ self._lastNoteOnTime = 0
+
+ def header(self, format=0, nTracks=1, division=96):
+ print 'format: %s, nTracks: %s, division: %s' % (format, nTracks, division)
+ print '----------------------------------'
+ print ''
+ self.division = division
+
+ def tempo(self,value):
+ self.quarterNoteLength = value/1000
+ print "quarterNoteLength in ms :" + str(self.quarterNoteLength)
+
+ def note_on(self, channel=0, note=0x40, velocity=0x40):
+ self.midiNoteNumbers.append(note)
+ # if it's the first note_on take note of time (the song begins here)
+ # from the second note_on and after, mark the length of the preceding note
+ if self.firstNoteOn :
+ self.firstNoteOn = False
+ self._lastNoteOnTime = self.abs_time()
+ else :
+ self.noteLengths.append(float(self.abs_time()-self._lastNoteOnTime)/float(self.division))
+ self._lastNoteOnTime = self.abs_time()
+
+ def eof(self):
+ self.noteLengths.append(4)
+ for i in range(len(self.midiNoteNumbers)):
+ print "note number :" + str(self.midiNoteNumbers[i]) + ", length in quarter Notes :" + str(self.noteLengths[i])
+ print"--------------"
+ print "end of file"
+
+if __name__ == '__main__':
+
+ # get data
+ test_file = '../songs/midis/test.mid'
+ f = open(test_file, 'rb')
+
+ # do parsing
+ from mxmMidi.MidiInFile import MidiInFile
+ mts = MidiToSong()
+ midiIn = MidiInFile(mts, f)
+ midiIn.read()
+ f.close()
+
\ No newline at end of file
diff --git a/src/songs/Song.py b/src/songs/Song.py
new file mode 100755
index 0000000..c018eae
--- /dev/null
+++ b/src/songs/Song.py
@@ -0,0 +1,137 @@
+'''
+Created on 2 oct. 2009
+
+@author: samsam
+'''
+import pickle
+import os.path
+from MidiToSong import MidiToSong
+from mxmMidi.MidiInFile import MidiInFile
+
+class Song:
+ '''
+ classdocs
+ '''
+
+
+ def __init__(self,scale,notesInExtendedScale=[], requiresExtendedScale = False,midiNoteNumbers = None, alterationIndexes = None, alterations = None, modulationIndexes = None, modulationScales = None, lyrics =None, noteLengths = None, quarterNoteLength = 750, name = "unknownSong"):
+ '''
+ Constructor
+ '''
+ self.name = name
+ self.scale = scale
+ self.notes = notesInExtendedScale
+ self.lyrics = lyrics
+ self.noteLengths = noteLengths
+ self.quarterNoteLength = quarterNoteLength
+ if midiNoteNumbers == None :
+ self.midiNoteNumbers = [self.scale[note] for note in self.notes]
+ else:
+ self.midiNoteNumbers = midiNoteNumbers
+ if self.notes == [] :
+ self.assignNotesFromMidiNoteNumbers()
+ self.requiresExtendedScale = requiresExtendedScale
+ if alterationIndexes != None and alterations != None :
+ self.alterNotes(alterationIndexes, alterations)
+ if modulationIndexes != None and modulationScales != None :
+ self.modulate(modulationIndexes, modulationScales)
+
+ def getSongIterator(self):
+ while True:
+ for i in range(len(self.notes)):
+ if self.lyrics :
+ lyrics = self.lyrics[i]
+ else :
+ lyrics = None
+
+ if self.noteLengths :
+ noteLength = self.noteLengths[i]
+ else :
+ noteLength = 1
+
+ yield [self.notes[i],self.midiNoteNumbers[i],lyrics,noteLength]
+
+ def alterNotes(self,noteIndexes,alterations):
+ for i in range(len(noteIndexes)) :
+ self.midiNoteNumbers[noteIndexes[i]] = self.midiNoteNumbers[noteIndexes[i]] + alterations[i]
+
+ def modulate(self,modulationIndexes,scales):
+ for i in range(len(scales)):
+ if i < len(scales)-1 :
+ bound = modulationIndexes[i+1]
+ else :
+ bound = len(self.notes)
+ for j in range(modulationIndexes[i],bound):
+ self.midiNoteNumbers[j]=scales[i][self.notes[j]]
+
+ def assignNotesFromMidiNoteNumbers(self):
+ for i in range(len(self.midiNoteNumbers)):
+ noteInExtendedScale = 0
+ while self.midiNoteNumbers[i] > self.scale[noteInExtendedScale] and noteInExtendedScale < len(self.scale)-1:
+ noteInExtendedScale += 1
+ if self.midiNoteNumbers[i]