Welcome!
This is the community forum for my apps Pythonista and Editorial.
For individual support questions, you can also send an email. If you have a very short question or just want to say hello — I'm @olemoritz on Twitter.
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.
-
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.
-
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.
-
@Webmaster4o Right...I have a Mac so I know exactly what you mean ;). Just trying to point in the right direction
-
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. -
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
-
@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 -
All the examples I can find online rely HEAVILY on OpenGL. :|
-
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. -
Looking forward to seeing more!
-
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)
-
Started animation. Somehow the first and last images end up stacked during animation, so that needs to be fixed.
-
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)
-
@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.
-
Yeah. It still needs work in the following areas:
- It only works with exactly 9 images, it should work with as many as you need
- You can see the last image sliding behind to the first
- It only supports the screen size of my iPad mini
But, I'm going to school now
-
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. -
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.