monomer-1.5.1.0: src/Monomer/Main/Handlers.hs
{-|
Module : Monomer.Main.Handlers
Copyright : (c) 2018 Francisco Vallarino
License : BSD-3-Clause (see the LICENSE file)
Maintainer : fjvallarino@gmail.com
Stability : experimental
Portability : non-portable
Handlers for WidgetRequests. Functions in this module handle focus, clipboard,
overlays and all SystemEvent related operations and updates.
-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE Strict #-}
module Monomer.Main.Handlers (
HandlerStep,
handleSystemEvents,
handleResourcesInit,
handleWidgetInit,
handleWidgetDispose,
handleWidgetResult,
handleRequests,
handleResizeWidgets
) where
import Control.Concurrent.Async (async)
import Control.Lens
((&), (^.), (^?), (.~), (?~), (%~), (.=), (?=), (%=), (%%~), _Just, _1, _2, ix, at, use)
import Control.Monad.STM (atomically)
import Control.Concurrent.STM.TChan (TChan, newTChanIO, readTChan, writeTChan)
import Control.Applicative ((<|>))
import Control.Monad
import Control.Monad.IO.Class
import Data.Default
import Data.Foldable (fold, toList)
import Data.Maybe
import Data.Sequence (Seq(..), (|>))
import Data.Text (Text)
import Data.Typeable (Typeable, typeOf)
import SDL (($=))
import qualified Data.Map as Map
import qualified Data.Sequence as Seq
import qualified Data.Set as Set
import qualified SDL
import qualified SDL.Raw.Enum as SDLEnum
import qualified SDL.Raw.Event as SDLE
import qualified SDL.Raw.Types as SDLT
import Monomer.Core
import Monomer.Event
import Monomer.Graphics
import Monomer.Helper (headMay, putStrLnErr, seqStartsWith)
import Monomer.Main.Types
import Monomer.Main.Util
import qualified Monomer.Lens as L
{-|
Tuple representing the current widget environment, widget root and accumulated
WidgetRequests. These requests have already been processed, they are collected
for unit testing purposes.
-}
type HandlerStep s e = (WidgetEnv s e, WidgetNode s e, Seq (WidgetRequest s e))
{-|
Processes a list of SystemEvents dispatching each of the to the corresponding
widget based on the current root. At each step the root may change, new events
may be generated (which will be processed interleaved with the list of events)
and this is handled before returning the latest "HandlerStep".
-}
handleSystemEvents
:: MonomerM s e m
=> WidgetEnv s e -- ^ The initial widget environment.
-> WidgetNode s e -- ^ The initial widget root.
-> [SystemEvent] -- ^ The starting list of events.
-> m (HandlerStep s e) -- ^ The resulting "HandlerStep."
handleSystemEvents wenv widgetRoot baseEvents = nextStep where
mainBtn = wenv ^. L.mainButton
reduceEvt curStep evt = do
let (curWenv, curRoot, curReqs) = curStep
systemEvents <- addRelatedEvents curWenv mainBtn curRoot evt
foldM reduceSysEvt (curWenv, curRoot, curReqs) systemEvents
reduceSysEvt curStep (evt, evtTarget) = do
focused <- getFocusedPath
let (curWenv, curRoot, curReqs) = curStep
let target = fromMaybe focused evtTarget
let curWidget = curRoot ^. L.widget
let targetWni = evtTarget >>= findChildNodeInfoByPath curWenv curRoot
let targetWid = (^. L.widgetId) <$> targetWni
when (isOnEnter evt) $
L.hoveredWidgetId .= targetWid
when (isOnMove evt)
restoreCursorOnWindowEnter
cursorIcon <- getCurrentCursorIcon
hoveredPath <- getHoveredPath
mainBtnPress <- use L.mainBtnPress
inputStatus <- use L.inputStatus
let tmpWenv = curWenv
& L.cursor .~ cursorIcon
& L.hoveredPath .~ hoveredPath
& L.mainBtnPress .~ mainBtnPress
& L.inputStatus .~ inputStatus
let findBranchByPath path = findChildBranchByPath tmpWenv curRoot path
let newWenv = tmpWenv
& L.findBranchByPath .~ findBranchByPath
(wenv2, root2, reqs2) <- handleSystemEvent newWenv curRoot evt target
when (isOnLeave evt) $ do
resetCursorOnNodeLeave evt curStep
L.hoveredWidgetId .= Nothing
return (wenv2, root2, curReqs <> reqs2)
newEvents = preProcessEvents baseEvents
nextStep = foldM reduceEvt (wenv, widgetRoot, Seq.empty) newEvents
-- | Processes a single SystemEvent.
handleSystemEvent
:: MonomerM s e m
=> WidgetEnv s e
-> WidgetNode s e
-> SystemEvent
-> Path
-> m (HandlerStep s e)
handleSystemEvent wenv widgetRoot event currentTarget = do
mainStart <- use L.mainBtnPress
overlay <- getOverlayPath
leaveEnterPair <- use L.leaveEnterPair
let pressed = fmap fst mainStart
case getTargetPath wenv widgetRoot pressed overlay currentTarget event of
Nothing -> return (wenv, widgetRoot, Seq.empty)
Just target -> do
let widget = widgetRoot ^. L.widget
let emptyResult = WidgetResult widgetRoot Seq.empty
let evtResult = widgetHandleEvent widget wenv widgetRoot target event
let resizeWidgets = not (leaveEnterPair && isOnLeave event)
let widgetResult = fromMaybe emptyResult evtResult
& L.requests %~ addFocusReq event
step <- handleWidgetResult wenv resizeWidgets widgetResult
if isOnDrop event
then handleFinalizeDrop step
else return step
-- | Initializes system resources (currently only icons).
handleResourcesInit :: MonomerM s e m => m ()
handleResourcesInit = do
cursors <- foldM insert Map.empty [toEnum 0 ..]
L.cursorIcons .= cursors
where
insert map icon = do
cursor <- SDLE.createSystemCursor (cursorToSDL icon)
return $ Map.insert icon cursor map
-- | Initializes a widget (in general, this is called for root).
handleWidgetInit
:: MonomerM s e m
=> WidgetEnv s e
-> WidgetNode s e
-> m (HandlerStep s e)
handleWidgetInit wenv widgetRoot = do
let widget = widgetRoot ^. L.widget
let widgetResult = widgetInit widget wenv widgetRoot
let reqs = widgetResult ^. L.requests
let focusReqExists = isJust $ Seq.findIndexL isFocusRequest reqs
L.resizeRequests .= Seq.singleton def
step <- handleWidgetResult wenv True widgetResult
currFocus <- getFocusedPath
if not focusReqExists && currFocus == emptyPath
then handleMoveFocus Nothing FocusFwd step
else return step
-- | Disposes a widget (in general, this is called for root).
handleWidgetDispose
:: MonomerM s e m
=> WidgetEnv s e
-> WidgetNode s e
-> m (HandlerStep s e)
handleWidgetDispose wenv widgetRoot = do
let widget = widgetRoot ^. L.widget
let widgetResult = widgetDispose widget wenv widgetRoot
handleWidgetResult wenv False widgetResult
{-|
Handles a WidgetResult instance, processing events and requests, and returning
an updated "HandlerStep".
-}
handleWidgetResult
:: MonomerM s e m
=> WidgetEnv s e
-> Bool
-> WidgetResult s e
-> m (HandlerStep s e)
handleWidgetResult wenv resizeWidgets result = do
let WidgetResult evtRoot reqs = result
step <- handleRequests reqs (wenv, evtRoot, reqs)
resizeRequests <- use L.resizeRequests
if resizeWidgets && not (null resizeRequests)
then handleResizeWidgets step
else return step
-- | Processes a Seq of WidgetRequest, returning the latest "HandlerStep".
handleRequests
:: MonomerM s e m
=> Seq (WidgetRequest s e) -- ^ Requests to process.
-> HandlerStep s e -- ^ Initial state/"HandlerStep".
-> m (HandlerStep s e) -- ^ Updated "HandlerStep",
handleRequests reqs step = foldM handleRequest step reqs where
handleRequest step req = case req of
IgnoreParentEvents -> return step
IgnoreChildrenEvents -> return step
ResizeWidgets wid -> handleAddPendingResize wid step
ResizeWidgetsImmediate wid -> handleResizeImmediate wid step
MoveFocus start dir -> handleMoveFocus start dir step
SetFocus path -> handleSetFocus path step
GetClipboard wid -> handleGetClipboard wid step
SetClipboard cdata -> handleSetClipboard cdata step
StartTextInput rect -> handleStartTextInput rect step
StopTextInput -> handleStopTextInput step
SetOverlay wid path -> handleSetOverlay wid path step
ResetOverlay wid -> handleResetOverlay wid step
SetCursorIcon wid icon -> handleSetCursorIcon wid icon step
ResetCursorIcon wid -> handleResetCursorIcon wid step
StartDrag wid path info -> handleStartDrag wid path info step
StopDrag wid -> handleStopDrag wid step
RenderOnce -> handleRenderOnce step
RenderEvery wid ms repeat -> handleRenderEvery wid ms repeat step
RenderStop wid -> handleRenderStop wid step
RemoveRendererImage path -> handleRemoveRendererImage path step
ExitApplication exit -> handleExitApplication exit step
UpdateWindow req -> handleUpdateWindow req step
UpdateModel fn -> handleUpdateModel fn step
SetWidgetPath wid path -> handleSetWidgetPath wid path step
ResetWidgetPath wid -> handleResetWidgetPath wid step
RaiseEvent msg -> handleRaiseEvent msg step
SendMessage wid msg -> handleSendMessage wid msg step
RunTask wid path handler -> handleRunTask wid path handler step
RunProducer wid path handler -> handleRunProducer wid path handler step
RunInRenderThread wid path handler -> handleRunInRenderThread wid path handler step
-- | Resizes the current root, and marks the render and resized flags.
handleResizeWidgets
:: MonomerM s e m
=> HandlerStep s e -- ^ Current state/"HandlerStep".
-> m (HandlerStep s e) -- ^ Updated state/"HandlerStep".
handleResizeWidgets previousStep = do
windowSize <- use L.windowSize
resizeCheckFn <- makeResizeCheckFn
let viewport = Rect 0 0 (windowSize ^. L.w) (windowSize ^. L.h)
let (wenv, root, reqs) = previousStep
let newWenv = wenv
& L.windowSize .~ windowSize
& L.viewport .~ viewport
let rootWidget = root ^. L.widget
let newResult = widgetResize rootWidget newWenv root viewport resizeCheckFn
L.renderRequested .= True
L.resizeRequests .= Seq.empty
(wenv2, root2, reqs2) <- handleWidgetResult newWenv True newResult
return (wenv2, root2, reqs <> reqs2)
where
makeResizeCheckFn = do
resizeRequests <- use L.resizeRequests
paths <- mapM getWidgetIdPath resizeRequests
let parts = Set.fromDistinctAscList . drop 1 . toList . Seq.inits
let sets = foldMap parts paths
return (`Set.member` sets)
handleAddPendingResize
:: MonomerM s e m
=> WidgetId
-> HandlerStep s e
-> m (HandlerStep s e)
handleAddPendingResize wid step = do
L.resizeRequests %= (|> wid)
return step
handleResizeImmediate
:: MonomerM s e m
=> WidgetId
-> HandlerStep s e
-> m (HandlerStep s e)
handleResizeImmediate wid step = do
L.resizeRequests %= (|> wid)
handleResizeWidgets step
handleMoveFocus
:: MonomerM s e m
=> Maybe WidgetId
-> FocusDirection
-> HandlerStep s e
-> m (HandlerStep s e)
handleMoveFocus startFromWid dir (wenv, root, reqs) = do
oldFocus <- getFocusedPath
tmpOverlay <- getOverlayPath
let tmpFocusWni = findNextFocus wenv dir oldFocus tmpOverlay root
let tmpFocus = tmpFocusWni ^. L.path
let blurEvt = Blur tmpFocus
let wenv0 = wenv & L.focusedPath .~ tmpFocus
(wenv1, root1, reqs1) <- handleSystemEvent wenv0 root blurEvt oldFocus
currFocus <- getFocusedPath
currOverlay <- getOverlayPath
if oldFocus == currFocus
then do
startFrom <- mapM getWidgetIdPath startFromWid
let searchFrom = fromMaybe currFocus startFrom
let newFocusWni = findNextFocus wenv1 dir searchFrom currOverlay root1
let newFocus = newFocusWni ^. L.path
let wenvF = wenv1 & L.focusedPath .~ newFocus
let focusEvt = Focus oldFocus
L.focusedWidgetId .= newFocusWni ^. L.widgetId
L.renderRequested .= True
(wenv2, root2, reqs2) <- handleSystemEvent wenvF root1 focusEvt newFocus
return (wenv2, root2, reqs <> reqs1 <> reqs2)
else
return (wenv1, root1, reqs <> reqs1)
handleSetFocus
:: MonomerM s e m => WidgetId -> HandlerStep s e -> m (HandlerStep s e)
handleSetFocus newFocusWid (wenv, root, reqs) = do
newFocus <- getWidgetIdPath newFocusWid
oldFocus <- getFocusedPath
if oldFocus /= newFocus && newFocus /= emptyPath
then do
let wenv0 = wenv & L.focusedPath .~ newFocus
let blurEvt = Blur newFocus
(wenv1, root1, reqs1) <- handleSystemEvent wenv0 root blurEvt oldFocus
let wenvF = wenv1 & L.focusedPath .~ newFocus
let focusEvt = Focus oldFocus
L.focusedWidgetId .= newFocusWid
L.renderRequested .= True
(wenv2, root2, reqs2) <- handleSystemEvent wenvF root1 focusEvt newFocus
return (wenv2, root2, reqs <> reqs1 <> reqs2)
else
return (wenv, root, reqs)
handleGetClipboard
:: MonomerM s e m => WidgetId -> HandlerStep s e -> m (HandlerStep s e)
handleGetClipboard widgetId (wenv, root, reqs) = do
path <- getWidgetIdPath widgetId
hasText <- SDL.hasClipboardText
contents <- fmap Clipboard $ if hasText
then fmap ClipboardText SDL.getClipboardText
else return ClipboardEmpty
(wenv2, root2, reqs2) <- handleSystemEvent wenv root contents path
return (wenv2, root2, reqs <> reqs2)
handleSetClipboard
:: MonomerM s e m => ClipboardData -> HandlerStep s e -> m (HandlerStep s e)
handleSetClipboard (ClipboardText text) previousStep = do
SDL.setClipboardText text
return previousStep
handleSetClipboard _ previousStep = return previousStep
handleStartTextInput
:: MonomerM s e m => Rect -> HandlerStep s e -> m (HandlerStep s e)
handleStartTextInput (Rect x y w h) previousStep = do
SDL.startTextInput (SDLT.Rect (c x) (c y) (c w) (c h))
return previousStep
where
c x = fromIntegral $ round x
handleStopTextInput :: MonomerM s e m => HandlerStep s e -> m (HandlerStep s e)
handleStopTextInput previousStep = do
SDL.stopTextInput
return previousStep
handleSetOverlay
:: MonomerM s e m
=> WidgetId
-> Path
-> HandlerStep s e
-> m (HandlerStep s e)
handleSetOverlay widgetId path previousStep = do
overlay <- use L.overlayWidgetId
L.overlayWidgetId .= Just widgetId
setWidgetIdPath widgetId path
return $ previousStep
& _1 . L.overlayPath ?~ path
handleResetOverlay
:: MonomerM s e m => WidgetId -> HandlerStep s e -> m (HandlerStep s e)
handleResetOverlay widgetId step = do
let (wenv, root, reqs) = step
let mousePos = wenv ^. L.inputStatus . L.mousePos
overlay <- use L.overlayWidgetId
(wenv2, root2, reqs2) <- if overlay == Just widgetId
then do
let newWenv = wenv & L.overlayPath .~ Nothing
L.overlayWidgetId .= Nothing
void $ handleResetCursorIcon widgetId step
handleSystemEvents newWenv root [Move mousePos]
else
return (wenv, root, Empty)
return (wenv2, root2, reqs <> reqs2)
handleSetCursorIcon
:: MonomerM s e m
=> WidgetId
-> CursorIcon
-> HandlerStep s e
-> m (HandlerStep s e)
handleSetCursorIcon wid icon previousStep = do
cursors <- use L.cursorStack >>= dropNonParentWidgetId wid
L.cursorStack .= (wid, icon) : cursors
cursor <- Map.lookup icon <$> use L.cursorIcons
when (isNothing cursor) $
liftIO . putStrLnErr $ "Invalid handleSetCursorIcon: " ++ show icon
forM_ cursor SDLE.setCursor
return previousStep
handleResetCursorIcon
:: MonomerM s e m
=> WidgetId
-> HandlerStep s e
-> m (HandlerStep s e)
handleResetCursorIcon wid previousStep = do
cursors <- use L.cursorStack >>= dropNonParentWidgetId wid
let newCursors = dropWhile ((==wid) . fst) cursors
let newCursorIcon
| null newCursors = CursorArrow
| otherwise = snd . head $ newCursors
L.cursorStack .= newCursors
cursor <- (Map.! newCursorIcon) <$> use L.cursorIcons
SDLE.setCursor cursor
currentPair <- headMay newCursors & _Just . _1 %%~ getWidgetIdPath
return $ previousStep
& _1 . L.cursor .~ currentPair
handleStartDrag
:: MonomerM s e m
=> WidgetId
-> Path
-> WidgetDragMsg
-> HandlerStep s e
-> m (HandlerStep s e)
handleStartDrag widgetId path dragData previousStep = do
oldDragAction <- use L.dragAction
let prevWidgetId = fmap (^. L.widgetId) oldDragAction
L.dragAction .= Just (DragAction widgetId dragData)
setWidgetIdPath widgetId path
return $ previousStep
& _1 . L.dragStatus ?~ (path, dragData)
handleStopDrag
:: MonomerM s e m
=> WidgetId
-> HandlerStep s e
-> m (HandlerStep s e)
handleStopDrag widgetId previousStep = do
oldDragAction <- use L.dragAction
let prevWidgetId = fmap (^. L.widgetId) oldDragAction
if prevWidgetId == Just widgetId
then do
L.renderRequested .= True
L.dragAction .= Nothing
return $ previousStep
& _1 . L.dragStatus .~ Nothing
else return previousStep
handleFinalizeDrop
:: MonomerM s e m
=> HandlerStep s e
-> m (HandlerStep s e)
handleFinalizeDrop previousStep = do
dragAction <- use L.dragAction
let widgetId = fmap (^. L.widgetId) dragAction
if isJust widgetId
then do
L.renderRequested .= True
L.dragAction .= Nothing
return $ previousStep
& _1 . L.dragStatus .~ Nothing
else return previousStep
handleRenderOnce :: MonomerM s e m => HandlerStep s e -> m (HandlerStep s e)
handleRenderOnce previousStep = do
L.renderRequested .= True
return previousStep
handleRenderEvery
:: MonomerM s e m
=> WidgetId
-> Millisecond
-> Maybe Int
-> HandlerStep s e
-> m (HandlerStep s e)
handleRenderEvery widgetId ms repeat previousStep = do
schedule <- use L.renderSchedule
L.renderSchedule .= addSchedule schedule
return previousStep
where
(wenv, _, _) = previousStep
newValue = RenderSchedule {
_rsWidgetId = widgetId,
_rsStart = _weTimestamp wenv,
_rsMs = ms,
_rsRepeat = repeat
}
addSchedule schedule
| ms > 0 = Map.insert widgetId newValue schedule
| otherwise = schedule
handleRenderStop
:: MonomerM s e m => WidgetId -> HandlerStep s e -> m (HandlerStep s e)
handleRenderStop widgetId previousStep = do
schedule <- use L.renderSchedule
L.renderSchedule .= Map.delete widgetId schedule
return previousStep
handleRemoveRendererImage
:: MonomerM s e m => Text -> HandlerStep s e -> m (HandlerStep s e)
handleRemoveRendererImage name previousStep = do
renderMethod <- use L.renderMethod
case renderMethod of
Left renderer -> liftIO $ deleteImage renderer name
Right chan -> liftIO . atomically $ writeTChan chan (MsgRemoveImage name)
return previousStep
handleExitApplication
:: MonomerM s e m => Bool -> HandlerStep s e -> m (HandlerStep s e)
handleExitApplication exit previousStep = do
L.exitApplication .= exit
return previousStep
handleUpdateWindow
:: MonomerM s e m => WindowRequest -> HandlerStep s e -> m (HandlerStep s e)
handleUpdateWindow windowRequest previousStep = do
window <- use L.window
case windowRequest of
WindowSetTitle title -> SDL.windowTitle window $= title
WindowSetFullScreen -> SDL.setWindowMode window SDL.FullscreenDesktop
WindowMaximize -> SDL.setWindowMode window SDL.Maximized
WindowMinimize -> SDL.setWindowMode window SDL.Minimized
WindowRestore -> SDL.setWindowMode window SDL.Windowed
WindowBringToFront -> SDL.raiseWindow window
return previousStep
handleUpdateModel
:: MonomerM s e m => (s -> s) -> HandlerStep s e -> m (HandlerStep s e)
handleUpdateModel fn (wenv, root, reqs) = do
L.mainModel .= _weModel wenv2
return (wenv2, root, reqs)
where
wenv2 = wenv & L.model %~ fn
handleSetWidgetPath
:: MonomerM s e m => WidgetId -> Path -> HandlerStep s e -> m (HandlerStep s e)
handleSetWidgetPath wid path step = do
setWidgetIdPath wid path
return step
handleResetWidgetPath
:: MonomerM s e m => WidgetId -> HandlerStep s e -> m (HandlerStep s e)
handleResetWidgetPath wid step = do
delWidgetIdPath wid
return step
handleRaiseEvent
:: forall s e m msg . (MonomerM s e m, Typeable msg)
=> msg
-> HandlerStep s e
-> m (HandlerStep s e)
handleRaiseEvent message step = do
return step
where
message = "Invalid state. RaiseEvent reached main handler. Type: "
handleSendMessage
:: forall s e m msg . (MonomerM s e m, Typeable msg)
=> WidgetId
-> msg
-> HandlerStep s e
-> m (HandlerStep s e)
handleSendMessage widgetId message (wenv, root, reqs) = do
path <- getWidgetIdPath widgetId
let emptyResult = WidgetResult root Seq.empty
let widget = root ^. L.widget
let msgResult = widgetHandleMessage widget wenv root path message
let result = fromMaybe emptyResult msgResult
(newWenv, newRoot, newReqs) <- handleWidgetResult wenv True result
return (newWenv, newRoot, reqs <> newReqs)
handleRunTask
:: forall s e m i . (MonomerM s e m, Typeable i)
=> WidgetId
-> Path
-> IO i
-> HandlerStep s e
-> m (HandlerStep s e)
handleRunTask widgetId path handler previousStep = do
asyncTask <- liftIO $ async (liftIO handler)
previousTasks <- use L.widgetTasks
L.widgetTasks .= previousTasks |> WidgetTask widgetId asyncTask
setWidgetIdPath widgetId path
return previousStep
handleRunProducer
:: forall s e m i . (MonomerM s e m, Typeable i)
=> WidgetId
-> Path
-> ((i -> IO ()) -> IO ())
-> HandlerStep s e
-> m (HandlerStep s e)
handleRunProducer widgetId path handler previousStep = do
newChannel <- liftIO newTChanIO
asyncTask <- liftIO $ async (liftIO $ handler (sendMessage newChannel))
previousTasks <- use L.widgetTasks
L.widgetTasks .= previousTasks |> WidgetProducer widgetId newChannel asyncTask
setWidgetIdPath widgetId path
return previousStep
handleRunInRenderThread
:: forall s e m i . (MonomerM s e m, Typeable i)
=> WidgetId
-> Path
-> IO i
-> HandlerStep s e
-> m (HandlerStep s e)
handleRunInRenderThread widgetId path handler previousStep = do
renderMethod <- use L.renderMethod
task <- case renderMethod of
Left renderer -> do
-- Force running in main thread to avoid issues with OpenGL
result <- liftIO handler
return (return result)
Right chan -> do
return $ liftIO (taskWrapper chan)
handleRunTask widgetId path task previousStep
where
taskWrapper renderChannel = do
msgChan <- newTChanIO
atomically $ writeTChan renderChannel (MsgRunInRender msgChan handler)
atomically $ readTChan msgChan
sendMessage :: TChan e -> e -> IO ()
sendMessage channel message = atomically $ writeTChan channel message
addFocusReq
:: SystemEvent
-> Seq (WidgetRequest s e)
-> Seq (WidgetRequest s e)
addFocusReq (KeyAction mod code KeyPressed) reqs = newReqs where
isTabPressed = isKeyTab code
stopProcessing = isJust $ Seq.findIndexL isIgnoreParentEvents reqs
focusReqExists = isJust $ Seq.findIndexL isFocusRequest reqs
focusReqNeeded = isTabPressed && not stopProcessing && not focusReqExists
direction
| mod ^. L.leftShift = FocusBwd
| otherwise = FocusFwd
newReqs
| focusReqNeeded = reqs |> MoveFocus Nothing direction
| otherwise = reqs
addFocusReq _ reqs = reqs
preProcessEvents :: [SystemEvent] -> [SystemEvent]
preProcessEvents [] = []
preProcessEvents (e:es) = case e of
WheelScroll p _ _ -> e : Move p : preProcessEvents es
_ -> e : preProcessEvents es
addRelatedEvents
:: MonomerM s e m
=> WidgetEnv s e
-> Button
-> WidgetNode s e
-> SystemEvent
-> m [(SystemEvent, Maybe Path)]
addRelatedEvents wenv mainBtn widgetRoot evt = case evt of
Move point -> do
(target, hoverEvts) <- addHoverEvents wenv widgetRoot point
-- Update input status
updateInputStatusMousePos point
-- Drag event
mainPress <- use L.mainBtnPress
draggedMsg <- getDraggedMsgInfo
let pressed = fmap fst mainPress
let isPressed = target == pressed
let dragEvts = case draggedMsg of
Just (path, msg) -> [(Drag point path msg, target) | not isPressed]
_ -> []
when (isJust mainPress || isJust draggedMsg) $
L.renderRequested .= True
return $ hoverEvts ++ dragEvts ++ [(evt, Nothing)]
ButtonAction point btn BtnPressed _ -> do
overlay <- getOverlayPath
let start = fromMaybe emptyPath overlay
let widget = widgetRoot ^. L.widget
let wni = widgetFindByPoint widget wenv widgetRoot start point
let curr = fmap (^. L.path) wni
when (btn == mainBtn) $
L.mainBtnPress .= fmap (, point) curr
updateInputStatusMousePos point
L.inputStatus . L.buttons . at btn ?= BtnPressed
SDLE.captureMouse True
return [(evt, Nothing)]
ButtonAction point btn BtnReleased clicks -> do
-- Hover changes need to be handled here too
mainPress <- use L.mainBtnPress
draggedMsg <- getDraggedMsgInfo
overlay <- getOverlayPath
when (btn == mainBtn) $
L.mainBtnPress .= Nothing
(target, hoverEvts) <- addHoverEvents wenv widgetRoot point
let pressed = fmap fst mainPress
let isPressed = btn == mainBtn && target == pressed
let clickEvt = [(Click point btn clicks, pressed) | isPressed || clicks > 1]
let releasedEvt = [(evt, pressed <|> target <|> overlay)]
let dropEvts = case draggedMsg of
Just (path, msg) -> [(Drop point path msg, target) | not isPressed]
_ -> []
updateInputStatusMousePos point
L.inputStatus . L.buttons . at btn ?= BtnReleased
SDLE.captureMouse False
return $ releasedEvt ++ dropEvts ++ clickEvt ++ hoverEvts
KeyAction mod code status -> do
L.inputStatus . L.keyMod .= mod
L.inputStatus . L.keys . at code ?= status
return [(evt, Nothing)]
-- These handlers are only here to help with testing functions
-- This will only be reached from `handleSystemEvents`
Click point btn clicks -> findEvtTargetByPoint wenv widgetRoot evt point
_ -> return [(evt, Nothing)]
updateInputStatusMousePos :: MonomerM s e m => Point -> m ()
updateInputStatusMousePos point = do
-- Update input status
status <- use L.inputStatus
L.inputStatus . L.mousePosPrev .= status ^. L.mousePos
L.inputStatus . L.mousePos .= point
addHoverEvents
:: MonomerM s e m
=> WidgetEnv s e
-> WidgetNode s e
-> Point
-> m (Maybe Path, [(SystemEvent, Maybe Path)])
addHoverEvents wenv widgetRoot point = do
overlay <- getOverlayPath
hover <- getHoveredPath
mainBtnPress <- use L.mainBtnPress
let start = fromMaybe emptyPath overlay
let widget = widgetRoot ^. L.widget
let wni = widgetFindByPoint widget wenv widgetRoot start point
let target = fmap (^. L.path) wni
let hoverChanged = target /= hover && isNothing mainBtnPress
let enter = [(Enter point, target) | isJust target && hoverChanged]
let leave = [(Leave point, hover) | isJust hover && hoverChanged]
L.leaveEnterPair .= not (null leave || null enter)
return (target, leave ++ enter)
findEvtTargetByPoint
:: MonomerM s e m
=> WidgetEnv s e
-> WidgetNode s e
-> SystemEvent
-> Point
-> m [(SystemEvent, Maybe Path)]
findEvtTargetByPoint wenv widgetRoot evt point = do
overlay <- getOverlayPath
let start = fromMaybe emptyPath overlay
let widget = widgetRoot ^. L.widget
let wni = widgetFindByPoint widget wenv widgetRoot start point
let curr = fmap (^. L.path) wni
return [(evt, curr)]
findNextFocus
:: WidgetEnv s e
-> FocusDirection
-> Path
-> Maybe Path
-> WidgetNode s e
-> WidgetNodeInfo
findNextFocus wenv dir start overlay widgetRoot = fromJust nextFocus where
widget = widgetRoot ^. L.widget
restartPath = fromMaybe emptyPath overlay
candidateWni = widgetFindNextFocus widget wenv widgetRoot dir start
fromRootWni = widgetFindNextFocus widget wenv widgetRoot dir restartPath
focusWni = fromMaybe def (findChildNodeInfoByPath wenv widgetRoot start)
nextFocus = candidateWni <|> fromRootWni <|> Just focusWni
dropNonParentWidgetId
:: MonomerM s e m
=> WidgetId
-> [(WidgetId, a)]
-> m [(WidgetId, a)]
dropNonParentWidgetId wid [] = return []
dropNonParentWidgetId wid (x:xs) = do
path <- getWidgetIdPath wid
cpath <- getWidgetIdPath cwid
if isParentPath cpath path
then return (x:xs)
else dropNonParentWidgetId wid xs
where
(cwid, _) = x
isParentPath parent child = seqStartsWith parent child && parent /= child
resetCursorOnNodeLeave
:: MonomerM s e m
=> SystemEvent
-> HandlerStep s e
-> m ()
resetCursorOnNodeLeave (Leave point) step = do
void $ handleResetCursorIcon widgetId step
where
(wenv, root, _) = step
widget = root ^. L.widget
childNode = widgetFindByPoint widget wenv root emptyPath point
widgetId = case childNode of
Just info -> info ^. L.widgetId
Nothing -> root ^. L.info . L.widgetId
resetCursorOnNodeLeave _ step = return ()
restoreCursorOnWindowEnter :: MonomerM s e m => m ()
restoreCursorOnWindowEnter = do
-- Restore old icon if needed
Size ww wh <- use L.windowSize
status <- use L.inputStatus
cursorIcons <- use L.cursorIcons
cursorPair <- headMay <$> use L.cursorStack
let windowRect = Rect 0 0 ww wh
let prevInside = pointInRect (status ^. L.mousePosPrev) windowRect
let currInside = pointInRect (status ^. L.mousePos) windowRect
let sdlCursor = cursorPair >>= (`Map.lookup` cursorIcons) . snd
when (isNothing sdlCursor && isJust cursorPair) $
liftIO. putStrLnErr $ "Invalid restoreCursorOnWindowEnter: " ++ show cursorPair
when (not prevInside && currInside && isJust sdlCursor) $ do
SDLE.setCursor (fromJust sdlCursor)
getTargetPath
:: WidgetEnv s e
-> WidgetNode s e
-> Maybe Path
-> Maybe Path
-> Path
-> SystemEvent
-> Maybe Path
getTargetPath wenv root pressed overlay target event = case event of
-- Keyboard
KeyAction{} -> pathEvent target
TextInput _ -> pathEvent target
-- Clipboard
Clipboard _ -> pathEvent target
-- Mouse/touch
ButtonAction point _ BtnPressed _ -> pointEvent point
ButtonAction _ _ BtnReleased _ -> pathEvent target
Click{} -> pathEvent target
WheelScroll point _ _ -> pointEvent point
Focus{} -> pathEvent target
Blur{} -> pathEvent target
Enter{} -> pathEvent target
Move point -> pointEvent point
Leave{} -> pathEvent target
-- Drag/drop
Drag point _ _ -> pointEvent point
Drop point _ _ -> pointEvent point
where
widget = root ^. L.widget
startPath = fromMaybe emptyPath overlay
pathEvent = Just
pathFromPoint p = fmap (^. L.path) wni where
wni = widgetFindByPoint widget wenv root startPath p
-- pressed is only really used for Move
pointEvent point = pressed <|> pathFromPoint point <|> overlay
cursorToSDL :: CursorIcon -> SDLEnum.SystemCursor
cursorToSDL CursorArrow = SDLEnum.SDL_SYSTEM_CURSOR_ARROW
cursorToSDL CursorHand = SDLEnum.SDL_SYSTEM_CURSOR_HAND
cursorToSDL CursorIBeam = SDLEnum.SDL_SYSTEM_CURSOR_IBEAM
cursorToSDL CursorInvalid = SDLEnum.SDL_SYSTEM_CURSOR_NO
cursorToSDL CursorSizeH = SDLEnum.SDL_SYSTEM_CURSOR_SIZEWE
cursorToSDL CursorSizeV = SDLEnum.SDL_SYSTEM_CURSOR_SIZENS
cursorToSDL CursorDiagTL = SDLEnum.SDL_SYSTEM_CURSOR_SIZENWSE
cursorToSDL CursorDiagTR = SDLEnum.SDL_SYSTEM_CURSOR_SIZENESW