Kivy:单击并拖动屏幕视觉效果

时间:2019-02-08 17:01:58

标签: python graphics widget kivy motionevent

我对Kivy完全陌生,我正在尝试找出最好的方法来整理我的意思。我希望能够在屏幕上绘制一些图形(例如,由3个圆组成的三角形和一条连接所有这些图形的线),其中我可以单击一个圆并将其拖动到另一个位置,然后重新绘制圆的新位置的线。最好是,在拖动时,我希望圆圈在光标/触摸输入上“粘住”。

我要制作这些小部件吗?我知道Kivy提供的图形工具可以绘制所需的形状,但是我不确定在绘制之后如何再次与它们进行交互。另外,我不确定如何用光标进行拖动的“粘”部分,因为看来Kivy只会在光标旁边重复绘制圆,这将导致它反复覆盖图形。 / p>

1 个答案:

答案 0 :(得分:0)

您可能能够从Bezier示例中获得启发,因为它对引导线并允许拖动它们的点进行了类似的操作。

https://github.com/kivy/kivy/blob/master/examples/canvas/bezier.py

我在这里也做了一个更复杂的例子 https://gist.github.com/tshirtman/78669a514f390bf246627b190e2eba1a 允许创建多行。

基本上,如果在一个小部件中有多个交互点,则该想法是在属性中跟踪这些点的位置,并使用此属性绘制画布,因此说明在以下情况时会自动更新属性更改,还可以使用on_touch_down方法中的属性来检查与他们的触摸距离,从而确定要与之交互的点(如果有),一旦确定,您只需要以某种方式将该接触链接到该点,这样就可以进行进一步的交互(on_touch_moveon_touch_up)(touch.ud对此很有用),并抓住接触,这样您就不会错过任何更新(父级小部件始终可以确定这种接触实际上不再传播了。)

要点中的代码可供参考(并且因为SO不太喜欢指向外部资源的答案)。

from kivy.app import App
from kivy.lang import Builder
from kivy.uix.widget import Widget
from kivy.properties import ListProperty, NumericProperty
from kivy.metrics import dp

KV = '''
#:import chain itertools.chain
FloatLayout:
    Label:
        size_hint_y: None
        text_size: self.width, None
        height: self.texture_size[1]
        pos_hint: {'top': 1}
        color: 0, 0, 0, 1
        padding: 10, 10
        text:
            '\\n'.join((
            'click to create line',
            'click near a point to drag it',
            'click near a line to create a new point in it',
            'double click a point to delete it'
            ))
        canvas.before:
            Color:
                rgba: 1, 1, 1, .8
            Rectangle:
                pos: self.pos
                size: self.size
    BezierCanvas:
<BezierLine>:
    _points: list(chain(*self.points))
    canvas:
        Color:
            rgba: 1, 1, 1, .2
        SmoothLine:
            points: self._points or []
        Color:
            rgba: 1, 1, 1, 1
        Line:
            bezier: self._points or []
            width: 2
        Color:
            rgba: 1, 1, 1, .5
        Point:
            points: self._points or []
            pointsize: 5
'''


def dist(a, b):
    return ((a[0] - b[0]) ** 2 + (a[1] - b[1]) ** 2) ** .5


class BezierLine(Widget):
    points = ListProperty()
    select_dist = NumericProperty(10)
    delete_dist = NumericProperty(5)

    def on_touch_down(self, touch):
        if super(BezierLine, self).on_touch_down(touch):
            return True

        max_dist = dp(self.select_dist)

        l = len(self.points)

        for i, p in enumerate(self.points):
            if dist(touch.pos, p) < max_dist:
                touch.ud['selected'] = i
                touch.grab(self)
                return True

        for i, p in enumerate(self.points[:-1]):
            if (
                dist(touch.pos, p)
                + dist(touch.pos, self.points[i + 1])
                - dist(p, self.points[i + 1])
                < max_dist
            ):
                self.points = (
                    self.points[:i + 1]
                    + [list(touch.pos)]
                    + self.points[i + 1:]
                )
                touch.ud['selected'] = i + 1
                touch.grab(self)
                return True

    def on_touch_move(self, touch):
        if touch.grab_current is not self:
            return super(BezierLine, self).on_touch_move(touch)
        point = touch.ud['selected']

        self.points[point] = touch.pos

    def on_touch_up(self, touch):
        if touch.grab_current is not self:
            return super(BezierLine, self).on_touch_up(touch)
        touch.ungrab(self)
        i = touch.ud['selected']
        if touch.is_double_tap:
            if len(self.points) < 3:
                self.parent.remove_widget(self)
            else:
                self.points = (
                    self.points[:i] + self.points[i + 1:]
                )


class BezierCanvas(Widget):
    def on_touch_down(self, touch):
        if super(BezierCanvas, self).on_touch_down(touch):
            return True

        bezierline = BezierLine()
        bezierline.points = [(touch.pos), (touch.pos)]
        touch.ud['selected'] = 1
        touch.grab(bezierline)
        self.add_widget(bezierline)
        return True


class BezierApp(App):
    def build(self):
        return Builder.load_string(KV)


if __name__ == '__main__':
    try:
        BezierApp().run()
    except:
import pudb; pudb.post_mortem()