[Android] Add Controller support
|
@ -43,6 +43,16 @@
|
|||
android:label="@string/ScanRomsActivity_title"
|
||||
android:theme="@style/Theme.AppCompat" >
|
||||
</activity>
|
||||
<activity
|
||||
android:name="emu.project64.profile.ControllerProfileActivity"
|
||||
android:exported="false"
|
||||
android:label="@string/ControllerProfileActivity_title"
|
||||
android:theme="@style/Theme.Project64.Apearance" >
|
||||
<intent-filter>
|
||||
<action android:name=".profile.ControllerProfileActivity" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<!--
|
||||
For the GameActivities, do not restart the activity when the phone's slider
|
||||
|
|
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 877 B |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 617 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.5 KiB |
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval" >
|
||||
<gradient
|
||||
android:startColor="@color/gray1"
|
||||
android:endColor="@color/gray2"
|
||||
android:angle="90" />
|
||||
<stroke
|
||||
android:width="6dp"
|
||||
android:color="@color/gray2" />
|
||||
</shape>
|
|
@ -0,0 +1,43 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item android:state_pressed="true" >
|
||||
<shape
|
||||
android:shape="oval" >
|
||||
<gradient
|
||||
android:startColor="@color/holo_blue1"
|
||||
android:endColor="@color/holo_blue2"
|
||||
android:angle="90" />
|
||||
<stroke
|
||||
android:width="3dp"
|
||||
android:color="@color/holo_blue2" />
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
<item android:state_focused="true" >
|
||||
<shape
|
||||
android:shape="oval" >
|
||||
<gradient
|
||||
android:startColor="@color/holo_purple1"
|
||||
android:endColor="@color/holo_purple2"
|
||||
android:angle="90" />
|
||||
<stroke
|
||||
android:width="3dp"
|
||||
android:color="@color/holo_purple2" />
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
<item>
|
||||
<shape
|
||||
android:shape="oval" >
|
||||
<gradient
|
||||
android:startColor="@color/blue1"
|
||||
android:endColor="@color/blue2"
|
||||
android:angle="90" />
|
||||
<stroke
|
||||
android:width="3dp"
|
||||
android:color="@color/blue2" />
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
|
@ -0,0 +1,44 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item android:state_pressed="true" >
|
||||
<shape
|
||||
android:shape="rectangle" >
|
||||
<gradient
|
||||
android:startColor="@color/holo_blue1"
|
||||
android:endColor="@color/holo_blue2"
|
||||
android:angle="90" />
|
||||
<stroke
|
||||
android:width="3dp"
|
||||
android:color="@color/holo_blue2" />
|
||||
<corners
|
||||
android:radius="20dp" />
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
<item android:state_focused="true" >
|
||||
<shape
|
||||
android:shape="rectangle" >
|
||||
<gradient
|
||||
android:startColor="@color/holo_purple1"
|
||||
android:endColor="@color/holo_purple2"
|
||||
android:angle="90" />
|
||||
<stroke
|
||||
android:width="3dp"
|
||||
android:color="@color/holo_purple2" />
|
||||
<corners
|
||||
android:radius="20dp" />
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
<item>
|
||||
<shape
|
||||
android:shape="rectangle" >
|
||||
<solid
|
||||
android:color="@android:color/transparent" />
|
||||
<corners
|
||||
android:radius="20dp" />
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
|
@ -0,0 +1,43 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item android:state_pressed="true" >
|
||||
<shape
|
||||
android:shape="oval" >
|
||||
<gradient
|
||||
android:startColor="@color/holo_blue1"
|
||||
android:endColor="@color/holo_blue2"
|
||||
android:angle="90" />
|
||||
<stroke
|
||||
android:width="3dp"
|
||||
android:color="@color/holo_blue2" />
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
<item android:state_focused="true" >
|
||||
<shape
|
||||
android:shape="oval" >
|
||||
<gradient
|
||||
android:startColor="@color/holo_purple1"
|
||||
android:endColor="@color/holo_purple2"
|
||||
android:angle="90" />
|
||||
<stroke
|
||||
android:width="3dp"
|
||||
android:color="@color/holo_purple2" />
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
<item>
|
||||
<shape
|
||||
android:shape="oval" >
|
||||
<gradient
|
||||
android:startColor="@color/green1"
|
||||
android:endColor="@color/green2"
|
||||
android:angle="90" />
|
||||
<stroke
|
||||
android:width="3dp"
|
||||
android:color="@color/green2" />
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
|
@ -0,0 +1,43 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item android:state_pressed="true" >
|
||||
<shape
|
||||
android:shape="oval" >
|
||||
<gradient
|
||||
android:startColor="@color/holo_blue1"
|
||||
android:endColor="@color/holo_blue2"
|
||||
android:angle="90" />
|
||||
<stroke
|
||||
android:width="3dp"
|
||||
android:color="@color/holo_blue2" />
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
<item android:state_focused="true" >
|
||||
<shape
|
||||
android:shape="oval" >
|
||||
<gradient
|
||||
android:startColor="@color/holo_purple1"
|
||||
android:endColor="@color/holo_purple2"
|
||||
android:angle="90" />
|
||||
<stroke
|
||||
android:width="3dp"
|
||||
android:color="@color/holo_purple2" />
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
<item>
|
||||
<shape
|
||||
android:shape="oval" >
|
||||
<gradient
|
||||
android:startColor="@color/yellow1"
|
||||
android:endColor="@color/yellow2"
|
||||
android:angle="90" />
|
||||
<stroke
|
||||
android:width="3dp"
|
||||
android:color="@color/yellow2" />
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
|
@ -0,0 +1,49 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item android:state_pressed="true" >
|
||||
<shape
|
||||
android:shape="rectangle" >
|
||||
<gradient
|
||||
android:startColor="@color/holo_blue1"
|
||||
android:endColor="@color/holo_blue2"
|
||||
android:angle="90" />
|
||||
<stroke
|
||||
android:width="3dp"
|
||||
android:color="@color/holo_blue2" />
|
||||
<corners
|
||||
android:radius="4dp" />
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
<item android:state_focused="true" >
|
||||
<shape
|
||||
android:shape="rectangle" >
|
||||
<gradient
|
||||
android:startColor="@color/holo_purple1"
|
||||
android:endColor="@color/holo_purple2"
|
||||
android:angle="90" />
|
||||
<stroke
|
||||
android:width="3dp"
|
||||
android:color="@color/holo_purple2" />
|
||||
<corners
|
||||
android:radius="4dp" />
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
<item>
|
||||
<shape
|
||||
android:shape="rectangle" >
|
||||
<gradient
|
||||
android:startColor="@color/gray3"
|
||||
android:endColor="@color/gray4"
|
||||
android:angle="90" />
|
||||
<stroke
|
||||
android:width="3dp"
|
||||
android:color="@color/gray4" />
|
||||
<corners
|
||||
android:radius="4dp" />
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
|
@ -0,0 +1,43 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item android:state_pressed="true" >
|
||||
<shape
|
||||
android:shape="oval" >
|
||||
<gradient
|
||||
android:startColor="@color/holo_blue1"
|
||||
android:endColor="@color/holo_blue2"
|
||||
android:angle="90" />
|
||||
<stroke
|
||||
android:width="3dp"
|
||||
android:color="@color/holo_blue2" />
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
<item android:state_focused="true" >
|
||||
<shape
|
||||
android:shape="oval" >
|
||||
<gradient
|
||||
android:startColor="@color/holo_purple1"
|
||||
android:endColor="@color/holo_purple2"
|
||||
android:angle="90" />
|
||||
<stroke
|
||||
android:width="3dp"
|
||||
android:color="@color/holo_purple2" />
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
<item>
|
||||
<shape
|
||||
android:shape="oval" >
|
||||
<gradient
|
||||
android:startColor="@color/red1"
|
||||
android:endColor="@color/red2"
|
||||
android:angle="90" />
|
||||
<stroke
|
||||
android:width="3dp"
|
||||
android:color="@color/red2" />
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
|
@ -0,0 +1,64 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/aPad"
|
||||
android:layout_width="@dimen/inputMapPreferenceButtonSizeX3"
|
||||
android:layout_height="@dimen/inputMapPreferenceButtonSizeX3"
|
||||
android:background="@drawable/analog_ring"
|
||||
android:focusable="false"
|
||||
android:focusableInTouchMode="false" >
|
||||
|
||||
<Button
|
||||
android:id="@+id/buttonAC"
|
||||
style="@style/btnMappable"
|
||||
android:layout_width="@dimen/inputMapPreferenceButtonSizeX1_5"
|
||||
android:layout_height="@dimen/inputMapPreferenceButtonSizeX1_5"
|
||||
android:layout_centerInParent="true"
|
||||
android:background="@drawable/analog_ring"
|
||||
android:enabled="false"
|
||||
android:focusable="false"
|
||||
android:focusableInTouchMode="false" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/buttonAU"
|
||||
style="@style/btnMappableNoText"
|
||||
android:layout_height="@dimen/inputMapPreferenceButtonSizeX1_5"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:background="@drawable/button_apad"
|
||||
android:drawableTop="@drawable/ic_arrow_u"
|
||||
android:text="@string/inputMapActivity_btnAU" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/buttonAD"
|
||||
style="@style/btnMappableNoText"
|
||||
android:layout_height="@dimen/inputMapPreferenceButtonSizeX1_5"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:background="@drawable/button_apad"
|
||||
android:drawableBottom="@drawable/ic_arrow_d"
|
||||
android:text="@string/inputMapActivity_btnAD" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/buttonAL"
|
||||
style="@style/btnMappableNoText"
|
||||
android:layout_width="@dimen/inputMapPreferenceButtonSizeX1_5"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:background="@drawable/button_apad"
|
||||
android:drawableLeft="@drawable/ic_arrow_l"
|
||||
android:text="@string/inputMapActivity_btnAL"
|
||||
tools:ignore="RtlHardcoded" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/buttonAR"
|
||||
style="@style/btnMappableNoText"
|
||||
android:layout_width="@dimen/inputMapPreferenceButtonSizeX1_5"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:background="@drawable/button_apad"
|
||||
android:drawableRight="@drawable/ic_arrow_r"
|
||||
android:text="@string/inputMapActivity_btnAR"
|
||||
tools:ignore="RtlHardcoded" />
|
||||
|
||||
</RelativeLayout>
|
|
@ -0,0 +1,41 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/bPad"
|
||||
android:layout_width="@dimen/inputMapPreferenceButtonSizeX5"
|
||||
android:layout_height="@dimen/inputMapPreferenceButtonSizeX3"
|
||||
android:focusable="false"
|
||||
android:focusableInTouchMode="false" >
|
||||
|
||||
<Button
|
||||
android:id="@+id/buttonZ"
|
||||
style="@style/btnMappable"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:text="@string/inputMapActivity_btnZ" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/buttonR"
|
||||
style="@style/btnMappable"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:text="@string/inputMapActivity_btnR"
|
||||
tools:ignore="RtlHardcoded" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/buttonL"
|
||||
style="@style/btnMappable"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:text="@string/inputMapActivity_btnL"
|
||||
tools:ignore="RtlHardcoded" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/buttonS"
|
||||
style="@style/btnMappable"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:background="@drawable/button_start"
|
||||
android:text="@string/inputMapActivity_btnS" />
|
||||
|
||||
</RelativeLayout>
|
|
@ -0,0 +1,59 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/cPad"
|
||||
android:layout_width="@dimen/inputMapPreferenceButtonSizeX3"
|
||||
android:layout_height="@dimen/inputMapPreferenceButtonSizeX3"
|
||||
android:focusable="false"
|
||||
android:focusableInTouchMode="false" >
|
||||
|
||||
<Button
|
||||
android:id="@+id/buttonCU"
|
||||
style="@style/btnMappableNoText"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:background="@drawable/button_cpad"
|
||||
android:drawableTop="@drawable/ic_arrow_u"
|
||||
android:text="@string/inputMapActivity_btnCU" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/buttonCD"
|
||||
style="@style/btnMappableNoText"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:background="@drawable/button_cpad"
|
||||
android:drawableBottom="@drawable/ic_arrow_d"
|
||||
android:text="@string/inputMapActivity_btnCD" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/buttonCL"
|
||||
style="@style/btnMappableNoText"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:background="@drawable/button_cpad"
|
||||
android:drawableLeft="@drawable/ic_arrow_l"
|
||||
android:text="@string/inputMapActivity_btnCL"
|
||||
tools:ignore="RtlHardcoded" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/buttonCR"
|
||||
style="@style/btnMappableNoText"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:background="@drawable/button_cpad"
|
||||
android:drawableRight="@drawable/ic_arrow_r"
|
||||
android:text="@string/inputMapActivity_btnCR"
|
||||
tools:ignore="RtlHardcoded" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/buttonCC"
|
||||
style="@style/btnMappable"
|
||||
android:layout_centerInParent="true"
|
||||
android:background="@android:color/transparent"
|
||||
android:enabled="false"
|
||||
android:focusable="false"
|
||||
android:focusableInTouchMode="false"
|
||||
android:text="@string/inputMapActivity_btnC"
|
||||
tools:ignore="RtlHardcoded" />
|
||||
|
||||
</RelativeLayout>
|
|
@ -0,0 +1,134 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:focusable="false"
|
||||
android:focusableInTouchMode="false"
|
||||
>
|
||||
<android.support.v7.widget.Toolbar
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="?attr/background_actionbar"
|
||||
android:theme="@style/Project64.Toolbar" />
|
||||
|
||||
<!--
|
||||
***********************************************************************************
|
||||
IMPORTANT:
|
||||
|
||||
Except for the root view, all views in this file MUST set these flags to FALSE:
|
||||
|
||||
android:focusable="false"
|
||||
android:focusableInTouchMode="false"
|
||||
|
||||
This allows focus to stay in the root view at all times. This is critical because
|
||||
the root view is the only view that listens to input from gamepads, keyboards, etc.
|
||||
***********************************************************************************
|
||||
-->
|
||||
<ScrollView
|
||||
android:id="@+id/inputMapPreference"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true"
|
||||
android:focusable="true"
|
||||
android:focusableInTouchMode="true"
|
||||
android:padding="5dp" >
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:focusable="false"
|
||||
android:focusableInTouchMode="false"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical" >
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/all_pads"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:focusable="false"
|
||||
android:focusableInTouchMode="false" >
|
||||
|
||||
<include
|
||||
android:id="@+id/include_b_pad"
|
||||
android:layout_width="@dimen/inputMapPreferenceButtonSizeX5"
|
||||
android:layout_height="@dimen/inputMapPreferenceButtonSizeX3"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_centerHorizontal="true"
|
||||
layout="@layout/b_pad" />
|
||||
|
||||
<include
|
||||
android:id="@+id/include_d_pad"
|
||||
android:layout_width="@dimen/inputMapPreferenceButtonSizeX3"
|
||||
android:layout_height="@dimen/inputMapPreferenceButtonSizeX3"
|
||||
android:layout_alignTop="@+id/include_b_pad"
|
||||
android:layout_marginTop="@dimen/inputMapPreferenceButtonSize"
|
||||
android:layout_toLeftOf="@+id/include_b_pad"
|
||||
layout="@layout/d_pad"
|
||||
tools:ignore="RtlHardcoded" />
|
||||
|
||||
<include
|
||||
android:id="@+id/include_c_pad"
|
||||
android:layout_width="@dimen/inputMapPreferenceButtonSizeX3"
|
||||
android:layout_height="@dimen/inputMapPreferenceButtonSizeX3"
|
||||
android:layout_alignTop="@+id/include_b_pad"
|
||||
android:layout_marginTop="@dimen/inputMapPreferenceButtonSize"
|
||||
android:layout_toRightOf="@+id/include_b_pad"
|
||||
layout="@layout/c_pad"
|
||||
tools:ignore="RtlHardcoded" />
|
||||
|
||||
<include
|
||||
android:id="@+id/include_a_pad"
|
||||
android:layout_width="@dimen/inputMapPreferenceButtonSizeX3"
|
||||
android:layout_height="@dimen/inputMapPreferenceButtonSizeX3"
|
||||
android:layout_below="@id/include_b_pad"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_marginTop="@dimen/inputMapPreferenceButtonSize"
|
||||
layout="@layout/a_pad" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/buttonB"
|
||||
style="@style/btnMappable"
|
||||
android:layout_alignBottom="@+id/include_c_pad"
|
||||
android:layout_toLeftOf="@+id/include_c_pad"
|
||||
android:background="@drawable/button_b"
|
||||
android:text="@string/inputMapActivity_btnB"
|
||||
tools:ignore="RtlHardcoded" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/buttonA"
|
||||
style="@style/btnMappable"
|
||||
android:layout_below="@+id/buttonB"
|
||||
android:layout_toRightOf="@+id/buttonB"
|
||||
android:background="@drawable/button_a"
|
||||
android:text="@string/inputMapActivity_btnA"
|
||||
tools:ignore="RtlHardcoded" />
|
||||
</RelativeLayout>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/dummyImeListener"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:inputType="text"
|
||||
android:visibility="invisible" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textFeedback"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/inputMapPreferenceButtonSize"
|
||||
android:focusable="false"
|
||||
android:focusableInTouchMode="false"
|
||||
android:text="@string/inputMapActivity_dummyFeedback"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:textColor="@android:color/primary_text_dark_nodisable" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
</LinearLayout>
|
|
@ -0,0 +1,118 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:focusable="false"
|
||||
android:focusableInTouchMode="false"
|
||||
>
|
||||
<android.support.v7.widget.Toolbar
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="?attr/background_actionbar"
|
||||
android:theme="@style/Project64.Toolbar" />
|
||||
|
||||
<!--
|
||||
***********************************************************************************
|
||||
IMPORTANT:
|
||||
|
||||
Except for the root view, all views in this file MUST set these flags to FALSE:
|
||||
|
||||
android:focusable="false"
|
||||
android:focusableInTouchMode="false"
|
||||
|
||||
This allows focus to stay in the root view at all times. This is critical because
|
||||
the root view is the only view that listens to input from gamepads, keyboards, etc.
|
||||
***********************************************************************************
|
||||
-->
|
||||
<ScrollView
|
||||
android:id="@+id/inputMapPreference"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true"
|
||||
android:focusable="true"
|
||||
android:focusableInTouchMode="true"
|
||||
android:padding="5dp" >
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:focusable="false"
|
||||
android:focusableInTouchMode="false"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical" >
|
||||
|
||||
<include
|
||||
android:id="@+id/include_b_pad"
|
||||
android:layout_width="@dimen/inputMapPreferenceButtonSizeX5"
|
||||
android:layout_height="@dimen/inputMapPreferenceButtonSizeX3"
|
||||
layout="@layout/b_pad" />
|
||||
|
||||
<include
|
||||
android:id="@+id/include_d_pad"
|
||||
android:layout_width="@dimen/inputMapPreferenceButtonSizeX3"
|
||||
android:layout_height="@dimen/inputMapPreferenceButtonSizeX3"
|
||||
android:layout_marginTop="@dimen/inputMapPreferenceButtonSize"
|
||||
layout="@layout/d_pad" />
|
||||
|
||||
<include
|
||||
android:id="@+id/include_a_pad"
|
||||
android:layout_width="@dimen/inputMapPreferenceButtonSizeX3"
|
||||
android:layout_height="@dimen/inputMapPreferenceButtonSizeX3"
|
||||
android:layout_marginTop="@dimen/inputMapPreferenceButtonSize"
|
||||
layout="@layout/a_pad" />
|
||||
|
||||
<include
|
||||
android:id="@+id/include_c_pad"
|
||||
android:layout_width="@dimen/inputMapPreferenceButtonSizeX3"
|
||||
android:layout_height="@dimen/inputMapPreferenceButtonSizeX3"
|
||||
android:layout_marginTop="@dimen/inputMapPreferenceButtonSize"
|
||||
layout="@layout/c_pad" />
|
||||
|
||||
<TableRow
|
||||
android:id="@+id/tableRowFaceButtons"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/inputMapPreferenceButtonSize" >
|
||||
|
||||
<Button
|
||||
android:id="@+id/buttonB"
|
||||
style="@style/btnMappable"
|
||||
android:background="@drawable/button_b"
|
||||
android:text="@string/inputMapActivity_btnB" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/buttonA"
|
||||
style="@style/btnMappable"
|
||||
android:layout_marginLeft="@dimen/inputMapPreferenceButtonSize"
|
||||
android:background="@drawable/button_a"
|
||||
android:text="@string/inputMapActivity_btnA"
|
||||
tools:ignore="RtlHardcoded" />
|
||||
</TableRow>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/dummyImeListener"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:inputType="text"
|
||||
android:visibility="invisible" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textFeedback"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/inputMapPreferenceButtonSize"
|
||||
android:focusable="false"
|
||||
android:focusableInTouchMode="false"
|
||||
android:text="@string/inputMapActivity_dummyFeedback"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:textColor="@android:color/primary_text_dark_nodisable" />
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</LinearLayout>
|
|
@ -0,0 +1,52 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/dPad"
|
||||
android:layout_width="@dimen/inputMapPreferenceButtonSizeX3"
|
||||
android:layout_height="@dimen/inputMapPreferenceButtonSizeX3"
|
||||
android:focusable="false"
|
||||
android:focusableInTouchMode="false" >
|
||||
|
||||
<Button
|
||||
android:id="@+id/buttonDU"
|
||||
style="@style/btnMappableNoText"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:drawableTop="@drawable/ic_arrow_u"
|
||||
android:text="@string/inputMapActivity_btnDU" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/buttonDD"
|
||||
style="@style/btnMappableNoText"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:drawableBottom="@drawable/ic_arrow_d"
|
||||
android:text="@string/inputMapActivity_btnDD" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/buttonDL"
|
||||
style="@style/btnMappableNoText"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:drawableLeft="@drawable/ic_arrow_l"
|
||||
android:text="@string/inputMapActivity_btnDL"
|
||||
tools:ignore="RtlHardcoded" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/buttonDR"
|
||||
style="@style/btnMappableNoText"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:drawableRight="@drawable/ic_arrow_r"
|
||||
android:text="@string/inputMapActivity_btnDR"
|
||||
tools:ignore="RtlHardcoded" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/buttonDC"
|
||||
style="@style/btnMappable"
|
||||
android:layout_centerInParent="true"
|
||||
android:enabled="false"
|
||||
android:focusable="false"
|
||||
android:focusableInTouchMode="false" />
|
||||
|
||||
</RelativeLayout>
|
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="10dp" >
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textFeedback"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:textIsSelectable="false" />
|
||||
|
||||
<SeekBar
|
||||
android:id="@+id/seekbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</LinearLayout>
|
|
@ -0,0 +1,37 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="5dp">
|
||||
<RadioButton
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="0"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:id="@+id/two_lines_list_view_row_radiobtn">
|
||||
</RadioButton>
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="0dip"
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_weight="1"
|
||||
android:padding="0dip"
|
||||
>
|
||||
<TextView
|
||||
android:id="@+id/two_lines_list_view_row_text"
|
||||
style="?android:attr/textAppearanceMedium"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
/>
|
||||
<TextView
|
||||
android:id="@+id/two_lines_list_view_row_subtext"
|
||||
style="?android:attr/textAppearanceSmall"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
|
@ -6,4 +6,13 @@
|
|||
<attr name="font_actionbar" format="reference|color" />
|
||||
<attr name="font_actionbar_selected" format="reference|color" />
|
||||
<attr name="textMenuColor" format="reference|color" />
|
||||
<declare-styleable name="SeekBarPreference">
|
||||
<attr name="minimumValue" format="integer" />
|
||||
<attr name="maximumValue" format="integer" />
|
||||
<attr name="stepSize" format="integer" />
|
||||
<attr name="units" format="string" />
|
||||
</declare-styleable>
|
||||
<declare-styleable name="TwoLinesListPreference">
|
||||
<attr name="entriesSubtitles" format="string"/>
|
||||
</declare-styleable>
|
||||
</resources>
|
|
@ -3,7 +3,18 @@
|
|||
|
||||
<color name="black">#000001</color>
|
||||
<color name="white">#ffffff</color>
|
||||
|
||||
<color name="yellow1">#F8E334</color>
|
||||
<color name="yellow2">#F29620</color>
|
||||
<color name="red1">#F1001C</color>
|
||||
<color name="red2">#A70014</color>
|
||||
<color name="green1">#2F9454</color>
|
||||
<color name="green2">#27512B</color>
|
||||
<color name="blue1">#3249BC</color>
|
||||
<color name="blue2">#262F7D</color>
|
||||
<color name="gray1">#E5E5E5</color>
|
||||
<color name="gray2">#9C9897</color>
|
||||
<color name="gray3">#747273</color>
|
||||
<color name="gray4">#424042</color>
|
||||
<color name="grey50">#fafafa</color>
|
||||
<color name="grey100">#f5f5f5</color>
|
||||
<color name="grey200">#eeeeee</color>
|
||||
|
@ -16,5 +27,9 @@
|
|||
<color name="grey850">#323232</color>
|
||||
<color name="grey875">#2a2a2a</color>
|
||||
<color name="grey900">#212121</color>
|
||||
<color name="holo_blue1">#33B5E5</color>
|
||||
<color name="holo_blue2">#0099CC</color>
|
||||
<color name="holo_purple1">#AA66CC</color>
|
||||
<color name="holo_purple2">#9933CC</color>
|
||||
|
||||
</resources>
|
|
@ -1,9 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<dimen name="inputMapPreferenceButtonSize">40dp</dimen>
|
||||
<dimen name="inputMapPreferenceButtonSizeX1_5">60dp</dimen>
|
||||
<dimen name="inputMapPreferenceButtonSizeX3">120dp</dimen>
|
||||
<dimen name="inputMapPreferenceButtonSizeX5">200dp</dimen>
|
||||
|
||||
<dimen name="galleryImageWidth">450dp</dimen>
|
||||
<dimen name="galleryImageHeight">15dp</dimen> <!-- galleryImageWidth * 177 / 256 -->
|
||||
<dimen name="galleryImageHeight">15dp</dimen>
|
||||
<dimen name="galleryHalfSpacing">5dp</dimen>
|
||||
<dimen name="actionBarSize">56dp</dimen>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -5,11 +5,13 @@
|
|||
<string name="app_name" translatable="false">Project64</string>
|
||||
<string name="assetExtractor_uriHelp" translatable="false">www.pj64-emu.com</string>
|
||||
<!-- Strings only seen in the eclipse editor -->
|
||||
<string name="inputMapActivity_dummyFeedback" translatable="false">KEYCODE_XXXX</string>
|
||||
<string name="galleryItemAdapter_dummyText" translatable="false">Good Name Goes Here</string>
|
||||
|
||||
<!-- Activity titles, aliased to other strings -->
|
||||
<string name="SplashActivity_title" translatable="false">@string/app_name</string>
|
||||
<string name="GalleryActivity_title" translatable="false">@string/app_name</string>
|
||||
<string name="ControllerProfileActivity_title" translatable="false">@string/app_name</string>
|
||||
<string name="ScanRomsActivity_title" translatable="false">@string/app_name</string>
|
||||
<string name="GameActivity_title" translatable="false">@string/app_name</string>
|
||||
|
||||
|
|
|
@ -66,9 +66,37 @@
|
|||
<string name="galleryRecentlyPlayed">Recently played</string>
|
||||
<string name="galleryLibrary">Games</string>
|
||||
|
||||
<!-- Seek Bar Preference -->
|
||||
<string name="seekBarPreference_summary">%1$d %2$s</string>
|
||||
|
||||
<!-- Input Mapping -->
|
||||
<string name="inputMapActivity_popupMessage">Press a button, key, or joystick to map…\n\nCurrent mappings:\n%1$s</string>
|
||||
<string name="inputMapActivity_popupUnmap">Unmap</string>
|
||||
<string name="inputMapActivity_btnA">A</string>
|
||||
<string name="inputMapActivity_btnB">B</string>
|
||||
<string name="inputMapActivity_btnC">C</string>
|
||||
<string name="inputMapActivity_btnL">L</string>
|
||||
<string name="inputMapActivity_btnR">R</string>
|
||||
<string name="inputMapActivity_btnZ">Z</string>
|
||||
<string name="inputMapActivity_btnS">S</string>
|
||||
<string name="inputMapActivity_btnCR">C-pad →</string>
|
||||
<string name="inputMapActivity_btnCL">C-pad ←</string>
|
||||
<string name="inputMapActivity_btnCD">C-pad ↓</string>
|
||||
<string name="inputMapActivity_btnCU">C-pad ↑</string>
|
||||
<string name="inputMapActivity_btnDR">D-pad →</string>
|
||||
<string name="inputMapActivity_btnDL">D-pad ←</string>
|
||||
<string name="inputMapActivity_btnDD">D-pad ↓</string>
|
||||
<string name="inputMapActivity_btnDU">D-pad ↑</string>
|
||||
<string name="inputMapActivity_btnAR">Analog →</string>
|
||||
<string name="inputMapActivity_btnAL">Analog ←</string>
|
||||
<string name="inputMapActivity_btnAD">Analog ↓</string>
|
||||
<string name="inputMapActivity_btnAU">Analog ↑</string>
|
||||
|
||||
<!-- Settings -->
|
||||
<string name="Hardware">Hardware</string>
|
||||
<string name="Other">Other</string>
|
||||
<string name="touch_screen_summary">On-screen controller setttings</string>
|
||||
<string name="touch_screen_title">Touchscreen</string>
|
||||
<string name="input_screen_summary">Controller settings</string>
|
||||
<string name="input_screen_title">Input</string>
|
||||
<string name="video_screen_summary">Graphic settings</string>
|
||||
|
@ -141,6 +169,10 @@
|
|||
<string name="VerticalInterruptsPerSecond">Vertical interrupts per second</string>
|
||||
<string name="ForceGfxReset_title">Reset GFX Plugin</string>
|
||||
<string name="ForceGfxReset_summary">Always reload GFX plugin</string>
|
||||
<string name="touchscreenScale_title">Button scale</string>
|
||||
<string name="touchscreenLayout_title">Touchscreen layout</string>
|
||||
<string name="gamepad_summary">Configure gamepad to use in game</string>
|
||||
<string name="gamepad_title">Gamepad settings</string>
|
||||
<string name="settings_reset_title">Reset settings</string>
|
||||
<string name="settings_reset_message">Reset all settings back to their defaults?</string>
|
||||
|
||||
|
|
|
@ -55,4 +55,16 @@
|
|||
</style>
|
||||
|
||||
<style name="Project64.Toolbar" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
|
||||
<style name="btnMappable" parent="@android:style/Widget.Button">
|
||||
<item name="android:layout_width">@dimen/inputMapPreferenceButtonSize</item>
|
||||
<item name="android:layout_height">@dimen/inputMapPreferenceButtonSize</item>
|
||||
<item name="android:background">@drawable/button_gray</item>
|
||||
<item name="android:focusable">false</item>
|
||||
<item name="android:focusableInTouchMode">false</item>
|
||||
<item name="android:textColor">@android:color/primary_text_dark_nodisable</item>
|
||||
</style>
|
||||
|
||||
<style name="btnMappableNoText" parent="@style/btnMappable">
|
||||
<item name="android:textColor">@android:color/transparent</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:focusable="false"
|
||||
android:focusableInTouchMode="false"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical" >
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</PreferenceScreen>
|
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<Preference
|
||||
android:summary="@string/touch_screen_summary"
|
||||
android:title="@string/touch_screen_title"
|
||||
android:key="settings_touch_screen"
|
||||
android:icon="@drawable/ic_phone"
|
||||
/>
|
||||
<Preference
|
||||
android:summary="@string/gamepad_summary"
|
||||
android:title="@string/gamepad_title"
|
||||
android:key="settings_gamepad_screen"
|
||||
android:icon="@drawable/ic_gamepad"
|
||||
/>
|
||||
</PreferenceScreen>
|
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:project64="http://schemas.android.com/apk/res/emu.project64"
|
||||
android:key="screenRoot" >
|
||||
|
||||
<emu.project64.settings.SeekBarPreference
|
||||
android:defaultValue="100"
|
||||
android:key="touchscreenScale"
|
||||
android:title="@string/touchscreenScale_title"
|
||||
project64:maximumValue="400"
|
||||
project64:minimumValue="50"
|
||||
project64:stepSize="1"
|
||||
project64:units="%"
|
||||
/>
|
||||
|
||||
<emu.project64.settings.TwoLinesListPreference
|
||||
android:defaultValue="0"
|
||||
android:key="touchscreenLayout"
|
||||
android:title="@string/touchscreenLayout_title"
|
||||
android:dialogTitle="@string/touchscreenLayout_title" />
|
||||
|
||||
</PreferenceScreen>
|
|
@ -25,6 +25,7 @@ import emu.project64.util.Utility;
|
|||
import tv.ouya.console.api.OuyaFacade;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.view.KeyEvent;
|
||||
|
||||
public class AndroidDevice
|
||||
{
|
||||
|
@ -46,6 +47,9 @@ public class AndroidDevice
|
|||
/** True if device is running KitKat or later (19 - Android 4.4.x) */
|
||||
public static final boolean IS_KITKAT = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
|
||||
|
||||
/** True if device is running Lollipop or later (21 - Android 5.0.x) */
|
||||
public static final boolean IS_LOLLIPOP = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
|
||||
|
||||
/** True if device is an OUYA. */
|
||||
public static final boolean IS_OUYA_HARDWARE = OuyaFacade.getInstance().isRunningOnOUYAHardware();
|
||||
|
||||
|
@ -55,6 +59,9 @@ public class AndroidDevice
|
|||
public static final boolean IS_ACTION_BAR_AVAILABLE = AndroidDevice.IS_HONEYCOMB && !AndroidDevice.IS_OUYA_HARDWARE;
|
||||
|
||||
final static boolean isTv;
|
||||
|
||||
public static boolean MapVolumeKeys = false;
|
||||
|
||||
static
|
||||
{
|
||||
isTv = Project64Application.getAppContext().getPackageManager().hasSystemFeature("android.software.leanback");
|
||||
|
@ -65,6 +72,24 @@ public class AndroidDevice
|
|||
return isTv;
|
||||
}
|
||||
|
||||
public static List<Integer> getUnmappableKeyCodes ()
|
||||
{
|
||||
List<Integer> unmappables = new ArrayList<Integer>();
|
||||
unmappables.add( KeyEvent.KEYCODE_MENU );
|
||||
if( IS_HONEYCOMB )
|
||||
{
|
||||
// Back key is needed to show/hide the action bar in HC+
|
||||
unmappables.add( KeyEvent.KEYCODE_BACK );
|
||||
}
|
||||
if( !MapVolumeKeys )
|
||||
{
|
||||
unmappables.add( KeyEvent.KEYCODE_VOLUME_UP );
|
||||
unmappables.add( KeyEvent.KEYCODE_VOLUME_DOWN );
|
||||
unmappables.add( KeyEvent.KEYCODE_VOLUME_MUTE );
|
||||
}
|
||||
return unmappables;
|
||||
}
|
||||
|
||||
public static ArrayList<String> getStorageDirectories()
|
||||
{
|
||||
BufferedReader bufReader = null;
|
||||
|
|
|
@ -15,6 +15,7 @@ import emu.project64.jni.SystemEvent;
|
|||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
public class GameActivity extends Activity
|
||||
|
@ -95,4 +96,13 @@ public class GameActivity extends Activity
|
|||
super.onDestroy();
|
||||
mLifecycleHandler.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data)
|
||||
{
|
||||
if (requestCode == GameLifecycleHandler.RC_SETTINGS)
|
||||
{
|
||||
mLifecycleHandler.onSettingDone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,46 +15,65 @@ import java.lang.ref.WeakReference;
|
|||
import javax.microedition.khronos.egl.EGLConfig;
|
||||
import javax.microedition.khronos.opengles.GL10;
|
||||
|
||||
import com.bda.controller.Controller;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Set;
|
||||
|
||||
import emu.project64.AndroidDevice;
|
||||
import emu.project64.R;
|
||||
import emu.project64.hack.MogaHack;
|
||||
import emu.project64.input.AbstractController;
|
||||
import emu.project64.input.PeripheralController;
|
||||
import emu.project64.input.TouchController;
|
||||
import emu.project64.input.map.InputMap;
|
||||
import emu.project64.input.map.VisibleTouchMap;
|
||||
import emu.project64.input.provider.AbstractProvider;
|
||||
import emu.project64.input.provider.AxisProvider;
|
||||
import emu.project64.input.provider.KeyProvider;
|
||||
import emu.project64.input.provider.KeyProvider.ImeFormula;
|
||||
import emu.project64.input.provider.MogaProvider;
|
||||
import emu.project64.jni.NativeExports;
|
||||
import emu.project64.jni.NativeXperiaTouchpad;
|
||||
import emu.project64.jni.SettingsID;
|
||||
import emu.project64.jni.SystemEvent;
|
||||
import emu.project64.jni.UISettingID;
|
||||
import emu.project64.persistent.ConfigFile;
|
||||
import emu.project64.persistent.ConfigFile.ConfigSection;
|
||||
import emu.project64.profile.Profile;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.app.ActivityManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.os.Bundle;
|
||||
import android.os.Vibrator;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager.LayoutParams;
|
||||
|
||||
public class GameLifecycleHandler implements SurfaceHolder.Callback, GameSurface.SurfaceInfo
|
||||
public class GameLifecycleHandler implements View.OnKeyListener, SurfaceHolder.Callback, GameSurface.SurfaceInfo
|
||||
{
|
||||
private final static boolean LOG_GAMELIFECYCLEHANDLER = false;
|
||||
public final static int RC_SETTINGS = 10005;
|
||||
|
||||
// Activity and views
|
||||
private Activity mActivity;
|
||||
private GameSurface mSurface;
|
||||
private GameOverlay mOverlay;
|
||||
|
||||
// Input resources
|
||||
private final ArrayList<AbstractController> mControllers;
|
||||
private VisibleTouchMap mTouchscreenMap;
|
||||
private KeyProvider mKeyProvider;
|
||||
private Controller mMogaController;
|
||||
|
||||
// Internal flags
|
||||
private final boolean mIsXperiaPlay;
|
||||
private boolean mStarted = false;
|
||||
|
@ -65,11 +84,15 @@ public class GameLifecycleHandler implements SurfaceHolder.Callback, GameSurface
|
|||
private boolean mIsResumed = false; // true if the activity is resumed
|
||||
private boolean mIsSurface = false; // true if the surface is available
|
||||
|
||||
private float mtouchscreenScale = ((float)NativeExports.UISettingsLoadDword(UISettingID.TouchScreen_ButtonScale.getValue())) / 100.0f;
|
||||
private String mlayout = NativeExports.UISettingsLoadString(UISettingID.TouchScreen_Layout.getValue());
|
||||
|
||||
public GameLifecycleHandler(Activity activity)
|
||||
{
|
||||
mActivity = activity;
|
||||
mControllers = new ArrayList<AbstractController>();
|
||||
mIsXperiaPlay = !(activity instanceof GameActivity);
|
||||
mMogaController = Controller.getInstance( mActivity );
|
||||
}
|
||||
|
||||
@TargetApi(11)
|
||||
|
@ -79,6 +102,11 @@ public class GameLifecycleHandler implements SurfaceHolder.Callback, GameSurface
|
|||
{
|
||||
Log.i("GameLifecycleHandler", "onCreateBegin");
|
||||
}
|
||||
// Initialize MOGA controller API
|
||||
// TODO: Remove hack after MOGA SDK is fixed
|
||||
// mMogaController.init();
|
||||
MogaHack.init( mMogaController, mActivity );
|
||||
|
||||
|
||||
// For Honeycomb, let the action bar overlay the rendered view (rather
|
||||
// than squeezing it)
|
||||
|
@ -128,29 +156,14 @@ public class GameLifecycleHandler implements SurfaceHolder.Callback, GameSurface
|
|||
mActivity.getActionBar().setBackgroundDrawable(color);
|
||||
}
|
||||
|
||||
boolean isFpsEnabled = false; //mGlobalPrefs.isFpsEnabled
|
||||
boolean isTouchscreenAnimated = false; //mGlobalPrefs.isTouchscreenAnimated
|
||||
boolean isTouchscreenHidden = false; //!isTouchscreenEnabled || globalPrefs.touchscreenTransparency == 0;
|
||||
String profilesDir = AndroidDevice.PACKAGE_DIRECTORY + "/profiles";
|
||||
String touchscreenProfiles_cfg = profilesDir + "/touchscreen.cfg";
|
||||
ConfigFile touchscreenConfigFile = new ConfigFile( touchscreenProfiles_cfg );
|
||||
//SharedPreferences mPreferences = context.getSharedPreferences( sharedPrefsName, Context.MODE_PRIVATE );
|
||||
Profile touchscreenProfile = new Profile( true, touchscreenConfigFile.get( "Analog")); //loadProfile( /*mPreferences*/ null, "touchscreenProfile", "Analog", touchscreenProfiles_cfg,touchscreenProfiles_cfg );
|
||||
int touchscreenTransparency = 100;
|
||||
String touchscreenSkinsDir = AndroidDevice.PACKAGE_DIRECTORY + "/skins/touchscreen";
|
||||
String touchscreenSkin = touchscreenSkinsDir + "/Outline";
|
||||
float touchscreenScale = 1.0f; //( (float) mPreferences.getInt( "touchscreenScale", 100 ) ) / 100.0f;
|
||||
|
||||
// The touch map and overlay are needed to display frame rate and/or controls
|
||||
mTouchscreenMap = new VisibleTouchMap( mActivity.getResources() );
|
||||
mTouchscreenMap.load(touchscreenSkin, touchscreenProfile,
|
||||
isTouchscreenAnimated, isFpsEnabled,
|
||||
touchscreenScale, touchscreenTransparency );
|
||||
mOverlay.initialize( mTouchscreenMap, !isTouchscreenHidden, isFpsEnabled, isTouchscreenAnimated );
|
||||
CreateTouchScreenControls();
|
||||
|
||||
// Initialize user interface devices
|
||||
View inputSource = mIsXperiaPlay ? new NativeXperiaTouchpad(mActivity) : mOverlay;
|
||||
initControllers(inputSource);
|
||||
|
||||
// Override the peripheral controllers' key provider, to add some extra functionality
|
||||
inputSource.setOnKeyListener( this );
|
||||
}
|
||||
|
||||
public void onStart()
|
||||
|
@ -169,6 +182,8 @@ public class GameLifecycleHandler implements SurfaceHolder.Callback, GameSurface
|
|||
}
|
||||
mIsResumed = true;
|
||||
tryRunning();
|
||||
|
||||
mMogaController.onResume();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -275,6 +290,7 @@ public class GameLifecycleHandler implements SurfaceHolder.Callback, GameSurface
|
|||
{
|
||||
AutoSave();
|
||||
}
|
||||
mMogaController.onPause();
|
||||
if (LOG_GAMELIFECYCLEHANDLER)
|
||||
{
|
||||
Log.i("GameLifecycleHandler", "onPause - done");
|
||||
|
@ -305,6 +321,67 @@ public class GameLifecycleHandler implements SurfaceHolder.Callback, GameSurface
|
|||
{
|
||||
Log.i("GameLifecycleHandler", "onDestroy");
|
||||
}
|
||||
mMogaController.exit();
|
||||
}
|
||||
|
||||
public void onSettingDone ()
|
||||
{
|
||||
float touchscreenScale = ((float)NativeExports.UISettingsLoadDword(UISettingID.TouchScreen_ButtonScale.getValue())) / 100.0f;
|
||||
boolean recreateTouchScreenControls = false;
|
||||
if (touchscreenScale != mtouchscreenScale)
|
||||
{
|
||||
mtouchscreenScale = touchscreenScale;
|
||||
recreateTouchScreenControls = true;
|
||||
}
|
||||
|
||||
String layout = NativeExports.UISettingsLoadString(UISettingID.TouchScreen_Layout.getValue());
|
||||
if (layout != mlayout)
|
||||
{
|
||||
mlayout = layout;
|
||||
recreateTouchScreenControls = true;
|
||||
}
|
||||
|
||||
if (recreateTouchScreenControls)
|
||||
{
|
||||
CreateTouchScreenControls();
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateTouchScreenControls()
|
||||
{
|
||||
boolean isTouchscreenAnimated = false; //mGlobalPrefs.isTouchscreenAnimated
|
||||
boolean isTouchscreenHidden = false; //!isTouchscreenEnabled || globalPrefs.touchscreenTransparency == 0;
|
||||
String profilesDir = AndroidDevice.PACKAGE_DIRECTORY + "/profiles";
|
||||
String touchscreenProfiles_cfg = profilesDir + "/touchscreen.cfg";
|
||||
ConfigFile touchscreenConfigFile = new ConfigFile( touchscreenProfiles_cfg );
|
||||
ConfigSection section = touchscreenConfigFile.get(mlayout);
|
||||
if (section == null)
|
||||
{
|
||||
mlayout = "Analog";
|
||||
section = touchscreenConfigFile.get(mlayout);
|
||||
}
|
||||
Profile touchscreenProfile = new Profile( true, section);
|
||||
int touchscreenTransparency = 100;
|
||||
String touchscreenSkinsDir = AndroidDevice.PACKAGE_DIRECTORY + "/skins/touchscreen";
|
||||
String touchscreenSkin = touchscreenSkinsDir + "/Outline";
|
||||
|
||||
// The touch map and overlay are needed to display frame rate and/or controls
|
||||
mTouchscreenMap = new VisibleTouchMap( mActivity.getResources() );
|
||||
mTouchscreenMap.load(touchscreenSkin, touchscreenProfile, isTouchscreenAnimated, mtouchscreenScale, touchscreenTransparency );
|
||||
mOverlay.initialize( mTouchscreenMap, !isTouchscreenHidden, isTouchscreenAnimated );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKey( View view, int keyCode, KeyEvent event )
|
||||
{
|
||||
// If PeripheralControllers exist and handle the event,
|
||||
// they return true. Else they return false, signaling
|
||||
// Android to handle the event (menu button, vol keys).
|
||||
if( mKeyProvider != null )
|
||||
{
|
||||
return mKeyProvider.onKey( view, keyCode, event );
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
|
@ -321,6 +398,23 @@ public class GameLifecycleHandler implements SurfaceHolder.Callback, GameSurface
|
|||
inputSource, mOverlay, vibrator, touchscreenAutoHold,
|
||||
isTouchscreenFeedbackEnabled, autoHoldableButtons );
|
||||
mControllers.add( touchscreenController );
|
||||
|
||||
// Create the input providers shared among all peripheral controllers
|
||||
String profile_name = NativeExports.UISettingsLoadString(UISettingID.Controller_CurrentProfile.getValue());
|
||||
ConfigFile ControllerConfigFile = new ConfigFile(NativeExports.UISettingsLoadString(UISettingID.Controller_ConfigFile.getValue()));
|
||||
ConfigSection section = ControllerConfigFile.get( profile_name );
|
||||
if (section != null)
|
||||
{
|
||||
Profile ControllerProfile = new Profile( false, section );
|
||||
InputMap map = new InputMap( ControllerProfile.get( "map" ) );
|
||||
|
||||
mKeyProvider = new KeyProvider( inputSource, ImeFormula.DEFAULT, AndroidDevice.getUnmappableKeyCodes() );
|
||||
MogaProvider mogaProvider = new MogaProvider( mMogaController );
|
||||
AbstractProvider axisProvider = AndroidDevice.IS_HONEYCOMB_MR1 ? new AxisProvider( inputSource ) : null;
|
||||
int Deadzone = NativeExports.UISettingsLoadDword(UISettingID.Controller_Deadzone.getValue());
|
||||
int Sensitivity = NativeExports.UISettingsLoadDword(UISettingID.Controller_Sensitivity.getValue());
|
||||
mControllers.add( new PeripheralController( 1, map, Deadzone, Sensitivity, mKeyProvider, axisProvider, mogaProvider ) );
|
||||
}
|
||||
}
|
||||
|
||||
private void tryRunning()
|
||||
|
|
|
@ -146,7 +146,7 @@ public class GameMenuHandler implements PopupMenu.OnMenuItemClickListener, Popup
|
|||
break;
|
||||
case R.id.menuItem_settings:
|
||||
Intent SettingsIntent = new Intent(mActivity, SettingsActivity.class);
|
||||
mActivity.startActivity( SettingsIntent );
|
||||
mActivity.startActivityForResult( SettingsIntent, GameLifecycleHandler.RC_SETTINGS );
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
|
@ -44,7 +44,7 @@ public class GameOverlay extends View implements TouchController.OnStateChangedL
|
|||
requestFocus();
|
||||
}
|
||||
|
||||
public void initialize( VisibleTouchMap touchMap, boolean drawingEnabled, boolean fpsEnabled, boolean joystickAnimated )
|
||||
public void initialize( VisibleTouchMap touchMap, boolean drawingEnabled, boolean joystickAnimated )
|
||||
{
|
||||
mTouchMap = touchMap;
|
||||
mDrawingEnabled = drawingEnabled;
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
/****************************************************************************
|
||||
* *
|
||||
* Project64 - A Nintendo 64 emulator. *
|
||||
* http://www.pj64-emu.com/ *
|
||||
* Copyright (C) 2016 Project64. All rights reserved. *
|
||||
* Copyright (C) 2013 Paul Lamb
|
||||
* *
|
||||
* License: *
|
||||
* GNU/GPLv2 http://www.gnu.org/licenses/gpl-2.0.html *
|
||||
* *
|
||||
****************************************************************************/
|
||||
package emu.project64.hack;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.content.pm.ServiceInfo;
|
||||
import android.util.Log;
|
||||
|
||||
import com.bda.controller.Controller;
|
||||
import com.bda.controller.IControllerService;
|
||||
|
||||
import emu.project64.AndroidDevice;
|
||||
|
||||
/**
|
||||
* Temporary hack for crash in MOGA library on Lollipop. This hack can be removed once MOGA fixes
|
||||
* their library. The actual issue is caused by the use of implicit service intents, which are
|
||||
* illegal in Lollipop, as seen in the logcat message below.
|
||||
*
|
||||
* <pre>
|
||||
* {@code Service Intent must be explicit: Intent { act=com.bda.controller.IControllerService } }
|
||||
* </pre>
|
||||
*
|
||||
* @see <a href="http://www.mogaanywhere.com/developers/">MOGA developer site</a>
|
||||
* @see <a href="http://commonsware.com/blog/2014/06/29/dealing-deprecations-bindservice.html">
|
||||
* Discussion on explicit intents</a>
|
||||
*/
|
||||
public class MogaHack
|
||||
{
|
||||
public static void init( Controller controller, Context context )
|
||||
{
|
||||
if( AndroidDevice.IS_LOLLIPOP )
|
||||
{
|
||||
boolean mIsBound = false;
|
||||
java.lang.reflect.Field fIsBound = null;
|
||||
android.content.ServiceConnection mServiceConnection = null;
|
||||
java.lang.reflect.Field fServiceConnection = null;
|
||||
try
|
||||
{
|
||||
Class<?> cMogaController = controller.getClass();
|
||||
fIsBound = cMogaController.getDeclaredField( "mIsBound" );
|
||||
fIsBound.setAccessible( true );
|
||||
mIsBound = fIsBound.getBoolean( controller );
|
||||
fServiceConnection = cMogaController.getDeclaredField( "mServiceConnection" );
|
||||
fServiceConnection.setAccessible( true );
|
||||
mServiceConnection = ( android.content.ServiceConnection ) fServiceConnection.get( controller );
|
||||
}
|
||||
catch( NoSuchFieldException e )
|
||||
{
|
||||
Log.e( "MogaHack", "MOGA Lollipop Hack NoSuchFieldException (get)", e );
|
||||
}
|
||||
catch( IllegalAccessException e )
|
||||
{
|
||||
Log.e( "MogaHack", "MOGA Lollipop Hack IllegalAccessException (get)", e );
|
||||
}
|
||||
catch( IllegalArgumentException e )
|
||||
{
|
||||
Log.e( "MogaHack", "MOGA Lollipop Hack IllegalArgumentException (get)", e );
|
||||
}
|
||||
if( ( !mIsBound ) && ( mServiceConnection != null ) )
|
||||
{
|
||||
// Convert implicit intent to explicit intent, see http://stackoverflow.com/a/26318757
|
||||
Intent intent = new Intent( IControllerService.class.getName() );
|
||||
List<ResolveInfo> resolveInfos = context.getPackageManager().queryIntentServices( intent, 0 );
|
||||
if( resolveInfos == null || resolveInfos.size() != 1 )
|
||||
{
|
||||
Log.e( "MogaHack", "Somebody is trying to intercept our intent. Disabling MOGA controller for security." );
|
||||
return;
|
||||
}
|
||||
ServiceInfo serviceInfo = resolveInfos.get( 0 ).serviceInfo;
|
||||
String packageName = serviceInfo.packageName;
|
||||
String className = serviceInfo.name;
|
||||
intent.setComponent( new ComponentName( packageName, className ) );
|
||||
|
||||
// Start the service explicitly
|
||||
context.startService( intent );
|
||||
context.bindService( intent, mServiceConnection, 1 );
|
||||
try
|
||||
{
|
||||
fIsBound.setBoolean( controller, true );
|
||||
}
|
||||
catch( IllegalAccessException e )
|
||||
{
|
||||
Log.e( "MogaHack", "MOGA Lollipop Hack IllegalAccessException (set)", e );
|
||||
}
|
||||
catch( IllegalArgumentException e )
|
||||
{
|
||||
Log.e( "MogaHack", "MOGA Lollipop Hack IllegalArgumentException (set)", e );
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
controller.init();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,6 +12,7 @@ package emu.project64.input;
|
|||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import android.util.Log;
|
||||
import emu.project64.jni.NativeInput;
|
||||
|
||||
/**
|
||||
|
@ -45,6 +46,8 @@ import emu.project64.jni.NativeInput;
|
|||
*/
|
||||
public abstract class AbstractController
|
||||
{
|
||||
protected final static boolean LOG_CONTROLLER = false;
|
||||
|
||||
/**
|
||||
* A small class that encapsulates controller state.
|
||||
*/
|
||||
|
@ -146,8 +149,13 @@ public abstract class AbstractController
|
|||
*/
|
||||
protected void notifyChanged()
|
||||
{
|
||||
|
||||
int axisX = Math.round( AXIS_SCALE * mState.axisFractionX );
|
||||
int axisY = Math.round( AXIS_SCALE * mState.axisFractionY );
|
||||
if (LOG_CONTROLLER)
|
||||
{
|
||||
Log.i("Controller", "notifyChanged: axisX=" + axisX + " axisY=" + axisY);
|
||||
}
|
||||
NativeInput.setState( mPlayerNumber - 1, mState.buttons, axisX, axisY );
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,173 @@
|
|||
/****************************************************************************
|
||||
* *
|
||||
* Project 64 - A Nintendo 64 emulator. *
|
||||
* http://www.pj64-emu.com/ *
|
||||
* Copyright (C) 2016 Project64. All rights reserved. *
|
||||
* Copyright (C) 2013 Paul Lamb, littleguy77 *
|
||||
* *
|
||||
* License: *
|
||||
* GNU/GPLv2 http://www.gnu.org/licenses/gpl-2.0.html *
|
||||
* *
|
||||
****************************************************************************/
|
||||
package emu.project64.input;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import emu.project64.input.map.InputMap;
|
||||
import emu.project64.input.provider.AbstractProvider;
|
||||
import emu.project64.util.Utility;
|
||||
import android.annotation.TargetApi;
|
||||
import android.util.FloatMath;
|
||||
import android.util.Log;
|
||||
import android.view.InputDevice;
|
||||
import android.view.KeyEvent;
|
||||
|
||||
/**
|
||||
* A class for generating N64 controller commands from peripheral hardware (gamepads, joysticks,
|
||||
* keyboards, mice, etc.).
|
||||
*/
|
||||
public class PeripheralController extends AbstractController implements
|
||||
AbstractProvider.OnInputListener
|
||||
{
|
||||
/** The map from input codes to commands. */
|
||||
private final InputMap mInputMap;
|
||||
|
||||
/** The analog deadzone, between 0 and 1, inclusive. */
|
||||
private final float mDeadzoneFraction;
|
||||
|
||||
/** The analog sensitivity, the amount by which to scale stick values, nominally 1. */
|
||||
private final float mSensitivityFraction;
|
||||
|
||||
/** The user input providers. */
|
||||
private final ArrayList<AbstractProvider> mProviders;
|
||||
|
||||
/** The positive analog-x strength, between 0 and 1, inclusive. */
|
||||
private float mStrengthXpos;
|
||||
|
||||
/** The negative analog-x strength, between 0 and 1, inclusive. */
|
||||
private float mStrengthXneg;
|
||||
|
||||
/** The positive analog-y strength, between 0 and 1, inclusive. */
|
||||
private float mStrengthYpos;
|
||||
|
||||
/** The negative analogy-y strength, between 0 and 1, inclusive. */
|
||||
private float mStrengthYneg;
|
||||
|
||||
/**
|
||||
* Instantiates a new peripheral controller.
|
||||
*
|
||||
* @param player The player number, between 1 and 4, inclusive.
|
||||
* @param inputMap The map from input codes to commands.
|
||||
* @param inputDeadzone The analog deadzone in percent.
|
||||
* @param inputSensitivity The analog sensitivity in percent.
|
||||
* @param providers The user input providers. Null elements are safe.
|
||||
*/
|
||||
public PeripheralController( int player, InputMap inputMap, int inputDeadzone, int inputSensitivity, AbstractProvider... providers )
|
||||
{
|
||||
setPlayerNumber( player );
|
||||
|
||||
// Assign the maps
|
||||
mInputMap = inputMap;
|
||||
mDeadzoneFraction = ( (float) inputDeadzone ) / 100f;
|
||||
mSensitivityFraction = ( (float) inputSensitivity ) / 100f;
|
||||
|
||||
// Assign the non-null input providers
|
||||
mProviders = new ArrayList<AbstractProvider>();
|
||||
for( AbstractProvider provider : providers )
|
||||
{
|
||||
if( provider != null )
|
||||
{
|
||||
mProviders.add( provider );
|
||||
provider.registerListener( this );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi( 16 )
|
||||
@Override
|
||||
public void onInput( int inputCode, float strength, int hardwareId )
|
||||
{
|
||||
// Apply user changes to the controller state
|
||||
apply( inputCode, strength );
|
||||
|
||||
// Notify the core that controller state has changed
|
||||
notifyChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInput( int[] inputCodes, float[] strengths, int hardwareId )
|
||||
{
|
||||
// Apply user changes to the controller state
|
||||
for( int i = 0; i < inputCodes.length; i++ )
|
||||
apply( inputCodes[i], strengths[i] );
|
||||
|
||||
// Notify the core that controller state has changed
|
||||
notifyChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply user input to the N64 controller state.
|
||||
*
|
||||
* @param inputCode The universal input code that was dispatched.
|
||||
* @param strength The input strength, between 0 and 1, inclusive.
|
||||
*
|
||||
* @return True, if controller state changed.
|
||||
*/
|
||||
private boolean apply( int inputCode, float strength )
|
||||
{
|
||||
boolean keyDown = strength > AbstractProvider.STRENGTH_THRESHOLD;
|
||||
int n64Index = mInputMap.get( inputCode );
|
||||
|
||||
if( n64Index >= 0 && n64Index < NUM_N64_BUTTONS )
|
||||
{
|
||||
mState.buttons[n64Index] = keyDown;
|
||||
return true;
|
||||
}
|
||||
else if( n64Index < InputMap.NUM_N64_CONTROLS )
|
||||
{
|
||||
switch( n64Index )
|
||||
{
|
||||
case InputMap.AXIS_R:
|
||||
mStrengthXpos = strength;
|
||||
break;
|
||||
case InputMap.AXIS_L:
|
||||
mStrengthXneg = strength;
|
||||
break;
|
||||
case InputMap.AXIS_D:
|
||||
mStrengthYneg = strength;
|
||||
break;
|
||||
case InputMap.AXIS_U:
|
||||
mStrengthYpos = strength;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
// Calculate the net position of the analog stick
|
||||
float rawX = mSensitivityFraction * ( mStrengthXpos - mStrengthXneg );
|
||||
float rawY = mSensitivityFraction * ( mStrengthYpos - mStrengthYneg );
|
||||
float magnitude = (float) Math.sqrt( ( rawX * rawX ) + ( rawY * rawY ) );
|
||||
|
||||
// Update controller state
|
||||
if( magnitude > mDeadzoneFraction )
|
||||
{
|
||||
// Normalize the vector
|
||||
float normalizedX = rawX / magnitude;
|
||||
float normalizedY = rawY / magnitude;
|
||||
|
||||
// Rescale strength to account for deadzone
|
||||
magnitude = ( magnitude - mDeadzoneFraction ) / ( 1f - mDeadzoneFraction );
|
||||
magnitude = Utility.clamp( magnitude, 0f, 1f );
|
||||
mState.axisFractionX = normalizedX * magnitude;
|
||||
mState.axisFractionY = normalizedY * magnitude;
|
||||
}
|
||||
else
|
||||
{
|
||||
// In the deadzone
|
||||
mState.axisFractionX = 0;
|
||||
mState.axisFractionY = 0;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -26,7 +26,6 @@ import android.view.View.OnTouchListener;
|
|||
/**
|
||||
* A class for generating N64 controller commands from a touchscreen.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public class TouchController extends AbstractController implements OnTouchListener
|
||||
{
|
||||
public interface OnStateChangedListener
|
||||
|
|
|
@ -0,0 +1,135 @@
|
|||
/****************************************************************************
|
||||
* *
|
||||
* Project64 - A Nintendo 64 emulator. *
|
||||
* http://www.pj64-emu.com/ *
|
||||
* Copyright (C) 2012 Project64. All rights reserved. *
|
||||
* *
|
||||
* License: *
|
||||
* GNU/GPLv2 http://www.gnu.org/licenses/gpl-2.0.html *
|
||||
* *
|
||||
****************************************************************************/
|
||||
package emu.project64.input.map;
|
||||
|
||||
import emu.project64.input.AbstractController;
|
||||
import emu.project64.input.provider.AbstractProvider;
|
||||
|
||||
/**
|
||||
* A class for mapping arbitrary user inputs to N64 buttons/axes.
|
||||
*
|
||||
* @see AbstractProvider
|
||||
* @see PeripheralController
|
||||
* @see ControllerProfileActivity
|
||||
*/
|
||||
public class InputMap extends SerializableMap
|
||||
{
|
||||
/** Map flag: Input code is not mapped. */
|
||||
public static final int UNMAPPED = -1;
|
||||
|
||||
/** Map offset: N64 non-button controls. */
|
||||
public static final int OFFSET_EXTRAS = AbstractController.NUM_N64_BUTTONS;
|
||||
|
||||
/** N64 control: analog-right. */
|
||||
public static final int AXIS_R = OFFSET_EXTRAS;
|
||||
|
||||
/** N64 control: analog-left. */
|
||||
public static final int AXIS_L = OFFSET_EXTRAS + 1;
|
||||
|
||||
/** N64 control: analog-down. */
|
||||
public static final int AXIS_D = OFFSET_EXTRAS + 2;
|
||||
|
||||
/** N64 control: analog-up. */
|
||||
public static final int AXIS_U = OFFSET_EXTRAS + 3;
|
||||
|
||||
/** Total number of N64 controls. */
|
||||
public static final int NUM_N64_CONTROLS = OFFSET_EXTRAS + 4;
|
||||
/** Total number of mappable controls/functions. */
|
||||
public static final int NUM_MAPPABLES = NUM_N64_CONTROLS;
|
||||
|
||||
public InputMap( String serializedMap )
|
||||
{
|
||||
super( serializedMap );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the command mapped to a given input code.
|
||||
*
|
||||
* @param inputCode The standardized input code.
|
||||
*
|
||||
* @return The command the code is mapped to, or UNMAPPED.
|
||||
*
|
||||
* @see AbstractProvider
|
||||
* @see InputMap#UNMAPPED
|
||||
*/
|
||||
public int get( int inputCode )
|
||||
{
|
||||
return mMap.get( inputCode, UNMAPPED );
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps an input code to a command.
|
||||
*
|
||||
* @param inputCode The standardized input code to be mapped.
|
||||
* @param command The index to the N64/Mupen command.
|
||||
*/
|
||||
public void map( int inputCode, int command )
|
||||
{
|
||||
// Map the input if a valid index was given
|
||||
if( command >= 0 && command < NUM_MAPPABLES && inputCode != 0 )
|
||||
{
|
||||
if( inputCode < 0 )
|
||||
{
|
||||
// If an analog input is mapped, it should be the only thing mapped to this command
|
||||
unmapCommand( command );
|
||||
}
|
||||
else
|
||||
{
|
||||
// If a digital input is mapped, no analog inputs can be mapped to this command
|
||||
for( int i = mMap.size() - 1; i >= 0; i-- )
|
||||
{
|
||||
if( mMap.valueAt( i ) == command && mMap.keyAt( i ) < 0 )
|
||||
mMap.removeAt( i );
|
||||
}
|
||||
}
|
||||
mMap.put( inputCode, command );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unmaps a command.
|
||||
*
|
||||
* @param command The index to the command.
|
||||
*/
|
||||
public void unmapCommand( int command )
|
||||
{
|
||||
// Remove any matching key-value pairs (count down to accommodate removal)
|
||||
for( int i = mMap.size() - 1; i >= 0; i-- )
|
||||
{
|
||||
if( mMap.valueAt( i ) == command )
|
||||
mMap.removeAt( i );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a command is mapped to at least one input code.
|
||||
*
|
||||
* @param command The index to the command.
|
||||
*
|
||||
* @return True, if the mapping exists.
|
||||
*/
|
||||
public boolean isMapped( int command )
|
||||
{
|
||||
return mMap.indexOfValue( command ) >= 0;
|
||||
}
|
||||
public String getMappedCodeInfo( int command )
|
||||
{
|
||||
String result = "";
|
||||
for( int i = 0; i < mMap.size(); i++ )
|
||||
{
|
||||
if( mMap.valueAt( i ) == command )
|
||||
{
|
||||
result += AbstractProvider.getInputName( mMap.keyAt( i ) ) + "\n";
|
||||
}
|
||||
}
|
||||
return result.trim();
|
||||
}
|
||||
}
|
|
@ -33,7 +33,6 @@ import android.util.SparseArray;
|
|||
* @see TouchController
|
||||
*/
|
||||
@SuppressLint("FloatMath")
|
||||
@SuppressWarnings("deprecation")
|
||||
public class TouchMap
|
||||
{
|
||||
/** Map flag: Touch location is not mapped. */
|
||||
|
|
|
@ -31,33 +31,6 @@ import android.util.Log;
|
|||
*/
|
||||
public class VisibleTouchMap extends TouchMap
|
||||
{
|
||||
/** FPS frame image. */
|
||||
private Image mFpsFrame;
|
||||
|
||||
/** X-coordinate of the FPS frame, in percent. */
|
||||
private int mFpsFrameX;
|
||||
|
||||
/** Y-coordinate of the FPS frame, in percent. */
|
||||
private int mFpsFrameY;
|
||||
|
||||
/** X-coordinate of the FPS text centroid, in percent. */
|
||||
private int mFpsTextX;
|
||||
|
||||
/** Y-coordinate of the FPS text centroid, in percent. */
|
||||
private int mFpsTextY;
|
||||
|
||||
/** The current FPS value. */
|
||||
private int mFpsValue;
|
||||
|
||||
/** The minimum size of the FPS indicator in pixels. */
|
||||
private float mFpsMinPixels;
|
||||
|
||||
/** The minimum size to scale the FPS indicator. */
|
||||
private float mFpsMinScale;
|
||||
|
||||
/** True if the FPS indicator should be drawn. */
|
||||
private boolean mFpsEnabled;
|
||||
|
||||
/** The factor to scale images by. */
|
||||
private float mScalingFactor = 1.0f;
|
||||
|
||||
|
@ -71,20 +44,14 @@ public class VisibleTouchMap extends TouchMap
|
|||
private int mReferenceHeight = 0;
|
||||
|
||||
/** The last width passed to {@link #resize(int, int, DisplayMetrics)}. */
|
||||
private int cacheWidth = 0;
|
||||
private static int cacheWidth = 0;
|
||||
|
||||
/** The last height passed to {@link #resize(int, int, DisplayMetrics)}. */
|
||||
private int cacheHeight = 0;
|
||||
private static int cacheHeight = 0;
|
||||
|
||||
/** The last height passed to {@link #resize(int, int, DisplayMetrics)}. */
|
||||
private DisplayMetrics cacheMetrics;
|
||||
|
||||
/** The set of images representing the FPS string. */
|
||||
private final CopyOnWriteArrayList<Image> mFpsDigits;
|
||||
|
||||
/** The set of images representing the numerals 0, 1, 2, ..., 9. */
|
||||
private final Image[] mNumerals;
|
||||
|
||||
/** Auto-hold overlay images. */
|
||||
public final Image[] autoHoldImages;
|
||||
|
||||
|
@ -102,8 +69,6 @@ public class VisibleTouchMap extends TouchMap
|
|||
public VisibleTouchMap( Resources resources )
|
||||
{
|
||||
super( resources );
|
||||
mFpsDigits = new CopyOnWriteArrayList<Image>();
|
||||
mNumerals = new Image[10];
|
||||
autoHoldImages = new Image[NUM_N64_PSEUDOBUTTONS];
|
||||
autoHoldX = new int[NUM_N64_PSEUDOBUTTONS];
|
||||
autoHoldY = new int[NUM_N64_PSEUDOBUTTONS];
|
||||
|
@ -118,13 +83,6 @@ public class VisibleTouchMap extends TouchMap
|
|||
public void clear()
|
||||
{
|
||||
super.clear();
|
||||
mFpsFrame = null;
|
||||
mFpsFrameX = mFpsFrameY = 0;
|
||||
mFpsTextX = mFpsTextY = 50;
|
||||
mFpsValue = 0;
|
||||
mFpsDigits.clear();
|
||||
for( int i = 0; i < mNumerals.length; i++ )
|
||||
mNumerals[i] = null;
|
||||
for( int i = 0; i < autoHoldImages.length; i++ )
|
||||
autoHoldImages[i] = null;
|
||||
for( int i = 0; i < autoHoldX.length; i++ )
|
||||
|
@ -195,25 +153,6 @@ public class VisibleTouchMap extends TouchMap
|
|||
autoHoldImages[i].fitPercent( autoHoldX[i], autoHoldY[i], w, h );
|
||||
}
|
||||
}
|
||||
|
||||
// Compute FPS frame location
|
||||
float fpsScale = scale;
|
||||
if( mFpsMinScale > scale )
|
||||
fpsScale = mFpsMinScale;
|
||||
if( mFpsFrame != null )
|
||||
{
|
||||
mFpsFrame.setScale( fpsScale );
|
||||
mFpsFrame.fitPercent( mFpsFrameX, mFpsFrameY, w, h );
|
||||
}
|
||||
for( int i = 0; i < mNumerals.length; i++ )
|
||||
{
|
||||
if( mNumerals[i] != null )
|
||||
mNumerals[i].setScale( fpsScale );
|
||||
}
|
||||
|
||||
// Compute the FPS digit locations
|
||||
refreshFpsImages();
|
||||
refreshFpsPositions();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -267,25 +206,6 @@ public class VisibleTouchMap extends TouchMap
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the FPS indicator.
|
||||
*
|
||||
* @param canvas The canvas on which to draw.
|
||||
*/
|
||||
public void drawFps( Canvas canvas )
|
||||
{
|
||||
if( canvas == null )
|
||||
return;
|
||||
|
||||
// Redraw the FPS indicator
|
||||
if( mFpsFrame != null )
|
||||
mFpsFrame.draw( canvas );
|
||||
|
||||
// Draw each digit of the FPS number
|
||||
for( Image digit : mFpsDigits )
|
||||
digit.draw( canvas );
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the analog stick assets to reflect a new position.
|
||||
*
|
||||
|
@ -318,32 +238,6 @@ public class VisibleTouchMap extends TouchMap
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the FPS indicator assets to reflect a new value.
|
||||
*
|
||||
* @param fps The new FPS value.
|
||||
*
|
||||
* @return True if the FPS assets changed.
|
||||
*/
|
||||
public boolean updateFps( int fps )
|
||||
{
|
||||
// Clamp to positive, four digits max [0 - 9999]
|
||||
fps = Utility.clamp( fps, 0, 9999 );
|
||||
|
||||
// Quick return if user has disabled FPS or it hasn't changed
|
||||
if( !mFpsEnabled || mFpsValue == fps )
|
||||
return false;
|
||||
|
||||
// Store the new value
|
||||
mFpsValue = fps;
|
||||
|
||||
// Refresh the FPS digits
|
||||
refreshFpsImages();
|
||||
refreshFpsPositions();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the auto-hold assets to reflect a new value.
|
||||
*
|
||||
|
@ -365,72 +259,17 @@ public class VisibleTouchMap extends TouchMap
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the images used to draw the FPS string.
|
||||
*/
|
||||
private void refreshFpsImages()
|
||||
{
|
||||
// Refresh the list of FPS digits
|
||||
String fpsString = Integer.toString( mFpsValue );
|
||||
mFpsDigits.clear();
|
||||
for( int i = 0; i < 4; i++ )
|
||||
{
|
||||
// Create a new sequence of numeral images
|
||||
if( i < fpsString.length() )
|
||||
{
|
||||
int numeral = SafeMethods.toInt( fpsString.substring( i, i + 1 ), -1 );
|
||||
if( numeral > -1 && numeral < 10 )
|
||||
{
|
||||
// Clone the numeral from the font images and move to next digit
|
||||
mFpsDigits.add( new Image( mResources, mNumerals[numeral] ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the positions of the FPS images.
|
||||
*/
|
||||
private void refreshFpsPositions()
|
||||
{
|
||||
// Compute the centroid of the FPS text
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
if( mFpsFrame != null )
|
||||
{
|
||||
x = mFpsFrame.x + (int) ( ( mFpsFrame.width * mFpsFrame.scale ) * ( mFpsTextX / 100f ) );
|
||||
y = mFpsFrame.y + (int) ( ( mFpsFrame.height * mFpsFrame.scale ) * ( mFpsTextY / 100f ) );
|
||||
}
|
||||
|
||||
// Compute the width of the FPS text
|
||||
int totalWidth = 0;
|
||||
for( Image digit : mFpsDigits )
|
||||
totalWidth += (int) ( digit.width * digit.scale );
|
||||
|
||||
// Compute the starting position of the FPS text
|
||||
x -= (int) ( totalWidth / 2f );
|
||||
|
||||
// Compute the position of each digit
|
||||
for( Image digit : mFpsDigits )
|
||||
{
|
||||
digit.setPos( x, y - (int) ( digit.hHeight * digit.scale ) );
|
||||
x += (int) ( digit.width * digit.scale );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all touch map data from the filesystem.
|
||||
*
|
||||
* @param skinDir The directory containing the skin.ini and image files.
|
||||
* @param profile The name of the touchscreen profile.
|
||||
* @param animated True to load the analog assets in two parts for animation.
|
||||
* @param fpsEnabled True to display the FPS indicator.
|
||||
* @param scale The factor to scale images by.
|
||||
* @param alpha The opacity of the visible elements.
|
||||
*/
|
||||
public void load( String skinDir, Profile profile, boolean animated, boolean fpsEnabled, float scale, int alpha )
|
||||
public void load( String skinDir, Profile profile, boolean animated, float scale, int alpha )
|
||||
{
|
||||
mFpsEnabled = fpsEnabled;
|
||||
mScalingFactor = scale;
|
||||
mTouchscreenTransparency = alpha;
|
||||
|
||||
|
@ -438,9 +277,6 @@ public class VisibleTouchMap extends TouchMap
|
|||
ConfigFile skin_ini = new ConfigFile( skinFolder + "/skin.ini" );
|
||||
mReferenceWidth = SafeMethods.toInt( skin_ini.get( "INFO", "referenceScreenWidth" ), 0 );
|
||||
mReferenceHeight = SafeMethods.toInt( skin_ini.get( "INFO", "referenceScreenHeight" ), 0 );
|
||||
mFpsTextX = SafeMethods.toInt( skin_ini.get( "INFO", "fps-numx" ), 50 );
|
||||
mFpsTextY = SafeMethods.toInt( skin_ini.get( "INFO", "fps-numy" ), 50 );
|
||||
mFpsMinPixels = SafeMethods.toInt( skin_ini.get( "INFO", "fps-minPixels" ), 0 );
|
||||
|
||||
// Scale the assets to the last screensize used
|
||||
resize( cacheWidth, cacheHeight, cacheMetrics );
|
||||
|
@ -472,10 +308,9 @@ public class VisibleTouchMap extends TouchMap
|
|||
analogForeImage.setAlpha( mTouchscreenTransparency );
|
||||
}
|
||||
|
||||
// Load the FPS and autohold images
|
||||
// Load the autohold images
|
||||
if( profile != null )
|
||||
{
|
||||
loadFpsIndicator( profile );
|
||||
loadAutoHoldImages( profile, "groupAB-holdA" );
|
||||
loadAutoHoldImages( profile, "groupAB-holdB" );
|
||||
loadAutoHoldImages( profile, "groupC-holdCu" );
|
||||
|
@ -489,48 +324,6 @@ public class VisibleTouchMap extends TouchMap
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads FPS indicator assets and properties from the filesystem.
|
||||
*
|
||||
* @param profile The touchscreen profile containing the FPS properties.
|
||||
*/
|
||||
private void loadFpsIndicator( Profile profile )
|
||||
{
|
||||
int x = profile.getInt( "fps-x", -1 );
|
||||
int y = profile.getInt( "fps-y", -1 );
|
||||
|
||||
if( x >= 0 && y >= 0 )
|
||||
{
|
||||
// Position (percentages of the screen dimensions)
|
||||
mFpsFrameX = x;
|
||||
mFpsFrameY = y;
|
||||
|
||||
// Load frame image
|
||||
mFpsFrame = new Image( mResources, skinFolder + "/fps.png" );
|
||||
|
||||
// Minimum factor the FPS indicator can be scaled by
|
||||
mFpsMinScale = mFpsMinPixels / (float) mFpsFrame.width;
|
||||
|
||||
// Load numeral images
|
||||
String filename = "";
|
||||
try
|
||||
{
|
||||
// Make sure we can load them (they might not even exist)
|
||||
for( int i = 0; i < mNumerals.length; i++ )
|
||||
{
|
||||
filename = skinFolder + "/fps-" + i + ".png";
|
||||
mNumerals[i] = new Image( mResources, filename );
|
||||
}
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
// Problem, let the user know
|
||||
Log.e( "VisibleTouchMap", "Problem loading fps numeral '" + filename
|
||||
+ "', error message: " + e.getMessage() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads auto-hold assets and properties from the filesystem.
|
||||
*
|
||||
|
|
|
@ -0,0 +1,391 @@
|
|||
/****************************************************************************
|
||||
* *
|
||||
* Project64 - A Nintendo 64 emulator. *
|
||||
* http://www.pj64-emu.com/ *
|
||||
* Copyright (C) 2016 Project64. All rights reserved. *
|
||||
* Copyright (C) 2013 Paul Lamb
|
||||
* *
|
||||
* License: *
|
||||
* GNU/GPLv2 http://www.gnu.org/licenses/gpl-2.0.html *
|
||||
* *
|
||||
****************************************************************************/
|
||||
package emu.project64.input.provider;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import emu.project64.AndroidDevice;
|
||||
import tv.ouya.console.api.OuyaController;
|
||||
import android.annotation.TargetApi;
|
||||
import android.view.InputDevice;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
/**
|
||||
* The base class for transforming arbitrary input data into a common format.
|
||||
*
|
||||
* @see KeyProvider
|
||||
* @see AxisProvider
|
||||
* @see SensorProvider
|
||||
* @see InputMap
|
||||
*/
|
||||
public abstract class AbstractProvider
|
||||
{
|
||||
/** The offset used to construct the hardware ID for a MOGA controller. */
|
||||
private static final int HARDWARE_ID_MOGA_OFFSET = 1000;
|
||||
|
||||
/** The maximum possible hardware ID for a MOGA controller, inclusive. */
|
||||
private static final int HARDWARE_ID_MOGA_MAX = 1010;
|
||||
|
||||
/**
|
||||
* The interface for listening to a provider.
|
||||
*/
|
||||
public interface OnInputListener
|
||||
{
|
||||
/**
|
||||
* Called when a single input has been dispatched.
|
||||
*
|
||||
* @param inputCode The universal input code that was dispatched.
|
||||
* @param strength The input strength, between 0 and 1, inclusive.
|
||||
* @param hardwareId The identifier of the source device.
|
||||
*/
|
||||
public void onInput( int inputCode, float strength, int hardwareId );
|
||||
|
||||
/**
|
||||
* Called when multiple inputs have been dispatched simultaneously.
|
||||
*
|
||||
* @param inputCodes The universal input codes that were dispatched.
|
||||
* @param strengths The input strengths, between 0 and 1, inclusive.
|
||||
* @param hardwareId The identifier of the source device.
|
||||
*/
|
||||
public void onInput( int[] inputCodes, float[] strengths, int hardwareId );
|
||||
}
|
||||
|
||||
/** The strength threshold above which an input is said to be "on". */
|
||||
public static final float STRENGTH_THRESHOLD = 0.5f;
|
||||
|
||||
/** Listener management. */
|
||||
private final ArrayList<AbstractProvider.OnInputListener> mPublisher;
|
||||
|
||||
/**
|
||||
* Instantiates a new abstract provider.
|
||||
*/
|
||||
protected AbstractProvider()
|
||||
{
|
||||
mPublisher = new ArrayList<AbstractProvider.OnInputListener>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a listener to start receiving input notifications.
|
||||
*
|
||||
* @param listener The listener to register. Null values are safe.
|
||||
*/
|
||||
public void registerListener( AbstractProvider.OnInputListener listener )
|
||||
{
|
||||
if( ( listener != null ) && !mPublisher.contains( listener ) )
|
||||
{
|
||||
mPublisher.add( listener );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters a listener to stop receiving input notifications.
|
||||
*
|
||||
* @param listener The listener to unregister. Null values are safe.
|
||||
*/
|
||||
public void unregisterListener( AbstractProvider.OnInputListener listener )
|
||||
{
|
||||
if( listener != null )
|
||||
{
|
||||
mPublisher.remove( listener );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters all listeners.
|
||||
*/
|
||||
public void unregisterAllListeners()
|
||||
{
|
||||
mPublisher.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains unique hardware id from an input event.
|
||||
*
|
||||
* @param event The event generated by the hardware.
|
||||
*
|
||||
* @return The unique identifier of the hardware.
|
||||
*/
|
||||
public static int getHardwareId( KeyEvent event )
|
||||
{
|
||||
// This might be replaced by something else in the future... so we abstract it
|
||||
return event.getDeviceId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains unique hardware id from an input event.
|
||||
*
|
||||
* @param event The event generated by the hardware.
|
||||
*
|
||||
* @return The unique identifier of the hardware.
|
||||
*/
|
||||
public static int getHardwareId( MotionEvent event )
|
||||
{
|
||||
// This might be replaced by something else in the future... so we abstract it
|
||||
return event.getDeviceId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains unique hardware id from an input event.
|
||||
*
|
||||
* @param event The event generated by the hardware.
|
||||
*
|
||||
* @return The unique identifier of the hardware.
|
||||
*/
|
||||
public static int getHardwareId( com.bda.controller.KeyEvent event )
|
||||
{
|
||||
// This might be replaced by something else in the future... so we abstract it
|
||||
return event.getControllerId() + HARDWARE_ID_MOGA_OFFSET;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains unique hardware id from an input event.
|
||||
*
|
||||
* @param event The event generated by the hardware.
|
||||
*
|
||||
* @return The unique identifier of the hardware.
|
||||
*/
|
||||
public static int getHardwareId( com.bda.controller.MotionEvent event )
|
||||
{
|
||||
// This might be replaced by something else in the future... so we abstract it
|
||||
return event.getControllerId() + HARDWARE_ID_MOGA_OFFSET;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the hardware is available. This is a conservative test in that it will
|
||||
* return true if the availability cannot be conclusively determined.
|
||||
*
|
||||
* @param id The unique hardware identifier.
|
||||
*
|
||||
* @return True if the associated hardware is available or indeterminate.
|
||||
*/
|
||||
@TargetApi( 9 )
|
||||
public static boolean isHardwareAvailable( int id )
|
||||
{
|
||||
// This might be replaced by something else in the future... so we abstract it
|
||||
if( id > HARDWARE_ID_MOGA_OFFSET && id <= HARDWARE_ID_MOGA_MAX )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if( AndroidDevice.IS_GINGERBREAD )
|
||||
{
|
||||
InputDevice device = InputDevice.getDevice( id );
|
||||
return device != null;
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the human-readable name of the hardware.
|
||||
*
|
||||
* @param id The unique hardware identifier.
|
||||
*
|
||||
* @return The name of the hardware, or null if hardware not found.
|
||||
*/
|
||||
@TargetApi( 9 )
|
||||
public static String getHardwareName( int id )
|
||||
{
|
||||
if( id > HARDWARE_ID_MOGA_OFFSET && id <= HARDWARE_ID_MOGA_MAX )
|
||||
{
|
||||
return "moga-" + ( id - HARDWARE_ID_MOGA_OFFSET );
|
||||
}
|
||||
else if( AndroidDevice.IS_GINGERBREAD )
|
||||
{
|
||||
InputDevice device = InputDevice.getDevice( id );
|
||||
return device == null ? null : device.getName();
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the human-readable name of the input.
|
||||
*
|
||||
* @param inputCode The universal input code.
|
||||
*
|
||||
* @return The name of the input.
|
||||
*/
|
||||
@SuppressWarnings( "deprecation" )
|
||||
@TargetApi( 12 )
|
||||
public static String getInputName( int inputCode )
|
||||
{
|
||||
if( inputCode > 0 )
|
||||
{
|
||||
if( AndroidDevice.IS_HONEYCOMB_MR1 )
|
||||
{
|
||||
String name = null;
|
||||
if( inputCode != -1 && AndroidDevice.IS_OUYA_HARDWARE )
|
||||
{
|
||||
if( inputCode == OuyaController.BUTTON_A )
|
||||
name = "OUYA BUTTON_A";
|
||||
else if( inputCode == OuyaController.BUTTON_DPAD_DOWN )
|
||||
name = "OUYA BUTTON_DPAD_DOWN";
|
||||
else if( inputCode == OuyaController.BUTTON_DPAD_LEFT )
|
||||
name = "OUYA BUTTON_DPAD_LEFT";
|
||||
else if( inputCode == OuyaController.BUTTON_DPAD_RIGHT )
|
||||
name = "OUYA BUTTON_DPAD_RIGHT";
|
||||
else if( inputCode == OuyaController.BUTTON_DPAD_UP )
|
||||
name = "OUYA BUTTON_DPAD_UP";
|
||||
else if( inputCode == OuyaController.BUTTON_L1 )
|
||||
name = "OUYA BUTTON_L1";
|
||||
else if( inputCode == OuyaController.BUTTON_L2 )
|
||||
name = "OUYA BUTTON_L2";
|
||||
else if( inputCode == OuyaController.BUTTON_L3 )
|
||||
name = "OUYA BUTTON_L3";
|
||||
else if( inputCode == OuyaController.BUTTON_MENU )
|
||||
name = "OUYA BUTTON_MENU";
|
||||
else if( inputCode == OuyaController.BUTTON_O )
|
||||
name = "OUYA BUTTON_O";
|
||||
else if( inputCode == OuyaController.BUTTON_R1 )
|
||||
name = "OUYA BUTTON_R1";
|
||||
else if( inputCode == OuyaController.BUTTON_R2 )
|
||||
name = "OUYA BUTTON_R2";
|
||||
else if( inputCode == OuyaController.BUTTON_R3 )
|
||||
name = "OUYA BUTTON_R3";
|
||||
else if( inputCode == OuyaController.BUTTON_U )
|
||||
name = "OUYA BUTTON_U";
|
||||
else if( inputCode == OuyaController.BUTTON_Y )
|
||||
name = "OUYA BUTTON_Y";
|
||||
}
|
||||
if( name == null )
|
||||
return KeyEvent.keyCodeToString( inputCode );
|
||||
else
|
||||
return name + " (" + KeyEvent.keyCodeToString( inputCode ) + ")";
|
||||
}
|
||||
else
|
||||
return "KEYCODE_" + inputCode;
|
||||
}
|
||||
else if( inputCode < 0 )
|
||||
{
|
||||
int axis = inputToAxisCode( inputCode );
|
||||
String direction = inputToAxisDirection( inputCode ) ? " (+)" : " (-)";
|
||||
if( AndroidDevice.IS_HONEYCOMB_MR1 )
|
||||
{
|
||||
String name = null;
|
||||
if( axis != -1 && AndroidDevice.IS_OUYA_HARDWARE )
|
||||
{
|
||||
if( axis == OuyaController.AXIS_L2 )
|
||||
name = "OUYA AXIS_L2";
|
||||
else if( axis == OuyaController.AXIS_LS_X )
|
||||
name = "OUYA AXIS_LS_X";
|
||||
else if( axis == OuyaController.AXIS_LS_Y )
|
||||
name = "OUYA AXIS_LS_Y";
|
||||
else if( axis == OuyaController.AXIS_R2 )
|
||||
name = "OUYA AXIS_R2";
|
||||
else if( axis == OuyaController.AXIS_RS_X )
|
||||
name = "OUYA AXIS_RS_X";
|
||||
else if( axis == OuyaController.AXIS_RS_Y )
|
||||
name = "OUYA AXIS_RS_Y";
|
||||
}
|
||||
if( name == null )
|
||||
return MotionEvent.axisToString( axis ) + direction;
|
||||
else
|
||||
return name + " (" + MotionEvent.axisToString( axis ) + ")" + direction;
|
||||
}
|
||||
else
|
||||
return "AXIS_" + axis + direction;
|
||||
}
|
||||
else
|
||||
return "NULL";
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the human-readable name of the input, appended with strength information.
|
||||
*
|
||||
* @param inputCode The universal input code.
|
||||
* @param strength The input strength, between 0 and 1, inclusive.
|
||||
*
|
||||
* @return The name of the input.
|
||||
*/
|
||||
public static String getInputName( int inputCode, float strength )
|
||||
{
|
||||
return getInputName( inputCode ) + ( inputCode == 0
|
||||
? ""
|
||||
: String.format( " %4.2f", strength ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility for child classes. Converts an Android axis code to a universal input code.
|
||||
*
|
||||
* @param axisCode The Android axis code.
|
||||
* @param positiveDirection Set true for positive Android axis, false for negative Android axis.
|
||||
*
|
||||
* @return The corresponding universal input code.
|
||||
*/
|
||||
protected static int axisToInputCode( int axisCode, boolean positiveDirection )
|
||||
{
|
||||
// Axis codes are encoded to negative values (versus buttons which are positive). Axis codes
|
||||
// are bit shifted by one so that the lowest bit can encode axis direction.
|
||||
return -( ( axisCode ) * 2 + ( positiveDirection ? 1 : 2 ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility for child classes. Converts a universal input code to an Android axis code.
|
||||
*
|
||||
* @param inputCode The universal input code.
|
||||
*
|
||||
* @return The corresponding Android axis code.
|
||||
*/
|
||||
protected static int inputToAxisCode( int inputCode )
|
||||
{
|
||||
return ( -inputCode - 1 ) / 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility for child classes. Converts a universal input code to an Android axis direction.
|
||||
*
|
||||
* @param inputCode The universal input code.
|
||||
*
|
||||
* @return True if the input code represents positive Android axis direction, false otherwise.
|
||||
*/
|
||||
protected static boolean inputToAxisDirection( int inputCode )
|
||||
{
|
||||
return ( ( -inputCode ) % 2 ) == 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies listeners that a single input was dispatched. Subclasses should invoke this method
|
||||
* to publish their input data.
|
||||
*
|
||||
* @param inputCode The universal input code that was dispatched.
|
||||
* @param strength The input strength, between 0 and 1, inclusive.
|
||||
* @param hardwareId The identifier of the source device.
|
||||
*/
|
||||
protected void notifyListeners( int inputCode, float strength, int hardwareId )
|
||||
{
|
||||
for( OnInputListener listener : mPublisher )
|
||||
{
|
||||
listener.onInput( inputCode, strength, hardwareId );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies listeners that multiple inputs were dispatched simultaneously. Subclasses should
|
||||
* invoke this method to publish their input data.
|
||||
*
|
||||
* @param inputCodes The universal input codes that were dispatched.
|
||||
* @param strengths The input strengths, between 0 and 1, inclusive.
|
||||
* @param hardwareId The identifier of the source device.
|
||||
*/
|
||||
protected void notifyListeners( int[] inputCodes, float[] strengths, int hardwareId )
|
||||
{
|
||||
for( OnInputListener listener : mPublisher )
|
||||
{
|
||||
listener.onInput( inputCodes.clone(), strengths.clone(), hardwareId );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,193 @@
|
|||
/****************************************************************************
|
||||
* *
|
||||
* Project64 - A Nintendo 64 emulator. *
|
||||
* http://www.pj64-emu.com/ *
|
||||
* Copyright (C) 2016 Project64. All rights reserved. *
|
||||
* Copyright (C) 2013 Paul Lamb, littleguy77
|
||||
* *
|
||||
* License: *
|
||||
* GNU/GPLv2 http://www.gnu.org/licenses/gpl-2.0.html *
|
||||
* *
|
||||
****************************************************************************/
|
||||
package emu.project64.input.provider;
|
||||
|
||||
import emu.project64.AndroidDevice;
|
||||
import emu.project64.input.map.AxisMap;
|
||||
import android.annotation.TargetApi;
|
||||
import android.view.InputDevice;
|
||||
import android.view.InputDevice.MotionRange;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
/**
|
||||
* A class for transforming Android MotionEvent inputs into a common format.
|
||||
*/
|
||||
public class AxisProvider extends AbstractProvider
|
||||
{
|
||||
/** The input codes to listen for. */
|
||||
private int[] mInputCodes;
|
||||
|
||||
/** The default number of input codes to listen for. */
|
||||
private static final int DEFAULT_NUM_INPUTS = 128;
|
||||
|
||||
/**
|
||||
* Instantiates a new axis provider.
|
||||
*/
|
||||
@TargetApi( 12 )
|
||||
public AxisProvider()
|
||||
{
|
||||
// By default, provide data from all possible axes
|
||||
mInputCodes = new int[DEFAULT_NUM_INPUTS];
|
||||
for( int i = 0; i < mInputCodes.length; i++ )
|
||||
mInputCodes[i] = -( i + 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a new axis provider.
|
||||
*
|
||||
* @param view The view receiving MotionEvent data.
|
||||
*/
|
||||
@TargetApi( 12 )
|
||||
public AxisProvider( View view )
|
||||
{
|
||||
this();
|
||||
|
||||
// Connect the input source
|
||||
view.setOnGenericMotionListener( new GenericMotionListener() );
|
||||
|
||||
// Request focus for proper listening
|
||||
view.requestFocus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Restricts listening to a set of universal input codes.
|
||||
*
|
||||
* @param inputCodeFilter The new input codes to listen for.
|
||||
*/
|
||||
public void setInputCodeFilter( int[] inputCodeFilter )
|
||||
{
|
||||
mInputCodes = inputCodeFilter.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually dispatches a MotionEvent through the provider's listening chain.
|
||||
*
|
||||
* @param event The MotionEvent object containing full information about the event.
|
||||
*
|
||||
* @return True if the listener has consumed the event, false otherwise.
|
||||
*/
|
||||
public boolean onGenericMotion( MotionEvent event )
|
||||
{
|
||||
if( AndroidDevice.IS_HONEYCOMB_MR1 )
|
||||
return new GenericMotionListener().onGenericMotion( null, event );
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Just an indirection class that eliminates some logcat chatter about benign errors. If we make
|
||||
* the parent class implement View.OnGenericMotionListener, then we get logcat error messages
|
||||
* <i>even if</i> we conditionally exclude calls to the class based on API. These errors are not
|
||||
* actually harmful, so the logcat messages are simply a nuisance during debugging.
|
||||
* <p/>
|
||||
* For a detailed explanation, see <a href=http://stackoverflow.com/questions/13103902/
|
||||
* android-recommended-way-of-safely-supporting-newer-apis-has-error-if-the-class-i>here</a>.
|
||||
*/
|
||||
@TargetApi( 12 )
|
||||
public class GenericMotionListener implements View.OnGenericMotionListener
|
||||
{
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see android.view.View.OnGenericMotionListener#onGenericMotion(android.view.View,
|
||||
* android.view.MotionEvent)
|
||||
*/
|
||||
@Override
|
||||
public boolean onGenericMotion( View v, MotionEvent event )
|
||||
{
|
||||
// Ignore motion events from non-joysticks (mice are a problem)
|
||||
if( event.getSource() != InputDevice.SOURCE_JOYSTICK )
|
||||
return false;
|
||||
|
||||
InputDevice device = event.getDevice();
|
||||
AxisMap axisInfo = AxisMap.getMap( device );
|
||||
|
||||
// Read all the requested axes
|
||||
float[] strengths = new float[mInputCodes.length];
|
||||
for( int i = 0; i < mInputCodes.length; i++ )
|
||||
{
|
||||
int inputCode = mInputCodes[i];
|
||||
|
||||
// Compute the axis code from the input code
|
||||
int axisCode = inputToAxisCode( inputCode );
|
||||
|
||||
// Get the analog value using the Android API
|
||||
float strength = event.getAxisValue( axisCode );
|
||||
|
||||
// Modify strength if necessary
|
||||
strength = normalizeStrength( strength, axisInfo, device, axisCode );
|
||||
|
||||
// If the strength points in the correct direction, record it
|
||||
boolean direction1 = inputToAxisDirection( inputCode );
|
||||
boolean direction2 = strength > 0;
|
||||
if( direction1 == direction2 )
|
||||
strengths[i] = Math.abs( strength );
|
||||
else
|
||||
strengths[i] = 0;
|
||||
}
|
||||
|
||||
// Notify listeners about new input data
|
||||
notifyListeners( mInputCodes, strengths, getHardwareId( event ) );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private float normalizeStrength( float strength, AxisMap axisInfo, InputDevice device,
|
||||
int axisCode )
|
||||
{
|
||||
if( axisInfo != null )
|
||||
{
|
||||
int axisClass = axisInfo.getClass( axisCode );
|
||||
|
||||
if( axisClass == AxisMap.AXIS_CLASS_IGNORED )
|
||||
{
|
||||
// We should ignore this axis
|
||||
strength = 0;
|
||||
}
|
||||
else if( device != null )
|
||||
{
|
||||
// We should normalize this axis
|
||||
MotionRange motionRange = device.getMotionRange( axisCode, InputDevice.SOURCE_JOYSTICK );
|
||||
if( motionRange != null )
|
||||
{
|
||||
switch( axisClass )
|
||||
{
|
||||
case AxisMap.AXIS_CLASS_STICK:
|
||||
// Normalize to [-1,1]
|
||||
strength = ( strength - motionRange.getMin() ) / motionRange.getRange() * 2f - 1f;
|
||||
break;
|
||||
case AxisMap.AXIS_CLASS_TRIGGER:
|
||||
// Normalize to [0,1]
|
||||
strength = ( strength - motionRange.getMin() ) / motionRange.getRange();
|
||||
break;
|
||||
case AxisMap.AXIS_CLASS_N64_USB_STICK:
|
||||
// Normalize to [-1,1]
|
||||
// The Raphnet adapters through v2.x and some other USB adapters assume the N64
|
||||
// controller produces values in the range [-127,127]. However, the official N64 spec
|
||||
// says that raw values of +/- 80 indicate full strength. Therefore we rescale by
|
||||
// multiplying by 127/80 (dividing by 0.63).
|
||||
// http://naesten.dyndns.org:8080/psyq/man/os/osContGetReadData.html
|
||||
// http://raphnet-tech.com/products/gc_n64_usb_adapters/
|
||||
strength = strength / 0.63f;
|
||||
break;
|
||||
case AxisMap.AXIS_CLASS_UNKNOWN:
|
||||
default:
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return strength;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,172 @@
|
|||
/****************************************************************************
|
||||
* *
|
||||
* Project64 - A Nintendo 64 emulator. *
|
||||
* http://www.pj64-emu.com/ *
|
||||
* Copyright (C) 2016 Project64. All rights reserved. *
|
||||
* Copyright (C) 2013 Paul Lamb, littleguy77
|
||||
* *
|
||||
* License: *
|
||||
* GNU/GPLv2 http://www.gnu.org/licenses/gpl-2.0.html *
|
||||
* *
|
||||
****************************************************************************/
|
||||
package emu.project64.input.provider;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import android.app.AlertDialog.Builder;
|
||||
import android.content.DialogInterface;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
|
||||
/**
|
||||
* A class for transforming Android KeyEvent inputs into a common format.
|
||||
*/
|
||||
public class KeyProvider extends AbstractProvider implements View.OnKeyListener,
|
||||
DialogInterface.OnKeyListener
|
||||
{
|
||||
/**
|
||||
* The formula for decoding KeyEvent data for specific Android IMEs.
|
||||
*/
|
||||
public enum ImeFormula
|
||||
{
|
||||
/** The default decoding formula. */
|
||||
DEFAULT,
|
||||
/** The formula for <i>USB/BT Joystick Center</i>, by Poke64738. */
|
||||
USB_BT_JOYSTICK_CENTER,
|
||||
/** The formula for <i>BT Controller</i>, by droidbean. */
|
||||
BT_CONTROLLER,
|
||||
/** An example decoding formula. */
|
||||
EXAMPLE_IME
|
||||
}
|
||||
|
||||
/** The IME formula for decoding KeyEvent data. */
|
||||
private final ImeFormula mImeFormula;
|
||||
|
||||
/** The list of key codes that should be ignored. */
|
||||
private final List<Integer> mIgnoredCodes;
|
||||
|
||||
/**
|
||||
* Instantiates a new key provider.
|
||||
*
|
||||
* @param formula The decoding formula to be used.
|
||||
* @param ignoredCodes List of key codes that should be ignored.
|
||||
*/
|
||||
public KeyProvider( ImeFormula formula, List<Integer> ignoredCodes )
|
||||
{
|
||||
// Assign the fields
|
||||
mImeFormula = formula;
|
||||
mIgnoredCodes = ignoredCodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a new key provider.
|
||||
*
|
||||
* @param view The view receiving KeyEvent data.
|
||||
* @param formula The decoding formula to be used.
|
||||
* @param ignoredCodes List of key codes that should be ignored.
|
||||
*/
|
||||
public KeyProvider( View view, ImeFormula formula, List<Integer> ignoredCodes )
|
||||
{
|
||||
this( formula, ignoredCodes );
|
||||
|
||||
// Connect the input source
|
||||
view.setOnKeyListener( this );
|
||||
|
||||
// Request focus for proper listening
|
||||
view.requestFocus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a new key provider.
|
||||
*
|
||||
* @param builder The builder for the dialog receiving KeyEvent data.
|
||||
* @param formula The decoding formula to be used.
|
||||
* @param ignoredCodes List of key codes that should be ignored.
|
||||
*/
|
||||
public KeyProvider( Builder builder, ImeFormula formula, List<Integer> ignoredCodes )
|
||||
{
|
||||
this( formula, ignoredCodes );
|
||||
|
||||
// Connect the input source
|
||||
builder.setOnKeyListener( this );
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see android.view.View.OnKeyListener#onKey(android.view.View, int, android.view.KeyEvent)
|
||||
*/
|
||||
@Override
|
||||
public boolean onKey( View v, int keyCode, KeyEvent event )
|
||||
{
|
||||
return onKey( keyCode, event );
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see android.content.DialogInterface.OnKeyListener#onKey(android.content.DialogInterface,
|
||||
* int, android.view.KeyEvent)
|
||||
*/
|
||||
@Override
|
||||
public boolean onKey( DialogInterface dialog, int keyCode, KeyEvent event )
|
||||
{
|
||||
return onKey( keyCode, event );
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually dispatches a KeyEvent through the provider's listening chain.
|
||||
*
|
||||
* @param keyCode The code for the physical key that was pressed.
|
||||
* @param event The KeyEvent object containing full information about the event.
|
||||
*
|
||||
* @return True if the listener has consumed the event, false otherwise.
|
||||
*/
|
||||
public boolean onKey( int keyCode, KeyEvent event )
|
||||
{
|
||||
// Ignore specified key codes
|
||||
if( mIgnoredCodes != null && mIgnoredCodes.contains( keyCode ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Translate input code and analog strength (ranges between 0.0 and 1.0)
|
||||
int inputCode;
|
||||
float strength;
|
||||
if( keyCode <= 0xFF )
|
||||
{
|
||||
// Ordinary key/button changed state
|
||||
inputCode = keyCode;
|
||||
strength = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Analog axis changed state, decode using IME-specific formula
|
||||
switch( mImeFormula )
|
||||
{
|
||||
case DEFAULT:
|
||||
case USB_BT_JOYSTICK_CENTER:
|
||||
case BT_CONTROLLER:
|
||||
default:
|
||||
// Formula defined between paulscode and poke64738
|
||||
inputCode = keyCode / 100;
|
||||
strength = ( (float) keyCode % 100 ) / 64f;
|
||||
break;
|
||||
case EXAMPLE_IME:
|
||||
// Low byte stores input code, high byte stores strength
|
||||
inputCode = keyCode & 0xFF;
|
||||
strength = ( (float) ( keyCode >> 8 ) ) / 0xFF;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Strength is zero when the button/axis is released
|
||||
if( event.getAction() == KeyEvent.ACTION_UP )
|
||||
strength = 0;
|
||||
|
||||
// Notify listeners about new input data
|
||||
notifyListeners( inputCode, strength, getHardwareId( event ) );
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
/****************************************************************************
|
||||
* *
|
||||
* Project64 - A Nintendo 64 emulator. *
|
||||
* http://www.pj64-emu.com/ *
|
||||
* Copyright (C) 2016 Project64. All rights reserved. *
|
||||
* Copyright (C) 2013 Paul Lamb, littleguy77
|
||||
* *
|
||||
* License: *
|
||||
* GNU/GPLv2 http://www.gnu.org/licenses/gpl-2.0.html *
|
||||
* *
|
||||
****************************************************************************/
|
||||
package emu.project64.input.provider;
|
||||
|
||||
import android.os.Handler;
|
||||
|
||||
import com.bda.controller.Controller;
|
||||
import com.bda.controller.ControllerListener;
|
||||
import com.bda.controller.KeyEvent;
|
||||
import com.bda.controller.MotionEvent;
|
||||
import com.bda.controller.StateEvent;
|
||||
|
||||
/**
|
||||
* A class for transforming MOGA input events into a common format.
|
||||
*/
|
||||
public class MogaProvider extends AbstractProvider implements ControllerListener
|
||||
{
|
||||
private final Controller mController;
|
||||
private final int[] mInputCodes;
|
||||
|
||||
/**
|
||||
* Instantiates a new MOGA provider.
|
||||
*/
|
||||
public MogaProvider( Controller controller )
|
||||
{
|
||||
mController = controller;
|
||||
mController.setListener( this, new Handler() );
|
||||
|
||||
mInputCodes = new int[10];
|
||||
//@formatter:off
|
||||
mInputCodes[0] = axisToInputCode( MotionEvent.AXIS_X, true );
|
||||
mInputCodes[1] = axisToInputCode( MotionEvent.AXIS_X, false );
|
||||
mInputCodes[2] = axisToInputCode( MotionEvent.AXIS_Y, true );
|
||||
mInputCodes[3] = axisToInputCode( MotionEvent.AXIS_Y, false );
|
||||
mInputCodes[4] = axisToInputCode( MotionEvent.AXIS_Z, true );
|
||||
mInputCodes[5] = axisToInputCode( MotionEvent.AXIS_Z, false );
|
||||
mInputCodes[6] = axisToInputCode( MotionEvent.AXIS_RZ, true );
|
||||
mInputCodes[7] = axisToInputCode( MotionEvent.AXIS_RZ, false );
|
||||
mInputCodes[8] = axisToInputCode( MotionEvent.AXIS_LTRIGGER, true );
|
||||
mInputCodes[9] = axisToInputCode( MotionEvent.AXIS_RTRIGGER, true );
|
||||
//@formatter:on
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onKeyEvent( KeyEvent event )
|
||||
{
|
||||
int inputCode = event.getKeyCode();
|
||||
float strength = event.getAction() == KeyEvent.ACTION_DOWN ? 1 : 0;
|
||||
int hardwareId = getHardwareId( event );
|
||||
|
||||
// Notify listeners about new input data
|
||||
notifyListeners( inputCode, strength, hardwareId );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMotionEvent( MotionEvent event )
|
||||
{
|
||||
// Read all the requested axes
|
||||
float[] strengths = new float[mInputCodes.length];
|
||||
for( int i = 0; i < mInputCodes.length; i++ )
|
||||
{
|
||||
int inputCode = mInputCodes[i];
|
||||
|
||||
// Compute the axis code from the input code
|
||||
int axisCode = inputToAxisCode( inputCode );
|
||||
|
||||
// Get the analog value using the MOGA API
|
||||
float strength = event.getAxisValue( axisCode );
|
||||
|
||||
// If the strength points in the correct direction, record it
|
||||
boolean direction1 = inputToAxisDirection( inputCode );
|
||||
boolean direction2 = strength > 0;
|
||||
if( direction1 == direction2 )
|
||||
strengths[i] = Math.abs( strength );
|
||||
else
|
||||
strengths[i] = 0;
|
||||
}
|
||||
int hardwareId = getHardwareId( event );
|
||||
|
||||
// Notify listeners about new input data
|
||||
notifyListeners( mInputCodes, strengths, hardwareId );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStateEvent( StateEvent arg0 )
|
||||
{
|
||||
}
|
||||
}
|
|
@ -26,6 +26,8 @@ public enum UISettingID
|
|||
//Controller Config
|
||||
Controller_ConfigFile,
|
||||
Controller_CurrentProfile,
|
||||
Controller_Deadzone,
|
||||
Controller_Sensitivity,
|
||||
;
|
||||
|
||||
private int value;
|
||||
|
|
|
@ -0,0 +1,510 @@
|
|||
/****************************************************************************
|
||||
* *
|
||||
* Project64 - A Nintendo 64 emulator. *
|
||||
* http://www.pj64-emu.com/ *
|
||||
* Copyright (C) 2012 Project64. All rights reserved. *
|
||||
* *
|
||||
* License: *
|
||||
* GNU/GPLv2 http://www.gnu.org/licenses/gpl-2.0.html *
|
||||
* *
|
||||
****************************************************************************/
|
||||
package emu.project64.profile;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import emu.project64.AndroidDevice;
|
||||
import emu.project64.R;
|
||||
import emu.project64.hack.MogaHack;
|
||||
import emu.project64.input.AbstractController;
|
||||
import emu.project64.input.map.InputMap;
|
||||
import emu.project64.input.provider.AbstractProvider;
|
||||
import emu.project64.input.provider.AbstractProvider.OnInputListener;
|
||||
import emu.project64.input.provider.AxisProvider;
|
||||
import emu.project64.input.provider.KeyProvider;
|
||||
import emu.project64.input.provider.KeyProvider.ImeFormula;
|
||||
import emu.project64.input.provider.MogaProvider;
|
||||
import emu.project64.jni.NativeExports;
|
||||
import emu.project64.jni.UISettingID;
|
||||
import emu.project64.persistent.ConfigFile;
|
||||
import emu.project64.persistent.ConfigFile.ConfigSection;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.AlertDialog.Builder;
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.text.TextUtils;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MenuItem;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.bda.controller.Controller;
|
||||
|
||||
public class ControllerProfileActivity extends AppCompatActivity implements OnInputListener,
|
||||
OnClickListener
|
||||
{
|
||||
// Visual settings
|
||||
private static final float UNMAPPED_BUTTON_ALPHA = 0.2f;
|
||||
private static final int UNMAPPED_BUTTON_FILTER = 0x66FFFFFF;
|
||||
private static final int MIN_LAYOUT_WIDTH_DP = 480;
|
||||
|
||||
// Controller profile objects
|
||||
private ConfigFile mConfigFile;
|
||||
private Profile mProfile;
|
||||
|
||||
// Input listening
|
||||
private KeyProvider mKeyProvider;
|
||||
private MogaProvider mMogaProvider;
|
||||
private AxisProvider mAxisProvider;
|
||||
private Controller mMogaController = Controller.getInstance( this );
|
||||
|
||||
// Widgets
|
||||
private final Button[] mN64Buttons = new Button[InputMap.NUM_MAPPABLES];
|
||||
private TextView mFeedbackText;
|
||||
|
||||
@Override
|
||||
public void onCreate( Bundle savedInstanceState )
|
||||
{
|
||||
super.onCreate( savedInstanceState );
|
||||
|
||||
// Initialize MOGA controller API
|
||||
// TODO: Remove hack after MOGA SDK is fixed
|
||||
// mMogaController.init();
|
||||
MogaHack.init( mMogaController, this );
|
||||
|
||||
// Load the profile; fail fast if there are any programmer usage errors
|
||||
String name = NativeExports.UISettingsLoadString(UISettingID.Controller_CurrentProfile.getValue());
|
||||
if( TextUtils.isEmpty( name ) )
|
||||
throw new Error( "Invalid usage: profile name cannot be null or empty" );
|
||||
mConfigFile = new ConfigFile(NativeExports.UISettingsLoadString(UISettingID.Controller_ConfigFile.getValue()));
|
||||
ConfigSection section = mConfigFile.get( name );
|
||||
if( section == null )
|
||||
{
|
||||
//profile not found create it
|
||||
mConfigFile.put(name, "map", "-");
|
||||
section = mConfigFile.get( name );
|
||||
if( section == null )
|
||||
{
|
||||
throw new Error( "Invalid usage: profile name not found in config file" );
|
||||
}
|
||||
}
|
||||
mProfile = new Profile( false, section );
|
||||
|
||||
// Set up input listeners
|
||||
mKeyProvider = new KeyProvider( ImeFormula.DEFAULT, AndroidDevice.getUnmappableKeyCodes() );
|
||||
mKeyProvider.registerListener( this );
|
||||
mMogaProvider = new MogaProvider( mMogaController );
|
||||
mMogaProvider.registerListener( this );
|
||||
if( AndroidDevice.IS_HONEYCOMB_MR1 )
|
||||
{
|
||||
mAxisProvider = new AxisProvider();
|
||||
mAxisProvider.registerListener( this );
|
||||
}
|
||||
|
||||
// Initialize the layout
|
||||
initLayoutDefault();
|
||||
|
||||
// Refresh everything
|
||||
refreshAllButtons();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume()
|
||||
{
|
||||
super.onResume();
|
||||
mMogaController.onResume();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause()
|
||||
{
|
||||
super.onPause();
|
||||
mMogaController.onPause();
|
||||
|
||||
// Lazily persist the profile data; only need to do it on pause
|
||||
mProfile.writeTo( mConfigFile );
|
||||
mConfigFile.save();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy()
|
||||
{
|
||||
super.onDestroy();
|
||||
mMogaController.exit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item)
|
||||
{
|
||||
switch (item.getItemId())
|
||||
{
|
||||
case android.R.id.home:
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed()
|
||||
{
|
||||
finish();
|
||||
}
|
||||
|
||||
private void initLayoutDefault()
|
||||
{
|
||||
WindowManager manager = (WindowManager) getSystemService( Context.WINDOW_SERVICE );
|
||||
DisplayMetrics metrics = new DisplayMetrics();
|
||||
manager.getDefaultDisplay().getMetrics( metrics );
|
||||
float scalefactor = (float) DisplayMetrics.DENSITY_DEFAULT / (float) metrics.densityDpi;
|
||||
int widthDp = Math.round( metrics.widthPixels * scalefactor );
|
||||
|
||||
// For narrow screens, use an alternate layout
|
||||
if( widthDp < MIN_LAYOUT_WIDTH_DP )
|
||||
{
|
||||
setContentView( R.layout.controller_profile_activity_port );
|
||||
}
|
||||
else
|
||||
{
|
||||
setContentView( R.layout.controller_profile_activity );
|
||||
}
|
||||
|
||||
// Add the tool bar to the activity (which supports the fancy menu/arrow animation)
|
||||
Toolbar toolbar = (Toolbar) findViewById( R.id.toolbar );
|
||||
toolbar.setTitle( getString(R.string.gamepad_title) );
|
||||
setSupportActionBar( toolbar );
|
||||
ActionBar actionbar = getSupportActionBar();
|
||||
|
||||
if (AndroidDevice.IS_ICE_CREAM_SANDWICH)
|
||||
{
|
||||
actionbar.setHomeButtonEnabled(true);
|
||||
actionbar.setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
// Initialize and refresh the widgets
|
||||
initWidgets();
|
||||
}
|
||||
|
||||
private void initWidgets()
|
||||
{
|
||||
// Get the text view object
|
||||
mFeedbackText = (TextView) findViewById( R.id.textFeedback );
|
||||
mFeedbackText.setText( "" );
|
||||
|
||||
// Create a button list to simplify highlighting and mapping
|
||||
setupButton( R.id.buttonDR, AbstractController.DPD_R );
|
||||
setupButton( R.id.buttonDL, AbstractController.DPD_L );
|
||||
setupButton( R.id.buttonDD, AbstractController.DPD_D );
|
||||
setupButton( R.id.buttonDU, AbstractController.DPD_U );
|
||||
setupButton( R.id.buttonS, AbstractController.START );
|
||||
setupButton( R.id.buttonZ, AbstractController.BTN_Z );
|
||||
setupButton( R.id.buttonB, AbstractController.BTN_B );
|
||||
setupButton( R.id.buttonA, AbstractController.BTN_A );
|
||||
setupButton( R.id.buttonCR, AbstractController.CPD_R );
|
||||
setupButton( R.id.buttonCL, AbstractController.CPD_L );
|
||||
setupButton( R.id.buttonCD, AbstractController.CPD_D );
|
||||
setupButton( R.id.buttonCU, AbstractController.CPD_U );
|
||||
setupButton( R.id.buttonR, AbstractController.BTN_R );
|
||||
setupButton( R.id.buttonL, AbstractController.BTN_L );
|
||||
setupButton( R.id.buttonAR, InputMap.AXIS_R );
|
||||
setupButton( R.id.buttonAL, InputMap.AXIS_L );
|
||||
setupButton( R.id.buttonAD, InputMap.AXIS_D );
|
||||
setupButton( R.id.buttonAU, InputMap.AXIS_U );
|
||||
}
|
||||
|
||||
private void setupButton( int resId, int index )
|
||||
{
|
||||
mN64Buttons[index] = (Button) findViewById( resId );
|
||||
if( mN64Buttons[index] != null )
|
||||
{
|
||||
mN64Buttons[index].setOnClickListener( this );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view)
|
||||
{
|
||||
// Handle button clicks in the mapping screen
|
||||
for( int i = 0; i < mN64Buttons.length; i++ )
|
||||
{
|
||||
// Find the button that was pressed
|
||||
if( view.equals( mN64Buttons[i] ) )
|
||||
{
|
||||
// Popup a dialog to listen to input codes from user
|
||||
Button button = (Button) view;
|
||||
popupListener( button.getText(), i );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private interface PromptInputCodeListener
|
||||
{
|
||||
public void onDialogClosed( int inputCode, int hardwareId, int which );
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a dialog to prompt the user for an input code.
|
||||
*
|
||||
* @param context The activity context.
|
||||
* @param moga The MOGA controller interface.
|
||||
* @param title The title of the dialog.
|
||||
* @param message The message to be shown inside the dialog.
|
||||
* @param neutralButtonText The text to be shown on the neutral button, or null.
|
||||
* @param ignoredKeyCodes The key codes to ignore.
|
||||
* @param listener The listener to process the input code, when provided.
|
||||
*/
|
||||
public static void promptInputCode( Context context, Controller moga, CharSequence title, CharSequence message,
|
||||
CharSequence neutralButtonText, final PromptInputCodeListener listener )
|
||||
{
|
||||
final ArrayList<AbstractProvider> providers = new ArrayList<AbstractProvider>();
|
||||
|
||||
// Create a widget to dispatch key/motion event data
|
||||
FrameLayout view = new FrameLayout( context );
|
||||
EditText dummyImeListener = new EditText( context );
|
||||
dummyImeListener.setVisibility( View.INVISIBLE );
|
||||
dummyImeListener.setHeight( 0 );
|
||||
view.addView( dummyImeListener );
|
||||
|
||||
// Set the focus parameters of the view so that it will dispatch events
|
||||
view.setFocusable( true );
|
||||
view.setFocusableInTouchMode( true );
|
||||
view.requestFocus();
|
||||
|
||||
// Create the input event providers
|
||||
providers.add( new KeyProvider( view, ImeFormula.DEFAULT, AndroidDevice.getUnmappableKeyCodes() ) );
|
||||
providers.add( new MogaProvider( moga ) );
|
||||
if( AndroidDevice.IS_HONEYCOMB_MR1 )
|
||||
{
|
||||
providers.add( new AxisProvider( view ) );
|
||||
}
|
||||
|
||||
// Notify the client when the user clicks the dialog's positive button
|
||||
DialogInterface.OnClickListener clickListener = new DialogInterface.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick( DialogInterface dialog, int which )
|
||||
{
|
||||
for( AbstractProvider provider : providers )
|
||||
provider.unregisterAllListeners();
|
||||
listener.onDialogClosed( 0, 0, which );
|
||||
}
|
||||
};
|
||||
|
||||
final AlertDialog dialog = new Builder( context ).setTitle( title ).setMessage( message ).setCancelable( false )
|
||||
.setNegativeButton( context.getString( android.R.string.cancel ), clickListener )
|
||||
.setPositiveButton( context.getString( android.R.string.ok ), clickListener )
|
||||
.setNeutralButton( neutralButtonText, clickListener ).setPositiveButton( null, null )
|
||||
.setView( view ).create();
|
||||
|
||||
OnInputListener inputListener = new OnInputListener()
|
||||
{
|
||||
@Override
|
||||
public void onInput( int[] inputCodes, float[] strengths, int hardwareId )
|
||||
{
|
||||
if( inputCodes == null || strengths == null )
|
||||
return;
|
||||
|
||||
// Find the strongest input
|
||||
float maxStrength = 0;
|
||||
int strongestInputCode = 0;
|
||||
for( int i = 0; i < inputCodes.length; i++ )
|
||||
{
|
||||
// Identify the strongest input
|
||||
float strength = strengths[i];
|
||||
if( strength > maxStrength )
|
||||
{
|
||||
maxStrength = strength;
|
||||
strongestInputCode = inputCodes[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Call the overloaded method with the strongest found
|
||||
onInput( strongestInputCode, maxStrength, hardwareId );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInput( int inputCode, float strength, int hardwareId )
|
||||
{
|
||||
if( inputCode != 0 && strength > AbstractProvider.STRENGTH_THRESHOLD )
|
||||
{
|
||||
for( AbstractProvider provider : providers )
|
||||
provider.unregisterAllListeners();
|
||||
listener.onDialogClosed( inputCode, hardwareId, DialogInterface.BUTTON_POSITIVE );
|
||||
dialog.dismiss();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Connect the upstream event listeners
|
||||
for( AbstractProvider provider : providers )
|
||||
{
|
||||
provider.registerListener( inputListener );
|
||||
}
|
||||
|
||||
// Launch the dialog
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
private void popupListener( CharSequence title, final int index )
|
||||
{
|
||||
final InputMap map = new InputMap( mProfile.get( "map" ) );
|
||||
String message = getString( R.string.inputMapActivity_popupMessage,map.getMappedCodeInfo( index ) );
|
||||
String btnText = getString( R.string.inputMapActivity_popupUnmap );
|
||||
|
||||
PromptInputCodeListener listener = new PromptInputCodeListener()
|
||||
{
|
||||
@Override
|
||||
public void onDialogClosed( int inputCode, int hardwareId, int which )
|
||||
{
|
||||
if( which != DialogInterface.BUTTON_NEGATIVE )
|
||||
{
|
||||
if( which == DialogInterface.BUTTON_POSITIVE )
|
||||
{
|
||||
map.map( inputCode, index );
|
||||
}
|
||||
else
|
||||
{
|
||||
map.unmapCommand( index );
|
||||
}
|
||||
mProfile.put( "map", map.serialize() );
|
||||
refreshAllButtons();
|
||||
}
|
||||
|
||||
// Refresh our MOGA provider since the prompt disconnected it
|
||||
mMogaProvider = new MogaProvider( mMogaController );
|
||||
mMogaProvider.registerListener( ControllerProfileActivity.this );
|
||||
}
|
||||
};
|
||||
promptInputCode( this, mMogaController, title, message, btnText, listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyDown( int keyCode, KeyEvent event )
|
||||
{
|
||||
return mKeyProvider.onKey( keyCode, event ) || super.onKeyDown( keyCode, event );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyUp( int keyCode, KeyEvent event )
|
||||
{
|
||||
return mKeyProvider.onKey( keyCode, event ) || super.onKeyUp( keyCode, event );
|
||||
}
|
||||
|
||||
@TargetApi( 12 )
|
||||
@Override
|
||||
public boolean onGenericMotionEvent( MotionEvent event )
|
||||
{
|
||||
if( !AndroidDevice.IS_HONEYCOMB_MR1 )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return mAxisProvider.onGenericMotion( event ) || super.onGenericMotionEvent( event );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInput(int inputCode, float strength, int hardwareId)
|
||||
{
|
||||
refreshButton( inputCode, strength );
|
||||
refreshFeedbackText( inputCode, strength );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInput(int[] inputCodes, float[] strengths, int hardwareId)
|
||||
{
|
||||
float maxStrength = AbstractProvider.STRENGTH_THRESHOLD;
|
||||
int strongestInputCode = 0;
|
||||
for( int i = 0; i < inputCodes.length; i++ )
|
||||
{
|
||||
int inputCode = inputCodes[i];
|
||||
float strength = strengths[i];
|
||||
|
||||
// Cache the strongest input
|
||||
if( strength > maxStrength )
|
||||
{
|
||||
maxStrength = strength;
|
||||
strongestInputCode = inputCode;
|
||||
}
|
||||
|
||||
refreshButton( inputCode, strength );
|
||||
}
|
||||
refreshFeedbackText( strongestInputCode, maxStrength );
|
||||
}
|
||||
|
||||
private void refreshFeedbackText( int inputCode, float strength )
|
||||
{
|
||||
// Update the feedback text (not all layouts include this, so check null)
|
||||
if( mFeedbackText != null )
|
||||
{
|
||||
mFeedbackText.setText( strength > AbstractProvider.STRENGTH_THRESHOLD ? AbstractProvider.getInputName( inputCode ) : "" );
|
||||
}
|
||||
}
|
||||
|
||||
private void refreshButton( int inputCode, float strength )
|
||||
{
|
||||
InputMap map = new InputMap( mProfile.get( "map" ) );
|
||||
int command = map.get( inputCode );
|
||||
if( command != InputMap.UNMAPPED )
|
||||
{
|
||||
Button button = mN64Buttons[command];
|
||||
refreshButton( button, strength, true );
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi( 11 )
|
||||
private void refreshButton( Button button, float strength, boolean isMapped )
|
||||
{
|
||||
if( button != null )
|
||||
{
|
||||
button.setPressed( strength > AbstractProvider.STRENGTH_THRESHOLD );
|
||||
|
||||
// Fade any buttons that aren't mapped
|
||||
if( AndroidDevice.IS_HONEYCOMB )
|
||||
{
|
||||
if( isMapped )
|
||||
{
|
||||
button.setAlpha( 1 );
|
||||
}
|
||||
else
|
||||
{
|
||||
button.setAlpha( UNMAPPED_BUTTON_ALPHA );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// For older APIs try something similar (not quite the same)
|
||||
if( isMapped )
|
||||
{
|
||||
button.getBackground().clearColorFilter();
|
||||
}
|
||||
else
|
||||
{
|
||||
button.getBackground().setColorFilter( UNMAPPED_BUTTON_FILTER, PorterDuff.Mode.MULTIPLY );
|
||||
}
|
||||
button.invalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void refreshAllButtons()
|
||||
{
|
||||
final InputMap map = new InputMap( mProfile.get( "map" ) );
|
||||
for( int i = 0; i < mN64Buttons.length; i++ )
|
||||
{
|
||||
refreshButton( mN64Buttons[i], 0, map.isMapped( i ) );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -26,6 +26,7 @@ package emu.project64.settings;
|
|||
import emu.project64.R;
|
||||
import emu.project64.SplashActivity;
|
||||
import emu.project64.jni.NativeExports;
|
||||
import emu.project64.profile.ControllerProfileActivity;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
@ -38,6 +39,8 @@ import android.support.v7.preference.PreferenceFragmentCompat;
|
|||
|
||||
public abstract class BaseSettingsFragment extends PreferenceFragmentCompat
|
||||
{
|
||||
private static final String DIALOG_FRAGMENT_TAG = "android.support.v7.preference.PreferenceFragment.DIALOG";
|
||||
|
||||
protected abstract int getXml();
|
||||
protected abstract int getTitleId();
|
||||
|
||||
|
@ -68,19 +71,45 @@ public abstract class BaseSettingsFragment extends PreferenceFragmentCompat
|
|||
@Override
|
||||
public void onDisplayPreferenceDialog(Preference preference)
|
||||
{
|
||||
/*if (AndroidUtil.isHoneycombOrLater() && preference instanceof MultiSelectListPreference) {
|
||||
DialogFragment dialogFragment = MultiSelectListPreferenceDialogFragmentCompat.newInstance(preference.getKey());
|
||||
DialogFragment dialogFragment = null;
|
||||
if (preference instanceof SeekBarPreference)
|
||||
{
|
||||
dialogFragment = SeekBarPreferencePreferenceDialogFragmentCompat.newInstance(preference.getKey());
|
||||
}
|
||||
else if (preference instanceof TwoLinesListPreference)
|
||||
{
|
||||
dialogFragment = TwoLinesListPreferenceDialogFragmentCompat.newInstance(preference.getKey());
|
||||
}
|
||||
else
|
||||
{
|
||||
super.onDisplayPreferenceDialog(preference);
|
||||
}
|
||||
|
||||
if (dialogFragment != null)
|
||||
{
|
||||
dialogFragment.setTargetFragment(this, 0);
|
||||
dialogFragment.show(getFragmentManager(), DIALOG_FRAGMENT_TAG);
|
||||
return;
|
||||
}*/
|
||||
super.onDisplayPreferenceDialog(preference);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceTreeClick(Preference preference)
|
||||
{
|
||||
if (preference.getKey().equals("settings_video"))
|
||||
if (preference.getKey().equals("settings_input"))
|
||||
{
|
||||
loadFragment(new InputFragment());
|
||||
}
|
||||
else if (preference.getKey().equals("settings_touch_screen"))
|
||||
{
|
||||
loadFragment(new TouchScreenFragment());
|
||||
}
|
||||
else if (preference.getKey().equals("settings_gamepad_screen"))
|
||||
{
|
||||
final AppCompatActivity activity = (AppCompatActivity)getActivity();
|
||||
Intent intent = new Intent( activity, ControllerProfileActivity.class );
|
||||
activity.startActivity( intent );
|
||||
}
|
||||
else if (preference.getKey().equals("settings_video"))
|
||||
{
|
||||
loadFragment(new VideoFragment());
|
||||
}
|
||||
|
|
|
@ -11,14 +11,6 @@
|
|||
package emu.project64.settings;
|
||||
|
||||
import emu.project64.R;
|
||||
import emu.project64.jni.NativeExports;
|
||||
import emu.project64.jni.SettingsID;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.preference.ListPreference;
|
||||
import android.support.v7.preference.Preference;
|
||||
import android.support.v7.preference.PreferenceManager;
|
||||
|
||||
public class GameSettingsFragment extends BaseSettingsFragment
|
||||
{
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
/****************************************************************************
|
||||
* *
|
||||
* Project64 - A Nintendo 64 emulator. *
|
||||
* http://www.pj64-emu.com/ *
|
||||
* Copyright (C) 2012 Project64. All rights reserved. *
|
||||
* *
|
||||
* License: *
|
||||
* GNU/GPLv2 http://www.gnu.org/licenses/gpl-2.0.html *
|
||||
* *
|
||||
****************************************************************************/
|
||||
package emu.project64.settings;
|
||||
|
||||
import android.os.Bundle;
|
||||
import emu.project64.R;
|
||||
|
||||
public class GamepadScreenFragment extends BaseSettingsFragment
|
||||
{
|
||||
@Override
|
||||
protected int getXml()
|
||||
{
|
||||
return R.xml.setting_gamepad;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getTitleId()
|
||||
{
|
||||
return R.string.gamepad_title;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(Bundle bundle, String s)
|
||||
{
|
||||
super.onCreatePreferences(bundle, s);
|
||||
|
||||
/*String profilesDir = AndroidDevice.PACKAGE_DIRECTORY + "/profiles";
|
||||
String touchscreenProfiles_cfg = profilesDir + "/touchscreen.cfg";
|
||||
ConfigFile touchscreenProfiles = new ConfigFile( touchscreenProfiles_cfg );
|
||||
Set<String> layoutsKeySet = touchscreenProfiles.keySet();
|
||||
String[] layouts = layoutsKeySet.toArray(new String[layoutsKeySet.size()]);
|
||||
|
||||
CharSequence[] entries = new CharSequence[layouts.length];
|
||||
String[] entryValues = new String[layouts.length];
|
||||
String[] entrySubtitles = new String[layouts.length];
|
||||
|
||||
for( int i = 0; i < layouts.length; i++ )
|
||||
{
|
||||
entries[i] = layouts[i];
|
||||
entryValues[i] = layouts[i];
|
||||
entrySubtitles[i] = touchscreenProfiles.get(layouts[i]).get("comment");
|
||||
}
|
||||
|
||||
final TwoLinesListPreference listPreference = (TwoLinesListPreference) findPreference("touchscreenLayout");
|
||||
listPreference.setEntries(entries);
|
||||
listPreference.setEntryValues(entryValues);
|
||||
listPreference.setEntriesSubtitles(entrySubtitles);*/
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
/****************************************************************************
|
||||
* *
|
||||
* Project64 - A Nintendo 64 emulator. *
|
||||
* http://www.pj64-emu.com/ *
|
||||
* Copyright (C) 2012 Project64. All rights reserved. *
|
||||
* *
|
||||
* License: *
|
||||
* GNU/GPLv2 http://www.gnu.org/licenses/gpl-2.0.html *
|
||||
* *
|
||||
****************************************************************************/
|
||||
package emu.project64.settings;
|
||||
|
||||
import emu.project64.R;
|
||||
|
||||
public class InputFragment extends SettingsFragment
|
||||
{
|
||||
@Override
|
||||
protected int getXml()
|
||||
{
|
||||
return R.xml.setting_input;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getTitleId()
|
||||
{
|
||||
return R.string.input_screen_title;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,222 @@
|
|||
/****************************************************************************
|
||||
* *
|
||||
* Project64 - A Nintendo 64 emulator. *
|
||||
* http://www.pj64-emu.com/ *
|
||||
* Copyright (C) 2012 Project64. All rights reserved. *
|
||||
* *
|
||||
* License: *
|
||||
* GNU/GPLv2 http://www.gnu.org/licenses/gpl-2.0.html *
|
||||
* *
|
||||
****************************************************************************/
|
||||
package emu.project64.settings;
|
||||
|
||||
import emu.project64.R;
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.support.v7.preference.DialogPreference;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.SeekBar;
|
||||
|
||||
/**
|
||||
* A type of {@link DialogPreference} that uses a {@link SeekBar} as a means of selecting a desired option.
|
||||
*/
|
||||
public class SeekBarPreference extends DialogPreference
|
||||
{
|
||||
private static final int DEFAULT_VALUE = 50;
|
||||
private static final int DEFAULT_MIN = 0;
|
||||
private static final int DEFAULT_MAX = 100;
|
||||
private static final int DEFAULT_STEP = 10;
|
||||
private static final String DEFAULT_UNITS = "%";
|
||||
|
||||
private int mValue = DEFAULT_VALUE;
|
||||
private int mMinValue = DEFAULT_MIN;
|
||||
private int mMaxValue = DEFAULT_MAX;
|
||||
private int mStepSize = DEFAULT_STEP;
|
||||
private String mUnits = DEFAULT_UNITS;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param context The {@link Context} this SeekBarPreference is being used in.
|
||||
* @param attrs A collection of attributes, as found associated with a tag in an XML document.
|
||||
*/
|
||||
public SeekBarPreference( Context context, AttributeSet attrs )
|
||||
{
|
||||
super( context, attrs );
|
||||
|
||||
// Get the attributes from the XML file, if provided
|
||||
TypedArray a = context.obtainStyledAttributes( attrs, R.styleable.SeekBarPreference );
|
||||
setMinValue( a.getInteger( R.styleable.SeekBarPreference_minimumValue, DEFAULT_MIN ) );
|
||||
setMaxValue( a.getInteger( R.styleable.SeekBarPreference_maximumValue, DEFAULT_MAX ) );
|
||||
setStepSize( a.getInteger( R.styleable.SeekBarPreference_stepSize, DEFAULT_STEP ) );
|
||||
setUnits( a.getString( R.styleable.SeekBarPreference_units ) );
|
||||
if (getUnits() == null)
|
||||
{
|
||||
setUnits(DEFAULT_UNITS);
|
||||
}
|
||||
a.recycle();
|
||||
|
||||
// Setup the layout
|
||||
setDialogLayoutResource( R.layout.seek_bar_preference );
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param context The {@link Context} this SeekBarPreference will be used in.
|
||||
*/
|
||||
public SeekBarPreference( Context context )
|
||||
{
|
||||
this( context, null );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets this SeekBarPreference to a specified value.
|
||||
*
|
||||
* @param value The value to set the SeekBarPreference to.
|
||||
*/
|
||||
public void setValue( int value )
|
||||
{
|
||||
mValue = validate( value );
|
||||
if( shouldPersist() )
|
||||
persistInt( mValue );
|
||||
setSummary( getValueString( mValue ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the minimum value this SeekBarPreference may have.
|
||||
*
|
||||
* @param minValue The minimum value for this SeekBarPreference.
|
||||
*/
|
||||
public void setMinValue( int minValue )
|
||||
{
|
||||
mMinValue = minValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum value this SeekBarPreference may have.
|
||||
*
|
||||
* @param maxValue The maximum value for this SeekBarPreference.
|
||||
*/
|
||||
public void setMaxValue( int maxValue )
|
||||
{
|
||||
mMaxValue = maxValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the size of each increment in this SeekBarPreference.
|
||||
*
|
||||
* @param stepSize The size of each increment.
|
||||
*/
|
||||
public void setStepSize( int stepSize )
|
||||
{
|
||||
mStepSize = stepSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the type of units this SeekBarPreference uses (e.g. "%").
|
||||
*
|
||||
* @param units The unit type for this SeekBarPreference to use.
|
||||
*/
|
||||
public void setUnits( String units )
|
||||
{
|
||||
mUnits = units;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the currently set value.
|
||||
*
|
||||
* @return The currently set value in this SeekBarPreference.
|
||||
*/
|
||||
public int getValue()
|
||||
{
|
||||
return mValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the currently set minimum value.
|
||||
*
|
||||
* @return The currently set minimum value for this SeekBarPreference.
|
||||
*/
|
||||
public int getMinValue()
|
||||
{
|
||||
return mMinValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the currently set maximum value.
|
||||
*
|
||||
* @return The currently set maximum value for this SeekBarPreference.
|
||||
*/
|
||||
public int getMaxValue()
|
||||
{
|
||||
return mMaxValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the currently set increment step size.
|
||||
*
|
||||
* @return The currently set increment step size for this SeekBarPreference.
|
||||
*/
|
||||
public int getStepSize()
|
||||
{
|
||||
return mStepSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the currently set units.
|
||||
*
|
||||
* @return The currently set unit type this SeekBarPreference uses.
|
||||
*/
|
||||
public String getUnits()
|
||||
{
|
||||
return mUnits;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value as a string with units appended.
|
||||
*
|
||||
* @param value The value to use in the string.
|
||||
*
|
||||
* @return The value as a String.
|
||||
*/
|
||||
public String getValueString( int value )
|
||||
{
|
||||
return getContext().getString( R.string.seekBarPreference_summary, value, mUnits );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object onGetDefaultValue( TypedArray a, int index )
|
||||
{
|
||||
return a.getInteger( index, DEFAULT_VALUE );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSetInitialValue( boolean restorePersistedValue, Object defaultValue )
|
||||
{
|
||||
setValue( restorePersistedValue ? getPersistedInt( mValue ) : (Integer) defaultValue );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttached()
|
||||
{
|
||||
setSummary( getValueString( mValue ) );
|
||||
super.onAttached();
|
||||
}
|
||||
|
||||
public int validate( int value )
|
||||
{
|
||||
// Round to nearest integer multiple of mStepSize
|
||||
int newValue = Math.round( value / (float)getStepSize() ) * getStepSize();
|
||||
|
||||
// Address issues when mStepSize is not an integral factor of mMaxValue
|
||||
// e.g. mMaxValue = 100, mMinValue = 0, mStepSize = 9, progress = 100 --> newValue = 99 (should be 100)
|
||||
// e.g. mMaxValue = 100, mMinValue = 0, mStepSize = 6, progress = 99 --> newValue = 102 (should be 100)
|
||||
if( value == getMinValue() || newValue < getMinValue() )
|
||||
newValue = getMinValue();
|
||||
if( value == getMaxValue() || newValue > getMaxValue() )
|
||||
newValue = getMaxValue();
|
||||
|
||||
return newValue;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
/****************************************************************************
|
||||
* *
|
||||
* Project64 - A Nintendo 64 emulator. *
|
||||
* http://www.pj64-emu.com/ *
|
||||
* Copyright (C) 2012 Project64. All rights reserved. *
|
||||
* *
|
||||
* License: *
|
||||
* GNU/GPLv2 http://www.gnu.org/licenses/gpl-2.0.html *
|
||||
* *
|
||||
****************************************************************************/
|
||||
package emu.project64.settings;
|
||||
|
||||
import emu.project64.R;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.preference.DialogPreference;
|
||||
import android.support.v7.preference.Preference;
|
||||
import android.support.v7.preference.PreferenceDialogFragmentCompat;
|
||||
import android.view.View;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.SeekBar.OnSeekBarChangeListener;
|
||||
|
||||
public class SeekBarPreferencePreferenceDialogFragmentCompat extends PreferenceDialogFragmentCompat
|
||||
implements DialogPreference.TargetFragment, OnSeekBarChangeListener
|
||||
{
|
||||
private TextView mTextView;
|
||||
private SeekBar mSeekBar;
|
||||
|
||||
public void onDialogClosed(boolean positiveResult)
|
||||
{
|
||||
if( positiveResult )
|
||||
{
|
||||
final SeekBarPreference preference = getSeekBarPreference();
|
||||
int value = mSeekBar.getProgress() + preference.getMinValue();
|
||||
preference.setValue( value );
|
||||
}
|
||||
}
|
||||
|
||||
public static SeekBarPreferencePreferenceDialogFragmentCompat newInstance(String key)
|
||||
{
|
||||
SeekBarPreferencePreferenceDialogFragmentCompat fragment = new SeekBarPreferencePreferenceDialogFragmentCompat();
|
||||
Bundle b = new Bundle(1);
|
||||
b.putString("key", key);
|
||||
fragment.setArguments(b);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Preference findPreference(CharSequence charSequence)
|
||||
{
|
||||
return getPreference();
|
||||
}
|
||||
|
||||
private SeekBarPreference getSeekBarPreference()
|
||||
{
|
||||
return (SeekBarPreference)this.getPreference();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBindDialogView(View view)
|
||||
{
|
||||
super.onBindDialogView(view);
|
||||
|
||||
// Grab the widget references
|
||||
mTextView = (TextView) view.findViewById( R.id.textFeedback );
|
||||
mSeekBar = (SeekBar) view.findViewById( R.id.seekbar );
|
||||
}
|
||||
|
||||
protected void onPrepareDialogBuilder(AlertDialog.Builder builder)
|
||||
{
|
||||
super.onPrepareDialogBuilder(builder);
|
||||
final SeekBarPreference preference = getSeekBarPreference();
|
||||
|
||||
// Initialize and refresh the widgets
|
||||
mSeekBar.setMax( preference.getMaxValue() - preference.getMinValue());
|
||||
mSeekBar.setOnSeekBarChangeListener( this );
|
||||
mSeekBar.setProgress( preference.getValue() - preference.getMinValue() );
|
||||
mTextView.setText( preference.getValueString( preference.getValue() ) );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch( SeekBar seekBar )
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch( SeekBar seekBar )
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProgressChanged( SeekBar seekBar, int progress, boolean fromUser )
|
||||
{
|
||||
final SeekBarPreference preference = getSeekBarPreference();
|
||||
|
||||
int value = preference.validate( progress + preference.getMinValue() );
|
||||
if( value != ( progress + preference.getMinValue() ) )
|
||||
seekBar.setProgress( value - preference.getMinValue() );
|
||||
mTextView.setText( preference.getValueString( value ) );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
/****************************************************************************
|
||||
* *
|
||||
* Project64 - A Nintendo 64 emulator. *
|
||||
* http://www.pj64-emu.com/ *
|
||||
* Copyright (C) 2012 Project64. All rights reserved. *
|
||||
* *
|
||||
* License: *
|
||||
* GNU/GPLv2 http://www.gnu.org/licenses/gpl-2.0.html *
|
||||
* *
|
||||
****************************************************************************/
|
||||
package emu.project64.settings;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import android.os.Bundle;
|
||||
import emu.project64.AndroidDevice;
|
||||
import emu.project64.R;
|
||||
import emu.project64.persistent.ConfigFile;
|
||||
|
||||
public class TouchScreenFragment extends BaseSettingsFragment
|
||||
{
|
||||
@Override
|
||||
protected int getXml()
|
||||
{
|
||||
return R.xml.setting_touch_screen;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getTitleId()
|
||||
{
|
||||
return R.string.touch_screen_title;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(Bundle bundle, String s)
|
||||
{
|
||||
super.onCreatePreferences(bundle, s);
|
||||
|
||||
String profilesDir = AndroidDevice.PACKAGE_DIRECTORY + "/profiles";
|
||||
String touchscreenProfiles_cfg = profilesDir + "/touchscreen.cfg";
|
||||
ConfigFile touchscreenProfiles = new ConfigFile( touchscreenProfiles_cfg );
|
||||
Set<String> layoutsKeySet = touchscreenProfiles.keySet();
|
||||
String[] layouts = layoutsKeySet.toArray(new String[layoutsKeySet.size()]);
|
||||
|
||||
CharSequence[] entries = new CharSequence[layouts.length];
|
||||
String[] entryValues = new String[layouts.length];
|
||||
String[] entrySubtitles = new String[layouts.length];
|
||||
|
||||
for( int i = 0; i < layouts.length; i++ )
|
||||
{
|
||||
entries[i] = layouts[i];
|
||||
entryValues[i] = layouts[i];
|
||||
entrySubtitles[i] = touchscreenProfiles.get(layouts[i]).get("comment");
|
||||
}
|
||||
|
||||
final TwoLinesListPreference listPreference = (TwoLinesListPreference) findPreference("touchscreenLayout");
|
||||
listPreference.setEntries(entries);
|
||||
listPreference.setEntryValues(entryValues);
|
||||
listPreference.setEntriesSubtitles(entrySubtitles);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
/****************************************************************************
|
||||
* *
|
||||
* Project64 - A Nintendo 64 emulator. *
|
||||
* http://www.pj64-emu.com/ *
|
||||
* Copyright (C) 2012 Project64. All rights reserved. *
|
||||
* *
|
||||
* License: *
|
||||
* GNU/GPLv2 http://www.gnu.org/licenses/gpl-2.0.html *
|
||||
* *
|
||||
****************************************************************************/
|
||||
package emu.project64.settings;
|
||||
|
||||
import emu.project64.R;
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.support.v7.preference.ListPreference;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
public class TwoLinesListPreference extends ListPreference
|
||||
{
|
||||
private CharSequence[] mEntriesSubtitles;
|
||||
private int mValueIndex;
|
||||
|
||||
public TwoLinesListPreference(Context context, AttributeSet attrs)
|
||||
{
|
||||
super(context, attrs);
|
||||
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TwoLinesListPreference);
|
||||
mEntriesSubtitles = a.getTextArray(R.styleable.TwoLinesListPreference_entriesSubtitles);
|
||||
a.recycle();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSetInitialValue(boolean restoreValue, Object defaultValue)
|
||||
{
|
||||
super.onSetInitialValue(restoreValue, defaultValue);
|
||||
|
||||
mEntriesSubtitles = getEntriesSubtitles();
|
||||
mValueIndex = getValueIndex();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(String value)
|
||||
{
|
||||
super.setValue(value);
|
||||
mValueIndex = getValueIndex();
|
||||
updateSummary();
|
||||
}
|
||||
/**
|
||||
* Returns the index of the given value (in the entry values array).
|
||||
*
|
||||
* @param value The value whose index should be returned.
|
||||
* @return The index of the value, or -1 if not found.
|
||||
*/
|
||||
public int findIndexOfValue(String value)
|
||||
{
|
||||
CharSequence[] EntryValues = getEntryValues();
|
||||
if (value != null && EntryValues != null)
|
||||
{
|
||||
for (int i = EntryValues.length - 1; i >= 0; i--)
|
||||
{
|
||||
if (EntryValues[i].equals(value))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public int getValueIndex()
|
||||
{
|
||||
return findIndexOfValue(getValue());
|
||||
}
|
||||
|
||||
public CharSequence[] getEntriesSubtitles()
|
||||
{
|
||||
return mEntriesSubtitles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEntries(CharSequence[] Entries)
|
||||
{
|
||||
super.setEntries(Entries);
|
||||
updateSummary();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEntryValues(CharSequence[] EntryValues)
|
||||
{
|
||||
super.setEntryValues(EntryValues);
|
||||
mValueIndex = getValueIndex();
|
||||
updateSummary();
|
||||
}
|
||||
|
||||
public void setEntriesSubtitles(CharSequence[] mEntriesSubtitles)
|
||||
{
|
||||
this.mEntriesSubtitles = mEntriesSubtitles;
|
||||
updateSummary();
|
||||
}
|
||||
|
||||
private void updateSummary()
|
||||
{
|
||||
if (mValueIndex < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
CharSequence[] Entries = getEntries();
|
||||
String summary = Entries[mValueIndex].toString();
|
||||
if (mEntriesSubtitles != null && mEntriesSubtitles.length > mValueIndex)
|
||||
{
|
||||
String subtitle = mEntriesSubtitles[mValueIndex].toString();
|
||||
if (summary.length() > 0 && subtitle.length() > 0)
|
||||
{
|
||||
summary += " - " + subtitle;
|
||||
}
|
||||
}
|
||||
setSummary( summary );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
/****************************************************************************
|
||||
* *
|
||||
* Project64 - A Nintendo 64 emulator. *
|
||||
* http://www.pj64-emu.com/ *
|
||||
* Copyright (C) 2012 Project64. All rights reserved. *
|
||||
* *
|
||||
* License: *
|
||||
* GNU/GPLv2 http://www.gnu.org/licenses/gpl-2.0.html *
|
||||
* *
|
||||
****************************************************************************/
|
||||
package emu.project64.settings;
|
||||
|
||||
import emu.project64.R;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.preference.PreferenceDialogFragmentCompat;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ListAdapter;
|
||||
import android.widget.RadioButton;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class TwoLinesListPreferenceDialogFragmentCompat extends PreferenceDialogFragmentCompat
|
||||
{
|
||||
private TwoLinesListPreference getTwoLinesListPreference()
|
||||
{
|
||||
return (TwoLinesListPreference)this.getPreference();
|
||||
}
|
||||
|
||||
class YourAdapter extends ArrayAdapter<String>
|
||||
{
|
||||
public YourAdapter(Context context, String[] values)
|
||||
{
|
||||
super(context, R.layout.two_lines_list_preference_row, values);
|
||||
}
|
||||
|
||||
class ViewHolder
|
||||
{
|
||||
TextView title;
|
||||
TextView subTitle;
|
||||
RadioButton radioBtn;
|
||||
}
|
||||
|
||||
ViewHolder holder;
|
||||
ViewHolderClickListener listener = new ViewHolderClickListener();
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent)
|
||||
{
|
||||
final LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
if (convertView == null)
|
||||
{
|
||||
convertView = inflater.inflate(R.layout.two_lines_list_preference_row, null);
|
||||
holder = new ViewHolder();
|
||||
holder.title = (TextView) convertView.findViewById(R.id.two_lines_list_view_row_text);
|
||||
holder.subTitle = (TextView) convertView.findViewById(R.id.two_lines_list_view_row_subtext);
|
||||
holder.radioBtn = (RadioButton) convertView.findViewById(R.id.two_lines_list_view_row_radiobtn);
|
||||
convertView.setTag(holder);
|
||||
|
||||
holder.title.setOnClickListener(listener);
|
||||
holder.subTitle.setOnClickListener(listener);
|
||||
holder.radioBtn.setOnClickListener(listener);
|
||||
}
|
||||
else
|
||||
{
|
||||
holder = (ViewHolder) convertView.getTag();
|
||||
}
|
||||
final TwoLinesListPreference preference = getTwoLinesListPreference();
|
||||
|
||||
holder.title.setText(preference.getEntries()[position]);
|
||||
holder.title.setTag(position);
|
||||
holder.subTitle.setText(preference.getEntriesSubtitles()[position]);
|
||||
holder.subTitle.setTag(position);
|
||||
|
||||
holder.radioBtn.setChecked(preference.getValueIndex() == position);
|
||||
holder.radioBtn.setTag(position);
|
||||
|
||||
return convertView;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPrepareDialogBuilder(AlertDialog.Builder builder)
|
||||
{
|
||||
super.onPrepareDialogBuilder(builder);
|
||||
final TwoLinesListPreference preference = getTwoLinesListPreference();
|
||||
CharSequence[] entries = preference.getEntries();
|
||||
String[] values = new String[ entries.length ];
|
||||
for (int i = 0; i < entries.length; i ++)
|
||||
{
|
||||
values[i] = entries[i].toString();
|
||||
}
|
||||
|
||||
ListAdapter adapter = new YourAdapter(builder.getContext(), values);
|
||||
builder.setAdapter(adapter, new DialogInterface.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which)
|
||||
{
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static TwoLinesListPreferenceDialogFragmentCompat newInstance(String key)
|
||||
{
|
||||
TwoLinesListPreferenceDialogFragmentCompat fragment = new TwoLinesListPreferenceDialogFragmentCompat();
|
||||
Bundle b = new Bundle(1);
|
||||
b.putString("key", key);
|
||||
fragment.setArguments(b);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
public void onDialogClosed(boolean positiveResult)
|
||||
{
|
||||
}
|
||||
|
||||
private class ViewHolderClickListener implements OnClickListener
|
||||
{
|
||||
@Override
|
||||
public void onClick(View v)
|
||||
{
|
||||
final TwoLinesListPreference preference = getTwoLinesListPreference();
|
||||
int EntryIndex = (Integer) v.getTag();
|
||||
preference.setValue(preference.getEntries()[EntryIndex].toString());
|
||||
TwoLinesListPreferenceDialogFragmentCompat.this.getDialog().dismiss();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -24,6 +24,8 @@ void RegisterUISettings(void)
|
|||
g_Settings->AddHandler((SettingID)(FirstUISettings + TouchScreen_Layout), new CSettingTypeApplication("Touch Screen", "Layout", "Analog"));
|
||||
g_Settings->AddHandler((SettingID)(FirstUISettings + Controller_ConfigFile), new CSettingTypeRelativePath("Config", "Controller.cfg"));
|
||||
g_Settings->AddHandler((SettingID)(FirstUISettings + Controller_CurrentProfile), new CSettingTypeApplication("Controller", "Profile", "User"));
|
||||
g_Settings->AddHandler((SettingID)(FirstUISettings + Controller_Deadzone), new CSettingTypeApplication("Controller", "Deadzone", (uint32_t)0));
|
||||
g_Settings->AddHandler((SettingID)(FirstUISettings + Controller_Sensitivity), new CSettingTypeApplication("Controller", "Sensitivity", (uint32_t)100));
|
||||
}
|
||||
|
||||
void UISettingsSaveBool(UISettingID Type, bool Value)
|
||||
|
|
|
@ -28,6 +28,8 @@ enum UISettingID
|
|||
//Controller Config
|
||||
Controller_ConfigFile,
|
||||
Controller_CurrentProfile,
|
||||
Controller_Deadzone,
|
||||
Controller_Sensitivity,
|
||||
};
|
||||
|
||||
void RegisterUISettings(void);
|
||||
|
|