3

I'm creating a simple drawing application and in it, i'd like to add an undo button. What I have tried so far is this:

class DrawScreen(Screen):
    r = NumericProperty(0)
    g = NumericProperty(0)
    b = NumericProperty(0)
    brush_width = NumericProperty(2)

    def on_touch_down(self, touch):
        self.slider = self.ids.slider
        if self.slider.collide_point(touch.x, touch.y):
            self.brush_width = self.slider.value
        else:
            self.undo = [touch.x, touch.y]
            with self.canvas.before:
                Color(self.r, self.g, self.b)
                touch.ud["line"] = Line(points=self.undo, width=self.brush_width)
        return super(DrawScreen, self).on_touch_down(touch)

    def on_touch_move(self, touch):
        if self.slider.collide_point(touch.x, touch.y):
            self.brush_width = self.slider.value
        else:
            try:
                self.undo += [touch.x, touch.y]
                touch.ud["line"].points = self.undo

            except:
                pass
        return super(DrawScreen, self).on_touch_move(touch)

    def color(self, r, g, b):
        self.r = r
        self.g = g
        self.b = b

    def undo_draw(self):
        self.undo = []

This undo button clears the list but it doesn't affect the canvas in any way and doesn't remove any lines at all. What would be the appropriate way to go about this?

Baxorr
  • 298
  • 6
  • 20

2 Answers2

9

Try putting them in an InstructionGroup then add to canvas, then simply remove the item from the canvas, with canvas.remove(item).

You might want to save the items if you want redo.
Try this example. I had to replace on_touch_down, it kept creating Instructions when I move my cursor with mouse pad, filling canvas with children:

from kivy.app import App
from kivy.lang import Builder
from kivy.graphics import Line, Color, InstructionGroup
from kivy.uix.widget import Widget


class MyWidget(Widget):

    undolist = []
    objects = []
    drawing = False

    def on_touch_up(self, touch):
        self.drawing = False

    def on_touch_move(self, touch):
        if self.drawing:
            self.points.append(touch.pos)
            self.obj.children[-1].points = self.points
        else:
            self.drawing = True
            self.points = [touch.pos]
            self.obj = InstructionGroup()
            self.obj.add(Color(1,0,0))
            self.obj.add(Line())
            self.objects.append(self.obj)
            self.canvas.add(self.obj)


    def undo(self):
        item = self.objects.pop(-1)
        self.undolist.append(item)
        self.canvas.remove(item)

    def redo(self):
        item = self.undolist.pop(-1)
        self.objects.append(item)
        self.canvas.add(item)


KV = """

BoxLayout:
    MyWidget:
        id: widget
    Button:
        text: "undo"
        on_release:
            widget.undo()
    Button:
        text: "redo"
        on_release:
            widget.redo()


"""


class MyApp(App):

    def build(self):
        root = Builder.load_string(KV)
        return root

MyApp().run()
el3ien
  • 5,362
  • 1
  • 17
  • 33
0

You may still wonder why your code doesn't work. The reason is that the keyword argument points=self.undo will copy a reference to the list that self.undo represents. Then, when your code executes self.undo = [], you are creating a new, empty list, and telling self.undo to point at it. But the original list, which self.undo pointed to just before this, still exists, and points still references that list.
If instead of self.undo = [], you did del self.undo[:], you would probably accomplish what you want.

user42541
  • 947
  • 6
  • 6