implements PR-07b asset navigator and row subscriptions

This commit is contained in:
bQUARKz 2026-03-12 09:32:02 +00:00
parent c351814b6f
commit 3e0592e75c
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
2 changed files with 176 additions and 82 deletions

View File

@ -1,16 +1,13 @@
package p.studio.workspaces.assets; package p.studio.workspaces.assets;
import javafx.geometry.Insets; import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node; import javafx.scene.Node;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane; import javafx.scene.control.ScrollPane;
import javafx.scene.control.TextField; import javafx.scene.control.TextField;
import javafx.scene.control.ToggleButton; import javafx.scene.control.ToggleButton;
import javafx.scene.layout.FlowPane; import javafx.scene.layout.FlowPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority; import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
import p.studio.Container; import p.studio.Container;
import p.studio.controls.lifecycle.StudioControlLifecycle; import p.studio.controls.lifecycle.StudioControlLifecycle;
@ -41,6 +38,7 @@ final class AssetWorkspaceNavigatorControl extends VBox implements StudioControl
private AssetWorkspaceNavigatorViewState viewState; private AssetWorkspaceNavigatorViewState viewState;
private EnumSet<AssetNavigatorFilter> activeFilters = EnumSet.noneOf(AssetNavigatorFilter.class); private EnumSet<AssetNavigatorFilter> activeFilters = EnumSet.noneOf(AssetNavigatorFilter.class);
private String projectionSignature = "";
AssetWorkspaceNavigatorControl( AssetWorkspaceNavigatorControl(
ProjectReference projectReference, ProjectReference projectReference,
@ -136,10 +134,16 @@ final class AssetWorkspaceNavigatorControl extends VBox implements StudioControl
navigatorStateLabel.setText(viewState.message()); navigatorStateLabel.setText(viewState.message());
if (!viewState.hasProjection()) { if (!viewState.hasProjection()) {
projectionSignature = "";
navigatorContent.getChildren().setAll(createMessage(viewState.message())); navigatorContent.getChildren().setAll(createMessage(viewState.message()));
return; return;
} }
final String nextSignature = projectionSignature(viewState.projection());
if (Objects.equals(projectionSignature, nextSignature)) {
return;
}
projectionSignature = nextSignature;
navigatorContent.getChildren().clear(); navigatorContent.getChildren().clear();
for (AssetNavigatorGroup group : viewState.projection().groups()) { for (AssetNavigatorGroup group : viewState.projection().groups()) {
final VBox groupBox = new VBox(6); final VBox groupBox = new VBox(6);
@ -150,61 +154,18 @@ final class AssetWorkspaceNavigatorControl extends VBox implements StudioControl
groupBox.getChildren().add(groupLabel); groupBox.getChildren().add(groupLabel);
for (AssetWorkspaceAssetSummary asset : group.assets()) { 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); 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) { private Node createMessage(String text) {
final Label label = new Label(text); final Label label = new Label(text);
label.getStyleClass().add("assets-workspace-empty-state"); label.getStyleClass().add("assets-workspace-empty-state");
@ -212,35 +173,15 @@ final class AssetWorkspaceNavigatorControl extends VBox implements StudioControl
return label; return label;
} }
private Node createBadge(String text, String styleClass) { private String projectionSignature(AssetNavigatorProjection projection) {
final Label badge = new Label(text); final StringBuilder signature = new StringBuilder();
badge.getStyleClass().add("assets-workspace-badge"); for (AssetNavigatorGroup group : projection.groups()) {
badge.getStyleClass().add(styleClass); signature.append(group.label()).append('|');
return badge; for (AssetWorkspaceAssetSummary asset : group.assets()) {
signature.append(asset.selectionKey().stableKey()).append(',');
} }
signature.append(';');
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);
};
} }
return signature.toString();
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";
};
} }
} }

View File

@ -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";
};
}
}