From 6eb30b09566a53ef510532f2a1705d7fc22985a8 Mon Sep 17 00:00:00 2001 From: Tony Anderson Date: Mon, 22 Jun 2009 14:04:24 +0000 Subject: initial commit --- (limited to 'pgu/gui') diff --git a/pgu/gui/__init__.py b/pgu/gui/__init__.py new file mode 100644 index 0000000..256fb63 --- /dev/null +++ b/pgu/gui/__init__.py @@ -0,0 +1,32 @@ +import pygame +from pygame.locals import * + +from theme import Theme +from style import Style +from widget import Widget +from surface import subsurface, ProxySurface +from const import * + +from container import Container +from app import App, Desktop +from table import Table +from document import Document +#html +from area import SlideBox, ScrollArea, List + +from form import Form +from group import Group + +from basic import Spacer, Color, Label, Image, parse_color +from button import Icon, Button, Switch, Checkbox, Radio, Tool, Link +from input import Input, Password +from keysym import Keysym +from slider import VSlider, HSlider, VScrollBar, HScrollBar +from select import Select +from misc import ProgressBar + +from menus import Menus +from dialog import Dialog, FileDialog +from textarea import TextArea + +from deprecated import Toolbox, action_open, action_setvalue, action_quit, action_exec diff --git a/pgu/gui/__init__.pyc b/pgu/gui/__init__.pyc new file mode 100644 index 0000000..2da4bda --- /dev/null +++ b/pgu/gui/__init__.pyc Binary files differ diff --git a/pgu/gui/app.py b/pgu/gui/app.py new file mode 100644 index 0000000..89ce66c --- /dev/null +++ b/pgu/gui/app.py @@ -0,0 +1,237 @@ +""" +""" +import pygame +from pygame.locals import * + +import pguglobals +import container +from const import * + +class App(container.Container): + """The top-level widget for an application. + +
App(theme=None)
+ +
+
theme
an instance of a Theme, optional as it will use the default Theme class. +
+ + Basic Example + + app = gui.App() + app.run(widget=widget,screen=screen) + + + Integrated Example + + app = gui.App() + gui.init(widget=widget) + while 1: + for e in pygame.event.get(): + app.event(e) + app.update(screen) + + + + + """ + def __init__(self,theme=None,**params): + self.set_global_app() + + if theme == None: + from theme import Theme + theme = Theme() + self.theme = theme + + params['decorate'] = 'app' + container.Container.__init__(self,**params) + self._quit = False + self.widget = None + self._chsize = False + self._repaint = False + + self.screen = None + self.container = None + self.events = [] + + def set_global_app(self): + # Keep a global reference to this application instance so that PGU + # components can easily find it. + pguglobals.app = self + # For backwards compatibility we keep a reference in the class + # itself too. + App.app = self + + def resize(self): + + screen = self.screen + w = self.widget + wsize = 0 + + #5 cases + + #input screen is already set use its size + if screen: + self.screen = screen + width,height = screen.get_width(),screen.get_height() + + #display.screen + elif pygame.display.get_surface(): + screen = pygame.display.get_surface() + self.screen = screen + width,height = screen.get_width(),screen.get_height() + + #app has width,height + elif self.style.width != 0 and self.style.height != 0: + screen = pygame.display.set_mode((self.style.width,self.style.height),SWSURFACE) + self.screen = screen + width,height = screen.get_width(),screen.get_height() + + #widget has width,height, or its own size.. + else: + wsize = 1 + width,height = w.rect.w,w.rect.h = w.resize() + #w._resize() + screen = pygame.display.set_mode((width,height),SWSURFACE) + self.screen = screen + + #use screen to set up size of this widget + self.style.width,self.style.height = width,height + self.rect.w,self.rect.h = width,height + self.rect.x,self.rect.y = 0,0 + + w.rect.x,w.rect.y = 0,0 + w.rect.w,w.rect.h = w.resize(width,height) + + for w in self.windows: + w.rect.w,w.rect.h = w.resize() + + self._chsize = False + + + def init(self,widget=None,screen=None): #TODO widget= could conflict with module widget + """Initialize the application. + +
App.init(widget=None,screen=None)
+ +
+
widget
main widget +
screen
pygame.Surface to render to +
+ """ + + self.set_global_app() + + if widget: self.widget = widget + if screen: self.screen = screen + + self.resize() + + w = self.widget + + self.widgets = [] + self.widgets.append(w) + w.container = self + self.focus(w) + + pygame.key.set_repeat(500,30) + + self._repaint = True + self._quit = False + + self.send(INIT) + + def event(self,e): + """Pass an event to the main widget. + +
App.event(e)
+ +
+
e
event +
+ """ + self.set_global_app() + + #NOTE: might want to deal with ACTIVEEVENT in the future. + self.send(e.type,e) + container.Container.event(self,e) + if e.type == MOUSEBUTTONUP: + if e.button not in (4,5): #ignore mouse wheel + sub = pygame.event.Event(CLICK,{ + 'button':e.button, + 'pos':e.pos}) + self.send(sub.type,sub) + container.Container.event(self,sub) + + + def loop(self): + self.set_global_app() + + s = self.screen + for e in pygame.event.get(): + if not (e.type == QUIT and self.mywindow): + self.event(e) + us = self.update(s) + pygame.display.update(us) + + + def paint(self,screen): + self.screen = screen + if self._chsize: + self.resize() + self._chsize = False + if hasattr(self,'background'): + self.background.paint(screen) + container.Container.paint(self,screen) + + def update(self,screen): + """Update the screen. + +
+
screen
pygame surface +
+ """ + self.screen = screen + if self._chsize: + self.resize() + self._chsize = False + if self._repaint: + self.paint(screen) + self._repaint = False + return [pygame.Rect(0,0,screen.get_width(),screen.get_height())] + else: + us = container.Container.update(self,screen) + return us + + def run(self,widget=None,screen=None): + """Run an application. + +

Automatically calls App.init and then forever loops App.event and App.update

+ +
+
widget
main widget +
screen
pygame.Surface to render to +
+ """ + self.init(widget,screen) + while not self._quit: + self.loop() + pygame.time.wait(10) + + def reupdate(self,w=None): pass + def repaint(self,w=None): self._repaint = True + def repaintall(self): self._repaint = True + def chsize(self): + self._chsize = True + self._repaint = True + + def quit(self,value=None): self._quit = True + +class Desktop(App): + """Create an App using the desktop theme class. + +
Desktop()
+ """ + def __init__(self,**params): + params.setdefault('cls','desktop') + App.__init__(self,**params) diff --git a/pgu/gui/app.pyc b/pgu/gui/app.pyc new file mode 100644 index 0000000..3199e66 --- /dev/null +++ b/pgu/gui/app.pyc Binary files differ diff --git a/pgu/gui/area.py b/pgu/gui/area.py new file mode 100644 index 0000000..39b8cbd --- /dev/null +++ b/pgu/gui/area.py @@ -0,0 +1,434 @@ +""" +""" +import os + +import pguglobals +from const import * +import surface +import container, table +import group +import basic, button, slider + +class SlideBox(container.Container): + """A scrollable area with no scrollbars. + +
SlideBox(widget,width,height)
+ +
+
widget
widget to be able to scroll around +
width, height
size of scrollable area +
+ + Example + + c = SlideBox(w,100,100) + c.offset = (10,10) + c.repaint() + + + """ + + def __init__(self, widget, width, height, **params): + params.setdefault('width', width) + params.setdefault('height', height) + container.Container.__init__(self, **params) + self.offset = [0, 0] + self.widget = widget + + def __setattr__(self,k,v): + if k == 'widget': + if hasattr(self,'widget'): + self.remove(self.widget) + self.add(v,0,0) + self.__dict__[k] = v + + + def paint(self, s): + #if not hasattr(self,'surface'): + self.surface = pygame.Surface((self.max_rect.w,self.max_rect.h),0,s) + #self.surface.fill((0,0,0,0)) + pguglobals.app.theme.render(self.surface,self.style.background,pygame.Rect(0,0,self.max_rect.w,self.max_rect.h)) + self.bkgr = pygame.Surface((s.get_width(),s.get_height()),0,s) + self.bkgr.blit(s,(0,0)) + container.Container.paint(self,self.surface) + s.blit(self.surface,(-self.offset[0],-self.offset[1])) + self._offset = self.offset[:] + return + + def paint_for_when_pygame_supports_other_tricks(self,s): + #this would be ideal if pygame had support for it! + #and if pgu also had a paint(self,s,rect) method to paint small parts + sr = (self.offset[0],self.offset[1],self.max_rect.w,self.max_rect.h) + cr = (-self.offset[0],-self.offset[1],s.get_width(),s.get_height()) + s2 = s.subsurface(sr) + s2.set_clip(cr) + container.Container.paint(self,s2) + + def proxy_paint(self, s): + container.Container.paint(self, surface.ProxySurface(parent=None, + rect=self.max_rect, + real_surface=s, + offset=self.offset)) + def update(self, s): + rects = container.Container.update(self,self.surface) + + rets = [] + s_rect = pygame.Rect(0,0,s.get_width(),s.get_height()) + + if self.offset == self._offset: + for r in rects: + r2 = r.move((-self.offset[0],-self.offset[1])) + if r2.colliderect(s_rect): + s.blit(self.surface.subsurface(r),r2) + rets.append(r2) + else: + s.blit(self.bkgr,(0,0)) + 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])) +# print sub +# print self.surface.get_width(),self.surface.get_height() +# print s.get_width(),s.get_height() +# print self.offset +# print self.style.width,self.style.height + s.blit(self.surface.subsurface(sub),(0,0)) + rets.append(s_rect) + self._offset = self.offset[:] + return rets + + def proxy_update(self, s): + rects = container.Container.update(self, surface.ProxySurface(parent=None, + rect=self.max_rect, + real_surface=s, + offset=self.offset)) + result = [] + for r in rects: result.append(pygame.Rect(r).move(self.offset)) + return result + + def resize(self, width=None, height=None): + container.Container.resize(self) + self.max_rect = pygame.Rect(self.widget.rect) + #self.max_rect.w = max(self.max_rect.w,self.style.width) + #self.max_rect.h = max(self.max_rect.h,self.style.height) + return self.style.width,self.style.height + #self.rect = pygame.Rect(self.rect[0], self.rect[1], self.style.width, self.style.height) + + def event(self, e): + if e.type in [MOUSEBUTTONDOWN, MOUSEBUTTONUP, MOUSEMOTION]: + pos = (e.pos[0] + self.offset[0], e.pos[1] + self.offset[1]) + if self.max_rect.collidepoint(pos): + e_params = {'pos': pos } + if e.type == MOUSEMOTION: + e_params['buttons'] = e.buttons + e_params['rel'] = e.rel + else: + e_params['button'] = e.button + e = pygame.event.Event(e.type, e_params) + container.Container.event(self, e) + +#class SlideBox(Area): +# def __init__(self,*args,**params): +# print 'gui.SlideBox','Scheduled to be renamed to Area.' +# Area.__init__(self,*args,**params) + +class ScrollArea(table.Table): + """A scrollable area with scrollbars. + +
ScrollArea(widget,width,height,hscrollbar=True)
+ +
+
widget
widget to be able to scroll around +
width, height
size of scrollable area. Set either to 0 to default to size of widget. +
hscrollbar
set to False if you do not wish to have a horizontal scrollbar +
vscrollbar
set to False if you do not wish to have a vertical scrollbar +
step
set to how far clicks on the icons will step +
+ """ + def __init__(self, widget, width=0, height=0, hscrollbar=True, vscrollbar=True,step=24, **params): + w= widget + params.setdefault('cls', 'scrollarea') + table.Table.__init__(self, width=width,height=height,**params) + + self.sbox = SlideBox(w, width=width, height=height, cls=self.cls+".content") + self.widget = w + self.vscrollbar = vscrollbar + self.hscrollbar = hscrollbar + + self.step = step + + def __setattr__(self,k,v): + if k == 'widget': + self.sbox.widget = v + self.__dict__[k] = v + + def resize(self,width=None,height=None): + widget = self.widget + box = self.sbox + + #self.clear() + table.Table.clear(self) + #print 'resize',self,self._rows + + self.tr() + self.td(box) + + widget.rect.w, widget.rect.h = widget.resize() + my_width,my_height = self.style.width,self.style.height + if not my_width: + my_width = widget.rect.w + self.hscrollbar = False + if not my_height: + my_height = widget.rect.h + self.vscrollbar = False + + box.style.width,box.style.height = my_width,my_height #self.style.width,self.style.height + + box.rect.w,box.rect.h = box.resize() + + #print widget.rect + #print box.rect + #r = table.Table.resize(self,width,height) + #print r + #return r + + #print box.offset + +# #this old code automatically adds in a scrollbar if needed +# #but it doesn't always work +# self.vscrollbar = None +# if widget.rect.h > box.rect.h: +# self.vscrollbar = slider.VScrollBar(box.offset[1],0, 65535, 0,step=self.step) +# self.td(self.vscrollbar) +# self.vscrollbar.connect(CHANGE, self._vscrollbar_changed, None) +# +# vs = self.vscrollbar +# vs.rect.w,vs.rect.h = vs.resize() +# box.style.width = self.style.width - vs.rect.w +# +# +# self.hscrollbar = None +# if widget.rect.w > box.rect.w: +# self.hscrollbar = slider.HScrollBar(box.offset[0], 0,65535, 0,step=self.step) +# self.hscrollbar.connect(CHANGE, self._hscrollbar_changed, None) +# self.tr() +# self.td(self.hscrollbar) +# +# hs = self.hscrollbar +# hs.rect.w,hs.rect.h = hs.resize() +# box.style.height = self.style.height - hs.rect.h + + xt,xr,xb,xl = pguglobals.app.theme.getspacing(box) + + + if self.vscrollbar: + self.vscrollbar = slider.VScrollBar(box.offset[1],0, 65535, 0,step=self.step) + self.td(self.vscrollbar) + self.vscrollbar.connect(CHANGE, self._vscrollbar_changed, None) + + vs = self.vscrollbar + vs.rect.w,vs.rect.h = vs.resize() + if self.style.width: + box.style.width = self.style.width - (vs.rect.w + xl+xr) + + if self.hscrollbar: + self.hscrollbar = slider.HScrollBar(box.offset[0], 0,65535, 0,step=self.step) + self.hscrollbar.connect(CHANGE, self._hscrollbar_changed, None) + self.tr() + self.td(self.hscrollbar) + + hs = self.hscrollbar + hs.rect.w,hs.rect.h = hs.resize() + if self.style.height: + box.style.height = self.style.height - (hs.rect.h + xt + xb) + + if self.hscrollbar: + hs = self.hscrollbar + hs.min = 0 + hs.max = widget.rect.w - box.style.width + hs.style.width = box.style.width + hs.size = hs.style.width * box.style.width / max(1,widget.rect.w) + else: + box.offset[0] = 0 + + if self.vscrollbar: + vs = self.vscrollbar + vs.min = 0 + vs.max = widget.rect.h - box.style.height + vs.style.height = box.style.height + vs.size = vs.style.height * box.style.height / max(1,widget.rect.h) + else: + box.offset[1] = 0 + + #print self.style.width,box.style.width, hs.style.width + + r = table.Table.resize(self,width,height) + return r + + def x_resize(self, width=None, height=None): + w,h = table.Table.resize(self, width, height) + if self.hscrollbar: + if self.widget.rect.w <= self.sbox.rect.w: + self.hscrollbar.size = self.hscrollbar.style.width + else: + self.hscrollbar.size = max(20,self.hscrollbar.style.width * self.sbox.rect.w / self.widget.rect.w) + self._hscrollbar_changed(None) + if self.widget.rect.h <= self.sbox.rect.h: + self.vscrollbar.size = self.vscrollbar.style.height + else: + self.vscrollbar.size = max(20,self.vscrollbar.style.height * self.sbox.rect.h / self.widget.rect.h) + self._vscrollbar_changed(None) + return w,h + + def _vscrollbar_changed(self, xxx): + #y = (self.widget.rect.h - self.sbox.rect.h) * self.vscrollbar.value / 1000 + #if y >= 0: self.sbox.offset[1] = -y + self.sbox.offset[1] = self.vscrollbar.value + self.sbox.reupdate() + + def _hscrollbar_changed(self, xxx): + #x = (self.widget.rect.w - self.sbox.rect.w) * self.hscrollbar.value / 1000 + #if x >= 0: self.sbox.offset[0] = -x + self.sbox.offset[0] = self.hscrollbar.value + self.sbox.reupdate() + + + def set_vertical_scroll(self, percents): + #if not self.vscrollbar: return + if not hasattr(self.vscrollbar,'value'): return + self.vscrollbar.value = percents #min(max(percents*10, 0), 1000) + self._vscrollbar_changed(None) + + def set_horizontal_scroll(self, percents): + #if not self.hscrollbar: return + if not hasattr(self.hscrollbar,'value'): return + self.hscrollbar.value = percents #min(max(percents*10, 0), 1000) + self._hscrollbar_changed(None) + + + + +class _List_Item(button._button): + def __init__(self,label=None,image=None,value=None,**params): #TODO label= could conflict with the module label + #param image: an imagez.Image object (optional) + #param text: a string object + params.setdefault('cls','list.item') + button._button.__init__(self,**params) + self.group = None + self.value = value #(self, value) + self.widget = None + + if type(label) == str: + label = basic.Label(label, cls=self.cls+".label") + + if image and label: + self.widget = container.Container() + self.widget.add(image, 0, 0) + #HACK: improper use of .resize() + image.rect.w,image.rect.h = image.resize() + self.widget.add(label, image.rect.w, 0) + elif image: self.widget = image + elif label: self.widget = label + + self.pcls = "" + + def resize(self,width=None,height=None): + self.widget.rect.w,self.widget.rect.h = self.widget.resize() + return self.widget.rect.w,self.widget.rect.h +# self.widget._resize() +# self.rect.w,self.rect.h = self.widget.rect_margin.w,self.widget.rect_margin.h + + def event(self,e): + button._button.event(self,e) + if self.group.value == self.value: self.pcls = "down" + + def paint(self,s): + if self.group.value == self.value: self.pcls = "down" + self.widget.paint(surface.subsurface(s,self.widget.rect)) + + def click(self): + self.group.value = self.value + for w in self.group.widgets: + if w != self: w.pcls = "" + + + +class List(ScrollArea): + """A list of items in an area. + +

