asset details (WIP)

This commit is contained in:
bQUARKz 2026-03-19 08:40:44 +00:00
parent 7569e93234
commit 91799bccd7
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
9 changed files with 772 additions and 641 deletions

View File

@ -57,6 +57,10 @@ public abstract class StudioDualListView<T> extends HBox {
moveToLeftButton.getStyleClass().addAll("studio-button", "studio-button-secondary");
moveUpButton.getStyleClass().addAll("studio-button", "studio-button-secondary");
moveDownButton.getStyleClass().addAll("studio-button", "studio-button-secondary");
moveToRightButton.setFocusTraversable(false);
moveToLeftButton.setFocusTraversable(false);
moveUpButton.setFocusTraversable(false);
moveDownButton.setFocusTraversable(false);
moveToRightButton.setOnAction(ignored -> onMoveToRight.accept(List.copyOf(leftListView.getSelectionModel().getSelectedItems())));
moveToLeftButton.setOnAction(ignored -> onMoveToLeft.accept(List.copyOf(rightListView.getSelectionModel().getSelectedItems())));

View File

@ -30,11 +30,16 @@ public final class StudioFormActionBar extends HBox {
}
public void updateState(StudioFormMode mode, boolean dirty) {
updateState(mode, dirty, true);
}
public void updateState(StudioFormMode mode, boolean dirty, boolean canBeginEdit) {
final boolean editing = mode == StudioFormMode.EDITING;
setVisibleManaged(changeButton, !editing);
setVisibleManaged(applyButton, editing);
setVisibleManaged(resetButton, editing);
setVisibleManaged(cancelButton, editing);
changeButton.setDisable(!canBeginEdit);
applyButton.setDisable(!dirty);
resetButton.setDisable(!dirty);
}

View File

@ -0,0 +1,8 @@
package p.studio.controls.forms;
import p.studio.events.StudioEvent;
public record StudioFormEditScopeChangedEvent(
String scopeKey,
String ownerSectionId) implements StudioEvent {
}

View File

@ -4,7 +4,11 @@ import javafx.application.Platform;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import p.studio.events.StudioWorkspaceEventBus;
import java.util.Objects;
public abstract class StudioFormSection extends StudioSection {
private final StudioFormActionBar actionBar = new StudioFormActionBar(
@ -12,10 +16,15 @@ public abstract class StudioFormSection extends StudioSection {
this::handleApply,
this::handleReset,
this::handleCancel);
private final VBox contentHost = new VBox();
private boolean editAllowed = true;
private VBox mountedSection;
protected StudioFormSection() {
super();
actionBar.getStyleClass().add("studio-form-section-actions");
contentHost.setFillWidth(true);
VBox.setVgrow(contentHost, Priority.ALWAYS);
}
public static VBox createReadOnlySection(String title, Node content) {
@ -28,16 +37,55 @@ public abstract class StudioFormSection extends StudioSection {
protected final void renderFormSection(Node content) {
updateActionBarState();
final VBox section = createEditableSection(sectionTitle(), actionBar, content, formMode());
final String sectionStyleClass = sectionStyleClass();
if (sectionStyleClass != null && !sectionStyleClass.isBlank()) {
section.getStyleClass().add(sectionStyleClass);
if (mountedSection == null) {
mountedSection = createEditableSection(sectionTitle(), actionBar, contentHost, formMode());
final String sectionStyleClass = sectionStyleClass();
if (sectionStyleClass != null && !sectionStyleClass.isBlank()) {
mountedSection.getStyleClass().add(sectionStyleClass);
}
getChildren().setAll(mountedSection);
}
getChildren().setAll(section);
syncMountedSectionState();
contentHost.getChildren().setAll(content);
}
protected final void clearRenderedSection() {
contentHost.getChildren().clear();
mountedSection = null;
getChildren().clear();
}
protected final void updateActionBarState() {
actionBar.updateState(formMode(), isDirty());
actionBar.updateState(formMode(), isDirty(), editAllowed);
}
protected final void setEditAllowed(boolean editAllowed) {
this.editAllowed = editAllowed;
updateActionBarState();
}
protected final boolean isEditAllowed() {
return editAllowed;
}
protected abstract String formSectionId();
protected final void handleEditScopeChanged(StudioFormEditScopeChangedEvent event, String currentScopeKey) {
if (!Objects.equals(currentScopeKey, event.scopeKey())) {
setEditAllowed(true);
return;
}
if (event.ownerSectionId() == null && formMode() == StudioFormMode.EDITING) {
cancel();
renderSection();
return;
}
setEditAllowed(event.ownerSectionId() == null || formSectionId().equals(event.ownerSectionId()));
}
protected final void publishEditScope(StudioWorkspaceEventBus workspaceBus, String scopeKey, String ownerSectionId) {
setEditAllowed(ownerSectionId == null || formSectionId().equals(ownerSectionId));
workspaceBus.publish(new StudioFormEditScopeChangedEvent(scopeKey, ownerSectionId));
}
protected final void rerenderPreservingScrollPosition() {
@ -70,6 +118,9 @@ public abstract class StudioFormSection extends StudioSection {
}
private void handleBeginEdit() {
if (!editAllowed) {
return;
}
beginEdit();
rerenderPreservingScrollPosition();
}
@ -109,4 +160,14 @@ public abstract class StudioFormSection extends StudioSection {
}
return null;
}
private void syncMountedSectionState() {
if (mountedSection == null) {
return;
}
mountedSection.getStyleClass().remove("studio-form-section-editing");
if (formMode() == StudioFormMode.EDITING) {
mountedSection.getStyleClass().add("studio-form-section-editing");
}
}
}

View File

@ -12,6 +12,7 @@ import p.packer.dtos.PackerAssetActionAvailabilityDTO;
import p.packer.dtos.PackerAssetDetailsDTO;
import p.packer.messages.*;
import p.studio.Container;
import p.studio.controls.forms.StudioFormEditScopeChangedEvent;
import p.studio.controls.forms.StudioSection;
import p.studio.events.StudioWorkspaceEventBus;
import p.studio.projects.ProjectReference;
@ -192,8 +193,19 @@ public final class AssetDetailsControl extends VBox implements StudioEventAware
}
private void publishViewState(AssetWorkspaceDetailsViewState nextViewState) {
final AssetReference previousAssetReference = viewState == null ? null : viewState.selectedAssetReference();
final String previousScopeKey = previousAssetReference == null ? null : "asset-details:" + previousAssetReference;
final boolean assetChanged = !Objects.equals(previousAssetReference, nextViewState.selectedAssetReference());
viewState = Objects.requireNonNull(nextViewState, "nextViewState");
if (assetChanged || nextViewState.detailsStatus() != AssetWorkspaceDetailsStatus.READY) {
workspaceBus.publish(new StudioFormEditScopeChangedEvent(
previousScopeKey,
null));
}
render();
if (assetChanged && nextViewState.selectedAssetReference() != null) {
Platform.runLater(this::scrollToTop);
}
workspaceBus.publish(new StudioAssetsDetailsViewStateChangedEvent(nextViewState));
}

View File

@ -7,6 +7,7 @@ import javafx.scene.layout.VBox;
import p.packer.messages.ApplyBankCompositionRequest;
import p.studio.Container;
import p.studio.controls.banks.StudioAssetCapacityMeter;
import p.studio.controls.forms.StudioFormEditScopeChangedEvent;
import p.studio.controls.forms.StudioFormMode;
import p.studio.controls.forms.StudioFormSection;
import p.studio.events.StudioWorkspaceEventBus;
@ -27,6 +28,7 @@ import java.util.Objects;
public final class AssetDetailsBankCompositionControl extends StudioFormSection {
private static final double CAPACITY_METER_WIDTH = 180.0d;
private static final String SECTION_ID = "asset-details.bank-composition";
private final ProjectReference projectReference;
private final StudioWorkspaceEventBus workspaceBus;
@ -34,6 +36,8 @@ public final class AssetDetailsBankCompositionControl extends StudioFormSection
private final AssetDetailsBankCompositionDualListView dualListView = new AssetDetailsBankCompositionDualListView();
private final StudioAssetCapacityMeter capacityMeter = new StudioAssetCapacityMeter();
private final AssetDetailsBankCompositionCoordinator coordinator = new AssetDetailsBankCompositionCoordinator();
private final HBox body = new HBox(16, dualListView, capacityMeter);
private final VBox content = new VBox(12, body);
private AssetWorkspaceDetailsViewState viewState;
@ -43,6 +47,9 @@ public final class AssetDetailsBankCompositionControl extends StudioFormSection
capacityMeter.setMinWidth(CAPACITY_METER_WIDTH);
capacityMeter.setPrefWidth(CAPACITY_METER_WIDTH);
capacityMeter.setMaxWidth(CAPACITY_METER_WIDTH);
body.getStyleClass().add("assets-details-bank-composition-body");
HBox.setHgrow(dualListView, Priority.ALWAYS);
dualListView.setMaxWidth(Double.MAX_VALUE);
}
@Override
@ -50,9 +57,14 @@ public final class AssetDetailsBankCompositionControl extends StudioFormSection
subscriptions.add(workspaceBus.subscribe(StudioAssetsDetailsViewStateChangedEvent.class, event -> {
viewState = event.viewState();
coordinator.replaceDetails(viewState == null ? null : viewState.selectedAssetDetails());
if (viewState == null || viewState.selectedAssetReference() == null) {
publishEditScope(workspaceBus, currentScopeKey(), null);
}
renderSection();
publishStateNotifications();
}));
subscriptions.add(workspaceBus.subscribe(StudioFormEditScopeChangedEvent.class,
event -> handleEditScopeChanged(event, currentScopeKey())));
}
@Override
@ -63,11 +75,11 @@ public final class AssetDetailsBankCompositionControl extends StudioFormSection
@Override
protected void renderSection() {
if (viewState == null || viewState.selectedAssetDetails() == null) {
getChildren().clear();
clearRenderedSection();
return;
}
if (!coordinator.ready()) {
getChildren().clear();
clearRenderedSection();
return;
}
@ -78,22 +90,22 @@ public final class AssetDetailsBankCompositionControl extends StudioFormSection
dualListView.setInteractionEnabled(viewModel.editing());
dualListView.setOnMoveToRight(items -> {
coordinator.moveToSelected(items);
renderSection();
rerenderPreservingScrollPosition();
publishStateNotifications();
});
dualListView.setOnMoveToLeft(items -> {
coordinator.moveToAvailable(items);
renderSection();
rerenderPreservingScrollPosition();
publishStateNotifications();
});
dualListView.setOnMoveUp(index -> {
coordinator.moveUp(index);
renderSection();
rerenderPreservingScrollPosition();
publishStateNotifications();
});
dualListView.setOnMoveDown(index -> {
coordinator.moveDown(index);
renderSection();
rerenderPreservingScrollPosition();
publishStateNotifications();
});
@ -104,12 +116,6 @@ public final class AssetDetailsBankCompositionControl extends StudioFormSection
? 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");
HBox.setHgrow(dualListView, Priority.ALWAYS);
dualListView.setMaxWidth(Double.MAX_VALUE);
final VBox content = new VBox(12, body);
renderFormSection(content);
}
@ -133,9 +139,15 @@ public final class AssetDetailsBankCompositionControl extends StudioFormSection
return "assets-details-bank-composition-section";
}
@Override
protected String formSectionId() {
return SECTION_ID;
}
@Override
protected void beginEdit() {
coordinator.beginEdit();
publishEditScope(workspaceBus, currentScopeKey(), SECTION_ID);
publishStateNotifications();
}
@ -159,6 +171,7 @@ public final class AssetDetailsBankCompositionControl extends StudioFormSection
Platform.runLater(() -> {
if (response.success()) {
coordinator.apply();
publishEditScope(workspaceBus, currentScopeKey(), null);
renderSection();
publishStateNotifications();
workspaceBus.publish(new StudioAssetBankCompositionAppliedEvent(assetReference));
@ -186,6 +199,7 @@ public final class AssetDetailsBankCompositionControl extends StudioFormSection
@Override
protected void cancel() {
coordinator.cancel();
publishEditScope(workspaceBus, currentScopeKey(), null);
publishStateNotifications();
}
@ -208,4 +222,10 @@ public final class AssetDetailsBankCompositionControl extends StudioFormSection
viewModel.capacityState().labelText(),
viewModel.capacityState().hintText()));
}
private String currentScopeKey() {
return viewState == null || viewState.selectedAssetReference() == null
? null
: "asset-details:" + viewState.selectedAssetReference();
}
}

View File

@ -11,6 +11,7 @@ import p.packer.dtos.PackerCodecConfigurationFieldDTO;
import p.packer.messages.UpdateAssetContractRequest;
import p.packer.messages.assets.OutputCodecCatalog;
import p.studio.Container;
import p.studio.controls.forms.StudioFormEditScopeChangedEvent;
import p.studio.controls.forms.StudioFormMode;
import p.studio.controls.forms.StudioFormSession;
import p.studio.controls.forms.StudioFormSection;
@ -28,9 +29,15 @@ import p.studio.workspaces.framework.StudioSubscriptionBag;
import java.util.Objects;
public final class AssetDetailsContractControl extends StudioFormSection {
private static final String SECTION_ID = "asset-details.runtime-contract";
private final ProjectReference projectReference;
private final StudioWorkspaceEventBus workspaceBus;
private final StudioSubscriptionBag subscriptions = new StudioSubscriptionBag();
private final VBox generalColumn = new VBox(8);
private final VBox codecColumn = new VBox(10);
private final HBox contractBody = new HBox(16);
private final VBox content = new VBox(12);
private AssetWorkspaceDetailsViewState viewState;
private StudioFormSession<AssetContractDraft> formSession;
@ -38,6 +45,17 @@ public final class AssetDetailsContractControl extends StudioFormSection {
public AssetDetailsContractControl(ProjectReference projectReference, StudioWorkspaceEventBus workspaceBus) {
this.projectReference = Objects.requireNonNull(projectReference, "projectReference");
this.workspaceBus = Objects.requireNonNull(workspaceBus, "workspaceBus");
generalColumn.getStyleClass().add("assets-details-contract-column");
codecColumn.getStyleClass().addAll("assets-details-contract-column", "assets-details-contract-codec-column");
contractBody.getStyleClass().add("assets-details-contract-body");
HBox.setHgrow(generalColumn, Priority.ALWAYS);
HBox.setHgrow(codecColumn, Priority.ALWAYS);
generalColumn.prefWidthProperty().bind(contractBody.widthProperty().subtract(16).multiply(0.5d));
codecColumn.prefWidthProperty().bind(contractBody.widthProperty().subtract(16).multiply(0.5d));
generalColumn.setMaxWidth(Double.MAX_VALUE);
codecColumn.setMaxWidth(Double.MAX_VALUE);
contractBody.getChildren().setAll(generalColumn, codecColumn);
content.getChildren().setAll(contractBody);
}
@Override
@ -47,6 +65,8 @@ public final class AssetDetailsContractControl extends StudioFormSection {
syncFormSession();
renderSection();
}));
subscriptions.add(workspaceBus.subscribe(StudioFormEditScopeChangedEvent.class,
event -> handleEditScopeChanged(event, currentScopeKey())));
}
@Override
@ -56,6 +76,7 @@ public final class AssetDetailsContractControl extends StudioFormSection {
private void syncFormSession() {
if (viewState == null || viewState.selectedAssetDetails() == null) {
publishEditScope(workspaceBus, currentScopeKey(), null);
formSession = null;
return;
}
@ -67,6 +88,7 @@ public final class AssetDetailsContractControl extends StudioFormSection {
}
if (!Objects.equals(formSession.source(), source)) {
publishEditScope(workspaceBus, currentScopeKey(), null);
formSession.replaceSource(source);
}
}
@ -74,7 +96,7 @@ public final class AssetDetailsContractControl extends StudioFormSection {
@Override
protected void renderSection() {
if (viewState == null || viewState.selectedAssetDetails() == null) {
getChildren().clear();
clearRenderedSection();
return;
}
@ -82,7 +104,7 @@ public final class AssetDetailsContractControl extends StudioFormSection {
syncFormSession();
}
if (formSession == null) {
getChildren().clear();
clearRenderedSection();
return;
}
@ -90,8 +112,6 @@ public final class AssetDetailsContractControl extends StudioFormSection {
final AssetContractDraft draft = formSession.draft();
final boolean editing = formSession.mode() == StudioFormMode.EDITING;
final VBox generalColumn = new VBox(8);
generalColumn.getStyleClass().add("assets-details-contract-column");
generalColumn.getChildren().setAll(
AssetDetailsUiSupport.createKeyValueRow(
Container.i18n().text(I18n.ASSETS_LABEL_PRELOAD),
@ -101,27 +121,12 @@ public final class AssetDetailsContractControl extends StudioFormSection {
draft.bank()),
createMetadataSection(details, draft, editing));
final VBox codecColumn = new VBox(10);
codecColumn.getStyleClass().addAll("assets-details-contract-column", "assets-details-contract-codec-column");
codecColumn.getChildren().setAll(
AssetDetailsUiSupport.createKeyValueRow(
Container.i18n().text(I18n.ASSETS_LABEL_CODEC),
createCodecEditor(details, draft, editing)),
createCodecConfigurationSection(details, draft, editing));
final HBox contractBody = new HBox(16);
contractBody.getStyleClass().add("assets-details-contract-body");
HBox.setHgrow(generalColumn, Priority.ALWAYS);
HBox.setHgrow(codecColumn, Priority.ALWAYS);
generalColumn.prefWidthProperty().bind(contractBody.widthProperty().subtract(16).multiply(0.5d));
codecColumn.prefWidthProperty().bind(contractBody.widthProperty().subtract(16).multiply(0.5d));
generalColumn.setMaxWidth(Double.MAX_VALUE);
codecColumn.setMaxWidth(Double.MAX_VALUE);
contractBody.getChildren().setAll(generalColumn, codecColumn);
final VBox content = new VBox(12);
content.getChildren().setAll(contractBody);
renderFormSection(content);
}
@ -145,6 +150,11 @@ public final class AssetDetailsContractControl extends StudioFormSection {
return "assets-details-contract-section";
}
@Override
protected String formSectionId() {
return SECTION_ID;
}
private Node createPreloadEditor(AssetContractDraft draft, boolean editing) {
final CheckBox checkBox = new CheckBox();
checkBox.getStyleClass().add("assets-details-readonly-check");
@ -446,6 +456,7 @@ public final class AssetDetailsContractControl extends StudioFormSection {
return;
}
formSession.beginEdit();
publishEditScope(workspaceBus, currentScopeKey(), SECTION_ID);
}
@Override
@ -467,6 +478,7 @@ public final class AssetDetailsContractControl extends StudioFormSection {
if (response.success()) {
Platform.runLater(() -> {
final var r = formSession.apply();
publishEditScope(workspaceBus, currentScopeKey(), null);
workspaceBus.publish(new StudioAssetLogEvent("ASSET DETAILS CONTRACT", "Asset contract updated"));
workspaceBus.publish(new StudioAssetsRefreshRequestedEvent(r.assetReference()));
renderSection();
@ -502,5 +514,12 @@ public final class AssetDetailsContractControl extends StudioFormSection {
return;
}
formSession.cancelEdit();
publishEditScope(workspaceBus, currentScopeKey(), null);
}
private String currentScopeKey() {
return viewState == null || viewState.selectedAssetReference() == null
? null
: "asset-details:" + viewState.selectedAssetReference();
}
}

File diff suppressed because it is too large Load Diff

View File

@ -3,9 +3,7 @@
"asset_uuid" : "b15b319f-5cab-4254-93ea-d83f4742d204",
"name" : "ui_atlas",
"type" : "tile_bank",
"inputs" : {
"sprites" : [ "sprites/confirm.png" ]
},
"inputs" : { },
"output" : {
"format" : "TILES/indexed_v1",
"codec" : "NONE",
@ -13,5 +11,9 @@
},
"preload" : {
"enabled" : false
}
},
"artifacts" : [ {
"file" : "confirm.png",
"index" : 0
} ]
}