From 0679f4154e2033ef8496a0b1304990117c5779b1 Mon Sep 17 00:00:00 2001 From: Humza Shahid Date: Wed, 21 Jan 2026 20:38:13 +0000 Subject: [PATCH] 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.) --- shell/gl-draw.sml | 57 ++++--- shell/glfw-gamepad.sml | 354 ++++++++++++++++------------------------- 2 files changed, 171 insertions(+), 240 deletions(-) diff --git a/shell/gl-draw.sml b/shell/gl-draw.sml index 3d6565f..47c17f6 100644 --- a/shell/gl-draw.sml +++ b/shell/gl-draw.sml @@ -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 diff --git a/shell/glfw-gamepad.sml b/shell/glfw-gamepad.sml index a4f96bf..b44d2ee 100644 --- a/shell/glfw-gamepad.sml +++ b/shell/glfw-gamepad.sml @@ -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