"""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]))