diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserNavBar.java b/app/src/main/java/io/xpipe/app/browser/BrowserNavBar.java index f4c4f55f6..06fb8cd32 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserNavBar.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserNavBar.java @@ -84,7 +84,7 @@ public class BrowserNavBar extends Comp { var graphic = Bindings.createStringBinding( () -> { return model.getCurrentDirectory() != null - ? FileIconManager.getFileIcon(model.getCurrentDirectory(), false) + ? FileIconManager.getFileIcon(model.getCurrentDirectory()) : null; }, model.getCurrentPath()); diff --git a/app/src/main/java/io/xpipe/app/browser/file/BrowserEntry.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserEntry.java index 616693aab..184cc12a7 100644 --- a/app/src/main/java/io/xpipe/app/browser/file/BrowserEntry.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserEntry.java @@ -65,11 +65,11 @@ public class BrowserEntry { if (fileType != null) { return fileType.getIcon(); } else if (directoryType != null) { - return directoryType.getIcon(rawFileEntry, false); + return directoryType.getIcon(rawFileEntry); } else { return rawFileEntry != null && rawFileEntry.resolved().getKind() == FileKind.DIRECTORY - ? "default_folder.svg" - : "default_file.svg"; + ? "browser/default_folder.svg" + : "browser/default_file.svg"; } } diff --git a/app/src/main/java/io/xpipe/app/browser/file/BrowserQuickAccessContextMenu.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserQuickAccessContextMenu.java index bedb69614..9d98d53de 100644 --- a/app/src/main/java/io/xpipe/app/browser/file/BrowserQuickAccessContextMenu.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserQuickAccessContextMenu.java @@ -143,7 +143,7 @@ public class BrowserQuickAccessContextMenu extends ContextMenu { // Use original name, not the link target browserEntry.getRawFileEntry().getName(), PrettyImageHelper.ofFixedSize( - FileIconManager.getFileIcon(browserEntry.getRawFileEntry(), false), 24, 24) + FileIconManager.getFileIcon(browserEntry.getRawFileEntry()), 24, 24) .createRegion()); createMenu(); addInputListeners(); diff --git a/app/src/main/java/io/xpipe/app/browser/icon/BrowserIconDirectoryType.java b/app/src/main/java/io/xpipe/app/browser/icon/BrowserIconDirectoryType.java index 93f969469..19bc93962 100644 --- a/app/src/main/java/io/xpipe/app/browser/icon/BrowserIconDirectoryType.java +++ b/app/src/main/java/io/xpipe/app/browser/icon/BrowserIconDirectoryType.java @@ -42,8 +42,8 @@ public abstract class BrowserIconDirectoryType { } @Override - public String getIcon(FileEntry entry, boolean open) { - return open ? "default_root_folder_opened.svg" : "default_root_folder.svg"; + public String getIcon(FileEntry entry) { + return "browser/default_root_folder.svg"; } }); @@ -60,16 +60,12 @@ public abstract class BrowserIconDirectoryType { }) .collect(Collectors.toSet()); - var closedIcon = split[2].trim(); - var openIcon = split[3].trim(); - - var lightClosedIcon = split.length > 4 ? split[4].trim() : closedIcon; - var lightOpenIcon = split.length > 4 ? split[5].trim() : openIcon; + var closedIcon = "browser/" + split[2].trim(); + var lightClosedIcon = split.length > 4 ? "browser/" + split[4].trim() : closedIcon; ALL.add(new Simple( id, new IconVariant(lightClosedIcon, closedIcon), - new IconVariant(lightOpenIcon, openIcon), filter)); } } @@ -84,7 +80,7 @@ public abstract class BrowserIconDirectoryType { public abstract boolean matches(FileEntry entry); - public abstract String getIcon(FileEntry entry, boolean open); + public abstract String getIcon(FileEntry entry); public static class Simple extends BrowserIconDirectoryType { @@ -92,13 +88,11 @@ public abstract class BrowserIconDirectoryType { private final String id; private final IconVariant closed; - private final IconVariant open; private final Set names; - public Simple(String id, IconVariant closed, IconVariant open, Set names) { + public Simple(String id, IconVariant closed, Set names) { this.id = id; this.closed = closed; - this.open = open; this.names = names; } @@ -113,8 +107,8 @@ public abstract class BrowserIconDirectoryType { } @Override - public String getIcon(FileEntry entry, boolean open) { - return open ? this.open.getIcon() : this.closed.getIcon(); + public String getIcon(FileEntry entry) { + return this.closed.getIcon(); } } } diff --git a/app/src/main/java/io/xpipe/app/browser/icon/BrowserIconFileType.java b/app/src/main/java/io/xpipe/app/browser/icon/BrowserIconFileType.java index 2f3bb25be..4293aa2b6 100644 --- a/app/src/main/java/io/xpipe/app/browser/icon/BrowserIconFileType.java +++ b/app/src/main/java/io/xpipe/app/browser/icon/BrowserIconFileType.java @@ -47,8 +47,8 @@ public abstract class BrowserIconFileType { return "." + r; }) .collect(Collectors.toSet()); - var darkIcon = split[2].trim(); - var lightIcon = split.length > 3 ? split[3].trim() : darkIcon; + var darkIcon = "browser/" + split[2].trim(); + var lightIcon = (split.length > 3 ? "browser/" + split[3].trim() : darkIcon); ALL.add(new BrowserIconFileType.Simple(id, lightIcon, darkIcon, filter)); } } diff --git a/app/src/main/java/io/xpipe/app/browser/icon/BrowserIcons.java b/app/src/main/java/io/xpipe/app/browser/icon/BrowserIcons.java index e156867ea..18fc39bde 100644 --- a/app/src/main/java/io/xpipe/app/browser/icon/BrowserIcons.java +++ b/app/src/main/java/io/xpipe/app/browser/icon/BrowserIcons.java @@ -7,11 +7,11 @@ import io.xpipe.core.store.FileEntry; public class BrowserIcons { public static Comp createDefaultFileIcon() { - return PrettyImageHelper.ofFixedSizeSquare("default_file.svg", 24); + return PrettyImageHelper.ofFixedSizeSquare("browser/default_file.svg", 24); } public static Comp createDefaultDirectoryIcon() { - return PrettyImageHelper.ofFixedSizeSquare("default_folder.svg", 24); + return PrettyImageHelper.ofFixedSizeSquare("browser/default_folder.svg", 24); } public static Comp createIcon(BrowserIconFileType type) { @@ -19,6 +19,6 @@ public class BrowserIcons { } public static Comp createIcon(FileEntry entry) { - return PrettyImageHelper.ofFixedSizeSquare(FileIconManager.getFileIcon(entry, false), 24); + return PrettyImageHelper.ofFixedSizeSquare(FileIconManager.getFileIcon(entry), 24); } } diff --git a/app/src/main/java/io/xpipe/app/browser/icon/FileIconManager.java b/app/src/main/java/io/xpipe/app/browser/icon/FileIconManager.java index f71ccf164..0797b6d11 100644 --- a/app/src/main/java/io/xpipe/app/browser/icon/FileIconManager.java +++ b/app/src/main/java/io/xpipe/app/browser/icon/FileIconManager.java @@ -1,7 +1,5 @@ package io.xpipe.app.browser.icon; -import io.xpipe.app.resources.AppImages; -import io.xpipe.app.resources.AppResources; import io.xpipe.core.store.FileEntry; import io.xpipe.core.store.FileKind; @@ -13,12 +11,11 @@ public class FileIconManager { if (!loaded) { BrowserIconFileType.loadDefinitions(); BrowserIconDirectoryType.loadDefinitions(); - AppImages.loadDirectory(AppResources.XPIPE_MODULE, "img/browser", true, false); loaded = true; } } - public static synchronized String getFileIcon(FileEntry entry, boolean open) { + public static synchronized String getFileIcon(FileEntry entry) { if (entry == null) { return null; } @@ -33,13 +30,13 @@ public class FileIconManager { } else { for (var f : BrowserIconDirectoryType.getAll()) { if (f.matches(r)) { - return f.getIcon(r, open); + return f.getIcon(r); } } } - return r.getKind() == FileKind.DIRECTORY - ? (open ? "default_folder_opened.svg" : "default_folder.svg") - : "default_file.svg"; + return "browser/" + (r.getKind() == FileKind.DIRECTORY + ? "default_folder.svg" + : "default_file.svg"); } } diff --git a/app/src/main/java/io/xpipe/app/comp/store/StoreEntryWrapper.java b/app/src/main/java/io/xpipe/app/comp/store/StoreEntryWrapper.java index 44ed9823f..a94f1fd9e 100644 --- a/app/src/main/java/io/xpipe/app/comp/store/StoreEntryWrapper.java +++ b/app/src/main/java/io/xpipe/app/comp/store/StoreEntryWrapper.java @@ -9,15 +9,15 @@ import io.xpipe.app.storage.DataStorage; import io.xpipe.app.storage.DataStoreCategory; import io.xpipe.app.storage.DataStoreEntry; import io.xpipe.app.util.ThreadHelper; - import javafx.beans.property.*; import javafx.collections.FXCollections; - import lombok.Getter; import java.time.Duration; import java.time.Instant; -import java.util.*; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; @Getter public class StoreEntryWrapper { @@ -40,7 +40,8 @@ public class StoreEntryWrapper { private final Property category = new SimpleObjectProperty<>(); private final Property summary = new SimpleObjectProperty<>(); private final Property notes; - private final Property icon = new SimpleObjectProperty<>(); + private final Property customIcon = new SimpleObjectProperty<>(); + private final Property iconFile = new SimpleObjectProperty<>(); public StoreEntryWrapper(DataStoreEntry entry) { this.entry = entry; @@ -138,7 +139,8 @@ public class StoreEntryWrapper { } color.setValue(entry.getColor()); notes.setValue(new StoreNotes(entry.getNotes(), entry.getNotes())); - icon.setValue(entry.getIcon()); + customIcon.setValue(entry.getIcon()); + iconFile.setValue(getEffectiveIconFile()); busy.setValue(entry.getBusyCounter().get() != 0); deletable.setValue(entry.getConfiguration().isDeletable() @@ -192,6 +194,20 @@ public class StoreEntryWrapper { } } + private String getEffectiveIconFile() { + if (disabledProperty().get()) { + return "disabled_icon.png"; + } + + if (getCustomIcon().getValue() == null) { + return getEntry() + .getProvider() + .getDisplayIconFileName(getEntry().getStore()); + } + + return "app:system/" + getCustomIcon().getValue() + ".svg"; + } + private boolean showActionProvider(ActionProvider p) { var leaf = p.getLeafDataStoreCallSite(); if (leaf != null) { diff --git a/app/src/main/java/io/xpipe/app/comp/store/StoreIconChoiceComp.java b/app/src/main/java/io/xpipe/app/comp/store/StoreIconChoiceComp.java index f5db06ebb..4676a2191 100644 --- a/app/src/main/java/io/xpipe/app/comp/store/StoreIconChoiceComp.java +++ b/app/src/main/java/io/xpipe/app/comp/store/StoreIconChoiceComp.java @@ -137,7 +137,7 @@ public class StoreIconChoiceComp extends SimpleComp { } root.setText(icon.getDisplayName()); - image.set(icon.getIconName() + ".svg"); + image.set("app:system/" + icon.getIconName() + ".svg"); setGraphic(root); } } diff --git a/app/src/main/java/io/xpipe/app/comp/store/StoreIconChoiceDialogComp.java b/app/src/main/java/io/xpipe/app/comp/store/StoreIconChoiceDialogComp.java index 85ddbf4bb..b522d9064 100644 --- a/app/src/main/java/io/xpipe/app/comp/store/StoreIconChoiceDialogComp.java +++ b/app/src/main/java/io/xpipe/app/comp/store/StoreIconChoiceDialogComp.java @@ -27,7 +27,6 @@ import java.util.List; public class StoreIconChoiceDialogComp extends SimpleComp { public static void show(DataStoreEntry entry) { - SystemIcons.load(); var window = AppWindowHelper.sideWindow( AppI18n.get("chooseCustomIcon"), stage -> new StoreIconChoiceDialogComp(entry, stage), false, null); window.initModality(Modality.APPLICATION_MODAL); diff --git a/app/src/main/java/io/xpipe/app/comp/store/StoreIconComp.java b/app/src/main/java/io/xpipe/app/comp/store/StoreIconComp.java index 85ccb9af1..5eab09747 100644 --- a/app/src/main/java/io/xpipe/app/comp/store/StoreIconComp.java +++ b/app/src/main/java/io/xpipe/app/comp/store/StoreIconComp.java @@ -3,15 +3,12 @@ package io.xpipe.app.comp.store; import io.xpipe.app.fxcomps.SimpleComp; import io.xpipe.app.fxcomps.impl.PrettyImageHelper; import io.xpipe.app.fxcomps.impl.TooltipAugment; -import io.xpipe.app.resources.SystemIcons; - import javafx.beans.binding.Bindings; import javafx.geometry.Pos; import javafx.scene.input.MouseButton; import javafx.scene.input.MouseEvent; import javafx.scene.layout.Region; import javafx.scene.layout.StackPane; - import lombok.AllArgsConstructor; import org.kordamp.ikonli.javafx.FontIcon; @@ -24,12 +21,7 @@ public class StoreIconComp extends SimpleComp { @Override protected Region createSimple() { - var icon = Bindings.createStringBinding( - () -> { - return getImage(); - }, - wrapper.getIcon()); - var imageComp = PrettyImageHelper.ofFixedSize(icon, w, h); + var imageComp = PrettyImageHelper.ofFixedSize(wrapper.getIconFile(), w, h); var storeIcon = imageComp.createRegion(); if (wrapper.getValidity().getValue().isUsable()) { new TooltipAugment<>(wrapper.getEntry().getProvider().displayName(), null).augment(storeIcon); @@ -67,19 +59,4 @@ public class StoreIconComp extends SimpleComp { return stack; } - - private String getImage() { - if (wrapper.disabledProperty().get()) { - return "disabled_icon.png"; - } - - if (wrapper.getIcon().getValue() == null) { - return wrapper.getEntry() - .getProvider() - .getDisplayIconFileName(wrapper.getEntry().getStore()); - } - - SystemIcons.load(); - return "app:system/" + wrapper.getIcon().getValue() + ".svg"; - } } diff --git a/app/src/main/java/io/xpipe/app/core/check/AppAvCheck.java b/app/src/main/java/io/xpipe/app/core/check/AppAvCheck.java index eb56da272..53dc49092 100644 --- a/app/src/main/java/io/xpipe/app/core/check/AppAvCheck.java +++ b/app/src/main/java/io/xpipe/app/core/check/AppAvCheck.java @@ -1,20 +1,20 @@ package io.xpipe.app.core.check; import io.xpipe.app.comp.base.MarkdownComp; -import io.xpipe.app.core.*; +import io.xpipe.app.core.AppI18n; +import io.xpipe.app.core.AppProperties; +import io.xpipe.app.core.AppState; +import io.xpipe.app.core.AppStyle; import io.xpipe.app.core.mode.OperationMode; import io.xpipe.app.core.window.AppWindowHelper; -import io.xpipe.app.resources.AppImages; import io.xpipe.app.resources.AppResources; import io.xpipe.app.util.PlatformState; import io.xpipe.app.util.WindowsRegistry; import io.xpipe.core.process.OsType; - import javafx.geometry.Insets; import javafx.scene.control.Alert; import javafx.scene.control.ButtonBar; import javafx.scene.control.ButtonType; - import lombok.Getter; import java.nio.file.Files; @@ -44,7 +44,6 @@ public class AppAvCheck { PlatformState.initPlatformOrThrow(); AppStyle.init(); - AppImages.init(); var a = AppWindowHelper.showBlockingAlert(alert -> { alert.setTitle(AppI18n.get("antivirusNoticeTitle")); diff --git a/app/src/main/java/io/xpipe/app/core/mode/GuiMode.java b/app/src/main/java/io/xpipe/app/core/mode/GuiMode.java index fef22244d..f6b69283d 100644 --- a/app/src/main/java/io/xpipe/app/core/mode/GuiMode.java +++ b/app/src/main/java/io/xpipe/app/core/mode/GuiMode.java @@ -4,6 +4,7 @@ import io.xpipe.app.browser.file.LocalFileSystem; import io.xpipe.app.browser.icon.FileIconManager; import io.xpipe.app.core.App; import io.xpipe.app.core.AppGreetings; +import io.xpipe.app.core.AppLayoutModel; import io.xpipe.app.core.check.AppPtbCheck; import io.xpipe.app.core.window.AppMainWindow; import io.xpipe.app.fxcomps.util.PlatformThread; @@ -12,7 +13,6 @@ import io.xpipe.app.issue.TrackEvent; import io.xpipe.app.update.UpdateChangelogAlert; import io.xpipe.app.util.NativeBridge; import io.xpipe.app.util.ThreadHelper; - import javafx.stage.Stage; public class GuiMode extends PlatformMode { @@ -39,6 +39,7 @@ public class GuiMode extends PlatformMode { AppGreetings.showIfNeeded(); AppPtbCheck.check(); NativeBridge.init(); + AppLayoutModel.init(); TrackEvent.info("Waiting for window setup completion ..."); PlatformThread.runLaterIfNeededBlocking(() -> { diff --git a/app/src/main/java/io/xpipe/app/core/mode/PlatformMode.java b/app/src/main/java/io/xpipe/app/core/mode/PlatformMode.java index fe9bcdb65..1c31fd485 100644 --- a/app/src/main/java/io/xpipe/app/core/mode/PlatformMode.java +++ b/app/src/main/java/io/xpipe/app/core/mode/PlatformMode.java @@ -9,7 +9,6 @@ import io.xpipe.app.resources.AppImages; import io.xpipe.app.update.UpdateAvailableAlert; import io.xpipe.app.util.PlatformState; import io.xpipe.app.util.ThreadHelper; - import javafx.application.Application; public abstract class PlatformMode extends OperationMode { @@ -30,11 +29,13 @@ public abstract class PlatformMode extends OperationMode { PlatformState.initPlatformOrThrow(); // Check if we can load system fonts or fail AppFontLoadingCheck.check(); + // Can be loaded async + var imageThread = ThreadHelper.runFailableAsync(() -> { + AppImages.init(); + }); AppFont.init(); AppTheme.init(); AppStyle.init(); - AppImages.init(); - AppLayoutModel.init(); TrackEvent.info("Finished essential component initialization before platform"); TrackEvent.info("Launching application ..."); @@ -57,6 +58,7 @@ public abstract class PlatformMode extends OperationMode { } StoreViewState.init(); + imageThread.join(); } @Override diff --git a/app/src/main/java/io/xpipe/app/fxcomps/impl/DataStoreChoiceComp.java b/app/src/main/java/io/xpipe/app/fxcomps/impl/DataStoreChoiceComp.java index 851f530e4..a23691f9f 100644 --- a/app/src/main/java/io/xpipe/app/fxcomps/impl/DataStoreChoiceComp.java +++ b/app/src/main/java/io/xpipe/app/fxcomps/impl/DataStoreChoiceComp.java @@ -216,7 +216,6 @@ public class DataStoreChoiceComp extends SimpleComp { selected.getValue().getStore()); } - SystemIcons.load(); return "app:system/" + selected.getValue().get().getIcon() + ".svg"; }, diff --git a/app/src/main/java/io/xpipe/app/resources/AppImages.java b/app/src/main/java/io/xpipe/app/resources/AppImages.java index ce4402536..1843279c6 100644 --- a/app/src/main/java/io/xpipe/app/resources/AppImages.java +++ b/app/src/main/java/io/xpipe/app/resources/AppImages.java @@ -3,10 +3,8 @@ package io.xpipe.app.resources; import io.xpipe.app.core.AppExtensionManager; import io.xpipe.app.issue.ErrorEvent; import io.xpipe.app.issue.TrackEvent; - import javafx.scene.image.Image; import javafx.scene.image.WritableImage; - import org.apache.commons.io.FilenameUtils; import java.awt.image.BufferedImage; @@ -16,6 +14,8 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; +import java.time.Duration; +import java.time.Instant; import java.util.HashMap; import java.util.Map; @@ -37,6 +37,7 @@ public class AppImages { } public static void loadDirectory(String module, String dir, boolean loadImages, boolean loadSvgs) { + var start = Instant.now(); AppResources.with(module, dir, basePath -> { if (!Files.exists(basePath)) { return; @@ -49,12 +50,17 @@ public class AppImages { public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { var relativeFileName = FilenameUtils.separatorsToUnix( basePath.relativize(file).toString()); + var key = defaultPrefix + relativeFileName; + if (images.containsKey(key) || svgImages.containsKey(key)) { + return FileVisitResult.CONTINUE; + } + try { if (FilenameUtils.getExtension(file.toString()).equals("svg") && loadSvgs) { var s = Files.readString(file); - svgImages.put(defaultPrefix + relativeFileName, s); + svgImages.put(key, s); } else if (loadImages) { - images.put(defaultPrefix + relativeFileName, loadImage(file)); + images.put(key, loadImage(file)); } } catch (IOException ex) { ErrorEvent.fromThrowable(ex).omitted(true).build().handle(); @@ -63,6 +69,8 @@ public class AppImages { } }); }); + var elapsed = Duration.between(start, Instant.now()); + TrackEvent.trace("Loaded images in " + module + ":" + dir + " in " + elapsed.toMillis() + " ms"); } public static String svgImage(String file) { diff --git a/app/src/main/java/io/xpipe/app/storage/DataStoreEntry.java b/app/src/main/java/io/xpipe/app/storage/DataStoreEntry.java index 73b1d9182..37a4be454 100644 --- a/app/src/main/java/io/xpipe/app/storage/DataStoreEntry.java +++ b/app/src/main/java/io/xpipe/app/storage/DataStoreEntry.java @@ -172,6 +172,20 @@ public class DataStoreEntry extends StorageElement { return entry; } + + public String getEffectiveIconFile() { + if (getValidity() == Validity.LOAD_FAILED) { + return "disabled_icon.png"; + } + + if (icon == null) { + return getProvider() + .getDisplayIconFileName(getStore()); + } + + return "app:system/" + icon + ".svg"; + } + void refreshIcon() { if (icon != null && SystemIcons.getForId(icon).isEmpty()) { icon = null;