cairo-appbase 0.2 → 0.3
raw patch · 2 files changed
+124/−49 lines, 2 filesdep ~base
Dependency ranges changed: base
Files
- cairo-appbase.cabal +84/−25
- src/cairo-appbase.hs +40/−24
cairo-appbase.cabal view
@@ -1,28 +1,87 @@-Cabal-Version: >= 1.6-Name: cairo-appbase-Version: 0.2-Synopsis: Template code for an app using GTK, Glade and Cairo graphics-Description: This is a template for building new GUI applications. The- GTK widget layout is done via a Glade XML file which can be- edited visually using glade.+Name: cairo-appbase - This template includes working callbacks to handle the File- and Help menus and File Save/Open dialogs, with dummy- handlers for selecting filenames and the Edit menu's- cut/copy/paste.+Version: 0.3 - The main canvas for this application uses Cairo for- graphics rendering, and includes example code from the- cairo package.-License: BSD3-License-file: LICENSE-Author: Conrad Parker, Johan Bockgård-Maintainer: conrad@metadecks.org-Category: GUI-Build-Depends: base < 5, glib, gtk, glade, cairo-Build-Type: Simple-Data-Files: data/main.glade+Synopsis: A template for building new GUI applications using GTK, Glade and Cairo. -Executable: cairo-appbase-Main-Is: cairo-appbase.hs-Hs-Source-Dirs: src+Description:+ This template includes working callbacks to handle the File and Help+ menus and File Save/Open dialogs, with dummy handlers for selecting+ filenames and the Edit menu's Cut, Copy, and Paste. The main canvas uses+ Cairo for graphics rendering, and includes example code from the cairo+ package.+ .+ To build your own application on top of this, first grab the code. You+ can either grab it from hackage with @cabal unpack cairo-appbase@, or+ clone the git repo:+ .+ > git clone git://github.com/kfish/cairo-appbase.git+ .+ To add widgets, install glade from your distro system and run+ @glade data/main.glade@+ .+ Note that you must run @cabal install@ to put the glade file in the correct+ place for your application to pick it up. To modify the code, edit+ @src/cairo-appbase.hs@. Hooking up functions to widgets is very simple: get+ a widget by name (which you set in glade file), and hook one of its+ signals (which you found in the Signals tab in glade) to an @IO ()@ action:+ .+ > cut1 <- get G.castToMenuItem "cut1"+ > G.onActivateLeaf cut1 $ myCut+ .+ The template code includes a trivial definition of myCut:+ .+ > myCut :: IO ()+ > myCut = putStrLn "Cut"+ .+ A real application will want to pass data to the callback. In C, this is+ fairly tedious as you only have a single void * to pass to callbacks as+ @user_data@, and applications typically do lots of marshalling and+ unmarshalling to pass data around. In Haskell however, you can make+ yourself a more complex callback handler and use a curried version of it+ in each instance:+ .+ > cut1 <- get G.castToMenuItem "cut1"+ > G.onActivateLeaf cut1 $ myComplexCut project phase 7+ >+ > ...+ >+ > myCut :: Project -> MoonPhase -> LuckyNumber -> IO ()+ > myCut project phase num = do+ > let selection = currentSelection project+ > when (phase == Full) howl+ > when (num /= 7) fail+ > doActualCut selection+ .++License: BSD3+License-file: LICENSE+Author: Conrad Parker, Johan Bockgård+Maintainer: conrad@metadecks.org+Category: Development++Cabal-Version: >= 1.8+Build-type: Simple+Data-Files: data/main.glade++flag splitBase+ description: Use the split-up base package.++Executable cairo-appbase+ if flag(splitBase)+ build-depends:+ base >= 3 && < 6+ else+ build-depends:+ base < 3++ Main-Is: cairo-appbase.hs+ Hs-Source-Dirs: src+ Build-Depends: glib, gtk, glade, cairo++------------------------------------------------------------------------+-- Git repo+--+source-repository head+ type: git+ location: git://github.com/kfish/cairo-appbase.git
src/cairo-appbase.hs view
@@ -1,3 +1,4 @@+{-# OPTIONS -Wall #-} -- -- Based on Gtk2Hs/demo/cairo/Drawing2.hs -- Author: Johan Bockgård <bojohan@dd.chalmers.se>@@ -5,6 +6,7 @@ -- This code is in the public domain. -- +import Control.Monad (replicateM_) import qualified System.Glib.Types as GTypes import qualified Graphics.UI.Gtk as G import qualified Graphics.UI.Gtk.Glade as Glade@@ -18,8 +20,8 @@ windowHeight = 500 -- Write image to file-writePng :: IO ()-writePng =+_writePng :: IO ()+_writePng = C.withImageSurface C.FormatARGB32 width height $ \ result -> do C.renderWith result $ example width height C.surfaceWriteToPNG result "Draw.png"@@ -27,8 +29,9 @@ height = windowHeight -- Display image in window+main :: IO () main = do- G.initGUI+ _ <- G.initGUI -- load up the glade file filename <- My.getDataFileName "data/main.glade"@@ -44,37 +47,37 @@ -- set up File->New new1 <- get G.castToMenuItem "new1"- G.onActivateLeaf new1 $ myNew+ _ <- G.onActivateLeaf new1 $ myNew -- set up the File->Open dialog open1 <- get G.castToMenuItem "open1" openDialog <- get G.castToFileChooserDialog "opendialog"- G.onActivateLeaf open1 $ G.widgetShow openDialog- G.onResponse openDialog $ myFileOpen openDialog+ _ <- G.onActivateLeaf open1 $ G.widgetShow openDialog+ _ <- G.onResponse openDialog $ myFileOpen openDialog -- set up the File->Save_As dialog save1 <- get G.castToMenuItem "save1" save_as1 <- get G.castToMenuItem "save_as1" saveDialog <- get G.castToFileChooserDialog "savedialog"- G.onActivateLeaf save_as1 $ G.widgetShow saveDialog- G.onActivateLeaf save1 $ G.widgetShow saveDialog- G.onResponse saveDialog $ myFileSave saveDialog+ _ <- G.onActivateLeaf save_as1 $ G.widgetShow saveDialog+ _ <- G.onActivateLeaf save1 $ G.widgetShow saveDialog+ _ <- G.onResponse saveDialog $ myFileSave saveDialog -- set up Edit menu cut1 <- get G.castToMenuItem "cut1"- G.onActivateLeaf cut1 $ myCut+ _ <- G.onActivateLeaf cut1 $ myCut copy1 <- get G.castToMenuItem "copy1"- G.onActivateLeaf copy1 $ myCopy+ _ <- G.onActivateLeaf copy1 $ myCopy paste1 <- get G.castToMenuItem "paste1"- G.onActivateLeaf paste1 $ myPaste+ _ <- G.onActivateLeaf paste1 $ myPaste delete1 <- get G.castToMenuItem "delete1"- G.onActivateLeaf delete1 $ myDelete+ _ <- G.onActivateLeaf delete1 $ myDelete -- set up the Help->About dialog about1 <- get G.castToMenuItem "about1" aboutdialog1 <- get G.castToAboutDialog "aboutdialog1"- G.onActivateLeaf about1 $ G.widgetShow aboutdialog1- G.onResponse aboutdialog1 $ const $ G.widgetHide aboutdialog1+ _ <- G.onActivateLeaf about1 $ G.widgetShow aboutdialog1+ _ <- G.onResponse aboutdialog1 $ const $ G.widgetHide aboutdialog1 -- fix size -- G.windowSetResizable window False@@ -82,12 +85,12 @@ -- quit on File->Quit menu selection quit1 <- get G.castToMenuItem "quit1"- G.onActivateLeaf quit1 $ G.widgetDestroy window- G.onDestroy window G.mainQuit+ _ <- G.onActivateLeaf quit1 $ G.widgetDestroy window+ _ <- G.onDestroy window G.mainQuit -- set up the canvas canvas <- get G.castToDrawingArea "drawingarea1"- G.onExpose canvas $ const (updateCanvas canvas)+ _ <- G.onExpose canvas $ const (updateCanvas canvas) G.widgetShowAll window G.mainGUI @@ -102,6 +105,7 @@ G.ResponseCancel -> putStrLn "Cancelled!" G.ResponseDeleteEvent -> putStrLn "FileChooserDialog Deleted!" G.ResponseClose -> putStrLn "Closed!"+ _ -> return () G.widgetHide fcdialog myFileSave :: G.FileChooserDialog -> G.ResponseId -> IO ()@@ -121,8 +125,8 @@ updateCanvas :: G.DrawingArea -> IO Bool updateCanvas canvas = do- win <- G.drawingAreaGetDrawWindow canvas- (width, height) <- G.drawingAreaGetSize canvas+ win <- G.widgetGetDrawWindow canvas+ (width, height) <- G.widgetGetSize canvas G.renderWithDrawable win $ example width height return True@@ -132,24 +136,29 @@ foreach :: (Monad m) => [a] -> (a -> m b) -> m [b] foreach = flip mapM +keepState :: C.Render t -> C.Render () keepState render = do C.save- render+ _ <- render C.restore +drawCircle :: Double -> Double -> Double -> C.Render () drawCircle x y r = do C.arc x y r 0 (2 * pi) fillStroke +drawRectangle :: Double -> Double -> Double -> Double -> C.Render () drawRectangle x y w h = do C.rectangle x y w h fillStroke +stroke :: C.Render () stroke = keepState $ do C.setSourceRGBA 0 0 0 0.7 C.stroke +fillStroke :: C.Render () fillStroke = do C.fillPreserve stroke@@ -158,11 +167,13 @@ -- Example +example :: Int -> Int -> C.Render () example width height = do prologue width height example1 -- Set up stuff+prologue :: Int -> Int -> C.Render () prologue wWidth wHeight = do let width = 10 height = 10@@ -191,6 +202,7 @@ -- Grid and axes+grid :: Double -> Double -> Double -> Double -> C.Render () grid xmin xmax ymin ymax = keepState $ do C.setSourceRGBA 0 0 0 0.7@@ -204,20 +216,22 @@ C.lineTo x ymax C.stroke +example1 :: C.Render () example1 = do -- circles drawCircle 0 0 1 drawCircle 2 2 3 -- a bunch of rectangles keepState $- foreach [1 .. 5] $ \ _ ->- do drawRectangle 0 1 2 3- C.rotate (pi/8)+ replicateM_ 5 $ do+ drawRectangle 0 1 2 3+ C.rotate (pi/8) -- some cute stuff thought apple snake +thought :: C.Render () thought = keepState $ do C.scale 0.04 0.04@@ -274,6 +288,7 @@ c 146 424 144 422 143 422 z +apple :: C.Render () apple = keepState $ do C.scale 0.05 0.05@@ -309,6 +324,7 @@ c 1153 249 1152 249 1151 249 z +snake :: C.Render () snake = keepState $ do C.scale 0.04 0.04