reactive-web: Lift をシンプルに

レシピ

ダブルクリック

あるイベントが短時間の間に連続して 2 回起こったかどうかを知りたい場合があります。よくある例として、GUI でのダブルクリックの検出があります。

    clicks.map(_ => System.currentTimeMillis)
      .foldLeft((0L,0L)) {
        case ((_, lastT), newT) => (lastT, newT)
      }.collect{ case (lastT, newT) if newT-lastT < 500 => () }
  

複数の異なるイベントタイプを持つ EventStream

X がイベントのタイプを表し、Y がイベントの値を表す EventStream[(X,Y)] 型のイベントを処理する場合に、それぞれのイベントのタイプの直前の状態をその後の計算のために保持しておく必要がある場合もあります。このような場合への対応は、冒頭のケースほど簡単ではありません。X の可能性がすべてわかっている場合には、filter を使用し、それぞれの可能性について、そのタイプのイベントだけを含むイベントストリームを派生させるのが最も簡単な解決方法です。もう 1 つの方法として、Map を使って、イベントのタイプごとに、直前の 2 つの値と時刻を保持するやり方があります。次に示すのは、後者の方法を使う例です。

  def lastValues[X, Y](x: EventStream[(X, Y)], defaultX: X) = x.foldLeft((Map.empty[X, (Y, Long)], Map.empty[X, (Y, Long)], defaultX)) {
    case ((mapBeforeLast, mapLastT, b), (a, v)) => (mapLastT, mapLastT updated(a, (v, System.currentTimeMillis)), a)
  }.collect {
    case ((mapLastT, mapNewT, a)) =>
      for (
        (x1, t1) <- mapNewT.get(a);
        (x0, t0) <- mapLastT.get(a)
      ) yield (ctl, (x1, t1), (x0, t0))
  }

  val x = new EventSource[(String,Double)]

  // ダブルクリック:
  val doubleClicks = lastValues(x, "").collect {
    case Some( (key, (x1, t1), (x0, t0)) ) if t1 - t0 < 500 => "double click from: "+key
  }

  doubleClicks.foreach(println)

  x.fire( ("mouse",2.0) )
  x.fire( ("mouse",4.0) )
  x.fire( ("trackpad",2.3) )
  x.fire( ("trackpad",5.3) )
  x.fire( ("joystick",5.3) )
  
  // 結果
  //double click from: mouse
  //double click from: trackpad