-
Notifications
You must be signed in to change notification settings - Fork 35
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Cursor drift in Gtk.Entry in Todo example app #53
Comments
I did some debugging and I believe I know what's going on here. First, here are some things I experimentally discovered about
Now here's what happens in
|
@niteria Thanks for the comprehensive debugging work! This is indeed problematic. Have you found any way of receiving an "atomic" text change event? I'm guessing that trying to detect what events are relevant (if we say that the |
I think a slightly more reasonable cursor position after replacing would be at the end. But can we detect that an insertion of text actually followed a |
I was able to work around this by routing the incoming event to a skip channel (channel that only keeps the last value) that is consumed by a producer pipe that reemits deduplicated events back into the app. The way the deduplication works is that the pipe thread (blocking) reads from the channel, waits for a millisecond, and tries to (non-blocking) read from the channel again. It returns the last successful read. This is pretty ugly and annoying to scale to more inputs, but I can confirm that it works. What I'm considering doing is to modify the App Simple model to do the 1ms wait in the core loop and pass an ordered list of events to the update function. Then the client can deduplicate as needed. |
That does sound quite ugly. I hope we can find a way to interact with the widget's signals so that we don't have to mess around with this level of detail. Looking at https://stackoverflow.com/a/3878872, they seem to discuss the same thing, and this answer quotes:
This suggests that it shouldn't emit two events on Maybe |
I've tried a simple program of the form:
The answer on the StackOverflow is right that a paste produces one line, but unfortunately, replacement (pressing a key after selecting all text) produces two. I also have a simple example (https://github.com/niteria/gi-gtk-declarative/blob/cursor-drift/examples/LaggyUIDemo.hs) that demonstrates a similar problem if the render takes nontrivial time then the cursor will drift. The example in the MVar page that introduces Skip Channels suggests using them for fast-changing signals like mouse cursor position. I suspect that if I created an example that displays mouse cursor position in a Label I'd run into performance problems. |
FWIW, a workaround in application code may be to use the This requires a slightly more complicated event handler and of course the two signals have different semantics: An obvious difference is that Anyway, here is a complete example in case someone finds this useful. {-# LANGUAGE OverloadedLabels #-}
{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TupleSections #-}
import Control.Monad (void)
import Data.Text (Text)
import GI.Gdk.Structs.EventKey (EventKey)
import qualified GI.Gtk as Gtk
import GI.Gtk.Declarative
import GI.Gtk.Declarative.App.Simple
data State = State Text
data Event = TextChanged Text | Closed
view' :: State -> AppView Gtk.Window Event
view' (State t) =
bin Gtk.Window [on #deleteEvent (const (True, Closed))] $ entry t
update' :: State -> Event -> Transition State Event
update' _ (TextChanged t) = Transition (State t) (pure Nothing)
update' _ Closed = Exit
main :: IO ()
main =
void $
run
App
{ view = view',
update = update',
inputs = [],
initialState = State ""
} This implementation of entry :: Text -> Widget Event
entry t = widget Gtk.Entry [#text := t, onM #changed toEvent]
where
toEvent :: Gtk.Entry -> IO Event
toEvent e = TextChanged <$> Gtk.entryGetText e ... whereas this implementation uses the entry :: Text -> Widget Event
entry t = widget Gtk.Entry [#text := t, onM #keyReleaseEvent toEvent]
where
toEvent :: EventKey -> Gtk.Entry -> IO (Bool, Event)
toEvent _ e = (False,) . TextChanged <$> Gtk.entryGetText e Cheers, |
If you take the app built in https://haskell-at-work.com/episodes/2019-01-10-purely-functional-gtk-part-1-hello-world.html and https://haskell-at-work.com/episodes/2019-01-19-purely-functional-gtk-part-2-todo-mvc.html it manifests the following issue:
After I selected the text I started typing "123", but the cursor moved to the beginning after I typed "1" and I got "231".
The code for the app looks resonable, that's why I raise this issue here.
I have the app in a buildable form at niteria@2ccd690
The text was updated successfully, but these errors were encountered: