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() {