class Switch(_button): """ Switch([value, [labels, [options]]]) -> Switch widget ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This is very similar to a button. When clicked, it will cycle through it's list of options. The label displayed will be the the item in the labels list at the same index as the current option (value) in the options list. The labels and options need to be exactly the same size. Arguments --------- value Must be an item in the options list. labels A list of strings that will be used as labels. options A list of items that are cycled through to determin this widgets value. """ _value = util.ReplaceableCellDescriptor() on_label = util.ReplaceableCellDescriptor() off_label = util.ReplaceableCellDescriptor() def __init__(self, value=False, labels=("Off", "On"), options=(False, True), **params): super(Switch, self).__init__(**params) self._value = value self.labels = list(labels) self.options = list(options) if len(self.labels) != len(self.options): raise ValueError( "The number of labels has to equal the number of options!") self.set_value(value) def set_value(self, v): self._value = v if v not in self.options: error = """Could not find value in options. """ error = error + "Value:" + str(v) + " Options:" + str( self.options) raise ValueError(error) self.label = self.labels[self.options.index(v)] value = property(lambda self: self._value, set_value) def click(self): i = self.options.index(self.value) if i == len(self.options) - 1: i = 0 else: i += 1 self.value = self.options[i]
class Image(util.widgets.Widget): """ Image(value) -> Image widget ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Basic widget for simply displaying an image. value should be a pygame surface. """ value = util.ReplaceableCellDescriptor() def __init__(self, value, **params): util.widgets.Widget.__init__(self, **params) self.value = value def draw_widget(self): util.blit(self.value, self.pos, clip_rect=self.value.get_rect()) def width(self): return self.value.get_width() width = ComputedCellDescriptor(width) def height(self): return self.value.get_height() height = ComputedCellDescriptor(height)
class _slider(util.widgets.Widget): _value = util.ReplaceableCellDescriptor() length = util.ReplaceableCellDescriptor() min_value = util.ReplaceableCellDescriptor() step = util.ReplaceableCellDescriptor() def __init__(self, value=0, min_value=0, length=20, step=True, **params): util.widgets.Widget.__init__(self, **params) self.step = step self.length = length self.min_value = min_value self.value = value # Create the marker widget and link it's values to sliders. self.marker = util.widgets.Widget() self.marker.parent = self self.marker._cells["hovering"] = self._cells["hovering"] self.marker._cells["focused"] = self._cells["focused"] self.marker._cells["pressing"] = self._cells["pressing"] self.marker._cells["disabled"] = self._cells["disabled"] def set_value(self, v): v = max(v, self.min_value) v = min(v, self.min_value + self.length) if self.step: v = int(v) self._value = v self.send(CHANGE) value = property(lambda self: self._value, set_value) def marker_pos(self): w = self.style_option["width"] - self.marker.width w = float(self.value - self.min_value) / (self.length) * w h = self.style_option["height"] - self.marker.height h = float(self.value - self.min_value) / (self.length) * h return (int(w), int(h)) marker_pos = ComputedCellDescriptor(marker_pos)
class _box(util.widgets.Container): expand = util.ReplaceableCellDescriptor() def __init__(self, expand=True, **params): util.widgets.Container.__init__(self, **params) self.expand = expand def add(self, *widgets): super(_box, self).add(*widgets) self.position_widgets() def remove(self, widgets): util.widgets.Container.remove(self, widgets) self.position_widgets()
class _button(util.widgets.Widget): _label = util.ReplaceableCellDescriptor() def __init__(self, **params): self._label = None super(_button, self).__init__(**params) def set_label(self, v): if hasattr(v, "value"): l = util.widgets.Label(v.value, parent=self) l._cells["value"] = ComputedCell(lambda: str(v.value)) if type(v) == int: v = str(v) if type(v) == str: l = util.widgets.Label(v, parent=self) self.send(CHANGE) self._label = l self._label._cells["hovering"] = self._cells["hovering"] self._label._cells["focused"] = self._cells["focused"] self._label._cells["pressing"] = self._cells["pressing"] label = property(lambda self: self._label, set_label) def width(self): if not self.label: return util.widgets.Widget.width.function(self) w = self.label.width + self.style_option[ "padding-left"] + self.style_option["padding-right"] return util.widgets.Widget.width.function(self, w) width = ComputedCellDescriptor(width) def height(self): if not self.label: return util.widgets.Widget.height.function(self) h = self.label.height + self.style_option[ "padding-top"] + self.style_option["padding-bottom"] return util.widgets.Widget.height.function(self, h) height = ComputedCellDescriptor(height) def draw_widget(self): self.label.dirty = True self.label.draw()
class Button(_button): """ Button([value]) -> Button widget ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ A widget resembling a button. Arguments --------- value The text or widget to be displayed on the button. """ _value = util.ReplaceableCellDescriptor() def __init__(self, value=" ", **params): super(Button, self).__init__(**params) self._value = None self.value = value def set_value(self, v): self._value = v self.label = v value = property(lambda self: self._value, set_value)
class Input(util.widgets.Widget): """ Input([value, [max_length, [disallowed_chars]]]) - Input widget ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Creates an HTML like input field. Arguments --------- value The initial value of the input (defaults to ""). Note that due to the nature of the value, you cannot link to it (but you can from it). If you really want to link it's value you can do so it _value, but it may not always work like you expect! max_length The maximum number of character that can be put into the Input. disallowed_chars A list of characters that are disallowed to enter. """ _value = util.ReplaceableCellDescriptor() cur_pos = util.ReplaceableCellDescriptor() max_length = util.ReplaceableCellDescriptor() def __init__(self, value="", max_length=None, disallowed_chars=[], **params): util.widgets.Widget.__init__(self, **params) self._value = '' self.cur_pos = 0 self.max_length = max_length self.value = value self.disallowed_chars = disallowed_chars self.last_press = {K_BACKSPACE: 0, K_LEFT: 0, K_RIGHT: 0, K_DELETE: 0} def font(self): """ Input.font -> pygame.Font ^^^^^^^^^^^^^^^^^^^^^^^^^ The font that this input is using. """ f = util.app.get_font(self.style_option["font-family"], self.style_option["font-size"]) if self.style_option["font-weight"] == "bold": f.set_bold(True) return f font = ComputedCellDescriptor(font) def set_value(self, v): if v == None: v = '' v = str(v) if self.max_length and len(v) > self.max_length: v = v[0:self.max_length - 1] self._value = v if self.cur_pos - 1 > len(v): #print self.cur_pos-1, len(v) self.cur_pos = len(v) self.send(CHANGE) value = property(lambda self: self._value, set_value) def size(self): s, _ = self.font.size("e") num = self.style_option["width"] / s return self.font.size("e" * num) size = ComputedCellDescriptor(size) def width(self): w = self.size[0] + self.style_option["padding-left"]+\ self.style_option["padding-right"] return util.widgets.Widget.width.function(self, w) width = ComputedCellDescriptor(width) def height(self): h = self.size[1] + self.style_option["padding-top"]+\ self.style_option["padding-bottom"] return util.widgets.Widget.height.function(self, h) height = ComputedCellDescriptor(height) def font_clip_rect(self): vx = max(self.font.size(self.value)[0] - self.size[0], 0) return pygame.Rect((vx, 0), self.size) font_clip_rect = ComputedCellDescriptor(font_clip_rect) def font_x(self): x, _ = self.pos return x + self.style_option["padding-left"] font_x = ComputedCellDescriptor(font_x) def font_y(self): _, y = self.pos return y + self.style_option["padding-top"] font_y = ComputedCellDescriptor(font_y) def draw_widget(self): util.blit(self.font.render(self.value, self.style_option["antialias"], self.style_option["color"]), (self.font_x, self.font_y), clip_rect=self.font_clip_rect) if self.focused: w, h = self.font.size(self.value[0:self.cur_pos]) r = pygame.Surface((2, self.size[1])) r.fill(self.style_option["color"]) x = min(w, self.size[0]) util.blit(r, (self.font_x + x, self.font_y)) def repetitive_events(self): # Key repeating. Better way to do this? k = pygame.key.get_pressed() if k[K_BACKSPACE] and util.get_ticks( ) - self.last_press[K_BACKSPACE] > 300: self.value = self.value[:self.cur_pos - 1] + self.value[self.cur_pos:] self.cur_pos -= 1 elif k[K_DELETE] and util.get_ticks() - self.last_press[K_DELETE] > 300: if len(self.value) > self.cur_pos: self.value = self.value[:self. cur_pos] + self.value[self.cur_pos + 1:] elif k[K_LEFT] and util.get_ticks() - self.last_press[K_LEFT] > 300: if self.cur_pos > 0: self.cur_pos -= 1 elif k[K_RIGHT] and util.get_ticks() - self.last_press[K_RIGHT] > 300: if self.cur_pos < len(self.value): self.cur_pos += 1 def event(self, e): if e.type == KEYDOWN: if e.key == K_BACKSPACE: if self.cur_pos: self.value = self.value[:self.cur_pos - 1] + self.value[self.cur_pos:] self.cur_pos -= 1 self.last_press[K_BACKSPACE] = util.get_ticks() elif e.key == K_DELETE: if len(self.value) > self.cur_pos: self.value = self.value[:self.cur_pos] + self.value[ self.cur_pos + 1:] self.last_press[K_DELETE] = util.get_ticks() elif e.key == K_HOME: self.cur_pos = 0 elif e.key == K_END: self.cur_pos = len(self.value) elif e.key == K_LEFT: if self.cur_pos > 0: self.cur_pos -= 1 self.last_press[K_LEFT] = util.get_ticks() elif e.key == K_RIGHT: if self.cur_pos < len(self.value): self.cur_pos += 1 self.last_press[K_RIGHT] = util.get_ticks() elif e.key == K_ESCAPE: pass elif e.key == K_RETURN: self.next() else: if self.max_length: if len(self.value) == self.max_length: return c = str(e.unicode) #c = (e.unicode).encode('latin-1') if c and c not in self.disallowed_chars: self.value = self.value[:self.cur_pos] + c + self.value[ self.cur_pos:] self.cur_pos += 1
class TextBlock(util.widgets.Widget): """ TextBlock(value) -> TextBlock widget ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Basic widget for wrapping and displaying some text. Supports line breaks (ether \\n or <br>) """ value = util.ReplaceableCellDescriptor(restriction=StringRestriction()) def __init__(self, value, **params): util.widgets.Widget.__init__(self, **params) self.value = value def font(self): """ Label.font -> pygame.Font ^^^^^^^^^^^^^^^^^^^^^^^^^ The font that this label is using. """ f = util.app.get_font(self.style_option["font-family"], self.style_option["font-size"]) if self.style_option["font-weight"] == "bold": f.set_bold(True) return f font = ComputedCellDescriptor(font) def font_x(self): x,_ = self.pos return x + self.style_option["padding-left"] font_x = ComputedCellDescriptor(font_x) def font_y(self): _,y = self.pos return y + self.style_option["padding-top"] font_y = ComputedCellDescriptor(font_y) def lines(self): # First we need to wrap the text (split it into lines). char_w,line_h = self.font.size("e") max_chars = self.style_option["width"]/char_w line_w,_ = self.font.size(max_chars*"e") value = self.value.replace("\n", "<br>") value = value.replace("<br>", " <br> ") words = value.split(" ") lines = [] line_data = "" for word in words: if word != "<br>" and self.font.size(line_data +" "+ word)[0] < line_w: # We can fit this word onto this line. line_data = line_data+" "+word else: # Flush old line and start a new one. lines.append(line_data.strip()) if word != "<br>": line_data = word else: line_data = "" lines.append(line_data.strip()) return lines lines = ComputedCellDescriptor(lines) def height(self): _,line_h = self.font.size("e") h = line_h*len(self.lines) + self.style_option["padding-bottom"]+\ self.style_option["padding-top"] return util.widgets.Widget.height.function(self, h) height = ComputedCellDescriptor(height) def draw_widget(self): linenum = 0 _,line_h = self.font.size("e") for line in self.lines: x = self.font_x y = self.font_y + line_h*linenum util.blit(self.font.render(line, self.style_option["antialias"], self.style_option["color"]), (x,y)) linenum += 1 #util.blit(self.font_value, (self.font_x,self.font_y))
class SelectBox(util.widgets.VBox): """ SelectBox([multiple, [expand]]) -> SelectBox widget ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Subclasses from VBox. Like a html select box. You can access SelectBox.values to get a set of all selected values. Arguments --------- multiple If set to True, multiple options can be selected. expand If set to True (default) all widgets will expand to be the same width. """ multiple = util.ReplaceableCellDescriptor() def __init__(self, multiple=False, **params): util.widgets.VBox.__init__(self, **params) self.selected = set() #CellSet() self.options = CellDict() self.multiple = multiple def get_values(self): vs = set() for v in self.selected: vs.add(self.options[v]) return vs values = property(get_values) def get_label(self, v): if hasattr(v, "value"): l = util.widgets.Label(v.value, parent=self, container=self) l._cells["value"] = ComputedCell(lambda: str(v.value)) if type(v) == int: v = str(v) if type(v) == str: l = util.widgets.Label(v, parent=self) return l def add(self, label, value): """ SelectBox.add(label, value) -> Option widget ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Add an option to SelectBox. Arguments --------- label The text or widget to be displayed as the option. value The value of the option (not displayed). """ label = self.get_label(label) label._cells["disabled"] = self._cells["disabled"] util.widgets.VBox.add(self, label) self.options[label] = value self.send(CHANGE) def click(): if label in self.selected: self.selected.remove(label) label.selected = False else: self.selected.add(label) label.selected = True if not self.multiple: for l in self.options.keys(): if l != label: l.selected = False self.selected = set((label, )) #CellSet((label,)) label.click = click return label def remove(self, option): """ SelectBox.remove(option) -> None ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Remove option from SelectBox. Arguments --------- option Can ether be a string representing an option's value or the option widget. """ if type(option) == str: for k, v in self.options.items(): if v == option: util.widgets.VBox.remove(self, self.options[k]) del self.options[k] else: util.widgets.VBox.remove(self, option) del self.options[option] self.send(CHANGE) def clear(self): """ SelectBox.clear() -> None ^^^^^^^^^^^^^^^^^^^^^^^^^ clears all options from SelectBox. """ rops = set() for v in self.options.keys(): rops.add(v) for v in rops: self.remove(v)
class Widget(DependantCell): """ Widget([disabled, [active, [parent, [custom_styles]]]]) -> Widget ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Base widget class. Example of use -------------- :: mywidget = Widget(x=30, y=100, width=100, height=30) mywidget.connect(EVENT, myfunc, [values]) # One of the cool things about GooeyPy is that you can change any style # widget specific options (like 'value' in the input and slider widgets) # and the widget will automatically update itself. mywidget.value = "GooeyPy is awesome!" # One thing to note is that while when initializing a widget you can # pass it arguments as custom styles (see below), you can't access those # styles directly (i.e. mywidget.width won't give you what you expect, # and it will raise an error if you try to assign to it). So if you want # to change or read a style option you would have to do something like # the following: mywidget.stylesets["default"]["width"] = 140 # You can also do it like this: mywidget.style["width"] = 140 # But be warned, widget.style returns the current styleset that's in # use! So if you are hovering over the widget when you change the width, # that width will only apply to that widget when you hover over it. # So if you want the width to apply to the widget at all times, you # can apply it to the "default" styleset (as shown above) in which # case it will apply to all stylesets that don't already have a width # style set. Or you can loop through mywidget.stylesets and set it for # each one, like so: for s in mywidget.stylesets.values(): s["width"] = 140 Arguments --------- disabled If a widget is disabled, the "disabled" styling will apply and it will cease to interact with the user. active This widget will be ignored all together if it's not active. parent This is used internally when creating new widget types. custom_styles When you pass any extra arguments they get passed as a custom style; the key being the style option and the value passed as the tyle's option. Like in our example above, we pass 'x', 'y', 'width', and 'height'. Those will automatically get converted into styling options specific to that widget. Please refer to the documentation on styling for all available options and valid values. """ disabled = util.ReplaceableCellDescriptor() hovering = util.ReplaceableCellDescriptor() focused = util.ReplaceableCellDescriptor() stylesets = util.ReplaceableCellDescriptor() #parent = InputCellDescriptor() container = util.ReplaceableCellDescriptor() pressing = util.ReplaceableCellDescriptor() selected = util.ReplaceableCellDescriptor() active = util.ReplaceableCellDescriptor() def __init__(self, disabled=False, active=True, parent=None, **params): DependantCell.__init__(self) self.hovering = False self.focused = False self.active = active # There is a subtle difference between parent and container. A widget # can have any other widget as a parent while a container must be a # Container widget. parent is used for inheriting styles and positioning # with padding and aligning. Normally, when a widget has a container, # it's parent is the same as it. self.parent = parent self.container = None self.connects = {} # Move to widgets that use it? self.selected = False self._last_blit_rect = None self.effect = None # Weather or not this widget is being pressed. self.pressing = 0 # This widget is still drawn if disabled is True, it just doesn't work. self.disabled = disabled def get_style_option(key): s = self.style[key] if hasattr(s, "values"): return s.values return s self.style_option = ComputedDict(get_style_option) # FIXME: Adding one more causes a really wierd bug where sometimes a # widget will inherit from Widget styling when not using default style. self.stylesets = {"disabled":None, "hover":None, "default":None, "focused":None, "down":None} # Generate the stylesets. surf = None style = None s = util.app.get_style(self) if s: #TODO: clear unnecessary spaces. for cmd in s.split(";"): cmd = cmd.strip() t = cmd.split(":") if len(t) == 2: option = t[0] values = t[1].split(" ") if option == "event": # A new section! if values[0] != "default" and not self.stylesets["default"]: print "Missing default styling for "+str(self)+" You must have the styling for the default event defined first!" sys.exit() if (self.stylesets.has_key(values[0]) and not\ self.stylesets[values[0]]) or not\ self.stylesets.has_key(values[0]): if values[0] != "default": parent = self.stylesets["default"] else: parent = None style = util.widgets.StyleSet(self, parent) self.stylesets[values[0]] = style else: style = self.stylesets[values[0]] if option != "event": style[option] = values # Set any stylesets that are still blank. for (e, v) in self.stylesets.items(): if not v: self.stylesets[e] = util.widgets.StyleSet(self, self.stylesets["default"]) for (option,values) in params.items(): self.stylesets["default"][option] = values def link(self, attr): """ Widget.link(attr) -> cellulose.DependencyCell ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This is one of those very cool things in GooeyPy where you won't find anywhere else. You can have one widget share a value with one or more other widgets! In the below example, whenever the value of myslider changes, so will the fontsize for mylabel and vise-versa (and it automatically redraws the stuff too... of course). Example of use -------------- :: myslider = Slider(value=10, length=20) mylabel = Label(value="hi", font_size=myslider.link("value")) Linking works with any attribute (that you would want to change) or styling option in any widget! Cool eh? Well, actually it's not quite all working perfectly, so expect a bug or two with linking certain attributes on certain widgets. There is one pit-fall with this... if the thing you are trying to link isn't an attribute or a Cell (i.e. linkable), it will return None (and that could cause rather obscure errors). """ if attr in self._cells: return self._cells[attr] # Isn't a linkable cell, but perhaps there is a privet attribute that is elif attr[0] != "_": return self.link("_"+attr) else: return None def connect(self, code, func, *values): """ Widget.connect(code, func, [values]) -> None ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Connect an event to a callback function. Example of use -------------- :: def onclick(value): print 'clicked', value mybutton = Button("GooeyPy") mybutton.connect(CLICK, onclick, "button") Arguments --------- code The event code to trigger the callback. func The callback values Extra values you may want to send on to your callback when it is called. """ self.connects[code] = {'fnc':func,'values':values} def _set_dirty(self, v): self._dirty = v def _get_dirty(self): self._verify_possibly_changed_dependencies() return self._dirty dirty = property(_get_dirty, _set_dirty) def possibly_changed(self): pass def set_x(self,v): self.stylesets["default"]["x"] = v x = property(lambda self:int(self.stylesets["default"]["x"]), set_x) def set_y(self,v): self.stylesets["default"]["y"] = v y = property(lambda self:int(self.stylesets["default"]["y"]), set_y) def pos(self, pos=None): """ Widget.pos -> Tuple ^^^^^^^^^^^^^^^^^^^ Get the actual position that the widget should be drawn to and receive events at. """ if pos: x,y = pos else: x = int(self.style_option["x"]) y = int(self.style_option["y"]) if self.style_option["position"] == "relative": if self.parent: px, py = self.parent.pos # Padding. x += px #+ int(self.parent.stylesets["default"]["padding-left"]) y += py #+ int(self.parent.stylesets["default"]["padding-top"]) # Alignment positioning. if self.style_option["align"] == "center": x += (self.parent.width - self.width) / 2 elif self.style_option["align"] == "right": x += (self.parent.width - self.width) else: x += int(self.parent.stylesets["default"]["padding-left"]) if self.style_option["valign"] == "center": y += (self.parent.height - self.height) / 2 elif self.style_option["valign"] == "bottom": y += (self.parent.height - self.height) else: y += int(self.parent.stylesets["default"]["padding-top"]) if self.container and self.container.scrollable: x -= self.container.offset_x y -= self.container.offset_y return (x,y) pos = ComputedCellDescriptor(pos) def width(self, w=None): """ Widget.width -> int ^^^^^^^^^^^^^^^^^^^ This returns the actual width of this widget. It takes into account padding (for widgets that contain other widgets), min-width and max-width styles. """ if not w or self.style_option["width"]: w = self.style_option["width"] + self.style_option["padding-left"]+\ self.style_option["padding-right"] w = max(w,0) width = w min_width = self.style_option["min-width"] max_width = self.style_option["max-width"] if min_width and width < min_width: w = min_width if max_width and width > max_width: w = max_width return w width = ComputedCellDescriptor(width) def height(self, h=None): """ Widget.height -> int ^^^^^^^^^^^^^^^^^^^^ This returns the actual height of this widget. It takes into account padding (for widgets that contain other widgets), min-height and max-height styles. """ if h is None or self.style_option["height"]: h = self.style_option["height"] + self.style_option["padding-top"]+\ self.style_option["padding-bottom"] h = max(h,0) height = h min_height = self.style_option["min-height"] max_height = self.style_option["max-height"] if min_height and height < min_height: h = min_height if max_height and height > max_height: h = max_height return h height = ComputedCellDescriptor(height) def rect(self): """ Widget.rect -> pygame.Rect ^^^^^^^^^^^^^^^^^^^^^^^^^^ Gets the rect of the widget's current surface. """ r = self.surface.get_rect() r.x, r.y = self.pos return r rect = ComputedCellDescriptor(rect) def focusable(self): return self.style_option["focusable"] focusable = ComputedCellDescriptor(focusable) def style(self): """ Widget.style -> StyleSet ^^^^^^^^^^^^^^^^^^^^^^^^ Returns the current styleset in use (from Widget.stylesets) """ if self.disabled: return self.stylesets["disabled"] elif self.pressing: return self.stylesets["down"] elif self.selected is True and self.stylesets.has_key("selected"): return self.stylesets["selected"] elif self.hovering: return self.stylesets["hover"] elif self.focused and self.stylesets["focused"]: return self.stylesets["focused"] else: return self.stylesets["default"] style = ComputedCellDescriptor(style) def surface(self): return self.style.surf surface = ComputedCellDescriptor(surface) def draw(self, drawbg=True): """ Widget.draw() -> None ^^^^^^^^^^^^^^^^^^^^^ draws the widget if necessary. """ #HACK: Better place to put this? Here because draw is run every frame. if self.focused: self.repetitive_events() if self.dirty or self.effect or self.style_option["effect"] != 'none': # We don't want to keep around old dependencies. We gotta clear them # before adding the new ones. for dep in self.dependencies.values(): dep.unregister_dependant(self) self.dependencies = {} self.push() try: surf = self.style.surf if self.effect: # An effect is running. r = self.effect.run(surf) if not r: self.effect = None else: surf = r elif self.style_option["effect"][0] == "pulsate": i = int(self.style_option["effect"][1]) self.effect = effects.Pulsate(i) surf = self.effect.run(surf) #print self.__class__.__name__ x,y = self.pos # FIXME: won't work if widget doesn't have container... but # fixes the x=y=0 display bug. if drawbg and self._last_blit_rect and self.container: lbrpos =(self._last_blit_rect.x, self._last_blit_rect.y) self._last_blit_rect.x -= self.container.x self._last_blit_rect.y -= self.container.y util.blit(self.container.surface, lbrpos, clip_rect=self._last_blit_rect) clip_rect = surf.get_rect() if self.container and self.container.scrollable: #y -= self.container.offset_y if self.container.offset_y > self.y+self.height: return if self.container.offset_y+self.container.height < self.y+\ self.container.style_option["padding-bottom"]+\ self.container.style_option["padding-top"]+\ self.height: # Off the bottom. Need to figure out how to do clipping return #clip_rect = surf.get_rect()#self.container.rect clip_rect.x = 0#min(self.x-self.container.offset_x, self.x) clip_rect.y = min(0, self.y-self.container.offset_y) #y = min(y, self.container.height-self.height) #if self.container.offset_y+self.container.height < self.y+\ #self.height+\ #self.container.style_option["padding-bottom"]+\ #self.container.style_option["padding-top"]: #clip_rect.height = self.container.height-\ #self.container.style_option["padding-bottom"] - self.y #print self.y self.clip_rect = clip_rect self._last_blit_rect = util.blit(surf, (x, y), clip_rect) self.draw_widget() self.dirty = False finally: self.pop() def draw_widget(self): """ When writing your own widgets, you can overwrite this function to have custom processes when drawing. """ pass def repetitive_events(self): pass def event(self, e): pass def click(self): """ When writing your own widgets, you can overwrite this function which is called every time the widget is clicked. """ pass def next(self): if self.container: self.container.next(self) def enter(self): self.hovering = True def exit(self): self.hovering = False def send(self,code,event=None): """ Widget.send(code, [event]) -> None ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sends the event code 'code' and event 'event' (if suplied) to trigger any callbacks. """ if code in self.connects: con = self.connects[code] fnc = con['fnc'] values = list(con['values']) nargs = fnc.func_code.co_argcount names = list(fnc.func_code.co_varnames)[:nargs] if hasattr(fnc,'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) fnc(*args) def _event(self,e): #return if self.disabled: return send = False if e.type == KEYDOWN: if self.focused: send = True if e.key == K_ESCAPE: self.focused = False #elif e.type == KEYDOWN and e.key == K_TAB: #self.next() elif e.type == MOUSEBUTTONDOWN: if self.rect.collidepoint(e.pos): self.pressing = 1 self.dirty = True self.focused = True send = True else: self.focused = False elif e.type == MOUSEBUTTONUP: if self.pressing == 1: self.pressing = 0 self.dirty = True if self.rect.collidepoint(e.pos): self.send(CLICK) self.click() send = True elif e.type == MOUSEMOTION: # This takes quite a bit of processing. if self.rect.collidepoint(e.pos): if not self.hovering: self.enter() send = True else: if self.hovering: self.exit() if self.focused: send = True if send: self.send(e.type, e) self.event(e)
class Label(util.widgets.Widget): """ Label(value) -> Label widget ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Basic widget for simply displaying some text. Not really much to it, you can pass it a value which it will display. """ value = util.ReplaceableCellDescriptor(restriction=StringRestriction()) def __init__(self, value, **params): super(Label, self).__init__(self, **params) self.value = value def font(self): """ Label.font -> pygame.Font ^^^^^^^^^^^^^^^^^^^^^^^^^ The font that this label is using. """ f = util.app.get_font(self.style_option["font-family"], self.style_option["font-size"]) if self.style_option["font-weight"] == "bold": f.set_bold(True) return f font = ComputedCellDescriptor(font) def width(self): w = self.font.size(self.value)[0] return util.widgets.Widget.width.function(self, w) width = ComputedCellDescriptor(width) def height(self): h = self.font.size(self.value)[1] return util.widgets.Widget.height.function(self, h) height = ComputedCellDescriptor(height) def font_x(self): x, _ = self.pos return x + self.style_option["padding-left"] font_x = ComputedCellDescriptor(font_x) def font_y(self): _, y = self.pos return y + self.style_option["padding-top"] font_y = ComputedCellDescriptor(font_y) #def clip_rect(self): #return pygame.Rect(0,0, #self.width-(self.style_option["padding-left"]+\ #self.style_option["padding-right"]), #self.height-(self.style_option["padding-top"]+\ #self.style_option["padding-bottom"])) #clip_rect = ComputedCellDescriptor(clip_rect) def font_value(self): return self.font.render(self.value, self.style_option["antialias"], self.style_option["color"]) font_value = ComputedCellDescriptor(font_value) def draw_widget(self): # We do this incase we are trying to blit the text into a space smaller # than it can fit into. util.blit(self.font_value, (self.font_x, self.font_y), clip_rect=self.clip_rect)
class Container(util.widgets.Widget): """ Container([scrollable]) -> Container widget ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ A base container widget for containing other widgets. (gosh that's descriptive!) scrollable is for wether or not the container is, well, scrollable (this feature isn't complete yet, but still usable)! """ scrollable = util.ReplaceableCellDescriptor() offset_x = util.ReplaceableCellDescriptor() offset_y = util.ReplaceableCellDescriptor() def __init__(self, scrollable=False, **params): super(Container, self).__init__(**params) self.widgets = CellList() self.scrollable = scrollable self.offset_x = 0 self.offset_y = 0 self.vslider = util.widgets.VSlider(length=self.content_height, step=False, active=self.scrollable, height=self.style_option["height"]-\ self.style_option["padding-top"]-\ self.style_option["padding-bottom"]) self.vslider.x = self.width - self.vslider.width-\ self.style_option["padding-left"]-\ self.style_option["padding-right"] #self.vslider.container = self self.vslider.parent = self self.offset_y = self.vslider.link("value") self.vslider._cells["disabled"] = self._cells["disabled"] self.vslider._cells["length"] = self._cells["content_height"] self.vslider.connect(CHANGE, self.draw_widget) #def width(self, width=0): #width = util.widgets.Widget.width.function(self, width) #return width+self.vslider.width #width = ComputedCellDescriptor(width) def content_height(self): return 0 content_height = ComputedCellDescriptor(content_height) def draw_widget(self): if not self.widgets: return for w in self.widgets: w.dirty = True self.vslider.dirty = True def draw(self, drawbg=True): if self.dirty: drawbg = False else: drawbg = True super(Container, self).draw() if not self.widgets: return for w in self.widgets: if w.active: w.draw(drawbg) if self.scrollable: self.vslider.draw(False) def exit(self): self.hovering = False if not self.widgets: return for w in self.widgets: w.exit() def event(self, e): if not self.widgets: return for w in self.widgets: if w.active: w._event(e) if self.scrollable: self.vslider._event(e) def add(self, *widgets): """ Container.add(widgets) -> None ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Add widgets to Container. Arguments --------- widgets can ether be a single widget or a list of widgets. """ for w in widgets: if type(w) == list: raise ValueError( "Got unexpected value. Remember that if you want to add multiple widgets to a container, do c.add(w1,w2,w3)!" ) self.widgets.append(w) w.container = self w.parent = self w.send(OPEN) def remove(self, widgets): """ Container.remove(widgets) -> None ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Remove widgets from Container. Arguments --------- widgets can ether be a single widget or a list of widgets. """ try: iter(widgets) except TypeError: widgets = [widgets] for w in widgets: self.widgets.remove(w) w.send(CLOSE) self.dirty = True def _next(self, orig=None): start = 0 if orig and 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 next(self, w=None): if self._next(w): return True if self.container: return self.container.next(self)
class StyleSet(object): """ StyleSet(widget, [parent]) -> StyleSet object ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ A styleset is a collection of style options grouped together for a specific event for a widget. Each widget has an dictionary named "stylesets" which contains several stylesets. For example:: {"disabled":styleset, "hover":styleset, "default":styleset, "focused":styleset, "down":styleset} When the widget is hovered for example it will use stylesets["hover"] At any time you can change a styling option for a widget by doing something like this:: mywidget.stylesets["hover"]["color"] = (255,10,10) **WARNING:** Do not use the color (0,0,0)! It is used as the surface color key. That means that if you use it it won't show up! If you want to use the color black, use (5,5,5) or something (which is indistinguishable from pure black). This applies to font colors sometimes. :P Style options ------------- =============== ======================================================= option value =============== ======================================================= padding 8 4 4 5 | 4 5 | 10 padding-top 3 padding-right 3 padding-bottom 3 padding-left 3 color (255,255,255) font-weight normal | bold font-family Verra.ttf font-size 12 effect pulsate 5 width 300 height 200 x 57 y 102 position relative | absolute bgcolor (200,0,0) bgimage image.png repeat|repeat-x|repeat-y|no-repeat|slice border (0,250,20) 3 spacing 12 # For certain container widgets. opacity 25 # Alpha value. Valid otpions between 0 and 255. antialias 1 | 0 align left | right | center valign top | bottom | center =============== ======================================================= Something to note about align and valign. When they are changed from their default values (top and left), the x and y values act as an offset. So if you are aligning a widget center and have an x value of 100, the widget will display 100 pixels to the right of the center of it's parent (or container) widget. Also, aligning only takes effect when positioning is relative (which is default). Arguments --------- widget The widget this styleset is for. parent Another styleset that this styleset should inherit from. """ parent = util.ReplaceableCellDescriptor() widget = util.ReplaceableCellDescriptor() # Setting a default style option here will make it use it at all times # unless explicitly set. In other words, inheriting doesn't work for these # options. default_styles = { "padding-top": 0, "padding-right": 0, "padding-bottom": 0, "padding-left": 0, "spacing": 0, "font-weight": "normal", "effect": "none", "width": util.screen_width, "height": util.screen_height, "x": 0, "y": 0, "position": "relative", } styles = { "opacity": Opacity, "bgcolor": BgColor, "border": Border, "bgimage": BgImage, "color": Color, "padding": Padding, "effect": Effect, } def __init__(self, widget, parent=None): self.widget = widget self.parent = parent self._applied_styles = CellDict(self.default_styles) def __getitem__(v): return self.__getitem__(v) self._applied_styles.__getitem__ = __getitem__ # We make this read only so people can only modify the applied_styles and # not reassign it. applied_styles = property(lambda self: self._applied_styles) def surf(self): """ StyleSet.surf -> pygame.Surface ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The surface generated from the styling. """ if self.parent: self.surface = self.parent.surf.copy() else: size = (int(self.widget.width), int(self.widget.height)) self.surface = pygame.Surface(size) self.surface.set_colorkey((0, 0, 0, 0)) for s in self.applied_styles.values(): if hasattr(s, "render"): s.render() return self.surface surf = ComputedCellDescriptor(surf) def __getitem__(self, v): r = None if self.applied_styles.has_key(v): r = self.applied_styles[v] if not r and self.parent: r = self.parent[v] if hasattr(r, "value"): r = r.value return r def __setitem__(self, k, v): self.apply(k, v) def apply(self, option, values): """ StyleSet.apply(surf, option, values) -> pygame.Surface ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Applies styling to surf. If you are just wanting to change a style option, use:: StyleSet["option"] = values This function is mainly for internal use. """ option = option.replace("_", "-") o = option.replace("-", "_") if type(values) == str or type(values) == int: if type(values) == str: values = values.split(" ") else: values = [values] if o in self.styles: self.styles[o](self, values) else: self.generic(option, values) def generic(self, n, v): if type(v) == list: if len(v) == 1: v = v[0] v = str(v) try: v = int(v) except ValueError: pass self.applied_styles[n] = v