Snippet for displaying images from a catalog in a cover-like way. You can rotate them left or right and you’ve got easy access to the middle one via current() method.
You can also specify distribution of the images on the screen via {x,y,z}_dist functions.
Update: (thanks Nemesis)
To see how it works specify path to catalog with images (and only images unless you want to add some code for filtering out files which are not images). Path is relative to current working directory.
from direct.showbase.ShowBase import ShowBase
from panda3d.core import CardMaker
from direct.task import Task
from direct.interval.LerpInterval import LerpFunc
from direct.interval.IntervalGlobal import *
from os import sep, listdir
class PreviewStrip(object):
def __init__(self, catalog, height = -0.5):
self.height = height
self.catalog = catalog
self.preview_size = [-0.1, 0.1, -0.1, 0.1]
self.generator = CardMaker("PreviewMaker")
self.generator.setFrame(*self.preview_size)
self.textures = []
self.loadPreviewImages()
#number of items visible on the screen
self.visible = min(len(self.textures),5)
#duration of an animation
self.duration = 0.3
self.positions = []
self.preparePositions()
self.head = 0
self.tail = self.visible - 1
def loadPreviewImages(self):
files = listdir(self.catalog)
files.sort()
for filename in files:
self.textures.append(loader.loadTexture(sep.join([self.catalog,filename])))
# distribution functions they specify the shape in which
# initially visible images are arranged
def x_dist(self, i):
return 0.5*(i - self.visible/2)
def y_dist(self, i):
return abs (i - self.visible /2 )
def z_dist(self, i):
return self.height
# initials scaling of the visible images
def scale(self, i):
try:
return 2.5 - 2*abs (float(i)/(self.visible-1) - 0.5)
except:
return 1.0
def preparePositions(self):
for i in range(0,self.visible):
model = aspect2d.attachNewNode(self.generator.generate())
model.setPos(self.x_dist(i), self.y_dist(i), self.z_dist(i))
model.setScale(self.scale(i))
# so that images are correctly displayed on top
# of each other
model.setDepthTest(True)
model.setDepthWrite(True)
self.positions.append(model)
# setting images
for i in range(len(self.positions)):
self.positions[i].setTexture(self.textures[i])
def _scaleItem(self, i, dir):
# if dir is negative item is scaled right
# if dir is positive item is scaled left
next = (i+dir)%len(self.positions)
return LerpScaleInterval (
self.positions[i],
duration = self.duration,
startScale = self.positions[i].getScale(),
scale = self.positions[next].getScale()
)
def _positionItem(self, i, dir):
# if dir is negative item is moved right
# if dir is positive item is moved left
next = (i+dir) % len(self.positions)
return LerpPosInterval (
self.positions[i],
duration = self.duration,
startPos = self.positions[i].getPos()z,
pos = self.positions[next].getPos()
)
def _adjustLeft(self):
# animation ends, we can re-eanble input
last = self.positions.pop()
self.head = (self.head - 1) % len(self.textures)
self.tail = (self.tail - 1) % len(self.textures)
last.setTexture(self.textures[self.head])
self.positions.insert(0,last)
base.acceptOnce("arrow_left",ps.rotateLeft)
base.acceptOnce("arrow_right",ps.rotateRight)
def _adjustRight(self):
first = self.positions.pop(0)
self.head = (self.head + 1) % len(self.textures)
self.tail = (self.tail + 1) % len(self.textures)
first.setTexture(self.textures[self.tail])
self.positions.append(first)
# animation ends, we can re-eanble input
base.acceptOnce("arrow_left",ps.rotateLeft)
base.acceptOnce("arrow_right",ps.rotateRight)
def rotateRight(self):
# prevent to starting another turnaround
base.ignore("arrow_left")
base.ignore("arrow_right")
parallel = Parallel()
for i in range(len(self.positions)-1):
parallel.append( self._positionItem(i, 1))
parallel.append( self._scaleItem(i, 1))
# last item is moved symetrically so it has its scale preserved
parallel.append(self._positionItem(-1,1))
self.seq = Sequence(parallel, Func(self._adjustLeft))
self.seq.start()
def rotateLeft(self):
# prevent to starting another turnaround
base.ignore("arrow_left")
base.ignore("arrow_right")
parallel = Parallel()
for i in range(len(self.positions)):
parallel.append( self._positionItem(i, -1))
parallel.append( self._scaleItem(i, -1))
parallel.append(self._positionItem(0,-1))
self.seq = Sequence(parallel, Func(self._adjustRight))
self.seq.start()
def current(self):
# list is being kept the way that the middle argument in the list is always current
return self.positions[self.visible/2]
def hide(self):
for item in self.positions:
item.hide()
def show(self):
for item in self.positions:
item.show()
if __name__ == "__main__":
base = ShowBase()
# change directory to your own
ps = PreviewStrip(".")
base.acceptOnce("arrow_left",ps.rotateLeft)
base.acceptOnce("arrow_right",ps.rotateRight)
base.run()