Keyboard signals and foldp in Elm - elm

I'm trying to make a text field that submits its input by pressing Enter and is cleared by 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)
This produces an empty field only while holding Escape and reverts to whatever was entered when Escape is released.
Trying to understand why this is the case lead me to a smaller example:
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
The accumulated string of characters is emptied on pressing Escape but releasing it results in a string of a single (last entered) character.
From what I understand, Keyboard.isDown signal is triggered while holding and on key release. So how can I clear the field persistently?

The reason you see this behaviour
When you create a signal of pairs (Signal (KeyCode,Bool)) out of two signals (Signal KeyCode/Signal Bool), that signal of pairs will update every time of of the signals update. So the value of Signal.map2 (,) Keyboard.presses escapeDown over time might be:
(0,False), (97,False), (98,False), (98,True), (98,False)
^ ^ press 'a' ^ press 'b' ^ ^ release escape
program start press escape
When you press escape, the pair of values changes and your foldp updates to the empty string. When you release escape the pair of values changes again, so foldp updates again, finds the False value, therefore appends the last character you pressed to the empty string.
Solution
In this case you're really interested in the event when escape is pressed, but not when it isDown. Instead of creating a pair of the signals, it is in this case better to merge the signals. To do so they need to be of the same type. Here's an example:
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
With a union type Input, we represent the different inputs to the program. The presses are wrapped in the KeyPress constructor of the union type. The escape button is represented with the other constructor EscapePress. Now you have two signals of the same type, which you can merge. In your step function you pattern match on the constructors of your Input, and handle the familiar cases.
Note that I'm filtering the Keyboard.presses signal so you don't get a KeyPress event from pressing down, holding down or letting go of the escape key.

Related

How to read keyboard inputs at every keystroke in julia?

I tried with, c::Char = read(stdin, Char);
It reads character(s) from keyboard only after hitting enter but not upon every keydown/release.
Please guide me in reading keyboard input upon key press or release!
Update 1:
function quit()
print("Press q to quit!");
opt = getc1();
while true
if opt = 'q'
break;
else
continue;
end
end
end
throws error:
TypeError:non-boolean(Int64) used in boolean context.
Please help me!
This is not that simple.
You can try this more low-level solution:
function getc1()
ret = ccall(:jl_tty_set_mode, Int32, (Ptr{Cvoid},Int32), stdin.handle, true)
ret == 0 || error("unable to switch to raw mode")
c = read(stdin, Char)
ccall(:jl_tty_set_mode, Int32, (Ptr{Cvoid},Int32), stdin.handle, false)
c
end
or this more higher level one:
function getc2()
t = REPL.TerminalMenus.terminal
REPL.TerminalMenus.enableRawMode(t) || error("unable to switch to raw mode")
c = Char(REPL.TerminalMenus.readKey(t.in_stream))
REPL.TerminalMenus.disableRawMode(t)
c
end
depending on what you need (or write yet another implementation using the ideas here). The key challenge is that "normal keys", like ASCII are always processed correctly. However, the solutions differ in the way how they handle characters like 'ą' (some character that is larger UNICODE) or UP_ARROW (when you press arrow up on the keyboard) - you here have to experiment and decide what you want (or maybe it is enough for you to read UInt8 values one by one and manually reconstruct what you want?).
EDIT
The problem is with your quit function. Here is how it should be defined:
function quit()
print("Press q to quit!");
while true
opt = getc1();
if opt == 'q'
break
else
continue
end
end
end
The following example may be helpful:
import REPL
function wait_for_key( ;
io_in::IO = stdin,
io_out::IO = stdout,
prompt::String = "press any key [d]raw [n]odraw [q]uit : ",
)
print(io_out, prompt)
t = REPL.TerminalMenus.terminal
REPL.Terminals.raw!(t, true)
char = read(io_in, Char)
REPL.Terminals.raw!(t, false)
write(io_out, char)
write(io_out, "\n")
return char
end

Elm: String.toFloat doesn't work with comma only with point - what to do?

I'm very new to elm and i want to do a simple mileage counter app.
If i get "1.2" (POINT) form input - String.toFloat returns in the OK branch with 1.2 as a number.
But if i get "1,2" (COMMA) form input, then String.toFloat returns in the Err branch with "You can't have words, only numbers!"
This pretty much works like a real time validator.
The code:
TypingInInput val ->
case String.toFloat val of
Ok success ->
{ model | inputValue = val, errorMessage = Nothing }
Err err ->
{ model | inputValue = val, errorMessage = Just "You can't have words, or spaces, only numbers!" }
.
Question: So how can i force String.toFloat of "1,2" to give me 1.2 the number?
Unfortunately the source for toFloat is hardcoded to only respect a dot as decimal separator. You can replace the comma with a dot in the string prior to passing it to toFloat as a workaround.
String.Extra.replace can be used for the simple string replacement.
The implementation of String.toFloat only supports a dot as a separator.
You should replace commas first before parsing the Float
Please see the example:
import Html exposing (text)
import String
import Regex
main =
"1,2"
|> Regex.replace Regex.All (Regex.regex ",") (\_ -> ".")
|> String.toFloat
|> toString
|> text -- 1.2
In JavaScript parseFloat doesn't support comma separator either.

