""" """ 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 ThemeTheme(dirs='default')
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
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)
Theme.render(s,box,r)