Behebe Zoom-Sprung und Zentrierung nach Rauszoomen

Bug 1: deltaY vor Akkumulation auf einen Notch-Wert begrenzen.
Plattformspezifische Scroll-Multiplikatoren (Windows-Mausgeschwindigkeit,
hohe DPI-Mäuse) können Werte wie 120 statt 40 liefern. Ohne Normierung
akkumuliert sich ein Überlaufwert, der Folge-Events sofort auslöst.

Bug 2: resetToFitView() setzt nach setFitToWidth(true) explizit
scrollPane.setHvalue(0.5) und setVvalue(0.5) (nach layout()-Aufruf),
damit vorherige Pan-Scroll-Werte die Zentrierung nicht nachwirken.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-05 14:31:29 +02:00
parent 9c27e4df01
commit 15ff034a2b
@@ -631,12 +631,19 @@ public final class PdfPreviewPane {
* Pro Raste (ca. {@value #ZOOM_NOTCH_THRESHOLD} Einheiten) ändert sich der Zoom * Pro Raste (ca. {@value #ZOOM_NOTCH_THRESHOLD} Einheiten) ändert sich der Zoom
* um {@value #ZOOM_STEP}. Pro ScrollEvent wird maximal eine Zoom-Stufe angewendet. * um {@value #ZOOM_STEP}. Pro ScrollEvent wird maximal eine Zoom-Stufe angewendet.
* *
* <p>Der Rohwert von {@code deltaY} wird vor der Akkumulation auf einen
* Notch-Wert ({@value #ZOOM_NOTCH_THRESHOLD}) begrenzt. Plattformspezifische
* Scroll-Multiplikatoren (z. B. Windows-Mausgeschwindigkeit, hohe DPI-Mäuse)
* können sonst Werte wie 120 oder mehr pro Raste liefern, was einen
* Akkumulator-Überlauf in Folge-Events verursacht.
*
* @param deltaY vertikaler Scroll-Delta des {@link ScrollEvent} * @param deltaY vertikaler Scroll-Delta des {@link ScrollEvent}
*/ */
private void accumulateAndApplyZoomDelta(double deltaY) { private void accumulateAndApplyZoomDelta(double deltaY) {
zoomAccumulator += deltaY; // Normierung: maximal einen Notch-Wert pro Event akkumulieren, um
// 32a: Pro Mausrad-Raste genau eine Zoom-Stufe (if statt while verhindert // plattformspezifische deltaY-Überhöhungen (z. B. 120 statt 40) abzufangen
// mehrfaches Springen bei großen deltaY-Werten) double capped = Math.signum(deltaY) * Math.min(Math.abs(deltaY), ZOOM_NOTCH_THRESHOLD);
zoomAccumulator += capped;
if (zoomAccumulator >= ZOOM_NOTCH_THRESHOLD) { if (zoomAccumulator >= ZOOM_NOTCH_THRESHOLD) {
zoomAccumulator -= ZOOM_NOTCH_THRESHOLD; zoomAccumulator -= ZOOM_NOTCH_THRESHOLD;
applyZoom(Math.min(ZOOM_MAX, zoomLevel + ZOOM_STEP)); applyZoom(Math.min(ZOOM_MAX, zoomLevel + ZOOM_STEP));
@@ -700,12 +707,18 @@ public final class PdfPreviewPane {
/** /**
* Setzt Zoom, Akkumulator und Pan-Zustand zurück und reaktiviert den Fit-to-View-Modus. * Setzt Zoom, Akkumulator und Pan-Zustand zurück und reaktiviert den Fit-to-View-Modus.
* Wird beim Laden einer neuen Datei und beim Leeren der Komponente aufgerufen. * Wird beim Laden einer neuen Datei und beim Leeren der Komponente aufgerufen.
*
* <p>Nach dem Reaktivieren von {@code fitToWidth} werden H- und V-Scroll-Werte
* explizit auf 0.5 gesetzt (nach einem {@code layout()}-Aufruf, damit die neuen
* Inhaltsgrenzen bekannt sind). Ohne diesen Schritt trägt der ScrollPane ggf. noch
* die H/V-Werte aus dem vorherigen Zoom-/Pan-Zustand und zeigt die PDF nach dem
* Reset nicht zentriert an.
*/ */
private void resetToFitView() { private void resetToFitView() {
zoomLevel = 1.0; zoomLevel = 1.0;
zoomAccumulator = 0.0; zoomAccumulator = 0.0;
naturalViewportWidth = 0.0; naturalViewportWidth = 0.0;
// 32e: Pan-Zustand und Mauszeiger zurücksetzen // Pan-Zustand und Mauszeiger zurücksetzen
panStartX = -1; panStartX = -1;
panStartY = -1; panStartY = -1;
viewStack.setCursor(null); viewStack.setCursor(null);
@@ -714,6 +727,13 @@ public final class PdfPreviewPane {
imageView.fitHeightProperty().bind(viewStack.heightProperty()); imageView.fitHeightProperty().bind(viewStack.heightProperty());
scrollPane.setFitToWidth(true); scrollPane.setFitToWidth(true);
scrollPane.setFitToHeight(true); scrollPane.setFitToHeight(true);
// Zentrierung sicherstellen: nach layout() H/V auf Mitte setzen,
// damit verbleibende Scroll-Werte aus dem Zoom-/Pan-Modus nicht nachwirken
Platform.runLater(() -> {
scrollPane.layout();
scrollPane.setHvalue(0.5);
scrollPane.setVvalue(0.5);
});
} }
} }