Fixes [stage]

This commit is contained in:
crschnick
2025-01-26 14:35:56 +00:00
parent 1b5bffb52e
commit 7629828df4
8 changed files with 25 additions and 355 deletions

View File

@@ -1,116 +0,0 @@
package io.xpipe.app.comp.base;
import io.xpipe.app.comp.SimpleComp;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.resources.AppImages;
import io.xpipe.app.util.PlatformThread;
import io.xpipe.core.store.FileNames;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Pos;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import java.util.function.Consumer;
public class PrettySvgComp extends SimpleComp {
private final ObservableValue<String> value;
private final double width;
private final double height;
public PrettySvgComp(ObservableValue<String> value, double width, double height) {
this.value = value;
this.width = width;
this.height = height;
}
@Override
protected Region createSimple() {
var image = new SimpleStringProperty();
var syncValue = PlatformThread.sync(value);
var storeIcon = SvgView.create(Bindings.createObjectBinding(
() -> {
if (image.get() == null) {
return null;
}
if (AppImages.hasSvgImage(image.getValue())) {
return AppImages.svgImage(image.getValue());
} else if (AppImages.hasSvgImage(image.getValue().replace("-dark", ""))) {
return AppImages.svgImage(image.getValue().replace("-dark", ""));
} else {
return null;
}
},
image));
var ar = Bindings.createDoubleBinding(
() -> {
return storeIcon.getWidth().getValue().doubleValue()
/ storeIcon.getHeight().getValue().doubleValue();
},
storeIcon.getWidth(),
storeIcon.getHeight());
var widthProperty = Bindings.createDoubleBinding(
() -> {
boolean widthLimited = width / height < ar.doubleValue();
if (widthLimited) {
return width;
} else {
return height * ar.doubleValue();
}
},
ar);
var heightProperty = Bindings.createDoubleBinding(
() -> {
boolean widthLimited = width / height < ar.doubleValue();
if (widthLimited) {
return width / ar.doubleValue();
} else {
return height;
}
},
ar);
var stack = new StackPane();
var wv = storeIcon.createWebview();
if (wv.isPresent()) {
var node = wv.get();
node.prefWidthProperty().bind(widthProperty);
node.maxWidthProperty().bind(widthProperty);
node.minWidthProperty().bind(widthProperty);
node.prefHeightProperty().bind(heightProperty);
node.maxHeightProperty().bind(heightProperty);
node.minHeightProperty().bind(heightProperty);
stack.getChildren().add(node);
}
Consumer<String> update = val -> {
var useDark = AppPrefs.get() != null
&& AppPrefs.get().theme.get() != null
&& AppPrefs.get().theme.get().isDark();
var fixed = val != null
? FileNames.getBaseName(val) + (useDark ? "-dark" : "") + "." + FileNames.getExtension(val)
: null;
image.set(fixed);
};
syncValue.subscribe(update);
if (AppPrefs.get() != null) {
AppPrefs.get().theme.addListener((observable, oldValue, newValue) -> {
update.accept(syncValue.getValue());
});
}
stack.setFocusTraversable(false);
stack.setPrefWidth(width);
stack.setMinWidth(width);
stack.setPrefHeight(height);
stack.setMinHeight(height);
stack.setAlignment(Pos.CENTER);
stack.getStyleClass().add("stack");
return stack;
}
}

View File

@@ -1,45 +0,0 @@
package io.xpipe.app.comp.base;
import javafx.css.Size;
import javafx.css.SizeUnits;
import javafx.geometry.Point2D;
import java.util.regex.Pattern;
public class SvgHelper {
public static Size parseSize(String string) {
for (SizeUnits unit : SizeUnits.values()) {
if (string.endsWith(unit.toString())) {
return new Size(
Double.parseDouble(string.substring(
0, string.length() - unit.toString().length())),
unit);
}
}
return new Size(Double.parseDouble(string), SizeUnits.PX);
}
public static Point2D getDimensions(String val) {
var regularExpression = Pattern.compile("<svg[^>]+?width=\"([^ ]+)\"", Pattern.DOTALL);
var matcher = regularExpression.matcher(val);
if (!matcher.find()) {
var viewBox = Pattern.compile(
"<svg.+?viewBox=\"([\\d.]+)\\s+([\\d.]+)\\s+([\\d.]+)\\s+([\\d.]+)\"", Pattern.DOTALL);
matcher = viewBox.matcher(val);
if (matcher.find()) {
return new Point2D(
parseSize(matcher.group(3)).pixels(),
parseSize(matcher.group(4)).pixels());
}
}
var width = matcher.group(1);
regularExpression = Pattern.compile("<svg.+?height=\"([^ ]+)\"", Pattern.DOTALL);
matcher = regularExpression.matcher(val);
matcher.find();
var height = matcher.group(1);
return new Point2D(parseSize(width).pixels(), parseSize(height).pixels());
}
}

