comparison engine/extensions/pychan/widgets/layout.py @ 255:51cc05d862f2

Merged editor_rewrite branch to trunk. This contains changes that may break compatibility against existing clients. For a list of changes that may affect your client, see: http://wiki.fifengine.de/Changes_to_pychan_and_FIFE_in_editor_rewrite_branch
author cheesesucker@33b003aa-7bff-0310-803a-e67f0ece8222
date Mon, 08 Jun 2009 16:00:02 +0000
parents 1cc51d145af9
children d8bcff5f7222
comparison
equal deleted inserted replaced
254:10b5f7f36dd4 255:51cc05d862f2
1 # -*- coding: utf-8 -*- 1 # -*- coding: utf-8 -*-
2 2
3 from common import * 3 from pychan.attrs import IntAttr
4
5 AlignTop, AlignBottom, AlignLeft, AlignRight, AlignCenter = range(5)
6 def isLayouted(widget):
7 return isinstance(widget,LayoutBase)
4 8
5 class LayoutBase(object): 9 class LayoutBase(object):
6 """ 10 """
7 This class is at the core of the layout engine. The two MixIn classes L{VBoxLayoutMixin} 11 This class is at the core of the layout engine. The two MixIn classes L{VBoxLayoutMixin}
8 and L{HBoxLayoutMixin} specialise on this by reimplementing the C{resizeToContent} and 12 and L{HBoxLayoutMixin} specialise on this by reimplementing the C{resizeToContent} and
10 14
11 Dynamic Layouting 15 Dynamic Layouting
12 ----------------- 16 -----------------
13 17
14 The layout is calculated in the L{Widget.show} method. Thus if you modify the layout, 18 The layout is calculated in the L{Widget.show} method. Thus if you modify the layout,
15 by adding or removing child widgets for example, you have to call L{Widget.adaptLayout} 19 by adding or removing child widgets for example, you have to call L{widgets.Widget.adaptLayout}
16 so that the changes ripple through the widget hierachy. 20 so that the changes ripple through the widget hierachy.
17 21
18 Internals 22 Internals
19 --------- 23 ---------
20 24
29 Inside bare Container instances (without a Layout MixIn) absolute positioning 33 Inside bare Container instances (without a Layout MixIn) absolute positioning
30 can be used. 34 can be used.
31 """ 35 """
32 def __init__(self,align = (AlignLeft,AlignTop), **kwargs): 36 def __init__(self,align = (AlignLeft,AlignTop), **kwargs):
33 self.align = align 37 self.align = align
34 self.spacer = None 38 self.spacer = []
35 super(LayoutBase,self).__init__(**kwargs) 39 super(LayoutBase,self).__init__(**kwargs)
36 40
37 def addSpacer(self,spacer): 41 def addSpacer(self,spacer):
38 if self.spacer: 42 self.spacer.append(spacer)
39 raise RuntimeException("Already a Spacer in %s!" % str(self))
40 self.spacer = spacer
41 spacer.index = len(self.children) 43 spacer.index = len(self.children)
42 44
43 def xdelta(self,widget):return 0 45 def xdelta(self,widget):return 0
44 def ydelta(self,widget):return 0 46 def ydelta(self,widget):return 0
45 47
46 def _adjustHeight(self): 48 def _applyHeight(self, spacers = []):
47 if self.align[1] == AlignTop:return #dy = 0 49 y = self.border_size + self.margins[1]
48 if self.align[1] == AlignBottom: 50 ydelta = map(self.ydelta,self.children)
49 y = self.height - self.childarea[1] - self.border_size - self.margins[1] 51 for index, child in enumerate(self.children):
50 else: 52 while spacers and spacers[0].index == index:
51 y = (self.height - self.childarea[1] - self.border_size - self.margins[1])/2 53 y += spacers.pop(0).size
52 for widget in self.children: 54 child.y = y
53 widget.y = y 55 y += ydelta.pop(0)
54 y += self.ydelta(widget)
55 56
56 def _adjustHeightWithSpacer(self): 57 def _adjustHeightWithSpacer(self):
57 pass 58 pass
58 59
59 def _adjustWidth(self): 60 def _applyWidth(self, spacers = []):
60 if self.align[0] == AlignLeft:return #dx = 0
61 if self.align[0] == AlignRight:
62 x = self.width - self.childarea[0] - self.border_size - self.margins[0]
63 else:
64 x = (self.width - self.childarea[0] - self.border_size - self.margins[0])/2
65 for widget in self.children:
66 widget.x = x
67 x += self.xdelta(widget)
68
69 def _expandWidthSpacer(self):
70 x = self.border_size + self.margins[0] 61 x = self.border_size + self.margins[0]
71 xdelta = map(self.xdelta,self.children) 62 xdelta = map(self.xdelta,self.children)
72 63 for index, child in enumerate(self.children):
73 for widget in self.children[:self.spacer.index]: 64 while spacers and spacers[0].index == index:
74 widget.x = x 65 x += spacers.pop(0).size
66 child.x = x
75 x += xdelta.pop(0) 67 x += xdelta.pop(0)
76 68
77 x = self.width - sum(xdelta) - self.border_size - self.margins[0] 69 def _expandWidthSpacer(self):
78 for widget in self.children[self.spacer.index:]: 70 xdelta = map(self.xdelta,self.children)
79 widget.x = x 71 xdelta += [spacer.min_size for spacer in self.spacer]
80 x += xdelta.pop(0) 72
73 available_space = self.width - 2*self.margins[0] - 2*self.border_size - self._extra_border[0]
74
75 used_space = sum(xdelta)
76 if self.children:
77 used_space -= self.padding
78 if used_space >= available_space:
79 return
80
81 expandable_items = self._getExpanders(vertical=False)
82 #print "AS/US - before",self,[o.width for o in expandable_items]
83 #print "SPACERS",self.spacer
84
85 index = 0
86 while used_space < available_space and expandable_items:
87 index = index % len(expandable_items)
88
89 expander = expandable_items[index]
90 old_width = expander.width
91 expander.width += 1
92 if old_width == expander.width:
93 expandable_items.pop(index)
94 else:
95 used_space += 1
96 index += 1
97
98 #print "AS/US - after",self,[o.width for o in expandable_items]
99 #print "SPACERS",self.spacer
100 self._applyWidth(spacers = self.spacer[:])
81 101
82 def _expandHeightSpacer(self): 102 def _expandHeightSpacer(self):
83 y = self.border_size + self.margins[1]
84 ydelta = map(self.ydelta,self.children) 103 ydelta = map(self.ydelta,self.children)
85 104 ydelta += [spacer.min_size for spacer in self.spacer]
86 for widget in self.children[:self.spacer.index]: 105
87 widget.y = y 106 available_space = self.height - 2*self.margins[1] - 2*self.border_size - self._extra_border[1]
88 y += ydelta.pop(0) 107
89 108 used_space = sum(ydelta)
90 y = self.height - sum(ydelta) - self.border_size - self.margins[1] 109 if self.children:
91 for widget in self.children[self.spacer.index:]: 110 used_space -= self.padding
92 widget.y = y 111
93 y += ydelta.pop(0) 112 if used_space >= available_space:
94 113 return
114
115 expandable_items = self._getExpanders(vertical=True)
116 #print "AS/US - before",self,[o.height for o in expandable_items]
117
118 index = 0
119 while used_space < available_space and expandable_items:
120 index = index % len(expandable_items)
121
122 expander = expandable_items[index]
123 old_width = expander.height
124 expander.height += 1
125 if old_width == expander.height:
126 expandable_items.pop(index)
127 else:
128 used_space += 1
129 index += 1
130
131 #print "AS/US - after",self,[o.height for o in expandable_items]
132 self._applyHeight(spacers = self.spacer[:])
133
134
135 def _getExpanders(self,vertical=True):
136 expanders = []
137 spacers = self.spacer[:]
138 for index, child in enumerate(self.children):
139 if spacers and spacers[0].index == index:
140 expanders.append( spacers.pop(0) )
141 if child.vexpand and vertical:
142 expanders += [child]*child.vexpand
143 if child.hexpand and not vertical:
144 expanders += [child]*child.hexpand
145 return expanders + spacers
146
147 def _resetSpacers(self):
148 for spacer in self.spacer:
149 spacer.size = 0
95 150
96 class VBoxLayoutMixin(LayoutBase): 151 class VBoxLayoutMixin(LayoutBase):
97 """ 152 """
98 A mixin class for a vertical layout. Do not use directly. 153 A mixin class for a vertical layout. Do not use directly.
99 """ 154 """
100 def __init__(self,**kwargs): 155 def __init__(self,**kwargs):
101 super(VBoxLayoutMixin,self).__init__(**kwargs) 156 super(VBoxLayoutMixin,self).__init__(**kwargs)
102 157
103 def resizeToContent(self, recurse = True): 158 def resizeToContent(self, recurse = True):
159 self._resetSpacers()
160
104 max_w = self.getMaxChildrenWidth() 161 max_w = self.getMaxChildrenWidth()
105 x = self.margins[0] + self.border_size 162 x = self.margins[0] + self.border_size
106 y = self.margins[1] + self.border_size 163 y = self.margins[1] + self.border_size
107 for widget in self.children: 164 for widget in self.children:
108 widget.x = x
109 widget.y = y
110 widget.width = max_w 165 widget.width = max_w
111 y += widget.height + self.padding 166 y += widget.height + self.padding
112 167
113 #Add the padding for the spacer. 168 if self.children:
114 if self.spacer: 169 y -= self.padding
115 y += self.padding 170
116 171 y += sum([spacer.min_size for spacer in self.spacer])
117 self.height = y + self.margins[1] - self.padding 172
118 self.width = max_w + 2*x 173 self.height = y + self.margins[1] + self.border_size + self._extra_border[1]
119 self.childarea = max_w, y - self.padding - self.margins[1] 174 self.width = max_w + 2*x + self._extra_border[0]
120 175
121 self._adjustHeight() 176 self._applyHeight(spacers = self.spacer[:])
122 self._adjustWidth() 177 self._applyWidth()
123 178
124 def expandContent(self): 179 def expandContent(self):
125 if self.spacer: 180 self._expandHeightSpacer()
126 self._expandHeightSpacer() 181 if not self.hexpand and self.parent:return
182 for widget in self.children:
183 widget.width = self.width - 2*self.margins[0] - 2*self.border_size - self._extra_border[0]
184
127 185
128 def ydelta(self,widget):return widget.height + self.padding 186 def ydelta(self,widget):return widget.height + self.padding
129 187
130 class HBoxLayoutMixin(LayoutBase): 188 class HBoxLayoutMixin(LayoutBase):
131 """ 189 """
133 """ 191 """
134 def __init__(self,**kwargs): 192 def __init__(self,**kwargs):
135 super(HBoxLayoutMixin,self).__init__(**kwargs) 193 super(HBoxLayoutMixin,self).__init__(**kwargs)
136 194
137 def resizeToContent(self, recurse = True): 195 def resizeToContent(self, recurse = True):
196 self._resetSpacers()
197
138 max_h = self.getMaxChildrenHeight() 198 max_h = self.getMaxChildrenHeight()
139 x = self.margins[0] + self.border_size 199 x = self.margins[0] + self.border_size
140 y = self.margins[1] + self.border_size 200 y = self.margins[1] + self.border_size
141 for widget in self.children: 201 for widget in self.children:
142 widget.x = x
143 widget.y = y
144 widget.height = max_h 202 widget.height = max_h
145 x += widget.width + self.padding 203 x += widget.width + self.padding
146 204 if self.children:
147 #Add the padding for the spacer. 205 x -= self.padding
148 if self.spacer: 206 x += sum([spacer.min_size for spacer in self.spacer])
149 x += self.padding 207
150 208 self.width = x + self.margins[0] + self._extra_border[0]
151 self.width = x + self.margins[0] - self.padding 209 self.height = max_h + 2*y + self._extra_border[1]
152 self.height = max_h + 2*y 210
153 self.childarea = x - self.margins[0] - self.padding, max_h 211 self._applyHeight()
154 212 self._applyWidth(spacers = self.spacer[:])
155 self._adjustHeight()
156 self._adjustWidth()
157 213
158 def expandContent(self): 214 def expandContent(self):
159 if self.spacer: 215 self._expandWidthSpacer()
160 self._expandWidthSpacer() 216 if not self.vexpand and self.parent:return
217 for widget in self.children:
218 widget.height = self.height - 2*self.margins[1] - 2*self.border_size - self._extra_border[1]
161 219
162 def xdelta(self,widget):return widget.width + self.padding 220 def xdelta(self,widget):return widget.width + self.padding
221
222 class Spacer(object):
223 """ A spacer represents expandable or fixed 'whitespace' in the GUI.
224
225 In a XML file you can get this by adding a <Spacer /> inside a VBox or
226 HBox element (Windows implicitly are VBox elements).
227
228 Attributes
229 ----------
230
231 As with widgets a number of attributes can be set on a spacer (inside the XML definition).
232
233 - min_size: Int: The minimal size this Spacer is allowed to have.
234 - max_size: Int: The maximal size this Spacer is allowed to have.
235 - fixed_size: Int: Set min_size and max_size to the same vale - effectively a Fixed size spacer.
236
237 """
238
239 ATTRIBUTES = [
240 IntAttr('min_size'), IntAttr('size'), IntAttr('max_size'),
241 IntAttr('fixed_size'),
242 ]
243
244 def __init__(self,parent=None,**kwargs):
245 self.parent = parent
246 self.min_size = 0
247 self.max_size = 1000
248 self.size = 0
249
250 def __str__(self):
251 return "Spacer(parent.name='%s')" % getattr(self.__parent,'name','None')
252
253 def __repr__(self):
254 return "<Spacer(parent.name='%s') at %x>" % (getattr(self.__parent,'name','None'),id(self))
255
256 def _getSize(self):
257 self.size = self._size
258 return self._size
259 def _setSize(self,size):
260 self._size = max(self.min_size, min(self.max_size,size))
261 size = property(_getSize,_setSize)
262
263 # Alias for size
264 width = property(_getSize,_setSize)
265 height = property(_getSize,_setSize)
266
267 def _setFixedSize(self,size):
268 self.min_size = self.max_size = size
269 self.size = size
270 fixed_size = property(fset=_setFixedSize)
271
272 def _isExpanding(self):
273 if self.min_size < self.max_size:
274 return 1
275 return 0
276 vexpand = property(_isExpanding)
277 hexpand = property(_isExpanding)