简体   繁体   中英

Kivy: Rotate clickable image

I have created a clickable image, and I can rotate it, but I can't get the origin of the rotation right. The following code places two images, one of which is clickable, onto a form. I expect them to be on top of one another, with the clickable one rotated. The clickable one is rotated, but the rotation origin is something other than the image center which results in it being moved, even though I specify the center as what I expect it to be. Interestingly, it is clickable, but the click region is where it is supposed be: on the unclickable image. That is, the red image turns green when you click the blue image!

I modified this code from some kv language I found online, and the rotation worked for that. But I need to set up the button programmatically, because I will eventually have a form with many, each rotated and moved differently.

I feel like the problem is in mismatched coordinate units, but I haven't found much online and in the docs. Any help is appreciated. The images are just 32x32 icons, which you can replace with whatever you have handy.

from kivy.app import App
from kivy.lang import Builder
from kivy.uix.image import Image
from kivy.uix.floatlayout import FloatLayout
from kivy.graphics.context_instructions import PopMatrix, PushMatrix
from kivy.graphics import Rotate
from kivy.uix.behaviors import ButtonBehavior

h = .05
w = h * 1.6

class IconButton(ButtonBehavior, Image):

    def __init__(self, angle=0, **kwargs):
        super(IconButton, self).__init__(**kwargs)

        self.canvas.before.add(PushMatrix())
        self.canvas.before.add(Rotate(angle = angle, origin = [self.get_center_x(), self.get_center_y()]))

        self.canvas.after.add(PopMatrix())

    def on_press(event):
        event.source = event.img_dn
        print '{} pressed'.format(event.id)

    def on_release(event):
        event.source = event.img_up
        print '{} released'.format(event.id)


class TestApp(App):

    def build(self):
        layout = FloatLayout()
        btn = IconButton(angle=-20, id='b1', size_hint=(h,w), pos_hint = {'center_x': .25, 'center_y': .9})
        btn.img_up = 'loudspeaker_red_32.png'
        btn.img_dn = 'loudspeaker_green_32.png'
        btn.source = btn.img_up

        layout.add_widget(btn)

        im = Image(id='i1', size_hint=(h,w), pos_hint = {'center_x': .25, 'center_y': .9})
        im.source = 'loudspeaker_blue_32.png'

        layout.add_widget(im)

        return layout

class MainApp(App):
    def build(self):
        layout = Builder.load_string(src)
        return layout


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

If you try to draw a line around your widget (a very useful debugging technique), you'll discover that when IconButton.__init__() is called pos atribute is still (0, 0) and code thinks that your button is in bottom left corner.

class IconButton(ButtonBehavior, Image):

    def __init__(self, angle=0, **kwargs):
        super(IconButton, self).__init__(**kwargs)

        self.canvas.before.add(PushMatrix())

        self.canvas.add(Color(rgba=(1,0,0,.5)))
        self.canvas.add(Line(rectangle=(self.x, self.y, self.width, self.height)))

        self.canvas.after.add(PopMatrix())

    def on_press(event):
        event.source = event.img_dn
        print '{} pressed'.format(event.id)

    def on_release(event):
        event.source = event.img_up
        print '{} released'.format(event.id)

This is happening beacuse pos attribute is actualized when your widget is placed inside a layout. When you're creating a Widget it doesn't know where it will be placed. That's why you need to actualize rotating origin inside on_pos method that called later, when all properties are set correctly:

class IconButton(ButtonBehavior, Image):

    def __init__(self, angle=0, **kwargs):
        super(IconButton, self).__init__(**kwargs)

        self.rotate = Rotate(angle = angle)

        self.canvas.before.add(PushMatrix())
        self.canvas.before.add(self.rotate)
        self.canvas.after.add(PopMatrix())

        self.bind(pos=self.update_canvas)
        self.bind(size=self.update_canvas)

    def update_canvas(self, *args):
        self.rotate.origin = self.center         

    def on_press(event):
        event.source = event.img_dn
        print '{} pressed'.format(event.id)

    def on_release(event):
        event.source = event.img_up
        print '{} released'.format(event.id)

This inconvenience is an exact reason why we prefer to use .kv language in order to describe layouts.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM