Source code for serge.blocks.visualblocks

"""Useful blocks for visual rendering"""

import pygame

import serge.visual

[docs]class InvalidSprite(Exception): """The selected sprite was not valid"""
[docs]class InvalidParameters(Exception): """The parameters for the shape were not valid"""
[docs]class OutOfRange(Exception): """The value was outside the valid range"""
[docs]class OverlappingRanges(Exception): """The ranges for the progress bar were overlapping"""
[docs]class RangesNotContiguous(Exception): """The ranges for the progress bar had gaps in them"""
### Simple shapes ###
[docs]class Rectangle(serge.visual.SurfaceDrawing): """A rectangle""" def __init__(self, (w, h), colour, stroke_width=0, stroke_colour=None): """Initialise the rectangle""" super(Rectangle, self).__init__(w, h) self._colour = colour self.stroke_colour = stroke_colour self.stroke_width = stroke_width if (stroke_width and stroke_colour) and (stroke_width > min(w, h)/2 or stroke_width < 0): raise InvalidParameters('Stroking width must be > 0 and less than half the smallest side (%s)' % (stroke_width,)) self._updateSurface() def _updateSurface(self): """Render to a surface""" surface = self.clearSurface() if self.stroke_colour and self.stroke_width: # # If we are stroking then draw a full size rectange with the stroke colour and # then a smaller one with the actual fill colour pygame.draw.rect(surface, self.stroke_colour, (0, 0, self.width, self.height)) pygame.draw.rect(surface, self.colour, (self.stroke_width, self.stroke_width, self.width-2*self.stroke_width, self.height-2*self.stroke_width)) else: pygame.draw.rect(surface, self.colour, (0, 0, self.width, self.height), self.stroke_width) @property def colour(self): return self._colour @colour.setter def colour(self, value): self._colour = value self._updateSurface()
[docs]class Polygon(serge.visual.SurfaceDrawing): """A polygon""" def __init__(self, points, colour, stroke_width=0, stroke_colour=None): """Initialise the rectangle""" w = max([x for x, y in points]) + stroke_width h = max([y for x, y in points]) + stroke_width self.points = points # super(Polygon, self).__init__(w, h) self._colour = colour self.stroke_colour = stroke_colour self.stroke_width = stroke_width if (stroke_width and stroke_colour) and (stroke_width > min(w, h) / 2 or stroke_width < 0): raise InvalidParameters( 'Stroking width must be > 0 and less than half the smallest side (%s)' % (stroke_width,)) self._updateSurface() def _updateSurface(self): """Render to a surface""" surface = self.clearSurface() pygame.draw.polygon(surface, self.colour, self.points, 0) if self.stroke_colour and self.stroke_width: # # If we are stroking then draw a full size rectange with the stroke colour and # then a smaller one with the actual fill colour pygame.draw.polygon(surface, self.stroke_colour, self.points, self.stroke_width) @property def colour(self): return self._colour @colour.setter def colour(self, value): self._colour = value self._updateSurface()
[docs]class Circle(serge.visual.SurfaceDrawing): """A circle""" def __init__(self, radius, colour, stroke_width=0, stroke_colour=None): """Initialise the circle""" super(Circle, self).__init__(radius*2, radius*2) self._radius = radius self._colour = colour self.stroke_colour = stroke_colour self.stroke_width = stroke_width if (stroke_width and stroke_colour) and (stroke_width > radius or stroke_width < 0): raise InvalidParameters('Stroking width must be > 0 and less than the radius (%d)' % stroke_width) self._updateSurface() def _updateSurface(self): """Render to a surface""" self.width = self.height = self._radius*2 surface = self.clearSurface() x = y = self._radius if self.stroke_colour and self.stroke_width: # # If we are stroking then draw a full size circle with the stroke colour and # then a smaller one with the actual fill colour pygame.draw.circle(surface, self.stroke_colour, (int(x), int(y)), self._radius) pygame.draw.circle(surface, self.colour, (int(x), int(y)), self._radius-self.stroke_width) else: pygame.draw.circle(surface, self.colour, (int(x), int(y)), self._radius, self.stroke_width)
[docs] def setAngle(self, angle): """Set the angle Pass through as this is a circle!""" pass
def _updateDimensions(self): """Update the dimensions of the drawing""" self.width = self.surface.get_width() self.height = self.surface.get_height() self.cx = self.width/2 self.cy = self.height/2 @property def colour(self): return self._colour @colour.setter def colour(self, value): self._colour = value self._updateSurface() @property def radius(self): return self._radius @radius.setter def radius(self, value): self._radius = value self._updateSurface()
### Shapes with text ###
[docs]class RectangleText(serge.visual.Drawing): """A rectangle with some text on it""" def __init__(self, text, text_colour, rect_dimensions, rect_colour, font_size=12, font_name='DEFAULT', stroke_width=0, stroke_colour=None, justify='center'): """Initialise the drawing""" self.text_visual = serge.visual.Text(text, text_colour, font_name, font_size, justify) self.rect_visual = Rectangle(rect_dimensions, rect_colour, stroke_width, stroke_colour) self.width = self.rect_visual.width self.height = self.rect_visual.height
[docs] def renderTo(self, milliseconds, surface, (x, y)): """Render to a surface""" w, h = self.rect_visual.width, self.rect_visual.height self.rect_visual.renderTo(milliseconds, surface, (x, y)) self.text_visual.renderTo(milliseconds, surface, (x+w/2-self.text_visual.width/2, y+h/2-self.text_visual.height/2))
[docs]class CircleText(serge.visual.Drawing): """A circle with some text on it""" def __init__(self, text, text_colour, radius, circle_colour, font_size=12, font_name='DEFAULT', stroke_width=0, stroke_colour=None, justify='center'): """Initialise the drawing""" self.text_visual = serge.visual.Text(text, text_colour, font_name, font_size, justify) self.circle_visual = Circle(radius, circle_colour, stroke_width, stroke_colour)
[docs] def renderTo(self, milliseconds, surface, (x, y)): """Render to a surface""" self.circle_visual.renderTo(milliseconds, surface, (x, y)) self.text_visual.renderTo(milliseconds, surface, (x-self.text_visual.width/2+self.circle_visual.width/2, y-self.text_visual.height/2+self.circle_visual.width/2))
[docs] def getSize(self): """Return the size of the drawing""" return [self.radius]
### Sprite with text ###
[docs]class SpriteText(serge.visual.Sprite): """A sprite with some text on it""" def __init__(self, text, text_colour, sprite_name, font_size=12, font_name='DEFAULT', stroke_width=0, stroke_colour=None, justify='center'): """Initialise the drawing""" super(SpriteText, self).__init__() self.text_visual = serge.visual.Text(text, text_colour, font_name, font_size, justify) sprite = serge.visual.Register.getItem(sprite_name) self.setImage(sprite.raw_image, (sprite.width, sprite.height), sprite.framerate, sprite.running)
[docs] def renderTo(self, milliseconds, surface, (x, y)): """Render to a surface""" super(SpriteText, self).renderTo(milliseconds, surface, (x, y)) self.text_visual.renderTo(milliseconds, surface, (x+self.width/2-self.text_visual.width/2, y+self.height/2-self.text_visual.height/2))
[docs] def setText(self, text): """Set the text""" self.text_visual.setText(text)
@property def text(self): return self.text_visual.text
[docs]class TextToggle(SpriteText): """A sprite text item that has multiple cells and can be used as a toggle You can set the cells directly of use On=0 and Off=1. """ def __init__(self, *args, **kw): """Initialise the TextToggle""" super(TextToggle, self).__init__(*args, **kw) # # Reality check - the underlying sprite must have at least two cells if len(self.cells) <= 1: raise InvalidSprite('The selected sprite does not have enough cells. Needs at least two')
[docs] def setOn(self): """Set to on""" self.setCell(0)
[docs] def setOff(self): """Set to off""" self.setCell(1)
[docs] def toggle(self): """Toggle the state""" if self.current_cell == 0: self.setOff() else: self.setOn()
[docs] def isOn(self): """Return if we are on""" return self.current_cell == 0
[docs] def isOff(self): """Return if we are on""" return self.current_cell != 0
[docs]class Toggle(TextToggle): """Like a text toggle but with no text""" def __init__(self, sprite_name): """Initialise the toggle""" super(Toggle, self).__init__('', (0,0,0,0), sprite_name)
[docs]class ProgressBar(serge.visual.SurfaceDrawing): """A progress bar The progress bar shows a rectangle on the screen which you can use to show progress or represent the number of certain items. The bar can be a single colour or can change colour within certain ranges. Use the **value** property to set the current value of the bar. :param size: (w, h) the size of the bar :param value_ranges: [(low, high, colour), ...] list of coloured ranges """ def __init__(self, size, value_ranges, border_width=0, border_colour=(255,255,255,255)): """Initialise the ProgressBar""" super(ProgressBar, self).__init__(*size) self._checkRanges(value_ranges) self.stroke_width = 0 self.border_width = border_width self.border_colour = border_colour self._updateSurface() def _checkRanges(self, value_ranges): """Check that the ranges make sense""" self.value_ranges = value_ranges last_low = last_high = None for low, high, colour in value_ranges: if last_low != None: if low < last_high: raise OverlappingRanges('The ranges overlap (%d, %d)' % (last_high, low)) elif low > last_high: raise RangesNotContiguous('The ranges have gaps (%d, %d)' % (last_high, low)) else: self._value = low self._colour = colour self._min = low last_low, last_high = low, high self._max = high # # The value property, which is used to determine the progress amount @property def value(self): return self._value @value.setter # def value(self, x): self._value = x for low, high, colour in self.value_ranges: if low <= x < high: self._colour = colour break else: # # Watch out for special case of x = maximum if x == high: self._colour = colour else: raise OutOfRange('The value %s was not in the range of the progress bar' % x) self._updateSurface() def _updateSurface(self): """Update our surface""" self.clearSurface() width = float(self._value-self._min)/(self._max-self._min)*self.width if width != 0.0: pygame.draw.rect(self.getSurface(), self._colour, (0, 0, width, self.height), self.stroke_width) if self.border_width: pygame.draw.rect(self.getSurface(), self.border_colour, (0, 0, self.width, self.height), self.border_width)
[docs]class BlockedProgressBar(serge.visual.SurfaceDrawing): """A progress bar made of a series of blocks The progress bar shows a series of rectangle on the screen which you can use to show progress or represent the number of certain items. The bar is made of a series of rectangles that can change colour within certain ranges. Use the **value** property to set the current value of the bar. :param size: (w, h) the size of the bar :param block_size: (w, h) the size of each block :param vertical: True/False whether to display vertical or horizontal :param value_ranges: [(high, colour, back-colour), ...] list of coloured ranges, assumed to start at 0 :param use_back_colour: if True then the back colour will be draw for inactive blocks """ def __init__(self, size, block_size, vertical, value_ranges, use_back_colour=True): """Initialise the ProgressBar""" super(BlockedProgressBar, self).__init__(*size) self.stroke_width = 0 self.value = 0.0 self.use_back_colour = use_back_colour self.block_size = block_size self.value_ranges = value_ranges self.vertical = vertical self._updateSurface()
[docs] def setValue(self, value): """Set the value""" self.value = value self._updateSurface()
[docs] def getValue(self): """Return the value""" return self.value
def _updateSurface(self): """Update our surface""" self.clearSurface() surface = self.getSurface() number_blocks = len(self.value_ranges) # for i, (high, colour, back_colour) in enumerate(self.value_ranges): # if self.value < high: if not self.use_back_colour: break else: colour = back_colour # # Position of the block if self.vertical: left = (self.width - self.block_size[0]) / 2 top = (number_blocks - 1 - float(i)) / (number_blocks - 1) * (self.height - self.block_size[1]) else: left = float(i) / (number_blocks - 1) * (self.width - self.block_size[0]) top = (self.height - self.block_size[1]) / 2 # # Draw the block pygame.draw.rect(surface, colour, (left, top, self.block_size[0], self.block_size[1]))