【问题标题】:Kivy: Click-drag Screen VisualsKivy:点击拖动屏幕视觉效果
【发布时间】:2019-02-09 20:24:02
【问题描述】:

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

我会将这些要点中的每一个都制作成小部件吗?我知道 Kivy 提供的图形工具可以绘制我需要的形状,但我不太确定在绘制它们之后如何再次与它们交互。此外,我不确定如何使用光标进行拖动的“粘贴”部分,因为 Kivy 似乎只是在光标旁边反复绘制圆圈,这会导致它反复覆盖图形。

【问题讨论】:

  • 如果你是 kivy 的新手,这对于第一个项目来说是一项相当艰巨的任务。就逻辑而言,您可能希望监视单击鼠标的位置,然后如果它在所有绘制的线条或其他东西的范围内,请清除您正在绘制的画布并移动线条 +/ - 拖动鼠标的 deltaX 和 deltaY。一些参考:kivy.org/doc/stable/api-kivy.input.motionevent.html(拖动鼠标事件)kivy.org/doc/stable/gettingstarted/drawing.html(在画布上绘图)
  • 我担心的部分原因是,如果我清除画布,我也会清除所有我没有触及的东西。您是否建议我在拖动点时反复重绘画布上的所有内容?我的问题的另一部分是我只是不确定在架构中应该是什么。是否有一些类代表我应该继承的 Widget 的组件?感谢您的回复,无论如何!

标签: python graphics widget kivy motionevent


【解决方案1】:

您可能可以从 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 对此有好处),并抓住触摸,这样你就不会错过任何更新(父小部件总是可以决定这种触摸实际上不再传播)。

来自 gist 的代码供参考(并且因为 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()

【讨论】:

    猜你喜欢
    • 2013-01-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多