barrie 0.4 → 0.4.1
raw patch · 29 files changed
+867/−122 lines, 29 files
Files
- Makefile +15/−0
- barrie.cabal +35/−11
- demos/AutoCalc.hs +15/−0
- demos/BarrieCalc.hs +15/−0
- demos/ButtonDemo.hs +18/−0
- demos/CalcGadget.hs +47/−0
- demos/CalcState.hs +45/−0
- demos/CalcWidget.hs +20/−0
- demos/ChooserDemo.hs +27/−0
- demos/DynamicLabelDemo.hs +16/−0
- demos/FileChooserDemo.hs +20/−0
- demos/InnerDemo.hs +31/−0
- demos/LabelDemo.hs +19/−0
- demos/LauncherDemo.hs +33/−0
- demos/Layout.hs +2/−0
- demos/LayoutDemo.hs +35/−0
- demos/ListDemo.hs +41/−0
- demos/MultiColumnList.hs +34/−0
- demos/SliderDemo.hs +13/−0
- demos/TextBoxDemo.hs +18/−0
- doc/barrie.tex +38/−0
- src/Barrie/Config.hs +25/−4
- src/Barrie/Gadgets.hs +97/−51
- src/Barrie/Gadgets/Connections.hs +18/−9
- src/Barrie/Packing.hs +41/−0
- src/Barrie/Render.hs +12/−7
- src/Barrie/Render/Gtk.hs +118/−38
- src/Barrie/Style.hs +2/−1
- src/Barrie/Widgets.hs +17/−1
+ Makefile view
@@ -0,0 +1,15 @@+all:+ runhaskell Setup.lhs build+# (cd demos ; make)++config:+ runhaskell Setup.lhs configure++install:+ runhaskell Setup.lhs install+clean:+ rm -f `find . -name "*.o" -print`+ rm -f `find . -name "*.hi" -print`+ rm -f `find . -name "*~" -print`+ (cd demos ; make clean)+ rm -rf dist OME
barrie.cabal view
@@ -1,37 +1,61 @@ Name: barrie-Version: 0.4-Synopsis: Pure Gtk GUI library+Version: 0.4.1+Synopsis: Declarative Gtk GUI library Description: Implementation of an idea for supporting certain kinds of GUI: that is state based, user-driven ones. The GUI is intended to manipulate a state, and it is responsive rather than active.- Configuration is an example. Category: GUI License: GPL License-file: LICENSE Author: Fraser Wilson-Maintainer: blancolioni@gmail.com-Copyright: (c) Fraser Wilson+Maintainer: Fraser Wilson <blancolioni@gmail.com>+Homepage: http://thewhitelion.org/haskell/barrie+Copyright: (c) 2009 Fraser Wilson Stability: unstable cabal-version: >= 0.2 build-type: Simple+Tested-With: GHC==6.10.0+Extra-Source-Files:+ Makefile+ doc/barrie.tex+ demos/AutoCalc.hs+ demos/BarrieCalc.hs+ demos/ButtonDemo.hs+ demos/CalcGadget.hs+ demos/CalcState.hs+ demos/CalcWidget.hs+ demos/ChooserDemo.hs+ demos/DynamicLabelDemo.hs+ demos/FileChooserDemo.hs+ demos/InnerDemo.hs+ demos/LabelDemo.hs+ demos/LauncherDemo.hs+ demos/LayoutDemo.hs+ demos/Layout.hs+ demos/ListDemo.hs+ demos/MultiColumnList.hs+ demos/SliderDemo.hs+ demos/TextBoxDemo.hs Library Build-Depends: base,filepath,gtk>=0.10.0,glib>=0.10.0,containers- Exposed-modules: Barrie- ghc-options: -Wall -optl-Wl,s- ghc-prof-options: -prof -auto-all- other-modules:- Barrie.Config+ Exposed-modules:+ Barrie Barrie.DrawPrimitive Barrie.Gadgets- Barrie.Gadgets.Connections+ Barrie.Packing Barrie.Render Barrie.Render.Gtk Barrie.Style Barrie.Trace Barrie.Widgets+ ghc-options: -Wall -optl-Wl,s,-O2+ ghc-prof-options: -prof -auto-all+ other-modules:+ Barrie.Config+ Barrie.Gadgets.Connections extensions: MultiParamTypeClasses,FunctionalDependencies, GeneralizedNewtypeDeriving,RankNTypes,ExistentialQuantification hs-source-dirs: src
+ demos/AutoCalc.hs view
@@ -0,0 +1,15 @@+-- Calculator demo for Barrie 0.3++module Main where++import Barrie++import CalcState+import CalcGadget++-- The style lets you change colours, fonts, etc+calcStyle :: Style+calcStyle = emptyStyle++main = do mapM_ putStrLn $ widgetToLines (createLayout calcGUI)+ gtkMain calcGUI (createLayout calcGUI) startState
+ demos/BarrieCalc.hs view
@@ -0,0 +1,15 @@+-- Calculator demo for Barrie 0.3++module Main where++import Barrie++import CalcState+import CalcGadget+import CalcWidget++-- The style lets you change colours, fonts, etc+calcStyle :: Style+calcStyle = emptyStyle++main = gtkMain calcGUI calcLayout startState
+ demos/ButtonDemo.hs view
@@ -0,0 +1,18 @@+module Main where++import Barrie++demoWidget :: Widget+demoWidget = vbox [ui "demo label" (textLabel ""),+ ui "demo command" (labelButton "click me")]++type DemoState = String++type DemoGadget = Gadget DemoState++demoGUI :: DemoGadget+demoGUI = sectionG "demo gui" [displayG "demo label" id,+ commandG "demo command" reverse]++main = gtkMain demoGUI demoWidget "Hello, world"+
+ demos/CalcGadget.hs view
@@ -0,0 +1,47 @@+module CalcGadget (CalcState, Operator(..),+ digitButtonName, operatorButtonName,+ startState, calcGUI) where++import Barrie++import CalcState++-- Names for various calculator gadgets+digitButtonName :: Int -> String+digitButtonName = show++operatorButtonName :: Operator -> String+operatorButtonName = show++-- CalcGadget puts the calculator operations into a gadget.+type CalcGadget = Gadget CalcState++data Operator = Plus | Minus | Multiply | Divide | Equals | Clear+ deriving (Eq, Ord, Enum, Bounded, Read, Show)++opFn :: Operator -> CalcState -> CalcState+opFn Plus = op (+)+opFn Minus = op (-)+opFn Multiply = op (*)+opFn Divide = op div+opFn Equals = eval+opFn Clear = clear++calcGUI :: CalcGadget+calcGUI = sectionG "calculator" [display, buttons]++display :: CalcGadget+display = displayG "display" (\ st -> showBase (calcBase st) (calcValue st))+ where showBase _ 0 = "0"+ showBase b n = reverse $ sb n+ where sb 0 = ""+ sb x = toEnum (x `mod` b + 48) : sb (x `div` b)+buttons :: CalcGadget+buttons = sectionG "buttonArea" [sectionG "ops" (map mkCmd [minBound ..]),+ sectionG "digits" (map mkDigit [0 .. 9]),+ sectionG "base" [base8]]+ where mkDigit d = enabled (\ st -> d < calcBase st) $+ commandG (digitButtonName d) (addDigit d)+ mkCmd op = commandG (operatorButtonName op) (opFn op)+ base8 = editorG "base8" (\ st -> calcBase st == 8)+ (\ v st -> if v then setBase 8 st else setBase 10 st)
+ demos/CalcState.hs view
@@ -0,0 +1,45 @@+module CalcState (CalcState, startState,+ calcValue, addDigit, op, eval, clear,+ calcBase, setBase)+where++-- This is the state of the calculator: the current value, the current+-- partial function, and a flag to tell us whether the next digit will+-- start a new number (actually, the sense is reversed from that, but you+-- get the idea)++data CalcState = CS { calcValue :: Int,+ calcOp :: Int -> Int,+ startNew :: Bool,+ calcBase :: Int+ }++startState :: CalcState+startState = CS 0 id False 10++-- Add a single digit to the state. Depending on the boolean value,+-- this could be the start of a new number, or the new rightmost digit+-- of the existing number+addDigit :: Int -> CalcState -> CalcState+addDigit n (CS r f True base) = CS n f False base+addDigit n (CS r f False base) = CS (r * base + n) f False base++-- Apply an operation to the calculator. In fact, this partially applies+-- the operation to what's already there. The current partial operation+-- is applied to the current number.+op :: (Int -> Int -> Int) -> CalcState -> CalcState+op f (CS r g _ base) = CS (g r) (f (g r)) True base++-- Evaluate the state. That is, apply the current partial operation to+-- the current number, resetting everything else+eval :: CalcState -> CalcState+eval (CS r f _ base) = CS (f r) id True base++-- Clear the state.+clear :: CalcState -> CalcState+clear = const startState++setBase :: Int -> CalcState -> CalcState+setBase newBase cs = cs { calcBase = newBase }++
+ demos/CalcWidget.hs view
@@ -0,0 +1,20 @@+module CalcWidget where++import Barrie++import CalcState+import CalcGadget++-- Widgets are layout, without regard for underlying semantics.+-- Here, we put some buttons into a table+calcLayout :: Widget+calcLayout = vbox [ui "display" $ textLabel ""+ ,hbox $ map digit [1,2,3] ++ [op "+" Plus]+ ,hbox $ map digit [4,5,6] ++ [op "-" Minus]+ ,hbox $ map digit [7,8,9] ++ [op "*" Multiply]+ ,hbox $ digit 0 : [op "=" Equals, op "C" Clear,+ op "/" Divide]+ ,hbox $ [textLabel "base 8", ui "base8" checkButton]+ ]+ where digit n = ui (digitButtonName n) (labelButton (show n))+ op lbl fn = ui (operatorButtonName fn) (labelButton lbl)
+ demos/ChooserDemo.hs view
@@ -0,0 +1,27 @@+{-# LANGUAGE DeriveDataTypeable #-}++module Main where++import Data.Maybe+import Data.Typeable++import Barrie++demoWidget :: Widget+demoWidget = vbox [ui "demo chooser" dropList,+ ui "demo chooser" listView,+ ui "choice" (textLabel "Nothing chosen")]++data Option = Trains | Planes | Automobiles+ deriving (Read, Show, Enum, Bounded, Typeable)++type DemoState = Option++type DemoGadget = Gadget DemoState++demoGUI :: DemoGadget+demoGUI = sectionG "demo gui" [enumChooserG "demo chooser" id const,+ displayG "choice" show]++main = gtkMain demoGUI demoWidget Trains+
+ demos/DynamicLabelDemo.hs view
@@ -0,0 +1,16 @@+module Main where++import Barrie++demoLayout :: Widget+demoLayout = vbox [ui "demo label" (textLabel "")]++type DemoState = String++type DemoGadget = Gadget DemoState++demoGUI :: DemoGadget+demoGUI = displayG "demo label" id++main = gtkMain demoGUI demoLayout "Hello, world"+
+ demos/FileChooserDemo.hs view
@@ -0,0 +1,20 @@+module Main where++import Barrie++demoWidget :: Widget+demoWidget = hbox [ui "demo entry" textBox,+ ui "browse" $ fileOpen "Open",+ ui "save" $ fileSave "Save"]++type DemoState = String++type DemoGadget = Gadget DemoState++demoGUI :: DemoGadget+demoGUI = sectionG "demo gui" [editorG "demo entry" id const,+ initG "browse" id,+ displayG "save" id]++main = gtkMain demoGUI demoWidget "Hello, world"+
+ demos/InnerDemo.hs view
@@ -0,0 +1,31 @@+module Main where++import Barrie++demoWidget :: Widget+demoWidget = vbox [ui "fst label" (textLabel "fst label"),+ ui "snd label" (textLabel "snd label"),+ innerWidget]++innerWidget :: Widget+innerWidget = ui "textEditor" textBox++type DemoState = (String, String)++type DemoGadget = Gadget DemoState++type InnerState = String+type InnerGadget = Gadget InnerState++demoGUI :: DemoGadget+demoGUI = sectionG "demo gui" [displayG "fst label" fst,+ displayG "snd label" snd,+ childG "demo inner" fst (\ x (_,y) -> (x,y))+ innerGUI]++innerGUI :: InnerGadget+innerGUI = editorG "textEditor" id (\ s _ -> s)++main = do mapM_ putStrLn $ widgetToLines demoWidget+ -- print $ abstract demoGUI+ gtkMain demoGUI demoWidget ("change me", "can't change me")
+ demos/LabelDemo.hs view
@@ -0,0 +1,19 @@+module Main where++import Barrie++demoStyle :: Style+demoStyle = emptyStyle++demoLayout :: Widget+demoLayout = vbox [ui "display" (textLabel "Hello, world")]++type DemoState = ()++type DemoGadget = Gadget DemoState++demoGUI :: DemoGadget+demoGUI = displayG "demo" id++main = gtkMain demoGUI demoLayout ()+
+ demos/LauncherDemo.hs view
@@ -0,0 +1,33 @@+module Main where++import Barrie++demoWidget :: Widget+demoWidget = vbox [ui "fst label" (textLabel "fst label"),+ ui "snd label" (textLabel "snd label"),+ mkPopup "demo popup" popupWidget (labelButton "click me")]++popupWidget :: Widget+popupWidget = vbox [ui "textEditor" textBox,+ ui "close" $ labelButton "Close"]++type DemoState = (String, String)++type DemoGadget = Gadget DemoState++type PopupState = String+type PopupGadget = Gadget PopupState++demoGUI :: DemoGadget+demoGUI = sectionG "demo gui"+ [displayG "fst label" fst,+ displayG "snd label" snd,+ childG "demo popup" fst (\ x (_,y) -> (x,y)) popupGUI]++popupGUI :: PopupGadget+popupGUI = sectionG "popup" [editorG "textEditor" id (\ s _ -> s),+ returnG "close"+ ]++main = do mapM_ putStrLn (abstract demoGUI)+ gtkMain demoGUI demoWidget ("change me", "can't change me")
+ demos/Layout.hs view
@@ -0,0 +1,2 @@+module Layout where+
+ demos/LayoutDemo.hs view
@@ -0,0 +1,35 @@+module Main where++import Barrie++demoWidget :: Widget+demoWidget = vbox [ui "entry" textBox+ ,hbox [ui "clear" (labelButton "clear")+ ,ui "set" (labelButton "set")+ ,ui "reverse" (labelButton "reverse")+ ]+ ,ui "label" (textLabel "Hello, world!")]++data DemoState = S { first :: String,+ second :: String }++setFirst, setSecond :: String -> DemoState -> DemoState+setFirst s st = st { first = s }+setSecond s st = st { second = s }++update :: (a -> b) -> (b -> a -> a) -> a -> a+update get set state = set (get state) state++type DemoGadget = Gadget DemoState++demoGUI :: DemoGadget+demoGUI = sectionG "demo gui" [editorG "entry" first setFirst+ ,commandG "clear" (setSecond "")+ ,commandG "set" (update first setSecond)+ ,commandG "reverse" (update (reverse . first)+ setSecond)+ ,displayG "label" second+ ]++main = gtkMain demoGUI demoWidget $ S "Type Here" "Hello, world"+
+ demos/ListDemo.hs view
@@ -0,0 +1,41 @@+module Main where++import Data.List+import Data.Maybe++import Barrie++demoWidget :: Widget+demoWidget = vbox [ui "demo list" listView,+ ui "selected" (textLabel ""),+ hbox [ui "newname" textBox, ui "add" (labelButton "Add")]+ ]++listItems :: [String]+listItems = ["Planes", "Trains", "Automobiles"]++type DemoState = ([String], Maybe String, String)++getList :: DemoState -> [String]+getList (items, _, _) = items++addItem :: DemoState -> DemoState+addItem (items, sel, item) = (item:items, sel, "")++type DemoGadget = Gadget DemoState++demoGUI :: DemoGadget+demoGUI = sectionG "demo gui" [chooseEqG "demo list" get set getList,+ displayG "selected" get,+ editorG "newname" getname setname,+ commandG "add" addItem]+ where set sel (items, _, new) = (items, Just sel, new)+ selected (_, Nothing, _) = -1+ selected (items, Just s, _) = maybe (-1) id (elemIndex s items)+ getname (_, _, nm) = nm+ setname nm (x, y, _) = (x,y,nm)+ get (_, Just v, _) = v+ get (_, Nothing, _) = ""++main = gtkMain demoGUI demoWidget (listItems, Nothing, "")+
+ demos/MultiColumnList.hs view
@@ -0,0 +1,34 @@+module Main where++import Data.List+import Data.Maybe++import Barrie+import Barrie.Packing++demoWidget :: Widget+demoWidget = vbox [treeHeaderVisible True $ ui "demo list" listView+ ,ui "selected" (textLabel "")+ ]++listItems :: [(String, Int)]+listItems = [("Planes", 29), ("Trains", 5), ("Automobiles", 52)]++type DemoState = ([(String, Int)], Int)++getList :: DemoState -> [(String, Int)]+getList (items, _) = items++type DemoGadget = Gadget DemoState++demoGUI :: DemoGadget+demoGUI = sectionG "demo gui" [columnTitles ["Product", "Price"] $+ chooserG' "demo list" selected set fst unpack,+ displayG "selected" getSelStr]+ where set sel (items, _) = (items, sel)+ selected (_, sel) = sel+ getSelStr (items, n) | n < 0 = ""+ | otherwise = fst (items !! n)++main = gtkMain demoGUI demoWidget (listItems, 0)+
+ demos/SliderDemo.hs view
@@ -0,0 +1,13 @@+module Main where++import Barrie++sliderW = vbox [ui "x" $ hslider 0 100 1,+ ui "100 - x" $ hslider 0 100 1,+ ui "100 - x" $ textLabel ""+ ]++sliderG = sectionG "slider demo" [editorG "x" id const,+ displayG "100 - x" (100-)]++main = gtkMain sliderG sliderW (50 :: Double)
+ demos/TextBoxDemo.hs view
@@ -0,0 +1,18 @@+module Main where++import Barrie++demoWidget :: Widget+demoWidget = vbox [ui "demo entry" textBox,+ ui "demo command" (labelButton "click me")]++type DemoState = String++type DemoGadget = Gadget DemoState++demoGUI :: DemoGadget+demoGUI = sectionG "demo gui" [editorG "demo entry" id const,+ commandG "demo command" reverse]++main = gtkMain demoGUI demoWidget "Hello, world"+
+ doc/barrie.tex view
@@ -0,0 +1,38 @@+\documentclass{article}+\usepackage{verbatim}+\usepackage{listings}+\title{The Barrie GUI}+\author{Fraser Wilson}+\begin{document}++\maketitle+\tableofcontents++\section{Introduction}++Last year, I read the Fudgets thesis. It was intriguing, and I+started thinking about connecting it to Gtk, so it would be prettier.+That was attempt number one.++I decided that mixing layout with widget definitions was a bad idea,+so I replaced layouts with connections to Glade XML. This was attempt+number two.++A thread on Haskell Caf\'{e} got me thinking more about the separation+of layout and widgets. This is attempt number three.++\section{Gadgets}++\subsection{Editor}++\subsection{Display}++\subsection{Initialiser}++\subsection{Choosers}++\subsection{Return}+++\end{document}+
src/Barrie/Config.hs view
@@ -1,12 +1,19 @@ {-# OPTIONS_GHC -XMultiParamTypeClasses -XFunctionalDependencies #-} -module Barrie.Config where+module Barrie.Config (Config, defaultConfig, transformConfig,+ enabledCfg, isEnabled,+ quitConfig, configQuits,+ labelConfig, configHasLabel, configLabel,+ titleConfig, configTitles)+ where import Data.Typeable data Config a = Cfg { cEnabled :: a -> Bool,- cLabel :: Maybe (a -> String)+ cQuits :: Bool,+ cLabel :: Maybe (a -> String),+ cTitle :: [String] } instance (Typeable a) => Typeable (Config a) where@@ -15,7 +22,11 @@ typeOf (cLabel cfg)] defaultConfig :: Config a-defaultConfig = Cfg { cEnabled = const True, cLabel = Nothing }+defaultConfig = Cfg { cEnabled = const True,+ cQuits = False,+ cLabel = Nothing,+ cTitle = []+ } transformConfig :: (a -> b) -> Config b -> Config a transformConfig get cfg = cfg { cEnabled = cEnabled cfg . get,@@ -25,13 +36,18 @@ 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 +quitConfig :: Bool -> Config a -> Config a+quitConfig q config = config { cQuits = q }++configQuits :: Config a -> Bool+configQuits config = cQuits config+ labelConfig :: (a -> String) -> Config a -> Config a labelConfig f config = config { cLabel = Just f } @@ -44,3 +60,8 @@ configLabel _ _ = error "tried to get a label from a config which has none" +configTitles :: Config a -> [String]+configTitles = cTitle++titleConfig :: [String] -> Config a -> Config a+titleConfig ts config = config { cTitle = ts }
src/Barrie/Gadgets.hs view
@@ -1,10 +1,14 @@ module Barrie.Gadgets (Gadget, Behaviour, displayG, editorG, commandG, sectionG,- chooserG, enumChooserG, chooseEqG, childG,+ chooserG, chooserG', enumChooserG, chooseEqG,+ childG, initG, returnG, gadgetDisplay, gadgetUpdate, gadgetCommand, gadgetChooser, gadgetChild, findGadget, gadgetName, flatName,+ gadgetElementType, gadgetConfig, gadgetEnabled, enabled, dynamicLabel,+ columnTitles,+ renderWith, gadgetRenderer, gadgets, abstract, createLayout) where@@ -23,7 +27,7 @@ data Gadget a = G { gadgetName :: [String], gadgetConfig :: Config a, gadgetElement :: Maybe (Element a),- gadgetInner :: Maybe (Interface a),+ gadgetInner :: Maybe (Gadget a), gadgetLayout :: AutoLayoutFunction a, gadgetChildren :: [Gadget a] }@@ -40,12 +44,10 @@ data Element a = E { elementType :: TypeRep, elementGet :: a -> Dynamic, elementSet :: Dynamic -> a -> a,- elementChoices :: Maybe (a -> [(Dynamic, Dynamic)]),- elementRender :: Dynamic -> Dynamic+ elementChoices :: Maybe (a -> [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] @@ -61,7 +63,7 @@ (a -> b) -> (b -> a -> a) -> Element a-mkElement get set = E tyrep (toDyn . get) (\ x -> set (get' x)) Nothing id+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))@@ -73,16 +75,34 @@ Nothing -> g Just e -> g { gadgetElement = Just $ update e } -renderWith :: (Dynamic -> Dynamic) -> Gadget a -> Gadget a-renderWith r = updateElement (\ e -> e { elementRender = r })+renderWith :: (Typeable b) =>+ ([b -> Dynamic]) -> Gadget a -> Gadget a+renderWith rs = updateElement+ (\ e -> e { elementRender = map (. fromJust . fromDynamic) rs+ }) -setChoices :: (a -> [(Dynamic, Dynamic)]) -> Gadget a -> Gadget a+gadgetRenderer :: Gadget a -> [Dynamic -> Dynamic]+gadgetRenderer G { gadgetElement = Nothing } = []+gadgetRenderer G { gadgetElement = Just e } = elementRender e++setChoices :: (a -> [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 } +lowLevelGadget :: (Typeable b) =>+ String+ -> (a -> b)+ -> (b -> a -> a)+ -> TypeRep+ -> Gadget a+lowLevelGadget name get set ty = mkGadget name elmt { elementType = ty }+ where elmt = mkElement get set+++ displayG :: (Typeable b) => String -> (a -> b) -> Gadget a displayG name get = autoLayout (typeableLayout (typeOf $ get undefined)) $ mkGadget name (mkElement get (\ _ st -> st))@@ -95,9 +115,18 @@ commandG name update = autoLayout buttonLayout $ mkGadget name (mkElement (const ()) (\ () -> update)) +initG :: (Typeable b) => String -> (b -> a) -> Gadget a+initG name initfn = autoLayout textLayout $+ mkGadget name (mkElement initError (\ x _ -> initfn x))+ where initError _ = error "can't get value from an init gadget"+ sectionG :: String -> [Gadget a] -> Gadget a sectionG name gs = G [name] defaultConfig Nothing Nothing boxLayout gs +returnG :: String -> Gadget a+returnG name = G [name] (quitConfig True defaultConfig)+ Nothing Nothing buttonLayout []+ childG :: String -- ^ gadget name -> (a -> b) -- ^ get child state -> (b -> a -> a) -- ^ update parent state with child state@@ -107,7 +136,7 @@ G { gadgetName = [name], gadgetConfig = defaultConfig, gadgetElement = Nothing,- gadgetInner = Just $ IF $ transform get set child,+ gadgetInner = Just $ transform get set child, gadgetLayout = boxLayout, gadgetChildren = [] }@@ -117,18 +146,11 @@ G { gadgetName = gadgetName g, gadgetConfig = transformConfig get (gadgetConfig g), gadgetElement = liftM (transformE get set) (gadgetElement g),- gadgetInner = liftM (transformI get set) (gadgetInner g),+ gadgetInner = liftM (transform 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),@@ -146,20 +168,32 @@ chooserG :: (Typeable b) => String -> (a -> Int)- -> (b -> a -> a)+ -> (Int -> a -> a) -> (a -> [b]) -> Gadget a chooserG name get set getChoices =- autoLayout chooserLayout $ setChoices choices $ editorG name get' set'+ chooserG' name get set getChoices [toDyn]++chooserG' :: (Typeable b) =>+ String+ -> (a -> Int)+ -> (Int -> a -> a)+ -> (a -> [b])+ -> ([b -> Dynamic])+ -> Gadget a+chooserG' name get set getChoices renderer =+ autoLayout chooserLayout $ setChoices choices $ renderWith renderer $+ lowLevelGadget name get' set'+ (typeOf (head (getChoices undefined))) where get' st = (choices st, get st)- set' (vs, v) = if v >= length vs+ set' (_, v) = set 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+ else set (getValues vs !! v) -}+ choices st = map toDyn (getChoices st)+ -- getValues ds = map (fromJust . fromDynamic) ds enumChooserG :: (Enum b, Bounded b, Read b, Show b) => String@@ -168,17 +202,17 @@ -> Gadget a enumChooserG name get set = autoLayout enumLayout $- setChoices elems $ renderWith toStr $ editorG name get' set'+ setChoices elems $ renderWith [renderEnum] $ 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))+ set' (_, v) = if v < 0+ then error ("not setting enum to " ++ show v)+ else set $ toEnum v+ elems st = let vs = [minBound .. maxBound] `asTypeOf` [get st]+ ts = map fromEnum vs+ in map toDyn ts+ renderEnum v = let e = toEnum v+ str = show (e `asTypeOf` get undefined)+ in toDyn str chooseEqG :: (Typeable b, Eq b) => String@@ -186,18 +220,24 @@ -> (b -> a -> a) -> (a -> [b]) -> Gadget a-chooseEqG name get set choices = chooserG name get' set choices+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+ set' v st = set (choices st !! v) st -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)+findGadget :: [String] -> Gadget a -> Maybe (Gadget a)+findGadget [] = Just+findGadget (name:names) = \ top -> case filter fst (tries top) of+ [] -> Nothing+ (_,g):_ -> findGadget names g+ where tries g = [match g, matchInner g] +++ map match (gadgetChildren g) +++ concatMap tries (gadgetChildren g)+ match g = ([name] == gadgetName g, g)+ matchInner g = case gadgetInner g of+ Nothing -> (False, g)+ Just inner -> match inner -- |Gadgets can change their properties based on the value of their state. -- Currently, the following configurations are implemented:@@ -222,12 +262,15 @@ dynamicLabel :: (a -> String) -> Gadget a -> Gadget a dynamicLabel f = addConfig (labelConfig f) +columnTitles :: [String] -> Gadget a -> Gadget a+columnTitles ts = addConfig (titleConfig ts)+ 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+ Just g -> walk (prefix ++ nm) f g gadgets :: Gadget a -> Behaviour a gadgets = B . walk [] getGadget@@ -236,6 +279,9 @@ abstract :: Gadget a -> [String] abstract = map flatName . walk [] (\ name g -> [g { gadgetName = name }]) +gadgetElementType :: Gadget a -> Maybe TypeRep+gadgetElementType g = gadgetElement g >>= return . elementType+ gadgetCommand :: Gadget a -> a -> a gadgetCommand G { gadgetElement = Just e } = elementSet e (toDyn ()) gadgetCommand _ = id@@ -250,7 +296,7 @@ gadgetUpdate g = error $ "Can't connect an update to empty gadget " ++ flatName g -gadgetChooser :: Gadget a -> a -> [(Dynamic, Dynamic)]+gadgetChooser :: Gadget a -> a -> [Dynamic] gadgetChooser g = case getChoices of Nothing -> error $ "Can't connect a chooser to gadget " ++ flatName g@@ -259,9 +305,9 @@ gadgetChild :: Gadget a -> Gadget a gadgetChild g = case gadgetInner g of- Just (IF child) -> child- Nothing -> error $ "Gadget " ++ flatName g ++- " has no inner child"+ Just child -> child+ Nothing -> error $ "Gadget " ++ flatName g +++ " has no inner child" startLayout :: AutoLayout startLayout = AL { alDirection = Vertical,@@ -269,8 +315,8 @@ alNames = [] } -editableLayout :: AutoLayout -> AutoLayout-editableLayout al = al { alEditable = True }+-- editableLayout :: AutoLayout -> AutoLayout+-- editableLayout al = al { alEditable = True } type AutoLayoutFunction a = AutoLayout -> [Gadget a] -> Widget
src/Barrie/Gadgets/Connections.hs view
@@ -1,6 +1,7 @@ module Barrie.Gadgets.Connections (connectChooser, connectCommand, connectDisplay,+ connectUpdater, connectEditor, connectChild) where @@ -9,7 +10,6 @@ import Data.Dynamic import Barrie.Gadgets-import Barrie.Trace connectCommand :: (IO () -> IO ()) -- ^ connection creator -> IO a -- ^ state retriever@@ -40,19 +40,30 @@ apply $ set curr st)) connect (gadgetDisplay gadget) (gadgetUpdate gadget) +connectUpdater :: (IO () -> IO ()) -- ^ connection creator+ -> IO a -- ^ gui state retriever+ -> IO Dynamic -- ^ current value of GUI element+ -> Gadget a -- ^ gadget representing the editor+ -> (a -> IO ()) -- ^ state change applier+ -> IO ()++connectUpdater onAction getState getValue gadget apply =+ onAction (do st <- getState+ curr <- getValue+ apply $ gadgetUpdate gadget curr st)+ -- |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+connectChooser :: ([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 =+connectChooser setChoices setValue gadget = do+ let update getChoices get st = (setChoices (getChoices st) >>- setValue (snd $ fromDyn (get st) ([(get st, get st)],-12)))- update (gadgetChooser gadget) (gadgetDisplay gadget) (gadgetUpdate gadget)+ setValue (snd $ fromDyn (get st) ([get st],-12)))+ update (gadgetChooser gadget) (gadgetDisplay gadget) connectChild :: (IO () -> IO ()) -- ^ show child -> (Gadget a -> a -> IO a) -- ^ child runner@@ -62,7 +73,5 @@ -> 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/Packing.hs view
@@ -0,0 +1,41 @@+module Barrie.Packing where++import Data.Dynamic+import Data.Typeable++class (Typeable a) => Packed a where+ unpack :: [a -> Dynamic]+ unpack = [toDyn]++instance Packed Int where++instance Packed Char where++instance (Packed a) => Packed [a] where++instance (Packed a, Packed b) => Packed (a,b) where+ unpack = [toDyn . fst, toDyn . snd]++instance (Packed a, Packed b, Packed c) => Packed (a,b,c) where+ unpack = [toDyn . get31, toDyn . get32, toDyn . get33]+ where get31 (x,_,_) = x+ get32 (_,x,_) = x+ get33 (_,_,x) = x++instance (Packed a, Packed b, Packed c, Packed d) => Packed (a,b,c,d) where+ unpack = [toDyn . get41, toDyn . get42, toDyn . get43, toDyn.get44]+ where get41 (x,_,_,_) = x+ get42 (_,x,_,_) = x+ get43 (_,_,x,_) = x+ get44 (_,_,_,x) = x++instance (Packed a, Packed b, Packed c, Packed d, Packed e) =>+ Packed (a,b,c,d,e) where+ unpack = [toDyn . get51, toDyn . get52, toDyn . get53,+ toDyn.get54, toDyn.get55]+ where get51 (x,_,_,_,_) = x+ get52 (_,x,_,_,_) = x+ get53 (_,_,x,_,_) = x+ get54 (_,_,_,x,_) = x+ get55 (_,_,_,_,x) = x+
src/Barrie/Render.hs view
@@ -71,16 +71,21 @@ -- grouping, although in practice it will often look similar to the -- widget layout. -renderWidget :: Behaviour w -> Widget -> RenderF r w+renderWidget :: [String] -- ^ name prefix for gadgets+ -> Gadget w -- ^ top gadget+ -> Widget -- ^ rendered widget+ -> RenderF r w -- ^ GUI rendering action -> IO (RunnableGUI r w)-renderWidget behaviour widget render = go widget- where -- go :: StyleSheet -> Layout -> RunnableGUI r w- go w = do let elmt = widgetElementName w+renderWidget prefix behaviour widget render = go widget+ where go w = do let elmt = widgetElementName w style = widgetStyle w- gadget = findGadget (uiElementName w) behaviour+ gadget = if (not . null . uiElementName) w+ then findGadget (uiElementName w)+ behaviour+ else Nothing traceMessage ("rendering element: " ++ elmt); traceMessage ("ui name = " ++- intercalate "." (uiElementName w))+ intercalate "." (uiElementName w)) traceMessage ("gadget = " ++ maybe "Nothing" flatName gadget) ws <- mapM go $ widgetChildren w@@ -99,7 +104,7 @@ [(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
src/Barrie/Render/Gtk.hs view
@@ -1,6 +1,7 @@ module Barrie.Render.Gtk (gtkMain, initGtk, renderGtk, runGtk) where import Data.Dynamic+import Data.Maybe import qualified Graphics.UI.Gtk as Gtk import System.Glib.Attributes@@ -21,7 +22,6 @@ import Barrie.Style import Barrie.Gadgets import Barrie.Gadgets.Connections-import Barrie.Trace import Barrie.Widgets @@ -47,7 +47,6 @@ -> 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@@ -56,7 +55,8 @@ gtkMain :: Gadget a -> Widget -> a -> IO a gtkMain gadget widget state = do initGtk- gui <- renderWidget (gadgets gadget) widget renderGtk+ gui <- renderWidget (gadgetName gadget) gadget+ widget renderGtk runGtk gui state False @@ -120,7 +120,9 @@ ("hslider", renderScale Gtk.hScaleNewWithRange), ("vslider", renderScale Gtk.vScaleNewWithRange), ("vbox", renderBox Gtk.vBoxNew),- ("hbox", renderBox Gtk.hBoxNew)]+ ("hbox", renderBox Gtk.hBoxNew),+ ("fileChooser", renderFileChooser)+ ] -- Currently, we only do popup buttons, but it should be easily -- generalisable.@@ -218,7 +220,8 @@ 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+ inner = \ g st -> do gui <- renderWidget (gadgetName g) g+ popup renderGtk runGtk gui st True clickUpdate = \ gets -> case gadget of Nothing -> \ _ -> return ()@@ -234,6 +237,9 @@ let cfg = gadgetConfig gadget when (configHasLabel cfg) $ do Gtk.buttonSetLabel btn (configLabel cfg state)+ when (configQuits cfg) $ do+ let closeCurrent w = Gtk.widgetGetToplevel w >>= Gtk.widgetDestroy+ Gtk.onClicked btn (closeCurrent btn) >> return () configureWidget btn style (Just gadget) update state configureButton _ _ _ update state = update state@@ -324,13 +330,15 @@ 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 (),+data ModelRender = MR { mrSetChoices :: [Dynamic] -> IO (), mrSetSelection :: Int -> IO (), mrGetValue :: IO Dynamic, mrHasChanged :: Dynamic -> IO Bool@@ -339,37 +347,30 @@ -- |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)])+renderListModel :: IO (Gtk.ListStore Dynamic) -- ^ get model from widget+ -> (IO () -> IO ()) -- ^ selection change handler+ -> IO Int -- ^ current selection from widget+ -> (Int -> IO ()) -- ^ set selection+ -> IO ModelRender+renderListModel getModel onChanged getCursor setCursor = do+ currentChoices <- newIORef ([] :: [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)])+ writeIORef currentChoices (xs :: [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"+ let (_, x) = fromDyn d ([d], -100)+ when (x == (-100)) (putStrLn $ "trouble converting " ++ show d) curr <- getCursor return (x /= curr) return MR { mrSetChoices = setChoices,@@ -380,21 +381,23 @@ renderChooser :: GtkRenderFn a renderChooser _ style gadget = do- store <- Gtk.listStoreNew ([] :: [(Dynamic, Dynamic)])+ store <- Gtk.listStoreNew ([] :: [Dynamic]) chooser <- Gtk.comboBoxNewWithModel store r <- Gtk.cellRendererTextNew Gtk.cellLayoutPackStart chooser r True- Gtk.cellLayoutSetAttributes chooser r store $- \ row -> [ Gtk.cellText := renderText $ fst row ]+ case gadget of+ Nothing -> return ()+ Just g -> Gtk.cellLayoutSetAttributes chooser r store $+ \ row -> [ Gtk.cellText := renderText (head (gadgetRenderer g) 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+ mr <- renderListModel getModel onChanged getSel setSel let connect = case gadget of Nothing -> \ _ -> return ()- Just g -> connectChooser (mrSetChoices mr) (mrSetSelection mr)- (mrGetValue mr) g+ Just g -> connectChooser (mrSetChoices mr)+ (mrSetSelection mr) g update getSt = case gadget of Nothing -> \ _-> return () Just g -> connectEditor onChanged getSt@@ -414,16 +417,30 @@ 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++addTextColumn :: Gtk.TreeView -- ^ view that gets the column+ -> Gtk.ListStore Dynamic+ -> (Dynamic -> Dynamic) -- ^ column data fetcher+ -> IO ()+addTextColumn view store fetch = do 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+ \ row -> [ Gtk.cellText := renderText (fetch row) ]+ Gtk.treeViewAppendColumn view col+ return ()++renderSingleColumnList :: GtkRenderFn a+renderSingleColumnList _ style gadget = do+ store <- Gtk.listStoreNew []+ list <- Gtk.treeViewNewWithModel store+ case gadget of+ Nothing -> return ()+ Just g -> do mapM_ (addTextColumn list store) (gadgetRenderer g)+ cols <- Gtk.treeViewGetColumns list+ mapM_ (\ (c,t) -> Gtk.treeViewColumnSetTitle c t)+ (zip cols (configTitles (gadgetConfig g))) let getModel = return store onChanged = \ handler -> Gtk.onCursorChanged list handler >> return ()@@ -434,11 +451,11 @@ _ -> 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+ mr <- renderListModel getModel onChanged getSel setSel let connect = case gadget of Nothing -> \ _ -> return ()- Just g -> connectChooser (mrSetChoices mr) (mrSetSelection mr)- (mrGetValue mr) g+ Just g -> connectChooser (mrSetChoices mr)+ (mrSetSelection mr) g update getSt = case gadget of Nothing -> \ _-> return () Just g -> connectEditor onChanged getSt@@ -447,3 +464,66 @@ return (Gtk.castToWidget list, configureTreeView list style gadget connect, update)++renderFileChooser :: GtkRenderFn a+renderFileChooser ws style gadget = do+ case fileChooserAction style of+ FileOpen -> renderFileOpen ws style gadget+ FileSave -> renderFileSave ws style gadget++renderFileOpen :: GtkRenderFn a+renderFileOpen _ style gadget = do+ chooser <- Gtk.fileChooserDialogNew (getCaption style) Nothing+ Gtk.FileChooserActionOpen+ [("Open", Gtk.ResponseOk),+ ("Cancel", Gtk.ResponseCancel)]+ Gtk.fileChooserSetDoOverwriteConfirmation chooser True+ let runChooseFile = do response <- Gtk.dialogRun chooser+ Gtk.widgetHide chooser+ if response == Gtk.ResponseOk+ then do+ fname <- Gtk.fileChooserGetFilename chooser+ case fname of+ Nothing -> return (toDyn "")+ Just name -> readFile name >>= return . toDyn+ else return (toDyn "")++ btn <- Gtk.buttonNew+ applyStyle (Gtk.buttonSetLabel btn) (getCaption style)+ let onClicked = \ handler -> Gtk.onClicked btn handler >> return ()+ clickUpdate = \ gets -> case gadget of+ Just g -> connectUpdater onClicked gets+ runChooseFile g+ Nothing -> const (return ())+ return (Gtk.castToWidget btn,+ configureButton btn style gadget (const (return ())),+ clickUpdate)++renderFileSave :: GtkRenderFn a+renderFileSave _ style gadget = do+ chooser <- Gtk.fileChooserDialogNew (getCaption style) Nothing+ Gtk.FileChooserActionSave+ [("Save", Gtk.ResponseOk),+ ("Cancel", Gtk.ResponseCancel)]++ let runSaveFile gets getStrVal =+ do response <- Gtk.dialogRun chooser+ Gtk.widgetHide chooser+ when (response == Gtk.ResponseOk) $ do+ fname <- Gtk.fileChooserGetFilename chooser+ case fname of+ Nothing -> return ()+ Just name -> do+ st <- gets+ writeFile name (renderText (getStrVal st))++ btn <- Gtk.buttonNew+ applyStyle (Gtk.buttonSetLabel btn) (getCaption style)+ let onClicked = \ handler -> Gtk.onClicked btn handler >> return ()+ clickUpdate = \ gets _ -> case gadget of+ Just g -> onClicked $+ runSaveFile gets (gadgetDisplay g)+ Nothing -> return ()+ return (Gtk.castToWidget btn,+ configureButton btn style gadget (const (return ())),+ clickUpdate)
src/Barrie/Style.hs view
@@ -1,6 +1,7 @@ {-# LANGUAGE GeneralizedNewtypeDeriving #-} module Barrie.Style (Style, Styled(..), addStyle, emptyStyle,+ mkStyle, styleValue, caption, getCaption, hideDisabled, getHideDisabled, homogenous, getHomogenous,@@ -41,7 +42,7 @@ caption :: (Styled a) => String -> a -> a caption text = addStyle $ mkStyle "caption" text -getCaption :: (Styled a) => a -> Maybe StyleValue+getCaption :: (Styled a) => a -> Maybe String getCaption = styleValue "caption" textValue :: (Styled a) => String -> a -> a
src/Barrie/Widgets.hs view
@@ -7,7 +7,9 @@ vbox, hbox, menu, menuItem, button, labelButton, checkButton, textLabel, slider, hslider, vslider, staticDropList, dropList, listView, radioButtonList,- textBox, textBoxWithValue)+ textBox, textBoxWithValue,+ FileChooserAction(..),+ fileChooserAction, fileChooser, fileOpen, fileSave) where import Data.List@@ -121,3 +123,17 @@ textBoxWithValue :: String -> Widget textBoxWithValue text = textValue text textBox++data FileChooserAction = FileOpen | FileSave deriving (Read, Show)++fileChooser :: FileChooserAction -> String -> Widget+fileChooser action label =+ addStyle (mkStyle "fileChooserAction" (show action)) $+ caption label $ mkWidget "fileChooser" []++fileOpen, fileSave :: String -> Widget+fileOpen = fileChooser FileOpen+fileSave = fileChooser FileSave++fileChooserAction :: (Styled a) => a -> FileChooserAction+fileChooserAction = maybe FileOpen read . styleValue "fileChooserAction"