[Android] Add Controller support

This commit is contained in:
zilmar 2016-10-02 23:21:25 +11:00
parent 71f07251ca
commit 967ed96a8b
67 changed files with 3731 additions and 338 deletions

View File

@ -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

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 877 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 617 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>
@ -190,7 +222,7 @@
<string name="GetSaveSupport_message">Save support is a one time purchase and the only purchase in Project64.\n\nProject64 may not be perfect and making this as an upgrade it allows you to play and test the emulator before you have to spend any money.\n\nThis is also a good way for you to support and give back to the creation of the emulator..</string>
<string name="GetSaveSupport_OkButton">Purchase</string>
<!-- Asset Extractor -->
<!-- Asset Extractor -->
<string name="assetExtractor_startingUp">Starting Up …</string>
<string name="assetExtractor_version">version</string>
<string name="assetExtractor_progress">%1$.0f%% done: extracting - %2$s</string>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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;

View File

@ -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
@ -22,16 +23,16 @@ public class GameActivity extends Activity
private GameLifecycleHandler mLifecycleHandler;
@SuppressWarnings("unused")
private GameMenuHandler mMenuHandler;
@Override
public void onWindowFocusChanged( boolean hasFocus )
{
super.onWindowFocusChanged( hasFocus );
mLifecycleHandler.onWindowFocusChanged( hasFocus );
}
@Override
public void onBackPressed()
public void onBackPressed()
{
NativeExports.ExternalEvent( SystemEvent.SysEvent_PauseCPU_AppLostActive.getValue());
AlertDialog.Builder builder = new AlertDialog.Builder(this);
@ -49,50 +50,59 @@ public class GameActivity extends Activity
})
.show();
}
@Override
protected void onCreate( Bundle savedInstanceState )
{
{
mLifecycleHandler = new GameLifecycleHandler( this );
mLifecycleHandler.onCreateBegin( savedInstanceState );
super.onCreate( savedInstanceState );
mLifecycleHandler.onCreateEnd( savedInstanceState );
mMenuHandler = new GameMenuHandler( this, mLifecycleHandler );
}
@Override
protected void onStart()
{
super.onStart();
mLifecycleHandler.onStart();
}
@Override
protected void onResume()
{
super.onResume();
mLifecycleHandler.onResume();
}
@Override
protected void onPause()
{
super.onPause();
mLifecycleHandler.onPause();
}
@Override
protected void onStop()
{
super.onStop();
mLifecycleHandler.onStop();
}
@Override
protected void onDestroy()
{
super.onDestroy();
mLifecycleHandler.onDestroy();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
if (requestCode == GameLifecycleHandler.RC_SETTINGS)
{
mLifecycleHandler.onSettingDone();
}
}
}

View File

