mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2026-05-09 04:13:28 -05:00
Merge 49f124e19d into d19952cc11
This commit is contained in:
commit
28ec1654b0
|
|
@ -363,11 +363,11 @@ object NativeLibrary {
|
|||
@JvmStatic
|
||||
external fun StopEmulation()
|
||||
|
||||
/**
|
||||
* Ensures that IsRunning will return true from now on until emulation exits.
|
||||
* (If this is not called, IsRunning will start returning true at some point
|
||||
* after calling Run.)
|
||||
*/
|
||||
/**
|
||||
* Ensures that IsRunning will return true from now on until emulation exits.
|
||||
* (If this is not called, IsRunning will start returning true at some point
|
||||
* after calling Run.)
|
||||
*/
|
||||
@JvmStatic
|
||||
external fun SetIsBooting()
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
package org.dolphinemu.dolphinemu.activities
|
||||
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.graphics.Rect
|
||||
|
|
@ -14,13 +15,17 @@ import android.util.SparseIntArray
|
|||
import android.view.KeyEvent
|
||||
import android.view.MenuItem
|
||||
import android.view.MotionEvent
|
||||
import android.view.ScaleGestureDetector
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewGroup.MarginLayoutParams
|
||||
import android.view.WindowManager
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
import androidx.core.view.doOnLayout
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
|
|
@ -36,12 +41,16 @@ import org.dolphinemu.dolphinemu.R
|
|||
import org.dolphinemu.dolphinemu.databinding.ActivityEmulationBinding
|
||||
import org.dolphinemu.dolphinemu.databinding.DialogInputAdjustBinding
|
||||
import org.dolphinemu.dolphinemu.databinding.DialogNfcFiguresManagerBinding
|
||||
import org.dolphinemu.dolphinemu.features.gba.GbaLibrary
|
||||
import org.dolphinemu.dolphinemu.features.gba.GbaOverlayView
|
||||
import org.dolphinemu.dolphinemu.features.gba.GbaRenderManager
|
||||
import org.dolphinemu.dolphinemu.features.infinitybase.InfinityConfig
|
||||
import org.dolphinemu.dolphinemu.features.infinitybase.model.Figure
|
||||
import org.dolphinemu.dolphinemu.features.infinitybase.ui.FigureSlot
|
||||
import org.dolphinemu.dolphinemu.features.infinitybase.ui.FigureSlotAdapter
|
||||
import org.dolphinemu.dolphinemu.features.input.model.ControllerInterface
|
||||
import org.dolphinemu.dolphinemu.features.input.model.DolphinSensorEventListener
|
||||
import org.dolphinemu.dolphinemu.features.input.model.InputOverrider
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.IntSetting
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.Settings
|
||||
|
|
@ -72,6 +81,12 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider {
|
|||
|
||||
private lateinit var settings: Settings
|
||||
|
||||
private val gbaViews = mutableListOf<GbaOverlayView>()
|
||||
|
||||
private val lastGbaTapTimes = mutableMapOf<Int, Long>()
|
||||
|
||||
private var isGbaLocked = false
|
||||
|
||||
override var themeId = 0
|
||||
|
||||
private var menuVisible = false
|
||||
|
|
@ -186,6 +201,12 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider {
|
|||
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
// gba overlay setup, also clean up views from previous launch to prevent stacking
|
||||
GbaRenderManager.detach()
|
||||
gbaViews.forEach { binding.root.removeView(it) }
|
||||
gbaViews.clear()
|
||||
lastGbaTapTimes.clear()
|
||||
|
||||
MainPresenter.skipRescanningLibrary()
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
|
|
@ -211,6 +232,38 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider {
|
|||
binding = ActivityEmulationBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
//Read snap state before creating new views
|
||||
val globalGbaPrefs = getSharedPreferences("gba_overlay", Context.MODE_PRIVATE)
|
||||
isGbaLocked = globalGbaPrefs.getBoolean("gba_locked", false)
|
||||
|
||||
for (slot in 0 until 4) {
|
||||
if (IntSetting.getSettingForSIDevice(slot).int != InputOverlay.EMULATED_GBA_CONTROLLER) continue
|
||||
val view = GbaOverlayView(this)
|
||||
view.gbaSlot = slot
|
||||
val slotPrefs = getSharedPreferences("gba_overlay_${'$'}slot", Context.MODE_PRIVATE)
|
||||
val sw = slotPrefs.getFloat("gba_width", 480f).coerceIn(120f, 960f)
|
||||
val sh = slotPrefs.getFloat("gba_height", 320f).coerceIn(80f, 640f)
|
||||
val screenW = resources.displayMetrics.widthPixels.toFloat()
|
||||
val screenH = resources.displayMetrics.heightPixels.toFloat()
|
||||
var sx = slotPrefs.getFloat("gba_x", 16f + slot * 20f)
|
||||
var sy = slotPrefs.getFloat("gba_y", screenH - sh - 16f - slot * 20f)
|
||||
if (sx < 0 || sx > screenW) sx = 16f + slot * 20f
|
||||
if (sy < 0 || sy > screenH) sy = screenH - sh - 16f
|
||||
val params = FrameLayout.LayoutParams(sw.toInt(), sh.toInt())
|
||||
binding.root.addView(view, 0, params)
|
||||
view.x = sx;
|
||||
view.y = sy
|
||||
view.visibility = android.view.View.VISIBLE
|
||||
InputOverrider.registerGBA(slot)
|
||||
attachGbaTouchListener(view, slot, slotPrefs)
|
||||
gbaViews.add(view)
|
||||
}
|
||||
|
||||
if (gbaViews.isNotEmpty() && NativeLibrary.IsGameMetadataValid()) {
|
||||
GbaRenderManager.attach(gbaViews)
|
||||
binding.root.doOnLayout { applyGbaLayout() }
|
||||
}
|
||||
|
||||
setInsets()
|
||||
|
||||
// Find or create the EmulationFragment
|
||||
|
|
@ -332,6 +385,25 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider {
|
|||
emulationFragment?.refreshInputOverlay()
|
||||
|
||||
updateDisplaySettings()
|
||||
|
||||
val activeSlots = (0 until 4).filter {
|
||||
IntSetting.getSettingForSIDevice(it).int == InputOverlay.EMULATED_GBA_CONTROLLER
|
||||
}
|
||||
gbaViews.forEachIndexed { index, view ->
|
||||
if (index < activeSlots.size) {
|
||||
view.gbaSlot = activeSlots[index]
|
||||
view.visibility = android.view.View.VISIBLE
|
||||
InputOverrider.registerGBA(activeSlots[index])
|
||||
} else view.visibility = android.view.View.GONE
|
||||
}
|
||||
if (gbaViews.isNotEmpty()) {
|
||||
if (GbaRenderManager.isAttached()) {
|
||||
GbaRenderManager.updateViews(gbaViews)
|
||||
} else {
|
||||
GbaRenderManager.attach(gbaViews)
|
||||
}
|
||||
binding.root.post { applyGbaLayout() }
|
||||
}
|
||||
} catch (_: IllegalStateException) {
|
||||
// Most likely the core delivered an onTitleChanged while emulation was shutting down.
|
||||
// Let's just ignore it, since we're about to shut down anyway.
|
||||
|
|
@ -340,9 +412,41 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider {
|
|||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
GbaRenderManager.detach()
|
||||
for (slot in 0 until 4) {
|
||||
InputOverrider.unregisterGBA(slot)
|
||||
}
|
||||
gbaViews.forEach { binding.root.removeView(it) }
|
||||
gbaViews.clear()
|
||||
settings.close()
|
||||
}
|
||||
|
||||
override fun onConfigurationChanged(newConfig: android.content.res.Configuration) {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
if (gbaViews.isNotEmpty()) {
|
||||
GbaRenderManager.updateViews(gbaViews)
|
||||
binding.root.post { applyGbaLayout() }
|
||||
|
||||
} else {
|
||||
//no gba - restore game to full screen to try stop portrait squish into landscape
|
||||
binding.frameEmulationFragment.x = 0f
|
||||
binding.frameEmulationFragment.layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT
|
||||
binding.frameEmulationFragment.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
|
||||
binding.frameEmulationFragment.requestLayout()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setGbaViewsTouchable(touchable: Boolean) {
|
||||
gbaViews.forEach { view ->
|
||||
view.isClickable = touchable
|
||||
view.isFocusable = touchable
|
||||
view.isFocusableInTouchMode = touchable
|
||||
if (!touchable) {
|
||||
view.setOnTouchListener(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
if (!closeSubmenu()) {
|
||||
toggleMenu()
|
||||
|
|
@ -361,7 +465,8 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider {
|
|||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||
WindowInsetsControllerCompat(window, window.decorView).let { controller ->
|
||||
controller.hide(WindowInsetsCompat.Type.systemBars())
|
||||
controller.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
||||
controller.systemBarsBehavior =
|
||||
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -443,6 +548,7 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider {
|
|||
menu.findItem(R.id.menu_emulation_ir_recenter).isChecked =
|
||||
BooleanSetting.MAIN_IR_ALWAYS_RECENTER.boolean
|
||||
}
|
||||
menu.findItem(R.id.menu_emulation_gba_snap)?.isChecked = isGbaLocked
|
||||
popup.setOnMenuItemClickListener { item: MenuItem -> onOptionsItemSelected(item) }
|
||||
popup.show()
|
||||
}
|
||||
|
|
@ -471,6 +577,11 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider {
|
|||
item.isChecked = !item.isChecked
|
||||
toggleRecenter(item.isChecked)
|
||||
}
|
||||
|
||||
MENU_ACTION_GBA_SNAP -> {
|
||||
item.isChecked = !item.isChecked
|
||||
toggleGBASnap()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -518,6 +629,14 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider {
|
|||
MENU_ACTION_SKYLANDERS -> showSkylanderPortalSettings()
|
||||
MENU_ACTION_INFINITY_BASE -> showInfinityBaseSettings()
|
||||
MENU_ACTION_EXIT -> emulationFragment!!.stopEmulation()
|
||||
MENU_ACTION_GBA_SNAP -> toggleGBASnap()
|
||||
MENU_ACTION_GBA_RESET -> {
|
||||
isGbaLocked = false;
|
||||
resetGBAScreens();
|
||||
binding.root.post { applyGbaLayout() }
|
||||
}
|
||||
|
||||
MENU_ACTION_GBA_RESET_CORE -> resetGbaCore()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -533,9 +652,21 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider {
|
|||
private fun editControlsPlacement() {
|
||||
if (emulationFragment!!.isConfiguringControls) {
|
||||
emulationFragment?.stopConfiguringControls()
|
||||
setGbaViewsTouchable(true)
|
||||
if (!isGbaLocked) {
|
||||
gbaViews.forEachIndexed { _, view ->
|
||||
val slot = view.gbaSlot
|
||||
val slotPrefs = getSharedPreferences(
|
||||
"gba_overlay_${'$'}slot",
|
||||
Context.MODE_PRIVATE
|
||||
)
|
||||
attachGbaTouchListener(view, slot, slotPrefs)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
closeSubmenu()
|
||||
closeMenu()
|
||||
setGbaViewsTouchable(false)
|
||||
emulationFragment?.startConfiguringControls()
|
||||
}
|
||||
}
|
||||
|
|
@ -571,12 +702,14 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider {
|
|||
emulationFragment?.refreshInputOverlay()
|
||||
}
|
||||
}
|
||||
|
||||
InputOverlay.OVERLAY_WIIMOTE_CLASSIC -> {
|
||||
val wiiClassicLatchingButtons = BooleanArray(11)
|
||||
val classicSettingBase = "MAIN_BUTTON_LATCHING_CLASSIC_"
|
||||
|
||||
for (i in wiiClassicLatchingButtons.indices) {
|
||||
wiiClassicLatchingButtons[i] = BooleanSetting.valueOf(classicSettingBase + i).boolean
|
||||
wiiClassicLatchingButtons[i] =
|
||||
BooleanSetting.valueOf(classicSettingBase + i).boolean
|
||||
}
|
||||
builder.setMultiChoiceItems(
|
||||
R.array.classicLatchableButtons, wiiClassicLatchingButtons
|
||||
|
|
@ -586,6 +719,7 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider {
|
|||
emulationFragment?.refreshInputOverlay()
|
||||
}
|
||||
}
|
||||
|
||||
InputOverlay.OVERLAY_WIIMOTE_NUNCHUK -> {
|
||||
val nunchukLatchingButtons = BooleanArray(9)
|
||||
val nunchukSettingBase = "MAIN_BUTTON_LATCHING_WII_"
|
||||
|
|
@ -603,21 +737,27 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider {
|
|||
builder.setMultiChoiceItems(
|
||||
R.array.nunchukLatchableButtons, nunchukLatchingButtons
|
||||
) { _: DialogInterface?, indexSelected: Int, isChecked: Boolean ->
|
||||
BooleanSetting.valueOf(nunchukSettingBase + translateToSettingsIndex(indexSelected))
|
||||
BooleanSetting.valueOf(
|
||||
nunchukSettingBase + translateToSettingsIndex(
|
||||
indexSelected
|
||||
)
|
||||
)
|
||||
.setBoolean(settings, isChecked)
|
||||
emulationFragment?.refreshInputOverlay()
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
val wiimoteLatchingButtons = BooleanArray(7)
|
||||
val wiimoteSettingBase = "MAIN_BUTTON_LATCHING_WII_"
|
||||
|
||||
for (i in wiimoteLatchingButtons.indices) {
|
||||
wiimoteLatchingButtons[i] = BooleanSetting.valueOf(wiimoteSettingBase + i).boolean
|
||||
wiimoteLatchingButtons[i] =
|
||||
BooleanSetting.valueOf(wiimoteSettingBase + i).boolean
|
||||
}
|
||||
|
||||
builder.setMultiChoiceItems(
|
||||
R.array.wiimoteLatchableButtons, wiimoteLatchingButtons
|
||||
R.array.wiimoteLatchableButtons, wiimoteLatchingButtons
|
||||
) { _: DialogInterface?, indexSelected: Int, isChecked: Boolean ->
|
||||
BooleanSetting.valueOf(wiimoteSettingBase + indexSelected)
|
||||
.setBoolean(settings, isChecked)
|
||||
|
|
@ -932,7 +1072,9 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider {
|
|||
when (position) {
|
||||
0 -> infinityFigures[position].label = getString(R.string.infinity_hexagon_label)
|
||||
1 -> infinityFigures[position].label = getString(R.string.infinity_power_hex_two_label)
|
||||
2 -> infinityFigures[position].label = getString(R.string.infinity_power_hex_three_label)
|
||||
2 -> infinityFigures[position].label =
|
||||
getString(R.string.infinity_power_hex_three_label)
|
||||
|
||||
3 -> infinityFigures[position].label = getString(R.string.infinity_p1_label)
|
||||
4 -> infinityFigures[position].label = getString(R.string.infinity_p1a1_label)
|
||||
5 -> infinityFigures[position].label = getString(R.string.infinity_p1a2_label)
|
||||
|
|
@ -975,6 +1117,22 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider {
|
|||
if (anyMenuClosed)
|
||||
return true
|
||||
}
|
||||
|
||||
if (!isGbaLocked && gbaViews.isNotEmpty()) {
|
||||
val loc = IntArray(2)
|
||||
for (gbaView in gbaViews) {
|
||||
gbaView.getLocationOnScreen(loc)
|
||||
val bounds = android.graphics.Rect(
|
||||
loc[0],
|
||||
loc[1],
|
||||
loc[0] + gbaView.width,
|
||||
loc[1] + gbaView.height
|
||||
)
|
||||
if (bounds.contains(event.rawX.toInt(), event.rawY.toInt())) {
|
||||
return gbaView.dispatchTouchEvent(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.dispatchTouchEvent(event)
|
||||
}
|
||||
|
||||
|
|
@ -1016,6 +1174,246 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider {
|
|||
this.themeId = themeId
|
||||
}
|
||||
|
||||
// gba touch for scale/drag listener while in unlocked gba mode.
|
||||
private fun attachGbaTouchListener(
|
||||
view: GbaOverlayView,
|
||||
slot: Int,
|
||||
slotPrefs: android.content.SharedPreferences
|
||||
) {
|
||||
var dragX = 0f
|
||||
var dragY = 0f
|
||||
|
||||
// Initial dimensions for scaling math
|
||||
val params = view.layoutParams as FrameLayout.LayoutParams
|
||||
var cw = params.width.toFloat()
|
||||
var ch = params.height.toFloat()
|
||||
|
||||
val scaleDetector = ScaleGestureDetector(
|
||||
this,
|
||||
object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
|
||||
override fun onScale(d: ScaleGestureDetector): Boolean {
|
||||
if (isGbaLocked) return true
|
||||
val sf = d.scaleFactor
|
||||
val ow = cw
|
||||
val oh = ch
|
||||
|
||||
// Scale width and maintain 3:2 aspect ratio
|
||||
cw = (cw * sf).coerceIn(120f, 960f)
|
||||
ch = cw * (2f / 3f)
|
||||
|
||||
// Center the scaling transformation
|
||||
view.x += (ow - cw) / 2f
|
||||
view.y += (oh - ch) / 2f
|
||||
|
||||
val p = view.layoutParams as FrameLayout.LayoutParams
|
||||
p.width = cw.toInt()
|
||||
p.height = ch.toInt()
|
||||
view.layoutParams = p
|
||||
|
||||
slotPrefs.edit()
|
||||
.putFloat("gba_width", cw)
|
||||
.putFloat("gba_height", ch)
|
||||
.putFloat("gba_x", view.x)
|
||||
.putFloat("gba_y", view.y)
|
||||
.apply()
|
||||
return true
|
||||
}
|
||||
})
|
||||
|
||||
view.setOnTouchListener { v, event ->
|
||||
// Prevent interaction if Snap Mode (Locked) is active
|
||||
if (isGbaLocked) return@setOnTouchListener false
|
||||
|
||||
// Let the scale detector handle pinch gestures first
|
||||
scaleDetector.onTouchEvent(event)
|
||||
|
||||
// If the user is currently pinching/scaling, stop the dragging logic
|
||||
if (scaleDetector.isInProgress) return@setOnTouchListener true
|
||||
|
||||
when (event.actionMasked) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
// Store the offset so the window doesn't "jump" to the finger center
|
||||
dragX = event.rawX - v.x
|
||||
dragY = event.rawY - v.y
|
||||
}
|
||||
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
// Update the view's position as the finger moves
|
||||
v.x = event.rawX - dragX
|
||||
v.y = event.rawY - dragY
|
||||
}
|
||||
|
||||
MotionEvent.ACTION_UP -> {
|
||||
// Double tap detection for visibility toggle
|
||||
val now = System.currentTimeMillis()
|
||||
val last = lastGbaTapTimes[slot] ?: 0L
|
||||
if (now - last < 300) {
|
||||
view.onDoubleTap()
|
||||
}
|
||||
lastGbaTapTimes[slot] = now
|
||||
|
||||
slotPrefs.edit()
|
||||
.putFloat("gba_x", v.x)
|
||||
.putFloat("gba_y", v.y)
|
||||
.apply()
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
//Android Gba layout
|
||||
private fun applyGbaLayout() {
|
||||
if (gbaViews.isEmpty())
|
||||
return
|
||||
val tw = binding.root.width
|
||||
val th = binding.root.height
|
||||
val count = gbaViews.size
|
||||
val isLandscape =
|
||||
resources.configuration.orientation == android.content.res.Configuration.ORIENTATION_LANDSCAPE
|
||||
|
||||
binding.frameEmulationFragment.x = 0f
|
||||
binding.frameEmulationFragment.layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT
|
||||
binding.frameEmulationFragment.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
|
||||
binding.frameEmulationFragment.requestLayout()
|
||||
|
||||
if (isGbaLocked) {
|
||||
if (isLandscape) {
|
||||
val slotH = th / count
|
||||
val maxW = (tw * 0.45f).toInt()
|
||||
// Set point for landscape height
|
||||
val maxH = 300
|
||||
|
||||
gbaViews.forEachIndexed { i, v ->
|
||||
v.setOnTouchListener(null)
|
||||
val p = v.layoutParams as FrameLayout.LayoutParams
|
||||
|
||||
// Calculate height first, then width based on 3:2 ratio
|
||||
var targetH = slotH.coerceAtMost(maxH)
|
||||
var targetW = (targetH * 3f / 2f).toInt()
|
||||
|
||||
// If it's too wide for the sidebar, scale down based on width
|
||||
if (targetW > maxW) {
|
||||
targetW = maxW
|
||||
targetH = (targetW * 2f / 3f).toInt()
|
||||
}
|
||||
|
||||
p.width = targetW
|
||||
p.height = targetH
|
||||
v.layoutParams = p
|
||||
|
||||
// Re-snapping: Force X to 0 and calculate centered Y within the slot
|
||||
v.x = 0f
|
||||
v.y = (i * slotH).toFloat() + (slotH - targetH) / 2f
|
||||
v.visibility = android.view.View.VISIBLE
|
||||
}
|
||||
} else {
|
||||
// Portrait Logic
|
||||
val gih = (tw * 3f / 4f).toInt()
|
||||
val topBar = (th - gih) / 2
|
||||
val gbaY = topBar + gih
|
||||
val availH = th - gbaY
|
||||
// Set point for portrait height
|
||||
val maxH = 400
|
||||
|
||||
when (count) {
|
||||
1 -> {
|
||||
val targetH = (tw * 2f / 3f).toInt().coerceAtMost(availH).coerceAtMost(maxH)
|
||||
val targetW = (targetH * 3f / 2f).toInt()
|
||||
|
||||
with(gbaViews[0]) {
|
||||
setOnTouchListener(null)
|
||||
val p = layoutParams as FrameLayout.LayoutParams
|
||||
p.width = targetW; p.height = targetH; layoutParams = p
|
||||
x = (tw - targetW) / 2f
|
||||
y = gbaY.toFloat() + (availH - targetH) / 2f
|
||||
visibility = android.view.View.VISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
// Multi-screen grid (2, 3, or 4)
|
||||
val cols = if (count <= 2) count else 2
|
||||
val rows = if (count <= 2) 1 else 2
|
||||
val slotW = tw / cols
|
||||
val slotH = availH / rows
|
||||
|
||||
val targetH =
|
||||
(slotW * 2f / 3f).toInt().coerceAtMost(slotH).coerceAtMost(maxH)
|
||||
val targetW = (targetH * 3f / 2f).toInt()
|
||||
|
||||
gbaViews.forEachIndexed { i, v ->
|
||||
v.setOnTouchListener(null)
|
||||
val p = v.layoutParams as FrameLayout.LayoutParams
|
||||
p.width = targetW; p.height = targetH; v.layoutParams = p
|
||||
|
||||
val col = i % cols
|
||||
val row = i / cols
|
||||
|
||||
v.x = (col * slotW).toFloat() + (slotW - targetW) / 2f
|
||||
v.y = gbaY.toFloat() + (row * slotH).toFloat() + (slotH - targetH) / 2f
|
||||
v.visibility = android.view.View.VISIBLE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
gbaViews.forEachIndexed { i, view ->
|
||||
val slot = view.gbaSlot
|
||||
val sp2 = getSharedPreferences("gba_overlay_${slot}", Context.MODE_PRIVATE)
|
||||
val sw = sp2.getFloat("gba_width", 480f).coerceIn(120f, 960f)
|
||||
val sh = sp2.getFloat("gba_height", 320f).coerceIn(80f, 640f)
|
||||
val screenW = resources.displayMetrics.widthPixels.toFloat()
|
||||
val screenH = resources.displayMetrics.heightPixels.toFloat()
|
||||
var sx = sp2.getFloat("gba_x", 16f + i * 20f)
|
||||
var sy = sp2.getFloat("gba_y", screenH - sh - 16f - i * 20f)
|
||||
if (sx < 0 || sx > screenW) sx = 16f + i * 20f
|
||||
if (sy < 0 || sy > screenH) sy = screenH - sh - 16f
|
||||
val p = view.layoutParams as FrameLayout.LayoutParams
|
||||
p.width = sw.toInt(); p.height = sh.toInt(); view.layoutParams = p
|
||||
view.x = sx; view.y = sy
|
||||
attachGbaTouchListener(view, slot, sp2)
|
||||
}
|
||||
}
|
||||
getSharedPreferences("gba_overlay", Context.MODE_PRIVATE).edit()
|
||||
.putBoolean("gba_locked", isGbaLocked).apply()
|
||||
}
|
||||
|
||||
private fun toggleGBASnap() {
|
||||
isGbaLocked = !isGbaLocked
|
||||
if (!isGbaLocked) {
|
||||
NativeLibrary.SetObscuredPixelsLeft(0)
|
||||
}
|
||||
binding.root.post { applyGbaLayout() }
|
||||
|
||||
}
|
||||
|
||||
private fun resetGBAScreens() {
|
||||
if (gbaViews.isEmpty()) return
|
||||
runOnUiThread {
|
||||
val screenH = resources.displayMetrics.heightPixels.toFloat()
|
||||
gbaViews.forEachIndexed { i, view ->
|
||||
val slot = view.gbaSlot
|
||||
val dx = 16f + i * 20f
|
||||
val dy = screenH - 320f - 16f - i * 20f
|
||||
getSharedPreferences("gba_overlay_${slot}", Context.MODE_PRIVATE).edit()
|
||||
.putFloat("gba_x", dx).putFloat("gba_y", dy).putFloat("gba_width", 480f)
|
||||
.putFloat("gba_height", 320f).apply()
|
||||
val p = view.layoutParams as? FrameLayout.LayoutParams ?: return@forEachIndexed
|
||||
p.width = 480; p.height = 320; view.layoutParams = p
|
||||
view.x = dx; view.y = dy
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun resetGbaCore() {
|
||||
for (slot in 0 until 4) {
|
||||
if (IntSetting.getSettingForSIDevice(slot).int == InputOverlay.EMULATED_GBA_CONTROLLER)
|
||||
GbaLibrary.resetGbaCore(slot)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
private const val BACKSTACK_NAME_MENU = "menu"
|
||||
private const val BACKSTACK_NAME_SUBMENU = "submenu"
|
||||
|
|
@ -1077,6 +1475,9 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider {
|
|||
const val MENU_ACTION_SKYLANDERS = 36
|
||||
const val MENU_ACTION_INFINITY_BASE = 37
|
||||
const val MENU_ACTION_LATCHING_CONTROLS = 38
|
||||
const val MENU_ACTION_GBA_SNAP = 39
|
||||
const val MENU_ACTION_GBA_RESET = 40
|
||||
const val MENU_ACTION_GBA_RESET_CORE = 41
|
||||
|
||||
init {
|
||||
buttonsActionsMap.apply {
|
||||
|
|
@ -1090,19 +1491,38 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider {
|
|||
append(R.id.menu_emulation_ir_recenter, MENU_SET_IR_RECENTER)
|
||||
append(R.id.menu_emulation_set_ir_mode, MENU_SET_IR_MODE)
|
||||
append(R.id.menu_emulation_choose_doubletap, MENU_ACTION_CHOOSE_DOUBLETAP)
|
||||
append(R.id.menu_emulation_gba_snap, MENU_ACTION_GBA_SNAP)
|
||||
append(R.id.menu_emulation_gba_reset, MENU_ACTION_GBA_RESET)
|
||||
append(R.id.menu_emulation_gba_reset_core, MENU_ACTION_GBA_RESET_CORE)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun launch(activity: FragmentActivity, filePaths: Array<String>, riivolution: Boolean, fromIntent: Boolean = false) {
|
||||
fun launch(
|
||||
activity: FragmentActivity,
|
||||
filePaths: Array<String>,
|
||||
riivolution: Boolean,
|
||||
fromIntent: Boolean = false
|
||||
) {
|
||||
if (ignoreLaunchRequests)
|
||||
return
|
||||
|
||||
performLaunchChecks(activity, fromIntent) { launchWithoutChecks(activity, filePaths, riivolution) }
|
||||
performLaunchChecks(activity, fromIntent) {
|
||||
launchWithoutChecks(
|
||||
activity,
|
||||
filePaths,
|
||||
riivolution
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun launch(activity: FragmentActivity, filePath: String, riivolution: Boolean, fromIntent: Boolean = false) =
|
||||
fun launch(
|
||||
activity: FragmentActivity,
|
||||
filePath: String,
|
||||
riivolution: Boolean,
|
||||
fromIntent: Boolean = false
|
||||
) =
|
||||
launch(activity, arrayOf(filePath), riivolution, fromIntent)
|
||||
|
||||
private fun launchWithoutChecks(
|
||||
|
|
@ -1117,7 +1537,11 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider {
|
|||
activity.startActivity(launcher)
|
||||
}
|
||||
|
||||
private fun performLaunchChecks(activity: FragmentActivity, fromIntent: Boolean, continueCallback: Runnable) {
|
||||
private fun performLaunchChecks(
|
||||
activity: FragmentActivity,
|
||||
fromIntent: Boolean,
|
||||
continueCallback: Runnable
|
||||
) {
|
||||
AfterDirectoryInitializationRunner().runWithLifecycle(activity) {
|
||||
if (fromIntent) {
|
||||
activity.finish()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
package org.dolphinemu.dolphinemu.features.gba
|
||||
|
||||
import androidx.annotation.Keep
|
||||
import org.dolphinemu.dolphinemu.features.gba.GbaRenderManager
|
||||
|
||||
object GbaLibrary{
|
||||
|
||||
@JvmStatic
|
||||
external fun copyGbaFramebuffer(slot: Int, buffer: java.nio.ByteBuffer): Boolean
|
||||
|
||||
@JvmStatic
|
||||
external fun resetGbaCore(slot: Int)
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun onGbaFrame(slot: Int) {
|
||||
GbaRenderManager.onFrame(slot)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
package org.dolphinemu.dolphinemu.features.gba
|
||||
|
||||
import android.content.Context
|
||||
import android.view.SurfaceHolder
|
||||
import android.view.SurfaceView
|
||||
import android.graphics.*
|
||||
|
||||
//Passive surfaceview displaying one gba screen
|
||||
//Rendering is driven by GbaRenderManager
|
||||
|
||||
class GbaOverlayView(context: Context) : SurfaceView(context),
|
||||
SurfaceHolder.Callback {
|
||||
var renderManager: GbaRenderManager? = null
|
||||
var gbaSlot: Int = 0
|
||||
var isScreenVisible = true
|
||||
var needsBorderRedraw = false
|
||||
var surfaceReady = false
|
||||
|
||||
private val paint = Paint().apply { isFilterBitmap = true }
|
||||
private val destRect = Rect()
|
||||
private val borderPaint = Paint().apply {
|
||||
color = Color.argb(120, 255, 255, 255); style = Paint.Style.STROKE
|
||||
strokeWidth = 2f; isAntiAlias = true
|
||||
}
|
||||
private val borderFillPaint = Paint().apply {
|
||||
color = Color.argb(0, 0, 0, 0); style = Paint.Style.FILL
|
||||
}
|
||||
private val borderTextPaint = Paint().apply {
|
||||
color = Color.argb(120, 255, 255, 255); textSize = 20f
|
||||
isAntiAlias = true; typeface = Typeface.DEFAULT_BOLD
|
||||
textAlign = Paint.Align.CENTER
|
||||
}
|
||||
|
||||
init {
|
||||
holder.setFormat(PixelFormat.TRANSLUCENT)
|
||||
setZOrderMediaOverlay(true)
|
||||
holder.addCallback(this)
|
||||
}
|
||||
|
||||
fun drawFrame(bitmap: Bitmap) {
|
||||
if (!holder.surface.isValid) return
|
||||
if (!isScreenVisible) {
|
||||
if (needsBorderRedraw) {
|
||||
needsBorderRedraw = false
|
||||
val canvas = holder.lockCanvas() ?: return
|
||||
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)
|
||||
val rect = RectF(2f, 2f, width - 2f, height - 2f)
|
||||
canvas.drawRoundRect(rect, 12f, 12f, borderFillPaint)
|
||||
canvas.drawRoundRect(rect, 12f, 12f, borderPaint)
|
||||
canvas.drawText("GBA", width / 2f, height / 2f + 8f, borderTextPaint)
|
||||
holder.unlockCanvasAndPost(canvas)
|
||||
}
|
||||
return
|
||||
}
|
||||
val canvas = holder.lockCanvas() ?: return
|
||||
destRect.set(0, 0, width, height)
|
||||
canvas.drawColor(Color.BLACK)
|
||||
canvas.drawBitmap(bitmap, null, destRect, paint)
|
||||
holder.unlockCanvasAndPost(canvas)
|
||||
}
|
||||
|
||||
fun onDoubleTap() {
|
||||
isScreenVisible = !isScreenVisible
|
||||
if (!isScreenVisible) needsBorderRedraw = true
|
||||
}
|
||||
|
||||
override fun surfaceCreated(h: SurfaceHolder) {
|
||||
surfaceReady = false
|
||||
}
|
||||
|
||||
override fun surfaceChanged(h: SurfaceHolder, f: Int, w: Int, h2: Int) {
|
||||
surfaceReady = true
|
||||
post { renderManager?.requestRedraw(gbaSlot) }
|
||||
}
|
||||
|
||||
override fun surfaceDestroyed(h: SurfaceHolder) {
|
||||
surfaceReady = false
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
package org.dolphinemu.dolphinemu.features.gba
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.os.Handler
|
||||
import android.os.HandlerThread
|
||||
import org.dolphinemu.dolphinemu.NativeLibrary
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
object GbaRenderManager {
|
||||
|
||||
private val buffers = Array(4) { ByteBuffer.allocateDirect(240 * 160 * 4) }
|
||||
private val bitmaps = Array(4) { Bitmap.createBitmap(240, 160, Bitmap.Config.ARGB_8888) }
|
||||
private val renderThread = HandlerThread("GBA_RENDER").apply { start() }
|
||||
private val handler = Handler(renderThread.looper)
|
||||
|
||||
@Volatile
|
||||
private var activeViews: List<GbaOverlayView> = emptyList()
|
||||
|
||||
@Volatile
|
||||
private var attached = false
|
||||
|
||||
fun isAttached() = attached
|
||||
|
||||
fun onFrame(slot: Int) {
|
||||
if (!attached || slot < 0 || slot >= 4) return
|
||||
handler.post { renderFrame(slot, forceRedraw = false) }
|
||||
}
|
||||
|
||||
private fun renderFrame(slot: Int, forceRedraw: Boolean = false) {
|
||||
val view = activeViews.firstOrNull { it.gbaSlot == slot } ?: return
|
||||
if (!view.holder.surface.isValid) return
|
||||
val buffer = buffers[slot]
|
||||
buffer.rewind()
|
||||
if (GbaLibrary.copyGbaFramebuffer(slot, buffer)) {
|
||||
buffer.rewind()
|
||||
bitmaps[slot].copyPixelsFromBuffer(buffer)
|
||||
view.drawFrame(bitmaps[slot])
|
||||
} else if (forceRedraw) {
|
||||
view.drawFrame(bitmaps[slot])
|
||||
}
|
||||
}
|
||||
|
||||
fun requestRedraw(slot: Int) {
|
||||
if (!attached || slot < 0 || slot >= 4) return
|
||||
handler.post { renderFrame(slot, forceRedraw = true) }
|
||||
}
|
||||
|
||||
fun attach(views: List<GbaOverlayView>) {
|
||||
handler.removeCallbacksAndMessages(null)
|
||||
attached = true
|
||||
activeViews = views
|
||||
views.forEach {
|
||||
it.renderManager = this
|
||||
if (it.surfaceReady) requestRedraw(it.gbaSlot)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateViews(views: List<GbaOverlayView>) {
|
||||
activeViews = views
|
||||
views.forEach { it.renderManager = this }
|
||||
views.forEach { if (it.surfaceReady) requestRedraw(it.gbaSlot) }
|
||||
}
|
||||
|
||||
fun detach() {
|
||||
handler.removeCallbacksAndMessages(null)
|
||||
//renderThread.quitSafely()
|
||||
activeViews = emptyList()
|
||||
attached = false
|
||||
}
|
||||
}
|
||||
|
|
@ -11,6 +11,10 @@ object InputOverrider {
|
|||
|
||||
external fun unregisterWii(controllerIndex: Int)
|
||||
|
||||
external fun registerGBA(controllerIndex: Int)
|
||||
|
||||
external fun unregisterGBA(controllerIndex: Int)
|
||||
|
||||
external fun setControlState(controllerIndex: Int, control: Int, state: Double)
|
||||
|
||||
external fun clearControlState(controllerIndex: Int, control: Int)
|
||||
|
|
|
|||
|
|
@ -72,6 +72,10 @@ enum class StringSetting(
|
|||
MAIN_GBA_BIOS_PATH(Settings.FILE_DOLPHIN, Settings.SECTION_INI_GBA, "BIOS", ""),
|
||||
MAIN_GB_PLAYER_ROM(Settings.FILE_DOLPHIN, Settings.SECTION_INI_GBA, "GBPlayerRom", ""),
|
||||
MAIN_GBA_SAVES_PATH(Settings.FILE_DOLPHIN, Settings.SECTION_INI_GBA, "SavesPath", ""),
|
||||
MAIN_GBA_ROM_PATH_1(Settings.FILE_DOLPHIN, Settings.SECTION_INI_GBA, "Rom1", ""),
|
||||
MAIN_GBA_ROM_PATH_2(Settings.FILE_DOLPHIN, Settings.SECTION_INI_GBA, "Rom2", ""),
|
||||
MAIN_GBA_ROM_PATH_3(Settings.FILE_DOLPHIN, Settings.SECTION_INI_GBA, "Rom3", ""),
|
||||
MAIN_GBA_ROM_PATH_4(Settings.FILE_DOLPHIN, Settings.SECTION_INI_GBA, "Rom4", ""),
|
||||
MAIN_TRIFORCE_IP_REDIRECTIONS(
|
||||
Settings.FILE_DOLPHIN,
|
||||
Settings.SECTION_INI_CORE,
|
||||
|
|
@ -137,6 +141,14 @@ enum class StringSetting(
|
|||
MAIN_GFX_BACKEND
|
||||
)
|
||||
|
||||
fun getGBARomPath(slot: Int): StringSetting = when (slot) {
|
||||
0 -> MAIN_GBA_ROM_PATH_1
|
||||
1 -> MAIN_GBA_ROM_PATH_2
|
||||
2 -> MAIN_GBA_ROM_PATH_3
|
||||
3 -> MAIN_GBA_ROM_PATH_4
|
||||
else -> MAIN_GBA_ROM_PATH_1
|
||||
}
|
||||
|
||||
private val NOT_RUNTIME_EDITABLE: Set<StringSetting> =
|
||||
HashSet(listOf(*NOT_RUNTIME_EDITABLE_ARRAY))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -251,6 +251,7 @@ class SettingsAdapter(
|
|||
slider.valueTo = item.max
|
||||
slider.stepSize = item.stepSize
|
||||
}
|
||||
|
||||
is IntSliderSetting -> {
|
||||
slider.valueFrom = item.min.toFloat()
|
||||
slider.valueTo = item.max.toFloat()
|
||||
|
|
@ -476,6 +477,7 @@ class SettingsAdapter(
|
|||
|
||||
closeDialog()
|
||||
}
|
||||
|
||||
is SingleChoiceSettingDynamicDescriptions -> {
|
||||
val scSetting = clickedItem as SingleChoiceSettingDynamicDescriptions
|
||||
|
||||
|
|
@ -486,6 +488,7 @@ class SettingsAdapter(
|
|||
|
||||
closeDialog()
|
||||
}
|
||||
|
||||
is StringSingleChoiceSetting -> {
|
||||
val scSetting = clickedItem as StringSingleChoiceSetting
|
||||
|
||||
|
|
@ -496,6 +499,7 @@ class SettingsAdapter(
|
|||
|
||||
closeDialog()
|
||||
}
|
||||
|
||||
is IntSliderSetting -> {
|
||||
val sliderSetting = clickedItem as IntSliderSetting
|
||||
if (sliderSetting.selectedValue != seekbarProgress.toInt()) {
|
||||
|
|
@ -504,6 +508,7 @@ class SettingsAdapter(
|
|||
sliderSetting.setSelectedValue(settings!!, seekbarProgress.toInt())
|
||||
closeDialog()
|
||||
}
|
||||
|
||||
is FloatSliderSetting -> {
|
||||
val sliderSetting = clickedItem as FloatSliderSetting
|
||||
|
||||
|
|
|
|||
|
|
@ -502,16 +502,16 @@ class SettingsFragmentPresenter(
|
|||
|
||||
override val isOverridden: Boolean
|
||||
get() = BooleanSetting.MAIN_DSP_HLE.isOverridden ||
|
||||
BooleanSetting.MAIN_DSP_JIT.isOverridden
|
||||
BooleanSetting.MAIN_DSP_JIT.isOverridden
|
||||
|
||||
override val isRuntimeEditable: Boolean
|
||||
get() = BooleanSetting.MAIN_DSP_HLE.isRuntimeEditable &&
|
||||
BooleanSetting.MAIN_DSP_JIT.isRuntimeEditable
|
||||
BooleanSetting.MAIN_DSP_JIT.isRuntimeEditable
|
||||
|
||||
override fun delete(settings: Settings): Boolean {
|
||||
// Not short circuiting
|
||||
return BooleanSetting.MAIN_DSP_HLE.delete(settings) and
|
||||
BooleanSetting.MAIN_DSP_JIT.delete(settings)
|
||||
BooleanSetting.MAIN_DSP_JIT.delete(settings)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1002,8 +1002,8 @@ class SettingsFragmentPresenter(
|
|||
0,
|
||||
false
|
||||
) {
|
||||
fragmentView.showDialogFragment(LoginDialog(this))
|
||||
loadSettingsList()
|
||||
fragmentView.showDialogFragment(LoginDialog(this))
|
||||
loadSettingsList()
|
||||
})
|
||||
} else {
|
||||
sl.add(
|
||||
|
|
@ -1015,8 +1015,8 @@ class SettingsFragmentPresenter(
|
|||
0,
|
||||
false
|
||||
) {
|
||||
logout()
|
||||
loadSettingsList()
|
||||
logout()
|
||||
loadSettingsList()
|
||||
})
|
||||
}
|
||||
sl.add(
|
||||
|
|
@ -1113,16 +1113,16 @@ class SettingsFragmentPresenter(
|
|||
|
||||
override val isOverridden: Boolean
|
||||
get() = BooleanSetting.MAIN_SYNC_ON_SKIP_IDLE.isOverridden ||
|
||||
BooleanSetting.MAIN_SYNC_GPU.isOverridden
|
||||
BooleanSetting.MAIN_SYNC_GPU.isOverridden
|
||||
|
||||
override val isRuntimeEditable: Boolean
|
||||
get() = BooleanSetting.MAIN_SYNC_ON_SKIP_IDLE.isRuntimeEditable &&
|
||||
BooleanSetting.MAIN_SYNC_GPU.isRuntimeEditable
|
||||
BooleanSetting.MAIN_SYNC_GPU.isRuntimeEditable
|
||||
|
||||
override fun delete(settings: Settings): Boolean {
|
||||
// Not short circuiting
|
||||
return BooleanSetting.MAIN_SYNC_ON_SKIP_IDLE.delete(settings) and
|
||||
BooleanSetting.MAIN_SYNC_GPU.delete(settings)
|
||||
BooleanSetting.MAIN_SYNC_GPU.delete(settings)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2247,7 +2247,7 @@ class SettingsFragmentPresenter(
|
|||
BooleanSetting.MAIN_DEBUG_JIT_ENABLE_PROFILING,
|
||||
R.string.debug_jit_enable_block_profiling,
|
||||
0
|
||||
)
|
||||
)
|
||||
)
|
||||
sl.add(
|
||||
RunRunnable(
|
||||
|
|
@ -2413,6 +2413,7 @@ class SettingsFragmentPresenter(
|
|||
addControllerMappingSettings(sl, gcPad, null)
|
||||
}
|
||||
}
|
||||
|
||||
7 -> {
|
||||
// Emulated keyboard controller
|
||||
val gcKeyboard = EmulatedController.getGcKeyboard(gcPadNumber)
|
||||
|
|
@ -2425,6 +2426,7 @@ class SettingsFragmentPresenter(
|
|||
addControllerMappingSettings(sl, gcKeyboard, null)
|
||||
}
|
||||
}
|
||||
|
||||
12 -> {
|
||||
// Adapter
|
||||
sl.add(
|
||||
|
|
@ -2444,6 +2446,21 @@ class SettingsFragmentPresenter(
|
|||
)
|
||||
)
|
||||
}
|
||||
|
||||
13 -> {
|
||||
//GBA emulator
|
||||
sl.add(HeaderSetting(context, R.string.gba_settings, 0))
|
||||
sl.add(
|
||||
FilePicker(
|
||||
context,
|
||||
StringSetting.getGBARomPath(gcPadNumber),
|
||||
R.string.gba_rom_path,
|
||||
R.string.gba_rom_path_description,
|
||||
fragmentView.activityResultLaunchers.requestGbaRomFile,
|
||||
null
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2643,11 +2660,11 @@ class SettingsFragmentPresenter(
|
|||
* @param groupTypeFilter If this is non-null, only groups whose types match this are considered.
|
||||
*/
|
||||
private fun addControllerMappingSettings(
|
||||
sl: ArrayList<SettingsItem>,
|
||||
controller: EmulatedController,
|
||||
groupTypeFilter: Set<Int>?
|
||||
sl: ArrayList<SettingsItem>,
|
||||
controller: EmulatedController,
|
||||
groupTypeFilter: Set<Int>?
|
||||
) {
|
||||
addContainerMappingSettings(sl, controller, controller, groupTypeFilter)
|
||||
addContainerMappingSettings(sl, controller, controller, groupTypeFilter)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -2742,7 +2759,7 @@ class SettingsFragmentPresenter(
|
|||
val defaultDevice = controller.getDefaultDevice()
|
||||
|
||||
hasOldControllerSettings = defaultDevice.startsWith("Android/") &&
|
||||
defaultDevice.endsWith("/Touchscreen")
|
||||
defaultDevice.endsWith("/Touchscreen")
|
||||
|
||||
fragmentView.setOldControllerSettingsWarningVisibility(hasOldControllerSettings)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,11 +51,17 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
|
|||
private var isFirstRun = true
|
||||
private val gcPadRegistered = BooleanArray(4)
|
||||
private val wiimoteRegistered = BooleanArray(4)
|
||||
private val gbaRegistered = BooleanArray(4)
|
||||
private val gbaOverlayButtons: MutableSet<InputOverlayDrawableButton> = HashSet()
|
||||
private val gbaOverlayDpads: MutableSet<InputOverlayDrawableDpad> = HashSet()
|
||||
private var gbaControllerIndex = -1
|
||||
var editMode = false
|
||||
private var controllerType = -1
|
||||
private var controllerIndex = 0
|
||||
private var buttonBeingConfigured: InputOverlayDrawableButton? = null
|
||||
private var dpadBeingConfigured: InputOverlayDrawableDpad? = null
|
||||
private var gbaButtonBeingConfigured: InputOverlayDrawableButton? = null
|
||||
private var gbaDpadBeingConfigured: InputOverlayDrawableDpad? = null
|
||||
private var joystickBeingConfigured: InputOverlayDrawableJoystick? = null
|
||||
|
||||
private val preferences: SharedPreferences
|
||||
|
|
@ -129,6 +135,35 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
|
|||
for (joystick in overlayJoysticks) {
|
||||
joystick.draw(canvas)
|
||||
}
|
||||
|
||||
for (button in gbaOverlayButtons) {
|
||||
button.draw(canvas)
|
||||
drawGBABadge(canvas, button.bounds)
|
||||
}
|
||||
|
||||
for (dpad in gbaOverlayDpads) {
|
||||
dpad.draw(canvas)
|
||||
drawGBABadge(canvas, dpad.bounds)
|
||||
}
|
||||
}
|
||||
|
||||
//draws gba badge on controlls for gba controller, to not get confused with the GC pad buttons
|
||||
private fun drawGBABadge(canvas: Canvas, bounds: android.graphics.Rect) {
|
||||
val bp = android.graphics.Paint().apply {
|
||||
isAntiAlias = true; color = android.graphics.Color.argb(200, 98, 0, 238)
|
||||
style = android.graphics.Paint.Style.FILL
|
||||
}
|
||||
val tp = android.graphics.Paint().apply {
|
||||
isAntiAlias = true; color = android.graphics.Color.WHITE; textSize = 18f
|
||||
typeface = android.graphics.Typeface.DEFAULT_BOLD
|
||||
textAlign = android.graphics.Paint.Align.CENTER
|
||||
}
|
||||
val r = android.graphics.RectF(
|
||||
bounds.left.toFloat(), bounds.top.toFloat(),
|
||||
bounds.left + 36f, bounds.top + 18f
|
||||
)
|
||||
canvas.drawRoundRect(r, 6f, 6f, bp)
|
||||
canvas.drawText("GBA", r.left + 18f, r.top + 14f, tp)
|
||||
}
|
||||
|
||||
override fun onTouch(v: View, event: MotionEvent): Boolean {
|
||||
|
|
@ -138,26 +173,107 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
|
|||
|
||||
val action = event.actionMasked
|
||||
val firstPointer = action != MotionEvent.ACTION_POINTER_DOWN &&
|
||||
action != MotionEvent.ACTION_POINTER_UP
|
||||
action != MotionEvent.ACTION_POINTER_UP
|
||||
val pointerIndex = if (firstPointer) 0 else event.actionIndex
|
||||
// Tracks if any button/joystick is pressed down
|
||||
var pressed = false
|
||||
// Process GBA buttons first and claim them so GCPAD doesnt fire on the same touch.
|
||||
val gbaClaimedPointers = mutableSetOf<Int>()
|
||||
|
||||
for (button in overlayButtons) {
|
||||
if (gbaControllerIndex >= 0) {
|
||||
pressed = processButtons(
|
||||
gbaOverlayButtons,
|
||||
gbaControllerIndex,
|
||||
action,
|
||||
event,
|
||||
pointerIndex,
|
||||
pressed,
|
||||
gbaClaimedPointers
|
||||
)
|
||||
pressed = processDpads(
|
||||
gbaOverlayDpads,
|
||||
gbaControllerIndex,
|
||||
action,
|
||||
event,
|
||||
pointerIndex,
|
||||
pressed,
|
||||
gbaClaimedPointers
|
||||
)
|
||||
}
|
||||
|
||||
pressed = processButtons(overlayButtons, controllerIndex, action, event, pointerIndex, pressed, excludePointers = gbaClaimedPointers)
|
||||
pressed = processDpads(overlayDpads, controllerIndex, action, event, pointerIndex, pressed, excludePointers = gbaClaimedPointers)
|
||||
|
||||
for (joystick in overlayJoysticks) {
|
||||
if (joystick.trackEvent(event)) {
|
||||
if (joystick.trackId != -1)
|
||||
pressed = true
|
||||
}
|
||||
|
||||
InputOverrider.setControlState(
|
||||
controllerIndex,
|
||||
joystick.xControl,
|
||||
joystick.x.toDouble()
|
||||
)
|
||||
InputOverrider.setControlState(
|
||||
controllerIndex,
|
||||
joystick.yControl,
|
||||
-joystick.y.toDouble()
|
||||
)
|
||||
}
|
||||
|
||||
// No button/joystick pressed, safe to move pointer
|
||||
if (!pressed && overlayPointer != null) {
|
||||
overlayPointer!!.onTouch(event)
|
||||
InputOverrider.setControlState(
|
||||
controllerIndex,
|
||||
ControlId.WIIMOTE_IR_X,
|
||||
overlayPointer!!.x.toDouble()
|
||||
)
|
||||
InputOverrider.setControlState(
|
||||
controllerIndex,
|
||||
ControlId.WIIMOTE_IR_Y,
|
||||
-overlayPointer!!.y.toDouble()
|
||||
)
|
||||
}
|
||||
|
||||
invalidate()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
fun processButtons(
|
||||
buttons: Set<InputOverlayDrawableButton>,
|
||||
controllerIndex: Int,
|
||||
action: Int,
|
||||
event: MotionEvent,
|
||||
pointerIndex: Int,
|
||||
pressedIn: Boolean,
|
||||
claimedPointers: MutableSet<Int> = mutableSetOf(),
|
||||
excludePointers: Set<Int> = emptySet()
|
||||
): Boolean {
|
||||
var pressed = pressedIn
|
||||
for (button in buttons) {
|
||||
// Determine the button state to apply based on the MotionEvent action flag.
|
||||
when (action) {
|
||||
MotionEvent.ACTION_DOWN,
|
||||
MotionEvent.ACTION_POINTER_DOWN -> {
|
||||
val pointerId = event.getPointerId(pointerIndex)
|
||||
// If a pointer enters the bounds of a button, press that button.
|
||||
if (button.bounds.contains(
|
||||
if (!excludePointers.contains(pointerId) && button.bounds.contains(
|
||||
event.getX(pointerIndex).toInt(),
|
||||
event.getY(pointerIndex).toInt()
|
||||
)
|
||||
) {
|
||||
button.setPressedState(if (button.latching) !button.getPressedState() else true)
|
||||
button.trackId = event.getPointerId(pointerIndex)
|
||||
button.trackId = pointerId
|
||||
pressed = true
|
||||
InputOverrider.setControlState(controllerIndex, button.control, if (button.getPressedState()) 1.0 else 0.0)
|
||||
claimedPointers.add(pointerId)
|
||||
InputOverrider.setControlState(
|
||||
controllerIndex,
|
||||
button.control,
|
||||
if (button.getPressedState()) 1.0 else 0.0
|
||||
)
|
||||
|
||||
val analogControl = getAnalogControlForTrigger(button.control)
|
||||
if (analogControl >= 0)
|
||||
|
|
@ -175,7 +291,11 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
|
|||
if (button.trackId == event.getPointerId(pointerIndex)) {
|
||||
if (!button.latching)
|
||||
button.setPressedState(false)
|
||||
InputOverrider.setControlState(controllerIndex, button.control, if (button.getPressedState()) 1.0 else 0.0)
|
||||
InputOverrider.setControlState(
|
||||
controllerIndex,
|
||||
button.control,
|
||||
if (button.getPressedState()) 1.0 else 0.0
|
||||
)
|
||||
|
||||
val analogControl = getAnalogControlForTrigger(button.control)
|
||||
if (analogControl >= 0)
|
||||
|
|
@ -190,14 +310,29 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
|
|||
}
|
||||
}
|
||||
}
|
||||
return pressed
|
||||
}
|
||||
|
||||
for (dpad in overlayDpads) {
|
||||
fun processDpads(
|
||||
dpads: Set<InputOverlayDrawableDpad>,
|
||||
controllerIndex: Int,
|
||||
action: Int,
|
||||
event: MotionEvent,
|
||||
pointerIndex: Int,
|
||||
pressedIn: Boolean,
|
||||
claimedPointers: MutableSet<Int> = mutableSetOf(),
|
||||
excludePointers: Set<Int> = emptySet()
|
||||
): Boolean
|
||||
{
|
||||
var pressed = pressedIn
|
||||
for (dpad in dpads) {
|
||||
// Determine the button state to apply based on the MotionEvent action flag.
|
||||
when (event.action and MotionEvent.ACTION_MASK) {
|
||||
MotionEvent.ACTION_DOWN,
|
||||
MotionEvent.ACTION_POINTER_DOWN -> {
|
||||
val pointerId = event.getPointerId(pointerIndex)
|
||||
// If a pointer enters the bounds of a button, press that button.
|
||||
if (dpad.bounds
|
||||
if (!excludePointers.contains(pointerId) && dpad.bounds
|
||||
.contains(
|
||||
event.getX(pointerIndex).toInt(),
|
||||
event.getY(pointerIndex).toInt()
|
||||
|
|
@ -205,6 +340,7 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
|
|||
) {
|
||||
dpad.trackId = event.getPointerId(pointerIndex)
|
||||
pressed = true
|
||||
claimedPointers.add(pointerId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -267,43 +403,7 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (joystick in overlayJoysticks) {
|
||||
if (joystick.trackEvent(event)) {
|
||||
if (joystick.trackId != -1)
|
||||
pressed = true
|
||||
}
|
||||
|
||||
InputOverrider.setControlState(
|
||||
controllerIndex,
|
||||
joystick.xControl,
|
||||
joystick.x.toDouble()
|
||||
)
|
||||
InputOverrider.setControlState(
|
||||
controllerIndex,
|
||||
joystick.yControl,
|
||||
-joystick.y.toDouble()
|
||||
)
|
||||
}
|
||||
|
||||
// No button/joystick pressed, safe to move pointer
|
||||
if (!pressed && overlayPointer != null) {
|
||||
overlayPointer!!.onTouch(event)
|
||||
InputOverrider.setControlState(
|
||||
controllerIndex,
|
||||
ControlId.WIIMOTE_IR_X,
|
||||
overlayPointer!!.x.toDouble()
|
||||
)
|
||||
InputOverrider.setControlState(
|
||||
controllerIndex,
|
||||
ControlId.WIIMOTE_IR_Y,
|
||||
-overlayPointer!!.y.toDouble()
|
||||
)
|
||||
}
|
||||
|
||||
invalidate()
|
||||
|
||||
return true
|
||||
return pressed
|
||||
}
|
||||
|
||||
fun onTouchWhileEditing(event: MotionEvent): Boolean {
|
||||
|
|
@ -425,6 +525,76 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
|
|||
}
|
||||
}
|
||||
}
|
||||
for (button in gbaOverlayButtons) {
|
||||
when (event.action and MotionEvent.ACTION_MASK) {
|
||||
MotionEvent.ACTION_DOWN,
|
||||
MotionEvent.ACTION_POINTER_DOWN -> {
|
||||
if (gbaButtonBeingConfigured == null &&
|
||||
button.bounds.contains(fingerPositionX, fingerPositionY)
|
||||
) {
|
||||
gbaButtonBeingConfigured = button
|
||||
gbaButtonBeingConfigured?.onConfigureTouch(event)
|
||||
}
|
||||
}
|
||||
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
if (gbaButtonBeingConfigured != null) {
|
||||
gbaButtonBeingConfigured?.onConfigureTouch(event)
|
||||
invalidate()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
MotionEvent.ACTION_UP,
|
||||
MotionEvent.ACTION_POINTER_UP -> {
|
||||
if (gbaButtonBeingConfigured == button) {
|
||||
saveControlPosition(
|
||||
gbaButtonBeingConfigured!!.legacyId,
|
||||
gbaButtonBeingConfigured!!.bounds.left,
|
||||
gbaButtonBeingConfigured!!.bounds.top,
|
||||
orientation
|
||||
)
|
||||
gbaButtonBeingConfigured = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (dpad in gbaOverlayDpads) {
|
||||
when (event.action and MotionEvent.ACTION_MASK) {
|
||||
MotionEvent.ACTION_DOWN,
|
||||
MotionEvent.ACTION_POINTER_DOWN -> {
|
||||
if (gbaDpadBeingConfigured == null && dpad.bounds.contains(
|
||||
fingerPositionX,
|
||||
fingerPositionY
|
||||
)
|
||||
) {
|
||||
gbaDpadBeingConfigured = dpad
|
||||
gbaDpadBeingConfigured?.onConfigureTouch(event)
|
||||
}
|
||||
}
|
||||
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
if (gbaDpadBeingConfigured != null) {
|
||||
gbaDpadBeingConfigured?.onConfigureTouch(event)
|
||||
invalidate()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
MotionEvent.ACTION_UP,
|
||||
MotionEvent.ACTION_POINTER_UP -> {
|
||||
if (gbaDpadBeingConfigured == dpad) {
|
||||
saveControlPosition(
|
||||
gbaDpadBeingConfigured!!.legacyId,
|
||||
gbaDpadBeingConfigured!!.bounds.left,
|
||||
gbaDpadBeingConfigured!!.bounds.top,
|
||||
orientation
|
||||
)
|
||||
gbaDpadBeingConfigured = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
@ -443,8 +613,16 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
|
|||
InputOverrider.unregisterWii(i)
|
||||
}
|
||||
|
||||
for (i in gbaRegistered.indices) {
|
||||
if (gbaRegistered[i]) InputOverrider.unregisterGBA(i)
|
||||
}
|
||||
|
||||
Arrays.fill(gcPadRegistered, false)
|
||||
Arrays.fill(wiimoteRegistered, false)
|
||||
Arrays.fill(gbaRegistered, false)
|
||||
gbaOverlayButtons.clear()
|
||||
gbaOverlayDpads.clear()
|
||||
gbaControllerIndex = -1
|
||||
}
|
||||
|
||||
private fun getAnalogControlForTrigger(control: Int): Int = when (control) {
|
||||
|
|
@ -640,6 +818,70 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
|
|||
}
|
||||
}
|
||||
|
||||
private fun addGBAOverlayControls(orientation: String) {
|
||||
gbaOverlayButtons.add(
|
||||
initializeOverlayButton(
|
||||
context, R.drawable.gcpad_a, R.drawable.gcpad_a_pressed,
|
||||
ButtonType.BUTTON_A + GBA_BUTTON_ID_OFFSET,
|
||||
ControlId.GCPAD_A_BUTTON, orientation, false
|
||||
)
|
||||
)
|
||||
|
||||
gbaOverlayButtons.add(
|
||||
initializeOverlayButton(
|
||||
context, R.drawable.gcpad_b, R.drawable.gcpad_b_pressed,
|
||||
ButtonType.BUTTON_B + GBA_BUTTON_ID_OFFSET,
|
||||
ControlId.GCPAD_B_BUTTON, orientation, false
|
||||
)
|
||||
)
|
||||
|
||||
gbaOverlayButtons.add(
|
||||
initializeOverlayButton(
|
||||
context,
|
||||
R.drawable.gcpad_start,
|
||||
R.drawable.gcpad_start_pressed,
|
||||
ButtonType.BUTTON_START + GBA_BUTTON_ID_OFFSET,
|
||||
ControlId.GCPAD_START_BUTTON,
|
||||
orientation,
|
||||
false
|
||||
)
|
||||
)
|
||||
|
||||
gbaOverlayButtons.add(
|
||||
initializeOverlayButton(
|
||||
context, R.drawable.gcpad_z, R.drawable.gcpad_z_pressed,
|
||||
ButtonType.BUTTON_Z + GBA_BUTTON_ID_OFFSET,
|
||||
ControlId.GCPAD_Z_BUTTON, orientation, false
|
||||
)
|
||||
)
|
||||
|
||||
gbaOverlayButtons.add(
|
||||
initializeOverlayButton(
|
||||
context, R.drawable.gcpad_l, R.drawable.gcpad_l_pressed,
|
||||
ButtonType.TRIGGER_L + GBA_BUTTON_ID_OFFSET,
|
||||
ControlId.GCPAD_L_DIGITAL, orientation, false
|
||||
)
|
||||
)
|
||||
|
||||
gbaOverlayButtons.add(
|
||||
initializeOverlayButton(
|
||||
context, R.drawable.gcpad_r, R.drawable.gcpad_r_pressed,
|
||||
ButtonType.TRIGGER_R + GBA_BUTTON_ID_OFFSET,
|
||||
ControlId.GCPAD_R_DIGITAL, orientation, false
|
||||
)
|
||||
)
|
||||
|
||||
gbaOverlayDpads.add(
|
||||
initializeOverlayDpad(
|
||||
context, R.drawable.gcwii_dpad, R.drawable.gcwii_dpad_pressed_one_direction,
|
||||
R.drawable.gcwii_dpad_pressed_two_directions,
|
||||
ButtonType.BUTTON_UP + GBA_BUTTON_ID_OFFSET,
|
||||
ControlId.GCPAD_DPAD_UP, ControlId.GCPAD_DPAD_DOWN,
|
||||
ControlId.GCPAD_DPAD_LEFT, ControlId.GCPAD_DPAD_RIGHT, orientation
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun addWiimoteOverlayControls(orientation: String) {
|
||||
if (BooleanSetting.MAIN_BUTTON_TOGGLE_WII_0.boolean) {
|
||||
overlayButtons.add(
|
||||
|
|
@ -1047,6 +1289,23 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
|
|||
|
||||
OVERLAY_NONE -> {}
|
||||
}
|
||||
// add GBA controls on top of primary, GC controller always visible
|
||||
gbaOverlayButtons.clear()
|
||||
gbaOverlayDpads.clear()
|
||||
gbaControllerIndex = -1
|
||||
|
||||
for (i in 0 until 4) {
|
||||
if (getSettingForSIDevice(i).int == EMULATED_GBA_CONTROLLER) {
|
||||
if (gbaControllerIndex < 0) gbaControllerIndex = i
|
||||
if (!gbaRegistered[i]) {
|
||||
InputOverrider.registerGBA(i)
|
||||
gbaRegistered[i] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if (gbaControllerIndex >= 0) {
|
||||
addGBAOverlayControls(orientation)
|
||||
}
|
||||
}
|
||||
|
||||
isFirstRun = false
|
||||
|
|
@ -1085,6 +1344,7 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
|
|||
wiiOnlyPortraitDefaultOverlay()
|
||||
}
|
||||
}
|
||||
if (isLandscape) gbaDefaultOverlay() else gbaPortraitDefaultOverlay()
|
||||
refreshControls()
|
||||
}
|
||||
|
||||
|
|
@ -1401,6 +1661,22 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
|
|||
) {
|
||||
wiiClassicPortraitDefaultOverlay()
|
||||
}
|
||||
|
||||
// GBA controls android
|
||||
if (preferences.getFloat(
|
||||
(ButtonType.BUTTON_A + GBA_BUTTON_ID_OFFSET)
|
||||
.toString() + "-X", 0f
|
||||
) == 0f
|
||||
) {
|
||||
gbaDefaultOverlay()
|
||||
}
|
||||
if (preferences.getFloat(
|
||||
(ButtonType.BUTTON_A + GBA_BUTTON_ID_OFFSET)
|
||||
.toString() + "-Portrait" + "-X", 0f
|
||||
) == 0f
|
||||
) {
|
||||
gbaPortraitDefaultOverlay()
|
||||
}
|
||||
}
|
||||
|
||||
if (!preferences.getBoolean("OverlayInitV3", false)) {
|
||||
|
|
@ -2276,6 +2552,120 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
|
|||
.apply()
|
||||
}
|
||||
|
||||
private fun gbaDefaultOverlay() {
|
||||
val dm = resources.displayMetrics
|
||||
var maxX = dm.heightPixels.toFloat()
|
||||
var maxY = dm.widthPixels.toFloat()
|
||||
if (maxY > maxX) {
|
||||
val tmp = maxX;
|
||||
maxX = maxY;
|
||||
maxY = tmp
|
||||
}
|
||||
|
||||
preferences.edit()
|
||||
.putFloat((ButtonType.BUTTON_A + GBA_BUTTON_ID_OFFSET).toString() + "-X", 0.82f * maxX)
|
||||
.putFloat((ButtonType.BUTTON_A + GBA_BUTTON_ID_OFFSET).toString() + "-Y", 0.60f * maxY)
|
||||
.putFloat((ButtonType.BUTTON_B + GBA_BUTTON_ID_OFFSET).toString() + "-X", 0.73f * maxX)
|
||||
.putFloat((ButtonType.BUTTON_B + GBA_BUTTON_ID_OFFSET).toString() + "-Y", 0.70f * maxY)
|
||||
.putFloat((ButtonType.TRIGGER_L + GBA_BUTTON_ID_OFFSET).toString() + "-X", 0.08f * maxX)
|
||||
.putFloat((ButtonType.TRIGGER_L + GBA_BUTTON_ID_OFFSET).toString() + "-Y", 0.25f * maxY)
|
||||
.putFloat((ButtonType.TRIGGER_R + GBA_BUTTON_ID_OFFSET).toString() + "-X", 0.78f * maxX)
|
||||
.putFloat((ButtonType.TRIGGER_R + GBA_BUTTON_ID_OFFSET).toString() + "-Y", 0.25f * maxY)
|
||||
.putFloat(
|
||||
(ButtonType.BUTTON_START + GBA_BUTTON_ID_OFFSET).toString() + "-X",
|
||||
0.60f * maxX
|
||||
)
|
||||
.putFloat(
|
||||
(ButtonType.BUTTON_START + GBA_BUTTON_ID_OFFSET).toString() + "-Y",
|
||||
0.80f * maxY
|
||||
)
|
||||
.putFloat((ButtonType.BUTTON_Z + GBA_BUTTON_ID_OFFSET).toString() + "-X", 0.45f * maxX)
|
||||
.putFloat((ButtonType.BUTTON_Z + GBA_BUTTON_ID_OFFSET).toString() + "-Y", 0.80f * maxY)
|
||||
.putFloat((ButtonType.BUTTON_UP + GBA_BUTTON_ID_OFFSET).toString() + "-X", 0.12f * maxX)
|
||||
.putFloat((ButtonType.BUTTON_UP + GBA_BUTTON_ID_OFFSET).toString() + "-Y", 0.55f * maxY)
|
||||
.apply()
|
||||
}
|
||||
|
||||
private fun gbaPortraitDefaultOverlay() {
|
||||
val dm = resources.displayMetrics
|
||||
var maxX = dm.heightPixels.toFloat()
|
||||
var maxY = dm.widthPixels.toFloat()
|
||||
if (maxY < maxX) {
|
||||
val tmp = maxX;
|
||||
maxX = maxY;
|
||||
maxY = tmp
|
||||
}
|
||||
val portrait = "-Portrait"
|
||||
|
||||
preferences.edit()
|
||||
.putFloat(
|
||||
(ButtonType.BUTTON_A + GBA_BUTTON_ID_OFFSET).toString() + portrait + "-X",
|
||||
0.82f * maxX
|
||||
)
|
||||
.putFloat(
|
||||
(ButtonType.BUTTON_A + GBA_BUTTON_ID_OFFSET).toString() + portrait + "-Y",
|
||||
0.72f * maxY
|
||||
)
|
||||
.putFloat(
|
||||
(ButtonType.BUTTON_B + GBA_BUTTON_ID_OFFSET).toString() + portrait + "-X",
|
||||
0.68f * maxX
|
||||
)
|
||||
.putFloat(
|
||||
(ButtonType.BUTTON_B + GBA_BUTTON_ID_OFFSET).toString() + portrait + "-Y",
|
||||
0.80f * maxY
|
||||
)
|
||||
.putFloat(
|
||||
(ButtonType.TRIGGER_L + GBA_BUTTON_ID_OFFSET).toString() + portrait + "-X",
|
||||
0.04f * maxX
|
||||
)
|
||||
.putFloat(
|
||||
(ButtonType.TRIGGER_L + GBA_BUTTON_ID_OFFSET).toString() + portrait + "-Y",
|
||||
0.55f * maxY
|
||||
)
|
||||
.putFloat(
|
||||
(ButtonType.TRIGGER_R + GBA_BUTTON_ID_OFFSET).toString() + portrait + "-X",
|
||||
0.78f * maxX
|
||||
)
|
||||
.putFloat(
|
||||
(ButtonType.TRIGGER_R + GBA_BUTTON_ID_OFFSET).toString() + portrait + "-Y",
|
||||
0.55f * maxY
|
||||
)
|
||||
.putFloat(
|
||||
(ButtonType.BUTTON_START + GBA_BUTTON_ID_OFFSET).toString() + portrait + "-X",
|
||||
0.62f * maxX
|
||||
)
|
||||
.putFloat(
|
||||
(ButtonType.BUTTON_START + GBA_BUTTON_ID_OFFSET).toString() + portrait + "-Y",
|
||||
0.90f * maxY
|
||||
)
|
||||
.putFloat(
|
||||
(ButtonType.BUTTON_Z + GBA_BUTTON_ID_OFFSET).toString() + portrait + "-X",
|
||||
0.42f * maxX
|
||||
)
|
||||
.putFloat(
|
||||
(ButtonType.BUTTON_Z + GBA_BUTTON_ID_OFFSET).toString() + portrait + "-Y",
|
||||
0.90f * maxY
|
||||
)
|
||||
.putFloat(
|
||||
(ButtonType.BUTTON_UP + GBA_BUTTON_ID_OFFSET).toString() + portrait + "-X",
|
||||
0.10f * maxX
|
||||
)
|
||||
.putFloat(
|
||||
(ButtonType.BUTTON_UP + GBA_BUTTON_ID_OFFSET).toString() + portrait + "-Y",
|
||||
0.72f * maxY
|
||||
)
|
||||
.apply()
|
||||
}
|
||||
|
||||
fun isTouchTracked(): Boolean {
|
||||
return overlayButtons.any {it.trackId !=-1} ||
|
||||
return overlayDpads.any {it.trackId !=-1} ||
|
||||
return overlayJoysticks.any {it.trackId !=-1} ||
|
||||
return gbaOverlayButtons.any {it.trackId !=-1} ||
|
||||
return gbaOverlayDpads.any {it.trackId !=-1}
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
const val OVERLAY_GAMECUBE = 0
|
||||
const val OVERLAY_WIIMOTE = 1
|
||||
|
|
@ -2283,11 +2673,15 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
|
|||
const val OVERLAY_WIIMOTE_NUNCHUK = 3
|
||||
const val OVERLAY_WIIMOTE_CLASSIC = 4
|
||||
const val OVERLAY_NONE = 5
|
||||
const val EMULATED_GBA_CONTROLLER = 13
|
||||
private const val DISABLED_GAMECUBE_CONTROLLER = 0
|
||||
private const val EMULATED_GAMECUBE_CONTROLLER = 6
|
||||
private const val EMULATED_AM_BASEBOARD = 11
|
||||
private const val GAMECUBE_ADAPTER = 12
|
||||
|
||||
//avoid ID collision with GC buttons
|
||||
private const val GBA_BUTTON_ID_OFFSET = 1000
|
||||
|
||||
// Buttons that have special positions in Wiimote only
|
||||
private val WIIMOTE_H_BUTTONS = ArrayList<Int>()
|
||||
|
||||
|
|
|
|||
|
|
@ -29,8 +29,8 @@ import java.util.Set;
|
|||
public final class FileBrowserHelper
|
||||
{
|
||||
public static final HashSet<String> GAME_EXTENSIONS = new HashSet<>(Arrays.asList(
|
||||
"gcm", "tgc", "bin", "iso", "ciso", "gcz", "wbfs", "wia", "rvz", "nfs", "wad", "dol",
|
||||
"elf", "json"));
|
||||
"gcm", "tgc", "bin", "iso", "ciso", "gcz", "wbfs", "wia", "rvz", "nfs", "wad", "dol",
|
||||
"elf", "json"));
|
||||
|
||||
public static final HashSet<String> GAME_LIKE_EXTENSIONS = new HashSet<>(GAME_EXTENSIONS);
|
||||
|
||||
|
|
@ -40,19 +40,19 @@ public final class FileBrowserHelper
|
|||
}
|
||||
|
||||
public static final HashSet<String> GBA_ROM_EXTENSIONS = new HashSet<>(Arrays.asList(
|
||||
"gba", "gbc", "gb", "agb", "mb", "rom", "bin"));
|
||||
"gba", "gbc", "gb", "agb", "mb", "rom", "bin"));
|
||||
|
||||
public static final HashSet<String> BIN_EXTENSION = new HashSet<>(Collections.singletonList(
|
||||
"bin"));
|
||||
"bin"));
|
||||
|
||||
public static final HashSet<String> RAW_EXTENSION = new HashSet<>(Collections.singletonList(
|
||||
"raw"));
|
||||
"raw"));
|
||||
|
||||
public static final HashSet<String> WAD_EXTENSION = new HashSet<>(Collections.singletonList(
|
||||
"wad"));
|
||||
"wad"));
|
||||
|
||||
public static Intent createDirectoryPickerIntent(FragmentActivity activity,
|
||||
HashSet<String> extensions)
|
||||
HashSet<String> extensions)
|
||||
{
|
||||
Intent i = new Intent(activity, CustomFilePickerActivity.class);
|
||||
|
||||
|
|
@ -60,7 +60,7 @@ public final class FileBrowserHelper
|
|||
i.putExtra(FilePickerActivity.EXTRA_ALLOW_CREATE_DIR, false);
|
||||
i.putExtra(FilePickerActivity.EXTRA_MODE, FilePickerActivity.MODE_DIR);
|
||||
i.putExtra(FilePickerActivity.EXTRA_START_PATH,
|
||||
Environment.getExternalStorageDirectory().getPath());
|
||||
Environment.getExternalStorageDirectory().getPath());
|
||||
i.putExtra(CustomFilePickerActivity.EXTRA_EXTENSIONS, extensions);
|
||||
|
||||
return i;
|
||||
|
|
@ -98,7 +98,7 @@ public final class FileBrowserHelper
|
|||
}
|
||||
|
||||
public static void runAfterExtensionCheck(Context context, Uri uri, Set<String> validExtensions,
|
||||
Runnable runnable)
|
||||
Runnable runnable)
|
||||
{
|
||||
String extension = null;
|
||||
|
||||
|
|
@ -123,18 +123,18 @@ public final class FileBrowserHelper
|
|||
else
|
||||
{
|
||||
int messageId = validExtensions.size() == 1 ?
|
||||
R.string.wrong_file_extension_single : R.string.wrong_file_extension_multiple;
|
||||
R.string.wrong_file_extension_single : R.string.wrong_file_extension_multiple;
|
||||
|
||||
message = context.getString(messageId, extension,
|
||||
setToSortedDelimitedString(validExtensions));
|
||||
setToSortedDelimitedString(validExtensions));
|
||||
}
|
||||
|
||||
new MaterialAlertDialogBuilder(context)
|
||||
.setMessage(message)
|
||||
.setPositiveButton(R.string.yes, (dialogInterface, i) -> runnable.run())
|
||||
.setNegativeButton(R.string.no, null)
|
||||
.setCancelable(false)
|
||||
.show();
|
||||
.setMessage(message)
|
||||
.setPositiveButton(R.string.yes, (dialogInterface, i) -> runnable.run())
|
||||
.setNegativeButton(R.string.no, null)
|
||||
.setCancelable(false)
|
||||
.show();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
|
|
|||
|
|
@ -11,7 +11,9 @@
|
|||
<SurfaceView
|
||||
android:id="@+id/surface_emulation"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_gravity="start"
|
||||
android:layout_marginStart="0dp"
|
||||
android:focusable="false"
|
||||
android:focusableInTouchMode="false" />
|
||||
|
||||
|
|
|
|||
|
|
@ -30,4 +30,14 @@
|
|||
<item
|
||||
android:id="@+id/menu_emulation_reset_overlay"
|
||||
android:title="@string/emulation_touch_overlay_reset"/>
|
||||
|
||||
<item android:id="@+id/menu_emulation_gba_snap"
|
||||
android:checkable="true"
|
||||
android:title="Snap Gba Layout"/>
|
||||
|
||||
<item android:id="@+id/menu_emulation_gba_reset"
|
||||
android:title="Reset Gba Screens"/>
|
||||
|
||||
<item android:id="@+id/menu_emulation_gba_reset_core"
|
||||
android:title="Reset Gba"/>
|
||||
</menu>
|
||||
|
|
|
|||
|
|
@ -379,6 +379,7 @@
|
|||
<item>@string/gcpad_taru_konga</item>
|
||||
<item>@string/gcpad_am_baseboard</item>
|
||||
<item>@string/gcpad_gc_adapter</item>
|
||||
<item>@string/gba_adapter</item>
|
||||
</string-array>
|
||||
<integer-array name="gcpadTypeValues">
|
||||
<item>0</item>
|
||||
|
|
@ -389,6 +390,7 @@
|
|||
<item>10</item>
|
||||
<item>11</item>
|
||||
<item>12</item>
|
||||
<item>13</item>
|
||||
</integer-array>
|
||||
|
||||
<string-array name="wiimoteTypeEntries">
|
||||
|
|
|
|||
|
|
@ -89,6 +89,8 @@
|
|||
<string name="gba_bios_path">BIOS</string>
|
||||
<string name="gb_player_rom">Game Boy Player ROM</string>
|
||||
<string name="gba_saves_path">Saves</string>
|
||||
<string name="gba_rom_path">GBA ROM Path</string>
|
||||
<string name="gba_rom_path_description">Rom to load for this slot.</string>
|
||||
<string name="wii_submenu">Wii</string>
|
||||
<string name="wii_misc_settings">Misc Settings</string>
|
||||
<string name="wii_sd_card_settings">SD Card Settings</string>
|
||||
|
|
@ -643,6 +645,7 @@ It can efficiently compress both junk data and encrypted Wii data.
|
|||
<string name="gc_adapter_rumble_description">Enable the vibration function for this GameCube controller.</string>
|
||||
<string name="gc_adapter_bongos">Bongo Controller</string>
|
||||
<string name="gc_adapter_bongos_description">Enable this if you are using bongos on this port.</string>
|
||||
<string name="gba_adapter">GBA (Integrated)</string>
|
||||
|
||||
<string name="path_not_changeable_scoped_storage">Due to the Scoped Storage policy in Android 11 and newer, you can\'t change this path.</string>
|
||||
<string name="load_settings">Loading Settings…</string>
|
||||
|
|
|
|||
|
|
@ -125,6 +125,9 @@ static jclass s_audio_utils_class;
|
|||
static jmethodID s_audio_utils_get_sample_rate;
|
||||
static jmethodID s_audio_utils_get_frames_per_buffer;
|
||||
|
||||
static jclass s_gba_library_class;
|
||||
static jmethodID s_on_gba_frame;
|
||||
|
||||
namespace IDCache
|
||||
{
|
||||
JNIEnv* GetEnvForThread()
|
||||
|
|
@ -575,11 +578,21 @@ jmethodID GetAudioUtilsGetFramesPerBuffer()
|
|||
return s_audio_utils_get_frames_per_buffer;
|
||||
}
|
||||
|
||||
jmethodID GetOnGbaFrame()
|
||||
{
|
||||
return s_on_gba_frame;
|
||||
}
|
||||
|
||||
jclass GetGbaLibraryClass()
|
||||
{
|
||||
return s_gba_library_class;
|
||||
}
|
||||
} // namespace IDCache
|
||||
|
||||
extern "C" {
|
||||
|
||||
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
|
||||
|
||||
{
|
||||
s_java_vm = vm;
|
||||
|
||||
|
|
@ -816,6 +829,11 @@ JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
|
|||
env->GetStaticMethodID(audio_utils_class, "getFramesPerBuffer", "()I");
|
||||
env->DeleteLocalRef(audio_utils_class);
|
||||
|
||||
const jclass gba_library_class = env->FindClass("org/dolphinemu/dolphinemu/features/gba/GbaLibrary");
|
||||
s_gba_library_class = reinterpret_cast<jclass>(env->NewGlobalRef(gba_library_class));
|
||||
s_on_gba_frame = env->GetStaticMethodID(gba_library_class, "onGbaFrame", "(I)V");
|
||||
env->DeleteLocalRef(gba_library_class);
|
||||
|
||||
return JNI_VERSION;
|
||||
}
|
||||
|
||||
|
|
@ -853,5 +871,6 @@ JNIEXPORT void JNI_OnUnload(JavaVM* vm, void* reserved)
|
|||
env->DeleteGlobalRef(s_input_detector_class);
|
||||
env->DeleteGlobalRef(s_permission_handler_class);
|
||||
env->DeleteGlobalRef(s_audio_utils_class);
|
||||
env->DeleteGlobalRef(s_gba_library_class);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -124,4 +124,7 @@ jclass GetAudioUtilsClass();
|
|||
jmethodID GetAudioUtilsGetSampleRate();
|
||||
jmethodID GetAudioUtilsGetFramesPerBuffer();
|
||||
|
||||
jclass GetGbaLibraryClass();
|
||||
jmethodID GetOnGbaFrame();
|
||||
|
||||
} // namespace IDCache
|
||||
|
|
|
|||
|
|
@ -36,6 +36,20 @@ Java_org_dolphinemu_dolphinemu_features_input_model_InputOverrider_unregisterWii
|
|||
ciface::Touch::UnregisterWiiInputOverrider(controller_index);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_dolphinemu_dolphinemu_features_input_model_InputOverrider_registerGBA(JNIEnv*, jclass,
|
||||
int controller_index)
|
||||
{
|
||||
ciface::Touch::RegisterGBAInputOverrider(controller_index);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_dolphinemu_dolphinemu_features_input_model_InputOverrider_unregisterGBA(
|
||||
JNIEnv*, jclass, int controller_index)
|
||||
{
|
||||
ciface::Touch::UnregisterGBAInputOverrider(controller_index);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_dolphinemu_dolphinemu_features_input_model_InputOverrider_setControlState(
|
||||
JNIEnv*, jclass, int controller_index, int control, double state)
|
||||
|
|
|
|||
|
|
@ -66,6 +66,14 @@
|
|||
#include "jni/AndroidCommon/AndroidCommon.h"
|
||||
#include "jni/AndroidCommon/IDCache.h"
|
||||
|
||||
#include <shared_mutex>
|
||||
#ifdef HAS_LIBMGBA
|
||||
#include "Core/HW/GBACore.h"
|
||||
#include "Core/HW/SI/SI.h"
|
||||
#include "Core/HW/SI/SI_Device.h"
|
||||
#include "Core/HW/SI/SI_DeviceGBAEmu.h"
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr char DOLPHIN_TAG[] = "DolphinEmuNative";
|
||||
|
|
@ -769,4 +777,41 @@ Java_org_dolphinemu_dolphinemu_NativeLibrary_GetCurrentTitleDescriptionUnchecked
|
|||
|
||||
return ToJString(env, description);
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_features_gba_GbaLibrary_copyGbaFramebuffer(
|
||||
JNIEnv* env, jclass, jint slot, jobject byte_buffer)
|
||||
{
|
||||
#ifdef HAS_LIBMGBA
|
||||
if (slot < 0 || slot >= 4)
|
||||
return JNI_FALSE;
|
||||
auto core = Core::System::GetInstance().GetSerialInterface().GetGBACore(slot);
|
||||
if (!core)
|
||||
return JNI_FALSE;
|
||||
std::shared_lock<std::shared_mutex> lock(core->GetVideoBufferMutex());
|
||||
const auto buffer = core->GetVideoBuffer();
|
||||
if (buffer.empty())
|
||||
return JNI_FALSE;
|
||||
void* dst = env->GetDirectBufferAddress(byte_buffer);
|
||||
if (!dst)
|
||||
return JNI_FALSE;
|
||||
jlong capacity = env->GetDirectBufferCapacity(byte_buffer);
|
||||
if (static_cast<size_t>(capacity) < buffer.size() * sizeof(u32))
|
||||
return JNI_FALSE;
|
||||
memcpy(dst, buffer.data(), buffer.size() * sizeof(u32));
|
||||
return JNI_TRUE;
|
||||
#else
|
||||
return JNI_FALSE;
|
||||
#endif
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_features_gba_GbaLibrary_resetGbaCore(JNIEnv*,
|
||||
jclass,
|
||||
jint slot)
|
||||
{
|
||||
#ifdef HAS_LIBMGBA
|
||||
auto core = Core::System::GetInstance().GetSerialInterface().GetGBACore(slot);
|
||||
if (core)
|
||||
core->Reset();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@
|
|||
|
||||
#ifdef ANDROID
|
||||
#include "jni/AndroidCommon/AndroidCommon.h"
|
||||
#include "jni/AndroidCommon/IDCache.h"
|
||||
#endif
|
||||
|
||||
namespace HW::GBA
|
||||
|
|
@ -245,7 +246,6 @@ bool Core::Start(u64 gc_ticks)
|
|||
mGameInfo info;
|
||||
m_core->getGameInfo(m_core, &info);
|
||||
m_game_title = info.title;
|
||||
|
||||
m_save_path = NetPlay::IsNetPlayRunning() ? NetPlay::GetGBASavePath(m_device_number) :
|
||||
GetSavePath(m_rom_path, m_device_number);
|
||||
if (!m_save_path.empty() && !LoadSave(m_save_path.c_str()))
|
||||
|
|
@ -445,6 +445,26 @@ void Core::SetAudioBufferSize()
|
|||
m_core->setAudioBufferSize(m_core, AUDIO_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
static void PushFrameReady(int slot)
|
||||
{
|
||||
#ifdef ANDROID
|
||||
JNIEnv* env = IDCache::GetEnvForThread();
|
||||
if (!env)
|
||||
return;
|
||||
|
||||
env->CallStaticVoidMethod(
|
||||
IDCache::GetGbaLibraryClass(),
|
||||
IDCache::GetOnGbaFrame(),
|
||||
static_cast<jint>(slot));
|
||||
|
||||
if (env->ExceptionCheck())
|
||||
{
|
||||
env->ExceptionDescribe();
|
||||
env->ExceptionClear();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void Core::AddCallbacks()
|
||||
{
|
||||
mCoreCallbacks callbacks{};
|
||||
|
|
@ -455,8 +475,11 @@ void Core::AddCallbacks()
|
|||
};
|
||||
callbacks.videoFrameEnded = [](void* context) {
|
||||
auto core = static_cast<Core*>(context);
|
||||
if (auto host = core->m_host.lock())
|
||||
host->FrameEnded(core->m_video_buffer);
|
||||
{
|
||||
if (auto host = core->m_host.lock())
|
||||
host->FrameEnded(core->m_video_buffer);
|
||||
}
|
||||
PushFrameReady(core->m_device_number);
|
||||
};
|
||||
m_core->addCoreCallbacks(m_core, &callbacks);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <shared_mutex>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
|
@ -91,6 +92,8 @@ public:
|
|||
|
||||
mAudioBuffer* GetAudioBuffer() { return m_core->getAudioBuffer(m_core); }
|
||||
std::span<const u32> GetVideoBuffer() const { return m_video_buffer; }
|
||||
// android video buffer mutex.
|
||||
std::shared_mutex& GetVideoBufferMutex() { return m_video_buffer_mutex; }
|
||||
|
||||
mPlatform GetPlatform() const { return m_core->platform(m_core); }
|
||||
u32 GetAudioSampleRate() const { return m_core->audioSampleRate(m_core); }
|
||||
|
|
@ -139,6 +142,8 @@ private:
|
|||
std::string m_save_path;
|
||||
std::array<u8, 20> m_rom_hash{};
|
||||
std::string m_game_title;
|
||||
// guard for m_video_buffer vulkan, surface changes caused a hard crash to main menu.
|
||||
mutable std::shared_mutex m_video_buffer_mutex;
|
||||
|
||||
mCore* m_core{};
|
||||
mCoreSync m_core_sync{};
|
||||
|
|
|
|||
|
|
@ -31,6 +31,11 @@
|
|||
|
||||
#include "InputCommon/ControllerInterface/ControllerInterface.h"
|
||||
|
||||
#ifdef HAS_LIBMGBA
|
||||
#include "Core/HW/GBACore.h"
|
||||
#include "Core/HW/SI/SI_DeviceGBAEmu.h"
|
||||
#endif
|
||||
|
||||
namespace SerialInterface
|
||||
{
|
||||
// SI Internal Hardware Addresses
|
||||
|
|
@ -597,5 +602,16 @@ u32 SerialInterfaceManager::GetPollXLines()
|
|||
{
|
||||
return m_poll.X;
|
||||
}
|
||||
|
||||
// Android GBA emulation.
|
||||
std::shared_ptr<HW::GBA::Core> SerialInterfaceManager::GetGBACore(int channel) const
|
||||
{
|
||||
#ifdef HAS_LIBMGBA
|
||||
if (channel < 0 || channel >= MAX_SI_CHANNELS)
|
||||
return nullptr;
|
||||
auto* dev = m_channel[channel].device.get();
|
||||
if (dev && dev->GetDeviceType() == SIDEVICE_GC_GBA_EMULATED)
|
||||
return static_cast<CSIDevice_GBAEmu*>(dev)->GetCore();
|
||||
#endif
|
||||
return nullptr;
|
||||
}
|
||||
} // namespace SerialInterface
|
||||
|
|
|
|||
|
|
@ -24,6 +24,11 @@ namespace MMIO
|
|||
{
|
||||
class Mapping;
|
||||
}
|
||||
// Android GBA
|
||||
namespace HW::GBA
|
||||
{
|
||||
class Core;
|
||||
}
|
||||
|
||||
namespace SerialInterface
|
||||
{
|
||||
|
|
@ -68,6 +73,8 @@ public:
|
|||
|
||||
u32 GetPollXLines();
|
||||
|
||||
std::shared_ptr<HW::GBA::Core> GetGBACore(int channel) const;
|
||||
|
||||
static constexpr u32 BUFFER_SIZE = 128;
|
||||
|
||||
private:
|
||||
|
|
|
|||
|
|
@ -31,6 +31,8 @@ public:
|
|||
void SendCommand(u32 command, u8 poll) override;
|
||||
void DoState(PointerWrap& p) override;
|
||||
void OnEvent(u64 userdata, s64 cycles_late) override;
|
||||
// android gba core
|
||||
std::shared_ptr<HW::GBA::Core> GetCore() const { return m_core; }
|
||||
|
||||
private:
|
||||
enum class NextAction
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@
|
|||
|
||||
#include "Common/Assert.h"
|
||||
|
||||
#include "Core/HW/GBAPad.h"
|
||||
#include "Core/HW/GBAPadEmu.h"
|
||||
#include "Core/HW/GCPad.h"
|
||||
#include "Core/HW/GCPadEmu.h"
|
||||
#include "Core/HW/Wiimote.h"
|
||||
|
|
@ -145,6 +147,19 @@ const ControlsMap s_classic_controls_map = {{
|
|||
ControlID::CLASSIC_RIGHT_STICK_Y},
|
||||
}};
|
||||
|
||||
static const ControlsMap s_gbapad_controls_map = {{
|
||||
{{GBAPad::BUTTONS_GROUP, GBAPad::A_BUTTON}, ControlID::GCPAD_A_BUTTON},
|
||||
{{GBAPad::BUTTONS_GROUP, GBAPad::B_BUTTON}, ControlID::GCPAD_B_BUTTON},
|
||||
{{GBAPad::BUTTONS_GROUP, GBAPad::L_BUTTON}, ControlID::GCPAD_L_DIGITAL},
|
||||
{{GBAPad::BUTTONS_GROUP, GBAPad::R_BUTTON}, ControlID::GCPAD_R_DIGITAL},
|
||||
{{GBAPad::BUTTONS_GROUP, GBAPad::START_BUTTON}, ControlID::GCPAD_START_BUTTON},
|
||||
{{GBAPad::BUTTONS_GROUP, GBAPad::SELECT_BUTTON}, ControlID::GCPAD_Z_BUTTON},
|
||||
{{GBAPad::DPAD_GROUP, DIRECTION_UP}, ControlID::GCPAD_DPAD_UP},
|
||||
{{GBAPad::DPAD_GROUP, DIRECTION_DOWN}, ControlID::GCPAD_DPAD_DOWN},
|
||||
{{GBAPad::DPAD_GROUP, DIRECTION_LEFT}, ControlID::GCPAD_DPAD_LEFT},
|
||||
{{GBAPad::DPAD_GROUP, DIRECTION_RIGHT}, ControlID::GCPAD_DPAD_RIGHT},
|
||||
}};
|
||||
|
||||
ControllerEmu::InputOverrideFunction GetInputOverrideFunction(const ControlsMap& controls_map,
|
||||
size_t i)
|
||||
{
|
||||
|
|
@ -221,6 +236,20 @@ void UnregisterWiiInputOverrider(int controller_index)
|
|||
s_state_arrays[controller_index][i].overriding = false;
|
||||
}
|
||||
|
||||
void RegisterGBAInputOverrider(int controller_index)
|
||||
{
|
||||
Pad::GetGBAConfig()
|
||||
->GetController(controller_index)
|
||||
->SetInputOverrideFunction(GetInputOverrideFunction(s_gbapad_controls_map, controller_index));
|
||||
}
|
||||
|
||||
void UnregisterGBAInputOverrider(int controller_index)
|
||||
{
|
||||
Pad::GetGBAConfig()->GetController(controller_index)->ClearInputOverrideFunction();
|
||||
for (size_t i = ControlID::FIRST_GC_CONTROL; i <= ControlID::LAST_GC_CONTROL; ++i)
|
||||
s_state_arrays[controller_index][i].overriding = false;
|
||||
}
|
||||
|
||||
void SetControlState(int controller_index, ControlID control, double state)
|
||||
{
|
||||
InputState& input_state = s_state_arrays[controller_index][control];
|
||||
|
|
|
|||
|
|
@ -80,6 +80,8 @@ void RegisterGameCubeInputOverrider(int controller_index);
|
|||
void RegisterWiiInputOverrider(int controller_index);
|
||||
void UnregisterGameCubeInputOverrider(int controller_index);
|
||||
void UnregisterWiiInputOverrider(int controller_index);
|
||||
void RegisterGBAInputOverrider(int controller_index);
|
||||
void UnregisterGBAInputOverrider(int controller_index);
|
||||
|
||||
void SetControlState(int controller_index, ControlID control, double state);
|
||||
void ClearControlState(int controller_index, ControlID control);
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user