mirror of
https://git.eden-emu.dev/archive/citron
synced 2026-03-22 17:46:08 -04:00
feat(android): implement Project Thor dual-screen dashboard
- Add ThorPresentation.kt for secondary display performance dashboard - Create thor_presentation.xml with real-time stats layout - Modify EmulationActivity to auto-detect secondary displays - Display FPS, speed, frame time, resolution, and shader status - Color-coded FPS indicator (green/yellow/orange/red) - Add 12 new string resources for Thor dashboard UI - Keep main screen immersive while bottom screen shows stats Signed-off-by: Zephyron <zephyron@citron-emu.org>
This commit is contained in:
@@ -53,6 +53,7 @@ import org.citron.citron_emu.utils.NativeConfig
|
|||||||
import org.citron.citron_emu.utils.NfcReader
|
import org.citron.citron_emu.utils.NfcReader
|
||||||
import org.citron.citron_emu.utils.ParamPackage
|
import org.citron.citron_emu.utils.ParamPackage
|
||||||
import org.citron.citron_emu.utils.ThemeHelper
|
import org.citron.citron_emu.utils.ThemeHelper
|
||||||
|
import org.citron.citron_emu.features.thor.ThorPresentation
|
||||||
import java.text.NumberFormat
|
import java.text.NumberFormat
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
@@ -72,6 +73,9 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||||||
private val actionMute = "ACTION_EMULATOR_MUTE"
|
private val actionMute = "ACTION_EMULATOR_MUTE"
|
||||||
private val actionUnmute = "ACTION_EMULATOR_UNMUTE"
|
private val actionUnmute = "ACTION_EMULATOR_UNMUTE"
|
||||||
|
|
||||||
|
// PROJECT THOR: Dual Screen Dashboard for AYN Thor
|
||||||
|
private var thorPresentation: ThorPresentation? = null
|
||||||
|
|
||||||
private val emulationViewModel: EmulationViewModel by viewModels()
|
private val emulationViewModel: EmulationViewModel by viewModels()
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
@@ -177,12 +181,67 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||||||
InputHandler.updateControllerData()
|
InputHandler.updateControllerData()
|
||||||
|
|
||||||
buildPictureInPictureParams()
|
buildPictureInPictureParams()
|
||||||
|
|
||||||
|
// PROJECT THOR: Detect secondary display and show performance dashboard
|
||||||
|
// This keeps the main screen immersive while showing stats on the bottom display
|
||||||
|
initThorPresentation()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PROJECT THOR: Initialize the dual-screen performance dashboard
|
||||||
|
* Automatically detects secondary displays (like AYN Thor's bottom screen)
|
||||||
|
* and shows a live performance dashboard with FPS, speed, and settings info.
|
||||||
|
*/
|
||||||
|
private fun initThorPresentation() {
|
||||||
|
if (thorPresentation == null && ThorPresentation.hasSecondaryDisplay(this)) {
|
||||||
|
try {
|
||||||
|
thorPresentation = ThorPresentation.createIfAvailable(this)
|
||||||
|
thorPresentation?.let { presentation ->
|
||||||
|
presentation.show()
|
||||||
|
presentation.startUpdates()
|
||||||
|
Toast.makeText(
|
||||||
|
this,
|
||||||
|
getString(R.string.thor_secondary_display_detected),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
Log.debug("[Thor] Secondary display detected - Dashboard enabled")
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.error("[Thor] Failed to initialize presentation: ${e.message}")
|
||||||
|
thorPresentation = null
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Resume updates if presentation already exists
|
||||||
|
thorPresentation?.startUpdates()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PROJECT THOR: Clean up the dual-screen presentation
|
||||||
|
*/
|
||||||
|
private fun cleanupThorPresentation() {
|
||||||
|
thorPresentation?.let { presentation ->
|
||||||
|
presentation.stopUpdates()
|
||||||
|
if (presentation.isShowing) {
|
||||||
|
presentation.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
thorPresentation = null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
super.onPause()
|
super.onPause()
|
||||||
nfcReader.stopScanning()
|
nfcReader.stopScanning()
|
||||||
stopMotionSensorListener()
|
stopMotionSensorListener()
|
||||||
|
|
||||||
|
// PROJECT THOR: Stop dashboard updates when paused
|
||||||
|
thorPresentation?.stopUpdates()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
// PROJECT THOR: Clean up dual-screen presentation on destroy
|
||||||
|
cleanupThorPresentation()
|
||||||
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onUserLeaveHint() {
|
override fun onUserLeaveHint() {
|
||||||
|
|||||||
@@ -0,0 +1,225 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 citron Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
package org.citron.citron_emu.features.thor
|
||||||
|
|
||||||
|
import android.app.Presentation
|
||||||
|
import android.content.Context
|
||||||
|
import android.hardware.display.DisplayManager
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
|
import android.view.Display
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.TextView
|
||||||
|
import org.citron.citron_emu.NativeLibrary
|
||||||
|
import org.citron.citron_emu.R
|
||||||
|
import org.citron.citron_emu.features.settings.model.BooleanSetting
|
||||||
|
import org.citron.citron_emu.features.settings.model.IntSetting
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PROJECT THOR: Dual Screen Implementation for AYN Thor
|
||||||
|
*
|
||||||
|
* This Presentation class renders a Live Performance Dashboard on the Thor's
|
||||||
|
* secondary bottom display, keeping the main 1080p screen completely immersive.
|
||||||
|
*
|
||||||
|
* Features:
|
||||||
|
* - Real-time FPS, Emulation Speed %, and Frame Time
|
||||||
|
* - Live settings display (Resolution, Docked State, CPU Accuracy)
|
||||||
|
* - Shader compilation indicator
|
||||||
|
*/
|
||||||
|
class ThorPresentation(
|
||||||
|
context: Context,
|
||||||
|
display: Display
|
||||||
|
) : Presentation(context, display) {
|
||||||
|
|
||||||
|
private lateinit var fpsText: TextView
|
||||||
|
private lateinit var speedText: TextView
|
||||||
|
private lateinit var frameTimeText: TextView
|
||||||
|
private lateinit var resolutionText: TextView
|
||||||
|
private lateinit var dockedText: TextView
|
||||||
|
private lateinit var cpuAccuracyText: TextView
|
||||||
|
private lateinit var shaderText: TextView
|
||||||
|
private lateinit var gpuDriverText: TextView
|
||||||
|
private lateinit var astcModeText: TextView
|
||||||
|
|
||||||
|
private val handler = Handler(Looper.getMainLooper())
|
||||||
|
private var isRunning = false
|
||||||
|
|
||||||
|
// Poll interval for performance stats (500ms as specified in report)
|
||||||
|
private val pollInterval = 500L
|
||||||
|
|
||||||
|
private val updateRunnable = object : Runnable {
|
||||||
|
override fun run() {
|
||||||
|
if (isRunning) {
|
||||||
|
updatePerformanceStats()
|
||||||
|
handler.postDelayed(this, pollInterval)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(R.layout.thor_presentation)
|
||||||
|
|
||||||
|
initializeViews()
|
||||||
|
updateStaticSettings()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initializeViews() {
|
||||||
|
fpsText = findViewById(R.id.thor_fps)
|
||||||
|
speedText = findViewById(R.id.thor_speed)
|
||||||
|
frameTimeText = findViewById(R.id.thor_frame_time)
|
||||||
|
resolutionText = findViewById(R.id.thor_resolution)
|
||||||
|
dockedText = findViewById(R.id.thor_docked)
|
||||||
|
cpuAccuracyText = findViewById(R.id.thor_cpu_accuracy)
|
||||||
|
shaderText = findViewById(R.id.thor_shader_status)
|
||||||
|
gpuDriverText = findViewById(R.id.thor_gpu_driver)
|
||||||
|
astcModeText = findViewById(R.id.thor_astc_mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update static settings that don't change during gameplay
|
||||||
|
*/
|
||||||
|
private fun updateStaticSettings() {
|
||||||
|
// Resolution scaling - based on arrays.xml rendererResolutionValues
|
||||||
|
val resolutionIndex = IntSetting.RENDERER_RESOLUTION.getInt()
|
||||||
|
val resolutionString = when (resolutionIndex) {
|
||||||
|
-1 -> "0.25X (180p)"
|
||||||
|
0 -> "0.5X (360p)"
|
||||||
|
1 -> "0.75X (540p)"
|
||||||
|
2 -> "1X (720p)"
|
||||||
|
11 -> "1.25X (900p)"
|
||||||
|
3 -> "1.5X (1080p)" // PROJECT THOR: Optimal for AYN Thor
|
||||||
|
12 -> "1.75X (1260p)"
|
||||||
|
4 -> "2X (1440p)"
|
||||||
|
5 -> "3X (2160p)"
|
||||||
|
6 -> "4X (2880p)"
|
||||||
|
7 -> "5X"
|
||||||
|
8 -> "6X"
|
||||||
|
9 -> "7X"
|
||||||
|
10 -> "8X"
|
||||||
|
else -> "${resolutionIndex}X"
|
||||||
|
}
|
||||||
|
resolutionText.text = resolutionString
|
||||||
|
|
||||||
|
// Docked mode
|
||||||
|
val isDocked = BooleanSetting.DOCKED_MODE.getBoolean()
|
||||||
|
dockedText.text = if (isDocked) "Docked" else "Handheld"
|
||||||
|
|
||||||
|
// CPU Accuracy
|
||||||
|
val cpuAccuracy = IntSetting.CPU_ACCURACY.getInt()
|
||||||
|
val cpuAccuracyString = when (cpuAccuracy) {
|
||||||
|
0 -> "Auto"
|
||||||
|
1 -> "Accurate"
|
||||||
|
2 -> "Unsafe"
|
||||||
|
3 -> "Paranoid"
|
||||||
|
else -> "Unknown"
|
||||||
|
}
|
||||||
|
cpuAccuracyText.text = cpuAccuracyString
|
||||||
|
|
||||||
|
// GPU Driver (if custom driver is loaded)
|
||||||
|
val gpuDriver = NativeLibrary.getGpuDriver()
|
||||||
|
gpuDriverText.text = if (gpuDriver.isNotEmpty()) gpuDriver else "System Driver"
|
||||||
|
|
||||||
|
// ASTC Decode Mode
|
||||||
|
val astcMode = IntSetting.ANDROID_ASTC_MODE.getInt()
|
||||||
|
val astcModeString = when (astcMode) {
|
||||||
|
0 -> "Auto"
|
||||||
|
1 -> "Native"
|
||||||
|
2 -> "Decompress"
|
||||||
|
else -> "Auto"
|
||||||
|
}
|
||||||
|
astcModeText.text = astcModeString
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update real-time performance statistics
|
||||||
|
* Called every 500ms while emulation is running
|
||||||
|
*/
|
||||||
|
private fun updatePerformanceStats() {
|
||||||
|
try {
|
||||||
|
val perfStats = NativeLibrary.getPerfStats()
|
||||||
|
if (perfStats != null && perfStats.size >= 4) {
|
||||||
|
// perfStats format: [fps, emulationSpeed, frameTime, fifoPercentage]
|
||||||
|
val fps = perfStats[0]
|
||||||
|
val speed = perfStats[1]
|
||||||
|
val frameTime = perfStats[2]
|
||||||
|
|
||||||
|
fpsText.text = String.format("%.1f FPS", fps)
|
||||||
|
speedText.text = String.format("%.0f%%", speed * 100)
|
||||||
|
frameTimeText.text = String.format("%.2f ms", frameTime * 1000)
|
||||||
|
|
||||||
|
// Color code FPS based on performance
|
||||||
|
val fpsColor = when {
|
||||||
|
fps >= 58 -> 0xFF00FF00.toInt() // Green - excellent
|
||||||
|
fps >= 45 -> 0xFFFFFF00.toInt() // Yellow - acceptable
|
||||||
|
fps >= 30 -> 0xFFFF8800.toInt() // Orange - playable
|
||||||
|
else -> 0xFFFF0000.toInt() // Red - poor
|
||||||
|
}
|
||||||
|
fpsText.setTextColor(fpsColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shader compilation status
|
||||||
|
val shadersBuilding = NativeLibrary.getShadersBuilding()
|
||||||
|
if (shadersBuilding > 0) {
|
||||||
|
shaderText.text = context.getString(R.string.thor_shaders_building) + " ($shadersBuilding)"
|
||||||
|
shaderText.visibility = View.VISIBLE
|
||||||
|
shaderText.setTextColor(0xFFFFAA00.toInt()) // Orange
|
||||||
|
} else {
|
||||||
|
shaderText.visibility = View.GONE
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// Handle any native library exceptions gracefully
|
||||||
|
fpsText.text = "-- FPS"
|
||||||
|
speedText.text = "--%"
|
||||||
|
frameTimeText.text = "-- ms"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun startUpdates() {
|
||||||
|
isRunning = true
|
||||||
|
handler.post(updateRunnable)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stopUpdates() {
|
||||||
|
isRunning = false
|
||||||
|
handler.removeCallbacks(updateRunnable)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStop() {
|
||||||
|
super.onStop()
|
||||||
|
stopUpdates()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "ThorPresentation"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the device has a secondary display (like AYN Thor)
|
||||||
|
*/
|
||||||
|
fun hasSecondaryDisplay(context: Context): Boolean {
|
||||||
|
val displayManager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
|
||||||
|
val displays = displayManager.displays
|
||||||
|
return displays.size > 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the secondary display for Thor presentation
|
||||||
|
*/
|
||||||
|
fun getSecondaryDisplay(context: Context): Display? {
|
||||||
|
val displayManager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
|
||||||
|
val displays = displayManager.displays
|
||||||
|
// Find the first non-default display
|
||||||
|
return displays.firstOrNull { it.displayId != Display.DEFAULT_DISPLAY }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create and show the Thor presentation on the secondary display
|
||||||
|
*/
|
||||||
|
fun createIfAvailable(context: Context): ThorPresentation? {
|
||||||
|
val secondaryDisplay = getSecondaryDisplay(context) ?: return null
|
||||||
|
return ThorPresentation(context, secondaryDisplay)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
301
src/android/app/src/main/res/layout/thor_presentation.xml
Normal file
301
src/android/app/src/main/res/layout/thor_presentation.xml
Normal file
@@ -0,0 +1,301 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- PROJECT THOR: Dual Screen Dashboard Layout for AYN Thor -->
|
||||||
|
<!-- This layout is displayed on the secondary bottom screen during emulation -->
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="#CC000000"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<!-- Title Bar -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/thor_title"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/thor_dashboard_title"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_marginBottom="12dp" />
|
||||||
|
|
||||||
|
<!-- Main Stats Row -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center"
|
||||||
|
android:layout_marginBottom="8dp">
|
||||||
|
|
||||||
|
<!-- FPS -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:gravity="center">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/thor_fps"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="60.0 FPS"
|
||||||
|
android:textColor="#00FF00"
|
||||||
|
android:textSize="24sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
tools:text="60.0 FPS" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/thor_fps_label"
|
||||||
|
android:textColor="#888888"
|
||||||
|
android:textSize="10sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Divider -->
|
||||||
|
<View
|
||||||
|
android:layout_width="1dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:background="#444444" />
|
||||||
|
|
||||||
|
<!-- Speed -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:gravity="center">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/thor_speed"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="100%"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="24sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
tools:text="100%" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/thor_speed_label"
|
||||||
|
android:textColor="#888888"
|
||||||
|
android:textSize="10sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Divider -->
|
||||||
|
<View
|
||||||
|
android:layout_width="1dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:background="#444444" />
|
||||||
|
|
||||||
|
<!-- Frame Time -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:gravity="center">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/thor_frame_time"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="16.67 ms"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="24sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
tools:text="16.67 ms" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/thor_frame_time_label"
|
||||||
|
android:textColor="#888888"
|
||||||
|
android:textSize="10sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Shader Compilation Status (shown only when compiling) -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/thor_shader_status"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/thor_shaders_building"
|
||||||
|
android:textColor="#FFAA00"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:textStyle="italic"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<!-- Divider Line -->
|
||||||
|
<View
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:background="#444444"
|
||||||
|
android:layout_marginVertical="8dp" />
|
||||||
|
|
||||||
|
<!-- Settings Row -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center">
|
||||||
|
|
||||||
|
<!-- Resolution -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:gravity="center">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/thor_resolution"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="1.5X"
|
||||||
|
android:textColor="#00AAFF"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
tools:text="1.5X (1080p)" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/thor_resolution_label"
|
||||||
|
android:textColor="#888888"
|
||||||
|
android:textSize="10sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Docked State -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:gravity="center">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/thor_docked"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Docked"
|
||||||
|
android:textColor="#00AAFF"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
tools:text="Docked" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/thor_mode_label"
|
||||||
|
android:textColor="#888888"
|
||||||
|
android:textSize="10sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- CPU Accuracy -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:gravity="center">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/thor_cpu_accuracy"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Auto"
|
||||||
|
android:textColor="#00AAFF"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
tools:text="Auto" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/thor_cpu_label"
|
||||||
|
android:textColor="#888888"
|
||||||
|
android:textSize="10sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Second Settings Row -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center"
|
||||||
|
android:layout_marginTop="8dp">
|
||||||
|
|
||||||
|
<!-- GPU Driver -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:gravity="center">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/thor_gpu_driver"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Turnip"
|
||||||
|
android:textColor="#AA88FF"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:ellipsize="end"
|
||||||
|
tools:text="Turnip v24.1" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/thor_gpu_driver_label"
|
||||||
|
android:textColor="#888888"
|
||||||
|
android:textSize="10sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- ASTC Mode -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:gravity="center">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/thor_astc_mode"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Native"
|
||||||
|
android:textColor="#AA88FF"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
tools:text="Native" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/thor_astc_label"
|
||||||
|
android:textColor="#888888"
|
||||||
|
android:textSize="10sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Citron Branding -->
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/thor_branding"
|
||||||
|
android:textColor="#444444"
|
||||||
|
android:textSize="10sp"
|
||||||
|
android:layout_marginTop="12dp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@@ -1325,4 +1325,18 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||||||
<string name="crt_mask_aperture">Aperture Grille</string>
|
<string name="crt_mask_aperture">Aperture Grille</string>
|
||||||
<string name="crt_mask_shadow">Shadow Mask</string>
|
<string name="crt_mask_shadow">Shadow Mask</string>
|
||||||
|
|
||||||
|
<!-- PROJECT THOR: Dual Screen Dashboard Strings -->
|
||||||
|
<string name="thor_dashboard_title">CITRON PERFORMANCE DASHBOARD</string>
|
||||||
|
<string name="thor_fps_label">Frame Rate</string>
|
||||||
|
<string name="thor_speed_label">Speed</string>
|
||||||
|
<string name="thor_frame_time_label">Frame Time</string>
|
||||||
|
<string name="thor_resolution_label">Resolution</string>
|
||||||
|
<string name="thor_mode_label">Mode</string>
|
||||||
|
<string name="thor_cpu_label">CPU</string>
|
||||||
|
<string name="thor_gpu_driver_label">GPU Driver</string>
|
||||||
|
<string name="thor_astc_label">ASTC</string>
|
||||||
|
<string name="thor_shaders_building">Compiling Shaders…</string>
|
||||||
|
<string name="thor_branding">Powered by Citron</string>
|
||||||
|
<string name="thor_secondary_display_detected">Secondary display detected - Dashboard enabled</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
Reference in New Issue
Block a user