Skip to content

Commit

Permalink
Merge pull request #27 from saalfeldlab/feat/1.2.0
Browse files Browse the repository at this point in the history
Feat/1.2.0
  • Loading branch information
cmhulbert authored Apr 30, 2024
2 parents 78e2b18 + 616865b commit 1c519f8
Show file tree
Hide file tree
Showing 8 changed files with 170 additions and 61 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

<groupId>org.janelia.saalfeldlab</groupId>
<artifactId>saalfx</artifactId>
<version>1.1.1-SNAPSHOT</version>
<version>1.2.0-SNAPSHOT</version>

<name>Saal FX</name>
<description>Saalfeld lab JavaFX tools and extensions</description>
Expand Down
23 changes: 17 additions & 6 deletions src/main/kotlin/org/janelia/saalfeldlab/fx/actions/ActionSet.kt
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ open class ActionSet(val name: String, var keyTracker: () -> KeyTracker? = { nul
* @param withAction [KeyAction] configuration callback
* @return the [KeyAction]
*/
operator fun EventType<KeyEvent>.invoke(withKeys: KeyCodeCombination, withAction: KeyAction.() -> Unit): KeyAction {
operator fun EventType<KeyEvent>.invoke(withKeys: KeyCombination, withAction: KeyAction.() -> Unit): KeyAction {
/* create the Action*/
return KeyAction(this).apply {
keyTracker = this@ActionSet.keyTracker
Expand Down Expand Up @@ -213,7 +213,7 @@ open class ActionSet(val name: String, var keyTracker: () -> KeyTracker? = { nul
/* set the default name */
name = this@ActionSet.name

/* configure based on the withKeys paramters*/
/* configure based on the withKeys parameters*/
if (eventType == KeyEvent.KEY_RELEASED) {
ignoreKeys()
keysReleased(*withKeys)
Expand All @@ -234,16 +234,27 @@ open class ActionSet(val name: String, var keyTracker: () -> KeyTracker? = { nul
* @param withAction [KeyAction] configuration callback
* @return the [KeyAction]
*/
operator fun EventType<KeyEvent>.invoke(keyBindings: NamedKeyCombination.CombinationMap, keyName: String, keysExclusive: Boolean = false, withAction: KeyAction.() -> Unit): KeyAction {
operator fun EventType<KeyEvent>.invoke(keyBindings: NamedKeyCombination.CombinationMap, keyName: String, keysExclusive: Boolean = true, withAction: KeyAction.() -> Unit): KeyAction {

return this(keyBindings[keyName]!!, keysExclusive, withAction)
}

/**
* Convenience operator to create a [KeyAction] from a [KeyEvent] [EventType] receiver, while specifying the required [NamedKeyBinding]
*
* @param keyBinding key combination to validate the action against
* @param withAction [KeyAction] configuration callback
* @return the [KeyAction]
*/
operator fun EventType<KeyEvent>.invoke(keyBinding: NamedKeyBinding, keysExclusive: Boolean = true, withAction: KeyAction.() -> Unit): KeyAction {

/* create the Action*/
return KeyAction(this).apply {
keyTracker = this@ActionSet.keyTracker
name = "${this@ActionSet.name}.$keyName"
name = keyBinding.keyBindingName

/* configure based on the withKeys paramters*/
keyMatchesBinding(keyBindings, keyName, keysExclusive)
/* configure based on the keyBinding */
keyMatchesBinding(keyBinding, keysExclusive)

/* configure via the callback*/
withAction()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ open class DragActionSet @JvmOverloads constructor(

/**
* if true, [startX],[startY] will be updated when [MOUSE_DRAGGED].
* if fals, [startX],[startY] are always the position of the MOUSE_PRESSED event that triggered the drag event.
* if false, [startX],[startY] are always the position of the MOUSE_PRESSED event that triggered the drag event.
*/
var relative = false

Expand Down
33 changes: 24 additions & 9 deletions src/main/kotlin/org/janelia/saalfeldlab/fx/actions/KeyAction.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,33 @@ class KeyAction(eventType: EventType<KeyEvent>) : Action<KeyEvent>(eventType) {
*/
@JvmOverloads
fun keyMatchesBinding(keyBindings: NamedKeyCombination.CombinationMap, keyName: String, keysExclusive: Boolean = true) {
if (name == null) name = keyName
val namedKeyCombo = keyBindings[keyName]!!
keyMatchesBinding(namedKeyCombo, keysExclusive)
}

/**
* Provide a [NamedKeyCombination.CombinationMap] and a [NamedKeyCombination] to use to verify the valid key combination.
* NOTE: the [NamedKeyCombination.keyCodes] are not directly used. This is purely a convenience function to get the [NamedKeyCombination.keyBindingName]
*
* @param keyBindings the map to query the key combinations for the given [NamedKeyCombination.keyBindingName]
* @param namedKeyCombination which provided the key name used to find the desired key combination in the [keyBindings].
*/
@JvmOverloads
fun keyMatchesBinding(namedKeyBinding: NamedKeyBinding, keysExclusive: Boolean = true) {


if (name == null) name = namedKeyBinding.keyBindingName
ignoreKeys()
if (eventType == KeyEvent.KEY_RELEASED)
keysReleased(*keyBindings[keyName]!!.keyCodes.toTypedArray())
keysReleased(*namedKeyBinding.keyCodes.toTypedArray())
else
verify { event ->
/* always valid here if the event is null; it indicates we are triggering the action programatically, not via an Event */
event?.let {
keyBindings.matches(keyName, it, keysExclusive).also { match ->
if (!match) logger.trace("key did not match bindings")
}
} ?: true
/* always valid here if the event is null; it indicates we are triggering the action programmatically, not via an Event */
event ?: return@verify true

namedKeyBinding.matches(event, keysExclusive).also { match ->
if (!match) logger.trace("key did not match bindings")
}
}
}

Expand All @@ -55,7 +70,7 @@ class KeyAction(eventType: EventType<KeyEvent>) : Action<KeyEvent>(eventType) {
verify {
val keyTracker = keyTracker()
val otherKeys = keyTracker?.getActiveKeyCodes(true)
val otherKeysAreDown = otherKeys?.let { keyTracker?.areKeysDown(*otherKeys.toTypedArray()) } ?: false
val otherKeysAreDown = otherKeys?.let { keyTracker.areKeysDown(*otherKeys.toTypedArray()) } ?: false
val onlyOtherKeys = keyTracker?.activeKeyCount() == otherKeys?.size && (otherKeys?.size ?: -1) > 0
if (keysExclusive && otherKeysAreDown && onlyOtherKeys) {
true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,73 +26,79 @@ private val KeyEvent.modifierCodes: Set<KeyCode>
if (isShortcutDown) Toolkit.getToolkit().platformShortcutKey else null
)

class NamedKeyCombination(val name: String, primaryCombination: KeyCombination) {

private val primaryCombinationProperty = SimpleObjectProperty(primaryCombination)
var primaryCombination: KeyCombination by primaryCombinationProperty.nonnull()

fun primaryCombinationProperty() = primaryCombinationProperty

fun matches(event: KeyEvent) = primaryCombination.match(event)
interface NamedKeyBinding {
val keyBindingName : String
val primaryCombinationProperty: SimpleObjectProperty<KeyCombination>
var primaryCombination : KeyCombination

val keyCodes: Set<KeyCode>
get() {
val codes = mutableSetOf<KeyCode>()
(primaryCombination as? KeyCodeCombination)?.code?.also { codes += it }
codes += primaryCombination.modifierCodes
(primaryCombinationProperty.get() as? KeyCodeCombination)?.code?.also { codes += it }
codes += primaryCombinationProperty.get().modifierCodes
return codes.toSet()
}

val deepCopy: NamedKeyCombination
get() = NamedKeyCombination(name, primaryCombination)
fun matches(event : KeyEvent, keysExclusive: Boolean = true) : Boolean {
return if (keysExclusive) {
primaryCombinationProperty.get().match(event)
} else {
val codesMatchIfCodeCombo = (primaryCombinationProperty.get() as? KeyCodeCombination)?.code?.let { it == event.code } ?: true
codesMatchIfCodeCombo && event.modifierCodes.containsAll(primaryCombinationProperty.get().modifierCodes)
}
}

val deepCopy: NamedKeyBinding
}

open class NamedKeyCombination(override val keyBindingName: String, primaryCombination: KeyCombination) : NamedKeyBinding {

override val primaryCombinationProperty = SimpleObjectProperty(primaryCombination)
override var primaryCombination: KeyCombination by primaryCombinationProperty.nonnull()

override val deepCopy: NamedKeyCombination
get() = NamedKeyCombination(keyBindingName, primaryCombination)

override fun equals(other: Any?): Boolean {
if (other is NamedKeyCombination)
return other.name === name
return other.keyBindingName === keyBindingName
return false
}

override fun toString() = ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
.append("name", name)
.append("name", keyBindingName)
.append("primaryCombination", primaryCombination)
.toString()

override fun hashCode() = name.hashCode()
override fun hashCode() = keyBindingName.hashCode()

class CombinationMap(vararg combinations: NamedKeyCombination) : MutableMap<String, NamedKeyCombination> by mutableMapOf() {
class CombinationMap(vararg combinations: NamedKeyBinding) : MutableMap<String, NamedKeyBinding> by mutableMapOf() {

init {
combinations.forEach { this += it }
}

class KeyCombinationAlreadyInserted(val keyCombination: NamedKeyCombination) :
RuntimeException("Action with name ${keyCombination.name} already present but tried to insert: $keyCombination")
class KeyCombinationAlreadyInserted(val keyCombination: NamedKeyBinding) :
RuntimeException("Action with name ${keyCombination.keyBindingName} already present but tried to insert: $keyCombination")

@Throws(KeyCombinationAlreadyInserted::class)
fun addCombination(keyCombination: NamedKeyCombination) {
if (containsKey(keyCombination.name))
fun addCombination(keyCombination: NamedKeyBinding) {
if (containsKey(keyCombination.keyBindingName))
throw KeyCombinationAlreadyInserted(keyCombination)
this[keyCombination.name] = keyCombination
this[keyCombination.keyBindingName] = keyCombination
}

fun matches(name: String, event: KeyEvent, keysExclusive : Boolean = true) : Boolean {
val namedCombo = get(name)!!
return if (keysExclusive) {
namedCombo.matches(event)
} else {
val combo = namedCombo.primaryCombination
val codesMatchIfCodeCombo = (combo as? KeyCodeCombination)?.code?.let { it == event.code } ?: true
codesMatchIfCodeCombo && event.modifierCodes.containsAll(combo.modifierCodes)
}
return get(name)!!.matches(event, keysExclusive)
}

operator fun plusAssign(keyCombination: NamedKeyCombination) = addCombination(keyCombination)
operator fun plusAssign(keyCombination: NamedKeyBinding) = addCombination(keyCombination)

operator fun plus(keyCombination: NamedKeyCombination) = this.also { it.plusAssign(keyCombination) }
operator fun plus(keyCombination: NamedKeyBinding) = this.also { it.plusAssign(keyCombination) }

operator fun contains(actionIdentifier: String) = this.containsKey(actionIdentifier)

operator fun contains(keyCombination: NamedKeyCombination) = contains(keyCombination.name)
operator fun contains(keyCombination: NamedKeyBinding) = contains(keyCombination.keyBindingName)

val deepCopy: CombinationMap
get() = values.map { it.deepCopy }.toTypedArray().let { CombinationMap(*it) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import javafx.stage.Window
import org.janelia.saalfeldlab.fx.actions.ActionSet
import org.janelia.saalfeldlab.fx.actions.ActionSet.Companion.installActionSet
import org.janelia.saalfeldlab.fx.actions.ActionSet.Companion.removeActionSet
import org.janelia.saalfeldlab.fx.actions.NamedKeyBinding

class KeyTracker {

Expand Down Expand Up @@ -80,6 +81,8 @@ class KeyTracker {

fun areOnlyTheseKeysDown(vararg codes: KeyCode) = activeKeys.synchronized { mutableSetOf(*codes) == this }

fun areKeysDown(namedKeyBinding: NamedKeyBinding) = activeKeys.synchronized { containsAll(listOf(*namedKeyBinding.keyCodes.toTypedArray())) }

fun areKeysDown(vararg codes: KeyCode) = activeKeys.synchronized { containsAll(listOf(*codes)) }

fun activeKeyCount() = activeKeys.synchronized { size }
Expand Down
26 changes: 14 additions & 12 deletions src/main/kotlin/org/janelia/saalfeldlab/fx/extensions/Delegates.kt
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,10 @@ class LazyForeignMap<K, V>(val foreignKeyProvider: () -> K, val valueGenerator:
}

operator fun getValue(t: Any, property: KProperty<*>): V {
val foreignKey = foreignKeyProvider()
return getOrPut(foreignKey) { valueGenerator(foreignKey) }
return synchronized(this) {
val foreignKey = foreignKeyProvider()
getOrPut(foreignKey) { valueGenerator(foreignKey) }
}
}
}

Expand Down Expand Up @@ -75,26 +77,26 @@ class LazyForeignValue<K, V>(val foreignKeyProvider: () -> K, val valueGenerator
}

operator fun getValue(t: Any?, property: KProperty<*>): V {
return synchronized(this) {
updateKeyAndValue()
}
}

private fun updateKeyAndValue(): V {
val foreignKey = foreignKeyProvider()
return if (foreignKey == currentKey) currentValue!!
return if (foreignKey == currentKey) currentValue as V
else {
currentKey = foreignKey
val oldValue = currentValue
currentValue = valueGenerator(currentKey!!)
oldValueHandler?.invoke(oldValue)
currentValue!!
currentValue as V
}
}

operator fun getValue(t: Nothing?, property: KProperty<*>): V {
val foreignKey = foreignKeyProvider()
return if (foreignKey == currentKey) currentValue!!
else {
currentKey = foreignKey
val oldValue = currentValue
currentValue = valueGenerator(currentKey!!)
oldValueHandler?.invoke(oldValue)
currentValue!!
return synchronized(this) {
updateKeyAndValue()
}
}
}
72 changes: 72 additions & 0 deletions src/main/kotlin/org/janelia/saalfeldlab/fx/ui/ScaleView.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package org.janelia.saalfeldlab.fx.ui

import javafx.beans.property.ObjectProperty
import javafx.css.*
import javafx.scene.Node
import javafx.scene.layout.Pane
import javafx.scene.layout.Region


open class ScaleView protected constructor() : Pane(), Styleable {

protected val paneWidthProperty: ObjectProperty<Number?> = SimpleStyleableObjectProperty(WIDTH, this, "paneWidth")
protected val paneHeightProperty: ObjectProperty<Number?> = SimpleStyleableObjectProperty(HEIGHT, this, "paneHeight")


init {
styleClass += "scale-pane"
}

constructor(child: Node = Region()) : this() {
children += child
paneWidthProperty.addListener { _, _, new -> new?.let { width ->
prefWidth = width.toDouble()
(child as? Region)?.minWidthProperty()?.bind(paneWidthProperty)
} }
paneHeightProperty.addListener { _, _, new -> new?.let { height ->
prefHeight = height.toDouble()
(child as? Region)?.minHeightProperty()?.bind(paneHeightProperty)
} }
}

override fun getCssMetaData(): MutableList<CssMetaData<out Styleable, *>> {
return getClassCssMetaData()
}

companion object {

private val WIDTH = object : CssMetaData<ScaleView, Number>("-pref-width", StyleConverter.getSizeConverter()) {
override fun isSettable(styleable: ScaleView) = !styleable.paneWidthProperty.isBound

override fun getStyleableProperty(styleable: ScaleView): StyleableProperty<Number> {
@Suppress("UNCHECKED_CAST")
return styleable.paneWidthProperty as StyleableProperty<Number>
}

override fun getInitialValue(styleable: ScaleView): Number {
return styleable.prefWidth
}
}

private val HEIGHT = object : CssMetaData<ScaleView, Number>("-pref-height", StyleConverter.getSizeConverter()) {
override fun isSettable(styleable: ScaleView) = !styleable.paneHeightProperty.isBound

override fun getStyleableProperty(styleable: ScaleView): StyleableProperty<Number> {
@Suppress("UNCHECKED_CAST")
return styleable.paneHeightProperty as StyleableProperty<Number>
}

override fun getInitialValue(styleable: ScaleView): Number {
return styleable.prefHeight
}
}

private val STYLEABLES: MutableList<CssMetaData<out Styleable, *>> = mutableListOf<CssMetaData<out Styleable, *>>().also {
it += Region.getClassCssMetaData()
it += WIDTH
it += HEIGHT
}

fun getClassCssMetaData(): MutableList<CssMetaData<out Styleable, *>> = STYLEABLES
}
}

0 comments on commit 1c519f8

Please sign in to comment.