From b26e2cecc0b9762eec1170750c62e701003ce884 Mon Sep 17 00:00:00 2001 From: Sarah Gerweck Date: Mon, 18 Apr 2016 01:12:16 -0700 Subject: [PATCH] Add `ReadOnlyObjectProperty` instances for 0.7.0 --- CHANGELOG.md | 5 ++ README.md | 2 +- .../org/gerweck/scalafx/util/observable.scala | 63 ++++++++++++++++--- version.sbt | 2 +- 4 files changed, 60 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 53c58fc..d382560 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,3 +9,8 @@ end users will be listed here. * Updates to several dependencies. * This includes Shapeless 2.3.0, which may introduce binary incompatibilities if you have compiled against a different version. + +### 0.7.0 + + * Modified Scalaz instance to add `ReadOnlyObjectProperty` instances and + improve specificity of the `ObservableValue[A, _]` instances. diff --git a/README.md b/README.md index a3ee08c..71a8c14 100644 --- a/README.md +++ b/README.md @@ -18,4 +18,4 @@ will be happy to merge high-quality pull requests if you find a bug. To use ScalaFX, add the following to your SBT build: - libraryDependencies += "org.gerweck.scala" %% "scalafx-utils" % "0.6.0" + libraryDependencies += "org.gerweck.scala" %% "scalafx-utils" % "0.7.0" diff --git a/src/main/scala/org/gerweck/scalafx/util/observable.scala b/src/main/scala/org/gerweck/scalafx/util/observable.scala index d9119d2..c5d865a 100644 --- a/src/main/scala/org/gerweck/scalafx/util/observable.scala +++ b/src/main/scala/org/gerweck/scalafx/util/observable.scala @@ -18,7 +18,7 @@ trait ObservableImplicits { */ implicit val observableInstances = new Applicative[Observable] with Functor[Observable] with Monad[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] = { + override def map[A, B](a: Observable[A])(f: A => B): ReadOnlyObjectProperty[B] = { @inline def recalculate(): B = f(a.value) val originalValue = recalculate() @@ -38,12 +38,12 @@ trait ObservableImplicits { prop } - def point[A](a: => A): ObservableValue[A, A] = { + def point[A](a: => A): ReadOnlyObjectProperty[A] = { ObjectProperty[A](a) } /* Ap can be derived from `point` and `bind`, but this has less overhead. */ - override def ap[A, B](fa: => Observable[A])(f: => Observable[A => B]): ObservableValue[B, B] = { + override def ap[A, B](fa: => Observable[A])(f: => Observable[A => B]): ReadOnlyObjectProperty[B] = { @inline def recalculate(): B = (f.value)(fa.value) val originalValue = recalculate() @@ -67,12 +67,55 @@ trait ObservableImplicits { } /* Aka `flatMap` */ - override def bind[A, B](fa: Observable[A])(f: A => Observable[B]): ObservableValue[B, B] = { + override def bind[A, B](fa: Observable[A])(f: A => Observable[B]): ReadOnlyObjectProperty[B] = { join(map(fa)(f)) } /* Aka `flatten` */ - override def join[A](ooa: Observable[Observable[A]]): ObservableValue[A, A] = { + override def join[A](ooa: Observable[Observable[A]]): ReadOnlyObjectProperty[A] = { + @inline def oa() = ooa.value + @inline def calc(): A = oa().value + + val originalValue = calc() + + val prop = ObjectProperty[A](originalValue) + + var prevValue = originalValue + + def innerHandle() = prop.synchronized { + val newVal = calc() + if (prevValue != newVal) { + prop.value = newVal + prevValue = newVal + } + } + var innerSub = oa() onChange innerHandle + + var prevOuter = oa() + def outerHandle() = prop.synchronized { + val newOuter = oa() + /* We need reference equality here: we're subscribing to a specific object. */ + if (prevOuter ne newOuter) { + innerSub.cancel() + innerSub = newOuter onChange innerHandle + prevOuter = newOuter + innerHandle() + } + } + + ooa onChange outerHandle + + prop + } + } + + implicit val readOnlyObjectPropertyInstances = new Applicative[ReadOnlyObjectProperty] with Functor[ReadOnlyObjectProperty] with Monad[ReadOnlyObjectProperty] { + override def map[A, B](a: ReadOnlyObjectProperty[A])(f: A => B): ReadOnlyObjectProperty[B] = observableInstances.map(a)(f) + override def point[A](a: => A): ReadOnlyObjectProperty[A] = observableInstances.point(a) + override def ap[A, B](fa: => ReadOnlyObjectProperty[A])(f: => ReadOnlyObjectProperty[A => B]): ReadOnlyObjectProperty[B] = observableInstances.ap(fa)(f) + override def bind[A, B](fa: ReadOnlyObjectProperty[A])(f: A => ReadOnlyObjectProperty[B]): ReadOnlyObjectProperty[B] = observableInstances.bind(fa)(f) + override def join[A](ooa: ReadOnlyObjectProperty[ReadOnlyObjectProperty[A]]): ReadOnlyObjectProperty[A] = { + /* NOTE: this is copy-pasted from `observableInstances`. TBD: Find a way to share this. */ @inline def oa() = ooa.value @inline def calc(): A = oa().value @@ -244,7 +287,7 @@ object ToObservableOps { } object RichToObservable { - @inline final def toObservable[A, B](a: A)(implicit ops: ToObservableOps[A, B]): ObservableValue[B, B] = { + @inline final def toObservable[A, B](a: A)(implicit ops: ToObservableOps[A, B]): ReadOnlyObjectProperty[B] = { @inline def recalculate(): B = ops.recalculate(a) val originalValue = recalculate() val prop = ObjectProperty[B](originalValue) @@ -263,23 +306,23 @@ object RichToObservable { } sealed trait RichObservableSeqLike[A] extends Any { - def observableSeqValue: ObservableValue[Seq[A], Seq[A]] + def observableSeqValue: ReadOnlyObjectProperty[Seq[A]] } final class RichObservableBuffer[A](val obs: ObservableBuffer[A]) extends AnyVal with RichObservableSeqLike[A] { - def observableSeqValue: ObservableValue[Seq[A], Seq[A]] = { + def observableSeqValue: ReadOnlyObjectProperty[Seq[A]] = { RichToObservable.toObservable(obs) } } final class RichObservableArray[A, B <: ObservableArray[A, B, C], C <: javafx.collections.ObservableArray[C]](val oaa: ObservableArray[A, B, C]) extends AnyVal with RichObservableSeqLike[A] { - def observableSeqValue: ObservableValue[Seq[A], Seq[A]] = { + def observableSeqValue: ReadOnlyObjectProperty[Seq[A]] = { RichToObservable.toObservable(oaa) } } final class RichObservableSet[A](val os: ObservableSet[A]) extends AnyVal { - def observableSetValue: ObservableValue[Set[A], Set[A]] = { + def observableSetValue: ReadOnlyObjectProperty[Set[A]] = { RichToObservable.toObservable(os) } } diff --git a/version.sbt b/version.sbt index 0bd9d17..a3ddb27 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version in ThisBuild := "0.6.1-SNAPSHOT" \ No newline at end of file +version in ThisBuild := "0.7.0-SNAPSHOT"