Cover Flow view

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.

A simplification of
pil_to_ui()
and a simplification ofCoverFlow.__init__()
via the addition ofmake_image_view()
. One of the things that still needs doing is to calculateframes
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))

Combined
left_anim()
andright_anim()
into a singleanim()
method. Tightened uptouch_moved()
andtouch_ended()
to make the ui more responsive. My sense is that Apple's CoverFlow starts to move intouch_moved()
whereas this version does not start to move untiltouch_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)

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

@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.

@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.

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.

This post is deleted!last edited by

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

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.

Maybe this is useless code. But could spark some ideas
# coding: utf8 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)

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((iN/2.00.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(xc512)/470.)
so, solving for the center,
xc=512+copysign(470.*log(w/370.),iN/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= (192w)
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.

@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

@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?

@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.

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.

Ok, I'm ready to start implementation now.

Please make it a Github repo so that we can add PythonistaTools link to it.

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

@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 minipolicy 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)

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(iN/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.