update shell

This commit is contained in:
bQUARKz 2026-03-11 14:01:46 +00:00
parent 4e27389df7
commit f3eb114359
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
21 changed files with 348 additions and 144 deletions

View File

@ -1,7 +0,0 @@
# Prometeu Studio Components
This module is the future home of reusable JavaFX components for `prometeu-studio`.
It is intentionally empty for now.
Components should only be added here when they are needed by the current Studio UI wave.

View File

@ -1,13 +0,0 @@
plugins {
id("gradle.java-library-conventions")
alias(libs.plugins.javafx)
}
dependencies {
api(libs.javafx.controls)
}
javafx {
version = libs.versions.javafx.get()
modules("javafx.controls")
}

View File

@ -0,0 +1,9 @@
package p.studio.controls.lifecycle;
public interface StudioControlLifecycle {
default void subscribe() {
}
default void unsubscribe() {
}
}

View File

@ -0,0 +1,38 @@
package p.studio.controls.lifecycle;
import javafx.scene.Node;
import javafx.scene.Scene;
import java.util.Objects;
public final class StudioControlLifecycleSupport {
private StudioControlLifecycleSupport() {
}
public static void install(Node node, StudioControlLifecycle lifecycle) {
Objects.requireNonNull(node, "node");
Objects.requireNonNull(lifecycle, "lifecycle");
node.sceneProperty().addListener((ignored, oldScene, newScene) -> {
if (oldScene == null && newScene != null) {
lifecycle.subscribe();
return;
}
if (oldScene != null && newScene == null) {
lifecycle.unsubscribe();
return;
}
if (oldScene != null && oldScene != newScene) {
lifecycle.unsubscribe();
lifecycle.subscribe();
}
});
final Scene currentScene = node.getScene();
if (currentScene != null) {
lifecycle.subscribe();
}
}
}

View File

@ -0,0 +1,38 @@
package p.studio.controls.shell;
import javafx.beans.value.ObservableValue;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.layout.BorderPane;
import p.studio.controls.lifecycle.StudioControlLifecycle;
import p.studio.controls.lifecycle.StudioControlLifecycleSupport;
import java.util.Objects;
public final class StudioRightUtilityPanelControl extends BorderPane implements StudioControlLifecycle {
public StudioRightUtilityPanelControl(
ObservableValue<String> activityTitle,
Node activityContent) {
StudioControlLifecycleSupport.install(this, this);
getStyleClass().add("studio-right-utility-panel");
setPrefWidth(280);
final Tab activityTab = new Tab();
activityTab.textProperty().bind(Objects.requireNonNull(activityTitle, "activityTitle"));
activityTab.setClosable(false);
activityTab.setContent(Objects.requireNonNull(activityContent, "activityContent"));
final TabPane tabs = new TabPane(activityTab);
tabs.getStyleClass().add("studio-right-utility-tabs");
setCenter(tabs);
}
public static Label createPlaceholderContent(ObservableValue<String> text) {
final Label label = new Label();
label.textProperty().bind(Objects.requireNonNull(text, "text"));
label.getStyleClass().add("studio-utility-placeholder");
return label;
}
}

View File

@ -0,0 +1,34 @@
package p.studio.controls.shell;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import p.studio.Container;
import p.studio.controls.lifecycle.StudioControlLifecycle;
import p.studio.controls.lifecycle.StudioControlLifecycleSupport;
import p.studio.utilities.i18n.I18n;
import java.util.Objects;
public final class StudioRunSurfaceControl extends HBox implements StudioControlLifecycle {
public StudioRunSurfaceControl() {
StudioControlLifecycleSupport.install(this, this);
getStyleClass().add("studio-run-surface");
setSpacing(8);
setAlignment(Pos.CENTER_RIGHT);
getChildren().addAll(
createButton(Container.i18n().bind(I18n.TOOLBAR_PLAY), () -> { }),
createButton(Container.i18n().bind(I18n.TOOLBAR_STOP), () -> { }),
createButton(Container.i18n().bind(I18n.TOOLBAR_EXPORT), () -> { }));
}
private Button createButton(ObservableValue<String> text, Runnable action) {
final Button button = new Button();
button.getStyleClass().add("studio-run-button");
button.textProperty().bind(Objects.requireNonNull(text, "text"));
button.setOnAction(ignored -> Objects.requireNonNull(action, "action").run());
return button;
}
}

View File

