mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-03-12 06:39:14 +01:00
Android: Fix CheatsActivity d-pad navigation
Special shoutout to Android for not having RTL compatible variants of nextFocusRight and nextFocusLeft. Ideally we would have some way to block the user from using the d-pad to switch between the two panes when in portrait mode, or make the list pane act as if it's to the left of the details pane rather than the right when the details pane is open, but I don't know of a good way to do this. SlidingPaneLayout doesn't really seem to have been implemented with d-pad navigation in mind. Thankfully, landscape is the most important use case for gamepads.
This commit is contained in:
parent
215492152c
commit
47efd3317d
@ -73,13 +73,12 @@ public class CheatDetailsFragment extends Fragment
|
||||
mViewModel.getIsEditing().observe(getViewLifecycleOwner(), this::onIsEditingUpdated);
|
||||
|
||||
mButtonDelete.setOnClickListener(this::onDeleteClicked);
|
||||
mButtonEdit.setOnClickListener((v) -> mViewModel.setIsEditing(true));
|
||||
mButtonCancel.setOnClickListener((v) ->
|
||||
{
|
||||
mViewModel.setIsEditing(false);
|
||||
onSelectedCheatUpdated(mCheat);
|
||||
});
|
||||
mButtonEdit.setOnClickListener(this::onEditClicked);
|
||||
mButtonCancel.setOnClickListener(this::onCancelClicked);
|
||||
mButtonOk.setOnClickListener(this::onOkClicked);
|
||||
|
||||
CheatsActivity.setOnFocusChangeListenerRecursively(view,
|
||||
(v, hasFocus) -> activity.onDetailsViewFocusChange(hasFocus));
|
||||
}
|
||||
|
||||
private void clearEditErrors()
|
||||
@ -98,6 +97,19 @@ public class CheatDetailsFragment extends Fragment
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private void onEditClicked(View view)
|
||||
{
|
||||
mViewModel.setIsEditing(true);
|
||||
mButtonOk.requestFocus();
|
||||
}
|
||||
|
||||
private void onCancelClicked(View view)
|
||||
{
|
||||
mViewModel.setIsEditing(false);
|
||||
onSelectedCheatUpdated(mCheat);
|
||||
mButtonDelete.requestFocus();
|
||||
}
|
||||
|
||||
private void onOkClicked(View view)
|
||||
{
|
||||
clearEditErrors();
|
||||
@ -118,6 +130,7 @@ public class CheatDetailsFragment extends Fragment
|
||||
mViewModel.notifySelectedCheatChanged();
|
||||
mViewModel.setIsEditing(false);
|
||||
}
|
||||
mButtonEdit.requestFocus();
|
||||
break;
|
||||
case Cheat.TRY_SET_FAIL_NO_NAME:
|
||||
mEditName.setError(getString(R.string.cheats_error_no_name));
|
||||
|
@ -36,7 +36,7 @@ public class CheatListFragment extends Fragment
|
||||
CheatsActivity activity = (CheatsActivity) requireActivity();
|
||||
CheatsViewModel viewModel = new ViewModelProvider(activity).get(CheatsViewModel.class);
|
||||
|
||||
recyclerView.setAdapter(new CheatsAdapter(getViewLifecycleOwner(), viewModel));
|
||||
recyclerView.setAdapter(new CheatsAdapter(activity, viewModel));
|
||||
recyclerView.setLayoutManager(new LinearLayoutManager(activity));
|
||||
recyclerView.addItemDecoration(new DividerItemDecoration(activity, null));
|
||||
}
|
||||
|
@ -37,6 +37,10 @@ public class CheatWarningFragment extends Fragment implements View.OnClickListen
|
||||
|
||||
Button settingsButton = view.findViewById(R.id.button_settings);
|
||||
settingsButton.setOnClickListener(this);
|
||||
|
||||
CheatsActivity activity = (CheatsActivity) requireActivity();
|
||||
CheatsActivity.setOnFocusChangeListenerRecursively(view,
|
||||
(v, hasFocus) -> activity.onListViewFocusChange(hasFocus));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -5,8 +5,12 @@ package org.dolphinemu.dolphinemu.features.cheats.ui;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.slidingpanelayout.widget.SlidingPaneLayout;
|
||||
|
||||
@ -18,6 +22,7 @@ import org.dolphinemu.dolphinemu.ui.TwoPaneOnBackPressedCallback;
|
||||
import org.dolphinemu.dolphinemu.ui.main.MainPresenter;
|
||||
|
||||
public class CheatsActivity extends AppCompatActivity
|
||||
implements SlidingPaneLayout.PanelSlideListener
|
||||
{
|
||||
private static final String ARG_GAME_ID = "game_id";
|
||||
private static final String ARG_REVISION = "revision";
|
||||
@ -29,6 +34,11 @@ public class CheatsActivity extends AppCompatActivity
|
||||
private CheatsViewModel mViewModel;
|
||||
|
||||
private SlidingPaneLayout mSlidingPaneLayout;
|
||||
private View mCheatList;
|
||||
private View mCheatDetails;
|
||||
|
||||
private View mCheatListLastFocus;
|
||||
private View mCheatDetailsLastFocus;
|
||||
|
||||
public static void launch(Context context, String gameId, int revision, boolean isWii)
|
||||
{
|
||||
@ -59,6 +69,13 @@ public class CheatsActivity extends AppCompatActivity
|
||||
setContentView(R.layout.activity_cheats);
|
||||
|
||||
mSlidingPaneLayout = findViewById(R.id.sliding_pane_layout);
|
||||
mCheatList = findViewById(R.id.cheat_list);
|
||||
mCheatDetails = findViewById(R.id.cheat_details);
|
||||
|
||||
mCheatListLastFocus = mCheatList;
|
||||
mCheatDetailsLastFocus = mCheatDetails;
|
||||
|
||||
mSlidingPaneLayout.addPanelSlideListener(this);
|
||||
|
||||
getOnBackPressedDispatcher().addCallback(this,
|
||||
new TwoPaneOnBackPressedCallback(mSlidingPaneLayout));
|
||||
@ -77,6 +94,25 @@ public class CheatsActivity extends AppCompatActivity
|
||||
mViewModel.saveIfNeeded(mGameId, mRevision);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPanelSlide(@NonNull View panel, float slideOffset)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPanelOpened(@NonNull View panel)
|
||||
{
|
||||
boolean rtl = ViewCompat.getLayoutDirection(panel) == ViewCompat.LAYOUT_DIRECTION_RTL;
|
||||
mCheatDetailsLastFocus.requestFocus(rtl ? View.FOCUS_LEFT : View.FOCUS_RIGHT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPanelClosed(@NonNull View panel)
|
||||
{
|
||||
boolean rtl = ViewCompat.getLayoutDirection(panel) == ViewCompat.LAYOUT_DIRECTION_RTL;
|
||||
mCheatListLastFocus.requestFocus(rtl ? View.FOCUS_RIGHT : View.FOCUS_LEFT);
|
||||
}
|
||||
|
||||
private void onSelectedCheatChanged(Cheat selectedCheat)
|
||||
{
|
||||
boolean cheatSelected = selectedCheat != null;
|
||||
@ -88,6 +124,30 @@ public class CheatsActivity extends AppCompatActivity
|
||||
SlidingPaneLayout.LOCK_MODE_UNLOCKED : SlidingPaneLayout.LOCK_MODE_LOCKED_CLOSED);
|
||||
}
|
||||
|
||||
public void onListViewFocusChange(boolean hasFocus)
|
||||
{
|
||||
if (hasFocus)
|
||||
{
|
||||
mCheatListLastFocus = mCheatList.findFocus();
|
||||
if (mCheatListLastFocus == null)
|
||||
throw new NullPointerException();
|
||||
|
||||
mSlidingPaneLayout.close();
|
||||
}
|
||||
}
|
||||
|
||||
public void onDetailsViewFocusChange(boolean hasFocus)
|
||||
{
|
||||
if (hasFocus)
|
||||
{
|
||||
mCheatDetailsLastFocus = mCheatDetails.findFocus();
|
||||
if (mCheatDetailsLastFocus == null)
|
||||
throw new NullPointerException();
|
||||
|
||||
mSlidingPaneLayout.open();
|
||||
}
|
||||
}
|
||||
|
||||
private void openDetailsView(boolean open)
|
||||
{
|
||||
if (open)
|
||||
@ -100,4 +160,20 @@ public class CheatsActivity extends AppCompatActivity
|
||||
settings.loadSettings(null, mGameId, mRevision, mIsWii);
|
||||
return settings;
|
||||
}
|
||||
|
||||
public static void setOnFocusChangeListenerRecursively(@NonNull View view,
|
||||
View.OnFocusChangeListener listener)
|
||||
{
|
||||
view.setOnFocusChangeListener(listener);
|
||||
|
||||
if (view instanceof ViewGroup)
|
||||
{
|
||||
ViewGroup viewGroup = (ViewGroup) view;
|
||||
for (int i = 0; i < viewGroup.getChildCount(); i++)
|
||||
{
|
||||
View child = viewGroup.getChildAt(i);
|
||||
setOnFocusChangeListenerRecursively(child, listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,25 +20,27 @@ import java.util.ArrayList;
|
||||
|
||||
public class CheatsAdapter extends RecyclerView.Adapter<CheatItemViewHolder>
|
||||
{
|
||||
private final CheatsActivity mActivity;
|
||||
private final CheatsViewModel mViewModel;
|
||||
|
||||
public CheatsAdapter(LifecycleOwner owner, CheatsViewModel viewModel)
|
||||
public CheatsAdapter(CheatsActivity activity, CheatsViewModel viewModel)
|
||||
{
|
||||
mActivity = activity;
|
||||
mViewModel = viewModel;
|
||||
|
||||
mViewModel.getCheatAddedEvent().observe(owner, (position) ->
|
||||
mViewModel.getCheatAddedEvent().observe(activity, (position) ->
|
||||
{
|
||||
if (position != null)
|
||||
notifyItemInserted(position);
|
||||
});
|
||||
|
||||
mViewModel.getCheatChangedEvent().observe(owner, (position) ->
|
||||
mViewModel.getCheatChangedEvent().observe(activity, (position) ->
|
||||
{
|
||||
if (position != null)
|
||||
notifyItemChanged(position);
|
||||
});
|
||||
|
||||
mViewModel.getCheatDeletedEvent().observe(owner, (position) ->
|
||||
mViewModel.getCheatDeletedEvent().observe(activity, (position) ->
|
||||
{
|
||||
if (position != null)
|
||||
notifyItemRemoved(position);
|
||||
@ -55,12 +57,15 @@ public class CheatsAdapter extends RecyclerView.Adapter<CheatItemViewHolder>
|
||||
{
|
||||
case CheatItem.TYPE_CHEAT:
|
||||
View cheatView = inflater.inflate(R.layout.list_item_cheat, parent, false);
|
||||
addViewListeners(cheatView);
|
||||
return new CheatViewHolder(cheatView);
|
||||
case CheatItem.TYPE_HEADER:
|
||||
View headerView = inflater.inflate(R.layout.list_item_header, parent, false);
|
||||
addViewListeners(headerView);
|
||||
return new HeaderViewHolder(headerView);
|
||||
case CheatItem.TYPE_ACTION:
|
||||
View actionView = inflater.inflate(R.layout.list_item_submenu, parent, false);
|
||||
addViewListeners(actionView);
|
||||
return new ActionViewHolder(actionView);
|
||||
default:
|
||||
throw new UnsupportedOperationException();
|
||||
@ -86,6 +91,12 @@ public class CheatsAdapter extends RecyclerView.Adapter<CheatItemViewHolder>
|
||||
return getItemAt(position).getType();
|
||||
}
|
||||
|
||||
private void addViewListeners(View view)
|
||||
{
|
||||
CheatsActivity.setOnFocusChangeListenerRecursively(view,
|
||||
(v, hasFocus) -> mActivity.onListViewFocusChange(hasFocus));
|
||||
}
|
||||
|
||||
private CheatItem getItemAt(int position)
|
||||
{
|
||||
// Patches
|
||||
|
@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/root"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:focusable="true"
|
||||
android:nextFocusLeft="@id/checkbox">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_name"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/TextAppearance.AppCompat.Headline"
|
||||
android:textSize="16sp"
|
||||
tools:text="Hyrule Field Speed Hack"
|
||||
android:layout_margin="@dimen/spacing_large"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/checkbox"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/checkbox"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="64dp"
|
||||
app:layout_constraintStart_toEndOf="@id/text_name"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:gravity="center"
|
||||
android:focusable="true"
|
||||
android:nextFocusRight="@id/root" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -6,7 +6,8 @@
|
||||
android:id="@+id/root"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:focusable="true">
|
||||
android:focusable="true"
|
||||
android:nextFocusRight="@id/checkbox">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_name"
|
||||
@ -30,6 +31,7 @@
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:gravity="center"
|
||||
android:focusable="true" />
|
||||
android:focusable="true"
|
||||
android:nextFocusLeft="@id/root" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
Loading…
x
Reference in New Issue
Block a user