[android / GameUI] Add grid compact layout and fix ugly borders on grid layout (#401)

Tis PR adds a new layout for the Game Adapter with shorter cards and fixes the ugly borders in Game Adapter's Grid layout.

Reviewed-on: #401
Reviewed-by: crueter <crueter@eden-emu.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Co-authored-by: Nixyn <contact@innix.space>
Co-committed-by: Nixyn <contact@innix.space>
This commit is contained in:
Nixyn 2025-09-15 06:26:06 +02:00 committed by crueter
parent 2990f30a70
commit ab6ffeef58
Signed by: crueter
GPG key ID: 425ACD2D4830EBC6
7 changed files with 161 additions and 8 deletions

View file

@ -36,17 +36,18 @@ import androidx.core.net.toUri
import androidx.core.content.edit
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.databinding.CardGameGridCompactBinding
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.utils.NativeConfig
class GameAdapter(private val activity: AppCompatActivity) :
AbstractDiffAdapter<Game, GameAdapter.GameViewHolder>(exact = false) {
companion object {
const val VIEW_TYPE_GRID = 0
const val VIEW_TYPE_LIST = 1
const val VIEW_TYPE_CAROUSEL = 2
const val VIEW_TYPE_GRID_COMPACT = 1
const val VIEW_TYPE_LIST = 2
const val VIEW_TYPE_CAROUSEL = 3
}
private var viewType = 0
@ -80,6 +81,7 @@ class GameAdapter(private val activity: AppCompatActivity) :
listBinding.root.layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT
listBinding.root.layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT
}
VIEW_TYPE_GRID -> {
val gridBinding = holder.binding as CardGameGridBinding
gridBinding.cardGameGrid.scaleX = 1f
@ -89,6 +91,17 @@ class GameAdapter(private val activity: AppCompatActivity) :
gridBinding.root.layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT
gridBinding.root.layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT
}
VIEW_TYPE_GRID_COMPACT -> {
val gridCompactBinding = holder.binding as CardGameGridCompactBinding
gridCompactBinding.cardGameGridCompact.scaleX = 1f
gridCompactBinding.cardGameGridCompact.scaleY = 1f
gridCompactBinding.cardGameGridCompact.alpha = 1f
// Reset layout params to XML defaults (same as normal grid)
gridCompactBinding.root.layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT
gridCompactBinding.root.layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT
}
VIEW_TYPE_CAROUSEL -> {
val carouselBinding = holder.binding as CardGameCarouselBinding
// soothens transient flickering
@ -105,16 +118,25 @@ class GameAdapter(private val activity: AppCompatActivity) :
parent,
false
)
VIEW_TYPE_GRID -> CardGameGridBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
VIEW_TYPE_GRID_COMPACT -> CardGameGridCompactBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
VIEW_TYPE_CAROUSEL -> CardGameCarouselBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
else -> throw IllegalArgumentException("Invalid view type")
}
return GameViewHolder(binding, viewType)
@ -130,6 +152,7 @@ class GameAdapter(private val activity: AppCompatActivity) :
VIEW_TYPE_LIST -> bindListView(model)
VIEW_TYPE_GRID -> bindGridView(model)
VIEW_TYPE_CAROUSEL -> bindCarouselView(model)
VIEW_TYPE_GRID_COMPACT -> bindGridCompactView(model)
}
}
@ -168,6 +191,23 @@ class GameAdapter(private val activity: AppCompatActivity) :
gridBinding.root.layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT
}
private fun bindGridCompactView(model: Game) {
val gridCompactBinding = binding as CardGameGridCompactBinding
gridCompactBinding.imageGameScreenCompact.scaleType = ImageView.ScaleType.CENTER_CROP
GameIconUtils.loadGameIcon(model, gridCompactBinding.imageGameScreenCompact)
gridCompactBinding.textGameTitleCompact.text = model.title.replace("[\\t\\n\\r]+".toRegex(), " ")
gridCompactBinding.textGameTitleCompact.marquee()
gridCompactBinding.cardGameGridCompact.setOnClickListener { onClick(model) }
gridCompactBinding.cardGameGridCompact.setOnLongClickListener { onLongClick(model) }
// Reset layout params to XML defaults (same as normal grid)
gridCompactBinding.root.layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT
gridCompactBinding.root.layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT
}
private fun bindCarouselView(model: Game) {
val carouselBinding = binding as CardGameCarouselBinding

View file

@ -194,6 +194,10 @@ class GamesFragment : Fragment() {
val columns = resources.getInteger(R.integer.game_columns_grid)
GridLayoutManager(context, columns)
}
GameAdapter.VIEW_TYPE_GRID_COMPACT -> {
val columns = resources.getInteger(R.integer.game_columns_grid)
GridLayoutManager(context, columns)
}
GameAdapter.VIEW_TYPE_LIST -> {
val columns = resources.getInteger(R.integer.game_columns_list)
GridLayoutManager(context, columns)
@ -300,6 +304,7 @@ class GamesFragment : Fragment() {
val currentViewType = getCurrentViewType()
when (currentViewType) {
GameAdapter.VIEW_TYPE_LIST -> popup.menu.findItem(R.id.view_list).isChecked = true
GameAdapter.VIEW_TYPE_GRID_COMPACT -> popup.menu.findItem(R.id.view_grid_compact).isChecked = true
GameAdapter.VIEW_TYPE_GRID -> popup.menu.findItem(R.id.view_grid).isChecked = true
GameAdapter.VIEW_TYPE_CAROUSEL -> popup.menu.findItem(R.id.view_carousel).isChecked = true
}
@ -314,6 +319,14 @@ class GamesFragment : Fragment() {
true
}
R.id.view_grid_compact -> {
if (getCurrentViewType() == GameAdapter.VIEW_TYPE_CAROUSEL) onPause()
setCurrentViewType(GameAdapter.VIEW_TYPE_GRID_COMPACT)
applyGridGamesBinding()
item.isChecked = true
true
}
R.id.view_list -> {
if (getCurrentViewType() == GameAdapter.VIEW_TYPE_CAROUSEL) onPause()
setCurrentViewType(GameAdapter.VIEW_TYPE_LIST)

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:angle="270"
android:startColor="@android:color/transparent"
android:centerColor="#66000000"
android:endColor="#AA000000"
android:type="linear" />
</shape>

View file

@ -5,27 +5,32 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:focusable="false"
android:focusableInTouchMode="false">
android:focusableInTouchMode="false"
android:padding="4dp">
<org.yuzu.yuzu_emu.views.GradientBorderCardView
android:id="@+id/card_game_grid"
app:cardElevation="0dp"
app:cardBackgroundColor="@color/eden_card_background"
app:strokeWidth="1dp"
app:strokeColor="@color/eden_border"
app:strokeWidth="0dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="4dp"
android:clickable="true"
android:clipToPadding="true"
android:focusable="true"
android:transitionName="card_game"
app:cardCornerRadius="16dp">
app:cardCornerRadius="16dp"
android:foreground="@color/eden_border_gradient_start">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="6dp">
android:paddingTop="14dp"
android:paddingLeft="6dp"
android:paddingRight="6dp"
android:paddingBottom="6dp">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/image_game_screen"

View file

@ -0,0 +1,82 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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:layout_width="match_parent"
android:layout_height="wrap_content"
android:focusable="false"
android:focusableInTouchMode="false"
android:padding="4dp">
<org.yuzu.yuzu_emu.views.GradientBorderCardView
android:id="@+id/card_game_grid_compact"
app:cardElevation="0dp"
app:cardBackgroundColor="@color/eden_card_background"
app:strokeWidth="0dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="4dp"
android:clickable="true"
android:clipToPadding="true"
android:focusable="true"
android:transitionName="card_game_compact"
app:cardCornerRadius="16dp"
android:foreground="@color/eden_border_gradient_start">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="14dp"
android:paddingLeft="6dp"
android:paddingRight="6dp"
android:paddingBottom="6dp">
<FrameLayout
android:id="@+id/image_container"
android:layout_width="150dp"
android:layout_height="100dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/image_game_screen_compact"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:shapeAppearance="@style/ShapeAppearance.Material3.Corner.Medium"
android:scaleType="centerCrop"
tools:src="@drawable/default_icon" />
<View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/gradient_overlay_bottom" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/text_game_title_compact"
style="@style/SynthwaveText.Body"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_margin="6dp"
android:requiresFadingEdge="horizontal"
android:textAlignment="center"
android:textSize="12sp"
android:textStyle="bold"
android:textColor="@android:color/white"
android:shadowColor="@android:color/black"
android:shadowDx="1"
android:shadowDy="1"
android:shadowRadius="2"
android:maxLines="2"
android:ellipsize="end"
tools:text="The Legend of Zelda: Skyward Sword" />
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</org.yuzu.yuzu_emu.views.GradientBorderCardView>
</FrameLayout>

View file

@ -4,6 +4,9 @@
<item
android:id="@+id/view_grid"
android:title="@string/view_grid"/>
<item
android:id="@+id/view_grid_compact"
android:title="@string/view_grid_compact"/>
<item
android:id="@+id/view_list"
android:title="@string/view_list"

View file

@ -266,6 +266,7 @@
<string name="alphabetical">Alphabetical</string>
<string name="view_list">List</string>
<string name="view_grid">Grid</string>
<string name="view_grid_compact">Compact Grid</string>
<string name="view_carousel">Carousel</string>
<string name="game_image_desc">Screenshot for %1$s</string>
<string name="folder">Folder</string>