View File

@@ -1,146 +0,0 @@
package io.xpipe.app.comp.base;
import io.xpipe.app.comp.CompStructure;
import io.xpipe.app.core.AppProperties;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.util.PlatformThread;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.ListChangeListener;
import javafx.scene.AccessibleRole;
import javafx.scene.Node;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.web.WebView;
import lombok.Builder;
import lombok.Getter;
import lombok.SneakyThrows;
import lombok.Value;
import java.util.Optional;
import java.util.Set;
@Getter
public class SvgView {
private final ObservableValue<Number> width;
private final ObservableValue<Number> height;
private final ObservableValue<String> svgContent;
private static boolean canCreateWebview = true;
private SvgView(ObservableValue<Number> width, ObservableValue<Number> height, ObservableValue<String> svgContent) {
this.width = PlatformThread.sync(width);
this.height = PlatformThread.sync(height);
this.svgContent = PlatformThread.sync(svgContent);
}
@SneakyThrows
public static SvgView create(ObservableValue<String> content) {
var widthProperty = new SimpleIntegerProperty();
var heightProperty = new SimpleIntegerProperty();
content.subscribe(val -> {
if (val == null || val.isBlank()) {
return;
}
var dim = SvgHelper.getDimensions(val);
widthProperty.set((int) Math.ceil(dim.getX()));
heightProperty.set((int) Math.ceil(dim.getY()));
});
return new SvgView(widthProperty, heightProperty, content);
}
private String getHtml(String content) {
return "<html><body style='margin: 0; padding: 0; border: none;' >" + content + "</body></html>";
}
private WebView createWebView() {
if (!canCreateWebview) {
return null;
}
WebView wv;
try {
// This can happen if we are using a custom JavaFX build without webkit
wv = new WebView();
} catch (Throwable t) {
ErrorEvent.fromThrowable(t).omit().expected().handle();
canCreateWebview = false;
return null;
}
wv.getStyleClass().add("svg-comp");
wv.getEngine()
.setUserDataDirectory(
AppProperties.get().getDataDir().resolve("webview").toFile());
// Sometimes a web view might not render when the background is set to transparent, at least according to stack
// overflow
wv.setPageFill(Color.valueOf("#00000001"));
// wv.setPageFill(Color.BLACK);
wv.getEngine().setJavaScriptEnabled(false);
wv.setContextMenuEnabled(false);
wv.setFocusTraversable(false);
wv.setAccessibleRole(AccessibleRole.IMAGE_VIEW);
wv.setDisable(true);
wv.getEngine().loadContent(svgContent.getValue() != null ? getHtml(svgContent.getValue()) : null);
svgContent.subscribe(n -> {
if (n == null) {
wv.setOpacity(0.0);
return;
}
wv.setOpacity(1.0);
var html = getHtml(n);
wv.getEngine().loadContent(html);
});
// Hide scrollbars that popup on every content change. Bug in WebView?
wv.getChildrenUnmodifiable().addListener((ListChangeListener<Node>) change -> {
Set<Node> scrolls = wv.lookupAll(".scroll-bar");
for (Node scroll : scrolls) {
scroll.setFocusTraversable(false);
scroll.setVisible(false);
scroll.setManaged(false);
}
});
// As the aspect ratio of the WebView is kept constant, we can compute the zoom only using the width
wv.zoomProperty()
.bind(Bindings.createDoubleBinding(
() -> {
return wv.getWidth() / width.getValue().doubleValue();
},
wv.widthProperty(),
width));
wv.maxWidthProperty().bind(wv.prefWidthProperty());
wv.maxHeightProperty().bind(wv.prefHeightProperty());
wv.minWidthProperty().bind(wv.prefWidthProperty());
wv.minHeightProperty().bind(wv.prefHeightProperty());
return wv;
}
public Optional<WebView> createWebview() {
var wv = createWebView();
return Optional.ofNullable(wv);
}
@Value
@Builder
public static class Structure implements CompStructure<StackPane> {
StackPane pane;
WebView webView;
@Override
public StackPane get() {
return pane;
}
}
}

View File

