Forum Archive

Cover Flow view

Webmaster4o

The Mac finder has a view called cover flow, which looks like this:


Icons/file previews are displayed with the selected one in front and the rest behind on either side.

I'd like to try to emulate this with a custom view class, where square buttons are displayed, each with an image on it. Ideally, the user would be able to swipe in the cover flow view and it would animate as it does on my Mac, then they can tap one to perform an action.

Is this feasible in UI, or is it beyond the scope of UI and I should try to do it in scene? It will ultimately be in a UI, so for scene I would have to use a SceneView. I'm kind of leaning towards using scene because of touch handling, but I wanted to see what other people thought.

techteej

I would imagine there would be a way for a scroll view to work horizontally, that would probably be ideal - I'm not sure if there is though. Haven't used it yet.

Webmaster4o

This would work, but the effect I'm going for, not everything moves at the same pace. Items flow at different speeds based on their position. Here's a video I found showing the animation.

techteej

@Webmaster4o Right...I have a Mac so I know exactly what you mean ;). Just trying to point in the right direction

JonB

implemented as a slider, i think this could be done. You will probably want both a position and a scale component, and will need to brint to front the view moving across the center. It probably wont be able to be that smooth, or have the "physics" , though if you delve into objective c, then maybe.

as a view where you swipe on the views, it gets tricky because without objc you have no control over which view gets the touch events.

if you just want to draw covers, and not have them "interactive", i.e just have images but not buttons, etc, then this could be done within a draw method.

Webmaster4o

Thanks @JonB. Right now I'm trying to write a function for the x positions of the imageviews based on the length of the stack and their position in the stack

Phuket2

@Webmaster4o, I think the I VirtualView Class am working on , could be helpful, even though its slanted for vertical display, but could be easily modified for horizontal display!
worth a look.
https://github.com/Phuket2/VirtualView.git

Webmaster4o

All the examples I can find online rely HEAVILY on OpenGL. :|

Webmaster4o

People have tried to do this in the past with pygame/pyopenGL, I'm gonna look at some of those examples. I got a UI set up:


The UI is the width of my iPad mini screen, and half the height. I'll setup ananimate function that scales and moves each ImageView to the position of the next. Then replace that view with the original, but all the images are shifted, so there is no jump, but the layout of the imageviews is the same.

techteej

Looking forward to seeing more!

Webmaster4o

Translated pyui to code, images in place. Have a commitment now, so I possibly won't be back for 4 hours.

# coding: utf-8
import ui
from PIL import Image
from io import BytesIO

def pil_to_ui(img):
    b = BytesIO()
    img.save(b, "PNG")
    data = b.getvalue()
    b.close()
    return ui.Image.from_data(data)

class CoverFlow(ui.View):
    def __init__(self, images):
        self.images = [pil_to_ui(image) for image in images]
        self.frame = (0, 0, 1024, 384)
        self.background_color='#0F0F0F'


        #Create subviews
        iv0 = ui.ImageView(frame=(-100, 142, 100, 100), name='iv0')
        iv0.image = self.images[0]
        self.add_subview(iv0)

        iv1 = ui.ImageView(frame=(-10, 122, 140, 140), name='iv1')
        iv1.image = self.images[1]
        self.add_subview(iv1)

        iv2 = ui.ImageView(frame=(90, 102, 180, 180), name='iv2')
        iv2.image = self.images[2]
        self.add_subview(iv2)

        iv3 = ui.ImageView(frame=(180, 72, 240, 240), name='iv3')
        iv3.image = self.images[3]
        self.add_subview(iv3)

        iv4 = ui.ImageView(frame=(327, 7, 370, 370), name='iv4')
        iv4.image = self.images[4]
        self.add_subview(iv4)

        iv5 = ui.ImageView(frame=(604, 72, 240, 240), name='iv5')
        iv5.image = self.images[5]
        self.add_subview(iv5)
        iv5.send_to_back()

        iv6 = ui.ImageView(frame=(754, 102, 180, 180), name='iv6')
        iv6.image = self.images[6]
        self.add_subview(iv6)
        iv6.send_to_back()

        iv7 = ui.ImageView(frame=(894, 122, 140, 140), name='iv7')
        iv7.image = self.images[7]
        self.add_subview(iv7)
        iv7.send_to_back()

        iv8 = ui.ImageView(frame=(1024, 142, 100, 100), name='iv8')
        iv8.image = self.images[8]
        self.add_subview(iv8)
        iv8.send_to_back()

    def did_load(self):
        self.images = [s.image for s in self.subviews]

