Android: Pad menu fragment when expanding to cutout area

This commit is contained in:
Charles Lombardo 2022-12-01 15:45:37 -05:00
parent 385dfb60a0
commit 2a0939ab98
7 changed files with 119 additions and 66 deletions

View File

@ -16,6 +16,7 @@ import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager; import android.view.WindowManager;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
@ -25,6 +26,9 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.PopupMenu; import androidx.appcompat.widget.PopupMenu;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
@ -342,6 +346,8 @@ public final class EmulationActivity extends AppCompatActivity implements ThemeP
ActivityEmulationBinding binding = ActivityEmulationBinding.inflate(getLayoutInflater()); ActivityEmulationBinding binding = ActivityEmulationBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot()); setContentView(binding.getRoot());
setInsets(binding.frameMenu);
// Find or create the EmulationFragment // Find or create the EmulationFragment
mEmulationFragment = (EmulationFragment) getSupportFragmentManager() mEmulationFragment = (EmulationFragment) getSupportFragmentManager()
.findFragmentById(R.id.frame_emulation_fragment); .findFragmentById(R.id.frame_emulation_fragment);
@ -538,6 +544,28 @@ public final class EmulationActivity extends AppCompatActivity implements ThemeP
} }
} }
private void setInsets(View view)
{
ViewCompat.setOnApplyWindowInsetsListener(view, (v, windowInsets) ->
{
Insets cutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout());
ViewGroup.MarginLayoutParams mlpMenu =
(ViewGroup.MarginLayoutParams) v.getLayoutParams();
int menuWidth = getResources().getDimensionPixelSize(R.dimen.menu_width);
if (ViewCompat.getLayoutDirection(v) == ViewCompat.LAYOUT_DIRECTION_LTR)
{
mlpMenu.width = cutInsets.left + menuWidth;
}
else
{
mlpMenu.width = cutInsets.right + menuWidth;
}
NativeLibrary.SetObscuredPixelsTop(cutInsets.top);
NativeLibrary.SetObscuredPixelsLeft(cutInsets.left);
return windowInsets;
});
}
public void showOverlayControlsMenu(@NonNull View anchor) public void showOverlayControlsMenu(@NonNull View anchor)
{ {
PopupMenu popup = new PopupMenu(this, anchor); PopupMenu popup = new PopupMenu(this, anchor);

View File

@ -3,7 +3,6 @@
package org.dolphinemu.dolphinemu.fragments; package org.dolphinemu.dolphinemu.fragments;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.graphics.Rect;
import android.os.Bundle; import android.os.Bundle;
import android.util.SparseIntArray; import android.util.SparseIntArray;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -14,6 +13,9 @@ import android.widget.LinearLayout;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import org.dolphinemu.dolphinemu.NativeLibrary; import org.dolphinemu.dolphinemu.NativeLibrary;
@ -21,6 +23,7 @@ import org.dolphinemu.dolphinemu.R;
import org.dolphinemu.dolphinemu.activities.EmulationActivity; import org.dolphinemu.dolphinemu.activities.EmulationActivity;
import org.dolphinemu.dolphinemu.databinding.FragmentIngameMenuBinding; import org.dolphinemu.dolphinemu.databinding.FragmentIngameMenuBinding;
import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting; import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting;
import org.dolphinemu.dolphinemu.utils.InsetsHelper;
public final class MenuFragment extends Fragment implements View.OnClickListener public final class MenuFragment extends Fragment implements View.OnClickListener
{ {
@ -28,6 +31,8 @@ public final class MenuFragment extends Fragment implements View.OnClickListener
private static final String KEY_WII = "wii"; private static final String KEY_WII = "wii";
private static SparseIntArray buttonsActionsMap = new SparseIntArray(); private static SparseIntArray buttonsActionsMap = new SparseIntArray();
private int mCutInset = 0;
static static
{ {
buttonsActionsMap buttonsActionsMap
@ -68,14 +73,6 @@ public final class MenuFragment extends Fragment implements View.OnClickListener
return fragment; return fragment;
} }
// This is primarily intended to account for any navigation bar at the bottom of the screen
private int getBottomPaddingRequired()
{
Rect visibleFrame = new Rect();
requireActivity().getWindow().getDecorView().getWindowVisibleDisplayFrame(visibleFrame);
return visibleFrame.bottom - visibleFrame.top - getResources().getDisplayMetrics().heightPixels;
}
@NonNull @NonNull
@Override @Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
@ -88,6 +85,7 @@ public final class MenuFragment extends Fragment implements View.OnClickListener
@Override @Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState)
{ {
setInsets();
updatePauseUnpauseVisibility(); updatePauseUnpauseVisibility();
if (!requireActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN)) if (!requireActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN))
@ -100,21 +98,6 @@ public final class MenuFragment extends Fragment implements View.OnClickListener
mBinding.menuRefreshWiimotes.setVisibility(View.GONE); mBinding.menuRefreshWiimotes.setVisibility(View.GONE);
} }
int bottomPaddingRequired = getBottomPaddingRequired();
// Provide a safe zone between the navigation bar and Exit Emulation to avoid accidental touches
float density = getResources().getDisplayMetrics().density;
if (bottomPaddingRequired >= 32 * density)
{
bottomPaddingRequired += 32 * density;
}
if (bottomPaddingRequired > view.getPaddingBottom())
{
view.setPadding(view.getPaddingLeft(), view.getPaddingTop(),
view.getPaddingRight(), bottomPaddingRequired);
}
LinearLayout options = mBinding.layoutOptions; LinearLayout options = mBinding.layoutOptions;
for (int childIndex = 0; childIndex < options.getChildCount(); childIndex++) for (int childIndex = 0; childIndex < options.getChildCount(); childIndex++)
{ {
@ -130,11 +113,41 @@ public final class MenuFragment extends Fragment implements View.OnClickListener
{ {
mBinding.textGameTitle.setText(title); mBinding.textGameTitle.setText(title);
} }
}
if (getResources().getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_LTR) private void setInsets()
{
ViewCompat.setOnApplyWindowInsetsListener(mBinding.getRoot(), (v, windowInsets) ->
{ {
view.post(() -> NativeLibrary.SetObscuredPixelsLeft(view.getWidth())); Insets cutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout());
} mCutInset = cutInsets.left;
int left = 0;
int right = 0;
if (ViewCompat.getLayoutDirection(v) == ViewCompat.LAYOUT_DIRECTION_LTR)
{
left = cutInsets.left;
}
else
{
right = cutInsets.right;
}
v.post(() -> NativeLibrary.SetObscuredPixelsLeft(v.getWidth()));
// Don't use padding if the navigation bar isn't in the way
if (InsetsHelper.getBottomPaddingRequired(requireActivity()) > 0)
{
v.setPadding(left, cutInsets.top, right,
cutInsets.bottom + InsetsHelper.getNavigationBarHeight(requireContext()));
}
else
{
v.setPadding(left, cutInsets.top, right,
cutInsets.bottom + getResources().getDimensionPixelSize(R.dimen.spacing_large));
}
return windowInsets;
});
} }
@Override @Override
@ -155,7 +168,7 @@ public final class MenuFragment extends Fragment implements View.OnClickListener
{ {
super.onDestroyView(); super.onDestroyView();
NativeLibrary.SetObscuredPixelsLeft(0); NativeLibrary.SetObscuredPixelsLeft(mCutInset);
mBinding = null; mBinding = null;
} }

