11 from basic
import parse_color
, is_color
13 __file__
= os
.path
.abspath(__file__
)
15 def _list_themes(dir):
17 for entry
in os
.listdir(dir):
18 if os
.path
.exists(os
.path
.join(dir, entry
, 'config.txt')):
19 d
[entry
] = os
.path
.join(dir, entry
)
25 <p>If you wish to create your own theme, create a class with this interface, and
26 pass it to gui.App via <tt>gui.App(theme=MyTheme())</tt>.</p>
28 <strong>Default Theme</strong>
30 <pre>Theme(dirs='default')</pre>
32 <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.
35 <strong>Example</strong>
38 theme = gui.Theme("default")
39 theme = gui.Theme(["mytheme","mytheme2"])
42 def __init__(self
,dirs
='default'):
50 def _preload(self
,ds
):
51 if not isinstance(ds
, list):
54 if d
not in self
._loaded
:
56 self
._loaded
.append(d
)
58 def _load(self
, name
):
59 #theme_dir = themes[name]
61 #try to load the local dir, or absolute path
64 #if the package isn't installed and people are just
65 #trying out the scripts or examples
66 dnames
.append(os
.path
.join(os
.path
.dirname(__file__
),"..","..","data","themes",name
))
68 #if the package is installed, and the package is installed
69 #in /usr/lib/python2.3/site-packages/pgu/
70 #or c:\python23\lib\site-packages\pgu\
71 #the data is in ... lib/../share/ ...
72 dnames
.append(os
.path
.join(os
.path
.dirname(__file__
),"..","..","..","..","share","pgu","themes",name
))
73 dnames
.append(os
.path
.join(os
.path
.dirname(__file__
),"..","..","..","..","..","share","pgu","themes",name
))
74 dnames
.append(os
.path
.join(os
.path
.dirname(__file__
),"..","..","share","pgu","themes",name
))
76 if os
.path
.isdir(dname
): break
77 if not os
.path
.isdir(dname
):
78 raise 'could not find theme '+name
80 fname
= os
.path
.join(dname
,"config.txt")
81 if os
.path
.isfile(fname
):
84 for line
in f
.readlines():
85 vals
= line
.strip().split()
86 if len(vals
) < 3: continue
91 cls
,pcls
= cls
.split(":")
94 self
.config
[cls
+":"+pcls
+" "+attr
] = (dname
, vals
)
97 fname
= os
.path
.join(dname
,"style.ini")
98 if os
.path
.isfile(fname
):
100 cfg
= ConfigParser
.ConfigParser()
103 for section
in cfg
.sections():
107 cls
,pcls
= cls
.split(":")
108 for attr
in cfg
.options(section
):
109 vals
= cfg
.get(section
,attr
).strip().split()
110 self
.config
[cls
+':'+pcls
+' '+attr
] = (dname
,vals
)
112 is_image
= re
.compile('\.(gif|jpg|bmp|png|tga)$', re
.I
)
114 if not key
in self
.config
: return
115 if key
in self
.dict: return self
.dict[key
]
116 dvals
= self
.config
[key
]
118 #theme_dir = themes[name]
123 # # Due to a bug in pygame 1.8 (?) we need to explicitly
124 # # specify the alpha value (otherwise it defaults to zero)
126 #v = pygame.color.Color(v0)
127 elif v0
.endswith(".ttf") or v0
.endswith(".TTF"):
128 v
= pygame
.font
.Font(os
.path
.join(dname
, v0
),int(vals
[1]))
129 elif self
.is_image
.search(v0
) is not None:
130 v
= pygame
.image
.load(os
.path
.join(dname
, v0
))
133 except: v
= pygame
.font
.SysFont(v0
, int(vals
[1]))
137 def get(self
,cls
,pcls
,attr
):
138 """Interface method -- get the value of a style attribute.
140 <pre>Theme.get(cls,pcls,attr): return value</pre>
143 <dt>cls<dd>class, for example "checkbox", "button", etc.
144 <dt>pcls<dd>pseudo class, for example "hover", "down", etc.
145 <dt>attr<dd>attribute, for example "image", "background", "font", "color", etc.
148 <p>returns the value of the attribute.</p>
150 <p>This method is called from [[gui-style]].</p>
153 if not self
._loaded
: self
._preload
("default")
155 o
= cls
+":"+pcls
+" "+attr
157 #if not hasattr(self,'_count'):
159 #if o not in self._count: self._count[o] = 0
165 v
= self
._get
(cls
+":"+pcls
+" "+attr
)
171 v
= self
._get
(cls
+":"+pcls
+" "+attr
)
177 v
= self
._get
(cls
+":"+pcls
+" "+attr
)
190 if style
.border_color
!= 0: c
= style
.border_color
191 w
,h
= s
.get_width(),s
.get_height()
193 s
.fill(c
,(0,0,w
,style
.border_top
))
194 s
.fill(c
,(0,h
-style
.border_bottom
,w
,style
.border_bottom
))
195 s
.fill(c
,(0,0,style
.border_left
,h
))
196 s
.fill(c
,(w
-style
.border_right
,0,style
.border_right
,h
))
199 def getspacing(self
,w
):
200 # return the top, right, bottom, left spacing around the widget
201 if not hasattr(w
,'_spacing'): #HACK: assume spacing doesn't change re pcls
203 xt
= s
.margin_top
+s
.border_top
+s
.padding_top
204 xr
= s
.padding_right
+s
.border_right
+s
.margin_right
205 xb
= s
.padding_bottom
+s
.border_bottom
+s
.margin_bottom
206 xl
= s
.margin_left
+s
.border_left
+s
.padding_left
207 w
._spacing
= xt
,xr
,xb
,xl
211 def resize(self
,w
,m
):
212 # Returns the rectangle expanded in each direction
213 def expand_rect(rect
, left
, top
, right
, bottom
):
214 return pygame
.Rect(rect
.x
- left
,
216 rect
.w
+ left
+ right
,
217 rect
.h
+ top
+ bottom
)
219 def func(width
=None,height
=None):
222 pt
,pr
,pb
,pl
= s
.padding_top
,s
.padding_right
,s
.padding_bottom
,s
.padding_left
223 bt
,br
,bb
,bl
= s
.border_top
,s
.border_right
,s
.border_bottom
,s
.border_left
224 mt
,mr
,mb
,ml
= s
.margin_top
,s
.margin_right
,s
.margin_bottom
,s
.margin_left
225 # Calculate the total space on each side
234 if width
!= None: ww
= width
-ttw
235 if height
!= None: hh
= height
-tth
238 if width
== None: width
= ww
239 if height
== None: height
= hh
241 #if the widget hasn't respected the style.width,
242 #style height, we'll add in the space for it...
243 width
= max(width
-ttw
, ww
, w
.style
.width
)
244 height
= max(height
-tth
, hh
, w
.style
.height
)
246 #width = max(ww,w.style.width-tw)
247 #height = max(hh,w.style.height-th)
249 r
= pygame
.Rect(left
,top
,width
,height
)
251 w
._rect
_padding
= expand_rect(r
, pl
, pt
, pr
, pb
)
252 w
._rect
_border
= expand_rect(w
._rect
_padding
, bl
, bt
, br
, bb
)
253 w
._rect
_margin
= expand_rect(w
._rect
_border
, ml
, mt
, mr
, mb
)
255 #w._rect_padding = pygame.Rect(r.x-pl,r.y-pt,r.w+pl+pr,r.h+pt+pb)
257 #w._rect_border = pygame.Rect(r.x-bl,r.y-bt,r.w+bl+br,r.h+bt+bb)
259 #w._rect_margin = pygame.Rect(r.x-ml,r.y-mt,r.w+ml+mr,r.h+mt+mb)
261 # align it within it's zone of power.
262 rect
= pygame
.Rect(left
, top
, ww
, hh
)
265 rect
.x
+= (w
.style
.align
+1)*dx
/2
266 rect
.y
+= (w
.style
.valign
+1)*dy
/2
268 w
._rect
_content
= rect
270 return (w
._rect
_margin
.w
, w
._rect
_margin
.h
)
277 # if not hasattr(w,'_disabled_bkgr'):
278 # w._disabled_bkgr = s.convert()
280 # s = w._disabled_bkgr.convert()
282 # if not hasattr(w,'_theme_paint_bkgr'):
283 # w._theme_paint_bkgr = s.convert()
285 # s.blit(w._theme_paint_bkgr,(0,0))
289 # s = w._theme_paint_bkgr.convert()
292 if (not (hasattr(w
,'_theme_bkgr') and
293 w
._theme
_bkgr
.get_width() == s
.get_width() and
294 w
._theme
_bkgr
.get_height() == s
.get_height())):
295 w
._theme
_bkgr
= s
.copy()
301 if hasattr(w
,'background'):
302 w
.background
.paint(surface
.subsurface(s
,w
._rect
_border
))
303 self
.box(w
,surface
.subsurface(s
,w
._rect
_border
))
304 r
= m(surface
.subsurface(s
,w
._rect
_content
))
311 # orig.blit(w._disabled_bkgr,(0,0))
321 rect
= w
._rect
_content
322 if e
.type == MOUSEBUTTONUP
or e
.type == MOUSEBUTTONDOWN
:
323 sub
= pygame
.event
.Event(e
.type,{
325 'pos':(e
.pos
[0]-rect
.x
,e
.pos
[1]-rect
.y
)})
326 elif e
.type == CLICK
:
327 sub
= pygame
.event
.Event(e
.type,{
329 'pos':(e
.pos
[0]-rect
.x
,e
.pos
[1]-rect
.y
)})
330 elif e
.type == MOUSEMOTION
:
331 sub
= pygame
.event
.Event(e
.type,{
333 'pos':(e
.pos
[0]-rect
.x
,e
.pos
[1]-rect
.y
),
341 def update(self
,w
,m
):
343 if w
.disabled
: return []
344 r
= m(surface
.subsurface(s
,w
._rect
_content
))
346 dx
,dy
= w
._rect
_content
.topleft
348 rr
.x
,rr
.y
= rr
.x
+dx
,rr
.y
+dy
353 def func(widget
=None,x
=None,y
=None):
354 if not hasattr(w
,'_rect_content'): w
.rect
.w
,w
.rect
.h
= w
.resize() #HACK: so that container.open won't resize again!
355 rect
= w
._rect
_content
356 ##print w.__class__.__name__, rect
357 if x
!= None: x
+= rect
.x
358 if y
!= None: y
+= rect
.y
363 # def func(widget=None):
367 def decorate(self
,widget
,level
):
368 """Interface method -- decorate a widget.
370 <p>The theme system is given the opportunity to decorate a widget methods at the
371 end of the Widget initializer.</p>
373 <pre>Theme.decorate(widget,level)</pre>
376 <dt>widget<dd>the widget to be decorated
377 <dt>level<dd>the amount of decoration to do, False for none, True for normal amount, 'app' for special treatment of App objects.
382 if level
== False: return
384 if type(w
.style
.background
) != int:
385 w
.background
= Background(w
,self
)
387 if level
== 'app': return
389 for k
,v
in w
.style
.__dict
__.items():
390 if k
in ('border','margin','padding'):
391 for kk
in ('top','bottom','left','right'):
392 setattr(w
.style
,'%s_%s'%(k
,kk
),v
)
394 w
.paint
= self
.paint(w
,w
.paint
)
395 w
.event
= self
.event(w
,w
.event
)
396 w
.update
= self
.update(w
,w
.update
)
397 w
.resize
= self
.resize(w
,w
.resize
)
398 w
.open = self
.open(w
,w
.open)
400 def render(self
,s
,box
,r
):
401 """Interface method - render a special widget feature.
403 <pre>Theme.render(s,box,r)</pre>
406 <dt>s<dt>pygame.Surface
407 <dt>box<dt>box data, a value returned from Theme.get, typically a pygame.Surface
408 <dt>r<dt>pygame.Rect with the size that the box data should be rendered
419 x
,y
,w
,h
=r
.x
,r
.y
,r
.w
,r
.h
420 ww
,hh
=box
.get_width()/3,box
.get_height()/3
422 src
= pygame
.rect
.Rect(0,0,ww
,hh
)
423 dest
= pygame
.rect
.Rect(0,0,ww
,hh
)
425 s
.set_clip(pygame
.Rect(x
+ww
,y
+hh
,w
-ww
*2,h
-hh
*2))
427 for dest
.y
in xrange(y
+hh
,yy
-hh
,hh
):
428 for dest
.x
in xrange(x
+ww
,xx
-ww
,ww
): s
.blit(box
,dest
,src
)
430 s
.set_clip(pygame
.Rect(x
+ww
,y
,w
-ww
*3,hh
))
431 src
.x
,src
.y
,dest
.y
= ww
,0,y
432 for dest
.x
in xrange(x
+ww
,xx
-ww
*2,ww
): s
.blit(box
,dest
,src
)
434 s
.set_clip(pygame
.Rect(x
+ww
,y
,w
-ww
*2,hh
))
437 s
.set_clip(pygame
.Rect(x
+ww
,yy
-hh
,w
-ww
*3,hh
))
438 src
.x
,src
.y
,dest
.y
= ww
,hh
*2,yy
-hh
439 for dest
.x
in xrange(x
+ww
,xx
-ww
*2,ww
): s
.blit(box
,dest
,src
)
441 s
.set_clip(pygame
.Rect(x
+ww
,yy
-hh
,w
-ww
*2,hh
))
444 s
.set_clip(pygame
.Rect(x
,y
+hh
,xx
,h
-hh
*3))
445 src
.y
,src
.x
,dest
.x
= hh
,0,x
446 for dest
.y
in xrange(y
+hh
,yy
-hh
*2,hh
): s
.blit(box
,dest
,src
)
448 s
.set_clip(pygame
.Rect(x
,y
+hh
,xx
,h
-hh
*2))
451 s
.set_clip(pygame
.Rect(xx
-ww
,y
+hh
,xx
,h
-hh
*3))
452 src
.y
,src
.x
,dest
.x
=hh
,ww
*2,xx
-ww
453 for dest
.y
in xrange(y
+hh
,yy
-hh
*2,hh
): s
.blit(box
,dest
,src
)
455 s
.set_clip(pygame
.Rect(xx
-ww
,y
+hh
,xx
,h
-hh
*2))
459 src
.x
,src
.y
,dest
.x
,dest
.y
= 0,0,x
,y
462 src
.x
,src
.y
,dest
.x
,dest
.y
= ww
*2,0,xx
-ww
,y
465 src
.x
,src
.y
,dest
.x
,dest
.y
= 0,hh
*2,x
,yy
-hh
468 src
.x
,src
.y
,dest
.x
,dest
.y
= ww
*2,hh
*2,xx
-ww
,yy
-hh
472 class Background(widget
.Widget
):
473 def __init__(self
,value
,theme
,**params
):
474 params
['decorate'] = False
475 widget
.Widget
.__init
__(self
,**params
)
480 r
= pygame
.Rect(0,0,s
.get_width(),s
.get_height())
481 v
= self
.value
.style
.background
485 self
.theme
.render(s
,v
,r
)