feat: ATL pictures/contains/function/time, colour matrices and more Python for Ren'Py
ATL
- Image blocks become timelines: picture steps with dissolve, parallel, contains,
finite repeat, time, function f(trans, st, at), calls of other transforms with
arguments, on hover/idle events for buttons. Animations stop when their element
leaves the page
- Pictures defined by an expression (get_face_path("base")) are drawn at show time
- Purely graphical hints (shader, mesh, gl_*, u_*) are accepted and dropped
Colour
- matrixcolor and im.matrix.* as 5x5 matrices combined with `*`, applied through SVG
filters; im.MatrixColor/Scale/Crop/Blur/Composite and Text/Image as values
Python
- match statement, re/copy/time/datetime/defaultdict/functools.partial, more
str/dict methods, Character(...) created by code
- narrator("..."), Character calls, renpy.say and renpy.show/hide with expressions in
Python blocks become script nodes
- Audit follows reachability: functions and classes nothing refers to (custom
statement lexers, unused tools) no longer block an import
Docs: README and ARCHITECTURE now describe a multi-engine library (GameMaker and
QSP planned). Tests for the new runtime pieces.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
62ece25902
commit
ea03cebf6a
21 files changed
+1264
-93
No files matched your search
@@ -21,6 +21,7 @@ from typing import Any
|
||||
|
||||
from app.converter import pyexpr
|
||||
from app.converter.atl import WARPER
|
||||
from app.converter.exprtokens import read_value
|
||||
from app.converter.pyexpr import Unsupported
|
||||
from app.converter.rpy_lexer import Line
|
||||
|
||||
@@ -41,7 +42,7 @@ ymargin left_margin right_margin top_margin bottom_margin width height""".split(
|
||||
LOOK = set("""background foreground idle hover insensitive selected selected_idle selected_hover
|
||||
selected_insensitive auto ground left_bar right_bar top_bar bottom_bar thumb alpha zoom rotate xzoom yzoom
|
||||
clipping fit_first xfit yfit box_reverse box_wrap order_reverse transpose rows cols crop size_group
|
||||
xtile ytile""".split())
|
||||
xtile ytile matrixcolor blur""".split())
|
||||
TEXT = set("""color size font bold italic underline strikethrough outlines text_align textalign line_spacing
|
||||
kerning layout first_indent min_width antialias xalign yalign justify hyperlink_functions slow_cps""".split())
|
||||
BEHAVIOUR = set("""action alternate hovered unhovered sensitive tooltip focus_mask mouse hover_sound
|
||||
@@ -58,8 +59,6 @@ STATE_PREFIXES = ("idle", "hover", "insensitive", "selected", "selected_idle", "
|
||||
ALL = LAYOUT | LOOK | TEXT | BEHAVIOUR
|
||||
SCREEN_PROPS = {"zorder", "modal", "tag", "variant", "style_prefix", "layer", "sensitive", "style"}
|
||||
DESKTOP_VARIANTS = {"pc", "large", "web", "medium", "small"}
|
||||
OPERATORS = {"+", "-", "*", "/", "//", "%", "**", "==", "!=", "<", ">", "<=", ">=", "and", "or", "in", "is",
|
||||
"if", "else", "not"}
|
||||
GLUED = re.compile(r"^([a-z_][a-z_0-9]*)(\(.*\)|\[.*\])$", re.S)
|
||||
|
||||
|
||||
@@ -110,29 +109,6 @@ def tokens(text: str) -> list[str]:
|
||||
return out
|
||||
|
||||
|
||||
def read_value(words: list[str], i: int) -> tuple[str, int]:
|
||||
"""One property value starting at words[i]. Decompiled sources keep operators unparenthesised
|
||||
(``text "Day " + str(day)``), so a value continues while the next word is an operator."""
|
||||
parts = [words[i]]
|
||||
i += 1
|
||||
if parts[0] in ("not", "-") and i < len(words):
|
||||
parts.append(words[i])
|
||||
i += 1
|
||||
while i < len(words):
|
||||
if words[i].startswith("(") and re.search(r"[\w\])]$", parts[-1]): # `ToggleVariable ("x")`
|
||||
parts[-1] += words[i]
|
||||
i += 1
|
||||
elif parts[-1].endswith(",") or parts[-1] in OPERATORS: # `A(), B()` / `a +` continue with the next word
|
||||
parts.append(words[i])
|
||||
i += 1
|
||||
elif i + 1 < len(words) and words[i] in OPERATORS:
|
||||
parts.extend([words[i], words[i + 1]])
|
||||
i += 2
|
||||
else:
|
||||
break
|
||||
return " ".join(parts), i
|
||||
|
||||
|
||||
class ScreenParser:
|
||||
def __init__(self, warn: Callable[[str, Line], None], transform_ref: Callable[[str, Line], dict],
|
||||
atl: Callable[[list[Line]], tuple[dict, dict | None]] | None = None):
|
||||
@@ -191,7 +167,8 @@ class ScreenParser:
|
||||
i = 0
|
||||
while i < len(lines):
|
||||
line = lines[i]
|
||||
words = tokens(line.text.rstrip(":").strip() if line.block else line.text.strip())
|
||||
text = line.text.rstrip(":").strip() if line.block else line.text.strip()
|
||||
words = tokens(re.sub(r"^(if|elif|showif)\(", r"\1 (", text))
|
||||
i += 1
|
||||
if not words:
|
||||
continue
|
||||
@@ -199,7 +176,7 @@ class ScreenParser:
|
||||
if first in ("if", "showif"):
|
||||
branches = [self.branch(first, words, line)]
|
||||
while i < len(lines):
|
||||
nxt = tokens(lines[i].text.rstrip(":").strip())
|
||||
nxt = tokens(re.sub(r"^(elif|else)\(", r"\1 (", lines[i].text.rstrip(":").strip()))
|
||||
if not nxt or nxt[0] not in ("elif", "else"):
|
||||
break
|
||||
branches.append(self.branch(nxt[0], nxt, lines[i]))
|
||||
|
||||
Reference in new issue
Block a user