-# 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.
-
- <p>If you wish to create your own theme, create a class with this interface, and
- pass it to gui.App via <tt>gui.App(theme=MyTheme())</tt>.</p>
-
- <strong>Default Theme</strong>
-
- <pre>Theme(dirs='default')</pre>
- <dl>
- <dt>dirs<dd>Name of the theme dir to load a theme from. May be an absolute path to a theme, if pgu is not installed, or if you created your own theme. May include several dirs in a list if data is spread across several themes.
- </dl>
-
- <strong>Example</strong>
-
- <code>
- theme = gui.Theme("default")
- theme = gui.Theme(["mytheme","mytheme2"])
- </code>
- """
- def __init__(self,dirs='default'):
- self.config = {}
- self.dict = {}
- self._loaded = []
- self.cache = {}
- self._preload(dirs)
- pygame.font.init()
-
- def _preload(self,ds):
- if not isinstance(ds, list):
- ds = [ds]
- for d in ds:
- if d not in self._loaded:
- self._load(d)
- self._loaded.append(d)
-
- def _load(self, name):
- #theme_dir = themes[name]
-
- #try to load the local dir, or absolute path
- dnames = [name]
-
- #if the package isn't installed and people are just
- #trying out the scripts or examples
- dnames.append(os.path.join(os.path.dirname(__file__),"..","..","data","themes",name))
-
- #if the package is installed, and the package is installed
- #in /usr/lib/python2.3/site-packages/pgu/
- #or c:\python23\lib\site-packages\pgu\
- #the data is in ... lib/../share/ ...
- dnames.append(os.path.join(os.path.dirname(__file__),"..","..","..","..","share","pgu","themes",name))
- dnames.append(os.path.join(os.path.dirname(__file__),"..","..","..","..","..","share","pgu","themes",name))
- dnames.append(os.path.join(os.path.dirname(__file__),"..","..","share","pgu","themes",name))
- for dname in dnames:
- if os.path.isdir(dname): break
- if not os.path.isdir(dname):
- raise 'could not find theme '+name
-
- fname = os.path.join(dname,"config.txt")
- if os.path.isfile(fname):
- try:
- f = open(fname)
- for line in f.readlines():
- vals = line.strip().split()
- if len(vals) < 3: continue
- cls = vals[0]
- del vals[0]
- pcls = ""
- if cls.find(":")>=0:
- cls,pcls = cls.split(":")
- attr = vals[0]
- del vals[0]
- self.config[cls+":"+pcls+" "+attr] = (dname, vals)
- finally:
- f.close()
- fname = os.path.join(dname,"style.ini")
- if os.path.isfile(fname):
- import ConfigParser
- cfg = ConfigParser.ConfigParser()
- f = open(fname,'r')
- cfg.readfp(f)
- for section in cfg.sections():
- cls = section
- pcls = ''
- if cls.find(":")>=0:
- cls,pcls = cls.split(":")
- for attr in cfg.options(section):
- vals = cfg.get(section,attr).strip().split()
- self.config[cls+':'+pcls+' '+attr] = (dname,vals)
-
- is_image = re.compile('\.(gif|jpg|bmp|png|tga)$', re.I)
- def _get(self,key):
- if not key in self.config: return
- if key in self.dict: return self.dict[key]
- dvals = self.config[key]
- dname, vals = dvals
- #theme_dir = themes[name]
- v0 = vals[0]
- if v0[0] == '#':
- v = parse_color(v0)
- #if (len(v0) == 7):
- # # Due to a bug in pygame 1.8 (?) we need to explicitly
- # # specify the alpha value (otherwise it defaults to zero)
- # v0 += "FF"
- #v = pygame.color.Color(v0)
- elif v0.endswith(".ttf") or v0.endswith(".TTF"):
- v = pygame.font.Font(os.path.join(dname, v0),int(vals[1]))
- elif self.is_image.search(v0) is not None:
- v = pygame.image.load(os.path.join(dname, v0))
- else:
- try: v = int(v0)
- except: v = pygame.font.SysFont(v0, int(vals[1]))
- self.dict[key] = v
- return v
-
- def get(self,cls,pcls,attr):
- """Interface method -- get the value of a style attribute.
-
- <pre>Theme.get(cls,pcls,attr): return value</pre>
-
- <dl>
- <dt>cls<dd>class, for example "checkbox", "button", etc.
- <dt>pcls<dd>pseudo class, for example "hover", "down", etc.
- <dt>attr<dd>attribute, for example "image", "background", "font", "color", etc.
- </dl>
-
- <p>returns the value of the attribute.</p>
-
- <p>This method is called from [[gui-style]].</p>
- """
-
- if not self._loaded: self._preload("default")
-
- o = cls+":"+pcls+" "+attr
-
- #if not hasattr(self,'_count'):
- # self._count = {}
- #if o not in self._count: self._count[o] = 0
- #self._count[o] += 1
-
- if o in self.cache:
- return self.cache[o]
-
- v = self._get(cls+":"+pcls+" "+attr)
- if v:
- self.cache[o] = v
- return v
-
- pcls = ""
- v = self._get(cls+":"+pcls+" "+attr)
- if v:
- self.cache[o] = v
- return v
-
- cls = "default"
- v = self._get(cls+":"+pcls+" "+attr)
- if v:
- self.cache[o] = v
- return v
-
- v = 0
- self.cache[o] = v
- return v
-
- def box(self,w,s):
- style = w.style
-
- c = (0,0,0)
- if style.border_color != 0: c = style.border_color
- w,h = s.get_width(),s.get_height()
-
- s.fill(c,(0,0,w,style.border_top))
- s.fill(c,(0,h-style.border_bottom,w,style.border_bottom))
- s.fill(c,(0,0,style.border_left,h))
- s.fill(c,(w-style.border_right,0,style.border_right,h))
-
-
- def getspacing(self,w):
- # return the top, right, bottom, left spacing around the widget
- if not hasattr(w,'_spacing'): #HACK: assume spacing doesn't change re pcls
- s = w.style
- xt = s.margin_top+s.border_top+s.padding_top
- xr = s.padding_right+s.border_right+s.margin_right
- xb = s.padding_bottom+s.border_bottom+s.margin_bottom
- xl = s.margin_left+s.border_left+s.padding_left
- w._spacing = xt,xr,xb,xl
- return w._spacing
-
-
- def resize(self,w,m):
- # Returns the rectangle expanded in each direction
- def expand_rect(rect, left, top, right, bottom):
- return pygame.Rect(rect.x - left,
- rect.y - top,
- rect.w + left + right,
- rect.h + top + bottom)
-
- def func(width=None,height=None):
- s = w.style
-
- pt,pr,pb,pl = s.padding_top,s.padding_right,s.padding_bottom,s.padding_left
- bt,br,bb,bl = s.border_top,s.border_right,s.border_bottom,s.border_left
- mt,mr,mb,ml = s.margin_top,s.margin_right,s.margin_bottom,s.margin_left
- # Calculate the total space on each side
- top = pt+bt+mt
- right = pr+br+mr
- bottom = pb+bb+mb
- left = pl+bl+ml
- ttw = left+right
- tth = top+bottom
-
- ww,hh = None,None
- if width != None: ww = width-ttw
- if height != None: hh = height-tth
- ww,hh = m(ww,hh)
-
- if width == None: width = ww
- if height == None: height = hh
-
- #if the widget hasn't respected the style.width,
- #style height, we'll add in the space for it...
- width = max(width-ttw, ww, w.style.width)
- height = max(height-tth, hh, w.style.height)
-
- #width = max(ww,w.style.width-tw)
- #height = max(hh,w.style.height-th)
-
- r = pygame.Rect(left,top,width,height)
-
- w._rect_padding = expand_rect(r, pl, pt, pr, pb)
- w._rect_border = expand_rect(w._rect_padding, bl, bt, br, bb)
- w._rect_margin = expand_rect(w._rect_border, ml, mt, mr, mb)
-
- #w._rect_padding = pygame.Rect(r.x-pl,r.y-pt,r.w+pl+pr,r.h+pt+pb)
- #r = w._rect_padding
- #w._rect_border = pygame.Rect(r.x-bl,r.y-bt,r.w+bl+br,r.h+bt+bb)
- #r = w._rect_border
- #w._rect_margin = pygame.Rect(r.x-ml,r.y-mt,r.w+ml+mr,r.h+mt+mb)
-
- # align it within it's zone of power.
- rect = pygame.Rect(left, top, ww, hh)
- dx = width-rect.w
- dy = height-rect.h
- rect.x += (w.style.align+1)*dx/2
- rect.y += (w.style.valign+1)*dy/2
-
- w._rect_content = rect
-
- return (w._rect_margin.w, w._rect_margin.h)
- return func
-
-
- def paint(self,w,m):
- def func(s):
-# if w.disabled:
-# if not hasattr(w,'_disabled_bkgr'):
-# w._disabled_bkgr = s.convert()
-# orig = s
-# s = w._disabled_bkgr.convert()
-
-# if not hasattr(w,'_theme_paint_bkgr'):
-# w._theme_paint_bkgr = s.convert()
-# else:
-# s.blit(w._theme_paint_bkgr,(0,0))
-#
-# if w.disabled:
-# orig = s
-# s = w._theme_paint_bkgr.convert()
-
- if w.disabled:
- if (not (hasattr(w,'_theme_bkgr') and
- w._theme_bkgr.get_width() == s.get_width() and
- w._theme_bkgr.get_height() == s.get_height())):
- w._theme_bkgr = s.copy()
- orig = s
- s = w._theme_bkgr
- s.fill((0,0,0,0))
- s.blit(orig,(0,0))
-
- if hasattr(w,'background'):
- w.background.paint(surface.subsurface(s,w._rect_border))
- self.box(w,surface.subsurface(s,w._rect_border))
- r = m(surface.subsurface(s,w._rect_content))
-
- if w.disabled:
- s.set_alpha(128)
- orig.blit(s,(0,0))
-
-# if w.disabled:
-# orig.blit(w._disabled_bkgr,(0,0))
-# s.set_alpha(128)
-# orig.blit(s,(0,0))
-
- w._painted = True
- return r
- return func
-
- def event(self,w,m):
- def func(e):
- rect = w._rect_content
- if e.type == MOUSEBUTTONUP or e.type == MOUSEBUTTONDOWN:
- sub = pygame.event.Event(e.type,{
- 'button':e.button,
- 'pos':(e.pos[0]-rect.x,e.pos[1]-rect.y)})
- elif e.type == CLICK:
- sub = pygame.event.Event(e.type,{
- 'button':e.button,
- 'pos':(e.pos[0]-rect.x,e.pos[1]-rect.y)})
- elif e.type == MOUSEMOTION:
- sub = pygame.event.Event(e.type,{
- 'buttons':e.buttons,
- 'pos':(e.pos[0]-rect.x,e.pos[1]-rect.y),
- 'rel':e.rel})
- else:
- sub = e
- r = m(sub)
- return r
- return func
-
- def update(self,w,m):
- def func(s):
- if w.disabled: return []
- r = m(surface.subsurface(s,w._rect_content))
- if type(r) == list:
- dx,dy = w._rect_content.topleft
- for rr in r:
- rr.x,rr.y = rr.x+dx,rr.y+dy
- return r
- return func
-
- def open(self,w,m):
- def func(widget=None,x=None,y=None):
- if not hasattr(w,'_rect_content'): w.rect.w,w.rect.h = w.resize() #HACK: so that container.open won't resize again!
- rect = w._rect_content
- ##print w.__class__.__name__, rect
- if x != None: x += rect.x
- if y != None: y += rect.y
- return m(widget,x,y)
- return func
-
- #def open(self,w,m):
- # def func(widget=None):
- # return m(widget)
- # return func
-
- def decorate(self,widget,level):
- """Interface method -- decorate a widget.
-
- <p>The theme system is given the opportunity to decorate a widget methods at the
- end of the Widget initializer.</p>
-
- <pre>Theme.decorate(widget,level)</pre>
-
- <dl>
- <dt>widget<dd>the widget to be decorated
- <dt>level<dd>the amount of decoration to do, False for none, True for normal amount, 'app' for special treatment of App objects.
- </dl>
- """
-
- w = widget
- if level == False: return
-
- if type(w.style.background) != int:
- w.background = Background(w,self)
-
- if level == 'app': return
-
- for k,v in w.style.__dict__.items():
- if k in ('border','margin','padding'):
- for kk in ('top','bottom','left','right'):
- setattr(w.style,'%s_%s'%(k,kk),v)
-
- w.paint = self.paint(w,w.paint)
- w.event = self.event(w,w.event)
- w.update = self.update(w,w.update)
- w.resize = self.resize(w,w.resize)
- w.open = self.open(w,w.open)
-
- def render(self,s,box,r):
- """Interface method - render a special widget feature.
-
- <pre>Theme.render(s,box,r)</pre>
-
- <dl>
- <dt>s<dt>pygame.Surface
- <dt>box<dt>box data, a value returned from Theme.get, typically a pygame.Surface
- <dt>r<dt>pygame.Rect with the size that the box data should be rendered
- </dl>
-
- """
-
- if box == 0: return
-
- if 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)