barrie 0.3.1 → 0.4
raw patch · 21 files changed
+1406/−1288 lines, 21 files
Files
- barrie.cabal +2/−3
- src/Barrie.hs +19/−0
- src/Barrie.lhs +0/−21
- src/Barrie/AbstractGadgets.lhs +0/−24
- src/Barrie/Config.hs +46/−0
- src/Barrie/Config.lhs +0/−39
- src/Barrie/DrawPrimitive.hs +131/−0
- src/Barrie/DrawPrimitive.lhs +0/−131
- src/Barrie/Gadgets.hs +321/−0
- src/Barrie/Gadgets.lhs +0/−383
- src/Barrie/Gadgets/Connections.hs +68/−0
- src/Barrie/Render.hs +122/−0
- src/Barrie/Render.lhs +0/−86
- src/Barrie/Render/Gtk.hs +449/−0
- src/Barrie/Render/Gtk.lhs +0/−412
- src/Barrie/Style.hs +104/−0
- src/Barrie/Style.lhs +0/−66
- src/Barrie/Trace.hs +21/−0
- src/Barrie/Trace.lhs +0/−21
- src/Barrie/Widgets.hs +123/−0
- src/Barrie/Widgets.lhs +0/−102
barrie.cabal view
@@ -1,5 +1,5 @@ Name: barrie-Version: 0.3.1+Version: 0.4 Synopsis: Pure Gtk GUI library Description: Implementation of an idea for supporting certain kinds of GUI:@@ -23,10 +23,10 @@ ghc-options: -Wall -optl-Wl,s ghc-prof-options: -prof -auto-all other-modules:- Barrie.AbstractGadgets Barrie.Config Barrie.DrawPrimitive Barrie.Gadgets+ Barrie.Gadgets.Connections Barrie.Render Barrie.Render.Gtk Barrie.Style@@ -34,5 +34,4 @@ Barrie.Widgets extensions: MultiParamTypeClasses,FunctionalDependencies, GeneralizedNewtypeDeriving,RankNTypes,ExistentialQuantification- -- ExistentialQualification hs-source-dirs: src
+ src/Barrie.hs view
@@ -0,0 +1,19 @@+module Barrie (+ module Barrie.DrawPrimitive,+ module Barrie.Gadgets,+ module Barrie.Render,+ module Barrie.Render.Gtk,+ module Barrie.Style,+ module Barrie.Trace,+ module Barrie.Widgets+ )++where++import Barrie.DrawPrimitive+import Barrie.Gadgets+import Barrie.Render+import Barrie.Render.Gtk+import Barrie.Style+import Barrie.Trace+import Barrie.Widgets
− src/Barrie.lhs
@@ -1,21 +0,0 @@-> module Barrie (-> module Barrie.AbstractGadgets,-> module Barrie.DrawPrimitive,-> module Barrie.Gadgets,-> module Barrie.Render,-> module Barrie.Render.Gtk,-> module Barrie.Style,-> module Barrie.Trace,-> module Barrie.Widgets-> )--> where--> import Barrie.AbstractGadgets-> import Barrie.DrawPrimitive-> import Barrie.Gadgets-> import Barrie.Render-> import Barrie.Render.Gtk-> import Barrie.Style-> import Barrie.Trace-> import Barrie.Widgets
− src/Barrie/AbstractGadgets.lhs
@@ -1,24 +0,0 @@-> module Barrie.AbstractGadgets (AbstractGadget,-> acommand, achoice, atext, alabel,-> ainner, alocal)-> where--> data AbstractGadget = AG AGadget [String]-> deriving (Read, Show)--> data AGadget = ACommand-> | AChoice-> | AText-> | ALabel-> | AInner-> | ALocal-> deriving (Read, Show)--> acommand, achoice, atext, alabel,-> ainner, alocal :: [String] -> AbstractGadget-> acommand = AG ACommand-> achoice = AG AChoice-> atext = AG AText-> alabel = AG ALabel-> ainner = AG AInner-> alocal = AG ALocal
+ src/Barrie/Config.hs view
@@ -0,0 +1,46 @@+{-# OPTIONS_GHC -XMultiParamTypeClasses -XFunctionalDependencies #-}++module Barrie.Config where++import Data.Typeable++data Config a = Cfg {+ cEnabled :: a -> Bool,+ cLabel :: Maybe (a -> String)+ }++instance (Typeable a) => Typeable (Config a) where+ typeOf cfg = mkTyConApp (mkTyCon "Barrie.Config.Config")+ [typeOf (cEnabled cfg),+ typeOf (cLabel cfg)]++defaultConfig :: Config a+defaultConfig = Cfg { cEnabled = const True, cLabel = Nothing }++transformConfig :: (a -> b) -> Config b -> Config a+transformConfig get cfg = cfg { cEnabled = cEnabled cfg . get,+ cLabel = label'+ }+ where label' = case cLabel cfg of+ Nothing -> Nothing+ Just f -> Just $ f . get+++enabledCfg :: (a -> Bool) -> Config a -> Config a+enabledCfg f config = config { cEnabled = f }++isEnabled :: Config a -> a -> Bool+isEnabled config state = cEnabled config state++labelConfig :: (a -> String) -> Config a -> Config a+labelConfig f config = config { cLabel = Just f }++configHasLabel :: Config a -> Bool+configHasLabel Cfg { cLabel = Nothing } = False+configHasLabel _ = True++configLabel :: Config a -> a -> String+configLabel Cfg { cLabel = Just f } st = f st+configLabel _ _ =+ error "tried to get a label from a config which has none"+
− src/Barrie/Config.lhs
@@ -1,39 +0,0 @@-> {-# OPTIONS_GHC -XMultiParamTypeClasses -XFunctionalDependencies #-}--> module Barrie.Config where--> data Config a = Cfg {-> cEnabled :: a -> Bool,-> cLabel :: Maybe (a -> String)-> }--> defaultConfig :: Config a-> defaultConfig = Cfg { cEnabled = const True, cLabel = Nothing }--> transformConfig :: (a -> b) -> Config b -> Config a-> transformConfig get cfg = cfg { cEnabled = cEnabled cfg . get,-> cLabel = label'-> }-> where label' = case cLabel cfg of-> Nothing -> Nothing-> Just f -> Just $ f . get---> enabledCfg :: (a -> Bool) -> Config a -> Config a-> enabledCfg f config = config { cEnabled = f }--> isEnabled :: Config a -> a -> Bool-> isEnabled config state = cEnabled config state--> labelConfig :: (a -> String) -> Config a -> Config a-> labelConfig f config = config { cLabel = Just f }--> configHasLabel :: Config a -> Bool-> configHasLabel Cfg { cLabel = Nothing } = False-> configHasLabel _ = True--> configLabel :: Config a -> a -> String-> configLabel Cfg { cLabel = Just f } st = f st-> configLabel _ _ =-> error "tried to get a label from a config which has none"-
+ src/Barrie/DrawPrimitive.hs view
@@ -0,0 +1,131 @@+module Barrie.DrawPrimitive+ (DrawFunction, Picture, Colour,+ background,+ setForeground,+ lineTo, moveTo, line, lineWidth, move, rectangle, polygon,+ multiLine, circle,+ image, showText,+ black, white, fromRGB,+ doDraw)+ where++import qualified Graphics.UI.Gtk as Gtk++type Picture = Gtk.Pixmap++data Colour = RGB Int Int Int deriving (Eq)++black, white :: Colour+black = fromRGB 0 0 0+white = fromRGB 65535 65535 65535++data DrawPrimitive = Background Colour+ | SetForeground Colour+ | SetLineThickness Int+ | Line Int Int+ | LineTo Int Int+ | LineWidth Int+ | Move Int Int+ | MoveTo Int Int+ | Polygon Bool [(Int, Int)]+ | Circle Bool Int+ | Rectangle Bool Int Int+ | ShowText String+ | Image String+ deriving (Eq)++type DrawFunction = [DrawPrimitive] -> [DrawPrimitive]++background :: Colour -> DrawFunction+lineTo, moveTo, line, move :: Int -> Int -> DrawFunction+lineWidth :: Int -> DrawFunction+rectangle :: Bool -> Int -> Int -> DrawFunction+polygon :: Bool -> [(Int, Int)] -> DrawFunction+circle :: Bool -> Int -> DrawFunction+showText :: String -> DrawFunction+image :: String -> DrawFunction+multiLine :: [(Int,Int)] -> DrawFunction+setForeground :: Colour -> DrawFunction++background colour xs = Background colour : xs+lineTo x y xs = LineTo x y : xs+moveTo x y xs = MoveTo x y : xs+line x y xs = Line x y : xs+lineWidth w xs = LineWidth w : xs+move x y xs = Move x y : xs+rectangle filled width height xs = Rectangle filled width height : xs+polygon filled pts xs = Polygon filled pts : xs+circle filled r xs = Circle filled r : xs+showText text xs = ShowText text : xs+image pic = (:) (Image pic)++multiLine [] = id+multiLine ((x, y):pts) = moveTo x y . foldl (.) id (map (uncurry lineTo) pts)++setForeground col xs = SetForeground col : xs++fromRGB :: Int -> Int -> Int -> Colour+fromRGB = RGB++toGtkColour :: Colour -> IO Gtk.Color+toGtkColour (RGB r g b) = return $ Gtk.Color (cvt r) (cvt g) (cvt b)+ where cvt = fromInteger . toInteger++doDraw :: Gtk.Pixmap+ -> Gtk.GC+ -> Gtk.PangoContext+ -> (Int,Int)+ -> [DrawPrimitive]+ -> IO ()+doDraw _ _ _ _ [] = return ()+doDraw win gc context (x,y) (p:ps) = do (newP,newGC) <- drawPrimitive p+ doDraw win newGC context newP ps+ where noChange = return ((x,y),gc)+ drawPrimitive (MoveTo x' y') = return ((x', y'), gc)+ drawPrimitive (Move dx dy) = return ((x + dx, y + dy), gc)+ drawPrimitive (LineTo x' y') = do+ Gtk.drawLine win gc (x,y) (x',y')+ return ((x',y'), gc)+ drawPrimitive (Line dx dy) = do+ let (x',y') = (x + dx, y + dy)+ Gtk.drawLine win gc (x,y) (x', y')+ return ((x',y'), gc)+ drawPrimitive (LineWidth w) = do+ Gtk.gcSetValues gc Gtk.newGCValues { Gtk.lineWidth = w }+ return ((x,y), gc)+ drawPrimitive (Rectangle filled width height) = do+ Gtk.drawRectangle win gc filled x y width height+ noChange+ drawPrimitive (Polygon _ []) = noChange+ drawPrimitive (Polygon filled pts) = do+ Gtk.drawPolygon win gc filled pts+ return (last pts, gc)+ drawPrimitive (Circle filled r) = do+ Gtk.drawArc win gc filled (x - r) (y - r)+ (r * 2) (r * 2) 0 (360 * 64)+ return ((x,y),gc)+ drawPrimitive (ShowText text) = do+ layout <- Gtk.layoutText context text+ layoutLine <- Gtk.layoutGetLine layout 0+ Gtk.drawLayoutLine win gc x y layoutLine+ return ((x,y),gc)+ drawPrimitive (SetForeground colour) = do+ col <- toGtkColour colour+ Gtk.gcSetValues gc $ Gtk.newGCValues {+ Gtk.foreground = col+ }+ return ((x,y),gc)+ drawPrimitive (Background colour) = do+ backgc <- Gtk.gcNew win+ gtkColour <- toGtkColour colour+ Gtk.gcSetValues backgc Gtk.newGCValues {+ Gtk.foreground = gtkColour+ }+ (width, height) <- Gtk.drawableGetSize win+ Gtk.drawRectangle win backgc True 0 0 width height+ Gtk.drawRectangle win backgc False 0 0 width height+ return ((x,y),gc)+ drawPrimitive (Image pic) = do+ pb <- Gtk.pixbufNewFromFile pic+ Gtk.drawPixbuf win gc pb 0 0 x y (-1) (-1) Gtk.RgbDitherNormal 0 0+ return ((x,y),gc)
− src/Barrie/DrawPrimitive.lhs
@@ -1,131 +0,0 @@-> module Barrie.DrawPrimitive-> (DrawFunction, Picture, Colour,-> background,-> setForeground,-> lineTo, moveTo, line, lineWidth, move, rectangle, polygon,-> multiLine, circle,-> image, showText,-> black, white, fromRGB,-> doDraw)-> where--> import qualified Graphics.UI.Gtk as Gtk--> type Picture = Gtk.Pixmap--> data Colour = RGB Int Int Int deriving (Eq)--> black, white :: Colour-> black = fromRGB 0 0 0-> white = fromRGB 65535 65535 65535--> data DrawPrimitive = Background Colour-> | SetForeground Colour-> | SetLineThickness Int-> | Line Int Int-> | LineTo Int Int-> | LineWidth Int-> | Move Int Int-> | MoveTo Int Int-> | Polygon Bool [(Int, Int)]-> | Circle Bool Int-> | Rectangle Bool Int Int-> | ShowText String-> | Image String-> deriving (Eq)--> type DrawFunction = [DrawPrimitive] -> [DrawPrimitive]--> background :: Colour -> DrawFunction-> lineTo, moveTo, line, move :: Int -> Int -> DrawFunction-> lineWidth :: Int -> DrawFunction-> rectangle :: Bool -> Int -> Int -> DrawFunction-> polygon :: Bool -> [(Int, Int)] -> DrawFunction-> circle :: Bool -> Int -> DrawFunction-> showText :: String -> DrawFunction-> image :: String -> DrawFunction-> multiLine :: [(Int,Int)] -> DrawFunction-> setForeground :: Colour -> DrawFunction--> background colour xs = Background colour : xs-> lineTo x y xs = LineTo x y : xs-> moveTo x y xs = MoveTo x y : xs-> line x y xs = Line x y : xs-> lineWidth w xs = LineWidth w : xs-> move x y xs = Move x y : xs-> rectangle filled width height xs = Rectangle filled width height : xs-> polygon filled pts xs = Polygon filled pts : xs-> circle filled r xs = Circle filled r : xs-> showText text xs = ShowText text : xs-> image pic = (:) (Image pic)--> multiLine [] = id-> multiLine ((x, y):pts) = moveTo x y . foldl (.) id (map (uncurry lineTo) pts)--> setForeground col xs = SetForeground col : xs--> fromRGB :: Int -> Int -> Int -> Colour-> fromRGB = RGB--> toGtkColour :: Colour -> IO Gtk.Color-> toGtkColour (RGB r g b) = return $ Gtk.Color (cvt r) (cvt g) (cvt b)-> where cvt = fromInteger . toInteger--> doDraw :: Gtk.Pixmap-> -> Gtk.GC-> -> Gtk.PangoContext-> -> (Int,Int)-> -> [DrawPrimitive]-> -> IO ()-> doDraw _ _ _ _ [] = return ()-> doDraw win gc context (x,y) (p:ps) = do (newP,newGC) <- drawPrimitive p-> doDraw win newGC context newP ps-> where noChange = return ((x,y),gc)-> drawPrimitive (MoveTo x' y') = return ((x', y'), gc)-> drawPrimitive (Move dx dy) = return ((x + dx, y + dy), gc)-> drawPrimitive (LineTo x' y') = do-> Gtk.drawLine win gc (x,y) (x',y')-> return ((x',y'), gc)-> drawPrimitive (Line dx dy) = do-> let (x',y') = (x + dx, y + dy)-> Gtk.drawLine win gc (x,y) (x', y')-> return ((x',y'), gc)-> drawPrimitive (LineWidth w) = do-> Gtk.gcSetValues gc Gtk.newGCValues { Gtk.lineWidth = w }-> return ((x,y), gc)-> drawPrimitive (Rectangle filled width height) = do-> Gtk.drawRectangle win gc filled x y width height-> noChange-> drawPrimitive (Polygon _ []) = noChange-> drawPrimitive (Polygon filled pts) = do-> Gtk.drawPolygon win gc filled pts-> return (last pts, gc)-> drawPrimitive (Circle filled r) = do-> Gtk.drawArc win gc filled (x - r) (y - r)-> (r * 2) (r * 2) 0 (360 * 64)-> return ((x,y),gc)-> drawPrimitive (ShowText text) = do-> layout <- Gtk.layoutText context text-> layoutLine <- Gtk.layoutGetLine layout 0-> Gtk.drawLayoutLine win gc x y layoutLine-> return ((x,y),gc)-> drawPrimitive (SetForeground colour) = do-> col <- toGtkColour colour-> Gtk.gcSetValues gc $ Gtk.newGCValues {-> Gtk.foreground = col-> }-> return ((x,y),gc)-> drawPrimitive (Background colour) = do-> backgc <- Gtk.gcNew win-> gtkColour <- toGtkColour colour-> Gtk.gcSetValues backgc Gtk.newGCValues {-> Gtk.foreground = gtkColour-> }-> (width, height) <- Gtk.drawableGetSize win-> Gtk.drawRectangle win backgc True 0 0 width height-> Gtk.drawRectangle win backgc False 0 0 width height-> return ((x,y),gc)-> drawPrimitive (Image pic) = do-> pb <- Gtk.pixbufNewFromFile pic-> Gtk.drawPixbuf win gc pb 0 0 x y (-1) (-1) Gtk.RgbDitherNormal 0 0-> return ((x,y),gc)
+ src/Barrie/Gadgets.hs view
@@ -0,0 +1,321 @@+module Barrie.Gadgets (Gadget, Behaviour,+ displayG, editorG, commandG, sectionG,+ chooserG, enumChooserG, chooseEqG, childG,+ gadgetDisplay, gadgetUpdate, gadgetCommand,+ gadgetChooser, gadgetChild,+ findGadget, gadgetName, flatName,+ gadgetConfig, gadgetEnabled, enabled, dynamicLabel,+ gadgets, abstract,+ createLayout)+where++import Control.Monad++import Data.Dynamic+import Data.List+import Data.Maybe+import Data.Typeable++import Barrie.Config+import Barrie.Style+import Barrie.Widgets++data Gadget a = G { gadgetName :: [String],+ gadgetConfig :: Config a,+ gadgetElement :: Maybe (Element a),+ gadgetInner :: Maybe (Interface a),+ gadgetLayout :: AutoLayoutFunction a,+ gadgetChildren :: [Gadget a]+ }++data AutoLayout = AL { alDirection :: LayoutDirection,+ alEditable :: Bool,+ alNames :: [String]+ }++instance (Typeable a) => Typeable (Gadget a) where+ typeOf g = mkTyConApp (mkTyCon "Barrie.Gadgets.Gadget")+ [typeOf (elementType (fromJust (gadgetElement g)))]++data Element a = E { elementType :: TypeRep,+ elementGet :: a -> Dynamic,+ elementSet :: Dynamic -> a -> a,+ elementChoices :: Maybe (a -> [(Dynamic, Dynamic)]),+ elementRender :: Dynamic -> Dynamic+ }++data Interface a = IF { ifGadget :: Gadget a }++instance (Typeable a) => Typeable (Element a) where+ typeOf e = mkTyConApp (mkTyCon "Barrie.Gadgets.Element") [elementType e]++data Behaviour a = B [Gadget a]++flatName :: Gadget a -> String+flatName = intercalate "." . gadgetName++mkGadget :: String -> Element a -> Gadget a+mkGadget name e = G [name] defaultConfig (Just e) Nothing boxLayout []++mkElement :: (Typeable b) =>+ (a -> b)+ -> (b -> a -> a)+ -> Element a+mkElement get set = E tyrep (toDyn . get) (\ x -> set (get' x)) Nothing id+ where get' x = case fromDynamic x of+ Nothing -> error $ "'impossible' type error detected: " +++ show x ++ " " ++ show (typeOf (get' x))+ Just v -> v+ tyrep = typeOf (get undefined)++updateElement :: (Element a -> Element a) -> Gadget a -> Gadget a+updateElement update g = case gadgetElement g of+ Nothing -> g+ Just e -> g { gadgetElement = Just $ update e }++renderWith :: (Dynamic -> Dynamic) -> Gadget a -> Gadget a+renderWith r = updateElement (\ e -> e { elementRender = r })++setChoices :: (a -> [(Dynamic, Dynamic)]) -> Gadget a -> Gadget a+setChoices getChoices =+ updateElement (\ e -> e { elementChoices = Just getChoices })++autoLayout :: AutoLayoutFunction a -> Gadget a -> Gadget a+autoLayout f g = g { gadgetLayout = f }++displayG :: (Typeable b) => String -> (a -> b) -> Gadget a+displayG name get = autoLayout (typeableLayout (typeOf $ get undefined)) $+ mkGadget name (mkElement get (\ _ st -> st))++editorG :: (Typeable b) => String -> (a -> b) -> (b -> a -> a) -> Gadget a+editorG name get set = autoLayout (typeableLayout (typeOf $ get undefined)) $+ mkGadget name (mkElement get set)++commandG :: String -> (a -> a) -> Gadget a+commandG name update = autoLayout buttonLayout $+ mkGadget name (mkElement (const ()) (\ () -> update))++sectionG :: String -> [Gadget a] -> Gadget a+sectionG name gs = G [name] defaultConfig Nothing Nothing boxLayout gs++childG :: String -- ^ gadget name+ -> (a -> b) -- ^ get child state+ -> (b -> a -> a) -- ^ update parent state with child state+ -> Gadget b -- ^ child gadget+ -> Gadget a+childG name get set child =+ G { gadgetName = [name],+ gadgetConfig = defaultConfig,+ gadgetElement = Nothing,+ gadgetInner = Just $ IF $ transform get set child,+ gadgetLayout = boxLayout,+ gadgetChildren = []+ }++transform :: (a -> b) -> (b -> a -> a) -> Gadget b -> Gadget a+transform get set g =+ G { gadgetName = gadgetName g,+ gadgetConfig = transformConfig get (gadgetConfig g),+ gadgetElement = liftM (transformE get set) (gadgetElement g),+ gadgetInner = liftM (transformI get set) (gadgetInner g),+ gadgetLayout = boxLayout,+ gadgetChildren = map (transform get set) (gadgetChildren g)+ }++-- transformL :: (a -> b) -> (b -> a -> a) -> AutoLayoutFunction b+-- -> AutoLayoutFunction a+-- transformL get set f = \ layout gs = map++transformI :: (a -> b) -> (b -> a -> a) -> Interface b -> Interface a+transformI get set (IF g) = IF (transform get set g)++transformE :: (a -> b) -> (b -> a -> a) -> Element b -> Element a+transformE get set e = e { elementGet = elementGet e . get,+ elementSet = xfset (elementSet e),+ elementChoices = xfchoices (elementChoices e)+ }+ where xfset setb = (\ v st -> set (setb v (get st)) st)+ xfchoices Nothing = Nothing+ xfchoices (Just getcs) = Just $ getcs . get++-- |A gadget that offers a choice between alternatives. This function+-- creates a gadget that expects an Int representing the index of the+-- chosen item from the state. If it's more convenient to supply the+-- actual chosen value, use chooseEqG instead.++chooserG :: (Typeable b) =>+ String+ -> (a -> Int)+ -> (b -> a -> a)+ -> (a -> [b])+ -> Gadget a+chooserG name get set getChoices =+ autoLayout chooserLayout $ setChoices choices $ editorG name get' set'+ where get' st = (choices st, get st)+ set' (vs, v) = if v >= length vs+ then error ("chooser index " ++ show v +++ " was not in value list " +++ show vs)+ else set (getValues vs !! v)+ choices st = zip (map toDyn (getChoices st))+ (map toDyn [0 :: Int ..])+ getValues ds = map (fromJust . fromDynamic . fst) ds++enumChooserG :: (Enum b, Bounded b, Read b, Show b) =>+ String+ -> (a -> b)+ -> (b -> a -> a)+ -> Gadget a+enumChooserG name get set =+ autoLayout enumLayout $+ setChoices elems $ renderWith toStr $ editorG name get' set'+ where get' st = (elems st, fromEnum $ get st)+ set' (_, v) = set $ toEnum v+ elems st = let vs = [minBound ..] `asTypeOf` [get st]+ is = map fromEnum vs+ ts = map show vs+ in zip (map toDyn ts) (map toDyn is)+ toStr d = let (xs, v) = fromDyn d ([], -100)+ e = toEnum ((v :: Int))+ _ = xs :: [(Dynamic,Dynamic)]+ in toDyn (show (e `asTypeOf` get undefined))++chooseEqG :: (Typeable b, Eq b) =>+ String+ -> (a -> b)+ -> (b -> a -> a)+ -> (a -> [b])+ -> Gadget a+chooseEqG name get set choices = chooserG name get' set choices+ where get' st = case elemIndex (get st) (choices st) of+ Nothing -> -1+ Just n -> n++findGadget :: [String] -> Behaviour a -> Maybe (Gadget a)+findGadget name (B gs) = case filter (match name) gs of+ [] -> Nothing+ g:_ -> Just g+ where match [] _ = False+ match nm G { gadgetName = gnm } =+ isPrefixOf (reverse nm) (reverse gnm)++-- |Gadgets can change their properties based on the value of their state.+-- Currently, the following configurations are implemented:++-- |dynamicLabel: for write-only gadgets like Command, this can add+-- some state read functionality. It is interpreted by the+-- renderer.++-- |enabled: a function on the state returning Bool. If False, the+-- gadget is disabled in some widget- or renderer-specific way+-- (e.g. dimmed, hidden).++addConfig :: (Config a -> Config a) -> Gadget a -> Gadget a+addConfig f g = g { gadgetConfig = f (gadgetConfig g) }++enabled :: (a -> Bool) -> Gadget a -> Gadget a+enabled f = addConfig (enabledCfg f)++gadgetEnabled :: Gadget a -> a -> Bool+gadgetEnabled = isEnabled . gadgetConfig++dynamicLabel :: (a -> String) -> Gadget a -> Gadget a+dynamicLabel f = addConfig (labelConfig f)++walk :: [String] -> ([String] -> Gadget a -> [b]) -> Gadget a -> [b]+walk prefix f gadget@(G nm _ _ inner _ gs) =+ f (prefix ++ nm) gadget ++ innerg ++ concatMap (walk (prefix ++ nm) f) gs+ where innerg = case inner of+ Nothing -> []+ Just (IF g) -> walk (prefix ++ nm) f g++gadgets :: Gadget a -> Behaviour a+gadgets = B . walk [] getGadget+ where getGadget name g = [g { gadgetName = name }]++abstract :: Gadget a -> [String]+abstract = map flatName . walk [] (\ name g -> [g { gadgetName = name }])++gadgetCommand :: Gadget a -> a -> a+gadgetCommand G { gadgetElement = Just e } = elementSet e (toDyn ())+gadgetCommand _ = id++gadgetDisplay :: Gadget a -> a -> Dynamic+gadgetDisplay G { gadgetElement = Just e } = elementGet e+gadgetDisplay g = error $ "Can't connect a display to empty gadget " +++ flatName g++gadgetUpdate :: Gadget a -> Dynamic -> a -> a+gadgetUpdate G { gadgetElement = Just e } = elementSet e+gadgetUpdate g = error $ "Can't connect an update to empty gadget " +++ flatName g++gadgetChooser :: Gadget a -> a -> [(Dynamic, Dynamic)]+gadgetChooser g = case getChoices of+ Nothing -> error $ "Can't connect a chooser to gadget " +++ flatName g+ Just get -> get+ where getChoices = gadgetElement g >>= elementChoices++gadgetChild :: Gadget a -> Gadget a+gadgetChild g = case gadgetInner g of+ Just (IF child) -> child+ Nothing -> error $ "Gadget " ++ flatName g +++ " has no inner child"++startLayout :: AutoLayout+startLayout = AL { alDirection = Vertical,+ alEditable = False,+ alNames = []+ }++editableLayout :: AutoLayout -> AutoLayout+editableLayout al = al { alEditable = True }++type AutoLayoutFunction a = AutoLayout -> [Gadget a] -> Widget++boxLayout :: AutoLayoutFunction a+boxLayout al = boxLayout' . map (createLayout' al')+ where (boxLayout', al') =+ case alDirection al of+ Vertical -> (vbox, al { alDirection = Horizontal })+ Horizontal -> (hbox, al { alDirection = Vertical })+++textLayout :: AutoLayoutFunction a+textLayout = \ al _ -> case alEditable al of+ True -> textBox+ False -> textLabel ""++intLayout :: Int -> Int -> AutoLayoutFunction a+intLayout lo hi = \ al _ -> case alEditable al of+ True -> slider (alDirection al) lo hi 1+ False -> textLabel ""++boolLayout :: AutoLayoutFunction a+boolLayout = \ _ _ -> checkButton++buttonLayout :: AutoLayoutFunction a+buttonLayout = \ al _ -> caption (last (alNames al)) $ button []++enumLayout :: AutoLayoutFunction a+enumLayout = chooserLayout++chooserLayout :: AutoLayoutFunction a+chooserLayout = \ _ _ -> listView++typeableLayout :: TypeRep -> AutoLayoutFunction a+typeableLayout t = case filter ((t==) . fst) typeableLayouts of+ [] -> textLayout+ layouts -> snd (head layouts)+ where typeableLayouts = [(typeOf (1::Int), intLayout 0 999),+ (typeOf False, boolLayout)]++createLayout :: Gadget a -> Widget+createLayout = createLayout' startLayout++createLayout' :: AutoLayout -> Gadget a -> Widget+createLayout' layout g =+ ui' nm $ gadgetLayout g layout' (gadgetChildren g)+ where nm = alNames layout ++ gadgetName g+ layout' = layout { alNames = nm }
− src/Barrie/Gadgets.lhs
@@ -1,383 +0,0 @@-> module Barrie.Gadgets (Gadget, Behaviour,-> findGadget, gadgets, abstract, gadgetName,-> commandG,-> displayG, displayG', textDisplayG,-> editorG, editorG', textEditorG,-> chooserG, chooserG', textChooserG, enumChooserG,-> localG, innerG,-> enabled, gadgetEnabled, dynamicLabel,-> gadgetConfig,-> connectChooserDisplay,-> connectDisplayGadget,-> connectEditorGadget,-> connectCommandGadget,-> connectInnerGadget-> ) where--> import Data.List-> import Control.Monad--> import Barrie.AbstractGadgets-> import Barrie.Config-> import Barrie.Trace--> data Behaviour a = B [Gadget a]--> -- |A gadget is a piece of user interface functionality (as opposed-> -- to a widget, which is a piece of user interface rendering).--> data Gadget a = G { gFullName :: [String],-> gConfig :: Config a,-> gElement :: Element a-> }--> -- |The semantics of a gadget are defined by its Element.-> -- Internally, all element manipulation is perform via conversions-> -- to and from String, which is a shame, but there it is.-> -- It is not beyond the bounds of possibility that existential types-> -- are a solution to this problem, but for now, this is where we-> -- are. The exported constructor functions hide this wart, to some-> -- extent (e.g. you can use showG for an instance of Show, or-> -- showG' to supply your own show function).--> -- |An element comes in five flavours:--> -- |Display: presents a value to the user. For example: labels,-> -- |read-only text boxes, check boxes, radio buttons -- anything-> -- |with a simple display.--> -- |Editor: a display that can edit, such as a text box, a check-> -- |box, or a slider.--> -- |Command: a gadget that updates the state, sort of like a-> -- |write-only Editor for type (), where the only thing you know is-> -- |that it's been activated (so the () argument can be dropped).-> -- |Can be rendered with buttons, menu items, that sort of thing.--> -- |Chooser: like displays and editors, but the values come from a-> -- |restricted set of possiblities. The value is a Maybe String,-> -- |because it's possible (and often reasonable) to not choose-> -- |anything.--> -- |Inner: a single gadget placed inside another one. Used to-> -- |construct nested and pop-up gadgets.--> -- |Local: gadgets nested inside another one. Sort of a superclass-> -- |of Inner, but it's not possible to make a pop-up out of them.-> -- |Used to structure gadgets (functionally, not in terms of-> -- |layout).--> -- |Constructor functions are used to create gadgets, which is how-> -- |we pretend we're not dealing with Strings only. In general, for-> -- |a gadget flavour F with signature SF a b, we have:--> -- | fG :: (Show b, Read b) => String -> SF a b -> Gadget a-> -- | textFG :: String -> SF a String -> Gadget a-> -- | fG' :: (b -> String) -> (String -> b) -> SF a b -> Gadget a--> -- |fG uses the built-in Read and Show to perform the conversions-> -- |fStrG uses id for read and show-> -- |fG' allows them to be specified.--> -- |Display gadgets don't need the Read instance (or the-> -- |corresponding function), and Command gadgets don't need either-> -- |instance (so we have commandG but not commandG')--> data Element a = Display (a -> String)-> | Editor (a -> String) (String -> a -> a)-> | Command (a -> a)-> | Chooser (a -> Maybe String)-> (String -> a -> a) (a -> [String])-> | Inner (Gadget a)-> | Local [Gadget a]---> -- data InnerElement a = forall b. InnerE (a -> b) (b -> a -> a) (Gadget b)--> -- |Returns the name of the gadget. Internally (and-> -- slightly externally), the name of a gadget is a list of Strings-> -- containing the full path from the top to the gadget. This is-> -- converted to a '.'-separated String.--> gadgetName :: Gadget a -> String-> gadgetName = intercalate ['.'] . gFullName--> -- |Create a command gadget--> commandG :: String -> (a -> a) -> Gadget a-> commandG name f = G [name] defaultConfig (Command f)--> -- |Create a display gadget--> displayG :: (Show b) => String -> (a -> b) -> Gadget a-> displayG = displayG' show--> textDisplayG :: String -> (a -> String) -> Gadget a-> textDisplayG = displayG' id--> displayG' :: (b -> String) -> String -> (a -> b) -> Gadget a-> displayG' toString name get = G [name] defaultConfig-> (Display (toString . get))--> -- |editorG allows a gadget to show and edit part of the state.--> editorG' :: (b -> String) -- ^ toString: convert to string-> -> (String -> b) -- ^ fromString: convert from String-> -> String -- ^ gadget name-> -> (a -> b) -- ^ get value from state-> -> (b -> a -> a) -- ^ update state-> -> Gadget a--> editorG' toString fromString name get set =-> G [name] defaultConfig (Editor (toString . get) (set . fromString))--> editorG :: (Show b, Read b) =>-> String -- ^ gadget name-> -> (a -> b) -- ^ get value from state-> -> (b -> a -> a) -- ^ update state-> -> Gadget a--> editorG = editorG' show read--> textEditorG :: String -- ^ gadget name-> -> (a -> String) -- ^ get value from state-> -> (String -> a -> a) -- ^ update state-> -> Gadget a--> textEditorG = editorG' id id-->-> -- |localG does not perform any state transformation directly, but-> -- can be used to structure a gadget tree.--> localG :: String -> [Gadget a] -> Gadget a-> localG name gs = G [name] defaultConfig (Local gs)---> -- |chooserG--> chooserG' :: (b -> String) -- ^ toString: convert to string-> -> (String -> b) -- ^ fromString: convert from String-> -> String -- ^ the name of the chooser gadget-> -> (a -> Maybe b) -- ^ current selected value-> -> (b -> a -> a) -- ^ update the state with a new value-> -> (a -> [b]) -- ^ list of available choices-> -> Gadget a-> chooserG' toString fromString name get set elems =-> G [name] defaultConfig-> (Chooser (liftM toString . get) (set . fromString)-> (map toString . elems))--> chooserG :: (Read b, Show b) =>-> String -- ^ the name of the chooser gadget-> -> (a -> Maybe b) -- ^ current selected value-> -> (b -> a -> a) -- ^ update the state with a new value-> -> (a -> [b]) -- ^ list of available choices-> -> Gadget a--> chooserG = chooserG' show read---> textChooserG :: String -- ^ the name of the chooser gadget-> -> (a -> Maybe String) -- ^ current selected value-> -> (String -> a -> a) -- ^ update the state with a new value-> -> (a -> [String]) -- ^ list of available choices-> -> Gadget a--> textChooserG = chooserG' id id--> -- A convenient way to create a choice from a bounded enum.--> enumChooserG :: (Enum b, Read b, Show b) =>-> String-> -> (a -> Maybe b)-> -> (b -> a -> a)-> -> Gadget a-> enumChooserG name get set = chooserG' show read name get set-> (const [toEnum 0 ..])--> innerG :: String -> (a -> b) -> (b -> a -> a) -> Gadget b -> Gadget a-> innerG name get set gadget = G [name] defaultConfig (Inner launch)-> where launch = transform get set gadget--> transform :: (a -> b) -> (b -> a -> a) -> Gadget b -> Gadget a-> transform get set (G name config elmt) =-> G name (transformConfig get config) (transformE get set elmt)--> transformE :: (a -> b) -> (b -> a -> a) -> Element b -> Element a-> transformE get set = xform-> where xform (Display getb) = Display (getb . get)-> xform (Editor getb setb) = Editor (getb . get) (xfset setb)-> xform (Command update) =-> Command (\ st -> set (update (get st)) st)-> xform (Chooser getb setb getvs) =-> Chooser (getb . get) (xfset setb)-> (getvs . get)-> xform (Inner g) = Inner (transform get set g)-> xform (Local gs) = Local (map (transform get set) gs)-> xfset setb = (\ v st -> set (setb v (get st)) st)---> -- |Gadgets can change their properties based on the value of their state.-> -- Currently, the following configurations are implemented:--> -- dynamicLabel: for write-only gadgets like Command, this can add-> -- some state read functionality. It is interpreted by the-> -- renderer.--> -- enabled: a function on the state returning Bool. If False, the-> -- gadget is disabled in some widget- or renderer-specific way-> -- (e.g. dimmed, hidden).--> addConfig :: (Config a -> Config a) -> Gadget a -> Gadget a-> addConfig f g = g { gConfig = f (gConfig g) }--> enabled :: (a -> Bool) -> Gadget a -> Gadget a-> enabled f = addConfig (enabledCfg f)--> dynamicLabel :: (a -> String) -> Gadget a -> Gadget a-> dynamicLabel f = addConfig (labelConfig f)--> gadgetConfig :: Gadget a -> (Config a -> b) -> b-> gadgetConfig gadget config = config $ gConfig gadget--> gadgetEnabled :: Gadget a -> a -> Bool-> gadgetEnabled g = gadgetConfig g isEnabled--> findGadget :: [String] -> Behaviour a -> Maybe (Gadget a)-> findGadget name (B gs) =-> case filter (match name) gs of-> [] -> Nothing-> g:_ -> Just g-> where match name1 (G name2 _ _) =-> not (null name1) &&-> isPrefixOf (reverse name1) (reverse name2)--> type Fetch a = a -> String-> type Update a = String -> a -> a--> gadgetEditor :: Gadget a -> (Fetch a, Update a)-> gadgetEditor (G _ _ (Editor get set)) = (get, set)-> gadgetEditor (G _ _ (Chooser get set _ )) = (maybe "" id . get, set)-> gadgetEditor g = error $ "Can't connect an editor to gadget '" ++-> gadgetName g ++ "'"--> gadgetChooser :: Gadget a -> (a -> [String], Fetch a)-> gadgetChooser (G _ _ (Chooser get _ elems)) = (elems, maybe "" id . get)-> gadgetChooser g = error $ "Can't connect a chooser to gadget '" ++-> gadgetName g ++ "'"--> gadgetDisplay :: Gadget a -> Fetch a-> gadgetDisplay (G _ _ (Display get)) = get-> gadgetDisplay (G _ _ (Editor get _)) = get-> gadgetDisplay (G _ _ (Chooser get _ _)) = (maybe "" id . get)-> gadgetDisplay g = error $ "Can't connect a display to gadget '" ++-> gadgetName g ++ "'\n" ++ show (abstract g)--> gadgetCommand :: Gadget a -> (a -> a)-> gadgetCommand (G _ _ (Command update)) = update-> gadgetCommand g = error $ "Can't connect a command to gadget '" ++-> gadgetName g ++ "'"--> gadgetInner :: Gadget a -> Gadget a-> gadgetInner (G _ _ (Inner g)) = g-> gadgetInner g = error $ "Can't connect a inner to gadget '" ++-> gadgetName g ++ "'\n" ++ show (abstract g)---> connectEditorGadget :: (IO () -> IO ()) -- ^ connection creator-> -> IO a -- ^ gui state retriever-> -> IO String -- ^ gadget value retriever-> -> Maybe (Gadget a) -- ^ optional gadget-> -> (a -> IO ()) -- ^ state change applier-> -> IO ()--> connectEditorGadget onAction getState gadgetValue gadget apply = do-> let connectEditor get set = onAction (do st <- getState-> curr <- gadgetValue-> when (get st /= curr) $ do-> apply $ set curr st)-> case liftM gadgetEditor gadget of-> Nothing -> return ()-> Just (get,set) -> connectEditor get set---> connectChooserDisplay :: ([String] -> IO ())-> -> (Int -> IO ())-> -> Maybe (Gadget a)-> -> a -> IO ()-> connectChooserDisplay setChoices setValue gadget = do-> let update getChoices get st = (do-> let choices = getChoices st-> value = get st-> setChoices choices-> case elemIndex value choices of-> Nothing -> setValue (-1)-> Just index -> setValue index)-> case liftM gadgetChooser gadget of-> Nothing -> \ _ -> return ()-> Just (getChoices, get) -> update getChoices get--> connectDisplayGadget :: (String -> IO ()) -- GUI element updater-> -> Maybe (Gadget a)-> -> a -> IO ()--> connectDisplayGadget update gadget = do-> let connectDisplay get = update . get-> case liftM gadgetDisplay gadget of-> Nothing -> \ _ -> return ()-> Just get -> connectDisplay get--> connectCommandGadget :: (IO () -> IO ()) -- connection creator-> -> IO a -- state retriever-> -> Maybe (Gadget a)-> -> (a -> IO ()) -- state updater-> -> IO ()-> connectCommandGadget onCommand getState gadget setState = do-> let connectCommand update = onCommand (do st <- getState-> setState (update st))-> case liftM gadgetCommand gadget of-> Nothing -> return ()-> Just update -> connectCommand update--> connectInnerGadget :: (IO () -> IO ()) -- launch connector-> -> (Gadget a -> a -> IO a) -- inner-> -> IO a -- state retriever-> -> Maybe (Gadget a)-> -> (a -> IO ()) -- state updater-> -> IO ()-> connectInnerGadget onLaunch inner getState gadget setState = do-> let connectInner g =-> onLaunch (do st <- getState-> traceMessage $ "connecting: " ++-> gadgetName g-> st' <- inner g st-> traceMessage $ "finished: " ++ gadgetName g-> setState st')-> case liftM gadgetInner gadget of-> Nothing -> return ()-> Just g -> connectInner g--> walk :: [String] -> ([String] -> Gadget a -> [b]) -> Gadget a -> [b]-> walk prefix f gadget@(G nm _ e) =-> case e of-> (Local ws) -> f (prefix ++ nm) gadget ++-> concatMap (walk (prefix ++ nm) f) ws-> (Inner w) -> f (prefix ++ nm) gadget ++ walk (prefix ++ nm) f w-> _ -> f (prefix ++ nm) gadget---> abstract :: Gadget a -> [AbstractGadget]-> abstract = walk [] abstractElem-> where abstractElem name e = case gElement e of-> Display _ -> [alabel name]-> Editor _ _ -> [atext name]-> Command _ -> [acommand name]-> Chooser _ _ _ -> [achoice name]-> Inner _ -> [ainner name]-> Local _ -> [alocal name]--> gadgets :: Gadget a -> Behaviour a-> gadgets = B . walk [] getGadget-> where getGadget name (G _ cfg elmt) = [G name cfg elmt]-
+ src/Barrie/Gadgets/Connections.hs view
@@ -0,0 +1,68 @@+module Barrie.Gadgets.Connections (connectChooser,+ connectCommand,+ connectDisplay,+ connectEditor,+ connectChild) where++import Control.Monad++import Data.Dynamic++import Barrie.Gadgets+import Barrie.Trace++connectCommand :: (IO () -> IO ()) -- ^ connection creator+ -> IO a -- ^ state retriever+ -> Gadget a -- ^ gadget representing the command+ -> (a -> IO ()) -- ^ state updater+ -> IO ()+connectCommand onCommand getState gadget setState = do+ onCommand (getState >>= setState . gadgetCommand gadget)++connectDisplay :: (Dynamic -> IO ()) -- ^ GUI element updater+ -> Gadget a -- ^ gadget representing the display+ -> a -- ^ new state+ -> IO ()+connectDisplay update gadget = update . gadgetDisplay gadget++connectEditor :: (IO () -> IO ()) -- ^ connection creator+ -> IO a -- ^ gui state retriever+ -> (Dynamic -> IO Bool) -- ^ true if update should happen+ -> IO Dynamic -- ^ current value of GUI element+ -> Gadget a -- ^ gadget representing the editor+ -> (a -> IO ()) -- ^ state change applier+ -> IO ()++connectEditor onAction getState doUpdate getValue gadget apply = do+ let connect get set = onAction (do st <- getState+ go <- doUpdate (get st)+ when go (do curr <- getValue+ apply $ set curr st))+ connect (gadgetDisplay gadget) (gadgetUpdate gadget)++-- |Connect a chooser to the GUI. Elements of the chooser are stored+-- |in a tuple; as (renderable value, state value).++connectChooser :: ([(Dynamic, Dynamic)] -> IO ()) -- ^ Set list of options+ -> (Int -> IO ()) -- ^ choose value+ -> IO Dynamic -- ^ get value+ -> Gadget a -- ^ gadget representing the chooser+ -> a -> IO ()+connectChooser setChoices setValue getValue gadget = do+ let update getChoices get set st =+ (setChoices (getChoices st) >>+ setValue (snd $ fromDyn (get st) ([(get st, get st)],-12)))+ update (gadgetChooser gadget) (gadgetDisplay gadget) (gadgetUpdate gadget)++connectChild :: (IO () -> IO ()) -- ^ show child+ -> (Gadget a -> a -> IO a) -- ^ child runner+ -> IO a -- ^ parent state retriever+ -> Gadget a -- ^ parent+ -> (a -> IO ()) -- ^ parent state updater+ -> IO ()+connectChild onLaunch inner getState gadget setState = do+ onLaunch (do st <- getState+ traceMessage $ "connecting: " ++ flatName gadget+ st' <- inner (gadgetChild gadget) st+ traceMessage $ "finished: " ++ flatName gadget+ setState st')
+ src/Barrie/Render.hs view
@@ -0,0 +1,122 @@+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 :: Behaviour w -> Widget -> RenderF r w+ -> IO (RunnableGUI r w)+renderWidget behaviour widget render = go widget+ where -- go :: StyleSheet -> Layout -> RunnableGUI r w+ go w = do let elmt = widgetElementName w+ style = widgetStyle w+ gadget = findGadget (uiElementName w) behaviour+ 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))+ ]+
− src/Barrie/Render.lhs
@@ -1,86 +0,0 @@-> module Barrie.Render where--> 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 :: Behaviour w -> Widget -> RenderF r w-> -> IO (RunnableGUI r w)-> renderWidget behaviour widget render = go widget-> where -- go :: StyleSheet -> Layout -> RunnableGUI r w-> go w = do let elmt = widgetElementName w-> style = widgetStyle w-> gadget = findGadget (uiElementName w) behaviour-> traceMessage ("rendering element: " ++ elmt);-> traceMessage ("ui name = " ++-> intercalate "." (uiElementName w))-> traceMessage ("gadget = " ++-> maybe "Nothing" (gadgetName) gadget)-> ws <- mapM go $ widgetChildren w-> render elmt ws style gadget (widgetPopupChild w)-
+ src/Barrie/Render/Gtk.hs view
@@ -0,0 +1,449 @@+module Barrie.Render.Gtk (gtkMain, initGtk, renderGtk, runGtk) where++import Data.Dynamic++import qualified Graphics.UI.Gtk as Gtk+import System.Glib.Attributes++import Control.Monad++-- IORefs are Barrie's dirty little secret. There are three: one is+-- for saving a global state that the GUI works with, the other two+-- save the contents of lists and choosers, so that we don't update+-- them more than necessary.++import Data.IORef++-- We import approximately everything++import Barrie.Config+import Barrie.Render+import Barrie.Style+import Barrie.Gadgets+import Barrie.Gadgets.Connections+import Barrie.Trace+import Barrie.Widgets+++-- |The rendering model is defined by the RenderF type in the+-- Barrie.Render module. Implementations of the renderer provide+-- a type which describes the GUI. In this case, it's just a+-- Gtk.Widget.++type GtkGUI = Gtk.Widget++-- All rendering functions in this module use the following type+-- GtkRenderFn.++-- It is a function from:+-- - a list of child guis (i.e. widgets inside the one being rendered)+-- - a style+-- - an optional gadget, which will be connected to the+-- rendered widget if present.+-- It returns a runnable GUI.++type GtkRenderFn a = [RunnableGUI GtkGUI a]+ -> Style+ -> Maybe (Gadget a)+ -> IO (RunnableGUI GtkGUI a)+++-- |Convenience function for launching a gadget/widget pair, with a+-- given state. It should only be called once, at the top level,+-- since it initialises Gtk (although I think multiple calls are+-- harmless).++gtkMain :: Gadget a -> Widget -> a -> IO a+gtkMain gadget widget state = do+ initGtk+ gui <- renderWidget (gadgets gadget) widget renderGtk+ runGtk gui state False+++initGtk :: IO ()+initGtk = do Gtk.initGUI+ return ()++renderGtk :: RenderF GtkGUI a+renderGtk nm gs style gadget popupChild = do+ let rendererList = case popupChild of+ Nothing -> renderers+ Just w -> map (\ (x,y) -> (x, y w)) popupRenderers+ case lookup nm rendererList of+ Nothing -> error $ "rendering element '" ++ nm ++ "' not found"+ Just r -> r gs style gadget++runGtk :: RunnableGUI GtkGUI a -> a -> Bool -> IO a+runGtk (top, start, connect) state modal = do+ stateref <- newIORef state+ let update st = do writeIORef stateref st+ start st+ getState = readIORef stateref+ connect getState update+ startGtk top start state modal+ getState++startGtk :: Gtk.Widget -> (a -> IO ()) -> a -> Bool -> IO ()+startGtk top start state modal = do+ Gtk.initGUI+ window <- Gtk.windowNew+ Gtk.windowSetModal window modal+ Gtk.onDestroy window Gtk.mainQuit+ set window [ Gtk.containerChild := top ]+ Gtk.widgetShowAll window+ start state+ Gtk.mainGUI++-----------------------------------+-- Renderers and popup renderers --+-----------------------------------++-- Two kinds of renderers are used. The normal one puts widgets+-- inside other widgets in the normal way. The popup renderer+-- instead puts widgets into a modal popup. The choice of which to+-- used is made based on the popupChild argument to renderGtk, which+-- is taken from the widget corresponding to the gadget.++-- I find this a bit dodgy, since one could come up with all sorts of+-- information that might be useful for a widget to pass to a gadget,+-- but we certainly don't want to add an argument for each. If I+-- come up with a bunch, I'll change the type from Maybe Widget to a+-- record of properties.++renderers :: [(String, GtkRenderFn a)]+renderers = [("button", renderButton),+ ("label", renderLabel),+ ("textbox", renderTextBox),+ ("checkbutton", renderCheckButton),+ ("droplist", renderChooser),+ ("list", renderSingleColumnList),+ ("hslider", renderScale Gtk.hScaleNewWithRange),+ ("vslider", renderScale Gtk.vScaleNewWithRange),+ ("vbox", renderBox Gtk.vBoxNew),+ ("hbox", renderBox Gtk.hBoxNew)]++-- Currently, we only do popup buttons, but it should be easily+-- generalisable.++popupRenderers :: [(String, Widget -> GtkRenderFn a)]+popupRenderers = [("button", renderPopupButton)]++-- Tables will come back when I work out a nice way to make them+-- dynamic. Yes, boxes should be dynamic too, but there's something+-- about tables which just seems to demand it.++-- Barrie, of course, is not terribly at ease with dynamic gadgets+-- and widgets.++{-+renderTable :: GtkRenderFn a+renderTable ss children widgets = do+ case tableShape children of+ (1, 1) -> head children+ (1, _) -> renderBox Gtk.hBoxNew ss (head children) widgets+ (_, 1) -> renderBox Gtk.vBoxNew ss (map head children) widgets+ (n, m) -> do table <- Gtk.tableNew n m False+ return $ (table,+ startTable table n m ss children widgets,+ updateContainer (Gtk.castToWidget table)+ ss (concat children) widgets)+-}++-- |The renderBox function renders horizontal and vertical layout boxes.++-- |Note the return type: it's a RunnableGUI from module Barrie.Render.++renderBox :: (Gtk.BoxClass b) =>+ (Bool -> Int -> IO b)+ -> GtkRenderFn a+renderBox new gs style gadget = do+ box <- new (getHomogenous style) 1+ startBox box gs gadget+ let result = Gtk.castToWidget box+ let connectBox getS update =+ mapM_ (\ (_, _, connect) -> connect getS update) gs+ return $ (result,+ configureWidget box style gadget $ onUpdate result (updates gs),+ connectBox)++startBox :: (Gtk.BoxClass b) =>+ b+ -> [RunnableGUI GtkGUI a]+ -> Maybe (Gadget a)+ -> IO ()+startBox box gs _ = do+ mapM_ (\ (w, _, _) -> Gtk.boxPackStartDefaults box w) gs++onUpdate :: Gtk.Widget -> [a -> IO ()] -> a -> IO ()+onUpdate _ childUpdates state = do+ mapM_ ($ state) childUpdates++applyStyle :: (a -> IO ()) -> Maybe a -> IO ()+applyStyle _ Nothing = return ()+applyStyle applier (Just style) = applier style++{-+tableShape :: [[a]] -> (Int, Int)+tableShape cells = (length cells, length (head cells))++startTable :: Gtk.Table+ -> StyleSheet -> [RunnableGUI GtkGUI a] -> [TreeWidget a]+ -> a -> IO ()+startTable table ss children widgets state = do+ let attachCell row col widget =+ Gtk.tableAttachDefaults table widget+ col (col + 1)+ row (row + 1)+ attachRow row cells = mapM_ attach (zip [0 ..] cells)+ where attach (col, (w, _, _)) = attachCell row col w+ attachRows = mapM_ attach (zip [0 ..] children)+ where attach (row, ws) = attachRow row (map (\ (w,_,_) -> w) ws)+ attachRows+-}++renderButton :: GtkRenderFn a+renderButton _ style gadget = do+ btn <- Gtk.buttonNew+ applyStyle (Gtk.buttonSetLabel btn) (getCaption style)+ let onClicked = \ handler -> Gtk.onClicked btn handler >> return ()+ clickUpdate = \ gets -> case gadget of+ Just g -> connectCommand onClicked gets g+ Nothing -> const (return ())+ return (Gtk.castToWidget btn,+ configureButton btn style gadget (const (return ())),+ clickUpdate)++renderPopupButton :: Widget -> GtkRenderFn a+renderPopupButton popup _ style gadget = do+ btn <- Gtk.buttonNew+ applyStyle (Gtk.buttonSetLabel btn) (getCaption style)+ let onClicked = \ handler -> Gtk.onClicked btn handler >> return ()+ inner = \ g st -> do gui <- renderWidget (gadgets g) popup renderGtk+ runGtk gui st True+ clickUpdate = \ gets -> case gadget of+ Nothing -> \ _ -> return ()+ Just g ->+ connectChild onClicked inner gets g+ return (Gtk.castToWidget btn,+ configureButton btn style gadget (const (return ())),+ clickUpdate)++configureButton :: Gtk.Button -> Style -> Maybe (Gadget a)+ -> (a -> IO ()) -> a -> IO ()+configureButton btn style (Just gadget) update state = do+ let cfg = gadgetConfig gadget+ when (configHasLabel cfg) $ do+ Gtk.buttonSetLabel btn (configLabel cfg state)+ configureWidget btn style (Just gadget) update state++configureButton _ _ _ update state = update state++configureTreeView :: Gtk.TreeView -> Style -> Maybe (Gadget a)+ -> (a -> IO ()) -> a -> IO ()+configureTreeView view style (Just gadget) update state = do+ Gtk.treeViewSetHeadersVisible view (getTreeHeaderVisible style)+ configureWidget view style (Just gadget) update state++configureTreeView _ _ _ update state = update state++configureWidget :: (Gtk.WidgetClass w) =>+ w+ -> Style+ -> Maybe (Gadget a)+ -> (a -> IO ())+ -> a -> IO ()+configureWidget widget style (Just gadget) update state = do+ let enable = case (gadgetEnabled gadget state, getHideDisabled style) of+ (True, True) -> Gtk.widgetShow+ (False, True) -> Gtk.widgetHide+ (isEnbld, False) -> flip Gtk.widgetSetSensitivity isEnbld+ enable widget+ update state++configureWidget _ _ _ update state = update state++renderScale :: (Gtk.ScaleClass scale) =>+ (Double -> Double -> Double -> IO scale) -- ^ constructor+ -> GtkRenderFn a+renderScale new _ style gadget = do+ scale <- new (getMinValue style) (getMaxValue style) (getAdjustment style)+ let onChanged = \ h -> (Gtk.onRangeValueChanged scale h >>+ return ())+ hasChanged = \ d -> (Gtk.rangeGetValue scale >>=+ \ t -> return $ t /= renderDouble d)+ getDynValue = Gtk.rangeGetValue scale >>= return . toDyn+ setDynValue d = Gtk.rangeSetValue scale (renderDouble d)+ changeUpdate getSt = case gadget of+ Nothing -> \ _ -> return ()+ Just g -> connectEditor onChanged getSt+ hasChanged getDynValue g+ updateValue = case gadget of+ Nothing -> \ _ -> return ()+ Just g -> connectDisplay setDynValue g+ return (Gtk.castToWidget scale,+ configureWidget scale style gadget updateValue,+ changeUpdate)++renderTextBox :: GtkRenderFn a+renderTextBox _ style gadget = do+ tb <- Gtk.entryNew+ let onChanged = \ h -> (Gtk.onEntryActivate tb h >>+ Gtk.onFocusOut tb (\ _ -> (h >> return False)) >>+ return ())+ hasChanged = \ d -> (Gtk.entryGetText tb >>=+ \ t -> return $ t /= renderText d)+ getDynValue = Gtk.entryGetText tb >>= return . toDyn+ setDynValue d = Gtk.entrySetText tb (renderText d)+ changeUpdate getSt = case gadget of+ Nothing -> \ _ -> return ()+ Just g -> connectEditor onChanged getSt+ hasChanged getDynValue g+ updateValue = case gadget of+ Nothing -> \ _ -> return ()+ Just g -> connectDisplay setDynValue g+ return (Gtk.castToWidget tb,+ configureWidget tb style gadget updateValue,+ changeUpdate)++renderCheckButton :: GtkRenderFn a+renderCheckButton _ style gadget = do+ cb <- Gtk.checkButtonNew+ let onChanged = \ h -> (Gtk.onToggled cb h >> return ())+ hasChanged = \ d -> (Gtk.toggleButtonGetActive cb >>=+ \ t -> return $ t /= renderBool d)+ getDynValue = Gtk.toggleButtonGetActive cb >>= return . toDyn+ setDynValue d = Gtk.toggleButtonSetActive cb (renderBool d)+ changeUpdate getSt = case gadget of+ Nothing -> \ _ -> return ()+ Just g -> connectEditor onChanged getSt+ hasChanged getDynValue g+ updateValue = case gadget of+ Nothing -> \ _ -> return ()+ Just g -> connectDisplay setDynValue g+ return (Gtk.castToWidget cb,+ configureWidget cb style gadget updateValue,+ changeUpdate)++traceGadget :: Maybe (Gadget a) -> String -> IO ()+traceGadget Nothing msg = traceMessage $ "<no gadget>: " ++ msg+traceGadget (Just g) msg = traceMessage $ (flatName g) ++ msg++-- |ModelRender: a record for rendering models.++data ModelRender = MR { mrSetChoices :: [(Dynamic, Dynamic)] -> IO (),+ mrSetSelection :: Int -> IO (),+ mrGetValue :: IO Dynamic,+ mrHasChanged :: Dynamic -> IO Bool+ }++-- |The function renderModel uses the Gtk combo box/list/tree+-- |abstraction to do some abstracting of its own.++-- |Currently, we only render single-column lists. This makes the+-- |implementation almost trivial. Yay!++renderSingleColumnModel :: IO (Gtk.ListStore (Dynamic, Dynamic)) -- ^ get model from widget+ -> (IO () -> IO ()) -- ^ selection change handler+ -> IO Int -- ^ current selection from widget+ -> (Int -> IO ()) -- ^ set selection+ -> IO ModelRender+renderSingleColumnModel getModel onChanged getCursor setCursor = do+ -- needUpdateRef <- newIORef True+ currentChoices <- newIORef ([] :: [(Dynamic, Dynamic)])+ let setChoices xs = do+ -- needUpdate <- readIORef needUpdateRef+ -- writeIORef needUpdateRef False+ model <- getModel+ es <- Gtk.listStoreToList model++ -- Note that we don't have a very good way of deciding+ -- when to update the list elements.+ when (length es /= length xs) $ do+ Gtk.listStoreClear model+ mapM_ (Gtk.listStoreAppend model) xs+ writeIORef currentChoices (xs :: [(Dynamic, Dynamic)])+ getValue = do+ cursor <- getCursor+ model <- getModel+ values <- Gtk.listStoreToList model+ return $ toDyn (values, cursor)+ hasChanged d = do+ let (_, x) = fromDyn d ([(d,d)], -100)+ s = fromDyn d "not a string"+ curr <- getCursor+ return (x /= curr)+ return MR { mrSetChoices = setChoices,+ mrSetSelection = setCursor,+ mrGetValue = getValue,+ mrHasChanged = hasChanged+ }++renderChooser :: GtkRenderFn a+renderChooser _ style gadget = do+ store <- Gtk.listStoreNew ([] :: [(Dynamic, Dynamic)])+ chooser <- Gtk.comboBoxNewWithModel store+ r <- Gtk.cellRendererTextNew+ Gtk.cellLayoutPackStart chooser r True+ Gtk.cellLayoutSetAttributes chooser r store $+ \ row -> [ Gtk.cellText := renderText $ fst row ]+ let getModel = return store+ onChanged = \ handler -> Gtk.on chooser Gtk.changed handler >> return ()+ getSel = Gtk.comboBoxGetActive chooser+ setSel = Gtk.comboBoxSetActive chooser+ mr <- renderSingleColumnModel getModel onChanged getSel setSel+ let connect = case gadget of+ Nothing -> \ _ -> return ()+ Just g -> connectChooser (mrSetChoices mr) (mrSetSelection mr)+ (mrGetValue mr) g+ update getSt = case gadget of+ Nothing -> \ _-> return ()+ Just g -> connectEditor onChanged getSt+ (mrHasChanged mr)+ (mrGetValue mr) g+ return (Gtk.castToWidget chooser,+ configureWidget chooser style gadget connect,+ update)++renderLabel :: GtkRenderFn a+renderLabel _ style gadget = do+ label <- Gtk.labelNew Nothing+ applyStyle (Gtk.labelSetText label) (getCaption style)+ let setDynText d = Gtk.labelSetText label (renderText d)+ updateText = case gadget of+ Nothing -> \ _ -> return ()+ Just g -> connectDisplay setDynText g+ return (Gtk.castToWidget label, updateText, \ _ _ -> return ())++renderSingleColumnList :: GtkRenderFn a+renderSingleColumnList children style gadget = do+ store <- Gtk.listStoreNew []+ list <- Gtk.treeViewNewWithModel store+ col <- Gtk.treeViewColumnNew+ r <- Gtk.cellRendererTextNew+ Gtk.cellLayoutPackStart col r True+ Gtk.cellLayoutSetAttributes col r store $+ \ row -> [ Gtk.cellText := renderText (fst row) ]+ Gtk.treeViewAppendColumn list col+ let getModel = return store+ onChanged = \ handler -> Gtk.onCursorChanged list handler >>+ return ()+ getSel = do (path, _) <- Gtk.treeViewGetCursor list+ case path of+ [] -> return (-1)+ [x] -> return x+ _ -> error $ "can't render tree view "+ setSel n | n < 0 = Gtk.treeViewSetCursor list [] Nothing+ | otherwise = Gtk.treeViewSetCursor list [n] Nothing+ mr <- renderSingleColumnModel getModel onChanged getSel setSel+ let connect = case gadget of+ Nothing -> \ _ -> return ()+ Just g -> connectChooser (mrSetChoices mr) (mrSetSelection mr)+ (mrGetValue mr) g+ update getSt = case gadget of+ Nothing -> \ _-> return ()+ Just g -> connectEditor onChanged getSt+ (mrHasChanged mr)+ (mrGetValue mr) g+ return (Gtk.castToWidget list,+ configureTreeView list style gadget connect,+ update)
− src/Barrie/Render/Gtk.lhs
@@ -1,412 +0,0 @@-This is Barrie's Gtk2hs rendering engine-- See module Barrie.Render for a discussion of the RunnableGUI type,- which renderers return.--> module Barrie.Render.Gtk (gtkMain, initGtk, renderGtk, runGtk) where--> import qualified Graphics.UI.Gtk as Gtk-> import System.Glib.Attributes--> import Control.Monad-- IORefs are Barrie's dirty little secret. There are three: one- is for saving a global state that the GUI works with, the other- two save the contents of lists and choosers, so that we don't- update them more than necessary.--> import Data.IORef--- We import approximately everything--> import Barrie.Config-> import Barrie.Render-> import Barrie.Style-> import Barrie.Gadgets-> -- import Barrie.GadgetValue-> -- import Barrie.GadgetValueType-> import Barrie.Trace-> import Barrie.Widgets-- - The rendering model is defined by the RenderF type in the- Barrie.Render module. Implementations of the renderer provide- a type which describes the GUI. In this case, it's just a- Gtk.Widget.--> type GtkGUI = Gtk.Widget-- All rendering functions in this module use the following type- GtkRenderFn.- - It is a function from:- - a list of child guis (i.e. widgets inside the one being rendered)- - a style- - an optional gadget, which will be connected to the- rendered widget if present.- It returns a runnable GUI.--> type GtkRenderFn a = [RunnableGUI GtkGUI a]-> -> Style-> -> Maybe (Gadget a)-> -> IO (RunnableGUI GtkGUI a)--- gtkMain is a convenience function for launching a gadget/widget pair,- with a given state. It should only be called once, at the top level,- since it initialises Gtk (although I think multiple calls are- harmless).--> gtkMain :: Gadget a -> Widget -> a -> IO a-> gtkMain gadget widget state = do-> initGtk-> gui <- renderWidget (gadgets gadget) widget renderGtk-> runGtk gui state False---> initGtk :: IO ()-> initGtk = do Gtk.initGUI-> return ()--> renderGtk :: RenderF GtkGUI a-> renderGtk nm gs style gadget popupChild = do-> let rendererList = case popupChild of-> Nothing -> renderers-> Just w -> map (\ (x,y) -> (x, y w)) popupRenderers-> case lookup nm rendererList of-> Nothing -> error $ "rendering element '" ++ nm ++ "' not found"-> Just r -> do traceMessage ("rendering: " ++ nm)-> r gs style gadget--> runGtk :: RunnableGUI GtkGUI a -> a -> Bool -> IO a-> runGtk (top, start, connect) state modal = do-> stateref <- newIORef state-> let update st = do writeIORef stateref st-> start st-> getState = readIORef stateref-> connect getState update-> startGtk top start state modal-> getState--> startGtk :: Gtk.Widget -> (a -> IO ()) -> a -> Bool -> IO ()-> startGtk top start state modal = do-> Gtk.initGUI-> window <- Gtk.windowNew-> Gtk.windowSetModal window modal-> Gtk.onDestroy window Gtk.mainQuit-> set window [ Gtk.containerChild := top ]-> Gtk.widgetShowAll window-> start state-> Gtk.mainGUI-- ------------------------------ Renderers and popup renderers- ------------------------------- Two kinds of renderers are used. The normal one puts widgets inside- other widgets in the normal way. The popup renderer instead puts- widgets into a modal popup. The choice of which to used is made- based on the popupChild argument to renderGtk, which is taken from- the widget corresponding to the gadget.-- I find this a bit dodgy, since one could come up with all sorts of- information that might be useful for a widget to pass to a gadget,- but we certainly don't want to add an argument for each. If I come- up with a bunch, I'll change the type from Maybe Widget to a record- of properties.--> renderers :: [(String, GtkRenderFn a)]-> renderers = [("button", renderButton),-> ("label", renderLabel),-> ("textbox", renderTextBox),-> ("droplist", renderChooser),-> ("list", renderSingleColumnList),-> ("vbox", renderBox Gtk.vBoxNew),-> ("hbox", renderBox Gtk.hBoxNew)]-- Currently, we only do popup buttons, but it should be easily- generalisable.--> popupRenderers :: [(String, Widget -> GtkRenderFn a)]-> popupRenderers = [("button", renderPopupButton)]-- Tables will come back when I work out a nice way to make them- dynamic. Yes, boxes should be dynamic too, but there's something- about tables which just seems to demand it.-- Barrie, of course, is not terribly at ease with dynamic gadgets- and widgets.--> {--> renderTable :: GtkRenderFn a-> renderTable ss children widgets = do-> case tableShape children of-> (1, 1) -> head children-> (1, _) -> renderBox Gtk.hBoxNew ss (head children) widgets-> (_, 1) -> renderBox Gtk.vBoxNew ss (map head children) widgets-> (n, m) -> do table <- Gtk.tableNew n m False-> return $ (table,-> startTable table n m ss children widgets,-> updateContainer (Gtk.castToWidget table)-> ss (concat children) widgets)-> -}--> -- |The renderBox function renders horizontal and vertical layout boxes.--> -- |Note the return type: it's a RunnableGUI from module Barrie.Render.--> renderBox :: (Gtk.BoxClass b) =>-> (Bool -> Int -> IO b)-> -> GtkRenderFn a-> renderBox new gs style gadget = do-> box <- new (getHomogenous style) 1-> startBox box gs gadget-> let result = Gtk.castToWidget box-> let connectBox getS update =-> mapM_ (\ (_, _, connect) -> connect getS update) gs-> return $ (result,-> configureWidget box style gadget $ onUpdate result (updates gs),-> connectBox)--> startBox :: (Gtk.BoxClass b) =>-> b-> -> [RunnableGUI GtkGUI a]-> -> Maybe (Gadget a)-> -> IO ()-> startBox box gs _ = do-> mapM_ (\ (w, _, _) -> Gtk.boxPackStartDefaults box w) gs--> onUpdate :: Gtk.Widget -> [a -> IO ()] -> a -> IO ()-> onUpdate _ childUpdates state =-> mapM_ ($ state) childUpdates--> applyStyle :: (a -> IO ()) -> Maybe a -> IO ()-> applyStyle _ Nothing = return ()-> applyStyle applier (Just style) = applier style--> {--> tableShape :: [[a]] -> (Int, Int)-> tableShape cells = (length cells, length (head cells))--> startTable :: Gtk.Table-> -> StyleSheet -> [RunnableGUI GtkGUI a] -> [TreeWidget a]-> -> a -> IO ()-> startTable table ss children widgets state = do-> let attachCell row col widget =-> Gtk.tableAttachDefaults table widget-> col (col + 1)-> row (row + 1)-> attachRow row cells = mapM_ attach (zip [0 ..] cells)-> where attach (col, (w, _, _)) = attachCell row col w-> attachRows = mapM_ attach (zip [0 ..] children)-> where attach (row, ws) = attachRow row (map (\ (w,_,_) -> w) ws)-> attachRows-> -}--> renderButton :: GtkRenderFn a-> renderButton _ style gadget = do-> btn <- Gtk.buttonNew-> applyStyle (Gtk.buttonSetLabel btn) (getCaption style)-> let onClicked = \ handler -> Gtk.onClicked btn handler >> return ()-> clickUpdate = \ gets -> connectCommandGadget onClicked gets gadget-> return (Gtk.castToWidget btn,-> configureButton btn style gadget (const (return ())),-> clickUpdate)--> renderPopupButton :: Widget -> GtkRenderFn a-> renderPopupButton popup _ style gadget = do-> btn <- Gtk.buttonNew-> applyStyle (Gtk.buttonSetLabel btn) (getCaption style)-> let onClicked = \ handler -> Gtk.onClicked btn handler >> return ()-> inner = \ g st -> do traceMessage ("popup: gadget = " ++-> gadgetName g)-> gui <- renderWidget (gadgets g) popup renderGtk-> runGtk gui st True-> clickUpdate = \ gets -> connectInnerGadget onClicked-> inner gets gadget-> return (Gtk.castToWidget btn,-> configureButton btn style gadget (const (return ())),-> clickUpdate)--> configureButton :: Gtk.Button -> Style -> Maybe (Gadget a)-> -> (a -> IO ()) -> a -> IO ()-> configureButton btn style (Just gadget) update state = do-> when (gadgetConfig gadget configHasLabel) $ do-> Gtk.buttonSetLabel btn (gadgetConfig gadget configLabel state)-> configureWidget btn style (Just gadget) update state--> configureButton _ _ _ update state = update state--> configureWidget :: (Gtk.WidgetClass w) =>-> w-> -> Style-> -> Maybe (Gadget a)-> -> (a -> IO ())-> -> a -> IO ()-> configureWidget widget style (Just gadget) update state = do-> let enable = case (gadgetEnabled gadget state, getHideDisabled style) of-> (True, True) -> Gtk.widgetShow-> (False, True) -> Gtk.widgetHide-> (isEnbld, False) -> flip Gtk.widgetSetSensitivity isEnbld-> enable widget-> update state--> configureWidget _ _ _ update state = update state--> renderTextBox :: GtkRenderFn a-> renderTextBox _ style gadget = do-> tb <- Gtk.entryNew-> let onChanged = \ handler -> (Gtk.onEntryActivate tb handler >>-> Gtk.onFocusOut tb (\ _ -> (handler >> return False)) >>-> return ())-> changeUpdate = \ getState ->-> connectEditorGadget onChanged getState-> (Gtk.entryGetText tb)-> gadget-> return (Gtk.castToWidget tb,-> configureWidget tb style gadget $-> connectDisplayGadget (Gtk.entrySetText tb) gadget,-> changeUpdate)--> traceGadget :: Maybe (Gadget a) -> String -> IO ()-> traceGadget Nothing msg = traceMessage $ "<no gadget>: " ++ msg-> traceGadget (Just g) msg = traceMessage $ (gadgetName g) ++ msg--> -- |ModelRender: a record for rendering models.--> data ModelRender = MR { mrSetChoices :: [String] -> IO (),-> mrSetSelection :: Int -> IO (),-> mrGetValue :: IO String-> }--> -- |The function renderModel uses the Gtk combo box/list/tree-> -- |abstraction to do some abstracting of its own.--> -- |Currently, we only render single-column lists. This makes the-> -- |implementation almost trivial. Yay!--> renderSingleColumnModel :: IO (Gtk.ListStore String) -- ^ get model from widget-> -> (IO () -> IO ()) -- ^ selection change handler-> -> IO Int -- ^ current selection from widget-> -> (Int -> IO ()) -- ^ set selection-> -> IO ModelRender-> renderSingleColumnModel getModel onChanged getCursor setCursor = do-> elemsRef <- newIORef []-> let setChoices xs = do-> current <- readIORef elemsRef-> when (current /= xs) $ do-> model <- getModel-> Gtk.listStoreClear model-> mapM_ (Gtk.listStoreAppend model) xs-> writeIORef elemsRef xs-> let getValue = do cursor <- getCursor-> model <- getModel-> if cursor < 0-> then return ""-> else Gtk.listStoreGetValue model cursor-> let setValue n | n < 0 = setCursor n-> | otherwise = setCursor n-> return MR { mrSetChoices = setChoices,-> mrSetSelection = setValue,-> mrGetValue = getValue-> }--> renderChooser :: GtkRenderFn a-> renderChooser _ style gadget = do-> store <- Gtk.listStoreNew []-> chooser <- Gtk.comboBoxNewWithModel store-> r <- Gtk.cellRendererTextNew-> Gtk.cellLayoutPackStart chooser r True-> Gtk.cellLayoutSetAttributes chooser r store $-> \ row -> [ Gtk.cellText := row ]-> let getModel = return store-> onChanged = \ handler -> Gtk.on chooser Gtk.changed handler >> return ()-> getSel = Gtk.comboBoxGetActive chooser-> setSel = Gtk.comboBoxSetActive chooser-> mr <- renderSingleColumnModel getModel onChanged getSel setSel-> let connect = connectChooserDisplay (mrSetChoices mr) (mrSetSelection mr)-> gadget-> update = \getState -> connectEditorGadget onChanged getState-> (mrGetValue mr) gadget-> return (Gtk.castToWidget chooser,-> configureWidget chooser style gadget connect,-> update)--> renderLabel :: GtkRenderFn a-> renderLabel _ style gadget = do-> label <- Gtk.labelNew Nothing-> applyStyle (Gtk.labelSetText label) (getCaption style)-> return (Gtk.castToWidget label,-> connectDisplayGadget (Gtk.labelSetText label) gadget,-> \ _ _ -> return ())--> renderSingleColumnList :: GtkRenderFn a-> renderSingleColumnList children style gadget = do-> store <- Gtk.listStoreNew []-> list <- Gtk.treeViewNewWithModel store-> col <- Gtk.treeViewColumnNew-> r <- Gtk.cellRendererTextNew-> Gtk.cellLayoutPackStart col r True-> Gtk.cellLayoutSetAttributes col r store $ \ row -> [ Gtk.cellText := row ]-> Gtk.treeViewAppendColumn list col-> let getModel = return store-> onChanged = \ handler -> Gtk.onCursorChanged list handler >>-> return ()-> getSel = do (path, _) <- Gtk.treeViewGetCursor list-> case path of-> [] -> return (-1)-> [x] -> return x-> _ -> error $ "can't render tree view "-> setSel n | n < 0 = Gtk.treeViewSetCursor list [] Nothing-> | otherwise = Gtk.treeViewSetCursor list [n] Nothing-> mr <- renderSingleColumnModel getModel onChanged getSel setSel-> let connect = connectChooserDisplay (mrSetChoices mr) (mrSetSelection mr)-> gadget-> update = \getState -> connectEditorGadget onChanged getState-> (mrGetValue mr) gadget-> return (Gtk.castToWidget list,-> configureWidget list style gadget connect,-> update)---> {- list <- Gtk.treeViewNew-> col <- Gtk.treeViewColumnNew-> Gtk.treeViewAppendColumn list col-> tr <- Gtk.cellRendererTextNew-> Gtk.treeViewColumnPackStart col tr True-> Gtk.treeViewColumnAddAttribute col tr "text" 0-> let onChanged = \ handler -> Gtk.onCursorChanged list handler >>-> return ()-> let getValue rd = do Just store <- Gtk.treeViewGetModel list-> (path, col) <- Gtk.treeViewGetCursor list-> miter <- Gtk.treeModelGetIter store path-> case miter of-> Nothing -> return gvNoValue-> (Just iter) -> do-> (Gtk.GVstring (Just s)) <--> Gtk.treeModelGetValue store iter 0-> return (rd s)-> return (Gtk.castToWidget list,-> setListChanger (\ m -> Gtk.treeViewSetModel list m) gadget,-> \ getState -> connectTextEditorGadget onChanged getState-> getValue gadget)-> -}--> {- setListChanger :: (Gtk.ListStore -> IO ())-> -> Maybe (Gadget a)-> -> a -> IO ()-> setListChanger update gadget st = connectDisplayGadget update' gadget st-> where update' gv = toListStore gv >>= update-> toListStore gv = case gvType gv of-> (GVList t) ->-> toSingleColumnList t (gvListValues gv)-> _ -> error ("can't display value in a list: " ++-> toString gv)-> toSingleColumnList t list = do-> store <- Gtk.listStoreNew [toGtkType t]-> mapM_ (listModelAddRow store) (map (toGtkValue t) list)-> return store-> -}--
+ src/Barrie/Style.hs view
@@ -0,0 +1,104 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++module Barrie.Style (Style, Styled(..), addStyle, emptyStyle,+ caption, getCaption,+ hideDisabled, getHideDisabled,+ homogenous, getHomogenous,+ textValue, textItems,+ minValue, maxValue,+ getMinValue, getMaxValue,+ adjustment, getAdjustment,+ treeHeaderVisible, getTreeHeaderVisible) where++import Data.Monoid++newtype Style = Style [(String, StyleValue)]+ deriving (Read, Show, Monoid)++type StyleValue = String++mkStyle :: String -> StyleValue -> Style+mkStyle name value = Style [(name, value)]++class Styled a where+ setStyle :: Style -> a -> a+ getStyle :: a -> Style++instance Styled Style where+ setStyle = const+ getStyle = id++addStyle :: (Styled a) => Style -> a -> a+addStyle style item = setStyle (style `mappend` getStyle item) item++emptyStyle :: Style+emptyStyle = Style []++styleValue :: (Styled a) => String -> a -> Maybe StyleValue+styleValue name ss = lookup name styleList+ where (Style styleList) = getStyle ss++caption :: (Styled a) => String -> a -> a+caption text = addStyle $ mkStyle "caption" text++getCaption :: (Styled a) => a -> Maybe StyleValue+getCaption = styleValue "caption"++textValue :: (Styled a) => String -> a -> a+textValue text = addStyle $ mkStyle "textValue" text++textItems :: (Styled a) => [String] -> a -> a+textItems items = addStyle $ mkStyle "textItems" (unlines items)++hideDisabled :: (Styled a) => Bool -> a -> a+hideDisabled hide = addStyle $ mkStyle "hideDisabled" (show hide)++getHideDisabled :: (Styled a) => a -> Bool+getHideDisabled style = case styleValue "hideDisabled" style of+ Nothing -> False+ Just text -> read text+++homogenous :: (Styled a) => Bool -> a -> a+homogenous set = addStyle $ mkStyle "homogenous" (show set)++getHomogenous :: (Styled a) => a -> Bool+getHomogenous style = case styleValue "homogenous" style of+ Nothing -> False+ Just text -> read text+++minValue :: (Styled a, Num b, Show b, Read b) => b -> a -> a+minValue value = addStyle $ mkStyle "minValue" (show value)++getMinValue :: (Styled a, Num b, Show b, Read b) => a -> b+getMinValue style = case styleValue "minValue" style of+ Nothing -> 0+ Just text -> read text+++maxValue :: (Styled a, Num b, Show b, Read b) => b -> a -> a+maxValue value = addStyle $ mkStyle "maxValue" (show value)++getMaxValue :: (Styled a, Num b, Show b, Read b) => a -> b+getMaxValue style = case styleValue "maxValue" style of+ Nothing -> 0+ Just text -> read text+++adjustment :: (Styled a, Num b, Show b, Read b) => b -> a -> a+adjustment value = addStyle $ mkStyle "adjustment" (show value)++getAdjustment :: (Styled a, Num b, Show b, Read b) => a -> b+getAdjustment style = case styleValue "adjustment" style of+ Nothing -> 1+ Just text -> read text+++treeHeaderVisible :: (Styled a) => Bool -> a -> a+treeHeaderVisible hide = addStyle $ mkStyle "treeHeaderVisible" (show hide)++getTreeHeaderVisible :: (Styled a) => a -> Bool+getTreeHeaderVisible style = case styleValue "treeHeaderVisible" style of+ Nothing -> False+ Just text -> read text
− src/Barrie/Style.lhs
@@ -1,66 +0,0 @@-> {-# LANGUAGE GeneralizedNewtypeDeriving #-}--> module Barrie.Style (Style, Styled(..), addStyle, emptyStyle,-> caption, getCaption,-> hideDisabled, getHideDisabled,-> homogenous, getHomogenous,-> textValue, textItems) where--> import Data.Monoid--> newtype Style = Style [(String, StyleValue)]-> deriving (Read, Show, Monoid)--> type StyleValue = String--> mkStyle :: String -> StyleValue -> Style-> mkStyle name value = Style [(name, value)]--> class Styled a where-> setStyle :: Style -> a -> a-> getStyle :: a -> Style--> instance Styled Style where-> setStyle = const-> getStyle = id--> addStyle :: (Styled a) => Style -> a -> a-> addStyle style item = setStyle (style `mappend` getStyle item) item--> emptyStyle :: Style-> emptyStyle = Style []--> styleValue :: (Styled a) => String -> a -> Maybe StyleValue-> styleValue name ss = lookup name styleList-> where (Style styleList) = getStyle ss--> caption :: (Styled a) => String -> a -> a-> caption text = addStyle $ mkStyle "caption" text--> getCaption :: (Styled a) => a -> Maybe StyleValue-> getCaption = styleValue "caption"--> textValue :: (Styled a) => String -> a -> a-> textValue text = addStyle $ mkStyle "textValue" text--> textItems :: (Styled a) => [String] -> a -> a-> textItems items = addStyle $ mkStyle "textItems" (unlines items)--> hideDisabled :: (Styled a) => Bool -> a -> a-> hideDisabled hide = addStyle $ mkStyle "hideDisabled" (show hide)--> getHideDisabled :: (Styled a) => a -> Bool-> getHideDisabled style = case styleValue "hideDisabled" style of-> Nothing -> False-> Just text -> read text---> homogenous :: (Styled a) => Bool -> a -> a-> homogenous set = addStyle $ mkStyle "homogenous" (show set)--> getHomogenous :: (Styled a) => a -> Bool-> getHomogenous style = case styleValue "homogenous" style of-> Nothing -> False-> Just text -> read text--
+ src/Barrie/Trace.hs view
@@ -0,0 +1,21 @@+-- Module for enabling trace++module Barrie.Trace (enableTrace, traceMessage) where++import Data.IORef+import System.IO.Unsafe++traceFile :: IORef (Maybe String)+traceFile = unsafePerformIO $ newIORef Nothing++enableTrace :: String -> IO ()+enableTrace fileName = do+ writeIORef traceFile (Just fileName)++traceMessage :: String -> IO ()+traceMessage msg = do+ fileName <- readIORef traceFile+ case fileName of+ Nothing -> return ()+ Just name -> appendFile name (msg ++ "\n")+
− src/Barrie/Trace.lhs
@@ -1,21 +0,0 @@-Module for enabling trace--> module Barrie.Trace (enableTrace, traceMessage) where--> import Data.IORef-> import System.IO.Unsafe--> traceFile :: IORef (Maybe String)-> traceFile = unsafePerformIO $ newIORef Nothing--> enableTrace :: String -> IO ()-> enableTrace fileName = do-> writeIORef traceFile (Just fileName)--> traceMessage :: String -> IO ()-> traceMessage msg = do-> fileName <- readIORef traceFile-> case fileName of-> Nothing -> return ()-> Just name -> appendFile name (msg ++ "\n")-
+ src/Barrie/Widgets.hs view
@@ -0,0 +1,123 @@+module Barrie.Widgets+ (Widget, widgetElementName, widgetToLines, widgetChildren, widgetStyle,+ widgetPopupChild,+ mkWidget, mkPopup,+ ui, ui', uiElementName,+ LayoutDirection(..),+ vbox, hbox, menu, menuItem, button, labelButton, checkButton,+ textLabel, slider, hslider, vslider,+ staticDropList, dropList, listView, radioButtonList,+ textBox, textBoxWithValue)+ where++import Data.List++import Barrie.Style++data Widget = Widget { elementName :: String,+ styles :: Style,+ uiName :: [String],+ wpopup :: Maybe Widget,+ wdirection :: Maybe LayoutDirection,+ children :: [Widget]+ }+ deriving (Read, Show)++data LayoutDirection = Vertical | Horizontal+ deriving (Read, Show)++instance Styled Widget where+ getStyle = styles+ setStyle style w = w { styles = style }++widgetElementName :: Widget -> String+widgetElementName w = prefix ++ elementName w+ where prefix = case wdirection w of+ Nothing -> ""+ Just Vertical -> "v"+ Just Horizontal -> "h"++widgetToLines :: Widget -> [String]+widgetToLines w = widgetLine : concatMap widgetToLines (children w)+ where widgetLine = elementName w ++ uiLine+ uiLine = case uiName w of+ [] -> ""+ nms -> " (" ++ intercalate "." nms ++ ")"++widgetChildren :: Widget -> [Widget]+widgetChildren = children++widgetStyle :: Widget -> Style+widgetStyle = styles++widgetPopupChild :: Widget -> Maybe Widget+widgetPopupChild = wpopup++mkWidget :: String -> [Widget] -> Widget+mkWidget elmtName ws = Widget elmtName emptyStyle [] Nothing Nothing ws++mkPopup :: String -> Widget -> Widget -> Widget+mkPopup uiname popup w = ui uiname w { wpopup = Just popup }++layoutDirection :: LayoutDirection -> Widget -> Widget+layoutDirection d w = w { wdirection = Just d }++ui :: String -> Widget -> Widget+ui name = ui' [name]++ui' :: [String] -> Widget -> Widget+ui' name widget = widget { uiName = name }++uiElementName :: Widget -> [String]+uiElementName = uiName++vbox, hbox :: [Widget] -> Widget+vbox = layoutDirection Vertical . mkWidget "box"+hbox = layoutDirection Horizontal . mkWidget "box"++slider :: (Real a) => LayoutDirection -> a -> a -> a -> Widget+slider d lo hi adj = (minValue ((realToFrac lo) :: Double) .+ maxValue ((realToFrac hi) :: Double) .+ adjustment ((realToFrac adj) :: Double) .+ layoutDirection d) (mkWidget "slider" [])++hslider, vslider :: (Real a) => a -> a -> a -> Widget+hslider = slider Horizontal+vslider = slider Vertical++menu :: [Widget] -> Widget+menu = mkWidget "menu"++menuItem :: [Widget] -> Widget+menuItem = mkWidget "menuitem"++button :: [Widget] -> Widget+button = mkWidget "button"++labelButton :: String -> Widget+labelButton text = caption text (button [])++checkButton :: Widget+checkButton = mkWidget "checkbutton" []++textLabel :: String -> Widget+textLabel text = caption text (mkWidget "label" [])++staticDropList :: String -> [String] -> Widget+staticDropList val vals = textValue val $ textItems vals $+ mkWidget "droplist" []++dropList :: Widget+dropList = mkWidget "droplist" []++listView :: Widget+listView = mkWidget "list" []++radioButtonList :: LayoutDirection -> Widget+radioButtonList d = layoutDirection d $ mkWidget "radioButtonList" []++textBox :: Widget+textBox = mkWidget "textbox" []++textBoxWithValue :: String -> Widget+textBoxWithValue text = textValue text textBox
− src/Barrie/Widgets.lhs
@@ -1,102 +0,0 @@-> module Barrie.Widgets-> (Widget, widgetElementName, widgetToLines, widgetChildren, widgetStyle,-> widgetPopupChild,-> mkWidget, mkPopup,-> ui, ui', uiElementName,-> vbox, hbox, menu, menuItem, button, labelButton, textLabel,-> staticDropList, dropList, list, textBox, textBoxWithValue)-> where--> import Data.List--> import Barrie.Style--> data Widget = Widget { elementName :: String,-> styles :: Style,-> uiName :: [String],-> wpopup :: Maybe Widget,-> children :: [Widget]-> }-> deriving (Read, Show)--> instance Styled Widget where-> getStyle = styles-> setStyle style w = w { styles = style }--> widgetElementName :: Widget -> String-> widgetElementName = elementName--> widgetToLines :: Widget -> [String]-> widgetToLines w = widgetLine : concatMap widgetToLines (children w)-> where widgetLine = elementName w ++ uiLine-> uiLine = case uiName w of-> [] -> ""-> nms -> " (" ++ intercalate "." nms ++ ")"--> widgetChildren :: Widget -> [Widget]-> widgetChildren = children--> widgetStyle :: Widget -> Style-> widgetStyle = styles--> widgetPopupChild :: Widget -> Maybe Widget-> widgetPopupChild = wpopup--> mkWidget :: String -> [Widget] -> Widget-> mkWidget elmtName ws = Widget elmtName emptyStyle [] Nothing ws--> mkPopup :: String -> Widget -> Widget -> Widget-> mkPopup uiname popup w = ui uiname w { wpopup = Just popup }--> ui :: String -> Widget -> Widget-> ui name = ui' [name]--> ui' :: [String] -> Widget -> Widget-> ui' name widget = widget { uiName = name }--> uiElementName :: Widget -> [String]-> uiElementName = uiName--> vbox, hbox :: [Widget] -> Widget-> vbox = mkWidget "vbox"-> hbox = mkWidget "hbox"--> table :: [[Widget]] -> Widget-> table = mkWidget "table" . map tableRow--> tableRow :: [Widget] -> Widget-> tableRow = mkWidget "tr" . map tableCell--> tableCell :: Widget -> Widget-> tableCell = mkWidget "td" . (:[])--> menu :: [Widget] -> Widget-> menu = mkWidget "menu"--> menuItem :: [Widget] -> Widget-> menuItem = mkWidget "menuitem"--> button :: [Widget] -> Widget-> button = mkWidget "button"--> labelButton :: String -> Widget-> labelButton text = caption text (button [])--> textLabel :: String -> Widget-> textLabel text = caption text (mkWidget "label" [])--> staticDropList :: String -> [String] -> Widget-> staticDropList val vals = textValue val $ textItems vals $-> mkWidget "droplist" []--> dropList :: Widget-> dropList = mkWidget "droplist" []--> list :: Widget-> list = mkWidget "list" []--> textBox :: Widget-> textBox = mkWidget "textbox" []--> textBoxWithValue :: String -> Widget-> textBoxWithValue text = textValue text textBox