Overmind, we can all see the thread that you started. It is still off-topic for you to bring it up here.
On Wed, Aug 10, 2016 at 7:40 AM, OvermindDL1 <[email protected]> wrote: > Well for what it is worth I made a library that can filter messages before > they are handled, so you could, for example, turn a keypress of 'w' into a > message of 'GoForward' and 's' into 'GoBackward' or whatever. And if you > cancel a message then it becomes entirely unhandled and you will never even > see it appear in the event loop (update/subscription/view). If your > convert a message then you never see the original, only your converted > one. I am curious if anyone wants to test it with these issues as I think > it would work very well as a, for example, key remapper. It is a > nice-central place to put key mapping (since it has a model you could even > setup a dynamic key mapper based on user settings with ease). Cleans up > code in update as since you can expect to only receive valid messages if > you filter them here first, vastly cleans up code there to only the actual > work. The package is up at the elm package website (ProgramEx I think I > named it). Still very in testing but it seems to be working utterly > perfect in one of my big apps and has already cleaned up a lot of code. > Here is how the filter code looks (this one 'delegates' to two different > TEA-form libraries their messages and alters a Scroll event just like you'd > expect to do with key remapping): > > ```elm > > filters : Msg -> Model -> ( Msg, States Model Msg )filters msg model = > let > log = > debug_log (model.programFlags.debug_log |> Maybe.withDefault > False) "filters" ( msg, model ) > in > case msg of > Helpers helpersMsg -> > ( msg > , States.enableAll |> States.delegate (helpers_delegate > Helpers helpersMsg) > ) > > Mdl mdlMsg -> > ( msg > , States.enableAll > |> States.delegate > { key = "Mdl" > , update = Just (\_ o -> Material.update mdlMsg o) > , subscriptions = Just (\o -> Material.subscriptions > Mdl o) > } > ) > > > MesgList_Scroll scrolled -> > case model.loc of > RoomLocation rid _ -> > let > doLoad : Bool > doLoad = > (model.firstMessageReached == False) > && (scrolled.pos < 16) > && (model.isLoadingOlder == False) > > && ((Dict.size model.active_room_msgs) >= > 10) > > in > if doLoad then > ( MesgList_LoadOlder rid, States.enableAll ) > else > ( msg, States.disableAll ) > > _ -> > ( msg, States.disableAll ) > > > _ -> > ( msg, States.enableAll ) > > ``` > > On Tuesday, August 9, 2016 at 10:55:43 PM UTC-6, Nick H wrote: >> >> This would be a really nice improvement. I doubt there is anybody using >> the keyboard API who wants to capture every key press. (But would love to >> see a counter-example!) Typing is handled by the HTML library >> >> On Mon, Aug 8, 2016 at 11:01 PM, Janis Voigtländer < >> [email protected]> wrote: >> >>> To up the proposal, here a revision. >>> >>> Instead of proposing to *add* new functions, I now propose to *replace* >>> >>> Keyboard.presses : (KeyCode -> msg) -> Sub msgKeyboard.downs : (KeyCode -> >>> msg) -> Sub msgKeyboard.ups : (KeyCode -> msg) -> Sub msg >>> >>> by >>> >>> Keyboard.presses : (KeyCode -> Maybe msg) -> Sub msgKeyboard.downs : >>> (KeyCode -> Maybe msg) -> Sub msgKeyboard.ups : (KeyCode -> Maybe msg) -> >>> Sub msg >>> >>> It would still be easy to recover the old behavior if wanted in a >>> specific situation, by throwing in Just. But actually I posit that one >>> almost never really wants to capture all keys. Usually, one wants to be >>> selective. That selectiveness has to be expressed somewhere, and doing it >>> with the Maybe type that is designed for such purpose is generally >>> better than going for something like extra NoOp or NothingHappened or >>> NotBound constructors that then need to be handled in a special way in >>> the app’s update logic, without help from the type system. Making >>> consideration of the selectiveness part of the modelling up front would >>> lead to better design. That’s at least the case in the >>> projects/repositories I have pointed to. >>> >>> >>> 2016-08-08 14:48 GMT+02:00 Janis Voigtländer <[email protected]>: >>> >>>> A while back there was a thread >>>> <https://groups.google.com/d/msg/elm-discuss/u-6aCwaJezo/fu-HMPy6CQAJ> >>>> about filtering subscriptions. The following is related, but can also (and >>>> probably better) be consumed and discussed independently. For those that do >>>> have that older thread as context in mind, the following differs in two >>>> essential ways: >>>> >>>> - Earlier, the discussion was about generic filtering of arbitrary >>>> subscriptions. The following involves no genericity whatsoever. It is >>>> only >>>> a proposal about the Keyboard API specifically. >>>> - The earlier thread was not rooted in practice, since very little >>>> stuff had been built yet with subscriptions. In the following, I point >>>> to >>>> how things have played out in practice, based on uses students have >>>> made of >>>> the current API in projects. >>>> >>>> ------------------------------ >>>> >>>> So, on to the subject matter: >>>> >>>> The keyboard package >>>> <http://package.elm-lang.org/packages/elm-lang/keyboard> currently >>>> contains functions such as: >>>> >>>> Keyboard.downs : (KeyCode -> msg) -> Sub msg >>>> >>>> Common uses (I’ll point to several repositories below) are such that >>>> only some keys are relevant for an application. My proposal is to have >>>> functions such as: >>>> >>>> Keyboard.downsSelectively : (KeyCode -> Maybe msg) -> Sub msg >>>> >>>> where the semantics is that if a given KeyCode is mapped to Nothing by >>>> the tagger, then no message gets sent along the subscription; otherwise the >>>> Just is peeled off and the message gets sent. >>>> ------------------------------ >>>> >>>> Let’s look at a practical case, https://github.com/arpad-m/dontfall. >>>> It’s a game, where the player uses the keyboard for part of the control. >>>> Important excerpts from the code are: >>>> >>>> The message type (in https://github.com/arpad-m/don >>>> tfall/blob/master/src/BaseStuff.elm): >>>> >>>> type GameMsg = NothingHappened | ... several other messages ... >>>> >>>> The subscriptions definition (in https://github.com/arpad-m/don >>>> tfall/blob/master/src/main.elm): >>>> >>>> subscriptions : GameData -> Sub GameMsgsubscriptions d = >>>> Sub.batch >>>> ([ Keyboard.downs (\c -> if Char.fromCode c == 'P' then >>>> PauseToogle else NothingHappened) ] ++ >>>> if d.state == Running then >>>> [ AnimationFrame.diffs Tick >>>> , Keyboard.downs (\c -> if Char.fromCode c == ' ' then >>>> JumpDown else NothingHappened) >>>> , Keyboard.ups (\c -> if Char.fromCode c == ' ' then >>>> JumpUp else NothingHappened) >>>> ] >>>> else >>>> []) >>>> >>>> The main case distinction in the main update function (in >>>> https://github.com/arpad-m/dontfall/blob/master/src/main.elm): >>>> >>>> updateScene : GameMsg -> GameData -> (GameData, Cmd GameMsg)updateScene >>>> msg d = >>>> (case d.state of >>>> ... >>>> Running -> case msg of >>>> MouseMove (x,_) -> { d | characterPosX = min x d.flWidth} >>>> Tick t -> stepTime d t >>>> PauseToogle -> { d | state = Paused } >>>> JumpDown -> { d | jumpPressed = True } >>>> JumpUp -> { d | jumpPressed = False } >>>> _ -> d >>>> , Cmd.none >>>> ) >>>> >>>> Given availability of the functions I propose above, the code could >>>> instead look as follows: >>>> >>>> type GameMsg = ... only the other messages, no NothingHappened ... >>>> subscriptions : GameData -> Sub GameMsgsubscriptions d = >>>> Sub.batch >>>> ([ Keyboard.downsSelectively (\c -> if Char.fromCode c == 'P' then >>>> Just PauseToogle else Nothing) ] ++ >>>> if d.state == Running then >>>> [ AnimationFrame.diffs Tick >>>> , Keyboard.downsSelectively (\c -> if Char.fromCode c == ' >>>> ' then Just JumpDown else Nothing) >>>> , Keyboard.upsSelectively (\c -> if Char.fromCode c == ' ' >>>> then Just JumpUp else Nothing) >>>> ] >>>> else >>>> []) >>>> updateScene : GameMsg -> GameData -> (GameData, Cmd GameMsg)updateScene >>>> msg d = >>>> (case d.state of >>>> ... >>>> Running -> case msg of >>>> MouseMove (x,_) -> { d | characterPosX = min x d.flWidth} >>>> Tick t -> stepTime d t >>>> PauseToogle -> { d | state = Paused } >>>> JumpDown -> { d | jumpPressed = True } >>>> JumpUp -> { d | jumpPressed = False } >>>> , Cmd.none >>>> ) >>>> >>>> Advantages: >>>> >>>> 1. >>>> >>>> simpler message type, no special role no-op constructor needed >>>> 2. >>>> >>>> no spurious update and render cycles while the game is running >>>> 3. >>>> >>>> less room for bugs in the update logic >>>> >>>> Some additional comments on the latter two of these points: >>>> >>>> Re 2., given the current implementation, whenever a key is hit that is >>>> not relevant, the update function is still called and produces an unchanged >>>> model, which is then rendered, which is extra/useless work. Since the game >>>> uses Graphics.*, no use can be made of Html.Lazy.* to avoid the >>>> re-rendering. Even if something like Graphics.Lazy.* were available, >>>> having to use it would not be as nice/pure as not causing those spurious >>>> updates in the first place. >>>> >>>> Re 3., given the current implementation, there is both more room for >>>> bugs in the now and in a potential later, when extending the game. In the >>>> now, the programmer has to make sure that NothingHappened does indeed >>>> not change the model. Concerning later, imagine that the programmer extends >>>> the message type for some reason. With the current version of >>>> updateScene, the programmer might forget to actually add a branch for >>>> handling the new message, and the compiler would not catch that, because of >>>> the _ -> d branch that will silently catch not only NothingHappened >>>> but also the new message which was actually supposed to make something >>>> happen. With the version of updateScene after the proposed change, the >>>> situation would be different. Since there is no _ -> d branch in that >>>> Running >>>> -> case msg of ... part anymore (thanks to NothingHappened not being a >>>> thing), the compiler will immediately complain if the message type is >>>> extended but the new message is not handled there. Bug prevented. >>>> ------------------------------ >>>> >>>> It’s not only this single project. I have observed students applying >>>> different strategies to deal with “Not all keys are relevant to my >>>> program”. In each case, using an API with functions of type (KeyCode >>>> -> Maybe msg) -> Sub msg instead of (KeyCode -> msg) -> Sub msg would >>>> have been conceptually nicer and would have simplified things. >>>> >>>> Some more example repos: >>>> >>>> - https://github.com/chemmi/elm-rocket, uses type Key = Left | >>>> Right | ... | NotBound and keyBinding : KeyCode -> Key and then >>>> needs to make sure to correctly (non)-deal with NotBound in >>>> functions like updateKeyDown; whereas just not having NotBound, but >>>> having keyBinding : KeyCode -> Maybe Key and using that in a call >>>> to a (KeyCode -> Maybe msg) -> Sub msg function would simplify >>>> things with the same benefits as in the above more fully elaborated >>>> example >>>> case. >>>> - https://github.com/Dinendal92/Abschlussprojekt-DP2016, less >>>> complete project, but with same approach and issues as in the preceding >>>> example, using type Key = Space | Unknown and fromCode : Int -> Key. >>>> Here, since eliminating Unknown would turn Key into a type with >>>> only one constructor, even more conceptual simplifications would be >>>> enabled >>>> after a switch to the (KeyCode -> Maybe msg) -> Sub msg approach. >>>> - https://github.com/Shaomada/Elm-Project, quite elaborate project, >>>> structured according to TEA, uses no special Key type, instead maps >>>> with Char.fromCode in the calls to the keyboard subscriptions, then >>>> has to case dispatch on actual Chars at several places distributed >>>> over the update functions of the TEA subcomponents. Subscribing with >>>> (KeyCode >>>> -> Maybe msg) -> Sub msg functions should allow to eliminate >>>> branches at some of those places, removing redundancies and room for >>>> bugs. >>>> - https://github.com/Sulring/elmaction, similar story (without TEA) >>>> >>>> >>>> >>> >>> -- >>> You received this message because you are subscribed to the Google >>> Groups "Elm Discuss" group. >>> To unsubscribe from this group and stop receiving emails from it, send >>> an email to [email protected]. >>> For more options, visit https://groups.google.com/d/optout. >>> >> >> -- > You received this message because you are subscribed to the Google Groups > "Elm Discuss" group. > To unsubscribe from this group and stop receiving emails from it, send an > email to [email protected]. > For more options, visit https://groups.google.com/d/optout. > -- You received this message because you are subscribed to the Google Groups "Elm Discuss" group. To unsubscribe from this group and stop receiving emails from it, send an email to [email protected]. For more options, visit https://groups.google.com/d/optout.