if __name__ == '__main__':

    names = ['test:Boat', 'test:Lenna', 'test:Pythonista', 'test:Peppers', 'test:Sailboat', 'test:Bridge', 'test:Mandrill', 'emj:Baby_Chick_3', 'emj:Tulip']
    images = [Image.open(n) for n in names]

    view = ui.View(background_color='#0f0f0f')
    view.add_subview(CoverFlow(images))
    view.present(hide_title_bar=True)   
Webmaster4o

Started animation. Somehow the first and last images end up stacked during animation, so that needs to be fixed.

Webmaster4o

Stopping for tonight. First day of high school tomorrow, that means summer's over and I have a lot less time. Here's what I have now. Fixed most bugs. I still want to tweak the animation, but here it is.

# coding: utf-8
import ui
from PIL import Image
from io import BytesIO


def pil_to_ui(img):
    b = BytesIO()
    img.save(b, "PNG")
    data = b.getvalue()
    b.close()
    return ui.Image.from_data(data)

class CoverFlow(ui.View):
    def __init__(self, images):
        self.images = [pil_to_ui(image) for image in images]
        self.frame = (0, 0, 1024, 384)
        self.background_color='#0F0F0F'


        #Create subviews
        iv0 = ui.ImageView(frame=(-100, 142, 100, 100), name='iv0')
        iv0.image = self.images[0]
        iv0.prev_frame = iv0.frame
        self.add_subview(iv0)

        iv1 = ui.ImageView(frame=(-10, 122, 140, 140), name='iv1')
        iv1.image = self.images[1]
        iv1.prev_frame = iv1.frame
        self.add_subview(iv1)

        iv2 = ui.ImageView(frame=(90, 102, 180, 180), name='iv2')
        iv2.image = self.images[2]
        iv2.prev_frame = iv2.frame
        self.add_subview(iv2)

        iv3 = ui.ImageView(frame=(180, 72, 240, 240), name='iv3')
        iv3.image = self.images[3]
        iv3.prev_frame = iv3.frame
        self.add_subview(iv3)

        iv4 = ui.ImageView(frame=(327, 7, 370, 370), name='iv4')
        iv4.image = self.images[4]
        iv4.prev_frame = iv4.frame
        self.add_subview(iv4)

        iv5 = ui.ImageView(frame=(604, 72, 240, 240), name='iv5')
        iv5.image = self.images[5]
        self.add_subview(iv5)
        iv5.prev_frame = iv5.frame
        iv5.send_to_back()

        iv6 = ui.ImageView(frame=(754, 102, 180, 180), name='iv6')
        iv6.image = self.images[6]
        self.add_subview(iv6)
        iv6.prev_frame = iv6.frame
        iv6.send_to_back()

        iv7 = ui.ImageView(frame=(894, 122, 140, 140), name='iv7')
        iv7.image = self.images[7]
        self.add_subview(iv7)
        iv7.prev_frame = iv7.frame
        iv7.send_to_back()

        iv8 = ui.ImageView(frame=(1024, 142, 100, 100), name='iv8')
        iv8.image = self.images[8]
        self.add_subview(iv8)
        iv8.prev_frame = iv8.frame
        iv8.send_to_back()


    def right_anim(self):
        for sub in self.subviews:
            index = self.subviews.index(sub)
            next = self.subviews[index+1] if index<8 else self.subviews[0]
            sub.frame = next.prev_frame
        for sub in self.subviews:
            sub.prev_frame = sub.frame

        #Reorder layers based on the fact that biggest images are in front
        #Bring smallest images to front first
        for s in sorted(self.subviews, key=lambda x: x.frame[3]): s.bring_to_front()

    def left_anim(self):
        for sub in self.subviews:
            index = self.subviews.index(sub)
            next = self.subviews[index-1] if index>0 else self.subviews[8]
            sub.frame = next.prev_frame
        for sub in self.subviews:
            sub.prev_frame = sub.frame

        #Reorder layers based on the fact that biggest images are in front
        #Bring smallest images to front first
        for s in sorted(self.subviews, key=lambda x: x.frame[3]): s.bring_to_front()

    def touch_began(self, touch):
        pass

    def touch_moved(self, touch):
        if touch.location[0]<touch.prev_location[0]:
            self.touch_direction = 0
        elif touch.location[0]>touch.prev_location[0]:
            self.touch_direction = 1

    def touch_ended(self, touch):
        if self.touch_direction:
            ui.animate(self.right_anim, duration=1.0)
        else:
            ui.animate(self.left_anim, duration=1.0)

