mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-27 03:28:22 -06:00
M13.11: scriptui concave fills, real images, wasm cfg gating
onDraw fillPath tessellates any path (non-zero winding trapezoid decomposition: concave, self-intersecting, holes) instead of egui's convex-only polygon. ScriptUIImage holds a file path or embedded PNG/JPEG bytes; drawImage, image controls and icon buttons draw the decoded image. Script-host thread code (host_main, run_threaded, ToHost::Start) is gated off wasm32. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
dc132bc219
commit
cb516c91c3
@@ -281,9 +281,34 @@ function DropDownList() {}
|
||||
function ListBox() {}
|
||||
function TabbedPanel() {}
|
||||
function Tab() {}
|
||||
function ScriptUIImage() {}
|
||||
// ScriptUIImage: an image file (`src`) or embedded image bytes (`data`, a binary string as in
|
||||
// resource-string images: one character per byte).
|
||||
function ScriptUIImage(src, data, name) {
|
||||
this.__img = true;
|
||||
this.src = src || null;
|
||||
this.data = data || null;
|
||||
this.name = name || (src ? String(src).replace(/^.*[\\/]/, "") : "");
|
||||
this.pathname = src || "";
|
||||
this.format = data ? (data.charCodeAt(0) === 0x89 ? "png" : "jpeg") : String(src || "").replace(/^.*\./, "").toLowerCase();
|
||||
}
|
||||
ScriptUIImage.prototype.toString = function () { return "[object ScriptUIImage]"; };
|
||||
function ListItem() {}
|
||||
|
||||
// Anything ScriptUI accepts as an image: a ScriptUIImage, a File, a path (or resource name), or
|
||||
// a binary string holding PNG / JPEG bytes.
|
||||
function __uiImage(v) {
|
||||
if (v === undefined || v === null || typeof v === "number" || typeof v === "boolean") return null;
|
||||
if (v.__img) return v;
|
||||
if (v.fsName !== undefined) return new ScriptUIImage(String(v.fsName), null, String(v.name || ""));
|
||||
var s = String(v);
|
||||
if ((s.charCodeAt(0) === 0x89 && s.substr(1, 3) === "PNG") || (s.charCodeAt(0) === 0xff && s.charCodeAt(1) === 0xd8)) return new ScriptUIImage(null, s, "");
|
||||
return s ? new ScriptUIImage(s, null, "") : null;
|
||||
}
|
||||
function __uiImageJson(v) {
|
||||
var img = __uiImage(v);
|
||||
return img ? { src: img.src, data: img.data, name: img.name } : null;
|
||||
}
|
||||
|
||||
var ScriptUI = {
|
||||
version: "6.2.2",
|
||||
frameworkName: "EffectCraft",
|
||||
@@ -293,7 +318,7 @@ var ScriptUI = {
|
||||
PenType: { SOLID_COLOR: 0, THEME_COLOR: 1 },
|
||||
Alignment: { LEFT: "left", RIGHT: "right", TOP: "top", BOTTOM: "bottom", CENTER: "center", FILL: "fill" },
|
||||
newFont: function (name, style, size) { return { name: name, style: style, size: size }; },
|
||||
newImage: function () { return {}; },
|
||||
newImage: function (normal) { return __uiImage(normal); },
|
||||
getResourceText: function (t) { return t; },
|
||||
events: { createEvent: function (t) { return { type: t }; } }
|
||||
};
|
||||
@@ -347,7 +372,7 @@ function __uiGraphics() {
|
||||
this.__push({ op: "text", text: String(text), color: pen ? pen.color : null, x: __num(x || 0), y: __num(y || 0), size: __uiFontSize(f, this), style: String(f && f.style !== undefined ? f.style : "") });
|
||||
},
|
||||
drawOSControl: function () { this.__push({ op: "os" }); },
|
||||
drawImage: function (img, x, y, w, h) { this.__push({ op: "image", x: __num(x || 0), y: __num(y || 0), w: w === undefined ? 0 : __num(w), h: h === undefined ? 0 : __num(h) }); },
|
||||
drawImage: function (img, x, y, w, h) { this.__push({ op: "image", image: __uiImageJson(img), x: __num(x || 0), y: __num(y || 0), w: w === undefined ? 0 : __num(w), h: h === undefined ? 0 : __num(h) }); },
|
||||
drawFocusRing: function () {},
|
||||
measureString: function (s, font, boundaryWidth) {
|
||||
var size = __uiFontSize(font, this);
|
||||
@@ -397,6 +422,11 @@ function __uiInit(o, type, win, parent, bounds, text, props) {
|
||||
o.__wid = __uiNewId();
|
||||
}
|
||||
o.properties = props || {};
|
||||
// Image controls and icon buttons take an image where other controls take their text.
|
||||
if ((type === "image" || type === "iconbutton") && __uiImage(text)) {
|
||||
o.image = __uiImage(text);
|
||||
text = "";
|
||||
}
|
||||
o.text = text === undefined || text === null ? "" : String(text);
|
||||
o.enabled = true;
|
||||
o.visible = true;
|
||||
@@ -459,7 +489,8 @@ function __uiProto() {
|
||||
if (!ctor || t === "window") throw __err("add(): unknown control type " + type);
|
||||
var c = Object.create(__ecGlobal[ctor].prototype);
|
||||
// Value arguments after `text`: slider (value, min, max), progressbar (value, max), lists (items).
|
||||
__uiInit(c, t, this.window, this, bounds, typeof text === "object" && text !== null && !(text instanceof Array) ? "" : text, props);
|
||||
var arg = t === "image" || t === "iconbutton" ? text : typeof text === "object" && text !== null && !(text instanceof Array) ? "" : text;
|
||||
__uiInit(c, t, this.window, this, bounds, arg, props);
|
||||
if ((t === "dropdownlist" || t === "listbox") && text instanceof Array) {
|
||||
for (var i = 0; i < text.length; i++) c.add("item", text[i]);
|
||||
c.text = "";
|
||||
@@ -718,6 +749,7 @@ function __uiJson(c) {
|
||||
}
|
||||
if (c.type === "tabbedpanel") o.activeTab = c.__sel.length ? c.__sel[0] : 0;
|
||||
if (c.__draw && typeof c.onDraw === "function") o.draw = c.__draw;
|
||||
if ((c.type === "image" || c.type === "iconbutton") && c.image) o.image = __uiImageJson(c.image);
|
||||
if (c.type === "edittext") {
|
||||
o.multiline = !!c.__multiline;
|
||||
o.readOnly = !!c.__readonly;
|
||||
|
||||
+22
-5
@@ -127,10 +127,24 @@ pub(crate) fn native_modal(_: &JsValue, args: &[JsValue], ctx: &mut Context) ->
|
||||
// ---------------------------------------------------------------- script-host threads
|
||||
|
||||
pub(crate) enum ToHost {
|
||||
/// Run the script (script-host threads exist only off the web: the browser runs scripts
|
||||
/// inline).
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
Start(Session),
|
||||
Event(Session, ScriptUiEvent),
|
||||
}
|
||||
|
||||
impl ToHost {
|
||||
/// The session a message carries (given back when the host has gone).
|
||||
fn into_session(self) -> Session {
|
||||
match self {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
ToHost::Start(s) => s,
|
||||
ToHost::Event(s, _) => s,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct FromHost {
|
||||
session: Session,
|
||||
outcome: crate::Outcome,
|
||||
@@ -191,6 +205,7 @@ fn yield_wait(outcome: crate::Outcome, windows: Vec<ScriptWindow>, done: bool) -
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn host_main(name: String, code: String, link: Link) {
|
||||
let Ok(ToHost::Start(session)) = link.rx.recv() else { return };
|
||||
let mut a = Active::new(session, &name);
|
||||
@@ -248,7 +263,9 @@ fn receive(session: &mut Session, host: u32, end: HostEnd) -> crate::Outcome {
|
||||
}
|
||||
}
|
||||
|
||||
/// Run a script file on a new script-host thread (see the module docs).
|
||||
/// Run a script file on a new script-host thread (see the module docs). Not on the web, where
|
||||
/// scripts run inline.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) fn run_threaded(session: &mut Session, req: &effectcraft_engine::ScriptRequest) -> crate::Outcome {
|
||||
let host = new_host_id();
|
||||
let (to_tx, to_rx) = std::sync::mpsc::channel();
|
||||
@@ -259,8 +276,8 @@ pub(crate) fn run_threaded(session: &mut Session, req: &effectcraft_engine::Scri
|
||||
if spawned.is_err() {
|
||||
return crate::runtime::run_inline(session, req);
|
||||
}
|
||||
if let Err(std::sync::mpsc::SendError(ToHost::Start(s) | ToHost::Event(s, _))) = to_tx.send(ToHost::Start(std::mem::take(session))) {
|
||||
*session = s;
|
||||
if let Err(std::sync::mpsc::SendError(msg)) = to_tx.send(ToHost::Start(std::mem::take(session))) {
|
||||
*session = msg.into_session();
|
||||
return crate::Outcome { error: Some(ScriptError { message: "the script host did not start".into(), ..Default::default() }), ..Default::default() };
|
||||
}
|
||||
receive(session, host, HostEnd { tx: to_tx, rx: from_rx })
|
||||
@@ -302,8 +319,8 @@ pub fn dispatch_ui(session: &mut Session, ev: &ScriptUiEvent) -> Result<J, Strin
|
||||
session.script_ui.revision += 1;
|
||||
return Err("the script that made this window has ended".into());
|
||||
};
|
||||
if let Err(std::sync::mpsc::SendError(ToHost::Event(s, _) | ToHost::Start(s))) = end.tx.send(ToHost::Event(std::mem::take(session), ev.clone())) {
|
||||
*session = s;
|
||||
if let Err(std::sync::mpsc::SendError(msg)) = end.tx.send(ToHost::Event(std::mem::take(session), ev.clone())) {
|
||||
*session = msg.into_session();
|
||||
session.script_ui.windows.retain(|w| w.host != host);
|
||||
session.script_ui.revision += 1;
|
||||
return Err("the script that made this window has ended".into());
|
||||
|
||||
Reference in New Issue
Block a user