Search code examples
pythonkivykivy-language

Kivy: scroll to zoom


Is there a way to zoom into an image on a desktop kivy app (e.g. zoom with a mouse scroll-wheel)? It appears to be discussed here: https://github.com/kivy/kivy/issues/3563 but I do not see if there was a work around given.

I began with a static image in my kivy app. I want to add the ability to zoom / pan into the image. I do not want the actual size of the image frame to change, just add zoom/pan functionality, like you might expect from interacting with google maps for example.

Possible Programming Directions

From what I've read, I should be using Scatter(?), and I see that I can manually set the Scatter scale to size up/down the image.

My initial thoughts are that I will have to add a separate widget with a scrollview to contain the scatter layout and that will keep the image frame a consistent size. Then I will need to add an event that dynamically changes the scale value.

The problems

  • I cannot find the appropriate event to use to change the scale value. the on_motion event seemed promising. My app can update values with a slider, but when I try a similar approach using on_motion, I get AttributeError: motion errors
  • I am struggling to create the widget. Most documentation seems to use .add_widget(...) in the python file. Is it possible to do this from the kv file? I imagine this process is similar to screens and the screen manager, but I am struggling to find an approach that works.

  • Is there a more straight forward way to do this?

Is there a way I can use on_motion type event in my kv file to adjust this value using the mouse scroll-wheel?

I give a watered down example of the structure of my kivy app - along with what I tried to do to add Scatter. I think I will need to put it into it's own widget to keep the image the same size?

Toy Example

import kivy
from kivy.lang import Builder
from kivy.core.window import Window


kivy.require('1.1.0')

from kivy.app import App

presentation = Builder.load_file("scatter.kv")
class TestApp(App):
    def build(self):
        Window.clearcolor = (1, 1, 1, 1)
        return presentation

    # def foo():
    #    print("You've reached foo")    

if __name__ == '__main__':
    TestApp().run()

and

#:kivy 1.10.0
GridLayout:
    cols: 2

    Scatter:
        scale: 5
        # on_motion: root.foo()
        Image :
            source: 'foo.png'
            allow_stretch: True
            keep_ratio: True

    Button:
        text: 'Hello World'

Produces: screenshot

Kind of Related:


Solution

  • To achieve my goal, I used a combination of the information in kivy python3 detect mousewheel, as pointed out ikolim, and the code given here: https://github.com/kivy/kivy/wiki/Draggable-Scalable-Button

    To keep my answer brief, here's the minimalistic object that extends Scatter object.

    class ResizableDraggablePicture(Scatter):
        def on_touch_down(self, touch):
            # Override Scatter's `on_touch_down` behavior for mouse scroll
            if touch.is_mouse_scrolling:
                if touch.button == 'scrolldown':
                    if self.scale < 10:
                        self.scale = self.scale * 1.1
                elif touch.button == 'scrollup':
                    if self.scale > 1:
                        self.scale = self.scale * 0.8
            # If some other kind of "touch": Fall back on Scatter's behavior
            else:
                super(ResizableDraggablePicture, self).on_touch_down(touch)
    

    The layout is slightly different and I changed the text on the button, but the functionality of my code can be seen in the following gif:

    demo of zooming and panning

    For anybody wanting to see my entire toy project to adapt for their own purposes, the entire code is on my github: https://github.com/melissadale/Learning-Kivy/tree/master/ZoomPanning

    UPDATE my code has been edited to be far more correct from an object-orientated approach, and so I could not reject the edits with a clear conscience. However, when I was first starting with kivy, I would have found this code confusing. If you want to just see the simple version that you can apply directly to verify the relevant, this is my original code:

        if touch.is_mouse_scrolling:
        if touch.button == 'scrolldown':
            print('down')
            ## zoom in
            if self.scale < 10:
                self.scale = self.scale * 1.1
    
        elif touch.button == 'scrollup':
            ## zoom out
            print('up')
            if self.scale > 1:
                self.scale = self.scale * 0.8