feat: drag boxes, transforms as values, Matrix() colours and offline renpy.fetch in the player

screens: `drag` with draggable True moves with the pointer (the place is kept while the screen is
shown); `at icon["at"]` picks a transform that Python code stored in a dict (transform names now
evaluate to a reference); PreviewSlowText and Move(...) exist as values. Python: Matrix([16 numbers])
for matrixcolor, renpy.has_live2d() is false, renpy.fetch fails like a missing network (games catch
it), cache.get_total_size and the texture helpers are no-ops.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
mrleo1nidandClaude Sonnet 5.5 committed 2026-10-26 04:29:20 +03:00
1 parent dcddd42aca
commit 353cd5e49e
4 files changed
+63 -4

No files matched your search

+40 -1
View File
@@ -66,6 +66,11 @@ type Props = Record<string, any>;
class ActionAbort extends Error {}
/** A transform used as a value: `{"at": move_in}`; `at icon["at"]` then applies it. */
export class TransformRef {
constructor(public name: string) {}
}
/** A screen action such as Jump("x"); run when the player activates a widget. */
export class ScreenAction {
/** Actions are callable in Ren'Py: `MainMenu(confirm=False)()` performs the action at once. */
@@ -571,6 +576,7 @@ export class ScreenManager {
private widget(node: any, ctx: Ctx): HTMLElement | null {
switch (node.w) {
case "fixed": return this.container(node, ctx, "div", "abs", ["fixed"], true);
case "drag": return this.dragBox(node, ctx);
case "frame":
case "window": return this.container(node, ctx, "div", "stack", [node.w, "frame"]);
case "vbox": return this.container(node, ctx, "div", "col", ["vbox"]);
@@ -594,6 +600,31 @@ export class ScreenManager {
return null;
}
/** A box the player can move with the pointer; where it was left is kept while the screen is shown. */
private dragBox(node: any, ctx: Ctx): HTMLElement {
const el = this.container(node, ctx, "div", "abs", ["fixed"]);
const props = this.resolve(node, ctx, ["fixed"]);
if (!truthy(this.pick(props, "draggable"))) return el;
const key = `__drag:${pyStr(this.pick(props, "drag_name") ?? "")}`;
const place = (ctx.inst.vars.get(key) ?? [0, 0]) as [number, number];
el.style.translate = `${place[0]}px ${place[1]}px`;
el.style.cursor = "grab";
el.onpointerdown = (down) => {
if ((down.target as HTMLElement).closest("button, [role=button], .pg-sbutton")) return;
down.stopPropagation();
el.setPointerCapture(down.pointerId);
const start = [...place] as [number, number];
el.onpointermove = (move) => {
place[0] = start[0] + (move.clientX - down.clientX);
place[1] = start[1] + (move.clientY - down.clientY);
el.style.translate = `${place[0]}px ${place[1]}px`;
ctx.inst.vars.set(key, [...place]);
};
el.onpointerup = () => { el.onpointermove = null; el.onpointerup = null; };
};
return el;
}
private container(node: any, ctx: Ctx, tag: string, layout: Ctx["layout"], kinds: string[], fill = false): HTMLElement {
const el = document.createElement(tag);
const props = this.resolve(node, ctx, kinds);
@@ -1345,7 +1376,12 @@ export class ScreenManager {
/** `at` transforms: static properties now, animated ones through the ATL runner. */
private transforms(el: HTMLElement, props: Record<string, Expr>, ctx: Ctx) {
const pick = (spec: AtSpec): AtSpec => (spec.choose ? pick(truthy(this.ev(spec.choose.c)) ? spec.choose.then : spec.choose.else) : spec);
const named = (value: any): AtSpec => {
const name = value instanceof TransformRef ? value.name : typeof value === "string" ? value : "";
return name ? { name, atl: this.host.atl(name) ? name : undefined } : {};
};
const pick = (spec: AtSpec): AtSpec => (spec.choose ? pick(truthy(this.ev(spec.choose.c)) ? spec.choose.then : spec.choose.else)
: spec.dyn ? named(this.ev(spec.dyn)) : spec);
const specs: AtSpec[] = Array.isArray((props as any).at) ? (props as any).at.map(pick) : [];
const live: Props = {};
const apply = (p: Props) => {
@@ -1714,6 +1750,9 @@ export class ScreenManager {
out.Flatten = (child: any) => child; // flattening only changes how the browser composites, not what is drawn
out.AlphaMask = shape("alphamask");
out.TrackCursor = shape("trackcursor");
out.PreviewSlowText = shape("text");
out.Move = (...a: any[]) => ({ __transition: "move", args: a }); // `default vpunch = Move(...)`: the effect itself is chosen by `with`
out.absolute = (x: any) => Number(x);
out.At = (child: any) => child;
Object.assign(out, colorMatrices);
out.im = {