This widget can be a form element, it has a value set to whatever item is selected.

+ +
List(width,height)
+ """ + def _change(self, value): + self.value = self.group.value + self.send(CHANGE) + + def __init__(self, width, height, **params): + params.setdefault('cls', 'list') + self.table = table.Table(width=width) + ScrollArea.__init__(self, self.table, width, height,hscrollbar=False ,**params) + + self.items = [] + + g = group.Group() + self.group = g + g.connect(CHANGE,self._change,None) + self.value = self.group.value = None + + self.add = self._add + self.remove = self._remove + + def clear(self): + """Clear the list. + +
List.clear()
+ """ + self.items = [] + self.group = group.Group() + self.group.connect(CHANGE,self._change,None) + self.table.clear() + self.set_vertical_scroll(0) + self.blur(self.myfocus) + + def _docs(self): #HACK: nasty hack to get the docs in "my way" + def add(self, label, image=None, value=None): + """Add an item to the list. + +
List.add(label,image=None,value=None)
+ +
+
label
a label for the item +
image
an image for the item +
value
a value for the item +
+ """ + + def remove(self,value): + """Remove an item from the list. + +
List.remove(value)
+ +
+
value
a value of an item to remove from the list +
+ """ + + def _add(self, label, image = None, value=None): + item = _List_Item(label,image=image,value=value) + self.table.tr() + self.table.add(item) + self.items.append(item) + item.group = self.group + item.group.add(item) + + def _remove(self, item): + for i in self.items: + if i.value == item: item = i + if item not in self.items: return + item.blur() + self.items.remove(item) + self.group.widgets.remove(item) + self.table.remove_row(item.style.row) + +#class List(ListArea): +# def __init__(self,*args,**params): +# print 'gui.List','Scheduled to be renamed to ListArea. API may also be changed in the future.' +# ListArea.__init__(self,*args,**params) diff --git a/pgu/gui/area.pyc b/pgu/gui/area.pyc new file mode 100644 index 0000000..f2aa1d1 --- /dev/null +++ b/pgu/gui/area.pyc Binary files differ diff --git a/pgu/gui/basic.py b/pgu/gui/basic.py new file mode 100644 index 0000000..093c6ee --- /dev/null +++ b/pgu/gui/basic.py @@ -0,0 +1,124 @@ +"""These widgets are all grouped together because they are non-interactive widgets. +""" + +import pygame + +from const import * +import widget + +# Turns a descriptive string or a tuple into a pygame color +def parse_color(desc): + if (isinstance(desc, pygame.Color)): + # Already a color + return desc + elif (desc and desc[0] == "#"): + # Because of a bug in pygame 1.8.1 we need to explicitly define the + # alpha value otherwise it will default to transparent. + if (len(desc) == 7): + desc += "FF" + return pygame.Color(desc) + + +class Spacer(widget.Widget): + """A invisible space. + +
Spacer(width,height)
+ + """ + def __init__(self,width,height,**params): + params.setdefault('focusable',False) + widget.Widget.__init__(self,width=width,height=height,**params) + + +class Color(widget.Widget): + """A block of color. + +

The color can be changed at run-time.

+ +
Color(value=None)
+ + Example + + c = Color() + c.value = (255,0,0) + c.value = (0,255,0) + + """ + + + def __init__(self,value=None,**params): + params.setdefault('focusable',False) + if value != None: params['value']=value + widget.Widget.__init__(self,**params) + + def paint(self,s): + if hasattr(self,'value'): s.fill(self.value) + + def __setattr__(self,k,v): + if k == 'value' and type(v) == str: + v = parse_color(v) + _v = self.__dict__.get(k,NOATTR) + self.__dict__[k]=v + if k == 'value' and _v != NOATTR and _v != v: + self.send(CHANGE) + self.repaint() + +class Label(widget.Widget): + """A text label. + +
Label(value)
+ +
+
value
text to be displayed +
+ + Example + + w = Label(value="I own a rubber chicken!") + + w = Label("3 rubber chickens") + + """ + def __init__(self,value,**params): + params.setdefault('focusable',False) + params.setdefault('cls','label') + widget.Widget.__init__(self,**params) + self.value = value + self.font = self.style.font + self.style.width, self.style.height = self.font.size(self.value) + + def paint(self,s): + s.blit(self.font.render(self.value, 1, self.style.color),(0,0)) + +class Image(widget.Widget): + """An image. + +
Image(value)
+ +
+
value
a file name or a pygame.Surface +
+ + """ + def __init__(self,value,**params): + params.setdefault('focusable',False) + widget.Widget.__init__(self,**params) + if type(value) == str: value = pygame.image.load(value) + + ow,oh = iw,ih = value.get_width(),value.get_height() + sw,sh = self.style.width,self.style.height + + if sw and not sh: + iw,ih = sw,ih*sw/iw + elif sh and not sw: + iw,ih = iw*sh/ih,sh + elif sw and sh: + iw,ih = sw,sh + + if (ow,oh) != (iw,ih): + value = pygame.transform.scale(value,(iw,ih)) + self.style.width,self.style.height = iw,ih + self.value = value + + def paint(self,s): + s.blit(self.value,(0,0)) diff --git a/pgu/gui/basic.pyc b/pgu/gui/basic.pyc new file mode 100644 index 0000000..80e1e93 --- /dev/null +++ b/pgu/gui/basic.pyc Binary files differ diff --git a/pgu/gui/button.py b/pgu/gui/button.py new file mode 100644 index 0000000..90cdd1e --- /dev/null +++ b/pgu/gui/button.py @@ -0,0 +1,351 @@ +""" +""" + +from pygame.locals import * + +from const import * +import widget, surface +import basic + +class _button(widget.Widget): + def __init__(self,**params): + widget.Widget.__init__(self,**params) + self.state = 0 + + def event(self,e): + if e.type == ENTER: self.repaint() + elif e.type == EXIT: self.repaint() + elif e.type == FOCUS: self.repaint() + elif e.type == BLUR: self.repaint() + elif e.type == KEYDOWN: + if e.key == K_SPACE or e.key == K_RETURN: + self.state = 1 + self.repaint() + elif e.type == MOUSEBUTTONDOWN: + self.state = 1 + self.repaint() + elif e.type == KEYUP: + if self.state == 1: + sub = pygame.event.Event(CLICK,{'pos':(0,0),'button':1}) + #self.send(sub.type,sub) + self._event(sub) + + self.state = 0 + self.repaint() + elif e.type == MOUSEBUTTONUP: + self.state = 0 + self.repaint() + elif e.type == CLICK: + self.click() + + self.pcls = "" + if self.state == 0 and self.container.myhover is self: + self.pcls = "hover" + if self.state == 1 and self.container.myhover is self: + self.pcls = "down" + + def click(self): + pass + + +class Button(_button): + """A button, buttons can be clicked, they are usually used to set up callbacks. + +
Button(value=None)
+ +
+
value
either a widget or a string +
+ + Example + + w = gui.Button("Click Me") + w.connect(gui.CLICK,fnc,value) + + """ + def __init__(self,value=None,**params): + params.setdefault('cls','button') + _button.__init__(self,**params) + self.value = value + + def __setattr__(self,k,v): + if k == 'value' and type(v) == str: v = basic.Label(v,cls=self.cls+".label") + _v = self.__dict__.get(k,NOATTR) + self.__dict__[k]=v + if k == 'value' and v != None: + pass + + if k == 'value' and _v != NOATTR and _v != None and _v != v: + self.send(CHANGE) + self.chsize() + + def resize(self,width=None,height=None): + self.value.rect.x,self.value.rect.y = 0,0 + self.value.rect.w,self.value.rect.h = self.value.resize(width,height) + return self.value.rect.w,self.value.rect.h +# +# self.value._resize() +# self.rect.w,self.rect.h = self.value.rect_margin.w,self.value.rect_margin.h +# +# if self.style.width: self.rect.w = max(self.rect.w,self.style.width) +# if self.style.height: self.rect.w = max(self.rect.w,self.style.height) +# +# xt,xr,xb,xl = self.value.getspacing() +# +# self.value._resize(self.rect.w-(xl+xr),self.rect.h-(xt+xb)) +# + def paint(self,s): + self.value.pcls = self.pcls + self.value.paint(surface.subsurface(s,self.value.rect)) + +class Switch(_button): + """A switch can have two states, True or False. + +
Switch(value=False)
+ +
+
value
initial value, (True, False) +
+ + Example + + w = gui.Switch(True) + w.connect(gui.CHANGE,fnc,value) + + """ + def __init__(self,value=False,**params): + params.setdefault('cls','switch') + _button.__init__(self,**params) + self.value = value + + img = self.style.off + self.style.width = img.get_width() + self.style.height = img.get_height() + + def paint(self,s): + #self.pcls = "" + #if self.container.myhover is self: self.pcls = "hover" + if self.value: img = self.style.on + else: img = self.style.off + s.blit(img,(0,0)) + + def __setattr__(self,k,v): + _v = self.__dict__.get(k,NOATTR) + self.__dict__[k]=v + if k == 'value' and _v != NOATTR and _v != v: + self.send(CHANGE) + self.repaint() + + def click(self): + self.value = not self.value + +class Checkbox(_button): + """Within a Group of Checkbox widgets several may be selected at a time. + +
Checkbox(group,value=None)
+ +
+
group
a gui.Group for the Checkbox to belong to +
value
the value +
+ + Example + + g = gui.Group(name='colors',value=['r','b']) + + t = gui.Table() + t.tr() + t.td(gui.Label('Red')) + t.td(gui.Checkbox(g,'r')) + t.tr() + t.td(gui.Label('Green')) + t.td(gui.Checkbox(g,'g')) + t.tr() + t.td(gui.Label('Blue')) + t.td(gui.Checkbox(g,'b')) + + """ + + def __init__(self,group,value=None,**params): + params.setdefault('cls','checkbox') + _button.__init__(self,**params) + self.group = group + self.group.add(self) + if self.group.value == None: + self.group.value = [] + self.value = value + + img = self.style.off + self.style.width = img.get_width() + self.style.height = img.get_height() + + def paint(self,s): + #self.pcls = "" + #if self.container.myhover is self: self.pcls = "hover" + if self.value in self.group.value: img = self.style.on + else: img = self.style.off + + s.blit(img,(0,0)) + + def click(self): + if self.value in self.group.value: + self.group.value.remove(self.value) + else: + self.group.value.append(self.value) + self.group._change() + +class Radio(_button): + """Within a Group of Radio widgets only one may be selected at a time. + +
Radio(group,value=None)
+ +
+
group
a gui.Group for the Radio to belong to +
value
the value +
+ + Example + + g = gui.Group(name='colors',value='g') + + t = gui.Table() + t.tr() + t.td(gui.Label('Red')) + t.td(gui.Radio(g,'r')) + t.tr() + t.td(gui.Label('Green')) + t.td(gui.Radio(g,'g')) + t.tr() + t.td(gui.Label('Blue')) + t.td(gui.Radio(g,'b')) + + """ + + + def __init__(self,group=None,value=None,**params): + params.setdefault('cls','radio') + _button.__init__(self,**params) + self.group = group + self.group.add(self) + self.value = value + + img = self.style.off + self.style.width = img.get_width() + self.style.height = img.get_height() + + def paint(self,s): + #self.pcls = "" + #if self.container.myhover is self: self.pcls = "hover" + if self.group.value == self.value: img = self.style.on + else: img = self.style.off + s.blit(img,(0,0)) + + def click(self): + self.group.value = self.value + +class Tool(_button): + """Within a Group of Tool widgets only one may be selected at a time. + +
Tool(group,widget=None,value=None)
+ +
+
group
a gui.Group for the Tool to belong to +
widget
a widget to appear on the Tool (similar to a Button) +
value
the value +
+ + Example + + g = gui.Group(name='colors',value='g') + + t = gui.Table() + t.tr() + t.td(gui.Tool(g,'Red','r')) + t.tr() + t.td(gui.Tool(g,'Green','g')) + t.tr() + t.td(gui.Tool(g,'Blue','b')) + + """ + + def __init__(self,group,widget=None,value=None,**params): #TODO widget= could conflict with module widget + params.setdefault('cls','tool') + _button.__init__(self,**params) + self.group = group + self.group.add(self) + self.value = value + + if widget: + self.setwidget(widget) + + if self.group.value == self.value: self.pcls = "down" + + def setwidget(self,w): + self.widget = w + + def resize(self,width=None,height=None): + self.widget.rect.w,self.widget.rect.h = self.widget.resize() + #self.widget._resize() + #self.rect.w,self.rect.h = self.widget.rect_margin.w,self.widget.rect_margin.h + + return self.widget.rect.w,self.widget.rect.h + + def event(self,e): + _button.event(self,e) + if self.group.value == self.value: self.pcls = "down" + + def paint(self,s): + if self.group.value == self.value: self.pcls = "down" + self.widget.paint(surface.subsurface(s,self.widget.rect)) + + def click(self): + self.group.value = self.value + for w in self.group.widgets: + if w != self: w.pcls = "" + + +class Icon(_button): + """TODO - might be deprecated + """ + def __init__(self,cls,**params): + params['cls'] = cls + _button.__init__(self,**params) + s = self.style.image + self.style.width = s.get_width() + self.style.height = s.get_height() + self.state = 0 + + def paint(self,s): + #self.pcls = "" + #if self.state == 0 and hasattr(self.container,'myhover') and self.container.myhover is self: self.pcls = "hover" + #if self.state == 1 and hasattr(self.container,'myhover') and self.container.myhover is self: self.pcls = "down" + s.blit(self.style.image,(0,0)) + +class Link(_button): + """A link, links can be clicked, they are usually used to set up callbacks. + Basically the same as the button widget, just text only with a different cls. Made for + convenience. + +
Link(value=None)
+ +
+
value
a string +
+ + Example + + w = gui.Link("Click Me") + w.connect(gui.CLICK,fnc,value) + + """ + def __init__(self,value,**params): + params.setdefault('focusable',True) + params.setdefault('cls','link') + _button.__init__(self,**params) + self.value = value + self.font = self.style.font + self.style.width, self.style.height = self.font.size(self.value) + + def paint(self,s): + s.blit(self.font.render(self.value, 1, self.style.color),(0,0)) + diff --git a/pgu/gui/button.pyc b/pgu/gui/button.pyc new file mode 100644 index 0000000..050236a --- /dev/null +++ b/pgu/gui/button.pyc Binary files differ diff --git a/pgu/gui/const.py b/pgu/gui/const.py new file mode 100644 index 0000000..c865cd8 --- /dev/null +++ b/pgu/gui/const.py @@ -0,0 +1,45 @@ +"""Constants. +

+Event Types + +

from pygame

+
+
QUIT +
MOUSEBUTTONDOWN +
MOUSEBUTTONUP +
MOUSEMOTION +
KEYDOWN +
+ +

gui specific

