Implement History mouse wheel navigation
This commit is contained in:
@@ -14,6 +14,7 @@ public class HistoryInteractionOpsTests
|
||||
|
||||
private sealed class StopAfterHistoryReturnsException : Exception { }
|
||||
private sealed class StopAfterHistoryVoiceException : Exception { }
|
||||
private sealed class StopAfterHistoryWheelException : Exception { }
|
||||
|
||||
private static void SeedSystem4AdvLayouts(Sys4ScriptProvider scripts, AdvTextHistory history)
|
||||
=> Assert.Equal(9, AdvTextLayoutBootstrap.ApplyLeadingDefinitionsAndResets(
|
||||
@@ -115,6 +116,81 @@ public class HistoryInteractionOpsTests
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class Sc0000HistoryWheelHost : RecordingHost
|
||||
{
|
||||
public VirtualMachine Vm = null!;
|
||||
private readonly Dictionary<int, int> _before = new();
|
||||
private long _now;
|
||||
private bool _wheelQueued;
|
||||
public bool HistoryRowsChanged;
|
||||
public override long InputClockMilliseconds => _now;
|
||||
|
||||
public override void Sleep(long duration)
|
||||
{
|
||||
base.Sleep(duration);
|
||||
_now += System.Math.Max(16, duration);
|
||||
if (!Vm.IsRawInputCallbackActive || ActiveHistoryRenders.Count == 0) return;
|
||||
if (!_wheelQueued)
|
||||
{
|
||||
foreach (var (slot, batch) in ActiveHistoryRenders)
|
||||
_before[slot] = batch.FirstRecordIndex;
|
||||
_wheelQueued = true;
|
||||
Vm.QueueMouseWheelDelta(120); // native wheel-up sign: move toward older retained rows
|
||||
return;
|
||||
}
|
||||
HistoryRowsChanged = ActiveHistoryRenders.Any(pair =>
|
||||
!_before.TryGetValue(pair.Key, out int first) || first != pair.Value.FirstRecordIndex);
|
||||
if (HistoryRowsChanged) throw new StopAfterHistoryWheelException();
|
||||
}
|
||||
|
||||
public override void WaitForInput(int layoutSlot, Func<bool> serviceInputCallback)
|
||||
{
|
||||
Waits++;
|
||||
if (Waits < 8) return;
|
||||
Vm.UpdatePointer(684, 572);
|
||||
while (serviceInputCallback()) { }
|
||||
Assert.True(Vm.TryActivatePointer(684, 572));
|
||||
while (serviceInputCallback()) { }
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MouseWheelDeltaAccumulatesAndIsClearedByOpcode10d()
|
||||
{
|
||||
var script = ScriptAssembler.Assemble(Table, "WHEEL_DELTA",
|
||||
new List<(int, Operand[])>
|
||||
{
|
||||
(0x10d, new[] { G(0x100) }),
|
||||
(0x10d, new[] { G(0x101) }),
|
||||
(0x2, Array.Empty<Operand>()),
|
||||
}, Array.Empty<string>());
|
||||
var vm = new VirtualMachine(script, Table, new RecordingHost());
|
||||
vm.QueueMouseWheelDelta(120);
|
||||
vm.QueueMouseWheelDelta(-360);
|
||||
|
||||
vm.Run();
|
||||
|
||||
Assert.Equal(-240, vm.Globals[0x100]);
|
||||
Assert.Equal(0, vm.Globals[0x101]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RealHistoryWheelUpNavigatesToOlderRetainedRows()
|
||||
{
|
||||
var scripts = Sys4ScriptProvider.Load(Table);
|
||||
var host = new Sc0000HistoryWheelHost();
|
||||
var vm = new VirtualMachine(scripts.RequireByName("SC0000.BIN"), Table, host,
|
||||
new VmOptions(MaxSteps: 2_000_000), scripts);
|
||||
host.Vm = vm;
|
||||
SeedSystem4AdvLayouts(scripts, vm.TextHistory);
|
||||
vm.Globals[0x6c1] = 1;
|
||||
|
||||
Assert.Throws<StopAfterHistoryWheelException>(() => vm.Run());
|
||||
|
||||
Assert.True(host.HistoryRowsChanged);
|
||||
Assert.Equal(8, host.Waits); // the eighth enclosing ADV wait was not released or re-entered
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RealHistoryRendersMultipleRowsAfterSeveralSc0000Messages()
|
||||
{
|
||||
|
||||
@@ -28,6 +28,7 @@ public sealed class VirtualMachine
|
||||
private ExecFrame? _rawInputFrame;
|
||||
private int _pointerX = int.MinValue, _pointerY = int.MinValue;
|
||||
private int _mouseButtonState;
|
||||
private int _mouseWheelDelta;
|
||||
private int _heldInputCallbackMask;
|
||||
private int _queuedInputCallbackMask;
|
||||
private bool _autoMessageEnabled;
|
||||
@@ -95,6 +96,14 @@ public sealed class VirtualMachine
|
||||
/// <summary>Update one native mouse-button bit (left=0x1, right=0x2 in Himegari).</summary>
|
||||
public void UpdateMouseButtonState(int bit, bool pressed) => UpdateMaskBit(ref _mouseButtonState, bit, pressed);
|
||||
|
||||
/// <summary>Accumulate a signed native mouse-wheel delta until op 0x10d consumes it.</summary>
|
||||
public void QueueMouseWheelDelta(int delta)
|
||||
{
|
||||
if (delta == 0) return;
|
||||
Interlocked.Add(ref _mouseWheelDelta, delta);
|
||||
_host.WakeInputCallbackService();
|
||||
}
|
||||
|
||||
/// <summary>Update one held AGE input-callback index used by ops 0xfb/0xff/0x100.</summary>
|
||||
public void UpdateInputCallbackState(int index, bool pressed)
|
||||
{
|
||||
@@ -725,6 +734,9 @@ public sealed class VirtualMachine
|
||||
case "u00415E70":
|
||||
case "get-mouse-button-state": // 0x108
|
||||
Write(a[0], Volatile.Read(ref _mouseButtonState)); return pc + 1;
|
||||
case "u00415F10":
|
||||
case "consume-mouse-wheel-delta": // 0x10d
|
||||
Write(a[0], Interlocked.Exchange(ref _mouseWheelDelta, 0)); return pc + 1;
|
||||
case "u00415EC0":
|
||||
case "get-cursor-virtual": // 0x109
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user