added asset workspace working with packer
This commit is contained in:
parent
1506858b25
commit
1f6df50f09
@ -145,6 +145,7 @@ public enum I18n {
|
|||||||
ASSETS_INPUTS_EMPTY("assets.inputs.empty"),
|
ASSETS_INPUTS_EMPTY("assets.inputs.empty"),
|
||||||
ASSETS_DIAGNOSTICS_EMPTY("assets.diagnostics.empty"),
|
ASSETS_DIAGNOSTICS_EMPTY("assets.diagnostics.empty"),
|
||||||
ASSETS_PREVIEW_EMPTY("assets.preview.empty"),
|
ASSETS_PREVIEW_EMPTY("assets.preview.empty"),
|
||||||
|
ASSETS_PREVIEW_ZOOM("assets.preview.zoom"),
|
||||||
ASSETS_PREVIEW_TEXT_ERROR("assets.preview.textError"),
|
ASSETS_PREVIEW_TEXT_ERROR("assets.preview.textError"),
|
||||||
ASSETS_PREVIEW_IMAGE_ERROR("assets.preview.imageError"),
|
ASSETS_PREVIEW_IMAGE_ERROR("assets.preview.imageError"),
|
||||||
ASSETS_PREVIEW_AUDIO_PLACEHOLDER("assets.preview.audioPlaceholder"),
|
ASSETS_PREVIEW_AUDIO_PLACEHOLDER("assets.preview.audioPlaceholder"),
|
||||||
|
|||||||
@ -9,11 +9,11 @@ public final class AssetNavigatorProjectionBuilder {
|
|||||||
|
|
||||||
public static AssetNavigatorProjection build(
|
public static AssetNavigatorProjection build(
|
||||||
List<AssetWorkspaceAssetSummary> assets,
|
List<AssetWorkspaceAssetSummary> assets,
|
||||||
Path assetsRoot,
|
Path projectRoot,
|
||||||
String searchQuery,
|
String searchQuery,
|
||||||
Set<AssetNavigatorFilter> filters) {
|
Set<AssetNavigatorFilter> filters) {
|
||||||
Objects.requireNonNull(assets, "assets");
|
Objects.requireNonNull(assets, "assets");
|
||||||
final Path normalizedAssetsRoot = Objects.requireNonNull(assetsRoot, "assetsRoot").toAbsolutePath().normalize();
|
final Path normalizedProjectRoot = Objects.requireNonNull(projectRoot, "projectRoot").toAbsolutePath().normalize();
|
||||||
final String normalizedQuery = normalizeQuery(searchQuery);
|
final String normalizedQuery = normalizeQuery(searchQuery);
|
||||||
final Set<AssetNavigatorFilter> normalizedFilters = filters == null || filters.isEmpty()
|
final Set<AssetNavigatorFilter> normalizedFilters = filters == null || filters.isEmpty()
|
||||||
? EnumSet.noneOf(AssetNavigatorFilter.class)
|
? EnumSet.noneOf(AssetNavigatorFilter.class)
|
||||||
@ -21,10 +21,10 @@ public final class AssetNavigatorProjectionBuilder {
|
|||||||
|
|
||||||
final Map<String, List<AssetWorkspaceAssetSummary>> grouped = new LinkedHashMap<>();
|
final Map<String, List<AssetWorkspaceAssetSummary>> grouped = new LinkedHashMap<>();
|
||||||
for (AssetWorkspaceAssetSummary asset : assets) {
|
for (AssetWorkspaceAssetSummary asset : assets) {
|
||||||
if (!matchesFilters(asset, normalizedFilters) || !matchesQuery(asset, normalizedAssetsRoot, normalizedQuery)) {
|
if (!matchesFilters(asset, normalizedFilters) || !matchesQuery(asset, normalizedProjectRoot, normalizedQuery)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
grouped.computeIfAbsent(groupLabel(asset, normalizedAssetsRoot), ignored -> new ArrayList<>())
|
grouped.computeIfAbsent(groupLabel(asset, normalizedProjectRoot), ignored -> new ArrayList<>())
|
||||||
.add(asset);
|
.add(asset);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,8 +35,8 @@ public final class AssetNavigatorProjectionBuilder {
|
|||||||
return new AssetNavigatorProjection(groups, visibleAssetCount);
|
return new AssetNavigatorProjection(groups, visibleAssetCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
static String relativeRoot(AssetWorkspaceAssetSummary asset, Path assetsRoot) {
|
static String relativeRoot(AssetWorkspaceAssetSummary asset, Path projectRoot) {
|
||||||
return relativize(asset.assetRoot(), assetsRoot).toString().replace('\\', '/');
|
return relativize(asset.assetRoot(), projectRoot).toString().replace('\\', '/');
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean matchesFilters(AssetWorkspaceAssetSummary asset, Set<AssetNavigatorFilter> filters) {
|
private static boolean matchesFilters(AssetWorkspaceAssetSummary asset, Set<AssetNavigatorFilter> filters) {
|
||||||
@ -65,29 +65,29 @@ public final class AssetNavigatorProjectionBuilder {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean matchesQuery(AssetWorkspaceAssetSummary asset, Path assetsRoot, String normalizedQuery) {
|
private static boolean matchesQuery(AssetWorkspaceAssetSummary asset, Path projectRoot, String normalizedQuery) {
|
||||||
if (normalizedQuery.isBlank()) {
|
if (normalizedQuery.isBlank()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
final String relativeRoot = relativeRoot(asset, assetsRoot);
|
final String relativeRoot = relativeRoot(asset, projectRoot);
|
||||||
return asset.assetName().toLowerCase(Locale.ROOT).contains(normalizedQuery)
|
return asset.assetName().toLowerCase(Locale.ROOT).contains(normalizedQuery)
|
||||||
|| asset.assetFamily().toLowerCase(Locale.ROOT).contains(normalizedQuery)
|
|| asset.assetFamily().toLowerCase(Locale.ROOT).contains(normalizedQuery)
|
||||||
|| relativeRoot.toLowerCase(Locale.ROOT).contains(normalizedQuery);
|
|| relativeRoot.toLowerCase(Locale.ROOT).contains(normalizedQuery);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String groupLabel(AssetWorkspaceAssetSummary asset, Path assetsRoot) {
|
private static String groupLabel(AssetWorkspaceAssetSummary asset, Path projectRoot) {
|
||||||
final Path relativeRoot = relativize(asset.assetRoot(), assetsRoot);
|
final Path relativeRoot = relativize(asset.assetRoot(), projectRoot);
|
||||||
final Path parent = relativeRoot.getParent();
|
final Path parent = relativeRoot.getParent();
|
||||||
if (parent == null) {
|
if (parent == null) {
|
||||||
return "assets";
|
return relativeRoot.toString().replace('\\', '/');
|
||||||
}
|
}
|
||||||
return parent.toString().replace('\\', '/');
|
return parent.toString().replace('\\', '/');
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Path relativize(Path assetRoot, Path assetsRoot) {
|
private static Path relativize(Path assetRoot, Path projectRoot) {
|
||||||
try {
|
try {
|
||||||
return assetsRoot.relativize(assetRoot.toAbsolutePath().normalize());
|
return projectRoot.relativize(assetRoot.toAbsolutePath().normalize());
|
||||||
} catch (IllegalArgumentException ignored) {
|
} catch (IllegalArgumentException ignored) {
|
||||||
return assetRoot.getFileName();
|
return assetRoot.getFileName();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,24 +9,20 @@ import javafx.scene.control.*;
|
|||||||
import javafx.scene.image.Image;
|
import javafx.scene.image.Image;
|
||||||
import javafx.scene.image.ImageView;
|
import javafx.scene.image.ImageView;
|
||||||
import javafx.scene.layout.*;
|
import javafx.scene.layout.*;
|
||||||
|
import p.packer.declarations.PackerAssetDeclarationParser;
|
||||||
|
import p.packer.foundation.PackerWorkspaceFoundation;
|
||||||
|
import p.packer.workspace.FileSystemPackerWorkspaceService;
|
||||||
import p.studio.Container;
|
import p.studio.Container;
|
||||||
import p.studio.events.*;
|
import p.studio.events.*;
|
||||||
import p.studio.projects.ProjectReference;
|
import p.studio.projects.ProjectReference;
|
||||||
import p.studio.utilities.i18n.I18n;
|
import p.studio.utilities.i18n.I18n;
|
||||||
import p.studio.workspaces.Workspace;
|
import p.studio.workspaces.Workspace;
|
||||||
import p.studio.workspaces.WorkspaceId;
|
import p.studio.workspaces.WorkspaceId;
|
||||||
import p.packer.declarations.PackerAssetDeclarationParser;
|
|
||||||
import p.packer.foundation.PackerWorkspaceFoundation;
|
|
||||||
import p.packer.workspace.FileSystemPackerWorkspaceService;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.EnumMap;
|
import java.util.*;
|
||||||
import java.util.EnumSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
public final class AssetWorkspace implements Workspace {
|
public final class AssetWorkspace implements Workspace {
|
||||||
@ -46,6 +42,7 @@ public final class AssetWorkspace implements Workspace {
|
|||||||
private final VBox detailsContent = new VBox(12);
|
private final VBox detailsContent = new VBox(12);
|
||||||
private final Label workspaceSummaryLabel = new Label();
|
private final Label workspaceSummaryLabel = new Label();
|
||||||
private final TextArea logsArea = new TextArea();
|
private final TextArea logsArea = new TextArea();
|
||||||
|
private final ScrollPane detailsScroll = new ScrollPane();
|
||||||
|
|
||||||
private final Map<AssetNavigatorFilter, ToggleButton> filterButtons = new EnumMap<>(AssetNavigatorFilter.class);
|
private final Map<AssetNavigatorFilter, ToggleButton> filterButtons = new EnumMap<>(AssetNavigatorFilter.class);
|
||||||
private final EnumSet<AssetNavigatorFilter> activeFilters = EnumSet.noneOf(AssetNavigatorFilter.class);
|
private final EnumSet<AssetNavigatorFilter> activeFilters = EnumSet.noneOf(AssetNavigatorFilter.class);
|
||||||
@ -56,6 +53,7 @@ public final class AssetWorkspace implements Workspace {
|
|||||||
private volatile String detailsErrorMessage;
|
private volatile String detailsErrorMessage;
|
||||||
private volatile AssetWorkspaceMutationPreview stagedMutationPreview;
|
private volatile AssetWorkspaceMutationPreview stagedMutationPreview;
|
||||||
private volatile Path selectedPreviewInput;
|
private volatile Path selectedPreviewInput;
|
||||||
|
private volatile int selectedPreviewZoom = 1;
|
||||||
private String searchQuery = "";
|
private String searchQuery = "";
|
||||||
|
|
||||||
public AssetWorkspace(ProjectReference projectReference) {
|
public AssetWorkspace(ProjectReference projectReference) {
|
||||||
@ -167,7 +165,7 @@ public final class AssetWorkspace implements Workspace {
|
|||||||
detailsTitle.getStyleClass().add("assets-workspace-pane-title");
|
detailsTitle.getStyleClass().add("assets-workspace-pane-title");
|
||||||
workspaceSummaryLabel.getStyleClass().add("assets-workspace-summary");
|
workspaceSummaryLabel.getStyleClass().add("assets-workspace-summary");
|
||||||
detailsContent.getStyleClass().add("assets-workspace-details-content");
|
detailsContent.getStyleClass().add("assets-workspace-details-content");
|
||||||
final ScrollPane detailsScroll = new ScrollPane(detailsContent);
|
detailsScroll.setContent(detailsContent);
|
||||||
detailsScroll.setFitToWidth(true);
|
detailsScroll.setFitToWidth(true);
|
||||||
detailsScroll.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
|
detailsScroll.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
|
||||||
detailsScroll.getStyleClass().add("assets-workspace-details-scroll");
|
detailsScroll.getStyleClass().add("assets-workspace-details-scroll");
|
||||||
@ -231,6 +229,7 @@ public final class AssetWorkspace implements Workspace {
|
|||||||
detailsErrorMessage = null;
|
detailsErrorMessage = null;
|
||||||
stagedMutationPreview = null;
|
stagedMutationPreview = null;
|
||||||
selectedPreviewInput = null;
|
selectedPreviewInput = null;
|
||||||
|
selectedPreviewZoom = 1;
|
||||||
setInlineProgress(Container.i18n().text(I18n.ASSETS_PROGRESS_REFRESHING), ProgressBar.INDETERMINATE_PROGRESS, true);
|
setInlineProgress(Container.i18n().text(I18n.ASSETS_PROGRESS_REFRESHING), ProgressBar.INDETERMINATE_PROGRESS, true);
|
||||||
appendLog("Assets refresh started.");
|
appendLog("Assets refresh started.");
|
||||||
renderState();
|
renderState();
|
||||||
@ -268,6 +267,7 @@ public final class AssetWorkspace implements Workspace {
|
|||||||
detailsErrorMessage = null;
|
detailsErrorMessage = null;
|
||||||
stagedMutationPreview = null;
|
stagedMutationPreview = null;
|
||||||
selectedPreviewInput = null;
|
selectedPreviewInput = null;
|
||||||
|
selectedPreviewZoom = 1;
|
||||||
setInlineProgress(Container.i18n().text(I18n.ASSETS_PROGRESS_LOADING_DETAILS), ProgressBar.INDETERMINATE_PROGRESS, true);
|
setInlineProgress(Container.i18n().text(I18n.ASSETS_PROGRESS_LOADING_DETAILS), ProgressBar.INDETERMINATE_PROGRESS, true);
|
||||||
appendLog("Loading details for " + selectionKey.stableKey() + ".");
|
appendLog("Loading details for " + selectionKey.stableKey() + ".");
|
||||||
renderState();
|
renderState();
|
||||||
@ -291,6 +291,7 @@ public final class AssetWorkspace implements Workspace {
|
|||||||
selectedAssetDetails = details;
|
selectedAssetDetails = details;
|
||||||
detailsStatus = AssetWorkspaceDetailsStatus.READY;
|
detailsStatus = AssetWorkspaceDetailsStatus.READY;
|
||||||
selectedPreviewInput = firstPreviewInput(details);
|
selectedPreviewInput = firstPreviewInput(details);
|
||||||
|
selectedPreviewZoom = 1;
|
||||||
setInlineProgress(Container.i18n().text(I18n.ASSETS_PROGRESS_IDLE), 0, false);
|
setInlineProgress(Container.i18n().text(I18n.ASSETS_PROGRESS_IDLE), 0, false);
|
||||||
appendLog("Asset details ready for " + details.summary().assetName() + ".");
|
appendLog("Asset details ready for " + details.summary().assetName() + ".");
|
||||||
renderState();
|
renderState();
|
||||||
@ -319,7 +320,7 @@ public final class AssetWorkspace implements Workspace {
|
|||||||
case READY -> {
|
case READY -> {
|
||||||
final AssetNavigatorProjection projection = AssetNavigatorProjectionBuilder.build(
|
final AssetNavigatorProjection projection = AssetNavigatorProjectionBuilder.build(
|
||||||
state.assets(),
|
state.assets(),
|
||||||
assetsRoot(),
|
projectRoot(),
|
||||||
searchQuery,
|
searchQuery,
|
||||||
activeFilters);
|
activeFilters);
|
||||||
if (projection.isEmpty()) {
|
if (projection.isEmpty()) {
|
||||||
@ -406,7 +407,7 @@ public final class AssetWorkspace implements Workspace {
|
|||||||
createKeyValueRow(Container.i18n().text(I18n.ASSETS_LABEL_STATE), summary.state().name().toLowerCase()),
|
createKeyValueRow(Container.i18n().text(I18n.ASSETS_LABEL_STATE), summary.state().name().toLowerCase()),
|
||||||
createKeyValueRow(Container.i18n().text(I18n.ASSETS_LABEL_ASSET_ID), summary.assetId() == null ? "—" : String.valueOf(summary.assetId())),
|
createKeyValueRow(Container.i18n().text(I18n.ASSETS_LABEL_ASSET_ID), summary.assetId() == null ? "—" : String.valueOf(summary.assetId())),
|
||||||
createKeyValueRow(Container.i18n().text(I18n.ASSETS_LABEL_TYPE), summary.assetFamily()),
|
createKeyValueRow(Container.i18n().text(I18n.ASSETS_LABEL_TYPE), summary.assetFamily()),
|
||||||
createKeyValueRow(Container.i18n().text(I18n.ASSETS_LABEL_LOCATION), summary.assetRoot().toString()));
|
createKeyValueRow(Container.i18n().text(I18n.ASSETS_LABEL_LOCATION), projectRelativePath(summary.assetRoot())));
|
||||||
return createSection(Container.i18n().text(I18n.ASSETS_SECTION_SUMMARY), content);
|
return createSection(Container.i18n().text(I18n.ASSETS_SECTION_SUMMARY), content);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -428,6 +429,7 @@ public final class AssetWorkspace implements Workspace {
|
|||||||
|
|
||||||
if (selectedPreviewInput == null || !containsInput(details, selectedPreviewInput)) {
|
if (selectedPreviewInput == null || !containsInput(details, selectedPreviewInput)) {
|
||||||
selectedPreviewInput = firstPreviewInput(details);
|
selectedPreviewInput = firstPreviewInput(details);
|
||||||
|
selectedPreviewZoom = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
final VBox inputsList = new VBox(8);
|
final VBox inputsList = new VBox(8);
|
||||||
@ -445,6 +447,7 @@ public final class AssetWorkspace implements Workspace {
|
|||||||
inputButton.setMaxWidth(Double.MAX_VALUE);
|
inputButton.setMaxWidth(Double.MAX_VALUE);
|
||||||
inputButton.setOnAction(event -> {
|
inputButton.setOnAction(event -> {
|
||||||
selectedPreviewInput = input;
|
selectedPreviewInput = input;
|
||||||
|
selectedPreviewZoom = 1;
|
||||||
renderState();
|
renderState();
|
||||||
});
|
});
|
||||||
roleBox.getChildren().add(inputButton);
|
roleBox.getChildren().add(inputButton);
|
||||||
@ -565,15 +568,18 @@ public final class AssetWorkspace implements Workspace {
|
|||||||
final String extension = extensionOf(input);
|
final String extension = extensionOf(input);
|
||||||
if (isImage(extension)) {
|
if (isImage(extension)) {
|
||||||
try {
|
try {
|
||||||
final Image image = new Image(input.toUri().toString(), true);
|
final Image image = new Image(input.toUri().toString(), false);
|
||||||
final ImageView imageView = new ImageView(image);
|
final ImageView imageView = new ImageView(image);
|
||||||
imageView.setPreserveRatio(true);
|
imageView.setPreserveRatio(true);
|
||||||
imageView.setFitWidth(420);
|
imageView.setSmooth(false);
|
||||||
|
imageView.getStyleClass().add("assets-details-preview-image");
|
||||||
|
previewBox.getChildren().add(createPreviewZoomBar(image));
|
||||||
|
applyPreviewScale(image, imageView, selectedPreviewZoom);
|
||||||
previewBox.getChildren().add(imageView);
|
previewBox.getChildren().add(imageView);
|
||||||
} catch (RuntimeException runtimeException) {
|
} catch (RuntimeException runtimeException) {
|
||||||
previewBox.getChildren().add(createSectionMessage(Container.i18n().text(I18n.ASSETS_PREVIEW_IMAGE_ERROR)));
|
previewBox.getChildren().add(createSectionMessage(Container.i18n().text(I18n.ASSETS_PREVIEW_IMAGE_ERROR)));
|
||||||
}
|
}
|
||||||
previewBox.getChildren().add(createKeyValueRow(Container.i18n().text(I18n.ASSETS_LABEL_LOCATION), input.toString()));
|
previewBox.getChildren().add(createKeyValueRow(Container.i18n().text(I18n.ASSETS_LABEL_LOCATION), projectRelativePath(input)));
|
||||||
return previewBox;
|
return previewBox;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -588,15 +594,78 @@ public final class AssetWorkspace implements Workspace {
|
|||||||
|
|
||||||
if (isAudio(extension)) {
|
if (isAudio(extension)) {
|
||||||
previewBox.getChildren().add(createSectionMessage(Container.i18n().format(I18n.ASSETS_PREVIEW_AUDIO_PLACEHOLDER, input.getFileName().toString())));
|
previewBox.getChildren().add(createSectionMessage(Container.i18n().format(I18n.ASSETS_PREVIEW_AUDIO_PLACEHOLDER, input.getFileName().toString())));
|
||||||
previewBox.getChildren().add(createKeyValueRow(Container.i18n().text(I18n.ASSETS_LABEL_LOCATION), input.toString()));
|
previewBox.getChildren().add(createKeyValueRow(Container.i18n().text(I18n.ASSETS_LABEL_LOCATION), projectRelativePath(input)));
|
||||||
return previewBox;
|
return previewBox;
|
||||||
}
|
}
|
||||||
|
|
||||||
previewBox.getChildren().add(createSectionMessage(Container.i18n().format(I18n.ASSETS_PREVIEW_GENERIC_PLACEHOLDER, input.getFileName().toString())));
|
previewBox.getChildren().add(createSectionMessage(Container.i18n().format(I18n.ASSETS_PREVIEW_GENERIC_PLACEHOLDER, input.getFileName().toString())));
|
||||||
previewBox.getChildren().add(createKeyValueRow(Container.i18n().text(I18n.ASSETS_LABEL_LOCATION), input.toString()));
|
previewBox.getChildren().add(createKeyValueRow(Container.i18n().text(I18n.ASSETS_LABEL_LOCATION), projectRelativePath(input)));
|
||||||
return previewBox;
|
return previewBox;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Node createPreviewZoomBar(Image image) {
|
||||||
|
final HBox zoomBar = new HBox(8);
|
||||||
|
zoomBar.setAlignment(Pos.CENTER_LEFT);
|
||||||
|
zoomBar.getStyleClass().add("assets-details-preview-zoom-bar");
|
||||||
|
|
||||||
|
final Label zoomLabel = new Label(Container.i18n().text(I18n.ASSETS_PREVIEW_ZOOM));
|
||||||
|
zoomLabel.getStyleClass().add("assets-details-preview-zoom-label");
|
||||||
|
zoomBar.getChildren().add(zoomLabel);
|
||||||
|
|
||||||
|
final ToggleGroup zoomGroup = new ToggleGroup();
|
||||||
|
final int maxZoom = maxPreviewZoom(image);
|
||||||
|
for (int zoom : List.of(1, 2, 4, 8)) {
|
||||||
|
final ToggleButton button = new ToggleButton("x" + zoom);
|
||||||
|
button.getStyleClass().add("assets-details-preview-zoom-button");
|
||||||
|
button.setToggleGroup(zoomGroup);
|
||||||
|
button.setSelected(selectedPreviewZoom == zoom);
|
||||||
|
button.setDisable(zoom > maxZoom);
|
||||||
|
button.setOnAction(event -> {
|
||||||
|
selectedPreviewZoom = zoom;
|
||||||
|
renderState();
|
||||||
|
});
|
||||||
|
zoomBar.getChildren().add(button);
|
||||||
|
}
|
||||||
|
return zoomBar;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyPreviewScale(Image image, ImageView imageView, int requestedZoom) {
|
||||||
|
final double width = image.getWidth();
|
||||||
|
final double height = image.getHeight();
|
||||||
|
if (width <= 0.0d || height <= 0.0d) {
|
||||||
|
imageView.setFitWidth(420);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final double scale = previewScale(width, height, requestedZoom);
|
||||||
|
imageView.setFitWidth(Math.max(1.0d, width * scale));
|
||||||
|
imageView.setFitHeight(Math.max(1.0d, height * scale));
|
||||||
|
imageView.setSmooth(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static double previewScale(double width, double height, int requestedZoom) {
|
||||||
|
final double longestEdge = Math.max(width, height);
|
||||||
|
if (longestEdge <= 0.0d) {
|
||||||
|
return 1.0d;
|
||||||
|
}
|
||||||
|
if (longestEdge > 420.0d) {
|
||||||
|
return 420.0d / longestEdge;
|
||||||
|
}
|
||||||
|
return Math.max(1, Math.min(Math.max(1, requestedZoom), maxPreviewZoom(width, height)));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int maxPreviewZoom(Image image) {
|
||||||
|
return maxPreviewZoom(image.getWidth(), image.getHeight());
|
||||||
|
}
|
||||||
|
|
||||||
|
static int maxPreviewZoom(double width, double height) {
|
||||||
|
final double longestEdge = Math.max(width, height);
|
||||||
|
if (longestEdge <= 0.0d || longestEdge > 420.0d) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return Math.max(1, (int) Math.floor(420.0d / longestEdge));
|
||||||
|
}
|
||||||
|
|
||||||
private Node createSection(String title, Node content) {
|
private Node createSection(String title, Node content) {
|
||||||
final VBox section = new VBox(10);
|
final VBox section = new VBox(10);
|
||||||
section.getStyleClass().add("assets-details-section");
|
section.getStyleClass().add("assets-details-section");
|
||||||
@ -678,7 +747,7 @@ public final class AssetWorkspace implements Workspace {
|
|||||||
}
|
}
|
||||||
topLine.getChildren().addAll(icon, name, spacer, badges);
|
topLine.getChildren().addAll(icon, name, spacer, badges);
|
||||||
|
|
||||||
final Label path = new Label(AssetNavigatorProjectionBuilder.relativeRoot(asset, assetsRoot()));
|
final Label path = new Label(AssetNavigatorProjectionBuilder.relativeRoot(asset, projectRoot()));
|
||||||
path.getStyleClass().add("assets-workspace-asset-path");
|
path.getStyleClass().add("assets-workspace-asset-path");
|
||||||
row.getChildren().addAll(topLine, path);
|
row.getChildren().addAll(topLine, path);
|
||||||
row.setOnMouseClicked(event -> selectAsset(asset.selectionKey()));
|
row.setOnMouseClicked(event -> selectAsset(asset.selectionKey()));
|
||||||
@ -722,10 +791,18 @@ public final class AssetWorkspace implements Workspace {
|
|||||||
if (selectedAsset == null) {
|
if (selectedAsset == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final AssetWorkspaceMutationPreview preview = mutationService.preview(projectReference, selectedAsset, action);
|
try {
|
||||||
stagedMutationPreview = preview;
|
stagedMutationPreview = mutationService.preview(projectReference, selectedAsset, action);
|
||||||
appendLog("Preview ready for " + actionLabel(action) + ".");
|
appendLog("Preview ready for " + actionLabel(action) + ".");
|
||||||
renderState();
|
renderState();
|
||||||
|
Platform.runLater(() -> detailsScroll.setVvalue(1.0d));
|
||||||
|
} catch (RuntimeException runtimeException) {
|
||||||
|
final String message = rootCauseMessage(runtimeException);
|
||||||
|
appendLog("Preview failed: " + message);
|
||||||
|
workspaceBus.publish(new StudioAssetsMutationFailedEvent(projectReference, action, message));
|
||||||
|
stagedMutationPreview = null;
|
||||||
|
renderState();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Node createStagedMutationPanel(AssetWorkspaceMutationPreview preview) {
|
private Node createStagedMutationPanel(AssetWorkspaceMutationPreview preview) {
|
||||||
@ -786,7 +863,7 @@ public final class AssetWorkspace implements Workspace {
|
|||||||
box.getChildren().add(createKeyValueRow(Container.i18n().text(I18n.ASSETS_LABEL_NAME), preview.asset().assetName()));
|
box.getChildren().add(createKeyValueRow(Container.i18n().text(I18n.ASSETS_LABEL_NAME), preview.asset().assetName()));
|
||||||
box.getChildren().add(createKeyValueRow(
|
box.getChildren().add(createKeyValueRow(
|
||||||
Container.i18n().text(I18n.ASSETS_LABEL_LOCATION),
|
Container.i18n().text(I18n.ASSETS_LABEL_LOCATION),
|
||||||
AssetNavigatorProjectionBuilder.relativeRoot(preview.asset(), assetsRoot())));
|
projectRelativePath(preview.asset().assetRoot())));
|
||||||
return box;
|
return box;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -921,6 +998,19 @@ public final class AssetWorkspace implements Workspace {
|
|||||||
return projectReference.rootPath().resolve("assets").toAbsolutePath().normalize();
|
return projectReference.rootPath().resolve("assets").toAbsolutePath().normalize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Path projectRoot() {
|
||||||
|
return projectReference.rootPath().toAbsolutePath().normalize();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String projectRelativePath(Path path) {
|
||||||
|
final Path normalizedPath = path.toAbsolutePath().normalize();
|
||||||
|
try {
|
||||||
|
return projectRoot().relativize(normalizedPath).toString().replace('\\', '/');
|
||||||
|
} catch (IllegalArgumentException ignored) {
|
||||||
|
return normalizedPath.toString().replace('\\', '/');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void setInlineProgress(String message, double progress, boolean visible) {
|
private void setInlineProgress(String message, double progress, boolean visible) {
|
||||||
inlineProgressLabel.setText(message);
|
inlineProgressLabel.setText(message);
|
||||||
inlineProgressBar.setVisible(visible);
|
inlineProgressBar.setVisible(visible);
|
||||||
|
|||||||
@ -135,6 +135,7 @@ assets.logs.title=Logs
|
|||||||
assets.inputs.empty=No previewable inputs are currently declared for this asset.
|
assets.inputs.empty=No previewable inputs are currently declared for this asset.
|
||||||
assets.diagnostics.empty=No diagnostics are currently attached to this asset.
|
assets.diagnostics.empty=No diagnostics are currently attached to this asset.
|
||||||
assets.preview.empty=Select an input to preview it here.
|
assets.preview.empty=Select an input to preview it here.
|
||||||
|
assets.preview.zoom=Zoom
|
||||||
assets.preview.textError=Unable to read this text-like input for preview.
|
assets.preview.textError=Unable to read this text-like input for preview.
|
||||||
assets.preview.imageError=Unable to decode this image for preview.
|
assets.preview.imageError=Unable to decode this image for preview.
|
||||||
assets.preview.audioPlaceholder=Audio preview placeholder: {0}
|
assets.preview.audioPlaceholder=Audio preview placeholder: {0}
|
||||||
|
|||||||
@ -422,6 +422,29 @@
|
|||||||
-fx-font-weight: bold;
|
-fx-font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.assets-details-preview-zoom-bar {
|
||||||
|
-fx-alignment: center-left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-details-preview-zoom-label {
|
||||||
|
-fx-text-fill: #9fc3e7;
|
||||||
|
-fx-font-size: 11px;
|
||||||
|
-fx-font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-details-preview-zoom-button {
|
||||||
|
-fx-background-color: #17202a;
|
||||||
|
-fx-text-fill: #e6eff8;
|
||||||
|
-fx-background-radius: 999;
|
||||||
|
-fx-border-radius: 999;
|
||||||
|
-fx-border-color: #2f4053;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assets-details-preview-zoom-button:selected {
|
||||||
|
-fx-background-color: #224160;
|
||||||
|
-fx-border-color: #4f8dc3;
|
||||||
|
}
|
||||||
|
|
||||||
.assets-details-preview-text {
|
.assets-details-preview-text {
|
||||||
-fx-control-inner-background: #10161d;
|
-fx-control-inner-background: #10161d;
|
||||||
-fx-text-fill: #e4edf6;
|
-fx-text-fill: #e4edf6;
|
||||||
|
|||||||
@ -11,27 +11,29 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
|||||||
final class AssetNavigatorProjectionBuilderTest {
|
final class AssetNavigatorProjectionBuilderTest {
|
||||||
@Test
|
@Test
|
||||||
void groupsAssetsByParentPath() {
|
void groupsAssetsByParentPath() {
|
||||||
final Path assetsRoot = Path.of("/tmp/project/assets");
|
final Path projectRoot = Path.of("/tmp/project");
|
||||||
|
final Path assetsRoot = projectRoot.resolve("assets");
|
||||||
final AssetNavigatorProjection projection = AssetNavigatorProjectionBuilder.build(
|
final AssetNavigatorProjection projection = AssetNavigatorProjectionBuilder.build(
|
||||||
List.of(
|
List.of(
|
||||||
managedAsset(1, "ui_atlas", "image_bank", assetsRoot.resolve("ui/atlas"), true, false),
|
managedAsset(1, "ui_atlas", "image_bank", assetsRoot.resolve("ui/atlas"), true, false),
|
||||||
orphanAsset("menu_sounds", "sound_bank", assetsRoot.resolve("audio/menu"), false, false)),
|
orphanAsset("menu_sounds", "sound_bank", assetsRoot.resolve("audio/menu"), false, false)),
|
||||||
assetsRoot,
|
projectRoot,
|
||||||
"",
|
"",
|
||||||
EnumSet.noneOf(AssetNavigatorFilter.class));
|
EnumSet.noneOf(AssetNavigatorFilter.class));
|
||||||
|
|
||||||
assertEquals(2, projection.visibleAssetCount());
|
assertEquals(2, projection.visibleAssetCount());
|
||||||
assertEquals(List.of("audio", "ui"), projection.groups().stream().map(AssetNavigatorGroup::label).sorted().toList());
|
assertEquals(List.of("assets/audio", "assets/ui"), projection.groups().stream().map(AssetNavigatorGroup::label).sorted().toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void managedAndOrphanFiltersBehaveAsStateFilterSet() {
|
void managedAndOrphanFiltersBehaveAsStateFilterSet() {
|
||||||
final Path assetsRoot = Path.of("/tmp/project/assets");
|
final Path projectRoot = Path.of("/tmp/project");
|
||||||
|
final Path assetsRoot = projectRoot.resolve("assets");
|
||||||
final AssetNavigatorProjection projection = AssetNavigatorProjectionBuilder.build(
|
final AssetNavigatorProjection projection = AssetNavigatorProjectionBuilder.build(
|
||||||
List.of(
|
List.of(
|
||||||
managedAsset(1, "ui_atlas", "image_bank", assetsRoot.resolve("ui/atlas"), true, false),
|
managedAsset(1, "ui_atlas", "image_bank", assetsRoot.resolve("ui/atlas"), true, false),
|
||||||
orphanAsset("menu_sounds", "sound_bank", assetsRoot.resolve("audio/menu"), false, false)),
|
orphanAsset("menu_sounds", "sound_bank", assetsRoot.resolve("audio/menu"), false, false)),
|
||||||
assetsRoot,
|
projectRoot,
|
||||||
"",
|
"",
|
||||||
EnumSet.of(AssetNavigatorFilter.MANAGED));
|
EnumSet.of(AssetNavigatorFilter.MANAGED));
|
||||||
|
|
||||||
@ -41,13 +43,14 @@ final class AssetNavigatorProjectionBuilderTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void diagnosticsAndPreloadActAsAdditionalConstraints() {
|
void diagnosticsAndPreloadActAsAdditionalConstraints() {
|
||||||
final Path assetsRoot = Path.of("/tmp/project/assets");
|
final Path projectRoot = Path.of("/tmp/project");
|
||||||
|
final Path assetsRoot = projectRoot.resolve("assets");
|
||||||
final AssetNavigatorProjection projection = AssetNavigatorProjectionBuilder.build(
|
final AssetNavigatorProjection projection = AssetNavigatorProjectionBuilder.build(
|
||||||
List.of(
|
List.of(
|
||||||
managedAsset(1, "ui_atlas", "image_bank", assetsRoot.resolve("ui/atlas"), true, true),
|
managedAsset(1, "ui_atlas", "image_bank", assetsRoot.resolve("ui/atlas"), true, true),
|
||||||
managedAsset(2, "bg_tiles", "image_bank", assetsRoot.resolve("bg/tiles"), true, false),
|
managedAsset(2, "bg_tiles", "image_bank", assetsRoot.resolve("bg/tiles"), true, false),
|
||||||
managedAsset(3, "voice_bank", "sound_bank", assetsRoot.resolve("audio/voice"), false, true)),
|
managedAsset(3, "voice_bank", "sound_bank", assetsRoot.resolve("audio/voice"), false, true)),
|
||||||
assetsRoot,
|
projectRoot,
|
||||||
"",
|
"",
|
||||||
EnumSet.of(AssetNavigatorFilter.MANAGED, AssetNavigatorFilter.PRELOAD, AssetNavigatorFilter.DIAGNOSTICS));
|
EnumSet.of(AssetNavigatorFilter.MANAGED, AssetNavigatorFilter.PRELOAD, AssetNavigatorFilter.DIAGNOSTICS));
|
||||||
|
|
||||||
@ -57,19 +60,20 @@ final class AssetNavigatorProjectionBuilderTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void searchMatchesAssetNameAndPathContext() {
|
void searchMatchesAssetNameAndPathContext() {
|
||||||
final Path assetsRoot = Path.of("/tmp/project/assets");
|
final Path projectRoot = Path.of("/tmp/project");
|
||||||
|
final Path assetsRoot = projectRoot.resolve("assets");
|
||||||
final List<AssetWorkspaceAssetSummary> assets = List.of(
|
final List<AssetWorkspaceAssetSummary> assets = List.of(
|
||||||
managedAsset(1, "ui_atlas", "image_bank", assetsRoot.resolve("ui/atlas"), true, false),
|
managedAsset(1, "ui_atlas", "image_bank", assetsRoot.resolve("ui/atlas"), true, false),
|
||||||
orphanAsset("menu_sounds", "sound_bank", assetsRoot.resolve("audio/menu"), false, false));
|
orphanAsset("menu_sounds", "sound_bank", assetsRoot.resolve("audio/menu"), false, false));
|
||||||
|
|
||||||
final AssetNavigatorProjection byName = AssetNavigatorProjectionBuilder.build(
|
final AssetNavigatorProjection byName = AssetNavigatorProjectionBuilder.build(
|
||||||
assets,
|
assets,
|
||||||
assetsRoot,
|
projectRoot,
|
||||||
"atlas",
|
"atlas",
|
||||||
EnumSet.noneOf(AssetNavigatorFilter.class));
|
EnumSet.noneOf(AssetNavigatorFilter.class));
|
||||||
final AssetNavigatorProjection byPath = AssetNavigatorProjectionBuilder.build(
|
final AssetNavigatorProjection byPath = AssetNavigatorProjectionBuilder.build(
|
||||||
assets,
|
assets,
|
||||||
assetsRoot,
|
projectRoot,
|
||||||
"audio",
|
"audio",
|
||||||
EnumSet.noneOf(AssetNavigatorFilter.class));
|
EnumSet.noneOf(AssetNavigatorFilter.class));
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,28 @@
|
|||||||
|
package p.studio.workspaces.assets;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
final class AssetWorkspacePreviewScaleTest {
|
||||||
|
@Test
|
||||||
|
void keepsSmallImageAtOriginalScaleByDefault() {
|
||||||
|
assertEquals(1.0d, AssetWorkspace.previewScale(16.0d, 16.0d, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void appliesIntegerZoomForSmallImages() {
|
||||||
|
assertEquals(8.0d, AssetWorkspace.previewScale(16.0d, 16.0d, 8));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void capsZoomOptionsByMaximumPreviewSize() {
|
||||||
|
assertEquals(6, AssetWorkspace.maxPreviewZoom(64.0d, 64.0d));
|
||||||
|
assertEquals(1.0d, AssetWorkspace.previewScale(300.0d, 200.0d, 8));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void scalesLargeImagesDownToPreviewLimit() {
|
||||||
|
assertEquals(420.0d / 512.0d, AssetWorkspace.previewScale(512.0d, 128.0d, 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
9
test-projects/main/assets/.prometeu/index.json
Normal file
9
test-projects/main/assets/.prometeu/index.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"schema_version" : 1,
|
||||||
|
"next_asset_id" : 2,
|
||||||
|
"assets" : [ {
|
||||||
|
"asset_id" : 1,
|
||||||
|
"asset_uuid" : "67cd978d-cd61-4641-ba9e-98fe4bc039bd",
|
||||||
|
"root" : "ui/atlas-relocated"
|
||||||
|
} ]
|
||||||
|
}
|
||||||
8
test-projects/main/assets/ui/atlas-relocated/asset.json
Normal file
8
test-projects/main/assets/ui/atlas-relocated/asset.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"schema_version": 1,
|
||||||
|
"name": "ui_atlas",
|
||||||
|
"type": "image_bank",
|
||||||
|
"inputs": { "sprites": ["sprites/confirm.png"] },
|
||||||
|
"output": { "format": "TILES/indexed_v1", "codec": "RAW" },
|
||||||
|
"preload": { "enabled": true }
|
||||||
|
}
|
||||||
BIN
test-projects/main/assets/ui/atlas-relocated/sprites/confirm.png
Normal file
BIN
test-projects/main/assets/ui/atlas-relocated/sprites/confirm.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 137 B |
Loading…
x
Reference in New Issue
Block a user