Kivy: экранные изображения, перетаскиваемые нажатием - PullRequest
0 голосов
/ 08 февраля 2019

Я совершенно новичок в Kivy и пытаюсь найти лучший способ даже структурировать то, что я хочу сделать.Мне бы хотелось, чтобы на экране была нарисованная фигура (например, треугольник, состоящий из 3 кругов и линия, нарисованная для их соединения), где я могу щелкнуть по кругу и перетащить его в другую позицию, перерисовываялиния с новой позицией круга.Желательно, чтобы при перетаскивании я хотел бы, чтобы круг «прилипал» к вводу курсора / сенсорного ввода.

Буду ли я создавать каждый из этих пунктов в качестве виджетов?Я знаю, что графические инструменты, которые предоставляет Kivy, могут рисовать нужные мне фигуры, но я не совсем уверен, как я буду взаимодействовать с ними снова после их отрисовки.Кроме того, я не уверен, как бы я выполнял часть «перетаскивания» при перетаскивании курсором, поскольку кажется, что Киви просто будет многократно рисовать окружность рядом с курсором, что приведет к повторному наложению фигуры.

1 Ответ

0 голосов
/ 09 февраля 2019

Для этого вы можете черпать вдохновение из примера Безье, поскольку он выполняет такие манипуляции с точками, которые направляют линию, и позволяет перетаскивать их, так же, как ваша линия.

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

я также сделал здесь более сложный пример https://gist.github.com/tshirtman/78669a514f390bf246627b190e2eba1a, который позволяет создавать несколько строк.

По сути, идея, если у вас есть несколько точек взаимодействия в виджете, состоит в том, чтобы сохранитьотслеживать положение этих точек в свойстве и использовать это свойство для рисования холста, чтобы инструкции автоматически обновлялись при изменении свойства, а также чтобы использовать свойство в методе on_touch_down для проверки расстоянияприкосновение к ним, чтобы решить, с какой (если есть) точкой взаимодействовать, как только это будет решено, вам просто нужно каким-то образом связать это прикосновение с этой точкой, чтобы дальнейшие взаимодействия (on_touch_move и on_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()
...