if __name__ == '__main__':

    names = ['test:Boat', 'test:Lenna', 'test:Pythonista', 'test:Peppers', 'test:Sailboat', 'test:Bridge', 'test:Mandrill', 'emj:Baby_Chick_3', 'emj:Tulip']
    images = [Image.open(n) for n in names]

    view = ui.View(background_color='#0f0f0f')
    view.add_subview(CoverFlow(images))
    view.present(hide_title_bar=True)
Phuket2

@Webmaster4o , very nice. Only thing I would say is the animate time. I am on a iPad Air 2 and it did not feel responsive enough. I changed your values to from 1.0 to 0.5, a lot nicer for animation experience. Maybe a nice addition would be to look at the hardware to set the animation delay. Gives me the idea to do the same also for some of the stuff I am trying to write.

Webmaster4o

Yeah. It still needs work in the following areas:
1. It only works with exactly 9 images, it should work with as many as you need
2. You can see the last image sliding behind to the first
3. It only supports the screen size of my iPad mini

But, I'm going to school now

Phuket2

Yeah, I can see needs a few things done. But still good. I will try to get into a loop handling 1 to x images. Try that is :)
Also use layout so that its adjusts to screen size and orientation.

ccc

A simplification of pil_to_ui() and a simplification of CoverFlow.__init__() via the addition of make_image_view(). One of the things that still needs doing is to calculate frames based on screen size/orientation.

def pil_to_ui(img):
    with BytesIO() as b:
        img.save(b, "PNG")
        return ui.Image.from_data(b.getvalue())

def make_image_view(image, frame, index):
    image_view = ui.ImageView(frame=frame, name='iv{}'.format(index))
    image_view.image = image
    image_view.prev_frame = frame
    return image_view

class CoverFlow(ui.View):
    def __init__(self, images):
        self.images = (pil_to_ui(image) for image in images)
        self.frame = (0, 0, 1024, 384)
        self.background_color='#0F0F0F'

        frames = ( (-100, 142, 100, 100), (-10, 122, 140, 140),
                   (90, 102, 180, 180), (180, 72, 240, 240),
                   (327, 7, 370, 370), (604, 72, 240, 240),
                   (754, 102, 180, 180), (894, 122, 140, 140),
                   (1024, 142, 100, 100) )

        #Create subviews
        for i, image in enumerate(self.images):
            self.add_subview(make_image_view(image, frames[i], i))
ccc

Combined left_anim() and right_anim() into a single anim() method. Tightened up touch_moved() and touch_ended() to make the ui more responsive. My sense is that Apple's CoverFlow starts to move in touch_moved() whereas this version does not start to move until touch_ended().

    def anim(self):
        len_subviews = len(self.subviews)
        for i, sub in enumerate(self.subviews):
            next = self.subviews[(i + (1 if self.touch_direction else -1)) % len_subviews]
            sub.frame = next.prev_frame
        for sub in self.subviews:
            sub.prev_frame = sub.frame

        #Reorder layers based on the fact that biggest images are in front
        #Bring smallest images to front first
        for s in sorted(self.subviews, key=lambda x: x.frame[3]): s.bring_to_front()

    def touch_began(self, touch):
        pass

    def touch_moved(self, touch):
        self.touch_direction = int(touch.location.x > touch.prev_location.x)

    def touch_ended(self, touch):
        ui.animate(self.anim, duration=0.25)
