Clicky Clicky

Jed Rembold

October 6, 2025

Announcements

  • Nothing due tonight! :)
  • Problem Set 4 will be released this afternoon/evening
  • A few people still need to take the exam owing to sickness, so please don’t discuss it yet. We will discuss on Wednesday
  • Grade reports going out once Exam 1 fully graded
    • Today is the last day to decide if you want to take this class (or any class) credit/no credit
    • If you need a grade report to help assist you with that, let me know and I’ll try to get you one early, but no promises
  • Graphics Contest is going live later this week! Deadline is end Nov 10
  • No class on Friday (Midsemester day)!
  • Polling: polling.jedrembold.prof

Understanding Check

What would be the final value of c printed in the code to the right?

  1. -15
  2. 20
  3. 50
  4. None of the above
def f(a, b=5, c=True):
    if c:
        return a + b
    else:
        return a * b

def g(c):
    for i in range(2):
        c+=b
    return c > b

a = -3
b = 2
c = f(10, c=g(a))
print(c)

Making Libraries

Returning Graphics

  • You can return any type of variable from a function, including GObject graphical objects

  • Can be useful to write simple functions that bundle together common tasks

  • For instance, to create a filled circle centered at some location:

    def make_filled_circ(x_cent, y_cent, radius, color='black'):
      circle = GOval( x_cent-radius, y_cent-radius, 
                      2*radius, 2*radius)
      circle.set_color(color)
      circle.set_filled(True)
      return circle

A Visual Library

  • Can create our own Python library by constructing a collection of constants or functions
  • All code not in functions gets executed as soon as we import so generally don’t want extraneous print statements or to be running any code directly
  • Want the library to be in the same location as any code we want to import the library in
  • When importing the library, we leave off the .py part of the extension

PGL Help Library

from pgl import GRect, GLabel
import random

def create_filled_rect(
    x_cent, y_cent, width, height, fill_col='black', border_col=None
):
    """
    Creates a GRect object with the desired fill color. 
    If a border color is specified, also draws the 
    border in the desired color.
    """
    rect = GRect(x_cent-width/2, y_cent-height/2, width, height)
    rect.set_filled(True)
    if border_col is None:
        rect.set_color(fill_col)
    else:
        rect.set_color(border_col)
        rect.set_fill_color(fill_col)
    return rect


def random_color():
    """
    Returns a random opaque color as a hex string.
    """
    color = "#"
    for i in range(6):
        color += random.choice("0123456789ABCDEF")
    return color


def create_centered_label(x_cent, y_cent, text, font=None):
    """
    Creates a GLabel object and centers it on the coordinates
    x_cent and y_cent.
    """
    label = GLabel(text)
    if font is not None:
        label.set_font(font)
    label.set_location(x_cent - label.get_width() / 2, 
                       y_cent + label.get_ascent() / 2 )
    return label

Functions as Objects

First Class Functions

  • Functions in Python are treated as objects just like anything else!
    • We will need to take advantage of this to write listener functions.
  • You can assign a function to a variable, pass it as a parameter, return it as a result, etc.
  • Functions treated like any other data objects are called first-class functions
  • To work with a function itself, you leave off the (). Including the parentheses is how you call the function!

A First Class Example

import math

def evaluate_numbers(func):
    print(func)
    print(func(0))
    print(func(2))
    print(func(10))

A = evaluate_numbers

A(math.sqrt)
A(math.exp)

Closures

Consider the code to the right.

  • Why does the line 12 not error?
    • Nothing named a should still exist when it is called!
  • Python Tutor
  • f2 must also keep track of any local variables!
  • The local variables that are included as part of a function are called its closure
b = 1
def f1(a):
    print(a)
    print(b)

    def f2():
        c = a + b
        return c * 3
    return f2 

f2 = f1(10) 
c = f2()

Interactive Graphics

New Graphics Methods

  • A new method that acts on a GWindow:
|||gw|||.get_element_at(|||x|||,|||y|||) Returns the frontmost object at (x,y) or None
  • And more new methods that act on any GObject

    |||obj|||.set_location(|||x|||,|||y|||) Resets the location of the object to the specified point
    |||obj|||.move(|||dx|||,|||dy|||) Moves the object |||dx||| and |||dy||| pixels from its current position
    |||obj|||.move_polar(|||dr|||,|||theta|||) Moves the object |||dr||| pixel units in direction |||theta|||
    |||obj|||.contains(|||x|||,|||y|||) Returns True if the object contains the specified point
    |||obj|||.get_color() Returns the color currently assigned to the object

The Python Event Model

  • Graphical applications usually make it possible for the user to control the action of a program by using an input device such a mouse.
    • Programs supporting this type of control are called interactive programs.
  • User actions such as clicking the mouse are called events.
  • Programs that respond to events are said to be event driven.
  • User input does not generally occur at predictable times. As the events are not controlled by the program, they are said to be asynchronous.
  • In Python, you write a function that acts as a listener for a particular event type. When the event happens, the listener is called.

Our First Interactive Example

  • Consider the simple program below, where we’ve imported the basics and some of our helper functions

    def draw_dots():
        def click_action(event):
            c = create_filled_rect(
                event.get_x(), event.get_y(), 
                10,10, random_color())
            gw.add(c)
    
        gw = GWindow(500, 500)
        gw.add_event_listener("click", click_action)
  • The click_action function specifies what to do when the mouse is clicked

    • Note that it has access to the gw variable since it is in the enclosing function and thus in the closure.

Registering a Listener

  • The last line of our example function:

    gw.add_event_listener("click", click_action)

    tells the graphics window (gw) to call the click_action function whenever a mouse “click” occurs within the window.

  • When the user clicks the mouse, the graphics window, in essense, calls the client (your program) back to let it know that a click has occured. Thus, functions such as click_action are known as callback functions.

  • The parameter event given to the callback function is a special data structure called a mouse event, which contains details about the specifics of the event that triggered the action.

Mouse Events

  • We have a fairly comprehensive list of mouse-events that we can trigger callbacks on:
Name Description
"click" The user clicks the mouse in the window
"dblclk" The user double-clicks the mouse in the window
"mousedown" The user presses the mouse button down
"mouseup" The user releases the mouse button
"mousemove" The user moves the mouse
"drag" The user moves the mouse with the button down

Event Details

  • Certain actions can trigger more than one event

    • Clicking generates a “mousedown”, “mouseup”, and then “click” event, in that order
  • Events trigger no action unless the window is listening for that event

    • If I drag my mouse in the draw_dots() function, you’ll notice that nothing happens
  • You can setup however many listeners you feel you need in order to make your program behave as desired

    gw.add_event_listener("click", click_action)
    gw.add_event_listener("dblclk", dblclk_action)

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()
// reveal.js plugins