Skip to main content

Along the River

Posted by malenkov on November 20, 2009 at 6:24 AM PST

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,

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 ""

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)
    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

A thumbnail is created by scaling. The viewport is shown over the thumbnail as a transparent rectangle.

original post

Related Topics >>