GameBar: Implement overlay font customisations (wip)
Signed-off-by: kenway214 <kenway214@outlook.com>
This commit is contained in:
@@ -23,6 +23,7 @@ android_app {
|
||||
|
||||
static_libs: [
|
||||
"androidx.core_core",
|
||||
"androidx.recyclerview_recyclerview",
|
||||
"org.lineageos.settings.resources"
|
||||
],
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
18
res/layout/activity_font_selector.xml
Normal file
18
res/layout/activity_font_selector.xml
Normal 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
47
res/layout/item_font.xml
Normal 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>
|
||||
@@ -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"
|
||||
|
||||
92
src/com/android/gamebar/FontAdapter.kt
Normal file
92
src/com/android/gamebar/FontAdapter.kt
Normal 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])
|
||||
}
|
||||
}
|
||||
}
|
||||
13
src/com/android/gamebar/FontItem.kt
Normal file
13
src/com/android/gamebar/FontItem.kt
Normal 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
|
||||
)
|
||||
@@ -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) {
|
||||
|
||||
109
src/com/android/gamebar/GameBarFontSelectorActivity.kt
Normal file
109
src/com/android/gamebar/GameBarFontSelectorActivity.kt
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user