+
+
ENTER +
EXIT +
BLUR +
FOCUS +
CLICK +
CHANGE +
OPEN +
CLOSE +
INIT +
+ +Other +
+
NOATTR +
+""" +import pygame + +from pygame.locals import QUIT, MOUSEBUTTONDOWN, MOUSEBUTTONUP, MOUSEMOTION, KEYDOWN, USEREVENT +ENTER = pygame.locals.USEREVENT + 0 +EXIT = pygame.locals.USEREVENT + 1 +BLUR = pygame.locals.USEREVENT + 2 +FOCUS = pygame.locals.USEREVENT + 3 +CLICK = pygame.locals.USEREVENT + 4 +CHANGE = pygame.locals.USEREVENT + 5 +OPEN = pygame.locals.USEREVENT + 6 +CLOSE = pygame.locals.USEREVENT + 7 +INIT = 'init' + +class NOATTR: pass \ No newline at end of file diff --git a/pgu/gui/const.pyc b/pgu/gui/const.pyc new file mode 100644 index 0000000..6694c12 --- /dev/null +++ b/pgu/gui/const.pyc Binary files differ diff --git a/pgu/gui/container.py b/pgu/gui/container.py new file mode 100644 index 0000000..b3dd810 --- /dev/null +++ b/pgu/gui/container.py @@ -0,0 +1,455 @@ +""" +""" +import pygame +from pygame.locals import * + +from const import * +import widget, surface +import pguglobals + +class Container(widget.Widget): + """The base container widget, can be used as a template as well as stand alone. + +
Container()
+ """ + def __init__(self,**params): + widget.Widget.__init__(self,**params) + self.myfocus = None + self.mywindow = None + self.myhover = None + #self.background = 0 + self.widgets = [] + self.windows = [] + self.toupdate = {} + self.topaint = {} + + def update(self,s): + updates = [] + + if self.myfocus: self.toupdate[self.myfocus] = self.myfocus + + for w in self.topaint: + if w is self.mywindow: + continue + else: + sub = surface.subsurface(s,w.rect) + sub.blit(w._container_bkgr,(0,0)) + w.paint(sub) + updates.append(pygame.rect.Rect(w.rect)) + + for w in self.toupdate: + if w is self.mywindow: + continue + else: + us = w.update(surface.subsurface(s,w.rect)) + if us: + for u in us: + updates.append(pygame.rect.Rect(u.x + w.rect.x,u.y+w.rect.y,u.w,u.h)) + + for w in self.topaint: + if w is self.mywindow: + w.paint(self.top_surface(s,w)) + updates.append(pygame.rect.Rect(w.rect)) + else: + continue + + for w in self.toupdate: + if w is self.mywindow: + us = w.update(self.top_surface(s,w)) + else: + continue + if us: + for u in us: + updates.append(pygame.rect.Rect(u.x + w.rect.x,u.y+w.rect.y,u.w,u.h)) + + self.topaint = {} + self.toupdate = {} + + return updates + + def repaint(self,w=None): + if not w: + return widget.Widget.repaint(self) + self.topaint[w] = w + self.reupdate() + + def reupdate(self,w=None): + if not w: + return widget.Widget.reupdate(self) + self.toupdate[w] = w + self.reupdate() + + def paint(self,s): + self.toupdate = {} + self.topaint = {} + for w in self.widgets: + try: + sub = surface.subsurface(s, w.rect) + except: + print 'container.paint(): %s not inside %s' % ( + w.__class__.__name__,self.__class__.__name__) + print s.get_width(), s.get_height(), w.rect + print "" + else: + if (not (hasattr(w,'_container_bkgr') and + w._container_bkgr.get_width() == sub.get_width() and + w._container_bkgr.get_height() == sub.get_height())): + w._container_bkgr = sub.copy() + w._container_bkgr.fill((0,0,0,0)) + w._container_bkgr.blit(sub,(0,0)) + + w.paint(sub) + + for w in self.windows: + print 'container: windows', len(self.windows), s, w.rect + w.paint(self.top_surface(s,w)) + + def top_surface(self,s,w): + x,y = s.get_abs_offset() + s = s.get_abs_parent() + return surface.subsurface(s,(x+w.rect.x,y+w.rect.y,w.rect.w,w.rect.h)) + + def event(self,e): + used = False + + if self.mywindow and e.type == MOUSEBUTTONDOWN: + w = self.mywindow + if self.myfocus is w: + if not w.rect.collidepoint(e.pos): self.blur(w) + if not self.myfocus: + if w.rect.collidepoint(e.pos): self.focus(w) + + if not self.mywindow: + #### by Gal Koren + ## + ## if e.type == FOCUS: + if e.type == FOCUS and not self.myfocus: + #self.first() + pass + elif e.type == EXIT: + if self.myhover: self.exit(self.myhover) + elif e.type == BLUR: + if self.myfocus: self.blur(self.myfocus) + elif e.type == MOUSEBUTTONDOWN: + h = None + for w in self.widgets: + if not w.disabled: #focusable not considered, since that is only for tabs + if w.rect.collidepoint(e.pos): + h = w + if self.myfocus is not w: self.focus(w) + if not h and self.myfocus: + self.blur(self.myfocus) + elif e.type == MOUSEMOTION: + if 1 in e.buttons: + if self.myfocus: ws = [self.myfocus] + else: ws = [] + else: ws = self.widgets + + h = None + for w in ws: + if w.rect.collidepoint(e.pos): + h = w + if self.myhover is not w: self.enter(w) + if not h and self.myhover: + self.exit(self.myhover) + w = self.myhover + + if w and w is not self.myfocus: + sub = pygame.event.Event(e.type,{ + 'buttons':e.buttons, + 'pos':(e.pos[0]-w.rect.x,e.pos[1]-w.rect.y), + 'rel':e.rel}) + used = w._event(sub) + + w = self.myfocus + if w: + sub = e + + if e.type == MOUSEBUTTONUP or e.type == MOUSEBUTTONDOWN: + sub = pygame.event.Event(e.type,{ + 'button':e.button, + 'pos':(e.pos[0]-w.rect.x,e.pos[1]-w.rect.y)}) + used = w._event(sub) + elif e.type == CLICK and self.myhover is w: + sub = pygame.event.Event(e.type,{ + 'button':e.button, + 'pos':(e.pos[0]-w.rect.x,e.pos[1]-w.rect.y)}) + used = w._event(sub) + elif e.type == CLICK: #a dead click + pass + elif e.type == MOUSEMOTION: + sub = pygame.event.Event(e.type,{ + 'buttons':e.buttons, + 'pos':(e.pos[0]-w.rect.x,e.pos[1]-w.rect.y), + 'rel':e.rel}) + used = w._event(sub) + else: + used = w._event(sub) + + if not used: + if e.type is KEYDOWN: + if e.key is K_TAB and self.myfocus: + if (e.mod&KMOD_SHIFT) == 0: + self.myfocus.next() + else: + self.myfocus.previous() + return True + elif e.key == K_UP: + self._move_focus(0,-1) + return True + elif e.key == K_RIGHT: + self._move_focus(1,0) + return True + elif e.key == K_DOWN: + self._move_focus(0,1) + return True + elif e.key == K_LEFT: + self._move_focus(-1,0) + return True + return used + + def _move_focus(self,dx_,dy_): + myfocus = self.myfocus + if not self.myfocus: return + + from pgu.gui import App + widgets = self._get_widgets(pguglobals.app) + #if myfocus not in widgets: return + #widgets.remove(myfocus) + if myfocus in widgets: + widgets.remove(myfocus) + rect = myfocus.get_abs_rect() + fx,fy = rect.centerx,rect.centery + + def sign(v): + if v < 0: return -1 + if v > 0: return 1 + return 0 + + dist = [] + for w in widgets: + wrect = w.get_abs_rect() + wx,wy = wrect.centerx,wrect.centery + dx,dy = wx-fx,wy-fy + if dx_ > 0 and wrect.left < rect.right: continue + if dx_ < 0 and wrect.right > rect.left: continue + if dy_ > 0 and wrect.top < rect.bottom: continue + if dy_ < 0 and wrect.bottom > rect.top: continue + dist.append((dx*dx+dy*dy,w)) + if not len(dist): return + dist.sort() + d,w = dist.pop(0) + w.focus() + + def _get_widgets(self,c): + widgets = [] + if c.mywindow: + widgets.extend(self._get_widgets(c.mywindow)) + else: + for w in c.widgets: + if isinstance(w,Container): + widgets.extend(self._get_widgets(w)) + elif not w.disabled and w.focusable: + widgets.append(w) + return widgets + + def remove(self,w): + """Remove a widget from the container. + +
Container.remove(w)
+ """ + self.blur(w) + self.widgets.remove(w) + #self.repaint() + self.chsize() + + def add(self,w,x,y): + """Add a widget to the container. + +
Container.add(w,x,y)
+ +
+
x, y
position of the widget +
+ """ + w.style.x = x + w.style.y = y + w.container = self + #NOTE: this might fix it, sort of... + #but the thing is, we don't really want to resize + #something if it is going to get resized again later + #for no reason... + #w.rect.x,w.rect.y = w.style.x,w.style.y + #w.rect.w, w.rect.h = w.resize() + self.widgets.append(w) + self.chsize() + + def open(self,w=None,x=None,y=None): + from app import App #HACK: I import it here to prevent circular importing + if not w: + if (not hasattr(self,'container') or + not self.container) and self is not pguglobals.app: + self.container = pguglobals.app + #print 'top level open' + return widget.Widget.open(self) + + if self.container: + if x != None: return self.container.open(w,self.rect.x+x,self.rect.y+y) + return self.container.open(w) + + w.container = self + + if w.rect.w == 0 or w.rect.h == 0: #this might be okay, not sure if needed. + #_chsize = App.app._chsize #HACK: we don't want this resize to trigger a chsize. + w.rect.w,w.rect.h = w.resize() + #App.app._chsize = _chsize + + if x == None or y == None: #auto center the window + #w.style.x,w.style.y = 0,0 + w.rect.x = (self.rect.w-w.rect.w)/2 + w.rect.y = (self.rect.h-w.rect.h)/2 + #w.resize() + #w._resize(self.rect.w,self.rect.h) + else: #show it where we want it + w.rect.x = x + w.rect.y = y + #w._resize() + + + self.windows.append(w) + self.mywindow = w + self.focus(w) + self.repaint(w) + w.send(OPEN) + + def close(self,w=None): + if not w: + return widget.Widget.close(self) + + if self.container: #make sure we're in the App + return self.container.close(w) + + if self.myfocus is w: self.blur(w) + + if w not in self.windows: return #no need to remove it twice! happens. + + self.windows.remove(w) + + self.mywindow = None + if self.windows: + self.mywindow = self.windows[-1] + self.focus(self.mywindow) + + if not self.mywindow: + self.myfocus = self.widget #HACK: should be done fancier, i think.. + if not self.myhover: + self.enter(self.widget) + + self.repaintall() + w.send(CLOSE) + + def focus(self,w=None): + widget.Widget.focus(self) ### by Gal koren +# if not w: +# return widget.Widget.focus(self) + if not w: return + if self.myfocus: self.blur(self.myfocus) + if self.myhover is not w: self.enter(w) + self.myfocus = w + w._event(pygame.event.Event(FOCUS)) + + #print self.myfocus,self.myfocus.__class__.__name__ + + def blur(self,w=None): + if not w: + return widget.Widget.blur(self) + if self.myfocus is w: + if self.myhover is w: self.exit(w) + self.myfocus = None + w._event(pygame.event.Event(BLUR)) + + def enter(self,w): + if self.myhover: self.exit(self.myhover) + self.myhover = w + w._event(pygame.event.Event(ENTER)) + + def exit(self,w): + if self.myhover and self.myhover is w: + self.myhover = None + w._event(pygame.event.Event(EXIT)) + + +# def first(self): +# for w in self.widgets: +# if w.focusable: +# self.focus(w) +# return +# if self.container: self.container.next(self) + +# def next(self,w): +# if w not in self.widgets: return #HACK: maybe. this happens in windows for some reason... +# +# for w in self.widgets[self.widgets.index(w)+1:]: +# if w.focusable: +# self.focus(w) +# return +# if self.container: return self.container.next(self) + + + def _next(self,orig=None): + start = 0 + if orig in self.widgets: start = self.widgets.index(orig)+1 + for w in self.widgets[start:]: + if not w.disabled and w.focusable: + if isinstance(w,Container): + if w._next(): + return True + else: + self.focus(w) + return True + return False + + def _previous(self,orig=None): + end = len(self.widgets) + if orig in self.widgets: end = self.widgets.index(orig) + ws = self.widgets[:end] + ws.reverse() + for w in ws: + if not w.disabled and w.focusable: + if isinstance(w,Container): + if w._previous(): + return True + else: + self.focus(w) + return True + return False + + def next(self,w=None): + if w != None and w not in self.widgets: return #HACK: maybe. this happens in windows for some reason... + + if self._next(w): return True + if self.container: return self.container.next(self) + + + def previous(self,w=None): + if w != None and w not in self.widgets: return #HACK: maybe. this happens in windows for some reason... + + if self._previous(w): return True + if self.container: return self.container.previous(self) + + def resize(self,width=None,height=None): + #r = self.rect + #r.w,r.h = 0,0 + ww,hh = 0,0 + if self.style.width: ww = self.style.width + if self.style.height: hh = self.style.height + + for w in self.widgets: + #w.rect.w,w.rect.h = 0,0 + w.rect.x,w.rect.y = w.style.x,w.style.y + w.rect.w, w.rect.h = w.resize() + #w._resize() + + ww = max(ww,w.rect.right) + hh = max(hh,w.rect.bottom) + return ww,hh diff --git a/pgu/gui/container.pyc b/pgu/gui/container.pyc new file mode 100644 index 0000000..6379fe2 --- /dev/null +++ b/pgu/gui/container.pyc Binary files differ diff --git a/pgu/gui/deprecated.py b/pgu/gui/deprecated.py new file mode 100644 index 0000000..8d53515 --- /dev/null +++ b/pgu/gui/deprecated.py @@ -0,0 +1,76 @@ +import pygame + +from const import * +import table +import group +import button, basic +import pguglobals + +def action_open(value): + print 'gui.action_open',"Scheduled to be deprecated." + value.setdefault('x',None) + value.setdefault('y',None) + value['container'].open(value['window'],value['x'],value['y']) + +def action_setvalue(value): + print 'gui.action_setvalue',"Scheduled to be deprecated." + a,b = value + b.value = a.value + +def action_quit(value): + print 'gui.action_quit',"Scheduled to be deprecated." + value.quit() + +def action_exec(value): + print 'gui.action_exec',"Scheduled to be deprecated." + exec(value['script'],globals(),value['dict']) + +class Toolbox(table.Table): + def __setattr__(self,k,v): + _v = self.__dict__.get(k,NOATTR) + self.__dict__[k]=v + if k == 'value' and _v != NOATTR and _v != v: + self.group.value = v + for w in self.group.widgets: + if w.value != v: w.pcls = "" + else: w.pcls = "down" + self.repaint() + + def _change(self,value): + self.value = self.group.value + self.send(CHANGE) + + def __init__(self,data,cols=0,rows=0,tool_cls='tool',value=None,**params): + print 'gui.Toolbox','Scheduled to be deprecated.' + params.setdefault('cls','toolbox') + table.Table.__init__(self,**params) + + if cols == 0 and rows == 0: cols = len(data) + if cols != 0 and rows != 0: rows = 0 + + self.tools = {} + + _value = value + + g = group.Group() + self.group = g + g.connect(CHANGE,self._change,None) + self.group.value = _value + + x,y,p,s = 0,0,None,1 + for ico,value in data: + #from __init__ import theme + img = pguglobals.app.theme.get(tool_cls+"."+ico,"","image") + if img: + i = basic.Image(img) + else: i = basic.Label(ico,cls=tool_cls+".label") + p = button.Tool(g,i,value,cls=tool_cls) + self.tools[ico] = p + #p.style.hexpand = 1 + #p.style.vexpand = 1 + self.add(p,x,y) + s = 0 + if cols != 0: x += 1 + if cols != 0 and x == cols: x,y = 0,y+1 + if rows != 0: y += 1 + if rows != 0 and y == rows: x,y = x+1,0 diff --git a/pgu/gui/deprecated.pyc b/pgu/gui/deprecated.pyc new file mode 100644 index 0000000..4e5c7bc --- /dev/null +++ b/pgu/gui/deprecated.pyc Binary files differ diff --git a/pgu/gui/dialog.py b/pgu/gui/dialog.py new file mode 100644 index 0000000..03b48f9 --- /dev/null +++ b/pgu/gui/dialog.py @@ -0,0 +1,157 @@ +""" +""" +import os + +from const import * +import table, area +import basic, input, button +import pguglobals + +class Dialog(table.Table): + """A dialog window with a title bar and an "close" button on the bar. + +
Dialog(title,main)
+ +
+
title
title widget, usually a label +
main
main widget, usually a container +
+ + Example + + title = gui.Label("My Title") + main = gui.Container() + #add stuff to the container... + + d = gui.Dialog(title,main) + d.open() + + """ + def __init__(self,title,main,**params): + params.setdefault('cls','dialog') + table.Table.__init__(self,**params) + + + self.tr() + self.td(title,align=-1,cls=self.cls+'.bar') + clos = button.Icon(self.cls+".bar.close") + clos.connect(CLICK,self.close,None) + self.td(clos,align=1,cls=self.cls+'.bar') + + self.tr() + self.td(main,colspan=2,cls=self.cls+".main") + + +# self.tr() +# +# +# t = table.Table(cls=self.cls+".bar") +# t.tr() +# t.td(title) +# clos = button.Icon(self.cls+".bar.close") +# t.td(clos,align=1) +# clos.connect(CLICK,self.close,None) +# self.add(t,0,0) +# +# main.rect.w,main.rect.h = main.resize() +# clos.rect.w,clos.rect.h = clos.resize() +# title.container.style.width = main.rect.w - clos.rect.w +# +# self.tr() +# self.td(main,cls=self.cls+".main") +# + + +class FileDialog(Dialog): + """A file picker dialog window. + +
FileDialog()
+

Some optional parameters:

