Skip to content

Commit 14cf185

Browse files
authored
Merge pull request #3489 from ControlSystemStudio/CSSTUDIO-3341
CSSTUDIO-3341 (1) Bugfix: fix zooming (2) Improve zooming: avoid hiding of OPI-elements behind scrollbars
2 parents d6812f5 + fdb77cd commit 14cf185

File tree

1 file changed

+145
-46
lines changed
  • app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx

1 file changed

+145
-46
lines changed

app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/JFXRepresentation.java

Lines changed: 145 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,22 @@
2525
import java.util.concurrent.TimeoutException;
2626
import java.util.function.Consumer;
2727
import java.util.logging.Level;
28+
import java.util.stream.Collectors;
2829

29-
import org.csstudio.display.builder.model.*;
30+
import javafx.geometry.BoundingBox;
31+
import javafx.geometry.Bounds;
32+
import javafx.geometry.Insets;
33+
import javafx.geometry.Orientation;
34+
import javafx.geometry.Point2D;
35+
import javafx.scene.control.Alert;
36+
import javafx.scene.control.ButtonType;
37+
import javafx.scene.control.ChoiceDialog;
38+
import javafx.scene.control.ScrollBar;
39+
import javafx.scene.control.ScrollPane;
40+
import org.csstudio.display.builder.model.DisplayModel;
41+
import org.csstudio.display.builder.model.UntypedWidgetPropertyListener;
42+
import org.csstudio.display.builder.model.Widget;
43+
import org.csstudio.display.builder.model.WidgetPropertyListener;
3044
import org.csstudio.display.builder.model.properties.PredefinedColorMaps;
3145
import org.csstudio.display.builder.model.properties.WidgetColor;
3246
import org.csstudio.display.builder.representation.ToolkitRepresentation;
@@ -47,18 +61,11 @@
4761
import javafx.collections.ObservableList;
4862
import javafx.embed.swing.SwingFXUtils;
4963
import javafx.event.EventHandler;
50-
import javafx.geometry.Bounds;
51-
import javafx.geometry.Insets;
52-
import javafx.geometry.Point2D;
5364
import javafx.scene.Cursor;
5465
import javafx.scene.Group;
5566
import javafx.scene.Node;
5667
import javafx.scene.Parent;
5768
import javafx.scene.Scene;
58-
import javafx.scene.control.Alert;
59-
import javafx.scene.control.ButtonType;
60-
import javafx.scene.control.ChoiceDialog;
61-
import javafx.scene.control.ScrollPane;
6269
import javafx.scene.image.WritableImage;
6370
import javafx.scene.input.MouseEvent;
6471
import javafx.scene.input.ScrollEvent;
@@ -452,47 +459,139 @@ else if (level_spec.equalsIgnoreCase(Messages.Zoom_Height))
452459
* @param zoom Zoom level: 1.0 for 100%, 0.5 for 50%, ZOOM_ALL, ZOOM_WIDTH, ZOOM_HEIGHT
453460
* @return Zoom level actually used
454461
*/
455-
private double setZoom(double zoom)
462+
private double setZoom(final double zoom)
456463
{
464+
final double zoomToSet;
457465
if (zoom <= 0.0)
458-
{ // Determine zoom to fit outline of display into available space
459-
final Bounds available = model_root.getLayoutBounds();
460-
final Bounds outline = widget_pane.getLayoutBounds();
461-
462-
// 'outline' will wrap the actual widgets when the display
463-
// is larger than the available viewport.
464-
// So it can be used to zoom 'out'.
465-
// But when the viewport is much larger than the widget,
466-
// the JavaFX outline grows to fill the viewport,
467-
// so falling back to the self-declared model width and height
468-
// to zoom 'in'.
469-
// This requires displays to be created with
470-
// correct width/height properties.
471-
final double zoom_x, zoom_y;
472-
if (outline.getWidth() > available.getWidth())
473-
zoom_x = available.getWidth() / outline.getWidth();
474-
else if (model.propWidth().getValue() > 0)
475-
zoom_x = available.getWidth() / model.propWidth().getValue();
476-
else
477-
zoom_x = 1.0;
478-
479-
if (outline.getHeight() > available.getHeight())
480-
zoom_y = available.getHeight() / outline.getHeight();
481-
else if (model.propHeight().getValue() > 0)
482-
zoom_y = available.getHeight() / model.propHeight().getValue();
483-
else
484-
zoom_y = 1.0;
485-
486-
if (zoom == ZOOM_WIDTH)
487-
zoom = zoom_x;
488-
else if (zoom == ZOOM_HEIGHT)
489-
zoom = zoom_y;
490-
else // Assume ZOOM_ALL
491-
zoom = Math.min(zoom_x, zoom_y);
466+
{ // Determine zoom to fit outline of display into available space.
467+
// In order to determine the actual bounds within which an OPI
468+
// can be displayed, model_root.getViewportBounds() is called
469+
// and then the width/height of the scrollbars are added/subtracted
470+
// in order to determine the bounds both _with_ and _without_
471+
// scrollbars being displayed.
472+
boolean hScrollbarVisible = false;
473+
boolean vScrollbarVisible = false;
474+
double vScrollbarWidth = 0.0;
475+
double hHcrollbarHeight = 0.0;
476+
{
477+
List<ScrollBar> scrollbars = model_root.lookupAll(".scroll-bar").stream().filter(node -> node instanceof ScrollBar).map(node -> (ScrollBar) node).collect(Collectors.toUnmodifiableList());
478+
List<ScrollBar> modelRootScrollbars = scrollbars.stream().filter(scrollbar -> scrollbar.getParent() == model_root).collect(Collectors.toUnmodifiableList());
479+
for (ScrollBar scrollBar : modelRootScrollbars) {
480+
if (scrollBar.getOrientation() == Orientation.HORIZONTAL) {
481+
hScrollbarVisible = scrollBar.isVisible();
482+
hHcrollbarHeight = scrollBar.getLayoutBounds().getHeight();
483+
}
484+
else if (scrollBar.isVisible() && scrollBar.getOrientation() == Orientation.VERTICAL) {
485+
vScrollbarVisible = scrollBar.isVisible();
486+
vScrollbarWidth = scrollBar.getLayoutBounds().getWidth();
487+
}
488+
}
489+
}
490+
491+
final Bounds viewportBounds = model_root.getViewportBounds();
492+
final BoundingBox layoutBoundsWithoutScrollbars = new BoundingBox(viewportBounds.getMinX(),
493+
viewportBounds.getMinY(),
494+
Math.max(0.0, viewportBounds.getWidth() + (vScrollbarVisible ? vScrollbarWidth : 0.0)),
495+
Math.max(0.0, viewportBounds.getHeight() + (hScrollbarVisible ? hHcrollbarHeight : 0.0)));
496+
final BoundingBox layoutBoundsWithScrollbars = new BoundingBox(viewportBounds.getMinX(),
497+
viewportBounds.getMinY(),
498+
Math.max(0.0, viewportBounds.getWidth() - (vScrollbarVisible ? 0.0 : vScrollbarWidth)),
499+
Math.max(0.0, viewportBounds.getHeight() - (hScrollbarVisible ? 0.0 : hHcrollbarHeight)));
500+
501+
final double zoomXWithScrollbarsRounded;
502+
final double zoomXWithoutScrollbarsRounded;
503+
final double zoomYWithScrollbarsRounded;
504+
final double zoomYWithoutScrollbarsRounded;
505+
506+
{
507+
final Bounds outline = widget_pane.getLayoutBounds();
508+
// 'outline' will wrap the actual widgets when the display
509+
// is larger than the available viewport.
510+
// So it can be used to zoom 'out'.
511+
// But when the viewport is much larger than the widget,
512+
// the JavaFX outline grows to fill the viewport,
513+
// so falling back to the self-declared model width and height
514+
// to zoom 'in'.
515+
// This requires displays to be created with
516+
// correct width/height properties.
517+
final double zoomXWithoutScrollbars, zoomYWithoutScrollbars;
518+
final double zoomXWithScrollbars, zoomYWithScrollbars;
519+
if (outline.getWidth() > layoutBoundsWithScrollbars.getWidth()) {
520+
zoomXWithoutScrollbars = layoutBoundsWithoutScrollbars.getWidth() / outline.getWidth();
521+
zoomXWithScrollbars = layoutBoundsWithScrollbars.getWidth() / outline.getWidth();
522+
}
523+
else if (model.propWidth().getValue() > 0) {
524+
zoomXWithoutScrollbars = layoutBoundsWithoutScrollbars.getWidth() / model.propWidth().getValue();
525+
zoomXWithScrollbars = layoutBoundsWithScrollbars.getWidth() / model.propWidth().getValue();
526+
}
527+
else {
528+
zoomXWithoutScrollbars = 1.0;
529+
zoomXWithScrollbars = 1.0;
530+
}
531+
532+
if (outline.getHeight() > layoutBoundsWithScrollbars.getHeight()) {
533+
zoomYWithoutScrollbars = layoutBoundsWithoutScrollbars.getHeight() / outline.getHeight();
534+
zoomYWithScrollbars = layoutBoundsWithScrollbars.getHeight() / outline.getHeight();
535+
}
536+
else if (model.propHeight().getValue() > 0) {
537+
zoomYWithoutScrollbars =layoutBoundsWithoutScrollbars.getHeight() / model.propHeight().getValue();
538+
zoomYWithScrollbars = layoutBoundsWithScrollbars.getHeight() / model.propHeight().getValue();
539+
}
540+
else {
541+
zoomYWithoutScrollbars = 1.0;
542+
zoomYWithScrollbars = 1.0;
543+
}
544+
545+
// Round down the zoom-level in order to avoid situations where the
546+
// zoom-level is instead being rounded up, causing a scrollbar to
547+
// be displayed when a scrollbar is not needed.
548+
zoomXWithScrollbarsRounded = Math.floor(zoomXWithScrollbars * 1000.0) / 1000.0;
549+
zoomXWithoutScrollbarsRounded = Math.floor(zoomXWithoutScrollbars * 1000.0) / 1000.0;
550+
zoomYWithScrollbarsRounded = Math.floor(zoomYWithScrollbars * 1000.0) / 1000.0;
551+
zoomYWithoutScrollbarsRounded = Math.floor(zoomYWithoutScrollbars * 1000.0) / 1000.0;
552+
}
553+
554+
if (zoom == ZOOM_WIDTH) {
555+
if (zoomXWithoutScrollbarsRounded * model.propHeight().getValue() > layoutBoundsWithoutScrollbars.getHeight()) {
556+
// Setting zoomToSet to 'zoomXWithoutScrollbarsRounded'
557+
// would result in the horizontal scrollbar being shown.
558+
// Therefore, set zoomToSet = zoomXWithScrollbarsRounded
559+
zoomToSet = zoomXWithScrollbarsRounded;
560+
}
561+
else {
562+
zoomToSet = zoomXWithoutScrollbarsRounded;
563+
}
564+
}
565+
else if (zoom == ZOOM_HEIGHT) {
566+
if (zoomYWithoutScrollbarsRounded * model.propWidth().getValue() > layoutBoundsWithoutScrollbars.getWidth()) {
567+
// Setting zoomToSet to 'zoomYWithoutScrollbarsRounded'
568+
// would result in the vertical scrollbar being shown.
569+
// Therefore, set zoomToSet = zoomYWithScrollbarsRounded:
570+
zoomToSet = zoomYWithScrollbarsRounded;
571+
}
572+
else {
573+
zoomToSet = zoomYWithoutScrollbarsRounded;
574+
}
575+
}
576+
else {
577+
// Assume ZOOM_ALL
578+
if (zoomYWithoutScrollbarsRounded * model.propWidth().getValue() > layoutBoundsWithoutScrollbars.getWidth() ||
579+
zoomXWithoutScrollbarsRounded * model.propHeight().getValue() > layoutBoundsWithoutScrollbars.getHeight()) {
580+
// Setting zoomToSet to 'Math.min(zoomXWithoutScrollbarsRounded, zoomYWithScrollbarsRounded)'
581+
// would result in at least either the vertical or the horizontal scrollbar being shown.
582+
// Therefore, set zoomToSet = Math.min(zoomXWithoutScrollbarsRounded, zoomYWithoutScrollbarsRounded):
583+
zoomToSet = Math.min(zoomXWithScrollbarsRounded, zoomYWithScrollbarsRounded); // Assume ZOOM_ALL
584+
}
585+
else {
586+
zoomToSet = Math.min(zoomXWithoutScrollbarsRounded, zoomYWithoutScrollbarsRounded);
587+
}
588+
}
589+
}
590+
else {
591+
zoomToSet = zoom;
492592
}
493593

494-
widget_parent.getTransforms().setAll(new Scale(zoom, zoom));
495-
widget_pane.getTransforms().setAll(new Scale(zoom, zoom));
594+
widget_pane.getTransforms().setAll(new Scale(zoomToSet, zoomToSet));
496595
// Appears similar to using this API:
497596
// widget_parent.setScaleX(zoom);
498597
// widget_parent.setScaleY(zoom);
@@ -506,7 +605,7 @@ else if (zoom == ZOOM_HEIGHT)
506605
if (isEditMode())
507606
updateModelSizeIndicators();
508607

509-
return zoom;
608+
return zoomToSet;
510609
}
511610

512611
/** @return Zoom factor, 1.0 for 1:1 */

0 commit comments

Comments
 (0)