I find myself in desire of a function with the signature:
-- VtyWidget is from https://github.com/reflex-frp/reflex-vty
now :: a -> VtyWidget t m (Event t a)
For some m. This function would take an element and produce a widget which fires an event once, immediately.
I cannot find such a function or any things that I could use to build this.
I can build things of this type like:
const (pure never)
But that ignores the input and never fires an event.
Is there a way to do this? Or am I not supposed to do this sort of thing?
I'm not sure I fully understand the question but it vaguely sounds like you may want getPostBuild. That is what I use whenever I want things to happen once during initialization of a widget.
Related
I'm writing a network description for a a listbox logic.
It's really simple: I have a behavior for the (Maybe) current selected item, and I want it so that whenever the user adds a new item to the list, the current selected item will select the just-created value.
It's also possible for the user to remove items from the list, and cause various of other changes, so I have to know when a new item is created; I can't just select the last item on every change.
I don't really have any code to show for it because all my speculations on what to do can't even be written with the API*, but I have the context of Frameworks t and (simplified):
bDb :: Behavior t [Entry] -- Created with accumB.
bSelected :: Behavior t (Maybe Entry) -- Created with accumB.
eAddEntry :: Event t () -- User clicked an add button. Created with fromAddHandler.
* Well, I did think about using eAddEntry to select the last entry, but that's so bad and even if it will work it's just a race between the adding of new item and the selecting of it.
How can I go about it?
I gave Cactus's suggestion in the comments a try, and it turns out it couldn't been done (I'd have to bind changes in the middle of let block where the selection behavior, and the list behavior is, but they depend on each other).
So I decided to start again from scratch but over-do it this time, and wrap the entire state in it's own data-type which the network will merely drive. So really all the network will do is call functions on the network according to events, and that's it. It turned out much superior imo, because there's no applicative-style mess, all the functionality is really just simple functions, and it's more modular (I could decide to just not use FRP at all for example, and the changes would be extremely minor -- just switch the firing with the functions; although I'd still have to find where to put the state, which would probably be something impure like IORef).
It's so clean, it looks something similar to this:
data DbState = DbState Database SelectedItem LastAction Etc Etc
emptyState :: DbState
stateRemove, stateAdd :: DbState -> DbState
and the behavior is just:
let bDb = accumB emptyState $ unions
[stateAdd <$ eAddEntry
,stateRemove <$ eRemoveEntry
]
Prior, I had length lines filled with lambdas, <$>, <*>, etc. all over.
And now I just rectimate' and see the changes through that LastAction.
I also added error-checking extremely trivially like that.
Using Gtk2Hs, I want to set up a callback to listen for "Owner change" events for the clipboard.
I can find out if this is supported by the X server by querying:
display <- fmap fromJust displayGetDefault
canBeNotified <- displayRequestSelectionNotification display selectionPrimary
It should be straight forward to do like this in C:
GtkClipboard* clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
g_signal_connect(clipboard, "owner-change",
G_CALLBACK(handle_owner_change), NULL);
I see that callbacks in Gtk2Hs are set up like this:
<widget> `on` <event> $ <action>
Problems
I cannot find any OwnerChangeEvent. Graphics.UI.Gtk.Gdk.EventM does only provide functions of type EventM to query the owner change events.
on from System.Glib.Signals needs an associated widget takes any object and an event associated to that object (on :: object -> Signal object callback -> callback -> IO (ConnectId object). If I have understood X correctly, it should not be necessary to have a window to listen for this event.
Either I have misunderstood how to wrap this together, or I have reached upon a yet not implemented part of gtk in Gtk2Hs.
I therefore looked at the definition of keyPressEvent which looks like this:
keyPressEvent :: WidgetClass self => Signal self (EventM EKey Bool)
keyPressEvent = Signal (eventM "key_press_event" [KeyPressMask])
So, I tested by simply adding a definition for ownerChangeEvent in gtk like keyPressEvent, but subsituted "key_press_event" for "owner_change_event".
That did not work as I got a runtime error about not finding the event. Naturally.
Any ideas on how to properly listen for "owner change" events in gtk if possible?
You're right that this is not implemented at the moment. Adding it shouldn't be too involved, and might be a fun project if you're looking to get into gtk2hs development. You'll want to add a new constructor to the Event type, together with a descriptive type alias like type EventOwnerChange = Event. You will need to extend marshalEvent to handle this new kind of event, and there may be an unmarshalEvent hanging around though I can't remember.
Once that's all done, it should be pretty easy to connect to the appropriate signal using the connect_* family of functions (which are for gtk2hs internal use only and therefore are not exposed in the API). From the documentation for the owner-change event, you will probably need to use something like Signal (connect_OBJECT__NONE "owner-change").
It may also be necessary to add to the EventMask type, though I'm not confident -- test it out and see.
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.
I am currently working on a small game utilizing reactive banana and SDL. Since the goal is mostly to learn more about reactive banana and FRP, I attempted to use dynamic switching to set up a collection of game objects, but without much success so far.
In the Bartab example, the only example that uses event switching I've found, the event that ultimately triggers the creation of a new entry in the collection is acquired from outside the event network instead of using an internal event. My question is: Is this the only way to do it or is it just a special case for this example?
Are there any more examples of dynamic event switching being used in reactive banana?
From what I understand I get a Moment t (Anytime Behavior a), use execute to create an Event t (Anytime Behavior a), which is then in turn used to update the Behavior carrying the collection.
The Moment t (Anytime Behavior a) is created using trimB on a Behavior created from the triggering Event. If this triggering event originates in the event network, it doesn't compile with the error message "Could not deduce (t ~ t1)". I'm not sure what the ~ exactly means, but it obviously throws the error because the two Frameworks values (t) of the event network and this new Moment value are different.
So, long story short, I don't understand how Event Switching in reactive banana works and I'm not sure why. It should be relatively straightforward in theory.
Edit:
-- Event Network
-- ==========================
setupNetwork :: forall t. Frameworks t => (EventSource Command, EventSource ()) -> IORef SpriteMap -> GameMap -> Moment t ()
setupNetwork ((addHandlerE, _), (addHandlerT, _)) sprites map = do
-- Input Events
----------------
eInput <- fromAddHandler addHandlerE -- Player Commands
eFrame <- fromAddHandler addHandlerT -- Event on each Frame
let
[...]
eSpawnEvent :: Event t (DSCoord)
eSpawnEvent = extractCoord <$> eLeftClick
where
extractCoord (LeftClick c) = c
spawnPos :: Frameworks s => Moment s (AnyMoment Behavior DSCoord)
spawnPos = trimB $ stepper (0,0) eSpawnEvent
eSpawnPos <- execute $ (FrameworksMoment spawnPos <$ eSpawnEvent)
[...]
I simply tried to mirror newEntry / eNewEntry from the example, just using a normal event to create the new behavior. This produces the "Could not deduce (t ~ s)" error in spawnPos.
Edit2:
It works, but now the same error comes up on the line where I use execute to create the Event. "Could not deduce t ~ t1"
It appears to me that the code is essentially correct except for a minor mistake:
The compiler rightly complains that spawnPos has a (polymorphic) starting time s while the starting time of eSpawnEvent is fixed to t, which is different. The latter time is brought into scope via the forall t part in type signature of setupNetwork. In other words, t represents the starting time of the whole network.
The simple fix is to change the offending line to
spawnPos :: Frameworks t => Moment t (AnyMoment Behavior DSCoord)
How do you get the content of ui elements when using reactive-banana? The event0 returns an event of type Event (), which has unit type instead of the type of the control. event1 takes an event of type Event w (a -> IO ()), but command is of type Event w (IO ()). mapAccumE and mapAccumB takes pure functions as parameters, so get text foo can't be used with them.
Basically, you want to work with functions instead of data. If you're thinking "How do I create a behavior which has the current text in a box", you don't. Instead you write functions that take the current text as a parameter, and pass it in when necessary. Suppose you want to print the contents of a textbox when a button is pressed. Then you would do something like this:
eButton :: NetworkDescription (Event ())
eButton = event0 button command
network = do
pressButton <- eButton
reactimate $ (\() -> get text foo >>= print) <$> pressButton
If you need to get input into a Behavior, you can similarly use a function with type Behavior (String -> a) (or whatever type you need), and then just pass the string in at the point of the reactimate call.
(Author of reactive-banana speaking. Sorry for the late reply, the possibility of questions being asked here didn't even cross my mind. :-) )
I discovered today that I omitted a very crucial feature from the library: getting the content of a UI element as a Behavior. Embarassing! :-D
John describes the current workaround, but the next version of reactive-banana will include the missing feature.
EDIT: I have released reactive-banana version 0.4 which now includes the functionality in form of a function
fromPoll :: IO a -> NetworkDescription (Behavior a)