text:
<repo_name>emrepara/lvl<file_sep>/qml/scenes/dialogs/UnpublishDialog.qml
import QtQuick 2.0
import VPlay 2.0
import "../../common"

DialogBase {
  id: unpublishDialog

  property var levelData

  Text {
    anchors.centerIn: parent

    text: "Do you really want to unpublish this level?"

    color: "white"
  }

  // Buttons ------------------------------------------

  PlatformerTextButton {
    id: okButton

    screenText: "Ok"

    width: 100

    anchors.bottom: parent.bottom
    anchors.bottomMargin: 50
    anchors.left: parent.left
    anchors.leftMargin: 100

    onClicked: {
      // unpublish this level
      levelEditor.unpublishLevel(levelData)

      // close dialog
      unpublishDialog.opacity = 0
    }
  }

  PlatformerTextButton {
    id: cancelButton

    screenText: "Cancel"

    width: 100

    anchors.bottom: parent.bottom
    anchors.bottomMargin: 50
    anchors.right: parent.right
    anchors.rightMargin: 100

    // when clicking "cancel" we only close the dialog
    onClicked: unpublishDialog.opacity = 0
  }
}

<file_sep>/qml/entities/GroundDirt.qml
import QtQuick 2.0
import VPlay 2.0

Ground {
  variationType: "dirt"

  image.source: "../../assets/ground/ground_dirt.png"
}

<file_sep>/qml/entities/Star.qml
import QtQuick 2.0
import VPlay 2.0
import "../editorElements"

PlatformerEntityBaseDraggable {
  id: star
  entityType: "star"

  // this property is true when the player collected the star
  property bool collected: false

  // when the star is collected, it shouldn't be visible anymore
  image.visible: !collected

  // define colliderComponent for collision detection while dragging
  colliderComponent: collider

  // set image
  image.source: "../../assets/powerups/star.png"

  CircleCollider {
    id: collider

    // make the collider a little smaller than the sprite
    radius: parent.width / 2 - 1

    // center collider
    x: 1
    y: 1

    // disable collider when star is collected
    active: !collected

    // the collider is static (shouldn't move) and only test
    // for collisions
    bodyType: Body.Static
    collisionTestingOnlyMode: true

    // Category6: powerup
    categories: Box.Category6
    // Category1: player body
    collidesWith: Box.Category1
  }

  // set collected to true
  function collect() {
    console.debug("collect star")
    star.collected = true
  }

  // reset star
  function reset() {
    star.collected = false
  }
}
<file_sep>/qml/entities/Platform.qml
import QtQuick 2.0
import VPlay 2.0
import "../editorElements"

PlatformerEntityBaseDraggable {
  id: platform
  entityType: "platform"

  // set the size to the sprite's size
  width: image.width
  height: image.height

  // define colliderComponent for collision detection while dragging
  colliderComponent: collider

  // set image
  image.source: "../../assets/platform/platform.png"

  BoxCollider {
    id: collider

    // set size and position to fit the sprite
    width: parent.width
    height: parent.height / 2

    anchors.horizontalCenter: parent.horizontalCenter
    anchors.top: parent.top

    // this entity shouldn't move
    bodyType: Body.Static

    // Category5: solids
    categories: Box.Category5
    // Category1: player body, Category2: player feet sensor,
    // Category3: opponent body, Category4: opponent sensor
    collidesWith: Box.Category1 | Box.Category2 | Box.Category3 | Box.Category4
  }
}
<file_sep>/qml/entities/Spikes.qml
import QtQuick 2.0
import VPlay 2.0
import "../editorElements"

PlatformerEntityBaseDraggable {
  id: spikes
  entityType: "spikes"
  variationType: "up"

  // set the size to the sprite's size
  width: image.width
  height: image.height

  // define colliderComponent for collision detection while dragging
  colliderComponent: collider

  // set image
  image.source: "../../assets/spikes/spikes.png"

  BoxCollider {
    id: collider

    // set the collider's size to fit to the sprite
    width: parent.width
    height: parent.height / 2 + 1

    // set position
    anchors.horizontalCenter: parent.horizontalCenter
    anchors.bottom: parent.bottom

    // the bodyType is static
    bodyType: Body.Static

    // the collider should not be active in edit mode
    active: !inLevelEditingMode

    // Category5: solids
    categories: Box.Category5
    // Category1: player body, Category2: player feet sensor,
    // Category3: opponent body, Category4: opponent sensor
    collidesWith: Box.Category1 | Box.Category2 | Box.Category3 | Box.Category4
  }
}

<file_sep>/qml/entities/Spikeball.qml
import QtQuick 2.0
import VPlay 2.0
import "../editorElements"

PlatformerEntityBaseDraggable {
  id: spikeball
  entityType: "spikes"
  variationType: "ball"

  // set the size to the sprite's size
  width: image.width
  height: image.height

  // define colliderComponent for collision detection while dragging
  colliderComponent: collider

  // set image
  image.source: "../../assets/spikeball/spikeball.png"

  CircleCollider {
    id: collider

    // make the collider a little smaller than the sprite
    radius: parent.width / 2 - 2
    x: 2
    y: 2

    // the bodyType is static
    bodyType: Body.Static

    // the collider should not be active in edit mode
    active: !inLevelEditingMode

    // Category5: solids
    categories: Box.Category5
    // Category1: player body, Category2: player feet sensor,
    // Category3: opponent body, Category4: opponent sensor
    collidesWith: Box.Category1 | Box.Category2 | Box.Category3 | Box.Category4
  }
}

<file_sep>/qml/scenes/MenuScene.qml
import QtQuick 2.0
import VPlay 2.0
import "../common"
import "dialogs"
import "sceneElements"

SceneBase {
  id: menuScene

  signal levelScenePressed

  // background
  Rectangle {
    id: background

    anchors.fill: parent.gameWindowAnchorItem

    gradient: Gradient {
      GradientStop { position: 0.0; color: "#4595e6" }
      GradientStop { position: 0.9; color: "#80bfff" }
      GradientStop { position: 0.95; color: "#009900" }
      GradientStop { position: 1.0; color: "#804c00" }
    }
  }

  // header
  Rectangle {
    id: header

    height: 95

    anchors.top: menuScene.gameWindowAnchorItem.top
    anchors.left: menuScene.gameWindowAnchorItem.left
    anchors.right: menuScene.gameWindowAnchorItem.right
    anchors.margins: 5

    // background color
    color: "#cce6ff"

    radius: height / 4

    // header image
    MultiResolutionImage {
      fillMode: Image.PreserveAspectFit

      anchors.top: parent.top
      anchors.left: parent.left
      anchors.right: parent.right

      anchors.topMargin: 10

      source: "../../assets/ui/header.png"
    }
  }

  PlatformerImageButton {
    id: playButton

    image.source: "../../assets/ui/playButton.png"

    width: 150
    height: 40

    anchors.horizontalCenter: parent.horizontalCenter
    anchors.top: header.bottom
    anchors.topMargin: 40

    color: "#cce6ff"

    radius: height / 4
    borderColor: "transparent"

    onClicked: {
      levelScene.state = "demoLevels"
      levelScenePressed()
    }
  }

  PlatformerImageButton {
    id: levelSceneButton

    image.source: "../../assets/ui/levelsButton.png"

    width: 150
    height: 40

    anchors.horizontalCenter: parent.horizontalCenter
    anchors.top: playButton.bottom
    anchors.topMargin: 30

    color: "#cce6ff"

    radius: height / 4
    borderColor: "transparent"

    onClicked: {
      levelScene.state = "myLevels"
      levelScene.subState = "createdLevels"
      levelScenePressed()
    }
  }

  MultiResolutionImage {
    id: musicButton

    // show music icon
    source: "../../assets/ui/music.png"
    // reduce opacity, if music is disabled
    opacity: settings.musicEnabled ? 0.9 : 0.4

    anchors.top: header.bottom
    anchors.topMargin: 30
    anchors.left: parent.left
    anchors.leftMargin: 30

    MouseArea {
      anchors.fill: parent

      onClicked: {
        // switch between enabled and disabled
        if(settings.musicEnabled)
          settings.musicEnabled = false
        else
          settings.musicEnabled = true
      }
    }
  }

  MultiResolutionImage {
    id: soundButton

    // show sound_on or sound_off icon, depending on if sound is enabled or not
    source: settings.soundEnabled ? "../../assets/ui/sound_on.png" : "../../assets/ui/sound_off.png"
    // reduce opacity, if sound is disabled
    opacity: settings.soundEnabled ? 0.9 : 0.4

    anchors.top: musicButton.bottom
    anchors.topMargin: 10
    anchors.left: parent.left
    anchors.leftMargin: 30

    MouseArea {
      anchors.fill: parent

      onClicked: {
        // switch between enabled and disabled
        if(settings.soundEnabled) {
          settings.soundEnabled = false
        }
        else {
          settings.soundEnabled = true

          // play sound to signal, that sound is now on
          audioManager.playSound("playerJump")
        }
      }
    }
  }

  // V-Play logo with link to website
  MultiResolutionImage {
    source: "../../assets/logo-vp/logo-vp.png"

    anchors.right: parent.right
    anchors.rightMargin: 30
    anchors.top: header.bottom
    anchors.topMargin: 70

    MouseArea {
      anchors.fill: parent
      onClicked: {
        nativeUtils.displayMessageBox(qsTr("V-Play Game Engine"), qsTr("This game is built with V-Play Game Engine. The source code is available in the free V-Play SDK - so you can build your own platformer in minutes!\n\nVisit V-Play.net now? \n(It contains a tutorial how to use the V-Play Level Editor to create your own platform game and more details)"), 2)
      }
    }
  }

  Connections {
    target: nativeUtils
    onMessageBoxFinished: {
      if(accepted) {
        nativeUtils.openUrl("http://v-play.net/")
      }
    }
  }

}
<file_sep>/qml/PlatformerWithLevelEditorMain.qml
import VPlay 2.0
import QtQuick 2.0
import "scenes"
import "common"

GameWindow {
  id: gameWindow

  // You get free licenseKeys from http://v-play.net/licenseKey
  // With a licenseKey you can:
  //  * Publish your games & apps for the app stores
  //  * Remove the V-Play Splash Screen or set a custom one (available with the Pro Licenses)
  //  * Add plugins to monetize, analyze & improve your apps (available with the Pro Licenses)

  activeScene: menuScene

  // the size of the Window can be changed at runtime by pressing Ctrl (or Cmd on Mac) + the number keys 1-8
  // the content of the logical scene size (480x320 for landscape mode by default) gets scaled to the window size based on the scaleMode
  // you can set this size to any resolution you would like your project to start with, most of the times the one of your main target device
  // this resolution is for iPhone 4 & iPhone 4S
  width: 960
  height: 640

  // aliases to make levelEditor and itemEditor accessible from the outside
  property alias levelEditor: levelEditor
  property alias itemEditor: gameScene.itemEditor

  // update background music when scene changes
  onActiveSceneChanged: {
    audioManager.handleMusic()
  }

  // level editor
  LevelEditor {
    id: levelEditor

    Component.onCompleted: levelEditor.loadAllLevelsFromStorageLocation(authorGeneratedLevelsLocation)

    // These are the entity types, that the can be stored and removed by the entityManager.
    // Note, that the player is not here. This is because we only
    // want ONE player instance - we don't want to be able to place
    // another player or delete the existing player.
    toRemoveEntityTypes: [ "ground", "platform", "spikes", "opponent", "coin", "mushroom", "star", "finish" ]
    toStoreEntityTypes: [ "ground", "platform", "spikes", "opponent", "coin", "mushroom", "star", "finish" ]

    // set the gameNetwork
    gameNetworkItem: gameNetwork

    // directory where the predefined json levels are
    applicationJSONLevelsDirectory: "levels/"

    onLevelPublished: {
      // save level
      gameScene.editorOverlay.saveLevel()

      //report a dummy score, to initialize the leaderboard
      var leaderboard = levelId
      if(leaderboard) {
        gameNetwork.reportScore(100000, leaderboard, null, "lowest_is_best")
      }

      gameWindow.state = "level"
    }
  }

  AudioManager {
    id: audioManager
  }

  // the entity manager handles all our entities
  EntityManager {
    id: entityManager

    // here we define the container the entityManager manages
    // so all entities, the entityManager creates are in this container
    entityContainer: gameScene.container

    poolingEnabled: true
  }

  VPlayGameNetwork {
    id: gameNetwork

    // set id and secret
    gameId: 220
    secret: "<PASSWORD>erEditorDevPasswordForVPlayGameNetwork"

    // set gameNetworkView
    gameNetworkView: myGameNetworkView
  }

  // custom mario style font
  FontLoader {
    id: marioFont
    source: "../assets/fonts/SuperMario256.ttf"
  }

  // Scenes -----------------------------------------
  MenuScene {
    id: menuScene

    onLevelScenePressed: {
      gameWindow.state = "level"
    }
  }

  LevelScene {
    id: levelScene

    VPlayGameNetworkView {
      id: myGameNetworkView

      z: 1000

      anchors.fill: parent.gameWindowAnchorItem

      // invisible by default
      visible: false

      onShowCalled: {
        myGameNetworkView.visible = true
      }

      onBackClicked: {
        myGameNetworkView.visible = false
      }
    }

    onNewLevelPressed: {
      // create a new level
      var creationProperties = {
        levelMetaData: {
          levelName: "newLevel"
        }
      }
      levelEditor.createNewLevel(creationProperties)

      // switch to gameScene, edit mode
      gameWindow.state = "game"
      gameScene.state = "edit"

      // initialize level
      gameScene.initLevel()
    }

    onPlayLevelPressed: {
      // load level
      levelEditor.loadSingleLevel(levelData)

      // switch to gameScene, play mode
      gameWindow.state = "game"
      gameScene.state = "play"

      // initialize level
      gameScene.initLevel()
    }

    onEditLevelPressed: {
      // load level
      levelEditor.loadSingleLevel(levelData)

      // switch to gameScene, play mode
      gameWindow.state = "game"
      gameScene.state = "edit"

      // initialize level
      gameScene.initLevel()
    }

    onRemoveLevelPressed: {
      // load level
      levelEditor.loadSingleLevel(levelData)

      // remove loaded level
      levelEditor.removeCurrentLevel()
    }

    onBackPressed: {
      gameWindow.state = "menu"
    }
  }

  GameScene {
    id: gameScene

    onBackPressed: {
      // reset level
      gameScene.resetLevel()

      // switch to levelScene
      gameWindow.state = "level"
    }
  }

  // states
  state: "menu"

  // this state machine handles the transition between scenes
  states: [
    State {
      name: "menu"
      PropertyChanges {target: menuScene; opacity: 1}
      PropertyChanges {target: gameWindow; activeScene: menuScene}
    },
    State {
      name: "level"
      PropertyChanges {target: levelScene; opacity: 1}
      PropertyChanges {target: gameWindow; activeScene: levelScene}
    },
    State {
      name: "game"
      PropertyChanges {target: gameScene; opacity: 1}
      PropertyChanges {target: gameWindow; activeScene: gameScene}
    }
  ]

}

<file_sep>/qml/scenes/dialogs/RemoveLevelDialog.qml
import QtQuick 2.0
import VPlay 2.0
import "../../common"

DialogBase {
  id: removeLevelDialog

  // is set from LevelScene
  // we need this to know which level to delete
  property var levelData

  Text {
    anchors.centerIn: parent

    text: "Do you really want to remove this level?"

    color: "white"
  }

  // Buttons ------------------------------------------

  PlatformerTextButton {
    id: okButton

    screenText: "Ok"

    width: 100

    anchors.bottom: parent.bottom
    anchors.bottomMargin: 50
    anchors.left: parent.left
    anchors.leftMargin: 100

    onClicked: {
      // if level is published, unpublish level
      if(levelData.publishedLevelId) {
        levelEditor.unpublishLevel(levelData)
        delete levelData.publishedLevelId
      }

      // remove level
      levelScene.removeLevelPressed(levelData)

      // close dialog
      removeLevelDialog.opacity = 0
    }
  }

  PlatformerTextButton {
    id: cancelButton

    screenText: "Cancel"

    width: 100

    anchors.bottom: parent.bottom
    anchors.bottomMargin: 50
    anchors.right: parent.right
    anchors.rightMargin: 100

    // when clicking "cancel" we only close the dialog
    onClicked: removeLevelDialog.opacity = 0
  }
}

<file_sep>/qml/entities/ResetSensor.qml
import VPlay 2.0
import QtQuick 2.0

// This sensor is always at the bottom of the level, directly
// below the player. So the player touches it just before he
// would fall out of bounds at the bottom of the level.
// When he touches it, he dies.
EntityBase {
  id: resetSensor
  entityType: "resetSensor"

  // the player
  property var player

  // set the position to below the player, at the bottom of the level
  x: player.x
  y: 0

  // set size
  width: player.width
  height: 10

  // only visible and enabled when playing, not when editing
  visible: !player.inLevelEditingMode
  enabled: !player.inLevelEditingMode

  // this is emitted, when the sensor collides with the player
  signal contact

  // this collider checks if the player hits the bottom of the level
  BoxCollider {
    anchors.fill: parent
    collisionTestingOnlyMode: true

    // Category7: reset sensor
    categories: Box.Category6
    // Category1: player body
    collidesWith: Box.Category1

    fixture.onBeginContact: {
      var otherEntity = other.getBody().target

      // If the player hits the sensor, we emit a signal which will be used to
      // reset the player.
      if(otherEntity.entityType === "player") {
        // We could also directly modify the player position here, but the
        // signal approach is a bit cleaner and helps seperating the components.
        resetSensor.contact()
      }
    }
  }

  // The rectangle and text are just for you to see how the
  // sensor moves - set visible to true, to see it.
  Rectangle {
    visible: false

    y: -resetSensor.height

    width: parent.width
    height: parent.height

    anchors.horizontalCenter: parent.horizontalCenter
    color: "yellow"
    opacity: 0.5

    Text {
      y: -resetSensor.height

      anchors.centerIn: parent
      text: "reset sensor"
      color: "white"
      font.pixelSize: 9
    }
  }
}

<file_sep>/qml/scenes/sceneElements/LevelSelectionItem.qml
import QtQuick 2.0
import VPlay 2.0
import "../../common"

Item {
  id: levelSelectionItem

  property bool isDownloaded: levelEditor.isLevelDownloaded(modelData.levelId)
  property bool isAuthorCreated: modelData.storageLocation == "authorGenerated"
  property bool isPublished: modelData.publishedLevelId !== undefined
  property bool isRated: modelData.rating !== undefined
  property bool isUserLevel: modelData.storageLocation === levelEditor.userGeneratedLevelsLocation
  property bool isLoggedInPlayer: (modelData && modelData.user && modelData.user.id === gameNetwork.user.userId) ? true : false

  // information visibility
  property bool ratingAndDownloadedVisible: levelScene.state == "communityLevels"

  // button visibility
  property bool playButtonVisible: levelScene.state != "demoLevels" && levelScene.state != "communityLevels"
  property bool demoPlayButtonVisible: levelScene.state == "demoLevels"
  property bool editButtonVisible: levelScene.state == "myLevels" && levelScene.subState == "createdLevels" && !isPublished
  property bool removeButtonVisible: levelScene.state == "myLevels" && levelScene.subState == "createdLevels" && !isPublished
  property bool unpublishButtonVisible: isAuthorCreated && isPublished
  property bool leaderboardButtonVisible: levelScene.state == "myLevels" && levelScene.subState == "downloadedLevels" || isAuthorCreated && isPublished
  property bool downloadButtonVisible: levelScene.state == "communityLevels" && !levelEditor.isLevelDownloaded(modelData.levelId) && !(isUserLevel && isLoggedInPlayer)
  property bool rateButtonVisible: levelScene.state == "communityLevels" && isDownloaded

  // define the size
  width: 110
  // set height, depending on if there's a user, whose name should be displayed
  height: modelData.user ? 70 : 65

  // this is the base rectangle in the background
  Rectangle {
    width: parent.width
    height: parent.height

    border.width: 1
    border.color: "black"
    radius: 3

    // background gradient
    gradient: {
      // all downloaded levels, and all self-published levels in the
      // communityLevels view, have this downloadedGradient as background
      if(isDownloaded && !isAuthorCreated || isUserLevel && isLoggedInPlayer)
        return downloadedGradient
      // all self created and published levels in the myLevels view, have this
      // publishedGradient as background
      else if(isAuthorCreated && isPublished)
        return publishedGradient
      // by default, defaultGradient is the background
      else
        return defaultGradient
    }

    Gradient {
      id: defaultGradient
      GradientStop { position: 0.0; color: "#cccccc" }
      GradientStop { position: 0.5; color: "#e0e0e0" }
      GradientStop { position: 1.0; color: "#cccccc" }
    }

    Gradient {
      id: downloadedGradient
      GradientStop { position: 0.0; color: "#67e667" }
      GradientStop { position: 0.5; color: "#8cff8c" }
      GradientStop { position: 1.0; color: "#67e667" }
    }

    Gradient {
      id: publishedGradient
      GradientStop { position: 0.0; color: "#e6e667" }
      GradientStop { position: 0.5; color: "#ffff8c" }
      GradientStop { position: 1.0; color: "#e6e667" }
    }
  }

  /**
   * INFORMATION
   */

  // level name
  Text {
    id: levelName
    text: modelData.levelName

    height: 16

    // position and size
    anchors.top: parent.top
    anchors.right: removeButton.left
    anchors.left: parent.left
    // margins
    anchors.margins: 5
    anchors.bottomMargin: 0

    // make font size dynamic
    fontSizeMode: Text.Fit
    font.pixelSize: 13
    minimumPixelSize: 8
  }

  // user name
  Text {
    id: userName

    // if user name exists, display it
    text: modelData.user ? modelData.user.name : ""

    height: 5

    // anchoring
    anchors.top: levelName.bottom
    anchors.right: removeButton.left
    anchors.left: parent.left
    // margins
    anchors.margins: 5
    anchors.topMargin: 0

    font.pixelSize: 8
  }

  MultiResolutionImage {
    id: ratingIcon

    visible: ratingAndDownloadedVisible

    width: 15
    height: 15

    anchors.left: parent.left
    anchors.bottom: downloadedIcon.top
    anchors.leftMargin: 5

    source: "../../../assets/ui/star.png"
  }

  Text {
    id: ratingText

    visible: ratingAndDownloadedVisible

    height: ratingIcon.height

    anchors.left: ratingIcon.right
    anchors.verticalCenter: ratingIcon.verticalCenter
    anchors.leftMargin: 2

    verticalAlignment: Text.AlignVCenter

    font.pixelSize: 10

    text: {
      // if rating exists, round it and set it as text
      if(modelData["average_quality"]) {
        var rating = modelData["average_quality"]
        var roundedRating = Math.round(rating * 10) / 10
        return ""+roundedRating
      }
      // otherwise set 0 as text
      else
        return "0"
    }
  }

  MultiResolutionImage {
    id: downloadedIcon

    visible: ratingAndDownloadedVisible

    width: 15
    height: 15

    anchors.left: parent.left
    anchors.bottom: parent.bottom
    anchors.margins: 5

    source: "../../../assets/ui/download.png"
  }

  Text {
    id: downloadedText

    visible: ratingAndDownloadedVisible

    height: downloadedIcon.height

    anchors.left: downloadedIcon.right
    anchors.verticalCenter: downloadedIcon.verticalCenter
    anchors.leftMargin: 2

    verticalAlignment: Text.AlignVCenter

    font.pixelSize: 10

    text: modelData["times_downloaded"] ? modelData["times_downloaded"] : "0"
  }

  /**
   * BUTTONS
   */

  // button to start the level
  PlatformerImageButton {
    id: playButton

    visible: playButtonVisible

    width: 50
    height: 30

    anchors.bottom: parent.bottom
    anchors.left: parent.left
    anchors.margins: 2

    image.source: "../../../assets/ui/play.png"

    // start level
    onClicked: {
      levelScene.playLevelPressed(modelData)

      audioManager.playSound("start")
    }
  }

  // a wider button to start a demo level
  PlatformerImageButton {
    id: demoPlayButton

    visible: demoPlayButtonVisible

    height: 30

    anchors.bottom: parent.bottom
    anchors.left: parent.left
    anchors.right: parent.right
    anchors.margins: 2

    image.source: "../../../assets/ui/play.png"

    // start level
    onClicked: {
      levelScene.playLevelPressed(modelData)

      audioManager.playSound("start")
    }
  }

  // button to edit the level
  PlatformerImageButton {
    id: editButton

    visible: editButtonVisible

    width: 50
    height: 30

    anchors.bottom: parent.bottom
    anchors.right: parent.right
    anchors.margins: 2

    image.source: "../../../assets/ui/edit.png"

    // edit level
    onClicked: levelScene.editLevelPressed(modelData)
  }

  // button to remove the level
  PlatformerImageButton {
    id: removeButton

    visible: removeButtonVisible

    width: 30
    height: 20

    anchors.top: parent.top
    anchors.right: parent.right
    anchors.margins: 2

    image.source: "../../../assets/ui/remove.png"

    color: "#ff9999"

    onClicked: {
      // show removeLevelDialog
      removeLevelDialog.levelData = modelData
      removeLevelDialog.opacity = 1
    }
  }

  // button to unpublish the level
  PlatformerImageButton {
    id: unpublishButton

    visible: unpublishButtonVisible

    width: 30
    height: 20

    anchors.top: parent.top
    anchors.right: parent.right
    anchors.margins: 2

    image.source: "../../../assets/ui/remove.png"

    color: "#ff9999"

    onClicked: {
      // show unpublishDialog
      unpublishDialog.levelData = modelData
      unpublishDialog.opacity = 1
    }
  }

  // button to show the leaderboard for this level
  PlatformerImageButton {
    id: leaderboardButton

    visible: leaderboardButtonVisible

    width: 50
    height: 30

    anchors.bottom: parent.bottom
    anchors.right: parent.right
    anchors.margins: 2

    image.source: "../../../assets/ui/leaderboard.png"

    onClicked: {
      // id only exists in published levels
      var leaderboard = modelData.id

      // if modelData doesn't have an id, check if it has publishedLevelId
      if(!leaderboard)
        leaderboard = modelData.publishedLevelId

      // if level is published...
      if(leaderboard) {
        // ...show leaderboard
        gameNetwork.showLeaderboard(leaderboard)
      }
    }
  }

  // button to download the level
  PlatformerImageButton {
    id: downloadButton

    visible: downloadButtonVisible

    width: 40
    height: 35

    anchors.bottom: parent.bottom
    anchors.right: parent.right
    anchors.margins: 2

    image.source: "../../../assets/ui/download.png"

    onClicked: levelEditor.downloadLevel(modelData)
  }

  // button to rate this level
  PlatformerImageButton {
    id: rateButton

    visible: rateButtonVisible

    // disable this button, if this level has been rated
    enabled: !isRated

    width: 40
    height: 35

    anchors.bottom: parent.bottom
    anchors.right: parent.right
    anchors.margins: 2

    image.source: {
      // if the level hasn't been rated yet, show rate image
      if(!isRated) {
        "../../../assets/ui/rate.png"
      }
      // otherwise show the user's rating
      else {
        switch(modelData.rating.quality) {
          case 5: return "../../../assets/ui/rate_5stars.png"
          case 4: return "../../../assets/ui/rate_4stars.png"
          case 3: return "../../../assets/ui/rate_3stars.png"
          case 2: return "../../../assets/ui/rate_2stars.png"
          default: return "../../../assets/ui/rate_1star.png"
        }
      }
    }

    onClicked: {
      // set level data and show rating dialog
      ratingDialog.levelData = modelData
      ratingDialog.opacity = 1
    }
  }
}

<file_sep>/qml/common/PlatformerTextButton.qml
import QtQuick 2.0
import QtQuick.Controls.Styles 1.0
import VPlay 2.0

ButtonVPlay {
  id: textButton

  width: screenText.width + 20
  height: 30

  // background color
  property color color: "#ffffff"

  // we use screenText instead of the ButtonVPlay text property,
  // because the existing text property causes bad fonts
  property alias screenText: screenText.text

  // alias to access the text color
  property alias textColor: screenText.color

  // we override the default V-Play style with our own
  style: ButtonStyle {
    background: Rectangle {
      border.width: 1
      border.color: "black"
      radius: 3

      // add a gradient as background
      gradient: Gradient {
        // take color as the first color
        GradientStop { position: 0.0; color: textButton.color }
        // tint color, to make it a little darker and use it as second color
        GradientStop { position: 1.0; color: Qt.tint(textButton.color, "#24000000") }
      }
    }
  }

  onClicked: audioManager.playSound("click")

  // text displayed in the button
  Text {
    id: screenText

    anchors.centerIn: parent

    font.pixelSize: 12
  }

  // this white rectangle covers the button when the mouse hovers above it
  Rectangle {
    anchors.fill: parent

    radius: 3

    color: "white"
    opacity: hovered ? 0.3 : 0
  }
}
<file_sep>/qml/editorElements/Sidebar.qml
import QtQuick 2.0
import VPlay 2.0
import "../common"

MultiResolutionImage {
  id: sidebar

  // holds the currently active tool
  // can be draw", "erase", "hand"
  // by default this is "hand"
  property string activeTool: "hand"

  // properties to access gameScene elements
  property var bgImage
  property var grid
  property var undoHandler

  // this property holds all BuildEntityButtons
  // we use this in the unselectAllButtonsButOne() function
  property var buttons: [groundButton, ground2Button, platformButton, spikeballButton, spikesButton, opponentJumperButton, opponentWalkerButton, coinButton, mushroomButton, starButton, finishButton]

  // aliases to allow access from outside
  property alias gridSizeButton: gridSizeButton

  z: 100 // make sure the sidebar is above all other elements on the screen

  // set size
  width: 100
  height: editorOverlay.height

  anchors.top: editorOverlay.top
  anchors.left: editorOverlay.left

  // set image source
  source: "../../assets/ui/sidebar.png"

  // sidebar column for layout
  Item {
    anchors.fill: parent
    anchors.margins: 4

    Row {
      id: undoRedo

      width: parent.width
      height: 30

      anchors.top: parent.top
      anchors.left: parent.left

      spacing: 2

      property int buttonWidth: width / 2 - spacing / 2

      // undo button
      PlatformerImageButton {
        width: parent.buttonWidth

        // set image to colored if undo is possible, otherwise
        // to grey
        image.source: undoHandler.pointer >= 0 ? "../../assets/ui/undo.png" : "../../assets/ui/undo_grey.png"

        // hide hoverRectangle, when undo isn't possible
        hoverRectangle.visible: undoHandler.pointer >= 0 ? true : false

        onClicked: undoHandler.undo()
      }

      // redo button
      PlatformerImageButton {
        width: parent.buttonWidth

        // set image to colored if redo is possible, otherwise
        // to grey
        image.source: undoHandler.pointer < undoHandler.undoArray.length - 1 ? "../../assets/ui/redo.png" : "../../assets/ui/redo_grey.png"

        // hide hoverRectangle, when redo isn't possible
        hoverRectangle.visible: undoHandler.pointer < undoHandler.undoArray.length - 1 ? true : false

        onClicked: undoHandler.redo()
      }
    }

    Row {
      id: tools

      width: parent.width
      height: 30

      anchors.top: undoRedo.bottom
      anchors.left: parent.left
      anchors.topMargin: 4

      spacing: 2

      property int buttonWidth: width / 2 - spacing / 2

      PlatformerSelectableImageButton {
        id: drawEraseButton

        // this property holds if draw is active
        // if draw is not active this means, that erase is active
        property bool drawActive: true

        width: parent.buttonWidth
        height: parent.height

        // display image, depending on if draw is active or not
        image.source: drawActive ? "../../assets/ui/drawActive.png" : "../../assets/ui/eraseActive.png"

        onClicked: {
          // if this button is selected, switch between draw and erase mode
          if(isSelected) {
            drawActive = !drawActive
          }
          // otherwise unselect the handButton and select this button
          else {
            handButton.isSelected = false
            isSelected = true
          }

          // update the activeTool
          updateActiveTool()

          // if the draw mode is activated and there is no BuildEntityButton
          // selected yet...
          if(drawActive && editorOverlay.selectedButton == null) {
            // ...switch to the first entity group...
            changeActiveEntityGroup(1)

            // ...and select the groundButton
            selectBuildEntityButton(groundButton)
          }
        }
      }

      PlatformerSelectableImageButton {
        id: handButton

        width: parent.buttonWidth
        height: parent.height

        image.source: "../../assets/ui/hand.png"

        isSelected: true

        onClicked: {
          // if this button is not selected unselect the drawEraseButton and select this button
          if(!isSelected) {
            drawEraseButton.isSelected = false
            isSelected = true
          }

          // update the activeTool
          updateActiveTool()
        }
      }
    }

    // entity groups
    // with these buttons the user can change the currently shown group
    // of entities
    Row {
      id: entityGroups

      width: parent.width
      height: 30

      anchors.top: tools.bottom
      anchors.left: parent.left
      anchors.topMargin: 4

      spacing: 2

      // the active entity group
      property int activeGroup: 1

      // set button width
      // Divide sidebar width by three. Then subtract a third of the total spacing.
      // Since there are 3 buttons, the total spacing is 2 * spacing.
      property int buttonWidth: width / 3 - spacing * 2 / 3

      ItemGroupButton {
        image.source: "../../assets/ui/entityGroups/group1.png"

        selected: entityGroups.activeGroup == 1

        onClicked: changeActiveEntityGroup(1)
      }

      ItemGroupButton {
        image.source: "../../assets/ui/entityGroups/group2.png"

        selected: entityGroups.activeGroup == 2

        onClicked: changeActiveEntityGroup(2)
      }

      ItemGroupButton {
        image.source: "../../assets/ui/entityGroups/group3.png"

        selected: entityGroups.activeGroup == 3

        onClicked: changeActiveEntityGroup(3)
      }
    }

    // this is the flickable component, where all buildEntityButtons are
    Flickable {
      id: buildFlickable

      width: parent.width

      anchors.top: entityGroups.bottom
      anchors.bottom: optionsButton.top
      anchors.left: parent.left
      anchors.topMargin: 4
      anchors.bottomMargin: 4

      // size of the flickable content
      contentWidth: width
      contentHeight: buildColumn.height

      // set margins
      topMargin: 5
      bottomMargin: 5

      // clip the content on the boundaries
      clip: true

      // we only want to allow vertical flicking
      flickableDirection: Flickable.VerticalFlick

      // pressDelay delays the delivery of the press event to the buildEntityButtons.
      // This allows the user to flick this Flickable, even when pressing on a buildEntityButton.
      pressDelay: 100

      // build entity buttons
      Column {
        id: buildColumn

        // set size
        width: parent.width

        spacing: 5

        /**
         * Editor options --------------------------------
         */

        // this item holds the level name as text and a button to change it
        Item {
          id: levelNameItem

          width: parent.width
          height: 30

          visible: entityGroups.activeGroup == 0

          // this text displays the current level name
          Text {
            // set text to level name, if it exists
            text: levelEditor.currentLevelName ? levelEditor.currentLevelName : ""

            anchors.left: parent.left
            anchors.top: parent.top
            anchors.bottom: parent.bottom
            anchors.right: nameLevelButton.left
            anchors.rightMargin: 4

            // align text in the vertical center
            verticalAlignment: Text.AlignVCenter

            // make font size dynamic
            fontSizeMode: Text.Fit
            font.pixelSize: 13
            minimumPixelSize: 7
          }

          // change level name button
          PlatformerImageButton {
            id: nameLevelButton

            image.source: "../../assets/ui/edit_black.png"

            width: 30
            height: parent.height

            anchors.right: parent.right
            anchors.top: parent.top
            anchors.bottom: parent.bottom

            onClicked: {
              // when we click this button, we show a native text input dialog
              nativeUtils.displayTextInput("Enter levelName", "Enter a level name. (max 15 characters)", "", levelEditor.currentLevelName)
            }

            // this listens for the end of the native text input dialog
            Connections {
              target: nativeUtils

              onTextInputFinished: {
                // if the text input dialog is closed
                // and the user clicked "ok"
                if(accepted) {

                  // the text can't be longer than 9 characters
                  if(enteredText.length > 15) {
                    nativeUtils.displayMessageBox("Invalid level name", "A maximum of 15 characters is allowed!")
                    return
                  }

                  // change level name
                  levelEditor.currentLevelName = enteredText
                }
              }
            }
          }
        }

        // change-background-image buttons
        Row {
          id: backgroundButtons

          width: parent.width
          height: 30

          visible: entityGroups.activeGroup == 0

          spacing: 2

          // set button width; subtract half of spacing, to make buttons fit
          property int buttonWidth: width / 3 - spacing * 2 / 3

          // with these three buttons the user can change the background image
          PlatformerImageButton {
            width: parent.buttonWidth

            image.source: bgImage.bg0
            image.fillMode: Image.PreserveAspectCrop

            onClicked: bgImage.bg = 0
          }

          PlatformerImageButton {
            width: parent.buttonWidth

            image.source: bgImage.bg1
            image.fillMode: Image.PreserveAspectCrop

            onClicked: bgImage.bg = 1
          }

          PlatformerImageButton {
            width: parent.buttonWidth

            image.source: bgImage.bg2
            image.fillMode: Image.PreserveAspectCrop

            onClicked: bgImage.bg = 2
          }
        }

        // grid options
        Item {
          id: gridOptions

          width: parent.width
          height: 30

          visible: entityGroups.activeGroup == 0

          // label
          Text {
            height: parent.height

            anchors.top: parent.top
            anchors.left: parent.left

            text: "GridSize:"
            font.pixelSize: 12

            // align text in the vertical center
            verticalAlignment: Text.AlignVCenter
          }

          // button to switch between gridSizes 16 and 32
          PlatformerTextButton {
            id: gridSizeButton

            screenText: "32"

            width: 30
            height: parent.height

            anchors.top: parent.top
            anchors.right: parent.right

            // alternate gridSizes 16 and 32 when clicking this button
            onClicked: {
              if(screenText == "32") {
                screenText = "16"
                editorOverlay.scene.gridSize = 16
              }
              else {
                screenText = "32"
                editorOverlay.scene.gridSize = 32
              }
            }
          }
        }

        // publish button
        PlatformerTextButton {
          id: publishButton

          screenText: "Publish"

          width: parent.width
          height: 30

          visible: entityGroups.activeGroup == 0

          onClicked: {
            // open publish dialog
            publishDialog.opacity = 1
          }
        }

        /**
         * BuiltEntityButtons --------------------------------
         */

        PlatformerBuildEntityButton {
          id: groundButton

          // each button is only visible, if the corresponding
          // entity group is active
          visible: entityGroups.activeGroup == 1

          // path to the entity type of the entity, which should be created
          toCreateEntityTypeUrl: "../entities/GroundGrass.qml"

          // handle selection and unselection of this button
          onSelected: selectBuildEntityButton(this)
          onUnselected: unselectBuildEntityButton()
        }

        PlatformerBuildEntityButton {
          id: ground2Button

          visible: entityGroups.activeGroup == 1

          toCreateEntityTypeUrl: "../entities/GroundDirt.qml"

          onSelected: selectBuildEntityButton(this)
          onUnselected: unselectBuildEntityButton()
        }

        PlatformerBuildEntityButton {
          id: platformButton

          visible: entityGroups.activeGroup == 1

          toCreateEntityTypeUrl: "../entities/Platform.qml"

          onSelected: selectBuildEntityButton(this)
          onUnselected: unselectBuildEntityButton()
        }

        PlatformerBuildEntityButton {
          id: spikeballButton

          visible: entityGroups.activeGroup == 1

          toCreateEntityTypeUrl: "../entities/Spikeball.qml"

          onSelected: selectBuildEntityButton(this)
          onUnselected: unselectBuildEntityButton()
        }

        PlatformerBuildEntityButton {
          id: spikesButton

          visible: entityGroups.activeGroup == 1

          toCreateEntityTypeUrl: "../entities/Spikes.qml"

          onSelected: selectBuildEntityButton(this)
          onUnselected: unselectBuildEntityButton()
        }

        PlatformerBuildEntityButton {
          id: opponentJumperButton

          visible: entityGroups.activeGroup == 2

          toCreateEntityTypeUrl: "../entities/OpponentJumper.qml"

          onSelected: selectBuildEntityButton(this)
          onUnselected: unselectBuildEntityButton()
        }

        PlatformerBuildEntityButton {
          id: opponentWalkerButton

          visible: entityGroups.activeGroup == 2

          toCreateEntityTypeUrl: "../entities/OpponentWalker.qml"

          onSelected: selectBuildEntityButton(this)
          onUnselected: unselectBuildEntityButton()
        }

        PlatformerBuildEntityButton {
          id: coinButton

          visible: entityGroups.activeGroup == 3

          toCreateEntityTypeUrl: "../entities/Coin.qml"

          onSelected: selectBuildEntityButton(this)
          onUnselected: unselectBuildEntityButton()
        }

        PlatformerBuildEntityButton {
          id: mushroomButton

          visible: entityGroups.activeGroup == 3

          toCreateEntityTypeUrl: "../entities/Mushroom.qml"

          onSelected: selectBuildEntityButton(this)
          onUnselected: unselectBuildEntityButton()
        }

        PlatformerBuildEntityButton {
          id: starButton

          visible: entityGroups.activeGroup == 3

          toCreateEntityTypeUrl: "../entities/Star.qml"

          onSelected: selectBuildEntityButton(this)
          onUnselected: unselectBuildEntityButton()
        }

        PlatformerBuildEntityButton {
          id: finishButton

          visible: entityGroups.activeGroup == 3

          toCreateEntityTypeUrl: "../entities/Finish.qml"

          onSelected: selectBuildEntityButton(this)
          onUnselected: unselectBuildEntityButton()
        }
      }
    }

    // options button
    ItemGroupButton {
      id: optionsButton

      width: 30
      height: 25

      anchors.bottom: parent.bottom
      anchors.right: parent.right

      image.source: "../../assets/ui/options.png"

      selected: entityGroups.activeGroup == 0

      onClicked: changeActiveEntityGroup(0)
    }
  }

  /**
   * JS FUNCTIONS
   */
  // handle the selection of a BuildEntityButton
  function selectBuildEntityButton(button) {
    // if active tool is erase, change to draw
    if(activeTool == "erase") {
      setActiveTool("draw")
    }

    // unselect all other buttons, select this button
    unselectAllButtonsButOne(button)
  }

  // handle the unselection of a BuildEntityButton
  function unselectBuildEntityButton() {
    // if active tool is draw or erase, change to hand
    if(activeTool == "draw" || activeTool == "erase") {
      setActiveTool("hand")
    }

    // reset selectedButton to null
    editorOverlay.selectedButton = null
  }

  // unselects all PlatformerBuildEntityButtons
  function unselectAllButtons() {
    for(var i=0; i<buttons.length; i++) {
      buttons[i].isSelected = false
    }

    // reset selectedButton to null
    editorOverlay.selectedButton = null
  }

  // unselects all buttons but one
  function unselectAllButtonsButOne(button) {
    // unselect all buttons
    unselectAllButtons()

    if(button) {
      // select given button
      button.isSelected = true

      // set selectedButton to this button
      editorOverlay.selectedButton = button
    }
  }

  function changeActiveEntityGroup(newGroup) {
    // if group isn't already selected...
    if(entityGroups.activeGroup !== newGroup) {
      // ...change active entity group
      entityGroups.activeGroup = newGroup
    }
  }

  // this function updates the activeTool property depending on which button is active
  function updateActiveTool() {
    if(drawEraseButton.isSelected) {
      if(drawEraseButton.drawActive) {
        activeTool = "draw"
      }
      else {
        activeTool = "erase"
      }
    }
    else if(handButton.isSelected) {
      activeTool = "hand"
    }
    else {
      activeTool = ""
    }
  }

  // this function sets the activeTool to the given tool parameter
  // also it selects the respective button/button mode
  function setActiveTool(tool) {
    if(tool === "draw") {
      handButton.isSelected = false
      drawEraseButton.isSelected = true
      drawEraseButton.drawActive = true
    }
    else if(tool === "erase") {
      handButton.isSelected = false
      drawEraseButton.isSelected = true
      drawEraseButton.drawActive = false
    }
    else if(tool === "hand") {
      drawEraseButton.isSelected = false
      handButton.isSelected = true
    }

    updateActiveTool()
  }

  // reset the sidebar
  function reset() {
    // unselect all buttons
    unselectAllButtons()

    // set active entity group to 1
    changeActiveEntityGroup(1)

    // reset drawEraseButton's drawActive property to true
    drawEraseButton.drawActive = true
  }
}
<file_sep>/qml/scenes/dialogs/LevelTimeLimitDialog.qml
import QtQuick 2.0
import VPlay 2.0
import "../../common"

DialogBase {
  id: levelTimeLimitDialog

  // Buttons ------------------------------------------

  PlatformerTextButton {
    id: allTime

    screenText: "All Time"

    width: 175

    anchors.centerIn: parent
    anchors.verticalCenterOffset: -50

    onClicked: {
      setTimeLimit(0)
    }
  }

  PlatformerTextButton {
    id: thisWeek

    screenText: "This Week"

    width: 175

    anchors.centerIn: parent

    onClicked: {
      setTimeLimit(24 * 7)
    }
  }

  PlatformerTextButton {
    id: today

    screenText: "Today"

    width: 175

    anchors.centerIn: parent
    anchors.verticalCenterOffset: 50

    onClicked: {
      setTimeLimit(24)
    }
  }

  function setTimeLimit(newTimeLimit) {
    // set new time limit
    levelScene.timeLimit = newTimeLimit

    // close dialog
    levelTimeLimitDialog.opacity = 0
  }
}

<file_sep>/qml/scenes/dialogs/PublishDialog.qml
import QtQuick 2.0
import VPlay 2.0
import "../../common"

DialogBase {
  id: publishDialog

  Text {
    anchors.centerIn: parent

    text: "Do you really want to publish this level?"

    color: "white"
  }

  // Buttons ------------------------------------------

  PlatformerTextButton {
    id: okButton

    screenText: "Ok"

    width: 100

    anchors.bottom: parent.bottom
    anchors.bottomMargin: 50
    anchors.left: parent.left
    anchors.leftMargin: 100

    onClicked: {
      // save level and publish it
      editorOverlay.saveLevel()
      levelEditor.publishLevel()

      // close dialog
      publishDialog.opacity = 0
    }
  }

  PlatformerTextButton {
    id: cancelButton

    screenText: "Cancel"

    width: 100

    anchors.bottom: parent.bottom
    anchors.bottomMargin: 50
    anchors.right: parent.right
    anchors.rightMargin: 100

    // when clicking "cancel" we only close the dialog
    onClicked: publishDialog.opacity = 0
  }
}

<file_sep>/qml/gameElements/JumpTouchButton.qml
import VPlay 2.0
import QtQuick 2.0

Item {
  id: jumpTouchButton

  // should only be visible and enabled on touch devices, when
  // not in edit mode
  visible: !system.desktopPlatform && gameScene.state != "edit"
  enabled: visible

  // set size
  height: 60
  width: 80

  // anchor to the bottom right of the gameWindowAnchorItem
  anchors.right: gameScene.gameWindowAnchorItem.right
  anchors.bottom: gameScene.gameWindowAnchorItem.bottom

  signal pressed
  signal released

  // this is a background of the button image
  // it is only visible while the button is pressed, as visual feedback for the user
  Rectangle {
    id: background

    anchors.fill: parent

    // the radius of the edges; setting the radius to height gives this a circle shape
    radius: height

    // set color and opacity
    color: "#ffffff"
    opacity: 0.5

    // not visible by default, gets set to visible in the onPressed event
    visible: false
  }

  MultiResolutionImage {
    id: image

    source: "../../assets/ui/arrow_up.png"

    // fill the background
    anchors.fill: background

    fillMode: Image.PreserveAspectFit

    // make it half the size of the background
    scale: 0.5
  }

  // handle touch events
  MouseArea {
    anchors.fill: parent

    onPressed: {
      jumpTouchButton.pressed() // start jump
      background.visible = true // make background visible
    }

    // end jump when released
    onReleased: {
      jumpTouchButton.released() // start jump
      background.visible = false // make background invisible again
    }
  }
}<file_sep>/qml/editorElements/EditorGrid.qml
import QtQuick 2.0
import VPlay 2.0

Canvas {
  id: grid

  // grid properties
  property real gridSize: editorOverlay.scene.gridSize * container.scale
  property string gridColor: "white"

  // the container, that is holding the grid
  property var container

  // Binding x and y makes the grid follow the position of the container.
  // Since the level size is unlimited, we can't draw the grid over the
  // whole level. So we just let it cover the gameWindow/editorOverlay.
  x: {
    if(container) {
      // The x position of the grid is: container x modulo the gridSize
      return (container.x % gridSize)
    }
    else {
      return 0
    }
  }
  y: {
    if(container) {
      // see above
      return (container.y % gridSize) - gridSize
    }
    else {
      return 0
    }
  }

  // The grid should cover the whole level. These values are calculated by:
  // + the editorOverlay width/height
  // + gridSize; we need a gridSize px buffer to cover the whole screen, while moving the camera around
  width: editorOverlay.width + gridSize
  height: editorOverlay.height + gridSize

  // we request paint in the beginning and everytime the gridSize is changed
  onPaint: drawGrid()

  function drawGrid()
  {
    var context = getContext("2d");

    // clear canvas
    context.clearRect(0, 0, grid.width, grid.height)

    // init drawing context and set properties
    context.beginPath()
    context.lineWidth = 0.4 * container.scale;
    context.strokeStyle = gridColor

    // put level size in variables, for better readability
    var xSize = grid.width
    var ySize = grid.height

    // vertical grid lines
    for(var x = 0; x*gridSize < xSize; x++)
    {
      context.moveTo(x*gridSize, 0)
      context.lineTo(x*gridSize, ySize)
    }

    // horizontal grid lines
    for(var y = 0; y*gridSize < ySize; y++)
    {
      context.moveTo(0, y*gridSize)
      context.lineTo(xSize, y*gridSize)
    }

    // draw and close
    context.stroke()
    context.closePath()
  }
}
<file_sep>/qml/scenes/dialogs/FinishDialog.qml
import QtQuick 2.0
import VPlay 2.0
import "../../common"

DialogBase {
  id: finishDialog

  // this dialog should not be closeable
  closeableByClickOnBackground: false

  // this holds the score, the player achieved
  property real score

  Text {
    anchors.centerIn: parent
    anchors.verticalCenterOffset: -20

    text: "Finished!"

    color: "white"
  }

  Text {
    anchors.centerIn: parent

    text: "Score: " + score

    color: "white"
  }

  Text {
    anchors.centerIn: parent
    anchors.verticalCenterOffset: 20

    // this property holds the current leaderboard
    property var currentLeaderboard: {
      if(levelEditor.currentLevelData.levelMetaData) {
        // if id exists, return it
        if(levelEditor.currentLevelData.levelMetaData.id)
          return levelEditor.currentLevelData.levelMetaData.id
        // else if publishedLevelId exists, return it
        else if(levelEditor.currentLevelData.levelMetaData.publishedLevelId)
          return levelEditor.currentLevelData.levelMetaData.publishedLevelId
      }
      // else return defaultLeaderboard
      return undefined
    }

    // show the user's current highscore and rank
    text: "Your Highscore: " + gameNetwork.userHighscoreForLeaderboard(currentLeaderboard)
          + " (#" + gameNetwork.userPositionForLeaderboard(currentLeaderboard) + ")"

    color: "#80bfff"

    // only visible if level has been published
    visible: currentLeaderboard !== undefined
  }

  // Buttons ------------------------------------------

  PlatformerTextButton {
    id: okButton

    screenText: "Restart"

    width: 100

    anchors.bottom: parent.bottom
    anchors.bottomMargin: 50
    anchors.left: parent.left
    anchors.leftMargin: 100

    onClicked: {
      // close dialog
      finishDialog.opacity = 0

      // reset state to play
      gameScene.state = "play"

      // reset and restart level
      gameScene.resetLevel()
    }
  }

  PlatformerTextButton {
    id: cancelButton

    screenText: "Menu"

    width: 100

    anchors.bottom: parent.bottom
    anchors.bottomMargin: 50
    anchors.right: parent.right
    anchors.rightMargin: 100

    onClicked: {
      // close dialog
      finishDialog.opacity = 0

      // reset state to play
      gameScene.state = "play"

      // go back to menu
      gameScene.backPressed()
    }
  }
}

<file_sep>/qml/editorElements/PlatformerEntityBaseDraggable.qml
import QtQuick 2.0
import VPlay 2.0

