2d80c92384
Additionally, widgets functions now use the Pioneers monad. Branch is compiling again and works.
251 lines
12 KiB
Haskell
251 lines
12 KiB
Haskell
{-# LANGUAGE InstanceSigs, MultiParamTypeClasses, FlexibleInstances #-}
|
|
|
|
module UI.UIClasses where
|
|
|
|
import Control.Lens ((^.))
|
|
import Control.Monad
|
|
--import Control.Monad.IO.Class -- MonadIO
|
|
import Control.Monad.RWS.Strict (get)
|
|
import Data.List
|
|
import Data.Maybe
|
|
import qualified Data.HashMap.Strict as Map
|
|
|
|
import qualified Types as T
|
|
import UI.UIBaseData
|
|
|
|
class GUIAnyMap m w where
|
|
guiAnyMap :: (w -> b) -> GUIAny m -> b
|
|
|
|
class (Monad m) => GUIWidget m uiw where
|
|
-- |The 'getBoundary' function gives the outer extents of the 'UIWidget'.
|
|
-- The bounding box wholly contains all children components.
|
|
getBoundary :: uiw -> m (ScreenUnit, ScreenUnit, ScreenUnit, ScreenUnit) -- ^@(x, y, width, height)@ in pixels (screen coordinates)
|
|
|
|
-- |The 'getChildren' function returns all children associated with this widget.
|
|
--
|
|
-- All children must be wholly inside the parent's bounding box specified by 'getBoundary'.
|
|
getChildren :: uiw -> m [UIId]
|
|
getChildren _ = return []
|
|
|
|
-- |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
|
|
-- component.
|
|
--
|
|
-- The default implementations tests if the point is within the rectangle specified by the
|
|
-- 'getBoundary' function.
|
|
isInside :: ScreenUnit -- ^screen x coordinate
|
|
-> ScreenUnit -- ^screen y coordinate
|
|
-> uiw -- ^the parent widget
|
|
-> m Bool
|
|
isInside x' y' wg = do
|
|
(x, y, w, h) <- getBoundary wg
|
|
return $ (x' - x <= w) && (x' - x >= 0) && (y' - y <= h) && (y' - y >= 0)
|
|
|
|
-- |The 'getPriority' function returns the priority score of a 'GUIWidget'.
|
|
-- A widget with a high score is more in the front than a low scored widget.
|
|
getPriority :: uiw -> m Int
|
|
getPriority _ = return 0
|
|
|
|
-- |The 'getShorthand' function returns a descriptive 'String' mainly for debuggin prupose.
|
|
-- The shorthand should be unique for each instance.
|
|
getShorthand :: uiw -> m String
|
|
|
|
-- |A 'GUIClickable' represents a widget with a 'UIButtonState'.
|
|
--
|
|
-- Minimal complete definition: 'getButtonState' and either 'updateButtonState' or 'setButtonState'.
|
|
class GUIClickable w where
|
|
updateButtonState :: (UIButtonState -> UIButtonState) -> w -> w
|
|
updateButtonState f w = setButtonState (f $ getButtonState w) w
|
|
setButtonState :: UIButtonState -> w -> w
|
|
setButtonState s = updateButtonState (\_ -> s)
|
|
getButtonState :: w -> UIButtonState
|
|
|
|
class Monad m => MouseHandler a m w where
|
|
-- |The function 'onMousePressed' is called when the primary button is pressed
|
|
-- while inside a screen coordinate within the widget ('isInside').
|
|
onMousePressed :: ScreenUnit -- ^screen x coordinate
|
|
-> ScreenUnit -- ^screen y coordinate
|
|
-> w -- ^widget the event is invoked on
|
|
-> a -> m (w, a) -- ^widget after the event and the altered handler
|
|
onMousePressed _ _ wg a = return (wg, a)
|
|
|
|
-- |The function 'onMouseReleased' is called when the primary button is released
|
|
-- while the pressing event occured within the widget ('isInside').
|
|
--
|
|
-- Thus, the mouse is either within the widget or outside while still dragging.
|
|
onMouseReleased :: ScreenUnit -- ^screen x coordinate
|
|
-> ScreenUnit -- ^screen x coordinate
|
|
-> w -- ^wdiget the event is invoked on
|
|
-> a -> m (w, a) -- ^widget after the event and the altered handler
|
|
onMouseReleased _ _ wg a = return (wg, a)
|
|
|
|
-- |The function 'onMousePressed' is called when the secondary button is pressed
|
|
-- while inside a screen coordinate within the widget ('isInside').
|
|
onMousePressedAlt :: ScreenUnit -- ^screen x coordinate
|
|
-> ScreenUnit -- ^screen y coordinate
|
|
-> w -- ^widget the event is invoked on
|
|
-> a -> m (w, a) -- ^widget after the event and the altered handler
|
|
onMousePressedAlt _ _ wg a = return (wg, a)
|
|
|
|
-- |The function 'onMouseReleased' is called when the secondary button is released
|
|
-- while the pressing event occured within the widget ('isInside').
|
|
--
|
|
-- Thus, the mouse is either within the widget or outside while still dragging.
|
|
onMouseReleasedAlt :: ScreenUnit -- ^screen x coordinate
|
|
-> ScreenUnit -- ^screen x coordinate
|
|
-> w -- ^wdiget the event is invoked on
|
|
-> a -> m (w, a) -- ^widget after the event and the altered handler
|
|
onMouseReleasedAlt _ _ wg a = return (wg, a)
|
|
|
|
-- |The function 'onMouseMove' is invoked when the mouse is moved inside the
|
|
-- widget's space ('isInside').
|
|
onMouseMove :: ScreenUnit -- ^screen x coordinate
|
|
-> ScreenUnit -- ^screen y coordinate
|
|
-> w -- ^widget the event is invoked on
|
|
-> a -> m (w, a) -- ^widget after the event and the altered handler
|
|
onMouseMove _ _ wg a = return (wg, a)
|
|
|
|
-- |The function 'onMouseMove' is invoked when the mouse enters the
|
|
-- widget's space ('isInside').
|
|
onMouseEnter :: ScreenUnit -- ^screen x coordinate
|
|
-> ScreenUnit -- ^screen y coordinate
|
|
-> w -- ^widget the event is invoked on
|
|
-> a -> m (w, a) -- ^widget after the event and the altered handler
|
|
onMouseEnter _ _ wg a = return (wg, a)
|
|
|
|
-- |The function 'onMouseMove' is invoked when the mouse leaves the
|
|
-- widget's space ('isInside').
|
|
onMouseLeave :: ScreenUnit -- ^screen x coordinate
|
|
-> ScreenUnit -- ^screen y coordinate
|
|
-> w -- ^widget the event is invoked on
|
|
-> a -> m (w, a) -- ^widget after the event and the altered handler
|
|
onMouseLeave _ _ wg a = return (wg, a)
|
|
|
|
instance (MouseHandler h m w) => MouseHandler (MouseHandlerSwitch h) m w where
|
|
onMousePressed x y w (MouseHandlerSwitch h) = do
|
|
(w', h') <- onMousePressedAlt x y w h
|
|
return (w', MouseHandlerSwitch h')
|
|
onMouseReleased x y w (MouseHandlerSwitch h) = do
|
|
(w', h') <- onMouseReleasedAlt x y w h
|
|
return (w', MouseHandlerSwitch h')
|
|
onMousePressedAlt x y w (MouseHandlerSwitch h) = do
|
|
(w', h') <- onMousePressed x y w h
|
|
return (w', MouseHandlerSwitch h')
|
|
onMouseReleasedAlt x y w (MouseHandlerSwitch h) = do
|
|
(w', h') <- onMouseReleased x y w h
|
|
return (w', MouseHandlerSwitch h')
|
|
onMouseMove x y w (MouseHandlerSwitch h) = do
|
|
(w', h') <- onMouseMove x y w h
|
|
return (w', MouseHandlerSwitch h')
|
|
onMouseEnter x y w (MouseHandlerSwitch h) = do
|
|
(w', h') <- onMouseEnter x y w h
|
|
return (w', MouseHandlerSwitch h')
|
|
onMouseLeave x y w (MouseHandlerSwitch h) = do
|
|
(w', h') <- onMouseLeave x y w h
|
|
return (w', MouseHandlerSwitch h')
|
|
|
|
instance (Monad m, GUIClickable w) => MouseHandler (ButtonHandler m w) m w where
|
|
-- |Change 'UIButtonState's '_buttonstateIsFiring' to @True@.
|
|
onMousePressed _ _ wg h =
|
|
return (updateButtonState (\s -> s {_buttonstateIsFiring = True}) wg, h)
|
|
|
|
-- |Change 'UIButtonState's '_buttonstateIsFiring' to @False@ and
|
|
-- call 'action' if inside the widget or
|
|
-- set '_buttonstateIsDeferred' to false otherwise.
|
|
onMouseReleased x y wg h@(ButtonHandler action) = if _buttonstateIsFiring $ getButtonState wg
|
|
then do
|
|
wg' <- action wg x y
|
|
return (updateButtonState (\s -> s {_buttonstateIsFiring = False}) wg', h)
|
|
else return (updateButtonState (\s -> s {_buttonstateIsDeferred = False}) wg, h)
|
|
|
|
-- |Do nothing.
|
|
onMouseMove _ _ wg h = return (wg, h)
|
|
|
|
-- |Set 'UIButtonState's '_buttonstateIsReady' to @True@ and
|
|
-- update dragging state (only drag if inside widget).
|
|
-- In detail, change 'UIButtonState's '_buttonstateIsDeferred' to '_buttonstateIsFiring's current value
|
|
-- and set '_buttonstateIsFiring' to @False@.
|
|
onMouseEnter _ _ wg h = return
|
|
(updateButtonState (\s -> s { _buttonstateIsFiring = _buttonstateIsDeferred s
|
|
, _buttonstateIsDeferred = False
|
|
, _buttonstateIsReady = True
|
|
}) wg
|
|
, h)
|
|
|
|
-- |Set 'UIButtonState's 'buttonstateIsReady' to @False@ and
|
|
-- update dragging state (only drag if inside widget).
|
|
-- In detail, change 'UIButtonState's '_buttonstateIsFiring' to '_buttonstateIsDeferred's current value
|
|
-- and set '_buttonstateIsDeferred's' to @False@.
|
|
onMouseLeave _ _ wg h = return
|
|
(updateButtonState (\s -> s { _buttonstateIsFiring = False
|
|
, _buttonstateIsDeferred = _buttonstateIsFiring s
|
|
, _buttonstateIsReady = False
|
|
}) wg
|
|
, h)
|
|
|
|
instance (Monad m) => GUIAnyMap m (GUIAny m) where
|
|
guiAnyMap f w = f w
|
|
|
|
instance GUIWidget T.Pioneers (GUIAny T.Pioneers) where
|
|
getBoundary (GUIAnyC w) = getBoundary w
|
|
getBoundary (GUIAnyP w) = getBoundary w
|
|
getBoundary (GUIAnyB w _) = getBoundary w
|
|
getChildren (GUIAnyC w) = getChildren w
|
|
getChildren (GUIAnyP w) = getChildren w
|
|
getChildren (GUIAnyB w _) = getChildren w
|
|
isInside x y (GUIAnyC w) = (isInside x y) w
|
|
isInside x y (GUIAnyP w) = (isInside x y) w
|
|
isInside x y (GUIAnyB w _) = (isInside x y) w
|
|
getPriority (GUIAnyC w) = getPriority w
|
|
getPriority (GUIAnyP w) = getPriority w
|
|
getPriority (GUIAnyB w _) = getPriority w
|
|
getShorthand (GUIAnyC w) = do { str <- getShorthand w; return $ "A" ++ str }
|
|
getShorthand (GUIAnyP w) = do { str <- getShorthand w; return $ "A" ++ str }
|
|
getShorthand (GUIAnyB w _) = do { str <- getShorthand w; return $ "A" ++ str }
|
|
|
|
instance (Monad m) => GUIAnyMap m GUIContainer where
|
|
guiAnyMap f (GUIAnyC c) = f c
|
|
guiAnyMap _ _ = error "invalid types in guiAnyMap"
|
|
instance (Monad m) => GUIWidget m GUIContainer where
|
|
getBoundary :: GUIContainer -> m (ScreenUnit, ScreenUnit, ScreenUnit ,ScreenUnit)
|
|
getBoundary cnt = return (_uiScreenX cnt, _uiScreenY cnt, _uiWidth cnt, _uiHeight cnt)
|
|
getChildren cnt = return $ _uiChildren cnt
|
|
getPriority cnt = return $ _uiPriority cnt
|
|
getShorthand _ = return $ "CNT"
|
|
|
|
instance GUIAnyMap m GUIPanel where
|
|
guiAnyMap f (GUIAnyP p) = f p
|
|
guiAnyMap _ _ = error "invalid types in guiAnyMap"
|
|
instance GUIWidget T.Pioneers GUIPanel where
|
|
getBoundary pnl = do
|
|
state <- get
|
|
let hmap = state ^. T.ui . T.uiMap
|
|
case _uiChildren $ _panelContainer pnl of
|
|
[] -> getBoundary $ _panelContainer pnl
|
|
cs -> do
|
|
let widgets = catMaybes $ map (flip Map.lookup hmap) cs
|
|
foldl' (liftM2 determineSize) (getBoundary $ _panelContainer pnl) $ map getBoundary widgets
|
|
where
|
|
determineSize :: (ScreenUnit, ScreenUnit, ScreenUnit ,ScreenUnit) -> (ScreenUnit, ScreenUnit, ScreenUnit ,ScreenUnit) -> (ScreenUnit, ScreenUnit, ScreenUnit ,ScreenUnit)
|
|
determineSize (x, y, w, h) (x', y', w', h') =
|
|
let x'' = if x' < x then x' else x
|
|
y'' = if y' < y then y' else y
|
|
w'' = if x' + w' > x + w then x' + w' - x'' else x + w - x''
|
|
h'' = if y' + h' > y + h then y' + h' - y'' else y + h - y''
|
|
in (x'', y'', w'', h'')
|
|
|
|
getChildren pnl = getChildren $ _panelContainer pnl
|
|
getPriority pnl = getPriority $ _panelContainer pnl
|
|
getShorthand _ = return $ "PNL"
|
|
|
|
instance (Monad m) => GUIAnyMap m GUIButton where
|
|
guiAnyMap f (GUIAnyB btn _) = f btn
|
|
guiAnyMap _ _ = error "invalid types in guiAnyMap"
|
|
instance GUIClickable GUIButton where
|
|
getButtonState = _uiButtonState
|
|
updateButtonState f btn = btn {_uiButtonState = f $ _uiButtonState btn}
|
|
instance (Monad m) => GUIWidget m GUIButton where
|
|
getBoundary btn = return (_uiScreenXB btn, _uiScreenYB btn, _uiWidthB btn, _uiHeightB btn)
|
|
getChildren _ = return []
|
|
getPriority btn = return $ _uiPriorityB btn
|
|
getShorthand _ = return "BTN" |