Along the River
The subject of the next JFXstudio Challenge competition is a Holiday. What are you going to do on this holiday? How about having a trip along the river?
The image can be dragged with the mouse cursor, or you can use the cursor keys for navigation. Also the thumbnail can be used to select the most appropriate viewport.
The painting used in the application was created in 12th century by an artist, Zhang Zeduan. This painting, as well as its remake made in the 18th century, is available on the Wikipedia website. You can find more panoramic views on this site. For example,
- Along the River During Qingming Festival (original)
14,203×608
∼2.92 Mb - Along the River During Qingming Festival (remake)
30,000×926
∼7.69 Mb - Plan de Corones, Dolomiti, Italy
20,000×1,580
∼7.84 Mb - London from the Saint Paul's Cathedral
10,450×972
∼2.51 Mb - Vancouver at Dusk
9,833×1,000
∼6.33 Mb - Hong Kong Victoria Harbour
3,495×480
∼574 Kb
Be patient! The application will start only when the image is loaded.
Study the script now.
def url = FX.getArgument("url");
def image = Image {
url: if (url != null)
then "{url}"
else "https://malenkov.dev.java.net/20091120/AlongTheRiver.jpg"
}
The code above is used to load the image by the specified URL. If it is not set, the default image is loaded.
class View extends Rectangle {
def scale = bind scene.width / image.width;
override var width = bind scene.width;
override var height = bind scene.height - image.height * scale;
def maxX = bind image.width - width on replace { setX(x) }
def maxY = bind image.height - height on replace { setY(y) }
function setX(newX: Number) { x = if (newX < 0 or maxX < 0) 0 else if (maxX < newX) maxX else newX }
function setY(newY: Number) { y = if (newY < 0 or maxY < 0) 0 else if (maxY < newY) maxY else newY }
}
The View class is used to specify the viewport bounds. It also processes the window events and adjusts the coordinates automatically.
onMousePressed: function(event) {
if (event.dragX != 0 or event.dragY != 0) {
view.setX(x - event.sceneX);
view.setY(y - event.sceneY)
}
x = event.sceneX + view.x;
y = event.sceneY + view.y
}
The method above is used to handle the onMousePressed and onMouseDragged events of the primary image.
onMouseDragged: function(event) {
view.setX(event.x - view.width / 2);
view.setY(event.y - view.height / 2)
}
The method above is used to handle the onMousePressed and onMouseDragged events of the thumbnail.
def timer = Timeline {
repeatCount: Timeline.INDEFINITE
keyFrames: KeyFrame {
canSkip: true
time: 10ms
action: function() {
if (key == KeyCode.VK_UP) view.setY(view.y - rate) else
if (key == KeyCode.VK_DOWN) view.setY(view.y + rate) else
if (key == KeyCode.VK_LEFT) view.setX(view.x - rate) else
if (key == KeyCode.VK_RIGHT) view.setX(view.x + rate);
rate *= 1.02
}
}
}
This timeline is used to change the coordinates according to the key pressed. Note that the rate increases gradually.
var rate: Number;
var key: KeyCode on replace {
rate = 0.2;
if (key != null)
then timer.play()
else timer.stop()
}
The timeline starts when the key is pressed and stops when the key is released. The rate sets to the initial state.
ImageView {
image: image
cursor: HAND
translateX: bind -view.x
translateY: bind -view.y
focusTraversable: true
onKeyPressed: function(event) { key = event.code }
onKeyReleased: function(event) { key = null }
onMousePressed: view.onMousePressed
onMouseDragged: view.onMousePressed
}
It is the primary image that is moved accordingly to the viewport coordinates. The focusTraversable variable is set to true to handle the key events.
Group {
translateY: bind view.height
transforms: Scale {
x: bind view.scale
y: bind view.scale
}
content: [
ImageView {
image: image
smooth: false
blocksMouse: true
onMousePressed: view.onMouseDragged
onMouseDragged: view.onMouseDragged
}
view
]
}
A thumbnail is created by scaling. The viewport is shown over the thumbnail as a transparent rectangle.
original post
- Login or register to post comments
- Printer-friendly version
- malenkov's blog
- 1296 reads