EntityBaseDraggable {
  id: entityBase

  // this is the scene this entity is in
  // NOTE: if your scene's id is NOT gameScene change this to make it fit to your implementation
  property var scene: gameScene

  // alias, to be able to access the sprite from the outside
  property alias image: sprite

  // this property stores the entity's last snapped position
  property point lastPosition

  // when this component is loaded, set lastPosition to the
  // current position
  Component.onCompleted: lastPosition = Qt.point(x, y)

  // the entity's size equals the size of it's sprite
  width: sprite.width
  height: sprite.height

  // this is the grid size the entity gets snapped to when it's dragged and dropped
  gridSize: scene.gridSize

  // this property must be set for EntityBaseDraggable
  selectionMouseArea {
    anchors.fill: sprite

    // if the hand tool is active, entities should not be draggable
    // so we don't accept the mouse event - it gets forwarded to the base mouse area (in GameScene)
    onPressed: {
      if(scene.editorOverlay.sidebar.activeTool == "hand") {
        mouse.accepted = false
      }
    }
  }

  // in levelEditingMode drag and drop is enabled
  // NOTE: if your editing state is not "edit", change this to make it fit to your implementation
  inLevelEditingMode: scene.state === "edit"

  // set dragOffset to (0, 0)
  dragOffset: Qt.point(0, 0)

  // this enables clicking on an object - we want this for removing entities
  delayDragOffset: true

  // make the notAllowedRectangle fit to the sprite
  notAllowedRectangle.anchors.fill: sprite

  // enable entity pooling to improve performance
  poolingEnabled: true

  // since our levels have no size limit, we don't want any
  // boundaries when dragging our entities
  ignoreBounds: true

  // this event is handled in the editorOverlay
  onEntityClicked: scene.editorOverlay.clickEntity(entityBase)

  onEntityStateChanged: {
    if(entityState == "entityDragged")
      audioManager.playSound("dragEntity")
  }

  onEntityReleased: {
    // If lastPosition is < 0, it means, that this entity
    // was dragged from the sidebar. In this case we don't
    // want to create a "move"-undoObject, but a "create".
    // We handle this in PlatformerBuildEntityButton.
    if(lastPosition.x < 0) return

    // get new position
    var currentPosition = scene.editorOverlay.snapToGrid(x, y)

    // if the entity's position has changed...
    if(lastPosition !== currentPosition) {

      // ...add a new "move"-undoObject to the undoHandler
      var undoObjectProperties = {"target": entityBase, "action": "move",
        "otherPosition": lastPosition, "currentPosition": currentPosition}
      var undoObject = scene.editorOverlay.undoHandler.createUndoObject(undoObjectProperties)
      scene.editorOverlay.undoHandler.push([undoObject])

      // update lastPosition
      lastPosition = currentPosition

      audioManager.playSound("createOrDropEntity")
    }
  }

  // handle position changes
  onXChanged: positionChanged()
  onYChanged: positionChanged()

  // when this entity is taken from the entity pool, set it's lastPosition property
  onUsedFromPool: {
    lastPosition = Qt.point(x, y)
  }

  // the sprite of the entity
  MultiResolutionImage {
    id: sprite
  }

  // in this function we check if the entity is dragged out of bounds
  function positionChanged() {
    // if entity is dragged, check if entity is in bounds
    if(entityBase.entityState == "entityDragged") {
      // calculate x screen coordinate
      // adjust entity position to scale, and add container position
      var xScreen = entityBase.x * scene.container.scale + scene.container.x

      // The leftLimit is the leftmost point where the entity
      // may be released.
      // To get this value, we take the width of the sidebar,
      // and subtract a small tolerance value.
      var leftLimit = scene.editorOverlay.sidebar.width - 8 * scene.container.scale

      // calculate y screen coordinate
      // adjust entity position to scale, and add container position
      var yScreen = entityBase.y * scene.container.scale + scene.container.y

      // The bottomLimit is the lowest point on the screen, where
      // the entity may be released. This value is calculated by
      // subtracting a small tolerance value from the game window
      // height.
      var bottomLimit = scene.gameWindowAnchorItem.height - 17 * scene.container.scale

      // If this entity is too far left or too low, forbid building.
      // We check if yScreen is larger than bottomLimit, because
      // the origin of the coordinate system is in the top left
      // corner. This means, that a higher y value is actually
      // lower on the screen.
      if(xScreen < leftLimit || yScreen > bottomLimit)
        forbidBuild = true
      else // otherwise allow it
        forbidBuild = false
    }
  }
}
<file_sep>/qml/editorElements/PlatformerBuildEntityButton.qml
import QtQuick 2.0
import VPlay 2.0

BuildEntityButton {

  // width and height of the button
  property int buttonSize: 32

  // this property is true, when the button is selected
  property bool isSelected: false

  // set size
  width: buttonSize
  height: buttonSize

  anchors.horizontalCenter: parent.horizontalCenter

  // set initialEntityPosition to a point outside the level
  initialEntityPosition: Qt.point(-100, 0)

  // set variationType to the variationType of the createdEntity
  variationType: createdEntity ? createdEntity.variationType : ""

  // these signals are emitted when the button gets selected/unselected
  signal selected
  signal unselected

  // if this button is selected, this rectangle emphasizes this
  Rectangle {
    id: selectedRectangle

    // only visible if the button is selected
    visible: isSelected

    // make it a little larger than the actual button
    width: parent.width + 8
    height: parent.height + 8

    // center this in the button
    anchors.centerIn: parent

    radius: 3

    // set color to white
    color: "white"
  }

  // this rectangle adds a grey background to the entity image,
  // this improves the visibility of part transparent entities
  Rectangle {
    id: background

    anchors.fill: buttonImage

    radius: 3

    color: "#a0b0b0b0"
  }

  // the image of the entity
  MultiResolutionImage {
    id: buttonImage

    source: createdEntity ? createdEntity.image.source : ""

    // increase buttonImage size, when button is selected
    width: isSelected ? 36 : 32
    height: isSelected ? 36 : 32

    anchors.centerIn: parent
  }

  onClicked: {
    // toggle isSelected
    isSelected = !isSelected

    // if the button is now selected, emit the selected signal
    if(isSelected)
      selected()
    else
      unselected()
  }

  onEntityWasBuilt: {
    // get built entity by it's id
    var builtEntity = entityManager.getEntityById(builtEntityId)

    // if buildEntity exists...
    if(builtEntity) {
      // ...add undoObject to undoHandler
      var undoObjectProperties = {"target": builtEntity, "action": "create",
        "currentPosition": Qt.point(builtEntity.x, builtEntity.y)}
      var undoObject = editorOverlay.undoHandler.createUndoObject(undoObjectProperties)
      editorOverlay.undoHandler.push([undoObject])
    }
  }
}
<file_sep>/qml/editorElements/EditorUnderlay.qml
import VPlay 2.0
import QtQuick 2.0

// the PinchArea enables pinch-zooming (2-finger-zoom)
PinchArea {
  id: pinchArea

  // this is the gameScene, under which this underlay is put
  property var scene: parent

  // makes accessing editorOverlay easier
  property var editorOverlay: scene.editorOverlay

  // disabled by default
  enabled: false

  // the pinch and mouse area should fill the game window, not just the scene
  anchors.fill: parent.gameWindowAnchorItem

  onPinchStarted: console.debug("pinch started")

  onPinchUpdated: {
    // calculate actual zoom factor
    var zoomFactor = pinch.scale / pinch.previousScale

    // apply zoom
    scene.camera.applyZoom(zoomFactor, pinch.startCenter)
  }

  onPinchFinished: console.debug("pinch finished")

  // mouse area for object placement and camera movement
  // Note: We have to put the MouseArea inside the PinchArea for both areas to work.
  // This is a known issue: https://bugreports.qt.io/browse/QTBUG-35273
  MouseArea {
    id: baseEditMouseArea

    anchors.fill: parent

    // enabled, if pinch area is enabled
    enabled: pinchArea.enabled

    // The time since the last try to place an entity. We
    // use this to add some time between placing entities
    // in draw mode. This improves the performance, since
    // we don't need to check if the entity can be built
    // in every frame.
    property var lastCreateTime: 0

    // store previous mouse location to calculate drag movement for camera
    property point prevMouseLocation: Qt.point(0, 0)

    // to check how much the camera was moved in a whole drag
    property point dragStartPosition

    // here we store the distance of the last mouse drag
    property real dragDistance: 0

    // When draw-creating, or -removing entities, we want to be able to undo/redo
    // the whole draw stroke at once. This list temporarily holds all created/removed
    // entities, while the drawing is in progress. onRelease this list is pushed to
    // the undoHandler and reset.
    property var undoObjectsSubList: []

    onClicked: {
      // if in draw mode,
      // OR if in hand mode AND the mouse moved just a little bit...
      // (We add this dragDistance check, because we also want to place an entity
      // if the user clicks to place, but the mouse/finger moves a little bit
      // during the click)
      if(editorOverlay.sidebar.activeTool === "draw" || (editorOverlay.sidebar.activeTool === "hand" && dragDistance < 4)) {
        // ...place entity
        var entity = editorOverlay.placeEntityAtPosition(mouseX, mouseY)

        // if entity was successfully created
        if(entity) {
          // add undoObject to undoHandler
          var undoObjectProperties = {"target": entity, "action": "create",
            "currentPosition": Qt.point(entity.x, entity.y)}
          var undoObject = editorOverlay.undoHandler.createUndoObject(undoObjectProperties)
          editorOverlay.undoHandler.push([undoObject])
        }
      }
    }

    onPressed: {
      // if draw editorOverlay.sidebar.activeTool is active, set editorOverlay.selectedButton property to currently
      // selected BuildEntityButton
      if(editorOverlay.sidebar.activeTool === "draw") {
        for(var i=0; i<editorOverlay.sidebar.buttons.length; i++) {
          if(editorOverlay.sidebar.buttons[i].isSelected) {
            editorOverlay.selectedButton = editorOverlay.sidebar.buttons[i]
          }
        }
      }

      if(editorOverlay.sidebar.activeTool === "hand") {
        // save current mouse location
        prevMouseLocation.x = mouseX
        prevMouseLocation.y = mouseY

        // save drag start position
        dragStartPosition = Qt.point(mouseX, mouseY)
      }
    }

    // this is called every time the mouse is moved while it's pressed
    onPositionChanged: {
      if(editorOverlay.sidebar.activeTool === "draw") {
        var currentTime = new Date().getTime() // get current time

        // Calculate time since last try to create an entity.
        // If it's over a threshold, try to place another entity.
        // This improves the performance, as we don't have to
        // check for collisions on every position change.
        if(currentTime - lastCreateTime > 5) {
          // place entity
          var entity = editorOverlay.placeEntityAtPosition(mouseX, mouseY)

          // if entity was successfully created
          if(entity) {
            // add undoObject of entity to temporary undoObjectsSubList
            var undoObjectProperties = {"target": entity, "action": "create",
              "currentPosition": Qt.point(entity.x, entity.y)}
            var undoObject = editorOverlay.undoHandler.createUndoObject(undoObjectProperties)

            undoObjectsSubList.push(undoObject)

            // save new lastCreateTime
            lastCreateTime = new Date().getTime()
          }
        }
      }
      else if(editorOverlay.sidebar.activeTool === "erase") {
        // convert mouse to level coordinates
        var mousePosInLevel = editorOverlay.mouseToLevelCoordinates(mouseX, mouseY)
        // get body at mouse position
        var body = physicsWorld.bodyAt(mousePosInLevel)

        // if body exists, remove entity
        if(body) {
          // get target object
          var target = body.target

          // remove entitiy
          var undoObject = editorOverlay.removeEntity(target)

          // add undoObject to temporary undoObjectsSubList
          undoObjectsSubList.push(undoObject)
        }
      }
      else if(editorOverlay.sidebar.activeTool === "hand"){
        // move camera
        // calculate mouse movement since last frame
        var deltaX = prevMouseLocation.x - mouseX
        var deltaY = prevMouseLocation.y - mouseY

        // update camera position
        scene.camera.moveFreeCamera(deltaX, deltaY)

        // save current mouse location as previous mouse location
        prevMouseLocation.x = mouseX
        prevMouseLocation.y = mouseY
      }
    }

    onReleased: {
      if(editorOverlay.sidebar.activeTool === "draw" || editorOverlay.sidebar.activeTool === "erase") {
        if(undoObjectsSubList.length > 0) {
          // push undoObjectsSubList to undoHandler
          editorOverlay.undoHandler.push(undoObjectsSubList)

          // reset undoObjectsSubList
          undoObjectsSubList = []
        }
      }
      else if(editorOverlay.sidebar.activeTool === "hand") {
        // calculate moving distance since pressed event
        var deltaX = dragStartPosition.x - mouseX
        var deltaY = dragStartPosition.y - mouseY

        // calculate the total distance of the drag
        dragDistance = Math.sqrt(deltaX * deltaX + deltaY * deltaY)

        // apply the current movement velocity to the camera
        scene.camera.applyVelocity()
      }
    } // onReleased end

    onWheel: {
      // get mouse position
      var mousePos = Qt.point(wheel.x, wheel.y)

      // determine if the mouse wheel is rotated upwards or downwards
      // zoom in or out, depending on the rotation direction
      if(wheel.angleDelta.y > 0)
        scene.camera.applyZoom(1.05, mousePos)
      else
        scene.camera.applyZoom(1 / 1.05, mousePos)

      console.debug("zoom via mouseWheel")
    }
  } // MouseArea end
}<file_sep>/qml/undo/UndoObject.qml
import QtQuick 2.0
import VPlay 2.0

Item {
  id: undoObject

  // the affected object
  property var target

  // for re-creation of entity
  property var targetEntityType
  property var targetVariationType

  // the executed action
  // "create", "remove" or "move"
  property string action

  // the object's current position
  property point currentPosition

  // the object's other position (only used for "move")
  property point otherPosition

  // when the target is set, we save it's entity- and variationType
  onTargetChanged: {
    if(target !== null) {
      targetEntityType = target.entityType
      targetVariationType = target.variationType
    }
  }

  /**
   * Undo, Redo ----------------------------------------
   */

  // undo this action
  function undo() {
    if(action == "create") {
      console.debug("undo create "+target+", at "+currentPosition)

      // remove entity
      removeTargetObject()
    }
    else if(action == "remove") {
      console.debug("undo remove "+target+", at "+currentPosition)

      // re-create the removed object
      createTargetObject()
    }
    else if(action == "move") {
      console.debug("undo move "+target+", from "+otherPosition
                    +" to "+currentPosition)

      // undo move
      moveTargetObject()
    }
    else {
      console.debug("unknown action: "+action)
    }
  }

  // redo this action
  function redo() {
    if(action == "create") {
      console.debug("redo create "+target+", at "+currentPosition)

      // re-create the previously removed object
      createTargetObject()
    }
    else if(action == "remove") {
      console.debug("redo remove "+target+", at "+currentPosition)

      // re-remove entity
      removeTargetObject()
    }
    else if(action == "move") {
      console.debug("redo move "+target+", from "+otherPosition
                    +" to "+currentPosition)

      // redo move
      moveTargetObject()
    }
    else {
      console.debug("unknown action: "+action)
    }
  }

  // create a new object with targetEntityType and
  // targetVariationType
  function createTargetObject() {
    // set properties
    var properties = {
      entityType: targetEntityType,
      variationType: targetVariationType,
      x: currentPosition.x,
      y: currentPosition.y
    }
    // create object from properties and store entityId
    var newEntityId = entityManager.createEntityFromEntityTypeAndVariationType(properties)

    // set freshly created entity as new target
    target = entityManager.getEntityById(newEntityId)
  }

  // remove a previously created object
  function removeTargetObject() {
      // get the object in the grid of currentPosition, and make it the new target
      var bodyAtPos = Qt.point(currentPosition.x + 16, currentPosition.y + 16)
      target = gameScene.physicsWorld.bodyAt(bodyAtPos).target

      // if the target now exists, remove it
      if(target) {
        entityManager.removeEntityById(target.entityId)
      }
  }

  // move the target from currentPosition to otherPosition
  function moveTargetObject() {
    // look for object at currentPosition
    var bodyAtPos = Qt.point(currentPosition.x + 16, currentPosition.y + 16)
    target = gameScene.physicsWorld.bodyAt(bodyAtPos).target

    // if the target now exists, remove it
    if(target) {
      // set position to otherPosition
      target.x = otherPosition.x
      target.y = otherPosition.y

      // set the entities lastPosition property
      target.lastPosition = Qt.point(target.x, target.y)

      // switch currentPosition and otherPosition
      var helper = Qt.point(currentPosition.x, currentPosition.y)
      currentPosition = otherPosition
      otherPosition = helper
    }
  }

}
<file_sep>/qml/scenes/LevelScene.qml
import QtQuick 2.0
import VPlay 2.0
import "../common"
import "dialogs"
import "sceneElements"

