64074d654a1304ca6be0595eeb1bb3e845ed4eae
[minwii.git] / src / pgu / gui / area.py
1 """
2 """
3 import os
4
5 import pguglobals
6 from const import *
7 import surface
8 import container, table
9 import group
10 import basic, button, slider
11 from pygame.font import Font
12
13 class SlideBox(container.Container):
14 """A scrollable area with no scrollbars.
15
16 <pre>SlideBox(widget,width,height)</pre>
17
18 <dl>
19 <dt>widget<dd>widget to be able to scroll around
20 <dt>width, height<dd>size of scrollable area
21 </dl>
22
23 <strong>Example</strong>
24 <code>
25 c = SlideBox(w,100,100)
26 c.offset = (10,10)
27 c.repaint()
28 </code>
29
30 """
31
32 def __init__(self, widget, width, height, **params):
33 params.setdefault('width', width)
34 params.setdefault('height', height)
35 container.Container.__init__(self, **params)
36 self.offset = [0, 0]
37 self.widget = widget
38
39 def __setattr__(self,k,v):
40 if k == 'widget':
41 if hasattr(self,'widget'):
42 self.remove(self.widget)
43 self.add(v,0,0)
44 self.__dict__[k] = v
45
46
47 def paint(self, s):
48 #if not hasattr(self,'surface'):
49 self.surface = pygame.Surface((self.max_rect.w,self.max_rect.h),0,s)
50 #self.surface.fill((0,0,0,0))
51 pguglobals.app.theme.render(self.surface,self.style.background,pygame.Rect(0,0,self.max_rect.w,self.max_rect.h))
52 self.bkgr = pygame.Surface((s.get_width(),s.get_height()),0,s)
53 self.bkgr.blit(s,(0,0))
54 container.Container.paint(self,self.surface)
55 s.blit(self.surface,(-self.offset[0],-self.offset[1]))
56 self._offset = self.offset[:]
57 return
58
59 def paint_for_when_pygame_supports_other_tricks(self,s):
60 #this would be ideal if pygame had support for it!
61 #and if pgu also had a paint(self,s,rect) method to paint small parts
62 sr = (self.offset[0],self.offset[1],self.max_rect.w,self.max_rect.h)
63 cr = (-self.offset[0],-self.offset[1],s.get_width(),s.get_height())
64 s2 = s.subsurface(sr)
65 s2.set_clip(cr)
66 container.Container.paint(self,s2)
67
68 def proxy_paint(self, s):
69 container.Container.paint(self, surface.ProxySurface(parent=None,
70 rect=self.max_rect,
71 real_surface=s,
72 offset=self.offset))
73 def update(self, s):
74 rects = container.Container.update(self,self.surface)
75
76 rets = []
77 s_rect = pygame.Rect(0,0,s.get_width(),s.get_height())
78
79 if self.offset == self._offset:
80 for r in rects:
81 r2 = r.move((-self.offset[0],-self.offset[1]))
82 if r2.colliderect(s_rect):
83 s.blit(self.surface.subsurface(r),r2)
84 rets.append(r2)
85 else:
86 s.blit(self.bkgr,(0,0))
87 sub = pygame.Rect(self.offset[0],self.offset[1],min(s.get_width(),self.max_rect.w-self.offset[0]),min(s.get_height(),self.max_rect.h-self.offset[1]))
88 # print sub
89 # print self.surface.get_width(),self.surface.get_height()
90 # print s.get_width(),s.get_height()
91 # print self.offset
92 # print self.style.width,self.style.height
93 s.blit(self.surface.subsurface(sub),(0,0))
94 rets.append(s_rect)
95 self._offset = self.offset[:]
96 return rets
97
98 def proxy_update(self, s):
99 rects = container.Container.update(self, surface.ProxySurface(parent=None,
100 rect=self.max_rect,
101 real_surface=s,
102 offset=self.offset))
103 result = []
104 for r in rects: result.append(pygame.Rect(r).move(self.offset))
105 return result
106
107 def resize(self, width=None, height=None):
108 container.Container.resize(self)
109 self.max_rect = pygame.Rect(self.widget.rect)
110 #self.max_rect.w = max(self.max_rect.w,self.style.width)
111 #self.max_rect.h = max(self.max_rect.h,self.style.height)
112 return self.style.width,self.style.height
113 #self.rect = pygame.Rect(self.rect[0], self.rect[1], self.style.width, self.style.height)
114
115 def event(self, e):
116 if e.type in [MOUSEBUTTONDOWN, MOUSEBUTTONUP, MOUSEMOTION]:
117 pos = (e.pos[0] + self.offset[0], e.pos[1] + self.offset[1])
118 if self.max_rect.collidepoint(pos):
119 e_params = {'pos': pos }
120 if e.type == MOUSEMOTION:
121 e_params['buttons'] = e.buttons
122 e_params['rel'] = e.rel
123 else:
124 e_params['button'] = e.button
125 e = pygame.event.Event(e.type, e_params)
126 container.Container.event(self, e)
127
128 #class SlideBox(Area):
129 # def __init__(self,*args,**params):
130 # print 'gui.SlideBox','Scheduled to be renamed to Area.'
131 # Area.__init__(self,*args,**params)
132
133 class ScrollArea(table.Table):
134 """A scrollable area with scrollbars.
135
136 <pre>ScrollArea(widget,width,height,hscrollbar=True)</pre>
137
138 <dl>
139 <dt>widget<dd>widget to be able to scroll around
140 <dt>width, height<dd>size of scrollable area. Set either to 0 to default to size of widget.
141 <dt>hscrollbar<dd>set to False if you do not wish to have a horizontal scrollbar
142 <dt>vscrollbar<dd>set to False if you do not wish to have a vertical scrollbar
143 <dt>step<dd>set to how far clicks on the icons will step
144 </dl>
145 """
146 def __init__(self, widget, width=0, height=0, hscrollbar=True, vscrollbar=True,step=24, **params):
147 w= widget
148 params.setdefault('cls', 'scrollarea')
149 table.Table.__init__(self, width=width,height=height,**params)
150
151 self.sbox = SlideBox(w, width=width, height=height, cls=self.cls+".content")
152 self.widget = w
153 self.vscrollbar = vscrollbar
154 self.hscrollbar = hscrollbar
155
156 self.step = step
157
158 def __setattr__(self,k,v):
159 if k == 'widget':
160 self.sbox.widget = v
161 self.__dict__[k] = v
162
163 def resize(self,width=None,height=None):
164 widget = self.widget
165 box = self.sbox
166
167 #self.clear()
168 table.Table.clear(self)
169 #print 'resize',self,self._rows
170
171 self.tr()
172 self.td(box)
173
174 widget.rect.w, widget.rect.h = widget.resize()
175 my_width,my_height = self.style.width,self.style.height
176 if not my_width:
177 my_width = widget.rect.w
178 self.hscrollbar = False
179 if not my_height:
180 my_height = widget.rect.h
181 self.vscrollbar = False
182
183 box.style.width,box.style.height = my_width,my_height #self.style.width,self.style.height
184
185 box.rect.w,box.rect.h = box.resize()
186
187 #print widget.rect
188 #print box.rect
189 #r = table.Table.resize(self,width,height)
190 #print r
191 #return r
192
193 #print box.offset
194
195 # #this old code automatically adds in a scrollbar if needed
196 # #but it doesn't always work
197 # self.vscrollbar = None
198 # if widget.rect.h > box.rect.h:
199 # self.vscrollbar = slider.VScrollBar(box.offset[1],0, 65535, 0,step=self.step)
200 # self.td(self.vscrollbar)
201 # self.vscrollbar.connect(CHANGE, self._vscrollbar_changed, None)
202 #
203 # vs = self.vscrollbar
204 # vs.rect.w,vs.rect.h = vs.resize()
205 # box.style.width = self.style.width - vs.rect.w
206 #
207 #
208 # self.hscrollbar = None
209 # if widget.rect.w > box.rect.w:
210 # self.hscrollbar = slider.HScrollBar(box.offset[0], 0,65535, 0,step=self.step)
211 # self.hscrollbar.connect(CHANGE, self._hscrollbar_changed, None)
212 # self.tr()
213 # self.td(self.hscrollbar)
214 #
215 # hs = self.hscrollbar
216 # hs.rect.w,hs.rect.h = hs.resize()
217 # box.style.height = self.style.height - hs.rect.h
218
219 xt,xr,xb,xl = pguglobals.app.theme.getspacing(box)
220
221
222 if self.vscrollbar:
223 self.vscrollbar = slider.VScrollBar(box.offset[1],0, 65535, 0,step=self.step)
224 self.td(self.vscrollbar)
225 self.vscrollbar.connect(CHANGE, self._vscrollbar_changed, None)
226
227 vs = self.vscrollbar
228 vs.rect.w,vs.rect.h = vs.resize()
229 if self.style.width:
230 box.style.width = self.style.width - (vs.rect.w + xl+xr)
231
232 if self.hscrollbar:
233 self.hscrollbar = slider.HScrollBar(box.offset[0], 0,65535, 0,step=self.step)
234 self.hscrollbar.connect(CHANGE, self._hscrollbar_changed, None)
235 self.tr()
236 self.td(self.hscrollbar)
237
238 hs = self.hscrollbar
239 hs.rect.w,hs.rect.h = hs.resize()
240 if self.style.height:
241 box.style.height = self.style.height - (hs.rect.h + xt + xb)
242
243 if self.hscrollbar:
244 hs = self.hscrollbar
245 hs.min = 0
246 hs.max = widget.rect.w - box.style.width
247 hs.style.width = box.style.width
248 hs.size = hs.style.width * box.style.width / max(1,widget.rect.w)
249 else:
250 box.offset[0] = 0
251
252 if self.vscrollbar:
253 vs = self.vscrollbar
254 vs.min = 0
255 vs.max = widget.rect.h - box.style.height
256 vs.style.height = box.style.height
257 vs.size = vs.style.height * box.style.height / max(1,widget.rect.h)
258 else:
259 box.offset[1] = 0
260
261 #print self.style.width,box.style.width, hs.style.width
262
263 r = table.Table.resize(self,width,height)
264 return r
265
266 def x_resize(self, width=None, height=None):
267 w,h = table.Table.resize(self, width, height)
268 if self.hscrollbar:
269 if self.widget.rect.w <= self.sbox.rect.w:
270 self.hscrollbar.size = self.hscrollbar.style.width
271 else:
272 self.hscrollbar.size = max(20,self.hscrollbar.style.width * self.sbox.rect.w / self.widget.rect.w)
273 self._hscrollbar_changed(None)
274 if self.widget.rect.h <= self.sbox.rect.h:
275 self.vscrollbar.size = self.vscrollbar.style.height
276 else:
277 self.vscrollbar.size = max(20,self.vscrollbar.style.height * self.sbox.rect.h / self.widget.rect.h)
278 self._vscrollbar_changed(None)
279 return w,h
280
281 def _vscrollbar_changed(self, xxx):
282 #y = (self.widget.rect.h - self.sbox.rect.h) * self.vscrollbar.value / 1000
283 #if y >= 0: self.sbox.offset[1] = -y
284 self.sbox.offset[1] = self.vscrollbar.value
285 self.sbox.reupdate()
286
287 def _hscrollbar_changed(self, xxx):
288 #x = (self.widget.rect.w - self.sbox.rect.w) * self.hscrollbar.value / 1000
289 #if x >= 0: self.sbox.offset[0] = -x
290 self.sbox.offset[0] = self.hscrollbar.value
291 self.sbox.reupdate()
292
293
294 def set_vertical_scroll(self, percents):
295 #if not self.vscrollbar: return
296 if not hasattr(self.vscrollbar,'value'): return
297 self.vscrollbar.value = percents #min(max(percents*10, 0), 1000)
298 self._vscrollbar_changed(None)
299
300 def set_horizontal_scroll(self, percents):
301 #if not self.hscrollbar: return
302 if not hasattr(self.hscrollbar,'value'): return
303 self.hscrollbar.value = percents #min(max(percents*10, 0), 1000)
304 self._hscrollbar_changed(None)
305
306 def event(self, e):
307 #checking for event recipient
308 if (table.Table.event(self, e)):
309 return True
310
311 #mouse wheel scrolling
312 if self.vscrollbar:
313 if not hasattr(self.vscrollbar,'value'):
314 return False
315
316 if e.type == pygame.locals.MOUSEBUTTONDOWN:
317 if e.button == 4: #wheel up
318 self.vscrollbar._click(-1)
319 return True
320 elif e.button == 5: #wheel down
321 self.vscrollbar._click(1)
322 return True
323 return False
324
325
326
327
328 class _List_Item(button._button):
329 def __init__(self,label=None,image=None,value=None,**params): #TODO label= could conflict with the module label
330 #param image: an imagez.Image object (optional)
331 #param text: a string object
332 params.setdefault('cls','list.item')
333 button._button.__init__(self,**params)
334 self.group = None
335 self.value = value #(self, value)
336 self.widget = None
337
338 if type(label) == str:
339 label = basic.Label(label, cls=self.cls+".label")
340
341 if image and label:
342 self.widget = container.Container()
343 self.widget.add(image, 0, 0)
344 #HACK: improper use of .resize()
345 image.rect.w,image.rect.h = image.resize()
346 self.widget.add(label, image.rect.w, 0)
347 elif image: self.widget = image
348 elif label: self.widget = label
349
350 self.pcls = ""
351
352 def resize(self,width=None,height=None):
353 self.widget.rect.w,self.widget.rect.h = self.widget.resize()
354 return self.widget.rect.w,self.widget.rect.h
355 # self.widget._resize()
356 # self.rect.w,self.rect.h = self.widget.rect_margin.w,self.widget.rect_margin.h
357
358 def event(self,e):
359 button._button.event(self,e)
360 if self.group.value == self.value: self.pcls = "down"
361
362 def paint(self,s):
363 if self.group.value == self.value: self.pcls = "down"
364 self.widget.paint(surface.subsurface(s,self.widget.rect))
365
366 def click(self):
367 self.group.value = self.value
368 for w in self.group.widgets:
369 if w != self: w.pcls = ""
370
371
372
373 class List(ScrollArea):
374 """A list of items in an area.
375
376 <p>This widget can be a form element, it has a value set to whatever item is selected.</p>
377
378 <pre>List(width,height)</pre>
379 """
380 def _change(self, value):
381 self.value = self.group.value
382 self.send(CHANGE)
383
384 def __init__(self, width, height, **params):
385 params.setdefault('cls', 'list')
386 self.table = table.Table(width=width)
387 ScrollArea.__init__(self, self.table, width, height,hscrollbar=False ,**params)
388
389 self.items = []
390
391 g = group.Group()
392 self.group = g
393 g.connect(CHANGE,self._change,None)
394 self.value = self.group.value = None
395
396 self.add = self._add
397 self.remove = self._remove
398
399 def clear(self):
400 """Clear the list.
401
402 <pre>List.clear()</pre>
403 """
404 self.items = []
405 self.group = group.Group()
406 self.group.connect(CHANGE,self._change,None)
407 self.table.clear()
408 self.set_vertical_scroll(0)
409 self.blur(self.myfocus)
410
411 def _docs(self): #HACK: nasty hack to get the docs in "my way"
412 def add(self, label, image=None, value=None):
413 """Add an item to the list.
414
415 <pre>List.add(label,image=None,value=None)</pre>
416
417 <dl>
418 <dt>label<dd>a label for the item
419 <dt>image<dd>an image for the item
420 <dt>value<dd>a value for the item
421 </dl>
422 """
423
424 def remove(self,value):
425 """Remove an item from the list.
426
427 <pre>List.remove(value)</pre>
428
429 <dl>
430 <dt>value<dd>a value of an item to remove from the list
431 </dl>
432 """
433
434 def _add(self, label, image = None, value=None):
435 item = _List_Item(label,image=image,value=value)
436 self.table.tr()
437 self.table.add(item)
438 self.items.append(item)
439 item.group = self.group
440 item.group.add(item)
441
442 def _remove(self, item):
443 for i in self.items:
444 if i.value == item: item = i
445 if item not in self.items: return
446 item.blur()
447 self.items.remove(item)
448 self.group.widgets.remove(item)
449 self.table.remove_row(item.style.row)
450
451 #class List(ListArea):
452 # def __init__(self,*args,**params):
453 # print 'gui.List','Scheduled to be renamed to ListArea. API may also be changed in the future.'
454 # ListArea.__init__(self,*args,**params)