Webmaster4o

@ccc Like your use of enumerate, it reminded me that enumerate exists. My code can be so much cleaner with it.

Phuket2

@ccc, looks very nice. I was also rewriting , but not as good as you. But I was trying to find a a nice mathematical way to derive the frames. Also, you pumped into my head about enumerate, is so handy.

Phuket2

@ccc , the first thing that comes to mind for me when I see coverflow, is like a sine wave. Does it make sense to you? But this sort of math is very difficult for me. I left school, too early. I am looking at some nice animations about sin & cos on Wikipedia.
https://en.m.wikipedia.org/wiki/Sine
My idea is that the answer for frames is in this formula. Just asking. Maybe you know.

Webmaster4o

I was going to use a quadratic. Going into NYS common core geometry this year, so I've just completed algebra 1. I can easily pull a quadratic function for x values using my graphing calculator, but I don't know how to do this with screen size as a variable. I just positioned the boxes by ear in a pyui, then took their frames and put them into the code.

A quadratic is preferable over a sine, because a sine goes up and down multiple times. Quadratics look like this:

Here x would be ImageView index, and y would be the distance from the previous box. Distances are greatest in the middle, boxes are closer together towards the edges.

What I'll probably end up doing is defining x as a percentage, which represents x value over total screen width. Then on my iPad, the value is x percent of 1024.

Webmaster4o

Ok, attempt 1 didn't go so well. Here, Y is the distance between two frames, x just increments. X1 is the distance between the centers of frames one and two, X2 is the difference bet

Webmaster4o

Ok, attempt one at mathematically describing my frame positions with a quadratic equation didn't go well. This table shows the distances between the centers of frames:

Y1 is the distance between the centers of the first and second frames, Y2 the distance between the centers of the second and third frames, etc.

Pulling a quadratic from this data resulted in a horribly messy function with a bad fit. The function I got is
And it's a bad fit. The function plotted over the points:

I'm going back to percentages. I was thinking having it be a smooth rounded function might make the whole thing look more even, but it's beyond my ability

Phuket2

Look this is way out of my league. But I can still experiment. I did something with sin using rads With Pi * 1(from what I can see this should be 180 deg) The points go from low to 1.0 to low again. The points each side of 1.0 are the same. First in ascending order up to 1 then decending on the other side of 1.0
Look, I don't know what I am doing. Just trying for fun. What I have done so far has flaws in it, I am am still unsure how to control it :) will keep reading though. Just hard without a math background.

One example output is. If I get the algo working correctly will post it. Even though I may not be the correct approach. I have a feeling if I understood it better, could prove interesting anyway. cos, could possibly play a role also.

Phuket2

Maybe this is useless code. But could spark some ideas

# coding: utf-8
import ui
import math

def pic_ratios(num_items):
     pi = math.pi
     divisor = (100. / num_items) / 100.

     #print divisor , '*'
     for i in range(1,num_items ):
        s = math.sin(pi * (i * divisor))
        cs = math.cos(s)
        print s

if __name__ == '__main__':
    f = (-100,142,100, 100)
    # even param , you will get 1.0
    pic_ratios(24)
JonB

keep in mind you basically need 3 equations: one for size, one for x and one for y. assuming we like the original proportions,

first, note that the width varies as:

