Skip to content

Lustige Giraffe zeichnet neue Elemente animiert#4

Merged
rradica merged 2 commits into
mainfrom
claude/giraffe-animation-new-elements-6uibqr
Jul 9, 2026
Merged

Lustige Giraffe zeichnet neue Elemente animiert#4
rradica merged 2 commits into
mainfrom
claude/giraffe-animation-new-elements-6uibqr

Conversation

@rradica

@rradica rradica commented Jul 9, 2026

Copy link
Copy Markdown
Owner

Überblick

Beim Anlegen neuer Netzelemente erscheint jetzt eine kleine, lustige Cartoon-Giraffe, die das Element animiert zeichnet, bevor es ins Modell übernommen wird. Statt schlagartig zu erscheinen, entsteht das Element vor den Augen der Nutzerin.

  • Knoten → wachsender Kreis an der Klickposition
  • Kante → Linie wird vom Start- zum Zielknoten gezogen
  • Signal → Signalarm wächst von der Gleiskante bis zum Marker

Die Giraffe steht neben der „Stiftspitze", wackelt leicht mit dem Kopf, und eine gestrichelte Linie verbindet ihre Schnauze mit dem funkelnden Endpunkt.

Änderungen

Datei Zweck
geometry/Easing.java smoothStep – sanftes Ein-/Ausblenden der Bewegung (rein, getestet)
geometry/Vec2.java lerp – Interpolation entlang einer Strecke (getestet)
view/DrawTarget.java beschreibt die Geometrie des neuen Elements (Kind + Start/Ende)
view/Giraffe.java zeichnet die Cartoon-Giraffe mit Canvas-Primitiven
view/GiraffeAnimator.java AnimationTimer, rendert pro Frame das wachsende Element + die Giraffe und committet das Element am Ende via Callback
view/NetworkRenderer.java wenige Konstanten paketweit sichtbar gemacht, damit die Vorschau Geometrie/Palette exakt trifft
view/NetworkEditorController.java löst die Animation beim Erstellen von Knoten/Kante/Signal aus
Features/lustige-giraffe-animation.md Feature-Dokumentation

Design-Entscheidungen

  • Domäne bleibt JavaFX-frei: Die Animation liegt vollständig im view-Paket; die zugrunde liegende Mathematik (Easing, Vec2.lerp) im geometry-Paket und ist per Unit-Test abgedeckt.
  • Element wird erst nach der Animation committet (Commit-Callback), sodass es tatsächlich „gezeichnet" erscheint statt sofort dazustehen.
  • Unterbrechungen sicher: Schnelles Nachklicken, Werkzeugwechsel sowie „Leeren"/„Beispielnetz" schließen die laufende Animation über finishNow() sauber ab – kein Element geht verloren.

Tests / Qualität

  • mvn verify läuft grün – 28 Tests bestehen (neu: EasingTest, Vec2Test).
  • Alle Guardrails erfüllt: Checkstyle (max. 3 Parameter / 500 LOC), PMD, SpotBugs, JaCoCo (≥ 80 % auf model/geometry).
  • Die Giraffe wurde headless über einen Scene-Snapshot visuell geprüft; live sichtbar mit mvn javafx:run.

🤖 Generated with Claude Code


Generated by Claude Code

Beim Anlegen von Knoten, Kanten und Signalen erscheint eine kleine
Cartoon-Giraffe, die das neue Element animiert zeichnet, bevor es ins
Modell übernommen wird.

- geometry: Easing.smoothStep und Vec2.lerp als reine, getestete Helfer
- view/Giraffe: zeichnet die Giraffe auf dem Canvas
- view/GiraffeAnimator: AnimationTimer, der das wachsende Element und die
  Giraffe pro Frame rendert und das Element per Callback committet
- view/DrawTarget: Geometrie des neuen Elements fuer die Animation
- NetworkEditorController loest die Animation beim Erstellen aus
- Feature-Dokumentation unter Features/

