/tornadofx

Lightweight JavaFX Framework for Kotlin

Primary LanguageKotlinApache License 2.0Apache-2.0

TornadoFX Logo

TornadoFX

Lightweight JavaFX Framework for Kotlin

Travis CI Maven Central Apache License

Features

  • Dependency injection
  • Type safe GUI builders
  • First class FXML support
  • Type safe CSS builders
  • Async task execution
  • Hot reload of Views and Stylesheets
  • REST client with automatic JSON conversion
  • Zero config, no XML, no annotations

Getting started

Generate a quickstart application with Maven

mvn archetype:generate -DarchetypeGroupId=no.tornado \
  -DarchetypeArtifactId=tornadofx-quickstart-archetype \
  -DarchetypeVersion=1.0.3

Remember to update version to 1.4.6 in pom.xml

Add TornadoFX to your project

Maven

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

Gradle

compile 'no.tornado:tornadofx:1.4.6'

What does it look like? (Code snippets)

Create a View

class HelloWorld : View() {
	override val root = HBox(Label("Hello world")) 
}

Load the root node from HelloWorld.fxml and inject controls by fx:id

class HelloWorld : View() {
	override val root: HBox by fxml()
	val myLabel: Label by fxid()
	
	init {
		myLabel.text = "Hello world"
	}
}

Start your application and show the primary View by extending the App class

class HelloWorldApp : App {
	override val primaryView = HelloWorld::class

	init {
		importStylesheet("/styles.css")
	}
}

Start app and load a stylesheet

Use Type Safe Builders to quickly create complex user interfaces

class MyView : View() {

    override val root = VBox()

    private val persons = FXCollections.observableArrayList<Person>(
            Person(1, "Samantha Stuart", LocalDate.of(1981,12,4)),
            Person(2, "Tom Marks", LocalDate.of(2001,1,23)),
            Person(3, "Stuart Gills", LocalDate.of(1989,5,23)),
            Person(3, "Nicole Williams", LocalDate.of(1998,8,11))
    )

    init {
        with(root) {
            tableview(persons) {
                column("ID", Person::id)
                column("Name", Person::name)
                column("Birthday", Person::birthday)
                column("Age", Person::age)
            }
        }
    }
}

RENDERED UI

Create a Customer model object that can be converted to and from JSON and complies with JavaFX Property guidelines:

class Customer : JsonModel {
    var id by property<Int>()
    fun idProperty() = getProperty(Customer::id)

    var name by property<String>()
    fun nameProperty() = getProperty(Customer::name)

    override fun updateModel(json: JsonObject) {
        with(json) {
            id = int("id")
            name = string("name")
        }
    }

    override fun toJSON(json: JsonBuilder) {
        with(json) {
            add("id", id)
            add("name", name)
        }
    }
}

Create a controller which downloads a JSON list of customers with the REST api:

class HelloWorldController : Controller() {
	val api : Rest by inject()
	
	fun loadCustomers(): ObservableList<Customer> = 
		api.get("customers").list().toModel() 
}

Configure the REST API with a base URI and Basic Authentication:

with (api) {
    baseURI = "http://contoso.com/api"
    setBasicAuth("user", "secret")
}

Load customers in the background and update a TableView on the UI thread:

runAsync {
	controller.loadCustomers()
} ui {
	customerTable.items = it
}

Load customers and apply to table declaratively:

customerTable.asyncItems { controller.loadCustomers() }

Define a type safe CSS stylesheet:

class Styles : Stylesheet() {
    companion object {
        // Define css classes
        val heading by cssclass()
        
        // Define colors
        val mainColor = c("#bdbd22")
    }

    init {
		s(heading) {
		    textFill = mainColor
		    fontSize = 20.px
		    fontWeight = BOLD
		}

        val flat = mixin {
            backgroundInsets = box(0.px)
            borderColor = box(Color.DARKGRAY)
        }
        
        s(button) {
            padding = box(10.px, 20.px)
            fontWeight = BOLD
        }

        s(button, textInput) {
            +flat
        }
    }
}

Create an HBox with a Label and a TextField with type safe builders:

hbox {
	label("Hello world") {
		addClass(heading)
	}
	
	textfield {
		promptText = "Enter your name"
	}
}

Get and set per component configuration settings:

// set prefWidth from setting or default to 200.0
node.prefWidth(config.double("width", 200.0))

// set username and age, then save
with (config) {
	set("username", "john")
	set("age", 30)
	save()
}

Create a Fragment instead of a View. A Fragment is not a Singleton like Viewis, so you will create a new instance and you can reuse the Fragment in multiple ui locations simultaneously.

class MyFragment : Fragment() {
	override val root = Hbox(..)
}

Open it in a Modal Window:

MyFragment().openModal()

Lookup and embed a View inside another Pane in one go

root += MyFragment::class

Inject a View and embed inside another Pane

val myView: MyView by inject()
 
init {
	root += myFragment
}