mirror of
https://github.com/xpipe-io/xpipe.git
synced 2026-04-22 23:49:09 -04:00
Fixes [stage]
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user