from . import shape
from psychopy.tools.attributetools import attributeSetter, setAttribute
# Aliases for directions
_directionAliases = {
# Horizontal
"horizontal": "horizontal",
"horiz": "horizontal",
"x": "horizontal",
"0": "horizontal",
"False": "horizontal",
0: "horizontal",
False: "horizontal",
# Vertical
"vertical": "vertical",
"vert": "vertical",
"y": "vertical",
"1": "vertical",
"True": "vertical",
1: "vertical",
True: "vertical",
}
[docs]class Progress(shape.ShapeStim):
"""
A basic progress bar, consisting of two rectangles: A background and a foreground. The foreground rectangle
fill the background as progress approaches 1.
Parameters
==========
win : :class:`~psychopy.visual.Window`
Window this shape is being drawn to. The stimulus instance will
allocate its required resources using that Windows context. In many
cases, a stimulus instance cannot be drawn on different windows
unless those windows share the same OpenGL context, which permits
resources to be shared between them.
name : str
Name to refer to this Progress bar by
progress : float
Value between 0 (not started) and 1 (complete) to set the progress
bar to.
direction : str
Which dimension the bar should fill along, either "horizontal"
(also accepts "horiz", "x" or 0) or "vertical" (also accepts
"vert", "y" or 1)
pos : array_like
Initial position (`x`, `y`) of the shape on-screen relative to
the origin located at the center of the window or buffer in `units`.
This can be updated after initialization by setting the `pos`
property. The default value is `(0.0, 0.0)` which results in no
translation.
size : array_like, float, int or None
Width and height of the shape as `(w, h)` or `[w, h]`. If a single
value is provided, the width and height will be set to the same
specified value. If `None` is specified, the `size` will be set
with values passed to `width` and `height`.
anchor : str
Point within the shape where size and pos are set from. This also
affects where the progress bar fills up from (e.g. if anchor is
"left" and direction is "horizontal", then the bar will fill from
left to right)
units : str
Units to use when drawing. This will affect how parameters and
attributes `pos` and `size` are interpreted.
barColor, foreColor : array_like, str, :class:`~psychopy.colors.Color` or None
Color of the full part of the progress bar.
backColor, fillColor : array_like, str, :class:`~psychopy.colors.Color` or None
Color of the empty part of the progress bar.
borderColor, lineColor : array_like, str, :class:`~psychopy.colors.Color` or None
Color of the outline around the outside of the progress bar.
colorSpace : str
Sets the colorspace, changing how values passed to `foreColor`,
`lineColor` and `fillColor` are interpreted.
lineWidth : float
Width of the shape outline.
opacity : float
Opacity of the shape. A value of 1.0 indicates fully opaque and 0.0
is fully transparent (therefore invisible). Values between 1.0 and
0.0 will result in colors being blended with objects in the
background.
"""
def __init__(
self, win, name="pb",
progress=0, direction="horizontal",
pos=(-.5, 0), size=(1, 0.1), anchor="center-left", units=None,
barColor=False, backColor=False, borderColor=False, colorSpace=None,
lineWidth=1.5, opacity=1.0, ori=0.0,
depth=0, autoLog=None, autoDraw=False,
# aliases
foreColor="white", fillColor=False, lineColor="white",
):
# Alias color names
if backColor is False and fillColor is not False:
backColor = fillColor
if barColor is False and foreColor is not False:
barColor = foreColor
if borderColor is False and lineColor is not False:
borderColor = lineColor
# Create backboard
shape.ShapeStim.__init__(
self, win, name=name,
pos=pos, size=size, anchor=anchor, units=units,
fillColor=backColor, lineColor=borderColor, colorSpace=colorSpace,
lineWidth=lineWidth, opacity=opacity, ori=ori,
depth=depth, autoLog=autoLog, autoDraw=autoDraw,
vertices="rectangle"
)
# Create bar
self.bar = shape.ShapeStim(
win, name=f"{name}Bar",
pos=pos, size=size, anchor=anchor, units=units,
fillColor=barColor, lineColor=None, colorSpace=colorSpace,
opacity=opacity, ori=ori,
depth=depth, autoLog=autoLog, autoDraw=autoDraw,
vertices="rectangle"
)
# Store direction
self.direction = direction
self.progress = progress
# --- Basic ---
@attributeSetter
def progress(self, value):
"""
How far along the progress bar is
Parameters
==========
value : float
Between 0 (not complete) and 1 (fully complete)
"""
# Sanitize value
value = float(value)
value = max(value, 0)
value = min(value, 1)
# Store value
self.__dict__['progress'] = value
# Multiply size by progress to get bar size
i = 0
if self.direction == "vertical":
i = 1
sz = self.size.copy()
sz[i] *= value
self.bar.size = sz
def setProgress(self, value, log=None, operation=False):
setAttribute(self, "progress", value, log=log, operation=operation)
@attributeSetter
def direction(self, value):
"""
What direction is this progress bar progressing in? Use in combination with "anchor"
to control which direction the bar fills up from.
Parameters
==========
value : str, int, bool
Is progress bar horizontal or vertical? Accepts the following values:
* horizontal: "horizontal", "horiz", "x", "0", "False", 0, False
* vertical: "vertical", "vert", "y", "1", "True", 1, True
"""
# Set value (sanitized)
self.__dict__['direction'] = self._sanitizeDirection(value)
if not isinstance(self.progress, attributeSetter):
# Get current progress
progress = self.progress
# Set progress to 1 so both dimensions are the same as box size
self.setProgress(1, log=False)
# Set progress back to layout
self.setProgress(progress, log=False)
def setDirection(self, value, log=None, operation=False):
setAttribute(self, "direction", value, log=log, operation=operation)
[docs] @staticmethod
def _sanitizeDirection(direction):
"""
Take a value indicating direction and convert it to a human readable string
"""
# Ignore case for strings
if isinstance(direction, str):
direction = direction.lower().strip()
# Return sanitized if valid, otherwise assume horizontal
return _directionAliases.get(direction, "horizontal")
@property
def complete(self):
return self.progress == 1
@complete.setter
def complete(self, value):
if value:
# If True, set progress to full
self.progress = 1
else:
# If False and complete, set progress to 0
if self.progress == 1:
self.progress = 0
def setComplete(self, value, log, operation=False):
setAttribute(self, "complete", value, log=log, operation=operation)
@property
def win(self):
return shape.ShapeStim.win.fget(self)
@win.setter
def win(self, value):
shape.ShapeStim.win.fset(self, value)
if hasattr(self, "bar"):
self.bar.win = value
[docs] def draw(self, win=None, keepMatrix=False):
shape.ShapeStim.draw(self, win=win, keepMatrix=keepMatrix)
self.bar.draw()
# --- Appearance ---
@property
def barColor(self):
return self.foreColor
@barColor.setter
def barColor(self, value):
self.foreColor = value
@property
def foreColor(self):
"""
Color of the full part of the progress bar.
"""
if hasattr(self, "bar"):
return self.bar.fillColor
@foreColor.setter
def foreColor(self, value):
if hasattr(self, "bar"):
self.bar.fillColor = value
# Store bar color as protected attribute, mostly just for the test suite
self._foreColor = self.bar._fillColor
@property
def opacity(self):
return shape.ShapeStim.opacity.fget(self)
@opacity.setter
def opacity(self, value):
shape.ShapeStim.opacity.fset(self, value)
if hasattr(self, "bar"):
self.bar.opacity = value
@property
def colorSpace(self):
return shape.ShapeStim.colorSpace.fget(self)
@colorSpace.setter
def colorSpace(self, value):
# Set color space for self and bar
shape.ShapeStim.colorSpace.fset(self, value)
if hasattr(self, "bar"):
self.bar.colorSpace = value
# --- Layout ---
@property
def pos(self):
return shape.ShapeStim.pos.fget(self)
@pos.setter
def pos(self, value):
shape.ShapeStim.pos.fset(self, value)
if hasattr(self, "bar"):
self.bar.pos = value
@property
def size(self):
return shape.ShapeStim.size.fget(self)
@size.setter
def size(self, value):
shape.ShapeStim.size.fset(self, value)
if hasattr(self, "bar"):
self.progress = self.progress
@property
def anchor(self):
return shape.ShapeStim.anchor.fget(self)
@anchor.setter
def anchor(self, value):
shape.ShapeStim.anchor.fset(self, value)
if hasattr(self, "bar"):
self.bar.anchor = value
@property
def units(self):
return shape.ShapeStim.units.fget(self)
@units.setter
def units(self, value):
shape.ShapeStim.units.fset(self, value)
if hasattr(self, "bar"):
self.bar.units = value
@attributeSetter
def ori(self, value):
shape.ShapeStim.ori.func(self, value)
if hasattr(self, "bar"):
self.bar.ori = value