func (app *MainApplication) requestSelectedTilesChange(modifier func(*model.TileProperties), updateNeighbours bool) {
	if !app.viewModelUpdating {
		projectID := app.viewModel.SelectedProject()
		archiveID := "archive"
		levelID := app.activeLevelID
		neighbours := make(map[editormodel.TileCoordinate]int)
		writesPending := 0

		onWriteCompleted := func() {
			writesPending--
			if writesPending == 0 {
				for coord := range neighbours {
					localCoord := coord
					x, y := localCoord.XY()
					app.store.Tile(projectID, archiveID, levelID, x, y, func(properties model.TileProperties) {
						app.onTilePropertiesUpdated(localCoord, &properties)
					}, app.simpleStoreFailure("GetTile"))
				}
			}
		}

		app.tileMap.ForEachSelected(func(coord editormodel.TileCoordinate, tile *editormodel.Tile) {
			var properties model.TileProperties

			modifier(&properties)

			writesPending++
			x, y := coord.XY()
			if updateNeighbours {
				if x > 0 {
					neighbours[editormodel.TileCoordinateOf(x-1, y)]++
				}
				if (x + 1) < TilesPerMapSide {
					neighbours[editormodel.TileCoordinateOf(x+1, y)]++
				}
				if y > 0 {
					neighbours[editormodel.TileCoordinateOf(x, y-1)]++
				}
				if (y + 1) < TilesPerMapSide {
					neighbours[editormodel.TileCoordinateOf(x, y+1)]++
				}
			}
			app.store.SetTile(projectID, archiveID, levelID, x, y, properties, func(newProperties model.TileProperties) {
				app.onTilePropertiesUpdated(coord, &newProperties)
				onWriteCompleted()
			}, app.simpleStoreFailure("SetTile"))
		})
	}
}
func (app *MainApplication) onMouseClick(modifierMask uint32) {
	worldMouseX, worldMouseY := app.unprojectPixel(app.mouseX, app.mouseY)
	tileX, _ := int(worldMouseX/TileBaseLength), (int(worldMouseX/TileBaseLength*256.0))%256
	tileY, _ := int(TilesPerMapSide)-1-int(worldMouseY/TileBaseLength), 255-((int(worldMouseY/TileBaseLength*256.0))%256)

	tileCoord := editormodel.TileCoordinateOf(tileX, tileY)
	if (modifierMask & env.ModControl) != 0 {
		app.tileMap.SetSelected(tileCoord, !app.tileMap.IsSelected(tileCoord))
	} else {
		app.tileMap.ClearSelection()
		app.tileMap.SetSelected(tileCoord, true)
	}
	app.onTileSelectionChanged()
}
func (app *MainApplication) onSelectedLevelChanged(levelIDString string) {
	projectID := app.viewModel.SelectedProject()
	levelID, levelIDError := strconv.ParseInt(levelIDString, 10, 16)

	if app.tileTextureMapRenderable != nil {
		app.tileTextureMapRenderable.Clear()
		app.tileTextureMapRenderable.Dispose()
		app.tileTextureMapRenderable = nil
	}
	if app.tileGridMapRenderable != nil {
		app.tileGridMapRenderable.Clear()
	}
	app.tileMap.Clear()
	app.onTileSelectionChanged()
	app.activeLevelID = -1
	app.updateViewModel(func() {
		app.viewModel.SetLevelTextures(nil)
	})

	if projectID != "" && levelIDError == nil {
		app.activeLevelID = int(levelID)

		if app.isActiveLevelRealWorld() {
			app.tileTextureMapRenderable = display.NewTileTextureMapRenderable(app.gl, app.paletteTexture, app.levelTexture)
		}

		app.store.Tiles(projectID, "archive", app.activeLevelID, func(data model.Tiles) {
			for y, row := range data.Table {
				for x := 0; x < len(row); x++ {
					coord := editormodel.TileCoordinateOf(x, y)
					properties := &row[x].Properties
					app.onTilePropertiesUpdated(coord, properties)
				}
			}
		}, app.simpleStoreFailure("Tiles"))

		app.store.LevelTextures(projectID, "archive", app.activeLevelID,
			app.onStoreLevelTexturesChanged, app.simpleStoreFailure("LevelTextures"))

		app.store.LevelObjects(projectID, "archive", app.activeLevelID,
			app.onStoreLevelObjectsChanged, app.simpleStoreFailure("LevelObjects"))
	}

	app.updateViewModel(func() {
		app.viewModel.SetLevelIsRealWorld(app.isActiveLevelRealWorld())
	})
}