skip to Main Content

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.

3

Answers


  1. Chosen as BEST ANSWER

    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()
    

  2. 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()
    
    Login or Signup to reply.
  3. 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.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search