Struggling with positioning, bounds, collide, dynamic behavior

411 views
Skip to first unread message

pixelal...@gmail.com

unread,
Oct 30, 2015, 10:43:03 PM10/30/15
to Kivy users support
I just spent a week trying to get wrapped around Kivy, writing test programs to try out the concepts. I keep hitting a wall and the documentation (and Alex Taylor's fine videos) are just not getting me over the wall. Rather than me posting my failures, would someone be willing to post an example (primarily Kv, not from Python) that does the following? It is a simple exercise that would demonstrate several things that are not well explained. I'd like to see this "done the right way"
  • Two widgets, each containing at a label. 
  • One widget is movable (a scatter) and one fixed in place.
  • Each widget has a color so you can see its extent (AABB)
  • You can drag the movable widget, but only by touching/clicking within it AABB.
  • When the movable widget is intersecting the stationary widget, it lights up (changes color).

Samuel Loury

unread,
Oct 31, 2015, 10:33:14 AM10/31/15
to pixelal...@gmail.com, Kivy users support
pixelal...@gmail.com writes:

[...]

> would someone be willing to post an example (primarily Kv, not from
> Python) that does the following?

[...]

> It is a simple exercise that would demonstrate several things that are
> not well explained. I'd like to see this "done the right way"

Challenge accepted! It will probably not be the good example you are
looking for but I decided it was a good exercise for me to learn kivy
also :-). I did not manage to avoid Python though because I needed a if
statement that the kivy language does not permit... I decided to post my
solution to see whether it could trigger interesting discussions.

> - Two widgets, each containing at a label.

I'd simply put the two labels in a float layout.

> - One widget is movable (a scatter) and one fixed in place.

I'd not use scatter since it becomes a real mess to handle collision
with it. Instead, I'd just handle the touch_down, touch_up and
touch_move events to update the position.

> - Each widget has a color so you can see its extent (AABB)

Simply add a coloured rectangle in the canvas of the Label. I am pretty
sure that this one is the correct way of doing.

> - You can drag the movable widget, but only by touching/clicking within
> it AABB.

Like said before, it could be done in the touch events.

> - When the movable widget is intersecting the stationary widget, it
> lights up (changes color).

Handling the collision in the touch_move event is pretty simple,
provided you avoided to use scatter stuff.

The kivy file would look like:

--8<---------------cut here---------------start------------->8---
#:kivy 1.0

FloatLayout:
A:
canvas.before:
Color:
rgb: 0,1,0
Rectangle:
pos: self.pos
size: self.size
id: a
text: "a"
size_hint: .2, .2
B:
canvas.before:
Color:
rgb: 1., self.green, 0
Rectangle:
pos: self.pos
size: self.size
text: "b"
size_hint: .2, .2
pos: root.width / 2., 0
on_touch_down:
self.handle_touch_down(args[1])
on_touch_up:
self.handle_touch_up(args[1])
on_touch_move:
self.handle_touch_move(args[1], a)
--8<---------------cut here---------------end--------------->8---

And the python file:

#!/usr/bin/env python
# -*- coding:utf-8 -*-

--8<---------------cut here---------------start------------->8---
import kivy
From kivy.app import App
From kivy.uix.scatterlayout import ScatterLayout
From kivy.uix.label import Label
From kivy.graphics import Color, Rectangle
From kivy.properties import NumericProperty, BooleanProperty

class A(Label):
pass

class B(Label):
green = NumericProperty(0)
touching = BooleanProperty(False)

def handle_touch_down(self, event):
if self.collide_point(*event.pos):
self.touching = True

def handle_touch_up(self, event):
self.touching = False

def handle_touch_move(self, event, other):
if self.touching == True and self.collide_point(*event.pos):
self.pos[0] = event.pos[0] - self.width / 2.
self.pos[1] = event.pos[1] - self.height / 2.
self.handle_collision(other)

def handle_collision(self, a):
if self.collide_widget(a):
self.green = 1.
else:
self.green = 0.

class TestApp(App):
pass

if __name__ == "__main__":
TestApp().run()
--8<---------------cut here---------------end--------------->8---

Hope that helps,
--
Konubinix
GPG Key : 7439106A
Fingerprint: 5993 BE7A DA65 E2D9 06CE 5C36 75D2 3CED 7439 106A
signature.asc

pixelal...@gmail.com

unread,
Oct 31, 2015, 12:48:27 PM10/31/15
to Kivy users support
Thank Samuel. The Scatter has been at the root of a lot of my confusion (as you can see from another question I posted today, trying to get an understanding of scatter). I believe I've cleaned up your approach in the example below. I'd also like to hear if this is the kivy way to tackle the problem. 

  • Got rid of the Kv wiring from events to handler functions in Python. I figured, if we're going to do it in Python, just grab touch events there.
  • Which means you can't pass in the id of the other widget from an on_touch in Kv. That's okay... to me, separation of concerns says one widget should not know about another. Instead, I expose the stationary widget with a property. In fact, to drive the point on separation of concerns, I added a second stationary widget and exposed a list of them. 
  • In the Python, I keep the class for the movable widget blind about any other widgets. It just asks the world around it if there are any collisions. 
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.label import Label 
from kivy.properties import NumericProperty, ListProperty

from kivy.base import runTouchApp
from kivy.lang import Builder

Builder.load_string('''

<Stationary>:
    id: id_stationary
    canvas.before:
        Color:
            rgb: 0.3,0.3,0.3
        Rectangle:
            pos: self.pos
            size: self.size
    size_hint: .2, .2

<Moveable>:
    canvas.before:
        Color:
            rgb: 0, self.green, 1
        Rectangle:
            pos: self.pos
            size: self.size
    size_hint: .1, .1
    pos: root.width / 2., 0

<World>:
    stationaryWidgets: [s1,s2]

    Stationary:
        id: s1
        text: 'not movable'
        center: root.width/3, root.center_y          
    Stationary:
        id: s2
        text: 'also not movable'
        center: root.width*2/3, root.center_y   
    Moveable:
        id: m1
        text: 'move me'

''')

class Stationary(Label):
    pass

class Moveable(Label):
    green = NumericProperty(0)

    def on_touch_down(self, touch):
        self.touchedMe = self.collide_point(touch.x,touch.y)

    def on_touch_up(self, touch):
        self.touchedMe = False     

    def on_touch_move(self, touch):
        if self.touchedMe:
            self.pos[0] = touch.pos[0] - self.width / 2
            self.pos[1] = touch.pos[1] - self.height / 2
            if self.parent.check_for_collisions(self):
                self.green = 1
            else:
                self.green = 0
 
class World(FloatLayout):
    stationaryWidgets = ListProperty(None)  

    def check_for_collisions(self, movingWidget):
        for otherWidget in self.stationaryWidgets:
            if movingWidget.collide_widget(otherWidget):
                return True
        return False

runTouchApp(World())


Samuel Loury

unread,
Nov 1, 2015, 8:41:42 AM11/1/15
to pixelal...@gmail.com, Kivy users support
pixelal...@gmail.com writes:

[...]

>> I believe I've cleaned up your approach in the example below.

I like the way you did it.

>> I'd also like to hear if this is *the kivy way* to tackle the
>> problem.

So would I :-).


[...]

> class World(FloatLayout):
> stationaryWidgets = ListProperty(None)
>
> def check_for_collisions(self, movingWidget):
> for otherWidget in self.stationaryWidgets:
> if movingWidget.collide_widget(otherWidget):
> return True
> return False

[...]

Referring to
https://groups.google.com/forum/#!msg/kivy-users/a4NeZzmei1w/hQrQRnkBNfIJ,
I can tell that this part is probably the correct Kivish way of doing
things.
signature.asc
Reply all
Reply to author
Forward
0 new messages