implements PR-10c bank composition section shell and form session integration

This commit is contained in:
bQUARKz 2026-03-19 00:45:50 +00:00
parent bd753a8f1e
commit de8720abc3
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
6 changed files with 183 additions and 5 deletions

View File

@ -22,6 +22,8 @@ public abstract class StudioDualListView<T> extends HBox {
private final ObservableList<T> rightItems = FXCollections.observableArrayList();
private final ListView<T> leftListView = new ListView<>(leftItems);
private final ListView<T> rightListView = new ListView<>(rightItems);
private final Label leftTitleLabel = new Label("Available");
private final Label rightTitleLabel = new Label("Selected");
private final Button moveToRightButton = new Button(">");
private final Button moveToLeftButton = new Button("<");
private final Button moveUpButton = new Button("Up");
@ -41,8 +43,8 @@ public abstract class StudioDualListView<T> extends HBox {
leftListView.setCellFactory(ignored -> createCell(false));
rightListView.setCellFactory(ignored -> createCell(true));
final VBox leftColumn = createColumn("Available", leftListView);
final VBox rightColumn = createColumn("Selected", rightListView);
final VBox leftColumn = createColumn(leftTitleLabel, leftListView);
final VBox rightColumn = createColumn(rightTitleLabel, rightListView);
final VBox centerActions = new VBox(8, moveToRightButton, moveToLeftButton, moveUpButton, moveDownButton);
centerActions.setAlignment(Pos.CENTER);
centerActions.getStyleClass().add("studio-dual-list-actions");
@ -114,14 +116,21 @@ public abstract class StudioDualListView<T> extends HBox {
this.onMoveDown = Objects.requireNonNull(onMoveDown, "onMoveDown");
}
public void setLeftTitle(String title) {
leftTitleLabel.setText(Objects.requireNonNull(title, "title"));
}
public void setRightTitle(String title) {
rightTitleLabel.setText(Objects.requireNonNull(title, "title"));
}
protected abstract String itemText(T item);
protected Node itemGraphic(T item) {
return null;
}
private VBox createColumn(String title, ListView<T> listView) {
final Label titleLabel = new Label(title);
private VBox createColumn(Label titleLabel, ListView<T> listView) {
titleLabel.getStyleClass().add("studio-dual-list-title");
final VBox column = new VBox(8, titleLabel, listView);
column.getStyleClass().add("studio-dual-list-column");

View File

@ -96,6 +96,7 @@ public enum I18n {
ASSETS_BADGE_DIAGNOSTICS("assets.badge.diagnostics"),
ASSETS_SECTION_SUMMARY("assets.section.summary"),
ASSETS_SECTION_RUNTIME_CONTRACT("assets.section.runtimeContract"),
ASSETS_SECTION_BANK_COMPOSITION("assets.section.bankComposition"),
ASSETS_SUBSECTION_CODEC_CONFIGURATION("assets.subsection.codecConfiguration"),
ASSETS_SUBSECTION_METADATA("assets.subsection.metadata"),
ASSETS_SECTION_INPUTS_PREVIEW("assets.section.inputsPreview"),
@ -177,6 +178,10 @@ public enum I18n {
ASSETS_DETAILS_EMPTY("assets.details.empty"),
ASSETS_DETAILS_READY("assets.details.ready"),
ASSETS_DETAILS_NO_SELECTION("assets.details.noSelection"),
ASSETS_DETAILS_BANK_COMPOSITION_AVAILABLE("assets.details.bankComposition.available"),
ASSETS_DETAILS_BANK_COMPOSITION_SELECTED("assets.details.bankComposition.selected"),
ASSETS_DETAILS_BANK_COMPOSITION_READONLY_HINT("assets.details.bankComposition.readonlyHint"),
ASSETS_DETAILS_BANK_COMPOSITION_EDITING_HINT("assets.details.bankComposition.editingHint"),
ASSETS_DETAILS_CODEC_CONFIGURATION_EMPTY("assets.details.codecConfiguration.empty"),
ASSETS_DETAILS_METADATA_EMPTY("assets.details.metadata.empty"),
ASSETS_ADD_WIZARD_TITLE("assets.addWizard.title"),

View File

@ -15,6 +15,7 @@ import p.studio.Container;
import p.studio.events.StudioWorkspaceEventBus;
import p.studio.projects.ProjectReference;
import p.studio.utilities.i18n.I18n;
import p.studio.workspaces.assets.details.bank.AssetDetailsBankCompositionControl;
import p.studio.workspaces.assets.details.contract.AssetDetailsContractControl;
import p.studio.workspaces.assets.details.summary.AssetDetailsSummaryControl;
import p.studio.workspaces.assets.messages.AssetWorkspaceAssetAction;
@ -45,6 +46,7 @@ public final class AssetDetailsControl extends VBox implements StudioEventAware
private final ScrollPane detailsScroll = new ScrollPane();
private final AssetDetailsSummaryControl summaryControl;
private final AssetDetailsContractControl contractControl;
private final AssetDetailsBankCompositionControl bankCompositionControl;
private final VBox actionsContent = new VBox(10);
private final ScrollPane actionsScroll = new ScrollPane();
private final VBox actionsSection;
@ -63,6 +65,7 @@ public final class AssetDetailsControl extends VBox implements StudioEventAware
this.workspaceBus = Objects.requireNonNull(workspaceBus, "workspaceBus");
this.summaryControl = new AssetDetailsSummaryControl(projectReference, workspaceBus);
this.contractControl = new AssetDetailsContractControl(projectReference, workspaceBus);
this.bankCompositionControl = new AssetDetailsBankCompositionControl(projectReference, workspaceBus);
this.actionsSection = createActionsSection();
getStyleClass().add("assets-workspace-pane");
@ -224,7 +227,7 @@ public final class AssetDetailsControl extends VBox implements StudioEventAware
renderActions();
if (!readyMounted) {
readyMounted = true;
detailsContent.getChildren().setAll(primarySectionsRow, contractControl);
detailsContent.getChildren().setAll(primarySectionsRow, contractControl, bankCompositionControl);
}
syncActionsSectionHeight(summaryControl.getHeight());
}

View File

@ -0,0 +1,149 @@
package p.studio.workspaces.assets.details.bank;
import javafx.scene.layout.HBox;
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;
import java.util.Objects;
public final class AssetDetailsBankCompositionControl extends VBox implements StudioControlLifecycle {
private final StudioWorkspaceEventBus workspaceBus;
private final StudioSubscriptionBag subscriptions = new StudioSubscriptionBag();
private final StudioFormActionBar actionBar = new StudioFormActionBar(
this::beginEdit,
this::applyDraft,
this::resetDraft,
this::cancelEdit);
private final AssetDetailsBankCompositionDualListView dualListView = new AssetDetailsBankCompositionDualListView();
private final StudioAssetCapacityMeter capacityMeter = new StudioAssetCapacityMeter();
private AssetWorkspaceDetailsViewState viewState;
private StudioFormSession<AssetWorkspaceBankCompositionDetails> formSession;
public AssetDetailsBankCompositionControl(ProjectReference projectReference, StudioWorkspaceEventBus workspaceBus) {
StudioControlLifecycleSupport.install(this, this);
Objects.requireNonNull(projectReference, "projectReference");
this.workspaceBus = Objects.requireNonNull(workspaceBus, "workspaceBus");
}
@Override
public void subscribe() {
subscriptions.add(workspaceBus.subscribe(StudioAssetsDetailsViewStateChangedEvent.class, event -> {
viewState = event.viewState();
syncFormSession();
render();
}));
}
@Override
public void unsubscribe() {
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) {
getChildren().clear();
return;
}
final boolean editing = formSession.mode() == StudioFormMode.EDITING;
final AssetWorkspaceBankCompositionDetails draft = formSession.draft();
dualListView.setLeftItems(draft.availableFiles());
dualListView.setRightItems(draft.selectedFiles());
dualListView.setInteractionEnabled(false);
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));
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);
actionBar.updateState(formSession.mode(), formSession.isDirty());
content.getChildren().add(actionBar);
final VBox section = AssetDetailsUiSupport.createSection(
Container.i18n().text(I18n.ASSETS_SECTION_BANK_COMPOSITION),
content);
section.getStyleClass().add("assets-details-bank-composition-section");
getChildren().setAll(section);
}
private void beginEdit() {
if (formSession == null) {
return;
}
formSession.beginEdit();
render();
}
private void applyDraft() {
if (formSession == null) {
return;
}
formSession.apply();
render();
}
private void resetDraft() {
if (formSession == null) {
return;
}
formSession.resetDraft();
render();
}
private void cancelEdit() {
if (formSession == null) {
return;
}
formSession.cancelEdit();
render();
}
}

View File

@ -1,9 +1,16 @@
package p.studio.workspaces.assets.details.bank;
import p.studio.controls.banks.StudioDualListView;
import p.studio.Container;
import p.studio.utilities.i18n.I18n;
import p.studio.workspaces.assets.messages.AssetWorkspaceBankCompositionFile;
public final class AssetDetailsBankCompositionDualListView extends StudioDualListView<AssetWorkspaceBankCompositionFile> {
public AssetDetailsBankCompositionDualListView() {
setLeftTitle(Container.i18n().text(I18n.ASSETS_DETAILS_BANK_COMPOSITION_AVAILABLE));
setRightTitle(Container.i18n().text(I18n.ASSETS_DETAILS_BANK_COMPOSITION_SELECTED));
}
@Override
protected String itemText(AssetWorkspaceBankCompositionFile item) {
if (item.displayName().equals(item.path())) {

View File

@ -86,6 +86,7 @@ assets.badge.preload=Preload
assets.badge.diagnostics=Diagnostics
assets.section.summary=Summary
assets.section.runtimeContract=Runtime Contract
assets.section.bankComposition=Bank Composition
assets.subsection.codecConfiguration=Codec Configuration
assets.subsection.metadata=Metadata
assets.section.inputsPreview=Inputs / Preview
@ -168,6 +169,10 @@ assets.details.loading=Waiting for asset data...
assets.details.empty=Create or add assets to this project to start using the Assets workspace.
assets.details.ready=Selected asset: {0}\nState: {1}\nRoot: {2}
assets.details.noSelection=Select an asset from the navigator once assets are available.
assets.details.bankComposition.available=Available
assets.details.bankComposition.selected=Selected
assets.details.bankComposition.readonlyHint=Current bank composition state is shown read-only until editing begins.
assets.details.bankComposition.editingHint=Bank composition editing shell is active. Behavior wiring lands in the next slice.
assets.details.codecConfiguration.empty=This codec does not expose configuration fields yet.
assets.details.metadata.empty=This asset does not expose metadata fields yet.
assets.addWizard.title=Add Asset