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)
|
2019-11-26 01:29:19 +03:00
|
|
|
import Control.Concurrent.Async (async, poll)
|
|
|
|
import Control.Exception.Base
|
2019-09-23 17:46:21 +03:00
|
|
|
import Control.Monad
|
2019-11-26 01:29:19 +03:00
|
|
|
import Control.Monad.Extra
|
2019-09-23 17:46:21 +03:00
|
|
|
import Control.Monad.IO.Class
|
|
|
|
import Control.Monad.State
|
|
|
|
import Data.Default
|
2019-10-07 07:45:46 +03:00
|
|
|
import Data.Maybe
|
2019-11-26 01:29:19 +03:00
|
|
|
import Data.Typeable
|
2019-09-23 17:46:21 +03:00
|
|
|
import Foreign.C.Types
|
|
|
|
import Lens.Micro.TH (makeLenses)
|
|
|
|
import Lens.Micro.Mtl
|
2019-12-30 00:13:03 +03:00
|
|
|
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
|
2019-12-18 07:02:52 +03:00
|
|
|
import qualified SDL.Raw.Event as SREv
|
2019-12-29 22:15:11 +03:00
|
|
|
import qualified SDL.Raw.Video as SRV
|
2019-12-18 07:02:52 +03:00
|
|
|
|
2019-09-23 17:46:21 +03:00
|
|
|
import Types
|
2019-10-18 05:30:58 +03:00
|
|
|
import GUI.Common.Core
|
2019-12-18 07:02:52 +03:00
|
|
|
import GUI.Common.Event
|
2019-12-23 08:49:19 +03:00
|
|
|
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)
|
2019-10-01 07:09:53 +03:00
|
|
|
|
2020-04-11 23:31:55 +03:00
|
|
|
type AppContext = GUIContext App AppEvent
|
2019-10-01 07:09:53 +03:00
|
|
|
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"
|
2019-12-23 08:49:19 +03:00
|
|
|
SDL.defaultWindow {
|
|
|
|
SDL.windowInitialSize = SDL.V2 screenWidth screenHeight,
|
2019-12-29 22:15:11 +03:00
|
|
|
SDL.windowHighDPI = windowHiDPI,
|
|
|
|
SDL.windowResizable = True,
|
2019-12-23 08:49:19 +03:00
|
|
|
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")
|
|
|
|
|
2019-12-18 07:02:52 +03:00
|
|
|
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
|
2020-04-10 01:36:19 +03:00
|
|
|
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
|
2019-12-30 00:13:03 +03:00
|
|
|
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 [
|
2020-04-10 01:36:19 +03:00
|
|
|
textField textField1 `style` textStyle,
|
2020-01-03 06:20:03 +03:00
|
|
|
spacer `visible` False,
|
2019-12-16 07:48:32 +03:00
|
|
|
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,
|
2019-12-16 07:48:32 +03:00
|
|
|
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 [
|
2020-04-09 19:46:49 +03:00
|
|
|
textField textField2 `style` textStyle,
|
2019-12-16 07:48:32 +03:00
|
|
|
scroll $ label "This is a really really really long label, you know?" `style` labelStyle
|
|
|
|
]
|
2019-10-21 07:10:44 +03:00
|
|
|
],
|
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,
|
2020-04-09 19:46:49 +03:00
|
|
|
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
|
|
|
|
2019-10-01 07:09:53 +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
|
|
|
|
2019-10-01 07:09:53 +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-10-01 07:09:53 +03:00
|
|
|
|
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
|
|
|
|
2019-10-01 07:09:53 +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
|
2019-11-26 01:29:19 +03:00
|
|
|
|
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-10-01 07:09:53 +03:00
|
|
|
|
2019-09-23 17:46:21 +03:00
|
|
|
renderWidgets window c renderer newWidgets ticks
|
2019-10-01 07:09:53 +03:00
|
|
|
|
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
|
2019-10-01 07:09:53 +03:00
|
|
|
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
|
2019-10-13 06:48:05 +03:00
|
|
|
|
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
|
2019-10-07 07:45:46 +03:00
|
|
|
let newRoot = fromMaybe widgets newWidgets
|
2019-10-06 22:39:06 +03:00
|
|
|
|
2020-04-10 01:36:19 +03:00
|
|
|
handleWidgetUserStateUpdate newRoot eventRequests
|
2019-11-30 21:37:23 +03:00
|
|
|
launchWidgetTasks renderer eventRequests
|
|
|
|
|
2020-04-11 23:31:55 +03:00
|
|
|
handleAppEvents renderer appEvents
|
|
|
|
>> handleFocusChange renderer currentFocus systemEvent stopProcessing newRoot
|
2019-12-23 08:49:19 +03:00
|
|
|
>>= handleClipboardGet renderer eventRequests
|
|
|
|
>>= handleClipboardSet renderer eventRequests
|
2019-11-30 21:37:23 +03:00
|
|
|
>>= 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
|
2019-11-30 21:37:23 +03:00
|
|
|
| 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
|
2019-11-30 21:37:23 +03:00
|
|
|
newFocus <- getCurrentFocus
|
2020-01-26 23:36:37 +03:00
|
|
|
newRoot2 <- handleSystemEvent renderer Focus newFocus newRoot1
|
|
|
|
return $ setFocusedStatus newFocus True (setFocusedStatus currentFocus False newRoot2)
|
2019-11-30 21:37:23 +03:00
|
|
|
| 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
|
2019-11-30 21:37:23 +03:00
|
|
|
handleResizeChildren renderer eventRequests widgetRoot =
|
2019-12-23 08:49:19 +03:00
|
|
|
case L.find (\(path, evt) -> isResizeChildren evt) eventRequests of
|
2019-11-30 21:37:23 +03:00
|
|
|
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
|
2019-12-23 08:49:19 +03:00
|
|
|
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
|
2019-12-23 08:49:19 +03:00
|
|
|
Nothing -> return widgetRoot
|
|
|
|
|
2020-04-10 01:39:10 +03:00
|
|
|
handleClipboardSet :: Renderer WidgetM -> [(Path, EventRequest)] -> WidgetTree -> AppM WidgetTree
|
2019-12-23 08:49:19 +03:00
|
|
|
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 ()
|
2020-04-10 01:36:19 +03:00
|
|
|
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 ()
|
2019-11-30 21:37:23 +03:00
|
|
|
launchWidgetTasks renderer eventRequests = do
|
|
|
|
let customHandlers = L.filter isCustomHandler eventRequests
|
2019-11-06 07:25:04 +03:00
|
|
|
|
2019-12-19 06:40:24 +03:00
|
|
|
tasks <- forM customHandlers $ \(path, RunCustom handler) -> do
|
2019-11-26 01:29:19 +03:00
|
|
|
asyncTask <- liftIO $ async (liftIO handler)
|
|
|
|
|
2019-12-19 06:40:24 +03:00
|
|
|
return $ WidgetTask path asyncTask
|
2019-11-26 01:29:19 +03:00
|
|
|
|
|
|
|
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
|
2019-11-26 01:29:19 +03:00
|
|
|
isCustomHandler _ = False
|
|
|
|
|
2020-04-10 01:39:10 +03:00
|
|
|
isUpdateUserState :: (Path, EventRequest) -> Bool
|
2020-04-10 01:36:19 +03:00
|
|
|
isUpdateUserState (_, UpdateUserState) = True
|
|
|
|
isUpdateUserState _ = False
|
|
|
|
|
2019-11-30 21:37:23 +03:00
|
|
|
processWidgetTasks :: Renderer WidgetM -> WidgetTree -> AppM WidgetTree
|
|
|
|
processWidgetTasks renderer widgets = do
|
2019-11-26 01:29:19 +03:00
|
|
|
tasks <- use widgetTasks
|
2019-12-19 06:40:24 +03:00
|
|
|
(active, finished) <- partitionM (\(WidgetTask _ task) -> fmap isNothing (liftIO $ poll task)) tasks
|
2019-11-26 01:29:19 +03:00
|
|
|
widgetTasks .= active
|
|
|
|
|
2019-11-30 21:37:23 +03:00
|
|
|
processCustomHandlers renderer widgets finished
|
2019-11-26 01:29:19 +03:00
|
|
|
|
2019-12-19 06:40:24 +03:00
|
|
|
processCustomHandlers :: Renderer WidgetM -> WidgetTree -> [WidgetTask] -> AppM WidgetTree
|
2019-11-30 21:37:23 +03:00
|
|
|
processCustomHandlers renderer widgets tasks = do
|
|
|
|
newWidgets <- foldM (stepWidgetTask renderer) widgets tasks
|
2019-11-26 01:29:19 +03:00
|
|
|
return newWidgets
|
|
|
|
|
2019-12-19 06:40:24 +03:00
|
|
|
stepWidgetTask :: Renderer WidgetM -> WidgetTree -> WidgetTask -> AppM WidgetTree
|
|
|
|
stepWidgetTask renderer widgets (WidgetTask path task) = do
|
2019-11-30 21:37:23 +03:00
|
|
|
taskStatus <- liftIO $ poll task
|
2019-11-26 01:29:19 +03:00
|
|
|
|
2019-11-30 21:37:23 +03:00
|
|
|
if (isJust taskStatus)
|
|
|
|
then processCustomHandler renderer widgets path (fromJust taskStatus)
|
|
|
|
else return widgets
|
2019-11-26 01:29:19 +03:00
|
|
|
|
2019-12-19 06:40:24 +03:00
|
|
|
processCustomHandler :: (Typeable a) => Renderer WidgetM -> WidgetTree -> Path -> Either SomeException a -> AppM WidgetTree
|
2019-11-30 21:37:23 +03:00
|
|
|
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
|
2019-11-30 21:37:23 +03:00
|
|
|
let newRoot = fromMaybe widgets newWidgets
|
|
|
|
|
|
|
|
launchWidgetTasks renderer eventRequests
|
|
|
|
|
2020-04-11 23:31:55 +03:00
|
|
|
handleAppEvents renderer appEvents
|
|
|
|
>> handleResizeChildren renderer eventRequests newRoot
|
2019-11-30 21:37:23 +03:00
|
|
|
|
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
|
|
|
|
2019-10-01 07:09:53 +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)]
|
2020-04-10 01:36:19 +03:00
|
|
|
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..])
|
2020-04-10 01:36:19 +03:00
|
|
|
remainingItems = concatMap (\(wn, path) -> collectReversedPaths wn path) pairs
|