monomer/app/Main.hs

566 lines
20 KiB
Haskell
Raw Normal View History

2019-09-23 17:46:21 +03:00
{-# LANGUAGE BangPatterns #-}
2020-04-11 23:31:55 +03:00
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE FlexibleContexts #-}
2019-09-23 17:46:21 +03:00
{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE OverloadedStrings #-}
module Main where
import Control.Concurrent (threadDelay)
import Control.Concurrent.Async (async, poll)
import Control.Exception.Base
2019-09-23 17:46:21 +03:00
import Control.Monad
import Control.Monad.Extra
2019-09-23 17:46:21 +03:00
import Control.Monad.IO.Class
import Control.Monad.State
import Data.Default
import Data.Maybe
import Data.Typeable
2019-09-23 17:46:21 +03:00
import Foreign.C.Types
import Lens.Micro.TH (makeLenses)
import Lens.Micro.Mtl
import NanoVG (Context(..), createGL3, CreateFlags(..), createFont, FileName(..), beginFrame, endFrame)
2019-09-23 17:46:21 +03:00
import SDL (($=))
import Unsafe.Coerce
import System.Remote.Monitoring
import qualified Data.List as L
2020-04-07 07:04:55 +03:00
import qualified Data.Map.Strict as M
2019-09-23 17:46:21 +03:00
import qualified Data.Set as S
import qualified Data.Sequence as SQ
import qualified Data.Text as T
import qualified Data.Vector.Generic as V
import qualified Foreign.C.String as STR
import qualified Graphics.Rendering.OpenGL as GL
import qualified SDL
import qualified SDL.Vect
2019-10-06 22:39:06 +03:00
import qualified SDL.Input.Keyboard as Keyboard
import qualified SDL.Input.Keyboard.Codes as KeyCodes
2019-09-23 17:46:21 +03:00
import qualified SDL.Input.Mouse as Mouse
import qualified SDL.Raw.Error as SRE
import qualified SDL.Raw.Event as SREv
2019-12-29 22:15:11 +03:00
import qualified SDL.Raw.Video as SRV
2019-09-23 17:46:21 +03:00
import Types
2019-10-18 05:30:58 +03:00
import GUI.Common.Core
import GUI.Common.Event
import GUI.Common.Keyboard
2019-10-18 05:30:58 +03:00
import GUI.Common.Style
2019-12-19 06:40:24 +03:00
import GUI.Common.Types
import GUI.Common.Util
import GUI.Data.Tree
2019-10-18 05:30:58 +03:00
import GUI.Widgets
import qualified GUI.Platform.NanoVGRenderer as NV
2019-09-23 17:46:21 +03:00
foreign import ccall unsafe "initGlew" glewInit :: IO CInt
2020-04-11 23:31:55 +03:00
type GWidgetMonad s e m = (MonadState (GUIContext s e) m, MonadIO m)
2020-04-13 05:54:12 +03:00
launchUserTasks :: GWidgetMonad a e m => [IO e] -> m ()
2020-04-11 23:31:55 +03:00
launchUserTasks handlers = do
2020-04-13 05:54:12 +03:00
tasks <- forM handlers $ \handler -> do
asyncTask <- liftIO $ async handler
2020-04-11 23:31:55 +03:00
return $ UserTask asyncTask
previousTasks <- use userTasks
userTasks .= previousTasks ++ tasks
checkUserTasks :: GWidgetMonad a e m => m [e]
checkUserTasks = do
tasks <- use userTasks
(active, finished) <- partitionM (\(UserTask task) -> fmap isNothing (liftIO $ poll task)) tasks
userTasks .= active
processUserTaskHandlers finished
processUserTaskHandlers :: GWidgetMonad a e m => [UserTask e] -> m [e]
processUserTaskHandlers tasks = do
results <- forM tasks stepUserTask
return $ catMaybes results
stepUserTask :: GWidgetMonad a e m => UserTask e -> m (Maybe e)
stepUserTask (UserTask task) = do
taskStatus <- liftIO $ poll task
return $ maybe Nothing processUserTaskHandler taskStatus
processUserTaskHandler :: Either SomeException e -> Maybe e
processUserTaskHandler (Left _) = Nothing
processUserTaskHandler (Right evt) = Just evt
data AppEvent = Action1 Int | Action2 | UpdateText3 T.Text deriving (Show, Eq)
2019-09-23 17:46:21 +03:00
type WidgetM = StateT App IO
2019-12-19 06:40:24 +03:00
type LocalWidget = Widget App AppEvent WidgetM
type WidgetTree = Tree (WidgetInstance App AppEvent WidgetM)
2020-04-11 23:31:55 +03:00
type AppContext = GUIContext App AppEvent
type AppM = StateT AppContext IO
2019-09-23 17:46:21 +03:00
main :: IO ()
main = do
2019-10-15 01:19:58 +03:00
--forkServer "localhost" 28000
2019-09-23 17:46:21 +03:00
SDL.initialize [SDL.InitVideo]
SDL.HintRenderScaleQuality $= SDL.ScaleLinear
do renderQuality <- SDL.get SDL.HintRenderScaleQuality
when (renderQuality /= SDL.ScaleLinear) $
putStrLn "Warning: Linear texture filtering not enabled!"
let customOpenGL = SDL.OpenGLConfig {
SDL.glColorPrecision = SDL.V4 8 8 8 0,
SDL.glDepthPrecision = 24,
SDL.glStencilPrecision = 8,
SDL.glProfile = SDL.Core SDL.Debug 3 2,
SDL.glMultisampleSamples = 1
}
2019-12-29 22:15:11 +03:00
let (screenWidth, screenHeight) = (640, 480)
windowHiDPI = True
useHiDPI = True
2019-09-23 17:46:21 +03:00
window <-
SDL.createWindow
"SDL / OpenGL Example"
SDL.defaultWindow {
SDL.windowInitialSize = SDL.V2 screenWidth screenHeight,
2019-12-29 22:15:11 +03:00
SDL.windowHighDPI = windowHiDPI,
SDL.windowResizable = True,
SDL.windowOpenGL = Just customOpenGL
}
2019-09-23 17:46:21 +03:00
err <- SRE.getError
err <- STR.peekCString err
putStrLn err
_ <- SDL.glCreateContext window
_ <- glewInit
c@(Context c') <- createGL3 (S.fromList [Antialias, StencilStrokes, Debug])
fontRes <- createFont c "sans" (FileName "./assets/fonts/Roboto-Regular.ttf")
SREv.startTextInput
2019-12-29 22:15:11 +03:00
winSize@(Rect rx ry rw rh) <- getWindowSize window
let devicePixelRate = _rw winSize / fromIntegral screenWidth
runStateT (runWidgets window c) (initGUIContext def winSize useHiDPI devicePixelRate)
2019-09-23 17:46:21 +03:00
putStrLn "About to destroyWindow"
SDL.destroyWindow window
SDL.quit
2020-04-11 23:31:55 +03:00
2020-04-13 05:54:12 +03:00
handleAppEvent :: App -> AppEvent -> WidgetM [IO AppEvent]
2020-04-11 23:31:55 +03:00
handleAppEvent app evt = do
2019-09-23 17:46:21 +03:00
case evt of
2020-04-13 05:54:12 +03:00
Action1 2 -> return [do
2020-04-11 23:31:55 +03:00
threadDelay $ 1 * 1000 * 1000
return $ UpdateText3 "HOLA"
]
2020-04-12 05:13:32 +03:00
otherwise -> handleSyncAppEvent app evt
2020-04-11 23:31:55 +03:00
2020-04-13 05:54:12 +03:00
handleSyncAppEvent :: App -> AppEvent -> WidgetM [IO AppEvent]
2020-04-12 05:13:32 +03:00
handleSyncAppEvent app evt = do
2020-04-11 23:31:55 +03:00
liftIO . putStrLn $ "Calledddd"
case evt of
Action1 0 -> do
txt1 <- use textField1
txt2 <- use textField2
txt3 <- use textField3
liftIO . putStrLn $ "Current text 1 is: " ++ (show txt1)
liftIO . putStrLn $ "Current text 2 is: " ++ (show txt2)
liftIO . putStrLn $ "Current text 3 is: " ++ (show txt3)
2020-04-11 23:31:55 +03:00
Action1 v -> do
clickCount += 1
count <- use clickCount
liftIO . putStrLn $ "Clicked button: " ++ (show v) ++ " - Count is: " ++ (show count)
2019-09-23 17:46:21 +03:00
Action2 -> liftIO . putStrLn $ "I don't know what's this"
2020-04-11 23:31:55 +03:00
UpdateText3 txt -> do
textField3 .= txt
return []
2019-09-23 17:46:21 +03:00
buildUI :: App -> WidgetTree
buildUI model = styledTree where
border1 = border 5 (rgb 0 255 0) 20
border2 = borderLeft 20 (rgb 200 200 0) <> borderRight 20 (rgb 200 0 200)
buttonStyle = bgColor (rgb 0 0 255) <> textSize 64 <> border1 <> border2 <> bgRadius 20
labelStyle = bgColor (rgb 100 100 100) <> textSize 48
2020-01-08 06:43:46 +03:00
textStyle = textColor (rgb 0 255 0) <> textAlignH ACenter
2019-12-09 08:06:43 +03:00
extraWidgets = map (\i -> sandbox (Action1 (10 + i))) [1..(_clickCount model)]
2020-01-03 06:20:03 +03:00
widgetTree = vstack [
2020-01-05 20:04:33 +03:00
hstack [
(scroll $ vstack [
textField textField1 `style` textStyle,
2020-01-03 06:20:03 +03:00
spacer `visible` False,
label "Label 1",
spacer,
label "Label 2",
2020-01-03 06:20:03 +03:00
spacer `visible` False,
label "Label 3" `visible` False,
spacer `visible` False,
label "Label 4",
spacer,
label "Label 5",
spacer,
label "Label 6",
spacer,
2019-12-29 22:15:11 +03:00
label "Label 7",
spacer,
label "Label 8",
spacer,
label "Label 9",
spacer,
label "Label 10",
spacer,
label "Label 11",
spacer,
label "Label 12"
2020-01-05 20:04:33 +03:00
]) `style` (swidth 400 <> sheight 300),
2020-01-03 06:20:03 +03:00
vstack [
textField textField2 `style` textStyle,
scroll $ label "This is a really really really long label, you know?" `style` labelStyle
]
],
2019-12-29 22:15:11 +03:00
vgrid ([
scroll $ hstack [
label "Short",
spacer,
label "Long",
spacer,
label "Very Long",
spacer,
label "Very Very Long",
spacer,
label "Very Very Very Long",
spacer,
label "Very Very Very Very Long"
],
2020-01-03 06:20:03 +03:00
hstack [
2020-04-10 01:39:10 +03:00
sandbox (Action1 0) `style` buttonStyle,
2019-12-29 22:15:11 +03:00
sandbox (Action1 1) `style` buttonStyle,
2020-04-10 01:39:10 +03:00
sandbox (Action1 2) `style` buttonStyle
2019-12-29 22:15:11 +03:00
],
button "Add items" (Action1 0) `style` buttonStyle,
textField textField3 `style` textStyle
2019-12-29 22:15:11 +03:00
] ++ extraWidgets)
]
2019-12-19 06:40:24 +03:00
styledTree = cascadeStyle mempty widgetTree
2019-09-23 17:46:21 +03:00
runWidgets :: SDL.Window -> Context -> AppM ()
2019-09-23 17:46:21 +03:00
runWidgets window c = do
2019-12-29 22:15:11 +03:00
useHiDPI <- use useHiDPI
devicePixelRate <- use devicePixelRate
Rect rx ry rw rh <- use windowSize
2019-09-23 17:46:21 +03:00
2019-12-29 22:15:11 +03:00
let dpr = if useHiDPI then devicePixelRate else 1
let renderer = NV.makeRenderer c dpr
let newWindowSize = Rect rx ry (rw / dpr) (rh / dpr)
windowSize .= newWindowSize
2019-09-23 17:46:21 +03:00
ticks <- SDL.ticks
2019-11-07 07:34:34 +03:00
newUI <- doInDrawingContext window c $ updateUI renderer empty
2019-10-15 01:19:58 +03:00
mainLoop window c renderer (fromIntegral ticks) newUI
2019-12-29 22:15:11 +03:00
getWindowSize :: (MonadIO m) => SDL.Window -> m Rect
getWindowSize window = do
SDL.V2 fbWidth fbHeight <- SDL.glGetDrawableSize window
return (Rect 0 0 (fromIntegral fbWidth) (fromIntegral fbHeight))
2019-10-15 06:19:49 +03:00
updateUI :: Renderer WidgetM -> WidgetTree -> AppM WidgetTree
updateUI renderer oldWidgets = do
2019-12-29 22:15:11 +03:00
windowSize <- use windowSize
2020-04-11 23:31:55 +03:00
oldFocus <- getCurrentFocus
2019-12-29 22:15:11 +03:00
resizedUI <- zoom appContext $ do
app <- get
2020-04-12 05:13:32 +03:00
resizeUI renderer windowSize (mergeTrees app (buildUI app) oldWidgets)
2019-12-19 06:40:24 +03:00
let paths = map snd $ filter (isFocusable . fst) $ collectPaths resizedUI []
2020-04-11 23:31:55 +03:00
focusRing .= rotateUntil oldFocus paths
2019-10-18 05:30:58 +03:00
currentFocus <- getCurrentFocus
2019-09-23 17:46:21 +03:00
2019-12-19 06:40:24 +03:00
return (setFocusedStatus currentFocus True resizedUI)
2019-09-23 17:46:21 +03:00
mainLoop :: SDL.Window -> Context -> Renderer WidgetM -> Int -> WidgetTree -> AppM ()
2019-09-23 17:46:21 +03:00
mainLoop window c renderer prevTicks widgets = do
2019-12-29 22:15:11 +03:00
useHiDPI <- use useHiDPI
devicePixelRate <- use devicePixelRate
2019-09-23 17:46:21 +03:00
ticks <- fmap fromIntegral SDL.ticks
events <- SDL.pollEvents
2019-11-14 07:36:42 +03:00
mousePos <- getCurrentMousePos
2019-09-23 17:46:21 +03:00
let frameLength = 1000 `div` 30
let nextFrame = \t -> if t >= frameLength then 0 else frameLength - t
let !ts = (ticks - prevTicks)
let eventsPayload = fmap SDL.eventPayload events
let quit = elem SDL.QuitEvent eventsPayload
2019-12-29 22:15:11 +03:00
let resized = not $ null [ e | e@SDL.WindowResizedEvent {} <- eventsPayload ]
let mousePixelRate = if not useHiDPI then devicePixelRate else 1
2020-03-31 06:14:52 +03:00
let baseSystemEvents = convertEvents mousePixelRate mousePos eventsPayload
2020-03-31 06:14:52 +03:00
-- Pre process events (change focus, add Enter/Leave events when Move is received, etc)
2020-04-11 23:31:55 +03:00
pendingEvents <- checkUserTasks
2020-03-31 06:14:52 +03:00
systemEvents <- preProcessEvents widgets baseSystemEvents
2020-04-11 23:31:55 +03:00
oldApp <- use appContext
2019-12-29 22:15:11 +03:00
2020-04-11 23:31:55 +03:00
newWidgets <- handleAppEvents renderer (SQ.fromList pendingEvents)
>> handleSystemEvents renderer systemEvents widgets
>>= rebuildIfNecessary renderer oldApp
2019-12-29 22:15:11 +03:00
>>= processWidgetTasks renderer
>>= bindIf resized (handleWindowResize window renderer)
2019-09-23 17:46:21 +03:00
renderWidgets window c renderer newWidgets ticks
2019-09-23 17:46:21 +03:00
liftIO $ threadDelay $ (nextFrame ts) * 1000
unless quit (mainLoop window c renderer ticks newWidgets)
2020-04-11 23:31:55 +03:00
rebuildIfNecessary :: Renderer WidgetM -> App -> WidgetTree -> AppM WidgetTree
rebuildIfNecessary renderer oldApp widgets = do
newApp <- use appContext
if oldApp /= newApp
then updateUI renderer widgets
else return widgets
2020-03-31 06:14:52 +03:00
preProcessEvents :: WidgetTree -> [SystemEvent] -> AppM [SystemEvent]
2020-04-07 07:04:55 +03:00
preProcessEvents widgets events = do
systemEvents <- concatMapM (preProcessEvent widgets) events
mapM_ updateInputStatus systemEvents
return systemEvents
2020-03-31 06:14:52 +03:00
preProcessEvent :: WidgetTree -> SystemEvent -> AppM [SystemEvent]
preProcessEvent widgets evt@(Move point) = do
hover <- use latestHover
let current = findPathFromPoint point widgets
let hoverChanged = isJust hover && current /= fromJust hover
let enter = if isNothing hover || hoverChanged then [Enter point] else []
let leave = if hoverChanged then [Leave (fromJust hover) point] else []
when (isNothing hover || hoverChanged) $
latestHover .= Just current
return $ leave ++ enter ++ [evt]
preProcessEvent widgets event = return [event]
2020-04-07 07:04:55 +03:00
updateInputStatus :: SystemEvent -> AppM ()
2020-04-07 07:13:21 +03:00
updateInputStatus (Click _ btn btnState) = inputStatus %= \ist -> ist {
statusButtons = M.insert btn btnState (statusButtons ist)
}
2020-04-07 07:04:55 +03:00
updateInputStatus (KeyAction kMod kCode kStatus) = inputStatus %= \ist -> ist {
2020-04-07 07:13:21 +03:00
statusKeyMod = kMod,
statusKeys = M.insert kCode kStatus (statusKeys ist)
2020-04-07 07:04:55 +03:00
}
updateInputStatus _ = return ()
2019-11-14 07:36:42 +03:00
getCurrentMousePos :: AppM Point
getCurrentMousePos = do
SDL.P (SDL.V2 x y) <- Mouse.getAbsoluteMouseLocation
return $ Point (fromIntegral x) (fromIntegral y)
2019-12-19 06:40:24 +03:00
getCurrentFocus :: AppM Path
2019-10-18 05:30:58 +03:00
getCurrentFocus = do
ring <- use focusRing
return (if length ring > 0 then ring!!0 else [])
2019-12-19 06:40:24 +03:00
handleEvent :: Renderer WidgetM -> SystemEvent -> Path -> WidgetTree -> ChildEventResult App AppEvent WidgetM
2020-03-31 06:14:52 +03:00
handleEvent renderer systemEvent targetPath widgets = case systemEvent of
-- Keyboard
KeyAction _ _ _ -> handleEventFromPath targetPath widgets systemEvent
TextInput _ -> handleEventFromPath targetPath widgets systemEvent
-- Clipboard
Clipboard _ -> handleEventFromPath targetPath widgets systemEvent
-- Mouse/touch
2019-12-19 06:40:24 +03:00
Click point _ _ -> handleEventFromPoint point widgets systemEvent
WheelScroll point _ _ -> handleEventFromPoint point widgets systemEvent
2020-03-31 06:14:52 +03:00
Focus -> handleEventFromPath targetPath widgets systemEvent
Blur -> handleEventFromPath targetPath widgets systemEvent
Enter point -> handleEventFromPoint point widgets systemEvent
Move point -> handleEventFromPoint point widgets systemEvent
Leave oldPath _ -> handleEventFromPath oldPath widgets systemEvent
2020-04-11 23:31:55 +03:00
handleSystemEvents :: Renderer WidgetM -> [SystemEvent] -> WidgetTree -> AppM WidgetTree
handleSystemEvents renderer systemEvents widgets = do
foldM (\newWidgets event -> do
focus <- getCurrentFocus
handleSystemEvent renderer event focus newWidgets) widgets systemEvents
2019-12-29 22:15:11 +03:00
2019-12-19 06:40:24 +03:00
handleSystemEvent :: Renderer WidgetM -> SystemEvent -> Path -> WidgetTree -> AppM WidgetTree
2019-10-06 22:39:06 +03:00
handleSystemEvent renderer systemEvent currentFocus widgets = do
2019-12-19 06:40:24 +03:00
let (ChildEventResult stopProcessing eventRequests appEvents newWidgets) = handleEvent renderer systemEvent currentFocus widgets
let newRoot = fromMaybe widgets newWidgets
2019-10-06 22:39:06 +03:00
handleWidgetUserStateUpdate newRoot eventRequests
launchWidgetTasks renderer eventRequests
2020-04-11 23:31:55 +03:00
handleAppEvents renderer appEvents
>> handleFocusChange renderer currentFocus systemEvent stopProcessing newRoot
>>= handleClipboardGet renderer eventRequests
>>= handleClipboardSet renderer eventRequests
>>= handleResizeChildren renderer eventRequests
2020-01-26 23:36:37 +03:00
handleFocusChange :: Renderer WidgetM -> Path -> SystemEvent -> Bool -> WidgetTree -> AppM WidgetTree
handleFocusChange renderer currentFocus systemEvent stopProcessing widgetRoot
| focusChangeRequested = do
ring <- use focusRing
2020-01-26 23:36:37 +03:00
oldFocus <- getCurrentFocus
newRoot1 <- handleSystemEvent renderer Blur oldFocus widgetRoot
2020-04-13 00:04:59 +03:00
focusRing .= rotate ring
newFocus <- getCurrentFocus
2020-01-26 23:36:37 +03:00
newRoot2 <- handleSystemEvent renderer Focus newFocus newRoot1
return $ setFocusedStatus newFocus True (setFocusedStatus currentFocus False newRoot2)
| otherwise = return widgetRoot
where
2020-04-13 00:04:59 +03:00
focusChangeRequested = not stopProcessing && isKeyPressed systemEvent keyTab
rotate = if isShiftPressed systemEvent then inverseRotateList else rotateList
2019-10-06 22:39:06 +03:00
2020-04-10 01:39:10 +03:00
handleResizeChildren :: Renderer WidgetM -> [(Path, EventRequest)] -> WidgetTree -> AppM WidgetTree
handleResizeChildren renderer eventRequests widgetRoot =
case L.find (\(path, evt) -> isResizeChildren evt) eventRequests of
Just (path, event) -> updateUI renderer widgetRoot
Nothing -> return widgetRoot
2019-10-06 22:39:06 +03:00
2020-04-10 01:39:10 +03:00
handleClipboardGet :: Renderer WidgetM -> [(Path, EventRequest)] -> WidgetTree -> AppM WidgetTree
handleClipboardGet renderer eventRequests widgetRoot =
case L.find (\(path, evt) -> isGetClipboard evt) eventRequests of
Just (path, event) -> do
hasText <- SDL.hasClipboardText
contents <- if hasText then fmap ClipboardText SDL.getClipboardText else return ClipboardEmpty
2020-04-11 23:31:55 +03:00
handleSystemEvent renderer (Clipboard contents) path widgetRoot
Nothing -> return widgetRoot
2020-04-10 01:39:10 +03:00
handleClipboardSet :: Renderer WidgetM -> [(Path, EventRequest)] -> WidgetTree -> AppM WidgetTree
handleClipboardSet renderer eventRequests widgetRoot =
case L.find (\(path, evt) -> isSetClipboard evt) eventRequests of
Just (path, SetClipboard (ClipboardText text)) -> do
SDL.setClipboardText text
return widgetRoot
Just _ -> return widgetRoot
Nothing -> return widgetRoot
2019-12-29 22:15:11 +03:00
handleWindowResize :: SDL.Window -> Renderer WidgetM -> WidgetTree -> AppM WidgetTree
handleWindowResize window renderer widgets = do
ctx <- get
dpr <- use devicePixelRate
Rect rx ry rw rh <- getWindowSize window
let newWindowSize = Rect rx ry (rw / dpr) (rh / dpr)
windowSize .= newWindowSize
liftIO $ GL.viewport GL.$= (GL.Position 0 0, GL.Size (round rw) (round rh))
zoom appContext $ do
resizeUI renderer newWindowSize widgets
2020-04-10 01:39:10 +03:00
handleWidgetUserStateUpdate :: WidgetTree -> [(Path, EventRequest)] -> AppM ()
handleWidgetUserStateUpdate widgets eventRequests = do
let runStateHandlers = L.filter isUpdateUserState eventRequests
zoom appContext $ do
forM_ runStateHandlers $ \(path, _) -> do
handleUserUpdateState (reverse path) widgets
2020-04-10 01:39:10 +03:00
launchWidgetTasks :: Renderer WidgetM -> [(Path, EventRequest)] -> AppM ()
launchWidgetTasks renderer eventRequests = do
let customHandlers = L.filter isCustomHandler eventRequests
2019-12-19 06:40:24 +03:00
tasks <- forM customHandlers $ \(path, RunCustom handler) -> do
asyncTask <- liftIO $ async (liftIO handler)
2019-12-19 06:40:24 +03:00
return $ WidgetTask path asyncTask
previousTasks <- use widgetTasks
widgetTasks .= previousTasks ++ tasks
2020-04-10 01:39:10 +03:00
isCustomHandler :: (Path, EventRequest) -> Bool
2019-12-19 06:40:24 +03:00
isCustomHandler (_, RunCustom _) = True
isCustomHandler _ = False
2020-04-10 01:39:10 +03:00
isUpdateUserState :: (Path, EventRequest) -> Bool
isUpdateUserState (_, UpdateUserState) = True
isUpdateUserState _ = False
processWidgetTasks :: Renderer WidgetM -> WidgetTree -> AppM WidgetTree
processWidgetTasks renderer widgets = do
tasks <- use widgetTasks
2019-12-19 06:40:24 +03:00
(active, finished) <- partitionM (\(WidgetTask _ task) -> fmap isNothing (liftIO $ poll task)) tasks
widgetTasks .= active
processCustomHandlers renderer widgets finished
2019-12-19 06:40:24 +03:00
processCustomHandlers :: Renderer WidgetM -> WidgetTree -> [WidgetTask] -> AppM WidgetTree
processCustomHandlers renderer widgets tasks = do
newWidgets <- foldM (stepWidgetTask renderer) widgets tasks
return newWidgets
2019-12-19 06:40:24 +03:00
stepWidgetTask :: Renderer WidgetM -> WidgetTree -> WidgetTask -> AppM WidgetTree
stepWidgetTask renderer widgets (WidgetTask path task) = do
taskStatus <- liftIO $ poll task
if (isJust taskStatus)
then processCustomHandler renderer widgets path (fromJust taskStatus)
else return widgets
2019-12-19 06:40:24 +03:00
processCustomHandler :: (Typeable a) => Renderer WidgetM -> WidgetTree -> Path -> Either SomeException a -> AppM WidgetTree
processCustomHandler renderer widgets _ (Left _) = return widgets
processCustomHandler renderer widgets path (Right val) = do
2019-12-19 06:40:24 +03:00
let (ChildEventResult stopProcessing eventRequests appEvents newWidgets) = handleCustomCommand path widgets val
let newRoot = fromMaybe widgets newWidgets
launchWidgetTasks renderer eventRequests
2020-04-11 23:31:55 +03:00
handleAppEvents renderer appEvents
>> handleResizeChildren renderer eventRequests newRoot
2020-04-11 23:31:55 +03:00
handleAppEvents :: Renderer WidgetM -> SQ.Seq AppEvent -> AppM ()
handleAppEvents renderer appEvents = do
tasks <- zoom appContext $ do
tasks <- forM appEvents $ \event -> do
currApp <- get
handleAppEvent currApp event
return $ msum tasks
launchUserTasks $ tasks
2019-09-23 17:46:21 +03:00
renderWidgets :: SDL.Window -> Context -> Renderer WidgetM -> WidgetTree -> Int -> AppM ()
2019-11-07 07:34:34 +03:00
renderWidgets !window !c !renderer widgets ticks =
doInDrawingContext window c $ do
guiContext <- get
zoom appContext $ do
2019-12-19 06:40:24 +03:00
handleRender renderer widgets ticks
2019-11-07 07:34:34 +03:00
doInDrawingContext :: SDL.Window -> Context -> AppM a -> AppM a
doInDrawingContext window c action = do
2019-09-23 17:46:21 +03:00
SDL.V2 fbWidth fbHeight <- SDL.glGetDrawableSize window
let !pxRatio = fromIntegral fbWidth / fromIntegral fbHeight
liftIO $ GL.clear [GL.ColorBuffer]
2019-12-29 22:15:11 +03:00
liftIO $ beginFrame c fbWidth fbHeight pxRatio
2019-09-23 17:46:21 +03:00
2019-11-07 07:34:34 +03:00
ret <- action
2019-09-23 17:46:21 +03:00
liftIO $ endFrame c
SDL.glSwapWindow window
2019-11-07 07:34:34 +03:00
return ret
2019-09-23 17:46:21 +03:00
2019-12-19 06:40:24 +03:00
collectPaths :: (MonadState s m) => Tree (WidgetInstance s e m) -> Path -> [(WidgetInstance s e m, Path)]
collectPaths treeNode path = fmap (\(node, path) -> (node, reverse path)) (collectReversedPaths treeNode path)
collectReversedPaths :: (MonadState s m) => Tree (WidgetInstance s e m) -> Path -> [(WidgetInstance s e m, Path)]
collectReversedPaths (Node widgetNode children) path = (widgetNode, path) : remainingItems where
2019-12-19 06:40:24 +03:00
pairs = zip (seqToNodeList children) (map (: path) [0..])
remainingItems = concatMap (\(wn, path) -> collectReversedPaths wn path) pairs