diff --git a/Features/signale-erstellen-und-gleiskante-zuordnen.md b/Features/signale-erstellen-und-gleiskante-zuordnen.md
new file mode 100644
index 0000000..c140fe3
--- /dev/null
+++ b/Features/signale-erstellen-und-gleiskante-zuordnen.md
@@ -0,0 +1,72 @@
+# Feature: Signale erstellen und einer Gleiskante zuordnen
+
+## Übersicht
+
+- **Status:** Abgeschlossen
+- **Autor:** Robin Radica
+- **Erstellt am:** 2026-07-09
+- **Letzte Änderung:** 2026-07-09
+
+## Ziel / Problemstellung
+
+Der Editor soll es erlauben, **Signale** als Fachdatum zu erstellen und sie einer
+**Gleiskante** (`TrackEdge`) zuzuordnen. Ein Signal hat keine eigenen Koordinaten,
+sondern wird per **Linear Referencing** über eine `EdgeRef` an einer Kante,
+einer relativen Position (0.0 … 1.0) und einer **Seite** (Links/Rechts) verankert.
+Dadurch bleibt es korrekt, wenn Knoten verschoben werden.
+
+Bisher wurde ein Signal immer auf der rechten Seite abgelegt. Anwender:innen
+sollen die Seite beim Platzieren bewusst wählen können.
+
+## Anforderungen
+
+### Funktionale Anforderungen
+
+- [x] Über das Werkzeug **Signal** in der Toolbar lässt sich der Signal-Modus aktivieren.
+- [x] Ein Klick nahe einer Gleiskante erzeugt ein Signal und ordnet es der
+ **nächstgelegenen** Kante an der angeklickten Position zu.
+- [x] Klicks, die keiner Kante nahe genug sind, erzeugen kein Signal.
+- [x] Die **Seite** (Links/Rechts) ist über einen Toolbar-Schalter „Signalseite“
+ wählbar und gilt für neu erstellte Signale (Standard: Rechts).
+- [x] Die Statuszeile zeigt die aktuell gewählte Signalseite an.
+- [x] Signale werden im Canvas dargestellt (Markierung + Label seitlich der Kante).
+
+### Nicht-funktionale Anforderungen
+
+- [x] Domänenlogik (`model`, `geometry`) bleibt frei von JavaFX-Importen und ist
+ unit-getestet; die Seitenwahl ist reine View-Verdrahtung über bestehende,
+ getestete Modelloperationen.
+- [x] Architektur-Guardrails werden eingehalten (max. 3 Parameter, max. 500 LOC/Klasse).
+
+## Nutzer / Zielgruppe
+
+Planer:innen, die ein schematisches Gleisnetz aufbauen und Signale als Fachdaten
+an Gleiskanten anbringen.
+
+## Ein- und Ausgaben
+
+- **Eingaben:** Werkzeug „Signal“, gewählte Signalseite (Links/Rechts),
+ Mausklick auf/nahe einer Gleiskante.
+- **Ausgaben:** Neues `Signal` im `TrackNetwork`, referenziert über eine `EdgeRef`
+ (Kante + Position + Seite); Darstellung im Canvas; aktualisierte Statuszeile.
+
+## Akzeptanzkriterien
+
+- [x] Mit aktivem Signal-Werkzeug erzeugt ein Klick nahe einer Kante genau ein
+ Signal auf dieser Kante.
+- [x] Ist „Links“ bzw. „Rechts“ gewählt, sitzt das neue Signal auf der jeweiligen
+ Seite der Kante.
+- [x] Ein Klick fernab jeder Kante erzeugt kein Signal.
+- [x] `mvn verify` läuft grün (Kompilierung, Tests, Static Analysis).
+
+## Offene Fragen / Annahmen
+
+- Umbenennen, Verschieben und Löschen bestehender Signale sowie das Zuordnen zu
+ einer anderen Kante sind nicht Teil dieses Features und können als Folge-Feature
+ ergänzt werden.
+
+## Referenzen
+
+- Domänenmodell: `Signal`, `EdgeRef`, `Side`, `TrackNetwork.addSignal(...)`
+- Geometrie: `Geometry.nearestParam`, `Geometry.distanceToSegment`, `Geometry.offsetPoint`
+- View: `NetworkEditorController` (Werkzeug- und Seitenwahl), `NetworkRenderer` (Darstellung)
diff --git a/src/main/java/com/example/vibeapp/view/NetworkEditorController.java b/src/main/java/com/example/vibeapp/view/NetworkEditorController.java
index ed8bd35..c135a9c 100644
--- a/src/main/java/com/example/vibeapp/view/NetworkEditorController.java
+++ b/src/main/java/com/example/vibeapp/view/NetworkEditorController.java
@@ -43,6 +43,9 @@ private enum Tool { SELECT, NODE, EDGE, SIGNAL }
@FXML private ToggleButton nodeButton;
@FXML private ToggleButton edgeButton;
@FXML private ToggleButton signalButton;
+ @FXML private ToggleGroup sideGroup;
+ @FXML private ToggleButton leftSideButton;
+ @FXML private ToggleButton rightSideButton;
@FXML private Label statusLabel;
private TrackNetwork network = SampleNetworks.switzerland();
@@ -77,6 +80,11 @@ private void initialize() {
toolGroup.selectedToggleProperty().addListener((obs, old, now) -> onToolChanged(old, now));
toolGroup.selectToggle(selectButton);
+ leftSideButton.setUserData(Side.LEFT);
+ rightSideButton.setUserData(Side.RIGHT);
+ sideGroup.selectedToggleProperty().addListener((obs, old, now) -> onSideChanged(old, now));
+ sideGroup.selectToggle(rightSideButton);
+
canvas.setOnMousePressed(this::onMousePressed);
canvas.setOnMouseDragged(this::onMouseDragged);
canvas.setOnMouseReleased(event -> draggedNode = null);
@@ -98,6 +106,21 @@ private void onToolChanged(Toggle old, Toggle now) {
redraw();
}
+ private void onSideChanged(Toggle old, Toggle now) {
+ // Never allow a fully deselected side; keep the last choice instead.
+ if (now == null) {
+ sideGroup.selectToggle(old);
+ return;
+ }
+ updateStatus();
+ }
+
+ /** The side newly placed signals are attached to, driven by the toolbar. */
+ private Side selectedSide() {
+ Toggle selected = sideGroup.getSelectedToggle();
+ return selected != null ? (Side) selected.getUserData() : Side.RIGHT;
+ }
+
private void onMousePressed(MouseEvent event) {
// Ignore secondary/middle clicks so they can't create elements by accident.
if (event.getButton() != MouseButton.PRIMARY) {
@@ -171,7 +194,7 @@ private void handleSignalClick(double x, double y) {
}
if (closestEdge != null) {
- network.addSignal(closestEdge.id(), closestParam, Side.RIGHT);
+ network.addSignal(closestEdge.id(), closestParam, selectedSide());
}
}
@@ -221,7 +244,8 @@ private void updateStatus() {
case EDGE -> pendingEdgeNode == null
? "Kante: Startknoten anklicken."
: "Kante: Zielknoten anklicken (Start: " + pendingEdgeNode.label() + ").";
- case SIGNAL -> "Signal: Auf eine Gleiskante klicken, um ein Signal zu setzen.";
+ case SIGNAL -> "Signal: Auf eine Gleiskante klicken, um ein Signal zu setzen (Seite: "
+ + (selectedSide() == Side.LEFT ? "Links" : "Rechts") + ").";
};
statusLabel.setText(String.format("%s | Knoten: %d Kanten: %d Signale: %d",
hint, network.nodes().size(), network.edges().size(), network.signals().size()));
diff --git a/src/main/resources/com/example/vibeapp/network-editor.fxml b/src/main/resources/com/example/vibeapp/network-editor.fxml
index a982fc1..792251f 100644
--- a/src/main/resources/com/example/vibeapp/network-editor.fxml
+++ b/src/main/resources/com/example/vibeapp/network-editor.fxml
@@ -17,6 +17,7 @@
+
@@ -26,6 +27,10 @@
+
+
+
+