implements PR-10d bank composition family policies and section coordinator

This commit is contained in:
bQUARKz 2026-03-19 00:48:22 +00:00
parent de8720abc3
commit 454f78d0fe
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
11 changed files with 618 additions and 54 deletions

View File

@ -0,0 +1,80 @@
package p.studio.workspaces.assets.details.bank;
import p.studio.workspaces.assets.messages.AssetWorkspaceAssetDetails;
import p.studio.workspaces.assets.messages.AssetWorkspaceBankCompositionFile;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
abstract class AssetDetailsAbstractBankCompositionFamilySupport implements AssetDetailsBankCompositionFamilySupport {
@Override
public AssetDetailsBankCompositionDraft createDraft(AssetWorkspaceAssetDetails details) {
final List<AssetWorkspaceBankCompositionFile> selectedFiles = details.bankComposition().selectedFiles();
final var selectedPaths = selectedFiles.stream().map(AssetWorkspaceBankCompositionFile::path).collect(java.util.stream.Collectors.toSet());
final List<AssetWorkspaceBankCompositionFile> availableFiles = details.bankComposition().availableFiles().stream()
.filter(file -> !selectedPaths.contains(file.path()))
.toList();
return new AssetDetailsBankCompositionDraft(availableFiles, selectedFiles);
}
@Override
public AssetDetailsBankCompositionDraft moveToSelected(
AssetDetailsBankCompositionDraft draft,
List<AssetWorkspaceBankCompositionFile> files) {
final List<AssetWorkspaceBankCompositionFile> movableFiles = selectMovableFiles(draft, files);
return draft.removeFromAvailable(movableFiles).appendToSelected(movableFiles);
}
@Override
public AssetDetailsBankCompositionDraft moveToAvailable(
AssetDetailsBankCompositionDraft draft,
List<AssetWorkspaceBankCompositionFile> files) {
final var returnedFiles = new ArrayList<>(files);
returnedFiles.sort(java.util.Comparator.comparing(AssetWorkspaceBankCompositionFile::path, String.CASE_INSENSITIVE_ORDER));
return draft.removeFromSelected(files).appendToAvailable(returnedFiles);
}
@Override
public AssetDetailsBankCompositionDraft moveUp(AssetDetailsBankCompositionDraft draft, int selectedIndex) {
if (selectedIndex <= 0 || selectedIndex >= draft.selectedFiles().size()) {
return draft;
}
final List<AssetWorkspaceBankCompositionFile> reordered = new ArrayList<>(draft.selectedFiles());
final AssetWorkspaceBankCompositionFile moved = reordered.remove(selectedIndex);
reordered.add(selectedIndex - 1, moved);
return new AssetDetailsBankCompositionDraft(draft.availableFiles(), reordered);
}
@Override
public AssetDetailsBankCompositionDraft moveDown(AssetDetailsBankCompositionDraft draft, int selectedIndex) {
if (selectedIndex < 0 || selectedIndex >= draft.selectedFiles().size() - 1) {
return draft;
}
final List<AssetWorkspaceBankCompositionFile> reordered = new ArrayList<>(draft.selectedFiles());
final AssetWorkspaceBankCompositionFile moved = reordered.remove(selectedIndex);
reordered.add(selectedIndex + 1, moved);
return new AssetDetailsBankCompositionDraft(draft.availableFiles(), reordered);
}
protected abstract boolean canAdd(
AssetDetailsBankCompositionDraft currentDraft,
List<AssetWorkspaceBankCompositionFile> accepted,
AssetWorkspaceBankCompositionFile candidate);
private List<AssetWorkspaceBankCompositionFile> selectMovableFiles(
AssetDetailsBankCompositionDraft draft,
List<AssetWorkspaceBankCompositionFile> files) {
final var requested = new LinkedHashSet<>(files);
final List<AssetWorkspaceBankCompositionFile> movable = new ArrayList<>();
for (AssetWorkspaceBankCompositionFile file : draft.availableFiles()) {
if (!requested.contains(file)) {
continue;
}
if (canAdd(draft, movable, file)) {
movable.add(file);
}
}
return List.copyOf(movable);
}
}

View File

