Performing a single switch in reactive-banana

荒凉一梦 提交于 2020-03-01 03:19:10

问题


I'm building a multi-modal editor using reactive-banana - and for the most part it's going perfect. To expand on my scenario, the editor is some mapping software, or you could think of it as a very simple vector graphics editor. It currently has two states - selection mode and polygon creation mode. In selection mode, the user is able to select previously created polygons with the right mouse button (which would in theory take you to a new selected mode) or they can begin creating a new polygon with the left mouse button.

The intention is, when the left mouse button is pressed, we switch from selection mode into polygon creation mode. In this mode, a left mouse button means "add a new vertex", until the user returns to the original vertex. At this point, they have closed the polygon, so we return to selection mode.

I've implemented this a few different ways, and recently noticed that event switch almost makes this very elegant. I can have:

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)

Along with

createSectorMode :: Frameworks t
                 => HadoomGUI
                 -> SectorBuilder
                 -> Point V2 Double
                 -> Moment t (Behavior t Diagram)
createSectorMode HadoomGUI{..} initialSectorBuilder firstVertex =
  do mouseClicked <- registerMouseClicked guiMap
     ...

This certainly works - for a single mouse click. If I click on the map once, I switch into sector creation mode from the state I was just in. However, if I click again, defaultMode receives the click event and switches into a new polygon creation mode, throwing away my previous state.

What I'd like to do is switch in defaultMode once and never have the possibility of coming back. Essentially I want to "swap" the Behavior t Diagram produced by defaultMode with the result of createSectorMode.

I understand reactive-banana has problems with garbage collection of dynamic events, but I'm willing to live with that for now. The above formulation is significantly more precise than anything else I've written so far - such as having a single CurrentState variable and filtering various events based on the contents of that. The problem I have with this is that it's too big, and leaves way too much scope for me to mess things up. With switching, I only have in scope the events I can about.


回答1:


The problem is somewhat open ended, so I can't give a definite answer. But I can certainly give my opinion. ;-)

However, what I would probably do is to separate the switching between modes from the behavior within a mode. If we forget about FRP for a moment, your program looks a bit like pair of functions that recursively call themselves:

defaultMode = ... `andthen` sectorMode
sectorMode  = ... `andthen` defaultMode

It's written a bit like a "sequential" program, "first do this mode, then do that mode". I think there is nothing wrong with that, though the default API reactive-banana, in particular switchB, does not support that style very well. You mentioned (privately) that you can write a

once :: Event t a -> Event t a

combinator that lets through the first occurrence of an event, but discards the rest. This is indeed what you would need for the sequential style.

Since you always return to the default mode, though, I would probably try a different approach, where each mode has an event that indicates that it wants to be switched away. The switching itself is taken care of by an "outside" entity. The idea is to avoid the explicit recursion in the program above by some higher-order combinator. In pseudo-code, this would look something like this:

modeManager = switchB initialMode changeMode
changeMode  = defaultModeSwitch `union` sectorModeSwitch

though I am a bit unsure about the details. In fact, I'm not entirely sure if it works at all, you probably still need the once combinator.

Anyway, that's just an idea on how to go about the switching. I fully agree that switching is the right way to deal with different modes.



来源:https://stackoverflow.com/questions/27816012/performing-a-single-switch-in-reactive-banana

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!