@ -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;
@ -64,22 +83,31 @@ public class GameLifecycleHandler implements SurfaceHolder.Callback, GameSurface
private boolean mIsFocused = false; // true if the window is focused
private boolean mIsResumed = false; // true if the activity is resumed
private boolean mIsSurface = false; // true if the surface is available
public GameLifecycleHandler(Activity activity)
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)
public void onCreateBegin(Bundle savedInstanceState)
public void onCreateBegin(Bundle savedInstanceState)
{
if (LOG_GAMELIFECYCLEHANDLER)
if (LOG_GAMELIFECYCLEHANDLER)
{
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)
// For earlier APIs, remove the title bar to yield more space
@ -97,9 +125,9 @@ public class GameLifecycleHandler implements SurfaceHolder.Callback, GameSurface
}
@TargetApi(11)
public void onCreateEnd(Bundle savedInstanceState)
public void onCreateEnd(Bundle savedInstanceState)
{
if (LOG_GAMELIFECYCLEHANDLER)
if (LOG_GAMELIFECYCLEHANDLER)
{
Log.i("GameLifecycleHandler", "onCreateEnd");
}
@ -114,13 +142,13 @@ public class GameLifecycleHandler implements SurfaceHolder.Callback, GameSurface
mActivity.setContentView(R.layout.game_activity);
mSurface = (GameSurface) mActivity.findViewById( R.id.gameSurface );
mOverlay = (GameOverlay) mActivity.findViewById(R.id.gameOverlay);
// Listen to game surface events (created, changed, destroyed)
mSurface.getHolder().addCallback( this );
mSurface.createGLContext((ActivityManager)mActivity.getSystemService(Context.ACTIVITY_SERVICE));
// Configure the action bar introduced in higher Android versions
if (AndroidDevice.IS_ACTION_BAR_AVAILABLE)
if (AndroidDevice.IS_ACTION_BAR_AVAILABLE)
{
mActivity.getActionBar().hide();
ColorDrawable color = new ColorDrawable(Color.parseColor("#303030"));
@ -128,62 +156,49 @@ 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()
public void onStart()
{
if (LOG_GAMELIFECYCLEHANDLER)
if (LOG_GAMELIFECYCLEHANDLER)
{
Log.i("GameLifecycleHandler", "onStart");
}
}
public void onResume()
public void onResume()
{
if (LOG_GAMELIFECYCLEHANDLER)
if (LOG_GAMELIFECYCLEHANDLER)
{
Log.i("GameLifecycleHandler", "onResume");
}
mIsResumed = true;
tryRunning();
mMogaController.onResume();
}
@Override
public void surfaceCreated(SurfaceHolder holder)
public void surfaceCreated(SurfaceHolder holder)
{
if (LOG_GAMELIFECYCLEHANDLER)
if (LOG_GAMELIFECYCLEHANDLER)
{
Log.i("GameLifecycleHandler", "surfaceCreated");
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
{
if (LOG_GAMELIFECYCLEHANDLER)
if (LOG_GAMELIFECYCLEHANDLER)
{
Log.i("GameLifecycleHandler", "surfaceChanged");
}
@ -191,24 +206,24 @@ public class GameLifecycleHandler implements SurfaceHolder.Callback, GameSurface
tryRunning();
}
public void onWindowFocusChanged(boolean hasFocus)
public void onWindowFocusChanged(boolean hasFocus)
{
if (LOG_GAMELIFECYCLEHANDLER)
if (LOG_GAMELIFECYCLEHANDLER)
{
Log.i("GameLifecycleHandler", "onWindowFocusChanged: " + hasFocus);
}
// Only try to run; don't try to pause. User may just be touching the
// in-game menu.
mIsFocused = hasFocus;
if (hasFocus)
if (hasFocus)
{
tryRunning();
tryRunning();
}
}
public void AutoSave()
{
if (LOG_GAMELIFECYCLEHANDLER)
if (LOG_GAMELIFECYCLEHANDLER)
{
Log.i("GameLifecycleHandler", "OnAutoSave");
}
@ -229,7 +244,7 @@ public class GameLifecycleHandler implements SurfaceHolder.Callback, GameSurface
for (int i = 0; i < 100; i++)
{
int LastSaveTime = NativeExports.SettingsLoadDword(SettingsID.Game_LastSaveTime.getValue());
if (LOG_GAMELIFECYCLEHANDLER)
if (LOG_GAMELIFECYCLEHANDLER)
{
Log.i("GameLifecycleHandler", "LastSaveTime = " + LastSaveTime + " OriginalSaveTime = " + OriginalSaveTime);
}
@ -237,9 +252,9 @@ public class GameLifecycleHandler implements SurfaceHolder.Callback, GameSurface
{
break;
}
try
try
{
Thread.sleep(100);
Thread.sleep(100);
}
catch(InterruptedException ex)
{
@ -255,18 +270,18 @@ public class GameLifecycleHandler implements SurfaceHolder.Callback, GameSurface
}
else if (LOG_GAMELIFECYCLEHANDLER)
{
Log.i("GameLifecycleHandler", "CPU not running, not doing anything");
Log.i("GameLifecycleHandler", "CPU not running, not doing anything");
}
if (LOG_GAMELIFECYCLEHANDLER)
if (LOG_GAMELIFECYCLEHANDLER)
{
Log.i("GameLifecycleHandler", "OnAutoSave Done");
}
}
public void onPause()
{
if (LOG_GAMELIFECYCLEHANDLER)
if (LOG_GAMELIFECYCLEHANDLER)
{
Log.i("GameLifecycleHandler", "onPause");
}
@ -275,7 +290,8 @@ public class GameLifecycleHandler implements SurfaceHolder.Callback, GameSurface
{
AutoSave();
}
if (LOG_GAMELIFECYCLEHANDLER)
mMogaController.onPause();
if (LOG_GAMELIFECYCLEHANDLER)
{
Log.i("GameLifecycleHandler", "onPause - done");
}
@ -284,7 +300,7 @@ public class GameLifecycleHandler implements SurfaceHolder.Callback, GameSurface
@Override
public void surfaceDestroyed(SurfaceHolder holder)
{
if (LOG_GAMELIFECYCLEHANDLER)
if (LOG_GAMELIFECYCLEHANDLER)
{
Log.i("GameLifecycleHandler", "surfaceDestroyed");
}
@ -293,7 +309,7 @@ public class GameLifecycleHandler implements SurfaceHolder.Callback, GameSurface
public void onStop()
{
if (LOG_GAMELIFECYCLEHANDLER)
if (LOG_GAMELIFECYCLEHANDLER)
{
Log.i("GameLifecycleHandler", "onStop");
}
@ -301,10 +317,71 @@ public class GameLifecycleHandler implements SurfaceHolder.Callback, GameSurface
public void onDestroy()
{
if (LOG_GAMELIFECYCLEHANDLER)
if (LOG_GAMELIFECYCLEHANDLER)
{
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")
@ -315,15 +392,32 @@ public class GameLifecycleHandler implements SurfaceHolder.Callback, GameSurface
int touchscreenAutoHold = 0;
boolean isTouchscreenFeedbackEnabled = false;
Set<Integer> autoHoldableButtons = null;
// Create the touchscreen controller
TouchController touchscreenController = new TouchController( mTouchscreenMap,
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()
private void tryRunning()
{
if (mIsFocused && mIsResumed && mIsSurface && mStopped)
{
@ -338,21 +432,21 @@ public class GameLifecycleHandler implements SurfaceHolder.Callback, GameSurface
NativeExports.StartGame(mActivity, new GameSurface.GLThread(new WeakReference<GameSurface>(mSurface), handler));
}
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config)
{
if (LOG_GAMELIFECYCLEHANDLER)
if (LOG_GAMELIFECYCLEHANDLER)
{
Log.i("GameLifecycleHandler", "onSurfaceCreated");
}
NativeExports.onSurfaceCreated();
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height)
{
if (LOG_GAMELIFECYCLEHANDLER)
if (LOG_GAMELIFECYCLEHANDLER)
{
Log.i("GameLifecycleHandler", "onSurfaceChanged");
}

View File

@ -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;

View File

@ -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;

View File

@ -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();
}
}
}

View File

@ -12,6 +12,7 @@ package emu.project64.input;
import java.util.ArrayList;
import android.util.Log;
import emu.project64.jni.NativeInput;
/**
@ -32,19 +33,21 @@ import emu.project64.jni.NativeInput;
* from the subclass. For best performance, subclasses should only call notifyChanged() when the
* input state has actually changed, and should bundle the protected field modifications before
* calling notifyChanged(). For example,
*
*
* <pre>
* {@code
* buttons[0] = true; notifyChanged(); buttons[1] = false; notifyChanged(); // Inefficient
* buttons[0] = true; buttons[1] = false; notifyChanged(); // Better
* }
* </pre>
*
*
* @see PeripheralController
* @see TouchController
*/
public abstract class AbstractController
{
protected final static boolean LOG_CONTROLLER = false;
/**
* A small class that encapsulates controller state.
*/
@ -52,58 +55,58 @@ public abstract class AbstractController
{
/** The pressed state of each controller button. */
public boolean[] buttons = new boolean[NUM_N64_BUTTONS];
/** The fractional value of the analog-x axis, between -1 and 1, inclusive. */
public float axisFractionX = 0;
/** The fractional value of the analog-y axis, between -1 and 1, inclusive. */
public float axisFractionY = 0;
}
// Constants must match EButton listing in plugin.h! (input-sdl plug-in)
/** N64 button: dpad-right. */
public static final int DPD_R = 0;
/** N64 button: dpad-left. */
public static final int DPD_L = 1;
/** N64 button: dpad-down. */
public static final int DPD_D = 2;
/** N64 button: dpad-up. */
public static final int DPD_U = 3;
/** N64 button: start. */
public static final int START = 4;
/** N64 button: trigger-z. */
public static final int BTN_Z = 5;
/** N64 button: b. */
public static final int BTN_B = 6;
/** N64 button: a. */
public static final int BTN_A = 7;
/** N64 button: cpad-right. */
public static final int CPD_R = 8;
/** N64 button: cpad-left. */
public static final int CPD_L = 9;
/** N64 button: cpad-down. */
public static final int CPD_D = 10;
/** N64 button: cpad-up. */
public static final int CPD_U = 11;
/** N64 button: shoulder-r. */
public static final int BTN_R = 12;
/** N64 button: shoulder-l. */
public static final int BTN_L = 13;
/** N64 button: reserved-1. */
public static final int BTN_RESERVED1 = 14;
@ -112,19 +115,19 @@ public abstract class AbstractController
/** Total number of N64 buttons. */
public static final int NUM_N64_BUTTONS = 16;
/** The state of all four player controllers. */
private static final ArrayList<State> sStates = new ArrayList<State>();
/** The state of this controller. */
protected State mState;
/** The player number, between 1 and 4, inclusive. */
protected int mPlayerNumber = 1;
/** The factor by which the axis fractions are scaled before going to the core. */
private static final float AXIS_SCALE = 80;
static
{
sStates.add( new State() );
@ -132,7 +135,7 @@ public abstract class AbstractController
sStates.add( new State() );
sStates.add( new State() );
}
/**
* Instantiates a new abstract controller.
*/
@ -140,30 +143,35 @@ public abstract class AbstractController
{
mState = sStates.get( 0 );
}
/**
* Notifies the core that the N64 controller state has changed.
*/
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 );
}
/**
* Gets the player number.
*
*
* @return The player number, between 1 and 4, inclusive.
*/
public int getPlayerNumber()
{
return mPlayerNumber;
}
/**
* Sets the player number.
*
*
* @param player The new player number, between 1 and 4, inclusive.
*/
public void setPlayerNumber( int player )

View File

@ -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;
}
}

View File

@ -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

View File

@ -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();
}
}

View File

@ -33,7 +33,6 @@ import android.util.SparseArray;
* @see TouchController
*/
@SuppressLint("FloatMath")
@SuppressWarnings("deprecation")
public class TouchMap
{
/** Map flag: Touch location is not mapped. */

View File

@ -30,34 +30,7 @@ import android.util.Log;
* @see GameOverlay
*/
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.
*
@ -364,73 +258,18 @@ 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.
*

View File

@ -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 );
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}

View File

@ -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 )
{
}
}

View File

@ -26,6 +26,8 @@ public enum UISettingID
//Controller Config
Controller_ConfigFile,
Controller_CurrentProfile,
Controller_Deadzone,
Controller_Sensitivity,
;
private int value;

View File

@ -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 ) );
}
}
}

View File

@ -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());
}

View File

@ -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
{

View File

@ -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);*/
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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 ) );
}
}

View File

@ -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);
}
}

View File

@ -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 );
}
}

View File

@ -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();
}
}
}

View File

@ -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)

View File

@ -28,6 +28,8 @@ enum UISettingID
//Controller Config
Controller_ConfigFile,
Controller_CurrentProfile,
Controller_Deadzone,
Controller_Sensitivity,
};
void RegisterUISettings(void);