Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
1f2a14c5f3 |
|||
|
6f4a95eb46 |
1 changed files with 66 additions and 20 deletions
86
app/UI.hs
86
app/UI.hs
|
|
@ -156,22 +156,38 @@ populateSidebar sidebar =
|
||||||
-- Events
|
-- Events
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
-- | Delegates focus to an internal @GtkText@, so the window's focus widget
|
||||||
|
-- is a descendant of the entry.
|
||||||
|
searchFocused :: Ctx -> IO Bool
|
||||||
|
searchFocused ctx = do
|
||||||
|
mfocus <- Gtk.windowGetFocus (appWindow (ctxWidgets ctx))
|
||||||
|
case mfocus of
|
||||||
|
Nothing -> pure False
|
||||||
|
Just f -> Gtk.widgetIsAncestor f (appSearch (ctxWidgets ctx))
|
||||||
|
|
||||||
wireEvents :: Ctx -> IO ()
|
wireEvents :: Ctx -> IO ()
|
||||||
wireEvents ctx = do
|
wireEvents ctx = do
|
||||||
let dispatch = handleAction ctx
|
let dispatch = handleAction ctx
|
||||||
keyCtrl <- Gtk.eventControllerKeyNew
|
keyCtrl <- Gtk.eventControllerKeyNew
|
||||||
Gtk.eventControllerSetPropagationPhase keyCtrl GtkEnums.PropagationPhaseCapture
|
Gtk.eventControllerSetPropagationPhase keyCtrl GtkEnums.PropagationPhaseCapture
|
||||||
_ <- on keyCtrl #keyPressed $ \keyval _ _ ->
|
_ <- on keyCtrl #keyPressed $ \keyval _ _ -> do
|
||||||
case keyval of
|
-- The controller is in capture phase, so it sees keys before the focused
|
||||||
Gdk.KEY_Right -> dispatch NextPage >> pure True
|
-- search box. While the user is typing a query, let every key through
|
||||||
Gdk.KEY_Left -> dispatch PrevPage >> pure True
|
-- untouched (otherwise letters like `p`/`f` fire commands and the arrows
|
||||||
Gdk.KEY_Page_Down -> dispatch NextChapter >> pure True
|
-- page the view — the latter crashes in scrolling mode via `turnPage`).
|
||||||
Gdk.KEY_Page_Up -> dispatch PrevChapter >> pure True
|
searching <- searchFocused ctx
|
||||||
Gdk.KEY_equal -> dispatch ZoomIn >> pure True
|
if searching
|
||||||
Gdk.KEY_minus -> dispatch ZoomOut >> pure True
|
then pure False
|
||||||
Gdk.KEY_p -> dispatch ToggleMode >> pure True
|
else case keyval of
|
||||||
Gdk.KEY_f -> dispatch SearchFocus >> pure True
|
Gdk.KEY_Right -> dispatch NextPage >> pure True
|
||||||
_ -> pure False
|
Gdk.KEY_Left -> dispatch PrevPage >> pure True
|
||||||
|
Gdk.KEY_Page_Down -> dispatch NextChapter >> pure True
|
||||||
|
Gdk.KEY_Page_Up -> dispatch PrevChapter >> pure True
|
||||||
|
Gdk.KEY_equal -> dispatch ZoomIn >> pure True
|
||||||
|
Gdk.KEY_minus -> dispatch ZoomOut >> pure True
|
||||||
|
Gdk.KEY_p -> dispatch ToggleMode >> pure True
|
||||||
|
Gdk.KEY_f -> dispatch SearchFocus >> pure True
|
||||||
|
_ -> pure False
|
||||||
|
|
||||||
Gtk.widgetAddController (appWindow (ctxWidgets ctx)) keyCtrl
|
Gtk.widgetAddController (appWindow (ctxWidgets ctx)) keyCtrl
|
||||||
_ <- on (appFileBttn (ctxWidgets ctx)) #clicked $ epPicker (Just (appWindow (ctxWidgets ctx))) ctx
|
_ <- on (appFileBttn (ctxWidgets ctx)) #clicked $ epPicker (Just (appWindow (ctxWidgets ctx))) ctx
|
||||||
|
|
@ -185,14 +201,14 @@ wireEvents ctx = do
|
||||||
case keyval of
|
case keyval of
|
||||||
Gdk.KEY_Down -> WebKit.findControllerSearchNext fc >> pure True
|
Gdk.KEY_Down -> WebKit.findControllerSearchNext fc >> pure True
|
||||||
Gdk.KEY_Up -> WebKit.findControllerSearchPrevious fc >> pure True
|
Gdk.KEY_Up -> WebKit.findControllerSearchPrevious fc >> pure True
|
||||||
Gdk.KEY_Escape -> Gtk.setEditableText entry "" >> WebKit.findControllerSearchFinish fc >> Gtk.widgetGrabFocus (appWebView (ctxWidgets ctx)) >> pure True
|
Gdk.KEY_Escape -> Gtk.setEditableText entry "" >> WebKit.findControllerSearchFinish fc >> Gtk.widgetGrabFocus (appWebView (ctxWidgets ctx)) >> snapPagination ctx >> pure True
|
||||||
_ -> pure False
|
_ -> pure False
|
||||||
Gtk.widgetAddController entry searchKeys
|
Gtk.widgetAddController entry searchKeys
|
||||||
|
|
||||||
_ <- on entry #searchChanged $ do
|
_ <- on entry #searchChanged $ do
|
||||||
q <- Gtk.editableGetText entry
|
q <- Gtk.editableGetText entry
|
||||||
if T.null q
|
if T.null q
|
||||||
then WebKit.findControllerSearchFinish fc
|
then WebKit.findControllerSearchFinish fc >> snapPagination ctx
|
||||||
else WebKit.findControllerSearch fc q findOpts maxBound
|
else WebKit.findControllerSearch fc q findOpts maxBound
|
||||||
let runfwd = runSearch ctx nextMatch
|
let runfwd = runSearch ctx nextMatch
|
||||||
_ <- on entry #activate $ runfwd -- pressing enter while having search text
|
_ <- on entry #activate $ runfwd -- pressing enter while having search text
|
||||||
|
|
@ -210,6 +226,7 @@ wireEvents ctx = do
|
||||||
case drop (fromIntegral i) (displayRows (stRefs st) (stToc st)) of
|
case drop (fromIntegral i) (displayRows (stRefs st) (stToc st)) of
|
||||||
(Row _ _ idx frag : _) -> dispatch (GoToChapter idx frag)
|
(Row _ _ idx frag : _) -> dispatch (GoToChapter idx frag)
|
||||||
[] -> pure ()
|
[] -> pure ()
|
||||||
|
|
||||||
pure ()
|
pure ()
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
@ -316,10 +333,16 @@ handleAction ctx action = do
|
||||||
NextChapter -> goto ctx (stIndex st + 1) StartTop
|
NextChapter -> goto ctx (stIndex st + 1) StartTop
|
||||||
PrevChapter -> goto ctx (stIndex st - 1) StartTop
|
PrevChapter -> goto ctx (stIndex st - 1) StartTop
|
||||||
GoToChapter i frag -> goto ctx i (startOfFrag frag)
|
GoToChapter i frag -> goto ctx i (startOfFrag frag)
|
||||||
NextPage -> evalJSBool wv "turnPage(1)" $ \moved ->
|
-- `turnPage` only exists in Paginated mode (it's injected by `pageScript`).
|
||||||
unless moved $ goto ctx (stIndex st + 1) StartTop
|
-- In Scrolling mode there's nothing to page within, so move by chapter.
|
||||||
PrevPage -> evalJSBool wv "turnPage(-1)" $ \moved ->
|
NextPage -> case stMode st of
|
||||||
unless moved $ goto ctx (stIndex st - 1) StartLast
|
Paginated -> evalJSBool wv "turnPage(1)" $ \moved ->
|
||||||
|
unless moved $ goto ctx (stIndex st + 1) StartTop
|
||||||
|
Scrolling -> goto ctx (stIndex st + 1) StartTop
|
||||||
|
PrevPage -> case stMode st of
|
||||||
|
Paginated -> evalJSBool wv "turnPage(-1)" $ \moved ->
|
||||||
|
unless moved $ goto ctx (stIndex st - 1) StartLast
|
||||||
|
Scrolling -> goto ctx (stIndex st - 1) StartTop
|
||||||
ToggleMode -> do
|
ToggleMode -> do
|
||||||
let newMode = if stMode st == Paginated then Scrolling else Paginated
|
let newMode = if stMode st == Paginated then Scrolling else Paginated
|
||||||
atomically $ modifyTVar' (ctxState ctx) $ \s -> s { stMode = newMode }
|
atomically $ modifyTVar' (ctxState ctx) $ \s -> s { stMode = newMode }
|
||||||
|
|
@ -502,13 +525,36 @@ wrapHtml (Html body) start mode =
|
||||||
let safe = T.filter (\c -> c /= '"' && c /= '\\') f
|
let safe = T.filter (\c -> c /= '"' && c /= '\\') f
|
||||||
in "<script>var el=document.getElementById(\"" <> safe <> "\");if(el)goToPage(Math.floor(el.offsetLeft/window.innerWidth));</script>"
|
in "<script>var el=document.getElementById(\"" <> safe <> "\");if(el)goToPage(Math.floor(el.offsetLeft/window.innerWidth));</script>"
|
||||||
|
|
||||||
|
|
||||||
|
-- | Re-align the paginated view after an in-page find. WebKit reveals a match
|
||||||
|
-- by scrolling the (multi-column) layout, which leaves the transform-based
|
||||||
|
-- pager out of sync so several half-pages show at once once the search ends.
|
||||||
|
-- Reset the scroll offset back to zero and re-apply the current page transform.
|
||||||
|
-- No-op in scrolling mode, which scrolls natively and needs no fix-up.
|
||||||
|
snapPagination :: Ctx -> IO ()
|
||||||
|
snapPagination ctx = do
|
||||||
|
mode <- getMode ctx
|
||||||
|
when (mode == Paginated) $
|
||||||
|
evalJSBool
|
||||||
|
(appWebView (ctxWidgets ctx))
|
||||||
|
( T.concat
|
||||||
|
[ "var d=document.documentElement,b=document.body;",
|
||||||
|
"d.scrollLeft=0;d.scrollTop=0;if(b){b.scrollLeft=0;b.scrollTop=0;}",
|
||||||
|
"window.scrollTo(0,0);if(typeof apply==='function'){apply();}true"
|
||||||
|
]
|
||||||
|
)
|
||||||
|
(\_ -> pure ())
|
||||||
|
|
||||||
evalJSBool :: WebKit.WebView -> T.Text -> (Bool -> IO ()) -> IO ()
|
evalJSBool :: WebKit.WebView -> T.Text -> (Bool -> IO ()) -> IO ()
|
||||||
evalJSBool wv src k =
|
evalJSBool wv src k =
|
||||||
WebKit.webViewEvaluateJavascript wv src (-1) Nothing Nothing (Nothing :: Maybe Gio.Cancellable)
|
WebKit.webViewEvaluateJavascript wv src (-1) Nothing Nothing (Nothing :: Maybe Gio.Cancellable)
|
||||||
(Just $ \_ res -> do
|
(Just $ \_ res -> do
|
||||||
val <- WebKit.webViewEvaluateJavascriptFinish wv res
|
-- A JS runtime error makes `...Finish` throw; treat that as `False`
|
||||||
b <- JSC.valueToBoolean val
|
-- rather than letting the exception abort the whole program.
|
||||||
k b)
|
r <- try (WebKit.webViewEvaluateJavascriptFinish wv res >>= JSC.valueToBoolean)
|
||||||
|
case r of
|
||||||
|
Right b -> k b
|
||||||
|
Left (_ :: SomeException) -> k False)
|
||||||
|
|
||||||
chaptersMatching :: T.Text -> M.Map ChapterIndex PlainText -> [ChapterIndex]
|
chaptersMatching :: T.Text -> M.Map ChapterIndex PlainText -> [ChapterIndex]
|
||||||
chaptersMatching q = map fst . filter (matches q . snd) . M.toAscList
|
chaptersMatching q = map fst . filter (matches q . snd) . M.toAscList
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue