From dc8949d54c61d2a9e59346ebc12257327bb5d00f Mon Sep 17 00:00:00 2001 From: Sarah Gerweck Date: Thu, 16 Apr 2015 23:51:58 -0700 Subject: [PATCH] Much progress on observables. Still some work to do on making the syntax work how we want. --- .../scalafx/util/ObservableTupler.scala | 81 ++++++++++++-- .../org/gerweck/scalafx/util/observable.scala | 95 ++++++++++++++++ .../org/gerweck/scalafx/util/package.scala | 101 ++---------------- .../org/gerweck/scalafx/util/scene.scala | 10 ++ 4 files changed, 185 insertions(+), 102 deletions(-) create mode 100644 src/main/scala/org/gerweck/scalafx/util/observable.scala create mode 100644 src/main/scala/org/gerweck/scalafx/util/scene.scala diff --git a/src/main/scala/org/gerweck/scalafx/util/ObservableTupler.scala b/src/main/scala/org/gerweck/scalafx/util/ObservableTupler.scala index 5de155f..406481e 100644 --- a/src/main/scala/org/gerweck/scalafx/util/ObservableTupler.scala +++ b/src/main/scala/org/gerweck/scalafx/util/ObservableTupler.scala @@ -7,6 +7,8 @@ package org.gerweck.scalafx.util +import language.existentials + import scalaz._ import Scalaz._ @@ -14,31 +16,90 @@ import shapeless._ import shapeless.syntax._ import shapeless.ops.hlist._ +import scalafx.beans.property.ObjectProperty import scalafx.beans.value.ObservableValue +import ObservableTupler._ + /** * * @author Sarah Gerweck */ -class ObservableTupler[HLObs <: HList, HLParams <: HList, TParams <: Product] private[util] +class ObservableTupler + [HLObs <: HList, HLParams <: HList, TParams <: Product] private (hlist: HLObs) - (implicit ops: HListOps[HLObs], - unwrapper: Mapper.Aux[unwrapObservable.type, HLObs, HLParams], - tupler: Generic.Aux[HLParams, TParams]) { - def |@|[O, P, Appended <: HList, Unwrapped, Tupled] + (implicit unwrapper: Mapper.Aux[ObservableUnwrapper.type, HLObs, HLParams], + tupler: Tupler.Aux[HLParams, TParams], + lister: ToList[HLObs, Observable[_]]) { + + 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]) + (implicit prepend: Prepend.Aux[HLObs, ObservableValue[O, P]::HNil, Appended], + uw: Mapper.Aux[ObservableUnwrapper.type, Appended, Unwrapped]): Unwrapped = { + uw(hlist :+ f) + } + + 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[unwrapObservable.type, Appended, Unwrapped], - tplr: Generic.Aux[Unwrapped, Tupled], - ops: HListOps[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 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 ObservableUnwrapper extends Poly1 { + implicit def apply[T, U, A <% ObservableValue[T, U]]: Case.Aux[A, T] = at[A]{ o => o.value } +} object ObservableTupler { - object unwrapObservable extends Poly1 { - implicit def apply[T, U, A <% ObservableValue[T, U]]: Case.Aux[A, T] = at[A]{ o => o.value } + + def apply[A, A1, B, B1](a: ObservableValue[A, A1], b: ObservableValue[B, B1]) = { + new ObservableTupler(a::b::HNil) } } diff --git a/src/main/scala/org/gerweck/scalafx/util/observable.scala b/src/main/scala/org/gerweck/scalafx/util/observable.scala new file mode 100644 index 0000000..3a961b9 --- /dev/null +++ b/src/main/scala/org/gerweck/scalafx/util/observable.scala @@ -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 + } +} diff --git a/src/main/scala/org/gerweck/scalafx/util/package.scala b/src/main/scala/org/gerweck/scalafx/util/package.scala index e7057e1..50d1ce4 100644 --- a/src/main/scala/org/gerweck/scalafx/util/package.scala +++ b/src/main/scala/org/gerweck/scalafx/util/package.scala @@ -1,6 +1,7 @@ package org.gerweck.scalafx import language.implicitConversions +import language.existentials import scalafx.beans.property._ import scalafx.beans.value._ @@ -17,102 +18,18 @@ import Scalaz._ * * @author Sarah Gerweck */ -package object util { +package object util extends ObservableImplicits { type Observable[A] = ObservableValue[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() - - val prop = ObjectProperty[B](originalValue) - - def changeHandler = { - prop.value = recalculate() - } - - 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 + object TextDisplay { + import scalafx.beans.property._ + import scalafx.scene.text.Text + def apply(text: ObservableValue[String,String]) = { + val t = new scalafx.scene.text.Text + t.text <== text + t } } diff --git a/src/main/scala/org/gerweck/scalafx/util/scene.scala b/src/main/scala/org/gerweck/scalafx/util/scene.scala new file mode 100644 index 0000000..d05a920 --- /dev/null +++ b/src/main/scala/org/gerweck/scalafx/util/scene.scala @@ -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 } +} +