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

tornadofx.Nodes.kt Maven / Gradle / Ivy

The newest version!
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.Scene
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.stage.Stage
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) addClass(className) else removeClass(className)

fun Scene.reloadStylesheets() {
    val styles = stylesheets.toMutableList()
    stylesheets.clear()
    stylesheets.addAll(styles)
}

fun Stage.reloadStylesheetsOnFocus() {
    focusedProperty().addListener { obs, old, new ->
        if (new)
            scene.reloadStylesheets()
    }
}

fun Pane.reloadStylesheets() {
    val styles = stylesheets.toMutableList()
    stylesheets.clear()
    stylesheets.addAll(styles)
}

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)
}

operator fun ToolBar.plusAssign(node: Node): Unit {
    items.add(node)
}

fun ToolBar.add(node: Node) = plusAssign(node)

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)

fun Pane.add(node: Node) = plusAssign(node)

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

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

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

val  TreeView.selectedValue: T
    get() = this.selectionModel.selectedItem.value

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

fun  TreeView.selectFirst() = selectionModel.selectFirst()

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

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

fun  TableColumn.fixedWidth(width: Double): TableColumn {
    minWidth = width
    maxWidth = width
    return this
}

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  TreeView.onUserSelect(action: (T) -> Unit) {
    selectionModel.selectedItemProperty().addListener { obs, old, new ->
        if (new != null && new.value != null)
            action(new.value)
    }
}

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)
    })
}

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

/**
 * 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
}