+
+
title_txt
title text +
button_txt
button text +
path
initial path +
+ """ + + def __init__(self, title_txt="File Browser", button_txt="Okay", cls="dialog", path=None): + + cls1 = 'filedialog' + if not path: self.curdir = os.getcwd() + else: self.curdir = path + self.dir_img = basic.Image( + pguglobals.app.theme.get(cls1+'.folder', '', 'image')) + td_style = {'padding_left': 4, + 'padding_right': 4, + 'padding_top': 2, + 'padding_bottom': 2} + self.title = basic.Label(title_txt, cls=cls+".title.label") + self.body = table.Table() + self.list = area.List(width=350, height=150) + self.input_dir = input.Input() + self.input_file = input.Input() + self._list_dir_() + self.button_ok = button.Button(button_txt) + self.body.tr() + self.body.td(basic.Label("Folder"), style=td_style, align=-1) + self.body.td(self.input_dir, style=td_style) + self.body.tr() + self.body.td(self.list, colspan=3, style=td_style) + self.list.connect(CHANGE, self._item_select_changed_, None) + self.button_ok.connect(CLICK, self._button_okay_clicked_, None) + self.body.tr() + self.body.td(basic.Label("File"), style=td_style, align=-1) + self.body.td(self.input_file, style=td_style) + self.body.td(self.button_ok, style=td_style) + self.value = None + Dialog.__init__(self, self.title, self.body) + + def _list_dir_(self): + self.input_dir.value = self.curdir + self.input_dir.pos = len(self.curdir) + self.input_dir.vpos = 0 + dirs = [] + files = [] + try: + for i in os.listdir(self.curdir): + if os.path.isdir(os.path.join(self.curdir, i)): dirs.append(i) + else: files.append(i) + except: + self.input_file.value = "Opps! no access" + #if '..' not in dirs: dirs.append('..') + dirs.sort() + dirs = ['..'] + dirs + + files.sort() + for i in dirs: + #item = ListItem(image=self.dir_img, text=i, value=i) + self.list.add(i,image=self.dir_img,value=i) + for i in files: + #item = ListItem(image=None, text=i, value=i) + self.list.add(i,value=i) + #self.list.resize() + self.list.set_vertical_scroll(0) + #self.list.repaintall() + + + def _item_select_changed_(self, arg): + self.input_file.value = self.list.value + fname = os.path.abspath(os.path.join(self.curdir, self.input_file.value)) + if os.path.isdir(fname): + self.input_file.value = "" + self.curdir = fname + self.list.clear() + self._list_dir_() + + + def _button_okay_clicked_(self, arg): + if self.input_dir.value != self.curdir: + if os.path.isdir(self.input_dir.value): + self.input_file.value = "" + self.curdir = os.path.abspath(self.input_dir.value) + self.list.clear() + self._list_dir_() + else: + self.value = os.path.join(self.curdir, self.input_file.value) + self.send(CHANGE) + self.close() diff --git a/pgu/gui/dialog.pyc b/pgu/gui/dialog.pyc new file mode 100644 index 0000000..8837016 --- /dev/null +++ b/pgu/gui/dialog.pyc Binary files differ diff --git a/pgu/gui/document.py b/pgu/gui/document.py new file mode 100644 index 0000000..1bd96df --- /dev/null +++ b/pgu/gui/document.py @@ -0,0 +1,112 @@ +""" +""" +import pygame + +import container +import layout + +class _document_widget: + def __init__(self,w,align=None): + #w.rect.w,w.rect.h = w.resize() + #self.rect = w.rect + self.widget = w + if align != None: self.align = align + +class Document(container.Container): + """A document container contains many widgets strung together in a document format. (How informative!) + +
Document()
+ + """ + def __init__(self,**params): + params.setdefault('cls','document') + container.Container.__init__(self,**params) + self.layout = layout.Layout(pygame.Rect(0,0,self.rect.w,self.rect.h)) + + def add(self,e,align=None): + """Add a widget. + +
Document.add(e,align=None)
+ +
+
e
widget +
align
alignment (None,-1,0,1) +
+ """ + dw = _document_widget(e,align) + self.layout.add(dw) + e.container = self + e._c_dw = dw + self.widgets.append(e) + self.chsize() + + def remove(self,e): + self.layout._widgets.remove(e._c_dw) + self.widgets.remove(e) + self.chsize() + + + def block(self,align): + """Start a new block. + +
Document.block(align)
+ +
+
align
alignment of block (-1,0,1) +
+ """ + self.layout.add(align) + + def space(self,e): + """Add a spacer. + +
Document.space(e)
+ +
+
e
a (w,h) size for the spacer +
+ """ + self.layout.add(e) + + def br(self,height): + """Add a line break. + +
Document.br(height)
+ +
+
height
height of line break +
+ """ + self.layout.add((0,height)) + + def resize(self,width=None,height=None): + if self.style.width: width = self.style.width + if self.style.height: height = self.style.height + + for w in self.widgets: + w.rect.w,w.rect.h = w.resize() + + if (width != None and w.rect.w > width) or (height != None and w.rect.h > height): + w.rect.w,w.rect.h = w.resize(width,height) + + dw = w._c_dw + dw.rect = pygame.Rect(0,0,w.rect.w,w.rect.h) + + if width == None: width = 65535 + self.layout.rect = pygame.Rect(0,0,width,0) + self.layout.resize() + + _max_w = 0 + + for w in self.widgets: + #xt,xl,xb,xr = w.getspacing() + dw = w._c_dw + w.style.x,w.style.y,w.rect.w,w.rect.h = dw.rect.x,dw.rect.y,dw.rect.w,dw.rect.h + #w.resize() + w.rect.x,w.rect.y = w.style.x,w.style.y + _max_w = max(_max_w,w.rect.right) + + #self.rect.w = _max_w #self.layout.rect.w + #self.rect.h = self.layout.rect.h + #print 'document',_max_w,self.layout.rect.h + return _max_w,self.layout.rect.h diff --git a/pgu/gui/document.pyc b/pgu/gui/document.pyc new file mode 100644 index 0000000..1cbdfed --- /dev/null +++ b/pgu/gui/document.pyc Binary files differ diff --git a/pgu/gui/form.py b/pgu/gui/form.py new file mode 100644 index 0000000..9a874f9 --- /dev/null +++ b/pgu/gui/form.py @@ -0,0 +1,79 @@ +""" +""" +import widget + +class Form(widget.Widget): + """A form that automatically will contain all named widgets. + +

After a form is created, all named widget that are subsequently created are added + to that form. You may use dict style access to access named widgets.

+ +
Form()
+ + Example + + f = gui.Form() + + w = gui.Input("Phil",name="firstname") + w = gui.Input("Hassey",name="lastname") + + print f.results() + print '' + print f.items() + print '' + print f['firstname'].value + print f['lastname'].value + + """ + + def __init__(self): + widget.Widget.__init__(self,decorate=False) + self._elist = [] + self._emap = {} + self._dirty = 0 + Form.form = self + + def add(self,e,name=None,value=None): + if name != None: e.name = name + if value != None: e.value = value + self._elist.append(e) + self._dirty = 1 + + def _clean(self): + for e in self._elist[:]: + if not hasattr(e,'name') or e.name == None: + self._elist.remove(e) + self._emap = {} + for e in self._elist: + self._emap[e.name] = e + self._dirty = 0 + + def __getitem__(self,k): + if self._dirty: self._clean() + return self._emap[k] + + def __contains__(self,k): + if self._dirty: self._clean() + if k in self._emap: return True + return False + + def results(self): + """Return a dict of name => values. + +
Form.results(): return dict
+ """ + if self._dirty: self._clean() + r = {} + for e in self._elist: + r[e.name] = e.value + return r + + def items(self): + """Return a list of name, value keys. + +
Form.items(): return list
+ """ + return self.results().items() + + #def start(self): + # Object.start(self,-1) diff --git a/pgu/gui/form.pyc b/pgu/gui/form.pyc new file mode 100644 index 0000000..f9e0e44 --- /dev/null +++ b/pgu/gui/form.pyc Binary files differ diff --git a/pgu/gui/group.py b/pgu/gui/group.py new file mode 100644 index 0000000..bcb231a --- /dev/null +++ b/pgu/gui/group.py @@ -0,0 +1,43 @@ +""" +""" +from const import * +import widget + +class Group(widget.Widget): + """An object for grouping together Form elements. + +
Group(name=None,value=None)
+ +
+
name
name as used in the Form +
value
values that are currently selected in the group +
+ +

See [[gui-button]] for several examples.

+ +

When the value changes, an gui.CHANGE event is sent. + Although note, that when the value is a list, it may have to be sent by hand via + g.send(gui.CHANGE)

+ """ + + def __init__(self,name=None,value=None): + widget.Widget.__init__(self,name=name,value=value) + self.widgets = [] + + def add(self,w): + """Add a widget to this group. + +
Group.add(w)
+ """ + self.widgets.append(w) + + def __setattr__(self,k,v): + _v = self.__dict__.get(k,NOATTR) + self.__dict__[k] = v + if k == 'value' and _v != NOATTR and _v != v: + self._change() + + def _change(self): + self.send(CHANGE) + for w in self.widgets: + w.repaint() diff --git a/pgu/gui/group.pyc b/pgu/gui/group.pyc new file mode 100644 index 0000000..1766fc6 --- /dev/null +++ b/pgu/gui/group.pyc Binary files differ diff --git a/pgu/gui/input.py b/pgu/gui/input.py new file mode 100644 index 0000000..74c3c9e --- /dev/null +++ b/pgu/gui/input.py @@ -0,0 +1,166 @@ +""" +""" +import pygame +from pygame.locals import * + +from const import * +import widget + +class Input(widget.Widget): + """A single line text input. + +
Input(value="",size=20)
+ +
+
value
initial text +
size
size for the text box, in characters +
+ + Example + + w = Input(value="Cuzco the Goat",size=20) + + w = Input("Marbles") + + + """ + def __init__(self,value="",size=20,**params): + params.setdefault('cls','input') + widget.Widget.__init__(self,**params) + self.value = value + self.pos = len(str(value)) + self.vpos = 0 + self.font = self.style.font + w,h = self.font.size("e"*size) + if not self.style.height: self.style.height = h + if not self.style.width: self.style.width = w + #self.style.height = max(self.style.height,h) + #self.style.width = max(self.style.width,w) + #self.rect.w=w+self.style.padding_left+self.style.padding_right; + #self.rect.h=h+self.style.padding_top+self.style.padding_bottom; + + def paint(self,s): + + r = pygame.Rect(0,0,self.rect.w,self.rect.h) + + cs = 2 #NOTE: should be in a style + + w,h = self.font.size(self.value[0:self.pos]) + x = w-self.vpos + if x < 0: self.vpos -= -x + if x+cs > s.get_width(): self.vpos += x+cs-s.get_width() + + s.blit(self.font.render(self.value, 1, self.style.color),(-self.vpos,0)) + + if self.container.myfocus is self: + w,h = self.font.size(self.value[0:self.pos]) + r.x = w-self.vpos + r.w = cs + r.h = h + s.fill(self.style.color,r) + + def _setvalue(self,v): + self.__dict__['value'] = v + self.send(CHANGE) + + def event(self,e): + used = None + if e.type == KEYDOWN: + if e.key == K_BACKSPACE: + if self.pos: + self._setvalue(self.value[:self.pos-1] + self.value[self.pos:]) + self.pos -= 1 + elif e.key == K_DELETE: + if len(self.value) > self.pos: + self._setvalue(self.value[:self.pos] + self.value[self.pos+1:]) + elif e.key == K_HOME: + self.pos = 0 + elif e.key == K_END: + self.pos = len(self.value) + elif e.key == K_LEFT: + if self.pos > 0: self.pos -= 1 + used = True + elif e.key == K_RIGHT: + if self.pos < len(self.value): self.pos += 1 + used = True + elif e.key == K_RETURN: + self.next() + elif e.key == K_TAB: + pass + else: + #c = str(e.unicode) + try: + c = (e.unicode).encode('latin-1') + if c: + self._setvalue(self.value[:self.pos] + c + self.value[self.pos:]) + self.pos += 1 + except: #ignore weird characters + pass + self.repaint() + elif e.type == FOCUS: + self.repaint() + elif e.type == BLUR: + self.repaint() + + self.pcls = "" + if self.container.myfocus is self: self.pcls = "focus" + + return used + + def __setattr__(self,k,v): + if k == 'value': + if v == None: v = '' + v = str(v) + self.pos = len(v) + _v = self.__dict__.get(k,NOATTR) + self.__dict__[k]=v + if k == 'value' and _v != NOATTR and _v != v: + self.send(CHANGE) + self.repaint() + +class Password(Input): + """A password input, text is *-ed out. + +
Password(value="",size=20)
+ +
+
value
initial text +
size
size for the text box, in characters +
+ + Example + + w = Password(value="password",size=20) + + w = Password("53[r3+") + + + """ + + def paint(self,s): + hidden="*" + show=len(self.value)*hidden + + #print "self.value:",self.value + + if self.pos == None: self.pos = len(self.value) + + r = pygame.Rect(0,0,self.rect.w,self.rect.h) + + cs = 2 #NOTE: should be in a style + + w,h = self.font.size(show) + x = w-self.vpos + if x < 0: self.vpos -= -x + if x+cs > s.get_width(): self.vpos += x+cs-s.get_width() + + s.blit(self.font.render(show, 1, self.style.color),(-self.vpos,0)) + + if self.container.myfocus is self: + #w,h = self.font.size(self.value[0:self.pos]) + w,h = self.font.size(show[0:self.pos]) + r.x = w-self.vpos + r.w = cs + r.h = h + s.fill(self.style.color,r) + diff --git a/pgu/gui/input.pyc b/pgu/gui/input.pyc new file mode 100644 index 0000000..f6ba8c1 --- /dev/null +++ b/pgu/gui/input.pyc Binary files differ diff --git a/pgu/gui/keysym.py b/pgu/gui/keysym.py new file mode 100644 index 0000000..cc24089 --- /dev/null +++ b/pgu/gui/keysym.py @@ -0,0 +1,72 @@ +""" +""" +import pygame +from pygame.locals import * + +from const import * +import widget + +class Keysym(widget.Widget): + """A keysym input. + +

This widget records the keysym of the key pressed while this widget is in focus.

