add support for pygame scripts

This commit is contained in:
Jean-Marie 'Histausse' Mineau 2026-06-07 15:57:09 +02:00
parent fe4b003971
commit 3be22c5654
Signed by: histausse
GPG key ID: B66AEEDA9B645AD2
6 changed files with 180 additions and 89 deletions

View file

@ -1,8 +1,22 @@
#import "./html_utils.typ": add-tag-in-head
#let state-use-pyscript = state("state-use-pyscript", false)
#let state-pyscript-version = state("state-pyscript-version", none)
#let state-pyscript-headers = state("state-pyscript-headers", (:))
#let state-pyscript-data-list = state("state-pyscript-data-list", (:))
#let state-pyscript-interpreters = state("state-pyscript-interpreters", (:))
#let state-pyscript-default-interpreter = state("state-pyscript-default-interpreter", none)
#let state-pyscript-canvas-ids = state("state-pyscript-canvas-ids", ())
/// Define data needed to load a version of pyscript
#let pyscript-data(
/// Url to `core.js`
core-js-url,
/// Additionnal tags to add to <head>, like mini-coi of core.css
additionnal-head-tags: {},
) = (
core-js-url: core-js-url,
additionnal-head-tags: additionnal-head-tags
)
#let get-pep723(script) = {
script.find(regex(
@ -31,81 +45,131 @@
metadata = (:)
}
state-use-pyscript.update(x => true)
context {
let config = metadata.at("tool", default: (:)).at("pyscript", default: (:))
let config = metadata.at("tool", default: (:)).at("pyscript", default: (:))
let pyscript-config = (:)
// Package dependencies
if "dependencies" in metadata {
pyscript-config.insert("packages", metadata.at("dependencies"))
}
// Files stetup
if "files" in config {
pyscript-config.insert("files", config.at("files"))
}
// Interpreteur selection
if "interpreter" in config {
pyscript-config.insert("interpreter", config.at("interpreter"))
} else if state-pyscript-default-interpreter.final() != none {
assert(
state-pyscript-default-interpreter.final() in state-pyscript-interpreters.final(),
message: state-pyscript-default-interpreter.final() + " is not in pyscript-interpreters",
)
pyscript-config.insert("interpreter", state-pyscript-interpreters.final().at(state-pyscript-default-interpreter.final()))
}
let attrs = (
type: "py"
let pyscript-config = (:)
// Package dependencies
if "dependencies" in metadata {
pyscript-config.insert("packages", metadata.at("dependencies"))
}
// Files stetup
if "files" in config {
pyscript-config.insert("files", config.at("files"))
}
// Interpreteur selection
if "interpreter" in config {
pyscript-config.insert("interpreter", config.at("interpreter"))
} else if state-pyscript-default-interpreter.final() != none {
assert(
state-pyscript-default-interpreter.final() in state-pyscript-interpreters.final(),
message: state-pyscript-default-interpreter.final() + " is not in pyscript-interpreters",
)
let default_val_terminal = true
let default_val_worker = true
let default_val_canvas = none
// TODO: canvas: can only be used once by page, need inserting canvas html tag and pyscript hook to link it to pyodide
if config.at("pygame", default: false) {
default_val_terminal = false
default_val_worker = false
default_val_canvas = "canvas"
}
if config.at("terminal", default: true) {
attrs.insert("terminal", "")
}
if config.at("worker", default: true) {
attrs.insert("worker", "")
}
if config.at("canvas", default: default_val_canvas) not in (none, false) {
attrs.insert("canvas", config.at("canvas", default: default_val_canvas))
}
pyscript-config.insert("interpreter", state-pyscript-interpreters.final().at(state-pyscript-default-interpreter.final()))
}
if pyscript-config != (:) {
attrs.insert("config", json.encode(pyscript-config))
}
let script = it.text;
if config.at("repl", default: false) {
script = "import code\n" + script + "\ncode.interact(banner='', local=globals())"
}
let displayed-code = it.text;
if config.at("hide-meta", default: false) {
displayed-code = displayed-code.replace(get-pep723(displayed-code), "").trim("\n")
}
let attrs = (
type: "py"
)
let default-val-terminal = true
let default-val-worker = true
let default-val-canvas = none
if config.at("pygame", default: false) {
default-val-terminal = false
default-val-worker = false
default-val-canvas = "canvas"
}
if config.at("terminal", default: default-val-terminal) {
attrs.insert("terminal", "")
}
if config.at("worker", default: default-val-worker) {
attrs.insert("worker", "")
}
let canvas-attr = config.at("canvas", default: default-val-canvas)
if canvas-attr == false {
canvas-attr = none
}
if canvas-attr == true {
canvas-attr = "canvas"
}
if canvas-attr != none {
attrs.insert("canvas", canvas-attr)
if not config.at("hide-code", default: false) {
raw(
displayed-code,
block: true,
lang: "python",
align: it.align,
syntaxes: it.syntaxes,
theme: it.theme,
tab-size: it.tab-size,
)
}
assert(
canvas-attr not in state-pyscript-canvas-ids.at(here()),
message: (
"Can not have multiple canvas with the same name. Note that SDL2 has issues working with canvas",
"with an id different than 'canvas', meaning that it is unadvised to have more than one script",
"using SDL2 by page.",
"(tool.pyscript.pygame = true set the default value for the SDL2 canvas to 'canvas' instead of none)"
).join(" ")
)
state-pyscript-canvas-ids.update(canvas-ids => canvas-ids + (canvas-attr,))
}
html.elem(
"script",
attrs: attrs,
script
if pyscript-config != (:) {
attrs.insert("config", json.encode(pyscript-config))
}
let script = it.text;
if config.at("repl", default: false) {
script = "import code\n" + script + "\ncode.interact(banner='', local=globals())"
}
let displayed-code = it.text;
if config.at("hide-meta", default: false) {
displayed-code = displayed-code.replace(get-pep723(displayed-code), "").trim("\n")
}
if not config.at("hide-code", default: false) {
raw(
displayed-code,
block: true,
lang: "python",
align: it.align,
syntaxes: it.syntaxes,
theme: it.theme,
tab-size: it.tab-size,
)
}
html.elem(
"script",
attrs: attrs,
script
)
if canvas-attr != none {
html.elem("canvas", attrs: ("id": canvas-attr))
let core-js-url = state-pyscript-data-list.final().at(state-pyscript-version.final()).core-js-url
add-tag-in-head(
html.script(
type: "module",
```
import { hooks } from "<CORE-JS-URL>";
hooks.main.onReady.add((wrap, script) => {
if (script.hasAttribute("canvas")) {
const target = script.getAttribute("canvas");
const canvas = document.getElementById(target);
wrap.interpreter.canvas.setCanvas2D(canvas);
}
});
```.text.replace("<CORE-JS-URL>", core-js-url)
)
)
}
//raw(block: true, lang: "json", json.encode(metadata))
}
// also need pyscript hook to link canvas to pyodide:
//
// <script type="module">
// // Pyscrypt has a py-game type that handle this, but it forces the interpreter to be
// // from the cdn, which is quite meh.
// // So I do the setup manually (https://docs.pyscript.net/2026.3.1/user-guide/pygame-ce/)
// import { hooks } from "./pyscript/core.js";
// hooks.main.onReady.add((wrap, script) => {
// if (script.hasAttribute("canvas")) {
// const target = script.getAttribute("canvas");
// const canvas = document.getElementById(target);
// wrap.interpreter.canvas.setCanvas2D(canvas);
// }
// });
// </script>
//raw(block: true, lang: "json", json.encode(metadata))