Kivy: экранные визуальные эффекты щелчка и перетаскивания

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

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


person David Smith    schedule 08.02.2019    source источник
comment
Это довольно сложная задача для первого проекта, если вы новичок в kivy. Что касается логики, вы, вероятно, захотите отслеживать положение, в котором щелкнули мышью, а затем, если оно находится в пределах границ от всех нарисованных линий или чего-то еще, очистите холст, на котором вы рисуете, и переместите линии +/ - deltaX и deltaY, которые перетаскивает ваша мышь. Некоторые ссылки: kivy.org/doc/stable/api-kivy.input .motionevent.html (событие перетаскивания мышью) kivy.org/doc/stable /gettingstarted/drawing.html (рисование на холсте)   -  person Erik    schedule 08.02.2019
comment
Частично меня беспокоит то, что если я очистил холст, я также очистил бы все, чего не касался. Вы предлагаете мне многократно перерисовывать все на моем холсте, перетаскивая точку? Другая часть моей проблемы заключается в том, что я просто не уверен в архитектуре, какой должна быть точка. Есть ли какой-то класс, представляющий компонент виджета, от которого я должен наследоваться? Спасибо за ответ, несмотря ни на что!   -  person David Smith    schedule 08.02.2019


Ответы (1)


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

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()
person Tshirtman    schedule 09.02.2019