+ +
Keysym(value=None)
+ +
+
value
initial keysym, see pygame keysyms
+ + Example + + w = Input(value=pygame.locals.K_g) + + w = Input(pygame.locals.K_g) + + w = Input() + + + """ + + def __init__(self,value=None,**params): + params.setdefault('cls','keysym') + widget.Widget.__init__(self,**params) + self.value = value + + self.font = self.style.font + w,h = self.font.size("Right Super") #"Right Shift") + self.style.width,self.style.height = w,h + #self.rect.w=w+self.style.padding_left+self.style.padding_right + #self.rect.h=h+self.style.padding_top+self.style.padding_bottom + + def event(self,e): + used = None + if e.type == FOCUS or e.type == BLUR: self.repaint() + elif e.type == KEYDOWN: + if e.key != K_TAB: + self.value = e.key + self.repaint() + self.send(CHANGE) + used = True + self.next() + self.pcls = "" + if self.container.myfocus is self: self.pcls = "focus" + return used + + def paint(self,s): + r = pygame.rect.Rect(0,0,self.rect.w,self.rect.h) + #render_box(s,self.style.background,r) + if self.value == None: return + name = "" + for p in pygame.key.name(self.value).split(): name += p.capitalize()+" " + #r.x = self.style.padding_left; + #r.y = self.style.padding_bottom; + s.blit(self.style.font.render(name, 1, self.style.color), r) + + def __setattr__(self,k,v): + if k == 'value' and v != None: + v = int(v) + _v = self.__dict__.get(k,NOATTR) + self.__dict__[k]=v + if k == 'value' and _v != NOATTR and _v != v: + self.send(CHANGE) + self.repaint() diff --git a/pgu/gui/keysym.pyc b/pgu/gui/keysym.pyc new file mode 100644 index 0000000..cc4bf2a --- /dev/null +++ b/pgu/gui/keysym.pyc Binary files differ diff --git a/pgu/gui/layout.py b/pgu/gui/layout.py new file mode 100644 index 0000000..01fe0cb --- /dev/null +++ b/pgu/gui/layout.py @@ -0,0 +1,172 @@ +"""document layout engine.""" +class Layout: + """the document layout engine + + .widgets -- elements are kept in this list. read-only, use add to add items to it. + """ + + def __init__(self,rect=None): + """initialize the object with the size of the box.""" + self._widgets = [] + self.rect = rect + + def add(self,e): + """add a document element to the layout. + + a document element may be + - a tuple (w,h) if it is a whitespace element + - a tuple (0,h) if it is a linebreak element + - an integer -1,0,1 if it is a command to start a new block of elements that are aligned either left,center, or right. + - an object with a .rect (for size) -- such as a word element + - an object with a .rect (for size) and .align -- such as an image element + """ + + self._widgets.append(e) + + + def resize(self): + """resize the layout + this method recalculates the position of all document elements + after they have been added to the document. .rect.x,y will be updated for all + objects. + """ + self.init() + self.widgets = [] + for e in self._widgets: + if type(e) is tuple and e[0] != 0: + self.do_space(e) + elif type(e) is tuple and e[0] == 0: + self.do_br(e[1]) + elif type(e) is int: + self.do_block(align=e) + elif hasattr(e,'align'): + self.do_align(e) + else: + self.do_item(e) + self.line() + self.rect.h = max(self.y,self.left_bottom,self.right_bottom) + + def init(self): + self.x,self.y = self.rect.x,self.rect.y + self.left = self.rect.left + self.right = self.rect.right + self.left_bottom = 0 + self.right_bottom = 0 + self.y = self.rect.y + self.x = self.rect.x + self.h = 0 + + self.items = [] + self.align = -1 + + def getleft(self): + if self.y > self.left_bottom: + self.left = self.rect.left + return self.left + + def getright(self): + if self.y > self.right_bottom: + self.right = self.rect.right + return self.right + + def do_br(self,h): + self.line() + self.h = h + + def do_block(self,align=-1): + self.line() + self.align = align + + def do_align(self,e): + align = e.align + ox,oy,oh = self.x,self.y,self.h + w,h = e.rect.w,e.rect.h + + if align == 0: + self.line() + self.x = self.rect.left + (self.rect.width-w)/2 + self.fit = 0 + elif align == -1: + self.line() + self.y = max(self.left_bottom,self.y + self.h) + self.h = 0 + self.x = self.rect.left + elif align == 1: + self.line() + self.y = max(self.right_bottom,self.y + self.h) + self.h = 0 + self.x = self.rect.left + (self.rect.width-w) + + e.rect.x,e.rect.y = self.x,self.y + + self.x = self.x + w + self.y = self.y + + if align == 0: + self.h = max(self.h,h) + self.y = self.y + self.h + self.x = self.getleft() + self.h = 0 + elif align == -1: + self.left = self.x + self.left_bottom = self.y + h + self.x,self.y,self.h = ox + w,oy,oh + elif align == 1: + self.right = self.x - w + self.right_bottom = self.y + h + self.x,self.y,self.h = ox,oy,oh + + self.widgets.append(e) + + def do_space(self,e): + w,h = e + if self.x+w >= self.getright(): + self.line() + else: + self.items.append(e) + self.h = max(self.h,h) + self.x += w + + def do_item(self,e): + w,h = e.rect.w,e.rect.h + if self.x+w >= self.getright(): + self.line() + self.items.append(e) + self.h = max(self.h,h) + self.x += w + + def line(self): + x1 = self.getleft() + x2 = self.getright() + align = self.align + y = self.y + + if len(self.items) != 0 and type(self.items[-1]) == tuple: + del self.items[-1] + + w = 0 + for e in self.items: + if type(e) == tuple: w += e[0] + else: w += e.rect.w + + if align == -1: x = x1 + elif align == 0: + x = x1 + ((x2-x1)-w)/2 + self.fit = 0 + elif align == 1: x = x2 - w + + for e in self.items: + if type(e) == tuple: x += e[0] + else: + e.rect.x,e.rect.y = x,y + self.widgets.append(e) + x += e.rect.w + + self.items = [] + self.y = self.y + self.h + self.x = self.getleft() + self.h = 0 + + + +# vim: set filetype=python sts=4 sw=4 noet si : diff --git a/pgu/gui/layout.pyc b/pgu/gui/layout.pyc new file mode 100644 index 0000000..4e6c10f --- /dev/null +++ b/pgu/gui/layout.pyc Binary files differ diff --git a/pgu/gui/menus.py b/pgu/gui/menus.py new file mode 100644 index 0000000..3f8a08f --- /dev/null +++ b/pgu/gui/menus.py @@ -0,0 +1,121 @@ +""" +""" +from const import * +import table +import basic, button + +class _Menu_Options(table.Table): + def __init__(self,menu,**params): + table.Table.__init__(self,**params) + + self.menu = menu + + def event(self,e): + handled = False + arect = self.get_abs_rect() + + if e.type == MOUSEMOTION: + abspos = e.pos[0]+arect.x,e.pos[1]+arect.y + for w in self.menu.container.widgets: + if not w is self.menu: + mrect = w.get_abs_rect() + if mrect.collidepoint(abspos): + self.menu._close(None) + w._open(None) + handled = True + + if not handled: table.Table.event(self,e) + +class _Menu(button.Button): + def __init__(self,parent,widget=None,**params): #TODO widget= could conflict with module widget + params.setdefault('cls','menu') + button.Button.__init__(self,widget,**params) + + self.parent = parent + + self._cls = self.cls + self.options = _Menu_Options(self, cls=self.cls+".options") + + self.connect(CLICK,self._open,None) + + self.pos = 0 + + def _open(self,value): + self.parent.value = self + self.pcls = 'down' + + self.repaint() + self.container.open(self.options,self.rect.x,self.rect.bottom) + self.options.connect(BLUR,self._pass,None) + self.options.blur(self.options.myfocus) + self.options.connect(BLUR,self._close,None) + self.options.focus() + self.repaint() + + def _pass(self,value): + pass + + def _close(self,value): + self.pcls = '' + self.parent.value = None + self.repaint() + self.options.close() + + def _value(self,value): + self._close(None) + if value['fnc'] != None: + value['fnc'](value['value']) + + def event(self,e): + button.Button.event(self,e) + + if self.parent.value == self: + self.pcls = 'down' + + def add(self,w,fnc=None,value=None): + w.style.align = -1 + b = button.Button(w,cls=self.cls+".option") + b.connect(CLICK,self._value,{'fnc':fnc,'value':value}) + + self.options.tr() + self.options.add(b) + + return b + +class Menus(table.Table): + """A drop down menu bar. + +
Menus(data)
+ +
+
data
Menu data, a list of (path,fnc,value), see example below +
+ + Example + + data = [ + ('File/Save',fnc_save,None), + ('File/New',fnc_new,None), + ('Edit/Copy',fnc_copy,None), + ('Edit/Cut',fnc_cut,None), + ('Help/About',fnc_help,help_about_content), + ('Help/Reference',fnc_help,help_reference_content), + ] + w = Menus(data) + """ + + def __init__(self,data,menu_cls='menu',**params): + params.setdefault('cls','menus') + table.Table.__init__(self,**params) + + self.value = None + + n,m,mt = 0,None,None + for path,cmd,value in data: + parts = path.split("/") + if parts[0] != mt: + mt = parts[0] + m = _Menu(self,basic.Label(mt,cls=menu_cls+".label"),cls=menu_cls) + self.add(m,n,0) + n += 1 + m.add(basic.Label(parts[1],cls=m.cls+".option.label"),cmd,value) diff --git a/pgu/gui/menus.pyc b/pgu/gui/menus.pyc new file mode 100644 index 0000000..911de1b --- /dev/null +++ b/pgu/gui/menus.pyc Binary files differ diff --git a/pgu/gui/misc.py b/pgu/gui/misc.py new file mode 100644 index 0000000..afb10c5 --- /dev/null +++ b/pgu/gui/misc.py @@ -0,0 +1,43 @@ +from const import * +import widget +import pguglobals + +class ProgressBar(widget.Widget): + """A progress bar. + +
ProgressBar(value,min,max)
+ +
+
value
starting value +
min
minimum value rendered on the screen (usually 0) +
max
maximum value +
+ + Example + + w = gui.ProgressBar(0,0,100) + w.value = 25 + + """ + + def __init__(self,value,min,max,**params): + params.setdefault('cls','progressbar') + widget.Widget.__init__(self,**params) + self.min,self.max,self.value = min,max,value + + def paint(self,s): + r = pygame.rect.Rect(0,0,self.rect.w,self.rect.h) + r.w = r.w*(self.value-self.min)/(self.max-self.min) + self.bar = r + pguglobals.app.theme.render(s,self.style.bar,r) + + def __setattr__(self,k,v): + if k == 'value': + v = int(v) + v = max(v,self.min) + v = min(v,self.max) + _v = self.__dict__.get(k,NOATTR) + self.__dict__[k]=v + if k == 'value' and _v != NOATTR and _v != v: + self.send(CHANGE) + self.repaint() diff --git a/pgu/gui/misc.pyc b/pgu/gui/misc.pyc new file mode 100644 index 0000000..fcc796f --- /dev/null +++ b/pgu/gui/misc.pyc Binary files differ diff --git a/pgu/gui/pguglobals.py b/pgu/gui/pguglobals.py new file mode 100644 index 0000000..dc0e673 --- /dev/null +++ b/pgu/gui/pguglobals.py @@ -0,0 +1,7 @@ +# pguglobals.py - A place to stick global variables that need to be accessed +# from other modules. To avoid problems with circular imports +# this module should not import any other PGU module. + +# A global reference to the application instance (App class) +app = None + diff --git a/pgu/gui/pguglobals.pyc b/pgu/gui/pguglobals.pyc new file mode 100644 index 0000000..cd5a648 --- /dev/null +++ b/pgu/gui/pguglobals.pyc Binary files differ diff --git a/pgu/gui/select.py b/pgu/gui/select.py new file mode 100644 index 0000000..90fcfe4 --- /dev/null +++ b/pgu/gui/select.py @@ -0,0 +1,191 @@ +""" +""" +from const import * +import table +import basic, button + +class Select(table.Table): + """A select input. + +
Select(value=None)
+ +
+
value
initial value +
+ + Example + + w = Select(value="goats") + w.add("Cats","cats") + w.add("Goats","goats") + w.add("Dogs","Dogs") + + w.value = 'dogs' #changes the value from goats to dogs + + + """ + + def __init__(self,value=None,**params): + params.setdefault('cls','select') + table.Table.__init__(self,**params) + + self.top_selected = button.Button(cls=self.cls+".selected") + table.Table.add(self,self.top_selected) #,hexpand=1,vexpand=1)#,0,0) + self.top_selected.value = basic.Label(" ",cls=self.cls+".option.label") + + self.top_arrow = button.Button(basic.Image(self.style.arrow),cls=self.cls+".arrow") + table.Table.add(self,self.top_arrow) #,hexpand=1,vexpand=1) #,1,0) + + self.options = table.Table() #style={'border':3}) + self.options_first = None + + self.options.tr() + self.spacer_top = basic.Spacer(0,0) + self.options.add(self.spacer_top) + + self.options.tr() + self._options = table.Table(cls=self.cls+".options") + self.options.add(self._options) + + self.options.tr() + self.spacer_bottom = basic.Spacer(0,0) + self.options.add(self.spacer_bottom) + + + self.options.connect(BLUR,self._close,None) + self.spacer_top.connect(CLICK,self._close,None) + self.spacer_bottom.connect(CLICK,self._close,None) + + self.values = [] + self.value = value + + def resize(self,width=None,height=None): + max_w,max_h = 0,0 + for w in self._options.widgets: + w.rect.w,w.rect.h = w.resize() + max_w,max_h = max(max_w,w.rect.w),max(max_h,w.rect.h) + + #xt,xr,xb,xl = self.top_selected.getspacing() + self.top_selected.style.width = max_w #+ xl + xr + self.top_selected.style.height = max_h #+ xt + xb + + self.top_arrow.connect(CLICK,self._open,None) + self.top_selected.connect(CLICK,self._open,None) + + w,h = table.Table.resize(self,width,height) + + self.spacer_top.style.width, self.spacer_top.style.height = w,h + self.spacer_bottom.style.width, self.spacer_bottom.style.height = w,h + self._options.style.width = w + #HACK: sort of, but not a big one.. + self._options.resize() + + return w,h + + def _open(self,value): + sh = self.rect.h #spacer height + opts = self.options + + self.spacer_top.style.height = 0 + self.spacer_bottom.style.height = 0 + opts.rect.w, opts.rect.h = opts.resize() + h = opts.rect.h + + y = self.rect.y + c = self.container + while hasattr(c,'container'): + y += c.rect.y + if c.container == None: break + c = c.container + + if y + sh + h <= c.rect.h: #down + self.spacer_top.style.height = sh + dy = self.rect.y + else: #up + self.spacer_bottom.style.height = sh + dy = self.rect.y - h + + opts.rect.w, opts.rect.h = opts.resize() + + self.container.open(opts,self.rect.x,dy) + self.options_first.focus() + + def _close(self,value): +# print 'my close!' + self.options.close() + self.top_selected.focus() +# self.blur() +# self.focus() +# print self.container.myfocus == self + + def _setvalue(self,value): + self.value = value._value + if hasattr(self,'container'): + #self.chsize() + #HACK: improper use of resize() + #self.resize() #to recenter the new value, etc. + pass + # #self._resize() + + self._close(None) + #self.repaint() #this will happen anyways + + + + def __setattr__(self,k,v): + mywidget = None + if k == 'value': + for w in self.values: + if w._value == v: + mywidget = w + _v = self.__dict__.get(k,NOATTR) + self.__dict__[k]=v + if k == 'value' and _v != NOATTR and _v != v: + self.send(CHANGE) + self.repaint() + if k == 'value': + if not mywidget: + mywidget = basic.Label(" ",cls=self.cls+".option.label") + self.top_selected.value = mywidget + + def add(self,w,value=None): + """Add a widget, value item to the Select. + +
Select.add(widget,value=None)
+ +
+
widget
Widget or string to represent the item +
value
value for this item +
+ + Example + + w = Select() + w.add("Goat") #adds a Label + w.add("Goat","goat") #adds a Label with the value goat + w.add(gui.Label("Cuzco"),"goat") #adds a Label with value goat + + """ + + if type(w) == str: w = basic.Label(w,cls=self.cls+".option.label") + + w.style.align = -1 + b = button.Button(w,cls=self.cls+".option") + b.connect(CLICK,self._setvalue,w) + #b = w + #w.cls = self.cls+".option" + #w.cls = self.cls+".option" + + self._options.tr() + self._options.add(b) #,align=-1) + + if self.options_first == None: + self.options_first = b + #self._options.td(b, align=-1, cls=self.cls+".option") + #self._options.td(_List_Item(w,value=value),align=-1) + + if value != None: w._value = value + else: w._value = w + if self.value == w._value: + self.top_selected.value = w + self.values.append(w) diff --git a/pgu/gui/select.pyc b/pgu/gui/select.pyc new file mode 100644 index 0000000..152ad9c --- /dev/null +++ b/pgu/gui/select.pyc Binary files differ diff --git a/pgu/gui/slider.py b/pgu/gui/slider.py new file mode 100644 index 0000000..f4fa623 --- /dev/null +++ b/pgu/gui/slider.py @@ -0,0 +1,279 @@ +"""All sliders and scroll bar widgets have the same parameters. + +
Slider(value,min,max,size)
+
+
value
initial value +
min
minimum value +
max
maximum value +
size
size of bar in pixels +
+""" +import pygame +from pygame.locals import * + +from const import * +import widget +import table +import basic +import pguglobals + +_SLIDER_HORIZONTAL = 0 +_SLIDER_VERTICAL = 1 + +class _slider(widget.Widget): + def __init__(self,value,orient,min,max,size,step=1,**params): + params.setdefault('cls','slider') + widget.Widget.__init__(self,**params) + self.min,self.max,self.value,self.orient,self.size,self.step = min,max,value,orient,size,step + + + def paint(self,s): + + self.value = self.value + r = pygame.rect.Rect(0,0,self.style.width,self.style.height) + if self.orient == _SLIDER_HORIZONTAL: + r.x = (self.value-self.min) * (r.w-self.size) / max(1,self.max-self.min); + r.w = self.size; + else: + r.y = (self.value-self.min) * (r.h-self.size) / max(1,self.max-self.min); + r.h = self.size; + + self.bar = r + + pguglobals.app.theme.render(s,self.style.bar,r) + + def event(self,e): + used = None + r = pygame.rect.Rect(0,0,self.style.width,self.style.height) + adj = 0 + if e.type == ENTER: self.repaint() + elif e.type == EXIT: self.repaint() + elif e.type == MOUSEBUTTONDOWN: + if self.bar.collidepoint(e.pos): + self.grab = e.pos[0],e.pos[1] + self.grab_value = self.value + else: + x,y,adj = e.pos[0],e.pos[1],1 + self.grab = None + self.repaint() + elif e.type == MOUSEBUTTONUP: + #x,y,adj = e.pos[0],e.pos[1],1 + self.repaint() + elif e.type == MOUSEMOTION: + if 1 in e.buttons and self.container.myfocus is self: + if self.grab != None: + rel = e.pos[0]-self.grab[0],e.pos[1]-self.grab[1] + if self.orient == _SLIDER_HORIZONTAL: + d = (r.w - self.size) + if d != 0: self.value = self.grab_value + ((self.max-self.min) * rel[0] / d) + else: + d = (r.h - self.size) + if d != 0: self.value = self.grab_value + ((self.max-self.min) * rel[1] / d) + else: + x,y,adj = e.pos[0],e.pos[1],1 + + elif e.type is KEYDOWN: + if self.orient == _SLIDER_HORIZONTAL and e.key == K_LEFT: + self.value -= self.step + used = True + elif self.orient == _SLIDER_HORIZONTAL and e.key == K_RIGHT: + self.value += self.step + used = True + elif self.orient == _SLIDER_VERTICAL and e.key == K_UP: + self.value -= self.step + used = True + elif self.orient == _SLIDER_VERTICAL and e.key == K_DOWN: + self.value += self.step + used = True + + if adj: + if self.orient == _SLIDER_HORIZONTAL: + d = self.size/2 - (r.w/(self.max-self.min+1))/2 + self.value = (x-d) * (self.max-self.min) / (r.w-self.size+1) + self.min + else: + d = self.size/2 - (r.h/(self.max-self.min+1))/2 + self.value = (y-d) * (self.max-self.min) / (r.h-self.size+1) + self.min + + self.pcls = "" + if self.container.myhover is self: self.pcls = "hover" + if (self.container.myfocus is self and 1 in pygame.mouse.get_pressed()): self.pcls = "down" + + return used + + + def __setattr__(self,k,v): + if k == 'value': + v = int(v) + v = max(v,self.min) + v = min(v,self.max) + _v = self.__dict__.get(k,NOATTR) + self.__dict__[k]=v + if k == 'value' and _v != NOATTR and _v != v: + self.send(CHANGE) + self.repaint() + + if hasattr(self,'size'): + sz = min(self.size,max(self.style.width,self.style.height)) + sz = max(sz,min(self.style.width,self.style.height)) + self.__dict__['size'] = sz + + if hasattr(self,'max') and hasattr(self,'min'): + if self.max < self.min: self.max = self.min + +class VSlider(_slider): + """A verticle slider. + +
VSlider(value,min,max,size)
+ """ + def __init__(self,value,min,max,size,step=1,**params): + params.setdefault('cls','vslider') + _slider.__init__(self,value,_SLIDER_VERTICAL,min,max,size,step,**params) + +class HSlider(_slider): + """A horizontal slider. + +
HSlider(value,min,max,size)
+ """ + def __init__(self,value,min,max,size,step=1,**params): + params.setdefault('cls','hslider') + _slider.__init__(self,value,_SLIDER_HORIZONTAL,min,max,size,step,**params) + +class HScrollBar(table.Table): + """A horizontal scroll bar. + +
HScrollBar(value,min,max,size,step=1)
+ """ + def __init__(self,value,min,max,size,step=1,**params): + params.setdefault('cls','hscrollbar') + + table.Table.__init__(self,**params) + + self.slider = _slider(value,_SLIDER_HORIZONTAL,min,max,size,step=step,cls=self.cls+'.slider') + + self.minus = basic.Image(self.style.minus) + self.minus.connect(MOUSEBUTTONDOWN,self._click,-1) + self.slider.connect(CHANGE,self.send,CHANGE) + + self.minus2 = basic.Image(self.style.minus) + self.minus2.connect(MOUSEBUTTONDOWN,self._click,-1) + + self.plus = basic.Image(self.style.plus) + self.plus.connect(MOUSEBUTTONDOWN,self._click,1) + + self.size = size + + def _click(self,value): + self.slider.value += self.slider.step*value + + def resize(self,width=None,height=None): + self.clear() + self.tr() + + w = self.style.width + h = self.slider.style.height + ww = 0 + + if w > (h*2 + self.minus.style.width+self.plus.style.width): + self.td(self.minus) + ww += self.minus.style.width + + self.td(self.slider) + + if w > (h*2 + self.minus.style.width+self.minus2.style.width+self.plus.style.width): + self.td(self.minus2) + ww += self.minus2.style.width + + if w > (h*2 + self.minus.style.width+self.plus.style.width): + self.td(self.plus) + ww += self.plus.style.width + + + #HACK: handle theme sizing properly + xt,xr,xb,xl = pguglobals.app.theme.getspacing(self.slider) + ww += xr+xl + + self.slider.style.width = self.style.width - ww + setattr(self.slider,'size',self.size * self.slider.style.width / max(1,self.style.width)) + return table.Table.resize(self,width,height) + + + def __setattr__(self,k,v): + if k in ('min','max','value','step'): + return setattr(self.slider,k,v) + self.__dict__[k]=v + + def __getattr__(self,k): + if k in ('min','max','value','step'): + return getattr(self.slider,k) + return table.Table.__getattr__(self,k) #self.__dict__[k] + +class VScrollBar(table.Table): + """A vertical scroll bar. + +
VScrollBar(value,min,max,size,step=1)
+ """ + def __init__(self,value,min,max,size,step=1,**params): + params.setdefault('cls','vscrollbar') + + table.Table.__init__(self,**params) + + self.minus = basic.Image(self.style.minus) + self.minus.connect(MOUSEBUTTONDOWN,self._click,-1) + + self.minus2 = basic.Image(self.style.minus) + self.minus2.connect(MOUSEBUTTONDOWN,self._click,-1) + + self.plus = basic.Image(self.style.plus) + self.plus.connect(MOUSEBUTTONDOWN,self._click,1) + + self.slider = _slider(value,_SLIDER_VERTICAL,min,max,size,step=step,cls=self.cls+'.slider') + self.slider.connect(CHANGE,self.send,CHANGE) + + self.size = size + + def _click(self,value): + self.slider.value += self.slider.step*value + + def resize(self,width=None,height=None): + self.clear() + + h = self.style.height + w = self.slider.style.width + hh = 0 + + if h > (w*2 + self.minus.style.height+self.plus.style.height): + self.tr() + self.td(self.minus) + hh += self.minus.style.height + + self.tr() + self.td(self.slider) + + if h > (w*2 + self.minus.style.height+self.minus2.style.height+self.plus.style.height): + self.tr() + self.td(self.minus2) + hh += self.minus2.style.height + + if h > (w*2 + self.minus.style.height+self.plus.style.height): + self.tr() + self.td(self.plus) + hh += self.plus.style.height + + + #HACK: handle theme sizing properly + xt,xr,xb,xl = pguglobals.app.theme.getspacing(self.slider) + hh += xt+xb + + self.slider.style.height = self.style.height - hh + setattr(self.slider,'size',self.size * self.slider.style.height / max(1,self.style.height)) + return table.Table.resize(self,width,height) + + def __setattr__(self,k,v): + if k in ('min','max','value','step'): + return setattr(self.slider,k,v) + self.__dict__[k]=v + + def __getattr__(self,k): + if k in ('min','max','value','step'): + return getattr(self.slider,k) + return table.Table.__getattr__(self,k) diff --git a/pgu/gui/slider.pyc b/pgu/gui/slider.pyc new file mode 100644 index 0000000..9ccc719 --- /dev/null +++ b/pgu/gui/slider.pyc Binary files differ diff --git a/pgu/gui/style.py b/pgu/gui/style.py new file mode 100644 index 0000000..3060928 --- /dev/null +++ b/pgu/gui/style.py @@ -0,0 +1,41 @@ +""" +""" + +import pguglobals + +class Style: + """The class used by widget for the widget.style + +

