finished storing ui widgets into a HashMap and referencing them by UIId.
Additionally, widgets functions now use the Pioneers monad. Branch is compiling again and works.
This commit is contained in:
parent
6879201c53
commit
2d80c92384
@ -103,6 +103,7 @@ main =
|
|||||||
far = 500 --far plane
|
far = 500 --far plane
|
||||||
ratio = fromIntegral fbWidth / fromIntegral fbHeight
|
ratio = fromIntegral fbWidth / fromIntegral fbHeight
|
||||||
frust = createFrustum fov near far ratio
|
frust = createFrustum fov near far ratio
|
||||||
|
(guiMap, guiRoots) = createGUI
|
||||||
aks = ArrowKeyState {
|
aks = ArrowKeyState {
|
||||||
_up = False
|
_up = False
|
||||||
, _down = False
|
, _down = False
|
||||||
@ -174,6 +175,8 @@ main =
|
|||||||
}
|
}
|
||||||
, _ui = UIState
|
, _ui = UIState
|
||||||
{ _uiHasChanged = True
|
{ _uiHasChanged = True
|
||||||
|
, _uiMap = guiMap
|
||||||
|
, _uiRoots = guiRoots
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,6 +115,7 @@ data GLState = GLState
|
|||||||
data UIState = UIState
|
data UIState = UIState
|
||||||
{ _uiHasChanged :: !Bool
|
{ _uiHasChanged :: !Bool
|
||||||
, _uiMap :: Map.HashMap UIId (GUIAny Pioneers)
|
, _uiMap :: Map.HashMap UIId (GUIAny Pioneers)
|
||||||
|
, _uiRoots :: [UIId]
|
||||||
}
|
}
|
||||||
|
|
||||||
data State = State
|
data State = State
|
||||||
|
@ -2,55 +2,87 @@
|
|||||||
|
|
||||||
module UI.Callbacks where
|
module UI.Callbacks where
|
||||||
|
|
||||||
|
|
||||||
|
import qualified Graphics.Rendering.OpenGL.GL as GL
|
||||||
|
import Control.Lens ((^.), (.~))
|
||||||
|
import Control.Monad (liftM)
|
||||||
|
import Control.Monad.RWS.Strict (get, modify)
|
||||||
import Control.Monad.Trans (liftIO)
|
import Control.Monad.Trans (liftIO)
|
||||||
|
import qualified Data.HashMap.Strict as Map
|
||||||
|
import Data.List (foldl')
|
||||||
|
import Data.Maybe
|
||||||
|
import Foreign.Marshal.Array (pokeArray)
|
||||||
|
import Foreign.Marshal.Alloc (allocaBytes)
|
||||||
|
import Render.Misc (genColorData)
|
||||||
|
|
||||||
import Types
|
import Types
|
||||||
import UI.UIBaseData
|
import UI.UIBaseData
|
||||||
import UI.UIClasses
|
import UI.UIClasses
|
||||||
import UI.UIOperations
|
import UI.UIOperations
|
||||||
|
|
||||||
import qualified Graphics.Rendering.OpenGL.GL as GL
|
|
||||||
import Control.Lens ((^.), (.~), (%~))
|
|
||||||
import Render.Misc (genColorData)
|
|
||||||
import Foreign.Marshal.Array (pokeArray)
|
|
||||||
import Foreign.Marshal.Alloc (allocaBytes)
|
|
||||||
import Control.Monad.RWS.Strict (get, liftIO, modify)
|
|
||||||
|
|
||||||
|
|
||||||
data Pixel = Pixel Int Int
|
data Pixel = Pixel Int Int
|
||||||
|
|
||||||
getGUI :: [GUIAny]
|
createGUI :: (Map.HashMap UIId (GUIAny Pioneers), [UIId])
|
||||||
getGUI = [ toGUIAny $ GUIContainer 0 0 120 80 [] 1
|
createGUI = (Map.fromList [ (UIId 0, GUIAnyP $ GUIPanel $ GUIContainer 0 0 0 0 [UIId 1, UIId 2] 0)
|
||||||
, toGUIAny $ GUIPanel $ GUIContainer 0 0 0 0
|
, (UIId 1, GUIAnyC $ GUIContainer 20 50 120 80 [] 1)
|
||||||
[toGUIAny $ GUIContainer 0 80 100 200 [] 4
|
, (UIId 2, GUIAnyP $ GUIPanel $ GUIContainer 100 140 0 0 [UIId 3, UIId 4] 3)
|
||||||
,toGUIAny $GUIButton 50 400 200 175 2 defaultUIState testMessage
|
, (UIId 3, GUIAnyC $ GUIContainer 100 140 130 200 [] 4 )
|
||||||
] 3
|
, (UIId 4, GUIAnyB (GUIButton 30 200 60 175 2 defaultUIState ) (ButtonHandler testMessage))
|
||||||
]
|
], [UIId 0])
|
||||||
|
|
||||||
testMessage :: (Show w) => w -> ScreenUnit -> ScreenUnit -> IO w
|
getGUI :: Map.HashMap UIId (GUIAny Pioneers) -> [GUIAny Pioneers]
|
||||||
|
getGUI hmap = Map.elems hmap
|
||||||
|
|
||||||
|
getRootIds :: Pioneers [UIId]
|
||||||
|
getRootIds = do
|
||||||
|
state <- get
|
||||||
|
return $ state ^. ui.uiRoots
|
||||||
|
|
||||||
|
getRoots :: Pioneers [GUIAny Pioneers]
|
||||||
|
getRoots = do
|
||||||
|
state <- get
|
||||||
|
rootIds <- getRootIds
|
||||||
|
let hMap = state ^. ui.uiMap
|
||||||
|
return $ toGUIAnys hMap rootIds
|
||||||
|
|
||||||
|
testMessage :: w -> ScreenUnit -> ScreenUnit -> Pioneers w
|
||||||
testMessage w x y = do
|
testMessage w x y = do
|
||||||
putStrLn ("\tclick on " ++ show x ++ "," ++ show y)
|
liftIO $ putStrLn ("\tclick on " ++ show x ++ "," ++ show y)
|
||||||
return w
|
return w
|
||||||
|
|
||||||
-- | Handler for UI-Inputs.
|
-- | Handler for UI-Inputs.
|
||||||
-- Indicates a primary click on something (e.g. left-click, touch on Touchpad, fire on Gamepad, ...
|
-- Indicates a primary click on something (e.g. left-click, touch on Touchpad, fire on Gamepad, ...
|
||||||
clickHandler :: Pixel -> Pioneers ()
|
clickHandler :: Pixel -> Pioneers ()
|
||||||
clickHandler (Pixel x y) = case concatMap (isInside x y) getGUI of
|
clickHandler (Pixel x y) = do
|
||||||
|
state <- get
|
||||||
|
let hMap = state ^. ui.uiMap
|
||||||
|
roots <- getRootIds
|
||||||
|
hits <- liftM concat $ mapM (getInsideId hMap x y) roots
|
||||||
|
case hits of
|
||||||
[] -> liftIO $ putStrLn $ unwords ["button press on (",show x,",",show y,")"]
|
[] -> liftIO $ putStrLn $ unwords ["button press on (",show x,",",show y,")"]
|
||||||
hit -> liftIO $ do
|
_ -> do
|
||||||
_ <- sequence $ map (\w ->
|
changes <- sequence $ map (\uid -> do
|
||||||
|
let w = toGUIAny hMap uid
|
||||||
|
short <- getShorthand w
|
||||||
|
bound <- getBoundary w
|
||||||
|
prio <- getPriority w
|
||||||
|
liftIO $ putStrLn $ "hitting " ++ short ++ ": " ++ show bound ++ " " ++ show prio
|
||||||
|
++ " at [" ++ show x ++ "," ++ show y ++ "]"
|
||||||
case w of
|
case w of
|
||||||
(GUIAnyB b h) -> do
|
(GUIAnyB b h) -> do
|
||||||
putStrLn $ "hitting " ++ getShorthand w ++ ": " ++ show (getBoundary w) ++ " " ++ show (getPriority w)
|
|
||||||
++ " at ["++show x++","++show y++"]"
|
|
||||||
(b', h') <- onMousePressed x y b h
|
(b', h') <- onMousePressed x y b h
|
||||||
_ <- onMouseReleased x y b' h'
|
(b'', h'') <- onMouseReleased x y b' h'
|
||||||
return ()
|
return $ Just (uid, GUIAnyB b'' h'')
|
||||||
_ -> putStrLn $ "hitting " ++ getShorthand w ++ ": " ++ show (getBoundary w) ++ " " ++ show (getPriority w)
|
_ -> return Nothing
|
||||||
++ " at ["++show x++","++show y++"]"
|
) $ hits
|
||||||
) hit
|
let newMap :: Map.HashMap UIId (GUIAny Pioneers)
|
||||||
|
newMap = foldl' (\hm (uid, w') -> Map.insert uid w' hm) hMap $ catMaybes changes
|
||||||
|
modify $ ui.uiMap .~ newMap
|
||||||
return ()
|
return ()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-- | Handler for UI-Inputs.
|
-- | Handler for UI-Inputs.
|
||||||
-- Indicates an alternate click on something (e.g. right-click, touch&hold on Touchpad, ...
|
-- Indicates an alternate click on something (e.g. right-click, touch&hold on Touchpad, ...
|
||||||
alternateClickHandler :: Pixel -> Pioneers ()
|
alternateClickHandler :: Pixel -> Pioneers ()
|
||||||
@ -69,36 +101,40 @@ alternateClickHandler (Pixel x y) = liftIO $ putStrLn $ unwords ["alternate pres
|
|||||||
prepareGUI :: Pioneers ()
|
prepareGUI :: Pioneers ()
|
||||||
prepareGUI = do
|
prepareGUI = do
|
||||||
state <- get
|
state <- get
|
||||||
|
roots <- getRoots
|
||||||
let tex = (state ^. gl.glHud.hudTexture)
|
let tex = (state ^. gl.glHud.hudTexture)
|
||||||
liftIO $ do
|
liftIO $ do
|
||||||
-- bind texture - all later calls work on this one.
|
-- bind texture - all later calls work on this one.
|
||||||
GL.textureBinding GL.Texture2D GL.$= Just tex
|
GL.textureBinding GL.Texture2D GL.$= Just tex
|
||||||
mapM_ (copyGUI tex) getGUI
|
mapM_ (copyGUI tex) roots
|
||||||
modify $ ui.uiHasChanged .~ False
|
modify $ ui.uiHasChanged .~ False
|
||||||
|
|
||||||
--TODO: Perform border-checking ... is xoff + width and yoff+height inside the screen-coordinates..
|
--TODO: Perform border-checking ... is xoff + width and yoff+height inside the screen-coordinates..
|
||||||
copyGUI :: GL.TextureObject -> GUIAny -> IO ()
|
copyGUI :: GL.TextureObject -> GUIAny Pioneers -> Pioneers ()
|
||||||
copyGUI tex widget = do
|
copyGUI tex widget = do
|
||||||
let (xoff, yoff, width, height) = getBoundary widget
|
(xoff, yoff, wWidth, wHeight) <- getBoundary widget
|
||||||
|
state <- get
|
||||||
|
let
|
||||||
|
hMap = state ^. ui.uiMap
|
||||||
int = fromInteger.toInteger --conversion between Int8, GLInt, Int, ...
|
int = fromInteger.toInteger --conversion between Int8, GLInt, Int, ...
|
||||||
--temporary color here. lateron better some getData-function to
|
--temporary color here. lateron better some getData-function to
|
||||||
--get a list of pixel-data or a texture.
|
--get a list of pixel-data or a texture.
|
||||||
color = case widget of
|
color = case widget of
|
||||||
(GUIAnyC _) -> [255,0,0,128]
|
(GUIAnyC _) -> [255,0,0,128]
|
||||||
(GUIAnyB _ _) -> [255,255,0,255]
|
(GUIAnyB _ _) -> [255,255,0,255]
|
||||||
(GUIAnyP _) -> [128,128,128,255]
|
(GUIAnyP _) -> [128,128,128,128]
|
||||||
_ -> [255,0,255,255]
|
_ -> [255,0,255,255]
|
||||||
allocaBytes (width*height*4) $ \ptr -> do
|
liftIO $ allocaBytes (wWidth*wHeight*4) $ \ptr -> do
|
||||||
--copy data into C-Array
|
--copy data into C-Array
|
||||||
pokeArray ptr (genColorData (width*height) color)
|
pokeArray ptr (genColorData (wWidth*wHeight) color)
|
||||||
GL.texSubImage2D
|
GL.texSubImage2D
|
||||||
GL.Texture2D
|
GL.Texture2D
|
||||||
0
|
0
|
||||||
(GL.TexturePosition2D (int xoff) (int yoff))
|
(GL.TexturePosition2D (int xoff) (int yoff))
|
||||||
(GL.TextureSize2D (int width) (int height))
|
(GL.TextureSize2D (int wWidth) (int wHeight))
|
||||||
(GL.PixelData GL.RGBA GL.UnsignedByte ptr)
|
(GL.PixelData GL.RGBA GL.UnsignedByte ptr)
|
||||||
mapM_ (copyGUI tex) (getChildren widget)
|
nextChildrenIds <- getChildren widget
|
||||||
copyGUI _ _ = return ()
|
mapM_ (copyGUI tex) $ toGUIAnys hMap $ nextChildrenIds
|
||||||
|
|
||||||
--TODO: Add scroll-Handler, return (Pioneers Bool) to indicate event-bubbling etc.
|
--TODO: Add scroll-Handler, return (Pioneers Bool) to indicate event-bubbling etc.
|
||||||
--TODO: Maybe queues are better?
|
--TODO: Maybe queues are better?
|
@ -9,7 +9,7 @@ import Data.Ix
|
|||||||
type ScreenUnit = Int
|
type ScreenUnit = Int
|
||||||
|
|
||||||
|
|
||||||
newtype UIId = UId Int deriving (Eq,Ord,Show,Read,Bounded,Ix,Hashable)
|
newtype UIId = UIId Int deriving (Eq,Ord,Show,Read,Bounded,Ix,Hashable)
|
||||||
|
|
||||||
-- |The state of a clickable ui widget.
|
-- |The state of a clickable ui widget.
|
||||||
data UIButtonState = UIButtonState
|
data UIButtonState = UIButtonState
|
||||||
|
@ -27,17 +27,17 @@ class (Monad m) => GUIWidget m uiw where
|
|||||||
getChildren :: uiw -> m [UIId]
|
getChildren :: uiw -> m [UIId]
|
||||||
getChildren _ = return []
|
getChildren _ = return []
|
||||||
|
|
||||||
-- |The function 'isInsideSelf' tests whether a point is inside the widget itself.
|
-- |The function 'isInside' tests whether a point is inside the widget itself.
|
||||||
-- A screen position may be inside the bounding box of a widget but not considered part of the
|
-- A screen position may be inside the bounding box of a widget but not considered part of the
|
||||||
-- component.
|
-- component.
|
||||||
--
|
--
|
||||||
-- The default implementations tests if the point is within the rectangle specified by the
|
-- The default implementations tests if the point is within the rectangle specified by the
|
||||||
-- 'getBoundary' function.
|
-- 'getBoundary' function.
|
||||||
isInsideSelf :: ScreenUnit -- ^screen x coordinate
|
isInside :: ScreenUnit -- ^screen x coordinate
|
||||||
-> ScreenUnit -- ^screen y coordinate
|
-> ScreenUnit -- ^screen y coordinate
|
||||||
-> uiw -- ^the parent widget
|
-> uiw -- ^the parent widget
|
||||||
-> m Bool
|
-> m Bool
|
||||||
isInsideSelf x' y' wg = do
|
isInside x' y' wg = do
|
||||||
(x, y, w, h) <- getBoundary wg
|
(x, y, w, h) <- getBoundary wg
|
||||||
return $ (x' - x <= w) && (x' - x >= 0) && (y' - y <= h) && (y' - y >= 0)
|
return $ (x' - x <= w) && (x' - x >= 0) && (y' - y <= h) && (y' - y >= 0)
|
||||||
|
|
||||||
@ -193,9 +193,9 @@ instance GUIWidget T.Pioneers (GUIAny T.Pioneers) where
|
|||||||
getChildren (GUIAnyC w) = getChildren w
|
getChildren (GUIAnyC w) = getChildren w
|
||||||
getChildren (GUIAnyP w) = getChildren w
|
getChildren (GUIAnyP w) = getChildren w
|
||||||
getChildren (GUIAnyB w _) = getChildren w
|
getChildren (GUIAnyB w _) = getChildren w
|
||||||
isInsideSelf x y (GUIAnyC w) = (isInsideSelf x y) w
|
isInside x y (GUIAnyC w) = (isInside x y) w
|
||||||
isInsideSelf x y (GUIAnyP w) = (isInsideSelf x y) w
|
isInside x y (GUIAnyP w) = (isInside x y) w
|
||||||
isInsideSelf x y (GUIAnyB w _) = (isInsideSelf x y) w
|
isInside x y (GUIAnyB w _) = (isInside x y) w
|
||||||
getPriority (GUIAnyC w) = getPriority w
|
getPriority (GUIAnyC w) = getPriority w
|
||||||
getPriority (GUIAnyP w) = getPriority w
|
getPriority (GUIAnyP w) = getPriority w
|
||||||
getPriority (GUIAnyB w _) = getPriority w
|
getPriority (GUIAnyB w _) = getPriority w
|
||||||
|
@ -1,26 +1,79 @@
|
|||||||
module UI.UIOperations where
|
module UI.UIOperations where
|
||||||
|
|
||||||
|
import Control.Monad (liftM)
|
||||||
import qualified Data.HashMap.Strict as Map
|
import qualified Data.HashMap.Strict as Map
|
||||||
|
import Data.Maybe
|
||||||
|
|
||||||
|
import Types
|
||||||
import UI.UIBaseData
|
import UI.UIBaseData
|
||||||
import UI.UIClasses
|
import UI.UIClasses
|
||||||
|
|
||||||
defaultUIState :: UIButtonState
|
defaultUIState :: UIButtonState
|
||||||
defaultUIState = UIButtonState False False False False False False
|
defaultUIState = UIButtonState False False False False False False
|
||||||
|
|
||||||
--TODO
|
toGUIAny :: Map.HashMap UIId (GUIAny m) -> UIId -> GUIAny m
|
||||||
-- |The function 'isInside' tests whether a point is inside the widget or any child.
|
toGUIAny m uid = case Map.lookup uid m of
|
||||||
-- A screen position may be inside the bounding box of a widget but not considered part of the component.
|
Just w -> w
|
||||||
-- The function returns all hit widgets that have no hit children or 'Nothing' if the point neither hits any
|
Nothing -> error "map does not contain requested key" --TODO: better error handling
|
||||||
-- component nor the parent widget itself.
|
{-# INLINE toGUIAny #-}
|
||||||
isInside :: ScreenUnit -- ^screen x coordinate
|
|
||||||
|
toGUIAnys :: Map.HashMap UIId (GUIAny m) -> [UIId] -> [GUIAny m]
|
||||||
|
toGUIAnys m ids = mapMaybe (flip Map.lookup m) ids
|
||||||
|
{-# INLINE toGUIAnys #-}
|
||||||
|
-- TODO: check for missing components?
|
||||||
|
|
||||||
|
|
||||||
|
-- |The function 'getInside' returns child widgets that overlap with a specific
|
||||||
|
-- screen position.
|
||||||
|
--
|
||||||
|
-- A screen position may be inside the bounding box of a widget but not
|
||||||
|
-- considered part of the component. The function returns all hit widgets that
|
||||||
|
-- have no hit children, which may be the input widget itself,
|
||||||
|
-- or @[]@ if the point does not hit the widget.
|
||||||
|
--
|
||||||
|
-- This function returns the widgets themselves unlike 'getInsideId'.
|
||||||
|
getInside :: Map.HashMap UIId (GUIAny Pioneers) -- ^map containing ui widgets
|
||||||
|
-> ScreenUnit -- ^screen x coordinate
|
||||||
|
-> ScreenUnit -- ^screen y coordinate
|
||||||
|
-> GUIAny Pioneers -- ^the parent widget
|
||||||
|
-> Pioneers [GUIAny Pioneers]
|
||||||
|
getInside hMap x' y' wg = do
|
||||||
|
inside <- isInside x' y' wg
|
||||||
|
if inside -- test inside parent's bounding box
|
||||||
|
then do
|
||||||
|
childrenIds <- getChildren wg
|
||||||
|
hitChildren <- liftM concat $ mapM (getInside hMap x' y') (toGUIAnys hMap childrenIds)
|
||||||
|
case hitChildren of
|
||||||
|
[] -> return [wg]
|
||||||
|
_ -> return hitChildren
|
||||||
|
else return []
|
||||||
|
--TODO: Priority queue?
|
||||||
|
|
||||||
|
-- |The function 'getInsideId' returns child widgets that overlap with a
|
||||||
|
-- specific screen position.
|
||||||
|
--
|
||||||
|
-- A screen position may be inside the bounding box of a widget but not
|
||||||
|
-- considered part of the component. The function returns all hit widgets that
|
||||||
|
-- have no hit children, which may be the input widget itself,
|
||||||
|
-- or @[]@ if the point does not hit the widget.
|
||||||
|
--
|
||||||
|
-- This function returns the 'UIId's of the widgets unlike 'getInside'.
|
||||||
|
getInsideId :: Map.HashMap UIId (GUIAny Pioneers) -- ^map containing ui widgets
|
||||||
|
-> ScreenUnit -- ^screen x coordinate
|
||||||
-> ScreenUnit -- ^screen y coordinate
|
-> ScreenUnit -- ^screen y coordinate
|
||||||
-> UIId -- ^the parent widget
|
-> UIId -- ^the parent widget
|
||||||
-> [UIId]
|
-> Pioneers [UIId]
|
||||||
isInside x' y' wg =
|
getInsideId hMap x' y' uid = do
|
||||||
case isInsideSelf x' y' wg of -- test inside parent's bounding box
|
let wg = toGUIAny hMap uid
|
||||||
False -> []
|
inside <- isInside x' y' wg
|
||||||
True -> case concat $ map (isInside x' y') (getChildren wg) of
|
if inside -- test inside parent's bounding box
|
||||||
[] -> [toGUIAny wg]
|
then do
|
||||||
l -> l
|
childrenIds <- getChildren wg
|
||||||
|
hitChildren <- liftM concat $ mapM (getInsideId hMap x' y') childrenIds
|
||||||
|
case hitChildren of
|
||||||
|
[] -> return [uid]
|
||||||
|
_ -> return hitChildren
|
||||||
|
else return []
|
||||||
--TODO: Priority queue?
|
--TODO: Priority queue?
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user