我正在使用reactive-banana
构建一个多模态编辑器 - 在大多数情况下,它非常完美。为了扩展我的场景,编辑器是一些映射软件,或者您可以将其视为非常简单的矢量图形编辑器。它目前有两种状态 - 选择模式和多边形创建模式。在选择模式下,用户可以使用鼠标右键选择以前创建的多边形(理论上这会将您带到新的选定模式),或者他们可以使用鼠标左键开始创建新多边形。
目的是,当按下鼠标左键时,我们从选择模式切换到多边形创建模式。在此模式下,鼠标左键表示"添加新顶点",直到用户返回到原始顶点。此时,他们已经关闭了多边形,因此我们返回到选择模式。
我已经以几种不同的方式实现了这一点,最近注意到事件切换几乎使它变得非常优雅。我可以有:
defaultMode :: Frameworks t => HadoomGUI -> Moment t (Behavior t Diagram)
defaultMode gui@HadoomGUI{..} =
do mouseMoved <- registerMotionNotify guiMap
mouseClicked <- registerMouseClicked guiMap
let lmbClicked = ...
gridCoords = ...
diagram = ...
switchToCreateSector <- execute ((m ->
FrameworksMoment
(=<< trimB =<< createSectorMode gui emptySectorBuilder m)) <$>
(gridCoords <@ lmbClicked))
return (switchB diagram switchToCreateSector)
以及
createSectorMode :: Frameworks t
=> HadoomGUI
-> SectorBuilder
-> Point V2 Double
-> Moment t (Behavior t Diagram)
createSectorMode HadoomGUI{..} initialSectorBuilder firstVertex =
do mouseClicked <- registerMouseClicked guiMap
...
这当然有效 - 只需单击鼠标即可。如果我在地图上单击一次,我会从刚刚所处的状态切换到扇区创建模式。但是,如果我再次单击,defaultMode
会收到单击事件并切换到新的多边形创建模式,从而丢弃我以前的状态。
我想做的是换一次defaultMode
,再也没有回来的可能。从本质上讲,我想将defaultMode
产生的Behavior t Diagram
与createSectorMode
的结果"交换"。
我知道reactive-banana
动态事件的垃圾收集方面存在问题,但我现在愿意忍受这一点。上面的公式比我到目前为止写的任何其他公式都要精确得多 - 例如具有单个CurrentState
变量并根据其内容过滤各种事件。我遇到的问题是它太大了,给我留下了太多的空间来搞砸事情。通过切换,我只能在范围内处理我能处理的事件。
这个问题有点开放式,所以我不能给出一个明确的答案。但我当然可以给出我的意见。;-)
但是,我可能会做的是将模式之间的切换与模式内的行为分开。如果我们暂时忘记FRP,您的程序看起来有点像递归调用自己的一对函数:
defaultMode = ... `andthen` sectorMode
sectorMode = ... `andthen` defaultMode
它写得有点像一个"顺序"程序,"先做这个模式,然后再做那个模式"。我认为这没有错,尽管默认的 API 反应式香蕉,特别是 switchB
,不能很好地支持这种风格。你(私下)提到你可以写一个
once :: Event t a -> Event t a
组合器,允许事件的第一次出现,但丢弃其余事件。这确实是顺序样式所需要的。
但是,由于您总是返回到默认模式,因此我可能会尝试不同的方法,其中每个模式都有一个事件,指示它想要切换。切换本身由"外部"实体负责。这个想法是为了避免上面程序中一些高阶组合器的显式递归。在伪代码中,这看起来像这样:
modeManager = switchB initialMode changeMode
changeMode = defaultModeSwitch `union` sectorModeSwitch
虽然我对细节有点不确定。事实上,我不完全确定它是否有效,您可能仍然需要once
运算器。
无论如何,这只是一个关于如何进行切换的想法。我完全同意切换是处理不同模式的正确方法。