This object is used mainly as a dictionary, accessed via widget.style.attr, as opposed to + widget.style['attr']. It automatically grabs information from the theme via value = theme.get(widget.cls,widget.pcls,attr).

+ + """ + def __init__(self,o,dict): + self.obj = o + for k,v in dict.items(): self.__dict__[k]=v + self._cache = {} + + def __getattr__(self,k): + key = self.obj.cls,self.obj.pcls,k + if key not in self._cache: + self._cache[key] = Style_get(self.obj.cls,self.obj.pcls,k) + v = self._cache[key] + if k in ( + 'border_top','border_right','border_bottom','border_left', + 'padding_top','padding_right','padding_bottom','padding_left', + 'margin_top','margin_right','margin_bottom','margin_left', + 'align','valign','width','height', + ): self.__dict__[k] = v + return v + + def __setattr__(self,k,v): + self.__dict__[k] = v + + +Style_cache = {} +def Style_get(cls,pcls,k): + key = cls,pcls,k + if key not in Style_cache: + Style_cache[key] = pguglobals.app.theme.get(cls,pcls,k) + return Style_cache[key] + diff --git a/pgu/gui/style.pyc b/pgu/gui/style.pyc new file mode 100644 index 0000000..ae59cf4 --- /dev/null +++ b/pgu/gui/style.pyc Binary files differ diff --git a/pgu/gui/surface.py b/pgu/gui/surface.py new file mode 100644 index 0000000..82d92d1 --- /dev/null +++ b/pgu/gui/surface.py @@ -0,0 +1,142 @@ +""" +""" +import pygame + +def subsurface(s,r): + """Return the subsurface of a surface, with some help, checks. + +
subsurface(s,r): return surface
+ """ + r = pygame.Rect(r) + if r.x < 0 or r.y < 0: + raise "gui.subsurface: %d %d %s"%(s.get_width(),s.get_height(),r) + w,h = s.get_width(),s.get_height() + if r.right > w: + r.w -= r.right-w + if r.bottom > h: + r.h -= r.bottom-h + assert(r.w >= 0 and r.h >= 0) + return s.subsurface(r) + +class ProxySurface: + """ + A surface-like object which smartly handle out-of-area blitting. + +
ProxySurface(parent, rect, real_surface=None, offset=(0, 0))
+ +

only one of parent and real_surface should be supplied (non None)

+
+
parent
a ProxySurface object +
real_surface
a pygame Surface object +
+ + Variables + +
+
mysubsurface
a real and valid pygame.Surface object to be used + for blitting. +
x, y
if the proxy surface is lefter or higher than the parent, + x, y hold the diffs. +
offset
an optional feature which let you scroll the whole blitted + content. +
+ """ + def __init__(self, parent, rect, real_surface, offset=(0, 0)): + self.offset = offset + self.x = self.y = 0 + if rect.x < 0: self.x = rect.x + if rect.y < 0: self.y = rect.y + self.real_surface = real_surface + if real_surface == None: + self.mysubsurface = parent.mysubsurface.subsurface( + parent.mysubsurface.get_rect().clip(rect)) + else: + self.mysubsurface = real_surface.subsurface( + real_surface.get_rect().clip(rect)) + rect.topleft = (0, 0) + self.rect = rect + + def blit(self, s, pos, rect=None): + if rect == None: rect = s.get_rect() + pos = (pos[0] + self.offset[0] + self.x, pos[1] + self.offset[1] + self.y) + self.mysubsurface.blit(s, pos, rect) + + def subsurface(self, rect): + r = pygame.Rect(rect).move(self.offset[0] + self.x, + self.offset[1] + self.y) + return ProxySurface(self, r, self.real_surface) + + def fill(self, color, rect=None): + if rect != None: self.mysubsurface.fill(color, rect) + else: self.mysubsurface.fill(color) + def get_rect(self): return self.rect + def get_width(self): return self.rect[2] + def get_height(self): return self.rect[3] + def get_abs_offset(): return self.rect[:2] + def get_abs_parent(): return self.mysubsurface.get_abs_parent() + def set_clip(self, rect=None): + if rect == None: self.mysubsurface.set_clip() + else: + rect = [rect[0] + self.offset[0] + self.x, rect[1] + self.offset[0] + self.y, rect[2], rect[3]] + self.mysubsurface.set_clip(rect) + + + + + + +class xProxySurface: + """ + A surface-like object which smartly handle out-of-area blitting. + +
ProxySurface(parent, rect, real_surface=None, offset=(0, 0))
+ +

only one of parent and real_surface should be supplied (non None)

+
+
parent
a ProxySurface object +
real_surface
a pygame Surface object +
+ + Variables + +
+
mysubsurface
a real and valid pygame.Surface object to be used + for blitting. +
x, y
if the proxy surface is lefter or higher than the parent, + x, y hold the diffs. +
offset
an optional feature which let you scroll the whole blitted + content. +
+ """ + def __init__(self, parent, rect, real_surface, offset=(0, 0)): + self.offset = offset + self.x = self.y = 0 + if rect[0] < 0: self.x = rect[0] + if rect[1] < 0: self.y = rect[1] + self.real_surface = real_surface + if real_surface == None: + self.mysubsurface = parent.mysubsurface.subsurface(parent.mysubsurface.get_rect().clip(rect)) + else: + self.mysubsurface = real_surface.subsurface(real_surface.get_rect().clip(rect)) + rect[0], rect[1] = 0, 0 + self.rect = rect + + def blit(self, s, pos, rect=None): + if rect == None: rect = s.get_rect() + pos = (pos[0] + self.offset[0] + self.x, pos[1] + self.offset[1] + self.y) + self.mysubsurface.blit(s, pos, rect) + + def subsurface(self, rect): return ProxySurface(self, pygame.Rect(rect).move(self.offset[0] + self.x, self.offset[1] + self.y),self.real_surface) + def fill(self, color, rect=None): + if rect != None: self.mysubsurface.fill(color, rect) + else: self.mysubsurface.fill(color) + def get_rect(self): return self.rect + def get_width(self): return self.rect[2] + def get_height(self): return self.rect[3] + def get_abs_offset(): return self.rect[:2] + def get_abs_parent(): return self.mysubsurface.get_abs_parent() + def set_clip(self, rect=None): + if rect == None: self.mysubsurface.set_clip() + else: + rect = [rect[0] + self.offset[0] + self.x, rect[1] + self.offset[0] + self.y, rect[2], rect[3]] + self.mysubsurface.set_clip(rect) diff --git a/pgu/gui/surface.pyc b/pgu/gui/surface.pyc new file mode 100644 index 0000000..13f9f89 --- /dev/null +++ b/pgu/gui/surface.pyc Binary files differ diff --git a/pgu/gui/table.py b/pgu/gui/table.py new file mode 100644 index 0000000..1a92593 --- /dev/null +++ b/pgu/gui/table.py @@ -0,0 +1,331 @@ +""" +""" +from const import * +import container + +class Table(container.Container): + """A table style container. + +

If you know HTML, this should all work roughly how you would expect. If you are not + familiar with HTML, please read Tables in HTML Documents. Pay attention to TABLE, TR, TD related parts of the document.

+ +
Table()
+ + Example + + t = gui.Table() + + t.tr() + t.td(gui.Label("First Name"), align=-1) + t.td(gui.Input()) + + t.tr() + t.td(gui.Label("Last Name"), align=-1) + t.td(gui.Input()) + + + """ + + + def __init__(self, **params): + params.setdefault('cls','table') + container.Container.__init__(self, **params) + self._rows = [] + self._curRow = 0 + self._trok = False + + def getRows(self): + return len(self._rows) + + def getColumns(self): + if self._rows: + return len(self._rows[0]) + else: + return 0 + + def remove_row(self, n): #NOTE: won't work in all cases. + if n >= self.getRows(): + print "Trying to remove a nonexistant row:", n, "there are only", self.getRows(), "rows" + return + + for cell in self._rows[n]: + if isinstance(cell, dict) and cell["widget"] in self.widgets: + #print 'removing widget' + self.widgets.remove(cell["widget"]) + del self._rows[n] + #print "got here" + + for w in self.widgets: + if w.style.row > n: w.style.row -= 1 + + if self._curRow >= n: + self._curRow -= 1 + + #self.rect.w, self.rect.h = self.resize() + #self.repaint() + + self.chsize() + + def clear(self): + self._rows = [] + self._curRow = 0 + self._trok = False + + self.widgets = [] + + self.chsize() + + #print 'clear',self,self._rows + + def _addRow(self): + self._rows.append([None for x in xrange(self.getColumns())]) + + def tr(self): + """Start on the next row.""" + if not self._trok: + self._trok = True + return + self._curRow += 1 + if self.getRows() <= self._curRow: + self._addRow() + + def _addColumn(self): + if not self._rows: + self._addRow() + for row in self._rows: + row.append(None) + + def _setCell(self, w, col, row, colspan=1, rowspan=1): + #make room for the widget by adding columns and rows + while self.getColumns() < col + colspan: + self._addColumn() + while self.getRows() < row + rowspan: + self._addRow() + + #print w.__class__.__name__,col,row,colspan,rowspan + + #actual widget setting and modification stuff + w.container = self + w.style.row = row #HACK - to work with gal's list + w.style.col = col #HACK - to work with gal's list + self._rows[row][col] = {"widget":w, "colspan":colspan, "rowspan":rowspan} + self.widgets.append(self._rows[row][col]["widget"]) + + #set the spanned columns + #for acell in xrange(col + 1, col + colspan): + # self._rows[row][acell] = True + + #set the spanned rows and the columns on them + #for arow in xrange(row + 1, row + rowspan): + # for acell in xrange(col, col + colspan): #incorrect? + # self._rows[arow][acell] = True + + for arow in xrange(row, row + rowspan): + for acell in xrange(col, col + colspan): #incorrect? + if row != arow or col != acell: + self._rows[arow][acell] = True + + + def td(self, w, col=None, row=None, colspan=1, rowspan=1, **params): + """Add a widget to a table after wrapping it in a TD container. + +
Table.td(w,col=None,row=None,colspan=1,rowspan=1,**params)
+ +
+
w
widget +
col
column +
row
row +
colspan
colspan +
rowspan
rowspan +
align
horizontal alignment (-1,0,1) +
valign
vertical alignment (-1,0,1) +
params
other params for the TD container, style information, etc +
+ """ + + Table.add(self,_Table_td(w, **params), col=col, row=row, colspan=colspan, rowspan=rowspan) + + def add(self, w, col=None, row=None, colspan=1, rowspan=1): + """Add a widget directly into the table, without wrapping it in a TD container. + +
Table.add(w,col=None,row=None,colspan=1,rowspan=1)
+ +

See Table.td for an explanation of the parameters.

