begin refactoring shell/glfw-gamepad.sml. (Most functions inside that file are pure now, and I think a 'modal' approach to the face buttons would be better/more ergonomic.)
This commit is contained in:
@@ -129,24 +129,22 @@ struct
|
||||
loop (0, DrawMailbox.getMessagesAndClear (), shellState)
|
||||
end
|
||||
|
||||
local
|
||||
fun updateLoop (pos, msgVec, app) =
|
||||
if pos = Vector.length msgVec then
|
||||
app
|
||||
else
|
||||
let
|
||||
val msg = Vector.sub (msgVec, pos)
|
||||
val app = Updater.update (app, msg)
|
||||
in
|
||||
updateLoop (pos + 1, msgVec, app)
|
||||
end
|
||||
in
|
||||
fun update app =
|
||||
updateLoop (0, InputMailbox.getMessagesAndClear (), app)
|
||||
end
|
||||
fun updateLoop (pos, msgVec, app) =
|
||||
if pos = Vector.length msgVec then
|
||||
app
|
||||
else
|
||||
let
|
||||
val msg = Vector.sub (msgVec, pos)
|
||||
val app = Updater.update (app, msg)
|
||||
in
|
||||
updateLoop (pos + 1, msgVec, app)
|
||||
end
|
||||
|
||||
fun helpLoop (app, shellState as {window, ...}: t) =
|
||||
case Glfw.windowShouldClose window of
|
||||
fun update app =
|
||||
updateLoop (0, InputMailbox.getMessagesAndClear (), app)
|
||||
|
||||
fun helpLoop (app, shellState: t, gamepad) =
|
||||
case Glfw.windowShouldClose (#window shellState) of
|
||||
false =>
|
||||
let
|
||||
val shellState = consumeDrawEvents shellState
|
||||
@@ -154,19 +152,34 @@ struct
|
||||
val _ = Gles3.clearColor (0.89, 0.89, 0.89, 1.0)
|
||||
val _ = Gles3.clear ()
|
||||
|
||||
val () = GlfwGamepad.query ()
|
||||
(* one update reacting to gamepad events *)
|
||||
val (gamepad, actions) = GlfwGamepad.query gamepad
|
||||
val app = updateLoop (0, Vector.fromList actions, app)
|
||||
|
||||
(* one update reacting to keyboard events *)
|
||||
val app = update app
|
||||
val _ = draw shellState
|
||||
|
||||
val _ = Glfw.swapBuffers window
|
||||
val _ = Glfw.swapBuffers (#window shellState)
|
||||
val _ = Glfw.waitEvents ()
|
||||
in
|
||||
helpLoop (app, shellState)
|
||||
helpLoop (app, shellState, gamepad)
|
||||
end
|
||||
| true => Glfw.terminate ()
|
||||
|
||||
fun loop (app, window) =
|
||||
let val shellState = create window
|
||||
in helpLoop (app, shellState)
|
||||
let
|
||||
val shellState = create window
|
||||
|
||||
val gamepad: GlfwGamepad.gamepad_state =
|
||||
{ mode = GlfwGamepad.PENDING
|
||||
, shiftChr = false
|
||||
, trianglePressed = false
|
||||
, circlePressed = false
|
||||
, crossPressed = false
|
||||
, squarePressed = false
|
||||
}
|
||||
in
|
||||
helpLoop (app, shellState, gamepad)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,33 +1,127 @@
|
||||
structure GlfwGamepad =
|
||||
struct
|
||||
datatype joystick_dir = UP | LEFT | DOWN | RIGHT | CENTRE | L2 | R2
|
||||
datatype mode =
|
||||
PENDING
|
||||
| TRIANGLE
|
||||
| TRIANGLE_CIRCLE
|
||||
| CIRCLE
|
||||
| CIRCLE_CROSS
|
||||
| CROSS
|
||||
| CROSS_SQUARE
|
||||
| SQUARE
|
||||
(* maybe SQUARE_TRIANGLE for numbers and symbols? *)
|
||||
| SQUARE_TRIANGLE
|
||||
|
||||
fun isInDeadZone (x, y) =
|
||||
x < 0.1 andalso x > ~0.1 andalso y < 0.1 andalso y > ~0.1
|
||||
structure IM = InputMsg
|
||||
|
||||
fun axisToDir (x, y, l2, r2) =
|
||||
if isInDeadZone (x, y) then
|
||||
(* analogue is in dead zone, so only query L2 and R2 *)
|
||||
if r2 < 0.3 andalso l2 < 0.3 then CENTRE
|
||||
else if abs r2 > abs l2 then R2
|
||||
else L2
|
||||
else
|
||||
type gamepad_state =
|
||||
{ mode: mode
|
||||
, shiftChr: bool
|
||||
, trianglePressed: bool
|
||||
, circlePressed: bool
|
||||
, crossPressed: bool
|
||||
, squarePressed: bool
|
||||
}
|
||||
|
||||
fun onPendingMode
|
||||
( state: gamepad_state
|
||||
, trianglePressed
|
||||
, circlePressed
|
||||
, crossPressed
|
||||
, squarePressed
|
||||
, actions: IM.t list
|
||||
) =
|
||||
if
|
||||
trianglePressed orelse circlePressed orelse crossPressed
|
||||
orelse squarePressed
|
||||
then
|
||||
(* some button is being pressed,
|
||||
* so we record that in the returned state,
|
||||
* in addition to whatever buttons were previously pressed *)
|
||||
let
|
||||
val ax = abs x
|
||||
val ay = abs y
|
||||
val al2 = abs l2
|
||||
val ar2 = abs r2
|
||||
val trianglePressed = #trianglePressed state orelse trianglePressed
|
||||
val circlePressed = #circlePressed state orelse circlePressed
|
||||
val crossPressed = #crossPressed state orelse crossPressed
|
||||
val squarePressed = #squarePressed state orelse squarePressed
|
||||
|
||||
val newState =
|
||||
{ mode = #mode state
|
||||
, shiftChr = #shiftChr state
|
||||
, trianglePressed = trianglePressed
|
||||
, circlePressed = circlePressed
|
||||
, crossPressed = crossPressed
|
||||
, squarePressed = squarePressed
|
||||
}
|
||||
in
|
||||
if ax > ay andalso ax > al2 andalso ax > ar2 then
|
||||
if x > 0.0 then RIGHT else LEFT
|
||||
else if ay > ax andalso ay > al2 andalso ay > ar2 then
|
||||
if y > 0.0 then DOWN else UP
|
||||
else if al2 > ax andalso al2 > ay andalso al2 > ar2 then
|
||||
L2
|
||||
else
|
||||
R2
|
||||
(newState, actions)
|
||||
end
|
||||
else
|
||||
(* nothing is currently pressed,
|
||||
* so we check if there is a valid mode indicated by the state
|
||||
* and change the mode if so *)
|
||||
let
|
||||
val
|
||||
{ trianglePressed
|
||||
, circlePressed
|
||||
, crossPressed
|
||||
, squarePressed
|
||||
, shiftChr
|
||||
, mode = _
|
||||
} = state
|
||||
val newMode =
|
||||
if trianglePressed andalso not (crossPressed orelse squarePressed) then
|
||||
if not circlePressed then TRIANGLE else TRIANGLE_CIRCLE
|
||||
else if circlePressed andalso not squarePressed then
|
||||
if not crossPressed then CIRCLE else CIRCLE_CROSS
|
||||
else if crossPressed then
|
||||
if not squarePressed then CROSS else CROSS_SQUARE
|
||||
else if squarePressed then
|
||||
if not trianglePressed then SQUARE else SQUARE_TRIANGLE
|
||||
else
|
||||
(* some buttons are being pressed,
|
||||
* but not a valid combination to switch
|
||||
* to another mode, so we are still on PENDING *)
|
||||
PENDING
|
||||
|
||||
val newState =
|
||||
{ mode = newMode
|
||||
, shiftChr = shiftChr
|
||||
, trianglePressed = false
|
||||
, circlePressed = false
|
||||
, crossPressed = false
|
||||
, squarePressed = false
|
||||
}
|
||||
in
|
||||
(newState, actions)
|
||||
end
|
||||
|
||||
fun handleButtons
|
||||
( gamepadState
|
||||
, trianglePressed
|
||||
, circlePressed
|
||||
, crossPressed
|
||||
, squarePressed
|
||||
, l1Pressed
|
||||
, r1Pressed
|
||||
) =
|
||||
let
|
||||
val actions = if l1Pressed then [IM.KEY_BACKSPACE] else []
|
||||
val actions =
|
||||
if r1Pressed then (IM.CHAR_EVENT #" ") :: actions else actions
|
||||
in
|
||||
case #mode gamepadState of
|
||||
PENDING =>
|
||||
onPendingMode
|
||||
( gamepadState
|
||||
, trianglePressed
|
||||
, circlePressed
|
||||
, crossPressed
|
||||
, squarePressed
|
||||
, actions
|
||||
)
|
||||
end
|
||||
|
||||
(* impure functions below *)
|
||||
fun getGamepadState () =
|
||||
Input.getGamepadState 0 <> 0 orelse Input.getGamepadState 1 <> 0
|
||||
orelse Input.getGamepadState 2 <> 0 orelse Input.getGamepadState 3 <> 0
|
||||
@@ -38,203 +132,27 @@ struct
|
||||
orelse Input.getGamepadState 12 <> 0 orelse Input.getGamepadState 13 <> 0
|
||||
orelse Input.getGamepadState 14 <> 0 orelse Input.getGamepadState 15 <> 0
|
||||
|
||||
local
|
||||
val state =
|
||||
{ crossPressed = ref false
|
||||
, circlePressed = ref false
|
||||
, squarePressed = ref false
|
||||
, trianglePressed = ref false
|
||||
, r1Pressed = ref false
|
||||
, l1Pressed = ref false
|
||||
, shiftChr = ref false
|
||||
}
|
||||
|
||||
fun appendChr chr =
|
||||
(InputMailbox.append (InputMsg.CHAR_EVENT chr); #shiftChr state := false)
|
||||
|
||||
fun handleTrianglePressed (x, y, l2, r2) =
|
||||
if !(#trianglePressed state) then
|
||||
()
|
||||
else
|
||||
let
|
||||
val () = #trianglePressed state := true
|
||||
val chr =
|
||||
if !(#shiftChr state) then
|
||||
case axisToDir (x, y, l2, r2) of
|
||||
CENTRE => #"A"
|
||||
| UP => #"E"
|
||||
| RIGHT => #"I"
|
||||
| DOWN => #"M"
|
||||
| LEFT => #"Q"
|
||||
| L2 => #"U"
|
||||
| R2 => #"Y"
|
||||
else
|
||||
case axisToDir (x, y, l2, r2) of
|
||||
CENTRE => #"a"
|
||||
| UP => #"e"
|
||||
| RIGHT => #"i"
|
||||
| DOWN => #"m"
|
||||
| LEFT => #"q"
|
||||
| L2 => #"u"
|
||||
| R2 => #"y"
|
||||
in
|
||||
appendChr chr
|
||||
end
|
||||
|
||||
fun handleCirclePressed (x, y, l2, r2) =
|
||||
if !(#circlePressed state) then
|
||||
()
|
||||
else
|
||||
let
|
||||
val () = #circlePressed state := true
|
||||
val chr =
|
||||
if !(#shiftChr state) then
|
||||
case axisToDir (x, y, l2, r2) of
|
||||
CENTRE => #"B"
|
||||
| UP => #"F"
|
||||
| RIGHT => #"J"
|
||||
| DOWN => #"N"
|
||||
| LEFT => #"R"
|
||||
| L2 => #"V"
|
||||
| R2 => #"Z"
|
||||
else
|
||||
case axisToDir (x, y, l2, r2) of
|
||||
CENTRE => #"b"
|
||||
| UP => #"f"
|
||||
| RIGHT => #"j"
|
||||
| DOWN => #"n"
|
||||
| LEFT => #"r"
|
||||
| L2 => #"v"
|
||||
| R2 => #"z"
|
||||
in
|
||||
appendChr chr
|
||||
end
|
||||
|
||||
fun handleCrossPressed (x, y, l2, r2) =
|
||||
if !(#crossPressed state) then
|
||||
()
|
||||
else
|
||||
let
|
||||
val () = #crossPressed state := true
|
||||
in
|
||||
if !(#shiftChr state) then
|
||||
case axisToDir (x, y, l2, r2) of
|
||||
CENTRE => appendChr #"U"
|
||||
| UP => appendChr #"U"
|
||||
| RIGHT => appendChr #"U"
|
||||
| DOWN => appendChr #"U"
|
||||
| LEFT => appendChr #"U"
|
||||
| L2 => appendChr #"U"
|
||||
| R2 => InputMailbox.append InputMsg.KEY_ENTER
|
||||
else
|
||||
case axisToDir (x, y, l2, r2) of
|
||||
CENTRE => appendChr #"c"
|
||||
| UP => appendChr #"g"
|
||||
| RIGHT => appendChr #"k"
|
||||
| DOWN => appendChr #"o"
|
||||
| LEFT => appendChr #"s"
|
||||
| L2 => appendChr #"w"
|
||||
| R2 => InputMailbox.append InputMsg.KEY_ENTER
|
||||
end
|
||||
|
||||
fun handleSquarePressed (x, y, l2, r2) =
|
||||
if !(#squarePressed state) then
|
||||
()
|
||||
else
|
||||
let
|
||||
val () = #squarePressed state := true
|
||||
in
|
||||
if !(#shiftChr state) then
|
||||
case axisToDir (x, y, l2, r2) of
|
||||
CENTRE => appendChr #"U"
|
||||
| UP => appendChr #"U"
|
||||
| RIGHT => appendChr #"U"
|
||||
| DOWN => appendChr #"U"
|
||||
| LEFT => appendChr #"U"
|
||||
| L2 => appendChr #"U"
|
||||
| R2 => #shiftChr state := true
|
||||
else
|
||||
case axisToDir (x, y, l2, r2) of
|
||||
CENTRE => appendChr #"d"
|
||||
| UP => appendChr #"h"
|
||||
| RIGHT => appendChr #"l"
|
||||
| DOWN => appendChr #"p"
|
||||
| LEFT => appendChr #"t"
|
||||
| L2 => appendChr #"x"
|
||||
| R2 => #shiftChr state := true
|
||||
end
|
||||
in
|
||||
fun handleIfJoystickIsPresent () =
|
||||
fun query gamepadState =
|
||||
if getGamepadState () then
|
||||
let
|
||||
open InputMsg
|
||||
|
||||
(* query possible events for information *)
|
||||
val xAxis = Input.getLeftJoystickXAxisState ()
|
||||
val yAxis = Input.getLeftJoystickYAxisState ()
|
||||
|
||||
val r2 = (Input.getR2State () + 1.0) / 2.0
|
||||
val l2 = (Input.getL2State () + 1.0) / 2.0
|
||||
|
||||
val crossPressed = Input.isCrossButtonPressed ()
|
||||
val circlePressed = Input.isCircleButtonPressed ()
|
||||
val squarePressed = Input.isSquareButtonPressed ()
|
||||
val trianglePressed = Input.isTriangleButtonPressed ()
|
||||
|
||||
val r1Pressed = Input.isR1ButtonPressed ()
|
||||
val l1Pressed = Input.isL1ButtonPressed ()
|
||||
|
||||
(* handle button events *)
|
||||
val () =
|
||||
if crossPressed = 0 then #crossPressed state := false
|
||||
else if !(#crossPressed state) then ()
|
||||
else handleCrossPressed (xAxis, yAxis, l2, r2)
|
||||
|
||||
val () =
|
||||
if circlePressed = 0 then #circlePressed state := false
|
||||
else if !(#circlePressed state) then ()
|
||||
else handleCirclePressed (xAxis, yAxis, l2, r2)
|
||||
|
||||
val () =
|
||||
if squarePressed = 0 then #squarePressed state := false
|
||||
else handleSquarePressed (xAxis, yAxis, l2, r2)
|
||||
|
||||
val () =
|
||||
if trianglePressed = 0 then #trianglePressed state := false
|
||||
else if !(#trianglePressed state) then ()
|
||||
else handleTrianglePressed (xAxis, yAxis, l2, r2)
|
||||
|
||||
val () =
|
||||
if r1Pressed = 0 then
|
||||
#r1Pressed state := false
|
||||
else if !(#r1Pressed state) then
|
||||
()
|
||||
else
|
||||
let val () = InputMailbox.append (InputMsg.CHAR_EVENT #" ")
|
||||
in #r1Pressed state := true
|
||||
end
|
||||
|
||||
val () =
|
||||
if l1Pressed = 0 then
|
||||
#l1Pressed state := false
|
||||
else if !(#l1Pressed state) then
|
||||
()
|
||||
else
|
||||
let val () = InputMailbox.append InputMsg.KEY_BACKSPACE
|
||||
in #l1Pressed state := true
|
||||
end
|
||||
|
||||
val () =
|
||||
(* if l2 and r2 are both pressed, then send escape event *)
|
||||
if l2 > 0.3 andalso r2 > 0.3 then
|
||||
InputMailbox.append InputMsg.KEY_ESC
|
||||
else
|
||||
()
|
||||
val trianglePressed = Input.isTriangleButtonPressed () <> 0
|
||||
val circlePressed = Input.isCircleButtonPressed () <> 0
|
||||
val crossPressed = Input.isCrossButtonPressed () <> 0
|
||||
val squarePressed = Input.isSquareButtonPressed () <> 0
|
||||
val l1Pressed = Input.isL1ButtonPressed () <> 0
|
||||
val r1Pressed = Input.isL1ButtonPressed () <> 0
|
||||
in
|
||||
()
|
||||
handleButtons
|
||||
( gamepadState
|
||||
, trianglePressed
|
||||
, circlePressed
|
||||
, crossPressed
|
||||
, squarePressed
|
||||
, l1Pressed
|
||||
, r1Pressed
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
fun query () =
|
||||
if getGamepadState () then handleIfJoystickIsPresent ()
|
||||
else (* nothing to do if no gamepad is present *) ()
|
||||
else
|
||||
(* nothing to do, so return same state *)
|
||||
(gamepadState, [])
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user