Mercurial > fife-parpg
view engine/extensions/pychan/widgets/containers.py @ 248:a2d5e2721489
widgets.py split up.
author | phoku@33b003aa-7bff-0310-803a-e67f0ece8222 |
---|---|
date | Thu, 26 Mar 2009 16:20:16 +0000 |
parents | |
children | 1cc51d145af9 |
line wrap: on
line source
# -*- coding: utf-8 -*- from common import * from widget import Widget ### Containers + Layout code ### class Container(Widget): """ This is the basic container class. It provides space in which child widgets can be position via the position attribute. If you want to use the layout engine, you have to use derived containers with vertical or horizontal orientation (L{VBox} or L{HBox}) New Attributes ============== - padding - Integer: Not used in the Container class istelf, distance between child widgets. - background_image - Set this to a GuiImage or a resource location (simply a filename). The image will be tiled over the background area. - opaque - Boolean: Whether the background should be drawn at all. Set this to False to make the widget transparent. - children - Just contains the list of contained child widgets. Do NOT modify. """ ATTRIBUTES = Widget.ATTRIBUTES + [ IntAttr('padding'), Attr('background_image'), BoolAttr('opaque'),PointAttr('margins') ] def __init__(self,padding=5,margins=(5,5),_real_widget=None, **kwargs): self.real_widget = _real_widget or fife.Container() self.children = [] self.margins = margins self.padding = padding self._background = [] self._background_image = None super(Container,self).__init__(**kwargs) def addChild(self, widget): widget.parent = self self.children.append(widget) self.real_widget.add(widget.real_widget) def removeChild(self,widget): if not widget in self.children: raise RuntimeError("%s does not have %s as direct child widget." % (str(self),str(widget))) self.children.remove(widget) self.real_widget.remove(widget.real_widget) widget.parent = None def add(self,*widgets): print "PyChan: Deprecation warning: Please use 'addChild' or 'addChildren' instead." self.addChildren(*widgets) def getMaxChildrenWidth(self): if not self.children: return 0 return max(widget.width for widget in self.children) def getMaxChildrenHeight(self): if not self.children: return 0 return max(widget.height for widget in self.children) def deepApply(self,visitorFunc): for child in self.children: child.deepApply(visitorFunc) visitorFunc(self) def beforeShow(self): self._resetTiling() def _resetTiling(self): image = self._background_image if image is None: return back_w,back_h = self.width, self.height image_w, image_h = image.getWidth(), image.getHeight() map(self.real_widget.remove,self._background) # Now tile the background over the widget self._background = [] icon = fife.Icon(image) x, w = 0, image_w while x < back_w: y, h = 0, image_h while y < self.height: icon = fife.Icon(image) icon.setPosition(x,y) self._background.append(icon) y += h x += w map(self.real_widget.add,self._background) for tile in self._background: tile.requestMoveToBottom() def setBackgroundImage(self,image): self._background = getattr(self,'_background',None) if image is None: self._background_image = image map(self.real_widget.remove,self._background) self._background = [] # Background generation is done in _resetTiling if not isinstance(image, fife.GuiImage): image = get_manager().loadImage(image) self._background_image = image def getBackgroundImage(self): return self._background_image background_image = property(getBackgroundImage,setBackgroundImage) def _setOpaque(self,opaque): self.real_widget.setOpaque(opaque) def _getOpaque(self): return self.real_widget.isOpaque() opaque = property(_getOpaque,_setOpaque) AlignTop, AlignBottom, AlignLeft, AlignRight, AlignCenter = range(5) class LayoutBase(object): """ This class is at the core of the layout engine. The two MixIn classes L{VBoxLayoutMixin} and L{HBoxLayoutMixin} specialise on this by reimplementing the C{resizeToContent} and the C{expandContent} methods. Dynamic Layouting ----------------- The layout is calculated in the L{Widget.show} method. Thus if you modify the layout, by adding or removing child widgets for example, you have to call L{Widget.adaptLayout} so that the changes ripple through the widget hierachy. Internals --------- At the core the layout engine works in two passes: Before a root widget loaded by the XML code is shown, its resizeToContent method is called recursively (walking the widget containment relation in post order). This shrinks all HBoxes and VBoxes to their minimum heigt and width. After that the expandContent method is called recursively in the same order, which will re-align the widgets if there is space left AND if a Spacer is contained. Inside bare Container instances (without a Layout MixIn) absolute positioning can be used. """ def __init__(self,align = (AlignLeft,AlignTop), **kwargs): self.align = align self.spacer = None super(LayoutBase,self).__init__(**kwargs) def addSpacer(self,spacer): if self.spacer: raise RuntimeException("Already a Spacer in %s!" % str(self)) self.spacer = spacer spacer.index = len(self.children) def xdelta(self,widget):return 0 def ydelta(self,widget):return 0 def _adjustHeight(self): if self.align[1] == AlignTop:return #dy = 0 if self.align[1] == AlignBottom: y = self.height - self.childarea[1] - self.border_size - self.margins[1] else: y = (self.height - self.childarea[1] - self.border_size - self.margins[1])/2 for widget in self.children: widget.y = y y += self.ydelta(widget) def _adjustHeightWithSpacer(self): pass def _adjustWidth(self): if self.align[0] == AlignLeft:return #dx = 0 if self.align[0] == AlignRight: x = self.width - self.childarea[0] - self.border_size - self.margins[0] else: x = (self.width - self.childarea[0] - self.border_size - self.margins[0])/2 for widget in self.children: widget.x = x x += self.xdelta(widget) def _expandWidthSpacer(self): x = self.border_size + self.margins[0] xdelta = map(self.xdelta,self.children) for widget in self.children[:self.spacer.index]: widget.x = x x += xdelta.pop(0) x = self.width - sum(xdelta) - self.border_size - self.margins[0] for widget in self.children[self.spacer.index:]: widget.x = x x += xdelta.pop(0) def _expandHeightSpacer(self): y = self.border_size + self.margins[1] ydelta = map(self.ydelta,self.children) for widget in self.children[:self.spacer.index]: widget.y = y y += ydelta.pop(0) y = self.height - sum(ydelta) - self.border_size - self.margins[1] for widget in self.children[self.spacer.index:]: widget.y = y y += ydelta.pop(0) class VBoxLayoutMixin(LayoutBase): """ A mixin class for a vertical layout. Do not use directly. """ def __init__(self,**kwargs): super(VBoxLayoutMixin,self).__init__(**kwargs) def resizeToContent(self, recurse = True): max_w = self.getMaxChildrenWidth() x = self.margins[0] + self.border_size y = self.margins[1] + self.border_size for widget in self.children: widget.x = x widget.y = y widget.width = max_w y += widget.height + self.padding #Add the padding for the spacer. if self.spacer: y += self.padding self.height = y + self.margins[1] - self.padding self.width = max_w + 2*x self.childarea = max_w, y - self.padding - self.margins[1] self._adjustHeight() self._adjustWidth() def expandContent(self): if self.spacer: self._expandHeightSpacer() def ydelta(self,widget):return widget.height + self.padding class HBoxLayoutMixin(LayoutBase): """ A mixin class for a horizontal layout. Do not use directly. """ def __init__(self,**kwargs): super(HBoxLayoutMixin,self).__init__(**kwargs) def resizeToContent(self, recurse = True): max_h = self.getMaxChildrenHeight() x = self.margins[0] + self.border_size y = self.margins[1] + self.border_size for widget in self.children: widget.x = x widget.y = y widget.height = max_h x += widget.width + self.padding #Add the padding for the spacer. if self.spacer: x += self.padding self.width = x + self.margins[0] - self.padding self.height = max_h + 2*y self.childarea = x - self.margins[0] - self.padding, max_h self._adjustHeight() self._adjustWidth() def expandContent(self): if self.spacer: self._expandWidthSpacer() def xdelta(self,widget):return widget.width + self.padding class VBox(VBoxLayoutMixin,Container): """ A vertically aligned box - for containement of child widgets. Widgets added to this container widget, will layout on top of each other. Also the minimal width of the container will be the maximum of the minimal widths of the contained widgets. The default alignment is to the top. This can be changed by adding a Spacer to the widget at any point (but only one!). The spacer will expand, so that widgets above the spacer are aligned to the top, while widgets below the spacer are aligned to the bottom. """ def __init__(self,padding=5,**kwargs): super(VBox,self).__init__(**kwargs) self.padding = padding class HBox(HBoxLayoutMixin,Container): """ A horizontally aligned box - for containement of child widgets. Please see L{VBox} for details - just change the directions :-). """ def __init__(self,padding=5,**kwargs): super(HBox,self).__init__(**kwargs) self.padding = padding class Window(VBoxLayoutMixin,Container): """ A L{VBox} with a draggable title bar aka a window New Attributes ============== - title: The Caption of the window - titlebar_height: The height of the window title bar """ ATTRIBUTES = Container.ATTRIBUTES + [ UnicodeAttr('title'), IntAttr('titlebar_height') ] def __init__(self,title=u"title",titlebar_height=0,**kwargs): super(Window,self).__init__(_real_widget = fife.Window(), **kwargs) if titlebar_height == 0: titlebar_height = self.real_font.getHeight() + 4 self.titlebar_height = titlebar_height self.title = title # Override explicit positioning self.position_technique = "automatic" def _getTitle(self): return gui2text(self.real_widget.getCaption()) def _setTitle(self,text): self.real_widget.setCaption(text2gui(text)) title = property(_getTitle,_setTitle) def _getTitleBarHeight(self): return self.real_widget.getTitleBarHeight() def _setTitleBarHeight(self,h): self.real_widget.setTitleBarHeight(h) titlebar_height = property(_getTitleBarHeight,_setTitleBarHeight) # Hackish way of hiding that title bar height in the perceived height. # Fixes VBox calculation def _setHeight(self,h): h = max(self.min_size[1],h) h = min(self.max_size[1],h) self.real_widget.setHeight(h + self.titlebar_height) def _getHeight(self): return self.real_widget.getHeight() - self.titlebar_height height = property(_getHeight,_setHeight) # Spacer class Spacer(object): """ A spacer represents expandable 'whitespace' in the GUI. In a XML file you can get this by adding a <Spacer /> inside a VBox or HBox element (Windows implicitly are VBox elements). The effect is, that elements before the spacer will be left (top) and elements after the spacer will be right (bottom) aligned. There can only be one spacer in VBox (HBox). """ def __init__(self,parent=None,**kwargs): self._parent = parent def __str__(self): return "Spacer(parent.name='%s')" % getattr(self._parent,'name','None') def __repr__(self): return "<Spacer(parent.name='%s') at %x>" % (getattr(self._parent,'name','None'),id(self))