diff --git a/Source/Android/app/src/main/assets/GCPadNew.ini b/Source/Android/app/src/main/assets/GCPadNew.ini deleted file mode 100644 index 2bf8ffdac3..0000000000 --- a/Source/Android/app/src/main/assets/GCPadNew.ini +++ /dev/null @@ -1,104 +0,0 @@ -[GCPad1] -Device = Android/0/Touchscreen -Buttons/A = `Button 0` -Buttons/B = `Button 1` -Buttons/Start = `Button 2` -Buttons/X = `Button 3` -Buttons/Y = `Button 4` -Buttons/Z = `Button 5` -D-Pad/Up = `Button 6` -D-Pad/Down = `Button 7` -D-Pad/Left = `Button 8` -D-Pad/Right = `Button 9` -Main Stick/Up = `Axis 11` -Main Stick/Down = `Axis 12` -Main Stick/Left = `Axis 13` -Main Stick/Right = `Axis 14` -C-Stick/Up = `Axis 16` -C-Stick/Down = `Axis 17` -C-Stick/Left = `Axis 18` -C-Stick/Right = `Axis 19` -Triggers/L = `Axis 20` -Triggers/R = `Axis 21` -Triggers/L-Analog = `Axis 20` -Triggers/R-Analog = `Axis 21` -Triggers/Threshold = 90,000000 -Rumble/Motor = `Rumble 700` -[GCPad2] -Device = Android/1/Touchscreen -Buttons/A = `Button 0` -Buttons/B = `Button 1` -Buttons/Start = `Button 2` -Buttons/X = `Button 3` -Buttons/Y = `Button 4` -Buttons/Z = `Button 5` -D-Pad/Up = `Button 6` -D-Pad/Down = `Button 7` -D-Pad/Left = `Button 8` -D-Pad/Right = `Button 9` -Main Stick/Up = `Axis 11` -Main Stick/Down = `Axis 12` -Main Stick/Left = `Axis 13` -Main Stick/Right = `Axis 14` -C-Stick/Up = `Axis 16` -C-Stick/Down = `Axis 17` -C-Stick/Left = `Axis 18` -C-Stick/Right = `Axis 19` -Triggers/L = `Axis 20` -Triggers/R = `Axis 21` -Triggers/L-Analog = `Axis 20` -Triggers/R-Analog = `Axis 21` -Triggers/Threshold = 90,000000 -Rumble/Motor = `Rumble 700` -[GCPad3] -Device = Android/2/Touchscreen -Buttons/A = `Button 0` -Buttons/B = `Button 1` -Buttons/Start = `Button 2` -Buttons/X = `Button 3` -Buttons/Y = `Button 4` -Buttons/Z = `Button 5` -D-Pad/Up = `Button 6` -D-Pad/Down = `Button 7` -D-Pad/Left = `Button 8` -D-Pad/Right = `Button 9` -Main Stick/Up = `Axis 11` -Main Stick/Down = `Axis 12` -Main Stick/Left = `Axis 13` -Main Stick/Right = `Axis 14` -C-Stick/Up = `Axis 16` -C-Stick/Down = `Axis 17` -C-Stick/Left = `Axis 18` -C-Stick/Right = `Axis 19` -Triggers/L = `Axis 20` -Triggers/R = `Axis 21` -Triggers/L-Analog = `Axis 20` -Triggers/R-Analog = `Axis 21` -Triggers/Threshold = 90,000000 -Rumble/Motor = `Rumble 700` -[GCPad4] -Device = Android/3/Touchscreen -Buttons/A = `Button 0` -Buttons/B = `Button 1` -Buttons/Start = `Button 2` -Buttons/X = `Button 3` -Buttons/Y = `Button 4` -Buttons/Z = `Button 5` -D-Pad/Up = `Button 6` -D-Pad/Down = `Button 7` -D-Pad/Left = `Button 8` -D-Pad/Right = `Button 9` -Main Stick/Up = `Axis 11` -Main Stick/Down = `Axis 12` -Main Stick/Left = `Axis 13` -Main Stick/Right = `Axis 14` -C-Stick/Up = `Axis 16` -C-Stick/Down = `Axis 17` -C-Stick/Left = `Axis 18` -C-Stick/Right = `Axis 19` -Triggers/L = `Axis 20` -Triggers/R = `Axis 21` -Triggers/L-Analog = `Axis 20` -Triggers/R-Analog = `Axis 21` -Triggers/Threshold = 90,000000 -Rumble/Motor = `Rumble 700` diff --git a/Source/Android/app/src/main/assets/WiimoteNew.ini b/Source/Android/app/src/main/assets/WiimoteNew.ini deleted file mode 100644 index 7237db7a3d..0000000000 --- a/Source/Android/app/src/main/assets/WiimoteNew.ini +++ /dev/null @@ -1,580 +0,0 @@ -[Wiimote1] -Device = Android/4/Touchscreen -Buttons/A = `Button 100` -Buttons/B = `Button 101` -Buttons/- = `Button 102` -Buttons/+ = `Button 103` -Buttons/Home = `Button 104` -Buttons/1 = `Button 105` -Buttons/2 = `Button 106` -D-Pad/Up = `Button 107` -D-Pad/Down = `Button 108` -D-Pad/Left = `Button 109` -D-Pad/Right = `Button 110` -IR/Up = `Axis 112` -IR/Down = `Axis 113` -IR/Left = `Axis 114` -IR/Right = `Axis 115` -IR/Forward = `Axis 116` -IR/Backward = `Axis 117` -IR/Hide = `Button 118` -IR/Total Pitch = 20 -IR/Total Yaw = 25 -IR/Vertical Offset = 10 -Swing/Up = `Axis 120` -Swing/Down = `Axis 121` -Swing/Left = `Axis 122` -Swing/Right = `Axis 123` -Swing/Forward = `Axis 124` -Swing/Backward = `Axis 125` -Tilt/Forward = `Axis 127` -Tilt/Backward = `Axis 128` -Tilt/Left = `Axis 129` -Tilt/Right = `Axis 130` -Tilt/Modifier = `Button 131` -Tilt/Modifier/Range = 50,000000 -Shake/X = `Button 132` -Shake/Y = `Button 133` -Shake/Z = `Button 134` -Extension = Nunchuk -Nunchuk/Buttons/C = `Button 200` -Nunchuk/Buttons/Z = `Button 201` -Nunchuk/Stick/Up = `Axis 203` -Nunchuk/Stick/Down = `Axis 204` -Nunchuk/Stick/Left = `Axis 205` -Nunchuk/Stick/Right = `Axis 206` -Nunchuk/Swing/Up = `Axis 208` -Nunchuk/Swing/Down = `Axis 209` -Nunchuk/Swing/Left = `Axis 210` -Nunchuk/Swing/Right = `Axis 211` -Nunchuk/Swing/Forward = `Axis 212` -Nunchuk/Swing/Backward = `Axis 213` -Nunchuk/Tilt/Forward = `Axis 215` -Nunchuk/Tilt/Backward = `Axis 216` -Nunchuk/Tilt/Left = `Axis 217` -Nunchuk/Tilt/Right = `Axis 218` -Nunchuk/Tilt/Modifier = `Button 219` -Nunchuk/Tilt/Modifier/Range = 50,000000 -Nunchuk/Shake/X = `Button 220` -Nunchuk/Shake/Y = `Button 221` -Nunchuk/Shake/Z = `Button 222` -Classic/Buttons/A = `Button 300` -Classic/Buttons/B = `Button 301` -Classic/Buttons/X = `Button 302` -Classic/Buttons/Y = `Button 303` -Classic/Buttons/- = `Button 304` -Classic/Buttons/+ = `Button 305` -Classic/Buttons/Home = `Button 306` -Classic/Buttons/ZL = `Button 307` -Classic/Buttons/ZR = `Button 308` -Classic/D-Pad/Up = `Button 309` -Classic/D-Pad/Down = `Button 310` -Classic/D-Pad/Left = `Button 311` -Classic/D-Pad/Right = `Button 312` -Classic/Left Stick/Up = `Axis 314` -Classic/Left Stick/Down = `Axis 315` -Classic/Left Stick/Left = `Axis 316` -Classic/Left Stick/Right = `Axis 317` -Classic/Right Stick/Up = `Axis 319` -Classic/Right Stick/Down = `Axis 320` -Classic/Right Stick/Left = `Axis 321` -Classic/Right Stick/Right = `Axis 322` -Classic/Triggers/L = `Axis 323` -Classic/Triggers/R = `Axis 324` -Classic/Triggers/Threshold = 90,000000 -Guitar/Buttons/- = `Button 400` -Guitar/Buttons/+ = `Button 401` -Guitar/Frets/Green = `Button 402` -Guitar/Frets/Red = `Button 403` -Guitar/Frets/Yellow = `Button 404` -Guitar/Frets/Blue = `Button 405` -Guitar/Frets/Orange = `Button 406` -Guitar/Strum/Up = `Button 407` -Guitar/Strum/Down = `Button 408` -Guitar/Stick/Up = `Axis 410` -Guitar/Stick/Down = `Axis 411` -Guitar/Stick/Left = `Axis 412` -Guitar/Stick/Right = `Axis 413` -Guitar/Whammy/Bar = `Axis 414` -Drums/Buttons/- = `Button 500` -Drums/Buttons/+ = `Button 501` -Drums/Pads/Red = `Button 502` -Drums/Pads/Yellow = `Button 503` -Drums/Pads/Blue = `Button 504` -Drums/Pads/Green = `Button 505` -Drums/Pads/Orange = `Button 506` -Drums/Pads/Bass = `Button 507` -Drums/Stick/Up = `Axis 509` -Drums/Stick/Down = `Axis 510` -Drums/Stick/Left = `Axis 511` -Drums/Stick/Right = `Axis 512` -Turntable/Buttons/Green Left = `Button 600` -Turntable/Buttons/Red Left = `Button 601` -Turntable/Buttons/Blue Left = `Button 602` -Turntable/Buttons/Green Right = `Button 603` -Turntable/Buttons/Red Right = `Button 604` -Turntable/Buttons/Blue Right = `Button 605` -Turntable/Buttons/- = `Button 606` -Turntable/Buttons/+ = `Button 607` -Turntable/Buttons/Home = `Button 608` -Turntable/Buttons/Euphoria = `Button 609` -Turntable/Table Left/Left = `Axis 611` -Turntable/Table Left/Right = `Axis 612` -Turntable/Table Right/Left = `Axis 614` -Turntable/Table Right/Right = `Axis 615` -Turntable/Stick/Up = `Axis 617` -Turntable/Stick/Down = `Axis 618` -Turntable/Stick/Left = `Axis 619` -Turntable/Stick/Right = `Axis 620` -Turntable/Effect/Dial = `Axis 621` -Turntable/Crossfade/Left = `Axis 623` -Turntable/Crossfade/Right = `Axis 624` -IMUAccelerometer/Left = `Axis 625` -IMUAccelerometer/Right = `Axis 626` -IMUAccelerometer/Forward = `Axis 627` -IMUAccelerometer/Backward = `Axis 628` -IMUAccelerometer/Up = `Axis 629` -IMUAccelerometer/Down = `Axis 630` -IMUGyroscope/Pitch Up = `Axis 631` -IMUGyroscope/Pitch Down = `Axis 632` -IMUGyroscope/Roll Left = `Axis 633` -IMUGyroscope/Roll Right = `Axis 634` -IMUGyroscope/Yaw Left = `Axis 635` -IMUGyroscope/Yaw Right = `Axis 636` -Source = 1 -Rumble/Motor = `Rumble 700` -[Wiimote2] -Device = Android/5/Touchscreen -Buttons/A = `Button 100` -Buttons/B = `Button 101` -Buttons/- = `Button 102` -Buttons/+ = `Button 103` -Buttons/Home = `Button 104` -Buttons/1 = `Button 105` -Buttons/2 = `Button 106` -D-Pad/Up = `Button 107` -D-Pad/Down = `Button 108` -D-Pad/Left = `Button 109` -D-Pad/Right = `Button 110` -IR/Up = `Axis 112` -IR/Down = `Axis 113` -IR/Left = `Axis 114` -IR/Right = `Axis 115` -IR/Forward = `Axis 116` -IR/Backward = `Axis 117` -IR/Hide = `Button 118` -IR/Total Pitch = 15 -IR/Total Yaw = 15 -IR/Vertical Offset = 10 -Swing/Up = `Axis 120` -Swing/Down = `Axis 121` -Swing/Left = `Axis 122` -Swing/Right = `Axis 123` -Swing/Forward = `Axis 124` -Swing/Backward = `Axis 125` -Tilt/Forward = `Axis 127` -Tilt/Backward = `Axis 128` -Tilt/Left = `Axis 129` -Tilt/Right = `Axis 130` -Tilt/Modifier = `Button 131` -Tilt/Modifier/Range = 50,000000 -Shake/X = `Button 132` -Shake/Y = `Button 133` -Shake/Z = `Button 134` -Extension = None -Nunchuk/Buttons/C = `Button 200` -Nunchuk/Buttons/Z = `Button 201` -Nunchuk/Stick/Up = `Axis 203` -Nunchuk/Stick/Down = `Axis 204` -Nunchuk/Stick/Left = `Axis 205` -Nunchuk/Stick/Right = `Axis 206` -Nunchuk/Swing/Up = `Axis 208` -Nunchuk/Swing/Down = `Axis 209` -Nunchuk/Swing/Left = `Axis 210` -Nunchuk/Swing/Right = `Axis 211` -Nunchuk/Swing/Forward = `Axis 212` -Nunchuk/Swing/Backward = `Axis 213` -Nunchuk/Tilt/Forward = `Axis 215` -Nunchuk/Tilt/Backward = `Axis 216` -Nunchuk/Tilt/Left = `Axis 217` -Nunchuk/Tilt/Right = `Axis 218` -Nunchuk/Tilt/Modifier = `Button 219` -Nunchuk/Tilt/Modifier/Range = 50,000000 -Nunchuk/Shake/X = `Button 220` -Nunchuk/Shake/Y = `Button 221` -Nunchuk/Shake/Z = `Button 222` -Classic/Buttons/A = `Button 300` -Classic/Buttons/B = `Button 301` -Classic/Buttons/X = `Button 302` -Classic/Buttons/Y = `Button 303` -Classic/Buttons/- = `Button 304` -Classic/Buttons/+ = `Button 305` -Classic/Buttons/Home = `Button 306` -Classic/Buttons/ZL = `Button 307` -Classic/Buttons/ZR = `Button 308` -Classic/D-Pad/Up = `Button 309` -Classic/D-Pad/Down = `Button 310` -Classic/D-Pad/Left = `Button 311` -Classic/D-Pad/Right = `Button 312` -Classic/Left Stick/Up = `Axis 314` -Classic/Left Stick/Down = `Axis 315` -Classic/Left Stick/Left = `Axis 316` -Classic/Left Stick/Right = `Axis 317` -Classic/Right Stick/Up = `Axis 319` -Classic/Right Stick/Down = `Axis 320` -Classic/Right Stick/Left = `Axis 321` -Classic/Right Stick/Right = `Axis 322` -Classic/Triggers/L = `Axis 323` -Classic/Triggers/R = `Axis 324` -Classic/Triggers/Threshold = 90,000000 -Guitar/Buttons/- = `Button 400` -Guitar/Buttons/+ = `Button 401` -Guitar/Frets/Green = `Button 402` -Guitar/Frets/Red = `Button 403` -Guitar/Frets/Yellow = `Button 404` -Guitar/Frets/Blue = `Button 405` -Guitar/Frets/Orange = `Button 406` -Guitar/Strum/Up = `Button 407` -Guitar/Strum/Down = `Button 408` -Guitar/Stick/Up = `Axis 410` -Guitar/Stick/Down = `Axis 411` -Guitar/Stick/Left = `Axis 412` -Guitar/Stick/Right = `Axis 413` -Guitar/Whammy/Bar = `Axis 414` -Drums/Buttons/- = `Button 500` -Drums/Buttons/+ = `Button 501` -Drums/Pads/Red = `Button 502` -Drums/Pads/Yellow = `Button 503` -Drums/Pads/Blue = `Button 504` -Drums/Pads/Green = `Button 505` -Drums/Pads/Orange = `Button 506` -Drums/Pads/Bass = `Button 507` -Drums/Stick/Up = `Axis 509` -Drums/Stick/Down = `Axis 510` -Drums/Stick/Left = `Axis 511` -Drums/Stick/Right = `Axis 512` -Turntable/Buttons/Green Left = `Button 600` -Turntable/Buttons/Red Left = `Button 601` -Turntable/Buttons/Blue Left = `Button 602` -Turntable/Buttons/Green Right = `Button 603` -Turntable/Buttons/Red Right = `Button 604` -Turntable/Buttons/Blue Right = `Button 605` -Turntable/Buttons/- = `Button 606` -Turntable/Buttons/+ = `Button 607` -Turntable/Buttons/Home = `Button 608` -Turntable/Buttons/Euphoria = `Button 609` -Turntable/Table Left/Left = `Axis 611` -Turntable/Table Left/Right = `Axis 612` -Turntable/Table Right/Left = `Axis 614` -Turntable/Table Right/Right = `Axis 615` -Turntable/Stick/Up = `Axis 617` -Turntable/Stick/Down = `Axis 618` -Turntable/Stick/Left = `Axis 619` -Turntable/Stick/Right = `Axis 620` -Turntable/Effect/Dial = `Axis 621` -Turntable/Crossfade/Left = `Axis 623` -Turntable/Crossfade/Right = `Axis 624` -IMUAccelerometer/Left = `Axis 625` -IMUAccelerometer/Right = `Axis 626` -IMUAccelerometer/Forward = `Axis 627` -IMUAccelerometer/Backward = `Axis 628` -IMUAccelerometer/Up = `Axis 629` -IMUAccelerometer/Down = `Axis 630` -IMUGyroscope/Pitch Up = `Axis 631` -IMUGyroscope/Pitch Down = `Axis 632` -IMUGyroscope/Roll Left = `Axis 633` -IMUGyroscope/Roll Right = `Axis 634` -IMUGyroscope/Yaw Left = `Axis 635` -IMUGyroscope/Yaw Right = `Axis 636` -Source = 0 -Rumble/Motor = `Rumble 700` -[Wiimote3] -Device = Android/6/Touchscreen -Buttons/A = `Button 100` -Buttons/B = `Button 101` -Buttons/- = `Button 102` -Buttons/+ = `Button 103` -Buttons/Home = `Button 104` -Buttons/1 = `Button 105` -Buttons/2 = `Button 106` -D-Pad/Up = `Button 107` -D-Pad/Down = `Button 108` -D-Pad/Left = `Button 109` -D-Pad/Right = `Button 110` -IR/Up = `Axis 112` -IR/Down = `Axis 113` -IR/Left = `Axis 114` -IR/Right = `Axis 115` -IR/Forward = `Axis 116` -IR/Backward = `Axis 117` -IR/Hide = `Button 118` -IR/Total Pitch = 15 -IR/Total Yaw = 15 -IR/Vertical Offset = 10 -Swing/Up = `Axis 120` -Swing/Down = `Axis 121` -Swing/Left = `Axis 122` -Swing/Right = `Axis 123` -Swing/Forward = `Axis 124` -Swing/Backward = `Axis 125` -Tilt/Forward = `Axis 127` -Tilt/Backward = `Axis 128` -Tilt/Left = `Axis 129` -Tilt/Right = `Axis 130` -Tilt/Modifier = `Button 131` -Tilt/Modifier/Range = 50,000000 -Shake/X = `Button 132` -Shake/Y = `Button 133` -Shake/Z = `Button 134` -Extension = None -Nunchuk/Buttons/C = `Button 200` -Nunchuk/Buttons/Z = `Button 201` -Nunchuk/Stick/Up = `Axis 203` -Nunchuk/Stick/Down = `Axis 204` -Nunchuk/Stick/Left = `Axis 205` -Nunchuk/Stick/Right = `Axis 206` -Nunchuk/Swing/Up = `Axis 208` -Nunchuk/Swing/Down = `Axis 209` -Nunchuk/Swing/Left = `Axis 210` -Nunchuk/Swing/Right = `Axis 211` -Nunchuk/Swing/Forward = `Axis 212` -Nunchuk/Swing/Backward = `Axis 213` -Nunchuk/Tilt/Forward = `Axis 215` -Nunchuk/Tilt/Backward = `Axis 216` -Nunchuk/Tilt/Left = `Axis 217` -Nunchuk/Tilt/Right = `Axis 218` -Nunchuk/Tilt/Modifier = `Button 219` -Nunchuk/Tilt/Modifier/Range = 50,000000 -Nunchuk/Shake/X = `Button 220` -Nunchuk/Shake/Y = `Button 221` -Nunchuk/Shake/Z = `Button 222` -Classic/Buttons/A = `Button 300` -Classic/Buttons/B = `Button 301` -Classic/Buttons/X = `Button 302` -Classic/Buttons/Y = `Button 303` -Classic/Buttons/- = `Button 304` -Classic/Buttons/+ = `Button 305` -Classic/Buttons/Home = `Button 306` -Classic/Buttons/ZL = `Button 307` -Classic/Buttons/ZR = `Button 308` -Classic/D-Pad/Up = `Button 309` -Classic/D-Pad/Down = `Button 310` -Classic/D-Pad/Left = `Button 311` -Classic/D-Pad/Right = `Button 312` -Classic/Left Stick/Up = `Axis 314` -Classic/Left Stick/Down = `Axis 315` -Classic/Left Stick/Left = `Axis 316` -Classic/Left Stick/Right = `Axis 317` -Classic/Right Stick/Up = `Axis 319` -Classic/Right Stick/Down = `Axis 320` -Classic/Right Stick/Left = `Axis 321` -Classic/Right Stick/Right = `Axis 322` -Classic/Triggers/L = `Axis 323` -Classic/Triggers/R = `Axis 324` -Classic/Triggers/Threshold = 90,000000 -Guitar/Buttons/- = `Button 400` -Guitar/Buttons/+ = `Button 401` -Guitar/Frets/Green = `Button 402` -Guitar/Frets/Red = `Button 403` -Guitar/Frets/Yellow = `Button 404` -Guitar/Frets/Blue = `Button 405` -Guitar/Frets/Orange = `Button 406` -Guitar/Strum/Up = `Button 407` -Guitar/Strum/Down = `Button 408` -Guitar/Stick/Up = `Axis 410` -Guitar/Stick/Down = `Axis 411` -Guitar/Stick/Left = `Axis 412` -Guitar/Stick/Right = `Axis 413` -Guitar/Whammy/Bar = `Axis 414` -Drums/Buttons/- = `Button 500` -Drums/Buttons/+ = `Button 501` -Drums/Pads/Red = `Button 502` -Drums/Pads/Yellow = `Button 503` -Drums/Pads/Blue = `Button 504` -Drums/Pads/Green = `Button 505` -Drums/Pads/Orange = `Button 506` -Drums/Pads/Bass = `Button 507` -Drums/Stick/Up = `Axis 509` -Drums/Stick/Down = `Axis 510` -Drums/Stick/Left = `Axis 511` -Drums/Stick/Right = `Axis 512` -Turntable/Buttons/Green Left = `Button 600` -Turntable/Buttons/Red Left = `Button 601` -Turntable/Buttons/Blue Left = `Button 602` -Turntable/Buttons/Green Right = `Button 603` -Turntable/Buttons/Red Right = `Button 604` -Turntable/Buttons/Blue Right = `Button 605` -Turntable/Buttons/- = `Button 606` -Turntable/Buttons/+ = `Button 607` -Turntable/Buttons/Home = `Button 608` -Turntable/Buttons/Euphoria = `Button 609` -Turntable/Table Left/Left = `Axis 611` -Turntable/Table Left/Right = `Axis 612` -Turntable/Table Right/Left = `Axis 614` -Turntable/Table Right/Right = `Axis 615` -Turntable/Stick/Up = `Axis 617` -Turntable/Stick/Down = `Axis 618` -Turntable/Stick/Left = `Axis 619` -Turntable/Stick/Right = `Axis 620` -Turntable/Effect/Dial = `Axis 621` -Turntable/Crossfade/Left = `Axis 623` -Turntable/Crossfade/Right = `Axis 624` -IMUAccelerometer/Left = `Axis 625` -IMUAccelerometer/Right = `Axis 626` -IMUAccelerometer/Forward = `Axis 627` -IMUAccelerometer/Backward = `Axis 628` -IMUAccelerometer/Up = `Axis 629` -IMUAccelerometer/Down = `Axis 630` -IMUGyroscope/Pitch Up = `Axis 631` -IMUGyroscope/Pitch Down = `Axis 632` -IMUGyroscope/Roll Left = `Axis 633` -IMUGyroscope/Roll Right = `Axis 634` -IMUGyroscope/Yaw Left = `Axis 635` -IMUGyroscope/Yaw Right = `Axis 636` -Source = 0 -Rumble/Motor = `Rumble 700` -[Wiimote4] -Device = Android/7/Touchscreen -Buttons/A = `Button 100` -Buttons/B = `Button 101` -Buttons/- = `Button 102` -Buttons/+ = `Button 103` -Buttons/Home = `Button 104` -Buttons/1 = `Button 105` -Buttons/2 = `Button 106` -D-Pad/Up = `Button 107` -D-Pad/Down = `Button 108` -D-Pad/Left = `Button 109` -D-Pad/Right = `Button 110` -IR/Up = `Axis 112` -IR/Down = `Axis 113` -IR/Left = `Axis 114` -IR/Right = `Axis 115` -IR/Forward = `Axis 116` -IR/Backward = `Axis 117` -IR/Hide = `Button 118` -IR/Total Pitch = 15 -IR/Total Yaw = 15 -IR/Vertical Offset = 10 -Swing/Up = `Axis 120` -Swing/Down = `Axis 121` -Swing/Left = `Axis 122` -Swing/Right = `Axis 123` -Swing/Forward = `Axis 124` -Swing/Backward = `Axis 125` -Tilt/Forward = `Axis 127` -Tilt/Backward = `Axis 128` -Tilt/Left = `Axis 129` -Tilt/Right = `Axis 130` -Tilt/Modifier = `Button 131` -Tilt/Modifier/Range = 50,000000 -Shake/X = `Button 132` -Shake/Y = `Button 133` -Shake/Z = `Button 134` -Extension = None -Nunchuk/Buttons/C = `Button 200` -Nunchuk/Buttons/Z = `Button 201` -Nunchuk/Stick/Up = `Axis 203` -Nunchuk/Stick/Down = `Axis 204` -Nunchuk/Stick/Left = `Axis 205` -Nunchuk/Stick/Right = `Axis 206` -Nunchuk/Swing/Up = `Axis 208` -Nunchuk/Swing/Down = `Axis 209` -Nunchuk/Swing/Left = `Axis 210` -Nunchuk/Swing/Right = `Axis 211` -Nunchuk/Swing/Forward = `Axis 212` -Nunchuk/Swing/Backward = `Axis 213` -Nunchuk/Tilt/Forward = `Axis 215` -Nunchuk/Tilt/Backward = `Axis 216` -Nunchuk/Tilt/Left = `Axis 217` -Nunchuk/Tilt/Right = `Axis 218` -Nunchuk/Tilt/Modifier = `Button 219` -Nunchuk/Tilt/Modifier/Range = 50,000000 -Nunchuk/Shake/X = `Button 220` -Nunchuk/Shake/Y = `Button 221` -Nunchuk/Shake/Z = `Button 222` -Classic/Buttons/A = `Button 300` -Classic/Buttons/B = `Button 301` -Classic/Buttons/X = `Button 302` -Classic/Buttons/Y = `Button 303` -Classic/Buttons/- = `Button 304` -Classic/Buttons/+ = `Button 305` -Classic/Buttons/Home = `Button 306` -Classic/Buttons/ZL = `Button 307` -Classic/Buttons/ZR = `Button 308` -Classic/D-Pad/Up = `Button 309` -Classic/D-Pad/Down = `Button 310` -Classic/D-Pad/Left = `Button 311` -Classic/D-Pad/Right = `Button 312` -Classic/Left Stick/Up = `Axis 314` -Classic/Left Stick/Down = `Axis 315` -Classic/Left Stick/Left = `Axis 316` -Classic/Left Stick/Right = `Axis 317` -Classic/Right Stick/Up = `Axis 319` -Classic/Right Stick/Down = `Axis 320` -Classic/Right Stick/Left = `Axis 321` -Classic/Right Stick/Right = `Axis 322` -Classic/Triggers/L = `Axis 323` -Classic/Triggers/R = `Axis 324` -Classic/Triggers/Threshold = 90,000000 -Guitar/Buttons/- = `Button 400` -Guitar/Buttons/+ = `Button 401` -Guitar/Frets/Green = `Button 402` -Guitar/Frets/Red = `Button 403` -Guitar/Frets/Yellow = `Button 404` -Guitar/Frets/Blue = `Button 405` -Guitar/Frets/Orange = `Button 406` -Guitar/Strum/Up = `Button 407` -Guitar/Strum/Down = `Button 408` -Guitar/Stick/Up = `Axis 410` -Guitar/Stick/Down = `Axis 411` -Guitar/Stick/Left = `Axis 412` -Guitar/Stick/Right = `Axis 413` -Guitar/Whammy/Bar = `Axis 414` -Drums/Buttons/- = `Button 500` -Drums/Buttons/+ = `Button 501` -Drums/Pads/Red = `Button 502` -Drums/Pads/Yellow = `Button 503` -Drums/Pads/Blue = `Button 504` -Drums/Pads/Green = `Button 505` -Drums/Pads/Orange = `Button 506` -Drums/Pads/Bass = `Button 507` -Drums/Stick/Up = `Axis 509` -Drums/Stick/Down = `Axis 510` -Drums/Stick/Left = `Axis 511` -Drums/Stick/Right = `Axis 512` -Turntable/Buttons/Green Left = `Button 600` -Turntable/Buttons/Red Left = `Button 601` -Turntable/Buttons/Blue Left = `Button 602` -Turntable/Buttons/Green Right = `Button 603` -Turntable/Buttons/Red Right = `Button 604` -Turntable/Buttons/Blue Right = `Button 605` -Turntable/Buttons/- = `Button 606` -Turntable/Buttons/+ = `Button 607` -Turntable/Buttons/Home = `Button 608` -Turntable/Buttons/Euphoria = `Button 609` -Turntable/Table Left/Left = `Axis 611` -Turntable/Table Left/Right = `Axis 612` -Turntable/Table Right/Left = `Axis 614` -Turntable/Table Right/Right = `Axis 615` -Turntable/Stick/Up = `Axis 617` -Turntable/Stick/Down = `Axis 618` -Turntable/Stick/Left = `Axis 619` -Turntable/Stick/Right = `Axis 620` -Turntable/Effect/Dial = `Axis 621` -Turntable/Crossfade/Left = `Axis 623` -Turntable/Crossfade/Right = `Axis 624` -IMUAccelerometer/Left = `Axis 625` -IMUAccelerometer/Right = `Axis 626` -IMUAccelerometer/Forward = `Axis 627` -IMUAccelerometer/Backward = `Axis 628` -IMUAccelerometer/Up = `Axis 629` -IMUAccelerometer/Down = `Axis 630` -IMUGyroscope/Pitch Up = `Axis 631` -IMUGyroscope/Pitch Down = `Axis 632` -IMUGyroscope/Roll Left = `Axis 633` -IMUGyroscope/Roll Right = `Axis 634` -IMUGyroscope/Yaw Left = `Axis 635` -IMUGyroscope/Yaw Right = `Axis 636` -Source = 0 -Rumble/Motor = `Rumble 700` diff --git a/Source/Android/app/src/main/assets/WiimoteProfile.ini b/Source/Android/app/src/main/assets/WiimoteProfile.ini deleted file mode 100644 index 89f7d87f5b..0000000000 --- a/Source/Android/app/src/main/assets/WiimoteProfile.ini +++ /dev/null @@ -1,144 +0,0 @@ -[Profile] -Device = Android/4/Touchscreen -Buttons/A = `Button 100` -Buttons/B = `Button 101` -Buttons/- = `Button 102` -Buttons/+ = `Button 103` -Buttons/Home = `Button 104` -Buttons/1 = `Button 105` -Buttons/2 = `Button 106` -D-Pad/Up = `Button 107` -D-Pad/Down = `Button 108` -D-Pad/Left = `Button 109` -D-Pad/Right = `Button 110` -IR/Up = `Axis 112` -IR/Down = `Axis 113` -IR/Left = `Axis 114` -IR/Right = `Axis 115` -IR/Forward = `Axis 116` -IR/Backward = `Axis 117` -IR/Hide = `Button 118` -IR/Total Pitch = 20 -IR/Total Yaw = 25 -IR/Vertical Offset = 10 -Swing/Up = `Axis 120` -Swing/Down = `Axis 121` -Swing/Left = `Axis 122` -Swing/Right = `Axis 123` -Swing/Forward = `Axis 124` -Swing/Backward = `Axis 125` -Tilt/Forward = `Axis 127` -Tilt/Backward = `Axis 128` -Tilt/Left = `Axis 129` -Tilt/Right = `Axis 130` -Tilt/Modifier = `Button 131` -Tilt/Modifier/Range = 50,000000 -Shake/X = `Button 132` -Shake/Y = `Button 133` -Shake/Z = `Button 134` -Extension = Nunchuk -Nunchuk/Buttons/C = `Button 200` -Nunchuk/Buttons/Z = `Button 201` -Nunchuk/Stick/Up = `Axis 203` -Nunchuk/Stick/Down = `Axis 204` -Nunchuk/Stick/Left = `Axis 205` -Nunchuk/Stick/Right = `Axis 206` -Nunchuk/Swing/Up = `Axis 208` -Nunchuk/Swing/Down = `Axis 209` -Nunchuk/Swing/Left = `Axis 210` -Nunchuk/Swing/Right = `Axis 211` -Nunchuk/Swing/Forward = `Axis 212` -Nunchuk/Swing/Backward = `Axis 213` -Nunchuk/Tilt/Forward = `Axis 215` -Nunchuk/Tilt/Backward = `Axis 216` -Nunchuk/Tilt/Left = `Axis 217` -Nunchuk/Tilt/Right = `Axis 218` -Nunchuk/Tilt/Modifier = `Button 219` -Nunchuk/Tilt/Modifier/Range = 50,000000 -Nunchuk/Shake/X = `Button 220` -Nunchuk/Shake/Y = `Button 221` -Nunchuk/Shake/Z = `Button 222` -Classic/Buttons/A = `Button 300` -Classic/Buttons/B = `Button 301` -Classic/Buttons/X = `Button 302` -Classic/Buttons/Y = `Button 303` -Classic/Buttons/- = `Button 304` -Classic/Buttons/+ = `Button 305` -Classic/Buttons/Home = `Button 306` -Classic/Buttons/ZL = `Button 307` -Classic/Buttons/ZR = `Button 308` -Classic/D-Pad/Up = `Button 309` -Classic/D-Pad/Down = `Button 310` -Classic/D-Pad/Left = `Button 311` -Classic/D-Pad/Right = `Button 312` -Classic/Left Stick/Up = `Axis 314` -Classic/Left Stick/Down = `Axis 315` -Classic/Left Stick/Left = `Axis 316` -Classic/Left Stick/Right = `Axis 317` -Classic/Right Stick/Up = `Axis 319` -Classic/Right Stick/Down = `Axis 320` -Classic/Right Stick/Left = `Axis 321` -Classic/Right Stick/Right = `Axis 322` -Classic/Triggers/L = `Axis 323` -Classic/Triggers/R = `Axis 324` -Classic/Triggers/Threshold = 90,000000 -Guitar/Buttons/- = `Button 400` -Guitar/Buttons/+ = `Button 401` -Guitar/Frets/Green = `Button 402` -Guitar/Frets/Red = `Button 403` -Guitar/Frets/Yellow = `Button 404` -Guitar/Frets/Blue = `Button 405` -Guitar/Frets/Orange = `Button 406` -Guitar/Strum/Up = `Button 407` -Guitar/Strum/Down = `Button 408` -Guitar/Stick/Up = `Axis 410` -Guitar/Stick/Down = `Axis 411` -Guitar/Stick/Left = `Axis 412` -Guitar/Stick/Right = `Axis 413` -Guitar/Whammy/Bar = `Axis 414` -Drums/Buttons/- = `Button 500` -Drums/Buttons/+ = `Button 501` -Drums/Pads/Red = `Button 502` -Drums/Pads/Yellow = `Button 503` -Drums/Pads/Blue = `Button 504` -Drums/Pads/Green = `Button 505` -Drums/Pads/Orange = `Button 506` -Drums/Pads/Bass = `Button 507` -Drums/Stick/Up = `Axis 509` -Drums/Stick/Down = `Axis 510` -Drums/Stick/Left = `Axis 511` -Drums/Stick/Right = `Axis 512` -Turntable/Buttons/Green Left = `Button 600` -Turntable/Buttons/Red Left = `Button 601` -Turntable/Buttons/Blue Left = `Button 602` -Turntable/Buttons/Green Right = `Button 603` -Turntable/Buttons/Red Right = `Button 604` -Turntable/Buttons/Blue Right = `Button 605` -Turntable/Buttons/- = `Button 606` -Turntable/Buttons/+ = `Button 607` -Turntable/Buttons/Home = `Button 608` -Turntable/Buttons/Euphoria = `Button 609` -Turntable/Table Left/Left = `Axis 611` -Turntable/Table Left/Right = `Axis 612` -Turntable/Table Right/Left = `Axis 614` -Turntable/Table Right/Right = `Axis 615` -Turntable/Stick/Up = `Axis 617` -Turntable/Stick/Down = `Axis 618` -Turntable/Stick/Left = `Axis 619` -Turntable/Stick/Right = `Axis 620` -Turntable/Effect/Dial = `Axis 621` -Turntable/Crossfade/Left = `Axis 623` -Turntable/Crossfade/Right = `Axis 624` -IMUAccelerometer/Left = `Axis 625` -IMUAccelerometer/Right = `Axis 626` -IMUAccelerometer/Forward = `Axis 627` -IMUAccelerometer/Backward = `Axis 628` -IMUAccelerometer/Up = `Axis 629` -IMUAccelerometer/Down = `Axis 630` -IMUGyroscope/Pitch Up = `Axis 631` -IMUGyroscope/Pitch Down = `Axis 632` -IMUGyroscope/Roll Left = `Axis 633` -IMUGyroscope/Roll Right = `Axis 634` -IMUGyroscope/Yaw Left = `Axis 635` -IMUGyroscope/Yaw Right = `Axis 636` -Rumble/Motor = `Rumble 700` diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java index d6fc236771..68fdfee277 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java @@ -18,7 +18,6 @@ import org.dolphinemu.dolphinemu.activities.EmulationActivity; import org.dolphinemu.dolphinemu.dialogs.AlertMessage; import org.dolphinemu.dolphinemu.utils.CompressCallback; import org.dolphinemu.dolphinemu.utils.Log; -import org.dolphinemu.dolphinemu.utils.Rumble; import java.lang.ref.WeakReference; import java.util.LinkedHashMap; @@ -44,7 +43,7 @@ public final class NativeLibrary } /** - * Button type for use in onTouchEvent + * Button type, for legacy use only */ public static final class ButtonType { @@ -234,52 +233,6 @@ public final class NativeLibrary // Disallows instantiation. } - /** - * Default touchscreen device - */ - public static final String TouchScreenDevice = "Touchscreen"; - - /** - * Handles button press events for a gamepad. - * - * @param Device The input descriptor of the gamepad. - * @param Button Key code identifying which button was pressed. - * @param Action Mask identifying which action is happening (button pressed down, or button released). - * @return If we handled the button press. - */ - public static native boolean onGamePadEvent(String Device, int Button, int Action); - - /** - * Handles gamepad movement events. - * - * @param Device The device ID of the gamepad. - * @param Axis The axis ID - * @param Value The value of the axis represented by the given ID. - */ - public static native void onGamePadMoveEvent(String Device, int Axis, float Value); - - /** - * Rumble sent from native. Currently only supports phone rumble. - * - * @param padID Ignored for now. Future use would be to pass rumble to a connected controller - * @param state Ignored for now since phone rumble can't just be 'turned' on/off - */ - @Keep - public static void rumble(int padID, double state) - { - final EmulationActivity emulationActivity = sEmulationActivity.get(); - if (emulationActivity == null) - { - Log.warning("[NativeLibrary] EmulationActivity is null"); - return; - } - - Rumble.checkRumble(padID, state); - } - - public static native void SetMotionSensorsEnabled(boolean accelerometerEnabled, - boolean gyroscopeEnabled); - /** * Gets the Dolphin version string. * @@ -451,8 +404,6 @@ public final class NativeLibrary */ public static native void RefreshWiimotes(); - public static native void ReloadWiimoteConfig(); - public static native LinkedHashMap GetLogTypeNames(); public static native void ReloadLoggerConfig(); diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.java index 808adbff26..9aebeb1ded 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.java @@ -2,7 +2,6 @@ package org.dolphinemu.dolphinemu.activities; -import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; @@ -11,7 +10,6 @@ import android.os.Build; import android.os.Bundle; import android.util.Pair; import android.util.SparseIntArray; -import android.view.InputDevice; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; @@ -40,15 +38,15 @@ import org.dolphinemu.dolphinemu.NativeLibrary; import org.dolphinemu.dolphinemu.R; import org.dolphinemu.dolphinemu.databinding.ActivityEmulationBinding; import org.dolphinemu.dolphinemu.databinding.DialogInputAdjustBinding; -import org.dolphinemu.dolphinemu.databinding.DialogIrSensitivityBinding; import org.dolphinemu.dolphinemu.databinding.DialogSkylandersManagerBinding; +import org.dolphinemu.dolphinemu.features.input.model.ControllerInterface; +import org.dolphinemu.dolphinemu.features.input.model.DolphinSensorEventListener; import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting; import org.dolphinemu.dolphinemu.features.settings.model.IntSetting; import org.dolphinemu.dolphinemu.features.settings.model.Settings; import org.dolphinemu.dolphinemu.features.settings.model.StringSetting; import org.dolphinemu.dolphinemu.features.settings.ui.MenuTag; import org.dolphinemu.dolphinemu.features.settings.ui.SettingsActivity; -import org.dolphinemu.dolphinemu.features.settings.utils.SettingsFile; import org.dolphinemu.dolphinemu.features.skylanders.SkylanderConfig; import org.dolphinemu.dolphinemu.features.skylanders.model.Skylander; import org.dolphinemu.dolphinemu.features.skylanders.ui.SkylanderSlot; @@ -61,14 +59,9 @@ import org.dolphinemu.dolphinemu.overlay.InputOverlayPointer; import org.dolphinemu.dolphinemu.ui.main.MainPresenter; import org.dolphinemu.dolphinemu.ui.main.ThemeProvider; import org.dolphinemu.dolphinemu.utils.AfterDirectoryInitializationRunner; -import org.dolphinemu.dolphinemu.utils.ControllerMappingHelper; import org.dolphinemu.dolphinemu.utils.FileBrowserHelper; -import org.dolphinemu.dolphinemu.utils.IniFile; -import org.dolphinemu.dolphinemu.utils.MotionListener; -import org.dolphinemu.dolphinemu.utils.Rumble; import org.dolphinemu.dolphinemu.utils.ThemeHelper; -import java.io.File; import java.lang.annotation.Retention; import java.util.ArrayList; import java.util.List; @@ -89,7 +82,6 @@ public final class EmulationActivity extends AppCompatActivity implements ThemeP private EmulationFragment mEmulationFragment; private SharedPreferences mPreferences; - private MotionListener mMotionListener; private Settings mSettings; @@ -127,9 +119,9 @@ public final class EmulationActivity extends AppCompatActivity implements ThemeP MENU_ACTION_LOAD_SLOT3, MENU_ACTION_LOAD_SLOT4, MENU_ACTION_LOAD_SLOT5, MENU_ACTION_LOAD_SLOT6, MENU_ACTION_EXIT, MENU_ACTION_CHANGE_DISC, MENU_ACTION_RESET_OVERLAY, MENU_SET_IR_RECENTER, MENU_SET_IR_MODE, - MENU_SET_IR_SENSITIVITY, MENU_ACTION_CHOOSE_DOUBLETAP, MENU_ACTION_MOTION_CONTROLS, - MENU_ACTION_PAUSE_EMULATION, MENU_ACTION_UNPAUSE_EMULATION, MENU_ACTION_OVERLAY_CONTROLS, - MENU_ACTION_SETTINGS, MENU_ACTION_SKYLANDERS}) + MENU_ACTION_CHOOSE_DOUBLETAP, MENU_ACTION_PAUSE_EMULATION, + MENU_ACTION_UNPAUSE_EMULATION, MENU_ACTION_OVERLAY_CONTROLS, MENU_ACTION_SETTINGS, + MENU_ACTION_SKYLANDERS}) public @interface MenuAction { } @@ -159,13 +151,10 @@ public final class EmulationActivity extends AppCompatActivity implements ThemeP public static final int MENU_ACTION_EXIT = 22; public static final int MENU_ACTION_CHANGE_DISC = 23; public static final int MENU_ACTION_JOYSTICK_REL_CENTER = 24; - public static final int MENU_ACTION_RUMBLE = 25; public static final int MENU_ACTION_RESET_OVERLAY = 26; public static final int MENU_SET_IR_RECENTER = 27; public static final int MENU_SET_IR_MODE = 28; - public static final int MENU_SET_IR_SENSITIVITY = 29; public static final int MENU_ACTION_CHOOSE_DOUBLETAP = 30; - public static final int MENU_ACTION_MOTION_CONTROLS = 31; public static final int MENU_ACTION_PAUSE_EMULATION = 32; public static final int MENU_ACTION_UNPAUSE_EMULATION = 33; public static final int MENU_ACTION_OVERLAY_CONTROLS = 34; @@ -194,19 +183,14 @@ public final class EmulationActivity extends AppCompatActivity implements ThemeP EmulationActivity.MENU_ACTION_CHOOSE_CONTROLLER); buttonsActionsMap.append(R.id.menu_emulation_joystick_rel_center, EmulationActivity.MENU_ACTION_JOYSTICK_REL_CENTER); - buttonsActionsMap.append(R.id.menu_emulation_rumble, EmulationActivity.MENU_ACTION_RUMBLE); buttonsActionsMap .append(R.id.menu_emulation_reset_overlay, EmulationActivity.MENU_ACTION_RESET_OVERLAY); buttonsActionsMap.append(R.id.menu_emulation_ir_recenter, EmulationActivity.MENU_SET_IR_RECENTER); buttonsActionsMap.append(R.id.menu_emulation_set_ir_mode, EmulationActivity.MENU_SET_IR_MODE); - buttonsActionsMap.append(R.id.menu_emulation_set_ir_sensitivity, - EmulationActivity.MENU_SET_IR_SENSITIVITY); buttonsActionsMap.append(R.id.menu_emulation_choose_doubletap, EmulationActivity.MENU_ACTION_CHOOSE_DOUBLETAP); - buttonsActionsMap.append(R.id.menu_emulation_motion_controls, - EmulationActivity.MENU_ACTION_MOTION_CONTROLS); } public static void launch(FragmentActivity activity, String filePath, boolean riivolution) @@ -302,30 +286,6 @@ public final class EmulationActivity extends AppCompatActivity implements ThemeP sIgnoreLaunchRequests = false; } - public static void updateWiimoteNewIniPreferences(Context context) - { - updateWiimoteNewController(InputOverlay.getConfiguredControllerType(context), context); - updateWiimoteNewImuIr(IntSetting.MAIN_MOTION_CONTROLS.getIntGlobal()); - } - - private static void updateWiimoteNewController(int value, Context context) - { - File wiimoteNewFile = SettingsFile.getSettingsFile(Settings.FILE_WIIMOTE); - IniFile wiimoteNewIni = new IniFile(wiimoteNewFile); - wiimoteNewIni.setString("Wiimote1", "Extension", - context.getResources().getStringArray(R.array.controllersValues)[value]); - wiimoteNewIni.setBoolean("Wiimote1", "Options/Sideways Wiimote", value == 2); - wiimoteNewIni.save(wiimoteNewFile); - } - - private static void updateWiimoteNewImuIr(int value) - { - File wiimoteNewFile = SettingsFile.getSettingsFile(Settings.FILE_WIIMOTE); - IniFile wiimoteNewIni = new IniFile(wiimoteNewFile); - wiimoteNewIni.setBoolean("Wiimote1", "IMUIR/Enabled", value != 1); - wiimoteNewIni.save(wiimoteNewFile); - } - @Override protected void onCreate(Bundle savedInstanceState) { @@ -359,13 +319,9 @@ public final class EmulationActivity extends AppCompatActivity implements ThemeP updateOrientation(); - mMotionListener = new MotionListener(this); - // Set these options now so that the SurfaceView the game renders into is the right size. enableFullscreenImmersive(); - Rumble.initRumble(this); - ActivityEmulationBinding binding = ActivityEmulationBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); @@ -453,15 +409,14 @@ public final class EmulationActivity extends AppCompatActivity implements ThemeP updateOrientation(); - if (NativeLibrary.IsGameMetadataValid()) - updateMotionListener(); + DolphinSensorEventListener.setDeviceRotation( + getWindowManager().getDefaultDisplay().getRotation()); } @Override protected void onPause() { super.onPause(); - mMotionListener.disable(); } @Override @@ -481,17 +436,8 @@ public final class EmulationActivity extends AppCompatActivity implements ThemeP } setTitle(NativeLibrary.GetCurrentTitleDescription()); - updateMotionListener(); - mEmulationFragment.refreshInputOverlay(); - } - - private void updateMotionListener() - { - if (NativeLibrary.IsEmulatingWii() && IntSetting.MAIN_MOTION_CONTROLS.getInt(mSettings) != 2) - mMotionListener.enable(); - else - mMotionListener.disable(); + mEmulationFragment.refreshInputOverlay(mSettings); } @Override @@ -644,8 +590,6 @@ public final class EmulationActivity extends AppCompatActivity implements ThemeP // Populate the switch value for joystick center on touch menu.findItem(R.id.menu_emulation_joystick_rel_center) .setChecked(BooleanSetting.MAIN_JOYSTICK_REL_CENTER.getBoolean(mSettings)); - menu.findItem(R.id.menu_emulation_rumble) - .setChecked(BooleanSetting.MAIN_PHONE_RUMBLE.getBoolean(mSettings)); if (wii) { menu.findItem(R.id.menu_emulation_ir_recenter) @@ -685,10 +629,6 @@ public final class EmulationActivity extends AppCompatActivity implements ThemeP item.setChecked(!item.isChecked()); toggleJoystickRelCenter(item.isChecked()); break; - case MENU_ACTION_RUMBLE: - item.setChecked(!item.isChecked()); - toggleRumble(item.isChecked()); - break; case MENU_SET_IR_RECENTER: item.setChecked(!item.isChecked()); toggleRecenter(item.isChecked()); @@ -721,7 +661,7 @@ public final class EmulationActivity extends AppCompatActivity implements ThemeP adjustScale(); break; - // (Wii games only) Change the controller for the input overlay. + // Change the controller for the input overlay. case MENU_ACTION_CHOOSE_CONTROLLER: chooseController(); break; @@ -823,18 +763,10 @@ public final class EmulationActivity extends AppCompatActivity implements ThemeP setIRMode(); break; - case MENU_SET_IR_SENSITIVITY: - setIRSensitivity(); - break; - case MENU_ACTION_CHOOSE_DOUBLETAP: chooseDoubleTapButton(); break; - case MENU_ACTION_MOTION_CONTROLS: - showMotionControlsOptions(); - break; - case MENU_ACTION_SETTINGS: SettingsActivity.launch(this, MenuTag.SETTINGS); break; @@ -859,12 +791,6 @@ public final class EmulationActivity extends AppCompatActivity implements ThemeP BooleanSetting.MAIN_JOYSTICK_REL_CENTER.setBoolean(mSettings, state); } - private void toggleRumble(boolean state) - { - BooleanSetting.MAIN_PHONE_RUMBLE.setBoolean(mSettings, state); - Rumble.setPhoneVibrator(state, this); - } - private void toggleRecenter(boolean state) { BooleanSetting.MAIN_IR_ALWAYS_RECENTER.setBoolean(mSettings, state); @@ -889,26 +815,15 @@ public final class EmulationActivity extends AppCompatActivity implements ThemeP @Override public boolean dispatchKeyEvent(KeyEvent event) { - if (mMenuVisible || event.getKeyCode() == KeyEvent.KEYCODE_BACK) + if (!mMenuVisible) { - return super.dispatchKeyEvent(event); + if (ControllerInterface.dispatchKeyEvent(event)) + { + return true; + } } - int action; - - switch (event.getAction()) - { - case KeyEvent.ACTION_DOWN: - action = NativeLibrary.ButtonState.PRESSED; - break; - case KeyEvent.ACTION_UP: - action = NativeLibrary.ButtonState.RELEASED; - break; - default: - return false; - } - InputDevice input = event.getDevice(); - return NativeLibrary.onGamePadEvent(input.getDescriptor(), event.getKeyCode(), action); + return super.dispatchKeyEvent(event); } private void toggleControls() @@ -916,9 +831,9 @@ public final class EmulationActivity extends AppCompatActivity implements ThemeP MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this) .setTitle(R.string.emulation_toggle_controls); - int currentController = InputOverlay.getConfiguredControllerType(this); + int currentController = InputOverlay.getConfiguredControllerType(mSettings); - if (!NativeLibrary.IsEmulatingWii() || currentController == InputOverlay.OVERLAY_GAMECUBE) + if (currentController == InputOverlay.OVERLAY_GAMECUBE) { boolean[] gcEnabledButtons = new boolean[11]; String gcSettingBase = "MAIN_BUTTON_TOGGLE_GC_"; @@ -972,7 +887,7 @@ public final class EmulationActivity extends AppCompatActivity implements ThemeP builder.setNeutralButton(R.string.emulation_toggle_all, (dialogInterface, i) -> mEmulationFragment.toggleInputOverlayVisibility(mSettings)) .setPositiveButton(R.string.ok, (dialogInterface, i) -> - mEmulationFragment.refreshInputOverlay()) + mEmulationFragment.refreshInputOverlay(mSettings)) .show(); } @@ -980,7 +895,7 @@ public final class EmulationActivity extends AppCompatActivity implements ThemeP { int currentValue = IntSetting.MAIN_DOUBLE_TAP_BUTTON.getInt(mSettings); - int buttonList = InputOverlay.getConfiguredControllerType(this) == + int buttonList = InputOverlay.getConfiguredControllerType(mSettings) == InputOverlay.OVERLAY_WIIMOTE_CLASSIC ? R.array.doubleTapWithClassic : R.array.doubleTap; int checkedItem = -1; @@ -999,7 +914,7 @@ public final class EmulationActivity extends AppCompatActivity implements ThemeP (DialogInterface dialog, int which) -> IntSetting.MAIN_DOUBLE_TAP_BUTTON.setInt( mSettings, InputOverlayPointer.DOUBLE_TAP_OPTIONS.get(which))) .setPositiveButton(R.string.ok, - (dialogInterface, i) -> mEmulationFragment.initInputPointer()) + (dialogInterface, i) -> mEmulationFragment.initInputPointer(mSettings)) .show(); } @@ -1033,55 +948,75 @@ public final class EmulationActivity extends AppCompatActivity implements ThemeP { IntSetting.MAIN_CONTROL_SCALE.setInt(mSettings, (int) scaleSlider.getValue()); IntSetting.MAIN_CONTROL_OPACITY.setInt(mSettings, (int) sliderOpacity.getValue()); - mEmulationFragment.refreshInputOverlay(); + mEmulationFragment.refreshInputOverlay(mSettings); }) .setNeutralButton(R.string.default_values, (dialog, which) -> { IntSetting.MAIN_CONTROL_SCALE.delete(mSettings); IntSetting.MAIN_CONTROL_OPACITY.delete(mSettings); - mEmulationFragment.refreshInputOverlay(); + mEmulationFragment.refreshInputOverlay(mSettings); }) .show(); } + private void addControllerIfNotNone(List entries, List values, + IntSetting controller, int entry, int value) + { + if (controller.getInt(mSettings) != 0) + { + entries.add(getString(entry)); + values.add(value); + } + } + private void chooseController() { + ArrayList entries = new ArrayList<>(); + ArrayList values = new ArrayList<>(); + + entries.add(getString(R.string.none)); + values.add(-1); + + addControllerIfNotNone(entries, values, IntSetting.MAIN_SI_DEVICE_0, R.string.controller_0, 0); + addControllerIfNotNone(entries, values, IntSetting.MAIN_SI_DEVICE_1, R.string.controller_1, 1); + addControllerIfNotNone(entries, values, IntSetting.MAIN_SI_DEVICE_2, R.string.controller_2, 2); + addControllerIfNotNone(entries, values, IntSetting.MAIN_SI_DEVICE_3, R.string.controller_3, 3); + + if (NativeLibrary.IsEmulatingWii()) + { + addControllerIfNotNone(entries, values, IntSetting.WIIMOTE_1_SOURCE, R.string.wiimote_0, 4); + addControllerIfNotNone(entries, values, IntSetting.WIIMOTE_2_SOURCE, R.string.wiimote_1, 5); + addControllerIfNotNone(entries, values, IntSetting.WIIMOTE_3_SOURCE, R.string.wiimote_2, 6); + addControllerIfNotNone(entries, values, IntSetting.WIIMOTE_4_SOURCE, R.string.wiimote_3, 7); + } + + IntSetting controllerSetting = NativeLibrary.IsEmulatingWii() ? + IntSetting.MAIN_OVERLAY_WII_CONTROLLER : IntSetting.MAIN_OVERLAY_GC_CONTROLLER; + int currentValue = controllerSetting.getInt(mSettings); + + int checkedItem = -1; + for (int i = 0; i < values.size(); i++) + { + if (values.get(i) == currentValue) + { + checkedItem = i; + break; + } + } + final SharedPreferences.Editor editor = mPreferences.edit(); new MaterialAlertDialogBuilder(this) .setTitle(R.string.emulation_choose_controller) - .setSingleChoiceItems(R.array.controllersEntries, - InputOverlay.getConfiguredControllerType(this), + .setSingleChoiceItems(entries.toArray(new CharSequence[]{}), checkedItem, (dialog, indexSelected) -> - { - editor.putInt("wiiController", indexSelected); - - updateWiimoteNewController(indexSelected, this); - NativeLibrary.ReloadWiimoteConfig(); - }) + controllerSetting.setInt(mSettings, values.get(indexSelected))) .setPositiveButton(R.string.ok, (dialogInterface, i) -> { editor.apply(); - mEmulationFragment.refreshInputOverlay(); + mEmulationFragment.refreshInputOverlay(mSettings); }) - .show(); - } - - private void showMotionControlsOptions() - { - new MaterialAlertDialogBuilder(this) - .setTitle(R.string.emulation_motion_controls) - .setSingleChoiceItems(R.array.motionControlsEntries, - IntSetting.MAIN_MOTION_CONTROLS.getInt(mSettings), - (dialog, indexSelected) -> - { - IntSetting.MAIN_MOTION_CONTROLS.setInt(mSettings, indexSelected); - - updateMotionListener(); - - updateWiimoteNewImuIr(indexSelected); - NativeLibrary.ReloadWiimoteConfig(); - }) - .setPositiveButton(R.string.ok, (dialogInterface, i) -> dialogInterface.dismiss()) + .setNeutralButton(R.string.emulation_more_controller_settings, + (dialogInterface, i) -> SettingsActivity.launch(this, MenuTag.SETTINGS)) .show(); } @@ -1098,88 +1033,6 @@ public final class EmulationActivity extends AppCompatActivity implements ThemeP .show(); } - private void setIRSensitivity() - { - // IR settings always get saved per-game since WiimoteNew.ini is wiped upon reinstall. - File file = SettingsFile.getCustomGameSettingsFile(NativeLibrary.GetCurrentGameID()); - IniFile ini = new IniFile(file); - - int ir_pitch = ini.getInt(Settings.SECTION_CONTROLS, SettingsFile.KEY_WIIBIND_IR_PITCH, 20); - - DialogIrSensitivityBinding dialogBinding = - DialogIrSensitivityBinding.inflate(getLayoutInflater()); - - TextView text_slider_value_pitch = dialogBinding.textIrPitch; - TextView units = dialogBinding.textIrPitchUnits; - Slider slider_pitch = dialogBinding.sliderPitch; - - text_slider_value_pitch.setText(String.valueOf(ir_pitch)); - units.setText(getString(R.string.pitch)); - slider_pitch.setValueTo(100); - slider_pitch.setValue(ir_pitch); - slider_pitch.setStepSize(1); - slider_pitch.addOnChangeListener( - (slider, progress, fromUser) -> text_slider_value_pitch.setText( - String.valueOf((int) progress))); - - int ir_yaw = ini.getInt(Settings.SECTION_CONTROLS, SettingsFile.KEY_WIIBIND_IR_YAW, 25); - - TextView text_slider_value_yaw = dialogBinding.textIrYaw; - TextView units_yaw = dialogBinding.textIrYawUnits; - Slider seekbar_yaw = dialogBinding.sliderYaw; - - text_slider_value_yaw.setText(String.valueOf(ir_yaw)); - units_yaw.setText(getString(R.string.yaw)); - seekbar_yaw.setValueTo(100); - seekbar_yaw.setValue(ir_yaw); - seekbar_yaw.setStepSize(1); - seekbar_yaw.addOnChangeListener((slider, progress, fromUser) -> text_slider_value_yaw.setText( - String.valueOf((int) progress))); - - int ir_vertical_offset = - ini.getInt(Settings.SECTION_CONTROLS, SettingsFile.KEY_WIIBIND_IR_VERTICAL_OFFSET, 10); - - TextView text_slider_value_vertical_offset = dialogBinding.textIrVerticalOffset; - TextView units_vertical_offset = dialogBinding.textIrVerticalOffsetUnits; - Slider seekbar_vertical_offset = dialogBinding.sliderVerticalOffset; - - text_slider_value_vertical_offset.setText(String.valueOf(ir_vertical_offset)); - units_vertical_offset.setText(getString(R.string.vertical_offset)); - seekbar_vertical_offset.setValueTo(100); - seekbar_vertical_offset.setValue(ir_vertical_offset); - seekbar_vertical_offset.setStepSize(1); - seekbar_vertical_offset.addOnChangeListener( - (slider, progress, fromUser) -> text_slider_value_vertical_offset.setText( - String.valueOf((int) progress))); - - new MaterialAlertDialogBuilder(this) - .setTitle(getString(R.string.emulation_ir_sensitivity)) - .setView(dialogBinding.getRoot()) - .setPositiveButton(R.string.ok, (dialogInterface, i) -> - { - ini.setString(Settings.SECTION_CONTROLS, SettingsFile.KEY_WIIBIND_IR_PITCH, - text_slider_value_pitch.getText().toString()); - ini.setString(Settings.SECTION_CONTROLS, SettingsFile.KEY_WIIBIND_IR_YAW, - text_slider_value_yaw.getText().toString()); - ini.setString(Settings.SECTION_CONTROLS, SettingsFile.KEY_WIIBIND_IR_VERTICAL_OFFSET, - text_slider_value_vertical_offset.getText().toString()); - ini.save(file); - - NativeLibrary.ReloadWiimoteConfig(); - }) - .setNegativeButton(R.string.cancel, null) - .setNeutralButton(R.string.default_values, (dialogInterface, i) -> - { - ini.deleteKey(Settings.SECTION_CONTROLS, SettingsFile.KEY_WIIBIND_IR_PITCH); - ini.deleteKey(Settings.SECTION_CONTROLS, SettingsFile.KEY_WIIBIND_IR_YAW); - ini.deleteKey(Settings.SECTION_CONTROLS, SettingsFile.KEY_WIIBIND_IR_VERTICAL_OFFSET); - ini.save(file); - - NativeLibrary.ReloadWiimoteConfig(); - }) - .show(); - } - private void showSkylanderPortalSettings() { mSkylandersBinding = @@ -1212,7 +1065,7 @@ public final class EmulationActivity extends AppCompatActivity implements ThemeP new MaterialAlertDialogBuilder(this) .setTitle(getString(R.string.emulation_touch_overlay_reset)) .setPositiveButton(R.string.yes, - (dialogInterface, i) -> mEmulationFragment.resetInputOverlay()) + (dialogInterface, i) -> mEmulationFragment.resetInputOverlay(mSettings)) .setNegativeButton(R.string.cancel, null) .show(); } @@ -1266,41 +1119,15 @@ public final class EmulationActivity extends AppCompatActivity implements ThemeP @Override public boolean dispatchGenericMotionEvent(MotionEvent event) { - if (mMenuVisible) + if (!mMenuVisible) { - return false; - } - - if (((event.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) == 0)) - { - return super.dispatchGenericMotionEvent(event); - } - - // Don't attempt to do anything if we are disconnecting a device. - if (event.getActionMasked() == MotionEvent.ACTION_CANCEL) - return true; - - InputDevice input = event.getDevice(); - List motions = input.getMotionRanges(); - - for (InputDevice.MotionRange range : motions) - { - int axis = range.getAxis(); - float origValue = event.getAxisValue(axis); - float value = ControllerMappingHelper.scaleAxis(input, axis, origValue); - // If the input is still in the "flat" area, that means it's really zero. - // This is used to compensate for imprecision in joysticks. - if (Math.abs(value) > range.getFlat()) + if (ControllerInterface.dispatchGenericMotionEvent(event)) { - NativeLibrary.onGamePadMoveEvent(input.getDescriptor(), axis, value); - } - else - { - NativeLibrary.onGamePadMoveEvent(input.getDescriptor(), axis, 0.0f); + return true; } } - return true; + return super.dispatchGenericMotionEvent(event); } private void showSubMenu(SaveLoadStateFragment.SaveOrLoad saveOrLoad) @@ -1333,7 +1160,7 @@ public final class EmulationActivity extends AppCompatActivity implements ThemeP public void initInputPointer() { - mEmulationFragment.initInputPointer(); + mEmulationFragment.initInputPointer(mSettings); } @Override diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/MotionAlertDialog.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/MotionAlertDialog.java deleted file mode 100644 index b0316f94cc..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/MotionAlertDialog.java +++ /dev/null @@ -1,171 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.dolphinemu.dolphinemu.dialogs; - -import android.content.Context; -import android.view.InputDevice; -import android.view.KeyEvent; -import android.view.MotionEvent; - -import androidx.annotation.NonNull; -import androidx.appcompat.app.AlertDialog; - -import org.dolphinemu.dolphinemu.features.settings.model.view.InputBindingSetting; -import org.dolphinemu.dolphinemu.features.settings.ui.SettingsAdapter; -import org.dolphinemu.dolphinemu.utils.ControllerMappingHelper; -import org.dolphinemu.dolphinemu.utils.Log; - -import java.util.ArrayList; -import java.util.List; - -/** - * {@link AlertDialog} derivative that listens for - * motion events from controllers and joysticks. - */ -public final class MotionAlertDialog extends AlertDialog -{ - // The selected input preference - private final InputBindingSetting setting; - private final ArrayList mPreviousValues = new ArrayList<>(); - private int mPrevDeviceId = 0; - private boolean mWaitingForEvent = true; - private SettingsAdapter mAdapter; - - /** - * Constructor - * - * @param context The current {@link Context}. - * @param setting The Preference to show this dialog for. - */ - public MotionAlertDialog(Context context, InputBindingSetting setting, SettingsAdapter adapter) - { - super(context); - - this.setting = setting; - mAdapter = adapter; - } - - public boolean onKeyEvent(int keyCode, KeyEvent event) - { - Log.debug("[MotionAlertDialog] Received key event: " + event.getAction()); - if (event.getAction() == KeyEvent.ACTION_UP) - { - if (!ControllerMappingHelper.shouldKeyBeIgnored(event.getDevice(), keyCode)) - { - setting.onKeyInput(mAdapter.getSettings(), event); - dismiss(); - } - // Even if we ignore the key, we still consume it. Thus return true regardless. - return true; - } - return false; - } - - @Override - public boolean onKeyLongPress(int keyCode, @NonNull KeyEvent event) - { - // Intended for devices with no touchscreen or mouse - if (keyCode == KeyEvent.KEYCODE_BACK) - { - setting.clearValue(mAdapter.getSettings()); - dismiss(); - return true; - } - return super.onKeyLongPress(keyCode, event); - } - - @Override - public boolean dispatchKeyEvent(KeyEvent event) - { - // Handle this key if we care about it, otherwise pass it down the framework - return onKeyEvent(event.getKeyCode(), event) || super.dispatchKeyEvent(event); - } - - @Override - public boolean dispatchGenericMotionEvent(@NonNull MotionEvent event) - { - // Handle this event if we care about it, otherwise pass it down the framework - return onMotionEvent(event) || super.dispatchGenericMotionEvent(event); - } - - private boolean onMotionEvent(MotionEvent event) - { - if ((event.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) == 0) - return false; - if (event.getAction() != MotionEvent.ACTION_MOVE) - return false; - - InputDevice input = event.getDevice(); - - List motionRanges = input.getMotionRanges(); - - if (input.getId() != mPrevDeviceId) - { - mPreviousValues.clear(); - } - mPrevDeviceId = input.getId(); - boolean firstEvent = mPreviousValues.isEmpty(); - - int numMovedAxis = 0; - float axisMoveValue = 0.0f; - InputDevice.MotionRange lastMovedRange = null; - char lastMovedDir = '?'; - if (mWaitingForEvent) - { - for (int i = 0; i < motionRanges.size(); i++) - { - InputDevice.MotionRange range = motionRanges.get(i); - int axis = range.getAxis(); - float origValue = event.getAxisValue(axis); - float value = ControllerMappingHelper.scaleAxis(input, axis, origValue); - if (firstEvent) - { - mPreviousValues.add(value); - } - else - { - float previousValue = mPreviousValues.get(i); - - // Only handle the axes that are not neutral (more than 0.5) - // but ignore any axis that has a constant value (e.g. always 1) - if (Math.abs(value) > 0.5f && value != previousValue) - { - // It is common to have multiple axes with the same physical input. For example, - // shoulder butters are provided as both AXIS_LTRIGGER and AXIS_BRAKE. - // To handle this, we ignore an axis motion that's the exact same as a motion - // we already saw. This way, we ignore axes with two names, but catch the case - // where a joystick is moved in two directions. - // ref: bottom of https://developer.android.com/training/game-controllers/controller-input.html - if (value != axisMoveValue) - { - axisMoveValue = value; - numMovedAxis++; - lastMovedRange = range; - lastMovedDir = value < 0.0f ? '-' : '+'; - } - } - // Special case for d-pads (axis value jumps between 0 and 1 without any values - // in between). Without this, the user would need to press the d-pad twice - // due to the first press being caught by the "if (firstEvent)" case further up. - else if (Math.abs(value) < 0.25f && Math.abs(previousValue) > 0.75f) - { - numMovedAxis++; - lastMovedRange = range; - lastMovedDir = previousValue < 0.0f ? '-' : '+'; - } - } - - mPreviousValues.set(i, value); - } - - // If only one axis moved, that's the winner. - if (numMovedAxis == 1) - { - mWaitingForEvent = false; - setting.onMotionInput(mAdapter.getSettings(), input, lastMovedRange, lastMovedDir); - dismiss(); - } - } - return true; - } -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/ControlGroupEnabledSetting.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/ControlGroupEnabledSetting.java new file mode 100644 index 0000000000..95aa080429 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/ControlGroupEnabledSetting.java @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.input.model; + +import org.dolphinemu.dolphinemu.features.input.model.controlleremu.ControlGroup; +import org.dolphinemu.dolphinemu.features.settings.model.AbstractBooleanSetting; +import org.dolphinemu.dolphinemu.features.settings.model.Settings; + +public class ControlGroupEnabledSetting implements AbstractBooleanSetting +{ + private final ControlGroup mControlGroup; + + public ControlGroupEnabledSetting(ControlGroup controlGroup) + { + mControlGroup = controlGroup; + } + + @Override + public boolean getBoolean(Settings settings) + { + return mControlGroup.getEnabled(); + } + + @Override + public void setBoolean(Settings settings, boolean newValue) + { + mControlGroup.setEnabled(newValue); + } + + @Override + public boolean isOverridden(Settings settings) + { + return false; + } + + @Override + public boolean isRuntimeEditable() + { + return true; + } + + @Override + public boolean delete(Settings settings) + { + boolean newValue = mControlGroup.getDefaultEnabledValue() != ControlGroup.DEFAULT_ENABLED_NO; + mControlGroup.setEnabled(newValue); + + return true; + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/ControllerInterface.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/ControllerInterface.java new file mode 100644 index 0000000000..732ed97a7c --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/ControllerInterface.java @@ -0,0 +1,178 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.input.model; + +import android.content.Context; +import android.hardware.input.InputManager; +import android.os.Build; +import android.os.Handler; +import android.os.VibrationEffect; +import android.os.Vibrator; +import android.os.VibratorManager; +import android.view.InputDevice; +import android.view.KeyEvent; +import android.view.MotionEvent; + +import androidx.annotation.Keep; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import org.dolphinemu.dolphinemu.DolphinApplication; +import org.dolphinemu.dolphinemu.utils.LooperThread; + +/** + * This class interfaces with the native ControllerInterface, + * which is where the emulator core gets inputs from. + */ +public final class ControllerInterface +{ + private static final class InputDeviceListener implements InputManager.InputDeviceListener + { + @Override + public void onInputDeviceAdded(int deviceId) + { + // Simple implementation for now. We could do something fancier if we wanted to. + refreshDevices(); + } + + @Override + public void onInputDeviceRemoved(int deviceId) + { + // Simple implementation for now. We could do something fancier if we wanted to. + refreshDevices(); + } + + @Override + public void onInputDeviceChanged(int deviceId) + { + // Simple implementation for now. We could do something fancier if we wanted to. + refreshDevices(); + } + } + + private static InputDeviceListener mInputDeviceListener; + private static LooperThread mLooperThread; + + /** + * Activities which want to pass on inputs to native code + * should call this in their own dispatchKeyEvent method. + * + * @return true if the emulator core seems to be interested in this event. + * false if the event should be passed on to the default dispatchKeyEvent. + */ + public static native boolean dispatchKeyEvent(KeyEvent event); + + /** + * Activities which want to pass on inputs to native code + * should call this in their own dispatchGenericMotionEvent method. + * + * @return true if the emulator core seems to be interested in this event. + * false if the event should be passed on to the default dispatchGenericMotionEvent. + */ + public static native boolean dispatchGenericMotionEvent(MotionEvent event); + + /** + * {@link DolphinSensorEventListener} calls this for each axis of a received SensorEvent. + * + * @return true if the emulator core seems to be interested in this event. + * false if the sensor can be suspended to save battery. + */ + public static native boolean dispatchSensorEvent(String deviceQualifier, String axisName, + float value); + + /** + * Called when a sensor is suspended or unsuspended. + * + * @param deviceQualifier A string used by native code for uniquely identifying devices. + * @param axisNames The name of all axes for the sensor. + * @param suspended Whether the sensor is now suspended. + */ + public static native void notifySensorSuspendedState(String deviceQualifier, String[] axisNames, + boolean suspended); + + /** + * Rescans for input devices. + */ + public static native void refreshDevices(); + + public static native String[] getAllDeviceStrings(); + + @Nullable + public static native CoreDevice getDevice(String deviceString); + + @Keep + private static void registerInputDeviceListener() + { + if (mLooperThread == null) + { + mLooperThread = new LooperThread("Hotplug thread"); + mLooperThread.start(); + } + + if (mInputDeviceListener == null) + { + InputManager im = (InputManager) + DolphinApplication.getAppContext().getSystemService(Context.INPUT_SERVICE); + + mInputDeviceListener = new InputDeviceListener(); + im.registerInputDeviceListener(mInputDeviceListener, new Handler(mLooperThread.getLooper())); + } + } + + @Keep + private static void unregisterInputDeviceListener() + { + if (mInputDeviceListener != null) + { + InputManager im = (InputManager) + DolphinApplication.getAppContext().getSystemService(Context.INPUT_SERVICE); + + im.unregisterInputDeviceListener(mInputDeviceListener); + mInputDeviceListener = null; + } + } + + @Keep @NonNull + private static DolphinVibratorManager getVibratorManager(InputDevice device) + { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) + { + return new DolphinVibratorManagerPassthrough(device.getVibratorManager()); + } + else + { + return new DolphinVibratorManagerCompat(device.getVibrator()); + } + } + + @Keep @NonNull + private static DolphinVibratorManager getSystemVibratorManager() + { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) + { + VibratorManager vibratorManager = (VibratorManager) + DolphinApplication.getAppContext().getSystemService(Context.VIBRATOR_MANAGER_SERVICE); + + if (vibratorManager != null) + return new DolphinVibratorManagerPassthrough(vibratorManager); + } + + Vibrator vibrator = (Vibrator) + DolphinApplication.getAppContext().getSystemService(Context.VIBRATOR_SERVICE); + + return new DolphinVibratorManagerCompat(vibrator); + } + + @Keep + private static void vibrate(@NonNull Vibrator vibrator) + { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + { + vibrator.vibrate(VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE)); + } + else + { + vibrator.vibrate(100); + } + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/CoreDevice.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/CoreDevice.java new file mode 100644 index 0000000000..b69e9a0236 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/CoreDevice.java @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.input.model; + +import androidx.annotation.Keep; + +/** + * Represents a C++ ciface::Core::Device. + */ +public final class CoreDevice +{ + /** + * Represents a C++ ciface::Core::Device::Control. + * + * This class is non-static to ensure that the CoreDevice parent does not get garbage collected + * while a Control is still accessible. (CoreDevice's finalizer may delete the native controls.) + */ + @SuppressWarnings("InnerClassMayBeStatic") + public final class Control + { + @Keep + private final long mPointer; + + @Keep + private Control(long pointer) + { + mPointer = pointer; + } + + public native String getName(); + } + + @Keep + private final long mPointer; + + @Keep + private CoreDevice(long pointer) + { + mPointer = pointer; + } + + @Override + protected native void finalize(); + + public native Control[] getInputs(); + + public native Control[] getOutputs(); +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinSensorEventListener.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinSensorEventListener.java new file mode 100644 index 0000000000..78d0e4785d --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinSensorEventListener.java @@ -0,0 +1,440 @@ +package org.dolphinemu.dolphinemu.features.input.model; + +import android.content.Context; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.os.Build; +import android.view.InputDevice; +import android.view.Surface; + +import androidx.annotation.Keep; + +import org.dolphinemu.dolphinemu.DolphinApplication; +import org.dolphinemu.dolphinemu.utils.Log; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class DolphinSensorEventListener implements SensorEventListener +{ + // Set of three axes. Creates a negative companion to each axis, and corrects for device rotation. + private static final int AXIS_SET_TYPE_DEVICE_COORDINATES = 0; + // Set of three axes. Creates a negative companion to each axis. + private static final int AXIS_SET_TYPE_OTHER_COORDINATES = 1; + + private static class AxisSetDetails + { + public final int firstAxisOfSet; + public final int axisSetType; + + public AxisSetDetails(int firstAxisOfSet, int axisSetType) + { + this.firstAxisOfSet = firstAxisOfSet; + this.axisSetType = axisSetType; + } + } + + private static class SensorDetails + { + public final int sensorType; + public final String[] axisNames; + public final AxisSetDetails[] axisSetDetails; + public boolean isSuspended = true; + + public SensorDetails(int sensorType, String[] axisNames, AxisSetDetails[] axisSetDetails) + { + this.sensorType = sensorType; + this.axisNames = axisNames; + this.axisSetDetails = axisSetDetails; + } + } + + private static int sDeviceRotation = Surface.ROTATION_0; + + private final SensorManager mSensorManager; + + private final HashMap mSensorDetails = new HashMap<>(); + + private final boolean mRotateCoordinatesForScreenOrientation; + + private String mDeviceQualifier = ""; + + // The fastest sampling rate Android lets us use without declaring the HIGH_SAMPLING_RATE_SENSORS + // permission is 200 Hz. This is also the sampling rate of a Wii Remote, so it fits us perfectly. + private static final int SAMPLING_PERIOD_US = 1000000 / 200; + + @Keep + public DolphinSensorEventListener() + { + mSensorManager = (SensorManager) + DolphinApplication.getAppContext().getSystemService(Context.SENSOR_SERVICE); + mRotateCoordinatesForScreenOrientation = true; + + addSensors(); + } + + @Keep + public DolphinSensorEventListener(InputDevice inputDevice) + { + mRotateCoordinatesForScreenOrientation = false; + + if (Build.VERSION.SDK_INT >= 31) + { + mSensorManager = inputDevice.getSensorManager(); + + // TODO: There is a bug where after suspending sensors, onSensorChanged can get called for + // a sensor that we never registered as a listener for. The way our code is currently written, + // this causes a NullPointerException, but if we checked for null we would instead have the + // problem of being spammed with onSensorChanged calls even though the sensor shouldn't be + // enabled. For now, let's comment out the ability to use InputDevice sensors. + + //addSensors(); + } + else + { + mSensorManager = null; + } + } + + private void addSensors() + { + tryAddSensor(Sensor.TYPE_ACCELEROMETER, new String[]{"Accel Right", "Accel Left", + "Accel Forward", "Accel Backward", "Accel Up", "Accel Down"}, + new AxisSetDetails[]{new AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES)}); + + tryAddSensor(Sensor.TYPE_GYROSCOPE, new String[]{"Gyro Pitch Up", "Gyro Pitch Down", + "Gyro Roll Right", "Gyro Roll Left", "Gyro Yaw Left", "Gyro Yaw Right"}, + new AxisSetDetails[]{new AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES)}); + + tryAddSensor(Sensor.TYPE_LIGHT, "Light"); + + tryAddSensor(Sensor.TYPE_PRESSURE, "Pressure"); + + tryAddSensor(Sensor.TYPE_TEMPERATURE, "Device Temperature"); + + tryAddSensor(Sensor.TYPE_PROXIMITY, "Proximity"); + + tryAddSensor(Sensor.TYPE_GRAVITY, new String[]{"Gravity Right", "Gravity Left", + "Gravity Forward", "Gravity Backward", "Gravity Up", "Gravity Down"}, + new AxisSetDetails[]{new AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES)}); + + tryAddSensor(Sensor.TYPE_LINEAR_ACCELERATION, + new String[]{"Linear Acceleration Right", "Linear Acceleration Left", + "Linear Acceleration Forward", "Linear Acceleration Backward", + "Linear Acceleration Up", "Linear Acceleration Down"}, + new AxisSetDetails[]{new AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES)}); + + // The values provided by this sensor can be interpreted as an Euler vector or a quaternion. + // The directions of X and Y are flipped to match the Wii Remote coordinate system. + tryAddSensor(Sensor.TYPE_ROTATION_VECTOR, + new String[]{"Rotation Vector X-", "Rotation Vector X+", "Rotation Vector Y-", + "Rotation Vector Y+", "Rotation Vector Z+", + "Rotation Vector Z-", "Rotation Vector R", "Rotation Vector Heading Accuracy"}, + new AxisSetDetails[]{new AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES)}); + + tryAddSensor(Sensor.TYPE_RELATIVE_HUMIDITY, "Relative Humidity"); + + tryAddSensor(Sensor.TYPE_AMBIENT_TEMPERATURE, "Ambient Temperature"); + + // The values provided by this sensor can be interpreted as an Euler vector or a quaternion. + // The directions of X and Y are flipped to match the Wii Remote coordinate system. + tryAddSensor(Sensor.TYPE_GAME_ROTATION_VECTOR, + new String[]{"Game Rotation Vector X-", "Game Rotation Vector X+", + "Game Rotation Vector Y-", "Game Rotation Vector Y+", "Game Rotation Vector Z+", + "Game Rotation Vector Z-", "Game Rotation Vector R"}, + new AxisSetDetails[]{new AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES)}); + + tryAddSensor(Sensor.TYPE_GYROSCOPE_UNCALIBRATED, + new String[]{"Gyro Uncalibrated Pitch Up", "Gyro Uncalibrated Pitch Down", + "Gyro Uncalibrated Roll Right", "Gyro Uncalibrated Roll Left", + "Gyro Uncalibrated Yaw Left", "Gyro Uncalibrated Yaw Right", + "Gyro Drift Pitch Up", "Gyro Drift Pitch Down", "Gyro Drift Roll Right", + "Gyro Drift Roll Left", "Gyro Drift Yaw Left", "Gyro Drift Yaw Right"}, + new AxisSetDetails[]{new AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES), + new AxisSetDetails(3, AXIS_SET_TYPE_DEVICE_COORDINATES)}); + + tryAddSensor(Sensor.TYPE_HEART_RATE, "Heart Rate"); + + if (Build.VERSION.SDK_INT >= 24) + { + tryAddSensor(Sensor.TYPE_HEART_BEAT, "Heart Beat"); + } + + if (Build.VERSION.SDK_INT >= 26) + { + tryAddSensor(Sensor.TYPE_ACCELEROMETER_UNCALIBRATED, + new String[]{"Accel Uncalibrated Right", "Accel Uncalibrated Left", + "Accel Uncalibrated Forward", "Accel Uncalibrated Backward", + "Accel Uncalibrated Up", "Accel Uncalibrated Down", + "Accel Bias Right", "Accel Bias Left", "Accel Bias Forward", + "Accel Bias Backward", "Accel Bias Up", "Accel Bias Down"}, + new AxisSetDetails[]{new AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES), + new AxisSetDetails(3, AXIS_SET_TYPE_DEVICE_COORDINATES)}); + } + + if (Build.VERSION.SDK_INT >= 30) + { + tryAddSensor(Sensor.TYPE_HINGE_ANGLE, "Hinge Angle"); + } + + if (Build.VERSION.SDK_INT >= 33) + { + // The values provided by this sensor can be interpreted as an Euler vector. + // The directions of X and Y are flipped to match the Wii Remote coordinate system. + tryAddSensor(Sensor.TYPE_HEAD_TRACKER, + new String[]{"Head Rotation Vector X-", "Head Rotation Vector X+", + "Head Rotation Vector Y-", "Head Rotation Vector Y+", + "Head Rotation Vector Z+", "Head Rotation Vector Z-", + "Head Pitch Up", "Head Pitch Down", "Head Roll Right", "Head Roll Left", + "Head Yaw Left", "Head Yaw Right"}, + new AxisSetDetails[]{new AxisSetDetails(0, AXIS_SET_TYPE_OTHER_COORDINATES), + new AxisSetDetails(3, AXIS_SET_TYPE_OTHER_COORDINATES)}); + + tryAddSensor(Sensor.TYPE_HEADING, new String[]{"Heading", "Heading Accuracy"}, + new AxisSetDetails[]{}); + } + } + + private void tryAddSensor(int sensorType, String axisName) + { + tryAddSensor(sensorType, new String[]{axisName}, new AxisSetDetails[]{}); + } + + private void tryAddSensor(int sensorType, String[] axisNames, AxisSetDetails[] axisSetDetails) + { + Sensor sensor = mSensorManager.getDefaultSensor(sensorType); + if (sensor != null) + { + mSensorDetails.put(sensor, new SensorDetails(sensorType, axisNames, axisSetDetails)); + } + } + + @Override + public void onSensorChanged(SensorEvent sensorEvent) + { + final SensorDetails sensorDetails = mSensorDetails.get(sensorEvent.sensor); + + final float[] values = sensorEvent.values; + final String[] axisNames = sensorDetails.axisNames; + final AxisSetDetails[] axisSetDetails = sensorDetails.axisSetDetails; + + int eventAxisIndex = 0; + int detailsAxisIndex = 0; + int detailsAxisSetIndex = 0; + boolean keepSensorAlive = false; + while (eventAxisIndex < values.length && detailsAxisIndex < axisNames.length) + { + if (detailsAxisSetIndex < axisSetDetails.length && + axisSetDetails[detailsAxisSetIndex].firstAxisOfSet == eventAxisIndex) + { + int rotation = Surface.ROTATION_0; + if (mRotateCoordinatesForScreenOrientation && + axisSetDetails[detailsAxisSetIndex].axisSetType == AXIS_SET_TYPE_DEVICE_COORDINATES) + { + rotation = sDeviceRotation; + } + + float x, y; + switch (rotation) + { + default: + case Surface.ROTATION_0: + x = values[eventAxisIndex]; + y = values[eventAxisIndex + 1]; + break; + case Surface.ROTATION_90: + x = -values[eventAxisIndex + 1]; + y = values[eventAxisIndex]; + break; + case Surface.ROTATION_180: + x = -values[eventAxisIndex]; + y = -values[eventAxisIndex + 1]; + break; + case Surface.ROTATION_270: + x = values[eventAxisIndex + 1]; + y = -values[eventAxisIndex]; + break; + } + + float z = values[eventAxisIndex + 2]; + + keepSensorAlive |= ControllerInterface.dispatchSensorEvent(mDeviceQualifier, + axisNames[detailsAxisIndex], x); + keepSensorAlive |= ControllerInterface.dispatchSensorEvent(mDeviceQualifier, + axisNames[detailsAxisIndex + 1], x); + keepSensorAlive |= ControllerInterface.dispatchSensorEvent(mDeviceQualifier, + axisNames[detailsAxisIndex + 2], y); + keepSensorAlive |= ControllerInterface.dispatchSensorEvent(mDeviceQualifier, + axisNames[detailsAxisIndex + 3], y); + keepSensorAlive |= ControllerInterface.dispatchSensorEvent(mDeviceQualifier, + axisNames[detailsAxisIndex + 4], z); + keepSensorAlive |= ControllerInterface.dispatchSensorEvent(mDeviceQualifier, + axisNames[detailsAxisIndex + 5], z); + + eventAxisIndex += 3; + detailsAxisIndex += 6; + detailsAxisSetIndex++; + } + else + { + keepSensorAlive |= ControllerInterface.dispatchSensorEvent(mDeviceQualifier, + axisNames[detailsAxisIndex], values[eventAxisIndex]); + + eventAxisIndex++; + detailsAxisIndex++; + } + } + + if (!keepSensorAlive) + { + setSensorSuspended(sensorEvent.sensor, sensorDetails, true); + } + } + + @Override + public void onAccuracyChanged(Sensor sensor, int i) + { + // We don't care about this + } + + /** + * The device qualifier set here will be passed on to native code, + * for the purpose of letting native code identify which device this object belongs to. + */ + @Keep + public void setDeviceQualifier(String deviceQualifier) + { + mDeviceQualifier = deviceQualifier; + } + + /** + * If a sensor has been suspended to save battery, this unsuspends it. + * If the sensor isn't currently suspended, nothing happens. + * + * @param axisName The name of any of the sensor's axes. + */ + @Keep + public void requestUnsuspendSensor(String axisName) + { + for (Map.Entry entry : mSensorDetails.entrySet()) + { + if (Arrays.asList(entry.getValue().axisNames).contains(axisName)) + { + setSensorSuspended(entry.getKey(), entry.getValue(), false); + } + } + } + + private void setSensorSuspended(Sensor sensor, SensorDetails sensorDetails, boolean suspend) + { + boolean changeOccurred = false; + + synchronized (sensorDetails) + { + if (sensorDetails.isSuspended != suspend) + { + ControllerInterface.notifySensorSuspendedState(mDeviceQualifier, sensorDetails.axisNames, + suspend); + + if (suspend) + mSensorManager.unregisterListener(this, sensor); + else + mSensorManager.registerListener(this, sensor, SAMPLING_PERIOD_US); + + sensorDetails.isSuspended = suspend; + + changeOccurred = true; + } + } + + if (changeOccurred) + { + Log.info((suspend ? "Suspended sensor " : "Unsuspended sensor ") + sensor.getName()); + } + } + + @Keep + public String[] getAxisNames() + { + ArrayList axisNames = new ArrayList<>(); + + for (SensorDetails sensorDetails : getSensorDetailsSorted()) + { + Collections.addAll(axisNames, sensorDetails.axisNames); + } + + return axisNames.toArray(new String[]{}); + } + + @Keep + public boolean[] getNegativeAxes() + { + ArrayList negativeAxes = new ArrayList<>(); + + for (SensorDetails sensorDetails : getSensorDetailsSorted()) + { + int eventAxisIndex = 0; + int detailsAxisIndex = 0; + int detailsAxisSetIndex = 0; + while (detailsAxisIndex < sensorDetails.axisNames.length) + { + if (detailsAxisSetIndex < sensorDetails.axisSetDetails.length && + sensorDetails.axisSetDetails[detailsAxisSetIndex].firstAxisOfSet == eventAxisIndex) + { + negativeAxes.add(false); + negativeAxes.add(true); + negativeAxes.add(false); + negativeAxes.add(true); + negativeAxes.add(false); + negativeAxes.add(true); + + eventAxisIndex += 3; + detailsAxisIndex += 6; + detailsAxisSetIndex++; + } + else + { + negativeAxes.add(false); + + eventAxisIndex++; + detailsAxisIndex++; + } + } + } + + boolean[] result = new boolean[negativeAxes.size()]; + for (int i = 0; i < result.length; i++) + { + result[i] = negativeAxes.get(i); + } + + return result; + } + + private List getSensorDetailsSorted() + { + ArrayList sensorDetails = new ArrayList<>(mSensorDetails.values()); + Collections.sort(sensorDetails, Comparator.comparingInt(s -> s.sensorType)); + return sensorDetails; + } + + /** + * Should be called when an activity or other component that uses sensor events is resumed. + * + * Sensor events that contain device coordinates will have the coordinates rotated by the value + * passed to this function. + * + * @param deviceRotation The current rotation of the device (i.e. rotation of the default display) + */ + public static void setDeviceRotation(int deviceRotation) + { + sDeviceRotation = deviceRotation; + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinVibratorManager.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinVibratorManager.java new file mode 100644 index 0000000000..abc04d969d --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinVibratorManager.java @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.input.model; + +import android.os.Vibrator; + +import androidx.annotation.Keep; +import androidx.annotation.NonNull; + +/** + * A wrapper around {@link android.os.VibratorManager}, for backwards compatibility. + */ +public interface DolphinVibratorManager +{ + @Keep @NonNull + Vibrator getVibrator(int vibratorId); + + @Keep @NonNull + int[] getVibratorIds(); +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinVibratorManagerCompat.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinVibratorManagerCompat.java new file mode 100644 index 0000000000..40c4fa95d1 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinVibratorManagerCompat.java @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.input.model; + +import android.os.Vibrator; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +public final class DolphinVibratorManagerCompat implements DolphinVibratorManager +{ + private final Vibrator mVibrator; + private final int[] mIds; + + public DolphinVibratorManagerCompat(@Nullable Vibrator vibrator) + { + mVibrator = vibrator; + mIds = vibrator != null && vibrator.hasVibrator() ? new int[]{0} : new int[]{}; + } + + @Override @NonNull + public Vibrator getVibrator(int vibratorId) + { + if (vibratorId > mIds.length) + throw new IndexOutOfBoundsException(); + + return mVibrator; + } + + @Override @NonNull + public int[] getVibratorIds() + { + return mIds; + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinVibratorManagerPassthrough.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinVibratorManagerPassthrough.java new file mode 100644 index 0000000000..2ca747f54f --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinVibratorManagerPassthrough.java @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.input.model; + +import android.os.Build; +import android.os.Vibrator; +import android.os.VibratorManager; + +import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; + +@RequiresApi(api = Build.VERSION_CODES.S) +public final class DolphinVibratorManagerPassthrough implements DolphinVibratorManager +{ + private final VibratorManager mVibratorManager; + + public DolphinVibratorManagerPassthrough(@NonNull VibratorManager vibratorManager) + { + mVibratorManager = vibratorManager; + } + + @Override @NonNull + public Vibrator getVibrator(int vibratorId) + { + return mVibratorManager.getVibrator(vibratorId); + } + + @Override @NonNull + public int[] getVibratorIds() + { + return mVibratorManager.getVibratorIds(); + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/InputMappingBooleanSetting.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/InputMappingBooleanSetting.java new file mode 100644 index 0000000000..7241d6e125 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/InputMappingBooleanSetting.java @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.input.model; + +import org.dolphinemu.dolphinemu.features.input.model.controlleremu.NumericSetting; +import org.dolphinemu.dolphinemu.features.settings.model.AbstractBooleanSetting; +import org.dolphinemu.dolphinemu.features.settings.model.Settings; + +public class InputMappingBooleanSetting implements AbstractBooleanSetting +{ + private final NumericSetting mNumericSetting; + + public InputMappingBooleanSetting(NumericSetting numericSetting) + { + mNumericSetting = numericSetting; + } + + @Override + public boolean getBoolean(Settings settings) + { + return mNumericSetting.getBooleanValue(); + } + + @Override + public void setBoolean(Settings settings, boolean newValue) + { + mNumericSetting.setBooleanValue(newValue); + } + + @Override + public boolean isOverridden(Settings settings) + { + return false; + } + + @Override + public boolean isRuntimeEditable() + { + return true; + } + + @Override + public boolean delete(Settings settings) + { + mNumericSetting.setBooleanValue(mNumericSetting.getBooleanDefaultValue()); + return true; + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/InputMappingDoubleSetting.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/InputMappingDoubleSetting.java new file mode 100644 index 0000000000..e84b61a045 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/InputMappingDoubleSetting.java @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.input.model; + +import org.dolphinemu.dolphinemu.features.input.model.controlleremu.NumericSetting; +import org.dolphinemu.dolphinemu.features.settings.model.AbstractFloatSetting; +import org.dolphinemu.dolphinemu.features.settings.model.Settings; + +// Yes, floats are not the same thing as doubles... They're close enough, though +public class InputMappingDoubleSetting implements AbstractFloatSetting +{ + private final NumericSetting mNumericSetting; + + public InputMappingDoubleSetting(NumericSetting numericSetting) + { + mNumericSetting = numericSetting; + } + + @Override + public float getFloat(Settings settings) + { + return (float) mNumericSetting.getDoubleValue(); + } + + @Override + public void setFloat(Settings settings, float newValue) + { + mNumericSetting.setDoubleValue(newValue); + } + + @Override + public boolean isOverridden(Settings settings) + { + return false; + } + + @Override + public boolean isRuntimeEditable() + { + return true; + } + + @Override + public boolean delete(Settings settings) + { + mNumericSetting.setDoubleValue(mNumericSetting.getDoubleDefaultValue()); + return true; + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/InputMappingIntSetting.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/InputMappingIntSetting.java new file mode 100644 index 0000000000..989f2b7cde --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/InputMappingIntSetting.java @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.input.model; + +import org.dolphinemu.dolphinemu.features.input.model.controlleremu.NumericSetting; +import org.dolphinemu.dolphinemu.features.settings.model.AbstractIntSetting; +import org.dolphinemu.dolphinemu.features.settings.model.Settings; + +public class InputMappingIntSetting implements AbstractIntSetting +{ + private final NumericSetting mNumericSetting; + + public InputMappingIntSetting(NumericSetting numericSetting) + { + mNumericSetting = numericSetting; + } + + @Override + public int getInt(Settings settings) + { + return mNumericSetting.getIntValue(); + } + + @Override + public void setInt(Settings settings, int newValue) + { + mNumericSetting.setIntValue(newValue); + } + + @Override + public boolean isOverridden(Settings settings) + { + return false; + } + + @Override + public boolean isRuntimeEditable() + { + return true; + } + + @Override + public boolean delete(Settings settings) + { + mNumericSetting.setIntValue(mNumericSetting.getIntDefaultValue()); + return true; + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/MappingCommon.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/MappingCommon.java new file mode 100644 index 0000000000..5e4c0076ea --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/MappingCommon.java @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.input.model; + +import androidx.annotation.NonNull; + +import org.dolphinemu.dolphinemu.features.input.model.controlleremu.EmulatedController; + +public final class MappingCommon +{ + private MappingCommon() + { + } + + /** + * Waits until the user presses one or more inputs or until a timeout, + * then returns the pressed inputs. + * + * When this is being called, a separate thread must be calling ControllerInterface's + * dispatchKeyEvent and dispatchGenericMotionEvent, otherwise no inputs will be registered. + * + * @param controller The device to detect inputs from. + * @param allDevices Whether to also detect inputs from devices other than the specified one. + * @return The input(s) pressed by the user in the form of an InputCommon expression, + * or an empty string if there were no inputs. + */ + public static native String detectInput(@NonNull EmulatedController controller, + boolean allDevices); + + public static native String getExpressionForControl(String control, String device, + String defaultDevice); + + public static native void save(); +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/Control.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/Control.java new file mode 100644 index 0000000000..6033e17e37 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/Control.java @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.input.model.controlleremu; + +import androidx.annotation.Keep; + +/** + * Represents a C++ ControllerEmu::Control. + * + * The lifetime of this class is managed by C++ code. Calling methods on it after it's destroyed + * in C++ is undefined behavior! + */ +public class Control +{ + @Keep + private final long mPointer; + + @Keep + private Control(long pointer) + { + mPointer = pointer; + } + + public native String getUiName(); + + public native ControlReference getControlReference(); +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/ControlGroup.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/ControlGroup.java new file mode 100644 index 0000000000..1bafd4a1f2 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/ControlGroup.java @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.input.model.controlleremu; + +import androidx.annotation.Keep; + +/** + * Represents a C++ ControllerEmu::ControlGroup. + * + * The lifetime of this class is managed by C++ code. Calling methods on it after it's destroyed + * in C++ is undefined behavior! + */ +public class ControlGroup +{ + public static final int TYPE_OTHER = 0; + public static final int TYPE_STICK = 1; + public static final int TYPE_MIXED_TRIGGERS = 2; + public static final int TYPE_BUTTONS = 3; + public static final int TYPE_FORCE = 4; + public static final int TYPE_ATTACHMENTS = 5; + public static final int TYPE_TILT = 6; + public static final int TYPE_CURSOR = 7; + public static final int TYPE_TRIGGERS = 8; + public static final int TYPE_SLIDER = 9; + public static final int TYPE_SHAKE = 10; + public static final int TYPE_IMU_ACCELEROMETER = 11; + public static final int TYPE_IMU_GYROSCOPE = 12; + public static final int TYPE_IMU_CURSOR = 13; + + public static final int DEFAULT_ENABLED_ALWAYS = 0; + public static final int DEFAULT_ENABLED_YES = 1; + public static final int DEFAULT_ENABLED_NO = 2; + + @Keep + private final long mPointer; + + @Keep + private ControlGroup(long pointer) + { + mPointer = pointer; + } + + public native String getUiName(); + + public native int getGroupType(); + + public native int getDefaultEnabledValue(); + + public native boolean getEnabled(); + + public native void setEnabled(boolean value); + + public native int getControlCount(); + + public native Control getControl(int i); + + public native int getNumericSettingCount(); + + public native NumericSetting getNumericSetting(int i); + + /** + * If getGroupType returns TYPE_ATTACHMENTS, this returns the attachment selection setting. + * Otherwise, undefined behavior! + */ + public native NumericSetting getAttachmentSetting(); +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/ControlReference.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/ControlReference.java new file mode 100644 index 0000000000..66fd9fa02d --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/ControlReference.java @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.input.model.controlleremu; + +import androidx.annotation.Keep; +import androidx.annotation.Nullable; + +/** + * Represents a C++ ControlReference. + * + * The lifetime of this class is managed by C++ code. Calling methods on it after it's destroyed + * in C++ is undefined behavior! + */ +public class ControlReference +{ + @Keep + private final long mPointer; + + @Keep + private ControlReference(long pointer) + { + mPointer = pointer; + } + + public native double getState(); + + public native String getExpression(); + + /** + * Sets the expression for this control reference. + * + * @param expr The new expression + * @return null on success, a human-readable error on failure + */ + @Nullable + public native String setExpression(String expr); + + public native boolean isInput(); +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/EmulatedController.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/EmulatedController.java new file mode 100644 index 0000000000..39645ffbba --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/EmulatedController.java @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.input.model.controlleremu; + +import androidx.annotation.Keep; + +/** + * Represents a C++ ControllerEmu::EmulatedController. + * + * The lifetime of this class is managed by C++ code. Calling methods on it after it's destroyed + * in C++ is undefined behavior! + */ +public class EmulatedController +{ + @Keep + private final long mPointer; + + @Keep + private EmulatedController(long pointer) + { + mPointer = pointer; + } + + public native String getDefaultDevice(); + + public native void setDefaultDevice(String device); + + public native int getGroupCount(); + + public native ControlGroup getGroup(int index); + + public native void updateSingleControlReference(ControlReference controlReference); + + public native void loadDefaultSettings(); + + public native void clearSettings(); + + public native void loadProfile(String path); + + public native void saveProfile(String path); + + public static native EmulatedController getGcPad(int controllerIndex); + + public static native EmulatedController getWiimote(int controllerIndex); + + public static native EmulatedController getWiimoteAttachment(int controllerIndex, + int attachmentIndex); + + public static native int getSelectedWiimoteAttachment(int controllerIndex); + + public static native NumericSetting getSidewaysWiimoteSetting(int controllerIndex); +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/NumericSetting.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/NumericSetting.java new file mode 100644 index 0000000000..bcb9dce181 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/NumericSetting.java @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.input.model.controlleremu; + +import androidx.annotation.Keep; + +/** + * Represents a C++ ControllerEmu::NumericSetting. + * + * The lifetime of this class is managed by C++ code. Calling methods on it after it's destroyed + * in C++ is undefined behavior! + */ +public class NumericSetting +{ + public static final int TYPE_INT = 0; + public static final int TYPE_DOUBLE = 1; + public static final int TYPE_BOOLEAN = 2; + + @Keep + private final long mPointer; + + @Keep + private NumericSetting(long pointer) + { + mPointer = pointer; + } + + /** + * @return The name used in the UI. + */ + public native String getUiName(); + + /** + * @return A string applied to the number in the UI (unit of measure). + */ + public native String getUiSuffix(); + + /** + * @return Detailed description of the setting. + */ + public native String getUiDescription(); + + /** + * @return TYPE_INT, TYPE_DOUBLE or TYPE_BOOLEAN + */ + public native int getType(); + + public native ControlReference getControlReference(); + + /** + * If the type is TYPE_INT, gets the current value. Otherwise, undefined behavior! + */ + public native int getIntValue(); + + /** + * If the type is TYPE_INT, sets the current value. Otherwise, undefined behavior! + */ + public native void setIntValue(int value); + + /** + * If the type is TYPE_INT, gets the default value. Otherwise, undefined behavior! + */ + public native int getIntDefaultValue(); + + /** + * If the type is TYPE_DOUBLE, gets the current value. Otherwise, undefined behavior! + */ + public native double getDoubleValue(); + + /** + * If the type is TYPE_DOUBLE, sets the current value. Otherwise, undefined behavior! + */ + public native void setDoubleValue(double value); + + /** + * If the type is TYPE_DOUBLE, gets the default value. Otherwise, undefined behavior! + */ + public native double getDoubleDefaultValue(); + + /** + * If the type is TYPE_DOUBLE, returns the minimum valid value. Otherwise, undefined behavior! + */ + public native double getDoubleMin(); + + /** + * If the type is TYPE_DOUBLE, returns the maximum valid value. Otherwise, undefined behavior! + */ + public native double getDoubleMax(); + + /** + * If the type is TYPE_BOOLEAN, gets the current value. Otherwise, undefined behavior! + */ + public native boolean getBooleanValue(); + + /** + * If the type is TYPE_BOOLEAN, sets the current value. Otherwise, undefined behavior! + */ + public native void setBooleanValue(boolean value); + + /** + * If the type is TYPE_BOOLEAN, gets the default value. Otherwise, undefined behavior! + */ + public native boolean getBooleanDefaultValue(); +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/view/InputDeviceSetting.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/view/InputDeviceSetting.java new file mode 100644 index 0000000000..e27d0f57ae --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/view/InputDeviceSetting.java @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.input.model.view; + +import android.content.Context; + +import org.dolphinemu.dolphinemu.features.input.model.ControllerInterface; +import org.dolphinemu.dolphinemu.features.input.model.controlleremu.EmulatedController; +import org.dolphinemu.dolphinemu.features.settings.model.Settings; +import org.dolphinemu.dolphinemu.features.settings.model.view.StringSingleChoiceSetting; + +public class InputDeviceSetting extends StringSingleChoiceSetting +{ + private final EmulatedController mController; + + public InputDeviceSetting(Context context, int titleId, int descriptionId, + EmulatedController controller) + { + super(context, null, titleId, descriptionId, null, null, null); + + mController = controller; + + refreshChoicesAndValues(); + } + + @Override + public String getSelectedChoice(Settings settings) + { + return mController.getDefaultDevice(); + } + + @Override + public String getSelectedValue(Settings settings) + { + return mController.getDefaultDevice(); + } + + @Override + public void setSelectedValue(Settings settings, String newValue) + { + mController.setDefaultDevice(newValue); + } + + @Override + public void refreshChoicesAndValues() + { + String[] devices = ControllerInterface.getAllDeviceStrings(); + + mChoices = devices; + mValues = devices; + } + + @Override + public boolean isEditable() + { + return true; + } + + @Override + public boolean canClear() + { + return true; + } + + @Override + public void clear(Settings settings) + { + setSelectedValue(settings, ""); + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/view/InputMappingControlSetting.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/view/InputMappingControlSetting.java new file mode 100644 index 0000000000..86379bf5a6 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/view/InputMappingControlSetting.java @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.input.model.view; + +import org.dolphinemu.dolphinemu.features.input.model.controlleremu.Control; +import org.dolphinemu.dolphinemu.features.input.model.controlleremu.ControlReference; +import org.dolphinemu.dolphinemu.features.input.model.controlleremu.EmulatedController; +import org.dolphinemu.dolphinemu.features.settings.model.AbstractSetting; +import org.dolphinemu.dolphinemu.features.settings.model.view.SettingsItem; + +public final class InputMappingControlSetting extends SettingsItem +{ + private final ControlReference mControlReference; + private final EmulatedController mController; + + public InputMappingControlSetting(Control control, EmulatedController controller) + { + super(control.getUiName(), ""); + mControlReference = control.getControlReference(); + mController = controller; + } + + public String getValue() + { + return mControlReference.getExpression(); + } + + public void setValue(String expr) + { + mControlReference.setExpression(expr); + mController.updateSingleControlReference(mControlReference); + } + + public void clearValue() + { + setValue(""); + } + + @Override + public int getType() + { + return TYPE_INPUT_MAPPING_CONTROL; + } + + @Override + public AbstractSetting getSetting() + { + return null; + } + + @Override + public boolean isEditable() + { + return true; + } + + public EmulatedController getController() + { + return mController; + } + + public ControlReference getControlReference() + { + return mControlReference; + } + + public boolean isInput() + { + return mControlReference.isInput(); + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/AdvancedMappingControlAdapter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/AdvancedMappingControlAdapter.java new file mode 100644 index 0000000000..0ad589fe40 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/AdvancedMappingControlAdapter.java @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.input.ui; + +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import org.dolphinemu.dolphinemu.databinding.ListItemAdvancedMappingControlBinding; + +import java.util.function.Consumer; + +public final class AdvancedMappingControlAdapter + extends RecyclerView.Adapter +{ + private final Consumer mOnClickCallback; + + private String[] mControls = new String[0]; + + public AdvancedMappingControlAdapter(Consumer onClickCallback) + { + mOnClickCallback = onClickCallback; + } + + @NonNull @Override + public AdvancedMappingControlViewHolder onCreateViewHolder(@NonNull ViewGroup parent, + int viewType) + { + LayoutInflater inflater = LayoutInflater.from(parent.getContext()); + + ListItemAdvancedMappingControlBinding binding = + ListItemAdvancedMappingControlBinding.inflate(inflater); + return new AdvancedMappingControlViewHolder(binding, mOnClickCallback); + } + + @Override + public void onBindViewHolder(@NonNull AdvancedMappingControlViewHolder holder, int position) + { + holder.bind(mControls[position]); + } + + @Override + public int getItemCount() + { + return mControls.length; + } + + public void setControls(String[] controls) + { + mControls = controls; + notifyDataSetChanged(); + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/AdvancedMappingControlViewHolder.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/AdvancedMappingControlViewHolder.java new file mode 100644 index 0000000000..2ce925235b --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/AdvancedMappingControlViewHolder.java @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.input.ui; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import org.dolphinemu.dolphinemu.databinding.ListItemAdvancedMappingControlBinding; + +import java.util.function.Consumer; + +public class AdvancedMappingControlViewHolder extends RecyclerView.ViewHolder +{ + private final ListItemAdvancedMappingControlBinding mBinding; + + private String mName; + + public AdvancedMappingControlViewHolder(@NonNull ListItemAdvancedMappingControlBinding binding, + Consumer onClickCallback) + { + super(binding.getRoot()); + + mBinding = binding; + + binding.getRoot().setOnClickListener(view -> onClickCallback.accept(mName)); + } + + public void bind(String name) + { + mName = name; + + mBinding.textName.setText(name); + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/AdvancedMappingDialog.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/AdvancedMappingDialog.java new file mode 100644 index 0000000000..de8e8265a6 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/AdvancedMappingDialog.java @@ -0,0 +1,151 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.input.ui; + +import android.content.Context; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; + +import androidx.appcompat.app.AlertDialog; +import androidx.recyclerview.widget.LinearLayoutManager; + +import com.google.android.material.divider.MaterialDividerItemDecoration; + +import org.dolphinemu.dolphinemu.databinding.DialogAdvancedMappingBinding; +import org.dolphinemu.dolphinemu.features.input.model.ControllerInterface; +import org.dolphinemu.dolphinemu.features.input.model.CoreDevice; +import org.dolphinemu.dolphinemu.features.input.model.MappingCommon; +import org.dolphinemu.dolphinemu.features.input.model.controlleremu.ControlReference; +import org.dolphinemu.dolphinemu.features.input.model.controlleremu.EmulatedController; + +import java.util.Arrays; +import java.util.Optional; + +public final class AdvancedMappingDialog extends AlertDialog + implements AdapterView.OnItemClickListener +{ + private final DialogAdvancedMappingBinding mBinding; + private final ControlReference mControlReference; + private final EmulatedController mController; + private final String[] mDevices; + private final AdvancedMappingControlAdapter mControlAdapter; + + private String mSelectedDevice; + + public AdvancedMappingDialog(Context context, DialogAdvancedMappingBinding binding, + ControlReference controlReference, EmulatedController controller) + { + super(context); + + mBinding = binding; + mControlReference = controlReference; + mController = controller; + + mDevices = ControllerInterface.getAllDeviceStrings(); + + // TODO: Remove workaround for text filtering issue in material components when fixed + // https://github.com/material-components/material-components-android/issues/1464 + mBinding.dropdownDevice.setSaveEnabled(false); + + binding.dropdownDevice.setOnItemClickListener(this); + + ArrayAdapter deviceAdapter = new ArrayAdapter<>( + context, android.R.layout.simple_spinner_dropdown_item, mDevices); + binding.dropdownDevice.setAdapter(deviceAdapter); + + mControlAdapter = new AdvancedMappingControlAdapter(this::onControlClicked); + mBinding.listControl.setAdapter(mControlAdapter); + mBinding.listControl.setLayoutManager(new LinearLayoutManager(context)); + + MaterialDividerItemDecoration divider = + new MaterialDividerItemDecoration(context, LinearLayoutManager.VERTICAL); + divider.setLastItemDecorated(false); + mBinding.listControl.addItemDecoration(divider); + + binding.editExpression.setText(controlReference.getExpression()); + + selectDefaultDevice(); + } + + public String getExpression() + { + return mBinding.editExpression.getText().toString(); + } + + @Override + public void onItemClick(AdapterView adapterView, View view, int position, long id) + { + setSelectedDevice(mDevices[position]); + } + + private void setSelectedDevice(String deviceString) + { + mSelectedDevice = deviceString; + + CoreDevice device = ControllerInterface.getDevice(deviceString); + if (device == null) + setControls(new CoreDevice.Control[0]); + else if (mControlReference.isInput()) + setControls(device.getInputs()); + else + setControls(device.getOutputs()); + } + + private void setControls(CoreDevice.Control[] controls) + { + mControlAdapter.setControls( + Arrays.stream(controls) + .map(CoreDevice.Control::getName) + .toArray(String[]::new)); + } + + private void onControlClicked(String control) + { + String expression = MappingCommon.getExpressionForControl(control, mSelectedDevice, + mController.getDefaultDevice()); + + int start = Math.max(mBinding.editExpression.getSelectionStart(), 0); + int end = Math.max(mBinding.editExpression.getSelectionEnd(), 0); + mBinding.editExpression.getText().replace( + Math.min(start, end), Math.max(start, end), expression, 0, expression.length()); + } + + private void selectDefaultDevice() + { + String defaultDevice = mController.getDefaultDevice(); + boolean isInput = mControlReference.isInput(); + + if (Arrays.asList(mDevices).contains(defaultDevice) && + (isInput || deviceHasOutputs(defaultDevice))) + { + // The default device is available, and it's an appropriate choice. Pick it + setSelectedDevice(defaultDevice); + mBinding.dropdownDevice.setText(defaultDevice, false); + return; + } + else if (!isInput) + { + // Find the first device that has an output. (Most built-in devices don't have any) + Optional deviceWithOutputs = Arrays.stream(mDevices) + .filter(AdvancedMappingDialog::deviceHasOutputs) + .findFirst(); + + if (deviceWithOutputs.isPresent()) + { + setSelectedDevice(deviceWithOutputs.get()); + mBinding.dropdownDevice.setText(deviceWithOutputs.get(), false); + return; + } + } + + // Nothing found + setSelectedDevice(""); + } + + private static boolean deviceHasOutputs(String deviceString) + { + CoreDevice device = ControllerInterface.getDevice(deviceString); + return device != null && device.getOutputs().length > 0; + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/MotionAlertDialog.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/MotionAlertDialog.java new file mode 100644 index 0000000000..9872bdd01e --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/MotionAlertDialog.java @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.input.ui; + +import android.app.Activity; +import android.view.InputDevice; +import android.view.KeyEvent; +import android.view.MotionEvent; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; + +import org.dolphinemu.dolphinemu.features.input.model.ControllerInterface; +import org.dolphinemu.dolphinemu.features.input.model.MappingCommon; +import org.dolphinemu.dolphinemu.features.input.model.view.InputMappingControlSetting; + +/** + * {@link AlertDialog} derivative that listens for + * motion events from controllers and joysticks. + */ +public final class MotionAlertDialog extends AlertDialog +{ + private final Activity mActivity; + private final InputMappingControlSetting mSetting; + private final boolean mAllDevices; + private boolean mRunning = false; + + /** + * Constructor + * + * @param activity The current {@link Activity}. + * @param setting The setting to show this dialog for. + * @param allDevices Whether to detect inputs from devices other than the configured one. + */ + public MotionAlertDialog(Activity activity, InputMappingControlSetting setting, + boolean allDevices) + { + super(activity); + + mActivity = activity; + mSetting = setting; + mAllDevices = allDevices; + } + + @Override + protected void onStart() + { + super.onStart(); + + mRunning = true; + new Thread(() -> + { + String result = MappingCommon.detectInput(mSetting.getController(), mAllDevices); + mActivity.runOnUiThread(() -> + { + if (mRunning) + { + mSetting.setValue(result); + dismiss(); + } + }); + }).start(); + } + + @Override + protected void onStop() + { + super.onStop(); + mRunning = false; + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) + { + ControllerInterface.dispatchKeyEvent(event); + + if (event.getKeyCode() == KeyEvent.KEYCODE_BACK && event.isLongPress()) + { + // Special case: Let the user cancel by long-pressing Back (intended for non-touch devices) + mSetting.clearValue(); + dismiss(); + } + + return true; + } + + @Override + public boolean dispatchGenericMotionEvent(@NonNull MotionEvent event) + { + if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) + { + // Special case: Let the user cancel by touching an on-screen button + return super.dispatchGenericMotionEvent(event); + } + + ControllerInterface.dispatchGenericMotionEvent(event); + return true; + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/ProfileAdapter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/ProfileAdapter.java new file mode 100644 index 0000000000..9908d6f5fd --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/ProfileAdapter.java @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.input.ui; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import org.dolphinemu.dolphinemu.databinding.ListItemProfileBinding; + +public final class ProfileAdapter extends RecyclerView.Adapter +{ + private final Context mContext; + private final ProfileDialogPresenter mPresenter; + + private final String[] mStockProfileNames; + private final String[] mUserProfileNames; + + public ProfileAdapter(Context context, ProfileDialogPresenter presenter) + { + mContext = context; + mPresenter = presenter; + + mStockProfileNames = presenter.getProfileNames(true); + mUserProfileNames = presenter.getProfileNames(false); + } + + @NonNull @Override + public ProfileViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) + { + LayoutInflater inflater = LayoutInflater.from(parent.getContext()); + + ListItemProfileBinding binding = ListItemProfileBinding.inflate(inflater, parent, false); + return new ProfileViewHolder(mPresenter, binding); + } + + @Override + public void onBindViewHolder(@NonNull ProfileViewHolder holder, int position) + { + if (position < mStockProfileNames.length) + { + holder.bind(mStockProfileNames[position], true); + return; + } + + position -= mStockProfileNames.length; + + if (position < mUserProfileNames.length) + { + holder.bind(mUserProfileNames[position], false); + return; + } + + holder.bindAsEmpty(mContext); + } + + @Override + public int getItemCount() + { + return mStockProfileNames.length + mUserProfileNames.length + 1; + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/ProfileDialog.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/ProfileDialog.kt new file mode 100644 index 0000000000..2779ee7473 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/ProfileDialog.kt @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.input.ui + +import android.os.Build +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.LinearLayoutManager +import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import com.google.android.material.divider.MaterialDividerItemDecoration +import org.dolphinemu.dolphinemu.R +import org.dolphinemu.dolphinemu.databinding.DialogInputProfilesBinding +import org.dolphinemu.dolphinemu.features.settings.ui.MenuTag + +class ProfileDialog : BottomSheetDialogFragment() { + private var presenter: ProfileDialogPresenter? = null + + private var _binding: DialogInputProfilesBinding? = null + private val binding get() = _binding!! + + override fun onCreate(savedInstanceState: Bundle?) { + val menuTag: MenuTag = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + requireArguments().getSerializable(KEY_MENU_TAG, MenuTag::class.java) as MenuTag + } else { + requireArguments().getSerializable(KEY_MENU_TAG) as MenuTag + } + + presenter = ProfileDialogPresenter(this, menuTag) + + super.onCreate(savedInstanceState) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = DialogInputProfilesBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + binding.profileList.adapter = ProfileAdapter(context, presenter) + binding.profileList.layoutManager = LinearLayoutManager(context) + val divider = MaterialDividerItemDecoration(requireActivity(), LinearLayoutManager.VERTICAL) + divider.isLastItemDecorated = false + binding.profileList.addItemDecoration(divider) + + // You can't expand a bottom sheet with a controller/remote/other non-touch devices + val behavior: BottomSheetBehavior = BottomSheetBehavior.from(view.parent as View) + if (!resources.getBoolean(R.bool.hasTouch)) { + behavior.state = BottomSheetBehavior.STATE_EXPANDED + } + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } + + companion object { + private const val KEY_MENU_TAG = "menu_tag" + + @JvmStatic + fun create(menuTag: MenuTag): ProfileDialog { + val dialog = ProfileDialog() + val args = Bundle() + args.putSerializable(KEY_MENU_TAG, menuTag) + dialog.arguments = args + return dialog + } + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/ProfileDialogPresenter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/ProfileDialogPresenter.java new file mode 100644 index 0000000000..667cc9017c --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/ProfileDialogPresenter.java @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.input.ui; + +import android.content.Context; +import android.view.LayoutInflater; + +import androidx.annotation.NonNull; +import androidx.fragment.app.DialogFragment; + +import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.google.android.material.textfield.TextInputEditText; + +import org.dolphinemu.dolphinemu.R; +import org.dolphinemu.dolphinemu.databinding.DialogInputStringBinding; +import org.dolphinemu.dolphinemu.features.settings.ui.MenuTag; +import org.dolphinemu.dolphinemu.features.settings.ui.SettingsActivityView; +import org.dolphinemu.dolphinemu.utils.DirectoryInitialization; + +import java.io.File; +import java.text.Collator; +import java.util.Arrays; + +public final class ProfileDialogPresenter +{ + private static final String EXTENSION = ".ini"; + + private final Context mContext; + private final DialogFragment mDialog; + private final MenuTag mMenuTag; + + public ProfileDialogPresenter(MenuTag menuTag) + { + mContext = null; + mDialog = null; + mMenuTag = menuTag; + } + + public ProfileDialogPresenter(DialogFragment dialog, MenuTag menuTag) + { + mContext = dialog.getContext(); + mDialog = dialog; + mMenuTag = menuTag; + } + + public String[] getProfileNames(boolean stock) + { + File[] profiles = new File(getProfileDirectoryPath(stock)).listFiles( + file -> !file.isDirectory() && file.getName().endsWith(EXTENSION)); + + if (profiles == null) + return new String[0]; + + return Arrays.stream(profiles) + .map(file -> file.getName().substring(0, file.getName().length() - EXTENSION.length())) + .sorted(Collator.getInstance()) + .toArray(String[]::new); + } + + public void loadProfile(@NonNull String profileName, boolean stock) + { + new MaterialAlertDialogBuilder(mContext) + .setMessage(mContext.getString(R.string.input_profile_confirm_load, profileName)) + .setPositiveButton(R.string.yes, (dialogInterface, i) -> + { + mMenuTag.getCorrespondingEmulatedController() + .loadProfile(getProfilePath(profileName, stock)); + ((SettingsActivityView) mDialog.requireActivity()).onControllerSettingsChanged(); + mDialog.dismiss(); + }) + .setNegativeButton(R.string.no, null) + .show(); + } + + public void saveProfile(@NonNull String profileName) + { + // If the user is saving over an existing profile, we should show an overwrite warning. + // If the user is creating a new profile, we normally shouldn't show a warning, + // but if they've entered the name of an existing profile, we should shown an overwrite warning. + + String profilePath = getProfilePath(profileName, false); + if (!new File(profilePath).exists()) + { + mMenuTag.getCorrespondingEmulatedController().saveProfile(profilePath); + mDialog.dismiss(); + } + else + { + new MaterialAlertDialogBuilder(mContext) + .setMessage(mContext.getString(R.string.input_profile_confirm_save, profileName)) + .setPositiveButton(R.string.yes, (dialogInterface, i) -> + { + mMenuTag.getCorrespondingEmulatedController().saveProfile(profilePath); + mDialog.dismiss(); + }) + .setNegativeButton(R.string.no, null) + .show(); + } + } + + public void saveProfileAndPromptForName() + { + LayoutInflater inflater = LayoutInflater.from(mContext); + + DialogInputStringBinding binding = DialogInputStringBinding.inflate(inflater); + TextInputEditText input = binding.input; + + new MaterialAlertDialogBuilder(mContext) + .setView(binding.getRoot()) + .setPositiveButton(R.string.ok, (dialogInterface, i) -> + saveProfile(input.getText().toString())) + .setNegativeButton(R.string.cancel, null) + .show(); + } + + public void deleteProfile(@NonNull String profileName) + { + new MaterialAlertDialogBuilder(mContext) + .setMessage(mContext.getString(R.string.input_profile_confirm_delete, profileName)) + .setPositiveButton(R.string.yes, (dialogInterface, i) -> + { + new File(getProfilePath(profileName, false)).delete(); + mDialog.dismiss(); + }) + .setNegativeButton(R.string.no, null) + .show(); + } + + private String getProfileDirectoryName() + { + if (mMenuTag.isGCPadMenu()) + return "GCPad"; + else if (mMenuTag.isWiimoteMenu()) + return "Wiimote"; + else + throw new UnsupportedOperationException(); + } + + private String getProfileDirectoryPath(boolean stock) + { + if (stock) + { + return DirectoryInitialization.getSysDirectory() + "/Profiles/" + getProfileDirectoryName() + + '/'; + } + else + { + return DirectoryInitialization.getUserDirectory() + "/Config/Profiles/" + + getProfileDirectoryName() + '/'; + } + } + + private String getProfilePath(String profileName, boolean stock) + { + return getProfileDirectoryPath(stock) + profileName + EXTENSION; + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/ProfileViewHolder.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/ProfileViewHolder.java new file mode 100644 index 0000000000..d4d40c0c65 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/ProfileViewHolder.java @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.input.ui; + +import android.content.Context; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import org.dolphinemu.dolphinemu.R; +import org.dolphinemu.dolphinemu.databinding.ListItemProfileBinding; + +public class ProfileViewHolder extends RecyclerView.ViewHolder +{ + private final ProfileDialogPresenter mPresenter; + private final ListItemProfileBinding mBinding; + + private String mProfileName; + private boolean mStock; + + public ProfileViewHolder(@NonNull ProfileDialogPresenter presenter, + @NonNull ListItemProfileBinding binding) + { + super(binding.getRoot()); + + mPresenter = presenter; + mBinding = binding; + + binding.buttonLoad.setOnClickListener(view -> loadProfile()); + binding.buttonSave.setOnClickListener(view -> saveProfile()); + binding.buttonDelete.setOnClickListener(view -> deleteProfile()); + } + + public void bind(String profileName, boolean stock) + { + mProfileName = profileName; + mStock = stock; + + mBinding.textName.setText(profileName); + + mBinding.buttonLoad.setVisibility(View.VISIBLE); + mBinding.buttonSave.setVisibility(stock ? View.GONE : View.VISIBLE); + mBinding.buttonDelete.setVisibility(stock ? View.GONE : View.VISIBLE); + } + + public void bindAsEmpty(Context context) + { + mProfileName = null; + mStock = false; + + mBinding.textName.setText(context.getText(R.string.input_profile_new)); + + mBinding.buttonLoad.setVisibility(View.GONE); + mBinding.buttonSave.setVisibility(View.VISIBLE); + mBinding.buttonDelete.setVisibility(View.GONE); + } + + private void loadProfile() + { + mPresenter.loadProfile(mProfileName, mStock); + } + + private void saveProfile() + { + if (mProfileName == null) + mPresenter.saveProfileAndPromptForName(); + else + mPresenter.saveProfile(mProfileName); + } + + private void deleteProfile() + { + mPresenter.deleteProfile(mProfileName); + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/viewholder/InputMappingControlSettingViewHolder.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/viewholder/InputMappingControlSettingViewHolder.java new file mode 100644 index 0000000000..fd4b92cf95 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/viewholder/InputMappingControlSettingViewHolder.java @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.input.ui.viewholder; + +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import org.dolphinemu.dolphinemu.databinding.ListItemMappingBinding; +import org.dolphinemu.dolphinemu.features.input.model.view.InputMappingControlSetting; +import org.dolphinemu.dolphinemu.features.settings.model.view.SettingsItem; +import org.dolphinemu.dolphinemu.features.settings.ui.SettingsAdapter; +import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.SettingViewHolder; + +public final class InputMappingControlSettingViewHolder extends SettingViewHolder +{ + private InputMappingControlSetting mItem; + + private final ListItemMappingBinding mBinding; + + public InputMappingControlSettingViewHolder(@NonNull ListItemMappingBinding binding, + SettingsAdapter adapter) + { + super(binding.getRoot(), adapter); + mBinding = binding; + } + + @Override + public void bind(SettingsItem item) + { + mItem = (InputMappingControlSetting) item; + + mBinding.textSettingName.setText(mItem.getName()); + mBinding.textSettingDescription.setText(mItem.getValue()); + mBinding.buttonAdvancedSettings.setOnClickListener(this::onLongClick); + + setStyle(mBinding.textSettingName, mItem); + } + + @Override + public void onClick(View clicked) + { + if (!mItem.isEditable()) + { + showNotRuntimeEditableError(); + return; + } + + if (mItem.isInput()) + getAdapter().onInputMappingClick(mItem, getBindingAdapterPosition()); + else + getAdapter().onAdvancedInputMappingClick(mItem, getBindingAdapterPosition()); + + setStyle(mBinding.textSettingName, mItem); + } + + @Override + public boolean onLongClick(View clicked) + { + if (!mItem.isEditable()) + { + showNotRuntimeEditableError(); + return true; + } + + getAdapter().onAdvancedInputMappingClick(mItem, getBindingAdapterPosition()); + + return true; + } + + @Nullable @Override + protected SettingsItem getItem() + { + return mItem; + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/BooleanSetting.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/BooleanSetting.java index 1194e1f56a..cbaa7a4292 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/BooleanSetting.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/BooleanSetting.java @@ -100,8 +100,6 @@ public enum BooleanSetting implements AbstractBooleanSetting "UseBlackBackgrounds", false), MAIN_JOYSTICK_REL_CENTER(Settings.FILE_DOLPHIN, Settings.SECTION_INI_ANDROID, "JoystickRelCenter", true), - MAIN_PHONE_RUMBLE(Settings.FILE_DOLPHIN, Settings.SECTION_INI_ANDROID, - "PhoneRumble", true), MAIN_SHOW_INPUT_OVERLAY(Settings.FILE_DOLPHIN, Settings.SECTION_INI_ANDROID, "ShowInputOverlay", true), MAIN_IR_ALWAYS_RECENTER(Settings.FILE_DOLPHIN, Settings.SECTION_INI_ANDROID, diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/IntSetting.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/IntSetting.java index a51eb71749..77cf7b339a 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/IntSetting.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/IntSetting.java @@ -33,6 +33,10 @@ public enum IntSetting implements AbstractIntSetting MAIN_AUDIO_VOLUME(Settings.FILE_DOLPHIN, Settings.SECTION_INI_DSP, "Volume", 100), + MAIN_OVERLAY_GC_CONTROLLER(Settings.FILE_DOLPHIN, Settings.SECTION_INI_ANDROID, + "OverlayGCController", 0), // Defaults to GameCube controller 1 + MAIN_OVERLAY_WII_CONTROLLER(Settings.FILE_DOLPHIN, Settings.SECTION_INI_ANDROID, + "OverlayWiiController", 4), // Defaults to Wii Remote 1 MAIN_CONTROL_SCALE(Settings.FILE_DOLPHIN, Settings.SECTION_INI_ANDROID, "ControlScale", 50), MAIN_CONTROL_OPACITY(Settings.FILE_DOLPHIN, Settings.SECTION_INI_ANDROID, "ControlOpacity", 65), MAIN_EMULATION_ORIENTATION(Settings.FILE_DOLPHIN, Settings.SECTION_INI_ANDROID, @@ -41,7 +45,6 @@ public enum IntSetting implements AbstractIntSetting MAIN_INTERFACE_THEME_MODE(Settings.FILE_DOLPHIN, Settings.SECTION_INI_ANDROID, "InterfaceThemeMode", -1), MAIN_LAST_PLATFORM_TAB(Settings.FILE_DOLPHIN, Settings.SECTION_INI_ANDROID, "LastPlatformTab", 0), - MAIN_MOTION_CONTROLS(Settings.FILE_DOLPHIN, Settings.SECTION_INI_ANDROID, "MotionControls", 1), MAIN_IR_MODE(Settings.FILE_DOLPHIN, Settings.SECTION_INI_ANDROID, "IRMode", InputOverlayPointer.MODE_FOLLOW), @@ -189,4 +192,16 @@ public enum IntSetting implements AbstractIntSetting { NativeConfig.setInt(layer, mFile, mSection, mKey, newValue); } + + public static IntSetting getSettingForSIDevice(int channel) + { + return new IntSetting[]{MAIN_SI_DEVICE_0, MAIN_SI_DEVICE_1, MAIN_SI_DEVICE_2, MAIN_SI_DEVICE_3} + [channel]; + } + + public static IntSetting getSettingForWiimoteSource(int index) + { + return new IntSetting[]{WIIMOTE_1_SOURCE, WIIMOTE_2_SOURCE, WIIMOTE_3_SOURCE, WIIMOTE_4_SOURCE, + WIIMOTE_BB_SOURCE}[index]; + } } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/LegacyFloatSetting.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/LegacyFloatSetting.java deleted file mode 100644 index 0b92cba802..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/LegacyFloatSetting.java +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.dolphinemu.dolphinemu.features.settings.model; - -import androidx.annotation.NonNull; - -public class LegacyFloatSetting extends AbstractLegacySetting implements AbstractFloatSetting -{ - private final float mDefaultValue; - - public LegacyFloatSetting(String file, String section, String key, float defaultValue) - { - super(file, section, key); - mDefaultValue = defaultValue; - } - - @Override - public float getFloat(@NonNull Settings settings) - { - return settings.getSection(mFile, mSection).getFloat(mKey, mDefaultValue); - } - - @Override - public void setFloat(@NonNull Settings settings, float newValue) - { - settings.getSection(mFile, mSection).setFloat(mKey, newValue); - } -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/Settings.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/Settings.java index ebdaf70691..fb5e16965b 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/Settings.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/Settings.java @@ -8,6 +8,7 @@ import android.widget.Toast; import org.dolphinemu.dolphinemu.NativeLibrary; import org.dolphinemu.dolphinemu.R; +import org.dolphinemu.dolphinemu.features.input.model.MappingCommon; import org.dolphinemu.dolphinemu.features.settings.ui.SettingsActivityView; import org.dolphinemu.dolphinemu.features.settings.utils.SettingsFile; import org.dolphinemu.dolphinemu.services.GameFileCacheManager; @@ -24,7 +25,6 @@ public class Settings implements Closeable public static final String FILE_SYSCONF = "SYSCONF"; public static final String FILE_GFX = "GFX"; public static final String FILE_LOGGER = "Logger"; - public static final String FILE_GCPAD = "GCPadNew"; public static final String FILE_WIIMOTE = "WiimoteNew"; public static final String SECTION_INI_ANDROID = "Android"; @@ -46,10 +46,7 @@ public class Settings implements Closeable public static final String SECTION_STEREOSCOPY = "Stereoscopy"; - public static final String SECTION_WIIMOTE = "Wiimote"; - public static final String SECTION_BINDINGS = "Android"; - public static final String SECTION_CONTROLS = "Controls"; public static final String SECTION_PROFILE = "Profile"; public static final String SECTION_ANALYTICS = "Analytics"; @@ -64,7 +61,6 @@ public class Settings implements Closeable FILE_WIIMOTE}; private Map mIniFiles = new HashMap<>(); - private final Map mWiimoteProfileFiles = new HashMap<>(); private boolean mLoadedRecursiveIsoPathsValue = false; @@ -99,50 +95,6 @@ public class Settings implements Closeable return mIsWii; } - public IniFile getWiimoteProfile(String profile, int padID) - { - IniFile wiimoteProfileIni = mWiimoteProfileFiles.computeIfAbsent(profile, profileComputed -> - { - IniFile newIni = new IniFile(); - newIni.load(SettingsFile.getWiiProfile(profileComputed), false); - return newIni; - }); - - if (!wiimoteProfileIni.exists(SECTION_PROFILE)) - { - String defaultWiiProfilePath = DirectoryInitialization.getUserDirectory() + - "/Config/Profiles/Wiimote/WiimoteProfile.ini"; - - wiimoteProfileIni.load(defaultWiiProfilePath, false); - - wiimoteProfileIni - .setString(SECTION_PROFILE, "Device", "Android/" + (padID + 4) + "/Touchscreen"); - } - - return wiimoteProfileIni; - } - - public void enableWiimoteProfile(Settings settings, String profile, String profileKey) - { - getWiimoteControlsSection(settings).setString(profileKey, profile); - } - - public boolean disableWiimoteProfile(Settings settings, String profileKey) - { - return getWiimoteControlsSection(settings).delete(profileKey); - } - - public boolean isWiimoteProfileEnabled(Settings settings, String profile, - String profileKey) - { - return profile.equals(getWiimoteControlsSection(settings).getString(profileKey, "")); - } - - private IniFile.Section getWiimoteControlsSection(Settings settings) - { - return settings.getSection(GAME_SETTINGS_PLACEHOLDER_FILE_NAME, SECTION_CONTROLS); - } - public int getWriteLayer() { return isGameSpecific() ? NativeConfig.LAYER_LOCAL_GAME : NativeConfig.LAYER_BASE_OR_CURRENT; @@ -218,13 +170,14 @@ public class Settings implements Closeable SettingsFile.saveFile(entry.getKey(), entry.getValue(), view); } + MappingCommon.save(); + NativeConfig.save(NativeConfig.LAYER_BASE); if (!NativeLibrary.IsRunning()) { // Notify the native code of the changes to legacy settings NativeLibrary.ReloadConfig(); - NativeLibrary.ReloadWiimoteConfig(); } // LogManager does use the new config system, but doesn't pick up on changes automatically @@ -251,11 +204,6 @@ public class Settings implements Closeable NativeConfig.save(NativeConfig.LAYER_LOCAL_GAME); } - - for (Map.Entry entry : mWiimoteProfileFiles.entrySet()) - { - entry.getValue().save(SettingsFile.getWiiProfile(entry.getKey())); - } } public void clearSettings() diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/WiimoteProfileBooleanSetting.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/WiimoteProfileBooleanSetting.java deleted file mode 100644 index 1705e969c4..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/WiimoteProfileBooleanSetting.java +++ /dev/null @@ -1,70 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.dolphinemu.dolphinemu.features.settings.model; - -import androidx.annotation.NonNull; - -import org.dolphinemu.dolphinemu.features.settings.utils.SettingsFile; - -// This stuff is pretty ugly. It's a kind of workaround for certain controller settings -// not actually being available as game-specific settings. -public class WiimoteProfileBooleanSetting implements AbstractBooleanSetting -{ - private final int mPadID; - - private final String mSection; - private final String mKey; - private final boolean mDefaultValue; - - private final String mProfileKey; - - private final String mProfile; - - public WiimoteProfileBooleanSetting(String gameID, int padID, String section, String key, - boolean defaultValue) - { - mPadID = padID; - mSection = section; - mKey = key; - mDefaultValue = defaultValue; - - mProfileKey = SettingsFile.KEY_WIIMOTE_PROFILE + (padID + 1); - - mProfile = gameID + "_Wii" + padID; - } - - @Override - public boolean isOverridden(@NonNull Settings settings) - { - return settings.isWiimoteProfileEnabled(settings, mProfile, mProfileKey); - } - - @Override - public boolean isRuntimeEditable() - { - return false; - } - - @Override - public boolean delete(@NonNull Settings settings) - { - return settings.disableWiimoteProfile(settings, mProfileKey); - } - - @Override - public boolean getBoolean(@NonNull Settings settings) - { - if (settings.isWiimoteProfileEnabled(settings, mProfile, mProfileKey)) - return settings.getWiimoteProfile(mProfile, mPadID).getBoolean(mSection, mKey, mDefaultValue); - else - return mDefaultValue; - } - - @Override - public void setBoolean(@NonNull Settings settings, boolean newValue) - { - settings.getWiimoteProfile(mProfile, mPadID).setBoolean(mSection, mKey, newValue); - - settings.enableWiimoteProfile(settings, mProfile, mProfileKey); - } -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/WiimoteProfileStringSetting.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/WiimoteProfileStringSetting.java deleted file mode 100644 index c62df5638d..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/WiimoteProfileStringSetting.java +++ /dev/null @@ -1,70 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.dolphinemu.dolphinemu.features.settings.model; - -import androidx.annotation.NonNull; - -import org.dolphinemu.dolphinemu.features.settings.utils.SettingsFile; - -// This stuff is pretty ugly. It's a kind of workaround for certain controller settings -// not actually being available as game-specific settings. -public class WiimoteProfileStringSetting implements AbstractStringSetting -{ - private final int mPadID; - - private final String mSection; - private final String mKey; - private final String mDefaultValue; - - private final String mProfileKey; - - private final String mProfile; - - public WiimoteProfileStringSetting(String gameID, int padID, String section, String key, - String defaultValue) - { - mPadID = padID; - mSection = section; - mKey = key; - mDefaultValue = defaultValue; - - mProfileKey = SettingsFile.KEY_WIIMOTE_PROFILE + (padID + 1); - - mProfile = gameID + "_Wii" + padID; - } - - @Override - public boolean isOverridden(@NonNull Settings settings) - { - return settings.isWiimoteProfileEnabled(settings, mProfile, mProfileKey); - } - - @Override - public boolean isRuntimeEditable() - { - return false; - } - - @Override - public boolean delete(@NonNull Settings settings) - { - return settings.disableWiimoteProfile(settings, mProfileKey); - } - - @NonNull @Override - public String getString(@NonNull Settings settings) - { - if (settings.isWiimoteProfileEnabled(settings, mProfile, mProfileKey)) - return settings.getWiimoteProfile(mProfile, mPadID).getString(mSection, mKey, mDefaultValue); - else - return mDefaultValue; - } - - @Override - public void setString(@NonNull Settings settings, @NonNull String newValue) - { - settings.getWiimoteProfile(mProfile, mPadID).setString(mSection, mKey, newValue); - - settings.enableWiimoteProfile(settings, mProfile, mProfileKey); - } -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/FloatSliderSetting.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/FloatSliderSetting.java index 167c5a471b..5b91517f75 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/FloatSliderSetting.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/FloatSliderSetting.java @@ -19,6 +19,13 @@ public class FloatSliderSetting extends SliderSetting mSetting = setting; } + public FloatSliderSetting(AbstractFloatSetting setting, CharSequence name, + CharSequence description, int min, int max, String units) + { + super(name, description, min, max, units); + mSetting = setting; + } + public int getSelectedValue(Settings settings) { return Math.round(mSetting.getFloat(settings)); diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/HeaderSetting.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/HeaderSetting.java index 14c5f49021..b65f45837b 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/HeaderSetting.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/HeaderSetting.java @@ -13,6 +13,11 @@ public class HeaderSetting extends SettingsItem super(context, titleId, descriptionId); } + public HeaderSetting(CharSequence title, CharSequence description) + { + super(title, description); + } + @Override public int getType() { diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/InputBindingSetting.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/InputBindingSetting.java deleted file mode 100644 index 175986d3ba..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/InputBindingSetting.java +++ /dev/null @@ -1,108 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.dolphinemu.dolphinemu.features.settings.model.view; - -import android.content.Context; -import android.content.SharedPreferences; -import android.view.InputDevice; -import android.view.KeyEvent; - -import androidx.preference.PreferenceManager; - -import org.dolphinemu.dolphinemu.DolphinApplication; -import org.dolphinemu.dolphinemu.features.settings.model.AbstractSetting; -import org.dolphinemu.dolphinemu.features.settings.model.Settings; - -public class InputBindingSetting extends SettingsItem -{ - private String mFile; - private String mSection; - private String mKey; - - private String mGameId; - - public InputBindingSetting(Context context, String file, String section, String key, int titleId, - String gameId) - { - super(context, titleId, 0); - mFile = file; - mSection = section; - mKey = key; - mGameId = gameId; - } - - public String getKey() - { - return mKey; - } - - public String getValue(Settings settings) - { - return settings.getSection(mFile, mSection).getString(mKey, ""); - } - - /** - * Saves the provided key input setting both to the INI file (so native code can use it) and as - * an Android preference (so it persists correctly and is human-readable.) - * - * @param keyEvent KeyEvent of this key press. - */ - public void onKeyInput(Settings settings, KeyEvent keyEvent) - { - InputDevice device = keyEvent.getDevice(); - String bindStr = "Device '" + device.getDescriptor() + "'-Button " + keyEvent.getKeyCode(); - String uiString = device.getName() + ": Button " + keyEvent.getKeyCode(); - setValue(settings, bindStr, uiString); - } - - /** - * Saves the provided motion input setting both to the INI file (so native code can use it) and as - * an Android preference (so it persists correctly and is human-readable.) - * - * @param device InputDevice from which the input event originated. - * @param motionRange MotionRange of the movement - * @param axisDir Either '-' or '+' - */ - public void onMotionInput(Settings settings, InputDevice device, - InputDevice.MotionRange motionRange, char axisDir) - { - String bindStr = - "Device '" + device.getDescriptor() + "'-Axis " + motionRange.getAxis() + axisDir; - String uiString = device.getName() + ": Axis " + motionRange.getAxis() + axisDir; - setValue(settings, bindStr, uiString); - } - - public void setValue(Settings settings, String bind, String ui) - { - SharedPreferences - preferences = - PreferenceManager.getDefaultSharedPreferences(DolphinApplication.getAppContext()); - SharedPreferences.Editor editor = preferences.edit(); - editor.putString(mKey + mGameId, ui); - editor.apply(); - - settings.getSection(mFile, mSection).setString(mKey, bind); - } - - public void clearValue(Settings settings) - { - setValue(settings, "", ""); - } - - @Override - public int getType() - { - return TYPE_INPUT_BINDING; - } - - public String getGameId() - { - return mGameId; - } - - @Override - public AbstractSetting getSetting() - { - return null; - } -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/RumbleBindingSetting.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/RumbleBindingSetting.java deleted file mode 100644 index 6c3e11e17b..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/RumbleBindingSetting.java +++ /dev/null @@ -1,69 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.dolphinemu.dolphinemu.features.settings.model.view; - -import android.content.Context; -import android.os.Vibrator; -import android.view.InputDevice; -import android.view.KeyEvent; - -import org.dolphinemu.dolphinemu.DolphinApplication; -import org.dolphinemu.dolphinemu.R; -import org.dolphinemu.dolphinemu.features.settings.model.AbstractSetting; -import org.dolphinemu.dolphinemu.features.settings.model.Settings; -import org.dolphinemu.dolphinemu.utils.Rumble; - -public class RumbleBindingSetting extends InputBindingSetting -{ - public RumbleBindingSetting(Context context, String file, String section, String key, int titleId, - String gameId) - { - super(context, file, section, key, titleId, gameId); - } - - /** - * Just need the device when saving rumble. - */ - @Override - public void onKeyInput(Settings settings, KeyEvent keyEvent) - { - saveRumble(settings, keyEvent.getDevice()); - } - - /** - * Just need the device when saving rumble. - */ - @Override - public void onMotionInput(Settings settings, InputDevice device, - InputDevice.MotionRange motionRange, char axisDir) - { - saveRumble(settings, device); - } - - private void saveRumble(Settings settings, InputDevice device) - { - Vibrator vibrator = device.getVibrator(); - if (vibrator != null && vibrator.hasVibrator()) - { - setValue(settings, device.getDescriptor(), device.getName()); - Rumble.doRumble(vibrator); - } - else - { - setValue(settings, "", - DolphinApplication.getAppContext().getString(R.string.rumble_not_found)); - } - } - - @Override - public int getType() - { - return TYPE_RUMBLE_BINDING; - } - - @Override - public AbstractSetting getSetting() - { - return null; - } -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/SettingsItem.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/SettingsItem.java index 7b41c8e3f8..6f06677be3 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/SettingsItem.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/SettingsItem.java @@ -21,9 +21,8 @@ public abstract class SettingsItem public static final int TYPE_SINGLE_CHOICE = 2; public static final int TYPE_SLIDER = 3; public static final int TYPE_SUBMENU = 4; - public static final int TYPE_INPUT_BINDING = 5; + public static final int TYPE_INPUT_MAPPING_CONTROL = 5; public static final int TYPE_STRING_SINGLE_CHOICE = 6; - public static final int TYPE_RUMBLE_BINDING = 7; public static final int TYPE_SINGLE_CHOICE_DYNAMIC_DESCRIPTIONS = 8; public static final int TYPE_FILE_PICKER = 9; public static final int TYPE_RUN_RUNNABLE = 10; @@ -96,6 +95,11 @@ public abstract class SettingsItem return getSetting() != null; } + public boolean canClear() + { + return hasSetting(); + } + public void clear(Settings settings) { getSetting().delete(settings); diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/SliderSetting.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/SliderSetting.java index 48961839c0..e63fde3bad 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/SliderSetting.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/SliderSetting.java @@ -23,6 +23,14 @@ public abstract class SliderSetting extends SettingsItem mStepSize = stepSize; } + public SliderSetting(CharSequence name, CharSequence description, int min, int max, String units) + { + super(name, description); + mMin = min; + mMax = max; + mUnits = units; + } + public abstract int getSelectedValue(Settings settings); public int getMin() diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/StringSingleChoiceSetting.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/StringSingleChoiceSetting.java index df7ee6f4e9..1a9e237728 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/StringSingleChoiceSetting.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/StringSingleChoiceSetting.java @@ -12,11 +12,12 @@ import org.dolphinemu.dolphinemu.features.settings.ui.MenuTag; public class StringSingleChoiceSetting extends SettingsItem { - private AbstractStringSetting mSetting; + private final AbstractStringSetting mSetting; - private String[] mChoices; - private String[] mValues; - private MenuTag mMenuTag; + protected String[] mChoices; + protected String[] mValues; + private final MenuTag mMenuTag; + private int mNoChoicesAvailableString = 0; public StringSingleChoiceSetting(Context context, AbstractStringSetting setting, int titleId, int descriptionId, String[] choices, String[] values, MenuTag menuTag) @@ -34,6 +35,13 @@ public class StringSingleChoiceSetting extends SettingsItem this(context, setting, titleId, descriptionId, choices, values, null); } + public StringSingleChoiceSetting(Context context, AbstractStringSetting setting, int titleId, + int descriptionId, String[] choices, String[] values, int noChoicesAvailableString) + { + this(context, setting, titleId, descriptionId, choices, values, null); + mNoChoicesAvailableString = noChoicesAvailableString; + } + public StringSingleChoiceSetting(Context context, AbstractStringSetting setting, int titleId, int descriptionId, int choicesId, int valuesId, MenuTag menuTag) { @@ -60,6 +68,19 @@ public class StringSingleChoiceSetting extends SettingsItem return mValues; } + public String getChoiceAt(int index) + { + if (mChoices == null) + return null; + + if (index >= 0 && index < mChoices.length) + { + return mChoices[index]; + } + + return ""; + } + public String getValueAt(int index) { if (mValues == null) @@ -73,6 +94,11 @@ public class StringSingleChoiceSetting extends SettingsItem return ""; } + public String getSelectedChoice(Settings settings) + { + return getChoiceAt(getSelectedValueIndex(settings)); + } + public String getSelectedValue(Settings settings) { return mSetting.getString(settings); @@ -97,11 +123,20 @@ public class StringSingleChoiceSetting extends SettingsItem return mMenuTag; } + public int getNoChoicesAvailableString() + { + return mNoChoicesAvailableString; + } + public void setSelectedValue(Settings settings, String selection) { mSetting.setString(settings, selection); } + public void refreshChoicesAndValues() + { + } + @Override public int getType() { diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/MenuTag.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/MenuTag.java index 6fc30a381b..a0a67dd838 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/MenuTag.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/MenuTag.java @@ -4,6 +4,8 @@ package org.dolphinemu.dolphinemu.features.settings.ui; import androidx.annotation.NonNull; +import org.dolphinemu.dolphinemu.features.input.model.controlleremu.EmulatedController; + public enum MenuTag { SETTINGS("settings"), @@ -31,14 +33,26 @@ public enum MenuTag GCPAD_2("gcpad", 1), GCPAD_3("gcpad", 2), GCPAD_4("gcpad", 3), - WIIMOTE_1("wiimote", 4), - WIIMOTE_2("wiimote", 5), - WIIMOTE_3("wiimote", 6), - WIIMOTE_4("wiimote", 7), - WIIMOTE_EXTENSION_1("wiimote_extension", 4), - WIIMOTE_EXTENSION_2("wiimote_extension", 5), - WIIMOTE_EXTENSION_3("wiimote_extension", 6), - WIIMOTE_EXTENSION_4("wiimote_extension", 7); + WIIMOTE_1("wiimote", 0), + WIIMOTE_2("wiimote", 1), + WIIMOTE_3("wiimote", 2), + WIIMOTE_4("wiimote", 3), + WIIMOTE_EXTENSION_1("wiimote_extension", 0), + WIIMOTE_EXTENSION_2("wiimote_extension", 1), + WIIMOTE_EXTENSION_3("wiimote_extension", 2), + WIIMOTE_EXTENSION_4("wiimote_extension", 3), + WIIMOTE_GENERAL_1("wiimote_general", 0), + WIIMOTE_GENERAL_2("wiimote_general", 1), + WIIMOTE_GENERAL_3("wiimote_general", 2), + WIIMOTE_GENERAL_4("wiimote_general", 3), + WIIMOTE_MOTION_SIMULATION_1("wiimote_motion_simulation", 0), + WIIMOTE_MOTION_SIMULATION_2("wiimote_motion_simulation", 1), + WIIMOTE_MOTION_SIMULATION_3("wiimote_motion_simulation", 2), + WIIMOTE_MOTION_SIMULATION_4("wiimote_motion_simulation", 3), + WIIMOTE_MOTION_INPUT_1("wiimote_motion_input", 0), + WIIMOTE_MOTION_INPUT_2("wiimote_motion_input", 1), + WIIMOTE_MOTION_INPUT_3("wiimote_motion_input", 2), + WIIMOTE_MOTION_INPUT_4("wiimote_motion_input", 3); private String tag; private int subType = -1; @@ -76,6 +90,16 @@ public enum MenuTag return subType; } + public EmulatedController getCorrespondingEmulatedController() + { + if (isGCPadMenu()) + return EmulatedController.getGcPad(getSubType()); + else if (isWiimoteMenu()) + return EmulatedController.getWiimote(getSubType()); + else + throw new UnsupportedOperationException(); + } + public boolean isSerialPort1Menu() { return this == CONFIG_SERIALPORT1; @@ -112,11 +136,27 @@ public enum MenuTag return getMenuTag("wiimote_extension", subtype); } + public static MenuTag getWiimoteGeneralMenuTag(int subtype) + { + return getMenuTag("wiimote_general", subtype); + } + + public static MenuTag getWiimoteMotionSimulationMenuTag(int subtype) + { + return getMenuTag("wiimote_motion_simulation", subtype); + } + + public static MenuTag getWiimoteMotionInputMenuTag(int subtype) + { + return getMenuTag("wiimote_motion_input", subtype); + } + private static MenuTag getMenuTag(String tag, int subtype) { for (MenuTag menuTag : MenuTag.values()) { - if (menuTag.tag.equals(tag) && menuTag.subType == subtype) return menuTag; + if (menuTag.tag.equals(tag) && menuTag.subType == subtype) + return menuTag; } throw new IllegalArgumentException("You are asking for a menu that is not available or " + diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsActivity.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsActivity.java index 2af17e2d2f..bce24c1c46 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsActivity.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsActivity.java @@ -9,6 +9,7 @@ import android.os.Bundle; import android.provider.Settings; import android.view.Menu; import android.view.MenuInflater; +import android.view.View; import android.widget.Toast; import androidx.annotation.NonNull; @@ -18,6 +19,7 @@ import androidx.core.graphics.Insets; import androidx.core.view.ViewCompat; import androidx.core.view.WindowCompat; import androidx.core.view.WindowInsetsCompat; +import androidx.fragment.app.DialogFragment; import androidx.fragment.app.FragmentTransaction; import androidx.lifecycle.ViewModelProvider; @@ -41,15 +43,18 @@ public final class SettingsActivity extends AppCompatActivity implements Setting private static final String ARG_GAME_ID = "game_id"; private static final String ARG_REVISION = "revision"; private static final String ARG_IS_WII = "is_wii"; + private static final String KEY_MAPPING_ALL_DEVICES = "all_devices"; private static final String FRAGMENT_TAG = "settings"; + private static final String FRAGMENT_DIALOG_TAG = "settings_dialog"; + private SettingsActivityPresenter mPresenter; - private AlertDialog dialog; - private CollapsingToolbarLayout mToolbarLayout; private ActivitySettingsBinding mBinding; + private boolean mMappingAllDevices = false; + public static void launch(Context context, MenuTag menuTag, String gameId, int revision, boolean isWii) { @@ -82,6 +87,10 @@ public final class SettingsActivity extends AppCompatActivity implements Setting { MainPresenter.skipRescanningLibrary(); } + else + { + mMappingAllDevices = savedInstanceState.getBoolean(KEY_MAPPING_ALL_DEVICES); + } mBinding = ActivitySettingsBinding.inflate(getLayoutInflater()); setContentView(mBinding.getRoot()); @@ -125,7 +134,10 @@ public final class SettingsActivity extends AppCompatActivity implements Setting { // Critical: If super method is not called, rotations will be busted. super.onSaveInstanceState(outState); + mPresenter.saveState(outState); + + outState.putBoolean(KEY_MAPPING_ALL_DEVICES, mMappingAllDevices); } @Override @@ -181,6 +193,12 @@ public final class SettingsActivity extends AppCompatActivity implements Setting transaction.commit(); } + @Override + public void showDialogFragment(DialogFragment fragment) + { + fragment.show(getSupportFragmentManager(), FRAGMENT_DIALOG_TAG); + } + private boolean areSystemAnimationsEnabled() { float duration = Settings.Global.getFloat( @@ -304,6 +322,12 @@ public final class SettingsActivity extends AppCompatActivity implements Setting mPresenter.onSettingChanged(); } + @Override + public void onControllerSettingsChanged() + { + getFragment().onControllerSettingsChanged(); + } + @Override public void onMenuTagAction(@NonNull MenuTag menuTag, int value) { @@ -330,7 +354,27 @@ public final class SettingsActivity extends AppCompatActivity implements Setting public void setToolbarTitle(String title) { - mToolbarLayout.setTitle(title); + mBinding.toolbarSettingsLayout.setTitle(title); + } + + @Override + public void setMappingAllDevices(boolean allDevices) + { + mMappingAllDevices = allDevices; + } + + @Override + public boolean isMappingAllDevices() + { + return mMappingAllDevices; + } + + @Override + public int setOldControllerSettingsWarningVisibility(boolean visible) + { + // We use INVISIBLE instead of GONE to avoid getting a stale height for the return value + mBinding.oldControllerSettingsWarning.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); + return visible ? mBinding.oldControllerSettingsWarning.getHeight() : 0; } private void setInsets() @@ -343,6 +387,10 @@ public final class SettingsActivity extends AppCompatActivity implements Setting mBinding.frameContentSettings.setPadding(insets.left, 0, insets.right, 0); + int textPadding = getResources().getDimensionPixelSize(R.dimen.spacing_large); + mBinding.oldControllerSettingsWarning.setPadding(textPadding + insets.left, textPadding, + textPadding + insets.right, textPadding + insets.bottom); + InsetsHelper.applyNavbarWorkaround(insets.bottom, mBinding.workaroundView); ThemeHelper.setNavigationBarColor(this, MaterialColors.getColor(mBinding.appbarSettings, R.attr.colorSurface)); diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsActivityView.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsActivityView.java index c835aa4399..464d0ea165 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsActivityView.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsActivityView.java @@ -5,6 +5,7 @@ package org.dolphinemu.dolphinemu.features.settings.ui; import android.os.Bundle; import androidx.annotation.NonNull; +import androidx.fragment.app.DialogFragment; import org.dolphinemu.dolphinemu.features.settings.model.Settings; @@ -21,6 +22,13 @@ public interface SettingsActivityView */ void showSettingsFragment(MenuTag menuTag, Bundle extras, boolean addToStack, String gameId); + /** + * Shows a DialogFragment. + * + * Only one can be shown at a time. + */ + void showDialogFragment(DialogFragment fragment); + /** * Called by a contained Fragment to get access to the Setting HashMap * loaded from disk, so that each Fragment doesn't need to perform its own @@ -60,6 +68,14 @@ public interface SettingsActivityView */ void onSettingChanged(); + /** + * Refetches the values of all controller settings. + * + * To be used when loading an input profile or performing some other action that changes all + * controller settings at once. + */ + void onControllerSettingsChanged(); + /** * Called by a containing Fragment to tell the containing Activity that the user wants to open the * MenuTag associated with a setting. @@ -97,4 +113,25 @@ public interface SettingsActivityView * Accesses the material toolbar layout and changes the title */ void setToolbarTitle(String title); + + /** + * Sets whether the input mapping dialog should detect inputs from all devices, + * not just the device configured for the controller. + */ + void setMappingAllDevices(boolean allDevices); + + /** + * Returns whether the input mapping dialog should detect inputs from all devices, + * not just the device configured for the controller. + */ + boolean isMappingAllDevices(); + + /** + * Shows or hides a warning telling the user that they're using incompatible controller settings. + * The warning is hidden by default. + * + * @param visible Whether the warning should be visible. + * @return The height of the warning view, or 0 if the view is now invisible. + */ + int setOldControllerSettingsWarningVisibility(boolean visible); } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.java index a7f028afe3..ca405b15fd 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.java @@ -31,21 +31,24 @@ import com.google.android.material.timepicker.MaterialTimePicker; import com.google.android.material.timepicker.TimeFormat; import org.dolphinemu.dolphinemu.R; +import org.dolphinemu.dolphinemu.databinding.DialogAdvancedMappingBinding; import org.dolphinemu.dolphinemu.databinding.DialogInputStringBinding; import org.dolphinemu.dolphinemu.databinding.DialogSliderBinding; import org.dolphinemu.dolphinemu.databinding.ListItemHeaderBinding; +import org.dolphinemu.dolphinemu.databinding.ListItemMappingBinding; import org.dolphinemu.dolphinemu.databinding.ListItemSettingBinding; import org.dolphinemu.dolphinemu.databinding.ListItemSettingSwitchBinding; import org.dolphinemu.dolphinemu.databinding.ListItemSubmenuBinding; -import org.dolphinemu.dolphinemu.dialogs.MotionAlertDialog; +import org.dolphinemu.dolphinemu.features.input.ui.AdvancedMappingDialog; +import org.dolphinemu.dolphinemu.features.input.ui.MotionAlertDialog; +import org.dolphinemu.dolphinemu.features.input.model.view.InputMappingControlSetting; +import org.dolphinemu.dolphinemu.features.input.ui.viewholder.InputMappingControlSettingViewHolder; import org.dolphinemu.dolphinemu.features.settings.model.Settings; import org.dolphinemu.dolphinemu.features.settings.model.view.DateTimeChoiceSetting; import org.dolphinemu.dolphinemu.features.settings.model.view.SwitchSetting; import org.dolphinemu.dolphinemu.features.settings.model.view.FilePicker; import org.dolphinemu.dolphinemu.features.settings.model.view.FloatSliderSetting; -import org.dolphinemu.dolphinemu.features.settings.model.view.InputBindingSetting; import org.dolphinemu.dolphinemu.features.settings.model.view.IntSliderSetting; -import org.dolphinemu.dolphinemu.features.settings.model.view.RumbleBindingSetting; import org.dolphinemu.dolphinemu.features.settings.model.view.SettingsItem; import org.dolphinemu.dolphinemu.features.settings.model.view.SingleChoiceSetting; import org.dolphinemu.dolphinemu.features.settings.model.view.SingleChoiceSettingDynamicDescriptions; @@ -57,9 +60,7 @@ import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.DateTimeSetting import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.FilePickerViewHolder; import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.HeaderHyperLinkViewHolder; import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.HeaderViewHolder; -import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.InputBindingSettingViewHolder; import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.InputStringSettingViewHolder; -import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.RumbleBindingViewHolder; import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.RunRunnableViewHolder; import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.SettingViewHolder; import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.SingleChoiceViewHolder; @@ -124,13 +125,9 @@ public final class SettingsAdapter extends RecyclerView.Adapter item.clearValue(getSettings())); + (dialogInterface, i) -> item.clearValue()); dialog.setOnDismissListener(dialog1 -> { notifyItemChanged(position); @@ -342,6 +362,44 @@ public final class SettingsAdapter extends RecyclerView.Adapter + { + item.setValue(dialog.getExpression()); + notifyItemChanged(position); + mView.onSettingChanged(); + }); + dialog.setButton(AlertDialog.BUTTON_NEGATIVE, mContext.getString(R.string.cancel), this); + dialog.setButton(AlertDialog.BUTTON_NEUTRAL, mContext.getString(R.string.clear), + (dialogInterface, i) -> + { + item.clearValue(); + notifyItemChanged(position); + mView.onSettingChanged(); + }); + dialog.setCanceledOnTouchOutside(false); + dialog.show(); + } + public void onFilePickerDirectoryClick(SettingsItem item, int position) { mClickedItem = item; diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragment.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragment.java index 81c7321339..1a7905565d 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragment.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragment.java @@ -13,6 +13,7 @@ import androidx.annotation.Nullable; import androidx.core.graphics.Insets; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; +import androidx.fragment.app.DialogFragment; import androidx.fragment.app.Fragment; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -36,6 +37,8 @@ public final class SettingsFragment extends Fragment implements SettingsFragment private SettingsAdapter mAdapter; + private int mOldControllerSettingsWarningHeight = 0; + private static final Map titles = new HashMap<>(); static @@ -65,14 +68,26 @@ public final class SettingsFragment extends Fragment implements SettingsFragment titles.put(MenuTag.GCPAD_2, R.string.controller_1); titles.put(MenuTag.GCPAD_3, R.string.controller_2); titles.put(MenuTag.GCPAD_4, R.string.controller_3); - titles.put(MenuTag.WIIMOTE_1, R.string.wiimote_4); - titles.put(MenuTag.WIIMOTE_2, R.string.wiimote_5); - titles.put(MenuTag.WIIMOTE_3, R.string.wiimote_6); - titles.put(MenuTag.WIIMOTE_4, R.string.wiimote_7); - titles.put(MenuTag.WIIMOTE_EXTENSION_1, R.string.wiimote_extension_4); - titles.put(MenuTag.WIIMOTE_EXTENSION_2, R.string.wiimote_extension_5); - titles.put(MenuTag.WIIMOTE_EXTENSION_3, R.string.wiimote_extension_6); - titles.put(MenuTag.WIIMOTE_EXTENSION_4, R.string.wiimote_extension_7); + titles.put(MenuTag.WIIMOTE_1, R.string.wiimote_0); + titles.put(MenuTag.WIIMOTE_2, R.string.wiimote_1); + titles.put(MenuTag.WIIMOTE_3, R.string.wiimote_2); + titles.put(MenuTag.WIIMOTE_4, R.string.wiimote_3); + titles.put(MenuTag.WIIMOTE_EXTENSION_1, R.string.wiimote_extension_0); + titles.put(MenuTag.WIIMOTE_EXTENSION_2, R.string.wiimote_extension_1); + titles.put(MenuTag.WIIMOTE_EXTENSION_3, R.string.wiimote_extension_2); + titles.put(MenuTag.WIIMOTE_EXTENSION_4, R.string.wiimote_extension_3); + titles.put(MenuTag.WIIMOTE_GENERAL_1, R.string.wiimote_general); + titles.put(MenuTag.WIIMOTE_GENERAL_2, R.string.wiimote_general); + titles.put(MenuTag.WIIMOTE_GENERAL_3, R.string.wiimote_general); + titles.put(MenuTag.WIIMOTE_GENERAL_4, R.string.wiimote_general); + titles.put(MenuTag.WIIMOTE_MOTION_SIMULATION_1, R.string.wiimote_motion_simulation); + titles.put(MenuTag.WIIMOTE_MOTION_SIMULATION_2, R.string.wiimote_motion_simulation); + titles.put(MenuTag.WIIMOTE_MOTION_SIMULATION_3, R.string.wiimote_motion_simulation); + titles.put(MenuTag.WIIMOTE_MOTION_SIMULATION_4, R.string.wiimote_motion_simulation); + titles.put(MenuTag.WIIMOTE_MOTION_INPUT_1, R.string.wiimote_motion_input); + titles.put(MenuTag.WIIMOTE_MOTION_INPUT_2, R.string.wiimote_motion_input); + titles.put(MenuTag.WIIMOTE_MOTION_INPUT_3, R.string.wiimote_motion_input); + titles.put(MenuTag.WIIMOTE_MOTION_INPUT_4, R.string.wiimote_motion_input); } private FragmentSettingsBinding mBinding; @@ -203,6 +218,12 @@ public final class SettingsFragment extends Fragment implements SettingsFragment mActivity.showSettingsFragment(menuKey, null, true, getArguments().getString(ARGUMENT_GAME_ID)); } + @Override + public void showDialogFragment(DialogFragment fragment) + { + mActivity.showDialogFragment(fragment); + } + @Override public void showToastMessage(String message) { @@ -221,6 +242,13 @@ public final class SettingsFragment extends Fragment implements SettingsFragment mActivity.onSettingChanged(); } + @Override + public void onControllerSettingsChanged() + { + mAdapter.notifyAllSettingsChanged(); + mPresenter.updateOldControllerSettingsWarningVisibility(); + } + @Override public void onMenuTagAction(@NonNull MenuTag menuTag, int value) { @@ -232,13 +260,35 @@ public final class SettingsFragment extends Fragment implements SettingsFragment return mActivity.hasMenuTagActionForValue(menuTag, value); } + @Override + public void setMappingAllDevices(boolean allDevices) + { + mActivity.setMappingAllDevices(allDevices); + } + + @Override + public boolean isMappingAllDevices() + { + return mActivity.isMappingAllDevices(); + } + + @Override + public void setOldControllerSettingsWarningVisibility(boolean visible) + { + mOldControllerSettingsWarningHeight = + mActivity.setOldControllerSettingsWarningVisibility(visible); + + // Trigger the insets listener we've registered + mBinding.listSettings.requestApplyInsets(); + } + private void setInsets() { ViewCompat.setOnApplyWindowInsetsListener(mBinding.listSettings, (v, windowInsets) -> { Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()); - v.setPadding(0, 0, 0, - insets.bottom + getResources().getDimensionPixelSize(R.dimen.spacing_list)); + int listSpacing = getResources().getDimensionPixelSize(R.dimen.spacing_list); + v.setPadding(0, 0, 0, insets.bottom + listSpacing + mOldControllerSettingsWarningHeight); return windowInsets; }); } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.java index 3604d53087..cd39f8c10f 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.java @@ -10,13 +10,24 @@ import android.text.TextUtils; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; +import androidx.collection.ArraySet; import org.dolphinemu.dolphinemu.NativeLibrary; import org.dolphinemu.dolphinemu.R; import org.dolphinemu.dolphinemu.activities.UserDataActivity; +import org.dolphinemu.dolphinemu.features.input.model.InputMappingBooleanSetting; +import org.dolphinemu.dolphinemu.features.input.model.InputMappingDoubleSetting; +import org.dolphinemu.dolphinemu.features.input.model.InputMappingIntSetting; +import org.dolphinemu.dolphinemu.features.input.model.controlleremu.ControlGroup; +import org.dolphinemu.dolphinemu.features.input.model.ControlGroupEnabledSetting; +import org.dolphinemu.dolphinemu.features.input.model.controlleremu.EmulatedController; +import org.dolphinemu.dolphinemu.features.input.model.controlleremu.NumericSetting; +import org.dolphinemu.dolphinemu.features.input.model.view.InputDeviceSetting; +import org.dolphinemu.dolphinemu.features.input.model.view.InputMappingControlSetting; +import org.dolphinemu.dolphinemu.features.input.ui.ProfileDialog; +import org.dolphinemu.dolphinemu.features.input.ui.ProfileDialogPresenter; import org.dolphinemu.dolphinemu.features.settings.model.AbstractBooleanSetting; import org.dolphinemu.dolphinemu.features.settings.model.AbstractIntSetting; -import org.dolphinemu.dolphinemu.features.settings.model.AbstractStringSetting; import org.dolphinemu.dolphinemu.features.settings.model.AdHocBooleanSetting; import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting; import org.dolphinemu.dolphinemu.features.settings.model.FloatSetting; @@ -26,26 +37,23 @@ import org.dolphinemu.dolphinemu.features.settings.model.PostProcessing; import org.dolphinemu.dolphinemu.features.settings.model.ScaledIntSetting; import org.dolphinemu.dolphinemu.features.settings.model.Settings; import org.dolphinemu.dolphinemu.features.settings.model.StringSetting; -import org.dolphinemu.dolphinemu.features.settings.model.WiimoteProfileStringSetting; import org.dolphinemu.dolphinemu.features.settings.model.view.DateTimeChoiceSetting; import org.dolphinemu.dolphinemu.features.settings.model.view.SwitchSetting; import org.dolphinemu.dolphinemu.features.settings.model.view.FilePicker; +import org.dolphinemu.dolphinemu.features.settings.model.view.FloatSliderSetting; import org.dolphinemu.dolphinemu.features.settings.model.view.HeaderSetting; import org.dolphinemu.dolphinemu.features.settings.model.view.HyperLinkHeaderSetting; -import org.dolphinemu.dolphinemu.features.settings.model.view.InputBindingSetting; import org.dolphinemu.dolphinemu.features.settings.model.view.InputStringSetting; import org.dolphinemu.dolphinemu.features.settings.model.view.IntSliderSetting; import org.dolphinemu.dolphinemu.features.settings.model.view.InvertedSwitchSetting; import org.dolphinemu.dolphinemu.features.settings.model.view.LogSwitchSetting; import org.dolphinemu.dolphinemu.features.settings.model.view.PercentSliderSetting; -import org.dolphinemu.dolphinemu.features.settings.model.view.RumbleBindingSetting; import org.dolphinemu.dolphinemu.features.settings.model.view.RunRunnable; import org.dolphinemu.dolphinemu.features.settings.model.view.SettingsItem; import org.dolphinemu.dolphinemu.features.settings.model.view.SingleChoiceSetting; import org.dolphinemu.dolphinemu.features.settings.model.view.SingleChoiceSettingDynamicDescriptions; import org.dolphinemu.dolphinemu.features.settings.model.view.StringSingleChoiceSetting; import org.dolphinemu.dolphinemu.features.settings.model.view.SubmenuSetting; -import org.dolphinemu.dolphinemu.features.settings.utils.SettingsFile; import org.dolphinemu.dolphinemu.ui.main.MainPresenter; import org.dolphinemu.dolphinemu.utils.BooleanSupplier; import org.dolphinemu.dolphinemu.utils.EGLHelper; @@ -54,8 +62,11 @@ import org.dolphinemu.dolphinemu.utils.ThreadUtil; import org.dolphinemu.dolphinemu.utils.WiiUtils; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; +import java.util.Set; public final class SettingsFragmentPresenter { @@ -72,6 +83,7 @@ public final class SettingsFragmentPresenter private Settings mSettings; private ArrayList mSettingsList; + private boolean mHasOldControllerSettings = false; private int mSerialPort1Type; private int mControllerNumber; @@ -136,6 +148,7 @@ public final class SettingsFragmentPresenter else { mView.showSettingsList(mSettingsList); + mView.setOldControllerSettingsWarningVisibility(mHasOldControllerSettings); } } @@ -246,6 +259,27 @@ public final class SettingsFragmentPresenter addExtensionTypeSettings(sl, mControllerNumber, mControllerType); break; + case WIIMOTE_GENERAL_1: + case WIIMOTE_GENERAL_2: + case WIIMOTE_GENERAL_3: + case WIIMOTE_GENERAL_4: + addWiimoteGeneralSubSettings(sl, mControllerNumber); + break; + + case WIIMOTE_MOTION_SIMULATION_1: + case WIIMOTE_MOTION_SIMULATION_2: + case WIIMOTE_MOTION_SIMULATION_3: + case WIIMOTE_MOTION_SIMULATION_4: + addWiimoteMotionSimulationSubSettings(sl, mControllerNumber); + break; + + case WIIMOTE_MOTION_INPUT_1: + case WIIMOTE_MOTION_INPUT_2: + case WIIMOTE_MOTION_INPUT_3: + case WIIMOTE_MOTION_INPUT_4: + addWiimoteMotionInputSubSettings(sl, mControllerNumber); + break; + default: throw new UnsupportedOperationException("Unimplemented menu"); } @@ -259,13 +293,10 @@ public final class SettingsFragmentPresenter sl.add(new SubmenuSetting(mContext, R.string.config, MenuTag.CONFIG)); sl.add(new SubmenuSetting(mContext, R.string.graphics_settings, MenuTag.GRAPHICS)); - if (!NativeLibrary.IsRunning()) + sl.add(new SubmenuSetting(mContext, R.string.gcpad_settings, MenuTag.GCPAD_TYPE)); + if (mSettings.isWii()) { - sl.add(new SubmenuSetting(mContext, R.string.gcpad_settings, MenuTag.GCPAD_TYPE)); - if (mSettings.isWii()) - { - sl.add(new SubmenuSetting(mContext, R.string.wiimote_settings, MenuTag.WIIMOTE)); - } + sl.add(new SubmenuSetting(mContext, R.string.wiimote_settings, MenuTag.WIIMOTE)); } sl.add(new HeaderSetting(mContext, R.string.setting_clear_info, 0)); @@ -801,14 +832,14 @@ public final class SettingsFragmentPresenter private void addWiimoteSettings(ArrayList sl) { - sl.add(new SingleChoiceSetting(mContext, IntSetting.WIIMOTE_1_SOURCE, R.string.wiimote_4, 0, - R.array.wiimoteTypeEntries, R.array.wiimoteTypeValues, MenuTag.getWiimoteMenuTag(4))); - sl.add(new SingleChoiceSetting(mContext, IntSetting.WIIMOTE_2_SOURCE, R.string.wiimote_5, 0, - R.array.wiimoteTypeEntries, R.array.wiimoteTypeValues, MenuTag.getWiimoteMenuTag(5))); - sl.add(new SingleChoiceSetting(mContext, IntSetting.WIIMOTE_3_SOURCE, R.string.wiimote_6, 0, - R.array.wiimoteTypeEntries, R.array.wiimoteTypeValues, MenuTag.getWiimoteMenuTag(6))); - sl.add(new SingleChoiceSetting(mContext, IntSetting.WIIMOTE_4_SOURCE, R.string.wiimote_7, 0, - R.array.wiimoteTypeEntries, R.array.wiimoteTypeValues, MenuTag.getWiimoteMenuTag(7))); + sl.add(new SingleChoiceSetting(mContext, IntSetting.WIIMOTE_1_SOURCE, R.string.wiimote_0, 0, + R.array.wiimoteTypeEntries, R.array.wiimoteTypeValues, MenuTag.getWiimoteMenuTag(0))); + sl.add(new SingleChoiceSetting(mContext, IntSetting.WIIMOTE_2_SOURCE, R.string.wiimote_1, 0, + R.array.wiimoteTypeEntries, R.array.wiimoteTypeValues, MenuTag.getWiimoteMenuTag(1))); + sl.add(new SingleChoiceSetting(mContext, IntSetting.WIIMOTE_3_SOURCE, R.string.wiimote_2, 0, + R.array.wiimoteTypeEntries, R.array.wiimoteTypeValues, MenuTag.getWiimoteMenuTag(2))); + sl.add(new SingleChoiceSetting(mContext, IntSetting.WIIMOTE_4_SOURCE, R.string.wiimote_3, 0, + R.array.wiimoteTypeEntries, R.array.wiimoteTypeValues, MenuTag.getWiimoteMenuTag(3))); } private void addGraphicsSettings(ArrayList sl) @@ -1079,62 +1110,17 @@ public final class SettingsFragmentPresenter { if (gcPadType == 6) // Emulated { - sl.add(new HeaderSetting(mContext, R.string.generic_buttons, 0)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_GCBIND_A + gcPadNumber, R.string.button_a, mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_GCBIND_B + gcPadNumber, R.string.button_b, mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_GCBIND_X + gcPadNumber, R.string.button_x, mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_GCBIND_Y + gcPadNumber, R.string.button_y, mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_GCBIND_Z + gcPadNumber, R.string.button_z, mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_GCBIND_START + gcPadNumber, R.string.button_start, mGameID)); + EmulatedController gcPad = EmulatedController.getGcPad(gcPadNumber); - sl.add(new HeaderSetting(mContext, R.string.controller_control, 0)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_GCBIND_CONTROL_UP + gcPadNumber, R.string.generic_up, mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_GCBIND_CONTROL_DOWN + gcPadNumber, R.string.generic_down, mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_GCBIND_CONTROL_LEFT + gcPadNumber, R.string.generic_left, mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_GCBIND_CONTROL_RIGHT + gcPadNumber, R.string.generic_right, - mGameID)); - - sl.add(new HeaderSetting(mContext, R.string.controller_c, 0)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_GCBIND_C_UP + gcPadNumber, R.string.generic_up, mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_GCBIND_C_DOWN + gcPadNumber, R.string.generic_down, mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_GCBIND_C_LEFT + gcPadNumber, R.string.generic_left, mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_GCBIND_C_RIGHT + gcPadNumber, R.string.generic_right, mGameID)); - - sl.add(new HeaderSetting(mContext, R.string.controller_trig, 0)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_GCBIND_TRIGGER_L + gcPadNumber, R.string.trigger_left, mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_GCBIND_TRIGGER_R + gcPadNumber, R.string.trigger_right, mGameID)); - - sl.add(new HeaderSetting(mContext, R.string.controller_dpad, 0)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_GCBIND_DPAD_UP + gcPadNumber, R.string.generic_up, mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_GCBIND_DPAD_DOWN + gcPadNumber, R.string.generic_down, mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_GCBIND_DPAD_LEFT + gcPadNumber, R.string.generic_left, mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_GCBIND_DPAD_RIGHT + gcPadNumber, R.string.generic_right, mGameID)); - - - sl.add(new HeaderSetting(mContext, R.string.emulation_control_rumble, 0)); - sl.add(new RumbleBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_EMU_RUMBLE + gcPadNumber, R.string.emulation_control_rumble, - mGameID)); + if (!TextUtils.isEmpty(mGameID)) + { + addControllerPerGameSettings(sl, "Pad", gcPadNumber); + } + else + { + addControllerMetaSettings(sl, gcPad); + addControllerMappingSettings(sl, gcPad, null); + } } else if (gcPadType == 12) // Adapter { @@ -1147,440 +1133,227 @@ public final class SettingsFragmentPresenter private void addWiimoteSubSettings(ArrayList sl, int wiimoteNumber) { - // Bindings use controller numbers 4-7 (0-3 are GameCube), but the extension setting uses 1-4. - // But game specific extension settings are saved in their own profile. These profiles - // do not have any way to specify the controller that is loaded outside of knowing the filename - // of the profile that was loaded. - AbstractStringSetting extension; - final String defaultExtension = "None"; - if (mGameID.isEmpty()) + EmulatedController wiimote = EmulatedController.getWiimote(wiimoteNumber); + + if (!TextUtils.isEmpty(mGameID)) { - extension = new LegacyStringSetting(Settings.FILE_WIIMOTE, - Settings.SECTION_WIIMOTE + (wiimoteNumber - 3), SettingsFile.KEY_WIIMOTE_EXTENSION, - defaultExtension); + addControllerPerGameSettings(sl, "Wiimote", wiimoteNumber); } else { - extension = new WiimoteProfileStringSetting(mGameID, wiimoteNumber - 4, - Settings.SECTION_PROFILE, SettingsFile.KEY_WIIMOTE_EXTENSION, defaultExtension); + addControllerMetaSettings(sl, wiimote); + + sl.add(new HeaderSetting(mContext, R.string.wiimote, 0)); + sl.add(new SubmenuSetting(mContext, R.string.wiimote_general, + MenuTag.getWiimoteGeneralMenuTag(wiimoteNumber))); + sl.add(new SubmenuSetting(mContext, R.string.wiimote_motion_simulation, + MenuTag.getWiimoteMotionSimulationMenuTag(wiimoteNumber))); + sl.add(new SubmenuSetting(mContext, R.string.wiimote_motion_input, + MenuTag.getWiimoteMotionInputMenuTag(wiimoteNumber))); + + // TYPE_OTHER is included here instead of in addWiimoteGeneralSubSettings so that touchscreen + // users won't have to dig into a submenu to find the Sideways Wii Remote setting + addControllerMappingSettings(sl, wiimote, new ArraySet<>( + Arrays.asList(ControlGroup.TYPE_ATTACHMENTS, ControlGroup.TYPE_OTHER))); } - - sl.add(new StringSingleChoiceSetting(mContext, extension, R.string.wiimote_extensions, 0, - R.array.wiimoteExtensionsEntries, R.array.wiimoteExtensionsValues, - MenuTag.getWiimoteExtensionMenuTag(wiimoteNumber))); - - sl.add(new HeaderSetting(mContext, R.string.generic_buttons, 0)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_A + wiimoteNumber, R.string.button_a, mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_B + wiimoteNumber, R.string.button_b, mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_1 + wiimoteNumber, R.string.button_one, mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_2 + wiimoteNumber, R.string.button_two, mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_MINUS + wiimoteNumber, R.string.button_minus, mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_PLUS + wiimoteNumber, R.string.button_plus, mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_HOME + wiimoteNumber, R.string.button_home, mGameID)); - - sl.add(new HeaderSetting(mContext, R.string.wiimote_ir, 0)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_IR_UP + wiimoteNumber, R.string.generic_up, mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_IR_DOWN + wiimoteNumber, R.string.generic_down, mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_IR_LEFT + wiimoteNumber, R.string.generic_left, mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_IR_RIGHT + wiimoteNumber, R.string.generic_right, mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_IR_FORWARD + wiimoteNumber, R.string.generic_forward, - mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_IR_BACKWARD + wiimoteNumber, R.string.generic_backward, - mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_IR_HIDE + wiimoteNumber, R.string.ir_hide, mGameID)); - - sl.add(new HeaderSetting(mContext, R.string.wiimote_swing, 0)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_SWING_UP + wiimoteNumber, R.string.generic_up, mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_SWING_DOWN + wiimoteNumber, R.string.generic_down, mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_SWING_LEFT + wiimoteNumber, R.string.generic_left, mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_SWING_RIGHT + wiimoteNumber, R.string.generic_right, mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_SWING_FORWARD + wiimoteNumber, R.string.generic_forward, - mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_SWING_BACKWARD + wiimoteNumber, R.string.generic_backward, - mGameID)); - - sl.add(new HeaderSetting(mContext, R.string.wiimote_tilt, 0)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_TILT_FORWARD + wiimoteNumber, R.string.generic_forward, - mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_TILT_BACKWARD + wiimoteNumber, R.string.generic_backward, - mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_TILT_LEFT + wiimoteNumber, R.string.generic_left, mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_TILT_RIGHT + wiimoteNumber, R.string.generic_right, mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_TILT_MODIFIER + wiimoteNumber, R.string.tilt_modifier, - mGameID)); - - sl.add(new HeaderSetting(mContext, R.string.wiimote_shake, 0)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_SHAKE_X + wiimoteNumber, R.string.shake_x, mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_SHAKE_Y + wiimoteNumber, R.string.shake_y, mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_SHAKE_Z + wiimoteNumber, R.string.shake_z, mGameID)); - - sl.add(new HeaderSetting(mContext, R.string.controller_dpad, 0)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_DPAD_UP + wiimoteNumber, R.string.generic_up, mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_DPAD_DOWN + wiimoteNumber, R.string.generic_down, mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_DPAD_LEFT + wiimoteNumber, R.string.generic_left, mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_DPAD_RIGHT + wiimoteNumber, R.string.generic_right, mGameID)); - - - sl.add(new HeaderSetting(mContext, R.string.emulation_control_rumble, 0)); - sl.add(new RumbleBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_EMU_RUMBLE + wiimoteNumber, R.string.emulation_control_rumble, - mGameID)); } private void addExtensionTypeSettings(ArrayList sl, int wiimoteNumber, - int extentionType) + int extensionType) { - switch (extentionType) + addControllerMappingSettings(sl, + EmulatedController.getWiimoteAttachment(wiimoteNumber, extensionType), null); + } + + private void addWiimoteGeneralSubSettings(ArrayList sl, int wiimoteNumber) + { + addControllerMappingSettings(sl, EmulatedController.getWiimote(wiimoteNumber), + Collections.singleton(ControlGroup.TYPE_BUTTONS)); + } + + private void addWiimoteMotionSimulationSubSettings(ArrayList sl, int wiimoteNumber) + { + addControllerMappingSettings(sl, EmulatedController.getWiimote(wiimoteNumber), + new ArraySet<>(Arrays.asList(ControlGroup.TYPE_FORCE, ControlGroup.TYPE_TILT, + ControlGroup.TYPE_CURSOR, ControlGroup.TYPE_SHAKE))); + } + + private void addWiimoteMotionInputSubSettings(ArrayList sl, int wiimoteNumber) + { + addControllerMappingSettings(sl, EmulatedController.getWiimote(wiimoteNumber), + new ArraySet<>(Arrays.asList(ControlGroup.TYPE_IMU_ACCELEROMETER, + ControlGroup.TYPE_IMU_GYROSCOPE, ControlGroup.TYPE_IMU_CURSOR))); + } + + /** + * Adds controller settings that can be set on a per-game basis. + * + * @param sl The list to place controller settings into. + * @param profileString The prefix used for the profile setting in game INI files. + * @param controllerNumber The index of the controller, 0-3. + */ + private void addControllerPerGameSettings(ArrayList sl, String profileString, + int controllerNumber) + { + String[] profiles = new ProfileDialogPresenter(mMenuTag).getProfileNames(false); + String profileKey = profileString + "Profile" + controllerNumber; + sl.add(new StringSingleChoiceSetting(mContext, + new LegacyStringSetting("", "Controls", profileKey, ""), + R.string.input_profile, 0, profiles, profiles, R.string.input_profiles_empty)); + } + + /** + * Adds settings and actions that apply to a controller as a whole. + * For instance, the device setting and the Clear action. + * + * @param sl The list to place controller settings into. + * @param controller The controller to add settings for. + */ + private void addControllerMetaSettings(ArrayList sl, EmulatedController controller) + { + sl.add(new InputDeviceSetting(mContext, R.string.input_device, 0, controller)); + + sl.add(new SwitchSetting(mContext, new AbstractBooleanSetting() { - case 1: // Nunchuk - sl.add(new HeaderSetting(mContext, R.string.generic_buttons, 0)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_NUNCHUK_C + wiimoteNumber, R.string.nunchuk_button_c, - mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_NUNCHUK_Z + wiimoteNumber, R.string.button_z, mGameID)); + @Override + public boolean isOverridden(Settings settings) + { + return false; + } - sl.add(new HeaderSetting(mContext, R.string.generic_stick, 0)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_NUNCHUK_UP + wiimoteNumber, R.string.generic_up, mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_NUNCHUK_DOWN + wiimoteNumber, R.string.generic_down, - mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_NUNCHUK_LEFT + wiimoteNumber, R.string.generic_left, - mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_NUNCHUK_RIGHT + wiimoteNumber, R.string.generic_right, - mGameID)); + @Override + public boolean isRuntimeEditable() + { + return true; + } - sl.add(new HeaderSetting(mContext, R.string.wiimote_swing, 0)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_NUNCHUK_SWING_UP + wiimoteNumber, R.string.generic_up, - mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_NUNCHUK_SWING_DOWN + wiimoteNumber, R.string.generic_down, - mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_NUNCHUK_SWING_LEFT + wiimoteNumber, R.string.generic_left, - mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_NUNCHUK_SWING_RIGHT + wiimoteNumber, - R.string.generic_right, mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_NUNCHUK_SWING_FORWARD + wiimoteNumber, - R.string.generic_forward, mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_NUNCHUK_SWING_BACKWARD + wiimoteNumber, - R.string.generic_backward, mGameID)); + @Override + public boolean delete(Settings settings) + { + mView.setMappingAllDevices(false); + return true; + } - sl.add(new HeaderSetting(mContext, R.string.wiimote_tilt, 0)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_NUNCHUK_TILT_FORWARD + wiimoteNumber, - R.string.generic_forward, mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_NUNCHUK_TILT_BACKWARD + wiimoteNumber, - R.string.generic_backward, mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_NUNCHUK_TILT_LEFT + wiimoteNumber, R.string.generic_left, - mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_NUNCHUK_TILT_RIGHT + wiimoteNumber, R.string.generic_right, - mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_NUNCHUK_TILT_MODIFIER + wiimoteNumber, - R.string.tilt_modifier, mGameID)); + @Override + public boolean getBoolean(Settings settings) + { + return mView.isMappingAllDevices(); + } - sl.add(new HeaderSetting(mContext, R.string.wiimote_shake, 0)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_NUNCHUK_SHAKE_X + wiimoteNumber, R.string.shake_x, - mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_NUNCHUK_SHAKE_Y + wiimoteNumber, R.string.shake_y, - mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_NUNCHUK_SHAKE_Z + wiimoteNumber, R.string.shake_z, - mGameID)); + @Override + public void setBoolean(Settings settings, boolean newValue) + { + mView.setMappingAllDevices(newValue); + } + }, R.string.input_device_all_devices, R.string.input_device_all_devices_description)); + + sl.add(new RunRunnable(mContext, R.string.input_reset_to_default, + R.string.input_reset_to_default_description, R.string.input_reset_warning, 0, true, + () -> loadDefaultControllerSettings(controller))); + + sl.add(new RunRunnable(mContext, R.string.input_clear, R.string.input_clear_description, + R.string.input_reset_warning, 0, true, () -> clearControllerSettings(controller))); + + sl.add(new RunRunnable(mContext, R.string.input_profiles, 0, 0, 0, true, + () -> mView.showDialogFragment(ProfileDialog.create(mMenuTag)))); + + updateOldControllerSettingsWarningVisibility(controller); + } + + /** + * Adds mapping settings and other control-specific settings. + * + * @param sl The list to place controller settings into. + * @param controller The controller to add settings for. + * @param groupTypeFilter If this is non-null, only groups whose types match this are considered. + */ + private void addControllerMappingSettings(ArrayList sl, + EmulatedController controller, Set groupTypeFilter) + { + updateOldControllerSettingsWarningVisibility(controller); + + int groupCount = controller.getGroupCount(); + for (int i = 0; i < groupCount; i++) + { + ControlGroup group = controller.getGroup(i); + int groupType = group.getGroupType(); + if (groupTypeFilter != null && !groupTypeFilter.contains(groupType)) + continue; + + sl.add(new HeaderSetting(group.getUiName(), "")); + + if (group.getDefaultEnabledValue() != ControlGroup.DEFAULT_ENABLED_ALWAYS) + { + sl.add(new SwitchSetting(mContext, new ControlGroupEnabledSetting(group), R.string.enabled, + 0)); + } + + int controlCount = group.getControlCount(); + for (int j = 0; j < controlCount; j++) + { + sl.add(new InputMappingControlSetting(group.getControl(j), controller)); + } + + if (groupType == ControlGroup.TYPE_ATTACHMENTS) + { + NumericSetting attachmentSetting = group.getAttachmentSetting(); + sl.add(new SingleChoiceSetting(mContext, new InputMappingIntSetting(attachmentSetting), + R.string.wiimote_extensions, 0, R.array.wiimoteExtensionsEntries, + R.array.wiimoteExtensionsValues, + MenuTag.getWiimoteExtensionMenuTag(mControllerNumber))); + } + + int numericSettingCount = group.getNumericSettingCount(); + for (int j = 0; j < numericSettingCount; j++) + { + addNumericSetting(sl, group.getNumericSetting(j)); + } + } + } + + private void addNumericSetting(ArrayList sl, NumericSetting setting) + { + switch (setting.getType()) + { + case NumericSetting.TYPE_DOUBLE: + sl.add(new FloatSliderSetting(new InputMappingDoubleSetting(setting), setting.getUiName(), + "", (int) Math.ceil(setting.getDoubleMin()), + (int) Math.floor(setting.getDoubleMax()), setting.getUiSuffix())); break; - case 2: // Classic - sl.add(new HeaderSetting(mContext, R.string.generic_buttons, 0)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_CLASSIC_A + wiimoteNumber, R.string.button_a, mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_CLASSIC_B + wiimoteNumber, R.string.button_b, mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_CLASSIC_X + wiimoteNumber, R.string.button_x, mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_CLASSIC_Y + wiimoteNumber, R.string.button_y, mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_CLASSIC_ZL + wiimoteNumber, R.string.classic_button_zl, - mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_CLASSIC_ZR + wiimoteNumber, R.string.classic_button_zr, - mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_CLASSIC_MINUS + wiimoteNumber, R.string.button_minus, - mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_CLASSIC_PLUS + wiimoteNumber, R.string.button_plus, - mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_CLASSIC_HOME + wiimoteNumber, R.string.button_home, - mGameID)); - - sl.add(new HeaderSetting(mContext, R.string.classic_leftstick, 0)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_CLASSIC_LEFT_UP + wiimoteNumber, R.string.generic_up, - mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_CLASSIC_LEFT_DOWN + wiimoteNumber, R.string.generic_down, - mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_CLASSIC_LEFT_LEFT + wiimoteNumber, R.string.generic_left, - mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_CLASSIC_LEFT_RIGHT + wiimoteNumber, R.string.generic_right, - mGameID)); - - sl.add(new HeaderSetting(mContext, R.string.classic_rightstick, 0)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_CLASSIC_RIGHT_UP + wiimoteNumber, R.string.generic_up, - mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_CLASSIC_RIGHT_DOWN + wiimoteNumber, R.string.generic_down, - mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_CLASSIC_RIGHT_LEFT + wiimoteNumber, R.string.generic_left, - mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_CLASSIC_RIGHT_RIGHT + wiimoteNumber, - R.string.generic_right, mGameID)); - - sl.add(new HeaderSetting(mContext, R.string.controller_trig, 0)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_CLASSIC_TRIGGER_L + wiimoteNumber, R.string.trigger_left, - mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_CLASSIC_TRIGGER_R + wiimoteNumber, R.string.trigger_right, - mGameID)); - - sl.add(new HeaderSetting(mContext, R.string.controller_dpad, 0)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_CLASSIC_DPAD_UP + wiimoteNumber, R.string.generic_up, - mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_CLASSIC_DPAD_DOWN + wiimoteNumber, R.string.generic_down, - mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_CLASSIC_DPAD_LEFT + wiimoteNumber, R.string.generic_left, - mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_CLASSIC_DPAD_RIGHT + wiimoteNumber, R.string.generic_right, - mGameID)); - break; - case 3: // Guitar - sl.add(new HeaderSetting(mContext, R.string.guitar_frets, 0)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_GUITAR_FRET_GREEN + wiimoteNumber, R.string.generic_green, - mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_GUITAR_FRET_RED + wiimoteNumber, R.string.generic_red, - mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_GUITAR_FRET_YELLOW + wiimoteNumber, - R.string.generic_yellow, mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_GUITAR_FRET_BLUE + wiimoteNumber, R.string.generic_blue, - mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_GUITAR_FRET_ORANGE + wiimoteNumber, - R.string.generic_orange, mGameID)); - - sl.add(new HeaderSetting(mContext, R.string.guitar_strum, 0)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_GUITAR_STRUM_UP + wiimoteNumber, R.string.generic_up, - mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_GUITAR_STRUM_DOWN + wiimoteNumber, R.string.generic_down, - mGameID)); - - sl.add(new HeaderSetting(mContext, R.string.generic_buttons, 0)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_GUITAR_MINUS + wiimoteNumber, R.string.button_minus, - mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_GUITAR_PLUS + wiimoteNumber, R.string.button_plus, - mGameID)); - - sl.add(new HeaderSetting(mContext, R.string.generic_stick, 0)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_GUITAR_STICK_UP + wiimoteNumber, R.string.generic_up, - mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_GUITAR_STICK_DOWN + wiimoteNumber, R.string.generic_down, - mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_GUITAR_STICK_LEFT + wiimoteNumber, R.string.generic_left, - mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_GUITAR_STICK_RIGHT + wiimoteNumber, R.string.generic_right, - mGameID)); - - sl.add(new HeaderSetting(mContext, R.string.guitar_whammy, 0)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_GUITAR_WHAMMY_BAR + wiimoteNumber, R.string.generic_right, - mGameID)); - break; - case 4: // Drums - sl.add(new HeaderSetting(mContext, R.string.drums_pads, 0)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_DRUMS_PAD_RED + wiimoteNumber, R.string.generic_red, - mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_DRUMS_PAD_YELLOW + wiimoteNumber, R.string.generic_yellow, - mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_DRUMS_PAD_BLUE + wiimoteNumber, R.string.generic_blue, - mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_DRUMS_PAD_GREEN + wiimoteNumber, R.string.generic_green, - mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_DRUMS_PAD_ORANGE + wiimoteNumber, R.string.generic_orange, - mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_DRUMS_PAD_BASS + wiimoteNumber, R.string.drums_pad_bass, - mGameID)); - - sl.add(new HeaderSetting(mContext, R.string.generic_stick, 0)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_DRUMS_STICK_UP + wiimoteNumber, R.string.generic_up, - mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_DRUMS_STICK_DOWN + wiimoteNumber, R.string.generic_down, - mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_DRUMS_STICK_LEFT + wiimoteNumber, R.string.generic_left, - mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_DRUMS_STICK_RIGHT + wiimoteNumber, R.string.generic_right, - mGameID)); - - sl.add(new HeaderSetting(mContext, R.string.generic_buttons, 0)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_DRUMS_MINUS + wiimoteNumber, R.string.button_minus, - mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_DRUMS_PLUS + wiimoteNumber, R.string.button_plus, - mGameID)); - break; - case 5: // Turntable - sl.add(new HeaderSetting(mContext, R.string.generic_buttons, 0)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_TURNTABLE_GREEN_LEFT + wiimoteNumber, - R.string.turntable_button_green_left, mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_TURNTABLE_RED_LEFT + wiimoteNumber, - R.string.turntable_button_red_left, mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_TURNTABLE_BLUE_LEFT + wiimoteNumber, - R.string.turntable_button_blue_left, mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_TURNTABLE_GREEN_RIGHT + wiimoteNumber, - R.string.turntable_button_green_right, mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_TURNTABLE_RED_RIGHT + wiimoteNumber, - R.string.turntable_button_red_right, mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_TURNTABLE_BLUE_RIGHT + wiimoteNumber, - R.string.turntable_button_blue_right, mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_TURNTABLE_MINUS + wiimoteNumber, - R.string.button_minus, mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_TURNTABLE_PLUS + wiimoteNumber, - R.string.button_plus, mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_TURNTABLE_EUPHORIA + wiimoteNumber, - R.string.turntable_button_euphoria, mGameID)); - - sl.add(new HeaderSetting(mContext, R.string.turntable_table_left, 0)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_TURNTABLE_LEFT_LEFT + wiimoteNumber, R.string.generic_left, - mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_TURNTABLE_LEFT_RIGHT + wiimoteNumber, - R.string.generic_right, mGameID)); - - sl.add(new HeaderSetting(mContext, R.string.turntable_table_right, 0)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_TURNTABLE_RIGHT_LEFT + wiimoteNumber, - R.string.generic_left, mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_TURNTABLE_RIGHT_RIGHT + wiimoteNumber, - R.string.generic_right, mGameID)); - - sl.add(new HeaderSetting(mContext, R.string.generic_stick, 0)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_TURNTABLE_STICK_UP + wiimoteNumber, R.string.generic_up, - mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_TURNTABLE_STICK_DOWN + wiimoteNumber, - R.string.generic_down, mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_TURNTABLE_STICK_LEFT + wiimoteNumber, - R.string.generic_left, mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_TURNTABLE_STICK_RIGHT + wiimoteNumber, - R.string.generic_right, mGameID)); - - sl.add(new HeaderSetting(mContext, R.string.turntable_effect, 0)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_TURNTABLE_EFFECT_DIAL + wiimoteNumber, - R.string.turntable_effect_dial, mGameID)); - - sl.add(new HeaderSetting(mContext, R.string.turntable_crossfade, 0)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_TURNTABLE_CROSSFADE_LEFT + wiimoteNumber, - R.string.generic_left, mGameID)); - sl.add(new InputBindingSetting(mContext, Settings.FILE_DOLPHIN, Settings.SECTION_BINDINGS, - SettingsFile.KEY_WIIBIND_TURNTABLE_CROSSFADE_RIGHT + wiimoteNumber, - R.string.generic_right, mGameID)); + case NumericSetting.TYPE_BOOLEAN: + sl.add(new SwitchSetting(new InputMappingBooleanSetting(setting), setting.getUiName(), + setting.getUiDescription())); break; } } + public void updateOldControllerSettingsWarningVisibility() + { + updateOldControllerSettingsWarningVisibility(mMenuTag.getCorrespondingEmulatedController()); + } + + private void updateOldControllerSettingsWarningVisibility(EmulatedController controller) + { + String defaultDevice = controller.getDefaultDevice(); + + mHasOldControllerSettings = defaultDevice.startsWith("Android/") && + defaultDevice.endsWith("/Touchscreen"); + + mView.setOldControllerSettingsWarningVisibility(mHasOldControllerSettings); + } + + private void loadDefaultControllerSettings(EmulatedController controller) + { + controller.loadDefaultSettings(); + mView.onControllerSettingsChanged(); + } + + private void clearControllerSettings(EmulatedController controller) + { + controller.clearSettings(); + mView.onControllerSettingsChanged(); + } + private static int getLogVerbosityEntries() { // Value obtained from LogLevel in Common/Logging/Log.h diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentView.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentView.java index 295ff95abd..809f26f7cc 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentView.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentView.java @@ -3,6 +3,7 @@ package org.dolphinemu.dolphinemu.features.settings.ui; import androidx.annotation.NonNull; +import androidx.fragment.app.DialogFragment; import androidx.fragment.app.FragmentActivity; import org.dolphinemu.dolphinemu.features.settings.model.Settings; @@ -55,6 +56,8 @@ public interface SettingsFragmentView */ void loadSubMenu(MenuTag menuKey); + void showDialogFragment(DialogFragment fragment); + /** * Tell the Fragment to tell the containing activity to display a toast message. * @@ -72,6 +75,14 @@ public interface SettingsFragmentView */ void onSettingChanged(); + /** + * Refetches the values of all controller settings. + * + * To be used when loading an input profile or performing some other action that changes all + * controller settings at once. + */ + void onControllerSettingsChanged(); + /** * Have the fragment tell the containing Activity that the user wants to open the MenuTag * associated with a setting. @@ -89,4 +100,24 @@ public interface SettingsFragmentView * @param value The current value of the setting. */ boolean hasMenuTagActionForValue(@NonNull MenuTag menuTag, int value); + + /** + * Sets whether the input mapping dialog should detect inputs from all devices, + * not just the device configured for the controller. + */ + void setMappingAllDevices(boolean allDevices); + + /** + * Returns whether the input mapping dialog should detect inputs from all devices, + * not just the device configured for the controller. + */ + boolean isMappingAllDevices(); + + /** + * Shows or hides a warning telling the user that they're using incompatible controller settings. + * The warning is hidden by default. + * + * @param visible Whether the warning should be visible. + */ + void setOldControllerSettingsWarningVisibility(boolean visible); } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/InputBindingSettingViewHolder.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/InputBindingSettingViewHolder.java deleted file mode 100644 index b61fcc458b..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/InputBindingSettingViewHolder.java +++ /dev/null @@ -1,67 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.dolphinemu.dolphinemu.features.settings.ui.viewholder; - -import android.content.Context; -import android.content.SharedPreferences; -import android.view.View; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.preference.PreferenceManager; - -import org.dolphinemu.dolphinemu.databinding.ListItemSettingBinding; -import org.dolphinemu.dolphinemu.features.settings.model.view.InputBindingSetting; -import org.dolphinemu.dolphinemu.features.settings.model.view.SettingsItem; -import org.dolphinemu.dolphinemu.features.settings.ui.SettingsAdapter; - -public final class InputBindingSettingViewHolder extends SettingViewHolder -{ - private InputBindingSetting mItem; - - private final Context mContext; - - private final ListItemSettingBinding mBinding; - - public InputBindingSettingViewHolder(@NonNull ListItemSettingBinding binding, - SettingsAdapter adapter, Context context) - { - super(binding.getRoot(), adapter); - mBinding = binding; - mContext = context; - } - - @Override - public void bind(SettingsItem item) - { - SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext); - - mItem = (InputBindingSetting) item; - - mBinding.textSettingName.setText(mItem.getName()); - mBinding.textSettingDescription - .setText(sharedPreferences.getString(mItem.getKey() + mItem.getGameId(), "")); - - setStyle(mBinding.textSettingName, mItem); - } - - @Override - public void onClick(View clicked) - { - if (!mItem.isEditable()) - { - showNotRuntimeEditableError(); - return; - } - - getAdapter().onInputBindingClick(mItem, getBindingAdapterPosition()); - - setStyle(mBinding.textSettingName, mItem); - } - - @Nullable @Override - protected SettingsItem getItem() - { - return mItem; - } -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/RumbleBindingViewHolder.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/RumbleBindingViewHolder.java deleted file mode 100644 index 3b7a6b0f75..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/RumbleBindingViewHolder.java +++ /dev/null @@ -1,67 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.dolphinemu.dolphinemu.features.settings.ui.viewholder; - -import android.content.Context; -import android.content.SharedPreferences; -import android.view.View; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.preference.PreferenceManager; - -import org.dolphinemu.dolphinemu.databinding.ListItemSettingBinding; -import org.dolphinemu.dolphinemu.features.settings.model.view.RumbleBindingSetting; -import org.dolphinemu.dolphinemu.features.settings.model.view.SettingsItem; -import org.dolphinemu.dolphinemu.features.settings.ui.SettingsAdapter; - -public class RumbleBindingViewHolder extends SettingViewHolder -{ - private RumbleBindingSetting mItem; - - private final Context mContext; - - private final ListItemSettingBinding mBinding; - - public RumbleBindingViewHolder(@NonNull ListItemSettingBinding binding, SettingsAdapter adapter, - Context context) - { - super(binding.getRoot(), adapter); - mBinding = binding; - mContext = context; - } - - @Override - public void bind(SettingsItem item) - { - SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext); - - mItem = (RumbleBindingSetting) item; - - mBinding.textSettingName.setText(item.getName()); - mBinding.textSettingDescription - .setText(sharedPreferences.getString(mItem.getKey() + mItem.getGameId(), "")); - - setStyle(mBinding.textSettingName, mItem); - } - - @Override - public void onClick(View clicked) - { - if (!mItem.isEditable()) - { - showNotRuntimeEditableError(); - return; - } - - getAdapter().onInputBindingClick(mItem, getBindingAdapterPosition()); - - setStyle(mBinding.textSettingName, mItem); - } - - @Nullable @Override - protected SettingsItem getItem() - { - return mItem; - } -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/SettingViewHolder.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/SettingViewHolder.java index d34e4a1536..18fd466461 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/SettingViewHolder.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/SettingViewHolder.java @@ -83,7 +83,7 @@ public abstract class SettingViewHolder extends RecyclerView.ViewHolder { SettingsItem item = getItem(); - if (item == null || !item.hasSetting()) + if (item == null || !item.canClear()) return false; if (!item.isEditable()) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/SingleChoiceViewHolder.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/SingleChoiceViewHolder.java index db56ebea40..beb15867c5 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/SingleChoiceViewHolder.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/SingleChoiceViewHolder.java @@ -64,10 +64,8 @@ public final class SingleChoiceViewHolder extends SettingViewHolder else if (item instanceof StringSingleChoiceSetting) { StringSingleChoiceSetting setting = (StringSingleChoiceSetting) item; - String[] choices = setting.getChoices(); - int valueIndex = setting.getSelectedValueIndex(settings); - if (valueIndex != -1) - mBinding.textSettingDescription.setText(choices[valueIndex]); + String choice = setting.getSelectedChoice(settings); + mBinding.textSettingDescription.setText(choice); } else if (item instanceof SingleChoiceSettingDynamicDescriptions) { diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/utils/SettingsFile.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/utils/SettingsFile.java index 87f58b4d02..acad8c2d19 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/utils/SettingsFile.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/utils/SettingsFile.java @@ -20,161 +20,6 @@ public final class SettingsFile public static final String KEY_ISO_PATH_BASE = "ISOPath"; public static final String KEY_ISO_PATHS = "ISOPaths"; - public static final String KEY_GCPAD_PLAYER_1 = "SIDevice0"; - - public static final String KEY_GCBIND_A = "InputA_"; - public static final String KEY_GCBIND_B = "InputB_"; - public static final String KEY_GCBIND_X = "InputX_"; - public static final String KEY_GCBIND_Y = "InputY_"; - public static final String KEY_GCBIND_Z = "InputZ_"; - public static final String KEY_GCBIND_START = "InputStart_"; - public static final String KEY_GCBIND_CONTROL_UP = "MainUp_"; - public static final String KEY_GCBIND_CONTROL_DOWN = "MainDown_"; - public static final String KEY_GCBIND_CONTROL_LEFT = "MainLeft_"; - public static final String KEY_GCBIND_CONTROL_RIGHT = "MainRight_"; - public static final String KEY_GCBIND_C_UP = "CStickUp_"; - public static final String KEY_GCBIND_C_DOWN = "CStickDown_"; - public static final String KEY_GCBIND_C_LEFT = "CStickLeft_"; - public static final String KEY_GCBIND_C_RIGHT = "CStickRight_"; - public static final String KEY_GCBIND_TRIGGER_L = "InputL_"; - public static final String KEY_GCBIND_TRIGGER_R = "InputR_"; - public static final String KEY_GCBIND_DPAD_UP = "DPadUp_"; - public static final String KEY_GCBIND_DPAD_DOWN = "DPadDown_"; - public static final String KEY_GCBIND_DPAD_LEFT = "DPadLeft_"; - public static final String KEY_GCBIND_DPAD_RIGHT = "DPadRight_"; - - public static final String KEY_EMU_RUMBLE = "EmuRumble"; - - public static final String KEY_WIIMOTE_EXTENSION = "Extension"; - - // Controller keys for game specific settings - public static final String KEY_WIIMOTE_PROFILE = "WiimoteProfile"; - - public static final String KEY_WIIBIND_A = "WiimoteA_"; - public static final String KEY_WIIBIND_B = "WiimoteB_"; - public static final String KEY_WIIBIND_1 = "Wiimote1_"; - public static final String KEY_WIIBIND_2 = "Wiimote2_"; - public static final String KEY_WIIBIND_MINUS = "WiimoteMinus_"; - public static final String KEY_WIIBIND_PLUS = "WiimotePlus_"; - public static final String KEY_WIIBIND_HOME = "WiimoteHome_"; - public static final String KEY_WIIBIND_IR_UP = "IRUp_"; - public static final String KEY_WIIBIND_IR_DOWN = "IRDown_"; - public static final String KEY_WIIBIND_IR_LEFT = "IRLeft_"; - public static final String KEY_WIIBIND_IR_RIGHT = "IRRight_"; - public static final String KEY_WIIBIND_IR_FORWARD = "IRForward_"; - public static final String KEY_WIIBIND_IR_BACKWARD = "IRBackward_"; - public static final String KEY_WIIBIND_IR_HIDE = "IRHide_"; - public static final String KEY_WIIBIND_IR_PITCH = "IRTotalPitch"; - public static final String KEY_WIIBIND_IR_YAW = "IRTotalYaw"; - public static final String KEY_WIIBIND_IR_VERTICAL_OFFSET = "IRVerticalOffset"; - public static final String KEY_WIIBIND_SWING_UP = "SwingUp_"; - public static final String KEY_WIIBIND_SWING_DOWN = "SwingDown_"; - public static final String KEY_WIIBIND_SWING_LEFT = "SwingLeft_"; - public static final String KEY_WIIBIND_SWING_RIGHT = "SwingRight_"; - public static final String KEY_WIIBIND_SWING_FORWARD = "SwingForward_"; - public static final String KEY_WIIBIND_SWING_BACKWARD = "SwingBackward_"; - public static final String KEY_WIIBIND_TILT_FORWARD = "TiltForward_"; - public static final String KEY_WIIBIND_TILT_BACKWARD = "TiltBackward_"; - public static final String KEY_WIIBIND_TILT_LEFT = "TiltLeft_"; - public static final String KEY_WIIBIND_TILT_RIGHT = "TiltRight_"; - public static final String KEY_WIIBIND_TILT_MODIFIER = "TiltModifier_"; - public static final String KEY_WIIBIND_SHAKE_X = "ShakeX_"; - public static final String KEY_WIIBIND_SHAKE_Y = "ShakeY_"; - public static final String KEY_WIIBIND_SHAKE_Z = "ShakeZ_"; - public static final String KEY_WIIBIND_DPAD_UP = "WiimoteUp_"; - public static final String KEY_WIIBIND_DPAD_DOWN = "WiimoteDown_"; - public static final String KEY_WIIBIND_DPAD_LEFT = "WiimoteLeft_"; - public static final String KEY_WIIBIND_DPAD_RIGHT = "WiimoteRight_"; - public static final String KEY_WIIBIND_NUNCHUK_C = "NunchukC_"; - public static final String KEY_WIIBIND_NUNCHUK_Z = "NunchukZ_"; - public static final String KEY_WIIBIND_NUNCHUK_UP = "NunchukUp_"; - public static final String KEY_WIIBIND_NUNCHUK_DOWN = "NunchukDown_"; - public static final String KEY_WIIBIND_NUNCHUK_LEFT = "NunchukLeft_"; - public static final String KEY_WIIBIND_NUNCHUK_RIGHT = "NunchukRight_"; - public static final String KEY_WIIBIND_NUNCHUK_SWING_UP = "NunchukSwingUp_"; - public static final String KEY_WIIBIND_NUNCHUK_SWING_DOWN = "NunchukSwingDown_"; - public static final String KEY_WIIBIND_NUNCHUK_SWING_LEFT = "NunchukSwingLeft_"; - public static final String KEY_WIIBIND_NUNCHUK_SWING_RIGHT = "NunchukSwingRight_"; - public static final String KEY_WIIBIND_NUNCHUK_SWING_FORWARD = "NunchukSwingForward_"; - public static final String KEY_WIIBIND_NUNCHUK_SWING_BACKWARD = "NunchukSwingBackward_"; - public static final String KEY_WIIBIND_NUNCHUK_TILT_FORWARD = "NunchukTiltForward_"; - public static final String KEY_WIIBIND_NUNCHUK_TILT_BACKWARD = "NunchukTiltBackward_"; - public static final String KEY_WIIBIND_NUNCHUK_TILT_LEFT = "NunchukTiltLeft_"; - public static final String KEY_WIIBIND_NUNCHUK_TILT_RIGHT = "NunchukTiltRight_"; - public static final String KEY_WIIBIND_NUNCHUK_TILT_MODIFIER = "NunchukTiltModifier_"; - public static final String KEY_WIIBIND_NUNCHUK_SHAKE_X = "NunchukShakeX_"; - public static final String KEY_WIIBIND_NUNCHUK_SHAKE_Y = "NunchukShakeY_"; - public static final String KEY_WIIBIND_NUNCHUK_SHAKE_Z = "NunchukShakeZ_"; - public static final String KEY_WIIBIND_CLASSIC_A = "ClassicA_"; - public static final String KEY_WIIBIND_CLASSIC_B = "ClassicB_"; - public static final String KEY_WIIBIND_CLASSIC_X = "ClassicX_"; - public static final String KEY_WIIBIND_CLASSIC_Y = "ClassicY_"; - public static final String KEY_WIIBIND_CLASSIC_ZL = "ClassicZL_"; - public static final String KEY_WIIBIND_CLASSIC_ZR = "ClassicZR_"; - public static final String KEY_WIIBIND_CLASSIC_MINUS = "ClassicMinus_"; - public static final String KEY_WIIBIND_CLASSIC_PLUS = "ClassicPlus_"; - public static final String KEY_WIIBIND_CLASSIC_HOME = "ClassicHome_"; - public static final String KEY_WIIBIND_CLASSIC_LEFT_UP = "ClassicLeftStickUp_"; - public static final String KEY_WIIBIND_CLASSIC_LEFT_DOWN = "ClassicLeftStickDown_"; - public static final String KEY_WIIBIND_CLASSIC_LEFT_LEFT = "ClassicLeftStickLeft_"; - public static final String KEY_WIIBIND_CLASSIC_LEFT_RIGHT = "ClassicLeftStickRight_"; - public static final String KEY_WIIBIND_CLASSIC_RIGHT_UP = "ClassicRightStickUp_"; - public static final String KEY_WIIBIND_CLASSIC_RIGHT_DOWN = "ClassicRightStickDown_"; - public static final String KEY_WIIBIND_CLASSIC_RIGHT_LEFT = "ClassicRightStickLeft_"; - public static final String KEY_WIIBIND_CLASSIC_RIGHT_RIGHT = "ClassicRightStickRight_"; - public static final String KEY_WIIBIND_CLASSIC_TRIGGER_L = "ClassicTriggerL_"; - public static final String KEY_WIIBIND_CLASSIC_TRIGGER_R = "ClassicTriggerR_"; - public static final String KEY_WIIBIND_CLASSIC_DPAD_UP = "ClassicUp_"; - public static final String KEY_WIIBIND_CLASSIC_DPAD_DOWN = "ClassicDown_"; - public static final String KEY_WIIBIND_CLASSIC_DPAD_LEFT = "ClassicLeft_"; - public static final String KEY_WIIBIND_CLASSIC_DPAD_RIGHT = "ClassicRight_"; - public static final String KEY_WIIBIND_GUITAR_FRET_GREEN = "GuitarGreen_"; - public static final String KEY_WIIBIND_GUITAR_FRET_RED = "GuitarRed_"; - public static final String KEY_WIIBIND_GUITAR_FRET_YELLOW = "GuitarYellow_"; - public static final String KEY_WIIBIND_GUITAR_FRET_BLUE = "GuitarBlue_"; - public static final String KEY_WIIBIND_GUITAR_FRET_ORANGE = "GuitarOrange_"; - public static final String KEY_WIIBIND_GUITAR_STRUM_UP = "GuitarStrumUp_"; - public static final String KEY_WIIBIND_GUITAR_STRUM_DOWN = "GuitarStrumDown_"; - public static final String KEY_WIIBIND_GUITAR_MINUS = "GuitarMinus_"; - public static final String KEY_WIIBIND_GUITAR_PLUS = "GuitarPlus_"; - public static final String KEY_WIIBIND_GUITAR_STICK_UP = "GuitarUp_"; - public static final String KEY_WIIBIND_GUITAR_STICK_DOWN = "GuitarDown_"; - public static final String KEY_WIIBIND_GUITAR_STICK_LEFT = "GuitarLeft_"; - public static final String KEY_WIIBIND_GUITAR_STICK_RIGHT = "GuitarRight_"; - public static final String KEY_WIIBIND_GUITAR_WHAMMY_BAR = "GuitarWhammy_"; - public static final String KEY_WIIBIND_DRUMS_PAD_RED = "DrumsRed_"; - public static final String KEY_WIIBIND_DRUMS_PAD_YELLOW = "DrumsYellow_"; - public static final String KEY_WIIBIND_DRUMS_PAD_BLUE = "DrumsBlue_"; - public static final String KEY_WIIBIND_DRUMS_PAD_GREEN = "DrumsGreen_"; - public static final String KEY_WIIBIND_DRUMS_PAD_ORANGE = "DrumsOrange_"; - public static final String KEY_WIIBIND_DRUMS_PAD_BASS = "DrumsBass_"; - public static final String KEY_WIIBIND_DRUMS_MINUS = "DrumsMinus_"; - public static final String KEY_WIIBIND_DRUMS_PLUS = "DrumsPlus_"; - public static final String KEY_WIIBIND_DRUMS_STICK_UP = "DrumsUp_"; - public static final String KEY_WIIBIND_DRUMS_STICK_DOWN = "DrumsDown_"; - public static final String KEY_WIIBIND_DRUMS_STICK_LEFT = "DrumsLeft_"; - public static final String KEY_WIIBIND_DRUMS_STICK_RIGHT = "DrumsRight_"; - public static final String KEY_WIIBIND_TURNTABLE_GREEN_LEFT = "TurntableGreenLeft_"; - public static final String KEY_WIIBIND_TURNTABLE_RED_LEFT = "TurntableRedLeft_"; - public static final String KEY_WIIBIND_TURNTABLE_BLUE_LEFT = "TurntableBlueLeft_"; - public static final String KEY_WIIBIND_TURNTABLE_GREEN_RIGHT = "TurntableGreenRight_"; - public static final String KEY_WIIBIND_TURNTABLE_RED_RIGHT = "TurntableRedRight_"; - public static final String KEY_WIIBIND_TURNTABLE_BLUE_RIGHT = "TurntableBlueRight_"; - public static final String KEY_WIIBIND_TURNTABLE_MINUS = "TurntableMinus_"; - public static final String KEY_WIIBIND_TURNTABLE_PLUS = "TurntablePlus_"; - public static final String KEY_WIIBIND_TURNTABLE_EUPHORIA = "TurntableEuphoria_"; - public static final String KEY_WIIBIND_TURNTABLE_LEFT_LEFT = "TurntableLeftLeft_"; - public static final String KEY_WIIBIND_TURNTABLE_LEFT_RIGHT = "TurntableLeftRight_"; - public static final String KEY_WIIBIND_TURNTABLE_RIGHT_LEFT = "TurntableRightLeft_"; - public static final String KEY_WIIBIND_TURNTABLE_RIGHT_RIGHT = "TurntableRightRight_"; - public static final String KEY_WIIBIND_TURNTABLE_STICK_UP = "TurntableUp_"; - public static final String KEY_WIIBIND_TURNTABLE_STICK_DOWN = "TurntableDown_"; - public static final String KEY_WIIBIND_TURNTABLE_STICK_LEFT = "TurntableLeft_"; - public static final String KEY_WIIBIND_TURNTABLE_STICK_RIGHT = "TurntableRight_"; - public static final String KEY_WIIBIND_TURNTABLE_EFFECT_DIAL = "TurntableEffDial_"; - public static final String KEY_WIIBIND_TURNTABLE_CROSSFADE_LEFT = "TurntableCrossLeft_"; - public static final String KEY_WIIBIND_TURNTABLE_CROSSFADE_RIGHT = "TurntableCrossRight_"; - private static BiMap sectionsMap = new BiMap<>(); static @@ -283,13 +128,4 @@ public final class SettingsFile return new File( DirectoryInitialization.getUserDirectory() + "/GameSettings/" + gameId + ".ini"); } - - public static File getWiiProfile(String profile) - { - String wiiConfigPath = - DirectoryInitialization.getUserDirectory() + "/Config/Profiles/Wiimote/" + - profile + ".ini"; - - return new File(wiiConfigPath); - } } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.java index 142aca0641..27e425d8b6 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.java @@ -115,7 +115,7 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C { int overlayX = mInputOverlay.getLeft(); int overlayY = mInputOverlay.getTop(); - mInputOverlay.setSurfacePosition(new Rect( + mInputOverlay.setSurfacePosition(activity.getSettings(), new Rect( surfaceView.getLeft() - overlayX, surfaceView.getTop() - overlayY, surfaceView.getRight() - overlayX, surfaceView.getBottom() - overlayY)); }); @@ -133,6 +133,10 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C public void onResume() { super.onResume(); + + if (mInputOverlay != null && NativeLibrary.IsGameMetadataValid()) + mInputOverlay.refreshControls(activity.getSettings()); + run(activity.isActivityRecreated()); } @@ -170,19 +174,19 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C .setBoolean(settings, !BooleanSetting.MAIN_SHOW_INPUT_OVERLAY.getBoolean(settings)); if (mInputOverlay != null) - mInputOverlay.refreshControls(); + mInputOverlay.refreshControls(settings); } - public void initInputPointer() + public void initInputPointer(Settings settings) { if (mInputOverlay != null) - mInputOverlay.initTouchPointer(); + mInputOverlay.initTouchPointer(settings); } - public void refreshInputOverlay() + public void refreshInputOverlay(Settings settings) { if (mInputOverlay != null) - mInputOverlay.refreshControls(); + mInputOverlay.refreshControls(settings); } public void refreshOverlayPointer(Settings settings) @@ -191,10 +195,10 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C mInputOverlay.refreshOverlayPointer(settings); } - public void resetInputOverlay() + public void resetInputOverlay(Settings settings) { if (mInputOverlay != null) - mInputOverlay.resetButtonPlacement(); + mInputOverlay.resetButtonPlacement(settings); } @Override diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/overlay/InputOverlay.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/overlay/InputOverlay.java index 7d949befed..f0a212248f 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/overlay/InputOverlay.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/overlay/InputOverlay.java @@ -29,15 +29,17 @@ import androidx.preference.PreferenceManager; import org.dolphinemu.dolphinemu.NativeLibrary; import org.dolphinemu.dolphinemu.NativeLibrary.ButtonType; import org.dolphinemu.dolphinemu.R; +import org.dolphinemu.dolphinemu.features.input.model.InputMappingBooleanSetting; import org.dolphinemu.dolphinemu.features.input.model.InputOverrider; import org.dolphinemu.dolphinemu.features.input.model.InputOverrider.ControlId; +import org.dolphinemu.dolphinemu.features.input.model.controlleremu.EmulatedController; +import org.dolphinemu.dolphinemu.features.input.model.controlleremu.NumericSetting; import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting; import org.dolphinemu.dolphinemu.features.settings.model.IntSetting; import org.dolphinemu.dolphinemu.features.settings.model.Settings; -import org.dolphinemu.dolphinemu.features.settings.utils.SettingsFile; -import org.dolphinemu.dolphinemu.utils.IniFile; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashSet; import java.util.Set; @@ -66,9 +68,11 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener private Rect mSurfacePosition = null; private boolean mIsFirstRun = true; - private boolean mGameCubeRegistered = false; - private boolean mWiiRegistered = false; + private boolean[] mGcPadRegistered = new boolean[4]; + private boolean[] mWiimoteRegistered = new boolean[4]; private boolean mIsInEditMode = false; + private int mControllerType = -1; + private int mControllerIndex = 0; private InputOverlayDrawableButton mButtonBeingConfigured; private InputOverlayDrawableDpad mDpadBeingConfigured; private InputOverlayDrawableJoystick mJoystickBeingConfigured; @@ -131,10 +135,6 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener if (!mPreferences.getBoolean("OverlayInitV3", false)) defaultOverlay(); - // Load the controls if we can. If not, EmulationActivity has to do it later. - if (NativeLibrary.IsGameMetadataValid()) - refreshControls(); - // Set the on touch listener. setOnTouchListener(this); @@ -145,13 +145,13 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener requestFocus(); } - public void setSurfacePosition(Rect rect) + public void setSurfacePosition(Settings settings, Rect rect) { mSurfacePosition = rect; - initTouchPointer(); + initTouchPointer(settings); } - public void initTouchPointer() + public void initTouchPointer(Settings settings) { // Check if we have all the data we need yet boolean aspectRatioAvailable = NativeLibrary.IsRunningAndStarted(); @@ -164,7 +164,7 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener int doubleTapButton = IntSetting.MAIN_DOUBLE_TAP_BUTTON.getIntGlobal(); - if (getConfiguredControllerType() != InputOverlay.OVERLAY_WIIMOTE_CLASSIC && + if (getConfiguredControllerType(settings) != InputOverlay.OVERLAY_WIIMOTE_CLASSIC && doubleTapButton == ButtonType.CLASSIC_BUTTON_A) { doubleTapButton = ButtonType.WIIMOTE_BUTTON_A; @@ -186,7 +186,7 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener overlayPointer = new InputOverlayPointer(mSurfacePosition, doubleTapControl, IntSetting.MAIN_IR_MODE.getIntGlobal(), - BooleanSetting.MAIN_IR_ALWAYS_RECENTER.getBooleanGlobal()); + BooleanSetting.MAIN_IR_ALWAYS_RECENTER.getBooleanGlobal(), mControllerIndex); } @Override @@ -239,11 +239,11 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener button.setPressedState(true); button.setTrackId(event.getPointerId(pointerIndex)); pressed = true; - InputOverrider.setControlState(0, button.getControl(), 1.0); + InputOverrider.setControlState(mControllerIndex, button.getControl(), 1.0); int analogControl = getAnalogControlForTrigger(button.getControl()); if (analogControl >= 0) - InputOverrider.setControlState(0, analogControl, 1.0); + InputOverrider.setControlState(mControllerIndex, analogControl, 1.0); } break; case MotionEvent.ACTION_UP: @@ -252,11 +252,11 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener if (button.getTrackId() == event.getPointerId(pointerIndex)) { button.setPressedState(false); - InputOverrider.setControlState(0, button.getControl(), 0.0); + InputOverrider.setControlState(mControllerIndex, button.getControl(), 0.0); int analogControl = getAnalogControlForTrigger(button.getControl()); if (analogControl >= 0) - InputOverrider.setControlState(0, analogControl, 0.0); + InputOverrider.setControlState(mControllerIndex, analogControl, 0.0); button.setTrackId(-1); } @@ -298,7 +298,7 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener { if (!dpadPressed[i]) { - InputOverrider.setControlState(0, dpad.getControl(i), 0.0); + InputOverrider.setControlState(mControllerIndex, dpad.getControl(i), 0.0); } } // Press buttons @@ -306,7 +306,7 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener { if (dpadPressed[i]) { - InputOverrider.setControlState(0, dpad.getControl(i), 1.0); + InputOverrider.setControlState(mControllerIndex, dpad.getControl(i), 1.0); } } setDpadState(dpad, dpadPressed[0], dpadPressed[1], dpadPressed[2], dpadPressed[3]); @@ -320,7 +320,7 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener for (int i = 0; i < 4; i++) { dpad.setState(InputOverlayDrawableDpad.STATE_DEFAULT); - InputOverrider.setControlState(0, dpad.getControl(i), 0.0); + InputOverrider.setControlState(mControllerIndex, dpad.getControl(i), 0.0); } dpad.setTrackId(-1); } @@ -336,16 +336,18 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener pressed = true; } - InputOverrider.setControlState(0, joystick.getXControl(), joystick.getX()); - InputOverrider.setControlState(0, joystick.getYControl(), -joystick.getY()); + InputOverrider.setControlState(mControllerIndex, joystick.getXControl(), joystick.getX()); + InputOverrider.setControlState(mControllerIndex, joystick.getYControl(), -joystick.getY()); } // No button/joystick pressed, safe to move pointer if (!pressed && overlayPointer != null) { overlayPointer.onTouch(event); - InputOverrider.setControlState(0, ControlId.WIIMOTE_IR_X, overlayPointer.getX()); - InputOverrider.setControlState(0, ControlId.WIIMOTE_IR_Y, -overlayPointer.getY()); + InputOverrider.setControlState(mControllerIndex, ControlId.WIIMOTE_IR_X, + overlayPointer.getX()); + InputOverrider.setControlState(mControllerIndex, ControlId.WIIMOTE_IR_Y, + -overlayPointer.getY()); } invalidate(); @@ -359,7 +361,6 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener int fingerPositionX = (int) event.getX(pointerIndex); int fingerPositionY = (int) event.getY(pointerIndex); - int controller = getConfiguredControllerType(); String orientation = getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT ? "-Portrait" : ""; @@ -398,7 +399,7 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener // Persist button position by saving new place. saveControlPosition(mButtonBeingConfigured.getLegacyId(), mButtonBeingConfigured.getBounds().left, - mButtonBeingConfigured.getBounds().top, controller, orientation); + mButtonBeingConfigured.getBounds().top, orientation); mButtonBeingConfigured = null; } break; @@ -436,7 +437,7 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener // Persist button position by saving new place. saveControlPosition(mDpadBeingConfigured.getLegacyId(), mDpadBeingConfigured.getBounds().left, mDpadBeingConfigured.getBounds().top, - controller, orientation); + orientation); mDpadBeingConfigured = null; } break; @@ -469,7 +470,7 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener { saveControlPosition(mJoystickBeingConfigured.getLegacyId(), mJoystickBeingConfigured.getBounds().left, - mJoystickBeingConfigured.getBounds().top, controller, orientation); + mJoystickBeingConfigured.getBounds().top, orientation); mJoystickBeingConfigured = null; } break; @@ -486,14 +487,20 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener private void unregisterControllers() { - if (mGameCubeRegistered) - InputOverrider.unregisterGameCube(0); + for (int i = 0; i < mGcPadRegistered.length; i++) + { + if (mGcPadRegistered[i]) + InputOverrider.unregisterGameCube(i); + } - if (mWiiRegistered) - InputOverrider.unregisterWii(0); + for (int i = 0; i < mWiimoteRegistered.length; i++) + { + if (mWiimoteRegistered[i]) + InputOverrider.unregisterWii(i); + } - mGameCubeRegistered = false; - mWiiRegistered = false; + Arrays.fill(mGcPadRegistered, false); + Arrays.fill(mWiimoteRegistered, false); } private int getAnalogControlForTrigger(int control) @@ -785,7 +792,7 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener } } - public void refreshControls() + public void refreshControls(Settings settings) { unregisterControllers(); @@ -798,67 +805,60 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT ? "-Portrait" : ""; + mControllerType = getConfiguredControllerType(settings); + + IntSetting controllerSetting = NativeLibrary.IsEmulatingWii() ? + IntSetting.MAIN_OVERLAY_WII_CONTROLLER : IntSetting.MAIN_OVERLAY_GC_CONTROLLER; + int controllerIndex = controllerSetting.getInt(settings); + if (BooleanSetting.MAIN_SHOW_INPUT_OVERLAY.getBooleanGlobal()) { // Add all the enabled overlay items back to the HashSet. - if (!NativeLibrary.IsEmulatingWii()) + switch (mControllerType) { - IniFile dolphinIni = new IniFile(SettingsFile.getSettingsFile(Settings.FILE_DOLPHIN)); + case OVERLAY_GAMECUBE: + if (IntSetting.getSettingForSIDevice(controllerIndex).getInt(settings) == + DISABLED_GAMECUBE_CONTROLLER && mIsFirstRun) + { + Toast.makeText(getContext(), R.string.disabled_gc_overlay_notice, Toast.LENGTH_SHORT) + .show(); + } - switch (dolphinIni.getInt(Settings.SECTION_INI_CORE, SettingsFile.KEY_GCPAD_PLAYER_1, - EMULATED_GAMECUBE_CONTROLLER)) - { - case DISABLED_GAMECUBE_CONTROLLER: - if (mIsFirstRun) - { - Toast.makeText(getContext(), R.string.disabled_gc_overlay_notice, Toast.LENGTH_SHORT) - .show(); - } - break; + mControllerIndex = controllerIndex; + InputOverrider.registerGameCube(mControllerIndex); + mGcPadRegistered[mControllerIndex] = true; - case EMULATED_GAMECUBE_CONTROLLER: - InputOverrider.registerGameCube(0); - mGameCubeRegistered = true; - addGameCubeOverlayControls(orientation); - break; + addGameCubeOverlayControls(orientation); + break; - case GAMECUBE_ADAPTER: - break; - } - } - else - { - switch (getConfiguredControllerType()) - { - case OVERLAY_GAMECUBE: - InputOverrider.registerGameCube(0); - mGameCubeRegistered = true; - addGameCubeOverlayControls(orientation); - break; + case OVERLAY_WIIMOTE: + case OVERLAY_WIIMOTE_SIDEWAYS: + mControllerIndex = controllerIndex - 4; + InputOverrider.registerWii(mControllerIndex); + mWiimoteRegistered[mControllerIndex] = true; - case OVERLAY_WIIMOTE: - case OVERLAY_WIIMOTE_SIDEWAYS: - InputOverrider.registerWii(0); - mWiiRegistered = true; - addWiimoteOverlayControls(orientation); - break; + addWiimoteOverlayControls(orientation); + break; - case OVERLAY_WIIMOTE_NUNCHUK: - InputOverrider.registerWii(0); - mWiiRegistered = true; - addWiimoteOverlayControls(orientation); - addNunchukOverlayControls(orientation); - break; + case OVERLAY_WIIMOTE_NUNCHUK: + mControllerIndex = controllerIndex - 4; + InputOverrider.registerWii(mControllerIndex); + mWiimoteRegistered[mControllerIndex] = true; - case OVERLAY_WIIMOTE_CLASSIC: - InputOverrider.registerWii(0); - mWiiRegistered = true; - addClassicOverlayControls(orientation); - break; + addWiimoteOverlayControls(orientation); + addNunchukOverlayControls(orientation); + break; - case OVERLAY_NONE: - break; - } + case OVERLAY_WIIMOTE_CLASSIC: + mControllerIndex = controllerIndex - 4; + InputOverrider.registerWii(mControllerIndex); + mWiimoteRegistered[mControllerIndex] = true; + + addClassicOverlayControls(orientation); + break; + + case OVERLAY_NONE: + break; } } @@ -875,20 +875,20 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener } } - public void resetButtonPlacement() + public void resetButtonPlacement(Settings settings) { boolean isLandscape = getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE; - // Values for these come from R.array.controllersEntries - if (!NativeLibrary.IsEmulatingWii() || getConfiguredControllerType() == OVERLAY_GAMECUBE) + final int controller = getConfiguredControllerType(settings); + if (controller == OVERLAY_GAMECUBE) { if (isLandscape) gcDefaultOverlay(); else gcPortraitDefaultOverlay(); } - else if (getConfiguredControllerType() == OVERLAY_WIIMOTE_CLASSIC) + else if (controller == OVERLAY_WIIMOTE_CLASSIC) { if (isLandscape) wiiClassicDefaultOverlay(); @@ -908,27 +908,52 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener wiiOnlyPortraitDefaultOverlay(); } } - refreshControls(); + refreshControls(settings); } - public static int getConfiguredControllerType(Context context) + public static int getConfiguredControllerType(Settings settings) { - return PreferenceManager.getDefaultSharedPreferences(context) - .getInt("wiiController", OVERLAY_WIIMOTE_NUNCHUK); + IntSetting controllerSetting = NativeLibrary.IsEmulatingWii() ? + IntSetting.MAIN_OVERLAY_WII_CONTROLLER : IntSetting.MAIN_OVERLAY_GC_CONTROLLER; + int controllerIndex = controllerSetting.getInt(settings); + + if (controllerIndex >= 0 && controllerIndex < 4) + { + // GameCube controller + if (IntSetting.getSettingForSIDevice(controllerIndex).getInt(settings) == 6) + return OVERLAY_GAMECUBE; + } + else if (controllerIndex >= 4 && controllerIndex < 8) + { + // Wii Remote + int wiimoteIndex = controllerIndex - 4; + if (IntSetting.getSettingForWiimoteSource(wiimoteIndex).getInt(settings) == 1) + { + int attachmentIndex = EmulatedController.getSelectedWiimoteAttachment(wiimoteIndex); + switch (attachmentIndex) + { + case 1: + return OVERLAY_WIIMOTE_NUNCHUK; + case 2: + return OVERLAY_WIIMOTE_CLASSIC; + } + + NumericSetting sidewaysSetting = EmulatedController.getSidewaysWiimoteSetting(wiimoteIndex); + boolean sideways = new InputMappingBooleanSetting(sidewaysSetting).getBoolean(settings); + + return sideways ? OVERLAY_WIIMOTE_SIDEWAYS : OVERLAY_WIIMOTE; + } + } + + return OVERLAY_NONE; } - private int getConfiguredControllerType() - { - return mPreferences.getInt("wiiController", OVERLAY_WIIMOTE_NUNCHUK); - } - - private void saveControlPosition(int sharedPrefsId, int x, int y, int controller, - String orientation) + private void saveControlPosition(int sharedPrefsId, int x, int y, String orientation) { final SharedPreferences sPrefs = PreferenceManager.getDefaultSharedPreferences(getContext()); SharedPreferences.Editor sPrefsEditor = sPrefs.edit(); - sPrefsEditor.putFloat(getXKey(sharedPrefsId, controller, orientation), x); - sPrefsEditor.putFloat(getYKey(sharedPrefsId, controller, orientation), y); + sPrefsEditor.putFloat(getXKey(sharedPrefsId, mControllerType, orientation), x); + sPrefsEditor.putFloat(getYKey(sharedPrefsId, mControllerType, orientation), y); sPrefsEditor.apply(); } @@ -989,7 +1014,7 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener * @param control Control identifier for the button the InputOverlayDrawableButton represents. * @return An {@link InputOverlayDrawableButton} with the correct drawing bounds set. */ - private static InputOverlayDrawableButton initializeOverlayButton(Context context, + private InputOverlayDrawableButton initializeOverlayButton(Context context, int defaultResId, int pressedResId, int legacyId, int control, String orientation) { // Resources handle for fetching the initial Drawable resource. @@ -997,7 +1022,6 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener // SharedPreference to retrieve the X and Y coordinates for the InputOverlayDrawableButton. final SharedPreferences sPrefs = PreferenceManager.getDefaultSharedPreferences(context); - int controller = getConfiguredControllerType(context); // Decide scale based on button ID and user preference float scale; @@ -1023,7 +1047,7 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener break; case ButtonType.WIIMOTE_BUTTON_1: case ButtonType.WIIMOTE_BUTTON_2: - if (controller == 2) + if (mControllerType == OVERLAY_WIIMOTE_SIDEWAYS) scale = 0.14f; else scale = 0.0875f; @@ -1061,8 +1085,8 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener // The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay. // These were set in the input overlay configuration menu. - int drawableX = (int) sPrefs.getFloat(getXKey(legacyId, controller, orientation), 0f); - int drawableY = (int) sPrefs.getFloat(getYKey(legacyId, controller, orientation), 0f); + int drawableX = (int) sPrefs.getFloat(getXKey(legacyId, mControllerType, orientation), 0f); + int drawableY = (int) sPrefs.getFloat(getYKey(legacyId, mControllerType, orientation), 0f); int width = overlayDrawable.getWidth(); int height = overlayDrawable.getHeight(); @@ -1092,7 +1116,7 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener * @param rightControl Control identifier for the right button. * @return the initialized {@link InputOverlayDrawableDpad} */ - private static InputOverlayDrawableDpad initializeOverlayDpad(Context context, + private InputOverlayDrawableDpad initializeOverlayDpad(Context context, int defaultResId, int pressedOneDirectionResId, int pressedTwoDirectionsResId, @@ -1108,7 +1132,6 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener // SharedPreference to retrieve the X and Y coordinates for the InputOverlayDrawableDpad. final SharedPreferences sPrefs = PreferenceManager.getDefaultSharedPreferences(context); - int controller = getConfiguredControllerType(context); // Decide scale based on button ID and user preference float scale; @@ -1122,7 +1145,7 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener scale = 0.275f; break; default: - if (controller == 2 || controller == 1) + if (mControllerType == OVERLAY_WIIMOTE_SIDEWAYS || mControllerType == OVERLAY_WIIMOTE) scale = 0.275f; else scale = 0.2125f; @@ -1148,8 +1171,8 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener // The X and Y coordinates of the InputOverlayDrawableDpad on the InputOverlay. // These were set in the input overlay configuration menu. - int drawableX = (int) sPrefs.getFloat(getXKey(legacyId, controller, orientation), 0f); - int drawableY = (int) sPrefs.getFloat(getYKey(legacyId, controller, orientation), 0f); + int drawableX = (int) sPrefs.getFloat(getXKey(legacyId, mControllerType, orientation), 0f); + int drawableY = (int) sPrefs.getFloat(getYKey(legacyId, mControllerType, orientation), 0f); int width = overlayDrawable.getWidth(); int height = overlayDrawable.getHeight(); @@ -1177,16 +1200,15 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener * @param yControl Control identifier for the Y axis. * @return the initialized {@link InputOverlayDrawableJoystick}. */ - private static InputOverlayDrawableJoystick initializeOverlayJoystick(Context context, - int resOuter, int defaultResInner, int pressedResInner, int legacyId, int xControl, - int yControl, String orientation) + private InputOverlayDrawableJoystick initializeOverlayJoystick(Context context, int resOuter, + int defaultResInner, int pressedResInner, int legacyId, int xControl, int yControl, + String orientation) { // Resources handle for fetching the initial Drawable resource. final Resources res = context.getResources(); // SharedPreference to retrieve the X and Y coordinates for the InputOverlayDrawableJoystick. final SharedPreferences sPrefs = PreferenceManager.getDefaultSharedPreferences(context); - int controller = getConfiguredControllerType(context); // Decide scale based on user preference float scale = 0.275f; @@ -1201,8 +1223,8 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener // The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay. // These were set in the input overlay configuration menu. - int drawableX = (int) sPrefs.getFloat(getXKey(legacyId, controller, orientation), 0f); - int drawableY = (int) sPrefs.getFloat(getYKey(legacyId, controller, orientation), 0f); + int drawableX = (int) sPrefs.getFloat(getXKey(legacyId, mControllerType, orientation), 0f); + int drawableY = (int) sPrefs.getFloat(getYKey(legacyId, mControllerType, orientation), 0f); // Decide inner scale based on joystick ID float innerScale; @@ -1225,7 +1247,8 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener // Send the drawableId to the joystick so it can be referenced when saving control position. final InputOverlayDrawableJoystick overlayDrawable = new InputOverlayDrawableJoystick(res, bitmapOuter, bitmapInnerDefault, - bitmapInnerPressed, outerRect, innerRect, legacyId, xControl, yControl); + bitmapInnerPressed, outerRect, innerRect, legacyId, xControl, yControl, + mControllerIndex); // Need to set the image's position overlayDrawable.setPosition(drawableX, drawableY); diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/overlay/InputOverlayDrawableJoystick.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/overlay/InputOverlayDrawableJoystick.java index d580df7349..ee8c852610 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/overlay/InputOverlayDrawableJoystick.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/overlay/InputOverlayDrawableJoystick.java @@ -31,6 +31,7 @@ public final class InputOverlayDrawableJoystick private int mPreviousTouchX, mPreviousTouchY; private final int mWidth; private final int mHeight; + private final int mControllerIndex; private Rect mVirtBounds; private Rect mOrigBounds; private int mOpacity; @@ -55,7 +56,7 @@ public final class InputOverlayDrawableJoystick */ public InputOverlayDrawableJoystick(Resources res, Bitmap bitmapOuter, Bitmap bitmapInnerDefault, Bitmap bitmapInnerPressed, Rect rectOuter, Rect rectInner, int legacyId, int xControl, - int yControl) + int yControl, int controllerIndex) { mJoystickLegacyId = legacyId; mJoystickXControl = xControl; @@ -68,6 +69,10 @@ public final class InputOverlayDrawableJoystick mWidth = bitmapOuter.getWidth(); mHeight = bitmapOuter.getHeight(); + if (controllerIndex < 0 || controllerIndex >= 4) + throw new IllegalArgumentException("controllerIndex must be 0-3"); + mControllerIndex = controllerIndex; + setBounds(rectOuter); mDefaultStateInnerBitmap.setBounds(rectInner); mPressedStateInnerBitmap.setBounds(rectInner); @@ -221,7 +226,8 @@ public final class InputOverlayDrawableJoystick double angle = Math.atan2(y, x) + Math.PI + Math.PI; double radius = Math.hypot(y, x); - double maxRadius = InputOverrider.getGateRadiusAtAngle(0, mJoystickXControl, angle); + double maxRadius = InputOverrider.getGateRadiusAtAngle(mControllerIndex, mJoystickXControl, + angle); if (radius > maxRadius) { y = maxRadius * Math.sin(angle); diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/overlay/InputOverlayPointer.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/overlay/InputOverlayPointer.java index 18de4f4b47..1d96f5c617 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/overlay/InputOverlayPointer.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/overlay/InputOverlayPointer.java @@ -32,6 +32,7 @@ public class InputOverlayPointer private int mMode; private boolean mRecenter; + private int mControllerIndex; private boolean doubleTap = false; private int mDoubleTapControl; @@ -47,11 +48,13 @@ public class InputOverlayPointer DOUBLE_TAP_OPTIONS.add(NativeLibrary.ButtonType.CLASSIC_BUTTON_A); } - public InputOverlayPointer(Rect surfacePosition, int doubleTapControl, int mode, boolean recenter) + public InputOverlayPointer(Rect surfacePosition, int doubleTapControl, int mode, boolean recenter, + int controllerIndex) { mDoubleTapControl = doubleTapControl; mMode = mode; mRecenter = recenter; + mControllerIndex = controllerIndex; mGameCenterX = (surfacePosition.left + surfacePosition.right) / 2.0f; mGameCenterY = (surfacePosition.top + surfacePosition.bottom) / 2.0f; @@ -128,8 +131,9 @@ public class InputOverlayPointer { if (doubleTap) { - InputOverrider.setControlState(0, mDoubleTapControl, 1.0); - new Handler().postDelayed(() -> InputOverrider.setControlState(0, mDoubleTapControl, 0.0), + InputOverrider.setControlState(mControllerIndex, mDoubleTapControl, 1.0); + new Handler().postDelayed(() -> InputOverrider.setControlState(mControllerIndex, + mDoubleTapControl, 0.0), 50); } else diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/ControllerMappingHelper.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/ControllerMappingHelper.java deleted file mode 100644 index 9e31a46a96..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/ControllerMappingHelper.java +++ /dev/null @@ -1,65 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.dolphinemu.dolphinemu.utils; - -import android.view.InputDevice; -import android.view.KeyEvent; -import android.view.MotionEvent; - -/** - * Some controllers have incorrect mappings. This class has special-case fixes for them. - */ -public class ControllerMappingHelper -{ - /** - * Some controllers report extra button presses that can be ignored. - */ - public static boolean shouldKeyBeIgnored(InputDevice inputDevice, int keyCode) - { - if (isDualShock4(inputDevice)) - { - // The two analog triggers generate analog motion events as well as a keycode. - // We always prefer to use the analog values, so throw away the button press - return keyCode == KeyEvent.KEYCODE_BUTTON_L2 || keyCode == KeyEvent.KEYCODE_BUTTON_R2; - } - return false; - } - - /** - * Scale an axis to be zero-centered with a proper range. - */ - public static float scaleAxis(InputDevice inputDevice, int axis, float value) - { - if (isDualShock4(inputDevice)) - { - // Android doesn't have correct mappings for this controller's triggers. It reports them - // as RX & RY, centered at -1.0, and with a range of [-1.0, 1.0] - // Scale them to properly zero-centered with a range of [0.0, 1.0]. - if (axis == MotionEvent.AXIS_RX || axis == MotionEvent.AXIS_RY) - { - return (value + 1) / 2.0f; - } - } - else if (isXboxOneWireless(inputDevice)) - { - // Same as the DualShock 4, the mappings are missing. - if (axis == MotionEvent.AXIS_Z || axis == MotionEvent.AXIS_RZ) - { - return (value + 1) / 2.0f; - } - } - return value; - } - - private static boolean isDualShock4(InputDevice inputDevice) - { - // Sony DualShock 4 controller - return inputDevice.getVendorId() == 0x54c && inputDevice.getProductId() == 0x9cc; - } - - private static boolean isXboxOneWireless(InputDevice inputDevice) - { - // Microsoft Xbox One controller - return inputDevice.getVendorId() == 0x45e && inputDevice.getProductId() == 0x2e0; - } -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/DirectoryInitialization.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/DirectoryInitialization.java index b1584df1be..bb2f45de43 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/DirectoryInitialization.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/DirectoryInitialization.java @@ -31,17 +31,21 @@ import java.io.InputStream; import java.io.OutputStream; /** - * A service that spawns its own thread in order to copy several binary and shader files - * from the Dolphin APK to the external file system. + * A class that spawns its own thread in order perform initialization. + * + * The initialization steps include: + * - Extracting the Sys directory from the APK so it can be accessed using regular file APIs + * - Letting the native code know where on external storage it should place the User directory + * - Running the native code's init steps (which include things like populating the User directory) */ public final class DirectoryInitialization { public static final String EXTRA_STATE = "directoryState"; - private static final int WiimoteNewVersion = 5; // Last changed in PR 8907 private static final MutableLiveData directoryState = new MutableLiveData<>(DirectoryInitializationState.NOT_YET_INITIALIZED); private static volatile boolean areDirectoriesAvailable = false; private static String userPath; + private static String sysPath; private static boolean isUsingLegacyUserDirectory = false; public enum DirectoryInitializationState @@ -73,8 +77,7 @@ public final class DirectoryInitialization System.exit(1); } - initializeInternalStorage(context); - boolean wiimoteIniWritten = initializeExternalStorage(context); + extractSysDirectory(context); NativeLibrary.Initialize(); NativeLibrary.ReportStartToAnalytics(); @@ -82,13 +85,6 @@ public final class DirectoryInitialization checkThemeSettings(context); - if (wiimoteIniWritten) - { - // This has to be done after calling NativeLibrary.Initialize(), - // as it relies on the config system - EmulationActivity.updateWiimoteNewIniPreferences(context); - } - directoryState.postValue(DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED); } @@ -131,7 +127,7 @@ public final class DirectoryInitialization return true; } - private static void initializeInternalStorage(Context context) + private static void extractSysDirectory(Context context) { File sysDirectory = new File(context.getFilesDir(), "Sys"); @@ -142,7 +138,7 @@ public final class DirectoryInitialization // There is no extracted Sys directory, or there is a Sys directory from another // version of Dolphin that might contain outdated files. Let's (re-)extract Sys. deleteDirectoryRecursively(sysDirectory); - copyAssetFolder("Sys", sysDirectory, true, context); + copyAssetFolder("Sys", sysDirectory, context); SharedPreferences.Editor editor = preferences.edit(); editor.putString("sysDirectoryVersion", revision); @@ -150,46 +146,8 @@ public final class DirectoryInitialization } // Let the native code know where the Sys directory is. - SetSysDirectory(sysDirectory.getPath()); - } - - // Returns whether the WiimoteNew.ini file was written to - private static boolean initializeExternalStorage(Context context) - { - // Create User directory structure and copy some NAND files from the extracted Sys directory. - CreateUserDirectories(); - - // GCPadNew.ini and WiimoteNew.ini must contain specific values in order for controller - // input to work as intended (they aren't user configurable), so we overwrite them just - // in case the user has tried to modify them manually. - // - // ...Except WiimoteNew.ini contains the user configurable settings for Wii Remote - // extensions in addition to all of its lines that aren't user configurable, so since we - // don't want to lose the selected extensions, we don't overwrite that file if it exists. - // - // TODO: Redo the Android controller system so that we don't have to extract these INIs. - String configDirectory = NativeLibrary.GetUserDirectory() + File.separator + "Config"; - String profileDirectory = - NativeLibrary.GetUserDirectory() + File.separator + "Config/Profiles/Wiimote/"; - createWiimoteProfileDirectory(profileDirectory); - - copyAsset("GCPadNew.ini", new File(configDirectory, "GCPadNew.ini"), true, context); - - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - boolean overwriteWiimoteIni = prefs.getInt("WiimoteNewVersion", 0) != WiimoteNewVersion; - boolean wiimoteIniWritten = copyAsset("WiimoteNew.ini", - new File(configDirectory, "WiimoteNew.ini"), overwriteWiimoteIni, context); - if (overwriteWiimoteIni) - { - SharedPreferences.Editor sPrefsEditor = prefs.edit(); - sPrefsEditor.putInt("WiimoteNewVersion", WiimoteNewVersion); - sPrefsEditor.apply(); - } - - copyAsset("WiimoteProfile.ini", new File(profileDirectory, "WiimoteProfile.ini"), true, - context); - - return wiimoteIniWritten; + sysPath = sysDirectory.getPath(); + SetSysDirectory(sysPath); } private static void deleteDirectoryRecursively(@NonNull final File file) @@ -240,26 +198,33 @@ public final class DirectoryInitialization return userPath; } + public static String getSysDirectory() + { + if (!areDirectoriesAvailable) + { + throw new IllegalStateException( + "DirectoryInitialization must run before accessing the Sys directory!"); + } + return sysPath; + } + public static File getGameListCache(Context context) { return new File(context.getExternalCacheDir(), "gamelist.cache"); } - private static boolean copyAsset(String asset, File output, Boolean overwrite, Context context) + private static boolean copyAsset(String asset, File output, Context context) { Log.verbose("[DirectoryInitialization] Copying File " + asset + " to " + output); try { - if (!output.exists() || overwrite) + try (InputStream in = context.getAssets().open(asset)) { - try (InputStream in = context.getAssets().open(asset)) + try (OutputStream out = new FileOutputStream(output)) { - try (OutputStream out = new FileOutputStream(output)) - { - copyFile(in, out); - return true; - } + copyFile(in, out); + return true; } } } @@ -271,8 +236,7 @@ public final class DirectoryInitialization return false; } - private static void copyAssetFolder(String assetFolder, File outputFolder, Boolean overwrite, - Context context) + private static void copyAssetFolder(String assetFolder, File outputFolder, Context context) { Log.verbose("[DirectoryInitialization] Copying Folder " + assetFolder + " to " + outputFolder); @@ -299,9 +263,8 @@ public final class DirectoryInitialization createdFolder = true; } copyAssetFolder(assetFolder + File.separator + file, new File(outputFolder, file), - overwrite, context); - copyAsset(assetFolder + File.separator + file, new File(outputFolder, file), overwrite, context); + copyAsset(assetFolder + File.separator + file, new File(outputFolder, file), context); } } catch (IOException e) @@ -322,18 +285,6 @@ public final class DirectoryInitialization } } - private static void createWiimoteProfileDirectory(String directory) - { - File wiiPath = new File(directory); - if (!wiiPath.isDirectory()) - { - if (!wiiPath.mkdirs()) - { - Log.error("[DirectoryInitialization] Failed to create folder " + wiiPath.getAbsolutePath()); - } - } - } - public static boolean preferOldFolderPicker(Context context) { // As of January 2021, ACTION_OPEN_DOCUMENT_TREE seems to be broken on the Nvidia Shield TV @@ -433,7 +384,5 @@ public final class DirectoryInitialization } } - private static native void CreateUserDirectories(); - private static native void SetSysDirectory(String path); } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/LooperThread.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/LooperThread.java new file mode 100644 index 0000000000..8a67ec7e89 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/LooperThread.java @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.utils; + +import android.os.Looper; + +public class LooperThread extends Thread +{ + private Looper mLooper; + + public LooperThread() + { + super(); + } + + public LooperThread(String name) + { + super(name); + } + + @Override + public void run() + { + Looper.prepare(); + + synchronized (this) + { + mLooper = Looper.myLooper(); + notifyAll(); + } + + Looper.loop(); + } + + public Looper getLooper() + { + if (!isAlive()) + { + throw new IllegalStateException(); + } + + synchronized (this) + { + while (mLooper == null) + { + try + { + wait(); + } + catch (InterruptedException ignored) + { + } + } + } + + return mLooper; + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/MotionListener.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/MotionListener.java deleted file mode 100644 index 0086f3beb5..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/MotionListener.java +++ /dev/null @@ -1,128 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.dolphinemu.dolphinemu.utils; - -import android.app.Activity; -import android.content.Context; -import android.hardware.Sensor; -import android.hardware.SensorEvent; -import android.hardware.SensorEventListener; -import android.hardware.SensorManager; -import android.view.Surface; - -import org.dolphinemu.dolphinemu.NativeLibrary; -import org.dolphinemu.dolphinemu.NativeLibrary.ButtonType; - -public class MotionListener implements SensorEventListener -{ - private final Activity mActivity; - private final SensorManager mSensorManager; - private final Sensor mAccelSensor; - private final Sensor mGyroSensor; - - private boolean mEnabled = false; - - // The same sampling period as for Wii Remotes - private static final int SAMPLING_PERIOD_US = 1000000 / 200; - - public MotionListener(Activity activity) - { - mActivity = activity; - mSensorManager = (SensorManager) activity.getSystemService(Context.SENSOR_SERVICE); - mAccelSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); - mGyroSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE); - } - - @Override - public void onSensorChanged(SensorEvent sensorEvent) - { - float x, y; - float z = sensorEvent.values[2]; - int orientation = mActivity.getWindowManager().getDefaultDisplay().getRotation(); - switch (orientation) - { - default: - case Surface.ROTATION_0: - x = -sensorEvent.values[0]; - y = -sensorEvent.values[1]; - break; - case Surface.ROTATION_90: - x = sensorEvent.values[1]; - y = -sensorEvent.values[0]; - break; - case Surface.ROTATION_180: - x = sensorEvent.values[0]; - y = sensorEvent.values[1]; - break; - case Surface.ROTATION_270: - x = -sensorEvent.values[1]; - y = sensorEvent.values[0]; - break; - } - - if (sensorEvent.sensor == mAccelSensor) - { - NativeLibrary.onGamePadMoveEvent(NativeLibrary.TouchScreenDevice, - ButtonType.WIIMOTE_ACCEL_LEFT, x); - NativeLibrary.onGamePadMoveEvent(NativeLibrary.TouchScreenDevice, - ButtonType.WIIMOTE_ACCEL_RIGHT, x); - NativeLibrary.onGamePadMoveEvent(NativeLibrary.TouchScreenDevice, - ButtonType.WIIMOTE_ACCEL_FORWARD, y); - NativeLibrary.onGamePadMoveEvent(NativeLibrary.TouchScreenDevice, - ButtonType.WIIMOTE_ACCEL_BACKWARD, y); - NativeLibrary.onGamePadMoveEvent(NativeLibrary.TouchScreenDevice, - ButtonType.WIIMOTE_ACCEL_UP, z); - NativeLibrary.onGamePadMoveEvent(NativeLibrary.TouchScreenDevice, - ButtonType.WIIMOTE_ACCEL_DOWN, z); - } - - if (sensorEvent.sensor == mGyroSensor) - { - NativeLibrary.onGamePadMoveEvent(NativeLibrary.TouchScreenDevice, - ButtonType.WIIMOTE_GYRO_PITCH_UP, x); - NativeLibrary.onGamePadMoveEvent(NativeLibrary.TouchScreenDevice, - ButtonType.WIIMOTE_GYRO_PITCH_DOWN, x); - NativeLibrary.onGamePadMoveEvent(NativeLibrary.TouchScreenDevice, - ButtonType.WIIMOTE_GYRO_ROLL_LEFT, y); - NativeLibrary.onGamePadMoveEvent(NativeLibrary.TouchScreenDevice, - ButtonType.WIIMOTE_GYRO_ROLL_RIGHT, y); - NativeLibrary.onGamePadMoveEvent(NativeLibrary.TouchScreenDevice, - ButtonType.WIIMOTE_GYRO_YAW_LEFT, z); - NativeLibrary.onGamePadMoveEvent(NativeLibrary.TouchScreenDevice, - ButtonType.WIIMOTE_GYRO_YAW_RIGHT, z); - } - } - - @Override - public void onAccuracyChanged(Sensor sensor, int i) - { - // We don't care about this - } - - public void enable() - { - if (mEnabled) - return; - - if (mAccelSensor != null) - mSensorManager.registerListener(this, mAccelSensor, SAMPLING_PERIOD_US); - if (mGyroSensor != null) - mSensorManager.registerListener(this, mGyroSensor, SAMPLING_PERIOD_US); - - NativeLibrary.SetMotionSensorsEnabled(mAccelSensor != null, mGyroSensor != null); - - mEnabled = true; - } - - public void disable() - { - if (!mEnabled) - return; - - mSensorManager.unregisterListener(this); - - NativeLibrary.SetMotionSensorsEnabled(false, false); - - mEnabled = false; - } -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/Rumble.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/Rumble.java deleted file mode 100644 index 27d556898a..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/Rumble.java +++ /dev/null @@ -1,97 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.dolphinemu.dolphinemu.utils; - -import android.content.Context; -import android.os.Build; -import android.os.VibrationEffect; -import android.os.Vibrator; -import android.util.SparseArray; -import android.view.InputDevice; - -import org.dolphinemu.dolphinemu.activities.EmulationActivity; -import org.dolphinemu.dolphinemu.features.settings.model.AdHocStringSetting; -import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting; -import org.dolphinemu.dolphinemu.features.settings.model.Settings; -import org.dolphinemu.dolphinemu.features.settings.utils.SettingsFile; - -public class Rumble -{ - private static Vibrator phoneVibrator; - private static final SparseArray emuVibrators = new SparseArray<>(); - - public static void initRumble(EmulationActivity activity) - { - clear(); - - if (BooleanSetting.MAIN_PHONE_RUMBLE.getBooleanGlobal()) - { - setPhoneVibrator(true, activity); - } - - for (int i = 0; i < 8; i++) - { - String deviceName = AdHocStringSetting.getStringGlobal(Settings.FILE_DOLPHIN, - Settings.SECTION_BINDINGS, SettingsFile.KEY_EMU_RUMBLE + i, ""); - - if (!deviceName.isEmpty()) - { - for (int id : InputDevice.getDeviceIds()) - { - InputDevice device = InputDevice.getDevice(id); - if (deviceName.equals(device.getDescriptor())) - { - Vibrator vib = device.getVibrator(); - if (vib != null && vib.hasVibrator()) - emuVibrators.put(i, vib); - } - } - } - } - } - - public static void setPhoneVibrator(boolean set, EmulationActivity activity) - { - if (set) - { - Vibrator vib = (Vibrator) activity.getSystemService(Context.VIBRATOR_SERVICE); - if (vib != null && vib.hasVibrator()) - phoneVibrator = vib; - } - else - { - phoneVibrator = null; - } - } - - private static void clear() - { - phoneVibrator = null; - emuVibrators.clear(); - } - - public static void checkRumble(int padId, double state) - { - if (phoneVibrator != null) - doRumble(phoneVibrator); - - if (emuVibrators.get(padId) != null) - doRumble(emuVibrators.get(padId)); - } - - public static void doRumble(Vibrator vib) - { - // Check again that it exists and can vibrate - if (vib != null && vib.hasVibrator()) - { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) - { - vib.vibrate(VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE)); - } - else - { - vib.vibrate(100); - } - } - } -} diff --git a/Source/Android/app/src/main/res/drawable/ic_delete.xml b/Source/Android/app/src/main/res/drawable/ic_delete.xml new file mode 100644 index 0000000000..c841ba25f4 --- /dev/null +++ b/Source/Android/app/src/main/res/drawable/ic_delete.xml @@ -0,0 +1,9 @@ + + + diff --git a/Source/Android/app/src/main/res/drawable/ic_more.xml b/Source/Android/app/src/main/res/drawable/ic_more.xml new file mode 100644 index 0000000000..429471329e --- /dev/null +++ b/Source/Android/app/src/main/res/drawable/ic_more.xml @@ -0,0 +1,9 @@ + + + diff --git a/Source/Android/app/src/main/res/drawable/ic_save.xml b/Source/Android/app/src/main/res/drawable/ic_save.xml new file mode 100644 index 0000000000..0f449672ee --- /dev/null +++ b/Source/Android/app/src/main/res/drawable/ic_save.xml @@ -0,0 +1,9 @@ + + + diff --git a/Source/Android/app/src/main/res/layout-ldrtl/list_item_mapping.xml b/Source/Android/app/src/main/res/layout-ldrtl/list_item_mapping.xml new file mode 100644 index 0000000000..709a7cbcf0 --- /dev/null +++ b/Source/Android/app/src/main/res/layout-ldrtl/list_item_mapping.xml @@ -0,0 +1,56 @@ + + + + + + + +