barrie-0.4.1: src/Barrie/Render.hs
module Barrie.Render (RunnableGUI, RenderF,
updates, renderWidget,
renderText, renderBool, renderInt, renderDouble) where
import Data.Dynamic
import Data.List
import Data.Maybe
import Barrie.Gadgets
import Barrie.Style
import Barrie.Trace
import Barrie.Widgets
-- |RenderF is the type of rendering functions. A rendering
-- function should be implmeneted for each backend. Currently,
-- we have Gtk and IO.
type RenderF a b = String -- name of the gui element type
-> [RunnableGUI a b] -- children of this gui element
-> Style
-> Maybe (Gadget b) -- corresponding implementation
-> Maybe Widget -- possible popup
-> IO (RunnableGUI a b)
-- RunnableGUI is a type representing a rendered set of gadgets,
-- widgets, and styles.
-- A rendering function retuns a single RunnableGUI, which
-- represents the top level gui element. Each of the inner
-- elements is also a RunnableGUI, and they are rendered in the
-- same way. Anything that can be rendered can be used as a top
-- level element, either in the main application, or in a
-- pop-up. Of course, this isn't enforced by this module, but
-- the RenderF functions should make sure it's true, because we
-- rely on it.
-- A RunnableGUI contains three fields. First is the GUI, which
-- is of a type chosen by the renderer (e.g. Gtk uses
-- Gtk.Widget). Second is an update from state function. When
-- the state changes, this is called for each widget, causing it
-- to display according to the latest state. Finally comes a
-- function which, give a function to extract state from IO
-- should call when the state is updated, which allows everything
-- else to be kept up to date. The final field is a function
-- which, given a way of extracting the current state from IO,
-- and a way of putting it back, can update it via its gadget
-- functionality.
-- One will rightly point out that this is all very ugly and
-- procedural, but luckily, anybody using Barrie doesn't have to
-- worry about it. It's the sewer of side-effects beneath the
-- cathedral of functional gui-ness.
type RunnableGUI gui state =
(gui, -- the GUI
state -> IO (), -- update from state
IO state -> (state -> IO ()) -> IO ()) -- state update function
-- The updates function extracts all the update functions from a
-- runnable gui. This is convenient for the implementation of
-- renderers.
updates :: [RunnableGUI gui state] -> [state -> IO ()]
updates = map (\ (_, update, _) -> update)
-- A behaviour is just a flat list of gadgets. Gadgets are defined
-- in a hierarchical way, but are flattened for rendering.
-- Note that the gadget hierarchy is only tangentially related to the
-- widget layout. The hierarchy is intended to be a functional
-- grouping, although in practice it will often look similar to the
-- widget layout.
renderWidget :: [String] -- ^ name prefix for gadgets
-> Gadget w -- ^ top gadget
-> Widget -- ^ rendered widget
-> RenderF r w -- ^ GUI rendering action
-> IO (RunnableGUI r w)
renderWidget prefix behaviour widget render = go widget
where go w = do let elmt = widgetElementName w
style = widgetStyle w
gadget = if (not . null . uiElementName) w
then findGadget (uiElementName w)
behaviour
else Nothing
traceMessage ("rendering element: " ++ elmt);
traceMessage ("ui name = " ++
intercalate "." (uiElementName w))
traceMessage ("gadget = " ++
maybe "Nothing" flatName gadget)
ws <- mapM go $ widgetChildren w
render elmt ws style gadget (widgetPopupChild w)
getRenderer :: String -> [(TypeRep, Dynamic -> a)] -> Dynamic -> a
getRenderer nm known d =
case filter ((ty==) . fst) known of
[] -> error $ "can't render as " ++ nm ++ ": " ++ show d
((_,f):_) -> f d
where ty = dynTypeRep d
renderText :: Dynamic -> String
renderText = getRenderer "text" knownTextTypes
where knownTextTypes =
[(typeOf "", flip fromDyn "")
,(typeOf (0::Int), show . flip fromDyn (0::Int))
,(typeOf (0::Double), show . flip fromDyn (0::Double))
]
renderBool :: Dynamic -> Bool
renderBool = getRenderer "bool" knownBoolTypes
where knownBoolTypes = [(typeOf False, flip fromDyn False)
]
renderInt :: Dynamic -> Int
renderInt = getRenderer "int" knownIntTypes
where knownIntTypes =
[(typeOf (0::Int), flip fromDyn 0)
,(typeOf (0::Double), round . flip fromDyn (0::Double))
]
renderDouble :: Dynamic -> Double
renderDouble = getRenderer "double" knownDoubleTypes
where knownDoubleTypes =
[(typeOf (0::Double), flip fromDyn 0)
,(typeOf (0::Int), realToFrac . flip fromDyn (0::Int))
]