SceneBase {
  id: levelScene

  property int timeLimit: 0

  // can be: "created_at", "average_quality", "times_downloaded"
  property string order: "created_at"

  // this alias enables access to the dialog from the outside
  property alias removeLevelDialog: removeLevelDialog

  // holds the currently active level grid
  property var activeLevelGrid

  // visibility settings
  property bool demoLevelsVisible: state == "demoLevels"
  property bool myLevelsVisible: state == "myLevels"
  property bool myCreatedLevelsVisible: myLevelsVisible && subState == "createdLevels"
  property bool myDownloadedLevelsVisible: myLevelsVisible && subState == "downloadedLevels"
  property bool communityLevelsVisible: state == "communityLevels"

  // signals
  signal newLevelPressed
  signal playLevelPressed(var levelData)
  signal editLevelPressed(var levelData)
  signal removeLevelPressed(var levelData)
  signal backPressed

  // can be: "demoLevels", "myLevels", "communityLevels"
  state: "myLevels"
  // can be: "createdLevels", "downloadedLevels"
  property string subState: "createdLevels"

  // handle state changes
  onStateChanged: reloadLevels()
  onSubStateChanged: reloadLevels()

  // reload levels, when time limit or order is changed
  onTimeLimitChanged: reloadLevels()
  onOrderChanged: reloadLevels()

  onActiveLevelGridChanged: {
    // if the communityLevelGrid is active, show the loading screen
    if(activeLevelGrid === communityLevelGrid) {
      activeLevelGrid.startLoading()
    }
    // all other level grids don't need any loading time, so we can immediately
    // call finishLoading(), which displays all levels
    else {
      activeLevelGrid.finishLoading()
    }
  }

  // background
  Rectangle {
    id: background

    anchors.fill: parent.gameWindowAnchorItem

    gradient: Gradient {
      GradientStop { position: 0.0; color: "#4595e6" }
      GradientStop { position: 0.9; color: "#80bfff" }
      GradientStop { position: 0.95; color: "#009900" }
      GradientStop { position: 1.0; color: "#804c00" }
    }
  }

  Rectangle {
    id: mainBar

    width: parent.gameWindowAnchorItem.width
    height: 40

    anchors.top: parent.gameWindowAnchorItem.top
    anchors.left: parent.gameWindowAnchorItem.left

    // background gradient
    gradient: Gradient {
      GradientStop { position: 0.0; color: "transparent" }
      GradientStop { position: 0.5; color: "#80ffffff" }
      GradientStop { position: 1.0; color: "transparent" }
    }

    Row {
      height: 30

      anchors.centerIn: parent

      spacing: 5

      PlatformerSelectableTextButton {
        id: demoLevels

        screenText: "Demos"

        width: 80

        // this button is selected if the state is demoLevels
        isSelected: levelScene.state == "demoLevels"

        // set state to demoLevels
        onClicked: levelScene.state = "demoLevels"
      }

      PlatformerSelectableTextButton {
        id: myLevels

        screenText: "My Levels"

        width: 80

        // this button is selected if the state is myLevels
        isSelected: levelScene.state == "myLevels"

        // set state to myLevels
        onClicked: levelScene.state = "myLevels"
      }

      PlatformerSelectableTextButton {
        id: communityLevels

        screenText: "Community"

        width: 80

        // this button is selected if the state is communityLevels
        isSelected: levelScene.state == "communityLevels"

        // set state to communityLevels
        onClicked: levelScene.state = "communityLevels"
      }
    }

    // back to menu button
    PlatformerImageButton {
      id: menuButton

      image.source: "../../assets/ui/home.png"

      width: 40
      height: 30

      anchors.verticalCenter: parent.verticalCenter
      anchors.right: parent.right
      anchors.rightMargin: 5

      // go back to MenuScene
      onClicked: backPressed()
    }
  }

  Rectangle {
    id: subBar

    width: parent.gameWindowAnchorItem.width
    height: 40

    anchors.top: mainBar.bottom
    anchors.left: parent.gameWindowAnchorItem.left

    // make mainBar and subBar overlap a little bit
    anchors.topMargin: -1

    // background gradient
    gradient: Gradient {
      GradientStop { position: 0.0; color: "transparent" }
      GradientStop { position: 0.5; color: "#80ffffff" }
      GradientStop { position: 1.0; color: "transparent" }
    }

    // show subBar when myLevels or communityLevels are visible
    visible: myLevelsVisible || communityLevelsVisible

    /**
     * MyLevels
     */
    Row {
      height: 30

      anchors.centerIn: parent

      spacing: 5

      // only visible if myLevels are selected
      visible: myLevelsVisible

      // add new level button
      PlatformerImageButton {
        id: newLevelButton

        width: 50
        height: 30

        // image
        image.source: "../../assets/ui/add.png"

        // background color
        color: "#00d900"

        // emit newLevelPressed signal
        onClicked: newLevelPressed()
      }

      PlatformerSelectableTextButton {
        id: createdLevelsButton

        screenText: "Created"

        width: 80

        // this button is selected, if the subState is createdLevels
        isSelected: subState == "createdLevels"

        // set subState to createdLevels
        onClicked: subState = "createdLevels"
      }

      PlatformerSelectableTextButton {
        id: downloadedLevelsButton

        screenText: "Downloaded"

        width: 80

        // this button is selected, if the subState is downloadedLevels
        isSelected: subState == "downloadedLevels"

        // set subState to downloadedLevels
        onClicked: subState = "downloadedLevels"
      }
    }

    /**
     * CommunityLevels
     */
    Row {
      height: 30

      anchors.centerIn: parent

      spacing: 5

      // only visible if communityLevels are selected
      visible: communityLevelsVisible

      PlatformerTextButton {
        id: orderButton

        // set text depending on order
        screenText: order == "created_at" ? "Newest" : order == "average_quality" ? "Highest Rated" : order == "times_downloaded" ? "Most Downloaded" : ""

        // open dialog to set another order
        onClicked: levelOrderDialog.opacity = 1
      }

      PlatformerTextButton {
        id: timeLimitButton

        // set text depending on timeLimit
        screenText: timeLimit == 0 ? "All Time" : timeLimit == 24 * 7 ? "This Week" : timeLimit == 24 ? "Today" : ""

        // set width to orderButton width, to make sure that these two buttons
        // always have the same width
        width: orderButton.width

        // open dialog to set another time limit
        onClicked: levelTimeLimitDialog.opacity = 1
      }
    }
  }

  // this displays all demo levels
  LevelGrid {
    id: demoLevelGrid

    visible: demoLevelsVisible

    levelMetaDataArray: gameWindow.levelEditor.applicationJSONLevels
  }

  // this displays all created levels
  LevelGrid {
    id: createdLevelGrid

    visible: myCreatedLevelsVisible

    // we only show authorGeneratedLevels
    levelMetaDataArray: gameWindow.levelEditor.authorGeneratedLevels
  }

  // this displays all downloaded levels
  LevelGrid {
    id: downloadedLevelGrid

    visible: myDownloadedLevelsVisible

    // we only show authorGeneratedLevels
    levelMetaDataArray: gameWindow.levelEditor.downloadedLevels
  }

  // this displays all community levels
  LevelGrid {
    id: communityLevelGrid

    visible: communityLevelsVisible

    // we only show authorGeneratedLevels
    levelMetaDataArray: gameWindow.levelEditor.communityLevels
  }

  /**
   * DIALOGS --------------------------------------------------
   */
  LevelOrderDialog {
    id: levelOrderDialog
  }

  LevelTimeLimitDialog {
    id: levelTimeLimitDialog
  }

  // this dialog pops up, when the user clicks the remove button of
  // one of the levels
  RemoveLevelDialog {
    id: removeLevelDialog
  }

  UnpublishDialog {
    id: unpublishDialog
  }

  RatingDialog {
    id: ratingDialog

    onLevelRated: reloadLevels()
  }

  /**
   * JAVASCRIPT FUNCTIONS
   */

  // reload levels
  function reloadLevels() {
    var params = {}
    if(timeLimit > 0) {
      params.timeLimit = timeLimit
    }
    params.order = order

    if(state == "demoLevels") {
      levelEditor.loadAllLevelsFromStorageLocation(levelEditor.applicationJSONLevelsLocation)
      activeLevelGrid = demoLevelGrid
    }
    else if(state == "myLevels") {
      if(subState == "createdLevels") {
        levelEditor.loadAllLevelsFromStorageLocation(levelEditor.authorGeneratedLevelsLocation)
        activeLevelGrid = createdLevelGrid
      }
      else if(subState == "downloadedLevels") {
        levelEditor.loadAllLevelsFromStorageLocation(levelEditor.downloadedLevelsLocation)
        activeLevelGrid = downloadedLevelGrid
      }
      else {
        console.debug("invalid subState", subState)
      }
    }
    else if (state == "communityLevels") {
      levelEditor.loadCommunityLevels(params)
      activeLevelGrid = communityLevelGrid
    }
    else {
      console.debug("invalid state", state)
    }
  }
}
<file_sep>/qml/common/AudioManager.qml
import VPlay 2.0
import QtQuick 2.0
import QtMultimedia 5.0

Item {
  id: audioManager

  Component.onCompleted: handleMusic()

  /**
   * Background Music ----------------------------------
   */
  BackgroundMusic {
    id: menuMusic

    autoPlay: false

    source: "../../assets/audio/music/menuMusic.mp3"
  }

  BackgroundMusic {
    id: playMusic

    autoPlay: false

    source: "../../assets/audio/music/playMusic.mp3"
  }

  BackgroundMusic {
    id: editMusic

    autoPlay: false

    source: "../../assets/audio/music/editMusic.mp3"
  }

  /**
   * Sounds ----------------------------------
   */
  SoundEffectVPlay {
    id: playerJump
    source: "../../assets/audio/sounds/phaseJump1.wav"
  }

  SoundEffectVPlay {
    id: playerHit
    source: "../../assets/audio/sounds/whizz.wav"
  }

  SoundEffectVPlay {
    id: playerDie
    source: "../../assets/audio/sounds/lose.wav"
  }

  SoundEffectVPlay {
    id: playerInvincible
    source: "../../assets/audio/sounds/threeTone2.wav"
    loops: SoundEffect.Infinite
  }

  SoundEffectVPlay {
    id: collectCoin
    source: "../../assets/audio/sounds/coin_3.wav"
  }

  SoundEffectVPlay {
    id: collectMushroom
    source: "../../assets/audio/sounds/zapThreeToneUp.wav"
  }

  SoundEffectVPlay {
    id: finish
    source: "../../assets/audio/sounds/coin-04.wav"
  }

  SoundEffectVPlay {
    id: opponentWalkerDie
    source: "../../assets/audio/sounds/bird-chirp.wav"
  }

  SoundEffectVPlay {
    id: opponentJumperDie
    source: "../../assets/audio/sounds/twitch.wav"
  }

  SoundEffectVPlay {
    id: start
    source: "../../assets/audio/sounds/yahoo.wav"
  }

  SoundEffectVPlay {
    id: click
    source: "../../assets/audio/sounds/click1.wav"
  }

  SoundEffectVPlay {
    id: dragEntity
    source: "../../assets/audio/sounds/slide-network.wav"
  }

  SoundEffectVPlay {
    id: createOrDropEntity
    source: "../../assets/audio/sounds/tap_professional.wav"
  }

  SoundEffectVPlay {
    id: removeEntity
    source: "../../assets/audio/sounds/tap_mellow.wav"
  }

  // this function sets the music, depending on the current scene and the gameScene's state
  function handleMusic() {
    if(activeScene === gameScene) {
      if(gameScene.state == "play" || gameScene.state == "test")
        audioManager.startMusic(playMusic)
      else if(gameScene.state == "edit")
        audioManager.startMusic(editMusic)
    }
    else {
      audioManager.startMusic(menuMusic)
    }
  }

  // starts the given music
  function startMusic(music) {
    // if music is already playing, we don't have to do anything
    if(music.playing)
      return

    // otherwise stop all music tracks
    menuMusic.stop()
    playMusic.stop()
    editMusic.stop()

    // play the music
    music.play()
  }

  // play the sound effect with the given name
  function playSound(sound) {
    if(sound === "playerJump")
      playerJump.play()
      else if(sound === "playerHit")
      playerHit.play()
    else if(sound === "playerDie")
      playerDie.play()
    else if(sound === "playerInvincible")
      playerInvincible.play()
    else if(sound === "collectCoin")
      collectCoin.play()
      else if(sound === "collectMushroom")
      collectMushroom.play()
    else if(sound === "finish")
      finish.play()
    else if(sound === "opponentWalkerDie")
      opponentWalkerDie.play()
    else if(sound === "opponentJumperDie")
      opponentJumperDie.play()
    else if(sound === "start")
      start.play()
    else if(sound === "click")
      click.play()
    else if(sound === "dragEntity")
      dragEntity.play()
    else if(sound === "createOrDropEntity")
      createOrDropEntity.play()
    else if(sound === "removeEntity")
      removeEntity.play()
    else
      console.debug("unknown sound name:", sound)
  }

  // stop the sound effect with the given name
  function stopSound(sound) {
    if(sound === "playerInvincible")
      playerInvincible.stop()
    else
      console.debug("unknown sound name:", sound)
  }
}
<file_sep>/qml/entities/Opponent.qml
import QtQuick 2.0
import VPlay 2.0
import "../editorElements"

// this is the base class for all opponents
PlatformerEntityBaseDraggable {
  id: opponent
  entityType: "opponent"

  // the opponent's start position
  // These are the coordinates, to which the opponent gets reset,
  // when resetting the level.
  property int startX
  property int startY

  // this is true while the opponent is alive
  property bool alive: true

  // After an opponent dies, we want to show it's dead-sprite for a short
  // time, and then hide it.
  // If this property is true, the opponent is invisible.
  property bool hidden: false

  z: 1 // to make opponent appear in front of the platforms

  // set the opponent's size to it's sprite's size
  width: image.width
  height: image.height

  // hide opponent after its death
  image.visible: !hidden

  // update the entity's start position when the entity is created or moved
  onEntityCreated: updateStartPosition()
  onEntityReleased: updateStartPosition()

  // this timer hides the opponent a few seconds after its death
  Timer {
    id: hideTimer
    interval: 2000

    onTriggered: hidden = true
  }

  function updateStartPosition()
  {
    startX = x
    startY = y
  }

  // this function resets all properties, which all opponents have in common
  function reset_super()
  {
    // reset alive property
    alive = true

    // stop hideTimer, to avoid unwanted, delayed hiding of the opponent
    hideTimer.stop()
    // reset hidden
    hidden = false

    // reset position
    x = startX
    y = startY

    // reset velocity
    collider.linearVelocity.x = 0
    collider.linearVelocity.y = 0

    // reset force
    collider.force = Qt.point(0, 0)
  }

  function die() {
    alive = false

    hideTimer.start()

    if(variationType == "walker")
      audioManager.playSound("opponentWalkerDie")
    else if(variationType == "jumper")
      audioManager.playSound("opponentJumperDie")

    // for every killed opponent, the time gets set back a little bit
    gameScene.time -= 5
  }
}
<file_sep>/qml/editorElements/ItemGroupButton.qml
import QtQuick 2.0
import VPlay 2.0
import "../common"

PlatformerImageButton {
  id: itemGroupButton

  width: parent.buttonWidth
  height: parent.height

  // make the color grey when selected, white otherwise
  color: selected ? "#c0c0c0" : "#ffffff"

  property bool selected: false
}
<file_sep>/qml/scenes/sceneElements/LevelGrid.qml
import QtQuick 2.0
import VPlay 2.0

Item {
  id: levelGrid

  // anchoring
  // if state is demoLevels, the subBar is hidden, so we anchor it to the mainBar
  anchors.top: levelScene.state == "demoLevels" ? mainBar.bottom : subBar.bottom
  anchors.bottom: levelScene.gameWindowAnchorItem.bottom
  anchors.left: levelScene.left
  anchors.right: levelScene.right

  anchors.margins: 5
  anchors.topMargin: 0
  anchors.bottomMargin: 0

  // this property stores all level's meta data
  property var levelMetaDataArray

  // is true, if the levels are loading right now
  property bool isLoading: false

  onLevelMetaDataArrayChanged: {
    // if the levels are not currently loading...
    if(!isLoading) {
      // ...set the levelListRepeater model to the levelMetaDataArray
      levelListRepeater.model = levelMetaDataArray
    }
  }

  Flickable {
    id: levelGridFlickable

    anchors.fill: parent

    // clip contents at item borders
    clip: true

    // set the content size to the grid's size
    contentWidth: grid.width
    // add twice the offset's height to the contentHeight, to add a little margin
    // at the top and bottom of the grid
    contentHeight: grid.height + 2 * offset.height

    // only allow vertical flicking
    flickableDirection: Flickable.VerticalFlick

    // this adds a little offset on top of the level grid
    Item {
      id: offset
      width: parent.width
      height: 5

      anchors.top: parent.top
    }

    Grid {
      id: grid

      anchors.top: offset.bottom

      columns: 4
      spacing: 10

      // the repeater adds a levelItemDelegate item for each level
      Repeater {
        id: levelListRepeater

        // delegate is the default property of Repeater
        delegate: LevelSelectionItem {}
      }
    }
  }

  /**
   * LOADING
   */

  Text {
    id: loadingText

    text: "Loading"

    // this text is visible while the levels are loading
    visible: isLoading

    // position
    anchors.centerIn: parent
    anchors.verticalCenterOffset: -40

    // font family and size
    font.family: marioFont.name
    font.pixelSize: 40

    // text color
    color: "#fff833"

    // add outline
    style: Text.Outline
    styleColor: "black"
  }

  Timer {
    id: loadingTimer

    interval: 1000

    onTriggered: finishLoading()
  }

  function startLoading() {
    // remove all elements immediately to see an instant level loading screen
    levelListRepeater.model = null

    // set isLoading to true
    isLoading = true

    // delay rest to render the loading sign
    loadingTimer.start()
  }

  function finishLoading() {
    // set isLoading to false again
    isLoading = false

    // add all level elements again
    levelListRepeater.model = levelMetaDataArray
  }
}
<file_sep>/qml/scenes/dialogs/DialogBase.qml
import QtQuick 2.0
import VPlay 2.0
import "../../common"

Item {
  id: dialogBase

  // make it appear in front of anything else
  z: 1000

  // fill game window
  anchors.fill: parent.gameWindowAnchorItem ? parent.gameWindowAnchorItem : parent

  opacity: 0 // opaque by default
  enabled: opacity > 0 // otherwise this item would receive mouse events when opaque

  // if this is true, the user can close this dialog by clicking anywhere in the background
  property bool closeableByClickOnBackground: true

  // this MouseArea fills the whole screen
  MouseArea {
    anchors.fill: parent

    // when it's clicked and closeableByClickOnBackground is true, the dialog should be closed
    onClicked: {
      if(closeableByClickOnBackground)
        dialogBase.opacity = 0
    }
  }

  // background
  Rectangle {
    anchors.fill: parent

    color: "black"
    opacity: 0.9
  }
}
<file_sep>/qml/entities/Player.qml
import VPlay 2.0
import QtQuick 2.0
import "../editorElements"

