Elm 中的键盘信号和 foldp

Keyboard signals and foldp in Elm

我正在尝试创建一个文本字段,该字段通过按 Enter 键提交输入并被 Escape 清除。

import Graphics.Element exposing (flow, down, show)
import Signal exposing ((<~), (~))
import Graphics.Input.Field exposing (noContent, defaultStyle, field)
import String
import Keyboard

main = Signal.map view (Signal.foldp step init signals)

signals = (,,) <~ box.signal
                ~ Signal.sampleOn Keyboard.enter box.signal
                ~ escape

escape = Keyboard.isDown 27

box = Signal.mailbox noContent

init = (noContent, noContent, False)

view (text, query, clear) =
    flow down
        [ field defaultStyle (Signal.message box.address) "Enter text" text
        , show (String.words query.string)
        ]

step (text, query, clear) _ =
    case clear of
        False -> (text, query, clear)
        True -> (noContent, noContent, clear)

这只会在按住 Escape 时产生一个空字段,并恢复为释放 Escape 时输入的内容。

为了理解为什么会这样,我举了一个更小的例子:

import Graphics.Element exposing (show)
import Signal
import Char
import String
import Keyboard

main = Signal.map show input

input = Signal.foldp step "" (Signal.map2 (,) Keyboard.presses escapeDown)

escapeDown = Keyboard.isDown 27

step (keyCode, esc) string =
    case esc of
        True -> ""
        False -> string ++ keyToString keyCode

keyToString = String.fromChar << Char.fromCode

累积的字符串在按下 Escape 时清空,但释放它会产生一个字符串(最后输入的)。

据我了解,Keyboard.isDown 信号在按住和释放键时触发。那么如何才能持久清场呢?

您看到此行为的原因

当您从两个信号 (Signal KeyCode/Signal Bool) 中创建成对信号 (Signal (KeyCode,Bool)) 时,该成对信号将在每次信号更新时更新.所以 Signal.map2 (,) Keyboard.presses escapeDown 随着时间的推移的值可能是:

(0,False), (97,False), (98,False), (98,True), (98,False)
^          ^ press 'a' ^ press 'b'  ^          ^ release escape
program start                       press escape

当您按下转义键时,这对值会发生变化,并且您的 foldp 会更新为空字符串。当您释放转义符时,这对值再次更改,因此 foldp 再次更新,找到 False 值,因此将您按下的最后一个字符附加到空字符串。

解决方案

在这种情况下,您真正​​感兴趣的是按下转义键时的事件,而不是 isDown 时的事件。在这种情况下,最好是 merge 个信号,而不是创建一对信号。为此,它们需要属于同一类型。这是一个例子:

import Graphics.Element exposing (show)
import Signal exposing ((<~))
import Char
import String
import Keyboard

type Input = KeyPress Keyboard.KeyCode | EscapePress

main = Signal.map show output

presses = KeyPress <~ Signal.filter ((/=) 27) 0 Keyboard.presses
escapePress = always EscapePress <~ escapeDown
input = Signal.merge escapePress presses

output = Signal.foldp step "" input

escapeDown = Keyboard.isDown 27

step input string =
    case input of
        EscapePress -> ""
        KeyPress keyCode -> string ++ keyToString keyCode

keyToString = String.fromChar << Char.fromCode

使用联合类型 Input,我们表示程序的不同输入。压力机被包装在联合类型的 KeyPress 构造函数中。退出按钮由另一个构造函数 EscapePress 表示。现在你有两个相同类型的信号,你可以merge。在您的 step 函数中,您对 Input 的构造函数进行模式匹配,并处理熟悉的情况。
请注意,我正在过滤 Keyboard.presses 信号,因此您不会因按下、按住或松开转义键而收到 KeyPress 事件。