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