View File

@ -1,7 +1,9 @@
package org.dolphinemu.dolphinemu.utils; package org.dolphinemu.dolphinemu.utils;
import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.res.Resources; import android.content.res.Resources;
import android.graphics.Rect;
import android.util.DisplayMetrics; import android.util.DisplayMetrics;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -222,4 +224,24 @@ public class InsetsHelper
} }
return 0; return 0;
} }
public static int getNavigationBarHeight(Context context)
{
Resources resources = context.getResources();
int resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android");
if (resourceId > 0)
{
return resources.getDimensionPixelSize(resourceId);
}
return 0;
}
// This is primarily intended to account for any navigation bar at the bottom of the screen
public static int getBottomPaddingRequired(Activity activity)
{
Rect visibleFrame = new Rect();
activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(visibleFrame);
return visibleFrame.bottom - visibleFrame.top -
activity.getResources().getDisplayMetrics().heightPixels;
}
} }

View File

@ -22,7 +22,7 @@
<FrameLayout <FrameLayout
android:id="@+id/frame_menu" android:id="@+id/frame_menu"
android:layout_width="260dp" android:layout_width="@dimen/menu_width"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:layout="@layout/fragment_ingame_menu"/> tools:layout="@layout/fragment_ingame_menu"/>

View File

@ -1,20 +1,19 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout
xmlns:tools="http://schemas.android.com/tools" xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:orientation="vertical"
android:layout_height="match_parent" android:layout_width="match_parent"
android:paddingBottom="16dp" android:layout_height="match_parent"
android:background="?attr/colorSurface" android:background="?attr/colorSurface"
tools:layout_width="250dp"> tools:layout_width="250dp">
<TextView <TextView
android:id="@+id/text_game_title" android:id="@+id/text_game_title"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_marginHorizontal="32dp" android:layout_marginHorizontal="32dp"
android:layout_marginTop="32dp" android:layout_marginVertical="24dp"
android:ellipsize="end" android:ellipsize="end"
android:letterSpacing="0" android:letterSpacing="0"
android:maxLines="@integer/game_title_lines" android:maxLines="@integer/game_title_lines"
@ -22,11 +21,16 @@
android:textColor="?attr/colorOnSurface" android:textColor="?attr/colorOnSurface"
tools:text="The Legend of Zelda: The Wind Waker" /> tools:text="The Legend of Zelda: The Wind Waker" />
<com.google.android.material.divider.MaterialDivider
android:id="@+id/divider"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<ScrollView <ScrollView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_weight="1" android:layout_weight="1"
android:scrollbarSize="8dp" android:scrollbarSize="4dp"
android:fadeScrollbars="false"> android:fadeScrollbars="false">
<LinearLayout <LinearLayout
@ -38,73 +42,62 @@
<Button <Button
android:id="@+id/menu_pause_emulation" android:id="@+id/menu_pause_emulation"
style="@style/InGameMenuOption" style="@style/InGameMenuOption"
android:letterSpacing="0"
android:text="@string/pause_emulation" /> android:text="@string/pause_emulation" />
<Button <Button
android:id="@+id/menu_unpause_emulation" android:id="@+id/menu_unpause_emulation"
style="@style/InGameMenuOption" style="@style/InGameMenuOption"
android:letterSpacing="0"
android:text="@string/unpause_emulation" android:text="@string/unpause_emulation"
android:visibility="gone"/> android:visibility="gone" />
<Button <Button
android:id="@+id/menu_take_screenshot" android:id="@+id/menu_take_screenshot"
style="@style/InGameMenuOption" style="@style/InGameMenuOption"
android:letterSpacing="0"
android:text="@string/emulation_screenshot" /> android:text="@string/emulation_screenshot" />
<Button <Button
android:id="@+id/menu_quicksave" android:id="@+id/menu_quicksave"
style="@style/InGameMenuOption" style="@style/InGameMenuOption"
android:letterSpacing="0"
android:text="@string/emulation_quicksave" android:text="@string/emulation_quicksave"
android:visibility="gone"/> android:visibility="gone" />
<Button <Button
android:id="@+id/menu_quickload" android:id="@+id/menu_quickload"
style="@style/InGameMenuOption" style="@style/InGameMenuOption"
android:letterSpacing="0"
android:text="@string/emulation_quickload" android:text="@string/emulation_quickload"
android:visibility="gone"/> android:visibility="gone" />
<Button <Button
android:id="@+id/menu_emulation_save_root" android:id="@+id/menu_emulation_save_root"
style="@style/InGameMenuOption" style="@style/InGameMenuOption"
android:letterSpacing="0"
android:text="@string/emulation_savestate" android:text="@string/emulation_savestate"
android:visibility="gone"/> android:visibility="gone" />
<Button <Button
android:id="@+id/menu_emulation_load_root" android:id="@+id/menu_emulation_load_root"
style="@style/InGameMenuOption" style="@style/InGameMenuOption"
android:letterSpacing="0"
android:text="@string/emulation_loadstate" android:text="@string/emulation_loadstate"
android:visibility="gone"/> android:visibility="gone" />
<Button <Button
android:id="@+id/menu_settings" android:id="@+id/menu_settings"
style="@style/InGameMenuOption" style="@style/InGameMenuOption"
android:letterSpacing="0"
android:text="@string/grid_menu_settings" /> android:text="@string/grid_menu_settings" />
<Button <Button
android:id="@+id/menu_overlay_controls" android:id="@+id/menu_overlay_controls"
style="@style/InGameMenuOption" style="@style/InGameMenuOption"
android:letterSpacing="0"
android:text="@string/emulation_overlay_controls" /> android:text="@string/emulation_overlay_controls" />
<Button <Button
android:id="@+id/menu_refresh_wiimotes" android:id="@+id/menu_refresh_wiimotes"
android:text="@string/emulation_refresh_wiimotes" android:text="@string/emulation_refresh_wiimotes"
android:letterSpacing="0" style="@style/InGameMenuOption" />
style="@style/InGameMenuOption"/>
<Button <Button
android:id="@+id/menu_change_disc" android:id="@+id/menu_change_disc"
android:text="@string/emulation_change_disc" android:text="@string/emulation_change_disc"
android:letterSpacing="0" style="@style/InGameMenuOption" />
style="@style/InGameMenuOption"/>
</LinearLayout> </LinearLayout>
@ -113,14 +106,12 @@
<com.google.android.material.divider.MaterialDivider <com.google.android.material.divider.MaterialDivider
android:id="@+id/divider_2" android:id="@+id/divider_2"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content" />
android:layout_marginTop="24dp"
android:layout_marginBottom="16dp" />
<Button <Button
android:id="@+id/menu_exit" android:id="@+id/menu_exit"
style="@style/InGameMenuOption" style="@style/InGameMenuOption"
android:letterSpacing="0" android:layout_marginTop="@dimen/spacing_large"
android:text="@string/emulation_exit" /> android:text="@string/emulation_exit" />
</LinearLayout> </LinearLayout>

