我有一个表示键盘输入流(KeyUp 或 KeyDown)的流IObservable<InputEvent> eventStream
。通过应用 Window 运算符,我可以隔离保留输入的持续时间:
public static IObservable<InputEvent> WhileHeld(this IObservable<InputEvent> source, String key) {
var k = source.Where(i => i.Key == key);
return source.Window(k.Press(), _ => k.Release()).Switch().Where(i => i.Key != key);
}
我现在想做的是找到多个窗口的"重叠"。例如:
var ctrlHeld = eventStream.WhileHeld("ctrl");
var shiftHeld = eventStream.WhileHeld("shift");
我想应用一个运算符来查找这两个序列之间的重叠,如下面的大理石图所示,其中
- K = 事件类型,其中
.
是新闻,'
是发布 - i = 事件流
- C = eventStream.WhileHeld("ctrl")
- S = eventStream.WhileHeld("shift")
- r = 结果流
大理石:
K |--.---.-.-.-'-'---.-.-.-'-'---.-.-'-.-.-.-'
i |--a---C-S-b-S-C---C-S-c-C-S---C-S-S-d-S-e-C-
C |--------S-b-S-------S-c---------S-S-d-S-e---
S |----------b-----------c-C---------------e-C
r |----------b-----------c-----------------e---
这样的运算符存在吗?或者它将如何组成?
编辑:
为了帮助可视化实际的事件流(我意识到上面的大理石图有点复杂)。这是我的事件流测试代码:
eventStream.Pump("a", EventType.Down); // should not propagate
eventStream.Pump("ctrl", EventType.Down);
eventStream.Pump("shift", EventType.Down);
eventStream.Pump("b", EventType.Down); // should propagate
eventStream.Pump("shift", EventType.Up);
eventStream.Pump("ctrl", EventType.Up);
eventStream.Pump("ctrl", EventType.Down);
eventStream.Pump("shift", EventType.Down);
eventStream.Pump("c", EventType.Down); // should propagate
eventStream.Pump("ctrl", EventType.Up);
eventStream.Pump("shift", EventType.Up);
eventStream.Pump("ctrl", EventType.Down);
eventStream.Pump("shift", EventType.Down);
eventStream.Pump("shift", EventType.Up);
eventStream.Pump("d", EventType.Down); // should not propagate
eventStream.Pump("shift", EventType.Down);
eventStream.Pump("e", EventType.Down); // should propagate
eventStream.Pump("ctrl", EventType.Up);
(注意:将在电脑前测试/修改一次)
我认为您可以使用GroupJoin
获得"重叠窗口"行为,如下所示:
var ctrlShift = ctrlHeld.GroupJoin(
shiftHeld,
e => eventStream.Key("ctrl").Release(), // "left duration" selector
e => eventStream.Key("shift").Release(), // "right duration" selector
(a,b) => b.Key(a.Key).Press())
.Switch();
但是我永远无法仅凭视觉获得GroupJoin
语法,所以很可能这不是完全一样......基本上,这个想法是:
- 从 ctrlHeld 中的事件开始(直到 ctrl 释放发生)
- 从 shiftHeld 中的事件开始(直到发生 shift 释放)
- 选择重叠 (B),仅选择两个窗口中的印刷机
- 展平那些嵌套的可观察量并返回
(编辑:进一步思考,您也许可以在持续时间内重复使用窗口选择器...
var ctrlShift = ctrlHeld.GroupJoin(
shiftHeld,
e => ctrlHeld, // "left duration" selector
e => shiftHeld, // "right duration" selector
(a,b) => b.Key(a.Key).Press())
.Switch();
啊哈,我知道有一个链接可以解释这一点:
不可估量的 Lee Campbell 谈 Rx 中的 Windows 和缓冲区
我最终提出的解决方案提供了 x.WhileHeld(ctrl).WhileHeld(shift)
的组合语法。诀窍是传入从原始源派生的IObservable<InputEvent>
,而不是string
,然后对其进行窗口化:
public static IObservable<InputEvent> WhileHeld(this IObservable<InputEvent> source, IObservable<InputEvent> held) {
return source.Window(held.Press(), _ => held.Release()).Switch();
}
...
var x = InputStream.Where(i => i.Key == "X");
var shift = InputStream.Where(i => i.Key == "Shift");
var ctrl = InputStream.Where(i => i.Key == "Ctrl");
// gets all key presses of X while shift and control are held
var ctrlShiftX = x.WhileHeld(shift).WhileHeld(ctrl);
这意味着捕获了持有键的第一次和最后一次按键,但我决定这不一定是一件坏事。
我最接近的是:
var ctrlShift = Observable.CombineLatest(ctrlHeld, shiftHeld)
.SelectMany(list =>
{
if (list.Any(o => o != list[0]))
return Observable.Empty<InputEvent>();
else
return Observable.Return(list[0]);
});
它完成了这项工作,但它有点丑陋。如果有比这更简洁或更有表现力的答案,我会接受的