Girar un objeto en un evento táctil en Kivy

Estoy trabajando en hacer un círculo que gire como una gran esfera. Actualmente, tengo una flecha en la parte superior para mostrar en qué dirección está orientada la esfera. Me gustaría que su comportamiento fuera algo así como un teléfono rotativo antiguo, de modo que mientras el dedo / cursor está abajo, puede girarlo, pero (lentamente) retrocederá hasta la parte superior después de soltarlo.

Así es como se ve mi objeto:

introduzca la descripción de la imagen aquí

Y aquí está mi código:

#!/usr/bin/kivy import kivy kivy.require('1.7.2') import math from random import random from kivy.app import App from kivy.uix.widget import Widget from kivy.uix.gridlayout import GridLayout from kivy.uix.anchorlayout import AnchorLayout from kivy.uix.relativelayout import RelativeLayout from kivy.graphics import Color, Ellipse, Rectangle class MinimalApp(App): title = 'My App' def build(self): root = RootLayout() return(root) class RootLayout(AnchorLayout): pass class Circley(RelativeLayout): angle = 0 def on_touch_down(self, touch): ud = touch.ud ud['group'] = g = str(touch.uid) return True def on_touch_move(self, touch): ud = touch.ud # print(touch.x, 0) # print(self.center) # print(0, touch.y) # print(touch.x - self.center[0], touch.y - self.center[1]) y = (touch.y - self.center[1]) x = (touch.x - self.center[0]) calc = math.degrees(math.atan2(y,x)) angle = calc if calc > 0 else (360 + calc) print(angle) def on_touch_up(self, touch): touch.ungrab(self) ud = touch.ud return True if __name__ == '__main__': MinimalApp().run() 

