feat: render API shim - a game's renpy.Displayable subclasses are drawn and receive events
Games write their own displayables with render(), renpy.Render, blit, fill, canvas() and renpy.redraw. render.ts keeps a tree of placed pieces (child renders, ordinary displayables, fills and drawing commands) and turns it into DOM elements that stay put between redraws; pointer events reach event() as pygame-style objects, and a returned value ends the interaction. Shaders and GPU properties are accepted and ignored. The converter whitelists the calls and methods and accepts `import pygame`. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6d78b8b847
commit
3f51f06fec
8 files changed
+354
-3
No files matched your search
@@ -8,6 +8,7 @@ import { isNativeAction } from "./musicroom";
|
||||
import { galleryClass } from "./gallery";
|
||||
import { parseExpression } from "./pyparse";
|
||||
import { movieDisplayable } from "./movie";
|
||||
import { RenderHost, isDisplayableInstance } from "./render";
|
||||
import { strftime } from "./pymodules";
|
||||
import { PyDict, PyInstance, callableOf, pyEq, pyStr, truthy, type Python } from "./python";
|
||||
import type { AtSpec, Expr } from "./types";
|
||||
@@ -49,6 +50,8 @@ export interface ScreenHost {
|
||||
saveSlot(slot: string, confirm: boolean): void;
|
||||
loadSlot(slot: string): void;
|
||||
deleteSlot(slot: string, confirm: boolean): void;
|
||||
/** Size of the game picture, for displayables that ask how much room they have. */
|
||||
resolution?: [number, number];
|
||||
/** The page of the save/load screen (`persistent._file_page`). */
|
||||
filePage: string;
|
||||
startReplay(label: string, scope: Record<string, any> | null): void;
|
||||
@@ -147,6 +150,17 @@ export class ScreenManager {
|
||||
window.addEventListener("pointercancel", this.onRelease, true);
|
||||
}
|
||||
|
||||
/** Game displayables written against the render API (renpy.Displayable subclasses). */
|
||||
private renderHostValue: RenderHost | null = null;
|
||||
get renderHost(): RenderHost {
|
||||
return (this.renderHostValue ??= new RenderHost({
|
||||
py: this.host.py,
|
||||
element: (value) => this.displayable(value),
|
||||
area: () => this.host.resolution ?? [1920, 1080],
|
||||
finish: (value) => this.interactionReturn?.(value),
|
||||
}));
|
||||
}
|
||||
|
||||
private pressed = false;
|
||||
private refreshAfterPress = false;
|
||||
private onPress = () => { this.pressed = true; };
|
||||
@@ -912,6 +926,7 @@ export class ScreenManager {
|
||||
/** An element for an image name/path or for a Crop/Solid/Frame/Transform built by game code. */
|
||||
private displayable(value: any, depth = 0): HTMLElement | null {
|
||||
if (depth > 8 || value === null || value === undefined) return null;
|
||||
if (isDisplayableInstance(value)) return this.renderHost.host(value);
|
||||
if (!(value instanceof Displayable)) return this.host.image(pyStr(value));
|
||||
const [a, b] = value.args;
|
||||
switch (value.kind) {
|
||||
|
||||
Reference in new issue
Block a user