4

When I register a polygon, or a compound shape with only a single component, I'm able to create a turtle cursor using that shape, add a drag event handler, and drag it around the screen.

But when I register a compound shape with a second component, I can no longer drag it:

from turtle import Turtle, Screen, Shape

def simple_polygon(turtle):

    turtle.begin_poly()
    turtle.circle(50)
    turtle.end_poly()
    screen.register_shape("simple_polygon", turtle.get_poly())

    turtle.reset()

def compound_single(turtle):

    shape = Shape("compound")

    turtle.begin_poly()
    turtle.circle(50)
    turtle.end_poly()
    shape.addcomponent(turtle.get_poly(), "blue", "blue")  # component #1
    screen.register_shape("compound_single", shape)

    turtle.reset()

def compound_double(turtle):

    shape = Shape("compound")

    turtle.begin_poly()
    turtle.circle(50)
    turtle.end_poly()
    shape.addcomponent(turtle.get_poly(), "green", "green")  # component #1

    turtle.penup()
    turtle.left(90)
    turtle.forward(25)
    turtle.right(90)
    turtle.pendown()

    turtle.begin_poly()
    turtle.circle(25)
    turtle.end_poly()
    shape.addcomponent(turtle.get_poly(), "yellow", "yellow")  # component #2
    screen.register_shape("compound_double", shape)

    turtle.reset()

def drag_handler(turtle, x, y):
    turtle.ondrag(None)  # disable ondrag event inside drag_handler
    turtle.goto(x, y)
    turtle.ondrag(lambda x, y, turtle=turtle: drag_handler(turtle, x, y))

screen = Screen()

magic_marker = Turtle()
simple_polygon(magic_marker)
compound_single(magic_marker)
compound_double(magic_marker)
magic_marker.hideturtle()

red = Turtle(shape="simple_polygon")
red.color("red")
red.penup()
red.goto(150, 150)
red.ondrag(lambda x, y: drag_handler(red, x, y))

blue = Turtle(shape="compound_single")
blue.penup()
blue.goto(-150, -150)
blue.ondrag(lambda x, y: drag_handler(blue, x, y))

mostly_green = Turtle(shape="compound_double")
mostly_green.penup()
mostly_green.goto(150, -150)
mostly_green.ondrag(lambda x, y: drag_handler(mostly_green, x, y))

screen.mainloop()

You'll find that only two of the three shapes generated can be dragged. Comment out this line:

shape.addcomponent(turtle.get_poly(), "yellow", "yellow")  # component #2

and the third circle will be all green and become draggable.

I can't find any mention in the turtle documentation about compound shapes with mutiple components not being valid cursors as far as dragging. It doesn't make any difference whether the second component is completely within, overlapping or outside the first.

Looking at the turtle code, I see no distinction, leading me to believe that this problem is in the tkinter underpinning and not documented properly in turtle. Is this problem Unix or OSX specific?

Am I missing something? Why can't I drag cursors built out of multiple components?

enter image description here

cdlane
  • 40,441
  • 5
  • 32
  • 81

2 Answers2

1

I too hit this problem recently and was grateful to find your question, cdlane, to confirm I was not just imagining things—that there might actually be a bug in Python’s turtle module.

I looked up the issue in the Python bug tracker, and thankfully someone had already identified the problem and someone else had submitted a patch: https://bugs.python.org/issue16428

Although the patch has not yet been incorporated into Python as of this writing, I tried modifying my installed version of Python to incorporate the patch, and it worked.

If you’d prefer not to make changes to your installed Python, you could monkey patch the turtle object to incorporate the patch, directly in your code, as follows:

[Please note that to improve performance I slightly modified your code, adding tracer(0) and update(); however, the patch works without those additions.]

from turtle import Turtle, Screen, Shape, tracer, update

#### Monkey patch Turtle object to allow compound shapes to be dragged. ####
####### Based on patch by Ingrid: https://bugs.python.org/issue16428 #######

def onclick(self, fun, btn=1, add=None):
    if (self.turtle._type == 'compound'):
        for i in self.turtle._item:
            self.screen._onclick(i, fun, btn, add)
    else:
        self.screen._onclick(self.turtle._item, fun, btn, add)
    self._update()

Turtle.onclick = onclick

def onrelease(self, fun, btn=1, add=None):
    if (self.turtle._type == 'compound'):
        for i in self.turtle._item:
            self.screen._onrelease(i, fun, btn, add)
    else:
        self.screen._onrelease(self.turtle._item, fun, btn, add)
    self._update()