View File

@ -2,5 +2,6 @@
<dimen name="spacing_small">4dp</dimen> <dimen name="spacing_small">4dp</dimen>
<dimen name="spacing_medlarge">12dp</dimen> <dimen name="spacing_medlarge">12dp</dimen>
<dimen name="spacing_large">16dp</dimen> <dimen name="spacing_large">16dp</dimen>
<dimen name="menu_width">256dp</dimen>
<dimen name="card_width">135dp</dimen> <dimen name="card_width">135dp</dimen>
</resources> </resources>

View File

@ -2,16 +2,14 @@
<resources> <resources>
<!-- Custom button styles --> <!-- Custom button styles -->
<style name="InGameMenuOption" parent="Widget.Material3.Button.TextButton"> <style name="InGameMenuOption" parent="Widget.Material3.Button.TextButton">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">48dp</item>
<item name="android:textColor">?attr/colorOnSurface</item> <item name="android:textColor">?attr/colorOnSurface</item>
<item name="android:textSize">16sp</item> <item name="android:textSize">16sp</item>
<item name="android:fontFamily">sans-serif-condensed</item> <item name="android:fontFamily">sans-serif-condensed</item>
<item name="android:textAllCaps">false</item>
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">48dp</item>
<item name="android:gravity">center_vertical|left</item> <item name="android:gravity">center_vertical|left</item>
<item name="android:paddingLeft">32dp</item> <item name="android:paddingLeft">32dp</item>
<item name="android:paddingRight">32dp</item> <item name="android:paddingRight">32dp</item>
<item name="android:layout_margin">0dp</item>
</style> </style>
<style name="OverlayInGameMenuOption" parent="InGameMenuOption"> <style name="OverlayInGameMenuOption" parent="InGameMenuOption">