""" """ import os, re import pygame from const import * import surface from basic import parse_color __file__ = os.path.abspath(__file__) def _list_themes(dir): d = {} for entry in os.listdir(dir): if os.path.exists(os.path.join(dir, entry, 'config.txt')): d[entry] = os.path.join(dir, entry) return d class Theme: """Theme interface.

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 isinstance(box, pygame.Color) or type(box) == tuple: s.fill(box,r) return x,y,w,h=r.x,r.y,r.w,r.h ww,hh=box.get_width()/3,box.get_height()/3 xx,yy=x+w,y+h src = pygame.rect.Rect(0,0,ww,hh) dest = pygame.rect.Rect(0,0,ww,hh) s.set_clip(pygame.Rect(x+ww,y+hh,w-ww*2,h-hh*2)) src.x,src.y = ww,hh for dest.y in xrange(y+hh,yy-hh,hh): for dest.x in xrange(x+ww,xx-ww,ww): s.blit(box,dest,src) s.set_clip(pygame.Rect(x+ww,y,w-ww*3,hh)) src.x,src.y,dest.y = ww,0,y for dest.x in xrange(x+ww,xx-ww*2,ww): s.blit(box,dest,src) dest.x = xx-ww*2 s.set_clip(pygame.Rect(x+ww,y,w-ww*2,hh)) s.blit(box,dest,src) s.set_clip(pygame.Rect(x+ww,yy-hh,w-ww*3,hh)) src.x,src.y,dest.y = ww,hh*2,yy-hh for dest.x in xrange(x+ww,xx-ww*2,ww): s.blit(box,dest,src) dest.x = xx-ww*2 s.set_clip(pygame.Rect(x+ww,yy-hh,w-ww*2,hh)) s.blit(box,dest,src) s.set_clip(pygame.Rect(x,y+hh,xx,h-hh*3)) src.y,src.x,dest.x = hh,0,x for dest.y in xrange(y+hh,yy-hh*2,hh): s.blit(box,dest,src) dest.y = yy-hh*2 s.set_clip(pygame.Rect(x,y+hh,xx,h-hh*2)) s.blit(box,dest,src) s.set_clip(pygame.Rect(xx-ww,y+hh,xx,h-hh*3)) src.y,src.x,dest.x=hh,ww*2,xx-ww for dest.y in xrange(y+hh,yy-hh*2,hh): s.blit(box,dest,src) dest.y = yy-hh*2 s.set_clip(pygame.Rect(xx-ww,y+hh,xx,h-hh*2)) s.blit(box,dest,src) s.set_clip() src.x,src.y,dest.x,dest.y = 0,0,x,y s.blit(box,dest,src) src.x,src.y,dest.x,dest.y = ww*2,0,xx-ww,y s.blit(box,dest,src) src.x,src.y,dest.x,dest.y = 0,hh*2,x,yy-hh s.blit(box,dest,src) src.x,src.y,dest.x,dest.y = ww*2,hh*2,xx-ww,yy-hh s.blit(box,dest,src) import pygame import widget class Background(widget.Widget): def __init__(self,value,theme,**params): params['decorate'] = False widget.Widget.__init__(self,**params) self.value = value self.theme = theme def paint(self,s): r = pygame.Rect(0,0,s.get_width(),s.get_height()) v = self.value.style.background if isinstance(v, pygame.Color) or type(v) == tuple: s.fill(v) else: self.theme.render(s,v,r)