@@ -6,6 +6,7 @@ import com.github.weisj.jsvg.attributes.ViewBox;
import com.github.weisj.jsvg.parser.SVGLoader;
import io.xpipe.app.core.AppProperties;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.resources.AppImages;
import lombok.Getter;
import javax.imageio.ImageIO;
@@ -41,7 +42,7 @@ public class SystemIconCache {
}
}
public static void buildCache(Map<SystemIconSource, SystemIconSourceData> all) {
public static void rebuildCache(Map<SystemIconSource, SystemIconSourceData> all) {
try {
for (var e : all.entrySet()) {
var target = DIRECTORY.resolve(e.getKey().getId());

View File

@@ -29,12 +29,12 @@ public class SystemIconManager {
}
public static void init() throws Exception {
loadSources();
reloadSources();
SystemIconCache.refreshBuilt();
loadImages();
reloadImages();
}
public static synchronized void loadSources() throws Exception {
public static synchronized void reloadSources() throws Exception {
Files.createDirectories(DIRECTORY);
LOADED.clear();
@@ -51,7 +51,8 @@ public class SystemIconManager {
});
}
public static void loadImages() {
public static void reloadImages() {
AppImages.remove(s -> s.startsWith("icons/"));
try {
for (var source : AppPrefs.get().getIconSources().getValue()) {
AppImages.loadRasterImages(SystemIconCache.getDirectory(source), "icons/" + source.getId());
@@ -66,9 +67,10 @@ public class SystemIconManager {
for (var source : AppPrefs.get().getIconSources().getValue()) {
source.refresh();
}
loadSources();
SystemIconCache.buildCache(LOADED);
reloadSources();
SystemIconCache.rebuildCache(LOADED);
SystemIconCache.refreshBuilt();
reloadImages();
}
public static Path getPoolPath() {

View File

@@ -11,6 +11,7 @@ import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
@Value
public class SystemIconSourceData {
@@ -39,7 +40,7 @@ public class SystemIconSourceData {
var hasLightVariant = Files.exists(file.getParent().resolve(cleanedName + "-light.svg"));
var hasDarkVariant = Files.exists(file.getParent().resolve(cleanedName + "-dark.svg"));
if (hasLightVariant && !hasDarkVariant && name.endsWith("-light")) {
var s = new SystemIconSourceFile(source, cleanedName, file, true);
var s = new SystemIconSourceFile(source, cleanedName.toLowerCase(Locale.ROOT), file, true);
sourceFiles.add(s);
continue;
}
@@ -48,7 +49,7 @@ public class SystemIconSourceData {
continue;
}
var s = new SystemIconSourceFile(source, cleanedName, file, false);
var s = new SystemIconSourceFile(source, cleanedName.toLowerCase(Locale.ROOT), file, false);
sourceFiles.add(s);
}
}

View File

@@ -20,25 +20,29 @@ import java.time.Duration;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Predicate;
public class AppImages {
public static final Image DEFAULT_IMAGE = new WritableImage(1, 1);
private static final Map<String, Image> images = new HashMap<>();
private static final Map<String, String> svgImages = new HashMap<>();
public static void remove(Predicate<String> filter) {
images.keySet().removeIf(filter);
}
public static void init() {
if (images.size() > 0 || svgImages.size() > 0) {
if (images.size() > 0) {
return;
}
TrackEvent.info("Loading images ...");
for (var module : AppExtensionManager.getInstance().getContentModules()) {
loadDirectory(module.getName(), "img", true, true);
loadDirectory(module.getName(), "img", true);
}
}
public static void loadDirectory(String module, String dir, boolean loadImages, boolean loadSvgs) {
public static void loadDirectory(String module, String dir, boolean loadImages) {
var start = Instant.now();
AppResources.with(module, dir, basePath -> {
if (!Files.exists(basePath)) {
@@ -53,19 +57,12 @@ public class AppImages {
var relativeFileName = FilenameUtils.separatorsToUnix(
basePath.relativize(file).toString());
var key = defaultPrefix + relativeFileName;
if (images.containsKey(key) || svgImages.containsKey(key)) {
if (images.containsKey(key)) {
return FileVisitResult.CONTINUE;
}
try {
if (FilenameUtils.getExtension(file.toString()).equals("svg") && loadSvgs) {
var s = Files.readString(file);
svgImages.put(key, s);
} else if (loadImages) {
images.put(key, loadImage(file));
}
} catch (IOException ex) {
ErrorEvent.fromThrowable(ex).omitted(true).build().handle();
if (loadImages) {
images.put(key, loadImage(file));
}
return FileVisitResult.CONTINUE;
}
@@ -97,21 +94,6 @@ public class AppImages {
});
}
public static String svgImage(String file) {
if (file == null) {
return "";
}
var key = file.contains(":") ? file : "app:" + file;
if (svgImages.containsKey(key)) {
return svgImages.get(key);
}
TrackEvent.warn("Svg image " + key + " not found");
return "";
}
public static boolean hasNormalImage(String file) {
if (file == null) {
return false;
@@ -125,15 +107,6 @@ public class AppImages {
return images.containsKey(key);
}
public static boolean hasSvgImage(String file) {
if (file == null) {
return false;
}
var key = file.contains(":") ? file : "app:" + file;
return svgImages.containsKey(key);
}
public static Image image(String file) {
if (file == null) {
return DEFAULT_IMAGE;

View File

@@ -1 +1 @@
15.0-6
15.0-7