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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With