Lustige Giraffe zeichnet neue Elemente animiert#4
Conversation
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
There was a problem hiding this comment.
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.
| public Vec2 lerp(Vec2 target, double t) { | ||
| return new Vec2(x + (target.x - x) * t, y + (target.y - y) * t); | ||
| } |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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");
}| Giraffe(GraphicsContext gc) { | ||
| this.gc = gc; | ||
| } |
There was a problem hiding this comment.
Defensive Programmierung (Null-Prüfung):
Der GraphicsContext sollte im Konstruktor auf null geprüft werden, um einen schnellen Ausfall (Fail-Fast) zu garantieren.
| Giraffe(GraphicsContext gc) { | |
| this.gc = gc; | |
| } | |
| Giraffe(GraphicsContext gc) { | |
| this.gc = java.util.Objects.requireNonNull(gc, "gc must not be null"); | |
| } |
| 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); | ||
| } | ||
| }; | ||
| } |
There was a problem hiding this comment.
Defensive Programmierung (Null-Prüfung):
Die Parameter gc und baseRedraw sollten im Konstruktor validiert werden, um NullPointerExceptions zu vermeiden.
| 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); | |
| } | |
| }; | |
| } |
| void play(DrawTarget target, Runnable onComplete) { | ||
| finishNow(); | ||
| this.target = target; | ||
| this.onComplete = onComplete; | ||
| this.startNanos = -1L; | ||
| timer.start(); | ||
| } |
There was a problem hiding this comment.
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.
| 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); |
There was a problem hiding this comment.
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.
| gc.setLineWidth(1.5); | |
| gc.setLineWidth(1.0); |
| @FXML | ||
| private void initialize() { | ||
| renderer = new NetworkRenderer(canvas.getGraphicsContext2D()); | ||
| animator = new GiraffeAnimator(canvas.getGraphicsContext2D(), this::redraw); |
There was a problem hiding this comment.
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.
| 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(); | |
| } | |
| }); |
There was a problem hiding this comment.
💡 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".
| animateDraw(new DrawTarget(DrawTarget.Kind.NODE, p, p), | ||
| () -> network.addNode(x, y)); |
There was a problem hiding this comment.
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
|
Danke für die Reviews! In
Bewusst nicht übernommen: der Null-Check in
Generated by Claude Code |
Ü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.
Die Giraffe steht neben der „Stiftspitze", wackelt leicht mit dem Kopf, und eine gestrichelte Linie verbindet ihre Schnauze mit dem funkelnden Endpunkt.
Änderungen
geometry/Easing.javasmoothStep– sanftes Ein-/Ausblenden der Bewegung (rein, getestet)geometry/Vec2.javalerp– Interpolation entlang einer Strecke (getestet)view/DrawTarget.javaview/Giraffe.javaview/GiraffeAnimator.javaAnimationTimer, rendert pro Frame das wachsende Element + die Giraffe und committet das Element am Ende via Callbackview/NetworkRenderer.javaview/NetworkEditorController.javaFeatures/lustige-giraffe-animation.mdDesign-Entscheidungen
view-Paket; die zugrunde liegende Mathematik (Easing,Vec2.lerp) imgeometry-Paket und ist per Unit-Test abgedeckt.finishNow()sauber ab – kein Element geht verloren.Tests / Qualität
mvn verifyläuft grün – 28 Tests bestehen (neu:EasingTest,Vec2Test).model/geometry).Scene-Snapshot visuell geprüft; live sichtbar mitmvn javafx:run.🤖 Generated with Claude Code
Generated by Claude Code