+ """ + self._trok = True + #if no row was specifically specified, set it to the current row + if row is None: + row = self._curRow + #print row + + #if its going to be a new row, have it be on the first column + if row >= self.getRows(): + col = 0 + + #try to find an open cell for the widget + if col is None: + for cell in xrange(self.getColumns()): + if col is None and not self._rows[row][cell]: + col = cell + break + + #otherwise put the widget in a new column + if col is None: + col = self.getColumns() + + self._setCell(w, col, row, colspan=colspan, rowspan=rowspan) + + self.chsize() + return + + def remove(self,w): + if hasattr(w,'_table_td'): w = w._table_td + row,col = w.style.row,w.style.col + cell = self._rows[row][col] + colspan,rowspan = cell['colspan'],cell['rowspan'] + + for arow in xrange(row , row + rowspan): + for acell in xrange(col, col + colspan): #incorrect? + self._rows[arow][acell] = False + self.widgets.remove(w) + self.chsize() + + + + def resize(self, width=None, height=None): + #if 1 or self.getRows() == 82: + #print '' + #print 'resize',self.getRows(),self.getColumns(),width,height + #import inspect + #for obj,fname,line,fnc,code,n in inspect.stack()[9:20]: + # print fname,line,':',fnc,code[0].strip() + + + #resize the widgets to their smallest size + for w in self.widgets: + w.rect.w, w.rect.h = w.resize() + + #calculate row heights and column widths + rowsizes = [0 for y in xrange(self.getRows())] + columnsizes = [0 for x in xrange(self.getColumns())] + for row in xrange(self.getRows()): + for cell in xrange(self.getColumns()): + if self._rows[row][cell] and self._rows[row][cell] is not True: + if not self._rows[row][cell]["colspan"] > 1: + columnsizes[cell] = max(columnsizes[cell], self._rows[row][cell]["widget"].rect.w) + if not self._rows[row][cell]["rowspan"] > 1: + rowsizes[row] = max(rowsizes[row], self._rows[row][cell]["widget"].rect.h) + + #distribute extra space if necessary for wide colspanning/rowspanning + for row in xrange(self.getRows()): + for cell in xrange(self.getColumns()): + if self._rows[row][cell] and self._rows[row][cell] is not True: + if self._rows[row][cell]["colspan"] > 1: + columns = xrange(cell, cell + self._rows[row][cell]["colspan"]) + totalwidth = 0 + for acol in columns: + totalwidth += columnsizes[acol] + if totalwidth < self._rows[row][cell]["widget"].rect.w: + for acol in columns: + columnsizes[acol] += _table_div(self._rows[row][cell]["widget"].rect.w - totalwidth, self._rows[row][cell]["colspan"],acol) + if self._rows[row][cell]["rowspan"] > 1: + rows = xrange(row, row + self._rows[row][cell]["rowspan"]) + totalheight = 0 + for arow in rows: + totalheight += rowsizes[arow] + if totalheight < self._rows[row][cell]["widget"].rect.h: + for arow in rows: + rowsizes[arow] += _table_div(self._rows[row][cell]["widget"].rect.h - totalheight, self._rows[row][cell]["rowspan"],arow) + + #make everything fill out to self.style.width, self.style.heigh, not exact, but pretty close... + w, h = sum(columnsizes), sum(rowsizes) + if w > 0 and w < self.style.width and len(columnsizes): + d = (self.style.width - w) + for n in xrange(0, len(columnsizes)): + v = columnsizes[n] + columnsizes[n] += v * d / w + if h > 0 and h < self.style.height and len(rowsizes): + d = (self.style.height - h) / len(rowsizes) + for n in xrange(0, len(rowsizes)): + v = rowsizes[n] + rowsizes[n] += v * d / h + + #set the widget's position by calculating their row/column x/y offset + cellpositions = [[[sum(columnsizes[0:cell]), sum(rowsizes[0:row])] for cell in xrange(self.getColumns())] for row in xrange(self.getRows())] + for row in xrange(self.getRows()): + for cell in xrange(self.getColumns()): + if self._rows[row][cell] and self._rows[row][cell] is not True: + x, y = cellpositions[row][cell] + w = sum(columnsizes[cell:cell+self._rows[row][cell]["colspan"]]) + h = sum(rowsizes[row:row+self._rows[row][cell]["rowspan"]]) + + widget = self._rows[row][cell]["widget"] + widget.rect.x = x + widget.rect.y = y + if 1 and (w,h) != (widget.rect.w,widget.rect.h): +# if h > 20: +# print widget.widget.__class__.__name__, (widget.rect.w,widget.rect.h),'=>',(w,h) + widget.rect.w, widget.rect.h = widget.resize(w, h) + + #print self._rows[row][cell]["widget"].rect + + print 'columnsizes', columnsizes + print sum(columnsizes) + size = sum(columnsizes), sum(rowsizes); print size + + #return the tables final size + return sum(columnsizes),sum(rowsizes) + + +def _table_div(a,b,c): + v,r = a/b, a%b + if r != 0 and (c%b) self.style.width) or (self.style.height!=0 and w.rect.h > self.style.height): +# ww,hh = None,None +# if self.style.width: ww = self.style.width +# if self.style.height: hh = self.style.height +# w.rect.w,w.rect.h = w.resize(ww,hh) + + + #in the case that the widget is too big, we try to resize it + if (width != None and width < w.rect.w) or (height != None and height < w.rect.h): + w.rect.w,w.rect.h = w.resize(width,height) + + width = max(width,w.rect.w,self.style.width) #,self.style.cell_width) + height = max(height,w.rect.h,self.style.height) #,self.style.cell_height) + + dx = width-w.rect.w + dy = height-w.rect.h + w.rect.x = (self.style.align+1)*dx/2 + w.rect.y = (self.style.valign+1)*dy/2 + + return width,height diff --git a/pgu/gui/table.pyc b/pgu/gui/table.pyc new file mode 100644 index 0000000..cc1e5d8 --- /dev/null +++ b/pgu/gui/table.pyc Binary files differ diff --git a/pgu/gui/textarea.py b/pgu/gui/textarea.py new file mode 100644 index 0000000..667076a --- /dev/null +++ b/pgu/gui/textarea.py @@ -0,0 +1,287 @@ +""" +""" +import pygame +from pygame.locals import * + +from const import * +import widget + +class TextArea(widget.Widget): + """A multi-line text input. + +
TextArea(value="",width = 120, height = 30, size=20)
+ +
+
value
initial text +
size
size for the text box, in characters +
+ + Example + + w = TextArea(value="Cuzco the Goat",size=20) + + w = TextArea("Marbles") + + w = TextArea("Groucho\nHarpo\nChico\nGummo\nZeppo\n\nMarx", 200, 400, 12) + + + """ + def __init__(self,value="",width = 120, height = 30, size=20,**params): + params.setdefault('cls','input') + params.setdefault('width', width) + params.setdefault('height', height) + + widget.Widget.__init__(self,**params) + self.value = value # The value of the TextArea + self.pos = len(str(value)) # The position of the cursor + self.vscroll = 0 # The number of lines that the TextArea is currently scrolled + self.font = self.style.font # The font used for rendering the text + self.cursor_w = 2 # Cursor width (NOTE: should be in a style) + w,h = self.font.size("e"*size) + if not self.style.height: self.style.height = h + if not self.style.width: self.style.width = w + + def resize(self,width=None,height=None): + if (width != None) and (height != None): + self.rect = pygame.Rect(self.rect.x, self.rect.y, width, height) + return self.rect.w, self.rect.h + + def paint(self,s): + + # TODO: What's up with this 20 magic number? It's the margin of the left and right sides, but I'm not sure how this should be gotten other than by trial and error. + max_line_w = self.rect.w - 20 + + # Update the line allocation for the box's value + self.doLines(max_line_w) + + # Make sure that the vpos and hpos of the cursor is set properly + self.updateCursorPos() + + # Make sure that we're scrolled vertically such that the cursor is visible + if (self.vscroll < 0): + self.vscroll = 0 + if (self.vpos < self.vscroll): + self.vscroll = self.vpos + elif ((self.vpos - self.vscroll + 1) * self.line_h > self.rect.h): + self.vscroll = - (self.rect.h / self.line_h - self.vpos - 1) + + # Blit each of the lines in turn + cnt = 0 + for line in self.lines: + line_pos = (0, (cnt - self.vscroll) * self.line_h) + if (line_pos[1] >= 0) and (line_pos[1] < self.rect.h): + s.blit( self.font.render(line, 1, self.style.color), line_pos ) + cnt += 1 + + # If the textarea is focused, then also show the cursor + if self.container.myfocus is self: + r = self.getCursorRect() + s.fill(self.style.color,r) + + # This function updates self.vpos and self.hpos based on self.pos + def updateCursorPos(self): + self.vpos = 0 # Reset the current line that the cursor is on + self.hpos = 0 + + line_cnt = 0 + char_cnt = 0 + + for line in self.lines: + line_char_start = char_cnt # The number of characters at the start of the line + + # Keep track of the character count for words + char_cnt += len(line) + + # If our cursor count is still less than the cursor position, then we can update our cursor line to assume that it's at least on this line + if (char_cnt > self.pos): + self.vpos = line_cnt + self.hpos = self.pos - line_char_start + + break # Now that we know where our cursor is, we exit the loop + + line_cnt += 1 + + if (char_cnt <= self.pos) and (len(self.lines) > 0): + self.vpos = len(self.lines) - 1 + self.hpos = len(self.lines[ self.vpos ] ) + + # Returns a rectangle that is of the size and position of where the cursor is drawn + def getCursorRect(self): + lw = 0 + if (len(self.lines) > 0): + lw, lh = self.font.size( self.lines[ self.vpos ][ 0:self.hpos ] ) + + r = pygame.Rect(lw, (self.vpos - self.vscroll) * self.line_h, self.cursor_w, self.line_h) + return r + + # This function sets the cursor position according to an x/y value (such as by from a mouse click) + def setCursorByXY(self, (x, y)): + self.vpos = ((int) (y / self.line_h)) + self.vscroll + if (self.vpos >= len(self.lines)): + self.vpos = len(self.lines) - 1 + + currentLine = self.lines[ self.vpos ] + + for cnt in range(0, len(currentLine) ): + self.hpos = cnt + lw, lh = self.font.size( currentLine[ 0:self.hpos + 1 ] ) + if (lw > x): + break + + lw, lh = self.font.size( currentLine ) + if (lw < x): + self.hpos = len(currentLine) + + self.setCursorByHVPos() + + # This function sets the cursor position by the horizontal/vertical cursor position. + def setCursorByHVPos(self): + line_cnt = 0 + char_cnt = 0 + + for line in self.lines: + line_char_start = char_cnt # The number of characters at the start of the line + + # Keep track of the character count for words + char_cnt += len(line) + + # If we're on the proper line + if (line_cnt == self.vpos): + # Make sure that we're not trying to go over the edge of the current line + if ( self.hpos >= len(line) ): + self.hpos = len(line) - 1 + # Set the cursor position + self.pos = line_char_start + self.hpos + break # Now that we've set our cursor position, we exit the loop + + line_cnt += 1 + + # Splits up the text found in the control's value, and assigns it into the lines array + def doLines(self, max_line_w): + self.line_h = 10 + self.lines = [] # Create an empty starter list to start things out. + + inx = 0 + line_start = 0 + while inx >= 0: + # Find the next breakable whitespace + # HACK: Find a better way to do this to include tabs and system characters and whatnot. + prev_word_start = inx # Store the previous whitespace + spc_inx = self.value.find(' ', inx+1) + nl_inx = self.value.find('\n', inx+1) + + if (min(spc_inx, nl_inx) == -1): + inx = max(spc_inx, nl_inx) + else: + inx = min(spc_inx, nl_inx) + + # Measure the current line + lw, self.line_h = self.font.size( self.value[ line_start : inx ] ) + + # If we exceeded the max line width, then create a new line + if (lw > max_line_w): + #Fall back to the previous word start + self.lines.append(self.value[ line_start : prev_word_start + 1 ]) + line_start = prev_word_start + 1 + # TODO: Check for extra-long words here that exceed the length of a line, to wrap mid-word + + # If we reached the end of our text + if (inx < 0): + # Then make sure we added the last of the line + if (line_start < len( self.value ) ): + self.lines.append( self.value[ line_start : len( self.value ) ] ) + # If we reached a hard line break + elif (self.value[inx] == "\n"): + # Then make a line break here as well. + newline = self.value[ line_start : inx + 1 ] + newline = newline.replace("\n", " ") # HACK: We know we have a newline character, which doesn't print nicely, so make it into a space. Comment this out to see what I mean. + self.lines.append( newline ) + + line_start = inx + 1 + else: + # Otherwise, we just continue progressing to the next space + pass + + def _setvalue(self,v): + self.__dict__['value'] = v + self.send(CHANGE) + + def event(self,e): + used = None + if e.type == KEYDOWN: + if e.key == K_BACKSPACE: + if self.pos: + self._setvalue(self.value[:self.pos-1] + self.value[self.pos:]) + self.pos -= 1 + elif e.key == K_DELETE: + if len(self.value) > self.pos: + self._setvalue(self.value[:self.pos] + self.value[self.pos+1:]) + elif e.key == K_HOME: + # Find the previous newline + newPos = self.value.rfind('\n', 0, self.pos) + if (newPos >= 0): + self.pos = newPos + elif e.key == K_END: + # Find the previous newline + newPos = self.value.find('\n', self.pos, len(self.value) ) + if (newPos >= 0): + self.pos = newPos + elif e.key == K_LEFT: + if self.pos > 0: self.pos -= 1 + used = True + elif e.key == K_RIGHT: + if self.pos < len(self.value): self.pos += 1 + used = True + elif e.key == K_UP: + self.vpos -= 1 + self.setCursorByHVPos() + elif e.key == K_DOWN: + self.vpos += 1 + self.setCursorByHVPos() + # The following return/tab keys are standard for PGU widgets, but I took them out here to facilitate multi-line text editing +# elif e.key == K_RETURN: +# self.next() +# elif e.key == K_TAB: +# pass + else: + #c = str(e.unicode) + try: + if (e.key == K_RETURN): + c = "\n" + elif (e.key == K_TAB): + c = " " + else: + c = (e.unicode).encode('latin-1') + if c: + self._setvalue(self.value[:self.pos] + c + self.value[self.pos:]) + self.pos += len(c) + except: #ignore weird characters + pass + self.repaint() + elif e.type == MOUSEBUTTONDOWN: + self.setCursorByXY(e.pos) + self.repaint() + + elif e.type == FOCUS: + self.repaint() + elif e.type == BLUR: + self.repaint() + + self.pcls = "" + if self.container.myfocus is self: self.pcls = "focus" + + return used + + def __setattr__(self,k,v): + if k == 'value': + if v == None: v = '' + v = str(v) + self.pos = len(v) + _v = self.__dict__.get(k,NOATTR) + self.__dict__[k]=v + if k == 'value' and _v != NOATTR and _v != v: + self.send(CHANGE) + self.repaint() + +# The first version of this code was done by Clint Herron, and is a modified version of input.py (by Phil Hassey). +# It is under the same license as the rest of the PGU library. \ No newline at end of file diff --git a/pgu/gui/textarea.pyc b/pgu/gui/textarea.pyc new file mode 100644 index 0000000..8c94884 --- /dev/null +++ b/pgu/gui/textarea.pyc Binary files differ diff --git a/pgu/gui/theme.py b/pgu/gui/theme.py new file mode 100644 index 0000000..8c75329 --- /dev/null +++ b/pgu/gui/theme.py @@ -0,0 +1,486 @@ +""" +""" +import os, re +import pygame + +from const import * +import surface +from basic import parse_color + +__file__ = os.path.abspath(__file__) + +def _list_themes(dir): + d = {} + for entry in os.listdir(dir): + if os.path.exists(os.path.join(dir, entry, 'config.txt')): + d[entry] = os.path.join(dir, entry) + return d + +class Theme: + """Theme interface. + +

If you wish to create your own theme, create a class with this interface, and + pass it to gui.App via gui.App(theme=MyTheme()).

+ + Default Theme + +
Theme(dirs='default')
+
+
dirs
Name of the theme dir to load a theme from. May be an absolute path to a theme, if pgu is not installed, or if you created your own theme. May include several dirs in a list if data is spread across several themes. +
+ + Example + + + theme = gui.Theme("default") + theme = gui.Theme(["mytheme","mytheme2"]) + + """ + def __init__(self,dirs='default'): + self.config = {} + self.dict = {} + self._loaded = [] + self.cache = {} + self._preload(dirs) + pygame.font.init() + + def _preload(self,ds): + if not isinstance(ds, list): + ds = [ds] + for d in ds: + if d not in self._loaded: + self._load(d) + self._loaded.append(d) + + def _load(self, name): + #theme_dir = themes[name] + + #try to load the local dir, or absolute path + dnames = [name] + + #if the package isn't installed and people are just + #trying out the scripts or examples + dnames.append(os.path.join(os.path.dirname(__file__),"..","..","data","themes",name)) + + #if the package is installed, and the package is installed + #in /usr/lib/python2.3/site-packages/pgu/ + #or c:\python23\lib\site-packages\pgu\ + #the data is in ... lib/../share/ ... + dnames.append(os.path.join(os.path.dirname(__file__),"..","..","..","..","share","pgu","themes",name)) + dnames.append(os.path.join(os.path.dirname(__file__),"..","..","..","..","..","share","pgu","themes",name)) + dnames.append(os.path.join(os.path.dirname(__file__),"..","..","share","pgu","themes",name)) + for dname in dnames: + if os.path.isdir(dname): break + if not os.path.isdir(dname): + raise 'could not find theme '+name + + fname = os.path.join(dname,"config.txt") + if os.path.isfile(fname): + try: + f = open(fname) + for line in f.readlines(): + vals = line.strip().split() + if len(vals) < 3: continue + cls = vals[0] + del vals[0] + pcls = "" + if cls.find(":")>=0: + cls,pcls = cls.split(":") + attr = vals[0] + del vals[0] + self.config[cls+":"+pcls+" "+attr] = (dname, vals) + finally: + f.close() + fname = os.path.join(dname,"style.ini") + if os.path.isfile(fname): + import ConfigParser + cfg = ConfigParser.ConfigParser() + f = open(fname,'r') + cfg.readfp(f) + for section in cfg.sections(): + cls = section + pcls = '' + if cls.find(":")>=0: + cls,pcls = cls.split(":") + for attr in cfg.options(section): + vals = cfg.get(section,attr).strip().split() + self.config[cls+':'+pcls+' '+attr] = (dname,vals) + + is_image = re.compile('\.(gif|jpg|bmp|png|tga)$', re.I) + def _get(self,key): + if not key in self.config: return + if key in self.dict: return self.dict[key] + dvals = self.config[key] + dname, vals = dvals + #theme_dir = themes[name] + v0 = vals[0] + if v0[0] == '#': + v = parse_color(v0) + #if (len(v0) == 7): + # # Due to a bug in pygame 1.8 (?) we need to explicitly + # # specify the alpha value (otherwise it defaults to zero) + # v0 += "FF" + #v = pygame.color.Color(v0) + elif v0.endswith(".ttf") or v0.endswith(".TTF"): + v = pygame.font.Font(os.path.join(dname, v0),int(vals[1])) + elif self.is_image.search(v0) is not None: + v = pygame.image.load(os.path.join(dname, v0)) + else: + try: v = int(v0) + except: v = pygame.font.SysFont(v0, int(vals[1])) + self.dict[key] = v + return v + + def get(self,cls,pcls,attr): + """Interface method -- get the value of a style attribute. + +
Theme.get(cls,pcls,attr): return value
+ +
+
cls
class, for example "checkbox", "button", etc. +
pcls
pseudo class, for example "hover", "down", etc. +
attr
attribute, for example "image", "background", "font", "color", etc. +
+ +

returns the value of the attribute.

+ +

This method is called from [[gui-style]].

+ """ + + if not self._loaded: self._preload("default") + + o = cls+":"+pcls+" "+attr + + #if not hasattr(self,'_count'): + # self._count = {} + #if o not in self._count: self._count[o] = 0 + #self._count[o] += 1 + + if o in self.cache: + return self.cache[o] + + v = self._get(cls+":"+pcls+" "+attr) + if v: + self.cache[o] = v + return v + + pcls = "" + v = self._get(cls+":"+pcls+" "+attr) + if v: + self.cache[o] = v + return v + + cls = "default" + v = self._get(cls+":"+pcls+" "+attr) + if v: + self.cache[o] = v + return v + + v = 0 + self.cache[o] = v + return v + + def box(self,w,s): + style = w.style + + c = (0,0,0) + if style.border_color != 0: c = style.border_color + w,h = s.get_width(),s.get_height() + + s.fill(c,(0,0,w,style.border_top)) + s.fill(c,(0,h-style.border_bottom,w,style.border_bottom)) + s.fill(c,(0,0,style.border_left,h)) + s.fill(c,(w-style.border_right,0,style.border_right,h)) + + + def getspacing(self,w): + # return the top, right, bottom, left spacing around the widget + if not hasattr(w,'_spacing'): #HACK: assume spacing doesn't change re pcls + s = w.style + xt = s.margin_top+s.border_top+s.padding_top + xr = s.padding_right+s.border_right+s.margin_right + xb = s.padding_bottom+s.border_bottom+s.margin_bottom + xl = s.margin_left+s.border_left+s.padding_left + w._spacing = xt,xr,xb,xl + return w._spacing + + + def resize(self,w,m): + # Returns the rectangle expanded in each direction + def expand_rect(rect, left, top, right, bottom): + return pygame.Rect(rect.x - left, + rect.y - top, + rect.w + left + right, + rect.h + top + bottom) + + def func(width=None,height=None): + s = w.style + + pt,pr,pb,pl = s.padding_top,s.padding_right,s.padding_bottom,s.padding_left + bt,br,bb,bl = s.border_top,s.border_right,s.border_bottom,s.border_left + mt,mr,mb,ml = s.margin_top,s.margin_right,s.margin_bottom,s.margin_left + # Calculate the total space on each side + top = pt+bt+mt + right = pr+br+mr + bottom = pb+bb+mb + left = pl+bl+ml + ttw = left+right + tth = top+bottom + + ww,hh = None,None + if width != None: ww = width-ttw + if height != None: hh = height-tth + ww,hh = m(ww,hh) + + if width == None: width = ww + if height == None: height = hh + + #if the widget hasn't respected the style.width, + #style height, we'll add in the space for it... + width = max(width-ttw, ww, w.style.width) + height = max(height-tth, hh, w.style.height) + + #width = max(ww,w.style.width-tw) + #height = max(hh,w.style.height-th) + + r = pygame.Rect(left,top,width,height) + + w._rect_padding = expand_rect(r, pl, pt, pr, pb) + w._rect_border = expand_rect(w._rect_padding, bl, bt, br, bb) + w._rect_margin = expand_rect(w._rect_border, ml, mt, mr, mb) + + #w._rect_padding = pygame.Rect(r.x-pl,r.y-pt,r.w+pl+pr,r.h+pt+pb) + #r = w._rect_padding + #w._rect_border = pygame.Rect(r.x-bl,r.y-bt,r.w+bl+br,r.h+bt+bb) + #r = w._rect_border + #w._rect_margin = pygame.Rect(r.x-ml,r.y-mt,r.w+ml+mr,r.h+mt+mb) + + # align it within it's zone of power. + rect = pygame.Rect(left, top, ww, hh) + dx = width-rect.w + dy = height-rect.h + rect.x += (w.style.align+1)*dx/2 + rect.y += (w.style.valign+1)*dy/2 + + w._rect_content = rect + + return (w._rect_margin.w, w._rect_margin.h) + return func + + + def paint(self,w,m): + def func(s): +# if w.disabled: +# if not hasattr(w,'_disabled_bkgr'): +# w._disabled_bkgr = s.convert() +# orig = s +# s = w._disabled_bkgr.convert() + +# if not hasattr(w,'_theme_paint_bkgr'): +# w._theme_paint_bkgr = s.convert() +# else: +# s.blit(w._theme_paint_bkgr,(0,0)) +# +# if w.disabled: +# orig = s +# s = w._theme_paint_bkgr.convert() + + if w.disabled: + if (not (hasattr(w,'_theme_bkgr') and + w._theme_bkgr.get_width() == s.get_width() and + w._theme_bkgr.get_height() == s.get_height())): + w._theme_bkgr = s.copy() + orig = s + s = w._theme_bkgr + s.fill((0,0,0,0)) + s.blit(orig,(0,0)) + + if hasattr(w,'background'): + w.background.paint(surface.subsurface(s,w._rect_border)) + self.box(w,surface.subsurface(s,w._rect_border)) + r = m(surface.subsurface(s,w._rect_content)) + + if w.disabled: + s.set_alpha(128) + orig.blit(s,(0,0)) + +# if w.disabled: +# orig.blit(w._disabled_bkgr,(0,0)) +# s.set_alpha(128) +# orig.blit(s,(0,0)) + + w._painted = True + return r + return func + + def event(self,w,m): + def func(e): + rect = w._rect_content + if e.type == MOUSEBUTTONUP or e.type == MOUSEBUTTONDOWN: + sub = pygame.event.Event(e.type,{ + 'button':e.button, + 'pos':(e.pos[0]-rect.x,e.pos[1]-rect.y)}) + elif e.type == CLICK: + sub = pygame.event.Event(e.type,{ + 'button':e.button, + 'pos':(e.pos[0]-rect.x,e.pos[1]-rect.y)}) + elif e.type == MOUSEMOTION: + sub = pygame.event.Event(e.type,{ + 'buttons':e.buttons, + 'pos':(e.pos[0]-rect.x,e.pos[1]-rect.y), + 'rel':e.rel}) + else: + sub = e + r = m(sub) + return r + return func + + def update(self,w,m): + def func(s): + if w.disabled: return [] + r = m(surface.subsurface(s,w._rect_content)) + if type(r) == list: + dx,dy = w._rect_content.topleft + for rr in r: + rr.x,rr.y = rr.x+dx,rr.y+dy + return r + return func + + def open(self,w,m): + def func(widget=None,x=None,y=None): + if not hasattr(w,'_rect_content'): w.rect.w,w.rect.h = w.resize() #HACK: so that container.open won't resize again! + rect = w._rect_content + ##print w.__class__.__name__, rect + if x != None: x += rect.x + if y != None: y += rect.y + return m(widget,x,y) + return func + + #def open(self,w,m): + # def func(widget=None): + # return m(widget) + # return func + + def decorate(self,widget,level): + """Interface method -- decorate a widget. + +

The theme system is given the opportunity to decorate a widget methods at the + end of the Widget initializer.

+ +
Theme.decorate(widget,level)
+ +
+
widget
the widget to be decorated +
level
the amount of decoration to do, False for none, True for normal amount, 'app' for special treatment of App objects. +
+ """ + + w = widget + if level == False: return + + if type(w.style.background) != int: + w.background = Background(w,self) + + if level == 'app': return + + for k,v in w.style.__dict__.items(): + if k in ('border','margin','padding'): + for kk in ('top','bottom','left','right'): + setattr(w.style,'%s_%s'%(k,kk),v) + + w.paint = self.paint(w,w.paint) + w.event = self.event(w,w.event) + w.update = self.update(w,w.update) + w.resize = self.resize(w,w.resize) + w.open = self.open(w,w.open) + + def render(self,s,box,r): + """Interface method - render a special widget feature. + +
Theme.render(s,box,r)
+ +
+
s
pygame.Surface +
box
box data, a value returned from Theme.get, typically a pygame.Surface +
r
pygame.Rect with the size that the box data should be rendered +
+ + """ + + if box == 0: return + + if isinstance(box, pygame.Color) or type(box) == tuple: + s.fill(box,r) + return + + x,y,w,h=r.x,r.y,r.w,r.h + ww,hh=box.get_width()/3,box.get_height()/3 + xx,yy=x+w,y+h + src = pygame.rect.Rect(0,0,ww,hh) + dest = pygame.rect.Rect(0,0,ww,hh) + + s.set_clip(pygame.Rect(x+ww,y+hh,w-ww*2,h-hh*2)) + src.x,src.y = ww,hh + for dest.y in xrange(y+hh,yy-hh,hh): + for dest.x in xrange(x+ww,xx-ww,ww): s.blit(box,dest,src) + + s.set_clip(pygame.Rect(x+ww,y,w-ww*3,hh)) + src.x,src.y,dest.y = ww,0,y + for dest.x in xrange(x+ww,xx-ww*2,ww): s.blit(box,dest,src) + dest.x = xx-ww*2 + s.set_clip(pygame.Rect(x+ww,y,w-ww*2,hh)) + s.blit(box,dest,src) + + s.set_clip(pygame.Rect(x+ww,yy-hh,w-ww*3,hh)) + src.x,src.y,dest.y = ww,hh*2,yy-hh + for dest.x in xrange(x+ww,xx-ww*2,ww): s.blit(box,dest,src) + dest.x = xx-ww*2 + s.set_clip(pygame.Rect(x+ww,yy-hh,w-ww*2,hh)) + s.blit(box,dest,src) + + s.set_clip(pygame.Rect(x,y+hh,xx,h-hh*3)) + src.y,src.x,dest.x = hh,0,x + for dest.y in xrange(y+hh,yy-hh*2,hh): s.blit(box,dest,src) + dest.y = yy-hh*2 + s.set_clip(pygame.Rect(x,y+hh,xx,h-hh*2)) + s.blit(box,dest,src) + + s.set_clip(pygame.Rect(xx-ww,y+hh,xx,h-hh*3)) + src.y,src.x,dest.x=hh,ww*2,xx-ww + for dest.y in xrange(y+hh,yy-hh*2,hh): s.blit(box,dest,src) + dest.y = yy-hh*2 + s.set_clip(pygame.Rect(xx-ww,y+hh,xx,h-hh*2)) + s.blit(box,dest,src) + + s.set_clip() + src.x,src.y,dest.x,dest.y = 0,0,x,y + s.blit(box,dest,src) + + src.x,src.y,dest.x,dest.y = ww*2,0,xx-ww,y + s.blit(box,dest,src) + + src.x,src.y,dest.x,dest.y = 0,hh*2,x,yy-hh + s.blit(box,dest,src) + + src.x,src.y,dest.x,dest.y = ww*2,hh*2,xx-ww,yy-hh + s.blit(box,dest,src) + + + +import pygame +import widget + +class Background(widget.Widget): + def __init__(self,value,theme,**params): + params['decorate'] = False + widget.Widget.__init__(self,**params) + self.value = value + self.theme = theme + + def paint(self,s): + r = pygame.Rect(0,0,s.get_width(),s.get_height()) + v = self.value.style.background + if isinstance(v, pygame.Color) or type(v) == tuple: + s.fill(v) + else: + self.theme.render(s,v,r) diff --git a/pgu/gui/theme.pyc b/pgu/gui/theme.pyc new file mode 100644 index 0000000..ca21e90 --- /dev/null +++ b/pgu/gui/theme.pyc Binary files differ diff --git a/pgu/gui/widget.py b/pgu/gui/widget.py new file mode 100644 index 0000000..eb0a7bc --- /dev/null +++ b/pgu/gui/widget.py @@ -0,0 +1,352 @@ +""" +""" +import pygame + +import pguglobals +import style + +class SignalCallback: + # The function to call + func = None + # The parameters to pass to the function (as a list) + params = None + +class Widget: + """Template object - base for all widgets. + +
Widget(**params)
+ +

A number of optional params may be passed to the Widget initializer.

+ +
+
decorate
defaults to True. If true, will call theme.decorate(self) to allow the theme a chance to decorate the widget. +
style
a dict of style parameters. +
x, y, width, height
position and size parameters, passed along to style +
align, valign
alignment parameters, passed along to style +
font, color, background
other common parameters that are passed along to style +
cls
class name as used by Theme +
name
name of widget as used by Form. If set, will call form.add(self,name) to add the widget to the most recently created Form. +
focusable
True if this widget can receive focus via Tab, etc. Defaults to True. +
disabled
True of this widget is disabled. Defaults to False. +
value
initial value +
+ + Example - Creating your own Widget +

This example shows which methods are template methods.

+ + class Draw(gui.Widget): + def paint(self,s): + #paint the pygame.Surface + return + + def update(self,s): + #update the pygame.Surface and return the update rects + return [pygame.Rect(0,0,self.rect.w,self.rect.h)] + + def event(self,e): + #handle the pygame.Event + return + + def resize(self,width=None,height=None): + #return the width and height of this widget + return 256,256 + + """ + + def __init__(self,**params): + #object.Object.__init__(self) + self.connects = {} + params.setdefault('decorate',True) + params.setdefault('style',{}) + params.setdefault('focusable',True) + params.setdefault('disabled',False) + + self.focusable = params['focusable'] + self.disabled = params['disabled'] + + self.rect = pygame.Rect(params.get('x',0),params.get('y',0),params.get('width',0),params.get('height',0)) + + s = params['style'] + #some of this is a bit "theme-ish" but it is very handy, so these + #things don't have to be put directly into the style. + for att in ('align','valign','x','y','width','height','color','font','background'): + if att in params: s[att] = params[att] + self.style = style.Style(self,s) + + self.cls = 'default' + if 'cls' in params: self.cls = params['cls'] + if 'name' in params: + import form + self.name = params['name'] + if hasattr(form.Form,'form') and form.Form.form != None: + form.Form.form.add(self) + self.form = form.Form.form + if 'value' in params: self.value = params['value'] + self.pcls = "" + + if params['decorate'] != False: + if (not pguglobals.app): + # TODO - fix this somehow + import app + print 'gui.widget: creating an App' + app.App() + pguglobals.app.theme.decorate(self,params['decorate']) + + def focus(self): + """Focus this Widget. + +
Widget.focus()
+ """ + if getattr(self,'container',None) != None: + if self.container.myfocus != self: ## by Gal Koren + self.container.focus(self) + def blur(self): + """Blur this Widget. + +
Widget.blur()
+ """ + if getattr(self,'container',None) != None: self.container.blur(self) + def open(self): + """Open this Widget as a modal dialog. + +
Widget.open()
+ """ + if getattr(self,'container',None) != None: self.container.open(self) + def close(self): + """Close this Widget (if it is a modal dialog.) + +
Widget.close()
+ """ + if getattr(self,'container',None) != None: self.container.close(self) + def resize(self,width=None,height=None): + """Template method - return the size and width of this widget. + +

Responsible for also resizing all sub-widgets.

+ +
Widget.resize(width,height): return width,height
+ +
+
width
suggested width +
height
suggested height +
+ +

If not overridden, will return self.style.width, self.style.height

+ """ + return self.style.width, self.style.height + def chsize(self): + """Change the size of this widget. + +

Calling this method will cause a resize on all the widgets, + including this one.

+ +
Widget.chsize()
+ """ + + if not hasattr(self,'_painted'): return + + if not hasattr(self,'container'): return + + if pguglobals.app: + if pguglobals.app._chsize: + return + pguglobals.app.chsize() + return + + #if hasattr(app.App,'app'): + # w,h = self.rect.w,self.rect.h + # w2,h2 = self.resize() + # if w2 != w or h2 != h: + # app.App.app.chsize() + # else: + # self.repaint() + + + def update(self,s): + """Template method - update the surface + +
Widget.update(s): return list of pygame.Rect(s)
+ +
+
s
pygame.Surface to update +
+ +

return - a list of the updated areas as pygame.Rect(s).

+ """ + return + + def paint(self,s): + """Template method - paint the surface + +
Widget.paint(s)
+ +
+
s
pygame.Surface to paint +
+ """ + return + + def repaint(self): + """Request a repaint of this Widget. + +
Widget.repaint()
+ """ + if getattr(self,'container',None) != None: self.container.repaint(self) + def repaintall(self): + """Request a repaint of all Widgets. + +
Widget.repaintall()
+ """ + if getattr(self,'container',None) != None: self.container.repaintall() + def reupdate(self): + """Request a reupdate of this Widget + +
Widget.reupdate()
+ """ + if getattr(self,'container',None) != None: self.container.reupdate(self) + def next(self): + """Pass focus to next Widget. + +

Widget order determined by the order they were added to their container.

+ +
Widget.next()
+ """ + if getattr(self,'container',None) != None: self.container.next(self) + def previous(self): + """Pass focus to previous Widget. + +

Widget order determined by the order they were added to their container.

+ +
Widget.previous()
+ """ + + if getattr(self,'container',None) != None: self.container.previous(self) + + def get_abs_rect(self): + """Get the absolute rect of this widget on the App screen + +
Widget.get_abs_rect(): return pygame.Rect
+ """ + x, y = self.rect.x , self.rect.y + x += self._rect_content.x + y += self._rect_content.y + c = getattr(self,'container',None) + while c: + x += c.rect.x + y += c.rect.y + if hasattr(c,'_rect_content'): + x += c._rect_content.x + y += c._rect_content.y + c = getattr(c,'container',None) + return pygame.Rect(x, y, self.rect.w, self.rect.h) + + def connect(self,code,func,*params): + """Connect a event code to a callback function. + +

There may be multiple callbacks per event code.

+ +
Object.connect(code,fnc,value)
+ +
+
code
event type [[gui-const]] +
fnc
callback function +
*values
values to pass to callback. Please note that callbacks may also have "magicaly" parameters. Such as: +
+
_event
receive the event +
_code
receive the event code +
_widget
receive the sending widget +
+
+ + Example + + def onclick(value): + print 'click',value + + w = Button("PGU!") + w.connect(gui.CLICK,onclick,'PGU Button Clicked') + + """ + # Wrap the callback function and add it to the list + cb = SignalCallback() + cb.func = func + cb.params = params + if (not code in self.connects): + self.connects[code] = [] + self.connects[code].append(cb) + + # Remove signal handlers from the given event code. If func is specified, + # only those handlers will be removed. If func is None, all handlers + # will be removed. + def disconnect(self, code, func=None): + if (not code in self.connects): + return + if (not func): + # Remove all signal handlers + del self.connects[code] + else: + # Remove handlers that call 'func' + n = 0 + callbacks = self.connects[code] + while (n < len(callbacks)): + if (callbacks[n].func == func): + # Remove this callback + del callbacks[n] + else: + n += 1 + + def send(self,code,event=None): + """Send a code, event callback trigger. + +
Object.send(code,event=None)
+ +
+
code
event code +
event
event +
+ """ + if (not code in self.connects): + return + # Trigger all connected signal handlers + for cb in self.connects[code]: + func = cb.func + values = list(cb.params) + + nargs = func.func_code.co_argcount + names = list(func.func_code.co_varnames)[:nargs] + if hasattr(func,'im_class'): names.pop(0) + + args = [] + magic = {'_event':event,'_code':code,'_widget':self} + for name in names: + if name in magic.keys(): + args.append(magic[name]) + elif len(values): + args.append(values.pop(0)) + else: + break + args.extend(values) + func(*args) + + def _event(self,e): + if self.disabled: return + self.send(e.type,e) + return self.event(e) +# return +# import app +# if hasattr(app.App,'app'): +# app.App.app.events.append((self,e)) + + def event(self,e): + """Template method - called when an event is passed to this object. + +

Please note that if you use an event, returning the value True + will stop parent containers from also using the event. (For example, if + your widget handles TABs or arrow keys, and you don't want those to + also alter the focus.)

+ +
+
e
event +
+ """ + + return diff --git a/pgu/gui/widget.pyc b/pgu/gui/widget.pyc new file mode 100644 index 0000000..b053a6c --- /dev/null +++ b/pgu/gui/widget.pyc Binary files differ -- cgit v0.9.1