pooriabt
pooriabt

Reputation: 40

Spin a circular arranged of widgets around the center point in kivy

I have some Textinputs that arranged circular and i am looking for a way to spin the whole package of these widgets around their center like a wheel with dragging mouse on part of them. i try to combine some codes that i found in some pages but have no idea how to make this works properly.

from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.floatlayout import FloatLayout
from kivy.lang import Builder
from kivy.uix.textinput import TextInput
from kivy.properties import NumericProperty
from kivy.core.window import Window
import math

pi = math.pi

kv = '''
<-RotatableTI>:
    size_hint: None, None
    canvas.before:
        PushMatrix
        Rotate:
            angle: root.angle
            axis: 0,0,1
            origin: self.center

        Color:
            rgba: self.background_color
        BorderImage:
            border: self.border
            pos: self.pos
            size: self.size
            source: self.background_active if self.focus else (self.background_disabled_normal if self.disabled else self.background_normal)
        Color:
            rgba:
                (self.cursor_color
                if self.focus and not self._cursor_blink
                else (0, 0, 0, 0))
        Rectangle:
            pos: self._cursor_visual_pos
            size: root.cursor_width, -self._cursor_visual_height
        Color:
            rgba: self.disabled_foreground_color if self.disabled else (self.hint_text_color if not self.text else self.foreground_color)
    canvas.after:
        PopMatrix
        
<Dial>:
    canvas:
        Rotate:
            angle: self.angle
            origin: self.center

'''
Builder.load_string(kv)

class Circle(Widget):
    def on_touch_down(self, touch):
        if self.collide_point(*touch.pos):
            pass

class RotatableTI(TextInput):
    angle = NumericProperty(0)

class Scat(FloatLayout,Widget):
    Window.size = (600, 600)
    def __init__(self, **kwargs):
        super(Scat, self).__init__(**kwargs)
        p_x = 0
        p_y = 0
        b = 0
        r = 150
        div = 20
        pt = []
        for i in range(div):
            angle = 360.0 / (div - 1) * i
            p_x = Window.size[0] / 2 + math.cos(2 * pi / (div - 1) * i) * r
            p_y = Window.size[1] / 2 + math.sin(2 * pi / (div - 1) * i) * r
            pt.append(p_x)
            pt.append(p_y)
            if i > 0:
                self.add_widget(RotatableTI(text="hi" + str(i), size=(50, 30), pos=(p_x, p_y), angle=angle))
                
    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

        return super(Scat, 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

class DialApp(App):
    def build(self):
        return Scat()

if __name__ == "__main__":
    DialApp().run()

I want to array polar some textinputs in kivy but only find a way to rotate textinput with mouse dragging in scatter class. all of my try reached to a textinput that is not rotated and only when I type in it the text shows rotated(with not rotated textinput box!), and not looks good, so im looking for a better way, I could reach close to solution with buttons, but they are not exactly I want and just their position are polar, not their orirntation. is there any way for textInputs to array like described?

at the end im looking for a way have 3 or 4 of this whirling packages of textinputs and I am wondering mouse dragging would recognize which one of them selected perfectly or selected sometimes wrongly? because when i want spin one item in kivy with mouse, if select the outside of object`s border still can select and rotate it.

EDITED one step forward with response of dear John Anderson; but still works not good. i mention the problems below(under of the code) in the second picture.

from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.floatlayout import FloatLayout
from kivy.lang import Builder
from kivy.uix.textinput import TextInput
from kivy.properties import NumericProperty
from kivy.core.window import Window
import math

pi = math.pi

kv = '''

<Scat>:
    canvas.before:
        PushMatrix
        Rotate:
            angle: self.angle
            origin: self.center
    canvas.after:
        PopMatrix
<-RotatableTI>:
    size_hint: None, None
    canvas.before:
        PushMatrix
        Rotate:
            angle: root.angle
            axis: 0,0,1
            origin: self.center

        Color:
            rgba: self.background_color
        BorderImage:
            border: self.border
            pos: self.pos
            size: self.size
            source: self.background_active if self.focus else (self.background_disabled_normal if self.disabled else self.background_normal)
        Color:
            rgba:
                (self.cursor_color
                if self.focus and not self._cursor_blink
                else (0, 0, 0, 0))
        Rectangle:
            pos: self._cursor_visual_pos
            size: root.cursor_width, -self._cursor_visual_height
        Color:
            rgba: self.disabled_foreground_color if self.disabled else (self.hint_text_color if not self.text else self.foreground_color)
    canvas.after:
        PopMatrix
        
    canvas:
        Rotate:
            angle: self.angle
            origin: self.center

'''
Builder.load_string(kv)

class Circle(Widget):
    def on_touch_down(self, touch):
        if self.collide_point(*touch.pos):
            pass

class RotatableTI(TextInput):
    angle = NumericProperty(0)

class Scat(FloatLayout):
    Window.size = (600, 600)
    def __init__(self, **kwargs):
        super(Scat, self).__init__(**kwargs)
        p_x = 0
        p_y = 0
        b = 0
        r = 150
        div = 20
        pt = []
        for i in range(div):
            angle = 360.0 / (div - 1) * i
            p_x = Window.size[0] / 2 + math.cos(2 * pi / (div - 1) * i) * r
            p_y = Window.size[1] / 2 + math.sin(2 * pi / (div - 1) * i) * r
            pt.append(p_x)
            pt.append(p_y)
            if i > 0:
                self.add_widget(RotatableTI(text="hi" + str(i), size=(50, 30), pos=(p_x, p_y), angle=angle))

    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

        return super(Scat, 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

class DialApp(App):
    def build(self):
        return Scat()

if __name__ == "__main__":
    DialApp().run()

beside that ring except orbiting moves other directions(its center point is not permanent and can change it with dragging) that its not expected! [1]: https://i.sstatic.net/3jhQ8.png [2]: https://i.sstatic.net/Mrpf7.png

Upvotes: 0

Views: 113

Answers (1)

John Anderson
John Anderson

Reputation: 38837

You almost have the rotation working. Just add this to your kv:

<Scat>:
    canvas.before:
        PushMatrix
        Rotate:
            angle: self.angle
            origin: self.center
    canvas.after:
        PopMatrix

Also,

class Scat(FloatLayout,Widget):

should be just:

class Scat(FloatLayout):

because FloatLayout is a Widget.

If you intend to use several Scat Widgets, you should use collide_point() to determine which instance is being touched.

Upvotes: 1

Related Questions