w=370*exp(-2.4*(abs((i-N/2.0-0.5)/N)**(1.38/N**0.25))

where i is the index of the cover. i have written this in a way that scales with number of covers N, still leaving the same min width, but also trying to keep a prominent center frame.

next, the relationship between center and width from the original is roughly

w=370.*exp(-abs(xc-512)/470.)

so, solving for the center,

xc=512+copysign(470.*log(w/370.),i-N/2)

where exp, log, and copysign are all imports from math. This only works properly with an odd number, so increase N by 1 if N is even.

finally,

 yc= (192-w)

All of the large numbers (512,470,370,192) porbably should be expressed as a fraction of screen width (divide by 1024 and multiply by screen width). Also, everything above assumes future division. Also, note this solves for xc and yc, i.e center positions. Use the center property, or else subtract 0.5*w to get x and y.

Phuket2

@JonB , that's great. Only if I was 100 years younger and could go back to school and actually learn something.
Would be fantastic to be able to express things like this. I suspect it didn't take much effort on your part. Would love to be able to do the same. But thanks for sharing

Webmaster4o

@JonB, Thanks a ton! I'll try it. Probably will end up making the numbers a function of screen size. Is the 192 half screen height?

Phuket2

@Webmaster4o , look fwd to seeing the revised code. Maybe think to make some of the variables constants so can change the look and feel of the coverflow without changing the methods directly.i think a great candidate for the pythonistia tools repo once you nail it.

JonB

192 is actually related to the 512, and the amount that things move vertically. I think you could sart with having it scale only by width, and see how that does.

Webmaster4o

Ok, I'm ready to start implementation now.

ccc

Please make it a Github repo so that we can add Pythonista-Tools link to it.

Webmaster4o

Sure, @ccc I'll do that when I'm finished with this bit.

Webmaster4o

@JonB None of the numbers besides 512 make sense to me as logical fractions of 1024. Where did they come from?

Additionally, the sentence

divide by 1024 and multiply by screen width

is confusing to me, because 1024 IS the screen width... x/1024*1024 = x

Also, if it's not a lot of work, could you try to explain the math behind your equations? I don't like to borrow code I don't understand, it's a mini-policy of mine. (No, I don't understand all the code behind all the modules I import, I mean I don't like to insert others' code into my own without understanding it)

JonB

Yes you correct re screen width... my point is if you want this to scale for a different screen width, like say 768 in portrait, you would divide by 1024, and multiply by screen width. in retrospect, you could just scale the final numbers.

when i plotted width vs index, and noticed it looked a lot like an exponential decay about the center.. That gives an exp(-abs(i-N/2)) form. The leading factor is the width of the central frame. To get the width at the end, i played with the factor and exponent inside the exponential. Then, i experimented with scaling for more frames, keeping the center width and end width the same. I thought you would still want a big difference betwenn center and next frames, so i played with the exponent.

Likewise, i plotted xc vs w, and saw a similar exponential factor, and again some trial and error. Y vs w appeared very linear, so i just fit a line.

Webmaster4o

Spent today working on image2ASCII, will get to this tomorrow.

Webmaster4o

I get the feeling I'm using your code wrong. What am I doing that isn't as intended? some of your sentences don't make a lot of sense to me:

the relationship between center and width from the original

I've implemented your code as follows:

#Make frames for all views
N = 9
frames = []
for i in range(N):
    #Thanks to @JonB for this code.
    w=370*exp(-2.4*(abs((i-N/2.0-0.5)/N)**(1.38/N**0.25)))
    xc=512+copysign(470.*log(w/370.),i-N/2)
    yc= (192-w)
    frames.append((xc+.5*w, yc+.5*w, w, w))

Looks pretty good for the first half, the rest are messed up. I feel stupid, but if you could please correct my mistakes that'd be wonderful. (>ლ)

JonB

Sorry, I think I made an off-by-one error in the width equation. The i-N/2.0-0.5 should be i-N/2.0+0.5.

Also, I think you want frames.append((xc-0.5*w, yc-0.5*w,w,w)) instead of +0.5 in both places

Webmaster4o

Thanks! It actually needed to be (xc-0.5*w, yc+0.5*w, w, w) in order for it to work correctly.

JonB

by the way, one of the nice things re: the equation is that you can use touch_moved to create continuous motion along the curve. Basically you would associate each image with a index variable, touch_moved generates a fractional index offset added to each one, say, the x part of the motion divided by the center diff, and wrapping around negative values. still use a zero time animatin to sync up the motion. Then touch_ended would round to the nearest index, and do a slower animation to snap everybody in place. You could also use the "velocity" of the last swipe to over scan.

Webmaster4o

New code, supports different screen sizes, and auto-resizes with device rotation. Also supports arbitrary numbers of images. It will display your images whether you have 1 image or 100. Now running it will also pick random images from the included images to present.

# coding: utf-8
import ui
from PIL import Image
from io import BytesIO
from time import sleep
from math import exp, log, copysign, ceil

BGCOLOR = '#0F0F0F'

def pil_to_ui(img):
    b = BytesIO()
    img.save(b, "PNG")
    data = b.getvalue()
    b.close()
    return ui.Image.from_data(data)


class CoverFlow(ui.View):
    def __init__(self, images):
        self.images = [pil_to_ui(image) for image in images]
        if len(self.images) < 9:
            self.images *= int(ceil(9./len(self.images)))
        self.frame = (0, 0, 1024, 768)
        self.oldframe = (0, 0, 1024)
        self.background_color = BGCOLOR
        #Make frames for all views
        N = 9
        frames = []
        #Create frames
        for i in xrange(N):
            #Thanks to @JonB for this code.
            w=370/1024.*self.width*exp(-2.4*(abs((i-N/2.0+0.5)/N)**(1.38/N**0.25)))
            xc=512/1024.*self.width+copysign(470.0/1024*self.width*log(w/(370.0/1024*self.width)),i-N/2)
            yc= (192/1024.*self.width-w)
            frames.append((xc-0.5*w, yc+0.5*w, w, w))

        #Create subviews for the minimum of 9 images
        for index, frame in enumerate(frames):
            iv = ui.ImageView(frame=frame)
            iv.image = self.images[index]
            iv.prev_frame = iv.frame
            self.add_subview(iv)
            if index > 4:
                iv.send_to_back()

        #Handle any additional images that are provided
        for i in self.images[9:]:
            iv9 = ui.ImageView(frame=frames[-1])
            iv9.image = i
            iv9.prev_frame = iv9.frame
            self.add_subview(iv9)
            iv9.send_to_back()

    def layout(self):
        if self.frame != self.oldframe:
            N = 9
            frames = []
            #Create frames
            for i in range(N):
                #Thanks to @JonB for this code.
                w=370/1024.*self.width*exp(-2.4*(abs((i-N/2.0+0.5)/N)**(1.38/N**0.25)))
                xc=512/1024.*self.width+copysign(470.0/1024*self.width*log(w/(370.0/1024*self.width)),i-N/2)
                yc= (192/1024.*self.width-w)
                frames.append((xc-0.5*w, yc+0.5*w, w, w))

            for sv in self.subviews:
                self.remove_subview(sv)
            #Create subviews for the minimum of 9 images
            for index, frame in enumerate(frames):
                iv = ui.ImageView(frame=frame)
                iv.image = self.images[index]
                iv.prev_frame = iv.frame
                self.add_subview(iv)
                if index > 4:
                    iv.send_to_back()

            #Handle any additional images that are provided
            for i in self.images[9:]:
                iv9 = ui.ImageView(frame=frames[-1])
                iv9.image = i
                iv9.prev_frame = iv9.frame
                self.add_subview(iv9)
                iv9.send_to_back()
            self.oldframe = self.frame

    def anim(self):
        #Change frames
        len_subviews = len(self.subviews)
        for i, sub in enumerate(self.subviews):
            next = self.subviews[(i + (1 if self.touch_direction else -1)) % len_subviews]
            sub.frame = next.prev_frame
        for sub in self.subviews:
            sub.prev_frame = sub.frame

        #Resort self.images
        if self.touch_direction:
            self.images = [self.images[-1]]+self.images
            self.images.pop(-1)
        else:
            self.images = self.images + [self.images[0]]
            self.images.pop(0)
        #Reorder layers based on the fact that biggest images are in front
        #Bring smallest images to front first
        for s in sorted(self.subviews, key=lambda x: x.frame[3]): s.bring_to_front()

    def touch_began(self, touch):
        self.touch_direction = None

    def touch_moved(self, touch):
        if touch.location != touch.prev_location:
            self.touch_direction = int(touch.location.x > touch.prev_location.x)
        else:
            pass

    def touch_ended(self, touch):
        if self.touch_direction != None:
            ui.animate(self.anim, duration=0.25)

if __name__ == '__main__':
    #Pick random images from default textures
    import os, random
    app_path = os.path.abspath(os.path.join(os.__file__, '../../../..'))
    os.chdir(app_path + '/Textures')
    imagenames = os.listdir(os.curdir)
    validnames = []
    for x in imagenames:
        if not (x.startswith('ionicons') or x.startswith('Typicons')):
            validnames.append(x)

    images = [Image.open(random.choice(validnames)) for x in xrange(15)]
    view = ui.View(background_color=BGCOLOR)
    cf = CoverFlow(images)
    view.add_subview(cf)
    cf.present(hide_title_bar=1)

layout literally removes all the subviews and recreates them, because I couldn't figure out how to adjust the frames easily, but the layout method takes under a hundredth of a second to perform so I don't think it's a problem.

Webmaster4o

Realized I can cut out most of __init__, gist is here. I think I'm done with it, now. I'd like to maybe get rid of the objects animating sliding behind, I'll edit the gist for that.

techteej

@Webmaster4o I would recommend making it a repo instead- as you can accept pull requests and make it easier to collaborate and build on.

Webmaster4o

Sure. I've also just realized that the only thing (I think) preventing it from being 1.5-compatible is the assigning of arbitrary attributes to views (sv.prev_frame). I should be able to eliminate this easily, then it'll work on non-beta devices.

Webmaster4o

Having trouble setting alpha to 0 for the frame passing behind. I tried to set the frames alpha before and after animating, but this doesn't work because code after ui.animate is executed before the animation has finished. My code was this, but it's sloppy and doesn't work:
def animate(self):
len_subviews = len(self.subviews)

    for i, sub in enumerate(self.subviews):
        next = self.subviews[(i + (1 if self.touch_direction else -1)) % len_subviews]
        if next.prev_frame[0] < sub.prev_frame[0]:
            resetter = sub
            sub.alpha = 0

    def anim():
        #Change frames
        for i, sub in enumerate(self.subviews):
            next = self.subviews[(i + (1 if self.touch_direction else -1)) % len_subviews]
            sub.frame = next.prev_frame

    #Animate
    ui.animate(anim, 0.25)
    resetter.alpha = 1
    #Re-sort self.images
    if self.touch_direction:
        self.images = [self.images[-1]]+self.images
        self.images.pop(-1)
    else:
        self.images = self.images + [self.images[0]]
        self.images.pop(0)

    #Reorder layers based on the fact that biggest images are in front,
    #Bring smallest images to front first
    for s in sorted(self.subviews, key=lambda x: x.frame[3]): s.bring_to_front()

    #Reassign prev_frame
    for sub in self.subviews:
        sub.prev_frame = sub.frame

```

Phuket2

@Webmaster4o, I don't have much time to look at the forum at the moment. Many friends in town now. But your CoverFlow class is a great improvement.

Webmaster4o

Gist updated with backwards compatibility to 1.5, successful on my iPhone running ios7 and 1.5. I probably will end up making it a repo soon.

TutorialDoctor

Nice work! Coming along nicely. I am wondering if this could browse IOS photos? What would you use it for?

Webmaster4o

Browsing iOS photos is a good idea.

Phuket2

@TutorialDoctor, you ask what it could be used for. I think is just nice ui control, same as a slider. I think a nice step fwd would be to be able to provide a ui.view. Then not just images anymore. Also the cover controller doesn't care, just providing a series of frames with all the correct math and animations and touch events.