Mercurial > fife-parpg
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) |