Add Reset button, fix disk swap regression, and add sticky Shift
- Reset button (left of Sound) ejects disk and power-cycles the C64
to BASIC without reloading — loadedADisks is cleared so episode
buttons trigger autostart again on next press
- A-side episode disks that were already loaded now hot-swap instead
of resetting the machine; use Reset explicitly when a cold start
is needed (fixes unintended reset when returning from hero disk)
- Virtual keyboard Shift is now a toggle: tap SHF to lock shift on
(key stays highlighted), tap again to release — no multi-touch
required to type capital letters
This commit is contained in:
+3
@@ -25,6 +25,9 @@ class C64Engine {
|
||||
/** Hot-swap disk in drive 8 without resetting (use for B-side / hero disks). */
|
||||
external fun attachDisk(path: String): Boolean
|
||||
|
||||
/** Eject the disk and hard-reset the C64 to a BASIC ready prompt. */
|
||||
external fun resetMachine(): Boolean
|
||||
|
||||
/** Run the emulator for one video frame (~20 000 CPU cycles). */
|
||||
external fun runFrame()
|
||||
|
||||
|
||||
+17
-2
@@ -91,6 +91,15 @@ class C64KeyboardView @JvmOverloads constructor(
|
||||
|
||||
private val heldKeys = mutableMapOf<Int, C64Key>()
|
||||
|
||||
/* Sticky Shift: tapping either SHF key toggles LSHIFT on/off.
|
||||
* When true, the SHF keys are drawn highlighted and LSHIFT stays held. */
|
||||
private var shiftActive = false
|
||||
private val lshiftKey = C64Key("SHF", C64Engine.KEY_LSHIFT)
|
||||
|
||||
private fun isShiftKey(key: C64Key) =
|
||||
key.codes.size == 1 &&
|
||||
(key.codes[0] == C64Engine.KEY_LSHIFT || key.codes[0] == C64Engine.KEY_RSHIFT)
|
||||
|
||||
private val keyPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
color = Color.parseColor("#2C2C6C")
|
||||
}
|
||||
@@ -127,12 +136,12 @@ class C64KeyboardView @JvmOverloads constructor(
|
||||
|
||||
override fun onDraw(canvas: Canvas) {
|
||||
val held = heldKeys.values.toSet()
|
||||
// Scale text so it fits roughly in half the row height
|
||||
textPaint.textSize = (height / rows.size) * 0.38f
|
||||
|
||||
for (row in keyRects) {
|
||||
for ((rect, key) in row) {
|
||||
canvas.drawRoundRect(rect, 5f, 5f, if (key in held) pressedPaint else keyPaint)
|
||||
val pressed = key in held || (shiftActive && isShiftKey(key))
|
||||
canvas.drawRoundRect(rect, 5f, 5f, if (pressed) pressedPaint else keyPaint)
|
||||
drawKeyLabel(canvas, rect, key.label)
|
||||
}
|
||||
}
|
||||
@@ -157,8 +166,14 @@ class C64KeyboardView @JvmOverloads constructor(
|
||||
when (event.actionMasked) {
|
||||
MotionEvent.ACTION_DOWN, MotionEvent.ACTION_POINTER_DOWN -> {
|
||||
val key = keyAt(ex, ey) ?: return true
|
||||
if (isShiftKey(key)) {
|
||||
// Toggle sticky shift; always use LSHIFT so there's no phantom held key.
|
||||
shiftActive = !shiftActive
|
||||
onKeyEvent?.invoke(lshiftKey, shiftActive)
|
||||
} else {
|
||||
heldKeys[pid] = key
|
||||
onKeyEvent?.invoke(key, true)
|
||||
}
|
||||
invalidate()
|
||||
}
|
||||
MotionEvent.ACTION_UP, MotionEvent.ACTION_POINTER_UP,
|
||||
|
||||
+22
-3
@@ -80,6 +80,7 @@ class MainActivity : AppCompatActivity() {
|
||||
private val episodeButtons = mutableMapOf<DiskSlot, Button>()
|
||||
private val loadedADisks = mutableSetOf<Int>()
|
||||
private var activeSlot: DiskSlot? = null
|
||||
private var currentDiskPath: String? = null // path last passed to engine.loadDisk()
|
||||
|
||||
// ---- Disk import (multi-select) -----------------------------------------
|
||||
|
||||
@@ -157,6 +158,18 @@ class MainActivity : AppCompatActivity() {
|
||||
btnSound.text = if (soundEnabled) "Sound: ON" else "Sound: OFF"
|
||||
}
|
||||
|
||||
val btnReset = findViewById<Button>(R.id.btn_reset)
|
||||
btnReset.setOnClickListener {
|
||||
val ok = engine.resetMachine()
|
||||
if (ok) {
|
||||
loadedADisks.clear()
|
||||
activeSlot = null
|
||||
refreshDiskButtons()
|
||||
drawerLayout.closeDrawer(GravityCompat.START)
|
||||
}
|
||||
appendLog(if (ok) "Reset: C64 reset to BASIC" else "Reset failed")
|
||||
}
|
||||
|
||||
for (slot in diskSlots) {
|
||||
val btn = findViewById<Button>(slot.btnId)
|
||||
episodeButtons[slot] = btn
|
||||
@@ -291,12 +304,18 @@ class MainActivity : AppCompatActivity() {
|
||||
appendLog("Disk not found: ${slot.fileName}")
|
||||
return
|
||||
}
|
||||
// A-side episode disks restart the machine; B-side and hero disks are hot-swapped.
|
||||
val resetMachine = slot.part == 'A'
|
||||
// Only reset + autostart for the first load of an A-side episode disk.
|
||||
// Returning to an already-loaded episode (or any B-side / hero disk) is a hot-swap.
|
||||
// Use the explicit Reset button to restart the machine on demand.
|
||||
val resetMachine = slot.part == 'A' && slot.episode !in loadedADisks
|
||||
val ok = if (resetMachine) engine.loadDisk(file.absolutePath)
|
||||
else engine.attachDisk(file.absolutePath)
|
||||
if (ok) {
|
||||
if (slot.part == 'A') loadedADisks.add(slot.episode)
|
||||
if (resetMachine) {
|
||||
loadedADisks.add(slot.episode)
|
||||
currentDiskPath = file.absolutePath
|
||||
findViewById<Button>(R.id.btn_reset).isEnabled = true
|
||||
}
|
||||
activeSlot = slot
|
||||
refreshDiskButtons()
|
||||
}
|
||||
|
||||
@@ -36,14 +36,16 @@ static uint32_t g_framebuf[FRAME_W * FRAME_H];
|
||||
static pthread_mutex_t g_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
static int g_ready = 0;
|
||||
|
||||
/* Pending disk operations — written by Android thread, consumed by
|
||||
* video_canvas_refresh on the VICE thread (both attach and autostart APIs
|
||||
/* Pending disk/reset operations — written by Android thread, consumed by
|
||||
* video_canvas_refresh on the VICE thread (machine_trigger_reset and disk APIs
|
||||
* must be called from the VICE thread to avoid corrupting internal state).
|
||||
* g_pending_disk → autostart_disk (resets the machine, used for A-side disks)
|
||||
* g_pending_attach → file_system_attach_disk (hot-swap, used for B-side / hero disks) */
|
||||
* g_pending_attach → file_system_attach_disk (hot-swap, used for B-side / hero disks)
|
||||
* g_pending_reset → detach disk + hard reset to BASIC prompt */
|
||||
static pthread_mutex_t g_pending_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
static char g_pending_disk[512];
|
||||
static char g_pending_attach[512];
|
||||
static volatile int g_pending_reset = 0;
|
||||
|
||||
/* =========================================================================
|
||||
* VICE integration (compiled only when libvice.a is linked in)
|
||||
@@ -352,7 +354,20 @@ void video_canvas_refresh(video_canvas_t *canvas,
|
||||
unsigned int xs, unsigned int ys,
|
||||
unsigned int xi, unsigned int yi,
|
||||
unsigned int w, unsigned int h) {
|
||||
/* Process any pending disk operations on the VICE thread. */
|
||||
/* Process any pending operations on the VICE thread. */
|
||||
|
||||
/* Hard reset: eject disk and return to BASIC prompt. */
|
||||
if (g_pending_reset) {
|
||||
g_pending_reset = 0;
|
||||
pthread_mutex_lock(&g_pending_lock);
|
||||
g_pending_disk[0] = '\0';
|
||||
g_pending_attach[0] = '\0';
|
||||
pthread_mutex_unlock(&g_pending_lock);
|
||||
file_system_detach_disk(8, 0);
|
||||
machine_trigger_reset(MACHINE_RESET_MODE_POWER_CYCLE);
|
||||
g_io_frames = g_load_frames = g_attach_frames = 0;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&g_pending_lock);
|
||||
char autostart_path[512] = {0};
|
||||
char attach_path[512] = {0};
|
||||
@@ -649,6 +664,16 @@ JNI_FN(jboolean, attachDisk)(JNIEnv *env, jobject obj, jstring path) {
|
||||
#endif
|
||||
}
|
||||
|
||||
JNI_FN(jboolean, resetMachine)(JNIEnv *env, jobject obj) {
|
||||
(void)env; (void)obj;
|
||||
#ifdef HAVE_VICE_SRC
|
||||
g_pending_reset = 1;
|
||||
return JNI_TRUE;
|
||||
#else
|
||||
return JNI_FALSE;
|
||||
#endif
|
||||
}
|
||||
|
||||
JNI_FN(jboolean, getDriveLed)(JNIEnv *env, jobject obj) {
|
||||
(void)env; (void)obj;
|
||||
#ifdef HAVE_VICE_SRC
|
||||
|
||||
@@ -235,16 +235,35 @@
|
||||
android:text="Import Disks"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_reset"
|
||||
style="?attr/materialButtonOutlinedStyle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:text="Reset"
|
||||
android:textSize="12sp"
|
||||
android:enabled="false" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_sound"
|
||||
style="?attr/materialButtonOutlinedStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Sound: OFF"
|
||||
android:textSize="12sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.drawerlayout.widget.DrawerLayout>
|
||||
|
||||
<!-- Splash screen overlay — covers everything until VICE is ready -->
|
||||
|
||||
Reference in New Issue
Block a user