mirror of
https://git.citron-emu.org/Citron/Citron.git
synced 2025-02-12 11:36:26 +01:00
feat: Add RAM usage overlay and improve thermal display
- Add new RAM usage overlay showing current/max memory usage with color gradient - Simplify thermal overlay to show temperature in C/F with color indication - Add SHOW_RAM_OVERLAY boolean setting (disabled by default in UI) - Set default values for thermal overlay and black backgrounds to true - Update copyright headers to include Citron Emulator Project The RAM overlay displays native heap usage with color gradient from green (low usage) to red (high usage). The thermal display was simplified to show just temperatures while maintaining the color indication based on system thermal status.
This commit is contained in:
parent
137034ca2c
commit
0216eaa071
6 changed files with 95 additions and 26 deletions
|
@ -1,4 +1,5 @@
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||||
|
// SPDX-FileCopyrightText: 2025 Citron Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
package org.citron.citron_emu.features.settings.model
|
package org.citron.citron_emu.features.settings.model
|
||||||
|
@ -27,6 +28,7 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
|
||||||
SHOW_INPUT_OVERLAY("show_input_overlay"),
|
SHOW_INPUT_OVERLAY("show_input_overlay"),
|
||||||
TOUCHSCREEN("touchscreen"),
|
TOUCHSCREEN("touchscreen"),
|
||||||
SHOW_THERMAL_OVERLAY("show_thermal_overlay"),
|
SHOW_THERMAL_OVERLAY("show_thermal_overlay"),
|
||||||
|
SHOW_RAM_OVERLAY("show_ram_overlay"),
|
||||||
USE_AUTO_STUB("use_auto_stub");
|
USE_AUTO_STUB("use_auto_stub");
|
||||||
|
|
||||||
override fun getBoolean(needsGlobal: Boolean): Boolean =
|
override fun getBoolean(needsGlobal: Boolean): Boolean =
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||||
|
// SPDX-FileCopyrightText: 2025 Citron Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
package org.citron.citron_emu.fragments
|
package org.citron.citron_emu.fragments
|
||||||
|
@ -11,9 +12,11 @@ import android.content.Intent
|
||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
import android.content.pm.ActivityInfo
|
import android.content.pm.ActivityInfo
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
|
import android.graphics.Color
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.BatteryManager
|
import android.os.BatteryManager
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.os.Debug
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.os.PowerManager
|
import android.os.PowerManager
|
||||||
|
@ -68,6 +71,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
private var emulationActivity: EmulationActivity? = null
|
private var emulationActivity: EmulationActivity? = null
|
||||||
private var perfStatsUpdater: (() -> Unit)? = null
|
private var perfStatsUpdater: (() -> Unit)? = null
|
||||||
private var thermalStatsUpdater: (() -> Unit)? = null
|
private var thermalStatsUpdater: (() -> Unit)? = null
|
||||||
|
private var ramStatsUpdater: (() -> Unit)? = null
|
||||||
|
|
||||||
private var _binding: FragmentEmulationBinding? = null
|
private var _binding: FragmentEmulationBinding? = null
|
||||||
private val binding get() = _binding!!
|
private val binding get() = _binding!!
|
||||||
|
@ -83,6 +87,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
|
|
||||||
private lateinit var powerManager: PowerManager
|
private lateinit var powerManager: PowerManager
|
||||||
|
|
||||||
|
private val ramStatsUpdateHandler = Handler(Looper.myLooper()!!)
|
||||||
|
|
||||||
override fun onAttach(context: Context) {
|
override fun onAttach(context: Context) {
|
||||||
super.onAttach(context)
|
super.onAttach(context)
|
||||||
if (context is EmulationActivity) {
|
if (context is EmulationActivity) {
|
||||||
|
@ -376,6 +382,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
// Setup overlays
|
// Setup overlays
|
||||||
updateShowFpsOverlay()
|
updateShowFpsOverlay()
|
||||||
updateThermalOverlay()
|
updateThermalOverlay()
|
||||||
|
updateRamOverlay()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
emulationViewModel.isEmulationStopping.collect(viewLifecycleOwner) {
|
emulationViewModel.isEmulationStopping.collect(viewLifecycleOwner) {
|
||||||
|
@ -470,6 +477,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
|
if (ramStatsUpdater != null) {
|
||||||
|
ramStatsUpdateHandler.removeCallbacks(ramStatsUpdater!!)
|
||||||
|
}
|
||||||
_binding = null
|
_binding = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -552,8 +562,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
emulationViewModel.emulationStarted.value &&
|
emulationViewModel.emulationStarted.value &&
|
||||||
!emulationViewModel.isEmulationStopping.value
|
!emulationViewModel.isEmulationStopping.value
|
||||||
) {
|
) {
|
||||||
// Get thermal status
|
// Get thermal status for color
|
||||||
val thermalStatus = when (powerManager.currentThermalStatus) {
|
val thermalStatus = when (powerManager.currentThermalStatus) {
|
||||||
|
PowerManager.THERMAL_STATUS_NONE -> 0f
|
||||||
PowerManager.THERMAL_STATUS_LIGHT -> 0.25f
|
PowerManager.THERMAL_STATUS_LIGHT -> 0.25f
|
||||||
PowerManager.THERMAL_STATUS_MODERATE -> 0.5f
|
PowerManager.THERMAL_STATUS_MODERATE -> 0.5f
|
||||||
PowerManager.THERMAL_STATUS_SEVERE -> 0.75f
|
PowerManager.THERMAL_STATUS_SEVERE -> 0.75f
|
||||||
|
@ -563,34 +574,57 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
else -> 0f
|
else -> 0f
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert to Fahrenheit for additional info
|
// Convert to Fahrenheit
|
||||||
val fahrenheit = (temperature * 9f / 5f) + 32f
|
val fahrenheit = (temperature * 9f / 5f) + 32f
|
||||||
|
|
||||||
// Create progress bar using block elements
|
// Color based on thermal status (green to red)
|
||||||
val progressBarLength = 12
|
val red = (thermalStatus * 255).toInt()
|
||||||
val filledBars = (thermalStatus * progressBarLength).toInt()
|
val green = ((1f - thermalStatus) * 255).toInt()
|
||||||
val progressBar = buildString {
|
|
||||||
append("│") // Left border
|
|
||||||
repeat(filledBars) { append("█") }
|
|
||||||
repeat(progressBarLength - filledBars) { append("░") }
|
|
||||||
append("│") // Right border
|
|
||||||
append(" ")
|
|
||||||
append(String.format("%3d%%", (thermalStatus * 100).toInt()))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Color interpolation based on temperature (green at 30°C, red at 45°C)
|
|
||||||
val normalizedTemp = ((temperature - 30f) / 15f).coerceIn(0f, 1f)
|
|
||||||
val red = (normalizedTemp * 255).toInt()
|
|
||||||
val green = ((1f - normalizedTemp) * 255).toInt()
|
|
||||||
val color = android.graphics.Color.rgb(red, green, 0)
|
val color = android.graphics.Color.rgb(red, green, 0)
|
||||||
|
|
||||||
binding.showThermalsText.setTextColor(color)
|
binding.showThermalsText.setTextColor(color)
|
||||||
binding.showThermalsText.text = String.format(
|
binding.showThermalsText.text = String.format("%.1f°C • %.1f°F", temperature, fahrenheit)
|
||||||
"%s\n%.1f°C • %.1f°F",
|
}
|
||||||
progressBar,
|
}
|
||||||
temperature,
|
|
||||||
fahrenheit
|
private fun updateRamOverlay() {
|
||||||
|
val showOverlay = BooleanSetting.SHOW_RAM_OVERLAY.getBoolean()
|
||||||
|
binding.showRamText.setVisible(showOverlay)
|
||||||
|
if (showOverlay) {
|
||||||
|
ramStatsUpdater = {
|
||||||
|
if (emulationViewModel.emulationStarted.value &&
|
||||||
|
!emulationViewModel.isEmulationStopping.value
|
||||||
|
) {
|
||||||
|
val runtime = Runtime.getRuntime()
|
||||||
|
val nativeHeapSize = Debug.getNativeHeapSize()
|
||||||
|
val nativeHeapFreeSize = Debug.getNativeHeapFreeSize()
|
||||||
|
val nativeHeapUsed = nativeHeapSize - nativeHeapFreeSize
|
||||||
|
|
||||||
|
val usedMemInMB = nativeHeapUsed / 1048576L
|
||||||
|
val maxMemInMB = nativeHeapSize / 1048576L
|
||||||
|
val percentUsed = (nativeHeapUsed.toFloat() / nativeHeapSize.toFloat() * 100f)
|
||||||
|
|
||||||
|
// Color interpolation from green to red based on usage percentage
|
||||||
|
val normalizedUsage = (percentUsed / 100f).coerceIn(0f, 1f)
|
||||||
|
val red = (normalizedUsage * 255).toInt()
|
||||||
|
val green = ((1f - normalizedUsage) * 255).toInt()
|
||||||
|
val color = Color.rgb(red, green, 0)
|
||||||
|
|
||||||
|
binding.showRamText.setTextColor(color)
|
||||||
|
binding.showRamText.text = String.format(
|
||||||
|
"\nRAM: %d/%d MB (%.1f%%)",
|
||||||
|
usedMemInMB,
|
||||||
|
maxMemInMB,
|
||||||
|
percentUsed
|
||||||
)
|
)
|
||||||
|
ramStatsUpdateHandler.postDelayed(ramStatsUpdater!!, 1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ramStatsUpdateHandler.post(ramStatsUpdater!!)
|
||||||
|
} else {
|
||||||
|
if (ramStatsUpdater != null) {
|
||||||
|
ramStatsUpdateHandler.removeCallbacks(ramStatsUpdater!!)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -723,6 +757,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
BooleanSetting.SHOW_PERFORMANCE_OVERLAY.getBoolean()
|
BooleanSetting.SHOW_PERFORMANCE_OVERLAY.getBoolean()
|
||||||
findItem(R.id.thermal_indicator).isChecked =
|
findItem(R.id.thermal_indicator).isChecked =
|
||||||
BooleanSetting.SHOW_THERMAL_OVERLAY.getBoolean()
|
BooleanSetting.SHOW_THERMAL_OVERLAY.getBoolean()
|
||||||
|
findItem(R.id.ram_meter).apply {
|
||||||
|
isChecked = BooleanSetting.SHOW_RAM_OVERLAY.getBoolean()
|
||||||
|
isEnabled = false // This grays out the option
|
||||||
|
}
|
||||||
findItem(R.id.menu_rel_stick_center).isChecked =
|
findItem(R.id.menu_rel_stick_center).isChecked =
|
||||||
BooleanSetting.JOYSTICK_REL_CENTER.getBoolean()
|
BooleanSetting.JOYSTICK_REL_CENTER.getBoolean()
|
||||||
findItem(R.id.menu_dpad_slide).isChecked = BooleanSetting.DPAD_SLIDE.getBoolean()
|
findItem(R.id.menu_dpad_slide).isChecked = BooleanSetting.DPAD_SLIDE.getBoolean()
|
||||||
|
@ -749,6 +787,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
R.id.ram_meter -> {
|
||||||
|
// Do nothing since it's disabled
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
R.id.menu_edit_overlay -> {
|
R.id.menu_edit_overlay -> {
|
||||||
binding.drawerLayout.close()
|
binding.drawerLayout.close()
|
||||||
binding.surfaceInputOverlay.requestFocus()
|
binding.surfaceInputOverlay.requestFocus()
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||||
|
// SPDX-FileCopyrightText: 2025 Citron Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
@ -51,7 +52,7 @@ struct Values {
|
||||||
|
|
||||||
Settings::Setting<s32> theme{linkage, 0, "theme", Settings::Category::Android};
|
Settings::Setting<s32> theme{linkage, 0, "theme", Settings::Category::Android};
|
||||||
Settings::Setting<s32> theme_mode{linkage, -1, "theme_mode", Settings::Category::Android};
|
Settings::Setting<s32> theme_mode{linkage, -1, "theme_mode", Settings::Category::Android};
|
||||||
Settings::Setting<bool> black_backgrounds{linkage, false, "black_backgrounds",
|
Settings::Setting<bool> black_backgrounds{linkage, true, "black_backgrounds",
|
||||||
Settings::Category::Android};
|
Settings::Category::Android};
|
||||||
|
|
||||||
// Input/performance overlay settings
|
// Input/performance overlay settings
|
||||||
|
@ -67,7 +68,9 @@ struct Values {
|
||||||
Settings::Category::Overlay};
|
Settings::Category::Overlay};
|
||||||
Settings::Setting<bool> show_performance_overlay{linkage, true, "show_performance_overlay",
|
Settings::Setting<bool> show_performance_overlay{linkage, true, "show_performance_overlay",
|
||||||
Settings::Category::Overlay};
|
Settings::Category::Overlay};
|
||||||
Settings::Setting<bool> show_thermal_overlay{linkage, false, "show_thermal_overlay",
|
Settings::Setting<bool> show_thermal_overlay{linkage, true, "show_thermal_overlay",
|
||||||
|
Settings::Category::Overlay};
|
||||||
|
Settings::Setting<bool> show_ram_overlay{linkage, true, "show_ram_overlay",
|
||||||
Settings::Category::Overlay};
|
Settings::Category::Overlay};
|
||||||
Settings::Setting<bool> show_input_overlay{linkage, true, "show_input_overlay",
|
Settings::Setting<bool> show_input_overlay{linkage, true, "show_input_overlay",
|
||||||
Settings::Category::Overlay};
|
Settings::Category::Overlay};
|
||||||
|
|
|
@ -171,6 +171,21 @@
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
android:id="@+id/show_ram_text"
|
||||||
|
style="@style/TextAppearance.Material3.BodySmall"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="left"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
android:clickable="false"
|
||||||
|
android:focusable="false"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
android:shadowColor="@android:color/black"
|
||||||
|
android:shadowRadius="3"
|
||||||
|
android:layout_below="@id/show_fps_text"
|
||||||
|
tools:ignore="RtlHardcoded" />
|
||||||
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
|
||||||
<com.google.android.material.navigation.NavigationView
|
<com.google.android.material.navigation.NavigationView
|
||||||
|
|
|
@ -11,6 +11,11 @@
|
||||||
android:title="@string/emulation_thermal_indicator"
|
android:title="@string/emulation_thermal_indicator"
|
||||||
android:checkable="true" />
|
android:checkable="true" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/ram_meter"
|
||||||
|
android:title="@string/emulation_ram_meter"
|
||||||
|
android:checkable="true" />
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/menu_edit_overlay"
|
android:id="@+id/menu_edit_overlay"
|
||||||
android:title="@string/emulation_touch_overlay_edit" />
|
android:title="@string/emulation_touch_overlay_edit" />
|
||||||
|
|
|
@ -480,6 +480,7 @@
|
||||||
<string name="emulation_done">Done</string>
|
<string name="emulation_done">Done</string>
|
||||||
<string name="emulation_fps_counter">FPS counter</string>
|
<string name="emulation_fps_counter">FPS counter</string>
|
||||||
<string name="emulation_thermal_indicator">Thermal indicator</string>
|
<string name="emulation_thermal_indicator">Thermal indicator</string>
|
||||||
|
<string name="emulation_ram_meter">RAM meter</string>
|
||||||
<string name="emulation_toggle_controls">Toggle controls</string>
|
<string name="emulation_toggle_controls">Toggle controls</string>
|
||||||
<string name="emulation_rel_stick_center">Relative stick center</string>
|
<string name="emulation_rel_stick_center">Relative stick center</string>
|
||||||
<string name="emulation_dpad_slide">D-pad slide</string>
|
<string name="emulation_dpad_slide">D-pad slide</string>
|
||||||
|
|
Loading…
Reference in a new issue