@ -0,0 +1,20 @@
package p.studio.workspaces.assets.details.bank;
import p.studio.controls.banks.StudioAssetCapacitySeverity;
import java.util.Objects;
public record AssetDetailsBankCompositionCapacityState(
double progress,
StudioAssetCapacitySeverity severity,
boolean blocked,
String labelText,
String hintText) {
public AssetDetailsBankCompositionCapacityState {
progress = Math.max(0.0d, Math.min(1.0d, progress));
severity = severity == null ? StudioAssetCapacitySeverity.GREEN : severity;
labelText = Objects.requireNonNullElse(labelText, "");
hintText = Objects.requireNonNullElse(hintText, "");
}
}

View File

@ -5,17 +5,13 @@ import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import p.studio.Container;
import p.studio.controls.banks.StudioAssetCapacityMeter;
import p.studio.controls.banks.StudioAssetCapacitySeverity;
import p.studio.controls.forms.StudioFormActionBar;
import p.studio.controls.forms.StudioFormMode;
import p.studio.controls.forms.StudioFormSession;
import p.studio.controls.lifecycle.StudioControlLifecycle;
import p.studio.controls.lifecycle.StudioControlLifecycleSupport;
import p.studio.events.StudioWorkspaceEventBus;
import p.studio.projects.ProjectReference;
import p.studio.utilities.i18n.I18n;
import p.studio.workspaces.assets.details.AssetDetailsUiSupport;
import p.studio.workspaces.assets.messages.AssetWorkspaceBankCompositionDetails;
import p.studio.workspaces.assets.messages.AssetWorkspaceDetailsViewState;
import p.studio.workspaces.assets.messages.events.StudioAssetsDetailsViewStateChangedEvent;
import p.studio.workspaces.framework.StudioSubscriptionBag;
@ -32,9 +28,9 @@ public final class AssetDetailsBankCompositionControl extends VBox implements St
this::cancelEdit);
private final AssetDetailsBankCompositionDualListView dualListView = new AssetDetailsBankCompositionDualListView();
private final StudioAssetCapacityMeter capacityMeter = new StudioAssetCapacityMeter();
private final AssetDetailsBankCompositionCoordinator coordinator = new AssetDetailsBankCompositionCoordinator();
private AssetWorkspaceDetailsViewState viewState;
private StudioFormSession<AssetWorkspaceBankCompositionDetails> formSession;
public AssetDetailsBankCompositionControl(ProjectReference projectReference, StudioWorkspaceEventBus workspaceBus) {
StudioControlLifecycleSupport.install(this, this);
@ -46,7 +42,7 @@ public final class AssetDetailsBankCompositionControl extends VBox implements St
public void subscribe() {
subscriptions.add(workspaceBus.subscribe(StudioAssetsDetailsViewStateChangedEvent.class, event -> {
viewState = event.viewState();
syncFormSession();
coordinator.replaceDetails(viewState == null ? null : viewState.selectedAssetDetails());
render();
}));
}
@ -56,48 +52,46 @@ public final class AssetDetailsBankCompositionControl extends VBox implements St
subscriptions.clear();
}
private void syncFormSession() {
if (viewState == null || viewState.selectedAssetDetails() == null) {
formSession = null;
return;
}
final AssetWorkspaceBankCompositionDetails source = viewState.selectedAssetDetails().bankComposition();
if (formSession == null) {
formSession = new StudioFormSession<>(source);
return;
}
if (!Objects.equals(formSession.source(), source)) {
formSession.replaceSource(source);
}
}
private void render() {
if (viewState == null || viewState.selectedAssetDetails() == null) {
getChildren().clear();
return;
}
if (formSession == null) {
syncFormSession();
}
if (formSession == null) {
if (!coordinator.ready()) {
getChildren().clear();
return;
}
final boolean editing = formSession.mode() == StudioFormMode.EDITING;
final AssetWorkspaceBankCompositionDetails draft = formSession.draft();
final AssetDetailsBankCompositionViewModel viewModel = coordinator.viewModel();
dualListView.setLeftItems(draft.availableFiles());
dualListView.setRightItems(draft.selectedFiles());
dualListView.setInteractionEnabled(false);
dualListView.setLeftItems(viewModel.availableFiles());
dualListView.setRightItems(viewModel.selectedFiles());
dualListView.setInteractionEnabled(viewModel.editing());
dualListView.setOnMoveToRight(items -> {
coordinator.moveToSelected(items);
render();
});
dualListView.setOnMoveToLeft(items -> {
coordinator.moveToAvailable(items);
render();
});
dualListView.setOnMoveUp(index -> {
coordinator.moveUp(index);
render();
});
dualListView.setOnMoveDown(index -> {
coordinator.moveDown(index);
render();
});
capacityMeter.setProgress(0.0d);
capacityMeter.setSeverity(StudioAssetCapacitySeverity.GREEN);
capacityMeter.setLabelText(draft.measuredBankSizeBytes() + " bytes");
capacityMeter.setHintText(editing
? Container.i18n().text(I18n.ASSETS_DETAILS_BANK_COMPOSITION_EDITING_HINT)
: Container.i18n().text(I18n.ASSETS_DETAILS_BANK_COMPOSITION_READONLY_HINT));
capacityMeter.setProgress(viewModel.capacityState().progress());
capacityMeter.setSeverity(viewModel.capacityState().severity());
capacityMeter.setLabelText(viewModel.capacityState().labelText());
capacityMeter.setHintText(viewModel.capacityState().hintText().isBlank()
? (viewModel.editing()
? Container.i18n().text(I18n.ASSETS_DETAILS_BANK_COMPOSITION_EDITING_HINT)
: Container.i18n().text(I18n.ASSETS_DETAILS_BANK_COMPOSITION_READONLY_HINT))
: viewModel.capacityState().hintText());
final HBox body = new HBox(16, dualListView, capacityMeter);
body.getStyleClass().add("assets-details-bank-composition-body");
@ -105,7 +99,7 @@ public final class AssetDetailsBankCompositionControl extends VBox implements St
dualListView.setMaxWidth(Double.MAX_VALUE);
final VBox content = new VBox(12, body);
actionBar.updateState(formSession.mode(), formSession.isDirty());
actionBar.updateState(viewModel.editing() ? p.studio.controls.forms.StudioFormMode.EDITING : p.studio.controls.forms.StudioFormMode.READ_ONLY, viewModel.dirty());
content.getChildren().add(actionBar);
final VBox section = AssetDetailsUiSupport.createSection(
@ -116,34 +110,22 @@ public final class AssetDetailsBankCompositionControl extends VBox implements St
}
private void beginEdit() {
if (formSession == null) {
return;
}
formSession.beginEdit();
coordinator.beginEdit();
render();
}
private void applyDraft() {
if (formSession == null) {
return;
}
formSession.apply();
coordinator.apply();
render();
}
private void resetDraft() {
if (formSession == null) {
return;
}
formSession.resetDraft();
coordinator.reset();
render();
}
private void cancelEdit() {
if (formSession == null) {
return;
}
formSession.cancelEdit();
coordinator.cancel();
render();
}
}

View File

@ -0,0 +1,115 @@
package p.studio.workspaces.assets.details.bank;
import p.studio.controls.forms.StudioFormMode;
import p.studio.controls.forms.StudioFormSession;
import p.studio.workspaces.assets.messages.AssetWorkspaceAssetDetails;
import p.studio.workspaces.assets.messages.AssetWorkspaceBankCompositionFile;
import java.util.List;
import java.util.Objects;
public final class AssetDetailsBankCompositionCoordinator {
private final List<AssetDetailsBankCompositionFamilySupport> familySupports = List.of(
new AssetDetailsTileBankCompositionFamilySupport(),
new AssetDetailsSoundBankCompositionFamilySupport(),
new AssetDetailsFallbackBankCompositionFamilySupport());
private AssetDetailsBankCompositionFamilySupport familySupport;
private StudioFormSession<AssetDetailsBankCompositionDraft> formSession;
public void replaceDetails(AssetWorkspaceAssetDetails details) {
if (details == null) {
familySupport = null;
formSession = null;
return;
}
familySupport = familySupports.stream()
.filter(candidate -> candidate.supports(details.summary().assetFamily()))
.findFirst()
.orElseThrow();
final AssetDetailsBankCompositionDraft source = familySupport.createDraft(details);
if (formSession == null) {
formSession = new StudioFormSession<>(source);
return;
}
if (!Objects.equals(formSession.source(), source)) {
formSession.replaceSource(source);
}
}
public boolean ready() {
return formSession != null && familySupport != null;
}
public void beginEdit() {
if (ready()) {
formSession.beginEdit();
}
}
public void apply() {
if (ready()) {
formSession.apply();
}
}
public void reset() {
if (ready()) {
formSession.resetDraft();
}
}
public void cancel() {
if (ready()) {
formSession.cancelEdit();
}
}
public void moveToSelected(List<AssetWorkspaceBankCompositionFile> files) {
if (!ready() || formSession.mode() != StudioFormMode.EDITING) {
return;
}
formSession.updateDraft(current -> familySupport.moveToSelected(current, files));
}
public void moveToAvailable(List<AssetWorkspaceBankCompositionFile> files) {
if (!ready() || formSession.mode() != StudioFormMode.EDITING) {
return;
}
formSession.updateDraft(current -> familySupport.moveToAvailable(current, files));
}
public void moveUp(int selectedIndex) {
if (!ready() || formSession.mode() != StudioFormMode.EDITING) {
return;
}
formSession.updateDraft(current -> familySupport.moveUp(current, selectedIndex));
}
public void moveDown(int selectedIndex) {
if (!ready() || formSession.mode() != StudioFormMode.EDITING) {
return;
}
formSession.updateDraft(current -> familySupport.moveDown(current, selectedIndex));
}
public AssetDetailsBankCompositionViewModel viewModel() {
if (!ready()) {
return new AssetDetailsBankCompositionViewModel(
false,
false,
List.of(),
List.of(),
new AssetDetailsBankCompositionCapacityState(0.0d, null, false, "", ""));
}
final AssetDetailsBankCompositionDraft draft = formSession.draft();
return new AssetDetailsBankCompositionViewModel(
formSession.mode() == StudioFormMode.EDITING,
formSession.isDirty(),
draft.availableFiles(),
draft.selectedFiles(),
familySupport.evaluate(draft));
}
}

View File

@ -0,0 +1,43 @@
package p.studio.workspaces.assets.details.bank;
import p.studio.workspaces.assets.messages.AssetWorkspaceBankCompositionFile;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Objects;
public record AssetDetailsBankCompositionDraft(
List<AssetWorkspaceBankCompositionFile> availableFiles,
List<AssetWorkspaceBankCompositionFile> selectedFiles) {
public AssetDetailsBankCompositionDraft {
availableFiles = List.copyOf(Objects.requireNonNull(availableFiles, "availableFiles"));
selectedFiles = List.copyOf(Objects.requireNonNull(selectedFiles, "selectedFiles"));
}
public AssetDetailsBankCompositionDraft removeFromAvailable(List<AssetWorkspaceBankCompositionFile> files) {
final var removed = new LinkedHashSet<>(Objects.requireNonNull(files, "files"));
return new AssetDetailsBankCompositionDraft(
availableFiles.stream().filter(file -> !removed.contains(file)).toList(),
selectedFiles);
}
public AssetDetailsBankCompositionDraft appendToSelected(List<AssetWorkspaceBankCompositionFile> files) {
final var nextSelected = new java.util.ArrayList<>(selectedFiles);
nextSelected.addAll(Objects.requireNonNull(files, "files"));
return new AssetDetailsBankCompositionDraft(availableFiles, nextSelected);
}
public AssetDetailsBankCompositionDraft removeFromSelected(List<AssetWorkspaceBankCompositionFile> files) {
final var removed = new LinkedHashSet<>(Objects.requireNonNull(files, "files"));
return new AssetDetailsBankCompositionDraft(
availableFiles,
selectedFiles.stream().filter(file -> !removed.contains(file)).toList());
}
public AssetDetailsBankCompositionDraft appendToAvailable(List<AssetWorkspaceBankCompositionFile> files) {
final var nextAvailable = new java.util.ArrayList<>(availableFiles);
nextAvailable.addAll(Objects.requireNonNull(files, "files"));
return new AssetDetailsBankCompositionDraft(nextAvailable, selectedFiles);
}
}

View File

@ -0,0 +1,31 @@
package p.studio.workspaces.assets.details.bank;
import p.packer.messages.assets.AssetFamilyCatalog;
import p.studio.workspaces.assets.messages.AssetWorkspaceAssetDetails;
import p.studio.workspaces.assets.messages.AssetWorkspaceBankCompositionFile;
import java.util.List;
interface AssetDetailsBankCompositionFamilySupport {
boolean supports(AssetFamilyCatalog assetFamily);
AssetDetailsBankCompositionDraft createDraft(AssetWorkspaceAssetDetails details);
AssetDetailsBankCompositionDraft moveToSelected(
AssetDetailsBankCompositionDraft draft,
List<AssetWorkspaceBankCompositionFile> files);
AssetDetailsBankCompositionDraft moveToAvailable(
AssetDetailsBankCompositionDraft draft,
List<AssetWorkspaceBankCompositionFile> files);
AssetDetailsBankCompositionDraft moveUp(
AssetDetailsBankCompositionDraft draft,
int selectedIndex);
AssetDetailsBankCompositionDraft moveDown(
AssetDetailsBankCompositionDraft draft,
int selectedIndex);
AssetDetailsBankCompositionCapacityState evaluate(AssetDetailsBankCompositionDraft draft);
}

View File

@ -0,0 +1,20 @@
package p.studio.workspaces.assets.details.bank;
import p.studio.workspaces.assets.messages.AssetWorkspaceBankCompositionFile;
import java.util.List;
import java.util.Objects;
public record AssetDetailsBankCompositionViewModel(
boolean editing,
boolean dirty,
List<AssetWorkspaceBankCompositionFile> availableFiles,
List<AssetWorkspaceBankCompositionFile> selectedFiles,
AssetDetailsBankCompositionCapacityState capacityState) {
public AssetDetailsBankCompositionViewModel {
availableFiles = List.copyOf(Objects.requireNonNull(availableFiles, "availableFiles"));
selectedFiles = List.copyOf(Objects.requireNonNull(selectedFiles, "selectedFiles"));
capacityState = Objects.requireNonNull(capacityState, "capacityState");
}
}

View File

@ -0,0 +1,32 @@
package p.studio.workspaces.assets.details.bank;
import p.packer.messages.assets.AssetFamilyCatalog;
import p.studio.controls.banks.StudioAssetCapacitySeverity;
import p.studio.workspaces.assets.messages.AssetWorkspaceBankCompositionFile;
import java.util.List;
final class AssetDetailsFallbackBankCompositionFamilySupport extends AssetDetailsAbstractBankCompositionFamilySupport {
@Override
public boolean supports(AssetFamilyCatalog assetFamily) {
return true;
}
@Override
protected boolean canAdd(
AssetDetailsBankCompositionDraft currentDraft,
List<AssetWorkspaceBankCompositionFile> accepted,
AssetWorkspaceBankCompositionFile candidate) {
return true;
}
@Override
public AssetDetailsBankCompositionCapacityState evaluate(AssetDetailsBankCompositionDraft draft) {
return new AssetDetailsBankCompositionCapacityState(
0.0d,
StudioAssetCapacitySeverity.GREEN,
false,
draft.selectedFiles().size() + " selected",
"");
}
}

View File

@ -0,0 +1,52 @@
package p.studio.workspaces.assets.details.bank;
import p.packer.messages.assets.AssetFamilyCatalog;
import p.studio.controls.banks.StudioAssetCapacitySeverity;
import p.studio.workspaces.assets.messages.AssetWorkspaceBankCompositionFile;
import java.util.List;
final class AssetDetailsSoundBankCompositionFamilySupport extends AssetDetailsAbstractBankCompositionFamilySupport {
private static final long MAX_BYTES = 1024L * 1024L;
@Override
public boolean supports(AssetFamilyCatalog assetFamily) {
return assetFamily == AssetFamilyCatalog.SOUND_BANK;
}
@Override
protected boolean canAdd(
AssetDetailsBankCompositionDraft currentDraft,
List<AssetWorkspaceBankCompositionFile> accepted,
AssetWorkspaceBankCompositionFile candidate) {
final long usedBytes = selectedBytes(currentDraft.selectedFiles()) + selectedBytes(accepted);
return usedBytes + candidate.size() <= MAX_BYTES;
}
@Override
public AssetDetailsBankCompositionCapacityState evaluate(AssetDetailsBankCompositionDraft draft) {
final long usedBytes = selectedBytes(draft.selectedFiles());
final double progress = (double) usedBytes / (double) MAX_BYTES;
final boolean blocked = usedBytes >= MAX_BYTES;
return new AssetDetailsBankCompositionCapacityState(
progress,
severityFor(progress),
blocked,
usedBytes + " / " + MAX_BYTES + " bytes",
blocked ? "Sound bank byte capacity reached." : "");
}
private long selectedBytes(List<AssetWorkspaceBankCompositionFile> files) {
return files.stream().mapToLong(AssetWorkspaceBankCompositionFile::size).sum();
}
private StudioAssetCapacitySeverity severityFor(double progress) {
if (progress >= 0.95d) {
return StudioAssetCapacitySeverity.RED;
}
if (progress >= 0.65d) {
return StudioAssetCapacitySeverity.ORANGE;
}
return StudioAssetCapacitySeverity.GREEN;
}
}

View File

@ -0,0 +1,68 @@
package p.studio.workspaces.assets.details.bank;
import p.packer.messages.assets.AssetFamilyCatalog;
import p.studio.controls.banks.StudioAssetCapacitySeverity;
import p.studio.workspaces.assets.messages.AssetWorkspaceAssetDetails;
import p.studio.workspaces.assets.messages.AssetWorkspaceBankCompositionFile;
import java.util.List;
final class AssetDetailsTileBankCompositionFamilySupport extends AssetDetailsAbstractBankCompositionFamilySupport {
private int maxSlots = 64;
@Override
public boolean supports(AssetFamilyCatalog assetFamily) {
return assetFamily == AssetFamilyCatalog.TILE_BANK;
}
@Override
public AssetDetailsBankCompositionDraft createDraft(AssetWorkspaceAssetDetails details) {
maxSlots = computeMaxSlots(details);
return super.createDraft(details);
}
@Override
protected boolean canAdd(
AssetDetailsBankCompositionDraft currentDraft,
List<AssetWorkspaceBankCompositionFile> accepted,
AssetWorkspaceBankCompositionFile candidate) {
return currentDraft.selectedFiles().size() + accepted.size() < maxSlots;
}
@Override
public AssetDetailsBankCompositionCapacityState evaluate(AssetDetailsBankCompositionDraft draft) {
final int used = draft.selectedFiles().size();
final double progress = maxSlots <= 0 ? 0.0d : (double) used / (double) maxSlots;
final boolean blocked = used >= maxSlots;
return new AssetDetailsBankCompositionCapacityState(
progress,
severityFor(progress),
blocked,
used + " / " + maxSlots,
blocked ? "Tile bank slot capacity reached." : "");
}
private int computeMaxSlots(AssetWorkspaceAssetDetails details) {
final String tileSizeValue = details.metadataFields().stream()
.filter(field -> field.key().equals("tile_size"))
.map(field -> field.value())
.findFirst()
.orElse("16x16");
final int tileSize = switch (tileSizeValue) {
case "8x8" -> 8;
case "32x32" -> 32;
default -> 16;
};
return Math.max(1, (256 / tileSize) * (256 / tileSize));
}
private StudioAssetCapacitySeverity severityFor(double progress) {
if (progress >= 0.95d) {
return StudioAssetCapacitySeverity.RED;
}
if (progress >= 0.65d) {
return StudioAssetCapacitySeverity.ORANGE;
}
return StudioAssetCapacitySeverity.GREEN;
}
}

View File

@ -0,0 +1,121 @@
package p.studio.workspaces.assets.details.bank;
import org.junit.jupiter.api.Test;
import p.packer.dtos.PackerCodecConfigurationFieldDTO;
import p.packer.messages.AssetReference;
import p.packer.messages.assets.AssetFamilyCatalog;
import p.packer.messages.assets.OutputCodecCatalog;
import p.packer.messages.assets.OutputFormatCatalog;
import p.packer.messages.assets.PackerCodecConfigurationFieldType;
import p.studio.workspaces.assets.messages.*;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.*;
final class AssetDetailsBankCompositionCoordinatorTest {
@Test
void tileFamilyBlocksSelectionAfterSlotCapacity() {
final AssetDetailsBankCompositionCoordinator coordinator = new AssetDetailsBankCompositionCoordinator();
coordinator.replaceDetails(tileDetails("32x32", 65));
coordinator.beginEdit();
coordinator.moveToSelected(coordinator.viewModel().availableFiles());
assertEquals(64, coordinator.viewModel().selectedFiles().size());
assertTrue(coordinator.viewModel().capacityState().blocked());
}
@Test
void soundFamilyBlocksSelectionAfterByteCapacity() {
final AssetDetailsBankCompositionCoordinator coordinator = new AssetDetailsBankCompositionCoordinator();
coordinator.replaceDetails(soundDetails(600_000L, 600_000L));
coordinator.beginEdit();
coordinator.moveToSelected(coordinator.viewModel().availableFiles());
assertEquals(1, coordinator.viewModel().selectedFiles().size());
assertTrue(coordinator.viewModel().capacityState().progress() > 0.5d);
}
@Test
void resetAndCancelFollowStudioFormSessionLifecycle() {
final AssetDetailsBankCompositionCoordinator coordinator = new AssetDetailsBankCompositionCoordinator();
coordinator.replaceDetails(tileDetails("16x16", 2));
coordinator.beginEdit();
final var firstFile = coordinator.viewModel().availableFiles().subList(0, 1);
coordinator.moveToSelected(firstFile);
assertTrue(coordinator.viewModel().dirty());
coordinator.reset();
assertFalse(coordinator.viewModel().dirty());
coordinator.moveToSelected(firstFile);
assertTrue(coordinator.viewModel().dirty());
coordinator.cancel();
assertFalse(coordinator.viewModel().editing());
assertFalse(coordinator.viewModel().dirty());
}
private AssetWorkspaceAssetDetails tileDetails(String tileSize, int fileCount) {
return new AssetWorkspaceAssetDetails(
summary(AssetFamilyCatalog.TILE_BANK),
List.of(),
OutputFormatCatalog.TILES_INDEXED_V1,
OutputCodecCatalog.NONE,
List.of(OutputCodecCatalog.NONE),
Map.of(OutputCodecCatalog.NONE, List.of()),
List.of(new PackerCodecConfigurationFieldDTO("tile_size", "Tile Size", PackerCodecConfigurationFieldType.TEXT, tileSize, true, List.of())),
new AssetWorkspaceBankCompositionDetails(
files("tile", fileCount, 1024L),
List.of(),
0L),
Map.of());
}
private AssetWorkspaceAssetDetails soundDetails(long firstSize, long secondSize) {
return new AssetWorkspaceAssetDetails(
summary(AssetFamilyCatalog.SOUND_BANK),
List.of(),
OutputFormatCatalog.SOUND_V1,
OutputCodecCatalog.NONE,
List.of(OutputCodecCatalog.NONE),
Map.of(OutputCodecCatalog.NONE, List.of()),
List.of(),
new AssetWorkspaceBankCompositionDetails(
List.of(
new AssetWorkspaceBankCompositionFile("a.wav", "a.wav", firstSize, 1L, null, Map.of()),
new AssetWorkspaceBankCompositionFile("b.wav", "b.wav", secondSize, 1L, null, Map.of())),
List.of(),
0L),
Map.of());
}
private List<AssetWorkspaceBankCompositionFile> files(String prefix, int count, long size) {
return java.util.stream.IntStream.range(0, count)
.mapToObj(index -> new AssetWorkspaceBankCompositionFile(
prefix + "-" + index + ".png",
prefix + "-" + index + ".png",
size,
1L,
null,
Map.of()))
.toList();
}
private AssetWorkspaceAssetSummary summary(AssetFamilyCatalog family) {
return new AssetWorkspaceAssetSummary(
AssetReference.forAssetId(1),
"bank",
AssetWorkspaceAssetState.REGISTERED,
AssetWorkspaceBuildParticipation.INCLUDED,
1,
family,
Path.of("/tmp/bank"),
false,
false);
}
}