Vítejte v dalším díle věnovaném jazyku Kotlin, kde si ukážeme, jak psát nabušené GUÍ-tka založené na JavaFX za pomocí výborného frameworku TornadoFX. Náš výsledek bude vypadat takto a celé nakódění nám nezabere déle jak 30 minut:

Zrovna jsem se vrátil z kite kurzu a jako obvykle si potřebuju vyúčtovat proplacení benzínu za cestu na spot a zpět. Zatímco jiní lidé by si udělali nějakou šablonku v Excelu, já jsem si na to rovnou napsal malou GUI aplikaci v Kotlinu, protože to až tolik nezabolí a výsledek prostě stojí za to! 🙂 Nejde však pouze o samotné GUI, ale taky o krásnou reaktivní funkcionalitu, resp. TornadoFX je natolik skvělé, že možnost psát GUI o sofistikovanosti nějaké soudobé ReactJS aplikace na webovém frontendu nyní přechází i na desktop. Bude potřeba jediná závislost (mimo klasický Kotlin základ), a sice:

<dependency>
    <groupId>no.tornado</groupId>
    <artifactId>tornadofx</artifactId>
    <version>1.7.15</version>
</dependency>

A nyní již zbývá napsat následujících 85 řádků kódu do jediného souboru:

package com.svetylkovo.fuelrefund

import javafx.application.Application
import javafx.beans.property.SimpleDoubleProperty
import javafx.beans.property.SimpleObjectProperty
import javafx.beans.property.SimpleStringProperty
import tornadofx.*
import kotlin.math.roundToInt

class Spot(val name: String, val distance: Double) {
    override fun toString() = name
}

class FuelRefund : View("Fuel Refund App") {

    private val spots = listOf(
        Spot("Příbor", 24.0),
        Spot("Veselák", 101.0)
    ).observable()

    private val selectedSpot = SimpleObjectProperty<Spot>()

    private val consumption = SimpleDoubleProperty(0.0)
    private val distance = SimpleDoubleProperty(0.0)
    private val fuelPrice = SimpleDoubleProperty(0.0)
    private val result = SimpleStringProperty("")

    override val root = vbox {
        prefWidth = 400.0
        prefHeight = 400.0

        form {
            fieldset("Input parameters") {
                field("Spot:") {
                    combobox(selectedSpot, spots)
                }
                field("Distance:") {
                    textfield(distance)
                    label("km")
                }
                field("Consumption:") {
                    textfield(consumption)
                    label("1l/100km")
                }
                field("Fuel price (1l):") {
                    textfield(fuelPrice)
                    label("Kč")
                }
            }

            fieldset("Result:") {
                textarea(result)
            }
            button("Copy to clipboard").action {
                clipboard.putString(result.get())
            }
        }
    }

    private fun computePrice() {
        val consumption = consumption.get()
        val distance = distance.get()
        val fuelPrice = fuelPrice.get()
        val priceResult = (consumption/100) * distance * fuelPrice
        result.set("($consumption/100) * $distance * $fuelPrice = ${priceResult.roundToInt()} Kč")
    }

    init {
        selectedSpot.onChange { distance.set(it?.distance ?: 0.0) }
        distance.onChange { computePrice() }
        consumption.onChange { computePrice() }
        fuelPrice.onChange { computePrice() }

        selectedSpot.set(spots.first())
    }
}

class FuelRefundApp : App(FuelRefund::class) {
    companion object {
        @JvmStatic
        fun main(args: Array<String>) {
            Application.launch(FuelRefundApp::class.java)
        }
    }
}