2

I'm trying to use python 3 turtle graphics to do something like presentation software: draw something, pause for a keystroke so the presenter can explain, then draw the next thing.

Here is one solution I've tried (that doesn't work):

import turtle
import time

paused = False

def unpause():
    print("unpause() called")
    global paused
    paused = False

def pause():
    global paused
    paused = True
    while paused:
        time.sleep(0.1)


t = turtle.Turtle()

# set up listener
t.screen.listen()
t.screen.onkeypress(unpause)

# draw something
t.hideturtle()
t.pensize(5)
t.speed(1)
t.pu()
t.goto(-300,-300)
t.pd()
t.goto(-300, 300)

# pause until key is pressed
pause()

# draw some more
t.pu()
t.goto(300,-300)
t.pd()
t.goto(300, 300)

t.screen.mainloop()

The problem is that the sleep call loop totally blocks the keypress from being detected, even when I use a while loop of very short (100ms) sleeps.

If I hit a key while the first line is drawing, I see "unpause() called" in my console, so I know that the key binding is active.

Why doesn't the keypress get detected? I don't know about the internals, but I thought that the keystroke would be recorded in a buffer somewhere, and during the break between sleep calls, the listener would read the buffer and unset the paused global variable. This is not happening.

Is there some other way I could implement this?

This is on a Debian Linux system.

martineau
  • 119,623
  • 25
  • 170
  • 301
fcahoon
  • 73
  • 1
  • 6

3 Answers3

1

Turtle graphics is based on tkinter, which is an event-driven GUI framework, so you can't do things like you would in a regular procedurally-driven program — see @Bryan Oakley's answer to the question Tkinter — executing functions over time for a more detailed explanation (although the turtle module hides most of these details). Anyway, this fact means you shouldn't call time.sleep() in an tight loop like that because everything has to happen without interfering with running of the mainloop().

The "trick" to avoid calling time.sleep() is to schedule periodic checks of the global variable using the turtle.ontimer() function — so your program will work if the first part of it is changed as shown below:

import turtle

paused = False

def unpause():
    print("unpause() called")
    global paused
    paused = False

def pause():
    global paused
    paused = True
    pausing()  # Start watching for global to be changed.

def pausing():
    if paused:
        turtle.ontimer(pausing, 250)  # Check again after delay.
    # else quit checking.

t = turtle.Turtle()

# set up listener
t.screen.onkeypress(unpause)  # Reversed order of
t.screen.listen()             # these two statements.

# draw something
t.hideturtle()
t.pensize(5)
t.speed(1)
t.pu()
t.goto(-300,-300)
t.pd()
t.goto(-300, 300)

# pause until key is pressed
pause()

# draw some more
t.pu()
t.goto(300,-300)
t.pd()
t.goto(300, 300)

t.screen.mainloop()
martineau
  • 119,623
  • 25
  • 170
  • 301
  • the program just stops if I press any key, it does work for you? – kederrac Mar 29 '20 at 19:27
  • Sorry, this code doesn't work for me. If I don't press any key, the second line will still get drawn. I thought a solution could use `ontimer` in some way, but it appears that it simply ensures that the function is called when the timer's time is up: it doesn't stop the execution of the main program code. I tested this by adding this function and calling it before drawing the first line: `def disrupt(): global t t.goto(t.ycor() - 10, t.xcor() + 10) turtle.ontimer(disrupt, 500)` The line drawing is interrupted in the middle and an interesting pattern is drawn. – fcahoon Mar 29 '20 at 20:50
  • Bah, StackOverflow will only let me edit my comments for five minutes. I hope the newlines in my posted python function can be inferred. – fcahoon Mar 29 '20 at 20:58
  • You can't put newlines in comments, so they're not a good place to post multiple lines of code. Anyway, the `250` millisecond delay may be too long, try making it shorter. Meanwhile, I'll see if I can come up with a better solution. – martineau Mar 29 '20 at 21:13
0

Taking the ideas your suggestions have given me (thanks martineau and kederrac!) I was able to come up with a solution. It involves wrapping each of my drawing tasks in a function, then using a dispatch function that either waits for a keypress with an ontimer loop, or calls the next drawing function.

This proof-of-concept code uses entirely too many globals, but it shows the technique:

import turtle

t = turtle.Turtle()
paused = False
current_task = 0

def unpause():
    global paused
    paused = False

def turtle_setup():
    global t
    t.screen.onkeypress(unpause)
    t.screen.listen()
    t.hideturtle()
    t.pensize(5)
    t.speed(1)

def draw_task_finished():
    global paused, current_task, drawing_tasks
    current_task += 1
    paused = True
    if current_task < len(drawing_tasks):
        draw_task_after_keypress()

def draw_task_after_keypress():
    global paused, current_task
    if paused:
        turtle.ontimer(draw_task_after_keypress, 100)
    else:
        drawing_tasks[current_task]()

def draw_thing_one():
    global t
    t.pu()
    t.goto(-300,-300)
    t.pd()
    t.goto(-300, 300)
    draw_task_finished()

def draw_thing_two():
    global t
    t.pu()
    t.goto(300,-300)
    t.pd()
    t.goto(300, 300)
    draw_task_finished()

drawing_tasks = [draw_thing_one, draw_thing_two]

turtle_setup()
drawing_tasks[0]()

t.screen.mainloop()
fcahoon
  • 73
  • 1
  • 6
0

You Can Use turtle.done()function. Just make a input() function, and if input is entered, the program will run. I tried this with basic approach.

Samir Tak
  • 13
  • 2