GameBar: Implement overlay font customisations (wip)

Signed-off-by: kenway214 <kenway214@outlook.com>
This commit is contained in:
kenway214
2025-10-31 01:57:51 +05:30
parent dae63bbeef
commit 4e65bbe4f1
10 changed files with 350 additions and 0 deletions

View File

@@ -23,6 +23,7 @@ android_app {
static_libs: [
"androidx.core_core",
"androidx.recyclerview_recyclerview",
"org.lineageos.settings.resources"
],

View File

@@ -140,6 +140,11 @@
android:exported="false"
android:theme="@style/Theme.SubSettingsBase.Expressive" />
<activity
android:name=".GameBarFontSelectorActivity"
android:exported="false"
android:theme="@style/Theme.SubSettingsBase.Expressive" />
<!-- FileProvider for sharing log files -->
<provider
android:name="androidx.core.content.FileProvider"

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
SPDX-FileCopyrightText: 2025 kenway214
SPDX-License-Identifier: Apache-2.0
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="?android:attr/colorBackground">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/font_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical" />
</LinearLayout>

47
res/layout/item_font.xml Normal file
View File

@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
SPDX-FileCopyrightText: 2025 kenway214
SPDX-License-Identifier: Apache-2.0
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp"
android:background="?android:attr/selectableItemBackground"
android:gravity="center_vertical">
<RadioButton
android:id="@+id/font_radio"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="false"
android:focusable="false" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="16dp"
android:orientation="vertical">
<TextView
android:id="@+id/font_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:textColor="?android:attr/textColorPrimary"
android:text="Font Name" />
<TextView
android:id="@+id/font_preview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textSize="18sp"
android:textColor="?android:attr/textColorSecondary"
android:text="AaBbCc 123" />
</LinearLayout>
</LinearLayout>

View File

@@ -199,6 +199,11 @@
app:gamebar_allowCustom="true"
app:gamebar_colorPresets="@array/fps_overlay_color_int_values" />
<Preference
android:key="game_bar_font_selector"
android:title="Overlay Font Style"
android:summary="Choose custom font for overlay text" />
<ListPreference
android:key="game_bar_position"
android:title="Overlay Position"

View File

@@ -0,0 +1,92 @@
/*
* SPDX-FileCopyrightText: 2025 kenway214
* SPDX-License-Identifier: Apache-2.0
*/
package com.android.gamebar
import android.graphics.Typeface
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.RadioButton
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
class FontAdapter(
private val fonts: List<FontItem>,
private val onFontSelected: (FontItem) -> Unit
) : RecyclerView.Adapter<FontAdapter.FontViewHolder>() {
private var selectedPosition = fonts.indexOfFirst { it.isSelected }
inner class FontViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val fontName: TextView = view.findViewById(R.id.font_name)
val fontPreview: TextView = view.findViewById(R.id.font_preview)
val radioButton: RadioButton = view.findViewById(R.id.font_radio)
init {
view.setOnClickListener {
val position = adapterPosition
if (position != RecyclerView.NO_POSITION) {
selectFont(position)
}
}
radioButton.setOnClickListener {
val position = adapterPosition
if (position != RecyclerView.NO_POSITION) {
selectFont(position)
}
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FontViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_font, parent, false)
return FontViewHolder(view)
}
override fun onBindViewHolder(holder: FontViewHolder, position: Int) {
val fontItem = fonts[position]
holder.fontName.text = fontItem.displayName
holder.fontPreview.text = "AaBbCc 123"
holder.radioButton.isChecked = (position == selectedPosition)
// Apply the font to preview
try {
val typeface = if (fontItem.path == "default") {
Typeface.DEFAULT
} else {
// Load from assets
Typeface.createFromAsset(holder.itemView.context.assets, fontItem.path)
}
holder.fontPreview.setTypeface(typeface, Typeface.NORMAL)
} catch (e: Exception) {
// If font fails to load, use default
holder.fontPreview.setTypeface(Typeface.DEFAULT, Typeface.NORMAL)
android.util.Log.e("FontAdapter", "Failed to load font ${fontItem.path}: ${e.message}")
}
}
override fun getItemCount() = fonts.size
private fun selectFont(position: Int) {
if (selectedPosition != position) {
val oldPosition = selectedPosition
selectedPosition = position
// Update selection state
if (oldPosition >= 0 && oldPosition < fonts.size) {
fonts[oldPosition].isSelected = false
notifyItemChanged(oldPosition)
}
fonts[position].isSelected = true
notifyItemChanged(position)
onFontSelected(fonts[position])
}
}
}

View File

@@ -0,0 +1,13 @@
/*
* SPDX-FileCopyrightText: 2025 kenway214
* SPDX-License-Identifier: Apache-2.0
*/
package com.android.gamebar
data class FontItem(
val name: String,
val displayName: String,
val path: String,
var isSelected: Boolean = false
)

View File

@@ -11,6 +11,7 @@ import android.content.Intent
import android.content.SharedPreferences
import android.graphics.Color
import android.graphics.PixelFormat
import android.graphics.Typeface
import android.graphics.drawable.GradientDrawable
import android.os.Handler
import android.os.Looper
@@ -91,6 +92,7 @@ class GameBar private constructor(context: Context) {
private var paddingDp = 8
private var titleColorHex = "#FFFFFF"
private var valueColorHex = "#FFFFFF"
private var customTypeface: Typeface? = null
private var overlayFormat = "full"
private var position = "top_center"
private var splitMode = "side_by_side"
@@ -246,6 +248,11 @@ class GameBar private constructor(context: Context) {
val valueColorInt = prefs.getInt("game_bar_value_color", 0xFF4CAF50.toInt())
val valueColorHex = String.format("#%06X", 0xFFFFFF and valueColorInt)
updateValueColor(valueColorHex)
// Load custom font
val fontPath = prefs.getString("game_bar_font_path", "default") ?: "default"
loadCustomFont(fontPath)
updateOverlayFormat(prefs.getString("game_bar_format", "full") ?: "full")
updateUpdateInterval(prefs.getString("game_bar_update_interval", "1000") ?: "1000")
updatePosition(prefs.getString("game_bar_position", "draggable") ?: "draggable")
@@ -666,6 +673,7 @@ class GameBar private constructor(context: Context) {
} catch (e: Exception) {
setTextColor(Color.WHITE)
}
setTypeface(getTypeface(), Typeface.NORMAL)
text = "CPU Freq "
}
freqContainer.addView(labelTv)
@@ -687,6 +695,7 @@ class GameBar private constructor(context: Context) {
} catch (e: Exception) {
setTextColor(Color.WHITE)
}
setTypeface(getTypeface(), Typeface.NORMAL)
text = freqLine
}
@@ -720,6 +729,7 @@ class GameBar private constructor(context: Context) {
} catch (e: Exception) {
setTextColor(Color.WHITE)
}
setTypeface(getTypeface(), Typeface.NORMAL)
text = if (title.isEmpty()) "" else "$title "
}
@@ -730,6 +740,7 @@ class GameBar private constructor(context: Context) {
} catch (e: Exception) {
setTextColor(Color.WHITE)
}
setTypeface(getTypeface(), Typeface.NORMAL)
text = rawValue
}
@@ -743,6 +754,7 @@ class GameBar private constructor(context: Context) {
} catch (e: Exception) {
setTextColor(Color.WHITE)
}
setTypeface(getTypeface(), Typeface.NORMAL)
text = rawValue
}
lineLayout.addView(tvMinimal)
@@ -768,6 +780,7 @@ class GameBar private constructor(context: Context) {
} catch (e: Exception) {
setTextColor(Color.WHITE)
}
setTypeface(getTypeface(), Typeface.NORMAL)
text = " . "
}
}
@@ -818,6 +831,47 @@ class GameBar private constructor(context: Context) {
applyBackgroundStyle()
}
fun updateFont(fontPath: String) {
loadCustomFont(fontPath)
// Force refresh by recreating the overlay if it's showing
if (isShowing) {
val wasShowing = isShowing
hide()
if (wasShowing) {
show()
}
}
}
private fun loadCustomFont(fontPath: String) {
android.util.Log.d("GameBar", "Loading font: $fontPath")
customTypeface = if (fontPath == "default" || fontPath.isEmpty()) {
android.util.Log.d("GameBar", "Using default font")
null
} else {
try {
// Load font from assets
val typeface = Typeface.createFromAsset(context.assets, fontPath)
android.util.Log.d("GameBar", "Font loaded successfully: $fontPath")
android.util.Log.d("GameBar", "Typeface object: $typeface")
android.util.Log.d("GameBar", "Typeface is default: ${typeface == Typeface.DEFAULT}")
typeface
} catch (e: Exception) {
android.util.Log.e("GameBar", "Failed to load font from assets: $fontPath - ${e.message}")
e.printStackTrace()
null
}
}
android.util.Log.d("GameBar", "customTypeface set to: $customTypeface")
}
private fun getTypeface(): Typeface {
android.util.Log.d("GameBar", "getTypeface called - customTypeface: $customTypeface")
val typeface = customTypeface ?: Typeface.DEFAULT
android.util.Log.d("GameBar", "getTypeface returning: $typeface (isCustom: ${customTypeface != null})")
return typeface
}
fun updateOverlayFormat(format: String) {
overlayFormat = format
if (isShowing) {

View File

@@ -0,0 +1,109 @@
/*
* SPDX-FileCopyrightText: 2025 kenway214
* SPDX-License-Identifier: Apache-2.0
*/
package com.android.gamebar
import android.os.Bundle
import android.widget.Toast
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.android.settingslib.collapsingtoolbar.CollapsingToolbarBaseActivity
class GameBarFontSelectorActivity : CollapsingToolbarBaseActivity() {
private lateinit var recyclerView: RecyclerView
private lateinit var fontAdapter: FontAdapter
private val fontList = mutableListOf<FontItem>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_font_selector)
title = "Select Overlay Font"
recyclerView = findViewById(R.id.font_recycler_view)
recyclerView.layoutManager = LinearLayoutManager(this)
loadFonts()
setupAdapter()
}
private fun loadFonts() {
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
val currentFontPath = prefs.getString("game_bar_font_path", "default") ?: "default"
// Add default font
fontList.add(
FontItem(
name = "default",
displayName = "System Default",
path = "default",
isSelected = (currentFontPath == "default")
)
)
// Load fonts from assets
try {
val fontFiles = assets.list("fonts") ?: emptyArray()
fontFiles.filter { fileName ->
fileName.endsWith(".ttf", ignoreCase = true) ||
fileName.endsWith(".otf", ignoreCase = true)
}.forEach { fileName ->
val nameWithoutExt = fileName.substringBeforeLast(".")
val displayName = nameWithoutExt
.replace("-", " ")
.replace("_", " ")
.split(" ")
.joinToString(" ") { word ->
word.replaceFirstChar { it.uppercase() }
}
fontList.add(
FontItem(
name = nameWithoutExt,
displayName = displayName,
path = "fonts/$fileName", // Asset path
isSelected = ("fonts/$fileName" == currentFontPath)
)
)
}
} catch (e: Exception) {
android.util.Log.e("FontSelector", "Error loading fonts from assets: ${e.message}")
}
// Sort fonts alphabetically (keep default at top)
if (fontList.isNotEmpty()) {
val defaultFont = fontList.removeAt(0)
fontList.sortBy { it.displayName }
fontList.add(0, defaultFont)
}
}
private fun setupAdapter() {
fontAdapter = FontAdapter(fontList) { selectedFont ->
// Save selected font
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
prefs.edit()
.putString("game_bar_font_path", selectedFont.path)
.putString("game_bar_font_name", selectedFont.displayName)
.apply()
// Update GameBar font (will refresh overlay if showing)
if (GameBar.isInstanceCreated()) {
val gameBar = GameBar.getInstance(this)
gameBar.updateFont(selectedFont.path)
}
Toast.makeText(
this,
"Font changed to ${selectedFont.displayName}",
Toast.LENGTH_SHORT
).show()
}
recyclerView.adapter = fontAdapter
}
}

View File

@@ -128,6 +128,12 @@ class GameBarFragment : SettingsBasePreferenceFragment() {
startActivity(Intent(requireContext(), GameBarPerAppConfigActivity::class.java))
true
}
val fontSelectorPref: Preference? = findPreference("game_bar_font_selector")
fontSelectorPref?.setOnPreferenceClickListener {
startActivity(Intent(requireContext(), GameBarFontSelectorActivity::class.java))
true
}
}
private fun setupMasterSwitchListener() {