mvn verify (Checkstyle, PMD, SpotBugs, JaCoCo, 28 Tests) laeuft gruen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W7AffvFCKjCYp8nSuHRVGu

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a playful feature where a cartoon giraffe animates the creation of new track network elements (nodes, edges, and signals) before they are committed to the model. The changes include new animation and rendering classes (Giraffe, GiraffeAnimator, DrawTarget), easing and interpolation math helpers, and integration into the editor controller. The review feedback focuses on defensive programming by recommending null-checks across several classes, correcting a stroke width inconsistency for signals to ensure visual alignment with the renderer, and managing the AnimationTimer lifecycle in the controller to prevent potential memory leaks.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +22 to +24
public Vec2 lerp(Vec2 target, double t) {
return new Vec2(x + (target.x - x) * t, y + (target.y - y) * t);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Defensive Programmierung (Null-Prüfung):

Um eine NullPointerException bei der Dereferenzierung von target zu verhindern, sollte ein expliziter Null-Check hinzugefügt werden, wie es in den Qualitätsrichtlinien für Java-Projekte empfohlen wird.

    public Vec2 lerp(Vec2 target, double t) {
        java.util.Objects.requireNonNull(target, "target must not be null");
        return new Vec2(x + (target.x - x) * t, y + (target.y - y) * t);
    }

* two nodes, and a {@link Kind#SIGNAL} runs from its base on the edge to its
* offset marker.
*/
record DrawTarget(Kind kind, Vec2 start, Vec2 end) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Defensive Programmierung (Validierung im Record):

Da Java-Records standardmäßig null-Werte für ihre Felder erlauben, sollten wir einen kompakten Konstruktor hinzufügen, um sicherzustellen, dass kind, start und end niemals null sind. Dies verhindert unerwartete NullPointerExceptions zur Laufzeit im GiraffeAnimator.

record DrawTarget(Kind kind, Vec2 start, Vec2 end) {

    DrawTarget {
        java.util.Objects.requireNonNull(kind, "kind must not be null");
        java.util.Objects.requireNonNull(start, "start must not be null");
        java.util.Objects.requireNonNull(end, "end must not be null");
    }

Comment on lines +25 to +27
Giraffe(GraphicsContext gc) {
this.gc = gc;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Defensive Programmierung (Null-Prüfung):

Der GraphicsContext sollte im Konstruktor auf null geprüft werden, um einen schnellen Ausfall (Fail-Fast) zu garantieren.

Suggested change
Giraffe(GraphicsContext gc) {
this.gc = gc;
}
Giraffe(GraphicsContext gc) {
this.gc = java.util.Objects.requireNonNull(gc, "gc must not be null");
}

Comment on lines +37 to +47
GiraffeAnimator(GraphicsContext gc, Runnable baseRedraw) {
this.gc = gc;
this.baseRedraw = baseRedraw;
this.giraffe = new Giraffe(gc);
this.timer = new AnimationTimer() {
@Override
public void handle(long now) {
frame(now);
}
};
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Defensive Programmierung (Null-Prüfung):

Die Parameter gc und baseRedraw sollten im Konstruktor validiert werden, um NullPointerExceptions zu vermeiden.

Suggested change
GiraffeAnimator(GraphicsContext gc, Runnable baseRedraw) {
this.gc = gc;
this.baseRedraw = baseRedraw;
this.giraffe = new Giraffe(gc);
this.timer = new AnimationTimer() {
@Override
public void handle(long now) {
frame(now);
}
};
}
GiraffeAnimator(GraphicsContext gc, Runnable baseRedraw) {
this.gc = java.util.Objects.requireNonNull(gc, "gc must not be null");
this.baseRedraw = java.util.Objects.requireNonNull(baseRedraw, "baseRedraw must not be null");
this.giraffe = new Giraffe(gc);
this.timer = new AnimationTimer() {
@Override
public void handle(long now) {
frame(now);
}
};
}

Comment on lines +54 to +60
void play(DrawTarget target, Runnable onComplete) {
finishNow();
this.target = target;
this.onComplete = onComplete;
this.startNanos = -1L;
timer.start();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Defensive Programmierung (Null-Prüfung):

Die Parameter target und onComplete sollten vor der Verwendung auf null geprüft werden, um einen sicheren Zustand des Animators zu gewährleisten.

Suggested change
void play(DrawTarget target, Runnable onComplete) {
finishNow();
this.target = target;
this.onComplete = onComplete;
this.startNanos = -1L;
timer.start();
}
void play(DrawTarget target, Runnable onComplete) {
java.util.Objects.requireNonNull(target, "target must not be null");
java.util.Objects.requireNonNull(onComplete, "onComplete must not be null");
finishNow();
this.target = target;
this.onComplete = onComplete;
this.startNanos = -1L;
timer.start();
}

Vec2 base = target.start();
Vec2 tip = base.lerp(target.end(), t);
gc.setStroke(NetworkRenderer.SIGNAL);
gc.setLineWidth(1.5);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Visuelle Konsistenz (Strichstärke):

In NetworkRenderer wird die Strichstärke für Signalarme auf 1.0 gesetzt (gc.setLineWidth(1.0)). Hier im GiraffeAnimator wird jedoch 1.5 verwendet. Um eine exakte visuelle Übereinstimmung zwischen der Vorschau während der Animation und dem final gerenderten Element zu gewährleisten, sollte die Strichstärke auf 1.0 angepasst werden.

Suggested change
gc.setLineWidth(1.5);
gc.setLineWidth(1.0);

@FXML
private void initialize() {
renderer = new NetworkRenderer(canvas.getGraphicsContext2D());
animator = new GiraffeAnimator(canvas.getGraphicsContext2D(), this::redraw);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Ressourcen-Management (Speicherlecks verhindern):

Ein AnimationTimer läuft in JavaFX standardmäßig weiter, selbst wenn das Steuerelement oder die Scene nicht mehr aktiv oder sichtbar sind. Dies kann zu Speicherlecks und unnötiger CPU-Auslastung im Hintergrund führen.

Durch das Hinzufügen eines Listeners auf die sceneProperty des Canvas können wir sicherstellen, dass die laufende Animation sofort beendet und der Timer gestoppt wird, sobald das Canvas von der Scene getrennt wird.

Suggested change
animator = new GiraffeAnimator(canvas.getGraphicsContext2D(), this::redraw);
animator = new GiraffeAnimator(canvas.getGraphicsContext2D(), this::redraw);
canvas.sceneProperty().addListener((obs, oldScene, newScene) -> {
if (newScene == null) {
animator.finishNow();
}
});

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 251a801905

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +160 to +161
animateDraw(new DrawTarget(DrawTarget.Kind.NODE, p, p),
() -> network.addNode(x, y));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Finish pending node animations before hit-testing

When the NODE tool is clicked twice at the same coordinates before the first 1.1s animation completes, the findNodeAt check runs while the first node is still absent from network; animator.play() then finishes and commits that first node but still schedules this second commit, creating overlapping duplicate nodes despite the empty-space guard. Finish any pending animation before the hit-test, or include the pending node in the check.

Useful? React with 👍 / 👎.

- Korrektheit (Codex): laufende Zeichen-Animation vor dem Trefferttest in
  onMousePressed abschliessen, damit ein schneller Doppelklick den noch nicht
  committeten Knoten sieht und keine ueberlappenden Duplikate entstehen.
- Visuelle Konsistenz (Gemini): Signal-Strichstaerke in der Vorschau von 1.5
  auf 1.0 gesenkt, passend zum NetworkRenderer.
- Ressourcen (Gemini): AnimationTimer wird gestoppt, sobald das Canvas die
  Scene verlaesst (sceneProperty-Listener).
- Fail-Fast: Objects.requireNonNull in Giraffe, GiraffeAnimator (Konstruktor
  und play) sowie im DrawTarget-Record.

mvn verify (28 Tests, Checkstyle, PMD, SpotBugs, JaCoCo) laeuft gruen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W7AffvFCKjCYp8nSuHRVGu

rradica commented Jul 9, 2026

Copy link
Copy Markdown
Owner Author

Danke für die Reviews! In a57cab4 umgesetzt:

  • Korrektheit (Codex, P2): animator.finishNow() läuft jetzt zu Beginn von onMousePressed, also vor dem Trefferttest. Ein schneller Doppelklick auf dieselbe Stelle sieht damit den noch nicht committeten Knoten und die „leerer Raum"-Prüfung verhindert das Duplikat. Guter Fund.
  • Visuelle Konsistenz (Gemini): Signal-Strichstärke in der Vorschau von 1.51.0, passend zu NetworkRenderer.
  • Ressourcen-Management (Gemini): AnimationTimer wird über einen sceneProperty-Listener gestoppt, sobald das Canvas die Scene verlässt.
  • Fail-Fast (Gemini): Objects.requireNonNull in Giraffe, GiraffeAnimator (Konstruktor + play) und im DrawTarget-Record.

Bewusst nicht übernommen: der Null-Check in Vec2.lerp. Vec2 ist ein absichtlich minimaler Geometrie-Record; die Schwester-Methode subtract prüft ebenfalls nicht auf null, und lerp liegt auf dem Zeichen-Hot-Path. Ein einzelner Check dort wäre inkonsistent, ohne echten Mehrwert (die Aufrufer im view-Paket übergeben nie null).

mvn verify (28 Tests, Checkstyle, PMD, SpotBugs, JaCoCo) ist grün.


Generated by Claude Code

@rradica rradica merged commit 20a1239 into main Jul 9, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants