Rotating an object on a touch event in kivy
You can bind angle of canvas to NumericProperty
, to change it from inside your code. All you need to do is to compute those angles correctly. After playing a bit with it I created following code:
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 = '''
<Dial>:
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()
I'm calculating difference between initial (after pressing mouse) and later angle in on_touch_move
. Since angle is a property I can also modify it using kivy.animation
to make dial spin back after releasing mouse button.
EDIT
on_touch_down
event for child circle:
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 = '''
<Dial>:
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()
You can use GearTick from garden which is a rotating slider. It's not exactly what you need but can be adapted for your needs. "By default it allows rotation anti-clockwise you probably would need it to go clockwise"(Update: The widget now has a orientation
property that can be set to 'clockwise' or 'anti-clockwise').
You would need to manage the spring back and stopping at the "finger stop".
The example at the ends manage spring back using animation, however you still need to manage/implement the finger stop functionality.
https://github.com/kivy-garden/garden.geartick
Usage::
Python::
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)
To install::
pip install kivy-garden
garden install geartick
Working Example that you can copy paste::
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)
'''))