All Downloads are FREE. Search and download functionalities are using the official Maven repository.

tornadofx.Nodes.kt Maven / Gradle / Ivy

package tornadofx

import com.sun.javafx.scene.control.skin.TableColumnHeader
import javafx.beans.value.ObservableValue
import javafx.event.EventTarget
import javafx.scene.Node
import javafx.scene.control.*
import javafx.scene.input.InputEvent
import javafx.scene.input.KeyCode
import javafx.scene.input.KeyEvent
import javafx.scene.input.MouseEvent
import javafx.scene.layout.GridPane
import javafx.scene.layout.Pane
import javafx.util.Callback
import kotlin.reflect.KClass

fun Node.hasClass(className: String) = styleClass.contains(className)
fun Node.addClass(className: String) = styleClass.add(className)
fun Node.removeClass(className: String) = styleClass.remove(className)
fun Node.toggleClass(className: String, predicate: Boolean) = if (predicate) removeClass(className) else addClass(className)

infix fun Node.addTo(pane: Pane) = pane.children.add(this)

fun Pane.replaceChildren(vararg uiComponents: UIComponent) =
        this.replaceChildren(*(uiComponents.map { it.root }.toTypedArray()))

fun Pane.replaceChildren(vararg node: Node) {
    children.clear()
    children.addAll(node)
}

operator fun ToolBar.plusAssign(uiComponent: UIComponent): Unit {
    items.add(uiComponent.root)
}

inline fun  ToolBar.add(type: KClass): Unit = plusAssign(find(type))

operator fun Pane.plusAssign(node: Node) {
    children.add(node)
}

inline fun  Pane.add(type: KClass) = plusAssign(find(type).root)

operator fun  Pane.plusAssign(type: KClass) = plusAssign(find(type).root)

operator fun Pane.plusAssign(view: UIComponent): Unit {
    plusAssign(view.root)
}

fun GridPane.row(op: Pane.() -> Unit) {
    userData = if (userData is Int) userData as Int + 1 else 1

    // Allow the caller to add children to a fake pane
    val fake = Pane()
    op(fake)

    // Create a new row in the GridPane and add the children added to the fake pane
    addRow(userData as Int, *fake.children.toTypedArray())
}

val  TableView.selectedItem: T
    get() = this.selectionModel.selectedItem

fun  TableView.selectFirst() = selectionModel.selectFirst()

val  ListView.selectedItem: T
    get() = selectionModel.selectedItem

fun  TableView.onSelectionChange(func: (S?) -> Unit) =
    selectionModel.selectedItemProperty().addListener({ observable, oldValue, newValue -> func(newValue) })

inline fun  TableColumn.cellFormat(crossinline formatter: (TableCell.(T) -> Unit)) {
    cellFactory = Callback { column: TableColumn ->
        object : TableCell() {
            override fun updateItem(item: T, empty: Boolean) {
                super.updateItem(item, empty)

                if (item == null || empty) {
                    text = null
                    graphic = null
                } else {
                    formatter(this, item)
                }
            }
        }
    }
}

inline fun  TableView.addColumn(title: String, crossinline valueProvider: (TableColumn.CellDataFeatures) -> ObservableValue): TableColumn {
    val column = TableColumn(title)
    column.cellValueFactory = Callback { valueProvider(it) }
    columns.add(column)
    return column
}

inline fun  ListView.cellFormat(crossinline formatter: (ListCell.(T) -> Unit)) {
    cellFactory = Callback {
        object : ListCell() {
            override fun updateItem(item: T, empty: Boolean) {
                super.updateItem(item, empty)

                if (item == null || empty) {
                    text = null
                    graphic = null
                } else {
                    formatter(this, item)
                }
            }
        }
    }
}

/**
 * Execute action when the enter key is pressed or the mouse is clicked

 * @param clickCount The number of mouse clicks to trigger the action
 * *
 * @param action The action to execute on select
 */
fun  TableView.onUserSelect(clickCount: Int = 2, action: (T) -> Unit) {
    val isSelected = { event: InputEvent ->
        event.target.isInsideTableRow() && !selectionModel.isEmpty
    }

    addEventFilter(MouseEvent.MOUSE_CLICKED) { event ->
        if (event.clickCount == clickCount && isSelected(event))
            action(selectedItem)
    }

    addEventFilter(KeyEvent.KEY_PRESSED) { event ->
        if (event.code == KeyCode.ENTER && !event.isMetaDown && isSelected(event))
            action(selectedItem)
    }
}

fun  TableView.onUserDelete(action: (T) -> Unit) {
    addEventFilter(KeyEvent.KEY_PRESSED, { event ->
        if (event.code == KeyCode.BACK_SPACE && selectedItem != null)
            action(selectedItem)
    })
}

fun  ListView.onUserDelete(action: (T) -> Unit) {
    addEventFilter(KeyEvent.KEY_PRESSED, { event ->
        if (event.code == KeyCode.BACK_SPACE && selectedItem != null)
            action(selectedItem)
    })
}

/**
 * Execute action when the enter key is pressed or the mouse is clicked

 * @param clickCount The number of mouse clicks to trigger the action
 * *
 * @param action The runnable to execute on select
 */
fun  ListView.onUserSelect(clickCount: Int = 2, action: (T) -> Unit) {
    addEventFilter(MouseEvent.MOUSE_CLICKED) { event ->
        if (event.clickCount == clickCount && selectedItem != null)
            action(selectedItem)
    }

    addEventFilter(KeyEvent.KEY_PRESSED) { event ->
        if (event.code == KeyCode.ENTER && !event.isMetaDown && selectedItem != null)
            action(selectedItem)
    }
}

/**
 * Did the event occur inside a TableRow?
 */
fun EventTarget.isInsideTableRow(): Boolean {
    if (this !is Node)
        return false

    if (this is TableColumnHeader)
        return false

    if (this is TableRow<*> || this is TableView<*>)
        return true

    if (this.parent != null)
        return this.parent.isInsideTableRow()

    return false
}