PlatformerEntityBaseDraggable {
  id: player
  entityType: "player"

  /**
   * Custom properties ----------------------------------------------------
   */
  // the player's start position
  property int startX
  property int startY

  // add aliases for easier access
  property alias horizontalVelocity: collider.linearVelocity.x
  property alias verticalVelocity: collider.linearVelocity.y

  // the number of collected coins
  property int score: 0

  // if this is true, the player can jump once, without touching the ground
  property bool doubleJumpEnabled: true

  // if isBig is true, the player sprite is higher, and the
  // player touch one enemy/spike without dying (like in Super Mario games)
  property bool isBig: false

  // if true, the player is invincible
  property bool invincible: false

  // the time in ms, the player is invincible, after collecting a star
  property int starInvincibilityTime: 4000

  // this property is used to determine if the player touches any solid
  // objects (e.g. ground or platform)
  property int contacts: 0

  // these properties set the player's jump force for normal- and
  // kill-jumps
  property int normalJumpForce: 210
  property int killJumpForce: 420

  // This property holds how many more iterations the player's
  // jump height can increase. We use this to control the jump height.
  // See the ascentControl Timer for more details.
  property int jumpForceLeft: 20

  // the player's accelerationForce
  property int accelerationForce: 1000

  // the factor by how much the player's speed decreases when
  // the user stops pressing left or right
  property real decelerationFactor: 0.6

  // the player's maximum movement speed
  property int maxSpeed: 230

  // maximum falling speed
  property int maxFallingSpeed: 800

  /**
   * Signals ----------------------------------------------------
   */

  // signal for finishing the level
  signal finish

  /**
   * Object properties ----------------------------------------------------
   */

  // set the player's size to his image size
  width: image.width
  height: image.height

  // define colliderComponent for collision detection while dragging
  colliderComponent: collider

  // set image
  image.source: "../../assets/player/player.png"

  // make player smaller, if isBig is false
  scale: isBig ? 1 : 0.64
  // animate scale changes
  Behavior on scale { NumberAnimation { duration: 500 } }

  // transform from the center of the bottom
  transformOrigin: Item.Bottom

  // If the player touches another solid object, we set his state
  // to "walking". Otherwise we set it to "jumping".
  // The player can only jump when "walking", or by using the doubleJump.
  state: contacts > 0 ? "walking" : "jumping"

  // make player unremovable
  preventFromRemovalFromEntityManager: true

  // limit falling speed
  onVerticalVelocityChanged: {
    if(verticalVelocity > maxFallingSpeed)
      verticalVelocity = maxFallingSpeed
  }

  onFinish: audioManager.playSound("finish")

  /**
   * Colliders ---------------------------------------
   */

  // the player's main collider
  PolygonCollider {
    id: collider

    // We set the collider vertices to fit the body of the player.
    // Note, that we set the very left and right bottom vertices
    // to 62.8/62.85. This avoids collision with edges of solid objects,
    // when the player moves.
    // Also we have two different shapes, depending on the size
    // of the player.
    vertices: isBig ?
    // big collider
    [
      Qt.point(15, 24),
      Qt.point(25, 14),
      Qt.point(40, 14),
      Qt.point(47, 24),
      Qt.point(49, 62.8),
      Qt.point(44, 63),
      Qt.point(19, 63),
      Qt.point(14, 62.8)
    ] :
    // small collider
    [
      Qt.point(21, 39),
      Qt.point(28, 34),
      Qt.point(39, 34),
      Qt.point(43, 39),
      Qt.point(43, 62.85),
      Qt.point(40, 63),
      Qt.point(23, 63),
      Qt.point(20, 62.85)
    ]

    // the bodyType is dynamic in-game
    bodyType: Body.Dynamic

    // the collider should not be active in edit mode
    active: !inLevelEditingMode

    // Category1: player body
    categories: Box.Category1
    // Category5: opponent body, Category5: solids,
    // Category6: powerups, Category7: reset sensor
    collidesWith: Box.Category3 | Box.Category5 | Box.Category6 | Box.Category7

    // we set friction to zero to make the player slide down the sides of other entities
    friction: 0

    // don't allow sleeping
    sleepingAllowed: false

    // Apply the horizontal value of the TwoAxisController as force
    // to move the player left or right.
    force: Qt.point(controller.xAxis * accelerationForce, 0)

    // limit the horizontal velocity
    onLinearVelocityChanged: {
      if(linearVelocity.x > maxSpeed) linearVelocity.x = maxSpeed
      if(linearVelocity.x < -maxSpeed) linearVelocity.x = -maxSpeed
    }

    // this is called whenever the contact with another entity begins
    fixture.onBeginContact: {
      var otherEntity = other.getBody().target

      // on contact with a coin, collect it and increase score
      if(otherEntity.entityType === "coin") {
        otherEntity.collect()

        score += 1
      }
      // on contact with a mushroom, collect it and make player big
      else if(otherEntity.entityType === "mushroom") {
        otherEntity.collect()

        // make big
        isBig = true
      }
      // on contact with a star, collect it and make player invincible
      else if(otherEntity.entityType === "star") {
        otherEntity.collect()

        // make invincible
        startInvincibility(starInvincibilityTime)
      }
    }

    // this is called whenever the contact with another entity changes
    fixture.onContactChanged: {
      var otherEntity = other.getBody().target

      // on contact with an opponent or spikes, call the die() function
      if(otherEntity.entityType === "opponent" || otherEntity.entityType === "spikes") {
        die(false)
      }
    }
  }

  // this collider handles contacts and the killing of opponents
  BoxCollider {
    id: feetSensor

    // set and adjust size, depending on player size (big or small)
    width: 32 * parent.scale
    height: 10 * parent.scale

    // set position
    anchors.horizontalCenter: parent.horizontalCenter
    anchors.top: parent.bottom

    // the bodyType is dynamic
    bodyType: Body.Dynamic

    // the collider should not be active in edit mode
    active: gameScene.state === "edit" ? false : true

    // Category2: player feet sensor
    categories: Box.Category2
    // Category3: opponent body, Category5: solids
    collidesWith: Box.Category3 | Box.Category5

    // this is only a sensor, so it should not physically collide
    // with any other object
    collisionTestingOnlyMode: true

    // this is called whenever the contact with another entity begins
    fixture.onBeginContact: {
      var otherEntity = other.getBody().target

      // if colliding with opponent...
      if(otherEntity.entityType === "opponent") {
        // ...calculate the lowest point of the player and
        // the opponent...
        var playerLowestY = player.y + player.height
        var oppLowestY = otherEntity.y + otherEntity.height

        // ...and if the player's y position is at least
        // 5px above the opponent's...
        if(playerLowestY < oppLowestY - 5) {
          // ...kill opponent...
          console.debug("kill opponent")
          otherEntity.die()

          // ...and jump in the air
          startJump(false)
        }
      }
      // else if colliding with another solid object...
      else if(otherEntity.entityType === "platform" || otherEntity.entityType === "ground") {
        // ...increase the player's contacts
        contacts++

        // if the player stands on this object...
        if(verticalVelocity >= 0)
          // ...set doubleJumpEnabled to true again
          doubleJumpEnabled = true
      }
    }

    // when contact ends
    fixture.onEndContact: {
      var otherEntity = other.getBody().target

      // when collision with another object ends, decrease the player's contacts
      if(otherEntity.entityType === "platform" || otherEntity.entityType === "ground")
        contacts--
    }
  }

  // this sensor makes sure, that the user can't draw/create any
  // entities close to the player
  BoxCollider {
    id: editorCollider

    anchors.fill: parent

    collisionTestingOnlyMode: true

    // Category16: misc
    categories: Box.Category16
  }

  /**
   * Update timer --------------------------------------------------------
   */

  // This timer is used to slow down the players horizontal movement,
  // when the controllers xAxis is in neutral position (0).
  // The collider's linearDamping property works similar, but also
  // slows down the vertical velocity - which we don't want.
  Timer {
    id: updateTimer

    // Set this interval as high as possible to improve performance,
    // but as low as needed so it still looks good.
    interval: 60

    running: true
    repeat: true

    onTriggered: {
      var xAxis = controller.xAxis;

      // if xAxis is 0 (no movement command) we slow the player down
      // until he stops
      if(xAxis == 0) {
        if(Math.abs(player.horizontalVelocity) > 10)
          player.horizontalVelocity *= decelerationFactor
        else
          player.horizontalVelocity = 0
      }
    }
  }

  /**
   * Invincibility ------------------------------------------------------
   */

  // this is the overlay image, that signals, that the player is invincible
  MultiResolutionImage {
    id: invincibilityOverlayImage

    source: "../../assets/player/player_rainbow.png"

    opacity: 0

    // this animation fades out the incibility overlay, to signal, that
    // the invincibility will end soon
    NumberAnimation on opacity {
      id: invincibilityOverlayImageFadeOut

      // slowly reduce opacity from 1 to 0
      from: 1
      to: 0

      // duration of the animation, in ms
      duration: 500
    }
  }

  // this timer is triggered shortly before invincibility ends, to signal,
  // that it will end soon
  Timer {
    id: invincibilityWarningTimer

    onTriggered: warnInvincibility()
  }

  // as long as this timer is running, the player is invincible
  // when it is triggered, invincibility ends
  Timer {
    id: invincibilityTimer

    onTriggered: endInvincibility()
  }

  /**
   * ascentControl ------------------------------------------------------
   */

  // The ascentControl allows the player to jump higher, when pressing the
  // jump button longer, and lower, when pressing the jump button shorter.
  // It is running while the player presses the jump button.
  Timer {
    id: ascentControl

    // every 15 ms this is triggered
    interval: 15
    repeat: true

    onTriggered: {
      // If jumpForceLeft is > 0, we set the players verticalVelocity to make
      // him jump.
      if(jumpForceLeft > 0) {

        var verticalImpulse = 0

        // At the beginning of the jump we set the verticalVelocity to 0 and
        // apply a high vertical impulse, to get a high initial vertical
        // velocity.
        if(jumpForceLeft == 20) {
          verticalVelocity = 0

          verticalImpulse = -normalJumpForce
        }
        // After the first strong impulse, we only want to increase the
        // verticalVelocity slower.
        else if(jumpForceLeft >= 14) {
          verticalImpulse = -normalJumpForce / 5
        }
        // Then, after about a third of our maximum jump time, we further
        // reduce the verticalImpulse.
        else {
          verticalImpulse = -normalJumpForce / 15
        }
        // Reducing the verticalImpulse over time allows for a more precise
        // controlling of the jump height.
        // Also it gives the jump a more natural feeling, than using a constant
        // value.

        // apply the impulse
        collider.applyLinearImpulse(Qt.point(0, verticalImpulse))

        // decrease jumpForceLeft
        jumpForceLeft--
      }
    }
  }

  /**
   * Item editor -----------------------------------------
   */
  // make properties editable via itemEditor
  EditableComponent {
    editableType: "Balance"
    properties: {
      "Player" : {
        "normalJumpForce": {"min": 0, "max": 400, "stepSize": 5, "label": "Jump Force"},
        "killJumpForce": {"min": 0, "max": 1000, "stepSize": 10, "label": "Kill Jump Force"},
        "accelerationForce": {"min": 0, "max": 5000, "stepSize": 10, "label": "Acceleration"},
        "maxSpeed": {"min": 0, "max": 400, "stepSize": 5, "label": "Speed"},
        "maxFallingSpeed": {"min": 5, "max": 1000, "stepSize": 5, "label": "Max Falling Speed"}
      },
      "Power-Ups" : {
        "starInvincibilityTime": {"min": 500, "max": 10000, "stepSize": 100, "label": "Star Duration (ms)"}
      }
    }
  }

  /**
   * Game related JS functions --------------------------------------------------
   */

  // This function is called, when the user presses the jump button or, when
  // the player jumps on an opponent.
  // isNormalJump is true, when the user pressed the jump button.
  // When jumping after a kill jump, isNormalJump is false.
  function startJump(isNormalJump) {
    if(isNormalJump) {
      // when the player stands on the ground and the jump
      // button is pressed, we start the ascentControl
      if(player.state == "walking") {
        ascentControl.start()
        audioManager.playSound("playerJump")
      }
      // if doubleJumpEnabled, the player can also jump without
      // standing on the ground
      else if(doubleJumpEnabled) {
        ascentControl.start()
        doubleJumpEnabled = false
        audioManager.playSound("playerJump")
      }
    }
    else {
      // When killing an opponent, we want the player to jump
      // a little. We do that by just setting the verticalVelocity
      // to a negative value.
      verticalVelocity = -killJumpForce
    }
  }

  // this function is called, when the user releases the jump button
  function endJump() {
    // stop ascentControl
    ascentControl.stop()

    // reset jumpForceLeft
    jumpForceLeft = 20
  }

  function die(dieImmediately) {
    // if dieImmediately is true OR
    // the player is (NOT big AND NOT invincible)...
    if(dieImmediately || (!isBig && !invincible))
    {
      // ...die
      audioManager.playSound("playerDie")
      gameScene.resetLevel()
    }
    // else if invincible...
    else if(invincible) {
      // ... don't do anything
    }
    // else => (!dieImmediately && !invincible && isBig)...
    else {
      // ...make player small and invincible for a short time
      isBig = false
      startInvincibility(0)

      audioManager.playSound("playerHit")
    }
  }

  function startInvincibility(interval) {
    // this is the time the player is warned, that the invincibility will
    // end soon
    var warningTime = 500

    // the interval (invincibility time) must be at least as long as the
    // warning time
    if(interval < warningTime)
      interval = warningTime

    // show invincibility overlay
    invincibilityOverlayImage.opacity = 1

    // Calculate and set time until the invincibility warning.
    // This value is at least 0.
    invincibilityWarningTimer.interval = interval - warningTime
    // start timer
    invincibilityWarningTimer.start()

    // Calculate and set time until the invincibility ends.
    // This value is at least warningTime.
    invincibilityTimer.interval = interval
    // start timer
    invincibilityTimer.start()

    // enable invincibility
    invincible = true

    audioManager.playSound("playerInvincible")

    console.debug("start invincibility; interval: "+interval)
  }

  function warnInvincibility() {
    // fade out the invincibilityOverlayImage
    invincibilityOverlayImageFadeOut.start()

    console.debug("warn invincibility")
  }

  function endInvincibility() {
    // disable invincibility again
    invincible = false

    audioManager.stopSound("playerInvincible")

    console.debug("stop invincibility")
  }

  // changes the direction in which the player looks, depending on the direction
  // he moves in
  function changeSpriteOrientation() {
    if(controller.xAxis == -1) {
      image.mirrorX = true
      invincibilityOverlayImage.mirrorX = true
    }
    else if (controller.xAxis == 1) {
      image.mirrorX = false
      invincibilityOverlayImage.mirrorX = false
    }
  }

  /**
   * Start position JS functions -----------------------------------------------
   */

  // every time the player is released in edit mode, we update his start position
  onEntityReleased: updateStartPosition()

  function updateStartPosition() {
    startX = x
    startY = y
  }

  // this function tries to load the start position from a saved level, or sets
  // it to a default value
  function loadStartPosition() {
    // load startX if it is saved in the current level
    if(gameWindow.levelEditor && gameWindow.levelEditor.currentLevelData
        && gameWindow.levelEditor.currentLevelData["customData"]
        && gameWindow.levelEditor.currentLevelData["customData"]["playerX"]) {
      startX = parseInt(gameWindow.levelEditor.currentLevelData["customData"]["playerX"])
    }
    else {
      // if there is no startX saved, we set it to a default value
      startX = 32
    }

    // load startY if it is saved in the current level
    if(gameWindow.levelEditor && gameWindow.levelEditor.currentLevelData
        && gameWindow.levelEditor.currentLevelData["customData"]
        && gameWindow.levelEditor.currentLevelData["customData"]["playerY"]) {
      startY = parseInt(gameWindow.levelEditor.currentLevelData["customData"]["playerY"])
    }
    else {
      // if there is no startY saved, we set it to a default value
      startY = -96
    }
  }

  /**
   * Init and reset JS functions -------------------------------------
   */

  function initialize() {
    // load the player's start position from the level
    loadStartPosition()

    // reset the player
    reset()

    // set PlatformerEntityBaseDraggable's lastPosition property
    lastPosition = Qt.point(x, y)
  }

  function reset() {
    // reset position
    x = startX
    y = startY

    // reset velocity
    collider.linearVelocity.x = 0
    collider.linearVelocity.y = 0

    // reset score
    score = 0

    // reset doubleJumpEnabled
    doubleJumpEnabled = true

    // reset isBig
    isBig = false

    // reset invincibility
    invincible = false
    invincibilityTimer.stop()
    invincibilityWarningTimer.stop()
    invincibilityOverlayImage.opacity = 0
    audioManager.stopSound("playerInvincible")
  }

  function resetContacts() {
    // Reset the contacts to ensure the player starts each level
    // with zero contacts.
    contacts = 0
  }
}
<file_sep>/qml/scenes/dialogs/LevelOrderDialog.qml
import QtQuick 2.0
import VPlay 2.0
import "../../common"

DialogBase {
  id: levelOrderDialog

  // Buttons ------------------------------------------

  PlatformerTextButton {
    id: newest

    screenText: "Newest"

    width: 175

    anchors.centerIn: parent
    anchors.verticalCenterOffset: -50

    onClicked: {
      setOrder("created_at")
    }
  }

  PlatformerTextButton {
    id: highestRated

    screenText: "Highest Rated"

    width: 175

    anchors.centerIn: parent

    onClicked: {
      setOrder("average_quality")
    }
  }

  PlatformerTextButton {
    id: mostDownloaded

    screenText: "Most Downloaded"

    width: 175

    anchors.centerIn: parent
    anchors.verticalCenterOffset: 50

    onClicked: {
      setOrder("times_downloaded")
    }
  }

  function setOrder(newOrder) {
    // set new order
    levelScene.order = newOrder

    // close dialog
    levelOrderDialog.opacity = 0
  }
}

<file_sep>/qml/entities/Ground.qml
import QtQuick 2.0
import VPlay 2.0
import "../editorElements"

// this is the base class for all Ground objects
PlatformerEntityBaseDraggable {
  id: ground
  entityType: "ground"

  // define colliderComponent for collision detection while dragging
  colliderComponent: collider

  BoxCollider {
    id: collider

    anchors.fill: parent
    bodyType: Body.Static

    // Category5: solids
    categories: Box.Category5
    // Category1: player body, Category2: player feet sensor,
    // Category3: opponent body, Category4: opponent sensor
    collidesWith: Box.Category1 | Box.Category2 | Box.Category3 | Box.Category4
  }
}
<file_sep>/qml/entities/Finish.qml
import QtQuick 2.0
import VPlay 2.0
import "../editorElements"

PlatformerEntityBaseDraggable {
  id: finish
  entityType: "finish"

  // define colliderComponent for collision detection while dragging
  colliderComponent: collider

  // set image
  image.source: "../../assets/finish/finish.png"

  BoxCollider {
    id: collider

    anchors.fill: parent
    bodyType: Body.Static

    // Category5: solids
    categories: Box.Category5
    // Category1: player body, Category2: player feet sensor,
    // Category3: opponent body, Category4: opponent sensor
    collidesWith: Box.Category1 | Box.Category2 | Box.Category3 | Box.Category4

    // this is called whenever the contact with another entity begins
    fixture.onBeginContact: {
      var otherEntity = other.getBody().target

      // if the collided entity is the player...
      if(otherEntity.entityType === "player") {
        console.debug("Player touches finish")

        // ...we emit the player's finish() signal
        gameScene.player.finish()
      }
    }
  }
}
<file_sep>/qml/undo/UndoHandler.qml
import QtQuick 2.0
import VPlay 2.0

