update shell
This commit is contained in:
parent
0e6a79020e
commit
19396aec9e
@ -79,6 +79,7 @@ The exact event catalog may evolve incrementally.
|
||||
Illustrative baseline categories include:
|
||||
|
||||
- project lifecycle events;
|
||||
- project loading lifecycle events;
|
||||
- workspace selection events;
|
||||
- run and build intent events;
|
||||
- activity publication events;
|
||||
@ -86,6 +87,20 @@ Illustrative baseline categories include:
|
||||
- diagnostics update events;
|
||||
- asset selection events.
|
||||
|
||||
For project bootstrap and splash/loading integration, the Studio event catalog should reserve typed events for:
|
||||
|
||||
- project loading started;
|
||||
- project loading progress;
|
||||
- project loading completed;
|
||||
- project loading failed.
|
||||
|
||||
Project loading progress events should carry at least:
|
||||
|
||||
- the project identity,
|
||||
- a typed loading phase,
|
||||
- a user-facing status message,
|
||||
- and either determinate progress or an explicit indeterminate marker.
|
||||
|
||||
## Theme and i18n Compatibility
|
||||
|
||||
Shared Studio UI foundations must preserve:
|
||||
|
||||
22
prometeu-studio/src/main/java/p/studio/StudioAppInfo.java
Normal file
22
prometeu-studio/src/main/java/p/studio/StudioAppInfo.java
Normal file
@ -0,0 +1,22 @@
|
||||
package p.studio;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public final class StudioAppInfo {
|
||||
private static final String VERSION_PROPERTY = "prometeu.studio.version";
|
||||
private static final String FALLBACK_VERSION = "dev";
|
||||
|
||||
private StudioAppInfo() {
|
||||
}
|
||||
|
||||
public static String version() {
|
||||
final String explicit = System.getProperty(VERSION_PROPERTY);
|
||||
if (explicit != null && !explicit.isBlank()) {
|
||||
return explicit.trim();
|
||||
}
|
||||
|
||||
return Optional.ofNullable(StudioAppInfo.class.getPackage().getImplementationVersion())
|
||||
.filter(value -> !value.isBlank())
|
||||
.orElse(FALLBACK_VERSION);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
package p.studio.events;
|
||||
|
||||
import p.studio.projects.ProjectReference;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public record StudioProjectLoadingCompletedEvent(ProjectReference project) implements StudioEvent {
|
||||
public StudioProjectLoadingCompletedEvent {
|
||||
Objects.requireNonNull(project, "project");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
package p.studio.events;
|
||||
|
||||
import p.studio.projects.ProjectReference;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public record StudioProjectLoadingFailedEvent(
|
||||
ProjectReference project,
|
||||
StudioProjectLoadingPhase phase,
|
||||
String message) implements StudioEvent {
|
||||
|
||||
public StudioProjectLoadingFailedEvent {
|
||||
Objects.requireNonNull(project, "project");
|
||||
Objects.requireNonNull(phase, "phase");
|
||||
Objects.requireNonNull(message, "message");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
package p.studio.events;
|
||||
|
||||
public enum StudioProjectLoadingPhase {
|
||||
RESOLVING_PROJECT,
|
||||
RESTORING_WINDOW_STATE,
|
||||
RESTORING_WORKSPACES,
|
||||
INITIALIZING_SERVICES,
|
||||
READY
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
package p.studio.events;
|
||||
|
||||
import p.studio.projects.ProjectReference;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public record StudioProjectLoadingProgressEvent(
|
||||
ProjectReference project,
|
||||
StudioProjectLoadingPhase phase,
|
||||
String message,
|
||||
double progress,
|
||||
boolean indeterminate) implements StudioEvent {
|
||||
|
||||
public StudioProjectLoadingProgressEvent {
|
||||
Objects.requireNonNull(project, "project");
|
||||
Objects.requireNonNull(phase, "phase");
|
||||
Objects.requireNonNull(message, "message");
|
||||
if (!indeterminate && (progress < 0.0d || progress > 1.0d)) {
|
||||
throw new IllegalArgumentException("progress must be between 0.0 and 1.0 when determinate");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
package p.studio.events;
|
||||
|
||||
import p.studio.projects.ProjectReference;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public record StudioProjectLoadingStartedEvent(ProjectReference project) implements StudioEvent {
|
||||
public StudioProjectLoadingStartedEvent {
|
||||
Objects.requireNonNull(project, "project");
|
||||
}
|
||||
}
|
||||
@ -5,6 +5,16 @@ import lombok.Getter;
|
||||
public enum I18n {
|
||||
APP_TITLE("app.title"),
|
||||
APP_PROJECT_TITLE("app.projectTitle"),
|
||||
SHIELD_LOADING_PROJECT("shield.loadingProject"),
|
||||
SHIELD_VERSION("shield.version"),
|
||||
SHIELD_STATUS_INDEXING("shield.status.indexing"),
|
||||
SHIELD_STATUS_RESTORING("shield.status.restoring"),
|
||||
SHIELD_PHASE_INDEXING("shield.phase.indexing"),
|
||||
SHIELD_PHASE_PREPARING("shield.phase.preparing"),
|
||||
SHIELD_PHASE_RESTORING("shield.phase.restoring"),
|
||||
SHIELD_TIP_SHORTCUTS("shield.tip.shortcuts"),
|
||||
SHIELD_TIP_WORKSPACES("shield.tip.workspaces"),
|
||||
SHIELD_TIP_ACTIVITY("shield.tip.activity"),
|
||||
|
||||
MENU_FILE("menu.file"),
|
||||
MENU_FILE_NEWPROJECT("menu.file.newProject"),
|
||||
|
||||
@ -0,0 +1,112 @@
|
||||
package p.studio.window;
|
||||
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ProgressBar;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.util.Duration;
|
||||
import p.studio.Container;
|
||||
import p.studio.StudioAppInfo;
|
||||
import p.studio.projects.ProjectReference;
|
||||
import p.studio.utilities.i18n.I18n;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public final class ProjectLoadingShieldView extends VBox {
|
||||
public ProjectLoadingShieldView(ProjectReference projectReference) {
|
||||
Objects.requireNonNull(projectReference, "projectReference");
|
||||
|
||||
getStyleClass().add("studio-project-loading-shield");
|
||||
setPadding(new Insets(28));
|
||||
setSpacing(14);
|
||||
setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
final Label markLabel = new Label("P");
|
||||
markLabel.getStyleClass().add("studio-project-loading-mark");
|
||||
|
||||
final Label appTitle = new Label(Container.i18n().text(I18n.APP_TITLE));
|
||||
appTitle.getStyleClass().add("studio-project-loading-title");
|
||||
|
||||
final Label appVersion = new Label(Container.i18n().format(I18n.SHIELD_VERSION, StudioAppInfo.version()));
|
||||
appVersion.getStyleClass().add("studio-project-loading-version");
|
||||
|
||||
final VBox brandBlock = new VBox(4, appTitle, appVersion);
|
||||
final HBox brandRow = new HBox(14, buildMark(markLabel), brandBlock);
|
||||
brandRow.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
final Label projectLabel = new Label(projectReference.name());
|
||||
projectLabel.getStyleClass().add("studio-project-loading-project");
|
||||
|
||||
final Label statusLabel = new Label(Container.i18n().format(I18n.SHIELD_LOADING_PROJECT, projectReference.name()));
|
||||
statusLabel.getStyleClass().add("studio-project-loading-status");
|
||||
|
||||
final ProgressBar progressBar = new ProgressBar(0.18);
|
||||
progressBar.getStyleClass().add("studio-project-loading-progress");
|
||||
progressBar.setMaxWidth(Double.MAX_VALUE);
|
||||
|
||||
final HBox phaseRow = new HBox(
|
||||
8,
|
||||
phasePill(Container.i18n().text(I18n.SHIELD_PHASE_INDEXING), true),
|
||||
phasePill(Container.i18n().text(I18n.SHIELD_PHASE_PREPARING), false),
|
||||
phasePill(Container.i18n().text(I18n.SHIELD_PHASE_RESTORING), false));
|
||||
phaseRow.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
final Label tipLabel = new Label(Container.i18n().text(I18n.SHIELD_TIP_SHORTCUTS));
|
||||
tipLabel.getStyleClass().add("studio-project-loading-tip");
|
||||
tipLabel.setWrapText(true);
|
||||
|
||||
final Timeline timeline = new Timeline(
|
||||
new KeyFrame(Duration.ZERO, ignored -> {
|
||||
statusLabel.setText(Container.i18n().format(I18n.SHIELD_LOADING_PROJECT, projectReference.name()));
|
||||
tipLabel.setText(Container.i18n().text(I18n.SHIELD_TIP_SHORTCUTS));
|
||||
progressBar.setProgress(0.24);
|
||||
}),
|
||||
new KeyFrame(Duration.millis(500), ignored -> {
|
||||
statusLabel.setText(Container.i18n().text(I18n.SHIELD_STATUS_INDEXING));
|
||||
tipLabel.setText(Container.i18n().text(I18n.SHIELD_TIP_WORKSPACES));
|
||||
progressBar.setProgress(0.58);
|
||||
}),
|
||||
new KeyFrame(Duration.millis(1000), ignored -> {
|
||||
statusLabel.setText(Container.i18n().text(I18n.SHIELD_STATUS_RESTORING));
|
||||
tipLabel.setText(Container.i18n().text(I18n.SHIELD_TIP_ACTIVITY));
|
||||
progressBar.setProgress(0.86);
|
||||
}));
|
||||
timeline.setCycleCount(Timeline.INDEFINITE);
|
||||
timeline.play();
|
||||
|
||||
sceneProperty().addListener((ignored, oldScene, newScene) -> {
|
||||
if (newScene == null) {
|
||||
timeline.stop();
|
||||
}
|
||||
});
|
||||
|
||||
getChildren().addAll(
|
||||
brandRow,
|
||||
projectLabel,
|
||||
statusLabel,
|
||||
progressBar,
|
||||
phaseRow,
|
||||
tipLabel);
|
||||
}
|
||||
|
||||
private StackPane buildMark(Label markLabel) {
|
||||
final StackPane mark = new StackPane(markLabel);
|
||||
mark.getStyleClass().add("studio-project-loading-mark-shell");
|
||||
return mark;
|
||||
}
|
||||
|
||||
private Region phasePill(String text, boolean active) {
|
||||
final Label label = new Label(text);
|
||||
label.getStyleClass().add("studio-project-loading-phase");
|
||||
if (active) {
|
||||
label.getStyleClass().add("studio-project-loading-phase-active");
|
||||
}
|
||||
return label;
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,10 @@
|
||||
package p.studio.window;
|
||||
|
||||
import javafx.animation.PauseTransition;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.StageStyle;
|
||||
import javafx.util.Duration;
|
||||
import p.studio.Container;
|
||||
import p.studio.events.StudioProjectCreatedEvent;
|
||||
import p.studio.events.StudioProjectOpenedEvent;
|
||||
@ -16,8 +19,11 @@ import java.util.Objects;
|
||||
public final class StudioWindowCoordinator {
|
||||
private static final double LAUNCHER_WIDTH = 760;
|
||||
private static final double LAUNCHER_HEIGHT = 750;
|
||||
private static final double PROJECT_LOADING_WIDTH = 460;
|
||||
private static final double PROJECT_LOADING_HEIGHT = 220;
|
||||
private static final double PROJECT_WIDTH = 1280;
|
||||
private static final double PROJECT_HEIGHT = 840;
|
||||
private static final Duration PROJECT_OPEN_DELAY = Duration.seconds(1.5);
|
||||
|
||||
private final Stage launcherStage;
|
||||
private final ProjectCatalogService projectCatalogService;
|
||||
@ -83,6 +89,7 @@ public final class StudioWindowCoordinator {
|
||||
}
|
||||
|
||||
private void openProjectWindow(ProjectReference projectReference) {
|
||||
final Stage loadingStage = createProjectLoadingStage(projectReference);
|
||||
final Stage projectStage = new Stage();
|
||||
final Scene scene = new Scene(new MainView(projectReference), PROJECT_WIDTH, PROJECT_HEIGHT);
|
||||
scene.getStylesheets().add(Container.theme().getDefaultTheme());
|
||||
@ -98,8 +105,30 @@ public final class StudioWindowCoordinator {
|
||||
});
|
||||
|
||||
launcherStage.hide();
|
||||
projectStage.show();
|
||||
projectStage.toFront();
|
||||
loadingStage.show();
|
||||
loadingStage.centerOnScreen();
|
||||
loadingStage.toFront();
|
||||
final PauseTransition delay = new PauseTransition(PROJECT_OPEN_DELAY);
|
||||
delay.setOnFinished(ignored -> {
|
||||
loadingStage.close();
|
||||
projectStage.show();
|
||||
projectStage.toFront();
|
||||
});
|
||||
delay.play();
|
||||
}
|
||||
|
||||
private Stage createProjectLoadingStage(ProjectReference projectReference) {
|
||||
final Stage loadingStage = new Stage(StageStyle.UNDECORATED);
|
||||
loadingStage.initOwner(launcherStage);
|
||||
loadingStage.setResizable(false);
|
||||
|
||||
final Scene scene = new Scene(
|
||||
new ProjectLoadingShieldView(projectReference),
|
||||
PROJECT_LOADING_WIDTH,
|
||||
PROJECT_LOADING_HEIGHT);
|
||||
scene.getStylesheets().add(Container.theme().getDefaultTheme());
|
||||
loadingStage.setScene(scene);
|
||||
return loadingStage;
|
||||
}
|
||||
|
||||
private Path resolveDefaultProjectsRoot() {
|
||||
|
||||
@ -1,5 +1,15 @@
|
||||
app.title=Prometeu Studio
|
||||
app.projectTitle=Prometeu Studio - {0}
|
||||
shield.loadingProject=Loading project {0}...
|
||||
shield.version=Version {0}
|
||||
shield.status.indexing=Indexing sources and restoring services...
|
||||
shield.status.restoring=Restoring workspace surfaces and editor state...
|
||||
shield.phase.indexing=Indexing
|
||||
shield.phase.preparing=Preparing UI
|
||||
shield.phase.restoring=Restoring State
|
||||
shield.tip.shortcuts=Tip: the Studio shell will grow around workspace-first shortcuts and actions.
|
||||
shield.tip.workspaces=Tip: workspaces stay fixed on the left rail for fast switching.
|
||||
shield.tip.activity=Tip: global activity lives on the right; local logs stay in each workspace.
|
||||
|
||||
menu.file=File
|
||||
menu.file.newProject=New Project
|
||||
|
||||
@ -138,3 +138,70 @@
|
||||
.studio-launcher-feedback {
|
||||
-fx-text-fill: #ffb08f;
|
||||
}
|
||||
|
||||
.studio-project-loading-shield {
|
||||
-fx-background-color: linear-gradient(to bottom right, #102236, #0c1016);
|
||||
}
|
||||
|
||||
.studio-project-loading-mark-shell {
|
||||
-fx-background-color: linear-gradient(to bottom right, #4aa3ff, #1f5e97);
|
||||
-fx-background-radius: 16;
|
||||
-fx-min-width: 46;
|
||||
-fx-min-height: 46;
|
||||
-fx-pref-width: 46;
|
||||
-fx-pref-height: 46;
|
||||
-fx-alignment: center;
|
||||
}
|
||||
|
||||
.studio-project-loading-mark {
|
||||
-fx-text-fill: #ffffff;
|
||||
-fx-font-size: 24px;
|
||||
-fx-font-weight: bold;
|
||||
}
|
||||
|
||||
.studio-project-loading-title {
|
||||
-fx-text-fill: #f7fbff;
|
||||
-fx-font-size: 28px;
|
||||
-fx-font-weight: bold;
|
||||
}
|
||||
|
||||
.studio-project-loading-version {
|
||||
-fx-text-fill: #9fc4ea;
|
||||
-fx-font-size: 13px;
|
||||
}
|
||||
|
||||
.studio-project-loading-project {
|
||||
-fx-text-fill: #ffffff;
|
||||
-fx-font-size: 20px;
|
||||
-fx-font-weight: bold;
|
||||
-fx-padding: 12 0 0 0;
|
||||
}
|
||||
|
||||
.studio-project-loading-status {
|
||||
-fx-text-fill: #d9e7f5;
|
||||
-fx-font-size: 14px;
|
||||
}
|
||||
|
||||
.studio-project-loading-progress {
|
||||
-fx-accent: #5cb6ff;
|
||||
-fx-pref-height: 10px;
|
||||
-fx-max-width: 380px;
|
||||
}
|
||||
|
||||
.studio-project-loading-phase {
|
||||
-fx-background-color: rgba(255,255,255,0.08);
|
||||
-fx-background-radius: 999;
|
||||
-fx-text-fill: #d6e3f2;
|
||||
-fx-font-size: 11px;
|
||||
-fx-padding: 5 10 5 10;
|
||||
}
|
||||
|
||||
.studio-project-loading-phase-active {
|
||||
-fx-background-color: #2c6ba5;
|
||||
-fx-text-fill: #ffffff;
|
||||
}
|
||||
|
||||
.studio-project-loading-tip {
|
||||
-fx-text-fill: #b9cae0;
|
||||
-fx-font-size: 12px;
|
||||
}
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
package p.studio.events;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import p.studio.projects.ProjectReference;
|
||||
import p.studio.workspaces.WorkspaceId;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
@ -41,6 +43,25 @@ final class StudioWorkspaceEventBusTest {
|
||||
assertEquals(List.of("global-only"), globalReceived);
|
||||
}
|
||||
|
||||
@Test
|
||||
void projectLoadingProgressEventsCanFlowThroughTheStudioBus() {
|
||||
final StudioEventBus globalBus = new StudioEventBus();
|
||||
final StudioWorkspaceEventBus workspaceBus = new StudioWorkspaceEventBus(WorkspaceId.ASSETS, globalBus);
|
||||
final List<StudioProjectLoadingPhase> globalReceived = new CopyOnWriteArrayList<>();
|
||||
final ProjectReference project = new ProjectReference("Main", "1.0.0", "pbs", 1, Path.of("/tmp/main"));
|
||||
|
||||
globalBus.subscribe(StudioProjectLoadingProgressEvent.class, event -> globalReceived.add(event.phase()));
|
||||
|
||||
workspaceBus.publish(new StudioProjectLoadingProgressEvent(
|
||||
project,
|
||||
StudioProjectLoadingPhase.INITIALIZING_SERVICES,
|
||||
"Initializing services",
|
||||
0.5d,
|
||||
false));
|
||||
|
||||
assertEquals(List.of(StudioProjectLoadingPhase.INITIALIZING_SERVICES), globalReceived);
|
||||
}
|
||||
|
||||
private record TestStudioEvent(String name) implements StudioEvent {
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user