1 """Defines the Container class which is the base class for all widgets that contain other widgets.
4 from pygame.locals import *
7 from . import widget, surface
8 from . import pguglobals
9 from .surface import ProxySurface
10 from .errors import StyleError
12 class Container(widget.Widget):
13 """The base container widget, can be used as a template as well as stand alone."""
15 # The widget in this container that the mouse is hovering over
17 # The widget that has input focus in this container
19 # The currently open window
22 def __init__(self,**params):
23 widget.Widget.__init__(self,**params)
32 if self.myfocus: self.toupdate[self.myfocus] = self.myfocus
34 # Paint all child widgets, skipping over the currently open window (if any)
35 for w in self.topaint:
36 if w is self.mywindow:
38 sub = surface.subsurface(s,w.rect)
39 #if (hasattr(w, "_container_bkgr")):
40 # sub.blit(w._container_bkgr,(0,0))
42 # Check for the theme alpha hack. This is needed (for now) to accomodate rendering
43 # images with alpha blending (eg alpha value between fully opaque and fully transparent).
44 # Normally in PGU when a widget changes state (eg normal to highlighted state) the new
45 # widget appearance is rendered directly overtop of the old image. This usually works okay.
47 # However, if the images have a different shape, then the new image may fail to
48 # completely paint over the old image, leaving bits behind. As well, if the images use
49 # alpha blending you have a similar situation where the old image isn't completely
50 # covered by the new image (since alpha blending preserves some of the pixel data). The
51 # work-around is to paint the background behind the image, then paint the new image.
52 # And that's what this hack does.
54 # This hack isn't perfect and so it's not enabled by default, but only by
55 # themes that explicitly request it.
56 alpha = pguglobals.app.theme.getstyle("pgu", "", "themealpha")
61 # Look for the first parent container that has a rendered background
63 (x, y) = w._rect_content.topleft
64 while (cnt and not cnt.background):
67 # if (cnt and cnt.background):
68 # if (cnt._rect_content):
69 # x += cnt._rect_content.left
70 # y += cnt._rect_content.top
71 # r1 = cnt.get_abs_rect()
72 # r2 = w.get_abs_rect()
73 # x = r2.left - r1.left
75 # subrect = (x, y, sub.get_width(), sub.get_height())
76 # tmp = pygame.Surface(r1.size).convert_alpha()
77 # tmp.set_clip(subrect)
78 # cnt.background.paint(tmp)
80 # sub.blit(tmp, (0,0), subrect)
82 # Paint the background. This works reasonably okay but it's not exactly correct.
83 r1 = cnt.get_abs_rect()
87 cnt.background.paint(sub) #, size=r1.size, offset=(x, y))
90 updates.append(pygame.rect.Rect(w.rect))
92 # Update the child widgets, excluding the open window
93 for w in self.toupdate:
94 if w is self.mywindow:
96 us = w.update(surface.subsurface(s,w.rect))
99 updates.append(pygame.rect.Rect(u.x + w.rect.x,u.y+w.rect.y,u.w,u.h))
101 # Now handle the open window (if any)
104 self.mywindow.paint(self.top_surface(s,self.mywindow))
105 updates.append(pygame.rect.Rect(self.mywindow.rect))
107 us = self.mywindow.update(self.top_surface(s,self.mywindow))
110 updates.append(pygame.rect.Rect(
111 u.x + self.mywindow.rect.x,
112 u.y + self.mywindow.rect.y,
120 def repaint(self,w=None):
122 return widget.Widget.repaint(self)
126 def reupdate(self,w=None):
128 return widget.Widget.reupdate(self)
135 for w in self.widgets:
137 sub = surface.subsurface(s, w.rect)
139 print('container.paint(): %s not inside %s' % (
140 w.__class__.__name__,self.__class__.__name__))
141 print(s.get_width(), s.get_height(), w.rect)
146 for w in self.windows:
147 w.paint(self.top_surface(s,w))
149 def top_surface(self,s,w):
150 x,y = s.get_abs_offset()
151 s = s.get_abs_parent()
152 return surface.subsurface(s,(x+w.rect.x,y+w.rect.y,w.rect.w,w.rect.h))
157 if self.mywindow and e.type == MOUSEBUTTONDOWN:
159 if self.myfocus is w:
160 if not w.collidepoint(e.pos): self.blur(w)
162 if w.collidepoint(e.pos): self.focus(w)
164 if not self.mywindow:
167 ## if e.type == FOCUS:
168 if e.type == FOCUS and not self.myfocus:
172 if self.myhover: self.exit(self.myhover)
174 if self.myfocus: self.blur(self.myfocus)
175 elif e.type == MOUSEBUTTONDOWN:
177 for w in self.widgets:
179 # Focusable not considered, since that is only for tabs
180 if w.collidepoint(e.pos):
182 if self.myfocus is not w: self.focus(w)
183 if not h and self.myfocus:
184 self.blur(self.myfocus)
185 elif e.type == MOUSEMOTION:
187 if self.myfocus: ws = [self.myfocus]
189 else: ws = self.widgets
193 if w.collidepoint(e.pos):
195 if self.myhover is not w:
198 if not h and self.myhover:
199 self.exit(self.myhover)
202 if w and w is not self.myfocus:
203 sub = pygame.event.Event(e.type,{
205 'pos':(e.pos[0]-w.rect.x,e.pos[1]-w.rect.y),
211 if e.type == MOUSEBUTTONUP or e.type == MOUSEBUTTONDOWN:
212 sub = pygame.event.Event(e.type,{
214 'pos':(e.pos[0]-w.rect.x,e.pos[1]-w.rect.y)})
215 elif e.type == CLICK and self.myhover is w:
216 sub = pygame.event.Event(e.type,{
218 'pos':(e.pos[0]-w.rect.x,e.pos[1]-w.rect.y)})
219 elif e.type == MOUSEMOTION:
220 sub = pygame.event.Event(e.type,{
222 'pos':(e.pos[0]-w.rect.x,e.pos[1]-w.rect.y),
224 elif (e.type == KEYDOWN or e.type == KEYUP):
229 #elif e.type == CLICK: #a dead click
235 if not used and e.type is KEYDOWN:
236 if e.key is K_TAB and self.myfocus:
237 if (e.mod&KMOD_SHIFT) == 0:
240 self.myfocus.previous()
243 self._move_focus(0,-1)
245 elif e.key == K_RIGHT:
246 self._move_focus(1,0)
248 elif e.key == K_DOWN:
249 self._move_focus(0,1)
251 elif e.key == K_LEFT:
252 self._move_focus(-1,0)
256 def _move_focus(self,dx_,dy_):
257 myfocus = self.myfocus
258 if not self.myfocus: return
260 widgets = self._get_widgets(pguglobals.app)
261 #if myfocus not in widgets: return
262 #widgets.remove(myfocus)
263 if myfocus in widgets:
264 widgets.remove(myfocus)
265 rect = myfocus.get_abs_rect()
266 fx,fy = rect.centerx,rect.centery
275 wrect = w.get_abs_rect()
276 wx,wy = wrect.centerx,wrect.centery
278 if dx_ > 0 and wrect.left < rect.right: continue
279 if dx_ < 0 and wrect.right > rect.left: continue
280 if dy_ > 0 and wrect.top < rect.bottom: continue
281 if dy_ < 0 and wrect.bottom > rect.top: continue
282 dist.append((dx*dx+dy*dy,w))
283 if not len(dist): return
288 def _get_widgets(self,c):
291 widgets.extend(self._get_widgets(c.mywindow))
294 if isinstance(w,Container):
295 widgets.extend(self._get_widgets(w))
296 elif not w.disabled and w.focusable:
301 """Remove a widget from the container."""
303 self.widgets.remove(w)
308 """Add a widget to the container given the position."""
312 #NOTE: this might fix it, sort of...
313 #but the thing is, we don't really want to resize
314 #something if it is going to get resized again later
316 #w.rect.x,w.rect.y = w.style.x,w.style.y
317 #w.rect.w, w.rect.h = w.resize()
318 self.widgets.append(w)
321 def open(self,w=None,x=None,y=None):
326 # The position is relative to this container
327 rect = self.get_abs_rect()
328 pos = (rect.x + x, rect.y + y)
331 # Have the application open the window
332 pguglobals.app.open(w, pos)
334 def focus(self,w=None):
335 widget.Widget.focus(self) ### by Gal koren
337 # return widget.Widget.focus(self)
339 if self.myfocus: self.blur(self.myfocus)
340 if self.myhover is not w: self.enter(w)
342 w._event(pygame.event.Event(FOCUS))
344 #print self.myfocus,self.myfocus.__class__.__name__
346 def blur(self,w=None):
348 return widget.Widget.blur(self)
349 if self.myfocus is w:
350 if self.myhover is w: self.exit(w)
352 w._event(pygame.event.Event(BLUR))
355 if self.myhover: self.exit(self.myhover)
357 w._event(pygame.event.Event(ENTER))
360 if self.myhover and self.myhover is w:
362 w._event(pygame.event.Event(EXIT))
366 # for w in self.widgets:
370 # if self.container: self.container.next(self)
373 # if w not in self.widgets: return #HACK: maybe. this happens in windows for some reason...
375 # for w in self.widgets[self.widgets.index(w)+1:]:
379 # if self.container: return self.container.next(self)
382 def _next(self,orig=None):
384 if orig in self.widgets: start = self.widgets.index(orig)+1
385 for w in self.widgets[start:]:
386 if not w.disabled and w.focusable:
387 if isinstance(w,Container):
395 def _previous(self,orig=None):
396 end = len(self.widgets)
397 if orig in self.widgets: end = self.widgets.index(orig)
398 ws = self.widgets[:end]
401 if not w.disabled and w.focusable:
402 if isinstance(w,Container):
410 def next(self,w=None):
411 if w != None and w not in self.widgets: return #HACK: maybe. this happens in windows for some reason...
413 if self._next(w): return True
414 if self.container: return self.container.next(self)
417 def previous(self,w=None):
418 if w != None and w not in self.widgets: return #HACK: maybe. this happens in windows for some reason...
420 if self._previous(w): return True
421 if self.container: return self.container.previous(self)
423 def resize(self,width=None,height=None):
427 if self.style.width: ww = self.style.width
428 if self.style.height: hh = self.style.height
430 for w in self.widgets:
431 #w.rect.w,w.rect.h = 0,0
432 w.rect.x,w.rect.y = w.style.x,w.style.y
433 w.rect.w, w.rect.h = w.resize()
436 ww = max(ww,w.rect.right)
437 hh = max(hh,w.rect.bottom)
440 # Returns the widget with the given name
441 def find(self, name):
442 for w in self.widgets:
445 elif (isinstance(w, Container)):