From f2944c9a9153b35cbdca336ee3bbcb2048a37296 Mon Sep 17 00:00:00 2001 From: Mario Date: Fri, 14 Aug 2026 21:37:57 +0200 Subject: [PATCH] =?UTF-8?q?Umbau=20und=20Konsolidierung=20der=20Tools=20un?= =?UTF-8?q?d=20der=20Oberfl=C3=A4che=20abgeschlossen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../recipes/neues_rezept_1786731383674.recipe | 3 + .../main/java/de/blight/editor/EditorApp.java | 679 ++++++++++++++++-- .../java/de/blight/editor/JmeEditorApp.java | 11 + .../de/blight/editor/ui/AreaEditorView.java | 76 +- .../editor/ui/CraftingTableEditorView.java | 75 +- .../blight/editor/ui/FractionEditorView.java | 84 +-- .../de/blight/editor/ui/ItemEditorView.java | 96 +-- .../blight/editor/ui/LocationEditorView.java | 78 +- .../de/blight/editor/ui/QuestEditorView.java | 85 +-- .../de/blight/editor/ui/RecipeEditorView.java | 99 +-- 10 files changed, 799 insertions(+), 487 deletions(-) create mode 100644 blight-assets/src/main/resources/recipes/neues_rezept_1786731383674.recipe diff --git a/blight-assets/src/main/resources/recipes/neues_rezept_1786731383674.recipe b/blight-assets/src/main/resources/recipes/neues_rezept_1786731383674.recipe new file mode 100644 index 0000000..aafe76b --- /dev/null +++ b/blight-assets/src/main/resources/recipes/neues_rezept_1786731383674.recipe @@ -0,0 +1,3 @@ +{ + "createsItemId": "neues_rezept_1786731383674" +} \ No newline at end of file diff --git a/blight-editor/src/main/java/de/blight/editor/EditorApp.java b/blight-editor/src/main/java/de/blight/editor/EditorApp.java index f49a96f..ee520ce 100644 --- a/blight-editor/src/main/java/de/blight/editor/EditorApp.java +++ b/blight-editor/src/main/java/de/blight/editor/EditorApp.java @@ -102,6 +102,25 @@ public class EditorApp extends Application { // Content-Baum (Instanzfeld, damit wir Knoten gezielt expandieren/befüllen können) private javafx.scene.control.TreeView contentTreeView; private javafx.scene.control.TreeItem contentCharNode; + private javafx.scene.control.TreeItem contentQuestNode; + private javafx.scene.control.TreeItem contentItemNode; + private javafx.scene.control.TreeItem contentFractionNode; + private javafx.scene.control.TreeItem contentAreaNode; + private javafx.scene.control.TreeItem contentLocationNode; + private javafx.scene.control.TreeItem contentCraftingNode; + private javafx.scene.control.TreeItem contentRecipeNode; + // aktive Editor-View-Referenzen (für Baum-Selektion und Neu/Löschen) + private de.blight.editor.ui.QuestEditorView activeQuestView; + private de.blight.editor.ui.ItemEditorView activeItemView; + private de.blight.editor.ui.FractionEditorView activeFractionView; + private de.blight.editor.ui.AreaEditorView activeAreaView; + private de.blight.editor.ui.LocationEditorView activeLocationView; + private de.blight.editor.ui.CraftingTableEditorView activeCraftingView; + private de.blight.editor.ui.RecipeEditorView activeRecipeView; + private Button contentNewBtn; + private Button contentDeleteBtn; + private javafx.scene.Node worldBottomBar; + private boolean contentTreeUpdating = false; private ImageView treePreviewView; // aktualisiert wenn JME3 Bild neu erstellt private StackPane plantPreviewPanel; // einmaliges Vorschau-Panel für alle Generatoren @@ -183,7 +202,6 @@ public class EditorApp extends Application { private String animSetPendingPlayClip = null; private ComboBox animSetModelCombo; private boolean animSetDirty = false; - private boolean charDirty = false; private String animSetCurrentName = null; private Path animSetCurrentDir = null; // Anim-Offset-Editor (innerhalb AnimSet-Editor) @@ -442,7 +460,8 @@ public class EditorApp extends Application { setupAssetOverlay(); root.setCenter(centerStack); root.setRight(toolPanel); - root.setBottom(buildBottomBox()); + worldBottomBar = buildBottomBox(); + root.setBottom(worldBottomBar); Scene scene = new Scene(root, 1280, 760); java.net.URL cssUrl = getClass().getResource("/editor.css"); @@ -517,17 +536,6 @@ public class EditorApp extends Application { stage.setMinWidth(900); stage.setMinHeight(600); stage.setOnCloseRequest(e -> { - if (animSetDirty || charDirty) { - Alert confirm = new Alert(Alert.AlertType.CONFIRMATION, - "Es gibt ungespeicherte Änderungen.\nTrotzdem beenden?", - ButtonType.YES, ButtonType.NO); - confirm.initOwner(stage); - confirm.setHeaderText("Ungespeicherte Änderungen"); - confirm.showAndWait().ifPresent(btn -> { - if (btn != ButtonType.YES) e.consume(); - }); - if (e.isConsumed()) return; - } saveCameraPrefs(); if (jmeApp != null) jmeApp.stop(); Platform.exit(); @@ -940,6 +948,7 @@ public class EditorApp extends Application { boolean wasObjEditor = "objecteditor".equals(currentTool); boolean wasModelEditor = "modeleditor".equals(currentTool); currentTool = "world"; + if (jmeApp != null) jmeApp.setRenderThrottled(false); root.setLeft(null); // ggf. gesetzten Content-Tree entfernen topBar.getChildren().set(1, worldToolBar); if (routineJmeSlot != null) { @@ -954,6 +963,7 @@ public class EditorApp extends Application { setCenterView(worldViewport); } root.setRight(toolPanel); + root.setBottom(worldBottomBar); if (wasObjEditor && (input.activeLayer == SharedInput.LAYER_OBJECTS || input.activeLayer == SharedInput.LAYER_OBJECTS_EDIT)) { input.activeLayer = 0; @@ -11599,7 +11609,6 @@ public class EditorApp extends Application { else routineEditorView.loadNpc(new de.blight.common.model.NPC()); } if (charEditorStatusLabel != null) charEditorStatusLabel.setText("Geladen: " + id); - charDirty = true; } catch (Exception e) { if (charEditorStatusLabel != null) charEditorStatusLabel.setText("Fehler: " + e.getMessage()); } @@ -11663,7 +11672,6 @@ public class EditorApp extends Application { try { de.blight.common.model.CharacterIO.save(c, charDir); - charDirty = false; refreshCharacterList(); if (charEditorStatusLabel != null) charEditorStatusLabel.setText("Gespeichert: " + id); } catch (Exception e) { @@ -11909,7 +11917,7 @@ public class EditorApp extends Application { contentEditorRoot = new BorderPane(); contentEditorRoot.setTop(buildContentTopBar()); - contentEditorRoot.setLeft(buildContentTree()); + contentEditorRoot.setLeft(buildContentTreePanel()); contentEditorRoot.setCenter(new Label("Wähle einen Bereich in der Toolbar.")); Scene scene = new Scene(contentEditorRoot, 1200, 750); @@ -11947,8 +11955,10 @@ public class EditorApp extends Application { /** Wechselt im 1-Fenster-Modus zur Content-Editor-Ansicht in der Welt-Stage. */ private void switchToContentEditor() { currentTool = "content"; + if (jmeApp != null) jmeApp.setRenderThrottled(true); topBar.getChildren().set(1, buildSingleWindowContentToolBar()); - root.setLeft(buildContentTree()); + root.setLeft(buildContentTreePanel()); + root.setBottom(null); restoreContentSection(contentLastSection); } @@ -12036,59 +12046,322 @@ public class EditorApp extends Application { new javafx.scene.control.TreeItem<>("Content"); treeRoot.setExpanded(true); - contentCharNode = new javafx.scene.control.TreeItem<>("Characters"); - contentCharNode.setExpanded(false); - refreshContentCharacterTree(); + contentCharNode = new javafx.scene.control.TreeItem<>("Characters"); + contentQuestNode = new javafx.scene.control.TreeItem<>("Quests"); + contentFractionNode = new javafx.scene.control.TreeItem<>("Fraktionen"); + contentItemNode = new javafx.scene.control.TreeItem<>("Items"); + contentRecipeNode = new javafx.scene.control.TreeItem<>("Rezepte"); + contentCraftingNode = new javafx.scene.control.TreeItem<>("Crafting Tables"); + contentAreaNode = new javafx.scene.control.TreeItem<>("Areas"); + contentLocationNode = new javafx.scene.control.TreeItem<>("Locations"); - String[] otherSections = { - "Quests", "Fraktionen", "Items", "Rezepte", "Crafting Tables", "Areas", "Locations" - }; - treeRoot.getChildren().add(contentCharNode); - for (String s : otherSections) { - javafx.scene.control.TreeItem node = - new javafx.scene.control.TreeItem<>(s); + for (var node : new javafx.scene.control.TreeItem[]{ + contentCharNode, contentQuestNode, contentFractionNode, contentItemNode, + contentRecipeNode, contentCraftingNode, contentAreaNode, contentLocationNode}) { node.setExpanded(false); treeRoot.getChildren().add(node); } + refreshContentCharacterTree(); + refreshContentCraftingTree(); + refreshContentItemTree(); + refreshContentRecipeTree(); + refreshContentQuestTree(); + refreshContentFractionTree(); + refreshContentAreaTree(); + refreshContentLocationTree(); + contentTreeView = new javafx.scene.control.TreeView<>(treeRoot); contentTreeView.setShowRoot(false); - contentTreeView.setPrefWidth(200); - contentTreeView.getSelectionModel().selectedItemProperty().addListener((obs, ov, nv) -> { - if (nv == null) return; - javafx.scene.control.TreeItem parent = nv.getParent(); - // Klick auf einen Character-Eintrag (Kind-Knoten von Characters) - if (parent != null && parent == contentCharNode) { - if (contentCharBtn != null && !contentCharBtn.isSelected()) contentCharBtn.fire(); - loadCharacterById(nv.getValue()); - return; - } - switch (nv.getValue()) { - case "Characters" -> { if (contentCharBtn != null) contentCharBtn.fire(); } - case "Quests" -> { if (contentQuestBtn != null) contentQuestBtn.fire(); } - case "Fraktionen" -> { if (contentFraktionBtn != null) contentFraktionBtn.fire(); } - case "Items" -> { if (contentItemBtn != null) contentItemBtn.fire(); } - case "Rezepte" -> { if (contentRezeptBtn != null) contentRezeptBtn.fire(); } - case "Crafting Tables" -> { if (contentCraftingBtn != null) contentCraftingBtn.fire(); } - case "Areas" -> { if (contentAreaBtn != null) contentAreaBtn.fire(); } - case "Locations" -> { if (contentLocationBtn != null) contentLocationBtn.fire(); } + contentTreeView.setPrefWidth(420); + contentTreeView.setMinWidth(200); + contentTreeView.setCellFactory(tv -> new javafx.scene.control.TreeCell<>() { + @Override protected void updateItem(String item, boolean empty) { + super.updateItem(item, empty); + if (empty || item == null) { setText(null); setGraphic(null); setStyle(""); return; } + javafx.scene.control.TreeItem ti = getTreeItem(); + javafx.scene.control.TreeItem par = ti != null ? ti.getParent() : null; + boolean isColored = item.contains(":") && + (par == contentQuestNode || par == contentItemNode || par == contentRecipeNode); + if (isColored) { + String prefix = item.substring(0, item.indexOf(':')); + String id = item.substring(item.indexOf(':') + 1); + String color; + if (par == contentQuestNode) color = questTypeColor(prefix); + else if (par == contentItemNode) color = de.blight.editor.ui.ItemEditorView.categoryColor(safeItemCategory(prefix)); + else color = contentRecipeColor(prefix); + javafx.scene.shape.Rectangle box = new javafx.scene.shape.Rectangle(10, 10); + box.setFill(javafx.scene.paint.Color.web(color)); + box.setArcWidth(3); box.setArcHeight(3); + HBox graphic = new HBox(5, box); + graphic.setAlignment(Pos.CENTER_LEFT); + setText(id); setGraphic(graphic); setStyle(""); + } else { + setText(item); setGraphic(null); + boolean isSection = par == null || par.getParent() == null; + setStyle(isSection ? "-fx-font-weight:bold;" : ""); + } } }); + + contentTreeView.getSelectionModel().selectedItemProperty().addListener((obs, ov, nv) -> { + if (contentTreeUpdating) return; + if (nv == null) { + if (contentDeleteBtn != null) contentDeleteBtn.setDisable(true); + return; + } + boolean isLeaf = nv.isLeaf(); + if (contentDeleteBtn != null) contentDeleteBtn.setDisable(!isLeaf); + + javafx.scene.control.TreeItem parent = nv.getParent(); + if (parent == contentCharNode) { + if (contentCharBtn != null && !contentCharBtn.isSelected()) contentCharBtn.fire(); + loadCharacterById(nv.getValue()); return; + } + if (parent == contentQuestNode) { + if (contentQuestBtn != null && !contentQuestBtn.isSelected()) contentQuestBtn.fire(); + String qVal = nv.getValue(); + String qId = qVal.contains(":") ? qVal.substring(qVal.indexOf(':') + 1) : qVal; + if (activeQuestView != null) activeQuestView.selectById(qId); return; + } + if (parent == contentFractionNode) { + if (contentFraktionBtn != null && !contentFraktionBtn.isSelected()) contentFraktionBtn.fire(); + if (activeFractionView != null) activeFractionView.selectByNameId(nv.getValue()); return; + } + if (parent == contentItemNode) { + if (contentItemBtn != null && !contentItemBtn.isSelected()) contentItemBtn.fire(); + String iVal = nv.getValue(); + String iId = iVal.contains(":") ? iVal.substring(iVal.indexOf(':') + 1) : iVal; + if (activeItemView != null) activeItemView.selectItem(iId); return; + } + if (parent == contentRecipeNode) { + if (contentRezeptBtn != null && !contentRezeptBtn.isSelected()) contentRezeptBtn.fire(); + String rVal = nv.getValue(); + String rId = rVal.contains(":") ? rVal.substring(rVal.indexOf(':') + 1) : rVal; + if (activeRecipeView != null) activeRecipeView.selectById(rId); return; + } + if (parent == contentCraftingNode) { + if (contentCraftingBtn != null && !contentCraftingBtn.isSelected()) contentCraftingBtn.fire(); + if (activeCraftingView != null) activeCraftingView.selectByTypeName(nv.getValue()); return; + } + if (parent == contentAreaNode) { + if (contentAreaBtn != null && !contentAreaBtn.isSelected()) contentAreaBtn.fire(); + if (activeAreaView != null) activeAreaView.selectById(nv.getValue()); return; + } + if (parent == contentLocationNode) { + if (contentLocationBtn != null && !contentLocationBtn.isSelected()) contentLocationBtn.fire(); + if (activeLocationView != null) activeLocationView.selectById(nv.getValue()); return; + } + switch (nv.getValue()) { + case "Characters" -> { if (contentCharBtn != null && !contentCharBtn.isSelected()) contentCharBtn.fire(); } + case "Quests" -> { if (contentQuestBtn != null && !contentQuestBtn.isSelected()) contentQuestBtn.fire(); } + case "Fraktionen" -> { if (contentFraktionBtn != null && !contentFraktionBtn.isSelected()) contentFraktionBtn.fire(); } + case "Items" -> { if (contentItemBtn != null && !contentItemBtn.isSelected()) contentItemBtn.fire(); } + case "Rezepte" -> { if (contentRezeptBtn != null && !contentRezeptBtn.isSelected()) contentRezeptBtn.fire(); } + case "Crafting Tables" -> { if (contentCraftingBtn != null && !contentCraftingBtn.isSelected()) contentCraftingBtn.fire(); } + case "Areas" -> { if (contentAreaBtn != null && !contentAreaBtn.isSelected()) contentAreaBtn.fire(); } + case "Locations" -> { if (contentLocationBtn != null && !contentLocationBtn.isSelected()) contentLocationBtn.fire(); } + } + }); + + // Kontextmenü + javafx.scene.control.ContextMenu ctxMenu = new javafx.scene.control.ContextMenu(); + contentTreeView.setOnContextMenuRequested(e -> { + ctxMenu.getItems().clear(); + javafx.scene.control.TreeItem sel = + contentTreeView.getSelectionModel().getSelectedItem(); + boolean isLeaf = sel != null && sel.isLeaf(); + + javafx.scene.control.MenuItem addMi = new javafx.scene.control.MenuItem("Hinzufügen"); + addMi.setOnAction(ev -> contentNew()); + ctxMenu.getItems().add(addMi); + + if (isLeaf) { + javafx.scene.control.MenuItem delMi = new javafx.scene.control.MenuItem("Löschen"); + delMi.setOnAction(ev -> contentDelete()); + javafx.scene.control.MenuItem dupMi = new javafx.scene.control.MenuItem("Duplizieren"); + dupMi.setOnAction(ev -> contentDuplicate()); + ctxMenu.getItems().addAll(delMi, dupMi); + } + + ctxMenu.getItems().add(new javafx.scene.control.SeparatorMenuItem()); + javafx.scene.control.MenuItem refreshMi = new javafx.scene.control.MenuItem("Aktualisieren"); + refreshMi.setOnAction(ev -> contentRefresh()); + ctxMenu.getItems().add(refreshMi); + + ctxMenu.show(contentTreeView, e.getScreenX(), e.getScreenY()); + e.consume(); + }); + + // F5 = Refresh, Delete = Löschen + contentTreeView.setOnKeyPressed(e -> { + if (e.getCode() == javafx.scene.input.KeyCode.F5) { + contentRefresh(); e.consume(); + } else if (e.getCode() == javafx.scene.input.KeyCode.DELETE) { + contentDelete(); e.consume(); + } + }); + return contentTreeView; } + private javafx.scene.layout.VBox buildContentTreePanel() { + javafx.scene.control.TreeView tree = buildContentTree(); + + contentNewBtn = new Button("+ Neu"); + contentDeleteBtn = new Button("- Löschen"); + Button refreshBtn = new Button("..."); + + contentNewBtn.setStyle("-fx-background-color:#2a7a2a;-fx-text-fill:white;"); + contentDeleteBtn.setStyle("-fx-background-color:#7a2a2a;-fx-text-fill:white;"); + contentDeleteBtn.setDisable(true); + refreshBtn.setText("⟳"); + refreshBtn.setTooltip(new Tooltip("Aktualisieren (F5)")); + refreshBtn.setMinWidth(javafx.scene.control.Control.USE_PREF_SIZE); + + Button collapseBtn = new Button("◀"); + collapseBtn.setTooltip(new Tooltip("Alle zuklappen")); + collapseBtn.setMinWidth(javafx.scene.control.Control.USE_PREF_SIZE); + + contentNewBtn.setMaxWidth(Double.MAX_VALUE); + contentDeleteBtn.setMaxWidth(Double.MAX_VALUE); + HBox.setHgrow(contentNewBtn, Priority.ALWAYS); + HBox.setHgrow(contentDeleteBtn, Priority.ALWAYS); + + contentNewBtn.setOnAction(e -> contentNew()); + contentDeleteBtn.setOnAction(e -> contentDelete()); + refreshBtn.setOnAction(e -> contentRefresh()); + collapseBtn.setOnAction(e -> collapseAllContentSections()); + + HBox btnBar = new HBox(4, contentNewBtn, contentDeleteBtn, collapseBtn, refreshBtn); + btnBar.setPadding(new Insets(4)); + btnBar.setAlignment(Pos.CENTER_LEFT); + + javafx.scene.layout.VBox panel = new javafx.scene.layout.VBox(btnBar, tree); + javafx.scene.layout.VBox.setVgrow(tree, Priority.ALWAYS); + return panel; + } + + private void contentNew() { + switch (contentLastSection != null ? contentLastSection : "character") { + case "character" -> { clearCharacterForm(); if (contentCharBtn != null && !contentCharBtn.isSelected()) contentCharBtn.fire(); } + case "quest" -> { if (activeQuestView != null) activeQuestView.createNew(); } + case "fraction" -> { if (activeFractionView != null) activeFractionView.createNew(); } + case "item" -> { if (activeItemView != null) activeItemView.createNew(); } + case "recipe" -> { if (activeRecipeView != null) activeRecipeView.createNew(); } + case "area" -> { if (activeAreaView != null) activeAreaView.createNew(); } + case "location" -> { if (activeLocationView != null) activeLocationView.createNew(); } + } + } + + private void contentDelete() { + if (contentTreeView == null) return; + javafx.scene.control.TreeItem sel = + contentTreeView.getSelectionModel().getSelectedItem(); + if (sel == null || !sel.isLeaf()) return; + + javafx.scene.control.Alert confirm = new javafx.scene.control.Alert( + javafx.scene.control.Alert.AlertType.CONFIRMATION); + confirm.initOwner(primaryStage); + confirm.setTitle("Löschen bestätigen"); + confirm.setHeaderText("\"" + sel.getValue() + "\" wirklich löschen?"); + confirm.setContentText("Diese Aktion kann nicht rückgängig gemacht werden."); + confirm.showAndWait().ifPresent(bt -> { + if (bt != javafx.scene.control.ButtonType.OK) return; + javafx.scene.control.TreeItem parent = sel.getParent(); + if (parent == contentCharNode) deleteCharacterById(sel.getValue()); + else if (parent == contentQuestNode && activeQuestView != null) { activeQuestView.deleteCurrentItem(); refreshContentQuestTree(); } + else if (parent == contentFractionNode && activeFractionView != null) { activeFractionView.deleteCurrentItem(); refreshContentFractionTree(); } + else if (parent == contentItemNode && activeItemView != null) { activeItemView.deleteCurrentItem(); refreshContentItemTree(); } + else if (parent == contentRecipeNode && activeRecipeView != null) { activeRecipeView.deleteCurrentItem(); refreshContentRecipeTree(); } + else if (parent == contentAreaNode && activeAreaView != null) { activeAreaView.deleteCurrentItem(); refreshContentAreaTree(); } + else if (parent == contentLocationNode && activeLocationView != null) { activeLocationView.deleteCurrentItem(); refreshContentLocationTree(); } + else if (parent == contentCraftingNode && activeCraftingView != null) { activeCraftingView.deleteCurrentItem(); } + }); + } + + private void deleteCharacterById(String id) { + Path charFile = ASSET_ROOT.resolve("character").resolve(id + ".character"); + try { + java.nio.file.Files.deleteIfExists(charFile); + } catch (IOException ex) { + new javafx.scene.control.Alert(javafx.scene.control.Alert.AlertType.ERROR, + "Fehler beim Löschen: " + ex.getMessage()).showAndWait(); + return; + } + refreshContentCharacterTree(); + clearCharacterForm(); + if (charEditorStatusLabel != null) charEditorStatusLabel.setText("Character gelöscht."); + } + + private void contentDuplicate() { + if (contentTreeView == null) return; + javafx.scene.control.TreeItem sel = + contentTreeView.getSelectionModel().getSelectedItem(); + if (sel == null || !sel.isLeaf()) return; + javafx.scene.control.TreeItem parent = sel.getParent(); + if (parent == contentCharNode) { + duplicateCharacterById(sel.getValue()); + } + } + + private void duplicateCharacterById(String sourceId) { + javafx.scene.control.TextInputDialog dlg = + new javafx.scene.control.TextInputDialog(sourceId + "_copy"); + dlg.initOwner(primaryStage); + dlg.setTitle("Duplizieren"); + dlg.setHeaderText("Neue ID für die Kopie von \"" + sourceId + "\":"); + dlg.setContentText("ID:"); + dlg.showAndWait().ifPresent(newId -> { + if (newId == null || newId.isBlank()) return; + Path charDir = ASSET_ROOT.resolve("character"); + Path src = charDir.resolve(sourceId + ".character"); + Path dest = charDir.resolve(newId.trim() + ".character"); + try { + java.nio.file.Files.copy(src, dest, + java.nio.file.StandardCopyOption.REPLACE_EXISTING); + refreshContentCharacterTree(); + loadCharacterById(newId.trim()); + // neu erstellten Knoten selektieren + contentCharNode.getChildren().stream() + .filter(ti -> ti.getValue().equals(newId.trim())) + .findFirst() + .ifPresent(ti -> contentTreeView.getSelectionModel().select(ti)); + } catch (IOException ex) { + new javafx.scene.control.Alert(javafx.scene.control.Alert.AlertType.ERROR, + "Fehler beim Duplizieren: " + ex.getMessage()).showAndWait(); + } + }); + } + + private void contentRefresh() { + refreshContentCharacterTree(); + refreshContentQuestTree(); + refreshContentItemTree(); + refreshContentFractionTree(); + refreshContentRecipeTree(); + refreshContentAreaTree(); + refreshContentLocationTree(); + } + private void refreshContentCharacterTree() { if (contentCharNode == null) return; - contentCharNode.getChildren().clear(); - Path charDir = ASSET_ROOT.resolve("character"); - if (!java.nio.file.Files.isDirectory(charDir)) return; - try (var walk = java.nio.file.Files.list(charDir)) { - walk.filter(p -> p.toString().endsWith(".character")) - .map(p -> p.getFileName().toString().replace(".character", "")) - .sorted() - .forEach(id -> contentCharNode.getChildren().add( - new javafx.scene.control.TreeItem<>(id))); - } catch (IOException ignored) {} + contentTreeUpdating = true; + try { + contentCharNode.getChildren().clear(); + Path charDir = ASSET_ROOT.resolve("character"); + if (!java.nio.file.Files.isDirectory(charDir)) return; + java.util.List> items = new java.util.ArrayList<>(); + try (var walk = java.nio.file.Files.list(charDir)) { + walk.filter(p -> p.toString().endsWith(".character")) + .map(p -> p.getFileName().toString().replace(".character", "")) + .sorted() + .forEach(id -> items.add(new javafx.scene.control.TreeItem<>(id))); + } catch (IOException ignored) {} + contentCharNode.getChildren().addAll(items); + } finally { + contentTreeUpdating = false; + } } private void loadCharacterById(String id) { @@ -12154,7 +12427,6 @@ public class EditorApp extends Application { else routineEditorView.loadNpc(new de.blight.common.model.NPC()); } if (charEditorStatusLabel != null) charEditorStatusLabel.setText("Geladen: " + id); - charDirty = true; } catch (Exception e) { if (charEditorStatusLabel != null) charEditorStatusLabel.setText("Fehler: " + e.getMessage()); } @@ -12234,7 +12506,16 @@ public class EditorApp extends Application { catch (NumberFormatException e) { return def; } } + private void collapseAllContentSections() { + for (javafx.scene.control.TreeItem node : new javafx.scene.control.TreeItem[]{ + contentCharNode, contentQuestNode, contentFractionNode, contentItemNode, + contentRecipeNode, contentCraftingNode, contentAreaNode, contentLocationNode}) { + if (node != null) node.setExpanded(false); + } + } + private void buildCharacterViewInContent() { + collapseAllContentSections(); onF5 = null; dialogEditorView = new de.blight.editor.ui.DialogEditorView(); dialogEditorView.setDisable(true); @@ -12250,55 +12531,315 @@ public class EditorApp extends Application { contentTarget().setCenter(tabs); contentTarget().setRight(null); + contentTarget().setBottom(null); if (contentCharNode != null) contentCharNode.setExpanded(true); } private void buildQuestViewInContent() { + collapseAllContentSections(); onF5 = null; - contentTarget().setCenter( - new de.blight.editor.ui.QuestEditorView(ASSET_ROOT.resolve("quests"))); + activeQuestView = new de.blight.editor.ui.QuestEditorView(ASSET_ROOT.resolve("quests")); + contentTarget().setCenter(activeQuestView); contentTarget().setRight(null); + contentTarget().setBottom(buildQuestLegend()); + refreshContentQuestTree(); + if (contentQuestNode != null) contentQuestNode.setExpanded(true); } private void buildFractionViewInContent() { + collapseAllContentSections(); onF5 = null; - contentTarget().setCenter( - new de.blight.editor.ui.FractionEditorView(ASSET_ROOT.resolve("fractions"))); + activeFractionView = new de.blight.editor.ui.FractionEditorView(ASSET_ROOT.resolve("fractions")); + contentTarget().setCenter(activeFractionView); contentTarget().setRight(null); + contentTarget().setBottom(null); + refreshContentFractionTree(); + if (contentFractionNode != null) contentFractionNode.setExpanded(true); } private void buildItemViewInContent() { + collapseAllContentSections(); onF5 = null; - contentTarget().setCenter( - new de.blight.editor.ui.ItemEditorView(ASSET_ROOT.resolve("items"))); + activeItemView = new de.blight.editor.ui.ItemEditorView(ASSET_ROOT.resolve("items")); + contentTarget().setCenter(activeItemView); contentTarget().setRight(null); + contentTarget().setBottom(buildItemLegend()); + refreshContentItemTree(); + if (contentItemNode != null) contentItemNode.setExpanded(true); } private void buildRecipeViewInContent() { + collapseAllContentSections(); onF5 = null; - contentTarget().setCenter( - new de.blight.editor.ui.RecipeEditorView(ASSET_ROOT.resolve("recipes"))); + activeRecipeView = new de.blight.editor.ui.RecipeEditorView(ASSET_ROOT.resolve("recipes")); + contentTarget().setCenter(activeRecipeView); contentTarget().setRight(null); + contentTarget().setBottom(buildRecipeLegend()); + refreshContentRecipeTree(); + if (contentRecipeNode != null) contentRecipeNode.setExpanded(true); } private void buildCraftingViewInContent() { + collapseAllContentSections(); onF5 = null; - contentTarget().setCenter( - new de.blight.editor.ui.CraftingTableEditorView(ASSET_ROOT.resolve("craftingtables"))); + activeCraftingView = new de.blight.editor.ui.CraftingTableEditorView(ASSET_ROOT.resolve("craftingtables")); + contentTarget().setCenter(activeCraftingView); contentTarget().setRight(null); + contentTarget().setBottom(null); + if (contentCraftingNode != null) contentCraftingNode.setExpanded(true); } private void buildAreaViewInContent() { + collapseAllContentSections(); onF5 = null; - contentTarget().setCenter(new de.blight.editor.ui.AreaEditorView()); + activeAreaView = new de.blight.editor.ui.AreaEditorView(); + contentTarget().setCenter(activeAreaView); contentTarget().setRight(null); + contentTarget().setBottom(null); + refreshContentAreaTree(); + if (contentAreaNode != null) contentAreaNode.setExpanded(true); } private void buildLocationViewInContent() { + collapseAllContentSections(); onF5 = null; - contentTarget().setCenter(new de.blight.editor.ui.LocationEditorView()); + activeLocationView = new de.blight.editor.ui.LocationEditorView(); + contentTarget().setCenter(activeLocationView); contentTarget().setRight(null); + contentTarget().setBottom(null); + refreshContentLocationTree(); + if (contentLocationNode != null) contentLocationNode.setExpanded(true); + } + + // ── Baum-Refresh-Methoden ───────────────────────────────────────────────── + + private void refreshContentQuestTree() { + if (contentQuestNode == null) return; + contentTreeUpdating = true; + try { + contentQuestNode.getChildren().clear(); + Path dir = ASSET_ROOT.resolve("quests"); + if (!java.nio.file.Files.isDirectory(dir)) return; + java.util.List allQuests = + de.blight.common.model.quests.QuestIO.loadAll(dir); + java.util.List> items = allQuests.stream() + .sorted(java.util.Comparator + .comparing( + q -> de.blight.common.model.quests.QuestIO.typeOf(q)) + .thenComparing(q -> q.getQuestId() != null ? q.getQuestId() : "")) + .map(q -> { + String type = de.blight.common.model.quests.QuestIO.typeOf(q); + String id = q.getQuestId() != null ? q.getQuestId() : ""; + return new javafx.scene.control.TreeItem<>(type + ":" + id); + }) + .collect(java.util.stream.Collectors.toList()); + contentQuestNode.getChildren().addAll(items); + } finally { + contentTreeUpdating = false; + } + } + + private static String questTypeColor(String type) { + return switch (type) { + case "BRING" -> "#cc5555"; + case "FOLLOW" -> "#55aacc"; + case "INTERACT" -> "#aa55cc"; + case "ITEM" -> "#55cc88"; + case "TALK" -> "#ccaa33"; + default -> "#888888"; + }; + } + + private javafx.scene.layout.HBox buildQuestLegend() { + javafx.scene.layout.HBox bar = new javafx.scene.layout.HBox(14); + bar.setPadding(new javafx.geometry.Insets(5, 10, 5, 10)); + bar.setAlignment(javafx.geometry.Pos.CENTER_LEFT); + bar.setStyle("-fx-background-color: #1a1a2a;"); + for (String[] entry : new String[][]{ + {"BRING", "Bring"}, {"FOLLOW", "Follow"}, {"INTERACT", "Interact"}, + {"ITEM", "Item"}, {"TALK", "Talk"}}) { + javafx.scene.shape.Rectangle box = new javafx.scene.shape.Rectangle(10, 10); + box.setFill(javafx.scene.paint.Color.web(questTypeColor(entry[0]))); + box.setArcWidth(3); box.setArcHeight(3); + javafx.scene.control.Label lbl = new javafx.scene.control.Label(entry[1]); + lbl.setStyle("-fx-text-fill: #aaa; -fx-font-size: 11;"); + javafx.scene.layout.HBox item = new javafx.scene.layout.HBox(4, box, lbl); + item.setAlignment(javafx.geometry.Pos.CENTER_LEFT); + bar.getChildren().add(item); + } + return bar; + } + + private static de.blight.common.model.ItemCategory safeItemCategory(String name) { + try { return de.blight.common.model.ItemCategory.valueOf(name); } + catch (IllegalArgumentException e) { return null; } + } + + private static String contentRecipeColor(String tableTypeName) { + try { + de.blight.common.model.CraftingTable.CraftingTableType t = + de.blight.common.model.CraftingTable.CraftingTableType.valueOf(tableTypeName); + return de.blight.editor.ui.CraftingTableEditorView.typeColor(t); + } catch (IllegalArgumentException e) { + return "#666666"; + } + } + + private javafx.scene.layout.HBox buildLegendBar(String[][] entries, + java.util.function.Function colorFn) { + javafx.scene.layout.HBox bar = new javafx.scene.layout.HBox(14); + bar.setPadding(new javafx.geometry.Insets(5, 10, 5, 10)); + bar.setAlignment(javafx.geometry.Pos.CENTER_LEFT); + bar.setStyle("-fx-background-color: #1a1a2a;"); + for (String[] entry : entries) { + javafx.scene.shape.Rectangle box = new javafx.scene.shape.Rectangle(10, 10); + box.setFill(javafx.scene.paint.Color.web(colorFn.apply(entry[0]))); + box.setArcWidth(3); box.setArcHeight(3); + javafx.scene.control.Label lbl = new javafx.scene.control.Label(entry[1]); + lbl.setStyle("-fx-text-fill: #aaa; -fx-font-size: 11;"); + javafx.scene.layout.HBox item = new javafx.scene.layout.HBox(4, box, lbl); + item.setAlignment(javafx.geometry.Pos.CENTER_LEFT); + bar.getChildren().add(item); + } + return bar; + } + + private javafx.scene.layout.HBox buildItemLegend() { + return buildLegendBar(new String[][]{ + {"WEAPON", "Waffe"}, + {"GEAR", "Ausrüstung"}, + {"CONSUMABLES", "Verbrauchbar"}, + {"QUEST_ITEMS", "Quest"}, + {"CRAFTING_ITEMS","Crafting"}, + {"MISC_ITEMS", "Sonstiges"} + }, k -> de.blight.editor.ui.ItemEditorView.categoryColor(safeItemCategory(k))); + } + + private javafx.scene.layout.HBox buildRecipeLegend() { + return buildLegendBar(new String[][]{ + {"AlchemyTable", "Alchemie"}, + {"EnchantmentTable", "Verzauberung"}, + {"Smithy", "Schmiede"}, + {"Goldsmiths", "Goldschmied"}, + {"Workshop", "Werkstatt"}, + {"Fireplace", "Feuerstelle"}, + {"Kitchen", "Küche"}, + {"NONE", "Kein Tisch"} + }, EditorApp::contentRecipeColor); + } + + private void refreshContentItemTree() { + if (contentItemNode == null) return; + contentTreeUpdating = true; + try { + contentItemNode.getChildren().clear(); + Path dir = ASSET_ROOT.resolve("items"); + if (!java.nio.file.Files.isDirectory(dir)) return; + java.util.List> treeItems = + de.blight.common.model.ItemIO.loadAll(dir).stream() + .filter(i -> i.getItemId() != null && !i.getItemId().isBlank()) + .map(i -> { + String cat = i.getCategory() != null ? i.getCategory().name() : "MISC_ITEMS"; + return new javafx.scene.control.TreeItem<>(cat + ":" + i.getItemId()); + }) + .collect(java.util.stream.Collectors.toList()); + contentItemNode.getChildren().addAll(treeItems); + } finally { + contentTreeUpdating = false; + } + } + + private void refreshContentFractionTree() { + if (contentFractionNode == null) return; + contentTreeUpdating = true; + try { + contentFractionNode.getChildren().clear(); + Path dir = ASSET_ROOT.resolve("fractions"); + if (!java.nio.file.Files.isDirectory(dir)) return; + java.util.List> items = + de.blight.common.model.FractionIO.loadAll(dir).stream() + .filter(f -> f.getName() != null && !f.getName().id().isBlank()) + .map(f -> new javafx.scene.control.TreeItem<>(f.getName().id())) + .collect(java.util.stream.Collectors.toList()); + contentFractionNode.getChildren().addAll(items); + } finally { + contentTreeUpdating = false; + } + } + + private void refreshContentAreaTree() { + if (contentAreaNode == null) return; + contentTreeUpdating = true; + try { + contentAreaNode.getChildren().clear(); + java.util.List> items; + try { + items = de.blight.common.AreaDefinitionIO.load().stream() + .filter(a -> a.id() != null && !a.id().isBlank()) + .map(a -> new javafx.scene.control.TreeItem<>(a.id())) + .collect(java.util.stream.Collectors.toList()); + } catch (IOException ignored) { return; } + contentAreaNode.getChildren().addAll(items); + } finally { + contentTreeUpdating = false; + } + } + + private void refreshContentLocationTree() { + if (contentLocationNode == null) return; + contentTreeUpdating = true; + try { + contentLocationNode.getChildren().clear(); + java.util.List> items; + try { + items = de.blight.common.LocationIO.load().stream() + .filter(l -> l.getId() != null && !l.getId().isBlank()) + .map(l -> new javafx.scene.control.TreeItem<>(l.getId())) + .collect(java.util.stream.Collectors.toList()); + } catch (IOException ignored) { return; } + contentLocationNode.getChildren().addAll(items); + } finally { + contentTreeUpdating = false; + } + } + + private void refreshContentRecipeTree() { + if (contentRecipeNode == null) return; + contentTreeUpdating = true; + try { + contentRecipeNode.getChildren().clear(); + Path dir = ASSET_ROOT.resolve("recipes"); + if (!java.nio.file.Files.isDirectory(dir)) return; + java.util.List> treeItems = + de.blight.common.model.RecipeIO.loadAll(dir).stream() + .filter(r -> r.getCreates() != null && r.getCreates().getItemId() != null) + .map(r -> { + String table = (r.getTable() != null && r.getTable().getType() != null) + ? r.getTable().getType().name() : "NONE"; + return new javafx.scene.control.TreeItem<>(table + ":" + r.getCreates().getItemId()); + }) + .collect(java.util.stream.Collectors.toList()); + contentRecipeNode.getChildren().addAll(treeItems); + } finally { + contentTreeUpdating = false; + } + } + + private void refreshContentCraftingTree() { + if (contentCraftingNode == null) return; + contentTreeUpdating = true; + try { + contentCraftingNode.getChildren().clear(); + java.util.List> items = new java.util.ArrayList<>(); + for (de.blight.common.model.CraftingTable.CraftingTableType t : + de.blight.common.model.CraftingTable.CraftingTableType.values()) { + items.add(new javafx.scene.control.TreeItem<>(t.name())); + } + contentCraftingNode.getChildren().addAll(items); + } finally { + contentTreeUpdating = false; + } } } diff --git a/blight-editor/src/main/java/de/blight/editor/JmeEditorApp.java b/blight-editor/src/main/java/de/blight/editor/JmeEditorApp.java index ce28296..952dd24 100644 --- a/blight-editor/src/main/java/de/blight/editor/JmeEditorApp.java +++ b/blight-editor/src/main/java/de/blight/editor/JmeEditorApp.java @@ -60,6 +60,12 @@ public class JmeEditorApp extends SimpleApplication { private int currentW; private int currentH; + private volatile boolean renderThrottled = false; + + public void setRenderThrottled(boolean throttled) { + this.renderThrottled = throttled; + } + // ── NaN-sichere Comparatoren ────────────────────────────────────────────── /** Transparent-Bucket: back-to-front nach distanceToEdge, NaN-sicher. */ @@ -300,6 +306,11 @@ public class JmeEditorApp extends SimpleApplication { @Override public void simpleUpdate(float tpf) { + if (renderThrottled) { + try { Thread.sleep(50); } catch (InterruptedException ignored) {} + return; + } + // Viewport-Resize (von JavaFX angefordert) int[] req = input.resizeRequest.getAndSet(null); if (req != null && req[0] > 0 && req[1] > 0 diff --git a/blight-editor/src/main/java/de/blight/editor/ui/AreaEditorView.java b/blight-editor/src/main/java/de/blight/editor/ui/AreaEditorView.java index 0383f9a..f2003af 100644 --- a/blight-editor/src/main/java/de/blight/editor/ui/AreaEditorView.java +++ b/blight-editor/src/main/java/de/blight/editor/ui/AreaEditorView.java @@ -25,8 +25,6 @@ public class AreaEditorView extends BorderPane { private final PauseTransition savePause = new PauseTransition(Duration.millis(600)); private final String[] tracks = {"", "", ""}; - private ListView listView; - private Button deleteBtn; private AreaDefinition current = null; private boolean reloading = false; @@ -41,9 +39,7 @@ public class AreaEditorView extends BorderPane { setStyle("-fx-background-color: #1e1e2e;"); reload(); - SplitPane split = new SplitPane(buildListPanel(), buildFormPanel()); - split.setDividerPositions(0.28); - setCenter(split); + setCenter(buildFormPanel()); } // ── Auto-save ───────────────────────────────────────────────────────────── @@ -63,53 +59,6 @@ public class AreaEditorView extends BorderPane { persist(); } - // ── List panel ──────────────────────────────────────────────────────────── - - private VBox buildListPanel() { - listView = new ListView<>(areas); - listView.setStyle("-fx-background-color: #1a1a2a; -fx-control-inner-background: #1a1a2a;"); - listView.setCellFactory(lv -> new ListCell<>() { - @Override protected void updateItem(AreaDefinition d, boolean empty) { - super.updateItem(d, empty); - if (empty || d == null) { setText(null); setStyle(""); return; } - String shortId = d.id().startsWith(PREFIX) ? d.id().substring(PREFIX.length()) : d.id(); - setText(shortId.isBlank() ? "—" : shortId); - setStyle("-fx-text-fill: #dddddd;" - + " -fx-border-color: transparent transparent transparent #cc6644;" - + " -fx-border-width: 0 0 0 3; -fx-padding: 3 6 3 8;"); - } - }); - listView.getSelectionModel().selectedItemProperty() - .addListener((obs, old, nw) -> onSelected(old, nw)); - VBox.setVgrow(listView, Priority.ALWAYS); - - Button newBtn = new Button("Neue Area"); - newBtn.setMaxWidth(Double.MAX_VALUE); - newBtn.setStyle("-fx-background-color: #3a6a3a; -fx-text-fill: white;"); - newBtn.setOnAction(e -> createArea()); - - deleteBtn = new Button("Löschen"); - deleteBtn.setMaxWidth(Double.MAX_VALUE); - deleteBtn.setStyle("-fx-background-color: #6a2a2a; -fx-text-fill: white;"); - deleteBtn.setDisable(true); - deleteBtn.setOnAction(e -> deleteSelected()); - - Button refreshBtn = new Button("↺ Neu laden"); - refreshBtn.setMaxWidth(Double.MAX_VALUE); - refreshBtn.setOnAction(e -> reload()); - - HBox listButtons = new HBox(6, newBtn, deleteBtn); - HBox.setHgrow(newBtn, Priority.ALWAYS); - HBox.setHgrow(deleteBtn, Priority.ALWAYS); - listButtons.setPadding(new Insets(6, 8, 6, 8)); - - VBox panel = new VBox(6, listView, listButtons, refreshBtn); - VBox.setVgrow(listView, Priority.ALWAYS); - panel.setPadding(new Insets(8)); - panel.setStyle("-fx-background-color: #1a1a2a;"); - return panel; - } - // ── Form panel ──────────────────────────────────────────────────────────── private ScrollPane buildFormPanel() { @@ -193,7 +142,6 @@ public class AreaEditorView extends BorderPane { if (reloading) return; if (old != null) { saveFormToArea(old); persist(); } current = nw; - deleteBtn.setDisable(nw == null); if (nw != null) { formContainer.setDisable(false); loadForm(nw); } else { formContainer.setDisable(true); clearForm(); } } @@ -237,7 +185,7 @@ public class AreaEditorView extends BorderPane { private void createArea() { AreaDefinition d = new AreaDefinition(PREFIX + "neu_" + System.currentTimeMillis(), "", "", ""); areas.add(d); - listView.getSelectionModel().select(d); + onSelected(current, d); } private void deleteSelected() { @@ -246,11 +194,29 @@ public class AreaEditorView extends BorderPane { current = null; clearForm(); formContainer.setDisable(true); - deleteBtn.setDisable(true); persist(); reload(); } + public void createNew() { createArea(); } + + public void deleteCurrentItem() { deleteSelected(); } + + public void selectById(String id) { + areas.stream() + .filter(a -> id.equals(a.id())) + .findFirst() + .ifPresent(a -> onSelected(current, a)); + } + + public java.util.List getIds() { + return areas.stream() + .map(AreaDefinition::id) + .filter(id -> id != null && !id.isBlank()) + .sorted() + .collect(java.util.stream.Collectors.toList()); + } + private void persist() { try { AreaDefinitionIO.save(areas); } catch (IOException e) { diff --git a/blight-editor/src/main/java/de/blight/editor/ui/CraftingTableEditorView.java b/blight-editor/src/main/java/de/blight/editor/ui/CraftingTableEditorView.java index d69a2ce..5433908 100644 --- a/blight-editor/src/main/java/de/blight/editor/ui/CraftingTableEditorView.java +++ b/blight-editor/src/main/java/de/blight/editor/ui/CraftingTableEditorView.java @@ -26,8 +26,6 @@ public class CraftingTableEditorView extends BorderPane { // ── List ────────────────────────────────────────────────────────────────── - private ListView listView; - private Button deleteBtn; private CraftingTable.CraftingTableType currentType = null; // ── Form fields ─────────────────────────────────────────────────────────── @@ -48,51 +46,7 @@ public class CraftingTableEditorView extends BorderPane { setStyle("-fx-background-color: #1e1e2e;"); reloadMap(); - SplitPane split = new SplitPane(buildListPanel(), buildFormPanel()); - split.setDividerPositions(0.28); - setCenter(split); - } - - // ── List panel ──────────────────────────────────────────────────────────── - - private VBox buildListPanel() { - listView = new ListView<>(); - listView.getItems().setAll(CraftingTable.CraftingTableType.values()); - listView.setStyle("-fx-background-color: #1a1a2a; -fx-control-inner-background: #1a1a2a;"); - listView.setCellFactory(lv -> new ListCell<>() { - @Override protected void updateItem(CraftingTable.CraftingTableType type, boolean empty) { - super.updateItem(type, empty); - if (empty || type == null) { setText(null); setStyle(""); return; } - boolean configured = shared.containsKey(type); - String color = typeColor(type); - String suffix = configured ? " ✓" : " —"; - setText(type.name() + suffix); - String fillColor = configured ? "#dddddd" : "#666666"; - setStyle("-fx-text-fill: " + fillColor + ";" - + " -fx-border-color: transparent transparent transparent " + color + ";" - + " -fx-border-width: 0 0 0 3; -fx-padding: 3 6 3 8;"); - setTooltip(new Tooltip(type.name() + (configured ? " – konfiguriert" : " – nicht konfiguriert"))); - } - }); - listView.getSelectionModel().selectedItemProperty() - .addListener((obs, old, nw) -> onTypeSelected(old, nw)); - VBox.setVgrow(listView, Priority.ALWAYS); - - deleteBtn = new Button("Konfiguration löschen"); - deleteBtn.setMaxWidth(Double.MAX_VALUE); - deleteBtn.setStyle("-fx-background-color: #6a2a2a; -fx-text-fill: white;"); - deleteBtn.setDisable(true); - deleteBtn.setOnAction(e -> deleteSelected()); - - Button refreshBtn = new Button("↺ Neu laden"); - refreshBtn.setMaxWidth(Double.MAX_VALUE); - refreshBtn.setOnAction(e -> { reloadMap(); refresh(); }); - - VBox panel = new VBox(6, listView, deleteBtn, refreshBtn); - VBox.setVgrow(listView, Priority.ALWAYS); - panel.setPadding(new Insets(8)); - panel.setStyle("-fx-background-color: #1a1a2a;"); - return panel; + setCenter(buildFormPanel()); } // ── Form panel ──────────────────────────────────────────────────────────── @@ -151,12 +105,10 @@ public class CraftingTableEditorView extends BorderPane { currentType = nw; if (nw == null) { formContainer.setDisable(true); - deleteBtn.setDisable(true); clearForm(); } else { formContainer.setDisable(false); loadFormFromType(nw); - deleteBtn.setDisable(!shared.containsKey(nw)); } } @@ -193,7 +145,6 @@ public class CraftingTableEditorView extends BorderPane { } reloadMap(); refresh(); - listView.getSelectionModel().select(currentType); } private void deleteSelected() { @@ -206,7 +157,21 @@ public class CraftingTableEditorView extends BorderPane { } reloadMap(); refresh(); - listView.getSelectionModel().select(currentType); + } + + public void deleteCurrentItem() { deleteSelected(); } + + public void selectByTypeName(String name) { + try { + CraftingTable.CraftingTableType type = CraftingTable.CraftingTableType.valueOf(name); + onTypeSelected(currentType, type); + } catch (IllegalArgumentException ignored) {} + } + + public String[] getTypeNames() { + return java.util.Arrays.stream(CraftingTable.CraftingTableType.values()) + .map(Enum::name) + .toArray(String[]::new); } private void clearForm() { @@ -226,16 +191,14 @@ public class CraftingTableEditorView extends BorderPane { } private void refresh() { - listView.refresh(); - if (currentType != null) { - deleteBtn.setDisable(!shared.containsKey(currentType)); - if (!formContainer.isDisable()) loadFormFromType(currentType); + if (currentType != null && !formContainer.isDisable()) { + loadFormFromType(currentType); } } // ── Helpers ─────────────────────────────────────────────────────────────── - static String typeColor(CraftingTable.CraftingTableType type) { + public static String typeColor(CraftingTable.CraftingTableType type) { if (type == null) return "#666666"; return switch (type) { case AlchemyTable -> "#44bb88"; diff --git a/blight-editor/src/main/java/de/blight/editor/ui/FractionEditorView.java b/blight-editor/src/main/java/de/blight/editor/ui/FractionEditorView.java index cd5263d..ab5d06f 100644 --- a/blight-editor/src/main/java/de/blight/editor/ui/FractionEditorView.java +++ b/blight-editor/src/main/java/de/blight/editor/ui/FractionEditorView.java @@ -28,8 +28,6 @@ public class FractionEditorView extends BorderPane { // ── List ────────────────────────────────────────────────────────────────── private final SortedList sortedFractions; - private ListView listView; - private Button deleteBtn; private Fraction current = null; // ── Form fields ─────────────────────────────────────────────────────────── @@ -55,57 +53,7 @@ public class FractionEditorView extends BorderPane { setStyle("-fx-background-color: #1e1e2e;"); reload(); - SplitPane split = new SplitPane(buildListPanel(), buildFormPanel()); - split.setDividerPositions(0.28); - setCenter(split); - } - - // ── List panel ──────────────────────────────────────────────────────────── - - private VBox buildListPanel() { - listView = new ListView<>(sortedFractions); - listView.setStyle("-fx-background-color: #1a1a2a; -fx-control-inner-background: #1a1a2a;"); - listView.setCellFactory(lv -> new ListCell<>() { - @Override protected void updateItem(Fraction f, boolean empty) { - super.updateItem(f, empty); - if (empty || f == null) { setText(null); setStyle(""); return; } - String name = f.getName() != null ? f.getName().id() : "—"; - setText(name); - setTooltip(new Tooltip("ID: " + (f.getFractionId() != null ? f.getFractionId() : "?"))); - setStyle("-fx-text-fill: #dddddd;" - + " -fx-border-color: transparent transparent transparent #6699cc;" - + " -fx-border-width: 0 0 0 3; -fx-padding: 3 6 3 8;"); - } - }); - listView.getSelectionModel().selectedItemProperty() - .addListener((obs, old, nw) -> onFractionSelected(old, nw)); - VBox.setVgrow(listView, Priority.ALWAYS); - - Button newBtn = new Button("Neue Fraktion"); - newBtn.setMaxWidth(Double.MAX_VALUE); - newBtn.setStyle("-fx-background-color: #3a6a3a; -fx-text-fill: white;"); - newBtn.setOnAction(e -> createFraction()); - - deleteBtn = new Button("Löschen"); - deleteBtn.setMaxWidth(Double.MAX_VALUE); - deleteBtn.setStyle("-fx-background-color: #6a2a2a; -fx-text-fill: white;"); - deleteBtn.setDisable(true); - deleteBtn.setOnAction(e -> deleteSelected()); - - Button refreshBtn = new Button("↺ Neu laden"); - refreshBtn.setMaxWidth(Double.MAX_VALUE); - refreshBtn.setOnAction(e -> reload()); - - HBox listButtons = new HBox(6, newBtn, deleteBtn); - HBox.setHgrow(newBtn, Priority.ALWAYS); - HBox.setHgrow(deleteBtn, Priority.ALWAYS); - listButtons.setPadding(new Insets(6, 8, 6, 8)); - - VBox panel = new VBox(6, listView, listButtons, refreshBtn); - VBox.setVgrow(listView, Priority.ALWAYS); - panel.setPadding(new Insets(8)); - panel.setStyle("-fx-background-color: #1a1a2a;"); - return panel; + setCenter(buildFormPanel()); } // ── Form panel ──────────────────────────────────────────────────────────── @@ -185,7 +133,6 @@ public class FractionEditorView extends BorderPane { } } current = nw; - deleteBtn.setDisable(nw == null); if (nw != null) { formContainer.setDisable(false); loadFormFromFraction(nw); @@ -230,19 +177,17 @@ public class FractionEditorView extends BorderPane { Fraction f = new Fraction(); f.setFractionId(UUID.randomUUID()); fractions.add(f); - listView.getSelectionModel().select(f); + onFractionSelected(current, f); } private void deleteSelected() { - Fraction sel = listView.getSelectionModel().getSelectedItem(); - if (sel == null) return; - UUID id = sel.getFractionId(); - fractions.remove(sel); + if (current == null) return; + UUID id = current.getFractionId(); + fractions.remove(current); try { FractionIO.delete(id, fractionDir); } catch (IOException ignored) {} current = null; clearForm(); formContainer.setDisable(true); - deleteBtn.setDisable(true); reload(); } @@ -250,6 +195,25 @@ public class FractionEditorView extends BorderPane { fractions.setAll(FractionIO.loadAll(fractionDir)); } + public void createNew() { createFraction(); } + + public void deleteCurrentItem() { deleteSelected(); } + + public void selectByNameId(String nameId) { + sortedFractions.stream() + .filter(f -> f.getName() != null && nameId.equals(f.getName().id())) + .findFirst() + .ifPresent(f -> onFractionSelected(current, f)); + } + + public java.util.List getNameIds() { + return sortedFractions.stream() + .map(f -> f.getName() != null ? f.getName().id() : "") + .filter(n -> !n.isBlank()) + .sorted() + .collect(java.util.stream.Collectors.toList()); + } + // ── Helpers ─────────────────────────────────────────────────────────────── private static TextReference ref(String text) { diff --git a/blight-editor/src/main/java/de/blight/editor/ui/ItemEditorView.java b/blight-editor/src/main/java/de/blight/editor/ui/ItemEditorView.java index e72c71b..ca2190f 100644 --- a/blight-editor/src/main/java/de/blight/editor/ui/ItemEditorView.java +++ b/blight-editor/src/main/java/de/blight/editor/ui/ItemEditorView.java @@ -36,7 +36,6 @@ public class ItemEditorView extends BorderPane { private final Path assetRoot; private final SortedList sortedItems; - private final ListView listView; private Item current = null; // Form-Felder @@ -48,7 +47,6 @@ public class ItemEditorView extends BorderPane { private Spinner goldSpinner; private TextField modelRefField; private VBox formContainer; - private Button deleteBtn; private CheckBox consumableCheck; private VBox consumablesSection; private VBox effectsRows; @@ -65,65 +63,8 @@ public class ItemEditorView extends BorderPane { setStyle("-fx-background-color: #1e1e2e;"); reload(); - // ── Linke Seite: Liste ──────────────────────────────────────────────── sortedItems = new SortedList<>(items, ItemIO.SORT_ORDER); - listView = new ListView<>(sortedItems); - listView.setStyle("-fx-background-color: #1a1a2a; -fx-control-inner-background: #1a1a2a;"); - listView.setCellFactory(lv -> new ListCell<>() { - @Override protected void updateItem(Item item, boolean empty) { - super.updateItem(item, empty); - if (empty || item == null) { setText(null); setStyle(""); return; } - String catName = item.getCategory() != null ? item.getCategory().name() : "—"; - String id = item.getItemId() != null ? item.getItemId() : "—"; - setText(id); - String color = categoryColor(item.getCategory()); - setStyle("-fx-text-fill: #dddddd;" - + " -fx-border-color: transparent transparent transparent " + color + ";" - + " -fx-border-width: 0 0 0 3;" - + " -fx-padding: 3 6 3 8;"); - setTooltip(new Tooltip("[" + catName + "] " + id)); - } - }); - listView.getSelectionModel().selectedItemProperty() - .addListener((obs, old, nw) -> onItemSelected(old, nw)); - Button refreshBtn = new Button("↺"); - refreshBtn.setStyle("-fx-background-color: transparent; -fx-text-fill: #888; -fx-cursor: hand;"); - refreshBtn.setOnAction(e -> reload()); - Label titleLbl = new Label("Items"); - titleLbl.setStyle("-fx-font-weight: bold; -fx-font-size: 13; -fx-text-fill: #aaccee;"); - HBox header = new HBox(8, titleLbl, refreshBtn); - header.setPadding(new Insets(8, 10, 8, 10)); - header.setAlignment(Pos.CENTER_LEFT); - header.setStyle("-fx-background-color: #1a1a2a; -fx-border-color: #444; -fx-border-width: 0 0 1 0;"); - - HBox legend = buildLegend(); - legend.setPadding(new Insets(4, 8, 4, 8)); - legend.setStyle("-fx-background-color: #1a1a2a;"); - - Button newBtn = new Button("Neues Item"); - newBtn.setMaxWidth(Double.MAX_VALUE); - newBtn.setStyle("-fx-background-color: #3a6a3a; -fx-text-fill: white;"); - newBtn.setOnAction(e -> createItem()); - - deleteBtn = new Button("Löschen"); - deleteBtn.setMaxWidth(Double.MAX_VALUE); - deleteBtn.setStyle("-fx-background-color: #6a2a2a; -fx-text-fill: white;"); - deleteBtn.setDisable(true); - deleteBtn.setOnAction(e -> deleteSelected()); - - HBox listButtons = new HBox(6, newBtn, deleteBtn); - listButtons.setPadding(new Insets(6, 8, 6, 8)); - HBox.setHgrow(newBtn, Priority.ALWAYS); - HBox.setHgrow(deleteBtn, Priority.ALWAYS); - - VBox leftPane = new VBox(header, listView, legend, listButtons); - leftPane.setPrefWidth(440); - leftPane.setMinWidth(300); - leftPane.setStyle("-fx-background-color: #1a1a2a; -fx-border-color: #3a3a4a; -fx-border-width: 0 1 0 0;"); - VBox.setVgrow(listView, Priority.ALWAYS); - - // ── Rechte Seite: Formular ──────────────────────────────────────────── formContainer = buildForm(); formContainer.setDisable(true); @@ -131,7 +72,6 @@ public class ItemEditorView extends BorderPane { formScroll.setFitToWidth(true); formScroll.setStyle("-fx-background-color: #252535; -fx-background: #252535;"); - setLeft(leftPane); setCenter(formScroll); } @@ -142,7 +82,28 @@ public class ItemEditorView extends BorderPane { items.stream() .filter(i -> itemId.equals(i.getItemId())) .findFirst() - .ifPresent(listView.getSelectionModel()::select); + .ifPresent(i -> onItemSelected(current, i)); + } + + public void createNew() { createItem(); } + + public void deleteCurrentItem() { deleteSelected(); } + + public java.util.List getIds() { + return sortedItems.stream() + .map(Item::getItemId) + .filter(id -> id != null && !id.isBlank()) + .collect(java.util.stream.Collectors.toList()); + } + + public java.util.List getEncodedIds() { + return sortedItems.stream() + .filter(i -> i.getItemId() != null && !i.getItemId().isBlank()) + .map(i -> { + String cat = i.getCategory() != null ? i.getCategory().name() : "MISC_ITEMS"; + return cat + ":" + i.getItemId(); + }) + .collect(java.util.stream.Collectors.toList()); } // ── Reload ──────────────────────────────────────────────────────────────── @@ -156,7 +117,7 @@ public class ItemEditorView extends BorderPane { // ── Category colors ─────────────────────────────────────────────────────── - static String categoryColor(ItemCategory cat) { + public static String categoryColor(ItemCategory cat) { if (cat == null) return "#666666"; return switch (cat) { case WEAPON -> "#cc5555"; @@ -366,7 +327,6 @@ public class ItemEditorView extends BorderPane { } } current = nw; - deleteBtn.setDisable(nw == null); if (nw != null) { formContainer.setDisable(false); loadFormFromItem(nw); @@ -435,14 +395,13 @@ public class ItemEditorView extends BorderPane { item.setItemId("neues_item_" + System.currentTimeMillis()); item.setCategory(ItemCategory.MISC_ITEMS); items.add(item); - listView.getSelectionModel().select(item); + onItemSelected(current, item); } private void deleteSelected() { - Item sel = listView.getSelectionModel().getSelectedItem(); - if (sel == null) return; - String iId = sel.getItemId(); - items.remove(sel); + if (current == null) return; + String iId = current.getItemId(); + items.remove(current); if (iId != null && !iId.isBlank()) { try { ItemIO.delete(iId, itemDir); } catch (IOException e) { /* ignore */ } @@ -450,7 +409,6 @@ public class ItemEditorView extends BorderPane { current = null; clearForm(); formContainer.setDisable(true); - deleteBtn.setDisable(true); reload(); } diff --git a/blight-editor/src/main/java/de/blight/editor/ui/LocationEditorView.java b/blight-editor/src/main/java/de/blight/editor/ui/LocationEditorView.java index a61e912..896dc06 100644 --- a/blight-editor/src/main/java/de/blight/editor/ui/LocationEditorView.java +++ b/blight-editor/src/main/java/de/blight/editor/ui/LocationEditorView.java @@ -23,8 +23,6 @@ public class LocationEditorView extends BorderPane { private final ObservableList locations = FXCollections.observableArrayList(); private final PauseTransition savePause = new PauseTransition(Duration.millis(600)); - private ListView listView; - private Button deleteBtn; private Location current = null; private boolean reloading = false; @@ -39,9 +37,7 @@ public class LocationEditorView extends BorderPane { setStyle("-fx-background-color: #1e1e2e;"); reload(); - SplitPane split = new SplitPane(buildListPanel(), buildFormPanel()); - split.setDividerPositions(0.28); - setCenter(split); + setCenter(buildFormPanel()); } // ── Auto-save ───────────────────────────────────────────────────────────── @@ -53,55 +49,6 @@ public class LocationEditorView extends BorderPane { saveFormToLocation(current); if (current.getId() == null || current.getId().isBlank()) return; persist(); - listView.refresh(); - } - - // ── List panel ──────────────────────────────────────────────────────────── - - private VBox buildListPanel() { - listView = new ListView<>(locations); - listView.setStyle("-fx-background-color: #1a1a2a; -fx-control-inner-background: #1a1a2a;"); - listView.setCellFactory(lv -> new ListCell<>() { - @Override protected void updateItem(Location loc, boolean empty) { - super.updateItem(loc, empty); - if (empty || loc == null) { setText(null); setStyle(""); return; } - String shortId = loc.getId().startsWith(PREFIX) - ? loc.getId().substring(PREFIX.length()) : loc.getId(); - setText(shortId.isBlank() ? "—" : shortId); - setStyle("-fx-text-fill: #dddddd;" - + " -fx-border-color: transparent transparent transparent #66aacc;" - + " -fx-border-width: 0 0 0 3; -fx-padding: 3 6 3 8;"); - } - }); - listView.getSelectionModel().selectedItemProperty() - .addListener((obs, old, nw) -> onSelected(old, nw)); - VBox.setVgrow(listView, Priority.ALWAYS); - - Button newBtn = new Button("Neue Location"); - newBtn.setMaxWidth(Double.MAX_VALUE); - newBtn.setStyle("-fx-background-color: #3a6a3a; -fx-text-fill: white;"); - newBtn.setOnAction(e -> createLocation()); - - deleteBtn = new Button("Löschen"); - deleteBtn.setMaxWidth(Double.MAX_VALUE); - deleteBtn.setStyle("-fx-background-color: #6a2a2a; -fx-text-fill: white;"); - deleteBtn.setDisable(true); - deleteBtn.setOnAction(e -> deleteSelected()); - - Button refreshBtn = new Button("↺ Neu laden"); - refreshBtn.setMaxWidth(Double.MAX_VALUE); - refreshBtn.setOnAction(e -> reload()); - - HBox listButtons = new HBox(6, newBtn, deleteBtn); - HBox.setHgrow(newBtn, Priority.ALWAYS); - HBox.setHgrow(deleteBtn, Priority.ALWAYS); - listButtons.setPadding(new Insets(6, 8, 6, 8)); - - VBox panel = new VBox(6, listView, listButtons, refreshBtn); - VBox.setVgrow(listView, Priority.ALWAYS); - panel.setPadding(new Insets(8)); - panel.setStyle("-fx-background-color: #1a1a2a;"); - return panel; } // ── Form panel ──────────────────────────────────────────────────────────── @@ -152,7 +99,6 @@ public class LocationEditorView extends BorderPane { if (reloading) return; if (old != null) { saveFormToLocation(old); persist(); } current = nw; - deleteBtn.setDisable(nw == null); if (nw != null) { formContainer.setDisable(false); loadForm(nw); } else { formContainer.setDisable(true); clearForm(); } } @@ -187,7 +133,7 @@ public class LocationEditorView extends BorderPane { Location loc = new Location(); loc.setName(new TextReference(PREFIX + "neu_" + System.currentTimeMillis())); locations.add(loc); - listView.getSelectionModel().select(loc); + onSelected(current, loc); } private void deleteSelected() { @@ -196,11 +142,29 @@ public class LocationEditorView extends BorderPane { current = null; clearForm(); formContainer.setDisable(true); - deleteBtn.setDisable(true); persist(); reload(); } + public void createNew() { createLocation(); } + + public void deleteCurrentItem() { deleteSelected(); } + + public void selectById(String id) { + locations.stream() + .filter(loc -> id.equals(loc.getId())) + .findFirst() + .ifPresent(loc -> onSelected(current, loc)); + } + + public java.util.List getIds() { + return locations.stream() + .map(Location::getId) + .filter(id -> id != null && !id.isBlank()) + .sorted() + .collect(java.util.stream.Collectors.toList()); + } + private void persist() { try { LocationIO.save(locations); } catch (IOException e) { diff --git a/blight-editor/src/main/java/de/blight/editor/ui/QuestEditorView.java b/blight-editor/src/main/java/de/blight/editor/ui/QuestEditorView.java index 413b3c0..332cf25 100644 --- a/blight-editor/src/main/java/de/blight/editor/ui/QuestEditorView.java +++ b/blight-editor/src/main/java/de/blight/editor/ui/QuestEditorView.java @@ -31,8 +31,6 @@ public class QuestEditorView extends BorderPane { // ── List ────────────────────────────────────────────────────────────────── - private ListView listView; - private Button deleteBtn; private Quest current = null; // ── Common form fields ──────────────────────────────────────────────────── @@ -64,55 +62,7 @@ public class QuestEditorView extends BorderPane { reload(); - SplitPane split = new SplitPane(buildListPanel(), buildFormPanel()); - split.setDividerPositions(0.28); - setCenter(split); - } - - // ── List panel ──────────────────────────────────────────────────────────── - - private VBox buildListPanel() { - listView = new ListView<>(quests); - listView.setStyle("-fx-background-color: #1a1a2a; -fx-control-inner-background: #1a1a2a;"); - listView.setCellFactory(lv -> new ListCell<>() { - @Override protected void updateItem(Quest q, boolean empty) { - super.updateItem(q, empty); - if (empty || q == null) { setText(null); setStyle(""); return; } - String id = q.getQuestId() != null ? q.getQuestId() : "—"; - String type = QuestIO.typeOf(q); - setText("[" + type + "] " + id); - setStyle("-fx-text-fill: #cccccc;"); - } - }); - listView.getSelectionModel().selectedItemProperty() - .addListener((obs, old, nw) -> onQuestSelected(old, nw)); - VBox.setVgrow(listView, Priority.ALWAYS); - - Button newBtn = new Button("Neue Quest"); - newBtn.setMaxWidth(Double.MAX_VALUE); - newBtn.setStyle("-fx-background-color: #3a6a3a; -fx-text-fill: white;"); - newBtn.setOnAction(e -> createQuest()); - - deleteBtn = new Button("Löschen"); - deleteBtn.setMaxWidth(Double.MAX_VALUE); - deleteBtn.setStyle("-fx-background-color: #6a2a2a; -fx-text-fill: white;"); - deleteBtn.setDisable(true); - deleteBtn.setOnAction(e -> deleteSelected()); - - Button refreshBtn = new Button("↺ Neu laden"); - refreshBtn.setMaxWidth(Double.MAX_VALUE); - refreshBtn.setOnAction(e -> reload()); - - HBox listButtons = new HBox(6, newBtn, deleteBtn); - HBox.setHgrow(newBtn, Priority.ALWAYS); - HBox.setHgrow(deleteBtn, Priority.ALWAYS); - listButtons.setPadding(new Insets(6, 8, 6, 8)); - - VBox panel = new VBox(6, listView, listButtons, refreshBtn); - VBox.setVgrow(listView, Priority.ALWAYS); - panel.setPadding(new Insets(8)); - panel.setStyle("-fx-background-color: #1a1a2a;"); - return panel; + setCenter(buildFormPanel()); } // ── Form panel ──────────────────────────────────────────────────────────── @@ -151,12 +101,15 @@ public class QuestEditorView extends BorderPane { textField = new TextField(); textField.setPromptText("TextReference-Schlüssel"); + textField.setEditable(false); textField.textProperty().addListener((obs, o, n) -> scheduleSave()); descField = new TextField(); descField.setPromptText("TextReference-Schlüssel"); + descField.setEditable(false); descField.textProperty().addListener((obs, o, n) -> scheduleSave()); successField = new TextField(); successField.setPromptText("TextReference-Schlüssel"); + successField.setEditable(false); successField.textProperty().addListener((obs, o, n) -> scheduleSave()); form.getChildren().addAll( @@ -285,7 +238,6 @@ public class QuestEditorView extends BorderPane { } } current = nw; - deleteBtn.setDisable(nw == null); if (nw != null) { formContainer.setDisable(false); loadFormFromQuest(nw); @@ -424,14 +376,13 @@ public class QuestEditorView extends BorderPane { TalkQuest q = new TalkQuest(); q.setQuestId("neue_quest_" + System.currentTimeMillis()); quests.add(q); - listView.getSelectionModel().select(q); + onQuestSelected(current, q); } private void deleteSelected() { - Quest sel = listView.getSelectionModel().getSelectedItem(); - if (sel == null) return; - String qId = sel.getQuestId(); - quests.remove(sel); + if (current == null) return; + String qId = current.getQuestId(); + quests.remove(current); if (qId != null && !qId.isBlank()) { try { QuestIO.delete(qId, questDir); } catch (IOException e) { /* ignore */ } @@ -439,7 +390,6 @@ public class QuestEditorView extends BorderPane { current = null; clearForm(); formContainer.setDisable(true); - deleteBtn.setDisable(true); reload(); } @@ -448,6 +398,25 @@ public class QuestEditorView extends BorderPane { quests.setAll(loaded); } + public void createNew() { createQuest(); } + + public void deleteCurrentItem() { deleteSelected(); } + + public void selectById(String questId) { + quests.stream() + .filter(q -> questId.equals(q.getQuestId())) + .findFirst() + .ifPresent(q -> onQuestSelected(current, q)); + } + + public java.util.List getIds() { + return quests.stream() + .map(Quest::getQuestId) + .filter(id -> id != null && !id.isBlank()) + .sorted() + .collect(java.util.stream.Collectors.toList()); + } + // ── Helpers ─────────────────────────────────────────────────────────────── private static Label sectionTitle(String text) { diff --git a/blight-editor/src/main/java/de/blight/editor/ui/RecipeEditorView.java b/blight-editor/src/main/java/de/blight/editor/ui/RecipeEditorView.java index ccf1049..daad55e 100644 --- a/blight-editor/src/main/java/de/blight/editor/ui/RecipeEditorView.java +++ b/blight-editor/src/main/java/de/blight/editor/ui/RecipeEditorView.java @@ -31,8 +31,6 @@ public class RecipeEditorView extends BorderPane { // ── List ────────────────────────────────────────────────────────────────── private final SortedList sortedRecipes; - private ListView listView; - private Button deleteBtn; private Recipe current = null; private String oldFileId = null; @@ -63,60 +61,7 @@ public class RecipeEditorView extends BorderPane { setStyle("-fx-background-color: #1e1e2e;"); reload(); - SplitPane split = new SplitPane(buildListPanel(), buildFormPanel()); - split.setDividerPositions(0.28); - setCenter(split); - } - - // ── List panel ──────────────────────────────────────────────────────────── - - private VBox buildListPanel() { - listView = new ListView<>(sortedRecipes); - listView.setStyle("-fx-background-color: #1a1a2a; -fx-control-inner-background: #1a1a2a;"); - listView.setCellFactory(lv -> new ListCell<>() { - @Override protected void updateItem(Recipe r, boolean empty) { - super.updateItem(r, empty); - if (empty || r == null) { setText(null); setStyle(""); return; } - String creates = r.getCreates() != null ? safe(r.getCreates().getItemId()) : "—"; - String table = r.getTable() != null && r.getTable().getType() != null - ? r.getTable().getType().name() : "Handwerk"; - setText(creates); - setTooltip(new Tooltip("[" + table + "] erstellt: " + creates)); - String color = tableColor(r.getTable()); - setStyle("-fx-text-fill: #dddddd;" - + " -fx-border-color: transparent transparent transparent " + color + ";" - + " -fx-border-width: 0 0 0 3; -fx-padding: 3 6 3 8;"); - } - }); - listView.getSelectionModel().selectedItemProperty() - .addListener((obs, old, nw) -> onRecipeSelected(old, nw)); - VBox.setVgrow(listView, Priority.ALWAYS); - - Button newBtn = new Button("Neues Rezept"); - newBtn.setMaxWidth(Double.MAX_VALUE); - newBtn.setStyle("-fx-background-color: #3a6a3a; -fx-text-fill: white;"); - newBtn.setOnAction(e -> createRecipe()); - - deleteBtn = new Button("Löschen"); - deleteBtn.setMaxWidth(Double.MAX_VALUE); - deleteBtn.setStyle("-fx-background-color: #6a2a2a; -fx-text-fill: white;"); - deleteBtn.setDisable(true); - deleteBtn.setOnAction(e -> deleteSelected()); - - Button refreshBtn = new Button("↺ Neu laden"); - refreshBtn.setMaxWidth(Double.MAX_VALUE); - refreshBtn.setOnAction(e -> reload()); - - HBox listButtons = new HBox(6, newBtn, deleteBtn); - HBox.setHgrow(newBtn, Priority.ALWAYS); - HBox.setHgrow(deleteBtn, Priority.ALWAYS); - listButtons.setPadding(new Insets(6, 8, 6, 8)); - - VBox panel = new VBox(6, listView, listButtons, refreshBtn); - VBox.setVgrow(listView, Priority.ALWAYS); - panel.setPadding(new Insets(8)); - panel.setStyle("-fx-background-color: #1a1a2a;"); - return panel; + setCenter(buildFormPanel()); } // ── Form panel ──────────────────────────────────────────────────────────── @@ -252,7 +197,6 @@ public class RecipeEditorView extends BorderPane { } current = nw; oldFileId = nw != null ? RecipeIO.fileId(nw) : null; - deleteBtn.setDisable(nw == null); if (nw != null) { formContainer.setDisable(false); loadFormFromRecipe(nw); @@ -355,23 +299,52 @@ public class RecipeEditorView extends BorderPane { creates.setItemId("neues_rezept_" + System.currentTimeMillis()); r.setCreates(creates); recipes.add(r); - listView.getSelectionModel().select(r); + onRecipeSelected(current, r); } private void deleteSelected() { - Recipe sel = listView.getSelectionModel().getSelectedItem(); - if (sel == null) return; - String fid = RecipeIO.fileId(sel); - recipes.remove(sel); + if (current == null) return; + String fid = RecipeIO.fileId(current); + recipes.remove(current); try { RecipeIO.delete(fid, recipeDir); } catch (IOException ignored) {} current = null; oldFileId = null; clearForm(); formContainer.setDisable(true); - deleteBtn.setDisable(true); reload(); } + public void createNew() { createRecipe(); } + + public void deleteCurrentItem() { deleteSelected(); } + + public void selectById(String itemId) { + sortedRecipes.stream() + .filter(r -> r.getCreates() != null && itemId.equals(r.getCreates().getItemId())) + .findFirst() + .ifPresent(r -> onRecipeSelected(current, r)); + } + + public java.util.List getIds() { + return sortedRecipes.stream() + .filter(r -> r.getCreates() != null && r.getCreates().getItemId() != null) + .map(r -> r.getCreates().getItemId()) + .sorted() + .collect(java.util.stream.Collectors.toList()); + } + + public java.util.List getEncodedIds() { + return sortedRecipes.stream() + .filter(r -> r.getCreates() != null && r.getCreates().getItemId() != null) + .map(r -> { + String table = (r.getTable() != null && r.getTable().getType() != null) + ? r.getTable().getType().name() : "NONE"; + return table + ":" + r.getCreates().getItemId(); + }) + .collect(java.util.stream.Collectors.toList()); + } + + private void addComponent() { Dialog dlg = new Dialog<>(); dlg.setTitle("Zutat hinzufügen");