All Downloads are FREE. Search and download functionalities are using the official Maven repository.

iota.Combinators.scala Maven / Gradle / Ivy

There is a newer version: 2.0.0-RC3
Show newest version
package iota

import android.animation.Animator
import android.animation.Animator.AnimatorListener
import android.graphics.Color
import android.graphics.drawable.Drawable
import android.text.Html
import android.text.method.TransformationMethod
import android.view.{ViewPropertyAnimator, ViewGroup, View}
import android.widget.{ImageView, TextView}

import scala.concurrent.{ExecutionContext, Promise, Future}
import scala.reflect.internal.annotations.compileTimeOnly

/**
  * @author pfnguyen
  */

private[iota] trait Combinators {
  /** K-combinator, perform side-effects on A and automatically return A */
  def kestrel[A, B](f: A => B): Kestrel[A] = a => IO {
    f(a); a
  }

  private[iota] def noopK[A]: Kestrel[A] = kestrel(_ => ())
}

private[iota] trait FutureCombinators {
  /** K-combinators cannot be applied directly to `IO[Future[A]]` using `>>=`,
    * wrapping them in `defer()` makes this possible.
    */
  def defer[A](f: Kestrel[A])(implicit ec: ExecutionContext): Future[A] => IO[Future[A]] = future => IO {
    future.map(a => f(a).perform())
  }

  /** Any subsequent asynchronous operations that need to run (`>>=`) after an
    * `IO[Future[A]]` should be wrapped in `deferF()` to keep the proper
    * `IO[Future[A]]` type.
    */
  def deferF[A](f: A => IO[Future[A]])(implicit ec: ExecutionContext): Future[A] => IO[Future[A]] = future => IO {
    future.flatMap(a => f(a).perform())
  }
}

private[iota] trait ViewCombinators {
  def id[A <: View](id: Int): Kestrel[A] = macro IdMacros.tIdImpl[A]

  def padding[A <: View](left:   Int = 0,
                         top:    Int = 0,
                         right:  Int = 0,
                         bottom: Int = 0,
                         all: Int = -1): Kestrel[A] = kestrel { v =>
    if (all != -1) {
      v.setPadding(all, all, all, all)
    } else {
      v.setPadding(left, top, right, bottom)
    }
  }

  def animate[A <: View,B](animation: ViewPropertyAnimator => B, listener: Option[AnimatorListener] = None): A => IO[Future[A]] = view => IO {
    val animator = view.animate()
    animation(animator)
    val promise = Promise[A]()
    animator.setListener(new AnimatorListener {
      override def onAnimationEnd(animation: Animator) = {
        listener.foreach(_.onAnimationEnd(animation))
        animator.setListener(null)
        promise.success(view)
      }

      override def onAnimationRepeat(animation: Animator) =
        listener.foreach(_.onAnimationRepeat(animation))
      override def onAnimationStart(animation: Animator) =
        listener.foreach(_.onAnimationStart(animation))
      override def onAnimationCancel(animation: Animator) =
        listener.foreach(_.onAnimationCancel(animation))
    })
    animator.start()
    promise.future
  }

  import language.dynamics

  /** dynamic class for automatically setting event listeners. For example
    * `hook0.onClick(IO { perform work onClick })`, apply this to an
    * `IO[View]` using `>>=`
    */
  object hook0 extends Dynamic {
    def applyDynamic[V](event: String)(handler: IO[Any]): Kestrel[V] = macro HookMacro.applyHook0[V]
  }
  /** dynamic class for automatically setting event listeners. For example
    * `hookM0.onScroll.onScrollStateChanged(IO { perform work onClick })`, apply this to an
    * `IO[View]` using `>>=`
    */
  object hookM0 extends Dynamic {
    class _hookM0[V](val event: String) extends Dynamic {
      def applyDynamic(event: String)(handler: IO[Any]): Kestrel[V] = macro HookMacro.applyHookM0[V]
    }
    def selectDynamic[V](event: String): _hookM0[V] = new _hookM0[V](event)
  }
  /** dynamic class for automatically setting event listeners. For example
    * `hook.onClick((v: View) => IO { perform work onClick })`, apply this to an
    * `IO[View]` using `>>=`
    */
  object hook extends Dynamic {
    // must be Any because handler function may have arbitrary arity
    def applyDynamic[V](event: String)(handler: Any): Kestrel[V] = macro HookMacro.applyHook[V]
  }
  /** dynamic class for automatically setting event listeners. For example
    * `hookM.onScroll.onScrollStateChanged((rv: RecyclerView, state: Int) => IO { perform work onClick })`, apply this to an
    * `IO[View]` using `>>=`
    */
  object hookM extends Dynamic {
    class _hookM[V](val event: String) extends Dynamic {
      def applyDynamic(event: String)(handler: Any): Kestrel[V] = macro HookMacro.applyHookM[V]
    }
    def selectDynamic[V](event: String): _hookM[V] = new _hookM(event)
  }
}

private[iota] trait ViewCombinatorExtras {
  def visibility[A <: View](visibility: Int): Kestrel[A] =
    kestrel(_.setVisibility(visibility))
  def gone[A <: View]:      Kestrel[A] = visibility(View.GONE)
  def visible[A <: View]:   Kestrel[A] = visibility(View.VISIBLE)
  def invisible[A <: View]: Kestrel[A] = visibility(View.INVISIBLE)

  def enabled[A <: View](enable: Boolean): Kestrel[A] =
    kestrel(_.setEnabled(enable))
  def enabled[A <: View]:  Kestrel[A] = enabled(true)
  def disabled[A <: View]: Kestrel[A] = enabled(false)

  def elevation[A <: View](elevation: Float): Kestrel[A] = kestrel(_.setElevation(elevation))

  def clickable[A <: View](canclick: Boolean): Kestrel[A] = kestrel(_.setClickable(canclick))
  def clickable[A <: View]: Kestrel[A] = clickable[A](true)

  def backgroundDrawable[A <: View](d: Drawable): Kestrel[A] = kestrel(_.setBackgroundDrawable(d))
  def backgroundResource[A <: View](resid: Int): Kestrel[A] = kestrel(_.setBackgroundResource(resid))
  def backgroundColor[A <: View](color: Int):    Kestrel[A] = kestrel(_.setBackgroundColor(color))
  def backgroundColor[A <: View](color: String): Kestrel[A] = kestrel(_.setBackgroundColor(Color.parseColor(color)))
}

private[iota] trait LayoutCombinators {

  @inline
  /** set margins on a `LayoutParams`,
    *  e.g. `lpK(MATCH_PARENT, MATCH_PARENT)(margins(all = 4.dp))`
    */
  final def margins(left:   Int = 0,
              top:    Int = 0,
              right:  Int = 0,
              bottom: Int = 0,
              all: Int = -1): ViewGroup.MarginLayoutParams => Unit = lp => {
    if (all != -1) {
      lp.topMargin    = all
      lp.leftMargin   = all
      lp.rightMargin  = all
      lp.bottomMargin = all
    } else {
      lp.topMargin    = top
      lp.leftMargin   = left
      lp.rightMargin  = right
      lp.bottomMargin = bottom
    }
  }

  /** Set layout params for a view
    * `lp(MATCH_PARENT, MATCH_PARENT)`
    */
  @compileTimeOnly("lp can only be used from IO[_ <: ViewGroup].apply() or c[_ <: ViewGroup]()")
  final def lp[V <: View](args: Any*): Kestrel[V] = ???

  @compileTimeOnly("lpK can only be used from IO[_ <: ViewGroup].apply() or c[_ <: ViewGroup]()")
  /** K-combinator for LayoutParams, e.g.
    *  ```
    *  lpK(MATCH_PARENT, MATCH_PARENT) { p: FrameLayout.LayoutParams =>
    *    p.marginTop = 10.dp
    *  }
    *  ``` */
  final def lpK[V <: View,A,B](args: Any*)(k: A => B): Kestrel[V] = ???
}

private[iota] trait TextCombinators {
  def text[A <: TextView](text: CharSequence): Kestrel[A] = kestrel(_.setText(text))
  def text[A <: TextView](text: Int):          Kestrel[A] = kestrel(_.setText(text))

  def hint[A <: TextView](hint: CharSequence): Kestrel[A] = kestrel(_.setHint(hint))
  def hint[A <: TextView](hint: Int):          Kestrel[A] = kestrel(_.setHint(hint))

  def html[A <: TextView](html: String): Kestrel[A] = kestrel(_.setText(Html.fromHtml(html)))
  def hintColor[A <: TextView](color: Int): Kestrel[A] = kestrel(_.setHintTextColor(color))
  def textColor[A <: TextView](color: Int): Kestrel[A] = kestrel(_.setTextColor(color))
  def textTransformation[A <: TextView](transform: TransformationMethod): Kestrel[A] = kestrel(_.setTransformationMethod(transform))
  def inputType[A <: TextView](types: Int): Kestrel[A] = kestrel(_.setInputType(types))
  def textGravity[A <: TextView](gravity: Int): Kestrel[A] = kestrel(_.setGravity(gravity))
  def singleLine[A <: TextView](single: Boolean): Kestrel[A] = kestrel(_.setSingleLine(single))
  def singleLine[A <: TextView]: Kestrel[A] = singleLine[A](true)

  def textAppearance[A <: TextView](resid: Int): Kestrel[A] = kestrel(tv => tv.setTextAppearance(tv.getContext, resid))
}

private[iota] trait ImageCombinators {
  def imageResource[A <: ImageView](resid: Int): Kestrel[A] = kestrel(_.setImageResource(resid))
  def imageScale[A <: ImageView](scaleType: ImageView.ScaleType): Kestrel[A] = kestrel(_.setScaleType(scaleType))
}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy