Kivy: understanding widget instances in apps

2019-08-03 20:48发布

I'm still warming up to this Kivy stuff, hope you don't mind another question from me!

I'm trying to understand scheduled events for some of my widgets. For the sake of context, I want to be able to move nodes onto edges, and have the edges 'snap to' the node.

Here is my .kv file:

<GraphInterface>:
    node: graph_node
    edge: graph_edge

    GraphNode:
        id: graph_node
        center: self.parent.center

    GraphEdge:
        id: graph_edge
        center: 200,200

<GraphNode>:
    size: 50, 50
    canvas:
        Color:
        rgba: (root.r,1,1,1)
    Ellipse:
        pos: self.pos
        size: self.size


<GraphEdge>:
    size: self.size
    canvas:
        Color:
            rgba: (root.r,1,1,1)
        Line:
            width: 2.0
            close: True

I have a collision detection event between nodes and edges, and I am dynamically creating nodes and edges within my program. Here is the relevant python code:

class GraphInterface(Widget):
    node = ObjectProperty(None)
    edge = ObjectProperty(None)

    def update(self, dt):
        # detect node collision
        self.edge.snap_to_node(self.node)
        return True

class GraphEdge(Widget):
    r = NumericProperty(1.0)
    def __init__(self, **kwargs):
        super(GraphEdge, self).__init__(**kwargs)
        with self.canvas:
            self.line = Line(points=[100, 200, 200, 200], width = 2.0, close = True)

    def snap_to_node(self, node):
        if self.collide_widget(node):
            print "collision detected"
            del self.line.points[-2:]
            self.line.points+=node.center
            self.size = [math.sqrt(((self.line.points[0]-self.line.points[2])**2 + (self.line.points[1]-self.line.points[3])**2))]*2
            self.center = ((self.line.points[0]+self.line.points[2])/2,(self.line.points[1]+self.line.points[3])/2)
    pass

class GraphApp(App):

    def build(self):
        node = GraphNode()
        game = GraphInterface()

        createNodeButton = Button(text = 'CreateNode', pos=(100,0))
        createEdgeButton = Button(text = 'CreateEdge')
        game.add_widget(createNodeButton)
        game.add_widget(createEdgeButton)

        #scatter = Scatter(do_rotation=False, do_scale=False)
        #scatter.add_widget(node)

        def createNode(instance):
            newNode = GraphNode()
            game.add_widget(newNode)
            #scatter.add_widget(newNode)
            print "Node Created"

        def createEdge(instance):
            newEdge = GraphEdge()
            game.add_widget(newEdge)
            #scatter.add_widget(newEdge)
            print "Edge Created"

        createNodeButton.bind(on_press=createNode)
        createEdgeButton.bind(on_press=createEdge)

        Clock.schedule_interval(game.update, 1.0/60.0)   
        return game

Ok, so that's a lot to read (sorry)!

Basically my collision is only detected for the initial edges and nodes I create in the .kv file. The newly created edges and nodes cannot interact via the collision mechanism.

I can put up more code if people want but I think that should be all the relevant parts. Thanks!

EDIT:

New method:

def on_touch_move(self, touch):
    if touch.grab_current is self:
        self.pos=[touch.x-25,touch.y-25]
    for widget in self.parent.children:
        if isinstance(widget, GraphEdge) and widget.collide_widget(self):
            print "collision detected"
            widget.snap_to_node(self)
            return True
    return super(GraphNode, self).on_touch_move(touch)

With this code I can still break the connection by moving the nodes quickly.

EDIT2:

I perhaps gave the answer a little prematurely. I can only interact with the first edge I spawned. And I also have a weird bug where the first edge and the first node seem to have the same color properties. Though perhaps this should be posed in it's own question.

1条回答
倾城 Initia
2楼-- · 2019-08-03 21:26

The problem is that you don't react to new GraphEdges or GraphNodes being added to your GraphInterface. In update:

    self.edge.snap_to_node(self.node)

self.edge is always the kv-created GraphEdge. Same with self.node.

You should try adding this interaction to the GraphNode class itself, in your touch interactions. Try something like this in GraphNode.on_touch_move():

for widget in self.parent.children:
    if isinstance(widget, GraphEdge) and widget.collide_widget(self):
        print "collision detected"
        ...
        break

This way, each GraphNode searches through its siblings for any GraphEdge with which it may collide.

查看更多
登录 后发表回答