# on crash, click Settings->Restart system (due to an issue with pygame-ce + pycafe)
# from https://github.com/tank-king/BreakPong
import solara
import numpy as np
import pygame
import asyncio
import time
data = solara.reactive(None)
import config
width, height = 640, 480
# width = config.screen_width
# height = config.screen_height
player_pos = pygame.Vector2(width / 4, height / 2)
screen = None
canvas = None
if not hasattr(pygame, "events"):
pygame.events = []
def addEventListener(name, callback, flags):
pygame.events.append((name, callback))
print(name)
### BOILERPLATE START ###
def setup():
global screen, canvas
import js
canvas = js.OffscreenCanvas.new(width, height)
canvas.id = "canvas" # emscripten wants this?
canvas.style = js.Object.new() # otherwise we'll get a style.cursor error
js.pyodide.canvas.setCanvas2D(canvas)
# emscripten assumes globalThis.screen to exist
js.screen = {
"width": width,
"height": height,
}
pygame.display.init()
js.navigator.userActivation = js.Object.new()
js.navigator.userActivation.isActive = True
# we define a mock globalThis.window, and to
# avoid 'eventHandler.target.addEventListener is not a function'
# we put in this mock addEventListener
# print("adding event hander")
# why is this still needed?
js.window.addEventListener = addEventListener#js.Function.new()
js.fakeCanvas = js.Object.new()
# used for mouse events
# note that left and top should be the real coordinates of the canvas
# for screenX/Y, but we override those with offsetX/Y
js.fakeCanvas.getBoundingClientRect = js.Function.new(
"return {left: 0, top: 0, width: %i, height: %i}" % (width, height)
)
js.fakeCanvas.addEventListener = addEventListener
def querySelector(query):
print("query", query)
return js.fakeCanvas
# monkey patch document
js.document = js.Object.new()
js.document.querySelector = querySelector
js.document.fullscreenEnabled = False
js.document.addEventListener = js.Function.new()
screen = pygame.display.set_mode((width, height))
def transfer_image():
# read off a pixel from the canvas
context = canvas.getContext("2d")
# can possibly be faster?
mem_obj = context.getImageData(0, 0, width, height).data.to_py()
np_array = np.frombuffer(mem_obj, dtype=np.uint8)
np_array.shape = (height, width, 4)
data.value = np_array
# ideally, all the above boilerplate goes away
### BOILERPLATE END ###
print("setup")
setup()
#!/usr/bin/env python
""" pg.examples.stars
We are all in the gutter,
but some of us are looking at the stars.
-- Oscar Wilde
A simple starfield example. Note you can move the 'center' of
the starfield by leftclicking in the window. This example show
the basics of creating a window, simple pixel plotting, and input
event management.
"""
import random
import math
import pygame as pg
# constants
WINSIZE = [640, 480]
WINCENTER = [320, 240]
NUMSTARS = 150
def init_star(steps=-1):
"creates new star values"
dir = random.randrange(100000)
steps_velocity = 1 if steps == -1 else steps * 0.09
velmult = steps_velocity * (random.random() * 0.6 + 0.4)
vel = [math.sin(dir) * velmult, math.cos(dir) * velmult]
if steps is None:
return [vel, [WINCENTER[0] + (vel[0] * steps), WINCENTER[1] + (vel[1] * steps)]]
return [vel, WINCENTER[:]]
def initialize_stars():
"creates a new starfield"
random.seed()
stars = [init_star(steps=random.randint(0, WINCENTER[0])) for _ in range(NUMSTARS)]
move_stars(stars)
return stars
def draw_stars(surface, stars, color):
"used to draw (and clear) the stars"
for _, pos in stars:
pos = (int(pos[0]), int(pos[1]))
surface.set_at(pos, color)
def move_stars(stars):
"animate the star values"
for vel, pos in stars:
pos[0] = pos[0] + vel[0]
pos[1] = pos[1] + vel[1]
if not 0 <= pos[0] <= WINSIZE[0] or not 0 <= pos[1] <= WINSIZE[1]:
vel[:], pos[:] = init_star()
else:
vel[0] = vel[0] * 1.05
vel[1] = vel[1] * 1.05
async def main():
"This is the starfield code"
# create our starfield
stars = initialize_stars()
# initialize and prepare screen
pg.init()
screen = pg.display.set_mode(WINSIZE)
pg.display.set_caption("pygame Stars Example")
white = 255, 240, 200
black = 20, 20, 40
screen.fill(black)
clock = pg.time.Clock()
# main game loop
done = 0
while not done:
draw_stars(screen, stars, black)
move_stars(stars)
draw_stars(screen, stars, white)
pg.display.update()
for e in pg.event.get():
if e.type == pg.QUIT or (e.type == pg.KEYUP and e.key == pg.K_ESCAPE):
done = 1
break
if e.type == pg.MOUSEBUTTONDOWN and e.button == 1:
print(e, e.pos, pygame.mouse.get_pos(), pygame.mouse.get_rel())
WINCENTER[:] = list(e.pos)
clock.tick(50)
await asyncio.sleep(0.01)
transfer_image()
pg.quit()
reset_counter = solara.reactive(0)
def reset():
reset_counter.value += 1
import reacton
import ipyvue
from typing import cast
from reacton.core import get_render_context
import js
def use_events(
el, events
):
# to avoid add_event_handler having a stale reference to callback
events_ref = reacton.use_ref(events)
events_ref.current = events
def add_event_handler():
vue_widget = cast(ipyvue.VueWidget, reacton.core.get_widget(el))
# we are basically copying the logic from
# reacton.core._event_handler_exception_wrapper
rc = get_render_context()
context = rc.context
assert context is not None
def handler(widget, event_name, data):
try:
# avoid relying of a true bounding rect
if "offsetX" in data:
data["clientX"] = data["offsetX"]
data["clientY"] = data["offsetY"]
# print(event_name, data)
for name, callback in events_ref.current:
if(event_name == "mousedown"):
print(name, data)
# create a fake event object
data_js = js.Object.new()
for key, value in data.items():
setattr(data_js, key, value)
data_js.preventDefault = js.Function.new()
if name == event_name:
callback(data_js)
except Exception as e:
assert context is not None
# because widgets don't have a context, but are a child of a component
# we add it to exceptions_children, not exception_self
# this allows a component to catch the exception of a direct child
context.exceptions_children.append(e)
rc.force_update()
eventnames = set([name for name, callback in events])
print(eventnames)
for name in eventnames:
print("add", name)
vue_widget.on_event(name, handler)
def cleanup():
for name in eventnames:
vue_widget.on_event(name, handler, remove=True)
return cleanup
reacton.use_effect(add_event_handler, [events])
@solara.component
def Page():
solara.Button(label=f"Restart", on_click=reset, outlined=True, color="primary")
with solara.Div(attributes={'tabIndex': 0}) as div:
solara.lab.use_task(main, dependencies=[reset_counter.value])
if data.value is not None:
solara.Image(data.value)
# print(pygame.events)
use_events(div, pygame.events)