@ -0,0 +1,43 @@
package p.studio.controls.shell;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuItem;
import p.studio.Container;
import p.studio.controls.lifecycle.StudioControlLifecycle;
import p.studio.controls.lifecycle.StudioControlLifecycleSupport;
import p.studio.utilities.i18n.I18n;
public final class StudioShellMenuBarControl extends javafx.scene.control.MenuBar implements StudioControlLifecycle {
public StudioShellMenuBarControl() {
StudioControlLifecycleSupport.install(this, this);
getStyleClass().add("studio-shell-menu-bar");
final Menu fileMenu = new Menu();
fileMenu.textProperty().bind(Container.i18n().bind(I18n.MENU_FILE));
final MenuItem newProjectItem = new MenuItem();
newProjectItem.textProperty().bind(Container.i18n().bind(I18n.MENU_FILE_NEWPROJECT));
newProjectItem.setOnAction(ignored -> { });
final MenuItem openItem = new MenuItem();
openItem.textProperty().bind(Container.i18n().bind(I18n.MENU_FILE_OPEN));
openItem.setOnAction(ignored -> { });
final MenuItem saveItem = new MenuItem();
saveItem.textProperty().bind(Container.i18n().bind(I18n.MENU_FILE_SAVE));
saveItem.setOnAction(ignored -> { });
fileMenu.getItems().addAll(newProjectItem, openItem, saveItem);
final Menu editMenu = new Menu();
editMenu.textProperty().bind(Container.i18n().bind(I18n.MENU_EDIT));
final Menu viewMenu = new Menu();
viewMenu.textProperty().bind(Container.i18n().bind(I18n.MENU_VIEW));
final Menu helpMenu = new Menu();
helpMenu.textProperty().bind(Container.i18n().bind(I18n.MENU_HELP));
getMenus().setAll(fileMenu, editMenu, viewMenu, helpMenu);
}
}

View File

@ -0,0 +1,17 @@
package p.studio.controls.shell;
import javafx.scene.Node;
import javafx.scene.layout.BorderPane;
import p.studio.controls.lifecycle.StudioControlLifecycle;
import p.studio.controls.lifecycle.StudioControlLifecycleSupport;
import java.util.Objects;
public final class StudioShellTopBarControl extends BorderPane implements StudioControlLifecycle {
public StudioShellTopBarControl(Node menuBar, Node runSurface) {
StudioControlLifecycleSupport.install(this, this);
getStyleClass().add("studio-shell-top-bar");
setLeft(Objects.requireNonNull(menuBar, "menuBar"));
setRight(Objects.requireNonNull(runSurface, "runSurface"));
}
}

View File

@ -0,0 +1,55 @@
package p.studio.controls.shell;
import javafx.geometry.Insets;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.ToggleGroup;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.VBox;
import p.studio.controls.lifecycle.StudioControlLifecycle;
import p.studio.controls.lifecycle.StudioControlLifecycleSupport;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
public final class StudioWorkspaceRailControl<T> extends VBox implements StudioControlLifecycle {
private final Map<T, ToggleButton> buttons;
private final ToggleGroup toggleGroup = new ToggleGroup();
public StudioWorkspaceRailControl(
Collection<StudioWorkspaceRailItem<T>> items,
Consumer<T> onSelect) {
StudioControlLifecycleSupport.install(this, this);
getStyleClass().add("studio-workspace-rail");
setPadding(new Insets(8));
setSpacing(8);
setPrefWidth(64);
this.buttons = new HashMap<>();
for (StudioWorkspaceRailItem<T> item : items) {
final ToggleButton button = new ToggleButton(item.icon());
button.setToggleGroup(toggleGroup);
button.setFocusTraversable(false);
button.setPrefSize(44, 44);
button.setMinSize(44, 44);
button.setMaxSize(44, 44);
button.getStyleClass().add("studio-workspace-rail-button");
button.setTooltip(new Tooltip());
button.getTooltip().textProperty().bind(item.label());
button.setOnAction(ignored -> Objects.requireNonNull(onSelect, "onSelect").accept(item.id()));
buttons.put(item.id(), button);
getChildren().add(button);
}
}
public void select(T id) {
final ToggleButton button = buttons.get(id);
if (button != null) {
button.setSelected(true);
}
}
}

View File

@ -0,0 +1,9 @@
package p.studio.controls.shell;
import javafx.beans.value.ObservableValue;
public record StudioWorkspaceRailItem<T>(
T id,
String icon,
ObservableValue<String> label) {
}

