I have always had in interest in making a local photo organization tool, that supports albums and tagging the way I want to do it. Maybe down the line allowing others to connect and use it too. So I started on one a few years ago, using what I knew best, Python REST back-end with Angular JS web based front end.
And it worked decently well.
However I was never happy with it’s performance past 10K images (and I am dealing with hundreds of thousands) and I tried halfway through development to switch to Angular 2. And everything blew up.
Now that I am older and
wiser less stupid, I can face the fact that local data is best served with a desktop native application. It also removes the need for trying to keep up to date with ever changing web standards and libraries, in exchange for ones that might last through an entire development process (crazy thought, I know.)
To be honest, I didn’t even think about using Kivy for a desktop application at first. I went straight to PySide, as it’s less bad licensing compared to PyQT. However, at the time, it didn’t want to play ball with a halfway modern python version, 3.5, the oldest I will create new content on, 3.7-dev is preferred or 3.6 for stable. I then looked into alternatives, such as wx and Kivy. Lets just say a quick view at both of their home pages tells you plenty about who you trust more with front end design off the bat.
Even thought Kivy seems to be aimed at more mobile development, including touch capabilities, I have found it to be extremely capable as a desktop application. It is also very appealing to me as it is no BS, MIT licensed.
Just like any good python GUI, Kivy was a pain in the arse to install. On Windows, I just ended up grabbing the pre-compiled binaries from a well-loved unofficial binaries page. On my Linux VirtualBox, ended up having to disable 3D acceleration for it to start.
However, once I was able to start coding, it became stupid simple to be able to get content on the screen fast, and manipulate them. After about four hours of coding in one night, I had this:
An image viewer app that could display any directory of images. It included a preview bar that you could select past or upcoming images from, and control with either mouse clicks or keyboard presses. The actual code for it can be found here (just be aware it isn’t ready for the lime light yet, plenty of fixes to go.)
The application is composed of six widget classes overall. Here is a simple visual diagram of what they look like, and the Kivy widget classes they are using. (The padding is for visual ease only, not part of the program or to scale).
The two biggest issues I ran into were proper widget alignment, and the fact there is no built-in ‘on_hover’ like I am used to with CSS. I wrote up a quick class to do the latter, and including it here as it should be a drop-in for other’s kivy projects.
from kivy.factory import Factory from kivy.properties import BooleanProperty, ObjectProperty from kivy.core.window import Window from kivy.uix.widget import Widget class MouseOver(Widget): def __init__(self, **kwargs): Window.bind(mouse_pos=self._mouse_move) self.hovering = BooleanProperty(False) self.poi = ObjectProperty(None) self.register_event_type('on_hover') self.register_event_type('on_exit') super(MouseOver, self).__init__(**kwargs) def _mouse_move(self, *args): if not self.get_root_window(): return is_collide = self.collide_point(*self.to_widget(*args)) if self.hovering == is_collide: return self.poi = args self.hovering = is_collide self.dispatch('on_hover' if is_collide else 'on_exit') def on_hover(self): """Mouse over""" def on_exit(self): """Mouse leaves""" Factory.register('MouseOver', MouseOver)
Then you simply have to subclass it as well as the original widget class you want your object to be, and override the
class Selector(Button, MouseOver): """ Base class for Prev and Next Image Buttons""" def on_hover(self): self.opacity = .8 def on_exit(self): self.opacity = 0
Proper widget alignment was fun to figure out, because Kivy has both hard set properties
height for objects, as well as a more lose
size_hint feature. By default, everything has a
(1, 1) , think of this as a percentage of the screen, so (1,1) == (100% height, 100% width).
Simple right? Well, now add two of those objects in base layout. Some layouts, like
GridLayout will then make each of them 50% of the screen. Others, like
FloatLayout will let each of them take up the entire layer, so one will be hidden. Even more fun, is to disable it, you manually have to set
Then on top of that, you can start mix and matching, hinting and absolutes with different elements on the screen. I chose to do that in this case, because I always wanted the preview bar of images at the bottom to be only
100px tall. But I wanted the large image to expand to the full height. But if you set the bar to
100px tall, and leave the image (1,1), with just raising it’s starting position
100px from the bottom, it now is
100px off the top of the page. So now on top of the
size_hint you need to add an
on_size method, that will reduce the height of the image object every time the window is resized, like so:
def on_size(self, obj, size): """Make sure all children sizes adjust properly""" self.image.height = self.height - 100 self.next_button.pos = (self.width - 99, self.next_button.hpos)
You can notice we also have make sure that the right hand button always looks attached to the right hand of the screen. (Which I later learned could probably be accomplished with the
RelitiveLayout, but am still unsure how that would work compared to everything else I already have with my
So the takeaway for me was, even without CSS, you’re going to have fun with alignment issues.
So I have am image viewer, great! But you want to organize images into more manageable groups. I want to create a folder / album view to be able to select at a higher level what you want to view.
After that I am sure I will want to create a database system to store tags, album info and thumbnails (for faster preview loading).