comparison engine/python/fife/extensions/pychan/widgets/layout.py @ 378:64738befdf3b

bringing in the changes from the build_system_rework branch in preparation for the 0.3.0 release. This commit will require the Jan2010 devkit. Clients will also need to be modified to the new way to import fife.
author vtchill@33b003aa-7bff-0310-803a-e67f0ece8222
date Mon, 11 Jan 2010 23:34:52 +0000
parents
children
comparison
equal deleted inserted replaced
377:fe6fb0e0ed23 378:64738befdf3b
1 # -*- coding: utf-8 -*-
2
3 # ####################################################################
4 # Copyright (C) 2005-2009 by the FIFE team
5 # http://www.fifengine.de
6 # This file is part of FIFE.
7 #
8 # FIFE is free software; you can redistribute it and/or
9 # modify it under the terms of the GNU Lesser General Public
10 # License as published by the Free Software Foundation; either
11 # version 2.1 of the License, or (at your option) any later version.
12 #
13 # This library is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 # Lesser General Public License for more details.
17 #
18 # You should have received a copy of the GNU Lesser General Public
19 # License along with this library; if not, write to the
20 # Free Software Foundation, Inc.,
21 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22 # ####################################################################
23
24 from fife.extensions.pychan.attrs import IntAttr
25
26 AlignTop, AlignBottom, AlignLeft, AlignRight, AlignCenter = range(5)
27 def isLayouted(widget):
28 return isinstance(widget,LayoutBase)
29
30 class LayoutBase(object):
31 """
32 This class is at the core of the layout engine. The two MixIn classes L{VBoxLayoutMixin}
33 and L{HBoxLayoutMixin} specialise on this by reimplementing the C{resizeToContent} and
34 the C{expandContent} methods.
35
36 Dynamic Layouting
37 -----------------
38
39 The layout is calculated in the L{Widget.show} method. Thus if you modify the layout,
40 by adding or removing child widgets for example, you have to call L{widgets.Widget.adaptLayout}
41 so that the changes ripple through the widget hierachy.
42
43 Internals
44 ---------
45
46 At the core the layout engine works in two passes:
47
48 Before a root widget loaded by the XML code is shown, its resizeToContent method
49 is called recursively (walking the widget containment relation in post order).
50 This shrinks all HBoxes and VBoxes to their minimum heigt and width.
51 After that the expandContent method is called recursively in the same order,
52 which will re-align the widgets if there is space left AND if a Spacer is contained.
53
54 Inside bare Container instances (without a Layout MixIn) absolute positioning
55 can be used.
56 """
57 def __init__(self,align = (AlignLeft,AlignTop), **kwargs):
58 self.align = align
59 self.spacer = []
60 super(LayoutBase,self).__init__(**kwargs)
61
62 def addSpacer(self,spacer):
63 self.spacer.append(spacer)
64 spacer.index = len(self.children)
65
66 def xdelta(self,widget):return 0
67 def ydelta(self,widget):return 0
68
69 def _applyHeight(self, spacers = []):
70 y = self.border_size + self.margins[1]
71 ydelta = map(self.ydelta,self.children)
72 for index, child in enumerate(self.children):
73 while spacers and spacers[0].index == index:
74 y += spacers.pop(0).size
75 child.y = y
76 y += ydelta.pop(0)
77
78 def _adjustHeightWithSpacer(self):
79 pass
80
81 def _applyWidth(self, spacers = []):
82 x = self.border_size + self.margins[0]
83 xdelta = map(self.xdelta,self.children)
84 for index, child in enumerate(self.children):
85 while spacers and spacers[0].index == index:
86 x += spacers.pop(0).size
87 child.x = x
88 x += xdelta.pop(0)
89
90 def _expandWidthSpacer(self):
91 xdelta = map(self.xdelta,self.children)
92 xdelta += [spacer.min_size for spacer in self.spacer]
93
94 available_space = self.width - 2*self.margins[0] - 2*self.border_size - self._extra_border[0]
95
96 used_space = sum(xdelta)
97 if self.children:
98 used_space -= self.padding
99 if used_space >= available_space:
100 return
101
102 expandable_items = self._getExpanders(vertical=False)
103 #print "AS/US - before",self,[o.width for o in expandable_items]
104 #print "SPACERS",self.spacer
105
106 index = 0
107 n = len(expandable_items)
108 while used_space < available_space and expandable_items:
109 index = index % n
110 delta = (available_space - used_space) / n
111 if delta == 0:
112 delta = 1
113
114 expander = expandable_items[index]
115 old_width = expander.width
116 expander.width += delta
117 delta = expander.width - old_width
118 if delta == 0:
119 expandable_items.pop(index)
120 n -= 1
121 else:
122 used_space += delta
123 index += 1
124 #print "AS/US - after",self,[o.width for o in expandable_items]
125 #print "SPACERS",self.spacer
126 self._applyWidth(spacers = self.spacer[:])
127
128 def _expandHeightSpacer(self):
129 ydelta = map(self.ydelta,self.children)
130 ydelta += [spacer.min_size for spacer in self.spacer]
131
132 available_space = self.height - 2*self.margins[1] - 2*self.border_size - self._extra_border[1]
133
134 used_space = sum(ydelta)
135 if self.children:
136 used_space -= self.padding
137
138 if used_space >= available_space:
139 return
140
141 expandable_items = self._getExpanders(vertical=True)
142 #print "AS/US - before",self,[o.height for o in expandable_items]
143
144 index = 0
145 n = len(expandable_items)
146 while used_space < available_space and expandable_items:
147 index = index % n
148 delta = (available_space - used_space) / n
149 if delta == 0:
150 delta = 1
151
152 expander = expandable_items[index]
153 old_height = expander.height
154 expander.height += delta
155 delta = expander.height - old_height
156 if delta == 0:
157 expandable_items.pop(index)
158 n -= 1
159 else:
160 used_space += delta
161 index += 1
162
163 #print "AS/US - after",self,[o.height for o in expandable_items]
164 self._applyHeight(spacers = self.spacer[:])
165
166
167 def _getExpanders(self,vertical=True):
168 expanders = []
169 spacers = self.spacer[:]
170 for index, child in enumerate(self.children):
171 if spacers and spacers[0].index == index:
172 expanders.append( spacers.pop(0) )
173 if child.vexpand and vertical:
174 expanders += [child]*child.vexpand
175 if child.hexpand and not vertical:
176 expanders += [child]*child.hexpand
177 return expanders + spacers
178
179 def _resetSpacers(self):
180 for spacer in self.spacer:
181 spacer.size = 0
182
183 class VBoxLayoutMixin(LayoutBase):
184 """
185 A mixin class for a vertical layout. Do not use directly.
186 """
187 def __init__(self,**kwargs):
188 super(VBoxLayoutMixin,self).__init__(**kwargs)
189
190 def resizeToContent(self, recurse = True):
191 self._resetSpacers()
192
193 max_w = self.getMaxChildrenWidth()
194 x = self.margins[0] + self.border_size
195 y = self.margins[1] + self.border_size
196 for widget in self.children:
197 widget.width = max_w
198 y += widget.height + self.padding
199
200 if self.children:
201 y -= self.padding
202
203 y += sum([spacer.min_size for spacer in self.spacer])
204
205 self.height = y + self.margins[1] + self.border_size + self._extra_border[1]
206 self.width = max_w + 2*x + self._extra_border[0]
207
208 self._applyHeight(spacers = self.spacer[:])
209 self._applyWidth()
210
211 def expandContent(self):
212 self._expandHeightSpacer()
213 if not self.hexpand and self.parent:return
214 for widget in self.children:
215 widget.width = self.width - 2*self.margins[0] - 2*self.border_size - self._extra_border[0]
216
217
218 def ydelta(self,widget):return widget.height + self.padding
219
220 class HBoxLayoutMixin(LayoutBase):
221 """
222 A mixin class for a horizontal layout. Do not use directly.
223 """
224 def __init__(self,**kwargs):
225 super(HBoxLayoutMixin,self).__init__(**kwargs)
226
227 def resizeToContent(self, recurse = True):
228 self._resetSpacers()
229
230 max_h = self.getMaxChildrenHeight()
231 x = self.margins[0] + self.border_size
232 y = self.margins[1] + self.border_size
233 for widget in self.children:
234 widget.height = max_h
235 x += widget.width + self.padding
236 if self.children:
237 x -= self.padding
238 x += sum([spacer.min_size for spacer in self.spacer])
239
240 self.width = x + self.margins[0] + self._extra_border[0]
241 self.height = max_h + 2*y + self._extra_border[1]
242
243 self._applyHeight()
244 self._applyWidth(spacers = self.spacer[:])
245
246 def expandContent(self):
247 self._expandWidthSpacer()
248 if not self.vexpand and self.parent:return
249 for widget in self.children:
250 widget.height = self.height - 2*self.margins[1] - 2*self.border_size - self._extra_border[1]
251
252 def xdelta(self,widget):return widget.width + self.padding
253
254 class Spacer(object):
255 """ A spacer represents expandable or fixed 'whitespace' in the GUI.
256
257 In a XML file you can get this by adding a <Spacer /> inside a VBox or
258 HBox element (Windows implicitly are VBox elements).
259
260 Attributes
261 ----------
262
263 As with widgets a number of attributes can be set on a spacer (inside the XML definition).
264
265 - min_size: Int: The minimal size this Spacer is allowed to have.
266 - max_size: Int: The maximal size this Spacer is allowed to have.
267 - fixed_size: Int: Set min_size and max_size to the same vale - effectively a Fixed size spacer.
268
269 """
270
271 ATTRIBUTES = [
272 IntAttr('min_size'), IntAttr('size'), IntAttr('max_size'),
273 IntAttr('fixed_size'),
274 ]
275
276 def __init__(self,parent=None,**kwargs):
277 self.parent = parent
278 self.min_size = 0
279 self.max_size = 1000
280 self.size = 0
281
282 def __str__(self):
283 return "Spacer(parent.name='%s')" % getattr(self.parent,'name','None')
284
285 def __repr__(self):
286 return "<Spacer(parent.name='%s') at %x>" % (getattr(self.parent,'name','None'),id(self))
287
288 def _getSize(self):
289 self.size = self._size
290 return self._size
291 def _setSize(self,size):
292 self._size = max(self.min_size, min(self.max_size,size))
293 size = property(_getSize,_setSize)
294
295 # Alias for size
296 width = property(_getSize,_setSize)
297 height = property(_getSize,_setSize)
298
299 def _setFixedSize(self,size):
300 self.min_size = self.max_size = size
301 self.size = size
302 fixed_size = property(fset=_setFixedSize)
303
304 def _isExpanding(self):
305 if self.min_size < self.max_size:
306 return 1
307 return 0
308 vexpand = property(_isExpanding)
309 hexpand = property(_isExpanding)