View File

@ -14,16 +14,18 @@ public enum I18n {
MENU_VIEW("menu.view"),
MENU_HELP("menu.help"),
SHELL_ACTIVITY("shell.activity"),
TOOLBAR_PLAY("toolbar.play"),
TOOLBAR_STOP("toolbar.stop"),
TOOLBAR_EXPORT("toolbar.export"),
WORKSPACE_CODE("workspace.code"),
WORKSPACE_BUILDER("workspace.builder"),
WORKSPACE_BUILDER_LOGS("workspace.builder.logs"),
WORKSPACE_BUILDER_BUTTON_RUN("workspace.builder.button.run"),
WORKSPACE_BUILDER_BUTTON_CLEAR("workspace.builder.button.clear"),
WORKSPACE_SHIPPER("workspace.shipper"),
WORKSPACE_SHIPPER_LOGS("workspace.shipper.logs"),
WORKSPACE_SHIPPER_BUTTON_RUN("workspace.shipper.button.run"),
WORKSPACE_SHIPPER_BUTTON_CLEAR("workspace.shipper.button.clear"),
WORKSPACE_ASSETS("workspace.assets"),
WORKSPACE_DEBUG("workspace.debug"),

View File

@ -1,30 +1,52 @@
package p.studio.window;
import javafx.scene.layout.BorderPane;
import p.studio.Container;
import p.studio.controls.shell.*;
import p.studio.events.StudioWorkspaceSelectedEvent;
import p.studio.utilities.i18n.I18n;
import p.studio.workspaces.PlaceholderWorkspace;
import p.studio.workspaces.WorkspaceHost;
import p.studio.workspaces.WorkspaceId;
import p.studio.workspaces.builder.BuilderWorkspace;
import p.studio.workspaces.editor.EditorWorkspace;
import java.util.List;
public final class MainView extends BorderPane {
private static final WorkspaceHost HOST = new WorkspaceHost();
public MainView() {
var menubar = new MenuBar();
setTop(menubar);
final var menuBar = new StudioShellMenuBarControl();
final var runSurface = new StudioRunSurfaceControl();
setTop(new StudioShellTopBarControl(menuBar, runSurface));
HOST.register(new EditorWorkspace());
HOST.register(new PlaceholderWorkspace(WorkspaceId.ASSETS, "Assets"));
HOST.register(new PlaceholderWorkspace(WorkspaceId.ASSETS, I18n.WORKSPACE_ASSETS, "Assets"));
HOST.register(new BuilderWorkspace());
HOST.register(new PlaceholderWorkspace(WorkspaceId.DEVICE, "Device"));
HOST.register(new PlaceholderWorkspace(WorkspaceId.DEBUG, I18n.WORKSPACE_DEBUG, "Debug"));
var bar = new WorkspaceBar(HOST::show);
setLeft(bar);
final var workspaceRail = new StudioWorkspaceRailControl<>(
List.of(
new StudioWorkspaceRailItem<>(WorkspaceId.EDITOR, "📝", Container.i18n().bind(I18n.WORKSPACE_CODE)),
new StudioWorkspaceRailItem<>(WorkspaceId.ASSETS, "📦", Container.i18n().bind(I18n.WORKSPACE_ASSETS)),
new StudioWorkspaceRailItem<>(WorkspaceId.DEBUG, "🎮", Container.i18n().bind(I18n.WORKSPACE_DEBUG)),
new StudioWorkspaceRailItem<>(WorkspaceId.SHIPPER, "⚙️", Container.i18n().bind(I18n.WORKSPACE_SHIPPER))
),
this::showWorkspace);
setLeft(workspaceRail);
setCenter(HOST);
setRight(new StudioRightUtilityPanelControl(
Container.i18n().bind(I18n.SHELL_ACTIVITY),
StudioRightUtilityPanelControl.createPlaceholderContent(Container.i18n().bind(I18n.SHELL_ACTIVITY))));
// default
bar.select(WorkspaceId.EDITOR);
HOST.show(WorkspaceId.EDITOR);
workspaceRail.select(WorkspaceId.EDITOR);
showWorkspace(WorkspaceId.EDITOR);
}
private void showWorkspace(WorkspaceId workspaceId) {
HOST.show(workspaceId);
Container.events().publish(new StudioWorkspaceSelectedEvent(workspaceId));
}
}

View File

@ -1,36 +0,0 @@
package p.studio.window;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuItem;
import p.studio.Container;
import p.studio.utilities.i18n.I18n;
public final class MenuBar extends javafx.scene.control.MenuBar {
public MenuBar() {
Menu file = new Menu();
file.textProperty().bind(Container.i18n().bind(I18n.MENU_FILE));
MenuItem newProject = new MenuItem();
newProject.textProperty().bind(Container.i18n().bind(I18n.MENU_FILE_NEWPROJECT));
MenuItem open = new MenuItem();
open.textProperty().bind(Container.i18n().bind(I18n.MENU_FILE_OPEN));
MenuItem save = new MenuItem();
save.textProperty().bind(Container.i18n().bind(I18n.MENU_FILE_SAVE));
file.getItems().addAll(newProject, open, save);
Menu edit = new Menu();
edit.textProperty().bind(Container.i18n().bind(I18n.MENU_EDIT));
Menu view = new Menu();
view.textProperty().bind(Container.i18n().bind(I18n.MENU_VIEW));
Menu help = new Menu();
help.textProperty().bind(Container.i18n().bind(I18n.MENU_HELP));
getMenus().addAll(file, edit, view, help);
}
}

View File

@ -1,47 +0,0 @@
package p.studio.window;
import javafx.geometry.Insets;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.VBox;
import p.studio.workspaces.WorkspaceId;
import java.util.EnumMap;
import java.util.Map;
import java.util.function.Consumer;
public final class WorkspaceBar extends VBox {
private final ToggleGroup group = new ToggleGroup();
private final Map<WorkspaceId, ToggleButton> buttons = new EnumMap<>(WorkspaceId.class);
public WorkspaceBar(Consumer<WorkspaceId> onSelect) {
setPadding(new Insets(8));
setSpacing(8);
setPrefWidth(56);
addBtn(WorkspaceId.EDITOR, "📝", "Editor", onSelect);
addBtn(WorkspaceId.ASSETS, "📦", "Assets", onSelect);
addBtn(WorkspaceId.BUILDER, "⚙️", "Builder", onSelect);
addBtn(WorkspaceId.DEVICE, "🎮", "Device", onSelect);
}
private void addBtn(WorkspaceId id, String icon, String tooltip, Consumer<WorkspaceId> onSelect) {
ToggleButton b = new ToggleButton(icon);
b.setToggleGroup(group);
b.setFocusTraversable(false);
b.setPrefSize(40, 40);
b.setMinSize(40, 40);
b.setMaxSize(40, 40);
b.setUserData(id);
b.setOnAction(e -> onSelect.accept(id));
b.setStyle("-fx-font-size: 16px;");
buttons.put(id, b);
getChildren().add(b);
}
public void select(WorkspaceId id) {
ToggleButton b = buttons.get(id);
if (b != null) b.setSelected(true);
}
}

View File

@ -7,14 +7,16 @@ import p.studio.utilities.i18n.I18n;
public final class PlaceholderWorkspace implements Workspace {
private final WorkspaceId id;
private final I18n title;
private final StackPane root = new StackPane();
public PlaceholderWorkspace(WorkspaceId id, String label) {
public PlaceholderWorkspace(WorkspaceId id, I18n title, String label) {
this.id = id;
this.title = title;
root.getChildren().add(new Label(label + " (TODO)"));
}
@Override public WorkspaceId id() { return id; }
@Override public I18n title() { return I18n.WORKSPACE_ASSETS; }
@Override public I18n title() { return title; }
@Override public Node root() { return root; }
}

View File

@ -3,6 +3,6 @@ package p.studio.workspaces;
public enum WorkspaceId {
EDITOR,
ASSETS,
BUILDER,
DEVICE
}
SHIPPER,
DEBUG
}

View File

@ -21,12 +21,12 @@ public class BuilderWorkspace implements Workspace {
@Override
public WorkspaceId id() {
return WorkspaceId.BUILDER;
return WorkspaceId.SHIPPER;
}
@Override
public I18n title() {
return I18n.WORKSPACE_BUILDER;
return I18n.WORKSPACE_SHIPPER;
}
@Override
@ -49,7 +49,7 @@ public class BuilderWorkspace implements Workspace {
}
private ToolBar buildToolBar() {
buildButton.textProperty().bind(Container.i18n().bind(I18n.WORKSPACE_BUILDER_BUTTON_RUN));
buildButton.textProperty().bind(Container.i18n().bind(I18n.WORKSPACE_SHIPPER_BUTTON_RUN));
buildButton.setOnAction(e -> {
logs.clear();
final var logAggregator = LogAggregator.with(logs::appendText);
@ -57,7 +57,7 @@ public class BuilderWorkspace implements Workspace {
BuilderPipelineService.INSTANCE.run(config, logAggregator);
});
clearButton.textProperty().bind(Container.i18n().bind(I18n.WORKSPACE_BUILDER_BUTTON_CLEAR));
clearButton.textProperty().bind(Container.i18n().bind(I18n.WORKSPACE_SHIPPER_BUTTON_CLEAR));
clearButton.setOnAction(e -> logs.clear());
return new ToolBar(buildButton, clearButton);
@ -75,7 +75,7 @@ public class BuilderWorkspace implements Workspace {
final var pane = new TitledPane();
pane.setContent(logs);
pane.textProperty().bind(Container.i18n().bind(I18n.WORKSPACE_BUILDER_LOGS));
pane.textProperty().bind(Container.i18n().bind(I18n.WORKSPACE_SHIPPER_LOGS));
pane.setCollapsible(true);
pane.setExpanded(true);

View File

@ -8,14 +8,16 @@ menu.edit=Edit
menu.view=View
menu.help=Help
shell.activity=Activity
toolbar.play=Play
toolbar.stop=Stop
toolbar.export=Export
workspace.code=Code
workspace.builder=Builder
workspace.builder.logs=Logs
workspace.builder.button.run=Build
workspace.builder.button.clear=Clear
workspace.shipper=Shipper
workspace.shipper.logs=Logs
workspace.shipper.button.run=Build
workspace.shipper.button.clear=Clear
workspace.assets=Assets
workspace.debug=Debug
workspace.debug=Debug

View File

@ -13,10 +13,10 @@ toolbar.stop=Parar
toolbar.export=Exportar
workspace.code=Código
workspace.builder=Construtor
workspace.builder.logs=Logs
workspace.builder.button.run=Construir
workspace.builder.button.clear=Limpar
workspace.shipper=Empacotador
workspace.shipper.logs=Logs
workspace.shipper.button.run=Construir
workspace.shipper.button.clear=Limpar
workspace.assets=Assets
workspace.debug=Depurar

View File

@ -4,13 +4,14 @@
-fx-background: #1e1e1e;
}
.main-toolbar {
.studio-shell-top-bar {
-fx-background-color: #252526;
-fx-border-color: #2d2d2d;
-fx-border-width: 0 0 1 0;
-fx-padding: 0 12 0 0;
}
.toolbar-button {
.studio-run-button {
-fx-background-color: transparent;
-fx-text-fill: #d4d4d4;
-fx-font-size: 14px;
@ -18,19 +19,55 @@
-fx-background-radius: 6;
}
.toolbar-button:hover {
.studio-run-button:hover {
-fx-background-color: #2a2d2e;
}
.toolbar-button:pressed {
.studio-run-button:pressed {
-fx-background-color: #37373d;
}
.toolbar-button.accent {
-fx-background-color: #0e639c;
-fx-text-fill: white;
.studio-run-surface {
-fx-alignment: center-right;
-fx-padding: 6 0 6 0;
}
.toolbar-button.accent:hover {
-fx-background-color: #1177bb;
.studio-shell-menu-bar {
-fx-background-color: transparent;
}
.studio-workspace-rail {
-fx-background-color: #252526;
-fx-border-color: #2d2d2d;
-fx-border-width: 0 1 0 0;
}
.studio-workspace-rail-button {
-fx-font-size: 16px;
-fx-background-color: transparent;
-fx-text-fill: #d4d4d4;
-fx-background-radius: 8;
}
.studio-workspace-rail-button:hover {
-fx-background-color: #2a2d2e;
}
.studio-workspace-rail-button:selected {
-fx-background-color: #37373d;
}
.studio-right-utility-panel {
-fx-background-color: #1f1f1f;
-fx-border-color: #2d2d2d;
-fx-border-width: 0 0 0 1;
}
.studio-right-utility-tabs {
-fx-background-color: transparent;
}
.studio-utility-placeholder {
-fx-text-fill: #d4d4d4;
-fx-padding: 16;
}

View File

@ -5,7 +5,6 @@ plugins {
rootProject.name = "prometeu-studio"
include("prometeu-infra")
include("prometeu-studio-components")
include("prometeu-compiler:frontends:prometeu-frontend-pbs")
include("prometeu-compiler:prometeu-compiler-core")