Item {
  id: undoHandler

  // the array, which holds all undoObjects
  property var undoArray: []

  // the pointer, which points at the current position in the
  // undoArray
  property int pointer: -1

  onPointerChanged: console.debug("undo pointer: "+pointer)

  // create a new undoObject with a set of properties
  function createUndoObject(properties) {
    // create UndoObject component
    var component = Qt.createComponent("../undo/UndoObject.qml")

    // create object in gameScene with properties
    var undoObject = component.createObject(gameScene, properties)

    return undoObject
  }

  // add new undoObject to array
  function push(undoObjectList) {
    // before adding a new undoObject, all actions higher than the
    // pointer are removed
    if(undoArray.length > pointer + 1)
      undoArray.splice(pointer+1, undoArray.length)

    // add undoObjectList as element to undoArray
    undoArray.push(undoObjectList)

    // update pointer
    pointer++
  }

  // undo action where pointer points at
  function undo() {
    // if the pointed at undoObject exists...
    if(undoArray[pointer]) {

      // ...undo all actions in this entry
      for(var i=0; i<undoArray[pointer].length; i++) {
        undoArray[pointer][i].undo()
      }

      // update pointer
      pointer--
    }
    else {
      console.debug("nothing to undo")
    }
  }

  // redo action next to where pointer points at
  function redo() {
    // if the undoObject next to the pointed at undoObject exists...
    if(undoArray[pointer+1]) {

      // ...redo all actions in this entry
      for(var i=0; i<undoArray[pointer+1].length; i++) {
        undoArray[pointer+1][i].redo()
      }

      // update pointer
      pointer++
    }
    else {
      console.debug("nothing to redo")
    }
  }

  // print all array elements
  function printArray() {
    console.debug("print undoArray")
    for(var i=0; i< undoArray.length; i++) {
      for(var j=0; j<undoArray[i].length; j++) {
        console.debug(i+"-"+j+": "+undoArray[i][j].target+", "+undoArray[i][j].action+", "+undoArray[i][j].otherPosition+", "+undoArray[i][j].currentPosition)
      }
    }
  }

  // reset undoHandler
  function reset() {
    undoArray = []
    pointer = -1
  }

}
<file_sep>/qml/scenes/GameScene.qml
import VPlay 2.0
import QtQuick 2.0
import "../common"
import "../entities"
import "../editorElements"
import "../gameElements"
import "dialogs"

SceneBase {
  id: gameScene

  // set the scene alignment
  sceneAlignmentX: "left"
  sceneAlignmentY: "top"

  // the level's grid size
  gridSize: 32

  // the time the user is playing the current level, since his last death
  property int time: 0

  // make components visible from the outside
  property alias editorOverlay: editorOverlay
  property alias container: container
  property alias player: player
  property alias physicsWorld: physicsWorld
  property alias bgImage: bgImage
  property alias camera: camera

  // Make itemEditor accessible from the outside.
  // This is necessary for dynamically created items, to be able to
  // set this editor as their target editor.
  property alias itemEditor: editorOverlay.itemEditor

  // this signal is emitted when the user presses the back button
  signal backPressed

  /**
   * States
   */
  state: "play"

  states: [
    State {
      name: "play"
      StateChangeScript {script: audioManager.handleMusic()}
    },
    State {
      name: "edit"
      PropertyChanges {target: physicsWorld; gravity: Qt.point(0,0)} // disable gravity
      PropertyChanges {target: editorUnderlay; enabled: true} // enable the editorUnderlay for placing entities and moving the camera
      PropertyChanges {target: editorOverlay; visible: true} // show the editorOverlay
      PropertyChanges {target: editorOverlay; inEditMode: true}
      StateChangeScript {script: resetLevel()} // reset all entity positions
      StateChangeScript {script: editorOverlay.grid.requestPaint()}
      StateChangeScript {script: audioManager.handleMusic()}
    },
    State {
      name: "test"
      PropertyChanges {target: editorOverlay; visible: true} // show the editorOverlay
      StateChangeScript {script: audioManager.playSound("start")}
      StateChangeScript {script: audioManager.handleMusic()}
      PropertyChanges {target: gameScene; time: 0}
      StateChangeScript {script: levelTimer.restart()}
      PropertyChanges {target: camera; zoom: 1}
    },
    State {
      name: "finish"
      PropertyChanges {target: physicsWorld; running: false} // disable physics
    }
  ]

  /**
   * BACKGROUND -----------------------------------------
   */
  // background image
  BackgroundImage {
    id: bgImage

    anchors.centerIn: parent.gameWindowAnchorItem

    // this property holds which background to show
    property int bg: 0

    // paths to all backgrounds
    property string bg0: "../../assets/backgroundImage/day_bg.png"
    property string bg1: "../../assets/backgroundImage/dusk_bg.png"
    property string bg2: "../../assets/backgroundImage/night_bg.png"

    // if available, load background from levelData
    property int loadedBackground: {
      if(gameWindow.levelEditor && gameWindow.levelEditor.currentLevelData
          && gameWindow.levelEditor.currentLevelData["customData"]
          && gameWindow.levelEditor.currentLevelData["customData"]["background"])
        parseInt(gameWindow.levelEditor.currentLevelData["customData"]["background"])
      else
        -1 // set to -1 if background property is not available
    }

    // set image source depending on bg-property value
    source: bg == 0 ? bg0 : bg == 1 ? bg1 : bg2
  }

  /**
   * MOUSE AND GESTURE CONTROL --------------------------
   */
  EditorUnderlay {
    id: editorUnderlay
  }

  /**
   * GAME ELEMENTS -----------------------------------------
   */
  // entity container
  // here all our entities are placed
  Item {
    id: container

    // When the container is scaled we want the transformation to
    // be applied from the top left corner, since this is also the
    // coordinates origin.
    transformOrigin: Item.TopLeft

    PhysicsWorld {
      id: physicsWorld

      property int gravityY: 40

      // set the gravity
      gravity: Qt.point(0, gravityY)

      debugDrawVisible: false // enable this for physics debugging
      z: 1000 // make sure the debugDraw is above all other game elements

      running: true

      //this is called before the Box2DWorld handles contact events
      onPreSolve: {
        var entityA = contact.fixtureA.getBody().target
        var entityB = contact.fixtureB.getBody().target

        // We want our platforms to be cloud platforms.
        // A cloud platform is a platform through which you can jump
        // from below.

        // If one of the colliding entities is a platform,
        // and the other a moving entity (player or opponent),
        // and the moving entity is below the platform...
        if(entityA.entityType === "platform" && (entityB.entityType === "player" || entityB.entityType === "opponent") && entityB.y + entityB.height > entityA.y + 1 // add +1 to avoid wrong border-line decisions
            || (entityA.entityType === "player" || entityA.entityType === "opponent") && entityB.entityType === "platform" && entityA.y + entityA.height > entityB.y + 1) {

          // ...disable the contact
          contact.enabled = false
        }

        // Disable physical collision handling between player
        // and opponents. This way, we can still handle them,
        // but their physics are not affected.
        if(entityA.entityType === "player" && entityB.entityType === "opponent"
            || entityB.entityType === "player" && entityA.entityType === "opponent") {
          contact.enabled = false
        }
      }

      // add gravity property to item editor
      EditableComponent {
        editableType: "Balance"
        defaultGroup: "Physics"
        properties: {
          "gravityY": {"min": 0, "max": 100, "label": "Gravity"}
        }
      }
    }

    Player {
      id: player

      z: 1 // let the player appear in front of the platforms

      // when colliding with the finish:
      // in test mode: reset the level
      // in play mode: go back to the menu
      onFinish: {
        if(gameScene.state == "test")
          resetLevel()
        else if(gameScene.state == "play") {
          // set state to finish, to freeze the game
          gameScene.state = "finish"

          // handle score
          handleScore()

          // show finish dialog
          finishDialog.score = time
          finishDialog.opacity = 1
        }
      }
    }

    ResetSensor {
      // This sensor is always at the bottom of the level, directly
      // below the player. So the player touches it just before he
      // would fall out of bounds at the bottom of the level.
      // When he touches it, he dies.

      player: player

      // if the player collides with the reset sensor, he dies
      onContact: {
        player.die(true)
      }
    }
  } // container end

  // camera
  CameraVPlay {
    id: camera

    // set the gameWindowSize and entityContainer with which the camera works with
    gameWindowSize: Qt.point(gameScene.gameWindowAnchorItem.width, gameScene.gameWindowAnchorItem.height)
    entityContainer: container

    // disable the camera's mouseArea, since we handle the controls of the free
    // moving camera ourself, in EditorUnderlay
    mouseAreaEnabled: false

    // the camera follows the player when not in edit mode
    focusedObject: gameScene.state != "edit" ? player : null

    // set focused offset
    focusOffset: Qt.point(0.5, 0.3)

    // set limits
    limitLeft: 0
    limitBottom: 0

    // set free camera offset, if sidebar is visible
    freeOffset: gameScene.state != "edit" ? Qt.point(0, 0) : Qt.point(100, 0)
  }

  /**
   * CONTROLS ----------------------------------------------
   */

  // this button is for moving left and right on touch devices
  MoveTouchButton {
    id: moveTouchButton

    // pass TwoAxisController to moveTouchButton
    controller: controller
  }

  // this button is for jumping on touch devices
  JumpTouchButton {
    id: jumpTouchButton

    onPressed: player.startJump(true)
    onReleased: player.endJump()
  }

  // On dektops, we can move the player with the arrow keys,
  // on mobile devices we are using our custom inputs above,
  // to modify the controller axis values.
  // With this approach, we only need one actual logic for the
  // movement, always referring to the axis values of the controller.

  // forward the keyboard keys to the controller
  Keys.forwardTo: controller

  // this is the controller, which handles the user's input
  TwoAxisController {
    id: controller

    // controls should be only enabled when playing a level
    enabled: gameScene.state != "edit"

    // handle keyboard input
    onInputActionPressed: {
      console.debug("key pressed actionName " + actionName)

      // movement via left and right buttons is handled automatically

      // start jump when pressing the up button
      if(actionName == "up") {
        player.startJump(true)
      }
    }

    onInputActionReleased: {
      // end jump when releasing the up button
      if(actionName == "up") {
        player.endJump()
      }
    }

    // if the xAxis changes, we change the direction the player looks,
    // depending on the direction he moves
    onXAxisChanged: player.changeSpriteOrientation()
  }

  /**
   * HUD
   */

  HUDIconAndText {
    id: timeDisplay

    // When the time is a whole number, the text would be e.g. "1" - but we
    // want it to be "1.0". So we check if it is a whole number, and if so,
    // add a ".0" to the end of it.
    text: time
    icon.source: "../../assets/ui/time.png"
  }

  // this timer keeps track of the time, the user plays a level
  Timer {
    id: levelTimer

    interval: 100

    repeat: true

    onTriggered: {
      // increase time
      time += 1
    }
  }

  // this displays the player's score
  HUDIconAndText {
    id: scoreDisplay

    text: "x "+player.score
    icon.source: "../../assets/coin/coin.png"

    anchors.left: timeDisplay.right
  }

  /**
   * TOP BAR
   */

  // back to menu button
  PlatformerImageButton {
    id: menuButton

    width: 40
    height: 30

    anchors.right: editorOverlay.right
    anchors.top: editorOverlay.top

    image.source: "../../assets/ui/home.png"

    // this button should only be visible in play or edit mode
    visible: gameScene.state == "play"

    // go back to menu
    onClicked: backPressed()
  }

  /**
   * EDITOR OVERLAY
   */

  EditorOverlay {
    id: editorOverlay

    visible: false

    scene: gameScene
  }

  /**
   * DIALOGS
   */

  FinishDialog {
    id: finishDialog
  }

  /**
   * JAVASCRIPT FUNCTIONS --------------------------------------
   */

  function handleScore() {
    // id only exists in published levels
    var leaderboard = levelEditor.currentLevelData.levelMetaData ? levelEditor.currentLevelData.levelMetaData.id : undefined

    // if current levelMetaData doesn't have an id, check if it has publishedLevelId
    if(!leaderboard)
      leaderboard = levelEditor.currentLevelData.levelMetaData ? levelEditor.currentLevelData.levelMetaData.publishedLevelId : undefined

    // if level is published...
    if(leaderboard) {
      // ...report the score; the lower the score, the better
      gameNetwork.reportScore(time, leaderboard, null, "lowest_is_best")
    }
  }

  // initializes the level
  // this function is called after a level was loaded
  function initLevel() {

    // initialize the editor
    editorOverlay.initEditor()

    // set background image
    // when there is a background saved, load it
    if(bgImage.loadedBackground && bgImage.loadedBackground != -1)
      bgImage.bg = bgImage.loadedBackground
    else // otherwise take the default background
      bgImage.bg = 0

    // reset the camera
    camera.zoom = 1
    camera.freePosition = Qt.point(0, 0)

    // initialize the player
    player.initialize()

    // reset the player's contacts
    player.resetContacts()

    // reset the controller's xAxis to ensure, that it's zero
    // at the beginning of the level
    controller.xAxis = 0

    // reset time and timer
    time = 0
    levelTimer.restart()
  }

  // resets the level
  // this function is i.a. called everytime the player dies, or the user
  // switches from test to edit mode
  function resetLevel() {

    // reset the editor
    editorOverlay.resetEditor()

    // reset player
    player.reset()

    // reset opponents
    var opponents = entityManager.getEntityArrayByType("opponent")
    for(var opp in opponents) {
      opponents[opp].reset()
    }

    // reset coins
    var coins = entityManager.getEntityArrayByType("coin")
    for(var coin in coins) {
      coins[coin].reset()
    }

    // reset mushrooms
    var mushrooms = entityManager.getEntityArrayByType("mushroom")
    for(var mushroom in mushrooms) {
      mushrooms[mushroom].reset()
    }

    // reset stars
    var stars = entityManager.getEntityArrayByType("star")
    for(var star in stars) {
      stars[star].reset()
    }

    // reset time and timer
    time = 0
    levelTimer.restart()
  }
}
<file_sep>/qml/editorElements/EditorOverlay.qml
import VPlay 2.0
import QtQuick 2.0
import "../common"
import "../undo"
import "../scenes/dialogs"
import "EditorLogic.js" as EditorLogic

Item {
  id: editorOverlay

  // make components accessible from the outside
  property alias grid: grid
  property alias sidebar: sidebar
  property alias itemEditor: itemEditor
  property alias undoHandler: undoHandler

  // holds the currently selected BuildEntityButton
  property var selectedButton

  // this is the gameScene, over which this overlay is put
  // by default this is this item's parent
  property var scene: parent

  // makes accessing the gameScene's container easier
  property var containerComponent: scene.container

  // this switches between true and false, if the user clicks the itemEditorButton
  property bool itemEditorVisible: false

  property bool inEditMode: false
  visible: false

  anchors.fill: scene.gameWindowAnchorItem

  EditorGrid {
    id: grid

    visible: inEditMode

    container: containerComponent
  }

  /**
   * SIDEBAR --------------------------------------
   */
  Sidebar {
    id: sidebar

    visible: inEditMode

    // set all components, that can be accessed in the sidebar
    bgImage: scene.bgImage
    grid: grid
    undoHandler: undoHandler
  }

  /**
   * Item editor ----------------------------------
   */
  // item editor for balancing the game
  ItemEditor {
    id: itemEditor

    // invisible by default
    visible: false

    anchors.right: parent.right
    anchors.top: topbar.bottom
    anchors.bottom: parent.bottom

    opacity: 0.9
  }

  // button to show/hide itemEditor
  PlatformerTextButton {
    id: itemEditorButton

    screenText: itemEditorVisible ? ">" : "<"

    width: 12

    // if the item editor is visible, anchor this button to the left of the editor;
    // otherwise anchor it to the game window
    anchors.right: itemEditor.visible ? itemEditor.left : parent.right
    anchors.verticalCenter: parent.verticalCenter

    onClicked: {
      itemEditor.visible = !itemEditor.visible
    }
  }

  /**
   * TOP BAR --------------------------------------
   */

  // this button enables switching between edit and test mode
  PlatformerImageButton {
    id: testButton

    width: 50
    height: 30

    // place on top, centered
    anchors.horizontalCenter: editorOverlay.horizontalCenter
    anchors.top: editorOverlay.top

    // set image source, depending on if we're in edit mode
    image.source: inEditMode ? "../../assets/ui/play.png" : "../../assets/ui/edit.png"

    // set opacity to 0.5 when in test mode, to be less distracting
    opacity: inEditMode ? 1 : 0.5

    // set game state depending on current game state
    onClicked: {
      if(inEditMode) {
        scene.state = "test"
      }
      else
        scene.state = "edit"
    }
  }

  // this row holds the buttons in the top right corner
  Row {
    id: topbar

    visible: inEditMode

    height: 30

    anchors.right: editorOverlay.right
    anchors.top: editorOverlay.top

    spacing: 4

    // save level button
    PlatformerImageButton {
      id: saveButton

      width: 40

      image.source: "../../assets/ui/save.png"

      onClicked: {
        // save level
        saveLevel()

        // show saved text
        savedTextAnimation.restart()
      }

      // this text signals, that the level has been saved
      Text {
        // text and text color
        text: "saved"
        color: "#ffffff"

        // by default this text is opaque/invisible
        opacity: 0

        // anchor to the bottom of the save button
        anchors.top: saveButton.bottom

        // outline the text, to increase it's visibility
        style: Text.Outline
        styleColor: "#009900"

        // this animation shows and slowly fades out the save text
        NumberAnimation on opacity {
          id: savedTextAnimation

          // slowly reduce opacity from 1 to 0
          from: 1
          to: 0

          // duration of the animation, in ms
          duration: 2000
        }
      }
    }

    // back to menu button
    PlatformerImageButton {
      id: menuButton

      width: 40

      image.source: "../../assets/ui/home.png"

      // open save dialog when in edit mode
      onClicked: saveLevelDialog.opacity = 1
    }
  }

  /**
   * MISC
   */

  // for handling undo and redo
  UndoHandler {
    id: undoHandler
  }

  /**
   * DIALOGS
   */

  // this is the save dialog that pops up, when the user clicks
  // the backButton in edit mode
  SaveLevelDialog {
    id: saveLevelDialog
  }

  PublishDialog {
    id: publishDialog
  }

  /**
   * JAVASCRIPT FUNCTIONS --------------------------------------
   */
  // handles the clicking of an entity
  function clickEntity(entity) {
    EditorLogic.clickEntity(entity);
  }

  // if entity is removable: remove entity and return the undoObject of this action
  function removeEntity(entity) {
    return EditorLogic.removeEntity(entity);
  }

  // returns the top-left coordinate of the grid the mouse is in
  function getMouseGridPos(mouseX, mouseY) {
    return EditorLogic.getMouseGridPos(mouseX, mouseY);
  }

  // This function checks, if there is a body in the 32x32 area
  // below/right the position parameter. We use this to make sure
  // there isn't already an entity at the position where we want
  // to create a new one.
  // The position parameter is the top-left point of the checked
  // area.
  // Returns true, if there is a body in this area;
  // false else.
  function isBodyIn32Grid(position) {
    return EditorLogic.isBodyIn32Grid(position);
  }

  // place a new object and return it
  function placeEntityAtPosition(mouseX, mouseY) {
    return EditorLogic.placeEntityAtPosition(mouseX, mouseY);
  }

  // converts mouse coordinates to level coordinates
  // return point with level coordinates
  function mouseToLevelCoordinates(mouseX, mouseY) {
    return EditorLogic.mouseToLevelCoordinates(mouseX, mouseY);
  }

  // calculates the grid coordinates of the grid the position (levelX, levelY) is in
  function snapToGrid(levelX, levelY) {
    return EditorLogic.snapToGrid(levelX, levelY);
  }

  // saves the current level
  function saveLevel() {
    EditorLogic.saveLevel();
  }

  function initEditor() {
    EditorLogic.initEditor();
  }

  function resetEditor() {
    EditorLogic.resetEditor();
  }
}
<file_sep>/qml/entities/OpponentWalker.qml
import QtQuick 2.0
import VPlay 2.0

