-
Notifications
You must be signed in to change notification settings - Fork 0
Signals
Signals are one of the ways Acorn UI accomplishes an Observer Pattern.
Signals in Acorn UI are based off the Signals and Slots paradigm from Qt. A Signal has a list of callback handlers, and those handlers are invoked with the type-checked event provided in the signal's dispatch(event)
.
A simple example of a Signal representing a change event on an object.
class Sample(owner: Context) : ContextImpl(owner) {
val changed = signal<ChangeEvent<Int>>()
var value by observable(0) {
prop, old, new ->
changed.dispatch(ChangeEvent(old, new))
}
}
fun main() {
val s = Sample()
s.changed.listen { e ->
println("Changed from: ${e.oldData} to ${e.newData}")
}
s.value = 3 // prints "Changed from: 0 to 3"
}
// It is also possible to remove handlers by the disposable handle returned by `listen`.
fun main2() {
val s = Sample()
val changedSub = s.changed.listen(::changedCallback)
s.changed.once(::changedCallback2)
s.value = 3 // prints "Changed from: 0 to 3" then prints "Another changed handler" and removes the `changedCallback2` handler.
changedSub.dispose()
s.value = 4 // no print
}
private fun changedCallback(event: ChangeEvent) {
println("Changed from: ${event.oldData} to ${event.newData}")
}
private fun changedCallback2(event: ChangeEvent) {
println("Another changed handler")
}
- A Signal may not be dispatched while it's currently dispatching.
- A Signal may remove a handler from a handler (including itself).
- A Signal may add another handler from a handler.
- If a handler is added within a handler, the new handler will NOT be invoked in the current dispatch.
- If a handler is removed within a handler, the removed handler will not be invoked in the current dispatch (unless it already was).
- Signals are typically named in past tense after the verb they represent. With the exception of a Signal representing an opportunity to cancel an action.
Example:
class Sample(owner: Context) : ContextImpl(owner) {
val changing = signal<DataChangeEvent<Int>>())
val changed = signal<DataChangeEvent<Int>>())
var value: Int = 0
set(value) {
if (field == value) return
val e = ChangeEvent(field, value)
changing.dispatch(e)
if (!e.defaultPrevented) {
field = value
changed.dispatch(e)
}
}
}
The changing and changed events are separated to avoid complexity with priority and ordering. For example an observer that wants to perform an update after value
has changed should not need to ensure that its handler priority is lower than other handlers that may invoke preventDefault()
.
NB: It is a good practice to dispose a Signal when the containing class is disposed. This happens automatically if using the signal
dsl within a class that implements Owner
. (In this example ContextImpl)
Copyright © 2020 Poly Forest, LLC
- Overview
-
Developer's Guide
- Getting Started
- CI
- Building Views
- Acorn UI Architecture
- Components
- Button
- Calendar
- ColorPicker ....
- Fonts
- Style Guide
- FAQ
- Roadmap
- Support