diff --git a/Android.bp b/Android.bp
index b45e9dc..0652f15 100644
--- a/Android.bp
+++ b/Android.bp
@@ -23,6 +23,7 @@ android_app {
static_libs: [
"androidx.core_core",
+ "androidx.recyclerview_recyclerview",
"org.lineageos.settings.resources"
],
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 81df27f..57f0da9 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -140,6 +140,11 @@
android:exported="false"
android:theme="@style/Theme.SubSettingsBase.Expressive" />
+
+
+
+
+
+
+
+
diff --git a/res/layout/item_font.xml b/res/layout/item_font.xml
new file mode 100644
index 0000000..e0a14e9
--- /dev/null
+++ b/res/layout/item_font.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/xml/game_bar_preferences.xml b/res/xml/game_bar_preferences.xml
index b985d07..463bcad 100644
--- a/res/xml/game_bar_preferences.xml
+++ b/res/xml/game_bar_preferences.xml
@@ -199,6 +199,11 @@
app:gamebar_allowCustom="true"
app:gamebar_colorPresets="@array/fps_overlay_color_int_values" />
+
+
,
+ private val onFontSelected: (FontItem) -> Unit
+) : RecyclerView.Adapter() {
+
+ 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])
+ }
+ }
+}
diff --git a/src/com/android/gamebar/FontItem.kt b/src/com/android/gamebar/FontItem.kt
new file mode 100644
index 0000000..3a42d3b
--- /dev/null
+++ b/src/com/android/gamebar/FontItem.kt
@@ -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
+)
diff --git a/src/com/android/gamebar/GameBar.kt b/src/com/android/gamebar/GameBar.kt
index db2cf13..9c987a5 100644
--- a/src/com/android/gamebar/GameBar.kt
+++ b/src/com/android/gamebar/GameBar.kt
@@ -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) {
diff --git a/src/com/android/gamebar/GameBarFontSelectorActivity.kt b/src/com/android/gamebar/GameBarFontSelectorActivity.kt
new file mode 100644
index 0000000..fef76cb
--- /dev/null
+++ b/src/com/android/gamebar/GameBarFontSelectorActivity.kt
@@ -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()
+
+ 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
+ }
+}
diff --git a/src/com/android/gamebar/GameBarFragment.kt b/src/com/android/gamebar/GameBarFragment.kt
index 340beee..f84c915 100644
--- a/src/com/android/gamebar/GameBarFragment.kt
+++ b/src/com/android/gamebar/GameBarFragment.kt
@@ -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() {