From 4e74eeebbaa441cda3a6846c47d82516878f8f05 Mon Sep 17 00:00:00 2001 From: Akshay Nair Date: Wed, 25 Dec 2024 14:25:09 +0530 Subject: Add searching indication text for search mode + a lot of refactoring --- README.md | 28 +++++++---- cpp/libchelleport.cpp | 33 ++++++------- include/libchelleport.h | 12 +++++ src/Chelleport.hs | 116 +++++++++++++++++++++++++--------------------- src/Chelleport/Config.hs | 3 -- src/Chelleport/Context.hs | 17 ++++--- src/Chelleport/Control.hs | 43 +++++++++-------- src/Chelleport/Draw.hs | 9 ++-- src/Chelleport/OCR.hs | 18 ++++--- src/Chelleport/Types.hs | 5 +- src/Chelleport/Utils.hs | 6 +++ src/Chelleport/View.hs | 19 +++++++- 12 files changed, 186 insertions(+), 123 deletions(-) diff --git a/README.md b/README.md index 6cb9778..e26a626 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,19 @@ # Chelleport Control your mouse pointer with your keyboard -> Note: So far it only supports Linux running X11 display server with a compositor, because that's what I use. Might support more if there's interest. +> Note: So far it only supports Linux running X11 display server with a compositor, because that's what I use. Might look into supporting more systems if there is interest. https://github.com/user-attachments/assets/93ddc1ff-6cbe-4be4-9507-d68de880212a +## Features +- **Text search mode**: Pressing `` puts you in search mode which uses OCR to find words on the screen that you can search and move your cursor to. +- **Labelled hints mode**: This is the default mode. It shows a grid on the screen with 2 keys for each cell. You can move to any cell by pressing the keys shown. +- **Click**: Pressing `space` left clicks at current mouse position. Holding `shift` key left clicks and show the grid again. +- **Select text/Drag-n-drop**: Pressing `Ctrl+V` starts dragging/selecting/holding down left mouse button. Press `space` to stop dragging. Or press `Ctrl+V` again to stop dragging and show the grid again. +- **Double click**: Pressing `2` followed by `space` will click twice. Any digit key followed by `space` will click that many times. +- **Right click**: Pressing `minus` key right clicks at current mouse position. Holding `shift` key right clicks and shows the grid again. +- **Granular movement**: Once you match with a label on the screen, you can use `hjkl` keys to move your cursor. Holding `shift` key will use bigger steps for movements. You can also repeat movement by pressing a digit before the movement. Eg: `5k` moves 5 small steps up. `5K` moves 5 big steps up. + ## Install - Clone the repo and build it yourself: `cabal build chelleport` @@ -12,16 +21,15 @@ https://github.com/user-attachments/assets/93ddc1ff-6cbe-4be4-9507-d68de880212a ## Usage -- Use [sxhkd](https://github.com/baskerville/sxhkd), [shotkey](https://github.com/phenax/shotkey), your window manager or any other key binding manager to set up a keybinding for `chelleport` +Use [sxhkd](https://github.com/baskerville/sxhkd), [shotkey](https://github.com/phenax/shotkey), your window manager or any other key binding manager to set up a keybinding for `chelleport`. + +### Hints mode (default. `` to switch to hints mode) - With the grid open, type any of the key sequences shown on the grid to move the pointer there -- Once there, you can now use `hjkl` keys to make smaller movements. Hold `shift` to move in bigger increments. +- Once a match is found, you can now use `hjkl` keys to make smaller movements. Hold `shift` to move in bigger increments. - Press `space` to click - -## Features -- **Click**: Pressing `space` left clicks at current mouse position. Holding `shift` key left clicks and show the grid again. -- **Select text/Drag-n-drop**: Pressing `Ctrl+V` starts dragging/selecting/holding down left mouse button. Press `space` to stop dragging. Or press `Ctrl+V` again to stop dragging and show the grid again. -- **Double click**: Pressing `2` followed by `space` will click twice. Any digit key followed by `space` will click that many times. -- **Right click**: Pressing `minus` key right clicks at current mouse position. Holding `shift` key right clicks and shows the grid again. -- **Granular movement**: Once you match with a label on the screen, you can use `hjkl` keys to move your cursor. Holding `shift` key will use bigger steps for movements. You can also repeat movement by pressing a digit before the movement. Eg: `5k` moves 5 small steps up. `5K` moves 5 big steps up. +### Search mode (`` to switch to search mode) +- Words that are recognized by OCR will be highlighted +- Type the characters in one of the words to move the cursor to it +- Press `` & `` to go to next/previous match respectively diff --git a/cpp/libchelleport.cpp b/cpp/libchelleport.cpp index 345a454..4ec3599 100644 --- a/cpp/libchelleport.cpp +++ b/cpp/libchelleport.cpp @@ -10,25 +10,16 @@ #include "../include/libchelleport.h" -std::vector extractTextCoordinates(const char *imagePath); - -#define CONFIDENCE_THRESHOLD 30. -#define MIN_CHARACTER_COUNT 2 -const tesseract::PageIteratorLevel RESULT_ITER_MODE = tesseract::RIL_WORD; - OCRMatch *findWordCoordinates(const char *image_path, int *size) { - auto boxes = extractTextCoordinates(image_path); - static OCRMatch *ptr = new OCRMatch[boxes.size()]; - std::copy(boxes.begin(), boxes.end(), ptr); + auto matches = extractTextCoordinates(image_path); + + static OCRMatch *ptr = new OCRMatch[matches.size()]; + std::copy(matches.begin(), matches.end(), ptr); - // for (const auto &box : boxes) { - // std::cout << box.text << "\n\n"; - // std::cout << "Text: " << box.text << "\nPosition: (" << box.startX << "," - // << box.startY << ") -> (" << box.endX << "," << box.endY << ")" - // << "\n\n"; - // } + // for (const auto &match : matches) + // showMatch(match); - *size = boxes.size(); + *size = matches.size(); return ptr; } @@ -52,16 +43,16 @@ std::vector extractTextCoordinates(const char *imagePath) { tesseract::ResultIterator *iterator = tesseract->GetIterator(); auto level = RESULT_ITER_MODE; + int x1, y1, x2, y2; if (iterator != 0) { do { float conf = iterator->Confidence(level); const char *word = iterator->GetUTF8Text(level); - int x1, y1, x2, y2; - iterator->BoundingBox(level, &x1, &y1, &x2, &y2); if (conf > CONFIDENCE_THRESHOLD && word != nullptr && strlen(word) >= MIN_CHARACTER_COUNT) { + iterator->BoundingBox(level, &x1, &y1, &x2, &y2); results.push_back(OCRMatch{x1, y1, x2, y2, word}); } } while (iterator->Next(level)); @@ -74,3 +65,9 @@ std::vector extractTextCoordinates(const char *imagePath) { return results; } + +void showMatch(const OCRMatch &match) { + std::cout << "Text: " << match.text << "; Position: (" << match.startX << "," + << match.startY << ") -> (" << match.endX << "," << match.endY + << ")" << "\n\n"; +} diff --git a/include/libchelleport.h b/include/libchelleport.h index adb9f5d..ef693cb 100644 --- a/include/libchelleport.h +++ b/include/libchelleport.h @@ -1,3 +1,6 @@ +#include +#include + // NOTE: Remember to update size and alignment in ocr hs module on change struct OCRMatch { int startX, startY; @@ -5,6 +8,15 @@ struct OCRMatch { const char *text; }; +#define CONFIDENCE_THRESHOLD 25. +#define MIN_CHARACTER_COUNT 2 + +const tesseract::PageIteratorLevel RESULT_ITER_MODE = tesseract::RIL_WORD; + extern "C" { OCRMatch *findWordCoordinates(const char *image_path, /* returns */ int *size); } + +std::vector extractTextCoordinates(const char *imagePath); + +void showMatch(const OCRMatch &match); diff --git a/src/Chelleport.hs b/src/Chelleport.hs index 14c181a..d8f74ae 100644 --- a/src/Chelleport.hs +++ b/src/Chelleport.hs @@ -4,19 +4,22 @@ import Chelleport.AppShell (MonadAppShell (hideWindow, showWindow, shutdownApp), import Chelleport.Context (initializeContext) import Chelleport.Control ( MonadControl (clickMouseButton, getMousePointerPosition, moveMousePointer, pressMouseButton, releaseMouseButton), + anyAlphanumeric, + anyDigit, + checkKey, + ctrl, directionalIncrement, eventToKeycode, - isKeyPressWith, - isKeyPressed, - isKeyReleaseWith, - withCtrl, - withShift, + key, + pressed, + released, + shift, ) -import Chelleport.Draw (MonadDraw (windowPosition), cellSize) -import Chelleport.KeySequence (findMatchPosition, generateGrid, isKeycodeDigit, isValidKey, keycodeToInt, nextChars, toKeyChar) -import Chelleport.OCR (MonadOCR, getWordsOnScreen) +import Chelleport.Draw (MonadDraw (windowPosition, windowSize), cellSize) +import Chelleport.KeySequence (findMatchPosition, generateGrid, keycodeToInt, nextChars, toKeyChar) +import Chelleport.OCR (MonadOCR (captureScreenshot), getWordsInImage) import Chelleport.Types -import Chelleport.Utils (cIntToInt, clamp, intToCInt, isEmpty, isNotEmpty, itemAt) +import Chelleport.Utils (cIntToInt, clamp, intToCInt, isEmpty, itemAt, (<||>)) import qualified Chelleport.View import Control.Monad (forM_) import Control.Monad.IO.Class (MonadIO) @@ -54,46 +57,37 @@ eventHandler state event = case SDL.eventPayload event of SDL.QuitEvent -> Just ShutdownApp SDL.KeyboardEvent ev - -- Escape - | isKeyPressWith ev SDL.KeycodeEscape -> - Just ShutdownApp - -- minus / underscore - | isKeyPressWith ev SDL.KeycodeMinus || isKeyPressWith ev SDL.KeycodeUnderscore -> - if withShift ev + -- Esc: Quit + | checkKey [key SDL.KeycodeEscape, pressed] ev -> Just ShutdownApp + -- : Enable search mode + | checkKey [ctrl, key SDL.KeycodeS, pressed] ev -> Just $ SetMode defaultSearchMode + -- : Enable hints mode + | checkKey [ctrl, key SDL.KeycodeH, pressed] ev -> Just $ SetMode defaultHintsMode + -- , : Search increment next/prev + | checkKey [ctrl, key SDL.KeycodeN, pressed] ev -> Just $ IncrementHighlightIndex (stateRepetition state) + | checkKey [ctrl, key SDL.KeycodeP, pressed] ev -> Just $ IncrementHighlightIndex (-1 * stateRepetition state) + -- Space / Shift+Space : Left click/chain left click + | checkKey [key SDL.KeycodeSpace, pressed] ev -> + if shift ev + then Just $ ChainMouseClick LeftClick + else Just $ TriggerMouseClick LeftClick + -- Backspace: Reset keys + | checkKey [key SDL.KeycodeBackspace, pressed] ev -> Just ResetKeys + -- : Toggle mouse dragging + | checkKey [ctrl, key SDL.KeycodeV, pressed] ev -> Just MouseDragToggle + -- minus / underscore: Right click/chain right click + | checkKey [key SDL.KeycodeMinus <||> key SDL.KeycodeUnderscore, pressed] ev -> + if shift ev then Just $ ChainMouseClick RightClick else Just $ TriggerMouseClick RightClick - -- 0-9 - | isKeycodeDigit (eventToKeycode ev) -> + -- 0-9: Repetition digit + | checkKey [anyDigit, pressed] ev -> Just $ UpdateRepetition (fromMaybe 0 $ keycodeToInt $ eventToKeycode ev) - -- Enable search mode - | withCtrl ev && isKeyPressWith ev SDL.KeycodeS -> - Just $ SetMode defaultSearchMode - -- Enable hints mode - | withCtrl ev && isKeyPressWith ev SDL.KeycodeH -> - Just $ SetMode defaultHintsMode - -- Search increment next/prev - | withCtrl ev && isKeyPressWith ev SDL.KeycodeN -> Just $ IncrementHighlightIndex (stateRepetition state) - | withCtrl ev && isKeyPressWith ev SDL.KeycodeP -> Just $ IncrementHighlightIndex (-1 * stateRepetition state) - -- Space / Shift+Space - | isKeyPressWith ev SDL.KeycodeSpace -> - if withShift ev - then Just $ ChainMouseClick LeftClick - else Just $ TriggerMouseClick LeftClick - -- Tab / Backspace - | isKeyPressWith ev SDL.KeycodeTab || isKeyPressWith ev SDL.KeycodeBackspace -> - Just ResetKeys - -- Ctrl + V - | withCtrl ev && isKeyPressWith ev SDL.KeycodeV -> - Just MouseDragToggle -- A-Z - | isKeyPressed ev && isValidKey (eventToKeycode ev) -> - Just $ HandleKeyInput $ eventToKeycode ev - -- Shift press - | isKeyPressWith ev SDL.KeycodeLShift || isKeyPressWith ev SDL.KeycodeRShift -> - Just $ UpdateShiftState True - -- Shift release - | isKeyReleaseWith ev SDL.KeycodeLShift || isKeyReleaseWith ev SDL.KeycodeRShift -> - Just $ UpdateShiftState False + | checkKey [anyAlphanumeric, pressed] ev -> Just $ HandleKeyInput $ eventToKeycode ev + -- Shift press/release: Toggle shift mode + | checkKey [pressed, key SDL.KeycodeRShift <||> key SDL.KeycodeLShift] ev -> Just $ UpdateShiftState True + | checkKey [released, key SDL.KeycodeRShift <||> key SDL.KeycodeLShift] ev -> Just $ UpdateShiftState False _ -> Nothing wordPosition :: (MonadDraw m) => OCRMatch -> m (Int, Int) @@ -107,13 +101,17 @@ update state (SetMode mode) = do case mode of ModeHints -> pure (state {stateMode = mode}, Nothing) ModeSearch {} -> do - wordsOnScreen <- getWordsOnScreen + pos <- windowPosition + size <- windowSize + screenshot <- hideWindow >> captureScreenshot pos size <* showWindow + + wordsOnScreen <- getWordsInImage screenshot let updatedMode = mode {searchWords = wordsOnScreen, searchFilteredWords = wordsOnScreen} pure (state {stateMode = updatedMode}, Nothing) -- HINTS MODE: Act on key inputs -update state@(State {stateMode = ModeHints}) (HandleKeyInput key) = do - case (toKeyChar key, validChars) of +update state@(State {stateMode = ModeHints}) (HandleKeyInput keycode) = do + case (toKeyChar keycode, validChars) of (Just keyChar, Just validChars') | stateIsMatched state && keyChar `elem` ("HJKL" :: String) -> do incr <- incrementValue @@ -141,8 +139,8 @@ update state@(State {stateMode = ModeHints}) (HandleKeyInput key) = do else pure (wcell `div` 16, hcell `div` 16) -- SEARCH MODE: Act on key inputs -update state@(State {stateMode = ModeSearch {searchWords, searchInputText}}) (HandleKeyInput key) = do - case toKeyChar key of +update state@(State {stateMode = ModeSearch {searchWords, searchInputText}}) (HandleKeyInput keycode) = do + case toKeyChar keycode of Just keyChar -> do let searchText = searchInputText ++ [toLower keyChar] let matches = filterMatches searchText @@ -193,7 +191,19 @@ update state (MoveMousePosition (x, y)) = do -- Reset entered key sequence and state update state ResetKeys = do - pure (state {stateKeySequence = [], stateIsMatched = False, stateRepetition = 1}, Nothing) + pure + ( state + { stateKeySequence = [], + stateIsMatched = False, + stateRepetition = 1, + stateMode = resetMode (stateMode state) + }, + Nothing + ) + where + resetMode mode@ModeHints = mode + resetMode (ModeSearch {searchWords}) = + defaultSearchMode {searchWords = searchWords, searchFilteredWords = searchWords} -- Trigger click update state (TriggerMouseClick btn) = do @@ -241,5 +251,5 @@ update state (UpdateRepetition count) = do pure (state {stateRepetition = count}, Nothing) -- Set/unset whether shift is pressed -update state (UpdateShiftState shift) = - pure (state {stateIsShiftPressed = shift}, Nothing) +update state (UpdateShiftState shiftPressed) = + pure (state {stateIsShiftPressed = shiftPressed}, Nothing) diff --git a/src/Chelleport/Config.hs b/src/Chelleport/Config.hs index d8b4132..b75abc0 100644 --- a/src/Chelleport/Config.hs +++ b/src/Chelleport/Config.hs @@ -36,6 +36,3 @@ colorFineGrainGrid = SDL.V4 55 52 65 100 windowOpacity :: CFloat windowOpacity = 0.5 - -fontSize :: Int -fontSize = 24 diff --git a/src/Chelleport/Context.hs b/src/Chelleport/Context.hs index 30d8516..3109e05 100644 --- a/src/Chelleport/Context.hs +++ b/src/Chelleport/Context.hs @@ -19,7 +19,7 @@ initializeContext = do window <- initializeWindow renderer <- initializeRenderer window - font <- loadFont + (fontSm, fontLg) <- loadFonts display <- X11.openDisplay "" @@ -27,18 +27,21 @@ initializeContext = do DrawContext { ctxWindow = window, ctxRenderer = renderer, - ctxFont = font, + ctxFontLarge = fontLg, + ctxFontSmall = fontSm, ctxX11Display = display } rawFontData :: ByteString rawFontData = $(embedFileRelative "./static/font.ttf") -loadFont :: IO TTF.Font -loadFont = do - font <- TTF.decode rawFontData fontSize - TTF.setStyle font [TTF.Bold] - pure font +loadFonts :: IO (TTF.Font, TTF.Font) +loadFonts = do + fontSm <- TTF.decode rawFontData 14 + TTF.setStyle fontSm [TTF.Bold] + fontLg <- TTF.decode rawFontData 24 + TTF.setStyle fontLg [TTF.Bold] + pure (fontSm, fontLg) initializeRenderer :: SDL.Window -> IO SDL.Renderer initializeRenderer window = do diff --git a/src/Chelleport/Control.hs b/src/Chelleport/Control.hs index f7a9c66..48bdcb2 100644 --- a/src/Chelleport/Control.hs +++ b/src/Chelleport/Control.hs @@ -1,7 +1,8 @@ module Chelleport.Control where +import Chelleport.KeySequence (isKeycodeDigit, isValidKey) import Chelleport.Types -import Chelleport.Utils (cIntToInt) +import Chelleport.Utils import Control.Concurrent (threadDelay) import Control.Monad (unless) import Control.Monad.IO.Class (MonadIO (liftIO)) @@ -63,31 +64,35 @@ instance (MonadIO m) => MonadControl (AppM m) where xSimulateButtonEvent display X11.button1 False 0 X11.sync display False -isKeyPressed :: SDL.KeyboardEventData -> Bool -isKeyPressed = (SDL.Pressed ==) . SDL.keyboardEventKeyMotion - -isKeyRelease :: SDL.KeyboardEventData -> Bool -isKeyRelease = (SDL.Released ==) . SDL.keyboardEventKeyMotion - eventToKeycode :: SDL.KeyboardEventData -> SDL.Keycode eventToKeycode = SDL.keysymKeycode . SDL.keyboardEventKeysym -isKeyPressWith :: SDL.KeyboardEventData -> SDL.Keycode -> Bool -isKeyPressWith keyboardEvent keyCode = - isKeyPressed keyboardEvent && eventToKeycode keyboardEvent == keyCode - -isKeyReleaseWith :: SDL.KeyboardEventData -> SDL.Keycode -> Bool -isKeyReleaseWith keyboardEvent keyCode = - isKeyRelease keyboardEvent && eventToKeycode keyboardEvent == keyCode - keyModifier :: SDL.KeyboardEventData -> SDL.KeyModifier keyModifier = SDL.keysymModifier . SDL.keyboardEventKeysym -withShift :: SDL.KeyboardEventData -> Bool -withShift ev = SDL.keyModifierLeftShift (keyModifier ev) || SDL.keyModifierRightShift (keyModifier ev) +checkKey :: [SDL.KeyboardEventData -> Bool] -> SDL.KeyboardEventData -> Bool +checkKey = (<&&>) + +pressed :: SDL.KeyboardEventData -> Bool +pressed = (SDL.Pressed ==) . SDL.keyboardEventKeyMotion + +released :: SDL.KeyboardEventData -> Bool +released = (SDL.Released ==) . SDL.keyboardEventKeyMotion + +key :: SDL.Keycode -> SDL.KeyboardEventData -> Bool +key keycode = (keycode ==) . eventToKeycode + +ctrl :: SDL.KeyboardEventData -> Bool +ctrl ev = SDL.keyModifierLeftCtrl (keyModifier ev) || SDL.keyModifierRightCtrl (keyModifier ev) + +shift :: SDL.KeyboardEventData -> Bool +shift ev = SDL.keyModifierLeftShift (keyModifier ev) || SDL.keyModifierRightShift (keyModifier ev) + +anyDigit :: SDL.KeyboardEventData -> Bool +anyDigit = isKeycodeDigit . eventToKeycode -withCtrl :: SDL.KeyboardEventData -> Bool -withCtrl ev = SDL.keyModifierLeftCtrl (keyModifier ev) || SDL.keyModifierRightCtrl (keyModifier ev) +anyAlphanumeric :: SDL.KeyboardEventData -> Bool +anyAlphanumeric = isValidKey . eventToKeycode directionalIncrement :: (CInt, CInt) -> Char -> (Int, Int) directionalIncrement (incX, incY) = \case diff --git a/src/Chelleport/Draw.hs b/src/Chelleport/Draw.hs index 3720ba0..60944a3 100644 --- a/src/Chelleport/Draw.hs +++ b/src/Chelleport/Draw.hs @@ -12,7 +12,7 @@ import qualified SDL.Font as TTF class (Monad m) => MonadDraw m where drawLine :: (CInt, CInt) -> (CInt, CInt) -> m () - drawText :: (CInt, CInt) -> Color -> Text -> m (CInt, CInt) + drawText :: (CInt, CInt) -> Color -> FontSize -> Text -> m (CInt, CInt) drawCircle :: Int -> (CInt, CInt) -> m () fillRect :: (CInt, CInt) -> (CInt, CInt) -> m () setDrawColor :: Color -> m () @@ -33,8 +33,11 @@ instance (MonadIO m) => MonadDraw (AppM m) where let rect = SDL.Rectangle (SDL.P $ SDL.V2 x y) (SDL.V2 w h) SDL.fillRect renderer (Just rect) - drawText (x, y) color text = do - DrawContext {ctxRenderer = renderer, ctxFont = font} <- ask + drawText (x, y) color size text = do + DrawContext {ctxRenderer = renderer, ctxFontSmall, ctxFontLarge} <- ask + let font = case size of + FontSM -> ctxFontSmall + FontLG -> ctxFontLarge surface <- TTF.blended font color text texture <- SDL.createTextureFromSurface renderer surface SDL.freeSurface surface diff --git a/src/Chelleport/OCR.hs b/src/Chelleport/OCR.hs index f6bc5b9..ef9dc9e 100644 --- a/src/Chelleport/OCR.hs +++ b/src/Chelleport/OCR.hs @@ -1,6 +1,7 @@ module Chelleport.OCR (MonadOCR (..)) where import Chelleport.Types +import Control.Concurrent (threadDelay) import Control.Monad.IO.Class (MonadIO (liftIO)) import Control.Monad.RWS (MonadReader (ask)) import qualified Data.ByteString as BS @@ -9,7 +10,6 @@ import Foreign.C (CInt, CString, newCString) import GHC.IO.Handle.FD (withFile) import GHC.IO.IOMode (IOMode (WriteMode)) import qualified Graphics.X11 as X11 -import qualified SDL import System.Directory (removeFile) import System.IO (hPutStrLn) import System.IO.Temp (emptySystemTempFile) @@ -18,16 +18,20 @@ foreign import ccall unsafe "libchelleport.h findWordCoordinates" c_getAllWordCoordinates :: CString -> Ptr CInt -> IO (Ptr OCRMatch) class (Monad m) => MonadOCR m where - getWordsOnScreen :: m [OCRMatch] + captureScreenshot :: (CInt, CInt) -> (CInt, CInt) -> m FilePath + getWordsInImage :: FilePath -> m [OCRMatch] instance (MonadIO m) => MonadOCR (AppM m) where - getWordsOnScreen = do + captureScreenshot (x, y) (width, height) = do ctx <- ask - SDL.V2 width height <- SDL.get . SDL.windowSize . ctxWindow $ ctx - SDL.V2 x y <- SDL.getWindowAbsolutePosition . ctxWindow $ ctx liftIO $ do - imgFilePath <- liftIO $ createTemporaryScreenshot ctx (x, y) (width, height) - findWordCoordinates imgFilePath <* removeFile imgFilePath + threadDelay 20_000 + path <- createTemporaryScreenshot ctx (x, y) (width, height) + threadDelay 20_000 + pure path + + getWordsInImage filePath = do + liftIO $ findWordCoordinates filePath <* removeFile filePath findWordCoordinates :: String -> IO [OCRMatch] findWordCoordinates imgPath = alloca $ \sizePtr -> do diff --git a/src/Chelleport/Types.hs b/src/Chelleport/Types.hs index 43d063f..59ed43d 100644 --- a/src/Chelleport/Types.hs +++ b/src/Chelleport/Types.hs @@ -79,10 +79,13 @@ data AppAction | IncrementHighlightIndex Int deriving (Show, Eq) +data FontSize = FontSM | FontLG deriving (Show, Eq) + data DrawContext = DrawContext { ctxWindow :: SDL.Window, ctxRenderer :: SDL.Renderer, - ctxFont :: TTF.Font, + ctxFontSmall :: TTF.Font, + ctxFontLarge :: TTF.Font, ctxX11Display :: X11.Display } diff --git a/src/Chelleport/Utils.hs b/src/Chelleport/Utils.hs index 9423e5d..3f0dd73 100644 --- a/src/Chelleport/Utils.hs +++ b/src/Chelleport/Utils.hs @@ -43,3 +43,9 @@ itemAt (_ : xs) i = itemAt xs (i - 1) clamp :: (Integral a) => (a, a) -> a -> a clamp (low, high) n = max low (min high n) + +(<&&>) :: [a -> Bool] -> a -> Bool +(<&&>) preds ev = all (\p -> p ev) preds + +(<||>) :: (a -> Bool) -> (a -> Bool) -> a -> Bool +(<||>) p1 p2 x = p1 x || p2 x diff --git a/src/Chelleport/View.hs b/src/Chelleport/View.hs index c2e0ff8..202c636 100644 --- a/src/Chelleport/View.hs +++ b/src/Chelleport/View.hs @@ -15,13 +15,28 @@ render state = case stateMode state of ModeSearch {searchFilteredWords, searchHighlightedIndex} -> renderSearchView state searchFilteredWords searchHighlightedIndex +getSearchText :: State -> String +getSearchText state = case stateMode state of + ModeHints -> "" + ModeSearch {searchInputText, searchFilteredWords, searchHighlightedIndex} -> + "Searching (" ++ matchCount ++ "): " ++ searchInputText + where + matchCount + | isEmpty searchFilteredWords = "0/0" + | otherwise = show (searchHighlightedIndex + 1) ++ "/" ++ show (length searchFilteredWords) + renderSearchView :: (MonadDraw m) => State -> [OCRMatch] -> Int -> m () renderSearchView state matches highlightedIndex = do renderGridLines state + forM_ (zip [0 ..] matches) $ \(index, OCRMatch {matchStartX, matchStartY, matchEndX, matchEndY}) -> do setDrawColor $ if highlightedIndex == index then colorAccent else colorLightGray fillRectVertices (matchStartX, matchStartY) (matchEndX, matchEndY) + (w, h) <- windowSize + drawText (w `div` 2, h `div` 2) colorAccent FontSM (Text.pack $ getSearchText state) + pure () + renderHintsView :: (MonadDraw m) => State -> m () renderHintsView state = do renderGridLines state @@ -50,12 +65,12 @@ renderKeySequence keySequence cell (px, py) = do previousTextWidth <- if isNotEmpty matched - then fst <$> drawText (px, py) colorLightGray (Text.pack matched) + then fst <$> drawText (px, py) colorLightGray FontLG (Text.pack matched) else pure 0 when (isNotEmpty remaining) $ case textColor of Just color -> do - void $ drawText (px + previousTextWidth, py) color $ Text.pack remaining + void $ drawText (px + previousTextWidth, py) color FontLG $ Text.pack remaining Nothing -> pure () pure isVisible -- cgit v1.3.1