diff --git a/macroid-core/src/main/scala/macroid/Cakes.scala b/macroid-core/src/main/scala/macroid/Cakes.scala index b6d0d15..e45dc6b 100644 --- a/macroid-core/src/main/scala/macroid/Cakes.scala +++ b/macroid-core/src/main/scala/macroid/Cakes.scala @@ -4,8 +4,6 @@ private[macroid] trait LayoutDsl extends Searching with LayoutBuilding with FragmentBuilding - with Tweaking - with Snailing object LayoutDsl extends LayoutDsl @@ -22,10 +20,9 @@ private[macroid] trait ToastDsl object ToastDsl extends ToastDsl private[macroid] trait FullDsl - extends UiThreading + extends MediaQueries with LayoutDsl with Tweaks with Snails - with ToastDsl with Loafs with DialogDsl with Phrases - with MediaQueries + with ToastDsl with Loafs object FullDsl extends FullDsl diff --git a/macroid-core/src/main/scala/macroid/Snailing.scala b/macroid-core/src/main/scala/macroid/Snailing.scala index b0cfb9a..aca4359 100644 --- a/macroid-core/src/main/scala/macroid/Snailing.scala +++ b/macroid-core/src/main/scala/macroid/Snailing.scala @@ -12,8 +12,6 @@ trait CanSnail[W, S, R] { } object CanSnail { - import UiThreading._ - implicit def `Widget is snailable with Snail`[W <: View, S <: Snail[W]](implicit ec: ExecutionContext): CanSnail[W, S, W] = new CanSnail[W, S, W] { def snail(w: W, s: S) = s(w).withResultAsync(w) @@ -92,5 +90,3 @@ private[macroid] trait Snailing { def <~~[T, R](t: T)(implicit canSnail: CanSnail[W, T, R]): Ui[Future[R]] = canSnail.snail(w, t) } } - -object Snailing extends Snailing diff --git a/macroid-core/src/main/scala/macroid/Snails.scala b/macroid-core/src/main/scala/macroid/Snails.scala index dc0f775..6f89114 100644 --- a/macroid-core/src/main/scala/macroid/Snails.scala +++ b/macroid-core/src/main/scala/macroid/Snails.scala @@ -33,8 +33,6 @@ private[macroid] trait BasicSnails { } private[macroid] trait ProgressSnails extends BasicSnails with VisibilityTweaks { - import UiThreading._ - /** Show this progress bar with indeterminate progress and hide it once `future` is done */ def waitProgress(future: Future[Any])(implicit ec: ExecutionContext): Snail[ProgressBar] = Tweak[ProgressBar](_.setIndeterminate(true)) + show ++ wait(future) + hide diff --git a/macroid-core/src/main/scala/macroid/Tweaking.scala b/macroid-core/src/main/scala/macroid/Tweaking.scala index 68372eb..a764ae0 100644 --- a/macroid-core/src/main/scala/macroid/Tweaking.scala +++ b/macroid-core/src/main/scala/macroid/Tweaking.scala @@ -50,5 +50,3 @@ private[macroid] trait Tweaking { def <~[T, R](t: T)(implicit canTweak: CanTweak[W, T, R]): Ui[R] = canTweak.tweak(w, t) } } - -object Tweaking extends Tweaking diff --git a/macroid-core/src/main/scala/macroid/UiActions.scala b/macroid-core/src/main/scala/macroid/UiActions.scala index c5cb42c..8276254 100644 --- a/macroid-core/src/main/scala/macroid/UiActions.scala +++ b/macroid-core/src/main/scala/macroid/UiActions.scala @@ -1,14 +1,20 @@ package macroid -import android.os.Looper +import android.os.{Handler, Looper} import scala.concurrent.{ExecutionContext, Future} import scala.util.{ Failure, Success, Try } +/** An ExecutionContext associated with the UI thread */ +object UiThreadExecutionContext extends ExecutionContext { + private lazy val uiHandler = new Handler(Looper.getMainLooper) + + def reportFailure(t: Throwable) = t.printStackTrace() + def execute(runnable: Runnable) = uiHandler.post(runnable) +} + /** A UI action that can be sent to the UI thread for execution */ class Ui[+A](private[Ui] val v: () ⇒ A) { - import macroid.UiThreading._ - /** map combinator */ def map[B](f: A ⇒ B) = Ui(f(v())) @@ -56,4 +62,78 @@ object Ui { /** Combine (sequence) several UI actions together */ def sequence[A](vs: Ui[A]*) = Ui(vs.map(_.v())) + + /** Run a UI action on the UI thread */ + def run[A](ui: Ui[A]) = ui.run + + /** Run several UI actions on the UI thread */ + def run[A](ui1: Ui[A], ui2: Ui[A], uis: Ui[A]*) = sequence(ui1 +: ui2 +: uis: _*).run + + /** Get the result of executing an UI action on the current (hopefully, UI!) thread */ + def get[A](ui: Ui[A]) = ui.get +} + +/** Helpers to run UI actions as Future callbacks */ +case class UiFuture[T](future: Future[T]) extends AnyVal { + private def applyUi[A, B](f: Function[A, Ui[B]]): Function[A, B] = x ⇒ f(x).get + private def partialApplyUi[A, B](f: PartialFunction[A, Ui[B]]) = f andThen (_.get) + + /** Same as map, but performed on the UI thread. + * + * If the future is already completed and the current thread is the UI thread, + * the UI action will be applied in-place, rather than asynchronously. + */ + def mapUi[S](f: Function[T, Ui[S]]) = + if (future.isCompleted && Ui.uiThread == Thread.currentThread) { + future.value.get.map(applyUi(f)) match { + case Success(x) ⇒ Future.successful(x) + case Failure(t) ⇒ Future.failed(t) + } + } else { + future.map(applyUi(f))(UiThreadExecutionContext) + } + + /** Same as flatMap, but performed on the UI thread + * + * If the future is already completed and the current thread is the UI thread, + * the UI action will be applied in-place, rather than asynchronously. + */ + def flatMapUi[S](f: Function[T, Ui[Future[S]]]) = { + if (future.isCompleted && Ui.uiThread == Thread.currentThread) { + future.value.get.map(applyUi(f)) match { + case Success(x) ⇒ x + case Failure(t) ⇒ Future.failed(t) + } + } else { + future.flatMap(applyUi(f))(UiThreadExecutionContext) + } + } + + /** Same as foreach, but performed on the UI thread + * + * If the future is already completed and the current thread is the UI thread, + * the UI action will be applied in-place, rather than asynchronously. + */ + def foreachUi[U](f: Function[T, Ui[U]]) = + if (future.isCompleted && Ui.uiThread == Thread.currentThread) { + future.value.get.foreach(applyUi(f)) + } else { + future.foreach(applyUi(f))(UiThreadExecutionContext) + } + + /** Same as recover, but performed on the UI thread */ + def recoverUi[U >: T](pf: PartialFunction[Throwable, Ui[U]]) = + future.recover(partialApplyUi(pf))(UiThreadExecutionContext) + + /** Same as onSuccess, but performed on the UI thread */ + def onSuccessUi[U >: T](pf: PartialFunction[T, Ui[U]]) = + future.onSuccess(partialApplyUi(pf))(UiThreadExecutionContext) + + /** Same as onFailure, but performed on the UI thread */ + def onFailureUi[U](pf: PartialFunction[Throwable, Ui[U]]) = + future.onFailure(partialApplyUi(pf))(UiThreadExecutionContext) + + /** Same as onComplete, but performed on the UI thread */ + def onCompleteUi[U](pf: PartialFunction[Try[T], Ui[U]]) = + future.onComplete(partialApplyUi(pf))(UiThreadExecutionContext) } diff --git a/macroid-core/src/main/scala/macroid/UiThreading.scala b/macroid-core/src/main/scala/macroid/UiThreading.scala deleted file mode 100644 index eed8cb0..0000000 --- a/macroid-core/src/main/scala/macroid/UiThreading.scala +++ /dev/null @@ -1,92 +0,0 @@ -package macroid - -import android.os.{ Looper, Handler } - -import scala.concurrent.{ ExecutionContext, Future } -import scala.util.{ Failure, Success, Try } - -/** An ExecutionContext associated with the UI thread */ -object UiThreadExecutionContext extends ExecutionContext { - private lazy val uiHandler = new Handler(Looper.getMainLooper) - - def reportFailure(t: Throwable) = t.printStackTrace() - def execute(runnable: Runnable) = uiHandler.post(runnable) -} - -private[macroid] trait UiThreading { - /** Run UI code on the UI thread */ - def runUi[A](ui: Ui[A]) = ui.run - - /** Run UI code on the UI thread */ - def runUi[A](ui1: Ui[A], ui2: Ui[A], uis: Ui[A]*) = Ui.sequence(ui1 +: ui2 +: uis: _*).run - - /** Get the result of executing UI code on the current (hopefully, UI!) tread */ - def getUi[A](ui: Ui[A]) = ui.get - - /** Helpers to run UI actions as Future callbacks */ - implicit class UiFuture[T](future: Future[T]) { - private def applyUi[A, B](f: Function[A, Ui[B]]): Function[A, B] = x ⇒ f(x).get - private def partialApplyUi[A, B](f: PartialFunction[A, Ui[B]]) = f andThen (_.get) - - /** Same as map, but performed on the UI thread. - * - * If the future is already completed and the current thread is the UI thread, - * the UI action will be applied in-place, rather than asynchronously. - */ - def mapUi[S](f: Function[T, Ui[S]]) = - if (future.isCompleted && Ui.uiThread == Thread.currentThread) { - future.value.get.map(applyUi(f)) match { - case Success(x) ⇒ Future.successful(x) - case Failure(t) ⇒ Future.failed(t) - } - } else { - future.map(applyUi(f))(UiThreadExecutionContext) - } - - /** Same as flatMap, but performed on the UI thread - * - * If the future is already completed and the current thread is the UI thread, - * the UI action will be applied in-place, rather than asynchronously. - */ - def flatMapUi[S](f: Function[T, Ui[Future[S]]]) = { - if (future.isCompleted && Ui.uiThread == Thread.currentThread) { - future.value.get.map(applyUi(f)) match { - case Success(x) ⇒ x - case Failure(t) ⇒ Future.failed(t) - } - } else { - future.flatMap(applyUi(f))(UiThreadExecutionContext) - } - } - - /** Same as foreach, but performed on the UI thread - * - * If the future is already completed and the current thread is the UI thread, - * the UI action will be applied in-place, rather than asynchronously. - */ - def foreachUi[U](f: Function[T, Ui[U]]) = - if (future.isCompleted && Ui.uiThread == Thread.currentThread) { - future.value.get.foreach(applyUi(f)) - } else { - future.foreach(applyUi(f))(UiThreadExecutionContext) - } - - /** Same as recover, but performed on the UI thread */ - def recoverUi[U >: T](pf: PartialFunction[Throwable, Ui[U]]) = - future.recover(partialApplyUi(pf))(UiThreadExecutionContext) - - /** Same as onSuccess, but performed on the UI thread */ - def onSuccessUi[U >: T](pf: PartialFunction[T, Ui[U]]) = - future.onSuccess(partialApplyUi(pf))(UiThreadExecutionContext) - - /** Same as onFailure, but performed on the UI thread */ - def onFailureUi[U](pf: PartialFunction[Throwable, Ui[U]]) = - future.onFailure(partialApplyUi(pf))(UiThreadExecutionContext) - - /** Same as onComplete, but performed on the UI thread */ - def onCompleteUi[U](pf: PartialFunction[Try[T], Ui[U]]) = - future.onComplete(partialApplyUi(pf))(UiThreadExecutionContext) - } -} - -object UiThreading extends UiThreading diff --git a/macroid-core/src/main/scala/macroid/package.scala b/macroid-core/src/main/scala/macroid/package.scala new file mode 100644 index 0000000..d18b9c6 --- /dev/null +++ b/macroid-core/src/main/scala/macroid/package.scala @@ -0,0 +1,6 @@ +import scala.language.implicitConversions +import scala.concurrent.Future + +package object macroid extends Tweaking with Snailing { + implicit def futureToUiFuture[A](future: Future[A]): UiFuture[A] = UiFuture(future) +} diff --git a/macroid-core/src/main/scala/macroid/util/Effector.scala b/macroid-core/src/main/scala/macroid/util/Effector.scala index ade24c0..fcb056b 100644 --- a/macroid-core/src/main/scala/macroid/util/Effector.scala +++ b/macroid-core/src/main/scala/macroid/util/Effector.scala @@ -1,7 +1,6 @@ package macroid.util -import macroid.Ui -import macroid.UiThreading.UiFuture +import macroid._ import scala.language.higherKinds import scala.concurrent.{ Future, ExecutionContext } diff --git a/macroid-core/src/main/scala/macroid/warts/CheckUi.scala b/macroid-core/src/main/scala/macroid/warts/CheckUi.scala index d70b02c..6cccee9 100644 --- a/macroid-core/src/main/scala/macroid/warts/CheckUi.scala +++ b/macroid-core/src/main/scala/macroid/warts/CheckUi.scala @@ -10,7 +10,7 @@ object CheckUi extends WartTraverser { case LabelDef(_, _, _) ⇒ case stat ⇒ if (stat.tpe != null && stat.tpe <:< typeOf[macroid.Ui[_]]) - u.error(stat.pos, s"This statement returns an Ui action, but does not actually perform it. Call the `run` method, wrap it in `runUi`, or combine it with other Ui actions.") + u.error(stat.pos, s"This statement returns a UI action, but does not actually run it. Call the `run` method, or wrap it in `Ui.run`, or combine it with other UI actions.") } new u.Traverser { diff --git a/macroid-docs/guide/Bricks.md b/macroid-docs/guide/Bricks.md index 8ef57c8..d122f3d 100644 --- a/macroid-docs/guide/Bricks.md +++ b/macroid-docs/guide/Bricks.md @@ -50,15 +50,15 @@ layout <~ addViews(button, textView, ...) Finally, note that bricks return a [UI action](UiActions.html), therefore therefore if you need to use them e.g. in `setContentView`, -`getUi` is required. *This also means that `w[Widget]` will -return a new (different) instance of `Widget` each time you call `getUi(w[Widget])` or `w[Widget].get`*. +`Ui.get` is required. *This also means that `w[Widget]` will +return a new (different) instance of `Widget` each time you call `Ui.get(w[Widget])` or `w[Widget].get`*. ```scala override def onCreate(savedInstanceState: Bundle) = { super.onCreate(savedInstanceState) setContentView { - getUi { + Ui.get { l[LinearLayout]( w[Button], w[TextView] diff --git a/macroid-docs/guide/Imports.md b/macroid-docs/guide/Imports.md index 105720f..72529ef 100644 --- a/macroid-docs/guide/Imports.md +++ b/macroid-docs/guide/Imports.md @@ -3,24 +3,25 @@ Normally most *Macroid* features are imported via ```scala +import macroid._ import macroid.FullDsl._ ``` However you can also import them one by one (click list items to jump to docs): +* `macroid` package object includes + * [`macroid.UiFuture` implicit conversion](UiActions.html) + * [`macroid.Tweaking` operators](Tweaks.html#tweaking) (standard tweaks should be imported separately from `macroid.Tweaks`) + * [`macroid.Snailing` operators](Snails.html#-snailing-) (standard snails should be imported separately from `macroid.Snails`) * `macroid.LayoutDsl` includes * [`macroid.Searching`](Searching.html) * [`macroid.LayoutBuilding`](Bricks.html) * [`macroid.FragmentBuilding`](Fragments.html) - * [`macroid.Tweaking`](Tweaks.html#tweaking) (standard tweaks should be imported separately from `macroid.Tweaks`) - * [`macroid.Snailing`](Snails.html#-snailing-) (standard snails should be imported separately from `macroid.Snails`) - * [`macroid.Transforming`](Transformers.html) * [`macroid.DialogDsl`](ToastsDialogs.html#dialogs) (standard phrases should be imported separately from `macroid.Phrases`) -* [`macroid.ToastDsl`](ToastsDialogs.html#toasts) +* [`macroid.ToastDsl`](ToastsDialogs.html#toasts) (standard loafs should be imported separately from `macroid.Loafs`) * `macroid.FullDsl` includes - * [`macroid.UiThreading`](UiActions.html) * `macroid.LayoutDsl`, `macroid.Tweaks`, `macroid.Snails` - * `macroid.ToastDsl` + * `macroid.ToastDsl`, `macroid.Loafs` * `macroid.DialogDsl`, `macroid.Phrases` * [`macroid.MediaQueries`](MediaQueries.html) diff --git a/macroid-docs/guide/Snails.md b/macroid-docs/guide/Snails.md index 18ede6e..ea32739 100644 --- a/macroid-docs/guide/Snails.md +++ b/macroid-docs/guide/Snails.md @@ -54,8 +54,7 @@ val fadeAndDisappear = ## “Snailing” “Snailing” is a made-up word that denotes the process of applying snails to widgets. -Snailing is very similar to [tweaking](Tweaks.html#tweaking), except it uses the `<~~` operator -(see [Changelog](../Changelog.html) for the recent syntax changes): +Snailing is very similar to [tweaking](Tweaks.html#tweaking), except it uses the `<~~` operator: ```scala textView <~~ fadeIn diff --git a/macroid-docs/guide/ToastsDialogs.md b/macroid-docs/guide/ToastsDialogs.md index b54ef9c..aafa9bb 100644 --- a/macroid-docs/guide/ToastsDialogs.md +++ b/macroid-docs/guide/ToastsDialogs.md @@ -12,17 +12,17 @@ The toast API is similar to [tweaking](Tweaks.html#tweaking) and consists of `to The loafs are as follows: ```scala -runUi { +Ui.run { // `fry` shows the toast toast("Foo") <~ fry } -runUi { +Ui.run { // `long` makes the toast long toast("Foooo") <~ long <~ fry } -runUi { +Ui.run { // `gravity` should be self-evident toast("XY") <~ gravity(Gravity.CENTER, xOffset = 3 dp) <~ fry } @@ -42,7 +42,7 @@ The dialog API is also similar to [tweaking](Tweaks.html#tweaking) and consists It is also possible to provide a dialog theme: ```scala -runUi { +Ui.run { dialog(themeId)("I’m a message") } ``` @@ -50,7 +50,7 @@ runUi { The phrases can be discovered via the [scaladoc](../api/core/macroid/Phrases$.html), here are some examples: ```scala -runUi { +Ui.run { dialog("Please fasten your seat belts") <~ title("Warning") <~ speak // this shows the dialog @@ -65,7 +65,7 @@ Finally, two implicit conversions to `Dialog.OnClickListener` are awailable: Example: ```scala -runUi { +Ui.run { dialog("Are you sure") <~ positiveYes(textView <~ show) <~ negativeNo(textView <~ hide) <~ diff --git a/macroid-docs/guide/UiActions.md b/macroid-docs/guide/UiActions.md index 1153812..3295e4c 100644 --- a/macroid-docs/guide/UiActions.md +++ b/macroid-docs/guide/UiActions.md @@ -24,13 +24,12 @@ It does not do anything yet and has a type `Ui[Int]`, since it returns an intege If you know [Haskell](http://www.haskell.org/haskellwiki/Haskell) or [scalaz](https://github.com/scalaz/scalaz), so far that’s your typical `IO` monad. -An UI action can be sent to the UI thread to be run: +A UI action can be sent to the UI thread to be run: ```scala action.run // or -import macroid.FullDsl._ -runUi(action) +Ui.run(action) ``` Running an action of type `Ui[A]` returns a `Future[A]` (in this case — `Future[Int]`), @@ -45,8 +44,7 @@ the UI thread): ```scala action.get // or -import macroid.FullDsl._ -getUi(action) +Ui.get(action) ``` ## Advantages @@ -70,12 +68,12 @@ tweaking: val action: Ui[Button] = button <~ text("Hi") ``` -and so on. The first case is one of the reasons why `getUi` is needed sometimes, as `setContentView` expects a `View` and +and so on. The first case is one of the reasons why `Ui.get` is needed sometimes, as `setContentView` expects a `View` and not a `Ui[View]`: ```scala setContentView { - getUi { + Ui.get { w[TextView] <~ text("Hi") } } @@ -125,7 +123,7 @@ The `UiFuture` extension class provides `mapUi`, `flatMapUi` and other `xxxUi` m which allow to use UI actions as callbacks for `Future`s: ```scala - import macroid.UiThreading._ + import macroid._ import scala.concurrent.ExecutionContext.Implicits.global Future { @@ -167,5 +165,5 @@ For instructions refer to the [installation](../Installation.html) section. ## Low-level threading utilities -*Macroid* also provides `macroid.util.UiThreadExecutionContext`, which allows to run futures on the UI thread. +*Macroid* also provides `macroid.UiThreadExecutionContext`, which allows to run futures on the UI thread. You probably wouldn’t need to use this directly. diff --git a/macroid-docs/tutorial/BuildingLayouts.md b/macroid-docs/tutorial/BuildingLayouts.md index 7de653e..e4e6ae4 100644 --- a/macroid-docs/tutorial/BuildingLayouts.md +++ b/macroid-docs/tutorial/BuildingLayouts.md @@ -30,7 +30,8 @@ class GreetingActivity extends Activity with Contexts[Activity] { // the layout goes here setContentView { - getUi { + // + Ui.get { l[LinearLayout]( w[Button], w[TextView] diff --git a/macroid-docs/tutorial/CompleteCode.md b/macroid-docs/tutorial/CompleteCode.md index 012fa45..bc65dce 100644 --- a/macroid-docs/tutorial/CompleteCode.md +++ b/macroid-docs/tutorial/CompleteCode.md @@ -27,7 +27,7 @@ class GreetingActivity extends Activity with Contexts[Activity] { super.onCreate(savedInstanceState) setContentView { - getUi { + Ui.get { l[LinearLayout]( w[Button] <~ text("Click me") <~ diff --git a/macroid-viewable/src/main/scala/macroid/viewable/ListableListAdapter.scala b/macroid-viewable/src/main/scala/macroid/viewable/ListableListAdapter.scala index 09bad3d..1b76139 100644 --- a/macroid-viewable/src/main/scala/macroid/viewable/ListableListAdapter.scala +++ b/macroid-viewable/src/main/scala/macroid/viewable/ListableListAdapter.scala @@ -2,7 +2,6 @@ package macroid.viewable import android.view.{ View, ViewGroup } import android.widget.ArrayAdapter -import macroid.UiThreading._ import macroid.util.SafeCast import macroid.{ContextWrapper, Ui} @@ -19,7 +18,7 @@ class ListableListAdapter[A, W <: View](data: Seq[A])(implicit ctx: ContextWrapp super.getItemViewType(position) } - override def getView(position: Int, view: View, parent: ViewGroup): View = getUi { + override def getView(position: Int, view: View, parent: ViewGroup): View = Ui.get { val v = SafeCast[View, W](view).map(x ⇒ Ui(x)) .getOrElse(listable.makeView(getItemViewType(position))) listable.fillView(v, getItem(position)) diff --git a/macroid-viewable/src/main/scala/macroid/viewable/Viewable.scala b/macroid-viewable/src/main/scala/macroid/viewable/Viewable.scala index e07a61e..8f4ce62 100644 --- a/macroid-viewable/src/main/scala/macroid/viewable/Viewable.scala +++ b/macroid-viewable/src/main/scala/macroid/viewable/Viewable.scala @@ -2,9 +2,8 @@ package macroid.viewable import android.view.View import android.widget.TextView -import macroid.LayoutBuilding._ -import macroid.Tweaking._ import macroid._ +import macroid.LayoutBuilding._ import macroid.contrib.PagerTweaks import scala.annotation.implicitNotFound diff --git a/macroid-viewable/src/main/scala/macroid/viewable/ViewablePagerAdapter.scala b/macroid-viewable/src/main/scala/macroid/viewable/ViewablePagerAdapter.scala index cfb024d..0bf8d8c 100644 --- a/macroid-viewable/src/main/scala/macroid/viewable/ViewablePagerAdapter.scala +++ b/macroid-viewable/src/main/scala/macroid/viewable/ViewablePagerAdapter.scala @@ -3,12 +3,11 @@ package macroid.viewable import android.support.v4.view.PagerAdapter import android.view.{ ViewGroup, View } import macroid.ContextWrapper -import macroid.UiThreading._ /** A `PagerAdapter` based on the `Viewable` typeclass */ class ViewablePagerAdapter[A, +W <: View](data: Seq[A])(implicit ctx: ContextWrapper, viewable: Viewable[A, W]) extends PagerAdapter { override def instantiateItem(container: ViewGroup, position: Int) = { - val view = getUi(viewable.view(data(position))) + val view = viewable.view(data(position)).get container.addView(view, 0) view }