cairo-appbase (empty) → 0.2
raw patch · 5 files changed
+755/−0 lines, 5 filesdep +basedep +cairodep +gladesetup-changed
Dependencies added: base, cairo, glade, glib, gtk
Files
- LICENSE +27/−0
- Setup.hs +3/−0
- cairo-appbase.cabal +28/−0
- data/main.glade +358/−0
- src/cairo-appbase.hs +339/−0
+ LICENSE view
@@ -0,0 +1,27 @@+Copyright (c) Conrad Parker 2006++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions+are met:+1. Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.+2. Redistributions in binary form must reproduce the above copyright+ notice, this list of conditions and the following disclaimer in the+ documentation and/or other materials provided with the distribution.+3. Neither the name of the author nor the names of his contributors+ may be used to endorse or promote products derived from this software+ without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE+ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF+SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,3 @@+#!/usr/bin/env runhaskell+import Distribution.Simple+main = defaultMain
+ cairo-appbase.cabal view
@@ -0,0 +1,28 @@+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.++ 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.++ 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++Executable: cairo-appbase+Main-Is: cairo-appbase.hs+Hs-Source-Dirs: src
+ data/main.glade view
@@ -0,0 +1,358 @@+<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->+<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">++<glade-interface>++<widget class="GtkWindow" id="window1">+ <property name="visible">True</property>+ <property name="title" translatable="yes">window1</property>+ <property name="type">GTK_WINDOW_TOPLEVEL</property>+ <property name="window_position">GTK_WIN_POS_NONE</property>+ <property name="modal">False</property>+ <property name="resizable">True</property>+ <property name="destroy_with_parent">False</property>+ <property name="decorated">True</property>+ <property name="skip_taskbar_hint">False</property>+ <property name="skip_pager_hint">False</property>+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_NORMAL</property>+ <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>+ <property name="focus_on_map">True</property>+ <property name="urgency_hint">False</property>++ <child>+ <widget class="GtkVBox" id="vbox1">+ <property name="visible">True</property>+ <property name="homogeneous">False</property>+ <property name="spacing">0</property>++ <child>+ <widget class="GtkMenuBar" id="menubar1">+ <property name="visible">True</property>+ <property name="pack_direction">GTK_PACK_DIRECTION_LTR</property>+ <property name="child_pack_direction">GTK_PACK_DIRECTION_LTR</property>++ <child>+ <widget class="GtkMenuItem" id="menuitem1">+ <property name="visible">True</property>+ <property name="label" translatable="yes">_File</property>+ <property name="use_underline">True</property>++ <child>+ <widget class="GtkMenu" id="menu1">++ <child>+ <widget class="GtkImageMenuItem" id="new1">+ <property name="visible">True</property>+ <property name="label">gtk-new</property>+ <property name="use_stock">True</property>+ <signal name="activate" handler="on_new1_activate" last_modification_time="Sat, 05 Aug 2006 08:27:01 GMT"/>+ </widget>+ </child>++ <child>+ <widget class="GtkImageMenuItem" id="open1">+ <property name="visible">True</property>+ <property name="label">gtk-open</property>+ <property name="use_stock">True</property>+ <signal name="activate" handler="on_open1_activate" last_modification_time="Sat, 05 Aug 2006 08:27:01 GMT"/>+ </widget>+ </child>++ <child>+ <widget class="GtkImageMenuItem" id="save1">+ <property name="visible">True</property>+ <property name="label">gtk-save</property>+ <property name="use_stock">True</property>+ <signal name="activate" handler="on_save1_activate" last_modification_time="Sat, 05 Aug 2006 08:27:01 GMT"/>+ </widget>+ </child>++ <child>+ <widget class="GtkImageMenuItem" id="save_as1">+ <property name="visible">True</property>+ <property name="label">gtk-save-as</property>+ <property name="use_stock">True</property>+ <signal name="activate" handler="on_save_as1_activate" last_modification_time="Sat, 05 Aug 2006 08:27:01 GMT"/>+ </widget>+ </child>++ <child>+ <widget class="GtkSeparatorMenuItem" id="separatormenuitem1">+ <property name="visible">True</property>+ </widget>+ </child>++ <child>+ <widget class="GtkImageMenuItem" id="quit1">+ <property name="visible">True</property>+ <property name="label">gtk-quit</property>+ <property name="use_stock">True</property>+ <signal name="activate" handler="on_quit1_activate" last_modification_time="Sat, 05 Aug 2006 08:27:01 GMT"/>+ </widget>+ </child>+ </widget>+ </child>+ </widget>+ </child>++ <child>+ <widget class="GtkMenuItem" id="menuitem2">+ <property name="visible">True</property>+ <property name="label" translatable="yes">_Edit</property>+ <property name="use_underline">True</property>++ <child>+ <widget class="GtkMenu" id="menu2">++ <child>+ <widget class="GtkImageMenuItem" id="cut1">+ <property name="visible">True</property>+ <property name="label">gtk-cut</property>+ <property name="use_stock">True</property>+ <signal name="activate" handler="on_cut1_activate" last_modification_time="Sat, 05 Aug 2006 08:27:01 GMT"/>+ </widget>+ </child>++ <child>+ <widget class="GtkImageMenuItem" id="copy1">+ <property name="visible">True</property>+ <property name="label">gtk-copy</property>+ <property name="use_stock">True</property>+ <signal name="activate" handler="on_copy1_activate" last_modification_time="Sat, 05 Aug 2006 08:27:01 GMT"/>+ </widget>+ </child>++ <child>+ <widget class="GtkImageMenuItem" id="paste1">+ <property name="visible">True</property>+ <property name="label">gtk-paste</property>+ <property name="use_stock">True</property>+ <signal name="activate" handler="on_paste1_activate" last_modification_time="Sat, 05 Aug 2006 08:27:01 GMT"/>+ </widget>+ </child>++ <child>+ <widget class="GtkImageMenuItem" id="delete1">+ <property name="visible">True</property>+ <property name="label">gtk-delete</property>+ <property name="use_stock">True</property>+ <signal name="activate" handler="on_delete1_activate" last_modification_time="Sat, 05 Aug 2006 08:27:01 GMT"/>+ </widget>+ </child>+ </widget>+ </child>+ </widget>+ </child>++ <child>+ <widget class="GtkMenuItem" id="menuitem3">+ <property name="visible">True</property>+ <property name="label" translatable="yes">_View</property>+ <property name="use_underline">True</property>++ <child>+ <widget class="GtkMenu" id="menu3">+ </widget>+ </child>+ </widget>+ </child>++ <child>+ <widget class="GtkMenuItem" id="menuitem4">+ <property name="visible">True</property>+ <property name="label" translatable="yes">_Help</property>+ <property name="use_underline">True</property>++ <child>+ <widget class="GtkMenu" id="menu4">++ <child>+ <widget class="GtkMenuItem" id="about1">+ <property name="visible">True</property>+ <property name="label" translatable="yes">_About</property>+ <property name="use_underline">True</property>+ <signal name="activate" handler="on_about1_activate" last_modification_time="Sat, 05 Aug 2006 08:27:01 GMT"/>+ </widget>+ </child>+ </widget>+ </child>+ </widget>+ </child>+ </widget>+ <packing>+ <property name="padding">0</property>+ <property name="expand">False</property>+ <property name="fill">False</property>+ </packing>+ </child>++ <child>+ <widget class="GtkDrawingArea" id="drawingarea1">+ <property name="visible">True</property>+ </widget>+ <packing>+ <property name="padding">0</property>+ <property name="expand">True</property>+ <property name="fill">True</property>+ </packing>+ </child>++ <child>+ <widget class="GtkStatusbar" id="statusbar1">+ <property name="visible">True</property>+ <property name="has_resize_grip">True</property>+ </widget>+ <packing>+ <property name="padding">0</property>+ <property name="expand">False</property>+ <property name="fill">False</property>+ </packing>+ </child>+ </widget>+ </child>+</widget>++<widget class="GtkAboutDialog" id="aboutdialog1">+ <property name="destroy_with_parent">False</property>+ <property name="name" translatable="yes">Application Name</property>+ <property name="wrap_license">False</property>+ <property name="translator_credits" translatable="yes" comments="TRANSLATORS: Replace this string with your names, one name per line.">translator-credits</property>+</widget>++<widget class="GtkFileChooserDialog" id="opendialog">+ <property name="action">GTK_FILE_CHOOSER_ACTION_OPEN</property>+ <property name="local_only">True</property>+ <property name="select_multiple">False</property>+ <property name="show_hidden">False</property>+ <property name="do_overwrite_confirmation">False</property>+ <property name="type">GTK_WINDOW_TOPLEVEL</property>+ <property name="window_position">GTK_WIN_POS_NONE</property>+ <property name="modal">False</property>+ <property name="resizable">True</property>+ <property name="destroy_with_parent">False</property>+ <property name="decorated">True</property>+ <property name="skip_taskbar_hint">False</property>+ <property name="skip_pager_hint">False</property>+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>+ <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>+ <property name="focus_on_map">True</property>+ <property name="urgency_hint">False</property>++ <child internal-child="vbox">+ <widget class="GtkVBox" id="dialog-vbox1">+ <property name="visible">True</property>+ <property name="homogeneous">False</property>+ <property name="spacing">24</property>++ <child internal-child="action_area">+ <widget class="GtkHButtonBox" id="dialog-action_area1">+ <property name="visible">True</property>+ <property name="layout_style">GTK_BUTTONBOX_END</property>++ <child>+ <widget class="GtkButton" id="button1">+ <property name="visible">True</property>+ <property name="can_default">True</property>+ <property name="can_focus">True</property>+ <property name="label">gtk-cancel</property>+ <property name="use_stock">True</property>+ <property name="relief">GTK_RELIEF_NORMAL</property>+ <property name="focus_on_click">True</property>+ <property name="response_id">-6</property>+ </widget>+ </child>++ <child>+ <widget class="GtkButton" id="opendialog-Open-button">+ <property name="visible">True</property>+ <property name="can_default">True</property>+ <property name="has_default">True</property>+ <property name="can_focus">True</property>+ <property name="label">gtk-open</property>+ <property name="use_stock">True</property>+ <property name="relief">GTK_RELIEF_NORMAL</property>+ <property name="focus_on_click">True</property>+ <property name="response_id">-5</property>+ </widget>+ </child>+ </widget>+ <packing>+ <property name="padding">0</property>+ <property name="expand">False</property>+ <property name="fill">True</property>+ <property name="pack_type">GTK_PACK_END</property>+ </packing>+ </child>+ </widget>+ </child>+</widget>++<widget class="GtkFileChooserDialog" id="savedialog">+ <property name="action">GTK_FILE_CHOOSER_ACTION_SAVE</property>+ <property name="local_only">True</property>+ <property name="select_multiple">False</property>+ <property name="show_hidden">False</property>+ <property name="do_overwrite_confirmation">False</property>+ <property name="type">GTK_WINDOW_TOPLEVEL</property>+ <property name="window_position">GTK_WIN_POS_NONE</property>+ <property name="modal">False</property>+ <property name="resizable">True</property>+ <property name="destroy_with_parent">False</property>+ <property name="decorated">True</property>+ <property name="skip_taskbar_hint">False</property>+ <property name="skip_pager_hint">False</property>+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>+ <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>+ <property name="focus_on_map">True</property>+ <property name="urgency_hint">False</property>++ <child internal-child="vbox">+ <widget class="GtkVBox" id="dialog-vbox2">+ <property name="visible">True</property>+ <property name="homogeneous">False</property>+ <property name="spacing">24</property>++ <child internal-child="action_area">+ <widget class="GtkHButtonBox" id="dialog-action_area2">+ <property name="visible">True</property>+ <property name="layout_style">GTK_BUTTONBOX_END</property>++ <child>+ <widget class="GtkButton" id="button2">+ <property name="visible">True</property>+ <property name="can_default">True</property>+ <property name="can_focus">True</property>+ <property name="label">gtk-cancel</property>+ <property name="use_stock">True</property>+ <property name="relief">GTK_RELIEF_NORMAL</property>+ <property name="focus_on_click">True</property>+ <property name="response_id">-6</property>+ </widget>+ </child>++ <child>+ <widget class="GtkButton" id="button3">+ <property name="visible">True</property>+ <property name="can_default">True</property>+ <property name="has_default">True</property>+ <property name="can_focus">True</property>+ <property name="label">gtk-save</property>+ <property name="use_stock">True</property>+ <property name="relief">GTK_RELIEF_NORMAL</property>+ <property name="focus_on_click">True</property>+ <property name="response_id">-5</property>+ </widget>+ </child>+ </widget>+ <packing>+ <property name="padding">0</property>+ <property name="expand">False</property>+ <property name="fill">True</property>+ <property name="pack_type">GTK_PACK_END</property>+ </packing>+ </child>+ </widget>+ </child>+</widget>++</glade-interface>
+ src/cairo-appbase.hs view
@@ -0,0 +1,339 @@+--+-- Based on Gtk2Hs/demo/cairo/Drawing2.hs +-- Author: Johan Bockgård <bojohan@dd.chalmers.se>+--+-- This code is in the public domain.+--++import qualified System.Glib.Types as GTypes+import qualified Graphics.UI.Gtk as G+import qualified Graphics.UI.Gtk.Glade as Glade+import qualified Graphics.Rendering.Cairo as C+import qualified Graphics.Rendering.Cairo.Matrix as M++import Paths_cairo_appbase as My++windowWidth, windowHeight :: Int+windowWidth = 500+windowHeight = 500++-- Write image to file+writePng :: IO ()+writePng =+ C.withImageSurface C.FormatARGB32 width height $ \ result -> do+ C.renderWith result $ example width height+ C.surfaceWriteToPNG result "Draw.png"+ where width = windowWidth+ height = windowHeight++-- Display image in window+main = do+ G.initGUI++ -- load up the glade file+ filename <- My.getDataFileName "data/main.glade"+ windowXmlM <- Glade.xmlNew filename+ let windowXml = case windowXmlM of+ (Just wX) -> wX+ Nothing -> error ("can't find the glade file " ++ filename)+ get :: (G.WidgetClass widget) => (GTypes.GObject -> widget) -> String -> IO widget+ get = Glade.xmlGetWidget windowXml++ -- get a handle on widgets from the glade file+ window <- get G.castToWindow "window1"++ -- set up File->New+ new1 <- get G.castToMenuItem "new1"+ 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++ -- 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++ -- set up Edit menu+ cut1 <- get G.castToMenuItem "cut1"+ G.onActivateLeaf cut1 $ myCut+ copy1 <- get G.castToMenuItem "copy1"+ G.onActivateLeaf copy1 $ myCopy+ paste1 <- get G.castToMenuItem "paste1"+ G.onActivateLeaf paste1 $ myPaste+ delete1 <- get G.castToMenuItem "delete1"+ 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++ -- fix size+ -- G.windowSetResizable window False+ G.widgetSetSizeRequest window windowWidth windowHeight++ -- quit on File->Quit menu selection+ quit1 <- get G.castToMenuItem "quit1"+ 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.widgetShowAll window+ G.mainGUI++myNew :: IO ()+myNew = putStrLn "New"++myFileOpen :: G.FileChooserDialog -> G.ResponseId -> IO ()+myFileOpen fcdialog response = do+ case response of+ G.ResponseOk -> do Just filename <- G.fileChooserGetFilename fcdialog+ putStrLn filename+ G.ResponseCancel -> putStrLn "Cancelled!"+ G.ResponseDeleteEvent -> putStrLn "FileChooserDialog Deleted!"+ G.ResponseClose -> putStrLn "Closed!"+ G.widgetHide fcdialog++myFileSave :: G.FileChooserDialog -> G.ResponseId -> IO ()+myFileSave = myFileOpen++myCut :: IO ()+myCut = putStrLn "Cut"++myCopy :: IO ()+myCopy = putStrLn "Copy"++myPaste :: IO ()+myPaste = putStrLn "Paste"++myDelete :: IO ()+myDelete = putStrLn "Delete"++updateCanvas :: G.DrawingArea -> IO Bool+updateCanvas canvas = do+ win <- G.drawingAreaGetDrawWindow canvas+ (width, height) <- G.drawingAreaGetSize canvas+ G.renderWithDrawable win $+ example width height+ return True++----------------------------------------------------------------++foreach :: (Monad m) => [a] -> (a -> m b) -> m [b]+foreach = flip mapM++keepState render = do+ C.save+ render+ C.restore++drawCircle x y r = do+ C.arc x y r 0 (2 * pi)+ fillStroke++drawRectangle x y w h = do+ C.rectangle x y w h+ fillStroke++stroke =+ keepState $ do+ C.setSourceRGBA 0 0 0 0.7+ C.stroke++fillStroke = do+ C.fillPreserve+ stroke++----------------------------------------------------------------++-- Example++example width height = do+ prologue width height+ example1++-- Set up stuff+prologue wWidth wHeight = do+ let width = 10+ height = 10+ xmax = width / 2+ xmin = - xmax+ ymax = height / 2+ ymin = - ymax+ scaleX = realToFrac wWidth / width+ scaleY = realToFrac wHeight / height++ -- style and color+ C.setLineCap C.LineCapRound+ C.setLineJoin C.LineJoinRound+ C.setLineWidth $ 1 / max scaleX scaleY+ C.setSourceRGBA 0.5 0.7 0.5 0.5++ -- Set up user coordinates+ C.scale scaleX scaleY+ -- center origin+ C.translate (width / 2) (height / 2)+ -- positive y-axis upwards+ let flipY = M.Matrix 1 0 0 (-1) 0 0+ C.transform flipY++ grid xmin xmax ymin ymax+++-- Grid and axes+grid xmin xmax ymin ymax =+ keepState $ do+ C.setSourceRGBA 0 0 0 0.7+ -- axes+ C.moveTo 0 ymin; C.lineTo 0 ymax; C.stroke+ C.moveTo xmin 0; C.lineTo xmax 0; C.stroke+ -- grid+ C.setDash [0.01, 0.99] 0+ foreach [xmin .. xmax] $ \ x ->+ do C.moveTo x ymin+ C.lineTo x ymax+ C.stroke++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)+ -- some cute stuff+ thought+ apple+ snake++thought =+ keepState $ do+ C.scale 0.04 0.04+ C.translate (200) (380)+ C.rotate pi+ C.setSourceRGBA 0.5 0.5 1 0.7+ C.setLineWidth 1+ image+ fillStroke+ where+ m = C.moveTo+ c = C.curveTo+ z = C.closePath+ image = do+ m 184 327+ c 176 327 170 332 168 339+ c 166 333 160 329 153 329+ c 147 329 141 333 138 339+ c 137 339 136 338 134 338+ c 125 338 118 345 118 354+ c 118 363 125 371 134 371+ c 137 371 140 370 142 368+ c 142 368 142 368 142 369+ c 142 377 149 385 158 385+ c 162 385 166 383 168 381+ c 171 386 176 390 183 390+ c 188 390 193 387 196 383+ c 198 384 201 385 204 385+ c 212 385 220 378 220 369+ c 222 371 225 372 228 372+ c 237 372 244 364 244 355+ c 244 346 237 339 228 339+ c 227 339 226 339 225 340+ c 223 332 217 327 209 327+ c 204 327 199 330 196 333+ c 193 330 189 327 184 327+ z+ m 164 387+ c 158 387 153 391 153 397+ c 153 402 158 407 164 407+ c 170 407 174 402 174 397+ c 174 391 170 387 164 387+ z+ m 152 408+ c 149 408 146 411 146 414+ c 146 417 149 420 152 420+ c 155 420 158 417 158 414+ c 158 411 155 408 152 408+ z+ m 143 422+ c 141 422 139 424 139 426+ c 139 428 141 429 143 429+ c 144 429 146 428 146 426+ c 146 424 144 422 143 422+ z++apple =+ keepState $ do+ C.scale 0.05 0.05+ C.translate (1110) (220)+ C.rotate pi+ C.setLineWidth 0.5+ C.setSourceRGBA 0 0 0 0.7+ image1+ fillStroke+ C.setSourceRGBA 1 0 0 0.7+ image2+ fillStroke+ where+ m = C.moveTo+ c = C.curveTo+ z = C.closePath+ l = C.lineTo+ image1 = do+ m 1149 245+ l 1156 244+ l 1155 252+ l 1149 245+ z+ image2 = do+ m 1151 249+ c 1145 249 1140 254 1140 261+ c 1140 268 1145 273 1151 273+ c 1152 273 1153 273 1154 272+ c 1156 273 1157 273 1158 273+ c 1164 273 1169 268 1169 261+ c 1169 254 1164 249 1158 249+ c 1157 249 1156 249 1154 250+ c 1153 249 1152 249 1151 249+ z++snake =+ keepState $ do+ C.scale 0.04 0.04+ C.translate (150) (220)+ C.rotate pi+ C.setLineWidth 0.5+ C.setSourceRGBA 0.1 0.1 0 0.7+ image+ fillStroke+ where+ m = C.moveTo+ c = C.curveTo+ z = C.closePath+ l = C.lineTo+ image = do+ m 146 320+ c 143 308 130 314 123 319+ c 115 324 108 311 100 314+ c 93 317 92 319 81 318+ c 76 318 60 309 60 320+ c 60 328 73 321 82 323+ c 94 326 98 317 106 320+ c 113 323 120 330 128 323+ c 133 318 142 312 146 320+ l 146 320+ z++----------------------------------------------------------------