Turtle.onrelease = onrelease

def ondrag(self, fun, btn=1, add=None):
    if (self.turtle._type == 'compound'):
        for i in self.turtle._item:
            self.screen._ondrag(i, fun, btn, add)
    else:
        self.screen._ondrag(self.turtle._item, fun, btn, add)

Turtle.ondrag = ondrag

############################ End Monkey patch. #############################

def simple_polygon(turtle):

    turtle.begin_poly()
    turtle.circle(50)
    turtle.end_poly()
    screen.register_shape("simple_polygon", turtle.get_poly())

    turtle.reset()

def compound_single(turtle):

    shape = Shape("compound")

    turtle.begin_poly()
    turtle.circle(50)
    turtle.end_poly()
    shape.addcomponent(turtle.get_poly(), "blue", "blue")  # component #1
    screen.register_shape("compound_single", shape)

    turtle.reset()

def compound_double(turtle):

    shape = Shape("compound")

    turtle.begin_poly()
    turtle.circle(50)
    turtle.end_poly()
    shape.addcomponent(turtle.get_poly(), "green", "green")  # component #1

    turtle.penup()
    turtle.left(90)
    turtle.forward(25)
    turtle.right(90)
    turtle.pendown()

    turtle.begin_poly()
    turtle.circle(25)
    turtle.end_poly()
    shape.addcomponent(turtle.get_poly(), "yellow", "yellow")  # component #2
    screen.register_shape("compound_double", shape)

    turtle.reset()

def drag_handler(turtle, x, y):
    turtle.ondrag(None)  # disable ondrag event inside drag_handler
    turtle.goto(x, y)
    update()
    turtle.ondrag(lambda x, y, turtle=turtle: drag_handler(turtle, x, y))

screen = Screen()
tracer(0)

magic_marker = Turtle()
simple_polygon(magic_marker)
compound_single(magic_marker)
compound_double(magic_marker)
magic_marker.hideturtle()

red = Turtle(shape="simple_polygon")
red.color("red")
red.penup()
red.goto(150, 150)
red.ondrag(lambda x, y: drag_handler(red, x, y))

blue = Turtle(shape="compound_single")
blue.penup()
blue.goto(-150, -150)
blue.ondrag(lambda x, y: drag_handler(blue, x, y))

mostly_green = Turtle(shape="compound_double")
mostly_green.penup()
mostly_green.goto(150, -150)
mostly_green.ondrag(lambda x, y: drag_handler(mostly_green, x, y))
update()
screen.mainloop()
joelg
  • 46
  • 3
  • Thank you for the detailed information and patch. I wonder if it will make into release Python by the 10th anniversary of that original bug report! – cdlane Nov 08 '21 at 17:44
  • You're welcome, @cdlane. With all the changes Python has seen over the last decade, I'm simply grateful the turtle module still exists. The core devs have a lot on their plate, so I'm not expecting progress on this bug anytime soon. – joelg Nov 10 '21 at 02:15
-1

I think what's going on is when you drag one object, the whole canvas moves and it looks like it's moving.

The same thing happens in Canvas Tkinter. When you program something to happen when you click an object, the whole canvas is waiting for a click. SO that means if two objects have a onclick event, the whole thing won't work.

Hope this helps!!

  • 2
    I can't say your answer helps nor that I even understand it. Did you run the example code I provided (with and without the edit) to see the problem in action? – cdlane Mar 22 '19 at 21:03
  • I did, and i think I see what is going on I will edit my answer@cdlane –  Mar 23 '19 at 01:04
  • Wait @cdlane did you define the green shape as one WHOLE shape or just two shapes? It might matter. –  Mar 23 '19 at 02:39
  • Both the blue and green shapes are *compound* shapes. The blue shape is a compound shape of one element, the green shape is a compound shape of two elements. The red shape is not a compound shape. The blue and red shapes behave the same. – cdlane Mar 23 '19 at 03:54
  • I think if you remove the two colors for the green, or remove the yellow, see if it works. If it does, then two colors just can't drag. –  Mar 23 '19 at 03:58
  • 2
    You've simply restated the question being asked. I already know that two colors can't drag, the question is why? I suggest you delete your answer and reconsider the problem after you get some experience with defining turtle cursors, compound shapes and the underlying tkinter library. – cdlane Mar 23 '19 at 23:31
  • @cdlane I would suggest you put up a bounty for this question. – 10 Rep May 18 '20 at 19:57