Commit | Line | Data |
---|---|---|
c879c4db AM |
1 | package org.lttng.scope.tmf2.views.ui.jfx.examples; |
2 | ||
3 | import java.net.MalformedURLException; | |
4 | ||
5 | import org.eclipse.jdt.annotation.NonNullByDefault; | |
6 | ||
7 | import javafx.application.Application; | |
8 | import javafx.geometry.Bounds; | |
9 | import javafx.geometry.Point2D; | |
10 | import javafx.geometry.Pos; | |
11 | import javafx.scene.Group; | |
12 | import javafx.scene.Node; | |
13 | import javafx.scene.Scene; | |
14 | import javafx.scene.canvas.Canvas; | |
15 | import javafx.scene.canvas.GraphicsContext; | |
16 | import javafx.scene.control.Label; | |
17 | import javafx.scene.control.ScrollPane; | |
18 | import javafx.scene.control.SplitPane; | |
19 | import javafx.scene.layout.Pane; | |
20 | import javafx.scene.layout.Region; | |
21 | import javafx.scene.layout.StackPane; | |
22 | import javafx.scene.paint.Color; | |
23 | import javafx.scene.shape.Circle; | |
24 | import javafx.stage.Stage; | |
25 | ||
26 | @NonNullByDefault({}) | |
27 | public class ZoomExample extends Application { | |
28 | ||
29 | public static Region createContent() { | |
30 | double width = 1000; | |
31 | double height = 1000; | |
32 | ||
33 | Canvas canvas = new Canvas(width, height); | |
34 | GraphicsContext gc = canvas.getGraphicsContext2D(); | |
35 | ||
36 | gc.setFill(Color.LIGHTGREY); | |
37 | gc.fillRect(0, 0, width, height); | |
38 | ||
39 | gc.setStroke(Color.BLUE); | |
40 | gc.beginPath(); | |
41 | ||
42 | for (int i = 50; i < width; i += 50) { | |
43 | gc.moveTo(i, 0); | |
44 | gc.lineTo(i, height); | |
45 | } | |
46 | ||
47 | for (int i = 50; i < height; i += 50) { | |
48 | gc.moveTo(0, i); | |
49 | gc.lineTo(width, i); | |
50 | } | |
51 | gc.stroke(); | |
52 | ||
53 | Pane content = new Pane( | |
54 | new Circle(50, 50, 20), | |
55 | new Circle(120, 90, 20, Color.RED), | |
56 | new Circle(200, 70, 20, Color.GREEN) | |
57 | ); | |
58 | ||
59 | StackPane result = new StackPane(canvas, content); | |
60 | result.setAlignment(Pos.TOP_LEFT); | |
61 | ||
62 | class DragData { | |
63 | ||
64 | double startX; | |
65 | double startY; | |
66 | double startLayoutX; | |
67 | double startLayoutY; | |
68 | Node dragTarget; | |
69 | } | |
70 | ||
71 | DragData dragData = new DragData(); | |
72 | ||
73 | content.setOnMousePressed(evt -> { | |
74 | if (evt.getTarget() != content) { | |
75 | // initiate drag gesture, if a child of content receives the | |
76 | // event to prevent ScrollPane from panning. | |
77 | evt.consume(); | |
78 | evt.setDragDetect(true); | |
79 | } | |
80 | }); | |
81 | ||
82 | content.setOnDragDetected(evt -> { | |
83 | Node n = (Node) evt.getTarget(); | |
84 | if (n != content) { | |
85 | // set start paremeters | |
86 | while (n.getParent() != content) { | |
87 | n = n.getParent(); | |
88 | } | |
89 | dragData.startX = evt.getX(); | |
90 | dragData.startY = evt.getY(); | |
91 | dragData.startLayoutX = n.getLayoutX(); | |
92 | dragData.startLayoutY = n.getLayoutY(); | |
93 | dragData.dragTarget = n; | |
94 | n.startFullDrag(); | |
95 | evt.consume(); | |
96 | } | |
97 | }); | |
98 | ||
99 | // stop dragging when mouse is released | |
100 | content.setOnMouseReleased(evt -> dragData.dragTarget = null); | |
101 | ||
102 | content.setOnMouseDragged(evt -> { | |
103 | if (dragData.dragTarget != null) { | |
104 | // move dragged node | |
105 | dragData.dragTarget.setLayoutX(evt.getX() + dragData.startLayoutX - dragData.startX); | |
106 | dragData.dragTarget.setLayoutY(evt.getY() + dragData.startLayoutY - dragData.startY); | |
107 | // Point2D p = new Point2D(evt.getX(), evt.getY()); | |
108 | evt.consume(); | |
109 | } | |
110 | }); | |
111 | ||
112 | return result; | |
113 | } | |
114 | ||
115 | @Override | |
116 | public void start(Stage primaryStage) throws MalformedURLException { | |
117 | Region zoomTarget = createContent(); | |
118 | zoomTarget.setPrefSize(1000, 1000); | |
119 | zoomTarget.setOnDragDetected(evt -> { | |
120 | Node target = (Node) evt.getTarget(); | |
121 | while (target != zoomTarget && target != null) { | |
122 | target = target.getParent(); | |
123 | } | |
124 | if (target != null) { | |
125 | target.startFullDrag(); | |
126 | } | |
127 | }); | |
128 | ||
129 | Group group = new Group(zoomTarget); | |
130 | ||
131 | // stackpane for centering the content, in case the ScrollPane viewport | |
132 | // is larget than zoomTarget | |
133 | StackPane content = new StackPane(group); | |
134 | group.layoutBoundsProperty().addListener((observable, oldBounds, newBounds) -> { | |
135 | // keep it at least as large as the content | |
136 | content.setMinWidth(newBounds.getWidth()); | |
137 | content.setMinHeight(newBounds.getHeight()); | |
138 | }); | |
139 | ||
140 | ScrollPane scrollPane = new ScrollPane(content); | |
141 | scrollPane.setPannable(true); | |
142 | scrollPane.viewportBoundsProperty().addListener((observable, oldBounds, newBounds) -> { | |
143 | // use vieport size, if not too small for zoomTarget | |
144 | content.setPrefSize(newBounds.getWidth(), newBounds.getHeight()); | |
145 | }); | |
146 | ||
147 | content.setOnScroll(evt -> { | |
148 | if (evt.isControlDown()) { | |
149 | evt.consume(); | |
150 | ||
151 | final double zoomFactor = evt.getDeltaY() > 0 ? 1.2 : 1 / 1.2; | |
152 | ||
153 | Bounds groupBounds = group.getLayoutBounds(); | |
154 | final Bounds viewportBounds = scrollPane.getViewportBounds(); | |
155 | ||
156 | // calculate pixel offsets from [0, 1] range | |
157 | double valX = scrollPane.getHvalue() * (groupBounds.getWidth() - viewportBounds.getWidth()); | |
158 | double valY = scrollPane.getVvalue() * (groupBounds.getHeight() - viewportBounds.getHeight()); | |
159 | ||
160 | // convert content coordinates to zoomTarget coordinates | |
161 | Point2D posInZoomTarget = zoomTarget.parentToLocal(group.parentToLocal(new Point2D(evt.getX(), evt.getY()))); | |
162 | ||
163 | // calculate adjustment of scroll position (pixels) | |
164 | Point2D adjustment = zoomTarget.getLocalToParentTransform().deltaTransform(posInZoomTarget.multiply(zoomFactor - 1)); | |
165 | ||
166 | // do the resizing | |
167 | zoomTarget.setScaleX(zoomFactor * zoomTarget.getScaleX()); | |
168 | zoomTarget.setScaleY(zoomFactor * zoomTarget.getScaleY()); | |
169 | ||
170 | // refresh ScrollPane scroll positions & content bounds | |
171 | scrollPane.layout(); | |
172 | ||
173 | // convert back to [0, 1] range | |
174 | // (too large/small values are automatically corrected by ScrollPane) | |
175 | groupBounds = group.getLayoutBounds(); | |
176 | scrollPane.setHvalue((valX + adjustment.getX()) / (groupBounds.getWidth() - viewportBounds.getWidth())); | |
177 | scrollPane.setVvalue((valY + adjustment.getY()) / (groupBounds.getHeight() - viewportBounds.getHeight())); | |
178 | } | |
179 | }); | |
180 | ||
181 | StackPane left = new StackPane(new Label("Left Menu")); | |
182 | SplitPane root = new SplitPane(left, scrollPane); | |
183 | ||
184 | Scene scene = new Scene(root, 800, 600); | |
185 | ||
186 | primaryStage.setScene(scene); | |
187 | primaryStage.show(); | |
188 | } | |
189 | ||
190 | public static void main(String[] args) { | |
191 | launch(args); | |
192 | } | |
193 | ||
194 | } |