Opponent {
  id: opponentJumper
  variationType: "walker"

  // this property determines in which the opponent moves
  // (-1 = left, 1 = right)
  property int direction: -1

  // the moving speed of the opponent
  property int speed: 70

  // set image
  // if opponent is alive, use normal sprite,
  // else, use dead sprite
  image.source: alive ? "../../assets/opponent/opponent_walker.png"
                      : "../../assets/opponent/opponent_walker_dead.png"

  // mirror sprite, when the opponent is moving right
  image.mirror: collider.linearVelocity.x < 0 ? false : true

  // define colliderComponent for collision detection while dragging
  colliderComponent: collider

  // When this opponent dies, we reset it's abyssChecker's
  // contacts to zero. Otherwise, after a level reset,
  // the abyssCheckers might not start with 0 contacts.
  onAliveChanged: {
    if(!alive) {
      leftAbyssChecker.contacts = 0
      rightAbyssChecker.contacts = 0
    }
  }

  // When being moved to the entity pool, reset the abyss checker's contacts.
  // For more information on entity pooling have a look at:
  // http://v-play.net/doc/vplay-entitybase/#poolingEnabled-prop
  onMovedToPool: {
    leftAbyssChecker.contacts = 0
    rightAbyssChecker.contacts = 0
  }

  // the opponents main collider
  PolygonCollider {
    id: collider

    // the vertices, forming the shape of the colider
    vertices: [
      Qt.point(1, 15),
      Qt.point(31, 15),
      Qt.point(31, 30),
      Qt.point(26, 31),
      Qt.point(6, 31),
      Qt.point(1, 30)
    ]

    // the bodyType is dynamic
    bodyType: Body.Dynamic

    // the collider should not be active in edit mode or
    // when dead
    active: inLevelEditingMode || !alive ? false : true

    // Category3: opponent body
    categories: Box.Category3
    // Category2: player body, Category2: player feet sensor,
    // Category5: solids
    collidesWith: Box.Category1 | Box.Category2 | Box.Category5

    // set the opponent's velocity
    linearVelocity: Qt.point(direction * speed, 0)

    onLinearVelocityChanged: {
      // if the opponent stops moving, reverse direction
      if(linearVelocity.x === 0)
        direction *= -1

      // make sure the speed is constant
      linearVelocity.x = direction * speed
    }
  }

  // The abyss checkers check for abysses left and right of the
  // opponent. With this, we can let the opponent change direction,
  // before it would fall of an edge.
  BoxCollider {
    id: leftAbyssChecker

    // only active, when the main collider is active
    active: collider.active

    // we make it rather small
    width: 5
    height: 5

    // place it left, below the opponent
    anchors.top: parent.bottom
    anchors.left: parent.left

    // Category4: opponent sensor
    categories: Box.Category4
    // Category5: solids
    collidesWith: Box.Category5

    // this collider should only check for collisions
    collisionTestingOnlyMode: true

    // This property keeps track of the contacts. If contacts
    // is 0, there is an abyss and the opponent should reverse
    // it's direction.
    property int contacts: 0

    // handle number of contacts
    fixture.onBeginContact: contacts++
    fixture.onEndContact: if(contacts > 0) contacts--

    // change direction when there are no contacts
    onContactsChanged: if(contacts == 0) direction *= -1
  }
  BoxCollider {
    id: rightAbyssChecker

    active: collider.active

    // size and position
    width: 5
    height: 5
    anchors.top: parent.bottom
    anchors.right: parent.right

    // Category4: opponent sensor
    categories: Box.Category4
    // Category5: solids
    collidesWith: Box.Category5

    collisionTestingOnlyMode: true

    // handle contacts
    property int contacts: 0

    fixture.onBeginContact: contacts++
    fixture.onEndContact: if(contacts > 0) contacts--

    onContactsChanged: if(contacts == 0) direction *= -1
  }

  // make property editable via item editor
  EditableComponent {
    editableType: "Balance"
    defaultGroup: "OpponentWalker"

    targetEditor: gameScene.itemEditor

    properties: {
      "speed": {"min": 0, "max": 300, "stepSize": 5, "label": "Speed"}
    }
  }

  // when the speed is changed, via the itemEditor, we also want to update
  // the opponents velocity
  onSpeedChanged: {
    collider.linearVelocity.x = direction * speed
  }

  // reset the opponent
  function reset() {
    // this is the reset function of the base entity Opponent.qml
    reset_super()

    // reset direction
    direction = -1

    // reset force
    collider.linearVelocity.x = Qt.point(direction * speed, 0)
  }
}

<file_sep>/qml/entities/Coin.qml
import QtQuick 2.0
import VPlay 2.0
import "../editorElements"

PlatformerEntityBaseDraggable {
  id: coin
  entityType: "coin"

  // this property is true when the player collected the coin
  property bool collected: false

  // when the coin is collected, it shouldn't be visible anymore
  image.visible: !collected

  // define colliderComponent for collision detection while dragging
  colliderComponent: collider

  // set image
  image.source: "../../assets/coin/coin.png"

  CircleCollider {
    id: collider

    // make the collider a little smaller than the sprite
    radius: parent.width / 2 - 3

    // center collider
    x: 3
    y: 3

    // disable collider when coin is collected
    active: !collected

    // the collider is static (shouldn't move) and should only test
    // for collisions
    bodyType: Body.Static
    collisionTestingOnlyMode: true

    // Category6: powerup
    categories: Box.Category6
    // Category1: player body
    collidesWith: Box.Category1
  }

  // set collected to true
  function collect() {
    console.debug("collect coin")
    coin.collected = true

    // for every collected coin, the time gets set back a little bit
    gameScene.time -= 5

    audioManager.playSound("collectCoin")
  }

  // reset coin
  function reset() {
    coin.collected = false
  }
}
<file_sep>/qml/entities/GroundGrass.qml
import QtQuick 2.0
import VPlay 2.0

Ground {
  variationType: "grass"

  image.source: "../../assets/ground/ground_grass.png"
}

<file_sep>/qml/gameElements/MoveTouchButton.qml
import VPlay 2.0
import QtQuick 2.0

Item {
  // should only be visible and enabled on touch devices, when
  // not in edit mode
  visible: !system.desktopPlatform && gameScene.state != "edit"
  enabled: visible

  // set size
  height: 60
  width: 160

  // anchor to the bottom left of the gameWindowAnchorItem
  anchors.left: gameScene.gameWindowAnchorItem.left
  anchors.bottom: gameScene.gameWindowAnchorItem.bottom

  // the TwoAxisController, which controls the player's left/right movement
  property var controller

  // this is a background of the left button image
  // it is only visible while the button is pressed, as visual feedback for the user
  Rectangle {
    id: backgroundLeft

    // make this fill the left half of this item
    width: parent.width / 2
    height: parent.height

    anchors.left: parent.left

    // the radius of the edges; setting the radius to height gives this a circle shape
    radius: height

    // set color and opacity
    color: "#ffffff"
    opacity: 0.5

    // not visible by default, gets set to visible in the onPressed event
    visible: false
  }

  MultiResolutionImage {
    id: imageLeft

    source: "../../assets/ui/arrow_left.png"

    // fill the left background
    anchors.fill: backgroundLeft

    fillMode: Image.PreserveAspectFit

    // make it half the size of the background
    scale: 0.5
  }

  // this is a background of the right button image
  // it is only visible while the button is pressed, as visual feedback for the user
  Rectangle {
    id: backgroundRight

    width: parent.width / 2
    height: parent.height

    anchors.right: parent.right

    radius: height

    // set color and opacity
    color: "#ffffff"
    opacity: 0.5

    visible: false
  }

  MultiResolutionImage {
    id: imageRight

    source: "../../assets/ui/arrow_right.png"

    // fill the right background
    anchors.fill: backgroundRight

    fillMode: Image.PreserveAspectFit

    // make it half the size of the background
    scale: 0.5
  }

  // handle touch events
  MultiPointTouchArea {
    anchors.fill: parent

    // set controller's xAxis depending on where the user presses
    // make backgrounds visible
    onPressed: {
      if(touchPoints[0].x < width/2) {
        controller.xAxis = -1
        backgroundLeft.visible = true
      }
      else {
        controller.xAxis = 1
        backgroundRight.visible = true
      }
    }

    onUpdated: {
      if(touchPoints[0].x < width/2)
        controller.xAxis = -1
      else
        controller.xAxis = 1
    }

    onReleased: {
      // reset xAxis to zero
      controller.xAxis = 0

      // make backgrounds invisible again
      backgroundLeft.visible = false
      backgroundRight.visible = false
    }
  }
}<file_sep>/qml/common/PlatformerSelectableTextButton.qml
import QtQuick 2.0
import VPlay 2.0
import "../common"

PlatformerTextButton {
  id: selectableTextButton

  // this property is true, when the button is selected
  property bool isSelected: false

  // this signal is emitted when the button gets selected
  signal selected

  // set background and text color depending on if the button is selected or not
  color: isSelected ? "#c0c0c0" : "#ffffff"
  textColor: isSelected ? "#f0f0f0" : "#000000"

}
<file_sep>/qml/scenes/dialogs/RatingDialog.qml
import QtQuick 2.0
import VPlay 2.0
import "../../common"

DialogBase {
  id: ratingDialog

  // the level that should be rated
  property var levelData

  signal levelRated

  // Buttons ------------------------------------------

  PlatformerImageButton {
    image.source: "../../../assets/ui/rate_5stars.png"

    width: 175
    height: 30

    anchors.centerIn: parent
    anchors.verticalCenterOffset: -100

    onClicked: rateLevel(5)
  }

  PlatformerImageButton {
    image.source: "../../../assets/ui/rate_4stars.png"

    width: 175
    height: 30

    anchors.centerIn: parent
    anchors.verticalCenterOffset: -50

    onClicked: rateLevel(4)
  }

  PlatformerImageButton {
    image.source: "../../../assets/ui/rate_3stars.png"

    width: 175
    height: 30

    anchors.centerIn: parent

    onClicked: rateLevel(3)
  }

  PlatformerImageButton {
    image.source: "../../../assets/ui/rate_2stars.png"

    width: 175
    height: 30

    anchors.centerIn: parent
    anchors.verticalCenterOffset: 50

    onClicked: rateLevel(2)
  }

  PlatformerImageButton {
    image.source: "../../../assets/ui/rate_1star.png"

    width: 175
    height: 30

    anchors.centerIn: parent
    anchors.verticalCenterOffset: 100

    onClicked: rateLevel(1)
  }

  function rateLevel(rating) {
    // rate level
    levelEditor.rateLevel( { levelId: levelData.levelId, quality: rating } )

    // reload levels
    levelScene.reloadLevels()

    // close dialog
    ratingDialog.opacity = 0
  }
}
<file_sep>/qml/entities/Mushroom.qml
import QtQuick 2.0
import VPlay 2.0
import "../editorElements"

PlatformerEntityBaseDraggable {
  id: mushroom
  entityType: "mushroom"

  // this property is true when the player collected the mushroom
  property bool collected: false

  // when the mushroom is collected, it shouldn't be visible anymore
  image.visible: !collected

  // define colliderComponent for collision detection while dragging
  colliderComponent: collider

  // set image
  image.source: "../../assets/powerups/mushroom.png"

  BoxCollider {
    id: collider

    // make the collider a little smaller than the sprite
    width: parent.width - 2
    height: parent.height - 2
    anchors.centerIn: parent

    // disable collider when mushroom is collected
    active: !collected

    // the collider is static (shouldn't move) and only test
    // for collisions
    bodyType: Body.Static
    collisionTestingOnlyMode: true

    // Category6: powerup
    categories: Box.Category6
    // Category1: player body
    collidesWith: Box.Category1
  }

  function collect() {
    console.debug("collect mushroom")
    mushroom.collected = true

    audioManager.playSound("collectMushroom")
  }

  // reset mushroom
  function reset() {
    mushroom.collected = false
  }
}
<file_sep>/qml/scenes/dialogs/SaveLevelDialog.qml
import QtQuick 2.0
import VPlay 2.0
import "../../common"

DialogBase {
  id: removeLevelDialog

  Text {
    anchors.horizontalCenter: parent.horizontalCenter
    anchors.top: parent.top
    anchors.topMargin: 100

    text: "What do you want to do?"

    color: "white"
  }

  // Buttons ------------------------------------------

  PlatformerTextButton {
    id: saveAndExitButton

    screenText: "Save and Exit"

    width: 175

    anchors.horizontalCenter: parent.horizontalCenter
    anchors.bottom: parent.bottom
    anchors.bottomMargin: 150

    onClicked: {
      // when clicking "save and exit" the level gets saved, we
      // emit the gameScene's backPressed signal and close the dialog
      editorOverlay.saveLevel()
      editorOverlay.scene.backPressed()

      removeLevelDialog.opacity = 0
    }
  }

  PlatformerTextButton {
    id: discardAndExitButton

    screenText: "Exit"

    width: 175

    anchors.horizontalCenter: parent.horizontalCenter
    anchors.bottom: parent.bottom
    anchors.bottomMargin: 100

    onClicked: {
      // when clicking "exit" we only emit the gameScene's backPressed
      // signal and close the dialog
      editorOverlay.scene.backPressed()

      removeLevelDialog.opacity = 0
    }
  }

  PlatformerTextButton {
    id: cancelButton

    screenText: "Cancel"

    width: 175

    anchors.horizontalCenter: parent.horizontalCenter
    anchors.bottom: parent.bottom
    anchors.bottomMargin: 50

    // when clicking "exit" we only close the dialog and return to
    // the editor
    onClicked: removeLevelDialog.opacity = 0
  }
}

<file_sep>/qml/entities/OpponentJumper.qml
import QtQuick 2.0
import VPlay 2.0

Opponent {
  id: opponentJumper
  variationType: "jumper"

  // this property determines in which the opponent will jump next
  // (-1 = left, 1 = right)
  property int direction: -1

  // the opponents jumpForce in vertical and horizontal
  // direction
  property int verticalJumpForce: 510
  property int horizontalJumpForce: 40

  // set image
  // if opponent is alive, use normal sprite,
  // else, use dead sprite
  image.source: alive ? "../../assets/opponent/opponent_jumper.png"
                      : "../../assets/opponent/opponent_jumper_dead.png"

  // define colliderComponent for collision detection while dragging
  colliderComponent: collider

  // if the opponent dies, stop it's jumpTimer
  onAliveChanged: {
    if(!alive) {
      jumpTimer.stop()
    }
  }

  // the opponents main collider
  PolygonCollider {
    id: collider

    // vertices, forming the shape of the colider
    vertices: [
      Qt.point(1, 1),
      Qt.point(31, 1),
      Qt.point(31, 30),
      Qt.point(23, 31),
      Qt.point(9, 31),
      Qt.point(1, 30)
    ]

    // the bodyType is dynamic
    bodyType: Body.Dynamic

    // the collider should not be active in edit mode or
    // when dead
    active: inLevelEditingMode || !alive ? false : true

    // Category3: opponent body
    categories: Box.Category3
    // Category2: player body, Category2: player feet sensor,
    // Category5: solids
    collidesWith: Box.Category1 | Box.Category2 | Box.Category5

    // to avoid, that the opponent slides on the ground
    friction: 1
  }

  // this collider checks for collisions from the bottom and starts
  // the jumpTimer, on collision
  BoxCollider {
    id: bottomSensor

    // set size and position
    width: 30
    height: 3

    anchors.horizontalCenter: parent.horizontalCenter
    anchors.top: parent.bottom

    // the bodyType is dynamic
    bodyType: Body.Dynamic

    // only active when the main collider is active
    active: collider.active

    // Category4: opponent sensor
    categories: Box.Category4
    // Category5: solids
    collidesWith: Box.Category5

    // this collider is for collision testing only
    collisionTestingOnlyMode: true

    // this is called whenever the contact with another entity begins
    fixture.onContactChanged: {
      var otherEntity = other.getBody().target

      // When the opponent stands on a solid object, we want it
      // to wait a little and then jump again.
      // Since we set the collidesWith property, we can be sure
      // that it won't collide with any unwanted entities.
      if(collider.linearVelocity.y === 0 && !jumpTimer.running)
        jumpTimer.start()
    }
  }

  // this timer is started in the bottomSensor and makes the opponent jump
  // when triggered
  Timer {
    id: jumpTimer

    interval: 300

    onTriggered: {
      // jump in the current direction
      collider.applyLinearImpulse(Qt.point(direction * horizontalJumpForce, -verticalJumpForce))

      // alternate direction after every jump
      direction *= -1
    }
  }

  // make properties editable via itemEditor
  EditableComponent {
    editableType: "Balance"
    defaultGroup: "OpponentJumper"

    targetEditor: gameScene.itemEditor

    properties: {
      "horizontalJumpForce": {"min": 0, "max": 200, "stepSize": 10, "label": "Horizontal Jump"},
      "verticalJumpForce": {"min": 0, "max": 800, "stepSize": 10, "label": "Vertical Jump"}
    }
  }

  // reset the opponent
  function reset() {
    // We set alive to false here, and reset it to true later,
    // to deactivate the collider while the opponent is reset.
    alive = false

    // this is the reset function of the base entity Opponent.qml
    reset_super()

    // reset direction
    direction = -1

    // reset timer
    if(jumpTimer.running)
      jumpTimer.stop()

    alive = true
  }
}

repo_language: QML

repo_name: emrepara/lvl