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

scalacss.Compose.scala Maven / Gradle / Ivy

There is a newer version: 0.5.6
Show newest version
package scalacss

/**
 * Style composition logic.
 *
 * This is responsible for merges styles together, and producing warnings.
 * It has the power to perform merges on conflicting CSS values, and selectively ignore merges.
 */
final case class Compose(rules: Compose.Rules) {

  def apply(a: StyleS, b: StyleS): StyleS = {
    var warnings = a.warnings ++ b.warnings

    def absorbWarning[A](c: Cond, t: (A, Vector[WarningMsg])): A = {
      t._2.foreach { w => warnings :+= Warning(c, w) }
      t._1
    }

    def mergeAVs(c: Cond, newAVs: AVs, into: AVs): AVs = {

      // Remove exact matches
      var newData = newAVs.data
      for {(a, nvs) <- newData; ovs <- into.get(a)} {
        val o = ovs.toSet
        val newVals = nvs.whole.filterNot(o.contains)
        NonEmptyVector.maybe(newVals, newData -= a)(n => newData = newData.updated(a, n))
      }

      // Find conflicts
      newData.foldLeft(into) { case (results, (a, nvs)) =>
        results.filterKeys(k => a.cmp(k).conflict) match {
          case None =>
            // No conflicts - safe to add
            results.addAll(a, nvs)
          case Some(conflicts) =>
            // Handle conflict via rules
            val r = absorbWarning(c, rules.mergeAVs(conflicts, AVs(a, nvs)))
            val delete = conflicts.data.keySet -- r.order.whole
            results.filterKeys(!delete.contains(_)) match {
              case None    => r
              case Some(o) => r.data.foldLeft(o)((o, b) => o.modify(b._1, _ => b._2))
            }
        }
      }
    }

    val newData =
      b.data.foldLeft(a.data) { case (data, (cond, newAVs)) =>
        data.updated(cond,
          data.get(cond).fold(newAVs)(oldAVs =>
            mergeAVs(cond, newAVs, into = oldAVs)))}

    val exts = a.unsafeExts ++ b.unsafeExts

    val cns = a.addClassNames ++ b.addClassNames

    new StyleS(newData, exts, className = None, cns, warnings)
  }

  def apply[B](a: StyleS, b: StyleF[B]): StyleF[B] =
    b.mod(apply(a, _))

  def apply[B](a: StyleS, b: StyleF.P[B]): StyleF.P[B] =
    StyleF.P(d => apply(a, b(d)))

  def apply[A](a: StyleF[A], b: StyleS): StyleF[A] =
    a.mod(apply(_, b))

  def apply[A, B](a: StyleF[A], b: StyleF[B]): StyleF[(A, B)] =
    new StyleF[(A, B)](i => apply(a f i._1, b f i._2), a.domain *** b.domain)

  def apply[A, B](a: StyleF[A], b: StyleF.P[B]): Domain[B] => StyleF[(A, B)] =
    d => apply(a, b(d))

  def apply[A](a: StyleF.P[A], b: StyleS): StyleF.P[A] =
    StyleF.P(d => apply(a(d), b))

  def apply[A, B](a: StyleF.P[A], b: StyleF[B]): Domain[A] => StyleF[(A, B)] =
    d => apply(a(d), b)

  def apply[A, B](a: StyleF.P[A], b: StyleF.P[B]): (Domain[A], Domain[B]) => StyleF[(A, B)] =
    (da, db) => apply(a(da), b(db))
}

object Compose {

  val safe: Compose =
    new Compose(Rules(Rules.append, Rules.warn))

  val trust: Compose =
    new Compose(Rules(Rules.append, Rules.silent))

  trait Rules {
    def mergeAVs(lo: AVs, hi: AVs): (AVs, Vector[WarningMsg])
  }

  object Rules {
    type MergeRule = (AVs, AVs) => AVs
    type WarnRule  = (AVs, AVs) => Vector[WarningMsg]

    def apply(m: MergeRule, w: WarnRule): Rules =
      new Rules {
        override def mergeAVs(lo: AVs, hi: AVs): (AVs, Vector[WarningMsg]) =
          (m(lo, hi), w(lo, hi))
      }

    val append: MergeRule =
      (lo, hi) => lo ++ hi

    val replace: MergeRule =
      (_, hi) => hi

    val silent: WarnRule =
      (_, _) => Vector.empty

    val warn: WarnRule =
      (lo, hi) => {
        def showA(a: Attr): String = a.id
        def showV(vs: NonEmptyVector[Value]): String =
          if (vs.tail.isEmpty) vs.head else vs.whole.mkString("[", "; ", "]")
        def showAVt(t: (Attr, NonEmptyVector[Value])) =
          showAV(t._1, t._2)
        def showAV(a: Attr, vs: NonEmptyVector[Value]): String =
//          s"(${showA(a)}: ${showV(vs)})"
          s"${showA(a)}: ${showV(vs)}"
        def showAVs(avs: AVs): String = {
          val s = avs.toStream.map(showAVt)
//          if (s.lengthCompare(1) == 0) s.head else s.mkString("{ ", ", ", " }")
          s.mkString("{", ", ", "}")
        }
        hi.toStream
          .map(t => s"{${showAVt(t)}} conflicts with ${showAVs(lo)}")
          .toVector
      }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy