Kalibriere zoomLevel beim Verlassen des Fit-Modus auf visuellen Skalierungsfaktor
Beim ersten Zoom-Schritt sprang die ImageView abrupt von der visuell sichtbaren Breite (durch fitHeight aspekt-erhaltend verkleinert) auf naturalViewportWidth × zoomLevel, weil zoomLevel mit dem Wert 1.0 nicht zur tatsächlich angezeigten Skalierung passte und gleichzeitig setFitHeight(0) die Höhenrestriktion entfernte. applyZoom() initialisiert nun beim Verlassen des Fit-Modus zoomLevel auf currentVisualWidth / naturalImageWidth (= aktueller visueller Skalierungsfaktor) und setzt naturalViewportWidth auf die natürliche Bildbreite. Damit entspricht zoomLevel = 1.0 der pixel-genauen Originaldarstellung. Der vom Caller intendierte Delta-Schritt wird vor der Kalibrierung gesichert und nach der Kalibrierung auf den neuen zoomLevel re-appliziert, damit applyZoom(zoomLevel + 0.10) nicht unverändert auf den kalibrierten Wert feuert. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+48
-13
@@ -126,8 +126,12 @@ public final class PdfPreviewPane {
|
|||||||
private double zoomAccumulator = 0.0;
|
private double zoomAccumulator = 0.0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Viewport-Breite zum Zeitpunkt des ersten manuellen Zooms; dient als Referenzbreite
|
* Referenzbreite für die manuelle Zoom-Skalierung; gilt
|
||||||
* für alle nachfolgenden Zoomstufen. 0.0 bedeutet Fit-to-View-Modus ist aktiv.
|
* {@code imageView.fitWidth = naturalViewportWidth × zoomLevel} im manuellen
|
||||||
|
* Zoom-Modus. Beim Verlassen des Fit-Modus wird der Wert auf die natürliche
|
||||||
|
* Bildbreite gesetzt, sodass {@code zoomLevel = 1.0} der pixel-genauen
|
||||||
|
* Originalgröße entspricht und {@code zoomLevel} damit gleich dem visuellen
|
||||||
|
* Skalierungsfaktor ist. {@code 0.0} bedeutet Fit-to-View-Modus ist aktiv.
|
||||||
*/
|
*/
|
||||||
private double naturalViewportWidth = 0.0;
|
private double naturalViewportWidth = 0.0;
|
||||||
|
|
||||||
@@ -655,12 +659,25 @@ public final class PdfPreviewPane {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Setzt den Zoomfaktor und verlässt beim ersten Aufruf den Fit-to-View-Modus.
|
* Setzt den Zoomfaktor und verlässt beim ersten Aufruf den Fit-to-View-Modus.
|
||||||
* Die Referenzbreite wird einmalig aus dem Viewport übernommen.
|
* <p>
|
||||||
|
* Beim ersten Aufruf (Wechsel aus dem Fit-Modus) wird {@code zoomLevel} auf
|
||||||
|
* den aktuellen visuellen Skalierungsfaktor kalibriert: aktuelle visuelle
|
||||||
|
* Breite der ImageView (mit {@code preserveRatio} bereits aspekt-korrekt
|
||||||
|
* verkleinert) geteilt durch die natürliche Bildbreite. Damit entspricht
|
||||||
|
* {@code zoomLevel = 1.0} der pixel-genauen Originalgröße, und der erste
|
||||||
|
* Zoom-Schritt addiert sich auf den realen Skalierungsfaktor. Ohne diese
|
||||||
|
* Kalibrierung springt die ImageView abrupt auf {@code Viewport-Breite × 1.10},
|
||||||
|
* weil im Fit-Modus die {@code fitHeight}-Bindung das Bild aspekt-erhaltend
|
||||||
|
* deutlich kleiner zwingt als {@code naturalViewportWidth × 1.0} ergibt.
|
||||||
|
* Da der Caller den Delta-Schritt auf dem alten {@code zoomLevel = 1.0}
|
||||||
|
* berechnet hat, wird er nach der Kalibrierung auf den neuen, kalibrierten
|
||||||
|
* {@code zoomLevel} re-appliziert.
|
||||||
* <p>
|
* <p>
|
||||||
* Beim Wechsel aus dem Fit-to-View-Modus wird die Ansicht auf die Bildmitte
|
* Beim Wechsel aus dem Fit-to-View-Modus wird die Ansicht auf die Bildmitte
|
||||||
* zentriert (H/V = 0.5). Bei weiteren Zoom-Schritten bleibt die aktuelle
|
* zentriert (H/V = 0.5). Bei weiteren Zoom-Schritten bleibt die aktuelle
|
||||||
* Scrollposition erhalten. Ein {@code layout()}-Aufruf vor der Positionswiederherstellung
|
* Scrollposition erhalten. Ein {@code layout()}-Aufruf vor der
|
||||||
* stellt sicher, dass die neuen Inhaltsgrenzen bereits berechnet sind.
|
* Positionswiederherstellung stellt sicher, dass die neuen Inhaltsgrenzen
|
||||||
|
* bereits berechnet sind.
|
||||||
*
|
*
|
||||||
* @param newZoom gewünschter Zoomfaktor, wird auf [{@link #ZOOM_MIN}, {@link #ZOOM_MAX}] begrenzt
|
* @param newZoom gewünschter Zoomfaktor, wird auf [{@link #ZOOM_MIN}, {@link #ZOOM_MAX}] begrenzt
|
||||||
*/
|
*/
|
||||||
@@ -669,17 +686,35 @@ public final class PdfPreviewPane {
|
|||||||
|
|
||||||
boolean wasInFitMode = scrollPane.isFitToWidth();
|
boolean wasInFitMode = scrollPane.isFitToWidth();
|
||||||
if (wasInFitMode) {
|
if (wasInFitMode) {
|
||||||
Bounds viewport = scrollPane.getViewportBounds();
|
Image image = imageView.getImage();
|
||||||
double vpWidth = viewport != null ? viewport.getWidth() : viewStack.getWidth();
|
if (image == null || image.getWidth() <= 0) {
|
||||||
if (vpWidth <= 0) {
|
return; // Kein Bild – Zoom-Kalibrierung nicht möglich
|
||||||
return; // Layout noch nicht abgeschlossen
|
|
||||||
}
|
}
|
||||||
naturalViewportWidth = vpWidth;
|
double naturalImageWidth = image.getWidth();
|
||||||
|
double currentVisualWidth = imageView.getBoundsInLocal().getWidth();
|
||||||
|
if (currentVisualWidth <= 0) {
|
||||||
|
Bounds viewport = scrollPane.getViewportBounds();
|
||||||
|
currentVisualWidth = viewport != null ? viewport.getWidth() : viewStack.getWidth();
|
||||||
|
if (currentVisualWidth <= 0) {
|
||||||
|
return; // Layout noch nicht abgeschlossen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vom Caller intendierten Delta-Schritt vor der Kalibrierung sichern
|
||||||
|
double requestedDelta = newZoom - zoomLevel;
|
||||||
|
|
||||||
|
// zoomLevel auf den aktuellen visuellen Skalierungsfaktor kalibrieren
|
||||||
|
naturalViewportWidth = naturalImageWidth;
|
||||||
|
zoomLevel = currentVisualWidth / naturalImageWidth;
|
||||||
|
|
||||||
|
// effective neu berechnen, weil zoomLevel sich geändert hat
|
||||||
|
effective = Math.min(ZOOM_MAX, Math.max(ZOOM_MIN, zoomLevel + requestedDelta));
|
||||||
|
|
||||||
scrollPane.setFitToWidth(false);
|
scrollPane.setFitToWidth(false);
|
||||||
scrollPane.setFitToHeight(false);
|
scrollPane.setFitToHeight(false);
|
||||||
imageView.fitWidthProperty().unbind();
|
imageView.fitWidthProperty().unbind();
|
||||||
imageView.fitHeightProperty().unbind();
|
imageView.fitHeightProperty().unbind();
|
||||||
// 32d: Mauszeiger signalisiert Pan-Modus
|
// Mauszeiger signalisiert Pan-Modus
|
||||||
viewStack.setCursor(Cursor.OPEN_HAND);
|
viewStack.setCursor(Cursor.OPEN_HAND);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -687,7 +722,7 @@ public final class PdfPreviewPane {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 32b: Beim ersten Zoom Mitte beibehalten; danach aktuelle Position bewahren
|
// Beim ersten Zoom Mitte beibehalten; danach aktuelle Position bewahren
|
||||||
double hval = wasInFitMode ? 0.5 : scrollPane.getHvalue();
|
double hval = wasInFitMode ? 0.5 : scrollPane.getHvalue();
|
||||||
double vval = wasInFitMode ? 0.5 : scrollPane.getVvalue();
|
double vval = wasInFitMode ? 0.5 : scrollPane.getVvalue();
|
||||||
|
|
||||||
@@ -695,7 +730,7 @@ public final class PdfPreviewPane {
|
|||||||
imageView.setFitWidth(naturalViewportWidth * zoomLevel);
|
imageView.setFitWidth(naturalViewportWidth * zoomLevel);
|
||||||
imageView.setFitHeight(0);
|
imageView.setFitHeight(0);
|
||||||
|
|
||||||
// 32b: layout() stellt sicher, dass die neuen Inhaltsgrenzen bekannt sind,
|
// layout() stellt sicher, dass die neuen Inhaltsgrenzen bekannt sind,
|
||||||
// bevor die Scroll-Werte restauriert werden
|
// bevor die Scroll-Werte restauriert werden
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
scrollPane.layout();
|
scrollPane.layout();
|
||||||
|
|||||||
Reference in New Issue
Block a user