Swing Components in Default Processing Window_py5

AFAIK, there’s nothing in py5 or JPype that would make Java threads any safer!

If you feel the need to use SwingUtilities.invokeLater() in Processing’s Java Mode, you should also take the same precautions on py5 too!

“py5 Swing Demo II” is using SwingUtilities.invokeLater() now:

"""
 * py5 Swing Demo II (v1.0.0)
 * GoToLoop & Svan (2024/Jun/04)
 *
 * https://Discourse.Processing.org/t/
 * swing-components-in-default-processing-window-py5/15
"""

import py5

from java.awt.Color import GREEN
from javax.swing import JSlider, JLabel, JButton, JColorChooser, SwingUtilities

DIAM = 'Diameter: %d'
USE_INVOKE_LATER = True

this = py5._instance
fill = GREEN

def settings(): this.size(600, 660)

def setup():
    this.noLoop()
    this.fill(fill.getRGB())

    global w, h, cx, cy
    w, h = this.width, this.height
    cx, cy = w >> 1, h >> 1

    if USE_INVOKE_LATER: # delegate GUI creation to run on the EDT thread
        SwingUtilities.invokeLater(createGUI)
        while 'slider' not in globals(): this.delay(100)

    else: createGUI() # keep Swing GUI creation on the Animation thread


def draw():
    this.background(0o320)
    this.circle(cx, cy, slider.getValue())


def mouse_wheel(e): slider.setValue(slider.getValue() - e.get_count() * 10)

def createGUI():
    global slider, label

    slider = JSlider(0, w - 60, cx)
    slider.setBounds(180, 20, 200, 24)
    slider.addChangeListener(slideChange)
    slider.setToolTipText('circle diameter')

    (label := JLabel(DIAM % cx)).setBounds(75, 20, 80, 24)

    btn = JButton('Color')
    btn.setBounds(400, 20, 80, 24)
    btn.addActionListener(btnAction)

    (window := this.getSurface()).setTitle('py5 Swing Demo')
    (canvas := window.getNative()).setBounds(0, 60, w, h - 60)

    frame = canvas.getFrame()
    frame.add(slider); frame.add(label); frame.add(btn)
    slider.repaint(); label.repaint(); btn.repaint()


def slideChange(_):
    label.setText(DIAM % slider.getValue())
    this.redraw()


def btnAction(_, TITLE = 'Choose color:'):
    global fill
    fill = JColorChooser.showDialog(None, TITLE, fill) or fill

    this.fill(fill.getRGB())
    this.redraw()


__name__ == '__main__' and py5.run_sketch()

This new version is exactly the same as previous “py5 Swing Demo”, but w/ a constant USE_INVOKE_LATER, which we can toggle True or False before running the code.

When USE_INVOKE_LATER is False, it behaves exactly like its previous original version btW; using Processing’s Animation thread to build its Swing GUI components.

Also notice I had to pause the sketch execution inside setup() by invoking delay() inside a while loop, awaiting for createGUI() under EDT thread, so draw() doesn’t start running until global variable slider exists:

    if USE_INVOKE_LATER: # delegate GUI creation to run on the EDT thread
        SwingUtilities.invokeLater(createGUI)
        while 'slider' not in globals(): this.delay(100)

As you can notice, we can pass any Python function as an argument for SwingUtilities.invokeLater(), just like other listener methods such as addChangeListener() or addActionListener().

2 Likes