Count the time from my last arrow key press

I was explained before on the forum how to program an autohotkey script to pause the script execution until alpha-numerical character has been entered, by using an input command:
Input, L, V L1 T2 ;wait until you start typing a letter and then proceed after the T2 pause
If (ErrorLevel = "Timeout") {
Send, {Tab 5}
Send, {Enter}
Return
}
I wonder if I can use Input or some other autohotkey command to do the same for non-alphanumerical keys, like arrow keys, or even key combinations. For example, I'd like for the script to count the time from my last arrow key press when I'm selecting an item from a drop-down list, and when the pre-set time threshold is passed to continue with the execution of the script.
EDIT:
Thanks to Blauhirn, the script works now the up and down keys are added :) (as long as the first key is typed in within the pre-set time period of two seconds in this example, notepad will be launched as soon as a pause of one second in typing is made)
!^+u::
Input, L, V L1 T2, {up} {down}
If (ErrorLevel = "Timeout") {
Send, {Esc}
Return
}
Loop { ;wait until you haven't typed an arrow key for 1seconds
Input, L, V L1 T1, {up} {down}
If (ErrorLevel = "Timeout")
Break
}
Run, %windir%\system32\notepad.exe
Return
If you state some EndKeys, the input will also terminate and ErrorLevel will be EndKey:name, thus not "Timeout".
Input, L, V L1 T2, {up}{down} ;wait until you start typing a letter -OR UP ARROW OR DOWN ARROW - and then proceed after the T2 pause
If (ErrorLevel = "Timeout") {
Send, {Tab 5}
Send, {Enter}
Return
}

Display Signal Value with Other Text

I'm just starting to learn Elm. In this program I would like to update the screen with the mouse coordinates and arrow key state formatted in some output.
My plan was to create a record called Input and have that set with the Signals by function input. Then showGameInputs would use the Input record to get the values and combine them with some text to return an Element to main.
import Mouse
import Keyboard
-- Create a record named Input
type Input = { mouseX:Int, mouseY:Int, arrowUpdown:Int, arrowLeftRight:Int }
-- Combine Signals into Input type
input: Signal Input
input = Input <~ Mouse.x ~ Mouse.y ~ lift .y Keyboard.arrows ~ lift .x Keyboard.arrows
showGameInputs: Input -> Element
showGameInputs { mouseX, mouseY, arrowUpdown, arrowLeftRight } = plainText ("asdf" ++ show mouseX)
main: Signal Element
main = showGameInputs input
Here is the error:
[1 of 1] Compiling Main ( Functions.elm )
Type error on line 19, column 23 to 28:
input
Expected Type: Signal Input
Actual Type: Input
Very new to Elm and functional programming so I suspect I am missing something fundamental here.
Thanks for any help.
You're almost there. The error message is a bit confusingly formatted (a known problem), but it says the type that input should actually be is Input. The reason it says that is because you're applying showGameInputs: Input -> Element on input: Signal Input. All you need is to change main to:
main = showGameInputs <~ input

Detect a double key press in AutoHotkey

I'd like to trigger an event in AutoHotkey when the user double "presses" the esc key. But let the escape keystroke go through to the app in focus if it's not a double press (say within the space of a second).
How would I go about doing this?
I've come up with this so far, but I can't work out how to check for the second escape key press:
~Esc::
Input, TextEntry1, L1 T1
endKey=%ErrorLevel%
if( endKey != "Timeout" )
{
; perform my double press operation
WinMinimize, A
}
return
Found the answer in the AutoHotkey documentation!
; Example #4: Detects when a key has been double-pressed (similar to double-click).
; KeyWait is used to stop the keyboard's auto-repeat feature from creating an unwanted
; double-press when you hold down the RControl key to modify another key. It does this by
; keeping the hotkey's thread running, which blocks the auto-repeats by relying upon
; #MaxThreadsPerHotkey being at its default setting of 1.
; Note: There is a more elaborate script to distinguish between single, double, and
; triple-presses at the bottom of the SetTimer page.
~RControl::
if (A_PriorHotkey <> "~RControl" or A_TimeSincePriorHotkey > 400)
{
; Too much time between presses, so this isn't a double-press.
KeyWait, RControl
return
}
MsgBox You double-pressed the right control key.
return
So for my case:
~Esc::
if (A_PriorHotkey <> "~Esc" or A_TimeSincePriorHotkey > 400)
{
; Too much time between presses, so this isn't a double-press.
KeyWait, Esc
return
}
WinMinimize, A
return
With the script above, i found out that the button i wanted to detect was being forwared to the program (i.e. the "~" prefix).
This seems to do the trick for me (i wanted to detect a double "d" press)
d::
keywait,d
keywait,d,d t0.5 ; Increase the "t" value for a longer timeout.
if errorlevel
{
; pretend that nothing happened and forward the single "d"
Send d
return
}
; A double "d" has been detected, act accordingly.
Send {Del}
return
Source