mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-06-06 05:28:31 +09:00
various mouse nav fixes, font rom update
This commit is contained in:
@@ -3703,13 +3703,12 @@ function openRetunePopup() {
|
|||||||
if (sel < 0) sel = 0
|
if (sel < 0) sel = 0
|
||||||
let scroll = centerScroll(sel, 0, listH, n)
|
let scroll = centerScroll(sel, 0, listH, n)
|
||||||
|
|
||||||
// OK/Cancel button placement (bottom inside row)
|
let done = false
|
||||||
const btnRow = py + ph - 2
|
let confirmed = false
|
||||||
const labelOK = `[ OK ]`.length
|
const buttons = makePopupButtonRow(py + ph - 2, px, pw, [
|
||||||
const labelCan = `[ Cancel ]`.length
|
{ label: 'OK', action: () => { confirmed = true; done = true }, default: true },
|
||||||
const totalW = labelOK + 2 + labelCan
|
{ label: 'Cancel', action: () => { done = true } },
|
||||||
const btnXOk = px + ((pw - totalW) >>> 1)
|
])
|
||||||
const btnXCan = btnXOk + labelOK + 2
|
|
||||||
|
|
||||||
const repaint = () => {
|
const repaint = () => {
|
||||||
con.color_pair(230, colPopupBack)
|
con.color_pair(230, colPopupBack)
|
||||||
@@ -4089,8 +4088,18 @@ const MOUSE_POPUP_STACK = []
|
|||||||
|
|
||||||
// Wrap push/pop so closing a popup also drops any onHoverLeave that would otherwise
|
// Wrap push/pop so closing a popup also drops any onHoverLeave that would otherwise
|
||||||
// be invoked against the popup's stale regions on the next mouse move.
|
// be invoked against the popup's stale regions on the next mouse move.
|
||||||
|
//
|
||||||
|
// When the pop happens with a mouse button still held, the popup was almost certainly
|
||||||
|
// closed by a click. We arm `swallowResidualClick` so the trailing mouse_up (and any
|
||||||
|
// echo mouse_down from that same physical click) doesn't leak into the panel that the
|
||||||
|
// popup was covering. A keyboard close leaves no button held, so this is a no-op.
|
||||||
|
let swallowResidualClick = false
|
||||||
function pushMousePopup(regions) { MOUSE_POPUP_STACK.push(regions); lastHoveredRegion = null }
|
function pushMousePopup(regions) { MOUSE_POPUP_STACK.push(regions); lastHoveredRegion = null }
|
||||||
function popMousePopup() { MOUSE_POPUP_STACK.pop(); lastHoveredRegion = null }
|
function popMousePopup() {
|
||||||
|
MOUSE_POPUP_STACK.pop()
|
||||||
|
lastHoveredRegion = null
|
||||||
|
if ((sys.peek(-37) & 0x07) !== 0) swallowResidualClick = true
|
||||||
|
}
|
||||||
|
|
||||||
function pixelToCell(px, py) {
|
function pixelToCell(px, py) {
|
||||||
return [(py / CELL_PH | 0) + 1, (px / CELL_PW | 0) + 1] // [cy, cx], 1-indexed
|
return [(py / CELL_PH | 0) + 1, (px / CELL_PW | 0) + 1] // [cy, cx], 1-indexed
|
||||||
@@ -4108,6 +4117,16 @@ function dispatchMouseEvent(event) {
|
|||||||
const t = event[0]
|
const t = event[0]
|
||||||
if (t !== 'mouse_down' && t !== 'mouse_wheel' && t !== 'mouse_up' && t !== 'mouse_move') return false
|
if (t !== 'mouse_down' && t !== 'mouse_wheel' && t !== 'mouse_up' && t !== 'mouse_move') return false
|
||||||
|
|
||||||
|
// Eat residual events from the click that just closed a popup. The flag is armed
|
||||||
|
// by popMousePopup when a button was still held at pop time; it clears on the
|
||||||
|
// matching mouse_up so the next fresh press goes through normally.
|
||||||
|
if (swallowResidualClick && MOUSE_POPUP_STACK.length === 0) {
|
||||||
|
if (t === 'mouse_up') { swallowResidualClick = false; return true }
|
||||||
|
if (t === 'mouse_down') { return true }
|
||||||
|
if (t === 'mouse_move') { return true }
|
||||||
|
// mouse_wheel passes through — it's its own gesture, not part of the closing click
|
||||||
|
}
|
||||||
|
|
||||||
const [cy, cx] = pixelToCell(event[1], event[2])
|
const [cy, cx] = pixelToCell(event[1], event[2])
|
||||||
const pool = (MOUSE_POPUP_STACK.length > 0)
|
const pool = (MOUSE_POPUP_STACK.length > 0)
|
||||||
? MOUSE_POPUP_STACK[MOUSE_POPUP_STACK.length - 1]
|
? MOUSE_POPUP_STACK[MOUSE_POPUP_STACK.length - 1]
|
||||||
|
|||||||
@@ -737,6 +737,7 @@ function actActivate() {
|
|||||||
firstRunLatch = true
|
firstRunLatch = true
|
||||||
con.curs_set(0); clearScr()
|
con.curs_set(0); clearScr()
|
||||||
refreshFilePanelCache(windowMode)
|
refreshFilePanelCache(windowMode)
|
||||||
|
pendingPostExecDrain = true
|
||||||
redraw()
|
redraw()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -927,6 +928,7 @@ function actMore() {
|
|||||||
firstRunLatch = true
|
firstRunLatch = true
|
||||||
con.curs_set(0); clearScr()
|
con.curs_set(0); clearScr()
|
||||||
refreshFilePanelCache(windowMode)
|
refreshFilePanelCache(windowMode)
|
||||||
|
pendingPostExecDrain = true
|
||||||
redraw()
|
redraw()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -985,11 +987,17 @@ function setupPanelMouseRegions() {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onClick: (cy, cx, btn) => {
|
onClick: (cy, cx, btn) => {
|
||||||
if (btn !== 1) return
|
|
||||||
const target = scroll[windowMode] + rowIdx
|
const target = scroll[windowMode] + rowIdx
|
||||||
if (target >= dirFileList[windowMode].length) return
|
if (target >= dirFileList[windowMode].length) return
|
||||||
cursor[windowMode] = target
|
if (btn === 1) {
|
||||||
actActivate()
|
cursor[windowMode] = target
|
||||||
|
actActivate()
|
||||||
|
}
|
||||||
|
else if (btn === 2) {
|
||||||
|
cursor[windowMode] = target
|
||||||
|
drawFilePanel()
|
||||||
|
actMore()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -1026,11 +1034,19 @@ _redraw()
|
|||||||
// like fsh.js can hand off with the mouse button still held; without this,
|
// like fsh.js can hand off with the mouse button still held; without this,
|
||||||
// input.withEvent's first call edge-detects that as a fresh mouse_down at the
|
// input.withEvent's first call edge-detects that as a fresh mouse_down at the
|
||||||
// cursor and activates whichever file row happens to sit there.
|
// cursor and activates whichever file row happens to sit there.
|
||||||
input.withEvent(() => {})
|
//
|
||||||
|
// The same problem reappears after every child app returns, but draining
|
||||||
|
// inside the dispatcher callback is undone by TVDOS.SYS:1235 (input.withEvent
|
||||||
|
// unconditionally writes inputwork.oldMouse = its-stale-local-snapshot at the
|
||||||
|
// end of the outer call). So actActivate / actMore set pendingPostExecDrain
|
||||||
|
// and the main loop calls drainInheritedInput() AFTER input.withEvent returns.
|
||||||
|
function drainInheritedInput() { input.withEvent(() => {}) }
|
||||||
|
drainInheritedInput()
|
||||||
|
|
||||||
let redrawRequested = false
|
let redrawRequested = false
|
||||||
let exit = false
|
let exit = false
|
||||||
let firstRunLatch = true
|
let firstRunLatch = true
|
||||||
|
let pendingPostExecDrain = false
|
||||||
|
|
||||||
while (!exit) {
|
while (!exit) {
|
||||||
input.withEvent(event => {
|
input.withEvent(event => {
|
||||||
@@ -1066,6 +1082,16 @@ while (!exit) {
|
|||||||
_redraw()
|
_redraw()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Re-baseline mouse state AFTER input.withEvent returns so its trailing
|
||||||
|
// `inputwork.oldMouse = mouse` (TVDOS.SYS:1235) doesn't overwrite the
|
||||||
|
// freshly-correct state with the stale snapshot taken at the start of the
|
||||||
|
// outer call. Without this, a child app exited by a click leaves zfm with
|
||||||
|
// oldBtns=0 while the user is still holding → spurious mouse_down next poll.
|
||||||
|
if (pendingPostExecDrain) {
|
||||||
|
pendingPostExecDrain = false
|
||||||
|
drainInheritedInput()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
con.curs_set(1)
|
con.curs_set(1)
|
||||||
|
|||||||
@@ -385,6 +385,10 @@ const ag_workMid = new Float32Array(AG_WORK_N)
|
|||||||
const ag_workTmp = new Float32Array(AG_WORK_N >> 1)
|
const ag_workTmp = new Float32Array(AG_WORK_N >> 1)
|
||||||
const ag_bandEnergy = new Float32Array(AG_N_BANDS)
|
const ag_bandEnergy = new Float32Array(AG_N_BANDS)
|
||||||
|
|
||||||
|
// Sub-500 Hz residual — drops out of the wavelet modulator set on purpose,
|
||||||
|
// but we keep its RMS around to drive the bass mark.
|
||||||
|
let ag_bassEnergy = 0
|
||||||
|
|
||||||
// Persistence buffer — float intensity per cell, plus the glyph last written
|
// Persistence buffer — float intensity per cell, plus the glyph last written
|
||||||
// there. Decay shrinks intensity each frame; new beam samples overwrite the
|
// there. Decay shrinks intensity each frame; new beam samples overwrite the
|
||||||
// glyph and bump intensity.
|
// glyph and bump intensity.
|
||||||
@@ -397,6 +401,7 @@ const ag_cellFg = new Int16Array(AG_VIS_H * AG_VIS_W).fill(-1)
|
|||||||
const ag_waveGlyph = new Int16Array(AG_LANE_W * 3).fill(-1)
|
const ag_waveGlyph = new Int16Array(AG_LANE_W * 3).fill(-1)
|
||||||
const ag_stereoGlyph = new Int16Array(AG_LANE_W).fill(-1)
|
const ag_stereoGlyph = new Int16Array(AG_LANE_W).fill(-1)
|
||||||
const ag_stereoFg = new Int16Array(AG_LANE_W).fill(-1)
|
const ag_stereoFg = new Int16Array(AG_LANE_W).fill(-1)
|
||||||
|
let ag_lastBassFg = -1
|
||||||
|
|
||||||
// Render rate-limiter — playmp2 spins ~32 Hz, playtad ~1 Hz, playwav ~100 Hz
|
// Render rate-limiter — playmp2 spins ~32 Hz, playtad ~1 Hz, playwav ~100 Hz
|
||||||
// at decode time. Clamp visual refresh to 20 Hz so each caller can spam
|
// at decode time. Clamp visual refresh to 20 Hz so each caller can spam
|
||||||
@@ -579,6 +584,16 @@ function ag_analyseHaar() {
|
|||||||
ag_bandEnergy[lv] = rms > 1 ? 1 : rms
|
ag_bandEnergy[lv] = rms > 1 ? 1 : rms
|
||||||
len = half
|
len = half
|
||||||
}
|
}
|
||||||
|
// Residual approximation in ag_workMid[0..len-1] holds the sub-500 Hz
|
||||||
|
// energy that the modulator pipeline intentionally discards. Reuse it
|
||||||
|
// to drive the bass mark.
|
||||||
|
let bassSumSq = 0
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
const v = ag_workMid[i]
|
||||||
|
bassSumSq += v * v
|
||||||
|
}
|
||||||
|
const bassRms = Math.sqrt(bassSumSq / len) * 1.8
|
||||||
|
ag_bassEnergy = bassRms > 1 ? 1 : bassRms
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Wavescope (rows 3..5) ──────────────────────────────────────────────────
|
// ── Wavescope (rows 3..5) ──────────────────────────────────────────────────
|
||||||
@@ -652,6 +667,16 @@ const AG_XY_CY = AG_VIS_H >> 1 // centre row
|
|||||||
const AG_XY_SX = 18 // (L−R) → horizontal extent ±36 cells
|
const AG_XY_SX = 18 // (L−R) → horizontal extent ±36 cells
|
||||||
const AG_XY_SY = 9 // (L+R) → vertical extent ±18 cells
|
const AG_XY_SY = 9 // (L+R) → vertical extent ±18 cells
|
||||||
|
|
||||||
|
// Bass mark: 2×2 cell indicator pinned to the centre of the vectorscope so
|
||||||
|
// the bass "subwoofer" sits underneath the beam's pivot point. Half-blocks
|
||||||
|
// form a compact 16×16-pixel "dot" centred in the 16×32-pixel 2×2 area.
|
||||||
|
const AG_BASS_VIS_R0 = AG_XY_CY - 1
|
||||||
|
const AG_BASS_VIS_C0 = AG_XY_CX - 1
|
||||||
|
const AG_BASS_VIS_R1 = AG_BASS_VIS_R0 + 1
|
||||||
|
const AG_BASS_VIS_C1 = AG_BASS_VIS_C0 + 1
|
||||||
|
const AG_BASS_SCR_R = AG_ROW_VIS_TOP + AG_BASS_VIS_R0
|
||||||
|
const AG_BASS_SCR_C = AG_COL_INSIDE_L + AG_BASS_VIS_C0
|
||||||
|
|
||||||
// Glyphs.
|
// Glyphs.
|
||||||
const AG_G_DOT = 0xFA // ·
|
const AG_G_DOT = 0xFA // ·
|
||||||
const AG_G_BSL = 0x5C // \\
|
const AG_G_BSL = 0x5C // \\
|
||||||
@@ -741,7 +766,10 @@ function ag_drawVisualiser() {
|
|||||||
for (let r = 0; r < AG_VIS_H; r++) {
|
for (let r = 0; r < AG_VIS_H; r++) {
|
||||||
const rowOff = r * AG_VIS_W
|
const rowOff = r * AG_VIS_W
|
||||||
const screenY = AG_ROW_VIS_TOP + r
|
const screenY = AG_ROW_VIS_TOP + r
|
||||||
|
const inBassRow = (r === AG_BASS_VIS_R0 || r === AG_BASS_VIS_R1)
|
||||||
for (let c = 0; c < AG_VIS_W; c++) {
|
for (let c = 0; c < AG_VIS_W; c++) {
|
||||||
|
// Bass mark owns its 2×2 cells — let ag_drawBassMark() paint them.
|
||||||
|
if (inBassRow && (c === AG_BASS_VIS_C0 || c === AG_BASS_VIS_C1)) continue
|
||||||
const idx = rowOff + c
|
const idx = rowOff + c
|
||||||
const e = ag_persist[idx]
|
const e = ag_persist[idx]
|
||||||
let levelIdx = (e * 5) | 0
|
let levelIdx = (e * 5) | 0
|
||||||
@@ -758,6 +786,25 @@ function ag_drawVisualiser() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Bass mark (rows 29-30, cols 2-3) ───────────────────────────────────────
|
||||||
|
// Brightness-only indicator driven by the sub-500 Hz residual of the Haar
|
||||||
|
// pyramid. Uses indices 1..4 of the beam palette so the dot never falls all
|
||||||
|
// the way to background — a quiet track still shows a faint amber ember.
|
||||||
|
|
||||||
|
function ag_drawBassMark() {
|
||||||
|
let idx = (ag_bassEnergy * 4) | 0
|
||||||
|
if (idx > 3) idx = 3
|
||||||
|
if (idx < 0) idx = 0
|
||||||
|
const fg = AG_BEAM_PAL[idx + 1]
|
||||||
|
if (fg === ag_lastBassFg) return
|
||||||
|
ag_lastBassFg = fg
|
||||||
|
ag_color(fg, AG_COL_BG)
|
||||||
|
ag_mvprn(AG_BASS_SCR_R, AG_BASS_SCR_C, 0xDC)
|
||||||
|
ag_mvprn(AG_BASS_SCR_R, AG_BASS_SCR_C + 1, 0xDC)
|
||||||
|
ag_mvprn(AG_BASS_SCR_R + 1, AG_BASS_SCR_C, 0xDF)
|
||||||
|
ag_mvprn(AG_BASS_SCR_R + 1, AG_BASS_SCR_C + 1, 0xDF)
|
||||||
|
}
|
||||||
|
|
||||||
// ── Stereo energy bar (row 31) ─────────────────────────────────────────────
|
// ── Stereo energy bar (row 31) ─────────────────────────────────────────────
|
||||||
//
|
//
|
||||||
// Same idea as playtaud.drawStereo() but driven by raw PCM: for each sample,
|
// Same idea as playtaud.drawStereo() but driven by raw PCM: for each sample,
|
||||||
@@ -840,6 +887,8 @@ function audioInit(params) {
|
|||||||
ag_cellGlyph.fill(-1); ag_cellFg.fill(-1)
|
ag_cellGlyph.fill(-1); ag_cellFg.fill(-1)
|
||||||
ag_waveGlyph.fill(-1)
|
ag_waveGlyph.fill(-1)
|
||||||
ag_stereoGlyph.fill(-1); ag_stereoFg.fill(-1)
|
ag_stereoGlyph.fill(-1); ag_stereoFg.fill(-1)
|
||||||
|
ag_bassEnergy = 0
|
||||||
|
ag_lastBassFg = -1
|
||||||
|
|
||||||
con.curs_set(0)
|
con.curs_set(0)
|
||||||
con.clear()
|
con.clear()
|
||||||
@@ -861,6 +910,7 @@ function audioRender() {
|
|||||||
ag_updateXYScope()
|
ag_updateXYScope()
|
||||||
ag_drawWavescope()
|
ag_drawWavescope()
|
||||||
ag_drawVisualiser()
|
ag_drawVisualiser()
|
||||||
|
ag_drawBassMark()
|
||||||
ag_drawStereo()
|
ag_drawStereo()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -240,7 +240,7 @@ function showDialog(opts) {
|
|||||||
|
|
||||||
const c = opts.colours || {}
|
const c = opts.colours || {}
|
||||||
const fg = (c.fg != null) ? c.fg : 254
|
const fg = (c.fg != null) ? c.fg : 254
|
||||||
const bg = (c.bg != null) ? c.bg : 242
|
const bg = (c.bg != null) ? c.bg : 243
|
||||||
const fieldBg = (c.fieldBg != null) ? c.fieldBg : 240
|
const fieldBg = (c.fieldBg != null) ? c.fieldBg : 240
|
||||||
const dimFg = (c.dimFg != null) ? c.dimFg : 249
|
const dimFg = (c.dimFg != null) ? c.dimFg : 249
|
||||||
const hlFg = (c.hlFg != null) ? c.hlFg : 230
|
const hlFg = (c.hlFg != null) ? c.hlFg : 230
|
||||||
@@ -323,25 +323,26 @@ function showDialog(opts) {
|
|||||||
print(f.label + ':')
|
print(f.label + ':')
|
||||||
|
|
||||||
// Top border
|
// Top border
|
||||||
con.color_pair(frameFg, bg)
|
con.color_pair(fieldBg, bg)
|
||||||
con.move(fbRow, fbCol)
|
con.move(fbRow, fbCol)
|
||||||
print('\u00DA' + '\u00C4'.repeat(fw) + '\u00BF')
|
print('\u00EC' + '\u00A9'.repeat(fw) + '\u00ED')
|
||||||
|
|
||||||
// Side borders + content
|
// Side borders + content
|
||||||
con.color_pair(frameFg, bg)
|
|
||||||
con.move(fbRow + 1, fbCol)
|
con.move(fbRow + 1, fbCol)
|
||||||
print('\u00B3')
|
print('\u00AB')
|
||||||
|
|
||||||
con.color_pair(fg, fieldBg)
|
con.color_pair(fg, fieldBg)
|
||||||
const s = fieldScroll(cursors[i], fw)
|
const s = fieldScroll(cursors[i], fw)
|
||||||
const vis = values[i].substring(s, s + fw)
|
const vis = values[i].substring(s, s + fw)
|
||||||
print(vis.padEnd(fw, ' '))
|
print(vis.padEnd(fw, ' '))
|
||||||
con.color_pair(frameFg, bg)
|
|
||||||
|
con.color_pair(fieldBg, bg)
|
||||||
con.move(fbRow + 1, fbCol + fw + 1)
|
con.move(fbRow + 1, fbCol + fw + 1)
|
||||||
print('\u00B3')
|
print('\u00AA')
|
||||||
|
|
||||||
// Bottom border
|
// Bottom border
|
||||||
con.move(fbRow + 2, fbCol)
|
con.move(fbRow + 2, fbCol)
|
||||||
print('\u00C0' + '\u00C4'.repeat(fw) + '\u00D9')
|
print('\u00F4' + '\u00AC'.repeat(fw) + '\u00F5')
|
||||||
con.color_pair(fg, bg)
|
con.color_pair(fg, bg)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -484,7 +485,7 @@ function showDialog(opts) {
|
|||||||
render()
|
render()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (ks === '') {
|
if (ks === '\x08') {
|
||||||
const cur = cursors[focusIdx]
|
const cur = cursors[focusIdx]
|
||||||
if (cur > 0) {
|
if (cur > 0) {
|
||||||
const v = values[focusIdx]
|
const v = values[focusIdx]
|
||||||
@@ -494,7 +495,7 @@ function showDialog(opts) {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (ks === '<FORWARD_DEL>' || ks === '<DEL>') {
|
if (ks === '<DEL>') {
|
||||||
const cur = cursors[focusIdx]
|
const cur = cursors[focusIdx]
|
||||||
const v = values[focusIdx]
|
const v = values[focusIdx]
|
||||||
if (cur < v.length) {
|
if (cur < v.length) {
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.2 KiB |
@@ -127,7 +127,9 @@ class VMEmuExecutable(val windowWidth: Int, val windowHeight: Int, var panelsX:
|
|||||||
internal fun moveView(oldIndex: Int, newIndex: Int?) {
|
internal fun moveView(oldIndex: Int, newIndex: Int?) {
|
||||||
if (oldIndex != newIndex) {
|
if (oldIndex != newIndex) {
|
||||||
if (newIndex != null) {
|
if (newIndex != null) {
|
||||||
vms[newIndex] = vms[oldIndex]
|
val moved = vms[oldIndex]
|
||||||
|
vms[newIndex] = moved
|
||||||
|
moved?.vm?.let { applyMouseInputMappingForPanel(it, newIndex) }
|
||||||
}
|
}
|
||||||
vms[oldIndex] = null
|
vms[oldIndex] = null
|
||||||
}
|
}
|
||||||
@@ -135,6 +137,28 @@ class VMEmuExecutable(val windowWidth: Int, val windowHeight: Int, var panelsX:
|
|||||||
|
|
||||||
internal fun addVMtoView(vm: VM, profileName: String, index: Int) {
|
internal fun addVMtoView(vm: VM, profileName: String, index: Int) {
|
||||||
vms[index] = VMRunnerInfo(vm, profileName)
|
vms[index] = VMRunnerInfo(vm, profileName)
|
||||||
|
applyMouseInputMappingForPanel(vm, index)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wire the VM's IOSpace so the mouse pixels it sees are relative to its own
|
||||||
|
* GPU framebuffer rather than the whole TsvmEmulator window. Each tiled VM
|
||||||
|
* lives at panel (pposX, pposY) with a letterbox inside that panel, so the
|
||||||
|
* offset is `panel origin + (panel size − GPU size) / 2`.
|
||||||
|
*/
|
||||||
|
private fun applyMouseInputMappingForPanel(vm: VM, panelIndex: Int) {
|
||||||
|
val gpu = vm.peripheralTable.getOrNull(1)?.peripheral as? GraphicsAdapter ?: return
|
||||||
|
val pposX = panelIndex % panelsX
|
||||||
|
val pposY = panelIndex / panelsX
|
||||||
|
val gpuW = gpu.config.width
|
||||||
|
val gpuH = gpu.config.height
|
||||||
|
val io = vm.getIO()
|
||||||
|
// TsvmEmulator draws at 1:1 pixel scale, so no GDX viewport is needed.
|
||||||
|
io.inputViewport = null
|
||||||
|
io.inputOriginX = pposX * windowWidth + (windowWidth - gpuW) / 2
|
||||||
|
io.inputOriginY = pposY * windowHeight + (windowHeight - gpuH) / 2
|
||||||
|
io.inputAreaW = gpuW
|
||||||
|
io.inputAreaH = gpuH
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun getCurrentlySelectedVM(): VMRunnerInfo? = if (currentVMselection == null) null else vms[currentVMselection!!]
|
internal fun getCurrentlySelectedVM(): VMRunnerInfo? = if (currentVMselection == null) null else vms[currentVMselection!!]
|
||||||
@@ -201,6 +225,7 @@ class VMEmuExecutable(val windowWidth: Int, val windowHeight: Int, var panelsX:
|
|||||||
val vm1 = getVMbyProfileName("Initial VM")!!
|
val vm1 = getVMbyProfileName("Initial VM")!!
|
||||||
initVMenv(vm1, "Initial VM")
|
initVMenv(vm1, "Initial VM")
|
||||||
vms[0] = VMRunnerInfo(vm1, "Initial VM")
|
vms[0] = VMRunnerInfo(vm1, "Initial VM")
|
||||||
|
applyMouseInputMappingForPanel(vm1, 0)
|
||||||
|
|
||||||
init()
|
init()
|
||||||
}
|
}
|
||||||
@@ -307,6 +332,11 @@ class VMEmuExecutable(val windowWidth: Int, val windowHeight: Int, var panelsX:
|
|||||||
if (currentVMselection != null && vms[currentVMselection!!]?.vm?.id == vm.id) {
|
if (currentVMselection != null && vms[currentVMselection!!]?.vm?.id == vm.id) {
|
||||||
Gdx.input.inputProcessor = vm.getIO()
|
Gdx.input.inputProcessor = vm.getIO()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// peripheralTable[1] (the GPU) was disposed and re-installed; re-apply
|
||||||
|
// the mouse mapping so the rebooted VM keeps targeting its own panel.
|
||||||
|
val panelIndex = vms.indexOfFirst { it?.vm?.id == vm.id }
|
||||||
|
if (panelIndex >= 0) applyMouseInputMappingForPanel(vm, panelIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateGame(delta: Float) {
|
private fun updateGame(delta: Float) {
|
||||||
@@ -434,6 +464,10 @@ class VMEmuExecutable(val windowWidth: Int, val windowHeight: Int, var panelsX:
|
|||||||
this.panelsX = panelsX
|
this.panelsX = panelsX
|
||||||
this.panelsY = panelsY
|
this.panelsY = panelsY
|
||||||
resize(windowWidth * panelsX, windowHeight * panelsY)
|
resize(windowWidth * panelsX, windowHeight * panelsY)
|
||||||
|
// Panel positions shifted, so every VM needs its mouse origin re-mapped.
|
||||||
|
vms.forEachIndexed { index, info ->
|
||||||
|
info?.vm?.let { applyMouseInputMappingForPanel(it, index) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun resize(width: Int, height: Int) {
|
override fun resize(width: Int, height: Int) {
|
||||||
|
|||||||
Reference in New Issue
Block a user