Learning Goals
3 minBy the end of this lesson you can:
- Use
on_mouse_move(pos)to capture the mouse position into a list on every movement. - Loop through the list in
draw()to paint every recorded dot on screen. - Use
on_key_down(key)to detect SPACE and clear the list, wiping the canvas.
Warm-Up · Lists and Loops
5 minThe Doodle Pad stores mouse positions in a list. Quick prediction — what does this print?
points = [] for x in range(5): points.append((x * 10, 50)) print(len(points), "points") print("Last:", points[-1])
Show the answer
Output
5 points Last: (40, 50)
The list stores five coordinate tuples. points[-1] is the last item. In the Doodle Pad, points grows every time the mouse moves.
New Concept · Mouse Events & Key Events
12 minIn PZ-02 you met update() — called every frame whether or not anything happened. Event handlers are different: Pygame Zero calls them only when the event occurs. Give them the exact right name and Pygame Zero wires them up for you.
on_mouse_move(pos)
Called whenever the mouse moves over the window. pos is a tuple (x, y) — the cursor's current position.
points = [] def on_mouse_move(pos): points.append(pos) # save the position every time the mouse moves
on_key_down(key)
Called once when a keyboard key is pressed. Compare key with constants in the keys object:
def on_key_down(key): if key == keys.SPACE: points.clear() # wipe the list — blank canvas
Drawing the stored points
In draw(), loop through the list and paint a small circle at each saved position:
def draw(): screen.fill("white") for pos in points: screen.draw.filled_circle(pos, 4, "navy")
Worked Example · The Doodle Pad
12 minThe story
Mei Ling wants a digital drawing canvas. Move the mouse to draw; press SPACE to clear. Save as doodle.py:
# doodle.py — interactive doodle pad import pgzrun WIDTH = 600 HEIGHT = 400 TITLE = "Doodle Pad — SPACE to clear" points = [] # stores every (x, y) the mouse has visited def on_mouse_move(pos): points.append(pos) # record position on each movement def on_key_down(key): if key == keys.SPACE: points.clear() # wipe the canvas def draw(): screen.fill("white") for pos in points: screen.draw.filled_circle(pos, 4, "navy") pgzrun.go()
What you'll see
Moving the mouse quickly generates hundreds of points per second. The list gets large. In a real app you might cap it with if len(points) > 5000: points.pop(0) to limit memory use.
Try It Yourself
13 minChange the radius in screen.draw.filled_circle(pos, 4, "navy") to a larger number (try 10 or 15). Then add a variable brush_size = 10 at the top so you can change it in one place.
Hint
brush_size = 10 def draw(): screen.fill("white") for pos in points: screen.draw.filled_circle(pos, brush_size, "navy")
Store the current colour in a variable brush_colour = "navy". In on_key_down, add two more checks: press R to switch to "red" and press G to switch to "limegreen". Use the colour variable in draw().
Hint
brush_colour = "navy" def on_key_down(key): global brush_colour if key == keys.SPACE: points.clear() elif key == keys.R: brush_colour = "red" elif key == keys.G: brush_colour = "limegreen"
Mini-Challenge 🔥 · Rainbow Brush
8 minCombine today's doodle pad with the RGB knowledge from PZ-05. Make the brush colour shift through the rainbow as more points are added. Hint: store (pos, colour) tuples in the list instead of just pos. Use the point's index and the len(points) to calculate an ever-changing hue via the red and blue channels.
It works if…
the trail cycles through red, orange, yellow and back as you draw
Show one possible solution
# rainbow_doodle.py — colour shifts with each new point import pgzrun WIDTH = 600 HEIGHT = 400 TITLE = "Rainbow Doodle Pad" strokes = [] # list of (pos, colour) pairs def on_mouse_move(pos): idx = len(strokes) r = int((idx * 3) % 256) g = int((idx * 2) % 256) b = int((255 - idx) % 256) strokes.append((pos, (r, g, b))) def on_key_down(key): if key == keys.SPACE: strokes.clear() def draw(): screen.fill("black") for pos, colour in strokes: screen.draw.filled_circle(pos, 5, colour) pgzrun.go()
The rainbow comes from cycling the three channels at different rates using the point index. Your exact formula can differ — any cycle that shifts through multiple hues counts.
Recap
3 minThe Doodle Pad introduced two new event hooks: on_mouse_move(pos) fires every time the mouse moves, and on_key_down(key) fires each time a key is pressed. By storing mouse positions in a list and looping over them in draw(), the trail persists even though the screen is wiped each frame. Clearing the list with list.clear() wipes the canvas.
Vocabulary Card
- event handler
- A function Pygame Zero calls only when a specific event happens (mouse move, key press) — not every frame.
- on_mouse_move(pos)
- Event handler called when the mouse moves;
posis the current(x, y)cursor position. - on_key_down(key)
- Event handler called once when a key is pressed; compare
keywithkeys.SPACE,keys.R, etc. - list.clear()
- Removes all items from a list in place — equivalent to
del my_list[:].
Homework
4 minExtend the Doodle Pad so that drawing only happens while the left mouse button is held down. Add a boolean variable drawing = False. Set it to True in on_mouse_down and back to False in on_mouse_up. Only append to points in on_mouse_move if drawing is True. Save as click_doodle.py and bring a screenshot.
Stretch. Display a small hint at the bottom of the screen: "Hold mouse to draw · SPACE to clear".
Sample · click_doodle.py
# click_doodle.py — draw only while mouse button is held import pgzrun WIDTH = 600 HEIGHT = 400 TITLE = "Click Doodle Pad" points = [] drawing = False def on_mouse_down(pos, button): global drawing drawing = True def on_mouse_up(pos): global drawing drawing = False def on_mouse_move(pos): if drawing: points.append(pos) def on_key_down(key): if key == keys.SPACE: points.clear() def draw(): screen.fill("white") for pos in points: screen.draw.filled_circle(pos, 5, "navy") screen.draw.text( "Hold mouse to draw | SPACE to clear", center=(300, 388), fontsize=16, color="grey", ) pgzrun.go()
The key addition is the drawing flag — toggled in on_mouse_down and on_mouse_up so the trail only records while the button is held.