{-# LANGUAGE ExistentialQuantification, DeriveDataTypeable, TypeSynonymInstances, FlexibleInstances, BangPatterns #-} -- |This module is the core of this library; it provides -- infrastructure for creating new types of widgets and extending -- their functionality. This module provides various bits of -- infrastructure, including: -- -- * modeling user interface widgets -- -- * managing changes in focus between widgets -- -- * managing widget state -- -- This module does not provide any concrete widget types. For -- in-depth discussion on this module's API and widget implementation -- in particular, see the Vty-ui User's Manual. module Graphics.Vty.Widgets.Core ( -- ** Widget Infrastructure WidgetImpl(..) , Widget , getNormalAttr , defaultContext , updateWidget , updateWidgetState , newWidget , getState , getCurrentSize , setCurrentPosition , getCurrentPosition , growVertical , growHorizontal , getCursorPosition , showWidget , getVisible , setVisible , (<~) , (<~~) -- ** Rendering , RenderContext(..) , RenderError(..) , render , renderAndPosition -- ** Miscellaneous , HasNormalAttr(..) , HasFocusAttr(..) , withNormalAttribute , withFocusAttribute -- ** Events , handleKeyEvent , onKeyPressed , onGainFocus , onLoseFocus , onResize , relayKeyEvents , relayFocusEvents -- ** Focus management , FocusGroup , FocusGroupError(..) , newFocusGroup , mergeFocusGroups , appendFocusGroup , resetFocusGroup , addToFocusGroup , focusNext , focusPrevious , setFocusGroupNextKey , setFocusGroupPrevKey , focus , unfocus ) where import Data.Typeable import Data.IORef import Control.Applicative import Control.Monad import Control.Exception import Graphics.Vty import Graphics.Vty.Widgets.Util import Graphics.Vty.Widgets.Skins import Graphics.Vty.Widgets.Events -- |The class of types with a ''normal'' attribute. class HasNormalAttr w where setNormalAttribute :: w -> Attr -> IO () -- |The class of types with a ''focus'' attribute, i.e., a way of -- visually indicating that the object has input focus. class HasFocusAttr w where setFocusAttribute :: w -> Attr -> IO () instance HasNormalAttr (Widget a) where setNormalAttribute wRef a = updateWidget wRef $ \w -> w { normalAttribute = mergeAttr a (normalAttribute w) } instance HasFocusAttr (Widget a) where setFocusAttribute wRef a = updateWidget wRef $ \w -> w { focusAttribute = mergeAttr a (focusAttribute w) } -- |Set the normal attribute on a value. withNormalAttribute :: (HasNormalAttr w) => Attr -> w -> IO w withNormalAttribute att w = do setNormalAttribute w att return w -- |Set the focus attribute on a value. withFocusAttribute :: (HasFocusAttr w) => Attr -> w -> IO w withFocusAttribute att w = do setFocusAttribute w att return w -- |Render errors. data RenderError = ImageTooBig String DisplayRegion DisplayRegion -- ^An error indicating that a widget rendered to -- an image which exceeded the available space. -- Provides a representation of the violating -- widget, the size of the available space, and the -- size of the image which the widget's rendering -- routine produced. deriving (Show, Typeable) instance Exception RenderError -- |Context information used during the rendering process. data RenderContext = RenderContext { normalAttr :: Attr -- ^The default normal attribute to use unless -- overridden by a given widget. , focusAttr :: Attr -- ^The default focused attribute to use for a -- focused widget unless overridden by a given -- widget. , overrideAttr :: Attr -- ^An override attribute to be used to override -- both the normal and focus attributes in effect -- during rendering. Usually def_attr, this -- attribute is used when child widgets need to have -- their attributes overridden by a parent widget. , skin :: Skin -- ^The skin to use for rendering borders and other -- interface elements which use the skin for their -- representations. } -- |Get the normal attribute of a rendering context. getNormalAttr :: RenderContext -> Attr getNormalAttr ctx = mergeAttrs [ overrideAttr ctx, normalAttr ctx ] -- |Default context settings. defaultContext :: RenderContext defaultContext = RenderContext def_attr (white `on` blue) def_attr unicodeSkin -- |The type of widget implementations, parameterized on the type of -- the widget's state. data WidgetImpl a = WidgetImpl { state :: !a -- ^The state of the widget. , visible :: !Bool -- ^Whether the widget is visible. , render_ :: Widget a -> DisplayRegion -> RenderContext -> IO Image -- ^The rendering routine of the widget. Takes the widget itself, -- a region indicating how much space the rendering process has to -- work with, and a rendering context to be used to determine -- attribute and skin settings. This MUST return an image which -- is no larger than the specified rendering region. , growHorizontal_ :: a -> IO Bool -- ^Returns whether the widget will automatically grow to fill -- available horizontal space. , growVertical_ :: a -> IO Bool -- ^Returns whether the widget will automatically grow to fill -- available vertical space. , currentSize :: DisplayRegion -- ^The size of the widget after its most recent rendering pass. , currentPosition :: DisplayRegion -- ^The position of the widget after its most recent rendering -- pass. , normalAttribute :: Attr -- ^The normal (unfocused) attribute of the wiget. , focusAttribute :: Attr -- ^The focused attribute of the widget. , setCurrentPosition_ :: Widget a -> DisplayRegion -> IO () -- ^Sets the current position of the widget. Takes a widget -- reference and a display region indicating the coordinates of -- the widget's upper left corner. , keyEventHandler :: Widget a -> Key -> [Modifier] -> IO Bool -- ^The widget's key event handler. Takes a widget reference, a -- key event, and a list of keyboard modifiers. Returns whether -- the keyboard event was handled. True indicates that the event -- was handled and that event processing should halt; False -- indicates that other event handlers, if present, may handle the -- event. , gainFocusHandlers :: Handlers (Widget a) -- ^List of handlers to be invoked when the widget gains focus. , resizeHandlers :: Handlers (DisplayRegion, DisplayRegion) -- ^List of handlers to be invoked when the widget's size changes. , loseFocusHandlers :: Handlers (Widget a) -- ^List of handlers to be invoked when the widget loses focus. , focused :: Bool -- ^Whether the widget is focused. , getCursorPosition_ :: Widget a -> IO (Maybe DisplayRegion) -- ^Returns the current terminal cursor position. Should return -- Nothing if the widget does not need to show a cursor, or Just -- if it does. (For example, widgets receiving keyboard input for -- text editing would should a cursor, but most won't need to.) } type Widget a = IORef (WidgetImpl a) -- |Show a widget. Most widget show instances aren't going to contain -- all of the widget state, but this at least gives an indication of -- the widget type, which can be crucial for debugging. showWidget :: (Show a) => Widget a -> IO String showWidget wRef = show <$> readIORef wRef instance (Show a) => Show (WidgetImpl a) where show w = concat [ "WidgetImpl { " , show $ state w , ", currentSize = " , show $ currentSize w , ", currentPosition = " , show $ currentPosition w , ", focused = " , show $ focused w , " }" ] -- |Set the visibility of a widget. Invisible widgets do not grow in -- either direction, always render to an empty image, and never -- declare a cursor position. setVisible :: Widget a -> Bool -> IO () setVisible wRef v = updateWidget wRef $ \st -> st { visible = v } -- |Get the visibility of a widget. getVisible :: Widget a -> IO Bool getVisible = (visible <~) -- |Does a widget grow horizontally? growHorizontal :: Widget a -> IO Bool growHorizontal w = do v <- visible <~ w case v of True -> do act <- growHorizontal_ <~ w st <- state <~ w act st False -> return False -- |Does a widget grow vertically? growVertical :: Widget a -> IO Bool growVertical w = do v <- visible <~ w case v of True -> do act <- growVertical_ <~ w st <- state <~ w act st False -> return False -- |Render a widget. This function should be called by widget -- implementations, since it does more than 'render_'; this function -- takes care of setting up attributes in the rendering context, -- setting the size of the widget after it has been rendered, and -- checking for size violations. May throw a 'RenderError'. render :: (Show a) => Widget a -- ^The widget to render. -> DisplayRegion -- ^The amount of space in which to render the -- widget. -> RenderContext -- ^The rendering context to use. -> IO Image render wRef sz ctx = do impl <- readIORef wRef v <- visible <~ wRef case v of False -> return empty_image True -> do -- Merge the override attributes with the context. If the -- overrides haven't been set (still def_attr), they will -- have no effect on the context attributes. norm <- normalAttribute <~ wRef foc <- focusAttribute <~ wRef let newCtx = ctx { normalAttr = mergeAttr norm $ normalAttr ctx , focusAttr = mergeAttr foc $ focusAttr ctx } img <- render_ impl wRef sz newCtx let imgsz = DisplayRegion (image_width img) (image_height img) when (image_width img > region_width sz || image_height img > region_height sz) $ throw $ ImageTooBig (show impl) sz imgsz setCurrentSize wRef $ DisplayRegion (image_width img) (image_height img) return img -- |Render a widget and set its position after rendering is complete. -- This is exported for internal use; widget implementations should -- call 'render' instead. renderAndPosition :: (Show a) => Widget a -- ^The widget to render. -> DisplayRegion -- ^The position at which to render -- the widget. -> DisplayRegion -- ^The amount of space in which to -- render the widget. -> RenderContext -- ^The rendering context to use. -> IO Image renderAndPosition wRef pos sz ctx = do img <- render wRef sz ctx -- Position post-processing depends on the sizes being correct! setCurrentPosition wRef pos return img -- |Set the current size of a widget. Exported for internal use. When the -- size changes from its previous value, resize event handlers will be invoked. setCurrentSize :: Widget a -> DisplayRegion -> IO () setCurrentSize wRef newSize = do oldSize <- getCurrentSize wRef modifyIORef wRef $ \w -> let new = w { currentSize = newSize } in seq new new when (oldSize /= newSize) $ handleResizeEvent wRef (oldSize, newSize) -- |Get the current size of the widget (its size after its most recent -- rendering). getCurrentSize :: Widget a -> IO DisplayRegion getCurrentSize wRef = (return . currentSize) =<< (readIORef wRef) -- |Set the current position of a widget. getCurrentPosition :: Widget a -> IO DisplayRegion getCurrentPosition wRef = currentPosition <$> (readIORef wRef) -- |Set the current position of a widget. Exported for internal use. setCurrentPosition :: Widget a -> DisplayRegion -> IO () setCurrentPosition wRef pos = do updateWidget wRef $ \w -> w { currentPosition = pos } w <- readIORef wRef (setCurrentPosition_ w) wRef pos -- |Create a new widget. Takes an initial state value and a widget -- implementation transformation and passes it an implementation with -- default values. newWidget :: a -> (WidgetImpl a -> WidgetImpl a) -> IO (Widget a) newWidget initState f = do gfhs <- newHandlers lfhs <- newHandlers rhs <- newHandlers wRef <- newIORef $ WidgetImpl { state = initState , render_ = \_ _ _ -> return empty_image , growVertical_ = const $ return False , growHorizontal_ = const $ return False , setCurrentPosition_ = \_ _ -> return () , currentSize = DisplayRegion 0 0 , currentPosition = DisplayRegion 0 0 , focused = False , visible = True , gainFocusHandlers = gfhs , resizeHandlers = rhs , loseFocusHandlers = lfhs , keyEventHandler = \_ _ _ -> return False , getCursorPosition_ = defaultCursorInfo , normalAttribute = def_attr , focusAttribute = def_attr } updateWidget wRef f return wRef -- |Default cursor positioning implementation used by 'newWidget'. defaultCursorInfo :: Widget a -> IO (Maybe DisplayRegion) defaultCursorInfo w = do sz <- getCurrentSize w pos <- getCurrentPosition w if region_width sz > 0 then return $ Just $ pos `plusWidth` (region_width sz - 1) else return Nothing -- |Given a widget and key event information, invoke the widget's key -- event handler with the event information. Returns whether the -- event was handled. handleKeyEvent :: Widget a -> Key -> [Modifier] -> IO Bool handleKeyEvent wRef keyEvent mods = do act <- keyEventHandler <~ wRef act wRef keyEvent mods -- |Given a widget, invoke its resize event handlers with the old and new -- sizes. handleResizeEvent :: Widget a -> (DisplayRegion, DisplayRegion) -> IO () handleResizeEvent wRef szs = fireEvent wRef (resizeHandlers <~) szs -- |Given widgets A and B, causes any key events on widget A to be -- relayed to widget B. Note that this does behavior constitutes an -- ordinary key event handler from A's perspective, so if B does not -- handle a given key event, subsequent key event handlers on A will -- still get a chance to handle the event. This function is mostly -- useful for wrapper widgets which don't do any event handling of -- their own but want to ensure that all key events are relayed to the -- wrapped widget. relayKeyEvents :: Widget a -> Widget b -> IO () relayKeyEvents a b = a `onKeyPressed` \_ k mods -> handleKeyEvent b k mods -- |Given widgets A and B, cause all focus gain and loss events on A -- to cause focus gain and loss for B. relayFocusEvents :: Widget a -> Widget b -> IO () relayFocusEvents a b = do a `onGainFocus` \_ -> focus b a `onLoseFocus` \_ -> unfocus b -- |Given a widget and a key event handler, add the handler to the -- widget's key event handler structure. The event handler is added -- last, so any preexisting handlers will run before this one. onKeyPressed :: Widget a -> (Widget a -> Key -> [Modifier] -> IO Bool) -> IO () onKeyPressed wRef handler = do -- Create a new handler that calls this one but defers to the old -- one if the new one doesn't handle the event. oldHandler <- keyEventHandler <~ wRef let combinedHandler = \w k ms -> do v <- handler w k ms case v of True -> return True False -> oldHandler w k ms updateWidget wRef $ \w -> w { keyEventHandler = combinedHandler } -- |Focus a widget. Causes its focus gain event handlers to run. If -- the widget is in a 'FocusGroup' and if that group's -- currently-focused widget is some other widget, that widget will -- lose the focus and its focus loss event handlers will be called. focus :: Widget a -> IO () focus wRef = do updateWidget wRef $ \w -> w { focused = True } fireEvent wRef (gainFocusHandlers <~) wRef -- |Unfocus a widget. Causes its focus loss event handlers to run. unfocus :: Widget a -> IO () unfocus wRef = do updateWidget wRef $ \w -> w { focused = False } fireEvent wRef (loseFocusHandlers <~) wRef -- |Given a widget and a focus gain event handler, add the handler to -- the widget. The handler will be invoked when the widget receives -- focus. onGainFocus :: Widget a -> (Widget a -> IO ()) -> IO () onGainFocus = addHandler (gainFocusHandlers <~) -- |Given a widget and a resize event handler, add the handler to the widget. -- The handler will be invoked when the widget's size changes. This includes -- the first rendering, at which point its size changes from (0, 0). Note that -- if the resize handler needs to change the visual appearance of the widget -- when its size changes, be sure to use 'schedule' to ensure that visual -- changes are reflected immediately, and be absolutely sure that those changes -- will not cause further size changes; that will cause a resize event handler -- loop that will consume your CPU! onResize :: Widget a -> ((DisplayRegion, DisplayRegion) -> IO ()) -> IO () onResize = addHandler (resizeHandlers <~) -- |Given a widget and a focus loss event handler, add the handler to -- the widget. The handler will be invoked when the widget loses -- focus. onLoseFocus :: Widget a -> (Widget a -> IO ()) -> IO () onLoseFocus = addHandler (loseFocusHandlers <~) -- |Convenience projection on the contents of an 'IORef'. (<~) :: (a -> b) -> IORef a -> IO b (<~) f wRef = (return . f) =<< (readIORef wRef) -- |Convenience projection on the state of a widget. (<~~) :: (a -> b) -> Widget a -> IO b (<~~) f wRef = (return . f . state) =<< (readIORef wRef) -- |Given a widget and an implementation transformer, apply the -- transformer to the widget's implementation. updateWidget :: Widget a -> (WidgetImpl a -> WidgetImpl a) -> IO () updateWidget wRef f = modifyIORef wRef $ \val -> let new = f val in seq new new -- |Get the state value of a widget. getState :: Widget a -> IO a getState wRef = state <~ wRef -- |Apply a state transformation function to a widget's state. updateWidgetState :: Widget a -> (a -> a) -> IO () updateWidgetState wRef f = do w <- readIORef wRef writeIORef wRef $ let new = w { state = f (state w) } in seq new new -- |Focus group handling errors. data FocusGroupError = FocusGroupEmpty -- ^Thrown when the desired operation could not -- be completed because the focus group is empty. | FocusGroupBadIndex Int -- ^Thrown when the specified focus group entry -- index was invalid. deriving (Typeable, Show) instance Exception FocusGroupError -- |The state type of widgets added to a focus group. data FocusEntry = forall a. FocusEntry (Widget a) -- |Focus group. Represents an cycle of widgets which receive input -- focus. data FocusGroup = FocusGroup { entries :: [Widget FocusEntry] , currentEntryNum :: Int , nextKey :: (Key, [Modifier]) , prevKey :: (Key, [Modifier]) } newFocusEntry :: (Show a) => Widget a -> IO (Widget FocusEntry) newFocusEntry chRef = do let st = FocusEntry chRef wRef <- newWidget st $ \w -> w { growHorizontal_ = const $ growHorizontal chRef , growVertical_ = const $ growVertical chRef , render_ = \_ sz ctx -> render chRef sz ctx , setCurrentPosition_ = \this pos -> do (FocusEntry ch) <- getState this setCurrentPosition ch pos } wRef `relayFocusEvents` chRef wRef `relayKeyEvents` chRef return wRef -- |Create a new focus group. Note that the focus group is itself a -- widget; any input event handlers added to the focus group will fire -- before input events are handled by the currently-focused widget. newFocusGroup :: IO (Widget FocusGroup) newFocusGroup = do let initSt = FocusGroup { entries = [] , currentEntryNum = -1 , nextKey = (KASCII '\t', []) , prevKey = (KBackTab, []) } wRef <- newWidget initSt $ \w -> w { getCursorPosition_ = \this -> do cur <- currentEntryNum <~~ this case cur of (-1) -> return Nothing _ -> do eRef <- currentEntry this (FocusEntry e) <- state <~ eRef getCursorPosition e , keyEventHandler = \this key mods -> do st <- getState this case currentEntryNum st of (-1) -> return False i -> do if (key, mods) == nextKey st then (focusNext this >> return True) else if (key, mods) == prevKey st then (focusPrevious this >> return True) else do let e = entries st !! i handleKeyEvent e key mods } return wRef -- |Set the keyboard event information used to change focus to the -- next widget in a focus group. setFocusGroupNextKey :: Widget FocusGroup -> Key -> [Modifier] -> IO () setFocusGroupNextKey fg k mods = updateWidgetState fg $ \s -> s { nextKey = (k, mods) } -- |Set the keyboard event information used to change focus to the -- previous widget in a focus group. setFocusGroupPrevKey :: Widget FocusGroup -> Key -> [Modifier] -> IO () setFocusGroupPrevKey fg k mods = updateWidgetState fg $ \s -> s { prevKey = (k, mods) } -- |Merge two focus groups. Given two focus groups A and B, this -- returns a new focus group with all of the entries from A and B added to it, -- in that order. At least one A and B must be non-empty or 'FocusGroupEmpty' -- will be thrown. mergeFocusGroups :: Widget FocusGroup -> Widget FocusGroup -> IO (Widget FocusGroup) mergeFocusGroups a b = do c <- newFocusGroup aEntries <- entries <~~ a bEntries <- entries <~~ b when (null aEntries && null bEntries) $ throw FocusGroupEmpty updateWidgetState c $ \s -> s { entries = aEntries ++ bEntries , currentEntryNum = 0 } -- Now we need to be sure that we have the event handlers set -- correctly on each widget. The reason we don't just call -- addToFocusGroup on each entry's widget is because the user may -- have added event handlers to the FocusEntries themselves, and we -- want to preserve those, so we extract the widget from the focus -- entry to add the onGainFocus handler, but use the existing -- FocusEntries when constructing the new focus group. forM_ (zip [0..] aEntries) $ \(i, e) -> do (FocusEntry w) <- state <~ e w `onGainFocus` (const $ setCurrentFocus c i) forM_ (zip [(length aEntries)..] bEntries) $ \(i, e) -> do (FocusEntry w) <- state <~ e w `onGainFocus` (const $ setCurrentFocus c i) return c -- |Given two focus groups A and B, append the entries of B to A, -- mutating A in the process. Throws 'FocusGroupEmpty' if B is empty. appendFocusGroup :: Widget FocusGroup -> Widget FocusGroup -> IO () appendFocusGroup a b = do aEntries <- entries <~~ a bEntries <- entries <~~ b when (null bEntries) $ throw FocusGroupEmpty updateWidgetState a $ \s -> s { entries = (entries s) ++ bEntries , currentEntryNum = 0 } -- Now we need to be sure that we have the event handlers set -- correctly on each widget. The reason we don't just call -- addToFocusGroup on each entry's widget is because the user may -- have added event handlers to the FocusEntries themselves, and we -- want to preserve those, so we extract the widget from the focus -- entry to add the onGainFocus handler, but use the existing -- FocusEntries when constructing the new focus group. forM_ (zip [(length aEntries)..] bEntries) $ \(i, e) -> do (FocusEntry w) <- state <~ e w `onGainFocus` (const $ setCurrentFocus a i) -- |Reset a focus group. This ensures that the focus group's state is -- coherent by calling 'focus' on the group's focused entry and -- 'unfocus' on all the rest. This is for internal use, but is used -- by the 'Collection' switching implementation to ensure that focus -- state is sane. resetFocusGroup :: Widget FocusGroup -> IO () resetFocusGroup fg = do cur <- currentEntryNum <~~ fg es <- entries <~~ fg forM_ (zip [0..] es) $ \(i, e) -> when (i /= cur) $ unfocus e when (cur >= 0) $ focus =<< currentEntry fg -- |Get the desired cursor position, if any, for a widget. getCursorPosition :: Widget a -> IO (Maybe DisplayRegion) getCursorPosition wRef = do v <- visible <~ wRef case v of True -> do ci <- getCursorPosition_ <~ wRef ci wRef False -> return Nothing -- |Return the current focus entry of a focus group. currentEntry :: Widget FocusGroup -> IO (Widget FocusEntry) currentEntry wRef = do es <- entries <~~ wRef i <- currentEntryNum <~~ wRef when (i == -1) $ throw FocusGroupEmpty return (es !! i) -- |Add a widget to a focus group. This returns a focus group entry -- which wraps the specified widget; the focus group entry is also a -- widget and can take key event handlers and the like. During input -- event processing, the focus group entry receives keyboard events -- and passes them on to the wrapped widget. If you want a widget to -- have specific event handling in a particular interface, add event -- handlers to its focus entry/entries instead of the widget itself. addToFocusGroup :: (Show a) => Widget FocusGroup -> Widget a -> IO (Widget FocusEntry) addToFocusGroup cRef wRef = do eRef <- newFocusEntry wRef entryPos <- (length . entries) <~~ cRef updateWidgetState cRef $ \s -> s { entries = (entries s) ++ [eRef] } -- Add an event handler to the widget, NOT the entry wrapper, so -- others can call 'focus' on the widget and affect this focus -- group. wRef `onGainFocus` \_ -> setCurrentFocus cRef entryPos -- If we just added the first widget to the group, focus it so -- something has focus. when (entryPos == 0) $ focus eRef return eRef -- |Focus the next widget in a focus group. focusNext :: Widget FocusGroup -> IO () focusNext wRef = do st <- getState wRef let cur = currentEntryNum st when (cur == -1) $ throw FocusGroupEmpty let nextEntry = if cur < length (entries st) - 1 then (entries st) !! (cur + 1) else (entries st) !! 0 focus nextEntry -- |Focus the previous widget in a focus group. focusPrevious :: Widget FocusGroup -> IO () focusPrevious wRef = do st <- getState wRef let cur = currentEntryNum st when (cur == -1) $ throw FocusGroupEmpty let prevEntry = if cur > 0 then (entries st) !! (cur - 1) else (entries st) !! (length (entries st) - 1) focus prevEntry -- Note that this only 1) updates the focus index in the group and 2) -- calls unfocus on the previously-focused widget. This does NOT call -- focus on the newly-focused widget, because this is intended to be -- callable from a focus event handler for the widget that got -- focused. setCurrentFocus :: Widget FocusGroup -> Int -> IO () setCurrentFocus cRef i = do st <- state <~ cRef when (i >= length (entries st) || i < 0) $ throw $ FocusGroupBadIndex i -- If new entry number is different from existing one, invoke focus -- handlers. when (currentEntryNum st /= i) $ do when (currentEntryNum st >= 0) $ unfocus ((entries st) !! (currentEntryNum st)) updateWidgetState cRef $ \s -> s { currentEntryNum = i }