diff --git a/prometeu-studio/src/main/java/p/studio/workspaces/assets/AssetWorkspaceNavigatorControl.java b/prometeu-studio/src/main/java/p/studio/workspaces/assets/AssetWorkspaceNavigatorControl.java index ff7617c1..9b731068 100644 --- a/prometeu-studio/src/main/java/p/studio/workspaces/assets/AssetWorkspaceNavigatorControl.java +++ b/prometeu-studio/src/main/java/p/studio/workspaces/assets/AssetWorkspaceNavigatorControl.java @@ -1,16 +1,13 @@ package p.studio.workspaces.assets; import javafx.geometry.Insets; -import javafx.geometry.Pos; import javafx.scene.Node; import javafx.scene.control.Label; import javafx.scene.control.ScrollPane; import javafx.scene.control.TextField; import javafx.scene.control.ToggleButton; import javafx.scene.layout.FlowPane; -import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; -import javafx.scene.layout.Region; import javafx.scene.layout.VBox; import p.studio.Container; import p.studio.controls.lifecycle.StudioControlLifecycle; @@ -41,6 +38,7 @@ final class AssetWorkspaceNavigatorControl extends VBox implements StudioControl private AssetWorkspaceNavigatorViewState viewState; private EnumSet activeFilters = EnumSet.noneOf(AssetNavigatorFilter.class); + private String projectionSignature = ""; AssetWorkspaceNavigatorControl( ProjectReference projectReference, @@ -136,10 +134,16 @@ final class AssetWorkspaceNavigatorControl extends VBox implements StudioControl navigatorStateLabel.setText(viewState.message()); if (!viewState.hasProjection()) { + projectionSignature = ""; navigatorContent.getChildren().setAll(createMessage(viewState.message())); return; } + final String nextSignature = projectionSignature(viewState.projection()); + if (Objects.equals(projectionSignature, nextSignature)) { + return; + } + projectionSignature = nextSignature; navigatorContent.getChildren().clear(); for (AssetNavigatorGroup group : viewState.projection().groups()) { final VBox groupBox = new VBox(6); @@ -150,61 +154,18 @@ final class AssetWorkspaceNavigatorControl extends VBox implements StudioControl groupBox.getChildren().add(groupLabel); for (AssetWorkspaceAssetSummary asset : group.assets()) { - groupBox.getChildren().add(createAssetRow(asset, asset.selectionKey().equals(viewState.workspaceState().selectedKey()))); + groupBox.getChildren().add(new AssetWorkspaceNavigatorRowControl( + projectReference, + workspaceBus, + interactions, + asset, + asset.selectionKey().equals(viewState.workspaceState().selectedKey()))); } navigatorContent.getChildren().add(groupBox); } } - private Node createAssetRow(AssetWorkspaceAssetSummary asset, boolean selected) { - final VBox row = new VBox(4); - row.getStyleClass().add("assets-workspace-asset-row"); - row.getStyleClass().add(assetRowToneClass(asset.assetFamily())); - updateAssetRowSelection(row, selected); - - final HBox topLine = new HBox(8); - topLine.setAlignment(Pos.CENTER_LEFT); - final Label name = new Label(asset.assetName()); - name.getStyleClass().add("assets-workspace-asset-name"); - name.getStyleClass().add(assetNameToneClass(asset.assetFamily())); - final Region spacer = new Region(); - HBox.setHgrow(spacer, Priority.ALWAYS); - final HBox badges = new HBox(6); - badges.setAlignment(Pos.CENTER_RIGHT); - badges.getStyleClass().add("assets-workspace-asset-badges"); - if (asset.state() == AssetWorkspaceAssetState.UNREGISTERED) { - badges.getChildren().add(createBadge(Container.i18n().text(I18n.ASSETS_BADGE_UNREGISTERED), "assets-workspace-badge-orphan")); - } else if (asset.buildParticipation() == AssetWorkspaceBuildParticipation.INCLUDED) { - badges.getChildren().add(createBadge(buildParticipationLabel(asset.buildParticipation()), "assets-workspace-badge-preload")); - if (asset.preload()) { - badges.getChildren().add(createBadge(Container.i18n().text(I18n.ASSETS_BADGE_PRELOAD), "assets-workspace-badge-preload")); - } - } else { - badges.getChildren().add(createBadge(buildParticipationLabel(asset.buildParticipation()), "assets-workspace-badge-diagnostics")); - } - if (asset.hasDiagnostics()) { - badges.getChildren().add(createBadge(Container.i18n().text(I18n.ASSETS_BADGE_DIAGNOSTICS), "assets-workspace-badge-diagnostics")); - } - topLine.getChildren().addAll(name, spacer, badges); - - final Label path = new Label(AssetNavigatorProjectionBuilder.relativeRoot(asset, projectRoot)); - path.getStyleClass().add("assets-workspace-asset-path"); - row.getChildren().addAll(topLine, path); - row.setOnMouseClicked(event -> interactions.selectAsset(asset.selectionKey())); - return row; - } - - private void updateAssetRowSelection(VBox row, boolean selected) { - if (selected) { - if (!row.getStyleClass().contains("assets-workspace-asset-row-selected")) { - row.getStyleClass().add("assets-workspace-asset-row-selected"); - } - return; - } - row.getStyleClass().remove("assets-workspace-asset-row-selected"); - } - private Node createMessage(String text) { final Label label = new Label(text); label.getStyleClass().add("assets-workspace-empty-state"); @@ -212,35 +173,15 @@ final class AssetWorkspaceNavigatorControl extends VBox implements StudioControl return label; } - private Node createBadge(String text, String styleClass) { - final Label badge = new Label(text); - badge.getStyleClass().add("assets-workspace-badge"); - badge.getStyleClass().add(styleClass); - return badge; - } - - private String buildParticipationLabel(AssetWorkspaceBuildParticipation buildParticipation) { - return switch (buildParticipation) { - case INCLUDED -> Container.i18n().text(I18n.ASSETS_VALUE_INCLUDED); - case EXCLUDED -> Container.i18n().text(I18n.ASSETS_VALUE_EXCLUDED); - }; - } - - private String assetRowToneClass(String assetFamily) { - return switch (assetFamily == null ? "" : assetFamily.toLowerCase()) { - case "image_bank" -> "assets-workspace-asset-row-image"; - case "palette_bank" -> "assets-workspace-asset-row-palette"; - case "sound_bank" -> "assets-workspace-asset-row-sound"; - default -> "assets-workspace-asset-row-generic"; - }; - } - - private String assetNameToneClass(String assetFamily) { - return switch (assetFamily == null ? "" : assetFamily.toLowerCase()) { - case "image_bank" -> "assets-workspace-asset-name-image"; - case "palette_bank" -> "assets-workspace-asset-name-palette"; - case "sound_bank" -> "assets-workspace-asset-name-sound"; - default -> "assets-workspace-asset-name-generic"; - }; + private String projectionSignature(AssetNavigatorProjection projection) { + final StringBuilder signature = new StringBuilder(); + for (AssetNavigatorGroup group : projection.groups()) { + signature.append(group.label()).append('|'); + for (AssetWorkspaceAssetSummary asset : group.assets()) { + signature.append(asset.selectionKey().stableKey()).append(','); + } + signature.append(';'); + } + return signature.toString(); } } diff --git a/prometeu-studio/src/main/java/p/studio/workspaces/assets/AssetWorkspaceNavigatorRowControl.java b/prometeu-studio/src/main/java/p/studio/workspaces/assets/AssetWorkspaceNavigatorRowControl.java new file mode 100644 index 00000000..b77e90e2 --- /dev/null +++ b/prometeu-studio/src/main/java/p/studio/workspaces/assets/AssetWorkspaceNavigatorRowControl.java @@ -0,0 +1,153 @@ +package p.studio.workspaces.assets; + +import javafx.geometry.Pos; +import javafx.scene.control.Label; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; +import p.studio.Container; +import p.studio.controls.lifecycle.StudioControlLifecycle; +import p.studio.controls.lifecycle.StudioControlLifecycleSupport; +import p.studio.events.StudioAssetsAssetSummaryPatchedEvent; +import p.studio.events.StudioAssetsWorkspaceSelectionChangedEvent; +import p.studio.events.StudioWorkspaceEventBus; +import p.studio.projects.ProjectReference; +import p.studio.utilities.i18n.I18n; +import p.studio.workspaces.framework.StudioSubscriptionBag; + +import java.nio.file.Path; +import java.util.Objects; + +final class AssetWorkspaceNavigatorRowControl extends VBox implements StudioControlLifecycle { + private final ProjectReference projectReference; + private final Path projectRoot; + private final StudioWorkspaceEventBus workspaceBus; + private final AssetWorkspaceInteractionPort interactions; + private final StudioSubscriptionBag subscriptions = new StudioSubscriptionBag(); + + private AssetWorkspaceAssetSummary summary; + private boolean selected; + + AssetWorkspaceNavigatorRowControl( + ProjectReference projectReference, + StudioWorkspaceEventBus workspaceBus, + AssetWorkspaceInteractionPort interactions, + AssetWorkspaceAssetSummary summary, + boolean selected) { + StudioControlLifecycleSupport.install(this, this); + this.projectReference = Objects.requireNonNull(projectReference, "projectReference"); + this.projectRoot = this.projectReference.rootPath(); + this.workspaceBus = Objects.requireNonNull(workspaceBus, "workspaceBus"); + this.interactions = Objects.requireNonNull(interactions, "interactions"); + this.summary = Objects.requireNonNull(summary, "summary"); + this.selected = selected; + render(); + } + + @Override + public void subscribe() { + subscriptions.add(workspaceBus.subscribe(StudioAssetsWorkspaceSelectionChangedEvent.class, event -> { + if (projectReference.equals(event.project())) { + updateSelection(event.selectionKey().equals(summary.selectionKey())); + } + })); + subscriptions.add(workspaceBus.subscribe(StudioAssetsAssetSummaryPatchedEvent.class, event -> { + if (projectReference.equals(event.project()) + && summary.selectionKey().equals(event.summary().selectionKey())) { + summary = event.summary(); + render(); + } + })); + } + + @Override + public void unsubscribe() { + subscriptions.clear(); + } + + private void updateSelection(boolean selected) { + if (this.selected == selected) { + return; + } + this.selected = selected; + renderSelection(); + } + + private void render() { + getChildren().clear(); + getStyleClass().setAll("assets-workspace-asset-row", assetRowToneClass(summary.assetFamily())); + renderSelection(); + + final HBox topLine = new HBox(8); + topLine.setAlignment(Pos.CENTER_LEFT); + final Label name = new Label(summary.assetName()); + name.getStyleClass().addAll("assets-workspace-asset-name", assetNameToneClass(summary.assetFamily())); + final Region spacer = new Region(); + HBox.setHgrow(spacer, Priority.ALWAYS); + final HBox badges = new HBox(6); + badges.setAlignment(Pos.CENTER_RIGHT); + badges.getStyleClass().add("assets-workspace-asset-badges"); + populateBadges(badges); + topLine.getChildren().addAll(name, spacer, badges); + + final Label path = new Label(AssetNavigatorProjectionBuilder.relativeRoot(summary, projectRoot)); + path.getStyleClass().add("assets-workspace-asset-path"); + getChildren().setAll(topLine, path); + setOnMouseClicked(event -> interactions.selectAsset(summary.selectionKey())); + } + + private void renderSelection() { + getStyleClass().remove("assets-workspace-asset-row-selected"); + if (selected) { + getStyleClass().add("assets-workspace-asset-row-selected"); + } + } + + private void populateBadges(HBox badges) { + if (summary.state() == AssetWorkspaceAssetState.UNREGISTERED) { + badges.getChildren().add(createBadge(Container.i18n().text(I18n.ASSETS_BADGE_UNREGISTERED), "assets-workspace-badge-orphan")); + } else if (summary.buildParticipation() == AssetWorkspaceBuildParticipation.INCLUDED) { + badges.getChildren().add(createBadge(buildParticipationLabel(summary.buildParticipation()), "assets-workspace-badge-preload")); + if (summary.preload()) { + badges.getChildren().add(createBadge(Container.i18n().text(I18n.ASSETS_BADGE_PRELOAD), "assets-workspace-badge-preload")); + } + } else { + badges.getChildren().add(createBadge(buildParticipationLabel(summary.buildParticipation()), "assets-workspace-badge-diagnostics")); + } + if (summary.hasDiagnostics()) { + badges.getChildren().add(createBadge(Container.i18n().text(I18n.ASSETS_BADGE_DIAGNOSTICS), "assets-workspace-badge-diagnostics")); + } + } + + private Label createBadge(String text, String styleClass) { + final Label badge = new Label(text); + badge.getStyleClass().addAll("assets-workspace-badge", styleClass); + return badge; + } + + private String buildParticipationLabel(AssetWorkspaceBuildParticipation buildParticipation) { + return switch (buildParticipation) { + case INCLUDED -> Container.i18n().text(I18n.ASSETS_VALUE_INCLUDED); + case EXCLUDED -> Container.i18n().text(I18n.ASSETS_VALUE_EXCLUDED); + }; + } + + private String assetRowToneClass(String assetFamily) { + return switch (assetFamily == null ? "" : assetFamily.toLowerCase()) { + case "image_bank" -> "assets-workspace-asset-row-image"; + case "palette_bank" -> "assets-workspace-asset-row-palette"; + case "sound_bank" -> "assets-workspace-asset-row-sound"; + default -> "assets-workspace-asset-row-generic"; + }; + } + + private String assetNameToneClass(String assetFamily) { + return switch (assetFamily == null ? "" : assetFamily.toLowerCase()) { + case "image_bank" -> "assets-workspace-asset-name-image"; + case "palette_bank" -> "assets-workspace-asset-name-palette"; + case "sound_bank" -> "assets-workspace-asset-name-sound"; + default -> "assets-workspace-asset-name-generic"; + }; + } +}