Windows & Clicky Clicky

Jed Rembold & Fred Agbo

February 24, 2025

Announcements

  • Grading of midterm 1 is still ongoing
  • We are in Ch6 of the text this week, which is interactive graphics
  • Problem set #4 is being posted, due next week Monday at 10 pm
  • Polling continues here

Bubble Event

from pgl import GWindow, GOval
import random
def do_buble():
    def make_circle(x,y,r):
        c = GOval(x-r, y-r, 2*r, 2*r)
        c.set_filled(True)
        if random.randint(1, 100) > 75:
            c.set_color("#F92672") #pink
        else:
            c.set_color("#66D9EF") #blue
        return c
    def make_buble_action(e):
        for i in range(50):
            gw.add(make_circle(
                    random.randint(50,450), 
                    random.randint(50,450), 
                    random.randint(5,50)
                    )
                )
    gw = GWindow(500, 500)
    gw.add_event_listener("dblclick",make_buble_action)
do_buble()

Line Drawing

  • Say we wanted to write a simple program that allows the user to draw lines by clicking and dragging the mouse
  • Using two event listeners would be useful:
    • “mousedown” could start drawing a zero-length line at the current mouse position (and add it to the window)
    • “drag” could update the end-point of that line
  • The strategy would allow the user to have visual feedback as they drag around, helping them to position the line
    • Since the line stretches and contracts as you move the cursor around, the technique is commonly called rubber-banding

Attempt #1

from pgl import GWindow, GLine

WIDTH = 500
HEIGHT = 500

def draw_lines():
    def mousedown_event(e):
        x = e.get_x()
        y = e.get_y()
        line = GLine(x,y,x,y)
        gw.add(line)

    def drag_action(e):
        line.set_end_point(e.get_x(), e.get_y())

    gw = GWindow(WIDTH, HEIGHT)
    line = None
    gw.add_event_listener("mousedown", mousedown_event)
    gw.add_event_listener("drag", drag_action)

if __name__ == '__main__':
    draw_lines()

What Happened?

  • Remember that if you define a variable in a function, that variable is assumed to be local!
    • Keeps you from accidentally overwriting variables you may not have meant to
    • It works against us here, since we WANT to override the original value
  • We can’t pass in the info as a parameter, since it is not part of the event information
  • Python does have a nonlocal keyword, which allows you to state that a specific variable is not local, but it tends to just confuse students

In the Window

  • A common tactic is to store all variables that need to be shared between two or more functions in a state object
  • A state object is just a single object which serves as a storage space for a collection of values
  • The object is created in such a location as to ensure it is in the closure of any functions that need to access its contents
  • We will most often encounter this issue with graphics applications, where we actually already have an object that could serve as a state object
    • The GWindow object (mostly commonly named gw)!

Storage and Retrieval

  • Do you want to store a value in your state object?
    • We can store it as an attribute to the gw object
    • Requires specifying the object name, followed by a dot and then your desired attribute name:
    gw.my_attribute_name = some_cool_value
  • Do you want to retrieve a value from your state object?
    • Just refer to the object and attribute name:

      print(gw.my_attribute_name)

Fixed Line-Drawing

from pgl import GWindow, GLine

WIDTH = 500
HEIGHT = 500

def draw_lines():
    def mousedown_event(e):
        x = e.get_x()
        y = e.get_y()
        gw.line = GLine(x,y,x,y)
        gw.add(gw.line)

    def drag_action(e):
        gw.line.set_end_point(e.get_x(), e.get_y())

    gw = GWindow(WIDTH, HEIGHT)
    gw.line = None
    gw.add_event_listener("mousedown", mousedown_event)
    gw.add_event_listener("drag", drag_action)

if __name__ == '__main__':
    draw_lines()

Timer Events

  • Previously we looked at how our programs could react to mouse events
  • Can also listen for timer events, which occur after a specific time interval
  • You specify the listener for a timer event in the form of a callback function that is invoked at the end of the time interval
  • Can add animation to our graphics by creating a timer whose callback makes small updates to the graphical objects in the window
    • If the time interval is short enough (usually sub 30 milliseconds), the animations will appear smooth to the human eye

Timer Types

  • PGL supports two kinds of timers:
    • A one-shot timer invokes its callback only once after a specified delay
      • Created with

        gw.set_timeout(function, delay)

        where function is the callback function and delay is the time interval in milliseconds

    • An interval timer invokes its callback function repeatedly at regular intervals
      • Created with

        gw.set_interval(function, delay)
  • Both methods return a GTimer object that identifies the timer, and can be stopped by invoking the .stop() method on that timer

Moving Square

def moving_square():
    def step():
        square.move(dx, dy)
        if square.get_x() > 500:
            timer.stop()

    gw = GWindow(500, 200)
    dx = 1
    dy = 0
    square = create_filled_rect(12, 100, 24, 24, "red")
    gw.add(square)
    timer = gw.set_interval(step, 20)

Growing Circles

These circles are growing!
// reveal.js plugins