Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
88ae3012f7 | ||
| 2a1767ef74 |
3
.idea/copyright/profiles_settings.xml
generated
Normal file
3
.idea/copyright/profiles_settings.xml
generated
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<component name="CopyrightManager">
|
||||||
|
<settings default="OAPL notice" />
|
||||||
|
</component>
|
||||||
49
.idea/misc.xml
generated
49
.idea/misc.xml
generated
@@ -8,55 +8,6 @@
|
|||||||
</list>
|
</list>
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
<component name="NullableNotNullManager">
|
|
||||||
<option name="myDefaultNullable" value="org.jetbrains.annotations.Nullable" />
|
|
||||||
<option name="myDefaultNotNull" value="org.jetbrains.annotations.NotNull" />
|
|
||||||
<option name="myOrdered" value="false" />
|
|
||||||
<option name="myNullables">
|
|
||||||
<value>
|
|
||||||
<list size="16">
|
|
||||||
<item index="0" class="java.lang.String" itemvalue="org.jspecify.annotations.Nullable" />
|
|
||||||
<item index="1" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
|
|
||||||
<item index="2" class="java.lang.String" itemvalue="android.annotation.Nullable" />
|
|
||||||
<item index="3" class="java.lang.String" itemvalue="androidx.annotation.Nullable" />
|
|
||||||
<item index="4" class="java.lang.String" itemvalue="com.android.annotations.Nullable" />
|
|
||||||
<item index="5" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
|
|
||||||
<item index="6" class="java.lang.String" itemvalue="androidx.annotation.RecentlyNullable" />
|
|
||||||
<item index="7" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.Nullable" />
|
|
||||||
<item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableDecl" />
|
|
||||||
<item index="9" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableType" />
|
|
||||||
<item index="10" class="java.lang.String" itemvalue="org.eclipse.jdt.annotation.Nullable" />
|
|
||||||
<item index="11" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
|
|
||||||
<item index="12" class="java.lang.String" itemvalue="jakarta.annotation.Nullable" />
|
|
||||||
<item index="13" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
|
|
||||||
<item index="14" class="java.lang.String" itemvalue="javax.annotation.CheckForNull" />
|
|
||||||
<item index="15" class="java.lang.String" itemvalue="org.springframework.lang.Nullable" />
|
|
||||||
</list>
|
|
||||||
</value>
|
|
||||||
</option>
|
|
||||||
<option name="myNotNulls">
|
|
||||||
<value>
|
|
||||||
<list size="16">
|
|
||||||
<item index="0" class="java.lang.String" itemvalue="org.jspecify.annotations.NonNull" />
|
|
||||||
<item index="1" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
|
|
||||||
<item index="2" class="java.lang.String" itemvalue="android.annotation.NonNull" />
|
|
||||||
<item index="3" class="java.lang.String" itemvalue="androidx.annotation.NonNull" />
|
|
||||||
<item index="4" class="java.lang.String" itemvalue="com.android.annotations.NonNull" />
|
|
||||||
<item index="5" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
|
|
||||||
<item index="6" class="java.lang.String" itemvalue="androidx.annotation.RecentlyNonNull" />
|
|
||||||
<item index="7" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.NonNull" />
|
|
||||||
<item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullDecl" />
|
|
||||||
<item index="9" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullType" />
|
|
||||||
<item index="10" class="java.lang.String" itemvalue="org.eclipse.jdt.annotation.NonNull" />
|
|
||||||
<item index="11" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
|
|
||||||
<item index="12" class="java.lang.String" itemvalue="jakarta.annotation.Nonnull" />
|
|
||||||
<item index="13" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
|
|
||||||
<item index="14" class="java.lang.String" itemvalue="lombok.NonNull" />
|
|
||||||
<item index="15" class="java.lang.String" itemvalue="org.springframework.lang.NonNull" />
|
|
||||||
</list>
|
|
||||||
</value>
|
|
||||||
</option>
|
|
||||||
</component>
|
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_23" default="true" project-jdk-name="23" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_23" default="true" project-jdk-name="23" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/out" />
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
</component>
|
</component>
|
||||||
|
|||||||
21
pom.xml
21
pom.xml
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
<groupId>org.openautonomousconnection</groupId>
|
<groupId>org.openautonomousconnection</groupId>
|
||||||
<artifactId>LuaScript</artifactId>
|
<artifactId>LuaScript</artifactId>
|
||||||
<version>0.0.0-STABLE.1.4</version>
|
<version>0.0.0-STABLE.1.3</version>
|
||||||
|
|
||||||
<organization>
|
<organization>
|
||||||
<name>Open Autonomous Connection</name>
|
<name>Open Autonomous Connection</name>
|
||||||
@@ -16,8 +16,8 @@
|
|||||||
<description>The default DNS-Server</description>
|
<description>The default DNS-Server</description>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<maven.compiler.source>25</maven.compiler.source>
|
<maven.compiler.source>23</maven.compiler.source>
|
||||||
<maven.compiler.target>25</maven.compiler.target>
|
<maven.compiler.target>23</maven.compiler.target>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
@@ -66,9 +66,16 @@
|
|||||||
|
|
||||||
<repositories>
|
<repositories>
|
||||||
<repository>
|
<repository>
|
||||||
<id>repounlegitdqrk</id>
|
<id>unlegitdqrk</id>
|
||||||
<url>https://repo.unlegitdqrk.dev/api/packages/UnlegitDqrk/maven</url>
|
<url>https://repo.unlegitdqrk.dev/api/packages/UnlegitDqrk/maven</url>
|
||||||
</repository>
|
</repository>
|
||||||
|
<repository>
|
||||||
|
<id>oac</id>
|
||||||
|
<url>https://repo.open-autonomous-connection.org/api/packages/open-autonomous-connection/maven</url>
|
||||||
|
<snapshots>
|
||||||
|
<enabled>true</enabled>
|
||||||
|
</snapshots>
|
||||||
|
</repository>
|
||||||
</repositories>
|
</repositories>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
@@ -123,17 +130,13 @@
|
|||||||
<artifactId>maven-javadoc-plugin</artifactId>
|
<artifactId>maven-javadoc-plugin</artifactId>
|
||||||
<version>3.6.3</version>
|
<version>3.6.3</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<failOnError>true</failOnError>
|
<failOnError>false</failOnError>
|
||||||
<failOnWarnings>false</failOnWarnings>
|
<failOnWarnings>false</failOnWarnings>
|
||||||
<doclint>none</doclint>
|
<doclint>none</doclint>
|
||||||
<locale>en_US</locale>
|
<locale>en_US</locale>
|
||||||
<encoding>UTF-8</encoding>
|
<encoding>UTF-8</encoding>
|
||||||
<docencoding>UTF-8</docencoding>
|
<docencoding>UTF-8</docencoding>
|
||||||
<charset>UTF-8</charset>
|
<charset>UTF-8</charset>
|
||||||
|
|
||||||
<additionalOptions>
|
|
||||||
<additionalOption>--allow-script-in-comments</additionalOption>
|
|
||||||
</additionalOptions>
|
|
||||||
</configuration>
|
</configuration>
|
||||||
<executions>
|
<executions>
|
||||||
<execution>
|
<execution>
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ package org.openautonomousconnection.luascript.events;
|
|||||||
import org.luaj.vm2.LuaTable;
|
import org.luaj.vm2.LuaTable;
|
||||||
import org.luaj.vm2.LuaValue;
|
import org.luaj.vm2.LuaValue;
|
||||||
|
|
||||||
import java.lang.reflect.Array;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@@ -19,18 +17,12 @@ public final class JavaToLua {
|
|||||||
public static LuaValue coerce(Object v) {
|
public static LuaValue coerce(Object v) {
|
||||||
if (v == null) return LuaValue.NIL;
|
if (v == null) return LuaValue.NIL;
|
||||||
if (v instanceof LuaValue lv) return lv;
|
if (v instanceof LuaValue lv) return lv;
|
||||||
|
|
||||||
if (v instanceof String s) return LuaValue.valueOf(s);
|
if (v instanceof String s) return LuaValue.valueOf(s);
|
||||||
if (v instanceof Boolean b) return LuaValue.valueOf(b);
|
if (v instanceof Boolean b) return LuaValue.valueOf(b);
|
||||||
|
if (v instanceof Integer i) return LuaValue.valueOf(i);
|
||||||
if (v instanceof Byte n) return LuaValue.valueOf(n.intValue());
|
if (v instanceof Long l) return LuaValue.valueOf(l);
|
||||||
if (v instanceof Short n) return LuaValue.valueOf(n.intValue());
|
if (v instanceof Float f) return LuaValue.valueOf(f);
|
||||||
if (v instanceof Integer n) return LuaValue.valueOf(n);
|
if (v instanceof Double d) return LuaValue.valueOf(d);
|
||||||
if (v instanceof Long n) return LuaValue.valueOf(n);
|
|
||||||
if (v instanceof Float n) return LuaValue.valueOf(n.doubleValue());
|
|
||||||
if (v instanceof Double n) return LuaValue.valueOf(n);
|
|
||||||
|
|
||||||
if (v instanceof Number n) return LuaValue.valueOf(n.doubleValue());
|
|
||||||
|
|
||||||
if (v instanceof Map<?, ?> m) {
|
if (v instanceof Map<?, ?> m) {
|
||||||
LuaTable t = new LuaTable();
|
LuaTable t = new LuaTable();
|
||||||
@@ -51,24 +43,6 @@ public final class JavaToLua {
|
|||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (v instanceof Collection<?> col) {
|
|
||||||
LuaTable t = new LuaTable();
|
|
||||||
int i = 1;
|
|
||||||
for (Object o : col) {
|
|
||||||
t.set(i++, coerce(o));
|
|
||||||
}
|
|
||||||
return t;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (v.getClass().isArray()) {
|
|
||||||
LuaTable t = new LuaTable();
|
|
||||||
int len = Array.getLength(v);
|
|
||||||
for (int i = 0; i < len; i++) {
|
|
||||||
t.set(i + 1, coerce(Array.get(v, i)));
|
|
||||||
}
|
|
||||||
return t;
|
|
||||||
}
|
|
||||||
|
|
||||||
return LuaValue.valueOf(String.valueOf(v));
|
return LuaValue.valueOf(String.valueOf(v));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,212 +0,0 @@
|
|||||||
package org.openautonomousconnection.luascript.fx;
|
|
||||||
|
|
||||||
import javafx.application.Platform;
|
|
||||||
import javafx.scene.media.Media;
|
|
||||||
import javafx.scene.media.MediaPlayer;
|
|
||||||
import org.openautonomousconnection.luascript.hosts.AudioHost;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.net.URLConnection;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.StandardOpenOption;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* JavaFX MediaPlayer based AudioHost.
|
|
||||||
*
|
|
||||||
* <p>Note: JavaFX Media does not reliably use {@link java.net.URLStreamHandler} for custom schemes.
|
|
||||||
* Therefore, for {@code web://} this host resolves via {@link URLConnection} (your installed handler),
|
|
||||||
* spools to a temporary file and plays that file via {@code file://}.</p>
|
|
||||||
*/
|
|
||||||
public final class FxAudioHost implements AudioHost {
|
|
||||||
|
|
||||||
private static final Set<String> AUDIO_EXTENSIONS = Set.of(
|
|
||||||
"mp3", "wav", "ogg", "m4a", "opus", "web"
|
|
||||||
);
|
|
||||||
|
|
||||||
private volatile MediaPlayer player;
|
|
||||||
private volatile boolean loop;
|
|
||||||
private volatile double volume = 1.0;
|
|
||||||
|
|
||||||
private volatile Path lastTempFile;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void playFile(File file) {
|
|
||||||
Objects.requireNonNull(file, "file");
|
|
||||||
|
|
||||||
if (!file.isFile()) {
|
|
||||||
throw new IllegalArgumentException("Audio file not found: " + file.getAbsolutePath());
|
|
||||||
}
|
|
||||||
|
|
||||||
String ext = MediaExtensions.extensionOf(file.getName());
|
|
||||||
ensureAllowedAudioExtension(ext, file.getName());
|
|
||||||
|
|
||||||
playUri(file.toURI());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void playUrl(String url) {
|
|
||||||
Objects.requireNonNull(url, "url");
|
|
||||||
String u = url.trim();
|
|
||||||
if (u.isEmpty()) throw new IllegalArgumentException("URL is empty");
|
|
||||||
|
|
||||||
URI uri = URI.create(u);
|
|
||||||
String scheme = (uri.getScheme() == null) ? "" : uri.getScheme().toLowerCase(Locale.ROOT);
|
|
||||||
|
|
||||||
String ext = MediaExtensions.extensionOf(uri.getPath());
|
|
||||||
ensureAllowedAudioExtension(ext, uri.getPath());
|
|
||||||
|
|
||||||
if ("http".equals(scheme) || "https".equals(scheme) || "file".equals(scheme)) {
|
|
||||||
playUri(uri);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ("web".equals(scheme)) {
|
|
||||||
Path tmp = null;
|
|
||||||
try {
|
|
||||||
tmp = downloadToTempFile(u, ext);
|
|
||||||
rememberTemp(tmp);
|
|
||||||
playUri(tmp.toUri());
|
|
||||||
return;
|
|
||||||
} catch (IOException e) {
|
|
||||||
safeDelete(tmp);
|
|
||||||
throw new RuntimeException("Failed to load web audio: " + e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new IllegalArgumentException("Unsupported scheme: " + scheme);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void pause() {
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
MediaPlayer p = player;
|
|
||||||
if (p != null) p.pause();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void stop() {
|
|
||||||
Platform.runLater(this::stopInternal);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setVolume(double volume) {
|
|
||||||
double v = clamp01(volume);
|
|
||||||
this.volume = v;
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
MediaPlayer p = player;
|
|
||||||
if (p != null) p.setVolume(v);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setLoop(boolean loop) {
|
|
||||||
this.loop = loop;
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
MediaPlayer p = player;
|
|
||||||
if (p != null) p.setCycleCount(loop ? MediaPlayer.INDEFINITE : 1);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void playUri(URI uri) {
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
stopInternal();
|
|
||||||
|
|
||||||
Media media = new Media(uri.toString());
|
|
||||||
MediaPlayer p = new MediaPlayer(media);
|
|
||||||
|
|
||||||
p.setCycleCount(loop ? MediaPlayer.INDEFINITE : 1);
|
|
||||||
p.setVolume(volume);
|
|
||||||
|
|
||||||
p.setOnEndOfMedia(() -> {
|
|
||||||
if (!loop) {
|
|
||||||
try {
|
|
||||||
p.dispose();
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.player = p;
|
|
||||||
p.play();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void stopInternal() {
|
|
||||||
MediaPlayer p = this.player;
|
|
||||||
this.player = null;
|
|
||||||
|
|
||||||
if (p != null) {
|
|
||||||
try {
|
|
||||||
p.stop();
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
p.dispose();
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Path tmp = this.lastTempFile;
|
|
||||||
this.lastTempFile = null;
|
|
||||||
safeDelete(tmp);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Path downloadToTempFile(String url, String ext) throws IOException {
|
|
||||||
URL u = new URL(url);
|
|
||||||
URLConnection con = u.openConnection();
|
|
||||||
con.setUseCaches(false);
|
|
||||||
|
|
||||||
String safeExt = (ext == null || ext.isBlank()) ? "bin" : ext.toLowerCase(Locale.ROOT);
|
|
||||||
Path tmp = Files.createTempFile("oac-audio-", "." + safeExt);
|
|
||||||
|
|
||||||
try (InputStream in = con.getInputStream()) {
|
|
||||||
Files.copy(in, tmp, java.nio.file.StandardCopyOption.REPLACE_EXISTING);
|
|
||||||
} catch (IOException e) {
|
|
||||||
safeDelete(tmp);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
|
|
||||||
Files.write(tmp, new byte[0], StandardOpenOption.APPEND);
|
|
||||||
return tmp;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void rememberTemp(Path tmp) {
|
|
||||||
Path prev = this.lastTempFile;
|
|
||||||
this.lastTempFile = tmp;
|
|
||||||
safeDelete(prev);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void safeDelete(Path p) {
|
|
||||||
if (p == null) return;
|
|
||||||
try {
|
|
||||||
Files.deleteIfExists(p);
|
|
||||||
} catch (IOException ignored) {
|
|
||||||
// best-effort
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static double clamp01(double v) {
|
|
||||||
if (v < 0.0) return 0.0;
|
|
||||||
if (v > 1.0) return 1.0;
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ensureAllowedAudioExtension(String ext, String source) {
|
|
||||||
String e = (ext == null) ? "" : ext.toLowerCase(Locale.ROOT);
|
|
||||||
if (e.isEmpty() || !AUDIO_EXTENSIONS.contains(e)) {
|
|
||||||
throw new IllegalArgumentException("Unsupported audio format '" + e + "' for: " + source);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
package org.openautonomousconnection.luascript.fx;
|
|
||||||
|
|
||||||
import javafx.application.Platform;
|
|
||||||
import javafx.scene.input.Clipboard;
|
|
||||||
import javafx.scene.input.ClipboardContent;
|
|
||||||
import org.openautonomousconnection.luascript.hosts.ClipboardHost;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* JavaFX clipboard host (text only).
|
|
||||||
*/
|
|
||||||
public final class FxClipboardHost implements ClipboardHost {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setText(String text) {
|
|
||||||
String s = text == null ? "" : text;
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
ClipboardContent c = new ClipboardContent();
|
|
||||||
c.putString(s);
|
|
||||||
Clipboard.getSystemClipboard().setContent(c);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getText() {
|
|
||||||
AtomicReference<String> out = new AtomicReference<>("");
|
|
||||||
FxThreadBridge.runAndWait(() -> {
|
|
||||||
String s = Clipboard.getSystemClipboard().getString();
|
|
||||||
out.set(s == null ? "" : s);
|
|
||||||
});
|
|
||||||
return out.get();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
package org.openautonomousconnection.luascript.fx;
|
|
||||||
|
|
||||||
import javafx.scene.web.WebEngine;
|
|
||||||
import org.openautonomousconnection.luascript.hosts.CssHost;
|
|
||||||
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* CSS host via internal JS bridge.
|
|
||||||
*/
|
|
||||||
public final class FxCssHost implements CssHost {
|
|
||||||
|
|
||||||
private final WebEngine engine;
|
|
||||||
private final FxDomHost dom;
|
|
||||||
|
|
||||||
public FxCssHost(WebEngine engine, FxDomHost dom) {
|
|
||||||
this.engine = Objects.requireNonNull(engine, "engine");
|
|
||||||
this.dom = Objects.requireNonNull(dom, "dom");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getComputedStyle(String elementId, String property) {
|
|
||||||
Objects.requireNonNull(elementId, "elementId");
|
|
||||||
Objects.requireNonNull(property, "property");
|
|
||||||
|
|
||||||
return FxThreadBridge.callAndWait(() -> FxWebBridge.callWithJs(engine, () -> {
|
|
||||||
dom.requireDocument();
|
|
||||||
|
|
||||||
String script = ""
|
|
||||||
+ "(function(){"
|
|
||||||
+ " var id=" + FxWebBridge.toJsLiteral(elementId) + ";"
|
|
||||||
+ " var prop=" + FxWebBridge.toJsLiteral(property) + ";"
|
|
||||||
+ " var el=document.getElementById(id);"
|
|
||||||
+ " if(!el) throw new Error('Unknown element id: '+id);"
|
|
||||||
+ " var cs=getComputedStyle(el);"
|
|
||||||
+ " var v=cs.getPropertyValue(prop) || cs[prop] || '';"
|
|
||||||
+ " return String(v);"
|
|
||||||
+ "})();";
|
|
||||||
|
|
||||||
Object ret = engine.executeScript(script);
|
|
||||||
return ret == null ? "" : String.valueOf(ret);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, String> getComputedStyles(String elementId, String[] properties) {
|
|
||||||
Objects.requireNonNull(elementId, "elementId");
|
|
||||||
Objects.requireNonNull(properties, "properties");
|
|
||||||
|
|
||||||
Map<String, String> out = new LinkedHashMap<>();
|
|
||||||
for (String p : properties) {
|
|
||||||
if (p == null || p.isBlank()) continue;
|
|
||||||
out.put(p, getComputedStyle(elementId, p));
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setInlineStyle(String elementId, String property, String value) {
|
|
||||||
Objects.requireNonNull(elementId, "elementId");
|
|
||||||
Objects.requireNonNull(property, "property");
|
|
||||||
|
|
||||||
FxThreadBridge.runAndWait(() -> FxWebBridge.runWithJs(engine, () -> {
|
|
||||||
dom.requireDocument();
|
|
||||||
|
|
||||||
String v = value == null ? "" : value;
|
|
||||||
|
|
||||||
String script = ""
|
|
||||||
+ "(function(){"
|
|
||||||
+ " var id=" + FxWebBridge.toJsLiteral(elementId) + ";"
|
|
||||||
+ " var prop=" + FxWebBridge.toJsLiteral(property) + ";"
|
|
||||||
+ " var val=" + FxWebBridge.toJsLiteral(v) + ";"
|
|
||||||
+ " var el=document.getElementById(id);"
|
|
||||||
+ " if(!el) throw new Error('Unknown element id: '+id);"
|
|
||||||
+ " if(!val){ el.style.removeProperty(prop); return null; }"
|
|
||||||
+ " el.style.setProperty(prop, val);"
|
|
||||||
+ " return null;"
|
|
||||||
+ "})();";
|
|
||||||
|
|
||||||
engine.executeScript(script);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getInlineStyle(String elementId, String property) {
|
|
||||||
Objects.requireNonNull(elementId, "elementId");
|
|
||||||
Objects.requireNonNull(property, "property");
|
|
||||||
|
|
||||||
return FxThreadBridge.callAndWait(() -> FxWebBridge.callWithJs(engine, () -> {
|
|
||||||
dom.requireDocument();
|
|
||||||
|
|
||||||
String script = ""
|
|
||||||
+ "(function(){"
|
|
||||||
+ " var id=" + FxWebBridge.toJsLiteral(elementId) + ";"
|
|
||||||
+ " var prop=" + FxWebBridge.toJsLiteral(property) + ";"
|
|
||||||
+ " var el=document.getElementById(id);"
|
|
||||||
+ " if(!el) throw new Error('Unknown element id: '+id);"
|
|
||||||
+ " var v=el.style.getPropertyValue(prop) || '';"
|
|
||||||
+ " return String(v);"
|
|
||||||
+ "})();";
|
|
||||||
|
|
||||||
Object ret = engine.executeScript(script);
|
|
||||||
return ret == null ? "" : String.valueOf(ret);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setCssVariable(String elementId, String name, String value) {
|
|
||||||
Objects.requireNonNull(name, "name");
|
|
||||||
if (!name.trim().startsWith("--")) throw new IllegalArgumentException("CSS variable must start with '--': " + name);
|
|
||||||
setInlineStyle(elementId, name.trim(), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getCssVariable(String elementId, String name) {
|
|
||||||
Objects.requireNonNull(name, "name");
|
|
||||||
if (!name.trim().startsWith("--")) throw new IllegalArgumentException("CSS variable must start with '--': " + name);
|
|
||||||
return getComputedStyle(elementId, name.trim());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -10,9 +10,7 @@ import java.util.concurrent.atomic.AtomicLong;
|
|||||||
/**
|
/**
|
||||||
* DomHost implementation backed by JavaFX WebView's W3C DOM (WebEngine#getDocument()).
|
* DomHost implementation backed by JavaFX WebView's W3C DOM (WebEngine#getDocument()).
|
||||||
*
|
*
|
||||||
* <p>Uses W3C DOM for structure/attributes and a small JavaScript bridge for DOM properties and method calls
|
* <p>No jsoup and no JavaScript. All operations are performed via W3C DOM APIs.</p>
|
||||||
* (required for HTMLMediaElement like video/audio). Lua remains the scripting language; JavaScript is only
|
|
||||||
* used internally to access DOM properties/methods that are not available via W3C DOM APIs.</p>
|
|
||||||
*
|
*
|
||||||
* <p>Element identity is the {@code id} attribute. This host auto-assigns stable ids to elements that
|
* <p>Element identity is the {@code id} attribute. This host auto-assigns stable ids to elements that
|
||||||
* do not have one, ensuring addressability for Lua bindings and event routing.</p>
|
* do not have one, ensuring addressability for Lua bindings and event routing.</p>
|
||||||
@@ -79,24 +77,6 @@ public final class FxDomHost implements DomHost {
|
|||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String requireId(String id) {
|
|
||||||
if (id == null) throw new IllegalArgumentException("elementId is null");
|
|
||||||
String v = id.trim();
|
|
||||||
if (v.isEmpty()) throw new IllegalArgumentException("elementId is blank");
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String requireJsIdentifier(String s, String label) {
|
|
||||||
if (s == null) throw new IllegalArgumentException(label + " is null");
|
|
||||||
String v = s.trim();
|
|
||||||
if (v.isEmpty()) throw new IllegalArgumentException(label + " is blank");
|
|
||||||
// Prevent JS injection: only simple identifier.
|
|
||||||
if (!v.matches("^[A-Za-z_$][A-Za-z0-9_$]*$")) {
|
|
||||||
throw new IllegalArgumentException(label + " must be a JS identifier: " + v);
|
|
||||||
}
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensures every element has a stable id.
|
* Ensures every element has a stable id.
|
||||||
*/
|
*/
|
||||||
@@ -334,77 +314,6 @@ public final class FxDomHost implements DomHost {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setProperty(String elementId, String property, Object value) {
|
|
||||||
final String id = requireId(elementId);
|
|
||||||
final String prop = requireJsIdentifier(property, "property");
|
|
||||||
final String jsValue = toJsLiteral(value);
|
|
||||||
|
|
||||||
FxThreadBridge.runAndWait(() -> {
|
|
||||||
requireDocument();
|
|
||||||
ensureJsEnabled();
|
|
||||||
|
|
||||||
String script = ""
|
|
||||||
+ "(function(){"
|
|
||||||
+ " var el = document.getElementById(" + toJsLiteral(id) + ");"
|
|
||||||
+ " if(!el) throw new Error('Unknown element id: ' + " + toJsLiteral(id) + ");"
|
|
||||||
+ " el[" + toJsLiteral(prop) + "] = " + jsValue + ";"
|
|
||||||
+ " return null;"
|
|
||||||
+ "})();";
|
|
||||||
|
|
||||||
engine.executeScript(script);
|
|
||||||
engine.setJavaScriptEnabled(false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object getProperty(String elementId, String property) {
|
|
||||||
final String id = requireId(elementId);
|
|
||||||
final String prop = requireJsIdentifier(property, "property");
|
|
||||||
|
|
||||||
return FxThreadBridge.callAndWait(() -> {
|
|
||||||
requireDocument();
|
|
||||||
ensureJsEnabled();
|
|
||||||
|
|
||||||
String script = ""
|
|
||||||
+ "(function(){"
|
|
||||||
+ " var el = document.getElementById(" + toJsLiteral(id) + ");"
|
|
||||||
+ " if(!el) throw new Error('Unknown element id: ' + " + toJsLiteral(id) + ");"
|
|
||||||
+ " return el[" + toJsLiteral(prop) + "];"
|
|
||||||
+ "})();";
|
|
||||||
|
|
||||||
Object ret = engine.executeScript(script);
|
|
||||||
engine.setJavaScriptEnabled(false);
|
|
||||||
return ret;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object call(String elementId, String method, Object... args) {
|
|
||||||
final String id = requireId(elementId);
|
|
||||||
final String m = requireJsIdentifier(method, "method");
|
|
||||||
final Object[] safeArgs = (args == null) ? new Object[0] : args.clone();
|
|
||||||
final String argvLiteral = toJsArrayLiteral(safeArgs);
|
|
||||||
|
|
||||||
return FxThreadBridge.callAndWait(() -> {
|
|
||||||
requireDocument();
|
|
||||||
ensureJsEnabled();
|
|
||||||
|
|
||||||
String script = ""
|
|
||||||
+ "(function(){"
|
|
||||||
+ " var el = document.getElementById(" + toJsLiteral(id) + ");"
|
|
||||||
+ " if(!el) throw new Error('Unknown element id: ' + " + toJsLiteral(id) + ");"
|
|
||||||
+ " var fn = el[" + toJsLiteral(m) + "];"
|
|
||||||
+ " if(typeof fn !== 'function') throw new Error('Not a function: ' + " + toJsLiteral(m) + ");"
|
|
||||||
+ " return fn.apply(el, " + argvLiteral + ");"
|
|
||||||
+ "})();";
|
|
||||||
|
|
||||||
Object ret = engine.executeScript(script);
|
|
||||||
engine.setJavaScriptEnabled(false);
|
|
||||||
return ret;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Exposes the current document (FX thread access required by callers).
|
* Exposes the current document (FX thread access required by callers).
|
||||||
*
|
*
|
||||||
@@ -430,13 +339,6 @@ public final class FxDomHost implements DomHost {
|
|||||||
return el;
|
return el;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ensureJsEnabled() {
|
|
||||||
// Required for HTMLMediaElement and DOM property access beyond W3C DOM.
|
|
||||||
if (!engine.isJavaScriptEnabled()) {
|
|
||||||
engine.setJavaScriptEnabled(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String generateUniqueId(Document doc) {
|
private String generateUniqueId(Document doc) {
|
||||||
while (true) {
|
while (true) {
|
||||||
String id = "__auto_" + autoIdSeq.getAndIncrement();
|
String id = "__auto_" + autoIdSeq.getAndIncrement();
|
||||||
@@ -451,104 +353,4 @@ public final class FxDomHost implements DomHost {
|
|||||||
}
|
}
|
||||||
return generateUniqueId(doc);
|
return generateUniqueId(doc);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a Java value into a safe JavaScript literal.
|
|
||||||
*
|
|
||||||
* <p>Supported:</p>
|
|
||||||
* <ul>
|
|
||||||
* <li>null</li>
|
|
||||||
* <li>String</li>
|
|
||||||
* <li>Boolean</li>
|
|
||||||
* <li>Number (finite)</li>
|
|
||||||
* </ul>
|
|
||||||
*
|
|
||||||
* @param v value
|
|
||||||
* @return JS literal
|
|
||||||
*/
|
|
||||||
private static String toJsLiteral(Object v) {
|
|
||||||
if (v == null) return "null";
|
|
||||||
|
|
||||||
if (v instanceof String s) {
|
|
||||||
return "'" + escapeJsSingleQuotedString(s) + "'";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (v instanceof Boolean b) {
|
|
||||||
return b ? "true" : "false";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (v instanceof Byte || v instanceof Short || v instanceof Integer || v instanceof Long) {
|
|
||||||
return String.valueOf(((Number) v).longValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (v instanceof Float || v instanceof Double) {
|
|
||||||
double d = ((Number) v).doubleValue();
|
|
||||||
if (!Double.isFinite(d)) {
|
|
||||||
throw new IllegalArgumentException("Non-finite number is not supported for JS literal: " + d);
|
|
||||||
}
|
|
||||||
// Use plain Java formatting; JS accepts it.
|
|
||||||
return Double.toString(d);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (v instanceof Number n) {
|
|
||||||
double d = n.doubleValue();
|
|
||||||
if (!Double.isFinite(d)) {
|
|
||||||
throw new IllegalArgumentException("Non-finite number is not supported for JS literal: " + d);
|
|
||||||
}
|
|
||||||
return Double.toString(d);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new IllegalArgumentException("Unsupported value type for JS literal: " + v.getClass().getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds a JavaScript array literal from Java arguments.
|
|
||||||
*
|
|
||||||
* @param args args (nullable)
|
|
||||||
* @return JS array literal
|
|
||||||
*/
|
|
||||||
private static String toJsArrayLiteral(Object[] args) {
|
|
||||||
if (args == null || args.length == 0) return "[]";
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
sb.append('[');
|
|
||||||
for (int i = 0; i < args.length; i++) {
|
|
||||||
if (i > 0) sb.append(',');
|
|
||||||
sb.append(toJsLiteral(args[i]));
|
|
||||||
}
|
|
||||||
sb.append(']');
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Escapes a string for inclusion inside a single-quoted JavaScript string literal.
|
|
||||||
*
|
|
||||||
* @param s raw string
|
|
||||||
* @return escaped string
|
|
||||||
*/
|
|
||||||
private static String escapeJsSingleQuotedString(String s) {
|
|
||||||
if (s == null || s.isEmpty()) return "";
|
|
||||||
StringBuilder out = new StringBuilder(s.length() + 16);
|
|
||||||
for (int i = 0; i < s.length(); i++) {
|
|
||||||
char c = s.charAt(i);
|
|
||||||
switch (c) {
|
|
||||||
case '\'' -> out.append("\\'");
|
|
||||||
case '\\' -> out.append("\\\\");
|
|
||||||
case '\n' -> out.append("\\n");
|
|
||||||
case '\r' -> out.append("\\r");
|
|
||||||
case '\t' -> out.append("\\t");
|
|
||||||
case '\u0000' -> out.append("\\0");
|
|
||||||
case '\u2028' -> out.append("\\u2028");
|
|
||||||
case '\u2029' -> out.append("\\u2029");
|
|
||||||
default -> {
|
|
||||||
// Keep printable chars; escape other control chars.
|
|
||||||
if (c < 0x20) {
|
|
||||||
out.append(String.format("\\u%04x", (int) c));
|
|
||||||
} else {
|
|
||||||
out.append(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out.toString();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
package org.openautonomousconnection.luascript.fx;
|
|
||||||
|
|
||||||
import javafx.scene.web.WebEngine;
|
|
||||||
import org.openautonomousconnection.luascript.hosts.GeometryHost;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Geometry/scroll host via internal JS bridge.
|
|
||||||
*/
|
|
||||||
public final class FxGeometryHost implements GeometryHost {
|
|
||||||
|
|
||||||
private final WebEngine engine;
|
|
||||||
private final FxDomHost dom;
|
|
||||||
|
|
||||||
public FxGeometryHost(WebEngine engine, FxDomHost dom) {
|
|
||||||
this.engine = Objects.requireNonNull(engine, "engine");
|
|
||||||
this.dom = Objects.requireNonNull(dom, "dom");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, Object> getBoundingClientRect(String elementId) {
|
|
||||||
Objects.requireNonNull(elementId, "elementId");
|
|
||||||
return FxThreadBridge.callAndWait(() -> FxWebBridge.callWithJs(engine, () -> {
|
|
||||||
dom.requireDocument();
|
|
||||||
|
|
||||||
String script = ""
|
|
||||||
+ "(function(){"
|
|
||||||
+ " var id=" + FxWebBridge.toJsLiteral(elementId) + ";"
|
|
||||||
+ " var el=document.getElementById(id);"
|
|
||||||
+ " if(!el) throw new Error('Unknown element id: '+id);"
|
|
||||||
+ " var r=el.getBoundingClientRect();"
|
|
||||||
+ " return {"
|
|
||||||
+ " x:r.x, y:r.y, width:r.width, height:r.height,"
|
|
||||||
+ " top:r.top, left:r.left, right:r.right, bottom:r.bottom"
|
|
||||||
+ " };"
|
|
||||||
+ "})();";
|
|
||||||
|
|
||||||
Object ret = engine.executeScript(script);
|
|
||||||
return FxWebBridge.toStringObjectMap(ret);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, Object> getViewport() {
|
|
||||||
return FxThreadBridge.callAndWait(() -> FxWebBridge.callWithJs(engine, () -> {
|
|
||||||
dom.requireDocument();
|
|
||||||
|
|
||||||
String script = ""
|
|
||||||
+ "(function(){"
|
|
||||||
+ " return {"
|
|
||||||
+ " width: window.innerWidth || 0,"
|
|
||||||
+ " height: window.innerHeight || 0,"
|
|
||||||
+ " devicePixelRatio: window.devicePixelRatio || 1,"
|
|
||||||
+ " scrollX: window.scrollX || 0,"
|
|
||||||
+ " scrollY: window.scrollY || 0"
|
|
||||||
+ " };"
|
|
||||||
+ "})();";
|
|
||||||
|
|
||||||
Object ret = engine.executeScript(script);
|
|
||||||
return FxWebBridge.toStringObjectMap(ret);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void scrollTo(double x, double y) {
|
|
||||||
FxThreadBridge.runAndWait(() -> FxWebBridge.runWithJs(engine, () -> {
|
|
||||||
dom.requireDocument();
|
|
||||||
|
|
||||||
String script = ""
|
|
||||||
+ "(function(){"
|
|
||||||
+ " window.scrollTo(" + FxWebBridge.toJsLiteral(x) + "," + FxWebBridge.toJsLiteral(y) + ");"
|
|
||||||
+ " return null;"
|
|
||||||
+ "})();";
|
|
||||||
engine.executeScript(script);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void scrollIntoView(String elementId, String align) {
|
|
||||||
Objects.requireNonNull(elementId, "elementId");
|
|
||||||
String a = FxWebBridge.normalizeAlign(align);
|
|
||||||
|
|
||||||
FxThreadBridge.runAndWait(() -> FxWebBridge.runWithJs(engine, () -> {
|
|
||||||
dom.requireDocument();
|
|
||||||
|
|
||||||
String script = ""
|
|
||||||
+ "(function(){"
|
|
||||||
+ " var id=" + FxWebBridge.toJsLiteral(elementId) + ";"
|
|
||||||
+ " var el=document.getElementById(id);"
|
|
||||||
+ " if(!el) throw new Error('Unknown element id: '+id);"
|
|
||||||
+ " el.scrollIntoView({block:" + FxWebBridge.toJsLiteral(a) + ", inline:" + FxWebBridge.toJsLiteral(a) + "});"
|
|
||||||
+ " return null;"
|
|
||||||
+ "})();";
|
|
||||||
engine.executeScript(script);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,270 +0,0 @@
|
|||||||
package org.openautonomousconnection.luascript.fx;
|
|
||||||
|
|
||||||
import javafx.scene.web.WebEngine;
|
|
||||||
import org.openautonomousconnection.luascript.hosts.ImageHost;
|
|
||||||
import org.w3c.dom.Document;
|
|
||||||
import org.w3c.dom.Element;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.net.URLConnection;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ImageHost implementation that renders images inside the WebView DOM using an {@code <img>} element.
|
|
||||||
*
|
|
||||||
* <p>No JavaFX overlay panes are used. Positioning is done by setting CSS via {@code style} attribute.</p>
|
|
||||||
*
|
|
||||||
* <p>For {@code web://} URLs this host downloads the resource via {@link URLConnection} (your installed handler),
|
|
||||||
* stores it as a temporary file, then sets {@code src=file://...} because WebView won't reliably load custom schemes.</p>
|
|
||||||
*/
|
|
||||||
public final class FxImageHost implements ImageHost {
|
|
||||||
|
|
||||||
private static final Set<String> IMAGE_EXTENSIONS = Set.of(
|
|
||||||
"png", "jpg", "jpeg", "ico", "bmp", "avif", "heif", "heic", "webp"
|
|
||||||
);
|
|
||||||
|
|
||||||
private static final String IMG_ID = "__oac_image";
|
|
||||||
|
|
||||||
private final WebEngine engine;
|
|
||||||
private final FxDomHost dom;
|
|
||||||
|
|
||||||
private volatile double x;
|
|
||||||
private volatile double y;
|
|
||||||
private volatile double w = 320;
|
|
||||||
private volatile double h = 240;
|
|
||||||
|
|
||||||
private volatile boolean preserveRatio = true;
|
|
||||||
private volatile boolean smooth = true;
|
|
||||||
private volatile double opacity = 1.0;
|
|
||||||
|
|
||||||
private volatile Path lastTempFile;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new image host.
|
|
||||||
*
|
|
||||||
* @param engine web engine
|
|
||||||
* @param dom fx dom host
|
|
||||||
*/
|
|
||||||
public FxImageHost(WebEngine engine, FxDomHost dom) {
|
|
||||||
this.engine = Objects.requireNonNull(engine, "engine");
|
|
||||||
this.dom = Objects.requireNonNull(dom, "dom");
|
|
||||||
// Ensure element exists once document is present; if not yet loaded, calls will create on demand.
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void showFile(File file) {
|
|
||||||
Objects.requireNonNull(file, "file");
|
|
||||||
if (!file.isFile()) {
|
|
||||||
throw new IllegalArgumentException("Image file not found: " + file.getAbsolutePath());
|
|
||||||
}
|
|
||||||
|
|
||||||
String ext = MediaExtensions.extensionOf(file.getName());
|
|
||||||
ensureAllowed(ext, "image", file.getName());
|
|
||||||
|
|
||||||
URI uri = file.toURI();
|
|
||||||
setSource(uri.toString(), null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void showUrl(String url) {
|
|
||||||
Objects.requireNonNull(url, "url");
|
|
||||||
String u = url.trim();
|
|
||||||
if (u.isEmpty()) throw new IllegalArgumentException("URL is empty");
|
|
||||||
|
|
||||||
URI uri = URI.create(u);
|
|
||||||
String ext = MediaExtensions.extensionOf(uri.getPath());
|
|
||||||
ensureAllowed(ext, "image", uri.getPath());
|
|
||||||
|
|
||||||
String scheme = (uri.getScheme() == null) ? "" : uri.getScheme().toLowerCase(Locale.ROOT);
|
|
||||||
|
|
||||||
if ("http".equals(scheme) || "https".equals(scheme) || "file".equals(scheme)) {
|
|
||||||
setSource(uri.toString(), null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ("web".equals(scheme)) {
|
|
||||||
Path tmp = null;
|
|
||||||
try {
|
|
||||||
tmp = downloadToTempFile(u, ext, "oac-img-");
|
|
||||||
rememberTemp(tmp);
|
|
||||||
setSource(tmp.toUri().toString(), tmp);
|
|
||||||
return;
|
|
||||||
} catch (IOException e) {
|
|
||||||
safeDelete(tmp);
|
|
||||||
throw new RuntimeException("Failed to load web image: " + e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new IllegalArgumentException("Unsupported scheme: " + scheme);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void hide() {
|
|
||||||
FxThreadBridge.runAndWait(() -> {
|
|
||||||
Element img = ensureImgElement();
|
|
||||||
img.setAttribute("style", mergeStyle(baseStyle(false), rectStyle(), visualStyle()));
|
|
||||||
img.removeAttribute("src");
|
|
||||||
});
|
|
||||||
|
|
||||||
Path tmp = this.lastTempFile;
|
|
||||||
this.lastTempFile = null;
|
|
||||||
safeDelete(tmp);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setRect(double x, double y, double w, double h) {
|
|
||||||
this.x = x;
|
|
||||||
this.y = y;
|
|
||||||
this.w = Math.max(0.0, w);
|
|
||||||
this.h = Math.max(0.0, h);
|
|
||||||
applyStyle(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setPreserveRatio(boolean preserve) {
|
|
||||||
this.preserveRatio = preserve;
|
|
||||||
applyStyle(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setSmooth(boolean smooth) {
|
|
||||||
this.smooth = smooth;
|
|
||||||
applyStyle(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setOpacity(double opacity) {
|
|
||||||
this.opacity = clamp01(opacity);
|
|
||||||
applyStyle(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setSource(String src, Path tempFileKept) {
|
|
||||||
FxThreadBridge.runAndWait(() -> {
|
|
||||||
Element img = ensureImgElement();
|
|
||||||
img.setAttribute("src", src);
|
|
||||||
img.setAttribute("style", mergeStyle(baseStyle(true), rectStyle(), visualStyle()));
|
|
||||||
});
|
|
||||||
|
|
||||||
// Keep temp file reference (so previous gets cleaned up)
|
|
||||||
if (tempFileKept != null) {
|
|
||||||
rememberTemp(tempFileKept);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void applyStyle(boolean visible) {
|
|
||||||
FxThreadBridge.runAndWait(() -> {
|
|
||||||
Element img = ensureImgElement();
|
|
||||||
String src = img.getAttribute("src");
|
|
||||||
boolean show = visible && src != null && !src.isBlank();
|
|
||||||
img.setAttribute("style", mergeStyle(baseStyle(show), rectStyle(), visualStyle()));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private Element ensureImgElement() {
|
|
||||||
Document doc = dom.requireDocument();
|
|
||||||
Element img = doc.getElementById(IMG_ID);
|
|
||||||
if (img != null) return img;
|
|
||||||
|
|
||||||
Element body = (Element) doc.getElementsByTagName("body").item(0);
|
|
||||||
if (body == null) throw new IllegalStateException("No <body> element available");
|
|
||||||
|
|
||||||
img = doc.createElement("img");
|
|
||||||
img.setAttribute("id", IMG_ID);
|
|
||||||
img.setAttribute("draggable", "false");
|
|
||||||
img.setAttribute("alt", "");
|
|
||||||
|
|
||||||
body.appendChild(img);
|
|
||||||
return img;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String baseStyle(boolean visible) {
|
|
||||||
return "position:fixed;"
|
|
||||||
+ "left:0;top:0;"
|
|
||||||
+ "z-index:2147483647;"
|
|
||||||
+ "display:" + (visible ? "block" : "none") + ";";
|
|
||||||
}
|
|
||||||
|
|
||||||
private String rectStyle() {
|
|
||||||
return "left:" + x + "px;"
|
|
||||||
+ "top:" + y + "px;"
|
|
||||||
+ "width:" + w + "px;"
|
|
||||||
+ "height:" + h + "px;";
|
|
||||||
}
|
|
||||||
|
|
||||||
private String visualStyle() {
|
|
||||||
String fit;
|
|
||||||
if (preserveRatio) {
|
|
||||||
fit = "object-fit:contain;";
|
|
||||||
} else {
|
|
||||||
fit = "object-fit:fill;";
|
|
||||||
}
|
|
||||||
|
|
||||||
String smoothing = smooth ? "image-rendering:auto;" : "image-rendering:pixelated;";
|
|
||||||
return fit + smoothing + "opacity:" + opacity + ";";
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String mergeStyle(String... parts) {
|
|
||||||
StringBuilder sb = new StringBuilder(256);
|
|
||||||
for (String p : parts) {
|
|
||||||
if (p == null || p.isBlank()) continue;
|
|
||||||
String s = p.trim();
|
|
||||||
if (!s.endsWith(";")) s += ";";
|
|
||||||
sb.append(s);
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Path downloadToTempFile(String url, String ext, String prefix) throws IOException {
|
|
||||||
URL u = new URL(url);
|
|
||||||
URLConnection con = u.openConnection();
|
|
||||||
con.setUseCaches(false);
|
|
||||||
|
|
||||||
String safeExt = (ext == null || ext.isBlank()) ? "bin" : ext.toLowerCase(Locale.ROOT);
|
|
||||||
Path tmp = Files.createTempFile(prefix, "." + safeExt);
|
|
||||||
|
|
||||||
try (InputStream in = con.getInputStream()) {
|
|
||||||
Files.copy(in, tmp, java.nio.file.StandardCopyOption.REPLACE_EXISTING);
|
|
||||||
} catch (IOException e) {
|
|
||||||
safeDelete(tmp);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
|
|
||||||
return tmp;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void rememberTemp(Path tmp) {
|
|
||||||
Path prev = this.lastTempFile;
|
|
||||||
this.lastTempFile = tmp;
|
|
||||||
safeDelete(prev);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void safeDelete(Path p) {
|
|
||||||
if (p == null) return;
|
|
||||||
try {
|
|
||||||
Files.deleteIfExists(p);
|
|
||||||
} catch (IOException ignored) {
|
|
||||||
// best-effort
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ensureAllowed(String ext, String kind, String source) {
|
|
||||||
String e = (ext == null) ? "" : ext.toLowerCase(Locale.ROOT);
|
|
||||||
if (e.isEmpty() || !IMAGE_EXTENSIONS.contains(e)) {
|
|
||||||
throw new IllegalArgumentException("Unsupported " + kind + " format '" + e + "' for: " + source);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static double clamp01(double v) {
|
|
||||||
if (v < 0.0) return 0.0;
|
|
||||||
if (v > 1.0) return 1.0;
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,246 +0,0 @@
|
|||||||
package org.openautonomousconnection.luascript.fx;
|
|
||||||
|
|
||||||
import javafx.scene.web.WebEngine;
|
|
||||||
import netscape.javascript.JSObject;
|
|
||||||
import org.openautonomousconnection.luascript.hosts.ObserverHost;
|
|
||||||
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Observer host implemented via JS observers calling back into Java.
|
|
||||||
*
|
|
||||||
* <p>Requires JavaScript enabled (bridge only).</p>
|
|
||||||
*/
|
|
||||||
public final class FxObserverHost implements ObserverHost {
|
|
||||||
|
|
||||||
private final WebEngine engine;
|
|
||||||
private final FxDomHost dom;
|
|
||||||
|
|
||||||
private volatile ObserverCallback callback;
|
|
||||||
|
|
||||||
private final ConcurrentHashMap<String, Boolean> mutationObserved = new ConcurrentHashMap<>();
|
|
||||||
private final ConcurrentHashMap<String, Boolean> resizeObserved = new ConcurrentHashMap<>();
|
|
||||||
private final ConcurrentHashMap<String, Boolean> intersectionObserved = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
public FxObserverHost(WebEngine engine, FxDomHost dom) {
|
|
||||||
this.engine = Objects.requireNonNull(engine, "engine");
|
|
||||||
this.dom = Objects.requireNonNull(dom, "dom");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setCallback(ObserverCallback callback) {
|
|
||||||
this.callback = callback;
|
|
||||||
FxThreadBridge.runAndWait(this::installBridge);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void observeMutations(String elementId, boolean subtree, boolean attributes, boolean childList, boolean characterData) {
|
|
||||||
Objects.requireNonNull(elementId, "elementId");
|
|
||||||
FxThreadBridge.runAndWait(() -> {
|
|
||||||
dom.requireDocument();
|
|
||||||
FxWebBridge.ensureJsEnabled(engine);
|
|
||||||
installBridge();
|
|
||||||
|
|
||||||
String script = ""
|
|
||||||
+ "(function(){"
|
|
||||||
+ " var id=" + FxWebBridge.toJsLiteral(elementId) + ";"
|
|
||||||
+ " var el=document.getElementById(id);"
|
|
||||||
+ " if(!el) throw new Error('Unknown element id: '+id);"
|
|
||||||
+ " window.__oac_obs = window.__oac_obs || {};"
|
|
||||||
+ " window.__oac_obs.muts = window.__oac_obs.muts || new Map();"
|
|
||||||
+ " if(window.__oac_obs.muts.has(id)) return null;"
|
|
||||||
+ " var cfg={subtree:" + (subtree ? "true" : "false")
|
|
||||||
+ " ,attributes:" + (attributes ? "true" : "false")
|
|
||||||
+ " ,childList:" + (childList ? "true" : "false")
|
|
||||||
+ " ,characterData:" + (characterData ? "true" : "false")
|
|
||||||
+ " };"
|
|
||||||
+ " var mo=new MutationObserver(function(muts){"
|
|
||||||
+ " try{"
|
|
||||||
+ " var payload={count:muts.length};"
|
|
||||||
+ " window.__oac_bridge.emit('mutation', id, JSON.stringify(payload));"
|
|
||||||
+ " }catch(e){}"
|
|
||||||
+ " });"
|
|
||||||
+ " mo.observe(el,cfg);"
|
|
||||||
+ " window.__oac_obs.muts.set(id, mo);"
|
|
||||||
+ " return null;"
|
|
||||||
+ "})();";
|
|
||||||
engine.executeScript(script);
|
|
||||||
engine.setJavaScriptEnabled(false);
|
|
||||||
mutationObserved.put(elementId, true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void unobserveMutations(String elementId) {
|
|
||||||
Objects.requireNonNull(elementId, "elementId");
|
|
||||||
FxThreadBridge.runAndWait(() -> {
|
|
||||||
dom.requireDocument();
|
|
||||||
FxWebBridge.ensureJsEnabled(engine);
|
|
||||||
String script = ""
|
|
||||||
+ "(function(){"
|
|
||||||
+ " var id=" + FxWebBridge.toJsLiteral(elementId) + ";"
|
|
||||||
+ " if(!window.__oac_obs || !window.__oac_obs.muts) return null;"
|
|
||||||
+ " var mo=window.__oac_obs.muts.get(id);"
|
|
||||||
+ " if(mo){ mo.disconnect(); window.__oac_obs.muts.delete(id); }"
|
|
||||||
+ " return null;"
|
|
||||||
+ "})();";
|
|
||||||
engine.executeScript(script);
|
|
||||||
engine.setJavaScriptEnabled(false);
|
|
||||||
mutationObserved.remove(elementId);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void observeResize(String elementId) {
|
|
||||||
Objects.requireNonNull(elementId, "elementId");
|
|
||||||
FxThreadBridge.runAndWait(() -> {
|
|
||||||
dom.requireDocument();
|
|
||||||
FxWebBridge.ensureJsEnabled(engine);
|
|
||||||
installBridge();
|
|
||||||
|
|
||||||
String script = ""
|
|
||||||
+ "(function(){"
|
|
||||||
+ " var id=" + FxWebBridge.toJsLiteral(elementId) + ";"
|
|
||||||
+ " var el=document.getElementById(id);"
|
|
||||||
+ " if(!el) throw new Error('Unknown element id: '+id);"
|
|
||||||
+ " if(!('ResizeObserver' in window)) return null;"
|
|
||||||
+ " window.__oac_obs = window.__oac_obs || {};"
|
|
||||||
+ " window.__oac_obs.res = window.__oac_obs.res || new Map();"
|
|
||||||
+ " if(window.__oac_obs.res.has(id)) return null;"
|
|
||||||
+ " var ro=new ResizeObserver(function(entries){"
|
|
||||||
+ " try{"
|
|
||||||
+ " var r=entries && entries[0] && entries[0].contentRect;"
|
|
||||||
+ " var payload=r?{x:r.x,y:r.y,width:r.width,height:r.height}:{count:(entries?entries.length:0)};"
|
|
||||||
+ " window.__oac_bridge.emit('resize', id, JSON.stringify(payload));"
|
|
||||||
+ " }catch(e){}"
|
|
||||||
+ " });"
|
|
||||||
+ " ro.observe(el);"
|
|
||||||
+ " window.__oac_obs.res.set(id, ro);"
|
|
||||||
+ " return null;"
|
|
||||||
+ "})();";
|
|
||||||
engine.executeScript(script);
|
|
||||||
engine.setJavaScriptEnabled(false);
|
|
||||||
resizeObserved.put(elementId, true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void unobserveResize(String elementId) {
|
|
||||||
Objects.requireNonNull(elementId, "elementId");
|
|
||||||
FxThreadBridge.runAndWait(() -> {
|
|
||||||
dom.requireDocument();
|
|
||||||
FxWebBridge.ensureJsEnabled(engine);
|
|
||||||
String script = ""
|
|
||||||
+ "(function(){"
|
|
||||||
+ " var id=" + FxWebBridge.toJsLiteral(elementId) + ";"
|
|
||||||
+ " if(!window.__oac_obs || !window.__oac_obs.res) return null;"
|
|
||||||
+ " var ro=window.__oac_obs.res.get(id);"
|
|
||||||
+ " if(ro){ ro.disconnect(); window.__oac_obs.res.delete(id); }"
|
|
||||||
+ " return null;"
|
|
||||||
+ "})();";
|
|
||||||
engine.executeScript(script);
|
|
||||||
engine.setJavaScriptEnabled(false);
|
|
||||||
resizeObserved.remove(elementId);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void observeIntersection(String elementId, double threshold) {
|
|
||||||
Objects.requireNonNull(elementId, "elementId");
|
|
||||||
double t = threshold;
|
|
||||||
if (t < 0.0) t = 0.0;
|
|
||||||
if (t > 1.0) t = 1.0;
|
|
||||||
|
|
||||||
double finalT = t;
|
|
||||||
FxThreadBridge.runAndWait(() -> {
|
|
||||||
dom.requireDocument();
|
|
||||||
FxWebBridge.ensureJsEnabled(engine);
|
|
||||||
installBridge();
|
|
||||||
|
|
||||||
String script = ""
|
|
||||||
+ "(function(){"
|
|
||||||
+ " var id=" + FxWebBridge.toJsLiteral(elementId) + ";"
|
|
||||||
+ " var el=document.getElementById(id);"
|
|
||||||
+ " if(!el) throw new Error('Unknown element id: '+id);"
|
|
||||||
+ " if(!('IntersectionObserver' in window)) return null;"
|
|
||||||
+ " window.__oac_obs = window.__oac_obs || {};"
|
|
||||||
+ " window.__oac_obs.int = window.__oac_obs.int || new Map();"
|
|
||||||
+ " if(window.__oac_obs.int.has(id)) return null;"
|
|
||||||
+ " var io=new IntersectionObserver(function(entries){"
|
|
||||||
+ " try{"
|
|
||||||
+ " var e=entries && entries[0];"
|
|
||||||
+ " var payload=e?{"
|
|
||||||
+ " isIntersecting:!!e.isIntersecting,"
|
|
||||||
+ " ratio:(e.intersectionRatio||0)"
|
|
||||||
+ " }:{count:(entries?entries.length:0)};"
|
|
||||||
+ " window.__oac_bridge.emit('intersection', id, JSON.stringify(payload));"
|
|
||||||
+ " }catch(ex){}"
|
|
||||||
+ " }, {threshold:" + FxWebBridge.toJsLiteral(finalT) + "});"
|
|
||||||
+ " io.observe(el);"
|
|
||||||
+ " window.__oac_obs.int.set(id, io);"
|
|
||||||
+ " return null;"
|
|
||||||
+ "})();";
|
|
||||||
engine.executeScript(script);
|
|
||||||
engine.setJavaScriptEnabled(false);
|
|
||||||
intersectionObserved.put(elementId, true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void unobserveIntersection(String elementId) {
|
|
||||||
Objects.requireNonNull(elementId, "elementId");
|
|
||||||
FxThreadBridge.runAndWait(() -> {
|
|
||||||
dom.requireDocument();
|
|
||||||
FxWebBridge.ensureJsEnabled(engine);
|
|
||||||
String script = ""
|
|
||||||
+ "(function(){"
|
|
||||||
+ " var id=" + FxWebBridge.toJsLiteral(elementId) + ";"
|
|
||||||
+ " if(!window.__oac_obs || !window.__oac_obs.int) return null;"
|
|
||||||
+ " var io=window.__oac_obs.int.get(id);"
|
|
||||||
+ " if(io){ io.disconnect(); window.__oac_obs.int.delete(id); }"
|
|
||||||
+ " return null;"
|
|
||||||
+ "})();";
|
|
||||||
engine.executeScript(script);
|
|
||||||
engine.setJavaScriptEnabled(false);
|
|
||||||
intersectionObserved.remove(elementId);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void installBridge() {
|
|
||||||
dom.requireDocument();
|
|
||||||
FxWebBridge.ensureJsEnabled(engine);
|
|
||||||
|
|
||||||
JSObject win = (JSObject) engine.executeScript("window");
|
|
||||||
win.setMember("__oac_bridge", new Bridge());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Object exposed to JS.
|
|
||||||
*/
|
|
||||||
public final class Bridge {
|
|
||||||
/**
|
|
||||||
* Emits observer events from JS to Java.
|
|
||||||
*
|
|
||||||
* @param type observer type
|
|
||||||
* @param targetId element id
|
|
||||||
* @param json payload JSON string
|
|
||||||
*/
|
|
||||||
public void emit(String type, String targetId, String json) {
|
|
||||||
ObserverCallback cb = callback;
|
|
||||||
if (cb == null) return;
|
|
||||||
|
|
||||||
Map<String, Object> payload = new LinkedHashMap<>();
|
|
||||||
payload.put("json", json == null ? "" : json);
|
|
||||||
|
|
||||||
try {
|
|
||||||
cb.onEvent(type == null ? "" : type, targetId == null ? "" : targetId, payload);
|
|
||||||
} catch (RuntimeException ex) {
|
|
||||||
System.err.println("[observer] callback failed: " + ex.getMessage());
|
|
||||||
ex.printStackTrace(System.err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,127 +0,0 @@
|
|||||||
package org.openautonomousconnection.luascript.fx;
|
|
||||||
|
|
||||||
import javafx.animation.AnimationTimer;
|
|
||||||
import javafx.application.Platform;
|
|
||||||
import org.openautonomousconnection.luascript.hosts.SchedulerHost;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.*;
|
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* JavaFX-based scheduler providing setTimeout/setInterval/requestAnimationFrame.
|
|
||||||
*/
|
|
||||||
public final class FxSchedulerHost implements SchedulerHost, AutoCloseable {
|
|
||||||
|
|
||||||
private final ScheduledExecutorService scheduler;
|
|
||||||
private final AtomicLong seq = new AtomicLong(1);
|
|
||||||
|
|
||||||
private final ConcurrentHashMap<Long, ScheduledFuture<?>> scheduled = new ConcurrentHashMap<>();
|
|
||||||
private final ConcurrentHashMap<Long, Runnable> rafCallbacks = new ConcurrentHashMap<>();
|
|
||||||
private final Set<Long> canceledRaf = ConcurrentHashMap.newKeySet();
|
|
||||||
|
|
||||||
private final AnimationTimer rafTimer;
|
|
||||||
|
|
||||||
public FxSchedulerHost() {
|
|
||||||
this.scheduler = Executors.newSingleThreadScheduledExecutor(r -> {
|
|
||||||
Thread t = new Thread(r, "oac-fx-scheduler");
|
|
||||||
t.setDaemon(true);
|
|
||||||
return t;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.rafTimer = new AnimationTimer() {
|
|
||||||
@Override
|
|
||||||
public void handle(long now) {
|
|
||||||
// Drain RAF callbacks once per frame.
|
|
||||||
if (rafCallbacks.isEmpty()) return;
|
|
||||||
|
|
||||||
Map<Long, Runnable> snap = new ConcurrentHashMap<>(rafCallbacks);
|
|
||||||
rafCallbacks.clear();
|
|
||||||
|
|
||||||
for (Map.Entry<Long, Runnable> e : snap.entrySet()) {
|
|
||||||
long id = e.getKey();
|
|
||||||
Runnable cb = e.getValue();
|
|
||||||
if (canceledRaf.remove(id)) continue;
|
|
||||||
|
|
||||||
try {
|
|
||||||
cb.run();
|
|
||||||
} catch (RuntimeException ex) {
|
|
||||||
System.err.println("[scheduler.raf] callback failed: " + ex.getMessage());
|
|
||||||
ex.printStackTrace(System.err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Platform.runLater(rafTimer::start);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long setTimeout(long delayMillis, Runnable callback) {
|
|
||||||
if (delayMillis < 0) delayMillis = 0;
|
|
||||||
Runnable cb = Objects.requireNonNull(callback, "callback");
|
|
||||||
|
|
||||||
long id = seq.getAndIncrement();
|
|
||||||
ScheduledFuture<?> f = scheduler.schedule(() -> safeRun(cb, "timeout"), delayMillis, TimeUnit.MILLISECONDS);
|
|
||||||
scheduled.put(id, f);
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long setInterval(long intervalMillis, Runnable callback) {
|
|
||||||
if (intervalMillis <= 0) throw new IllegalArgumentException("intervalMillis must be > 0");
|
|
||||||
Runnable cb = Objects.requireNonNull(callback, "callback");
|
|
||||||
|
|
||||||
long id = seq.getAndIncrement();
|
|
||||||
ScheduledFuture<?> f = scheduler.scheduleAtFixedRate(() -> safeRun(cb, "interval"), intervalMillis, intervalMillis, TimeUnit.MILLISECONDS);
|
|
||||||
scheduled.put(id, f);
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean clear(long handle) {
|
|
||||||
ScheduledFuture<?> f = scheduled.remove(handle);
|
|
||||||
if (f == null) return false;
|
|
||||||
return f.cancel(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long requestAnimationFrame(Runnable callback) {
|
|
||||||
Runnable cb = Objects.requireNonNull(callback, "callback");
|
|
||||||
long id = seq.getAndIncrement();
|
|
||||||
rafCallbacks.put(id, cb);
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean cancelAnimationFrame(long handle) {
|
|
||||||
// If already queued, mark as canceled.
|
|
||||||
boolean existed = rafCallbacks.remove(handle) != null;
|
|
||||||
canceledRaf.add(handle);
|
|
||||||
return existed;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void safeRun(Runnable cb, String kind) {
|
|
||||||
try {
|
|
||||||
cb.run();
|
|
||||||
} catch (RuntimeException ex) {
|
|
||||||
System.err.println("[scheduler." + kind + "] callback failed: " + ex.getMessage());
|
|
||||||
ex.printStackTrace(System.err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
try {
|
|
||||||
Platform.runLater(rafTimer::stop);
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
scheduler.shutdownNow();
|
|
||||||
scheduled.clear();
|
|
||||||
rafCallbacks.clear();
|
|
||||||
canceledRaf.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
package org.openautonomousconnection.luascript.fx;
|
|
||||||
|
|
||||||
import javafx.scene.web.WebEngine;
|
|
||||||
import org.openautonomousconnection.luascript.hosts.SelectorHost;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* CSS selector host implemented via internal JS bridge.
|
|
||||||
*/
|
|
||||||
public final class FxSelectorHost implements SelectorHost {
|
|
||||||
|
|
||||||
private final WebEngine engine;
|
|
||||||
private final FxDomHost dom;
|
|
||||||
|
|
||||||
public FxSelectorHost(WebEngine engine, FxDomHost dom) {
|
|
||||||
this.engine = Objects.requireNonNull(engine, "engine");
|
|
||||||
this.dom = Objects.requireNonNull(dom, "dom");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String querySelector(String selector) {
|
|
||||||
Objects.requireNonNull(selector, "selector");
|
|
||||||
return FxThreadBridge.callAndWait(() -> FxWebBridge.callWithJs(engine, () -> {
|
|
||||||
dom.requireDocument();
|
|
||||||
dom.ensureAllElementsHaveId();
|
|
||||||
|
|
||||||
String script = ""
|
|
||||||
+ "(function(){"
|
|
||||||
+ " var sel = " + FxWebBridge.toJsLiteral(selector) + ";"
|
|
||||||
+ " var el = document.querySelector(sel);"
|
|
||||||
+ " if(!el) return null;"
|
|
||||||
+ " if(!el.id){"
|
|
||||||
+ " el.id='__auto_' + Math.floor(Math.random()*1e18).toString(36);"
|
|
||||||
+ " }"
|
|
||||||
+ " return el.id;"
|
|
||||||
+ "})();";
|
|
||||||
|
|
||||||
Object ret = engine.executeScript(script);
|
|
||||||
return ret == null ? null : String.valueOf(ret);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<String> querySelectorAll(String selector) {
|
|
||||||
Objects.requireNonNull(selector, "selector");
|
|
||||||
return FxThreadBridge.callAndWait(() -> FxWebBridge.callWithJs(engine, () -> {
|
|
||||||
dom.requireDocument();
|
|
||||||
dom.ensureAllElementsHaveId();
|
|
||||||
|
|
||||||
String script = ""
|
|
||||||
+ "(function(){"
|
|
||||||
+ " var sel=" + FxWebBridge.toJsLiteral(selector) + ";"
|
|
||||||
+ " var els=document.querySelectorAll(sel);"
|
|
||||||
+ " var out=[];"
|
|
||||||
+ " for(var i=0;i<els.length;i++){"
|
|
||||||
+ " var el=els[i];"
|
|
||||||
+ " if(!el.id){ el.id='__auto_' + Math.floor(Math.random()*1e18).toString(36); }"
|
|
||||||
+ " out.push(el.id);"
|
|
||||||
+ " }"
|
|
||||||
+ " return out;"
|
|
||||||
+ "})();";
|
|
||||||
|
|
||||||
Object ret = engine.executeScript(script);
|
|
||||||
return FxWebBridge.toStringList(ret);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean matches(String elementId, String selector) {
|
|
||||||
Objects.requireNonNull(elementId, "elementId");
|
|
||||||
Objects.requireNonNull(selector, "selector");
|
|
||||||
return FxThreadBridge.callAndWait(() -> FxWebBridge.callWithJs(engine, () -> {
|
|
||||||
dom.requireDocument();
|
|
||||||
|
|
||||||
String script = ""
|
|
||||||
+ "(function(){"
|
|
||||||
+ " var id=" + FxWebBridge.toJsLiteral(elementId) + ";"
|
|
||||||
+ " var sel=" + FxWebBridge.toJsLiteral(selector) + ";"
|
|
||||||
+ " var el=document.getElementById(id);"
|
|
||||||
+ " if(!el) throw new Error('Unknown element id: '+id);"
|
|
||||||
+ " return !!el.matches(sel);"
|
|
||||||
+ "})();";
|
|
||||||
|
|
||||||
Object ret = engine.executeScript(script);
|
|
||||||
return ret instanceof Boolean b ? b : Boolean.parseBoolean(String.valueOf(ret));
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String closest(String elementId, String selector) {
|
|
||||||
Objects.requireNonNull(elementId, "elementId");
|
|
||||||
Objects.requireNonNull(selector, "selector");
|
|
||||||
return FxThreadBridge.callAndWait(() -> FxWebBridge.callWithJs(engine, () -> {
|
|
||||||
dom.requireDocument();
|
|
||||||
|
|
||||||
String script = ""
|
|
||||||
+ "(function(){"
|
|
||||||
+ " var id=" + FxWebBridge.toJsLiteral(elementId) + ";"
|
|
||||||
+ " var sel=" + FxWebBridge.toJsLiteral(selector) + ";"
|
|
||||||
+ " var el=document.getElementById(id);"
|
|
||||||
+ " if(!el) throw new Error('Unknown element id: '+id);"
|
|
||||||
+ " var c=el.closest(sel);"
|
|
||||||
+ " if(!c) return null;"
|
|
||||||
+ " if(!c.id){ c.id='__auto_' + Math.floor(Math.random()*1e18).toString(36); }"
|
|
||||||
+ " return c.id;"
|
|
||||||
+ "})();";
|
|
||||||
|
|
||||||
Object ret = engine.executeScript(script);
|
|
||||||
return ret == null ? null : String.valueOf(ret);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,157 +0,0 @@
|
|||||||
package org.openautonomousconnection.luascript.fx;
|
|
||||||
|
|
||||||
import javafx.scene.web.WebEngine;
|
|
||||||
import org.openautonomousconnection.luascript.hosts.StorageHost;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Storage host backed by localStorage/sessionStorage via JS bridge.
|
|
||||||
*/
|
|
||||||
public final class FxStorageHost implements StorageHost {
|
|
||||||
|
|
||||||
private final WebEngine engine;
|
|
||||||
private final FxDomHost dom;
|
|
||||||
|
|
||||||
public FxStorageHost(WebEngine engine, FxDomHost dom) {
|
|
||||||
this.engine = Objects.requireNonNull(engine, "engine");
|
|
||||||
this.dom = Objects.requireNonNull(dom, "dom");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<String> localKeys() {
|
|
||||||
return keys("localStorage");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String localGet(String key) {
|
|
||||||
return get("localStorage", key);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void localSet(String key, String value) {
|
|
||||||
set("localStorage", key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void localRemove(String key) {
|
|
||||||
remove("localStorage", key);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void localClear() {
|
|
||||||
clear("localStorage");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<String> sessionKeys() {
|
|
||||||
return keys("sessionStorage");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String sessionGet(String key) {
|
|
||||||
return get("sessionStorage", key);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void sessionSet(String key, String value) {
|
|
||||||
set("sessionStorage", key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void sessionRemove(String key) {
|
|
||||||
remove("sessionStorage", key);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void sessionClear() {
|
|
||||||
clear("sessionStorage");
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<String> keys(String storageName) {
|
|
||||||
return FxThreadBridge.callAndWait(() -> FxWebBridge.callWithJs(engine, () -> {
|
|
||||||
dom.requireDocument();
|
|
||||||
|
|
||||||
String script = ""
|
|
||||||
+ "(function(){"
|
|
||||||
+ " var s=window[" + FxWebBridge.toJsLiteral(storageName) + "];"
|
|
||||||
+ " if(!s) return [];"
|
|
||||||
+ " var out=[];"
|
|
||||||
+ " for(var i=0;i<s.length;i++){ out.push(String(s.key(i))); }"
|
|
||||||
+ " return out;"
|
|
||||||
+ "})();";
|
|
||||||
|
|
||||||
Object ret = engine.executeScript(script);
|
|
||||||
return FxWebBridge.toStringList(ret);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
private String get(String storageName, String key) {
|
|
||||||
Objects.requireNonNull(key, "key");
|
|
||||||
|
|
||||||
return FxThreadBridge.callAndWait(() -> FxWebBridge.callWithJs(engine, () -> {
|
|
||||||
dom.requireDocument();
|
|
||||||
|
|
||||||
String script = ""
|
|
||||||
+ "(function(){"
|
|
||||||
+ " var s=window[" + FxWebBridge.toJsLiteral(storageName) + "];"
|
|
||||||
+ " if(!s) return null;"
|
|
||||||
+ " var v=s.getItem(" + FxWebBridge.toJsLiteral(key) + ");"
|
|
||||||
+ " return v===null?null:String(v);"
|
|
||||||
+ "})();";
|
|
||||||
|
|
||||||
Object ret = engine.executeScript(script);
|
|
||||||
return ret == null ? null : String.valueOf(ret);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void set(String storageName, String key, String value) {
|
|
||||||
Objects.requireNonNull(key, "key");
|
|
||||||
|
|
||||||
FxThreadBridge.runAndWait(() -> FxWebBridge.runWithJs(engine, () -> {
|
|
||||||
dom.requireDocument();
|
|
||||||
|
|
||||||
if (value == null) {
|
|
||||||
String script = ""
|
|
||||||
+ "(function(){"
|
|
||||||
+ " var s=window[" + FxWebBridge.toJsLiteral(storageName) + "];"
|
|
||||||
+ " if(!s) return null;"
|
|
||||||
+ " s.removeItem(" + FxWebBridge.toJsLiteral(key) + ");"
|
|
||||||
+ " return null;"
|
|
||||||
+ "})();";
|
|
||||||
engine.executeScript(script);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String script = ""
|
|
||||||
+ "(function(){"
|
|
||||||
+ " var s=window[" + FxWebBridge.toJsLiteral(storageName) + "];"
|
|
||||||
+ " if(!s) return null;"
|
|
||||||
+ " s.setItem(" + FxWebBridge.toJsLiteral(key) + "," + FxWebBridge.toJsLiteral(value) + ");"
|
|
||||||
+ " return null;"
|
|
||||||
+ "})();";
|
|
||||||
engine.executeScript(script);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void remove(String storageName, String key) {
|
|
||||||
Objects.requireNonNull(key, "key");
|
|
||||||
set(storageName, key, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void clear(String storageName) {
|
|
||||||
FxThreadBridge.runAndWait(() -> FxWebBridge.runWithJs(engine, () -> {
|
|
||||||
dom.requireDocument();
|
|
||||||
|
|
||||||
String script = ""
|
|
||||||
+ "(function(){"
|
|
||||||
+ " var s=window[" + FxWebBridge.toJsLiteral(storageName) + "];"
|
|
||||||
+ " if(!s) return null;"
|
|
||||||
+ " s.clear();"
|
|
||||||
+ " return null;"
|
|
||||||
+ "})();";
|
|
||||||
engine.executeScript(script);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,137 +0,0 @@
|
|||||||
package org.openautonomousconnection.luascript.fx;
|
|
||||||
|
|
||||||
import javafx.scene.web.WebEngine;
|
|
||||||
import org.openautonomousconnection.luascript.hosts.UtilHost;
|
|
||||||
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URLDecoder;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility host: Base64, random, URL parsing, query parsing, JSON stringify/normalize via JS.
|
|
||||||
*/
|
|
||||||
public final class FxUtilHost implements UtilHost {
|
|
||||||
|
|
||||||
private final WebEngine engine;
|
|
||||||
private final FxDomHost dom;
|
|
||||||
private final SecureRandom rng = new SecureRandom();
|
|
||||||
|
|
||||||
public FxUtilHost(WebEngine engine, FxDomHost dom) {
|
|
||||||
this.engine = Objects.requireNonNull(engine, "engine");
|
|
||||||
this.dom = Objects.requireNonNull(dom, "dom");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String base64Encode(String text) {
|
|
||||||
String s = text == null ? "" : text;
|
|
||||||
return Base64.getEncoder().encodeToString(s.getBytes(StandardCharsets.UTF_8));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String base64Decode(String base64) {
|
|
||||||
if (base64 == null) return "";
|
|
||||||
byte[] b = Base64.getDecoder().decode(base64);
|
|
||||||
return new String(b, StandardCharsets.UTF_8);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String randomHex(int numBytes) {
|
|
||||||
if (numBytes <= 0) throw new IllegalArgumentException("numBytes must be > 0");
|
|
||||||
byte[] b = new byte[numBytes];
|
|
||||||
rng.nextBytes(b);
|
|
||||||
StringBuilder sb = new StringBuilder(numBytes * 2);
|
|
||||||
for (byte x : b) sb.append(String.format("%02x", x));
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, String> parseUrl(String url) {
|
|
||||||
Objects.requireNonNull(url, "url");
|
|
||||||
URI u = URI.create(url.trim());
|
|
||||||
|
|
||||||
Map<String, String> out = new LinkedHashMap<>();
|
|
||||||
out.put("scheme", safe(u.getScheme()));
|
|
||||||
out.put("host", safe(u.getHost()));
|
|
||||||
out.put("port", u.getPort() < 0 ? "" : String.valueOf(u.getPort()));
|
|
||||||
out.put("path", safe(u.getPath()));
|
|
||||||
out.put("query", safe(u.getQuery()));
|
|
||||||
out.put("fragment", safe(u.getFragment()));
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, List<String>> parseQuery(String query) {
|
|
||||||
String q = query == null ? "" : query.trim();
|
|
||||||
if (q.startsWith("?")) q = q.substring(1);
|
|
||||||
|
|
||||||
Map<String, List<String>> out = new LinkedHashMap<>();
|
|
||||||
if (q.isEmpty()) return out;
|
|
||||||
|
|
||||||
for (String part : q.split("&")) {
|
|
||||||
if (part.isEmpty()) continue;
|
|
||||||
String k;
|
|
||||||
String v;
|
|
||||||
int idx = part.indexOf('=');
|
|
||||||
if (idx < 0) {
|
|
||||||
k = decode(part);
|
|
||||||
v = "";
|
|
||||||
} else {
|
|
||||||
k = decode(part.substring(0, idx));
|
|
||||||
v = decode(part.substring(idx + 1));
|
|
||||||
}
|
|
||||||
out.computeIfAbsent(k, __ -> new ArrayList<>()).add(v);
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String jsonStringifyExpr(String elementId, String jsExpr) {
|
|
||||||
Objects.requireNonNull(elementId, "elementId");
|
|
||||||
Objects.requireNonNull(jsExpr, "jsExpr");
|
|
||||||
|
|
||||||
return FxThreadBridge.callAndWait(() -> FxWebBridge.callWithJs(engine, () -> {
|
|
||||||
dom.requireDocument();
|
|
||||||
|
|
||||||
// NOTE: jsExpr is intentionally raw JS expression; this is a trusted host API.
|
|
||||||
// If you need untrusted usage, wrap/validate upstream.
|
|
||||||
String script = ""
|
|
||||||
+ "(function(){"
|
|
||||||
+ " var v=(" + jsExpr + ");"
|
|
||||||
+ " return JSON.stringify(v);"
|
|
||||||
+ "})();";
|
|
||||||
|
|
||||||
Object ret = engine.executeScript(script);
|
|
||||||
return ret == null ? "null" : String.valueOf(ret);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String jsonNormalize(String elementId, String json) {
|
|
||||||
Objects.requireNonNull(elementId, "elementId");
|
|
||||||
Objects.requireNonNull(json, "json");
|
|
||||||
|
|
||||||
return FxThreadBridge.callAndWait(() -> FxWebBridge.callWithJs(engine, () -> {
|
|
||||||
dom.requireDocument();
|
|
||||||
|
|
||||||
String script = ""
|
|
||||||
+ "(function(){"
|
|
||||||
+ " var s=" + FxWebBridge.toJsLiteral(json) + ";"
|
|
||||||
+ " var obj=JSON.parse(s);"
|
|
||||||
+ " return JSON.stringify(obj);"
|
|
||||||
+ "})();";
|
|
||||||
|
|
||||||
Object ret = engine.executeScript(script);
|
|
||||||
return ret == null ? "null" : String.valueOf(ret);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String safe(String s) {
|
|
||||||
return s == null ? "" : s;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String decode(String s) {
|
|
||||||
return URLDecoder.decode(s, StandardCharsets.UTF_8);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,313 +0,0 @@
|
|||||||
package org.openautonomousconnection.luascript.fx;
|
|
||||||
|
|
||||||
import javafx.scene.web.WebEngine;
|
|
||||||
import org.openautonomousconnection.luascript.hosts.VideoHost;
|
|
||||||
import org.w3c.dom.Document;
|
|
||||||
import org.w3c.dom.Element;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.net.URLConnection;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* VideoHost implementation that renders video inside the WebView DOM using a {@code <video>} element.
|
|
||||||
*
|
|
||||||
* <p>No JavaFX overlay panes are used. Positioning is done by setting CSS via {@code style} attribute.</p>
|
|
||||||
*
|
|
||||||
* <p>Playback control is done via {@link FxDomHost#setProperty(String, String, Object)} and
|
|
||||||
* {@link FxDomHost#call(String, String, Object...)}, i.e. HTMLMediaElement methods.</p>
|
|
||||||
*
|
|
||||||
* <p>For {@code web://} URLs this host downloads the resource via {@link URLConnection} (your installed handler),
|
|
||||||
* stores it as a temporary file, then sets {@code src=file://...} because WebView won't reliably load custom schemes.</p>
|
|
||||||
*/
|
|
||||||
public final class FxVideoHost implements VideoHost {
|
|
||||||
|
|
||||||
private static final Set<String> VIDEO_EXTENSIONS = Set.of(
|
|
||||||
"mp4", "mov", "ogg", "ogv", "gif", "gifv", "avi", "m4v"
|
|
||||||
);
|
|
||||||
|
|
||||||
private static final String VIDEO_ID = "__oac_video";
|
|
||||||
|
|
||||||
private final WebEngine engine;
|
|
||||||
private final FxDomHost dom;
|
|
||||||
|
|
||||||
private volatile boolean loop;
|
|
||||||
private volatile double volume = 1.0;
|
|
||||||
|
|
||||||
private volatile double x;
|
|
||||||
private volatile double y;
|
|
||||||
private volatile double w = 640;
|
|
||||||
private volatile double h = 360;
|
|
||||||
|
|
||||||
private volatile Path lastTempFile;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new video host.
|
|
||||||
*
|
|
||||||
* @param engine web engine
|
|
||||||
* @param dom fx dom host
|
|
||||||
*/
|
|
||||||
public FxVideoHost(WebEngine engine, FxDomHost dom) {
|
|
||||||
this.engine = Objects.requireNonNull(engine, "engine");
|
|
||||||
this.dom = Objects.requireNonNull(dom, "dom");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void playFile(File file) {
|
|
||||||
Objects.requireNonNull(file, "file");
|
|
||||||
if (!file.isFile()) {
|
|
||||||
throw new IllegalArgumentException("Video file not found: " + file.getAbsolutePath());
|
|
||||||
}
|
|
||||||
|
|
||||||
String ext = MediaExtensions.extensionOf(file.getName());
|
|
||||||
ensureAllowed(ext, "video", file.getName());
|
|
||||||
|
|
||||||
setSource(file.toURI().toString(), null);
|
|
||||||
resume();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void playUrl(String url) {
|
|
||||||
Objects.requireNonNull(url, "url");
|
|
||||||
String u = url.trim();
|
|
||||||
if (u.isEmpty()) throw new IllegalArgumentException("URL is empty");
|
|
||||||
|
|
||||||
URI uri = URI.create(u);
|
|
||||||
String ext = MediaExtensions.extensionOf(uri.getPath());
|
|
||||||
ensureAllowed(ext, "video", uri.getPath());
|
|
||||||
|
|
||||||
String scheme = (uri.getScheme() == null) ? "" : uri.getScheme().toLowerCase(Locale.ROOT);
|
|
||||||
|
|
||||||
if ("http".equals(scheme) || "https".equals(scheme) || "file".equals(scheme)) {
|
|
||||||
setSource(uri.toString(), null);
|
|
||||||
resume();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ("web".equals(scheme)) {
|
|
||||||
Path tmp = null;
|
|
||||||
try {
|
|
||||||
tmp = downloadToTempFile(u, ext, "oac-video-");
|
|
||||||
rememberTemp(tmp);
|
|
||||||
setSource(tmp.toUri().toString(), tmp);
|
|
||||||
resume();
|
|
||||||
return;
|
|
||||||
} catch (IOException e) {
|
|
||||||
safeDelete(tmp);
|
|
||||||
throw new RuntimeException("Failed to load web video: " + e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new IllegalArgumentException("Unsupported scheme: " + scheme);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void pause() {
|
|
||||||
FxThreadBridge.runAndWait(() -> {
|
|
||||||
ensureVideoElement();
|
|
||||||
dom.call(VIDEO_ID, "pause");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void resume() {
|
|
||||||
FxThreadBridge.runAndWait(() -> {
|
|
||||||
Element v = ensureVideoElement();
|
|
||||||
applyMediaProps();
|
|
||||||
v.setAttribute("style", mergeStyle(baseStyle(true), rectStyle(), visualStyle()));
|
|
||||||
dom.call(VIDEO_ID, "play");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void stop() {
|
|
||||||
FxThreadBridge.runAndWait(() -> {
|
|
||||||
Element v = ensureVideoElement();
|
|
||||||
dom.call(VIDEO_ID, "pause");
|
|
||||||
dom.setProperty(VIDEO_ID, "currentTime", 0);
|
|
||||||
v.removeAttribute("src");
|
|
||||||
v.setAttribute("style", mergeStyle(baseStyle(false), rectStyle(), visualStyle()));
|
|
||||||
});
|
|
||||||
|
|
||||||
Path tmp = this.lastTempFile;
|
|
||||||
this.lastTempFile = null;
|
|
||||||
safeDelete(tmp);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void hide() {
|
|
||||||
FxThreadBridge.runAndWait(() -> {
|
|
||||||
Element v = ensureVideoElement();
|
|
||||||
v.setAttribute("style", mergeStyle(baseStyle(false), rectStyle(), visualStyle()));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setRect(double x, double y, double w, double h) {
|
|
||||||
this.x = x;
|
|
||||||
this.y = y;
|
|
||||||
this.w = Math.max(0.0, w);
|
|
||||||
this.h = Math.max(0.0, h);
|
|
||||||
|
|
||||||
FxThreadBridge.runAndWait(() -> {
|
|
||||||
Element v = ensureVideoElement();
|
|
||||||
boolean visible = "block".equalsIgnoreCase(extractDisplay(v.getAttribute("style")));
|
|
||||||
v.setAttribute("style", mergeStyle(baseStyle(visible), rectStyle(), visualStyle()));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setVolume(double volume) {
|
|
||||||
this.volume = clamp01(volume);
|
|
||||||
FxThreadBridge.runAndWait(this::applyMediaProps);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setLoop(boolean loop) {
|
|
||||||
this.loop = loop;
|
|
||||||
FxThreadBridge.runAndWait(this::applyMediaProps);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void seek(double seconds) {
|
|
||||||
double s = Math.max(0.0, seconds);
|
|
||||||
FxThreadBridge.runAndWait(() -> {
|
|
||||||
ensureVideoElement();
|
|
||||||
dom.setProperty(VIDEO_ID, "currentTime", s);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setSource(String src, Path tempFileKept) {
|
|
||||||
FxThreadBridge.runAndWait(() -> {
|
|
||||||
Element v = ensureVideoElement();
|
|
||||||
v.setAttribute("src", src);
|
|
||||||
applyMediaProps();
|
|
||||||
v.setAttribute("style", mergeStyle(baseStyle(true), rectStyle(), visualStyle()));
|
|
||||||
});
|
|
||||||
|
|
||||||
if (tempFileKept != null) {
|
|
||||||
rememberTemp(tempFileKept);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void applyMediaProps() {
|
|
||||||
ensureVideoElement();
|
|
||||||
dom.setProperty(VIDEO_ID, "loop", loop);
|
|
||||||
dom.setProperty(VIDEO_ID, "volume", volume);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Element ensureVideoElement() {
|
|
||||||
Document doc = dom.requireDocument();
|
|
||||||
Element v = doc.getElementById(VIDEO_ID);
|
|
||||||
if (v != null) return v;
|
|
||||||
|
|
||||||
Element body = (Element) doc.getElementsByTagName("body").item(0);
|
|
||||||
if (body == null) throw new IllegalStateException("No <body> element available");
|
|
||||||
|
|
||||||
v = doc.createElement("video");
|
|
||||||
v.setAttribute("id", VIDEO_ID);
|
|
||||||
// Default: no controls; script can enable via dom.setAttr(id,"controls","controls") if needed.
|
|
||||||
v.setAttribute("preload", "metadata");
|
|
||||||
|
|
||||||
body.appendChild(v);
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String baseStyle(boolean visible) {
|
|
||||||
return "position:fixed;"
|
|
||||||
+ "left:0;top:0;"
|
|
||||||
+ "z-index:2147483647;"
|
|
||||||
+ "background-color:black;"
|
|
||||||
+ "display:" + (visible ? "block" : "none") + ";";
|
|
||||||
}
|
|
||||||
|
|
||||||
private String rectStyle() {
|
|
||||||
return "left:" + x + "px;"
|
|
||||||
+ "top:" + y + "px;"
|
|
||||||
+ "width:" + w + "px;"
|
|
||||||
+ "height:" + h + "px;";
|
|
||||||
}
|
|
||||||
|
|
||||||
private String visualStyle() {
|
|
||||||
return "object-fit:contain;";
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String mergeStyle(String... parts) {
|
|
||||||
StringBuilder sb = new StringBuilder(256);
|
|
||||||
for (String p : parts) {
|
|
||||||
if (p == null || p.isBlank()) continue;
|
|
||||||
String s = p.trim();
|
|
||||||
if (!s.endsWith(";")) s += ";";
|
|
||||||
sb.append(s);
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String extractDisplay(String style) {
|
|
||||||
if (style == null || style.isBlank()) return "";
|
|
||||||
String[] parts = style.split(";");
|
|
||||||
for (String part : parts) {
|
|
||||||
String p = part.trim();
|
|
||||||
if (p.isEmpty()) continue;
|
|
||||||
int idx = p.indexOf(':');
|
|
||||||
if (idx <= 0) continue;
|
|
||||||
String k = p.substring(0, idx).trim().toLowerCase(Locale.ROOT);
|
|
||||||
if ("display".equals(k)) return p.substring(idx + 1).trim().toLowerCase(Locale.ROOT);
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Path downloadToTempFile(String url, String ext, String prefix) throws IOException {
|
|
||||||
URL u = new URL(url);
|
|
||||||
URLConnection con = u.openConnection();
|
|
||||||
con.setUseCaches(false);
|
|
||||||
|
|
||||||
String safeExt = (ext == null || ext.isBlank()) ? "bin" : ext.toLowerCase(Locale.ROOT);
|
|
||||||
Path tmp = Files.createTempFile(prefix, "." + safeExt);
|
|
||||||
|
|
||||||
try (InputStream in = con.getInputStream()) {
|
|
||||||
Files.copy(in, tmp, java.nio.file.StandardCopyOption.REPLACE_EXISTING);
|
|
||||||
} catch (IOException e) {
|
|
||||||
safeDelete(tmp);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
|
|
||||||
return tmp;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void rememberTemp(Path tmp) {
|
|
||||||
Path prev = this.lastTempFile;
|
|
||||||
this.lastTempFile = tmp;
|
|
||||||
safeDelete(prev);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void safeDelete(Path p) {
|
|
||||||
if (p == null) return;
|
|
||||||
try {
|
|
||||||
Files.deleteIfExists(p);
|
|
||||||
} catch (IOException ignored) {
|
|
||||||
// best-effort
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ensureAllowed(String ext, String kind, String source) {
|
|
||||||
String e = (ext == null) ? "" : ext.toLowerCase(Locale.ROOT);
|
|
||||||
if (e.isEmpty() || !VIDEO_EXTENSIONS.contains(e)) {
|
|
||||||
throw new IllegalArgumentException("Unsupported " + kind + " format '" + e + "' for: " + source);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static double clamp01(double v) {
|
|
||||||
if (v < 0.0) return 0.0;
|
|
||||||
if (v > 1.0) return 1.0;
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,227 +0,0 @@
|
|||||||
package org.openautonomousconnection.luascript.fx;
|
|
||||||
|
|
||||||
import javafx.scene.web.WebEngine;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.function.Supplier;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shared utilities for WebEngine JS bridging.
|
|
||||||
*/
|
|
||||||
public final class FxWebBridge {
|
|
||||||
|
|
||||||
private FxWebBridge() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensures JavaScript is enabled for the engine.
|
|
||||||
*
|
|
||||||
* @param engine engine
|
|
||||||
*/
|
|
||||||
public static void ensureJsEnabled(WebEngine engine) {
|
|
||||||
Objects.requireNonNull(engine, "engine");
|
|
||||||
if (!engine.isJavaScriptEnabled()) {
|
|
||||||
engine.setJavaScriptEnabled(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Executes bridge code with JavaScript temporarily enabled.
|
|
||||||
*
|
|
||||||
* @param engine engine
|
|
||||||
* @param action action
|
|
||||||
*/
|
|
||||||
public static void runWithJs(WebEngine engine, Runnable action) {
|
|
||||||
Objects.requireNonNull(action, "action");
|
|
||||||
callWithJs(engine, () -> {
|
|
||||||
action.run();
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Executes bridge code with JavaScript temporarily enabled and restores the prior state.
|
|
||||||
*
|
|
||||||
* @param engine engine
|
|
||||||
* @param action action
|
|
||||||
* @param <T> result type
|
|
||||||
* @return action result
|
|
||||||
*/
|
|
||||||
public static <T> T callWithJs(WebEngine engine, Supplier<T> action) {
|
|
||||||
Objects.requireNonNull(engine, "engine");
|
|
||||||
Objects.requireNonNull(action, "action");
|
|
||||||
|
|
||||||
boolean enabledBefore = engine.isJavaScriptEnabled();
|
|
||||||
if (!enabledBefore) {
|
|
||||||
engine.setJavaScriptEnabled(true);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return action.get();
|
|
||||||
} finally {
|
|
||||||
if (!enabledBefore) {
|
|
||||||
engine.setJavaScriptEnabled(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a Java value into a safe JavaScript literal.
|
|
||||||
*
|
|
||||||
* <p>Supported: null, String, Boolean, Number (finite).</p>
|
|
||||||
*
|
|
||||||
* @param v value
|
|
||||||
* @return JS literal
|
|
||||||
*/
|
|
||||||
public static String toJsLiteral(Object v) {
|
|
||||||
if (v == null) return "null";
|
|
||||||
|
|
||||||
if (v instanceof String s) {
|
|
||||||
return "'" + escapeJsSingleQuotedString(s) + "'";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (v instanceof Boolean b) {
|
|
||||||
return b ? "true" : "false";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (v instanceof Number n) {
|
|
||||||
double d = n.doubleValue();
|
|
||||||
if (!Double.isFinite(d)) {
|
|
||||||
throw new IllegalArgumentException("Non-finite number is not supported for JS literal: " + d);
|
|
||||||
}
|
|
||||||
return Double.toString(d);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new IllegalArgumentException("Unsupported value type for JS literal: " + v.getClass().getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds a JavaScript array literal from Java values.
|
|
||||||
*
|
|
||||||
* @param values values
|
|
||||||
* @return array literal
|
|
||||||
*/
|
|
||||||
public static String toJsArrayLiteral(Object[] values) {
|
|
||||||
if (values == null || values.length == 0) return "[]";
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
sb.append('[');
|
|
||||||
for (int i = 0; i < values.length; i++) {
|
|
||||||
if (i > 0) sb.append(',');
|
|
||||||
sb.append(toJsLiteral(values[i]));
|
|
||||||
}
|
|
||||||
sb.append(']');
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Restricts input to a plain JavaScript identifier (prevents injection).
|
|
||||||
*
|
|
||||||
* @param s value
|
|
||||||
* @param label label
|
|
||||||
* @return identifier
|
|
||||||
*/
|
|
||||||
public static String requireJsIdentifier(String s, String label) {
|
|
||||||
if (s == null) throw new IllegalArgumentException(label + " is null");
|
|
||||||
String v = s.trim();
|
|
||||||
if (v.isEmpty()) throw new IllegalArgumentException(label + " is blank");
|
|
||||||
if (!v.matches("^[A-Za-z_$][A-Za-z0-9_$]*$")) {
|
|
||||||
throw new IllegalArgumentException(label + " must be a JS identifier: " + v);
|
|
||||||
}
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Normalizes alignment options for scrollIntoView.
|
|
||||||
*
|
|
||||||
* @param align input
|
|
||||||
* @return normalized
|
|
||||||
*/
|
|
||||||
public static String normalizeAlign(String align) {
|
|
||||||
if (align == null) return "nearest";
|
|
||||||
String a = align.trim().toLowerCase(Locale.ROOT);
|
|
||||||
return switch (a) {
|
|
||||||
case "start", "center", "end", "nearest" -> a;
|
|
||||||
default -> "nearest";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a JS array-like object into a Java string list.
|
|
||||||
*
|
|
||||||
* @param jsValue raw JS value
|
|
||||||
* @return list of string values
|
|
||||||
*/
|
|
||||||
public static List<String> toStringList(Object jsValue) {
|
|
||||||
if (jsValue == null) return List.of();
|
|
||||||
if (jsValue instanceof List<?> list) {
|
|
||||||
List<String> out = new ArrayList<>(list.size());
|
|
||||||
for (Object value : list) out.add(value == null ? null : String.valueOf(value));
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
Class<?> jsObj = Class.forName("netscape.javascript.JSObject");
|
|
||||||
if (jsObj.isInstance(jsValue)) {
|
|
||||||
Object lenObj = jsObj.getMethod("getMember", String.class).invoke(jsValue, "length");
|
|
||||||
int len = Integer.parseInt(String.valueOf(lenObj));
|
|
||||||
List<String> out = new ArrayList<>(len);
|
|
||||||
for (int i = 0; i < len; i++) {
|
|
||||||
Object value = jsObj.getMethod("getSlot", int.class).invoke(jsValue, i);
|
|
||||||
out.add(value == null ? null : String.valueOf(value));
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
// best-effort fallback below
|
|
||||||
}
|
|
||||||
|
|
||||||
return List.of(String.valueOf(jsValue));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a JS object into a Java string-object map when possible.
|
|
||||||
*
|
|
||||||
* @param jsValue raw JS value
|
|
||||||
* @return mapped values
|
|
||||||
*/
|
|
||||||
public static Map<String, Object> toStringObjectMap(Object jsValue) {
|
|
||||||
if (jsValue == null) return Map.of();
|
|
||||||
if (jsValue instanceof Map<?, ?> map) {
|
|
||||||
Map<String, Object> out = new LinkedHashMap<>();
|
|
||||||
for (Map.Entry<?, ?> entry : map.entrySet()) {
|
|
||||||
if (entry.getKey() == null) continue;
|
|
||||||
out.put(String.valueOf(entry.getKey()), entry.getValue());
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Map.of("value", jsValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String escapeJsSingleQuotedString(String s) {
|
|
||||||
if (s == null || s.isEmpty()) return "";
|
|
||||||
StringBuilder out = new StringBuilder(s.length() + 16);
|
|
||||||
for (int i = 0; i < s.length(); i++) {
|
|
||||||
char c = s.charAt(i);
|
|
||||||
switch (c) {
|
|
||||||
case '\'' -> out.append("\\'");
|
|
||||||
case '\\' -> out.append("\\\\");
|
|
||||||
case '\n' -> out.append("\\n");
|
|
||||||
case '\r' -> out.append("\\r");
|
|
||||||
case '\t' -> out.append("\\t");
|
|
||||||
case '\u0000' -> out.append("\\0");
|
|
||||||
case '\u2028' -> out.append("\\u2028");
|
|
||||||
case '\u2029' -> out.append("\\u2029");
|
|
||||||
default -> {
|
|
||||||
if (c < 0x20) out.append(String.format("\\u%04x", (int) c));
|
|
||||||
else out.append(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
package org.openautonomousconnection.luascript.fx;
|
|
||||||
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shared extension helper for media sources.
|
|
||||||
*/
|
|
||||||
public final class MediaExtensions {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extracts a lower-case extension without dot, or empty string if none.
|
|
||||||
*
|
|
||||||
* @param pathOrName path or file name
|
|
||||||
* @return extension without dot, lower-case, or empty string
|
|
||||||
*/
|
|
||||||
public static String extensionOf(String pathOrName) {
|
|
||||||
if (pathOrName == null) return "";
|
|
||||||
String s = pathOrName.trim();
|
|
||||||
if (s.isEmpty()) return "";
|
|
||||||
|
|
||||||
int q = s.indexOf('?');
|
|
||||||
if (q >= 0) s = s.substring(0, q);
|
|
||||||
int h = s.indexOf('#');
|
|
||||||
if (h >= 0) s = s.substring(0, h);
|
|
||||||
|
|
||||||
int slash = Math.max(s.lastIndexOf('/'), s.lastIndexOf('\\'));
|
|
||||||
String name = (slash >= 0) ? s.substring(slash + 1) : s;
|
|
||||||
|
|
||||||
int dot = name.lastIndexOf('.');
|
|
||||||
if (dot < 0 || dot == name.length() - 1) return "";
|
|
||||||
|
|
||||||
return name.substring(dot + 1).toLowerCase(Locale.ROOT);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,54 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* This code, and therefore this entire project, is licensed under
|
||||||
|
* the Open Autonomous Public License (OAPL) v1.0, or one that
|
||||||
|
* derives from that license.
|
||||||
|
* For more details, please refer to the LICENSE file.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2026 Maple.
|
||||||
|
*/
|
||||||
|
|
||||||
package org.openautonomousconnection.luascript.hosts;
|
package org.openautonomousconnection.luascript.hosts;
|
||||||
|
|
||||||
|
import org.openautonomousconnection.luascript.type.AudioValue;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Host services for audio playback.
|
* Abstract audio service host
|
||||||
*/
|
*/
|
||||||
public interface AudioHost {
|
public interface AudioHost {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Plays an audio file from the local filesystem.
|
* Load an audio file as a LuaValue (AudioValue type)
|
||||||
*
|
* @param audioFile audio media file
|
||||||
* @param file local audio file
|
* @return reference object to media
|
||||||
*/
|
*/
|
||||||
void playFile(File file);
|
AudioValue load(File audioFile);
|
||||||
|
|
||||||
/**
|
|
||||||
* Plays an audio resource identified by a URL string.
|
|
||||||
*
|
|
||||||
* <p>Supported schemes depend on the host implementation. Typical schemes:</p>
|
|
||||||
* <ul>
|
|
||||||
* <li>http://, https://</li>
|
|
||||||
* <li>file://</li>
|
|
||||||
* <li>web:// (custom, via installed URL handler)</li>
|
|
||||||
* </ul>
|
|
||||||
*
|
|
||||||
* @param url URL string
|
|
||||||
*/
|
|
||||||
void playUrl(String url);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pauses playback (if any).
|
|
||||||
*/
|
|
||||||
void pause();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stops playback (if any).
|
|
||||||
*/
|
|
||||||
void stop();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets volume in range [0.0, 1.0].
|
|
||||||
*
|
|
||||||
* @param volume volume
|
|
||||||
*/
|
|
||||||
void setVolume(double volume);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enables/disables looping.
|
|
||||||
*
|
|
||||||
* @param loop true to loop
|
|
||||||
*/
|
|
||||||
void setLoop(boolean loop);
|
|
||||||
}
|
}
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
package org.openautonomousconnection.luascript.hosts;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clipboard access.
|
|
||||||
*/
|
|
||||||
public interface ClipboardHost {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets clipboard text.
|
|
||||||
*
|
|
||||||
* @param text text
|
|
||||||
*/
|
|
||||||
void setText(String text);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets clipboard text.
|
|
||||||
*
|
|
||||||
* @return text or empty string if none
|
|
||||||
*/
|
|
||||||
String getText();
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
package org.openautonomousconnection.luascript.hosts;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* CSSOM / computed styles access.
|
|
||||||
*/
|
|
||||||
public interface CssHost {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets computed style value for a property.
|
|
||||||
*
|
|
||||||
* @param elementId element id
|
|
||||||
* @param property CSS property (kebab-case or camelCase accepted by browser)
|
|
||||||
* @return computed value (never null, may be empty)
|
|
||||||
*/
|
|
||||||
String getComputedStyle(String elementId, String property);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a computed style snapshot of selected properties.
|
|
||||||
*
|
|
||||||
* @param elementId element id
|
|
||||||
* @param properties properties to fetch
|
|
||||||
* @return map property->value
|
|
||||||
*/
|
|
||||||
Map<String, String> getComputedStyles(String elementId, String[] properties);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets an inline style property (style attribute).
|
|
||||||
*
|
|
||||||
* @param elementId element id
|
|
||||||
* @param property CSS property
|
|
||||||
* @param value CSS value (empty => remove)
|
|
||||||
*/
|
|
||||||
void setInlineStyle(String elementId, String property, String value);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets an inline style property (from style attribute).
|
|
||||||
*
|
|
||||||
* @param elementId element id
|
|
||||||
* @param property CSS property
|
|
||||||
* @return inline value (may be empty)
|
|
||||||
*/
|
|
||||||
String getInlineStyle(String elementId, String property);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets a CSS variable (custom property) on an element.
|
|
||||||
*
|
|
||||||
* @param elementId element id
|
|
||||||
* @param name variable name (e.g. "--primary")
|
|
||||||
* @param value value
|
|
||||||
*/
|
|
||||||
void setCssVariable(String elementId, String name, String value);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a CSS variable (computed) from an element.
|
|
||||||
*
|
|
||||||
* @param elementId element id
|
|
||||||
* @param name variable name (e.g. "--primary")
|
|
||||||
* @return computed var (may be empty)
|
|
||||||
*/
|
|
||||||
String getCssVariable(String elementId, String name);
|
|
||||||
}
|
|
||||||
@@ -147,19 +147,4 @@ public interface DomHost {
|
|||||||
* @return list of ids (never null)
|
* @return list of ids (never null)
|
||||||
*/
|
*/
|
||||||
List<String> queryByClass(String className);
|
List<String> queryByClass(String className);
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets a DOM property (e.g. video.currentTime, video.volume).
|
|
||||||
*/
|
|
||||||
void setProperty(String elementId, String property, Object value);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a DOM property.
|
|
||||||
*/
|
|
||||||
Object getProperty(String elementId, String property);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invokes a DOM method (e.g. video.play()).
|
|
||||||
*/
|
|
||||||
Object call(String elementId, String method, Object... args);
|
|
||||||
}
|
}
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
package org.openautonomousconnection.luascript.hosts;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Layout and scrolling related accessors.
|
|
||||||
*
|
|
||||||
* <p>All results are plain maps to keep dependencies minimal.</p>
|
|
||||||
*/
|
|
||||||
public interface GeometryHost {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns bounding client rect of an element.
|
|
||||||
*
|
|
||||||
* @param elementId element id
|
|
||||||
* @return map: x,y,width,height,top,left,right,bottom
|
|
||||||
*/
|
|
||||||
Map<String, Object> getBoundingClientRect(String elementId);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns viewport metrics.
|
|
||||||
*
|
|
||||||
* @return map: width,height,devicePixelRatio,scrollX,scrollY
|
|
||||||
*/
|
|
||||||
Map<String, Object> getViewport();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Scrolls the document to coordinates.
|
|
||||||
*
|
|
||||||
* @param x scroll x
|
|
||||||
* @param y scroll y
|
|
||||||
*/
|
|
||||||
void scrollTo(double x, double y);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Scrolls an element into view.
|
|
||||||
*
|
|
||||||
* @param elementId element id
|
|
||||||
* @param align one of: "start","center","end","nearest" (null => "nearest")
|
|
||||||
*/
|
|
||||||
void scrollIntoView(String elementId, String align);
|
|
||||||
}
|
|
||||||
@@ -10,24 +10,42 @@ import java.util.Optional;
|
|||||||
*/
|
*/
|
||||||
public interface HostServices {
|
public interface HostServices {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an optional UI host.
|
||||||
|
*
|
||||||
|
* @return ui host
|
||||||
|
*/
|
||||||
Optional<UiHost> ui();
|
Optional<UiHost> ui();
|
||||||
Optional<DomHost> dom();
|
|
||||||
Optional<EventHost> events();
|
|
||||||
Optional<ResourceHost> resources();
|
|
||||||
Optional<ConsoleHost> console();
|
|
||||||
Optional<AudioHost> audio();
|
|
||||||
Optional<ImageHost> image();
|
|
||||||
Optional<VideoHost> video();
|
|
||||||
|
|
||||||
/* NEW */
|
/**
|
||||||
Optional<SchedulerHost> scheduler();
|
* Returns an optional DOM host.
|
||||||
Optional<SelectorHost> selector();
|
*
|
||||||
Optional<GeometryHost> geometry();
|
* @return dom host
|
||||||
Optional<CssHost> css();
|
*/
|
||||||
Optional<StorageHost> storage();
|
Optional<DomHost> dom();
|
||||||
Optional<UtilHost> util();
|
|
||||||
Optional<ClipboardHost> clipboard();
|
/**
|
||||||
Optional<ObserverHost> observers();
|
* Returns an optional event host.
|
||||||
|
*
|
||||||
|
* @return event host
|
||||||
|
*/
|
||||||
|
Optional<EventHost> events();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an optional resource host.
|
||||||
|
*
|
||||||
|
* @return resource host
|
||||||
|
*/
|
||||||
|
Optional<ResourceHost> resources();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an optional console host.
|
||||||
|
*
|
||||||
|
* @return console host
|
||||||
|
*/
|
||||||
|
Optional<ConsoleHost> console();
|
||||||
|
|
||||||
|
Optional<AudioHost> audio();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Simple immutable implementation.
|
* Simple immutable implementation.
|
||||||
@@ -38,73 +56,55 @@ public interface HostServices {
|
|||||||
private final EventHost events;
|
private final EventHost events;
|
||||||
private final ResourceHost resources;
|
private final ResourceHost resources;
|
||||||
private final ConsoleHost console;
|
private final ConsoleHost console;
|
||||||
|
|
||||||
private final AudioHost audioHost;
|
private final AudioHost audioHost;
|
||||||
private final ImageHost imageHost;
|
|
||||||
private final VideoHost videoHost;
|
|
||||||
|
|
||||||
private final SchedulerHost schedulerHost;
|
/**
|
||||||
private final SelectorHost selectorHost;
|
* Creates a HostServices container.
|
||||||
private final GeometryHost geometryHost;
|
*
|
||||||
private final CssHost cssHost;
|
* @param ui ui host
|
||||||
private final StorageHost storageHost;
|
* @param dom dom host
|
||||||
private final UtilHost utilHost;
|
* @param events event host
|
||||||
private final ClipboardHost clipboardHost;
|
* @param resources resource host
|
||||||
private final ObserverHost observerHost;
|
* @param console console host
|
||||||
|
*/
|
||||||
public Default(
|
public Default(UiHost ui, DomHost dom, EventHost events, ResourceHost resources, ConsoleHost console, AudioHost audioHost) {
|
||||||
UiHost ui,
|
|
||||||
DomHost dom,
|
|
||||||
EventHost events,
|
|
||||||
ResourceHost resources,
|
|
||||||
ConsoleHost console,
|
|
||||||
AudioHost audioHost,
|
|
||||||
ImageHost imageHost,
|
|
||||||
VideoHost videoHost,
|
|
||||||
SchedulerHost schedulerHost,
|
|
||||||
SelectorHost selectorHost,
|
|
||||||
GeometryHost geometryHost,
|
|
||||||
CssHost cssHost,
|
|
||||||
StorageHost storageHost,
|
|
||||||
UtilHost utilHost,
|
|
||||||
ClipboardHost clipboardHost,
|
|
||||||
ObserverHost observerHost
|
|
||||||
) {
|
|
||||||
this.ui = ui;
|
this.ui = ui;
|
||||||
this.dom = dom;
|
this.dom = dom;
|
||||||
this.events = events;
|
this.events = events;
|
||||||
this.resources = resources;
|
this.resources = resources;
|
||||||
this.console = console;
|
this.console = console;
|
||||||
this.audioHost = audioHost;
|
this.audioHost = audioHost;
|
||||||
this.imageHost = imageHost;
|
|
||||||
this.videoHost = videoHost;
|
|
||||||
this.schedulerHost = schedulerHost;
|
|
||||||
this.selectorHost = selectorHost;
|
|
||||||
this.geometryHost = geometryHost;
|
|
||||||
this.cssHost = cssHost;
|
|
||||||
this.storageHost = storageHost;
|
|
||||||
this.utilHost = utilHost;
|
|
||||||
this.clipboardHost = clipboardHost;
|
|
||||||
this.observerHost = observerHost;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public Optional<UiHost> ui() { return Optional.ofNullable(ui); }
|
@Override
|
||||||
@Override public Optional<DomHost> dom() { return Optional.ofNullable(dom); }
|
public Optional<UiHost> ui() {
|
||||||
@Override public Optional<EventHost> events() { return Optional.ofNullable(events); }
|
return Optional.ofNullable(ui);
|
||||||
@Override public Optional<ResourceHost> resources() { return Optional.ofNullable(resources); }
|
}
|
||||||
@Override public Optional<ConsoleHost> console() { return Optional.ofNullable(console); }
|
|
||||||
@Override public Optional<AudioHost> audio() { return Optional.ofNullable(audioHost); }
|
|
||||||
@Override public Optional<ImageHost> image() { return Optional.ofNullable(imageHost); }
|
|
||||||
@Override public Optional<VideoHost> video() { return Optional.ofNullable(videoHost); }
|
|
||||||
|
|
||||||
@Override public Optional<SchedulerHost> scheduler() { return Optional.ofNullable(schedulerHost); }
|
@Override
|
||||||
@Override public Optional<SelectorHost> selector() { return Optional.ofNullable(selectorHost); }
|
public Optional<DomHost> dom() {
|
||||||
@Override public Optional<GeometryHost> geometry() { return Optional.ofNullable(geometryHost); }
|
return Optional.ofNullable(dom);
|
||||||
@Override public Optional<CssHost> css() { return Optional.ofNullable(cssHost); }
|
}
|
||||||
@Override public Optional<StorageHost> storage() { return Optional.ofNullable(storageHost); }
|
|
||||||
@Override public Optional<UtilHost> util() { return Optional.ofNullable(utilHost); }
|
@Override
|
||||||
@Override public Optional<ClipboardHost> clipboard() { return Optional.ofNullable(clipboardHost); }
|
public Optional<EventHost> events() {
|
||||||
@Override public Optional<ObserverHost> observers() { return Optional.ofNullable(observerHost); }
|
return Optional.ofNullable(events);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<ResourceHost> resources() {
|
||||||
|
return Optional.ofNullable(resources);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<ConsoleHost> console() {
|
||||||
|
return Optional.ofNullable(console);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<AudioHost> audio() {
|
||||||
|
return Optional.ofNullable(audioHost);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -113,6 +113,11 @@ public interface HostServices {
|
|||||||
final class StdoutConsole implements ConsoleHost {
|
final class StdoutConsole implements ConsoleHost {
|
||||||
private final String prefix;
|
private final String prefix;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new stdout console with a prefix.
|
||||||
|
*
|
||||||
|
* @param prefix prefix (may be empty)
|
||||||
|
*/
|
||||||
public StdoutConsole(String prefix) {
|
public StdoutConsole(String prefix) {
|
||||||
this.prefix = Objects.requireNonNull(prefix, "prefix");
|
this.prefix = Objects.requireNonNull(prefix, "prefix");
|
||||||
}
|
}
|
||||||
@@ -121,10 +126,29 @@ public interface HostServices {
|
|||||||
return s == null ? "" : s;
|
return s == null ? "" : s;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void info(String message) { System.out.println(prefix + "[info] " + safe(message)); }
|
@Override
|
||||||
@Override public void log(String message) { System.out.println(prefix + "[log] " + safe(message)); }
|
public void info(String message) {
|
||||||
@Override public void warn(String message) { System.out.println(prefix + "[warn] " + safe(message)); }
|
System.out.println(prefix + "[info] " + safe(message));
|
||||||
@Override public void error(String message) { System.err.println(prefix + "[error] " + safe(message)); }
|
}
|
||||||
@Override public void exception(String message) { System.err.println(prefix + "[exception] " + safe(message)); }
|
|
||||||
|
@Override
|
||||||
|
public void log(String message) {
|
||||||
|
System.out.println(prefix + "[log] " + safe(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void warn(String message) {
|
||||||
|
System.out.println(prefix + "[warn] " + safe(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void error(String message) {
|
||||||
|
System.err.println(prefix + "[error] " + safe(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void exception(String message) {
|
||||||
|
System.err.println(prefix + "[exception] " + safe(message));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
package org.openautonomousconnection.luascript.hosts;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Host services for image rendering in an overlay layer above the WebView.
|
|
||||||
*/
|
|
||||||
public interface ImageHost {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shows an image from the local filesystem.
|
|
||||||
*
|
|
||||||
* @param file image file
|
|
||||||
*/
|
|
||||||
void showFile(File file);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shows an image from a URL string (http/https/file/web).
|
|
||||||
*
|
|
||||||
* @param url url string
|
|
||||||
*/
|
|
||||||
void showUrl(String url);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hides the current image overlay.
|
|
||||||
*/
|
|
||||||
void hide();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the drawing rectangle (in WebView pixel coordinates).
|
|
||||||
*
|
|
||||||
* @param x x
|
|
||||||
* @param y y
|
|
||||||
* @param w width
|
|
||||||
* @param h height
|
|
||||||
*/
|
|
||||||
void setRect(double x, double y, double w, double h);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If true, preserves aspect ratio within the rectangle.
|
|
||||||
*
|
|
||||||
* @param preserve preserve aspect ratio
|
|
||||||
*/
|
|
||||||
void setPreserveRatio(boolean preserve);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If true, image is smoothed when scaled.
|
|
||||||
*
|
|
||||||
* @param smooth smooth
|
|
||||||
*/
|
|
||||||
void setSmooth(boolean smooth);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets opacity in range [0..1].
|
|
||||||
*
|
|
||||||
* @param opacity opacity
|
|
||||||
*/
|
|
||||||
void setOpacity(double opacity);
|
|
||||||
}
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
package org.openautonomousconnection.luascript.hosts;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reactive observers: Mutation/Resize/Intersection.
|
|
||||||
*
|
|
||||||
* <p>Observers emit events into Lua via a host-provided callback.</p>
|
|
||||||
*/
|
|
||||||
public interface ObserverHost {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback used by the host to notify observers.
|
|
||||||
*/
|
|
||||||
@FunctionalInterface
|
|
||||||
interface ObserverCallback {
|
|
||||||
/**
|
|
||||||
* Called on observer events.
|
|
||||||
*
|
|
||||||
* @param type observer type ("mutation","resize","intersection")
|
|
||||||
* @param targetId element id
|
|
||||||
* @param data payload map
|
|
||||||
*/
|
|
||||||
void onEvent(String type, String targetId, Map<String, Object> data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the callback to receive observer events.
|
|
||||||
*
|
|
||||||
* @param callback callback (nullable to disable)
|
|
||||||
*/
|
|
||||||
void setCallback(ObserverCallback callback);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Observes DOM mutations on an element (subtree).
|
|
||||||
*
|
|
||||||
* @param elementId element id
|
|
||||||
* @param subtree true to include subtree
|
|
||||||
* @param attributes true to observe attributes
|
|
||||||
* @param childList true to observe childList
|
|
||||||
* @param characterData true to observe text changes
|
|
||||||
*/
|
|
||||||
void observeMutations(String elementId, boolean subtree, boolean attributes, boolean childList, boolean characterData);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stops mutation observing for an element.
|
|
||||||
*
|
|
||||||
* @param elementId element id
|
|
||||||
*/
|
|
||||||
void unobserveMutations(String elementId);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Observes resize changes of an element.
|
|
||||||
*
|
|
||||||
* @param elementId element id
|
|
||||||
*/
|
|
||||||
void observeResize(String elementId);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stops resize observing for an element.
|
|
||||||
*
|
|
||||||
* @param elementId element id
|
|
||||||
*/
|
|
||||||
void unobserveResize(String elementId);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Observes intersection changes of an element with viewport.
|
|
||||||
*
|
|
||||||
* @param elementId element id
|
|
||||||
* @param threshold threshold in [0..1]
|
|
||||||
*/
|
|
||||||
void observeIntersection(String elementId, double threshold);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stops intersection observing for an element.
|
|
||||||
*
|
|
||||||
* @param elementId element id
|
|
||||||
*/
|
|
||||||
void unobserveIntersection(String elementId);
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
package org.openautonomousconnection.luascript.hosts;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Scheduling primitives comparable to JavaScript timers.
|
|
||||||
*
|
|
||||||
* <p>No networking. This host only provides time-based callbacks.</p>
|
|
||||||
*/
|
|
||||||
public interface SchedulerHost {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Schedules a one-shot callback after a delay.
|
|
||||||
*
|
|
||||||
* @param delayMillis delay in milliseconds (>= 0)
|
|
||||||
* @param callback callback to run on host-defined thread (typically Lua thread)
|
|
||||||
* @return handle id
|
|
||||||
*/
|
|
||||||
long setTimeout(long delayMillis, Runnable callback);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Schedules a repeating callback with fixed rate.
|
|
||||||
*
|
|
||||||
* @param intervalMillis interval in milliseconds (> 0)
|
|
||||||
* @param callback callback to run
|
|
||||||
* @return handle id
|
|
||||||
*/
|
|
||||||
long setInterval(long intervalMillis, Runnable callback);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cancels a timeout/interval handle.
|
|
||||||
*
|
|
||||||
* @param handle handle id
|
|
||||||
* @return true if canceled
|
|
||||||
*/
|
|
||||||
boolean clear(long handle);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Schedules a callback for the next animation frame.
|
|
||||||
*
|
|
||||||
* <p>Comparable to requestAnimationFrame. The callback is invoked once.</p>
|
|
||||||
*
|
|
||||||
* @param callback callback to run
|
|
||||||
* @return handle id
|
|
||||||
*/
|
|
||||||
long requestAnimationFrame(Runnable callback);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cancels a previously scheduled animation frame.
|
|
||||||
*
|
|
||||||
* @param handle handle id
|
|
||||||
* @return true if canceled
|
|
||||||
*/
|
|
||||||
boolean cancelAnimationFrame(long handle);
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
package org.openautonomousconnection.luascript.hosts;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* CSS selector based DOM querying/traversal.
|
|
||||||
*
|
|
||||||
* <p>All returned elements are identified by stable element ids.</p>
|
|
||||||
*/
|
|
||||||
public interface SelectorHost {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the first element matching the selector, or null.
|
|
||||||
*
|
|
||||||
* @param selector CSS selector
|
|
||||||
* @return element id or null
|
|
||||||
*/
|
|
||||||
String querySelector(String selector);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns all elements matching the selector.
|
|
||||||
*
|
|
||||||
* @param selector CSS selector
|
|
||||||
* @return list of element ids (never null)
|
|
||||||
*/
|
|
||||||
List<String> querySelectorAll(String selector);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if an element matches a selector.
|
|
||||||
*
|
|
||||||
* @param elementId element id
|
|
||||||
* @param selector CSS selector
|
|
||||||
* @return true if matches
|
|
||||||
*/
|
|
||||||
boolean matches(String elementId, String selector);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the closest ancestor (including itself) matching selector, or null.
|
|
||||||
*
|
|
||||||
* @param elementId element id
|
|
||||||
* @param selector CSS selector
|
|
||||||
* @return closest element id or null
|
|
||||||
*/
|
|
||||||
String closest(String elementId, String selector);
|
|
||||||
}
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
package org.openautonomousconnection.luascript.hosts;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Storage primitives comparable to localStorage/sessionStorage.
|
|
||||||
*/
|
|
||||||
public interface StorageHost {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return keys in localStorage
|
|
||||||
*/
|
|
||||||
List<String> localKeys();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param key key
|
|
||||||
* @return value or null
|
|
||||||
*/
|
|
||||||
String localGet(String key);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param key key
|
|
||||||
* @param value value (null removes)
|
|
||||||
*/
|
|
||||||
void localSet(String key, String value);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes a key from localStorage.
|
|
||||||
*
|
|
||||||
* @param key key
|
|
||||||
*/
|
|
||||||
void localRemove(String key);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clears localStorage.
|
|
||||||
*/
|
|
||||||
void localClear();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return keys in sessionStorage
|
|
||||||
*/
|
|
||||||
List<String> sessionKeys();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param key key
|
|
||||||
* @return value or null
|
|
||||||
*/
|
|
||||||
String sessionGet(String key);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param key key
|
|
||||||
* @param value value (null removes)
|
|
||||||
*/
|
|
||||||
void sessionSet(String key, String value);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes a key from sessionStorage.
|
|
||||||
*
|
|
||||||
* @param key key
|
|
||||||
*/
|
|
||||||
void sessionRemove(String key);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clears sessionStorage.
|
|
||||||
*/
|
|
||||||
void sessionClear();
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
package org.openautonomousconnection.luascript.hosts;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility helpers commonly available in browsers.
|
|
||||||
*
|
|
||||||
* <p>No networking.</p>
|
|
||||||
*/
|
|
||||||
public interface UtilHost {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encodes bytes (UTF-8) as Base64.
|
|
||||||
*
|
|
||||||
* @param text text
|
|
||||||
* @return base64
|
|
||||||
*/
|
|
||||||
String base64Encode(String text);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decodes Base64 into UTF-8 text.
|
|
||||||
*
|
|
||||||
* @param base64 base64
|
|
||||||
* @return decoded text
|
|
||||||
*/
|
|
||||||
String base64Decode(String base64);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates cryptographically-strong random bytes and returns as hex string.
|
|
||||||
*
|
|
||||||
* @param numBytes number of bytes (>0)
|
|
||||||
* @return hex string
|
|
||||||
*/
|
|
||||||
String randomHex(int numBytes);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses a URL string into components.
|
|
||||||
*
|
|
||||||
* @param url url string
|
|
||||||
* @return map containing scheme,host,port,path,query,fragment
|
|
||||||
*/
|
|
||||||
Map<String, String> parseUrl(String url);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses a query string into key->list(values).
|
|
||||||
*
|
|
||||||
* @param query query string (with or without leading '?')
|
|
||||||
* @return map key->values
|
|
||||||
*/
|
|
||||||
Map<String, List<String>> parseQuery(String query);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* JSON.stringify via browser engine (returns JSON string).
|
|
||||||
*
|
|
||||||
* @param elementId element id that provides the JS context (ignored by some engines but kept for safety)
|
|
||||||
* @param jsExpr JS expression returning a JSON-serializable value
|
|
||||||
* @return JSON string
|
|
||||||
*/
|
|
||||||
String jsonStringifyExpr(String elementId, String jsExpr);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* JSON.parse via browser engine and returns normalized JSON string (stringify(parse(x))).
|
|
||||||
*
|
|
||||||
* @param elementId element id providing JS context
|
|
||||||
* @param json json string
|
|
||||||
* @return normalized json
|
|
||||||
*/
|
|
||||||
String jsonNormalize(String elementId, String json);
|
|
||||||
}
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
package org.openautonomousconnection.luascript.hosts;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Host services for video playback in an overlay layer above the WebView.
|
|
||||||
*/
|
|
||||||
public interface VideoHost {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Plays a video from the local filesystem.
|
|
||||||
*
|
|
||||||
* @param file video file
|
|
||||||
*/
|
|
||||||
void playFile(File file);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Plays a video from a URL string (http/https/file/web).
|
|
||||||
*
|
|
||||||
* @param url url string
|
|
||||||
*/
|
|
||||||
void playUrl(String url);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pauses playback.
|
|
||||||
*/
|
|
||||||
void pause();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resumes playback (if paused).
|
|
||||||
*/
|
|
||||||
void resume();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stops playback and hides the view.
|
|
||||||
*/
|
|
||||||
void stop();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hides the video view (does not necessarily stop decoding).
|
|
||||||
*/
|
|
||||||
void hide();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the drawing rectangle (in WebView pixel coordinates).
|
|
||||||
*
|
|
||||||
* @param x x
|
|
||||||
* @param y y
|
|
||||||
* @param w width
|
|
||||||
* @param h height
|
|
||||||
*/
|
|
||||||
void setRect(double x, double y, double w, double h);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets volume in range [0..1].
|
|
||||||
*
|
|
||||||
* @param volume volume
|
|
||||||
*/
|
|
||||||
void setVolume(double volume);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enables/disables looping.
|
|
||||||
*
|
|
||||||
* @param loop loop
|
|
||||||
*/
|
|
||||||
void setLoop(boolean loop);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Seeks to a position in seconds.
|
|
||||||
*
|
|
||||||
* @param seconds seconds
|
|
||||||
*/
|
|
||||||
void seek(double seconds);
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,10 @@ package org.openautonomousconnection.luascript.runtime;
|
|||||||
import javafx.concurrent.Worker;
|
import javafx.concurrent.Worker;
|
||||||
import javafx.scene.web.WebEngine;
|
import javafx.scene.web.WebEngine;
|
||||||
import org.luaj.vm2.Globals;
|
import org.luaj.vm2.Globals;
|
||||||
import org.openautonomousconnection.luascript.fx.*;
|
import org.openautonomousconnection.luascript.fx.FxDomHost;
|
||||||
|
import org.openautonomousconnection.luascript.fx.FxEventHost;
|
||||||
|
import org.openautonomousconnection.luascript.fx.FxUiHost;
|
||||||
|
import org.openautonomousconnection.luascript.fx.FxWebViewResourceHost;
|
||||||
import org.openautonomousconnection.luascript.hosts.AudioHost;
|
import org.openautonomousconnection.luascript.hosts.AudioHost;
|
||||||
import org.openautonomousconnection.luascript.hosts.HostServices;
|
import org.openautonomousconnection.luascript.hosts.HostServices;
|
||||||
import org.openautonomousconnection.luascript.security.LuaExecutionPolicy;
|
import org.openautonomousconnection.luascript.security.LuaExecutionPolicy;
|
||||||
@@ -49,7 +52,7 @@ public final class FxLuaScriptEngine implements AutoCloseable {
|
|||||||
/**
|
/**
|
||||||
* Installs a load hook that bootstraps Lua when a page finished loading.
|
* Installs a load hook that bootstraps Lua when a page finished loading.
|
||||||
*/
|
*/
|
||||||
public void install(FxVideoHost videoHost, FxImageHost imageHost) {
|
public void install() {
|
||||||
engine.getLoadWorker().stateProperty().addListener((obs, oldState, newState) -> {
|
engine.getLoadWorker().stateProperty().addListener((obs, oldState, newState) -> {
|
||||||
if (newState == Worker.State.SUCCEEDED) {
|
if (newState == Worker.State.SUCCEEDED) {
|
||||||
bootstrapped.set(false);
|
bootstrapped.set(false);
|
||||||
@@ -80,27 +83,12 @@ public final class FxLuaScriptEngine implements AutoCloseable {
|
|||||||
.sandbox(true)
|
.sandbox(true)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
HostServices.StdoutConsole console = new HostServices.StdoutConsole("[lua] ");
|
||||||
|
FxUiHost uiHost = new FxUiHost(engine, dom);
|
||||||
|
FxWebViewResourceHost resourceHost = new FxWebViewResourceHost(engine);
|
||||||
FxEventHost eventHost = new FxEventHost(dom);
|
FxEventHost eventHost = new FxEventHost(dom);
|
||||||
|
// TODO: Default implementation or parameter for "audioHost"
|
||||||
HostServices services = new HostServices.Default(
|
HostServices services = new HostServices.Default(uiHost, dom, eventHost, resourceHost, console, audioHost);
|
||||||
new FxUiHost(engine, dom),
|
|
||||||
dom,
|
|
||||||
eventHost,
|
|
||||||
new FxWebViewResourceHost(engine),
|
|
||||||
new HostServices.StdoutConsole("[lua] "),
|
|
||||||
new FxAudioHost(),
|
|
||||||
new FxImageHost(engine, dom),
|
|
||||||
new FxVideoHost(engine, dom),
|
|
||||||
new FxSchedulerHost(),
|
|
||||||
new FxSelectorHost(engine, dom),
|
|
||||||
new FxGeometryHost(engine, dom),
|
|
||||||
new FxCssHost(engine, dom),
|
|
||||||
new FxStorageHost(engine, dom),
|
|
||||||
new FxUtilHost(engine, dom),
|
|
||||||
new FxClipboardHost(),
|
|
||||||
new FxObserverHost(engine, dom)
|
|
||||||
);
|
|
||||||
|
|
||||||
LuaRuntime rt = new LuaRuntime(globals, services, policy);
|
LuaRuntime rt = new LuaRuntime(globals, services, policy);
|
||||||
eventHost.setRouter(rt.eventRouter());
|
eventHost.setRouter(rt.eventRouter());
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ import org.openautonomousconnection.luascript.events.LuaEventDispatcher;
|
|||||||
import org.openautonomousconnection.luascript.hosts.HostServices;
|
import org.openautonomousconnection.luascript.hosts.HostServices;
|
||||||
import org.openautonomousconnection.luascript.security.LuaExecutionPolicy;
|
import org.openautonomousconnection.luascript.security.LuaExecutionPolicy;
|
||||||
import org.openautonomousconnection.luascript.security.LuaSecurityManager;
|
import org.openautonomousconnection.luascript.security.LuaSecurityManager;
|
||||||
import org.openautonomousconnection.luascript.tables.*;
|
import org.openautonomousconnection.luascript.tables.DomTable;
|
||||||
|
import org.openautonomousconnection.luascript.tables.EventsTable;
|
||||||
|
import org.openautonomousconnection.luascript.tables.UiTable;
|
||||||
import org.openautonomousconnection.luascript.tables.console.ConsoleTable;
|
import org.openautonomousconnection.luascript.tables.console.ConsoleTable;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
@@ -49,9 +51,6 @@ public final class LuaRuntime implements AutoCloseable {
|
|||||||
new ConsoleTable().inject(globals, services, overwrite);
|
new ConsoleTable().inject(globals, services, overwrite);
|
||||||
new EventsTable(dispatcher).inject(globals, services, overwrite);
|
new EventsTable(dispatcher).inject(globals, services, overwrite);
|
||||||
new DomTable().inject(globals, services, overwrite);
|
new DomTable().inject(globals, services, overwrite);
|
||||||
new AudioTable().inject(globals, services, overwrite);
|
|
||||||
new VideoTable().inject(globals, services, overwrite);
|
|
||||||
new ImageTable().inject(globals, services, overwrite);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void bootstrapFromDom() {
|
public void bootstrapFromDom() {
|
||||||
|
|||||||
@@ -2,90 +2,28 @@ package org.openautonomousconnection.luascript.tables;
|
|||||||
|
|
||||||
import org.luaj.vm2.LuaValue;
|
import org.luaj.vm2.LuaValue;
|
||||||
import org.luaj.vm2.lib.OneArgFunction;
|
import org.luaj.vm2.lib.OneArgFunction;
|
||||||
import org.luaj.vm2.lib.ZeroArgFunction;
|
|
||||||
import org.openautonomousconnection.luascript.hosts.AudioHost;
|
import org.openautonomousconnection.luascript.hosts.AudioHost;
|
||||||
import org.openautonomousconnection.luascript.hosts.HostServices;
|
import org.openautonomousconnection.luascript.hosts.HostServices;
|
||||||
import org.openautonomousconnection.luascript.utils.ScriptTable;
|
import org.openautonomousconnection.luascript.utils.ScriptTable;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
/**
|
public class AudioTable extends ScriptTable {
|
||||||
* Lua table: audio
|
protected AudioTable() {
|
||||||
*
|
|
||||||
* <p>Functions:</p>
|
|
||||||
* <ul>
|
|
||||||
* <li>audio.play(pathOrUrl)</li>
|
|
||||||
* <li>audio.pause()</li>
|
|
||||||
* <li>audio.stop()</li>
|
|
||||||
* <li>audio.volume(v) (0..1)</li>
|
|
||||||
* <li>audio.loop(boolean)</li>
|
|
||||||
* </ul>
|
|
||||||
*/
|
|
||||||
public final class AudioTable extends ScriptTable {
|
|
||||||
|
|
||||||
public AudioTable() {
|
|
||||||
super("audio");
|
super("audio");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void define(HostServices services) {
|
protected void define(HostServices services) {
|
||||||
AudioHost audioHost = services.audio()
|
AudioHost audioHost = services.audio().orElseThrow(() -> new IllegalStateException("AudioHost not provided"));
|
||||||
.orElseThrow(() -> new IllegalStateException("AudioHost not provided"));
|
|
||||||
|
|
||||||
table().set("play", new OneArgFunction() {
|
table().set("load", new OneArgFunction() {
|
||||||
@Override
|
@Override
|
||||||
public LuaValue call(LuaValue arg) {
|
public LuaValue call(LuaValue arg) {
|
||||||
String s = arg.checkjstring();
|
String fileName = arg.checkjstring();
|
||||||
|
|
||||||
if (looksLikeUrl(s)) {
|
return audioHost.load(new File(fileName));
|
||||||
audioHost.playUrl(s);
|
|
||||||
return LuaValue.NIL;
|
|
||||||
}
|
|
||||||
|
|
||||||
audioHost.playFile(new File(s));
|
|
||||||
return LuaValue.NIL;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
table().set("pause", new ZeroArgFunction() {
|
|
||||||
@Override
|
|
||||||
public LuaValue call() {
|
|
||||||
audioHost.pause();
|
|
||||||
return LuaValue.NIL;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
table().set("stop", new ZeroArgFunction() {
|
|
||||||
@Override
|
|
||||||
public LuaValue call() {
|
|
||||||
audioHost.stop();
|
|
||||||
return LuaValue.NIL;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
table().set("volume", new OneArgFunction() {
|
|
||||||
@Override
|
|
||||||
public LuaValue call(LuaValue arg) {
|
|
||||||
double v = arg.checkdouble();
|
|
||||||
if (v < 0.0) v = 0.0;
|
|
||||||
if (v > 1.0) v = 1.0;
|
|
||||||
audioHost.setVolume(v);
|
|
||||||
return LuaValue.NIL;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
table().set("loop", new OneArgFunction() {
|
|
||||||
@Override
|
|
||||||
public LuaValue call(LuaValue arg) {
|
|
||||||
audioHost.setLoop(arg.checkboolean());
|
|
||||||
return LuaValue.NIL;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean looksLikeUrl(String s) {
|
|
||||||
if (s == null) return false;
|
|
||||||
int idx = s.indexOf("://");
|
|
||||||
return idx > 0 && idx < 16;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
package org.openautonomousconnection.luascript.tables;
|
|
||||||
|
|
||||||
import org.luaj.vm2.LuaValue;
|
|
||||||
import org.luaj.vm2.lib.*;
|
|
||||||
import org.openautonomousconnection.luascript.hosts.ClipboardHost;
|
|
||||||
import org.openautonomousconnection.luascript.hosts.HostServices;
|
|
||||||
import org.openautonomousconnection.luascript.utils.ScriptTable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lua table: clipboard
|
|
||||||
*
|
|
||||||
* <p>Functions:</p>
|
|
||||||
* <ul>
|
|
||||||
* <li>clipboard.set(text)</li>
|
|
||||||
* <li>clipboard.get() -> text</li>
|
|
||||||
* </ul>
|
|
||||||
*/
|
|
||||||
public final class ClipboardTable extends ScriptTable {
|
|
||||||
|
|
||||||
public ClipboardTable() {
|
|
||||||
super("clipboard");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void define(HostServices services) {
|
|
||||||
ClipboardHost host = services.clipboard().orElseThrow(() -> new IllegalStateException("ClipboardHost not provided"));
|
|
||||||
|
|
||||||
table().set("set", new OneArgFunction() {
|
|
||||||
@Override
|
|
||||||
public LuaValue call(LuaValue text) {
|
|
||||||
host.setText(text.isnil() ? "" : text.tojstring());
|
|
||||||
return LuaValue.NIL;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
table().set("get", new ZeroArgFunction() {
|
|
||||||
@Override
|
|
||||||
public LuaValue call() {
|
|
||||||
return LuaValue.valueOf(host.getText());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
package org.openautonomousconnection.luascript.tables;
|
|
||||||
|
|
||||||
import org.luaj.vm2.LuaValue;
|
|
||||||
import org.luaj.vm2.lib.*;
|
|
||||||
import org.openautonomousconnection.luascript.events.JavaToLua;
|
|
||||||
import org.openautonomousconnection.luascript.hosts.CssHost;
|
|
||||||
import org.openautonomousconnection.luascript.hosts.HostServices;
|
|
||||||
import org.openautonomousconnection.luascript.utils.ScriptTable;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lua table: css
|
|
||||||
*
|
|
||||||
* <p>Functions:</p>
|
|
||||||
* <ul>
|
|
||||||
* <li>css.computed(id, prop) -> string</li>
|
|
||||||
* <li>css.computedMany(id, {props...}) -> map</li>
|
|
||||||
* <li>css.inlineGet(id, prop) -> string</li>
|
|
||||||
* <li>css.inlineSet(id, prop, value)</li>
|
|
||||||
* <li>css.varGet(id, name) -> string</li>
|
|
||||||
* <li>css.varSet(id, name, value)</li>
|
|
||||||
* </ul>
|
|
||||||
*/
|
|
||||||
public final class CssTable extends ScriptTable {
|
|
||||||
|
|
||||||
public CssTable() {
|
|
||||||
super("css");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void define(HostServices services) {
|
|
||||||
CssHost host = services.css().orElseThrow(() -> new IllegalStateException("CssHost not provided"));
|
|
||||||
|
|
||||||
table().set("computed", new TwoArgFunction() {
|
|
||||||
@Override
|
|
||||||
public LuaValue call(LuaValue id, LuaValue prop) {
|
|
||||||
return LuaValue.valueOf(host.getComputedStyle(id.checkjstring(), prop.checkjstring()));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
table().set("computedMany", new TwoArgFunction() {
|
|
||||||
@Override
|
|
||||||
public LuaValue call(LuaValue id, LuaValue propsTable) {
|
|
||||||
if (!propsTable.istable()) throw new IllegalArgumentException("props must be a table");
|
|
||||||
int n = propsTable.length();
|
|
||||||
String[] props = new String[n];
|
|
||||||
for (int i = 1; i <= n; i++) {
|
|
||||||
props[i - 1] = propsTable.get(i).checkjstring();
|
|
||||||
}
|
|
||||||
Map<String, String> m = host.getComputedStyles(id.checkjstring(), props);
|
|
||||||
return JavaToLua.coerce(m);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
table().set("inlineGet", new TwoArgFunction() {
|
|
||||||
@Override
|
|
||||||
public LuaValue call(LuaValue id, LuaValue prop) {
|
|
||||||
return LuaValue.valueOf(host.getInlineStyle(id.checkjstring(), prop.checkjstring()));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
table().set("inlineSet", new ThreeArgFunction() {
|
|
||||||
@Override
|
|
||||||
public LuaValue call(LuaValue id, LuaValue prop, LuaValue value) {
|
|
||||||
host.setInlineStyle(id.checkjstring(), prop.checkjstring(), value.isnil() ? "" : value.tojstring());
|
|
||||||
return LuaValue.NIL;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
table().set("varGet", new TwoArgFunction() {
|
|
||||||
@Override
|
|
||||||
public LuaValue call(LuaValue id, LuaValue name) {
|
|
||||||
return LuaValue.valueOf(host.getCssVariable(id.checkjstring(), name.checkjstring()));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
table().set("varSet", new ThreeArgFunction() {
|
|
||||||
@Override
|
|
||||||
public LuaValue call(LuaValue id, LuaValue name, LuaValue value) {
|
|
||||||
host.setCssVariable(id.checkjstring(), name.checkjstring(), value.isnil() ? "" : value.tojstring());
|
|
||||||
return LuaValue.NIL;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,11 @@
|
|||||||
package org.openautonomousconnection.luascript.tables;
|
package org.openautonomousconnection.luascript.tables;
|
||||||
|
|
||||||
import org.luaj.vm2.*;
|
import org.luaj.vm2.LuaTable;
|
||||||
import org.luaj.vm2.lib.*;
|
import org.luaj.vm2.LuaValue;
|
||||||
import org.openautonomousconnection.luascript.events.JavaToLua;
|
import org.luaj.vm2.lib.OneArgFunction;
|
||||||
|
import org.luaj.vm2.lib.ThreeArgFunction;
|
||||||
|
import org.luaj.vm2.lib.TwoArgFunction;
|
||||||
|
import org.luaj.vm2.lib.ZeroArgFunction;
|
||||||
import org.openautonomousconnection.luascript.hosts.DomHost;
|
import org.openautonomousconnection.luascript.hosts.DomHost;
|
||||||
import org.openautonomousconnection.luascript.hosts.HostServices;
|
import org.openautonomousconnection.luascript.hosts.HostServices;
|
||||||
import org.openautonomousconnection.luascript.utils.ScriptTable;
|
import org.openautonomousconnection.luascript.utils.ScriptTable;
|
||||||
@@ -32,20 +35,6 @@ public final class DomTable extends ScriptTable {
|
|||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Object luaToJavaScalar(LuaValue v) {
|
|
||||||
if (v == null || v.isnil()) return null;
|
|
||||||
if (v.isboolean()) return v.toboolean();
|
|
||||||
if (v.isnumber()) {
|
|
||||||
// Preserve integer when possible; otherwise double.
|
|
||||||
if (v.isint()) return v.toint();
|
|
||||||
if (v.islong()) return v.tolong();
|
|
||||||
return v.todouble();
|
|
||||||
}
|
|
||||||
if (v.isstring()) return v.tojstring();
|
|
||||||
// Reject complex types for safety (no silent assumptions).
|
|
||||||
throw new LuaError("Unsupported value type for dom.setProp/call argument: " + v.typename());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void define(HostServices services) {
|
protected void define(HostServices services) {
|
||||||
DomHost dom = services.dom().orElseThrow(() -> new IllegalStateException("DomHost not provided"));
|
DomHost dom = services.dom().orElseThrow(() -> new IllegalStateException("DomHost not provided"));
|
||||||
@@ -136,7 +125,8 @@ public final class DomTable extends ScriptTable {
|
|||||||
table().set("children", new OneArgFunction() {
|
table().set("children", new OneArgFunction() {
|
||||||
@Override
|
@Override
|
||||||
public LuaValue call(LuaValue id) {
|
public LuaValue call(LuaValue id) {
|
||||||
return toLuaArray(dom.getChildrenIds(id.checkjstring()));
|
List<String> children = dom.getChildrenIds(id.checkjstring());
|
||||||
|
return toLuaArray(children);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -188,38 +178,5 @@ public final class DomTable extends ScriptTable {
|
|||||||
return LuaValue.NIL;
|
return LuaValue.NIL;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
table().set("setProp", new ThreeArgFunction() {
|
|
||||||
@Override
|
|
||||||
public LuaValue call(LuaValue id, LuaValue prop, LuaValue value) {
|
|
||||||
dom.setProperty(id.checkjstring(), prop.checkjstring(), luaToJavaScalar(value));
|
|
||||||
return LuaValue.NIL;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
table().set("getProp", new TwoArgFunction() {
|
|
||||||
@Override
|
|
||||||
public LuaValue call(LuaValue id, LuaValue prop) {
|
|
||||||
Object ret = dom.getProperty(id.checkjstring(), prop.checkjstring());
|
|
||||||
return JavaToLua.coerce(ret);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
table().set("call", new VarArgFunction() {
|
|
||||||
@Override
|
|
||||||
public Varargs invoke(Varargs args) {
|
|
||||||
String id = args.arg(1).checkjstring();
|
|
||||||
String method = args.arg(2).checkjstring();
|
|
||||||
|
|
||||||
int n = args.narg();
|
|
||||||
Object[] argv = new Object[Math.max(0, n - 2)];
|
|
||||||
for (int i = 3; i <= n; i++) {
|
|
||||||
argv[i - 3] = luaToJavaScalar(args.arg(i));
|
|
||||||
}
|
|
||||||
|
|
||||||
Object ret = dom.call(id, method, argv);
|
|
||||||
return JavaToLua.coerce(ret);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
package org.openautonomousconnection.luascript.tables;
|
|
||||||
|
|
||||||
import org.luaj.vm2.LuaTable;
|
|
||||||
import org.luaj.vm2.LuaValue;
|
|
||||||
import org.luaj.vm2.lib.OneArgFunction;
|
|
||||||
import org.luaj.vm2.lib.TwoArgFunction;
|
|
||||||
import org.luaj.vm2.lib.VarArgFunction;
|
|
||||||
import org.luaj.vm2.Varargs;
|
|
||||||
import org.openautonomousconnection.luascript.events.JavaToLua;
|
|
||||||
import org.openautonomousconnection.luascript.hosts.GeometryHost;
|
|
||||||
import org.openautonomousconnection.luascript.hosts.HostServices;
|
|
||||||
import org.openautonomousconnection.luascript.utils.ScriptTable;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lua table: geometry
|
|
||||||
*
|
|
||||||
* <p>Functions:</p>
|
|
||||||
* <ul>
|
|
||||||
* <li>geometry.rect(id) -> map</li>
|
|
||||||
* <li>geometry.viewport() -> map</li>
|
|
||||||
* <li>geometry.scrollTo(x,y)</li>
|
|
||||||
* <li>geometry.scrollIntoView(id, align?)</li>
|
|
||||||
* </ul>
|
|
||||||
*/
|
|
||||||
public final class GeometryTable extends ScriptTable {
|
|
||||||
|
|
||||||
public GeometryTable() {
|
|
||||||
super("geometry");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void define(HostServices services) {
|
|
||||||
GeometryHost host = services.geometry().orElseThrow(() -> new IllegalStateException("GeometryHost not provided"));
|
|
||||||
|
|
||||||
table().set("rect", new OneArgFunction() {
|
|
||||||
@Override
|
|
||||||
public LuaValue call(LuaValue id) {
|
|
||||||
Map<String, Object> m = host.getBoundingClientRect(id.checkjstring());
|
|
||||||
return JavaToLua.coerce(m);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
table().set("viewport", new org.luaj.vm2.lib.ZeroArgFunction() {
|
|
||||||
@Override
|
|
||||||
public LuaValue call() {
|
|
||||||
return JavaToLua.coerce(host.getViewport());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
table().set("scrollTo", new VarArgFunction() {
|
|
||||||
@Override
|
|
||||||
public Varargs invoke(Varargs args) {
|
|
||||||
host.scrollTo(args.arg(1).checkdouble(), args.arg(2).checkdouble());
|
|
||||||
return LuaValue.NIL;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
table().set("scrollIntoView", new VarArgFunction() {
|
|
||||||
@Override
|
|
||||||
public Varargs invoke(Varargs args) {
|
|
||||||
String id = args.arg(1).checkjstring();
|
|
||||||
String align = args.narg() >= 2 && !args.arg(2).isnil() ? args.arg(2).tojstring() : null;
|
|
||||||
host.scrollIntoView(id, align);
|
|
||||||
return LuaValue.NIL;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
package org.openautonomousconnection.luascript.tables;
|
|
||||||
|
|
||||||
import org.luaj.vm2.LuaValue;
|
|
||||||
import org.luaj.vm2.Varargs;
|
|
||||||
import org.luaj.vm2.lib.VarArgFunction;
|
|
||||||
import org.luaj.vm2.lib.ZeroArgFunction;
|
|
||||||
import org.openautonomousconnection.luascript.hosts.HostServices;
|
|
||||||
import org.openautonomousconnection.luascript.hosts.ImageHost;
|
|
||||||
import org.openautonomousconnection.luascript.utils.ScriptTable;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lua table: image
|
|
||||||
*
|
|
||||||
* <p>Functions:</p>
|
|
||||||
* <ul>
|
|
||||||
* <li>image.show(pathOrUrl)</li>
|
|
||||||
* <li>image.hide()</li>
|
|
||||||
* <li>image.rect(x, y, w, h)</li>
|
|
||||||
* <li>image.opacity(v)</li>
|
|
||||||
* <li>image.preserveRatio(boolean)</li>
|
|
||||||
* <li>image.smooth(boolean)</li>
|
|
||||||
* </ul>
|
|
||||||
*/
|
|
||||||
public final class ImageTable extends ScriptTable {
|
|
||||||
|
|
||||||
public ImageTable() {
|
|
||||||
super("image");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void define(HostServices services) {
|
|
||||||
ImageHost host = services.image()
|
|
||||||
.orElseThrow(() -> new IllegalStateException("ImageHost not provided"));
|
|
||||||
|
|
||||||
table().set("show", new VarArgFunction() {
|
|
||||||
@Override
|
|
||||||
public Varargs invoke(Varargs args) {
|
|
||||||
String src = args.arg(1).checkjstring();
|
|
||||||
if (looksLikeUrl(src)) host.showUrl(src);
|
|
||||||
else host.showFile(new File(src));
|
|
||||||
return LuaValue.NIL;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
table().set("hide", new ZeroArgFunction() {
|
|
||||||
@Override
|
|
||||||
public LuaValue call() {
|
|
||||||
host.hide();
|
|
||||||
return LuaValue.NIL;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
table().set("rect", new VarArgFunction() {
|
|
||||||
@Override
|
|
||||||
public Varargs invoke(Varargs args) {
|
|
||||||
double x = args.arg(1).checkdouble();
|
|
||||||
double y = args.arg(2).checkdouble();
|
|
||||||
double w = args.arg(3).checkdouble();
|
|
||||||
double h = args.arg(4).checkdouble();
|
|
||||||
host.setRect(x, y, w, h);
|
|
||||||
return LuaValue.NIL;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
table().set("opacity", new VarArgFunction() {
|
|
||||||
@Override
|
|
||||||
public Varargs invoke(Varargs args) {
|
|
||||||
double o = args.arg(1).checkdouble();
|
|
||||||
host.setOpacity(o);
|
|
||||||
return LuaValue.NIL;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
table().set("preserveRatio", new VarArgFunction() {
|
|
||||||
@Override
|
|
||||||
public Varargs invoke(Varargs args) {
|
|
||||||
boolean b = args.arg(1).checkboolean();
|
|
||||||
host.setPreserveRatio(b);
|
|
||||||
return LuaValue.NIL;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
table().set("smooth", new VarArgFunction() {
|
|
||||||
@Override
|
|
||||||
public Varargs invoke(Varargs args) {
|
|
||||||
boolean b = args.arg(1).checkboolean();
|
|
||||||
host.setSmooth(b);
|
|
||||||
return LuaValue.NIL;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean looksLikeUrl(String s) {
|
|
||||||
if (s == null) return false;
|
|
||||||
int idx = s.indexOf("://");
|
|
||||||
return idx > 0 && idx < 16;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
package org.openautonomousconnection.luascript.tables;
|
|
||||||
|
|
||||||
import org.luaj.vm2.LuaFunction;
|
|
||||||
import org.luaj.vm2.LuaValue;
|
|
||||||
import org.luaj.vm2.Varargs;
|
|
||||||
import org.luaj.vm2.lib.*;
|
|
||||||
import org.openautonomousconnection.luascript.events.JavaToLua;
|
|
||||||
import org.openautonomousconnection.luascript.hosts.HostServices;
|
|
||||||
import org.openautonomousconnection.luascript.hosts.ObserverHost;
|
|
||||||
import org.openautonomousconnection.luascript.utils.ScriptTable;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lua table: observers
|
|
||||||
*
|
|
||||||
* <p>Functions:</p>
|
|
||||||
* <ul>
|
|
||||||
* <li>observers.on(fn(type, targetId, dataTable))</li>
|
|
||||||
* <li>observers.mutationObserve(id, subtree, attributes, childList, characterData)</li>
|
|
||||||
* <li>observers.mutationUnobserve(id)</li>
|
|
||||||
* <li>observers.resizeObserve(id)</li>
|
|
||||||
* <li>observers.resizeUnobserve(id)</li>
|
|
||||||
* <li>observers.intersectionObserve(id, threshold)</li>
|
|
||||||
* <li>observers.intersectionUnobserve(id)</li>
|
|
||||||
* </ul>
|
|
||||||
*/
|
|
||||||
public final class ObserversTable extends ScriptTable {
|
|
||||||
|
|
||||||
public ObserversTable() {
|
|
||||||
super("observers");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void define(HostServices services) {
|
|
||||||
ObserverHost host = services.observers().orElseThrow(() -> new IllegalStateException("ObserverHost not provided"));
|
|
||||||
|
|
||||||
table().set("on", new OneArgFunction() {
|
|
||||||
@Override
|
|
||||||
public LuaValue call(LuaValue fn) {
|
|
||||||
LuaFunction cb = fn.checkfunction();
|
|
||||||
host.setCallback((type, targetId, data) -> {
|
|
||||||
LuaValue luaData = JavaToLua.coerce(data);
|
|
||||||
cb.call(LuaValue.valueOf(type), LuaValue.valueOf(targetId), luaData);
|
|
||||||
});
|
|
||||||
return LuaValue.NIL;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
table().set("mutationObserve", new VarArgFunction() {
|
|
||||||
@Override
|
|
||||||
public Varargs invoke(Varargs args) {
|
|
||||||
String id = args.arg(1).checkjstring();
|
|
||||||
boolean subtree = args.arg(2).optboolean(false);
|
|
||||||
boolean attributes = args.arg(3).optboolean(true);
|
|
||||||
boolean childList = args.arg(4).optboolean(true);
|
|
||||||
boolean characterData = args.arg(5).optboolean(false);
|
|
||||||
host.observeMutations(id, subtree, attributes, childList, characterData);
|
|
||||||
return LuaValue.NIL;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
table().set("mutationUnobserve", new OneArgFunction() {
|
|
||||||
@Override
|
|
||||||
public LuaValue call(LuaValue id) {
|
|
||||||
host.unobserveMutations(id.checkjstring());
|
|
||||||
return LuaValue.NIL;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
table().set("resizeObserve", new OneArgFunction() {
|
|
||||||
@Override
|
|
||||||
public LuaValue call(LuaValue id) {
|
|
||||||
host.observeResize(id.checkjstring());
|
|
||||||
return LuaValue.NIL;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
table().set("resizeUnobserve", new OneArgFunction() {
|
|
||||||
@Override
|
|
||||||
public LuaValue call(LuaValue id) {
|
|
||||||
host.unobserveResize(id.checkjstring());
|
|
||||||
return LuaValue.NIL;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
table().set("intersectionObserve", new TwoArgFunction() {
|
|
||||||
@Override
|
|
||||||
public LuaValue call(LuaValue id, LuaValue threshold) {
|
|
||||||
host.observeIntersection(id.checkjstring(), threshold.checkdouble());
|
|
||||||
return LuaValue.NIL;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
table().set("intersectionUnobserve", new OneArgFunction() {
|
|
||||||
@Override
|
|
||||||
public LuaValue call(LuaValue id) {
|
|
||||||
host.unobserveIntersection(id.checkjstring());
|
|
||||||
return LuaValue.NIL;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
package org.openautonomousconnection.luascript.tables;
|
|
||||||
|
|
||||||
import org.luaj.vm2.LuaFunction;
|
|
||||||
import org.luaj.vm2.LuaValue;
|
|
||||||
import org.luaj.vm2.lib.OneArgFunction;
|
|
||||||
import org.luaj.vm2.lib.TwoArgFunction;
|
|
||||||
import org.luaj.vm2.lib.VarArgFunction;
|
|
||||||
import org.luaj.vm2.Varargs;
|
|
||||||
import org.openautonomousconnection.luascript.hosts.HostServices;
|
|
||||||
import org.openautonomousconnection.luascript.hosts.SchedulerHost;
|
|
||||||
import org.openautonomousconnection.luascript.utils.ScriptTable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lua table: scheduler
|
|
||||||
*
|
|
||||||
* <p>Functions:</p>
|
|
||||||
* <ul>
|
|
||||||
* <li>scheduler.timeout(ms, fn) -> id</li>
|
|
||||||
* <li>scheduler.interval(ms, fn) -> id</li>
|
|
||||||
* <li>scheduler.clear(id) -> boolean</li>
|
|
||||||
* <li>scheduler.raf(fn) -> id</li>
|
|
||||||
* <li>scheduler.cancelRaf(id) -> boolean</li>
|
|
||||||
* </ul>
|
|
||||||
*/
|
|
||||||
public final class SchedulerTable extends ScriptTable {
|
|
||||||
|
|
||||||
public SchedulerTable() {
|
|
||||||
super("scheduler");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void define(HostServices services) {
|
|
||||||
SchedulerHost host = services.scheduler().orElseThrow(() -> new IllegalStateException("SchedulerHost not provided"));
|
|
||||||
|
|
||||||
table().set("timeout", new TwoArgFunction() {
|
|
||||||
@Override
|
|
||||||
public LuaValue call(LuaValue ms, LuaValue fn) {
|
|
||||||
long delay = ms.checklong();
|
|
||||||
LuaFunction cb = fn.checkfunction();
|
|
||||||
long id = host.setTimeout(delay, () -> cb.call());
|
|
||||||
return LuaValue.valueOf(id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
table().set("interval", new TwoArgFunction() {
|
|
||||||
@Override
|
|
||||||
public LuaValue call(LuaValue ms, LuaValue fn) {
|
|
||||||
long interval = ms.checklong();
|
|
||||||
LuaFunction cb = fn.checkfunction();
|
|
||||||
long id = host.setInterval(interval, () -> cb.call());
|
|
||||||
return LuaValue.valueOf(id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
table().set("clear", new OneArgFunction() {
|
|
||||||
@Override
|
|
||||||
public LuaValue call(LuaValue id) {
|
|
||||||
return LuaValue.valueOf(host.clear(id.checklong()));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
table().set("raf", new OneArgFunction() {
|
|
||||||
@Override
|
|
||||||
public LuaValue call(LuaValue fn) {
|
|
||||||
LuaFunction cb = fn.checkfunction();
|
|
||||||
long id = host.requestAnimationFrame(() -> cb.call());
|
|
||||||
return LuaValue.valueOf(id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
table().set("cancelRaf", new OneArgFunction() {
|
|
||||||
@Override
|
|
||||||
public LuaValue call(LuaValue id) {
|
|
||||||
return LuaValue.valueOf(host.cancelAnimationFrame(id.checklong()));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
package org.openautonomousconnection.luascript.tables;
|
|
||||||
|
|
||||||
import org.luaj.vm2.LuaTable;
|
|
||||||
import org.luaj.vm2.LuaValue;
|
|
||||||
import org.luaj.vm2.lib.TwoArgFunction;
|
|
||||||
import org.luaj.vm2.lib.OneArgFunction;
|
|
||||||
import org.openautonomousconnection.luascript.hosts.HostServices;
|
|
||||||
import org.openautonomousconnection.luascript.hosts.SelectorHost;
|
|
||||||
import org.openautonomousconnection.luascript.utils.ScriptTable;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lua table: selector
|
|
||||||
*
|
|
||||||
* <p>Functions:</p>
|
|
||||||
* <ul>
|
|
||||||
* <li>selector.one(css) -> id|nil</li>
|
|
||||||
* <li>selector.all(css) -> { ids... }</li>
|
|
||||||
* <li>selector.matches(id, css) -> boolean</li>
|
|
||||||
* <li>selector.closest(id, css) -> id|nil</li>
|
|
||||||
* </ul>
|
|
||||||
*/
|
|
||||||
public final class SelectorTable extends ScriptTable {
|
|
||||||
|
|
||||||
public SelectorTable() {
|
|
||||||
super("selector");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static LuaValue toLuaArray(List<String> values) {
|
|
||||||
LuaTable t = new LuaTable();
|
|
||||||
if (values == null || values.isEmpty()) return t;
|
|
||||||
int i = 1;
|
|
||||||
for (String v : values) {
|
|
||||||
t.set(i++, v == null ? LuaValue.NIL : LuaValue.valueOf(v));
|
|
||||||
}
|
|
||||||
return t;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void define(HostServices services) {
|
|
||||||
SelectorHost host = services.selector().orElseThrow(() -> new IllegalStateException("SelectorHost not provided"));
|
|
||||||
|
|
||||||
table().set("one", new OneArgFunction() {
|
|
||||||
@Override
|
|
||||||
public LuaValue call(LuaValue css) {
|
|
||||||
String id = host.querySelector(css.checkjstring());
|
|
||||||
return id == null ? LuaValue.NIL : LuaValue.valueOf(id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
table().set("all", new OneArgFunction() {
|
|
||||||
@Override
|
|
||||||
public LuaValue call(LuaValue css) {
|
|
||||||
return toLuaArray(host.querySelectorAll(css.checkjstring()));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
table().set("matches", new TwoArgFunction() {
|
|
||||||
@Override
|
|
||||||
public LuaValue call(LuaValue id, LuaValue css) {
|
|
||||||
return LuaValue.valueOf(host.matches(id.checkjstring(), css.checkjstring()));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
table().set("closest", new TwoArgFunction() {
|
|
||||||
@Override
|
|
||||||
public LuaValue call(LuaValue id, LuaValue css) {
|
|
||||||
String out = host.closest(id.checkjstring(), css.checkjstring());
|
|
||||||
return out == null ? LuaValue.NIL : LuaValue.valueOf(out);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,121 +0,0 @@
|
|||||||
package org.openautonomousconnection.luascript.tables;
|
|
||||||
|
|
||||||
import org.luaj.vm2.LuaTable;
|
|
||||||
import org.luaj.vm2.LuaValue;
|
|
||||||
import org.luaj.vm2.lib.*;
|
|
||||||
import org.openautonomousconnection.luascript.hosts.HostServices;
|
|
||||||
import org.openautonomousconnection.luascript.hosts.StorageHost;
|
|
||||||
import org.openautonomousconnection.luascript.utils.ScriptTable;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lua table: storage
|
|
||||||
*
|
|
||||||
* <p>Functions:</p>
|
|
||||||
* <ul>
|
|
||||||
* <li>storage.localGet(key) -> string|nil</li>
|
|
||||||
* <li>storage.localSet(key, value|nil)</li>
|
|
||||||
* <li>storage.localKeys() -> {keys...}</li>
|
|
||||||
* <li>storage.localRemove(key)</li>
|
|
||||||
* <li>storage.localClear()</li>
|
|
||||||
* <li>same for session*</li>
|
|
||||||
* </ul>
|
|
||||||
*/
|
|
||||||
public final class StorageTable extends ScriptTable {
|
|
||||||
|
|
||||||
public StorageTable() {
|
|
||||||
super("storage");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static LuaValue toLuaArray(List<String> values) {
|
|
||||||
LuaTable t = new LuaTable();
|
|
||||||
if (values == null || values.isEmpty()) return t;
|
|
||||||
int i = 1;
|
|
||||||
for (String v : values) t.set(i++, v == null ? LuaValue.NIL : LuaValue.valueOf(v));
|
|
||||||
return t;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void define(HostServices services) {
|
|
||||||
StorageHost host = services.storage().orElseThrow(() -> new IllegalStateException("StorageHost not provided"));
|
|
||||||
|
|
||||||
table().set("localKeys", new ZeroArgFunction() {
|
|
||||||
@Override
|
|
||||||
public LuaValue call() {
|
|
||||||
return toLuaArray(host.localKeys());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
table().set("localGet", new OneArgFunction() {
|
|
||||||
@Override
|
|
||||||
public LuaValue call(LuaValue key) {
|
|
||||||
String v = host.localGet(key.checkjstring());
|
|
||||||
return v == null ? LuaValue.NIL : LuaValue.valueOf(v);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
table().set("localSet", new TwoArgFunction() {
|
|
||||||
@Override
|
|
||||||
public LuaValue call(LuaValue key, LuaValue value) {
|
|
||||||
host.localSet(key.checkjstring(), value.isnil() ? null : value.tojstring());
|
|
||||||
return LuaValue.NIL;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
table().set("localRemove", new OneArgFunction() {
|
|
||||||
@Override
|
|
||||||
public LuaValue call(LuaValue key) {
|
|
||||||
host.localRemove(key.checkjstring());
|
|
||||||
return LuaValue.NIL;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
table().set("localClear", new ZeroArgFunction() {
|
|
||||||
@Override
|
|
||||||
public LuaValue call() {
|
|
||||||
host.localClear();
|
|
||||||
return LuaValue.NIL;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
table().set("sessionKeys", new ZeroArgFunction() {
|
|
||||||
@Override
|
|
||||||
public LuaValue call() {
|
|
||||||
return toLuaArray(host.sessionKeys());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
table().set("sessionGet", new OneArgFunction() {
|
|
||||||
@Override
|
|
||||||
public LuaValue call(LuaValue key) {
|
|
||||||
String v = host.sessionGet(key.checkjstring());
|
|
||||||
return v == null ? LuaValue.NIL : LuaValue.valueOf(v);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
table().set("sessionSet", new TwoArgFunction() {
|
|
||||||
@Override
|
|
||||||
public LuaValue call(LuaValue key, LuaValue value) {
|
|
||||||
host.sessionSet(key.checkjstring(), value.isnil() ? null : value.tojstring());
|
|
||||||
return LuaValue.NIL;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
table().set("sessionRemove", new OneArgFunction() {
|
|
||||||
@Override
|
|
||||||
public LuaValue call(LuaValue key) {
|
|
||||||
host.sessionRemove(key.checkjstring());
|
|
||||||
return LuaValue.NIL;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
table().set("sessionClear", new ZeroArgFunction() {
|
|
||||||
@Override
|
|
||||||
public LuaValue call() {
|
|
||||||
host.sessionClear();
|
|
||||||
return LuaValue.NIL;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
package org.openautonomousconnection.luascript.tables;
|
|
||||||
|
|
||||||
import org.luaj.vm2.LuaValue;
|
|
||||||
import org.luaj.vm2.lib.*;
|
|
||||||
import org.openautonomousconnection.luascript.events.JavaToLua;
|
|
||||||
import org.openautonomousconnection.luascript.hosts.HostServices;
|
|
||||||
import org.openautonomousconnection.luascript.hosts.UtilHost;
|
|
||||||
import org.openautonomousconnection.luascript.utils.ScriptTable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lua table: util
|
|
||||||
*
|
|
||||||
* <p>Functions:</p>
|
|
||||||
* <ul>
|
|
||||||
* <li>util.base64Encode(text)</li>
|
|
||||||
* <li>util.base64Decode(b64)</li>
|
|
||||||
* <li>util.randomHex(bytes)</li>
|
|
||||||
* <li>util.parseUrl(url) -> map</li>
|
|
||||||
* <li>util.parseQuery(query) -> map(key->array)</li>
|
|
||||||
* <li>util.jsonStringifyExpr(elementId, jsExpr) -> jsonString</li>
|
|
||||||
* <li>util.jsonNormalize(elementId, json) -> jsonString</li>
|
|
||||||
* </ul>
|
|
||||||
*/
|
|
||||||
public final class UtilTable extends ScriptTable {
|
|
||||||
|
|
||||||
public UtilTable() {
|
|
||||||
super("util");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void define(HostServices services) {
|
|
||||||
UtilHost host = services.util().orElseThrow(() -> new IllegalStateException("UtilHost not provided"));
|
|
||||||
|
|
||||||
table().set("base64Encode", new OneArgFunction() {
|
|
||||||
@Override
|
|
||||||
public LuaValue call(LuaValue text) {
|
|
||||||
return LuaValue.valueOf(host.base64Encode(text.isnil() ? "" : text.tojstring()));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
table().set("base64Decode", new OneArgFunction() {
|
|
||||||
@Override
|
|
||||||
public LuaValue call(LuaValue b64) {
|
|
||||||
return LuaValue.valueOf(host.base64Decode(b64.isnil() ? "" : b64.tojstring()));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
table().set("randomHex", new OneArgFunction() {
|
|
||||||
@Override
|
|
||||||
public LuaValue call(LuaValue bytes) {
|
|
||||||
return LuaValue.valueOf(host.randomHex(bytes.checkint()));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
table().set("parseUrl", new OneArgFunction() {
|
|
||||||
@Override
|
|
||||||
public LuaValue call(LuaValue url) {
|
|
||||||
return JavaToLua.coerce(host.parseUrl(url.checkjstring()));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
table().set("parseQuery", new OneArgFunction() {
|
|
||||||
@Override
|
|
||||||
public LuaValue call(LuaValue q) {
|
|
||||||
return JavaToLua.coerce(host.parseQuery(q.isnil() ? "" : q.tojstring()));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
table().set("jsonStringifyExpr", new TwoArgFunction() {
|
|
||||||
@Override
|
|
||||||
public LuaValue call(LuaValue elementId, LuaValue jsExpr) {
|
|
||||||
return LuaValue.valueOf(host.jsonStringifyExpr(elementId.checkjstring(), jsExpr.checkjstring()));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
table().set("jsonNormalize", new TwoArgFunction() {
|
|
||||||
@Override
|
|
||||||
public LuaValue call(LuaValue elementId, LuaValue json) {
|
|
||||||
return LuaValue.valueOf(host.jsonNormalize(elementId.checkjstring(), json.checkjstring()));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,127 +0,0 @@
|
|||||||
package org.openautonomousconnection.luascript.tables;
|
|
||||||
|
|
||||||
import org.luaj.vm2.LuaValue;
|
|
||||||
import org.luaj.vm2.Varargs;
|
|
||||||
import org.luaj.vm2.lib.VarArgFunction;
|
|
||||||
import org.luaj.vm2.lib.ZeroArgFunction;
|
|
||||||
import org.openautonomousconnection.luascript.hosts.HostServices;
|
|
||||||
import org.openautonomousconnection.luascript.hosts.VideoHost;
|
|
||||||
import org.openautonomousconnection.luascript.utils.ScriptTable;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lua table: video
|
|
||||||
*
|
|
||||||
* <p>Functions:</p>
|
|
||||||
* <ul>
|
|
||||||
* <li>video.play(pathOrUrl)</li>
|
|
||||||
* <li>video.pause()</li>
|
|
||||||
* <li>video.resume()</li>
|
|
||||||
* <li>video.stop()</li>
|
|
||||||
* <li>video.hide()</li>
|
|
||||||
* <li>video.rect(x, y, w, h)</li>
|
|
||||||
* <li>video.volume(v)</li>
|
|
||||||
* <li>video.loop(boolean)</li>
|
|
||||||
* <li>video.seek(seconds)</li>
|
|
||||||
* </ul>
|
|
||||||
*/
|
|
||||||
public final class VideoTable extends ScriptTable {
|
|
||||||
|
|
||||||
public VideoTable() {
|
|
||||||
super("video");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void define(HostServices services) {
|
|
||||||
VideoHost host = services.video()
|
|
||||||
.orElseThrow(() -> new IllegalStateException("VideoHost not provided"));
|
|
||||||
|
|
||||||
table().set("play", new VarArgFunction() {
|
|
||||||
@Override
|
|
||||||
public Varargs invoke(Varargs args) {
|
|
||||||
String src = args.arg(1).checkjstring();
|
|
||||||
if (looksLikeUrl(src)) host.playUrl(src);
|
|
||||||
else host.playFile(new File(src));
|
|
||||||
return LuaValue.NIL;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
table().set("pause", new ZeroArgFunction() {
|
|
||||||
@Override
|
|
||||||
public LuaValue call() {
|
|
||||||
host.pause();
|
|
||||||
return LuaValue.NIL;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
table().set("resume", new ZeroArgFunction() {
|
|
||||||
@Override
|
|
||||||
public LuaValue call() {
|
|
||||||
host.resume();
|
|
||||||
return LuaValue.NIL;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
table().set("stop", new ZeroArgFunction() {
|
|
||||||
@Override
|
|
||||||
public LuaValue call() {
|
|
||||||
host.stop();
|
|
||||||
return LuaValue.NIL;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
table().set("hide", new ZeroArgFunction() {
|
|
||||||
@Override
|
|
||||||
public LuaValue call() {
|
|
||||||
host.hide();
|
|
||||||
return LuaValue.NIL;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
table().set("rect", new VarArgFunction() {
|
|
||||||
@Override
|
|
||||||
public Varargs invoke(Varargs args) {
|
|
||||||
double x = args.arg(1).checkdouble();
|
|
||||||
double y = args.arg(2).checkdouble();
|
|
||||||
double w = args.arg(3).checkdouble();
|
|
||||||
double h = args.arg(4).checkdouble();
|
|
||||||
host.setRect(x, y, w, h);
|
|
||||||
return LuaValue.NIL;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
table().set("volume", new VarArgFunction() {
|
|
||||||
@Override
|
|
||||||
public Varargs invoke(Varargs args) {
|
|
||||||
double v = args.arg(1).checkdouble();
|
|
||||||
host.setVolume(v);
|
|
||||||
return LuaValue.NIL;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
table().set("loop", new VarArgFunction() {
|
|
||||||
@Override
|
|
||||||
public Varargs invoke(Varargs args) {
|
|
||||||
boolean b = args.arg(1).checkboolean();
|
|
||||||
host.setLoop(b);
|
|
||||||
return LuaValue.NIL;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
table().set("seek", new VarArgFunction() {
|
|
||||||
@Override
|
|
||||||
public Varargs invoke(Varargs args) {
|
|
||||||
double s = args.arg(1).checkdouble();
|
|
||||||
host.seek(s);
|
|
||||||
return LuaValue.NIL;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean looksLikeUrl(String s) {
|
|
||||||
if (s == null) return false;
|
|
||||||
int idx = s.indexOf("://");
|
|
||||||
return idx > 0 && idx < 16;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,27 +2,23 @@ package org.openautonomousconnection.luascript.tables.console;
|
|||||||
|
|
||||||
import org.luaj.vm2.LuaValue;
|
import org.luaj.vm2.LuaValue;
|
||||||
import org.luaj.vm2.lib.OneArgFunction;
|
import org.luaj.vm2.lib.OneArgFunction;
|
||||||
import org.openautonomousconnection.luascript.hosts.ConsoleHost;
|
|
||||||
import org.openautonomousconnection.luascript.hosts.HostServices;
|
import org.openautonomousconnection.luascript.hosts.HostServices;
|
||||||
import org.openautonomousconnection.luascript.utils.ScriptTable;
|
import org.openautonomousconnection.luascript.utils.ScriptTable;
|
||||||
|
|
||||||
|
public class ConsoleLogTable extends ScriptTable {
|
||||||
/**
|
/**
|
||||||
* Lua table: console.log
|
* Creates a new script table with the given global name.
|
||||||
*/
|
*/
|
||||||
public final class ConsoleLogTable extends ScriptTable {
|
|
||||||
|
|
||||||
public ConsoleLogTable() {
|
public ConsoleLogTable() {
|
||||||
super("log");
|
super("log");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void define(HostServices services) {
|
protected void define(HostServices services) {
|
||||||
ConsoleHost console = services.console().orElseThrow(() -> new IllegalStateException("ConsoleHost not provided"));
|
|
||||||
|
|
||||||
table().set("info", new OneArgFunction() {
|
table().set("info", new OneArgFunction() {
|
||||||
@Override
|
@Override
|
||||||
public LuaValue call(LuaValue arg) {
|
public LuaValue call(LuaValue arg) {
|
||||||
console.info(arg.isnil() ? "nil" : arg.tojstring());
|
services.console().get().info(arg.isnil() ? "nil" : arg.tojstring());
|
||||||
return LuaValue.NIL;
|
return LuaValue.NIL;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -30,7 +26,7 @@ public final class ConsoleLogTable extends ScriptTable {
|
|||||||
table().set("log", new OneArgFunction() {
|
table().set("log", new OneArgFunction() {
|
||||||
@Override
|
@Override
|
||||||
public LuaValue call(LuaValue arg) {
|
public LuaValue call(LuaValue arg) {
|
||||||
console.log(arg.isnil() ? "nil" : arg.tojstring());
|
services.console().get().log(arg.isnil() ? "nil" : arg.tojstring());
|
||||||
return LuaValue.NIL;
|
return LuaValue.NIL;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -38,7 +34,7 @@ public final class ConsoleLogTable extends ScriptTable {
|
|||||||
table().set("warn", new OneArgFunction() {
|
table().set("warn", new OneArgFunction() {
|
||||||
@Override
|
@Override
|
||||||
public LuaValue call(LuaValue arg) {
|
public LuaValue call(LuaValue arg) {
|
||||||
console.warn(arg.isnil() ? "nil" : arg.tojstring());
|
services.console().get().warn(arg.isnil() ? "nil" : arg.tojstring());
|
||||||
return LuaValue.NIL;
|
return LuaValue.NIL;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,27 +2,23 @@ package org.openautonomousconnection.luascript.tables.console;
|
|||||||
|
|
||||||
import org.luaj.vm2.LuaValue;
|
import org.luaj.vm2.LuaValue;
|
||||||
import org.luaj.vm2.lib.OneArgFunction;
|
import org.luaj.vm2.lib.OneArgFunction;
|
||||||
import org.openautonomousconnection.luascript.hosts.ConsoleHost;
|
|
||||||
import org.openautonomousconnection.luascript.hosts.HostServices;
|
import org.openautonomousconnection.luascript.hosts.HostServices;
|
||||||
import org.openautonomousconnection.luascript.utils.ScriptTable;
|
import org.openautonomousconnection.luascript.utils.ScriptTable;
|
||||||
|
|
||||||
|
public class ConsoleStacktraceTable extends ScriptTable {
|
||||||
/**
|
/**
|
||||||
* Lua table: console.stacktrace
|
* Creates a new script table with the given global name.
|
||||||
*/
|
*/
|
||||||
public final class ConsoleStacktraceTable extends ScriptTable {
|
|
||||||
|
|
||||||
public ConsoleStacktraceTable() {
|
public ConsoleStacktraceTable() {
|
||||||
super("stacktrace");
|
super("stacktrace");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void define(HostServices services) {
|
protected void define(HostServices services) {
|
||||||
ConsoleHost console = services.console().orElseThrow(() -> new IllegalStateException("ConsoleHost not provided"));
|
|
||||||
|
|
||||||
table().set("print", new OneArgFunction() {
|
table().set("print", new OneArgFunction() {
|
||||||
@Override
|
@Override
|
||||||
public LuaValue call(LuaValue arg) {
|
public LuaValue call(LuaValue arg) {
|
||||||
console.error(arg.isnil() ? "nil" : arg.tojstring());
|
services.console().get().error(arg.isnil() ? "nil" : arg.tojstring());
|
||||||
return LuaValue.NIL;
|
return LuaValue.NIL;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -30,7 +26,7 @@ public final class ConsoleStacktraceTable extends ScriptTable {
|
|||||||
table().set("exception", new OneArgFunction() {
|
table().set("exception", new OneArgFunction() {
|
||||||
@Override
|
@Override
|
||||||
public LuaValue call(LuaValue arg) {
|
public LuaValue call(LuaValue arg) {
|
||||||
console.exception(arg.isnil() ? "nil" : arg.tojstring());
|
services.console().get().exception(arg.isnil() ? "nil" : arg.tojstring());
|
||||||
return LuaValue.NIL;
|
return LuaValue.NIL;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,11 +3,10 @@ package org.openautonomousconnection.luascript.tables.console;
|
|||||||
import org.openautonomousconnection.luascript.hosts.HostServices;
|
import org.openautonomousconnection.luascript.hosts.HostServices;
|
||||||
import org.openautonomousconnection.luascript.utils.ScriptTable;
|
import org.openautonomousconnection.luascript.utils.ScriptTable;
|
||||||
|
|
||||||
|
public class ConsoleTable extends ScriptTable {
|
||||||
/**
|
/**
|
||||||
* Lua table: console
|
* Creates a new script table with the given global name.
|
||||||
*/
|
*/
|
||||||
public final class ConsoleTable extends ScriptTable {
|
|
||||||
|
|
||||||
public ConsoleTable() {
|
public ConsoleTable() {
|
||||||
super("console");
|
super("console");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
/*
|
||||||
|
* This code, and therefore this entire project, is licensed under
|
||||||
|
* the Open Autonomous Public License (OAPL) v1.0, or one that
|
||||||
|
* derives from that license.
|
||||||
|
* For more details, please refer to the LICENSE file.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2026 Maple.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.openautonomousconnection.luascript.type;
|
||||||
|
|
||||||
|
import javafx.scene.media.MediaPlayer;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import org.luaj.vm2.LuaFunction;
|
||||||
|
import org.luaj.vm2.LuaNumber;
|
||||||
|
import org.luaj.vm2.LuaTable;
|
||||||
|
import org.luaj.vm2.LuaValue;
|
||||||
|
import org.openautonomousconnection.luascript.tables.AudioTable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Different from {@link AudioTable} in that this is an instance gathered from {@code AudioTable::load}
|
||||||
|
*/
|
||||||
|
public class AudioValue extends LuaTable {
|
||||||
|
@Getter @Setter
|
||||||
|
private MediaPlayer mediaPlayer;
|
||||||
|
|
||||||
|
public AudioValue(MediaPlayer mediaPlayer) {
|
||||||
|
this.mediaPlayer = mediaPlayer;
|
||||||
|
|
||||||
|
this.set("play", new LuaFunction() {
|
||||||
|
@Override
|
||||||
|
public LuaValue call() {
|
||||||
|
mediaPlayer.play();
|
||||||
|
|
||||||
|
return LuaValue.NIL;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.set("stop", new LuaFunction() {
|
||||||
|
@Override
|
||||||
|
public LuaValue call() {
|
||||||
|
mediaPlayer.stop();
|
||||||
|
|
||||||
|
return LuaValue.NIL;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.set("pause", new LuaFunction() {
|
||||||
|
@Override
|
||||||
|
public LuaValue call() {
|
||||||
|
mediaPlayer.pause();
|
||||||
|
|
||||||
|
return LuaValue.NIL;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.set("setVolume", new LuaFunction() {
|
||||||
|
@Override
|
||||||
|
public LuaValue call(LuaValue volume) {
|
||||||
|
double v = volume.checkdouble();
|
||||||
|
|
||||||
|
mediaPlayer.setVolume(Math.clamp(v, 0, 1));
|
||||||
|
|
||||||
|
return LuaValue.NIL;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.set("getVolume", new LuaFunction() {
|
||||||
|
@Override
|
||||||
|
public LuaValue call() {
|
||||||
|
return LuaNumber.valueOf(mediaPlayer.getVolume());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user