Much progress on observables.
Still some work to do on making the syntax work how we want.
This commit is contained in:
parent
c689dd3d3e
commit
dc8949d54c
@ -7,6 +7,8 @@
|
|||||||
|
|
||||||
package org.gerweck.scalafx.util
|
package org.gerweck.scalafx.util
|
||||||
|
|
||||||
|
import language.existentials
|
||||||
|
|
||||||
import scalaz._
|
import scalaz._
|
||||||
import Scalaz._
|
import Scalaz._
|
||||||
|
|
||||||
@ -14,31 +16,90 @@ import shapeless._
|
|||||||
import shapeless.syntax._
|
import shapeless.syntax._
|
||||||
import shapeless.ops.hlist._
|
import shapeless.ops.hlist._
|
||||||
|
|
||||||
|
import scalafx.beans.property.ObjectProperty
|
||||||
import scalafx.beans.value.ObservableValue
|
import scalafx.beans.value.ObservableValue
|
||||||
|
|
||||||
|
import ObservableTupler._
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sarah Gerweck <sarah@atscale.com>
|
* @author Sarah Gerweck <sarah@atscale.com>
|
||||||
*/
|
*/
|
||||||
class ObservableTupler[HLObs <: HList, HLParams <: HList, TParams <: Product] private[util]
|
class ObservableTupler
|
||||||
|
[HLObs <: HList, HLParams <: HList, TParams <: Product] private
|
||||||
(hlist: HLObs)
|
(hlist: HLObs)
|
||||||
(implicit ops: HListOps[HLObs],
|
(implicit unwrapper: Mapper.Aux[ObservableUnwrapper.type, HLObs, HLParams],
|
||||||
unwrapper: Mapper.Aux[unwrapObservable.type, HLObs, HLParams],
|
tupler: Tupler.Aux[HLParams, TParams],
|
||||||
tupler: Generic.Aux[HLParams, TParams]) {
|
lister: ToList[HLObs, Observable[_]]) {
|
||||||
def |@|[O, P, Appended <: HList, Unwrapped, Tupled]
|
|
||||||
|
def ap[O, P, Appended <: HList]
|
||||||
|
(f: ObservableValue[O, P])
|
||||||
|
(implicit prepend: Prepend.Aux[HLObs, ObservableValue[O, P]::HNil, Appended]) = {
|
||||||
|
hlist :+ f
|
||||||
|
}
|
||||||
|
|
||||||
|
def uw[O, P, Appended <: HList, Unwrapped <: HList]
|
||||||
(f: ObservableValue[O, P])
|
(f: ObservableValue[O, P])
|
||||||
(implicit prepend: Prepend.Aux[HLObs, ObservableValue[O, P]::HNil, Appended],
|
(implicit prepend: Prepend.Aux[HLObs, ObservableValue[O, P]::HNil, Appended],
|
||||||
uw: Mapper.Aux[unwrapObservable.type, Appended, Unwrapped],
|
uw: Mapper.Aux[ObservableUnwrapper.type, Appended, Unwrapped]): Unwrapped = {
|
||||||
tplr: Generic.Aux[Unwrapped, Tupled],
|
uw(hlist :+ f)
|
||||||
ops: HListOps[Appended]) = {
|
}
|
||||||
|
|
||||||
|
def tp[O, P, Appended <: HList, Unwrapped <: HList, Tupled <: Product]
|
||||||
|
(f: ObservableValue[O, P])
|
||||||
|
(implicit prepend: Prepend.Aux[HLObs, ObservableValue[O, P]::HNil, Appended],
|
||||||
|
uw: Mapper.Aux[ObservableUnwrapper.type, Appended, Unwrapped],
|
||||||
|
tplr: Tupler.Aux[Unwrapped, Tupled]): Tupled = {
|
||||||
|
uw(hlist :+ f).tupled
|
||||||
|
}
|
||||||
|
|
||||||
|
def tl[O, P, Appended <: HList, Unwrapped <: HList, Tupled <: Product, ApList]
|
||||||
|
(f: ObservableValue[O, P])
|
||||||
|
(implicit prepend: Prepend.Aux[HLObs, ObservableValue[O, P]::HNil, Appended],
|
||||||
|
uw: Mapper.Aux[ObservableUnwrapper.type, Appended, Unwrapped],
|
||||||
|
tplr: Tupler.Aux[Unwrapped, Tupled],
|
||||||
|
lst: ToList[Appended, Observable[_]]): Tupled = {
|
||||||
|
val hl = hlist :+ f
|
||||||
|
hl.toList
|
||||||
|
uw(hl).tupled
|
||||||
|
}
|
||||||
|
|
||||||
|
def |@|[O, P, Appended <: HList, Unwrapped <: HList, Tupled <: Product, ApList]
|
||||||
|
(f: ObservableValue[O, P])
|
||||||
|
(implicit prepend: Prepend.Aux[HLObs, ObservableValue[O, P]::HNil, Appended],
|
||||||
|
uw: Mapper.Aux[ObservableUnwrapper.type, Appended, Unwrapped],
|
||||||
|
tplr: Tupler.Aux[Unwrapped, Tupled],
|
||||||
|
lst: ToList[Appended, Observable[_]]): ObservableTupler[Appended, Unwrapped, Tupled] = {
|
||||||
val newHL: Appended = hlist :+ f
|
val newHL: Appended = hlist :+ f
|
||||||
new ObservableTupler[Appended, Unwrapped, Tupled](newHL)
|
new ObservableTupler[Appended, Unwrapped, Tupled](newHL)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def tupled: ObservableValue[TParams, TParams] = {
|
||||||
|
def calculate() = unwrapper(hlist).tupled
|
||||||
|
|
||||||
|
val original = calculate()
|
||||||
|
val prop = ObjectProperty[TParams](original)
|
||||||
|
|
||||||
|
for {
|
||||||
|
component <- hlist.toList
|
||||||
|
} {
|
||||||
|
component onChange {
|
||||||
|
prop.value = calculate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prop
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def apply[C](f: TParams => C): Observable[C] = tupled map f
|
||||||
|
}
|
||||||
|
|
||||||
object ObservableTupler {
|
object ObservableUnwrapper extends Poly1 {
|
||||||
object unwrapObservable extends Poly1 {
|
|
||||||
implicit def apply[T, U, A <% ObservableValue[T, U]]: Case.Aux[A, T] = at[A]{ o => o.value }
|
implicit def apply[T, U, A <% ObservableValue[T, U]]: Case.Aux[A, T] = at[A]{ o => o.value }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object ObservableTupler {
|
||||||
|
|
||||||
|
def apply[A, A1, B, B1](a: ObservableValue[A, A1], b: ObservableValue[B, B1]) = {
|
||||||
|
new ObservableTupler(a::b::HNil)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
95
src/main/scala/org/gerweck/scalafx/util/observable.scala
Normal file
95
src/main/scala/org/gerweck/scalafx/util/observable.scala
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
package org.gerweck.scalafx.util
|
||||||
|
|
||||||
|
import language.implicitConversions
|
||||||
|
|
||||||
|
import scalaz._
|
||||||
|
|
||||||
|
import scalafx.beans.property._
|
||||||
|
import scalafx.beans.value._
|
||||||
|
|
||||||
|
trait ObservableImplicits {
|
||||||
|
implicit val observableApplicative = new Applicative[Observable] with Functor[Observable] {
|
||||||
|
/* Map can be derived from `ap`, but this adds less overhead. */
|
||||||
|
override def map[A, B](a: Observable[A])(f: A => B): ObservableValue[B, B] = {
|
||||||
|
@inline def recalculate(): B = f(a.value)
|
||||||
|
|
||||||
|
val originalValue = recalculate()
|
||||||
|
|
||||||
|
val prop = ObjectProperty[B](originalValue)
|
||||||
|
|
||||||
|
def changeHandler = {
|
||||||
|
prop.value = recalculate()
|
||||||
|
}
|
||||||
|
|
||||||
|
a onChange changeHandler
|
||||||
|
prop
|
||||||
|
}
|
||||||
|
|
||||||
|
def point[A](a: => A): ObservableValue[A, A] = {
|
||||||
|
ObjectProperty[A](a)
|
||||||
|
}
|
||||||
|
|
||||||
|
def ap[A, B](fa: => Observable[A])(f: => Observable[A => B]): ObservableValue[B, B] = {
|
||||||
|
@inline def recalculate(): B = (f.value)(fa.value)
|
||||||
|
|
||||||
|
val originalValue = recalculate()
|
||||||
|
|
||||||
|
val prop = ObjectProperty[B](originalValue)
|
||||||
|
|
||||||
|
var prevValue = originalValue
|
||||||
|
|
||||||
|
def changeHandler = {
|
||||||
|
val newVal = recalculate()
|
||||||
|
if (prevValue != newVal) {
|
||||||
|
prop.value = newVal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fa onChange changeHandler
|
||||||
|
f onChange changeHandler
|
||||||
|
|
||||||
|
prop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
implicit def enrichObservable[A, B](o: ObservableValue[A, B]) = new RichObservable(o)
|
||||||
|
implicit def enrichProperty[A, B](o: Property[A, B]) = new RichProperty(o)
|
||||||
|
}
|
||||||
|
|
||||||
|
class RichObservable[A, C](val self: ObservableValue[A, C]) extends AnyVal {
|
||||||
|
private type ObjObs[X] = ObservableValue[X, X]
|
||||||
|
@inline private def oapp = observableApplicative
|
||||||
|
|
||||||
|
def map[B](f: A => B) = oapp.map(self)(f)
|
||||||
|
def <*>[B](f: Observable[A => B]): Observable[B] = oapp.ap(self)(f)
|
||||||
|
def tuple[B](f: Observable[B]): Observable[(A,B)] = oapp.tuple2(self, f)
|
||||||
|
final def *>[B](fb: ObjObs[B]): Observable[B] = oapp.apply2(self,fb)((_,b) => b)
|
||||||
|
final def <*[B](fb: ObjObs[B]): Observable[A] = oapp.apply2(self,fb)((a,_) => a)
|
||||||
|
|
||||||
|
final def |@|[B, B1](fb: ObservableValue[B, B1]) = ObservableTupler(self, fb)
|
||||||
|
|
||||||
|
/** Alias for `|@|` */
|
||||||
|
final def ⊛[B, B1](fb: ObservableValue[B, B1]) = |@|(fb)
|
||||||
|
}
|
||||||
|
|
||||||
|
class RichProperty[A, B](val inner: Property[A, B]) extends AnyVal {
|
||||||
|
def biMap[B <: AnyRef](push: A => B, pull: B => A): ObjectProperty[B] = {
|
||||||
|
val original = push(inner.value)
|
||||||
|
val op = ObjectProperty[B](original)
|
||||||
|
inner onChange {
|
||||||
|
val oldVal = op.value
|
||||||
|
val newVal = push(inner.value)
|
||||||
|
if (oldVal != newVal) {
|
||||||
|
op.value = push(inner.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
op onChange {
|
||||||
|
val oldVal = inner.value
|
||||||
|
val newVal = pull(op.value)
|
||||||
|
if (oldVal != newVal) {
|
||||||
|
inner.value = newVal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
op
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package org.gerweck.scalafx
|
package org.gerweck.scalafx
|
||||||
|
|
||||||
import language.implicitConversions
|
import language.implicitConversions
|
||||||
|
import language.existentials
|
||||||
|
|
||||||
import scalafx.beans.property._
|
import scalafx.beans.property._
|
||||||
import scalafx.beans.value._
|
import scalafx.beans.value._
|
||||||
@ -17,102 +18,18 @@ import Scalaz._
|
|||||||
*
|
*
|
||||||
* @author Sarah Gerweck <sarah@atscale.com>
|
* @author Sarah Gerweck <sarah@atscale.com>
|
||||||
*/
|
*/
|
||||||
package object util {
|
package object util extends ObservableImplicits {
|
||||||
type Observable[A] = ObservableValue[A, _]
|
type Observable[A] = ObservableValue[A, _]
|
||||||
type SimpleProperty[A] = Property[A, _]
|
type SimpleProperty[A] = Property[A, _]
|
||||||
|
|
||||||
implicit val observableFunctor = new Functor[Observable] {
|
|
||||||
def map[A, B](a: Observable[A])(f: A => B): Observable[B] = {
|
|
||||||
@inline def recalculate(): B = f(a.value)
|
|
||||||
|
|
||||||
val originalValue = recalculate()
|
object TextDisplay {
|
||||||
|
import scalafx.beans.property._
|
||||||
val prop = ObjectProperty[B](originalValue)
|
import scalafx.scene.text.Text
|
||||||
|
def apply(text: ObservableValue[String,String]) = {
|
||||||
def changeHandler = {
|
val t = new scalafx.scene.text.Text
|
||||||
prop.value = recalculate()
|
t.text <== text
|
||||||
}
|
t
|
||||||
|
|
||||||
a onChange changeHandler
|
|
||||||
prop
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
implicit val observableApplicative = new Applicative[Observable] {
|
|
||||||
def point[A](a: => A): Observable[A] = {
|
|
||||||
ObjectProperty[A](a)
|
|
||||||
}
|
|
||||||
|
|
||||||
def ap[A, B](fa: => Observable[A])(f: => Observable[A => B]): Observable[B] = {
|
|
||||||
def recalculate: B = (f.value)(fa.value)
|
|
||||||
|
|
||||||
val originalValue = recalculate
|
|
||||||
|
|
||||||
val prop = ObjectProperty[B](originalValue)
|
|
||||||
|
|
||||||
var prevValue = originalValue
|
|
||||||
|
|
||||||
def changeHandler = {
|
|
||||||
val newVal = recalculate
|
|
||||||
if (prevValue != newVal) {
|
|
||||||
prop.value = recalculate
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fa onChange changeHandler
|
|
||||||
f onChange changeHandler
|
|
||||||
|
|
||||||
prop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
implicit class RichObservable[A](val self: Observable[A]) {
|
|
||||||
private type F[X] = Observable[X]
|
|
||||||
@inline private def F = observableApplicative
|
|
||||||
|
|
||||||
def map[A1 >: A, B](f: A1 => B) = F.map(self)(f)
|
|
||||||
def <*>[B](f: Observable[A => B]): Observable[B] = F.ap(self)(f)
|
|
||||||
def tuple[B](f: Observable[B]): Observable[(A,B)] = F.tuple2(self, f)
|
|
||||||
final def *>[B](fb: F[B]): F[B] = F.apply2(self,fb)((_,b) => b)
|
|
||||||
final def <*[B](fb: F[B]): F[A] = F.apply2(self,fb)((a,_) => a)
|
|
||||||
|
|
||||||
import shapeless._
|
|
||||||
import shapeless.syntax._
|
|
||||||
import shapeless.ops.hlist._
|
|
||||||
import HList._
|
|
||||||
implicitly[HListOps[Int::Int::HNil]]
|
|
||||||
final def |@|[B](fb: F[B]) = new ObservableTupler(self::fb::HNil)
|
|
||||||
|
|
||||||
/** Alias for `|@|` */
|
|
||||||
final def ⊛[B](fb: F[B]) = |@|(fb)
|
|
||||||
}
|
|
||||||
|
|
||||||
object unwrapObservable extends Poly1 {
|
|
||||||
implicit def apply[T, A <% Observable[T]]: Case.Aux[A, T] = at[A]{ o => o.value }
|
|
||||||
}
|
|
||||||
|
|
||||||
trait TupleBuilder[]
|
|
||||||
|
|
||||||
implicit class RichProperty[A](val inner: SimpleProperty[A]) extends AnyVal {
|
|
||||||
def biMap[B <: AnyRef](push: A => B, pull: B => A): ObjectProperty[B] = {
|
|
||||||
val original = push(inner.value)
|
|
||||||
val op = ObjectProperty[B](original)
|
|
||||||
inner onChange {
|
|
||||||
val oldVal = op.value
|
|
||||||
val newVal = push(inner.value)
|
|
||||||
if (oldVal != newVal) {
|
|
||||||
op.value = push(inner.value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
op onChange {
|
|
||||||
val oldVal = inner.value
|
|
||||||
val newVal = pull(op.value)
|
|
||||||
if (oldVal != newVal) {
|
|
||||||
inner.value = newVal
|
|
||||||
}
|
|
||||||
}
|
|
||||||
op
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
10
src/main/scala/org/gerweck/scalafx/util/scene.scala
Normal file
10
src/main/scala/org/gerweck/scalafx/util/scene.scala
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package org.gerweck.scalafx.util
|
||||||
|
|
||||||
|
import scalafx.geometry.Orientation._
|
||||||
|
import scalafx.scene.control._
|
||||||
|
|
||||||
|
object Separators {
|
||||||
|
def vertical() = new Separator { orientation = VERTICAL }
|
||||||
|
def horizontal() = new Separator { orientation = HORIZONTAL }
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user