Y el kv:

     #:kivy 1.7.2 #:import kivy kivy : anchor_x: 'center' # I think this /is/ centered anchor_y: 'center' canvas.before: Color: rgba: 0.4, 0.4, 0.4, 1 Rectangle: pos: self.pos size: self.size Circley: anchor_x: 'center' # this is /not/ centered. anchor_y: 'center' canvas.before: PushMatrix Color: rgba: 0.94, 0.94, 0.94, 1 Rotate: angle: self.angle axis: 0, 0, 1 origin: self.center Ellipse: source: 'arrow.png' size: min(self.size), min(self.size) pos: 0.5*self.size[0] - 0.5*min(self.size), 0.5*self.size[1] - 0.5*min(self.size) Label: text: unicode(self.size) # this is /not/ appearing color: 1,0,0,1 canvas.after: PopMatrix 

    Parte de eso se toma prestada de la demo de kivy touchtracer, y de esta pregunta SO .

    Puede ver que tengo un cálculo que está imprimiendo correctamente el ángulo entre el origen del círculo y el evento táctil (no estoy seguro de cómo responderá esto a varios dedos, no he pensado hasta qué punto), pero no estoy seguro de cómo integrar esto en un evento de retroalimentación “giratorio” en la interfaz.

    Puede enlazar el ángulo del canvas a NumericProperty para cambiarlo desde dentro de su código. Todo lo que necesitas hacer es calcular esos angularjs correctamente. Después de jugar un poco con él creé el siguiente código:

     from kivy.app import App from kivy.uix.widget import Widget from kivy.lang import Builder from kivy.animation import Animation from kivy.properties import NumericProperty import math kv = ''' : canvas: Rotate: angle: root.angle origin: self.center Color: rgb: 1, 0, 0 Ellipse: size: min(self.size), min(self.size) pos: 0.5*self.size[0] - 0.5*min(self.size), 0.5*self.size[1] - 0.5*min(self.size) Color: rgb: 0, 0, 0 Ellipse: size: 50, 50 pos: 0.5*root.size[0]-25, 0.9*root.size[1]-25 ''' Builder.load_string(kv) class Dial(Widget): angle = NumericProperty(0) def on_touch_down(self, touch): y = (touch.y - self.center[1]) x = (touch.x - self.center[0]) calc = math.degrees(math.atan2(y, x)) self.prev_angle = calc if calc > 0 else 360+calc self.tmp = self.angle def on_touch_move(self, touch): y = (touch.y - self.center[1]) x = (touch.x - self.center[0]) calc = math.degrees(math.atan2(y, x)) new_angle = calc if calc > 0 else 360+calc self.angle = self.tmp + (new_angle-self.prev_angle)%360 def on_touch_up(self, touch): Animation(angle=0).start(self) class DialApp(App): def build(self): return Dial() if __name__ == "__main__": DialApp().run() 

    Estoy calculando la diferencia entre inicial (después de presionar el mouse) y ángulo posterior en on_touch_move . Dado que el ángulo es una propiedad, también puedo modificarlo utilizando kivy.animation para hacer que el dial gire nuevamente después de soltar el botón del mouse.

    EDITAR

    evento on_touch_down para círculo hijo:

     from kivy.app import App from kivy.uix.widget import Widget from kivy.uix.floatlayout import FloatLayout from kivy.lang import Builder from kivy.animation import Animation from kivy.properties import NumericProperty import math kv = ''' : circle_id: circle_id size: root.size pos: 0, 0 canvas: Rotate: angle: self.angle origin: self.center Color: rgb: 1, 0, 0 Ellipse: size: min(self.size), min(self.size) pos: 0.5*self.size[0] - 0.5*min(self.size), 0.5*self.size[1] - 0.5*min(self.size) Circle: id: circle_id size_hint: 0, 0 size: 50, 50 pos: 0.5*root.size[0]-25, 0.9*root.size[1]-25 canvas: Color: rgb: 0, 1, 0 Ellipse: size: 50, 50 pos: self.pos ''' Builder.load_string(kv) class Circle(Widget): def on_touch_down(self, touch): if self.collide_point(*touch.pos): print "small circle clicked" class Dial(Widget): angle = NumericProperty(0) def on_touch_down(self, touch): if not self.circle_id.collide_point(*touch.pos): print "big circle clicked" y = (touch.y - self.center[1]) x = (touch.x - self.center[0]) calc = math.degrees(math.atan2(y, x)) self.prev_angle = calc if calc > 0 else 360+calc self.tmp = self.angle return super(Dial, self).on_touch_down(touch) # dispatch touch event futher def on_touch_move(self, touch): y = (touch.y - self.center[1]) x = (touch.x - self.center[0]) calc = math.degrees(math.atan2(y, x)) new_angle = calc if calc > 0 else 360+calc self.angle = self.tmp + (new_angle-self.prev_angle)%360 def on_touch_up(self, touch): Animation(angle=0).start(self) class DialApp(App): def build(self): return Dial() if __name__ == "__main__": DialApp().run() 

    Puede utilizar GearTick desde el jardín, que es un control deslizante giratorio. No es exactamente lo que necesita, pero puede adaptarse a sus necesidades. “De manera predeterminada, permite la rotación en sentido contrario a las agujas del reloj, probablemente lo necesite para ir en el sentido de las agujas del reloj” (Actualización: el widget ahora tiene una propiedad de orientation que se puede configurar en “sentido horario” o “en sentido contrario a las agujas del reloj”).

    Necesitaría manejar el resorte hacia atrás y detenerse en la “parada de dedo”.

    El ejemplo de los extremos administra la recuperación mediante la animación, sin embargo, aún necesita administrar / implementar la funcionalidad de detención de dedos.

    https://github.com/kivy-garden/garden.geartick

    Uso::

    Pitón::

     from kivy.garden.geartick import GearTick parent.add_widget(GearTick(range=(0, 100))) 

    kv ::

     BoxLayout: orientation: 'vertical' GearTick: id: gear_tick zoom_factor: 1.1 # uncomment the following to use non default values #max: 100 #background_image: 'background.png' #overlay_image: 'gear.png' #orientation: 'anti-clockwise' on_release: Animation.stop_all(self) Animation(value=0).start(self) Label: size_hint: 1, None height: '22dp' color: 0, 1, 0, 1 text: ('value: {}').format(gear_tick.value) 

    introduzca la descripción de la imagen aquí

    Instalar::

     pip install kivy-garden garden install geartick 

    Ejemplo de trabajo que puede copiar pegar:

     from kivy.lang import Builder from kivy.app import runTouchApp from kivy.garden.geartick import GearTick runTouchApp(Builder.load_string(''' #:import Animation kivy.animation.Animation GridLayout: cols: 2 canvas.before: Color: rgba: 1, 1, 1, 1 Rectangle: size: self.size pos: self.pos BoxLayout: orientation: 'vertical' GearTick: id: gear_tick zoom_factor: 1.1 # uncomment the following to use non default values #max: 100 #background_image: 'background.png' #overlay_image: 'gear.png' #orientation: 'anti-clockwise' on_release: Animation.stop_all(self) Animation(value=0).start(self) Label: size_hint: 1, None height: '22dp' color: 0, 1, 0, 1 text: ('value: {}').format(gear_tick.value) '''))