blank-canvas 0.5 → 0.6
raw patch · 76 files changed
+5012/−766 lines, 76 filesdep +base-compatdep +blank-canvasdep +directorydep ~aesondep ~basedep ~colourPVP ok
version bump matches the API change (PVP)
Dependencies added: base-compat, blank-canvas, directory, mime-types, process, shake, text-show, time, unix
Dependency ranges changed: aeson, base, colour, data-default-class, http-types, kansas-comet, scotty, transformers, vector
API changes (from Hackage documentation)
- Graphics.Blank: debug :: Options -> Bool
- Graphics.Blank: eMetaKey :: Event -> Bool
- Graphics.Blank: ePageXY :: Event -> Maybe (Double, Double)
- Graphics.Blank: eType :: Event -> EventName
- Graphics.Blank: eWhich :: Event -> Maybe Int
- Graphics.Blank: events :: Options -> [EventName]
- Graphics.Blank: instance Num Options
- Graphics.Blank: middleware :: Options -> [Middleware]
- Graphics.Blank: port :: Options -> Int
- Graphics.Blank: root :: Options -> String
- Graphics.Blank: showJS :: JSArg a => a -> String
- Graphics.Blank.Style: type Alpha = Double
- Graphics.Blank.Style: type Percentage = Double
+ Graphics.Blank: [debug] :: Options -> Bool
+ Graphics.Blank: [eMetaKey] :: Event -> Bool
+ Graphics.Blank: [ePageXY] :: Event -> Maybe (Double, Double)
+ Graphics.Blank: [eType] :: Event -> EventName
+ Graphics.Blank: [eWhich] :: Event -> Maybe Int
+ Graphics.Blank: [events] :: Options -> [EventName]
+ Graphics.Blank: [middleware] :: Options -> [Middleware]
+ Graphics.Blank: [port] :: Options -> Int
+ Graphics.Blank: [root] :: Options -> String
+ Graphics.Blank: [weak] :: Options -> Bool
+ Graphics.Blank: alphabetic :: TextBaselineAlignment
+ Graphics.Blank: bevel :: LineJoinCorner
+ Graphics.Blank: bottom :: TextBaselineAlignment
+ Graphics.Blank: butt :: LineEndCap
+ Graphics.Blank: center :: TextAnchorAlignment
+ Graphics.Blank: class RoundProperty a
+ Graphics.Blank: cursor :: Text -> Canvas ()
+ Graphics.Blank: data CanvasAudio
+ Graphics.Blank: end :: TextAnchorAlignment
+ Graphics.Blank: hanging :: TextBaselineAlignment
+ Graphics.Blank: ideographic :: TextBaselineAlignment
+ Graphics.Blank: instance GHC.Num.Num Graphics.Blank.Options
+ Graphics.Blank: left :: TextAnchorAlignment
+ Graphics.Blank: middle :: TextBaselineAlignment
+ Graphics.Blank: miter :: LineJoinCorner
+ Graphics.Blank: newAudio :: Text -> Canvas CanvasAudio
+ Graphics.Blank: noRepeat :: RepeatDirection
+ Graphics.Blank: repeatX :: RepeatDirection
+ Graphics.Blank: repeatY :: RepeatDirection
+ Graphics.Blank: repeat_ :: RepeatDirection
+ Graphics.Blank: right :: TextAnchorAlignment
+ Graphics.Blank: round_ :: RoundProperty a => a
+ Graphics.Blank: showbJS :: JSArg a => a -> Builder
+ Graphics.Blank: square :: LineEndCap
+ Graphics.Blank: start :: TextAnchorAlignment
+ Graphics.Blank: top :: TextBaselineAlignment
+ Graphics.Blank: type Alpha = Interval
+ Graphics.Blank: type Degrees = Double
+ Graphics.Blank: type Interval = Double
+ Graphics.Blank: type Percentage = Double
+ Graphics.Blank: type Radians = Double
+ Graphics.Blank.Cursor: Alias :: Cursor
+ Graphics.Blank.Cursor: AllScroll :: Cursor
+ Graphics.Blank.Cursor: Auto :: Cursor
+ Graphics.Blank.Cursor: Cell :: Cursor
+ Graphics.Blank.Cursor: ColResize :: Cursor
+ Graphics.Blank.Cursor: ContextMenu :: Cursor
+ Graphics.Blank.Cursor: Copy :: Cursor
+ Graphics.Blank.Cursor: Crosshair :: Cursor
+ Graphics.Blank.Cursor: Default :: Cursor
+ Graphics.Blank.Cursor: EResize :: Cursor
+ Graphics.Blank.Cursor: EWResize :: Cursor
+ Graphics.Blank.Cursor: Grab :: Cursor
+ Graphics.Blank.Cursor: Grabbing :: Cursor
+ Graphics.Blank.Cursor: Help :: Cursor
+ Graphics.Blank.Cursor: Move :: Cursor
+ Graphics.Blank.Cursor: NEResize :: Cursor
+ Graphics.Blank.Cursor: NESWResize :: Cursor
+ Graphics.Blank.Cursor: NResize :: Cursor
+ Graphics.Blank.Cursor: NSResize :: Cursor
+ Graphics.Blank.Cursor: NWResize :: Cursor
+ Graphics.Blank.Cursor: NWSEResize :: Cursor
+ Graphics.Blank.Cursor: NoDrop :: Cursor
+ Graphics.Blank.Cursor: None :: Cursor
+ Graphics.Blank.Cursor: NotAllowed :: Cursor
+ Graphics.Blank.Cursor: Pointer :: Cursor
+ Graphics.Blank.Cursor: Progress :: Cursor
+ Graphics.Blank.Cursor: RowResize :: Cursor
+ Graphics.Blank.Cursor: SEResize :: Cursor
+ Graphics.Blank.Cursor: SResize :: Cursor
+ Graphics.Blank.Cursor: SWResize :: Cursor
+ Graphics.Blank.Cursor: Text :: Cursor
+ Graphics.Blank.Cursor: URL :: Text -> Cursor -> Cursor
+ Graphics.Blank.Cursor: VerticalText :: Cursor
+ Graphics.Blank.Cursor: WResize :: Cursor
+ Graphics.Blank.Cursor: Wait :: Cursor
+ Graphics.Blank.Cursor: ZoomIn :: Cursor
+ Graphics.Blank.Cursor: ZoomOut :: Cursor
+ Graphics.Blank.Cursor: alias :: Cursor
+ Graphics.Blank.Cursor: allScroll :: Cursor
+ Graphics.Blank.Cursor: auto :: Cursor
+ Graphics.Blank.Cursor: cell :: Cursor
+ Graphics.Blank.Cursor: class CanvasCursor a
+ Graphics.Blank.Cursor: colResize :: Cursor
+ Graphics.Blank.Cursor: contextMenu :: Cursor
+ Graphics.Blank.Cursor: copy :: Cursor
+ Graphics.Blank.Cursor: crosshair :: Cursor
+ Graphics.Blank.Cursor: cursor :: CanvasCursor cursor => cursor -> Canvas ()
+ Graphics.Blank.Cursor: data Cursor
+ Graphics.Blank.Cursor: default_ :: Cursor
+ Graphics.Blank.Cursor: eResize :: Cursor
+ Graphics.Blank.Cursor: ewResize :: Cursor
+ Graphics.Blank.Cursor: grab :: Cursor
+ Graphics.Blank.Cursor: grabbing :: Cursor
+ Graphics.Blank.Cursor: help :: Cursor
+ Graphics.Blank.Cursor: jsCanvasCursor :: CanvasCursor a => a -> Builder
+ Graphics.Blank.Cursor: move :: Cursor
+ Graphics.Blank.Cursor: nResize :: Cursor
+ Graphics.Blank.Cursor: neResize :: Cursor
+ Graphics.Blank.Cursor: neswResize :: Cursor
+ Graphics.Blank.Cursor: noDrop :: Cursor
+ Graphics.Blank.Cursor: none :: Cursor
+ Graphics.Blank.Cursor: notAllowed :: Cursor
+ Graphics.Blank.Cursor: nsResize :: Cursor
+ Graphics.Blank.Cursor: nwResize :: Cursor
+ Graphics.Blank.Cursor: nwseResize :: Cursor
+ Graphics.Blank.Cursor: pointer :: Cursor
+ Graphics.Blank.Cursor: progress :: Cursor
+ Graphics.Blank.Cursor: rowResize :: Cursor
+ Graphics.Blank.Cursor: sResize :: Cursor
+ Graphics.Blank.Cursor: seResize :: Cursor
+ Graphics.Blank.Cursor: swResize :: Cursor
+ Graphics.Blank.Cursor: text :: Cursor
+ Graphics.Blank.Cursor: url :: Text -> Cursor -> Cursor
+ Graphics.Blank.Cursor: verticalText :: Cursor
+ Graphics.Blank.Cursor: wResize :: Cursor
+ Graphics.Blank.Cursor: wait :: Cursor
+ Graphics.Blank.Cursor: zoomIn :: Cursor
+ Graphics.Blank.Cursor: zoomOut :: Cursor
+ Graphics.Blank.Font: BoldWeight :: FontWeight
+ Graphics.Blank.Font: BolderWeight :: FontWeight
+ Graphics.Blank.Font: CaptionFont :: Font
+ Graphics.Blank.Font: Ch :: Double -> Length
+ Graphics.Blank.Font: Cm :: Double -> Length
+ Graphics.Blank.Font: CursiveFamily :: FontFamily
+ Graphics.Blank.Font: Em :: Double -> Length
+ Graphics.Blank.Font: Ex :: Double -> Length
+ Graphics.Blank.Font: FantasyFamily :: FontFamily
+ Graphics.Blank.Font: FontFamilyName :: Text -> FontFamily
+ Graphics.Blank.Font: FontProperties :: FontStyle -> FontVariant -> FontWeight -> FontSize -> LineHeight -> [FontFamily] -> Font
+ Graphics.Blank.Font: FontSizeLength :: Length -> FontSize
+ Graphics.Blank.Font: FontSizePercentage :: Percentage -> FontSize
+ Graphics.Blank.Font: IconFont :: Font
+ Graphics.Blank.Font: In :: Double -> Length
+ Graphics.Blank.Font: ItalicStyle :: FontStyle
+ Graphics.Blank.Font: LargeSize :: FontSize
+ Graphics.Blank.Font: LargerSize :: FontSize
+ Graphics.Blank.Font: LighterWeight :: FontWeight
+ Graphics.Blank.Font: LineHeightLength :: Length -> LineHeight
+ Graphics.Blank.Font: LineHeightNumber :: Double -> LineHeight
+ Graphics.Blank.Font: LineHeightPercentage :: Percentage -> LineHeight
+ Graphics.Blank.Font: MediumSize :: FontSize
+ Graphics.Blank.Font: MenuFont :: Font
+ Graphics.Blank.Font: MessageBoxFont :: Font
+ Graphics.Blank.Font: Mm :: Double -> Length
+ Graphics.Blank.Font: MonospaceFamily :: FontFamily
+ Graphics.Blank.Font: NormalLineHeight :: LineHeight
+ Graphics.Blank.Font: NormalStyle :: FontStyle
+ Graphics.Blank.Font: NormalVariant :: FontVariant
+ Graphics.Blank.Font: NormalWeight :: FontWeight
+ Graphics.Blank.Font: ObliqueStyle :: FontStyle
+ Graphics.Blank.Font: Pc :: Double -> Length
+ Graphics.Blank.Font: Pt :: Double -> Length
+ Graphics.Blank.Font: Px :: Double -> Length
+ Graphics.Blank.Font: Rem :: Double -> Length
+ Graphics.Blank.Font: SansSerifFamily :: FontFamily
+ Graphics.Blank.Font: SerifFamily :: FontFamily
+ Graphics.Blank.Font: SmallCapsVariant :: FontVariant
+ Graphics.Blank.Font: SmallCaptionFont :: Font
+ Graphics.Blank.Font: SmallSize :: FontSize
+ Graphics.Blank.Font: SmallerSize :: FontSize
+ Graphics.Blank.Font: StatusBarFont :: Font
+ Graphics.Blank.Font: Vh :: Double -> Length
+ Graphics.Blank.Font: Vmax :: Double -> Length
+ Graphics.Blank.Font: Vmin :: Double -> Length
+ Graphics.Blank.Font: Vw :: Double -> Length
+ Graphics.Blank.Font: Weight100 :: FontWeight
+ Graphics.Blank.Font: Weight200 :: FontWeight
+ Graphics.Blank.Font: Weight300 :: FontWeight
+ Graphics.Blank.Font: Weight400 :: FontWeight
+ Graphics.Blank.Font: Weight500 :: FontWeight
+ Graphics.Blank.Font: Weight600 :: FontWeight
+ Graphics.Blank.Font: Weight700 :: FontWeight
+ Graphics.Blank.Font: Weight800 :: FontWeight
+ Graphics.Blank.Font: Weight900 :: FontWeight
+ Graphics.Blank.Font: XLargeSize :: FontSize
+ Graphics.Blank.Font: XSmallSize :: FontSize
+ Graphics.Blank.Font: XXLargeSize :: FontSize
+ Graphics.Blank.Font: XXSmallSize :: FontSize
+ Graphics.Blank.Font: [fontFamily] :: Font -> [FontFamily]
+ Graphics.Blank.Font: [fontSize] :: Font -> FontSize
+ Graphics.Blank.Font: [fontStyle] :: Font -> FontStyle
+ Graphics.Blank.Font: [fontVariant] :: Font -> FontVariant
+ Graphics.Blank.Font: [fontWeight] :: Font -> FontWeight
+ Graphics.Blank.Font: [lineHeight] :: Font -> LineHeight
+ Graphics.Blank.Font: [runLength] :: Length -> Double
+ Graphics.Blank.Font: bold :: FontWeight
+ Graphics.Blank.Font: bolder :: FontWeight
+ Graphics.Blank.Font: caption :: Font
+ Graphics.Blank.Font: ch :: LengthProperty a => Double -> a
+ Graphics.Blank.Font: class CanvasFont a
+ Graphics.Blank.Font: class LengthProperty a
+ Graphics.Blank.Font: class Default a => NormalProperty a where normal = def
+ Graphics.Blank.Font: class PercentageProperty a
+ Graphics.Blank.Font: cm :: LengthProperty a => Double -> a
+ Graphics.Blank.Font: cursive :: FontFamily
+ Graphics.Blank.Font: data Font
+ Graphics.Blank.Font: data FontFamily
+ Graphics.Blank.Font: data FontSize
+ Graphics.Blank.Font: data FontStyle
+ Graphics.Blank.Font: data FontVariant
+ Graphics.Blank.Font: data FontWeight
+ Graphics.Blank.Font: data Length
+ Graphics.Blank.Font: data LineHeight
+ Graphics.Blank.Font: defFont :: [FontFamily] -> Font
+ Graphics.Blank.Font: em :: LengthProperty a => Double -> a
+ Graphics.Blank.Font: ex :: LengthProperty a => Double -> a
+ Graphics.Blank.Font: fantasy :: FontFamily
+ Graphics.Blank.Font: font :: CanvasFont canvasFont => canvasFont -> Canvas ()
+ Graphics.Blank.Font: fromLength :: LengthProperty a => Length -> a
+ Graphics.Blank.Font: icon :: Font
+ Graphics.Blank.Font: in_ :: LengthProperty a => Double -> a
+ Graphics.Blank.Font: italic :: FontStyle
+ Graphics.Blank.Font: jsCanvasFont :: CanvasFont a => a -> Builder
+ Graphics.Blank.Font: large :: FontSize
+ Graphics.Blank.Font: larger :: FontSize
+ Graphics.Blank.Font: lighter :: FontWeight
+ Graphics.Blank.Font: medium :: FontSize
+ Graphics.Blank.Font: menu :: Font
+ Graphics.Blank.Font: messageBox :: Font
+ Graphics.Blank.Font: mm :: LengthProperty a => Double -> a
+ Graphics.Blank.Font: monospace :: FontFamily
+ Graphics.Blank.Font: normal :: NormalProperty a => a
+ Graphics.Blank.Font: oblique :: FontStyle
+ Graphics.Blank.Font: pc :: LengthProperty a => Double -> a
+ Graphics.Blank.Font: percent :: PercentageProperty a => Percentage -> a
+ Graphics.Blank.Font: pt :: LengthProperty a => Double -> a
+ Graphics.Blank.Font: px :: LengthProperty a => Double -> a
+ Graphics.Blank.Font: rem_ :: LengthProperty a => Double -> a
+ Graphics.Blank.Font: sansSerif :: FontFamily
+ Graphics.Blank.Font: serif :: FontFamily
+ Graphics.Blank.Font: small :: FontSize
+ Graphics.Blank.Font: smallCaps :: FontVariant
+ Graphics.Blank.Font: smallCaption :: Font
+ Graphics.Blank.Font: smaller :: FontSize
+ Graphics.Blank.Font: statusBar :: Font
+ Graphics.Blank.Font: vh :: LengthProperty a => Double -> a
+ Graphics.Blank.Font: vmax :: LengthProperty a => Double -> a
+ Graphics.Blank.Font: vmin :: LengthProperty a => Double -> a
+ Graphics.Blank.Font: vw :: LengthProperty a => Double -> a
+ Graphics.Blank.Font: xLarge :: FontSize
+ Graphics.Blank.Font: xSmall :: FontSize
+ Graphics.Blank.Font: xxLarge :: FontSize
+ Graphics.Blank.Font: xxSmall :: FontSize
+ Graphics.Blank.Style: data AlphaColour a :: * -> *
+ Graphics.Blank.Style: data Colour a :: * -> *
- Graphics.Blank: (#) :: a -> (a -> Canvas b) -> Canvas b
+ Graphics.Blank: (#) :: a -> (a -> b) -> b
- Graphics.Blank: Options :: Int -> [EventName] -> Bool -> String -> [Middleware] -> Options
+ Graphics.Blank: Options :: Int -> [EventName] -> Bool -> String -> [Middleware] -> Bool -> Options
- Graphics.Blank: addColorStop :: (Double, Text) -> CanvasGradient -> Canvas ()
+ Graphics.Blank: addColorStop :: (Interval, Text) -> CanvasGradient -> Canvas ()
- Graphics.Blank: arc :: (Double, Double, Double, Double, Double, Bool) -> Canvas ()
+ Graphics.Blank: arc :: (Double, Double, Double, Radians, Radians, Bool) -> Canvas ()
- Graphics.Blank: globalAlpha :: Double -> Canvas ()
+ Graphics.Blank: globalAlpha :: Alpha -> Canvas ()
- Graphics.Blank: rotate :: Double -> Canvas ()
+ Graphics.Blank: rotate :: Radians -> Canvas ()
- Graphics.Blank: scale :: (Double, Double) -> Canvas ()
+ Graphics.Blank: scale :: (Interval, Interval) -> Canvas ()
- Graphics.Blank.Style: addColorStop :: CanvasColor color => (Double, color) -> CanvasGradient -> Canvas ()
+ Graphics.Blank.Style: addColorStop :: CanvasColor color => (Interval, color) -> CanvasGradient -> Canvas ()
- Graphics.Blank.Style: hsl :: Int -> Percentage -> Percentage -> Colour Double
+ Graphics.Blank.Style: hsl :: Degrees -> Percentage -> Percentage -> Colour Double
- Graphics.Blank.Style: hsla :: Int -> Percentage -> Percentage -> Alpha -> AlphaColour Double
+ Graphics.Blank.Style: hsla :: Degrees -> Percentage -> Percentage -> Alpha -> AlphaColour Double
- Graphics.Blank.Style: jsStyle :: Style a => a -> String
+ Graphics.Blank.Style: jsStyle :: Style a => a -> Builder
Files
- Changelog.md +19/−0
- Graphics/Blank.hs +230/−100
- Graphics/Blank/Canvas.hs +219/−82
- Graphics/Blank/Cursor.hs +63/−0
- Graphics/Blank/DeviceContext.hs +21/−16
- Graphics/Blank/Events.hs +20/−18
- Graphics/Blank/Font.hs +86/−0
- Graphics/Blank/GHCi.hs +14/−3
- Graphics/Blank/Generated.hs +387/−50
- Graphics/Blank/JavaScript.hs +387/−224
- Graphics/Blank/Parser.hs +82/−0
- Graphics/Blank/Style.hs +202/−192
- Graphics/Blank/Types.hs +19/−0
- Graphics/Blank/Types/CSS.hs +155/−0
- Graphics/Blank/Types/Cursor.hs +337/−0
- Graphics/Blank/Types/Font.hs +679/−0
- Graphics/Blank/Utils.hs +9/−12
- LICENSE +1/−1
- README.md +1/−1
- blank-canvas.cabal +120/−35
- static/index.html +40/−32
- wiki-suite/Arc.hs +29/−0
- wiki-suite/Bezier_Curve.hs +20/−0
- wiki-suite/Bounce.hs +67/−0
- wiki-suite/Circle.hs +24/−0
- wiki-suite/Clipping_Region.hs +55/−0
- wiki-suite/Color_Fill.hs +28/−0
- wiki-suite/Color_Square.hs +22/−0
- wiki-suite/Custom_Shape.hs +26/−0
- wiki-suite/Custom_Transform.hs +28/−0
- wiki-suite/Draw_Canvas.hs +20/−0
- wiki-suite/Draw_Device.hs +19/−0
- wiki-suite/Draw_Image.hs +16/−0
- wiki-suite/Favicon.hs +37/−0
- wiki-suite/Font_Size_and_Style.hs +15/−0
- wiki-suite/Get_Image_Data_URL.hs +31/−0
- wiki-suite/Global_Alpha.hs +25/−0
- wiki-suite/Global_Composite_Operations.hs +70/−0
- wiki-suite/Grayscale.hs +33/−0
- wiki-suite/Image_Crop.hs +16/−0
- wiki-suite/Image_Loader.hs +18/−0
- wiki-suite/Image_Size.hs +16/−0
- wiki-suite/Is_Point_In_Path.hs +29/−0
- wiki-suite/Key_Read.hs +52/−0
- wiki-suite/Line.hs +17/−0
- wiki-suite/Line_Cap.hs +23/−0
- wiki-suite/Line_Color.hs +18/−0
- wiki-suite/Line_Join.hs +39/−0
- wiki-suite/Line_Width.hs +17/−0
- wiki-suite/Linear_Gradient.hs +22/−0
- wiki-suite/Load_Image_Data_URL.hs +17/−0
- wiki-suite/Load_Image_Data_URL_2.hs +15/−0
- wiki-suite/Main.hs +298/−0
- wiki-suite/Miter_Limit.hs +32/−0
- wiki-suite/Path.hs +27/−0
- wiki-suite/Pattern.hs +19/−0
- wiki-suite/Quadratic_Curve.hs +20/−0
- wiki-suite/Radial_Gradient.hs +22/−0
- wiki-suite/Rectangle.hs +20/−0
- wiki-suite/Red_Line.hs +17/−0
- wiki-suite/Rotate_Transform.hs +20/−0
- wiki-suite/Rotating_Square.hs +36/−0
- wiki-suite/Rounded_Corners.hs +29/−0
- wiki-suite/Scale_Transform.hs +20/−0
- wiki-suite/Semicircle.hs +21/−0
- wiki-suite/Shadow.hs +20/−0
- wiki-suite/Square.hs +49/−0
- wiki-suite/Text_Align.hs +19/−0
- wiki-suite/Text_Baseline.hs +20/−0
- wiki-suite/Text_Color.hs +16/−0
- wiki-suite/Text_Metrics.hs +28/−0
- wiki-suite/Text_Stroke.hs +17/−0
- wiki-suite/Text_Wrap.hs +34/−0
- wiki-suite/Tic_Tac_Toe.hs +132/−0
- wiki-suite/Translate_Transform.hs +18/−0
- wiki-suite/Wiki.hs +123/−0
Changelog.md view
@@ -1,3 +1,22 @@+### 0.6+API changes+ * The `(#)` function had its type generalized from `a -> (a -> Canvas b) -> Canvas b` to `a -> (a -> b) -> b`. This allows it to be used with font length units.+ * Added more type synonyms (`Interval`, `Degrees`, `Radians`, etc.) to more clearly indicate what functions expect constrained values.+ * `showbJS` (formerly `showJS`) and `jsStyle` now return a text `Builder` instead of a `String`. This change was introduced as part of a larger `blank-canvas` refactoring to increase performance. See the `Data.Text.Lazy.Builder` module from the `text` package for more details on how to use `Builder`s.++API additions+ * A new ADT for `Font`s has been added in `Graphics.Blank.Font` that can be used in place of `Text`. For example, `"30pt Calibri"` is equivalent to `(defFont "Calibri") { fontSize = 30 # pt }`.+ * A generalized `font` function of type `CanvasFont canvasFont => canvasFont -> Canvas ()` was added to `Graphics.Blank.Font` that can accept a `Text` or `Font` argument. The `font` function in `Graphics.Blank` remains of type `Text -> Canvas ()`.+ * Added a `cursor` function to change the browser cursor. Also added the `Graphics.Blank.Cursor` module containing a generalized `cursor` function that uses a `Cursor` ADT instead of `Text`.+ * Added `Bounded`, `Enum`, `Eq`, `Ix`, `Ord`, and `Show` instances for more data types+ * Added support for more MIME types via the `mime-types` library`++Additions+ * Allowed building with `base-4.8.0.0`++Other+ * Require `scotty` >= 0.10 and `kansas-comet` >= 0.4+ ## 0.5 API changes
Graphics/Blank.hs view
@@ -1,23 +1,35 @@-{-# LANGUAGE OverloadedStrings, TemplateHaskell, GADTs, KindSignatures, CPP, BangPatterns, ScopedTypeVariables #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-} --- | blank-canvas is a Haskell binding to the complete HTML5 Canvas--- API. blank-canvas allows Haskell users to write, in Haskell,--- interactive images onto their web browsers. blank-canvas gives--- the users a single full-window canvas, and provides many--- well-documented functions for rendering images.+{-|+Module: Graphics.Blank+Copyright: (C) 2014-2015, The University of Kansas+License: BSD-style (see the file LICENSE)+Maintainer: Andy Gill+Stability: Beta+Portability: GHC +@blank-canvas@ is a Haskell binding to the complete+<https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API HTML5 Canvas API>.+@blank-canvas@ allows Haskell users to write, in Haskell,+interactive images onto their web browsers. @blank-canvas@ gives+the users a single full-window canvas, and provides many+well-documented functions for rendering images.+-} module Graphics.Blank (- -- * Starting blank-canvas+ -- * Starting @blank-canvas@ blankCanvas , Options(..) -- ** 'send'ing to the Graphics 'DeviceContext' , DeviceContext -- abstact , send -- * HTML5 Canvas API- -- | See <http://www.nihilogic.dk/labs/canvas_sheet/HTML5_Canvas_Cheat_Sheet.pdf> for the JavaScript+ -- | See <https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API> for the JavaScript -- version of this API.- , Canvas -- abstact+ , Canvas -- abstract -- ** Canvas element , height , width@@ -43,7 +55,11 @@ , lineJoin , miterLimit , LineEndCap(..)+ , butt+ , square , LineJoinCorner(..)+ , bevel+ , miter -- ** Colors, styles and shadows , strokeStyle , fillStyle@@ -56,6 +72,10 @@ , createPattern , addColorStop , RepeatDirection(..)+ , repeat_+ , repeatX+ , repeatY+ , noRepeat , CanvasGradient , CanvasPattern -- ** Paths@@ -80,7 +100,18 @@ , strokeText , measureText , TextAnchorAlignment(..)+ , start+ , end+ , center+ , left+ , right , TextBaselineAlignment(..)+ , top+ , hanging+ , middle+ , alphabetic+ , ideographic+ , bottom , TextMetrics(..) -- ** Rectangles , clearRect@@ -90,10 +121,19 @@ , getImageData , putImageData , ImageData(..)- -- * blank-canvas Extensions+ -- * Type information+ , Alpha+ , Degrees+ , Interval+ , Percentage+ , Radians+ , RoundProperty(..)+ -- * @blank-canvas@ Extensions -- ** Reading from 'Canvas' , newImage , CanvasImage -- abstract+ , newAudio+ , CanvasAudio -- ** 'DeviceContext' attributes , devicePixelRatio -- ** 'CanvasContext', and off-screen Canvas.@@ -118,6 +158,8 @@ , Event(..) , EventName , EventQueue+ -- ** Cursor manipulation+ , cursor -- ** Middleware , local_only ) where@@ -125,30 +167,33 @@ import Control.Concurrent import Control.Concurrent.STM import Control.Exception-import Control.Monad+import Control.Monad (forever) import Control.Monad.IO.Class import Data.Aeson import Data.Aeson.Types (parse) import Data.List as L+import qualified Data.Map as M (lookup) import Data.Monoid ((<>)) import qualified Data.Set as S-import Data.String import qualified Data.Text as T import Data.Text (Text)+import Data.Text.Encoding (decodeUtf8) import qualified Data.Text.Lazy as LT import qualified Graphics.Blank.Canvas as Canvas-import Graphics.Blank.Canvas hiding (addColorStop)+import Graphics.Blank.Canvas hiding (addColorStop, cursor) import Graphics.Blank.DeviceContext import Graphics.Blank.Events import qualified Graphics.Blank.Generated as Generated-import Graphics.Blank.Generated hiding (fillStyle, strokeStyle, shadowColor)+import Graphics.Blank.Generated hiding (fillStyle, font, strokeStyle, shadowColor) import qualified Graphics.Blank.JavaScript as JavaScript import Graphics.Blank.JavaScript hiding (width, height)+import Graphics.Blank.Types import Graphics.Blank.Utils import qualified Network.HTTP.Types as H+import Network.Mime (defaultMimeMap, fileNameExtensions) import Network.Wai (Middleware, responseLBS) import Network.Wai.Middleware.Local import Network.Wai.Handler.Warp@@ -157,14 +202,18 @@ import Paths_blank_canvas +import Prelude.Compat+ import System.IO.Unsafe (unsafePerformIO) -- import System.Mem.StableName +import TextShow (Builder, showb, showt, singleton)+ import qualified Web.Scotty as Scotty import Web.Scotty (scottyApp, get, file) import qualified Web.Scotty.Comet as KC --- | blankCanvas is the main entry point into blank-canvas.+-- | 'blankCanvas' is the main entry point into @blank-canvas@. -- A typical invocation would be -- -- >{-# LANGUAGE OverloadedStrings #-}@@ -181,61 +230,61 @@ -- > stroke() -- > - blankCanvas :: Options -> (DeviceContext -> IO ()) -> IO () blankCanvas opts actions = do dataDir <- getDataDir kComet <- KC.kCometPlugin - locals :: TVar (S.Set Text) <- atomically $ newTVar S.empty -- print dataDir + -- use the comet+ let kc_opts :: KC.Options+ kc_opts = KC.Options { KC.prefix = "/blank", KC.verbose = if debug opts then 3 else 0 }++ connectApp <- KC.connect kc_opts $ \ kc_doc -> do+ -- register the events we want to watch for+ KC.send kc_doc $ T.unlines+ [ "register(" <> showt nm <> ");"+ | nm <- events opts+ ]+ + queue <- atomically newTChan+ _ <- forkIO $ forever $ do+ val <- atomically $ readTChan $ KC.eventQueue $ kc_doc+ case fromJSON val of+ Success (event :: Event) -> do+ atomically $ writeTChan queue event+ _ -> return ()+ + + let cxt0 = DeviceContext kc_doc queue 300 300 1 locals False+ + -- A bit of bootstrapping+ DeviceAttributes w h dpr <- send cxt0 device+ -- print (DeviceAttributes w h dpr)+ + let cxt1 = cxt0+ { ctx_width = w+ , ctx_height = h+ , ctx_devicePixelRatio = dpr+ , weakRemoteMonad = weak opts+ }+ + (actions $ cxt1) `catch` \ (e :: SomeException) -> do+ print ("Exception in blank-canvas application:" :: String)+ print e+ throw e+ app <- scottyApp $ do -- middleware logStdoutDev sequence_ [ Scotty.middleware ware | ware <- middleware opts ]- -- use the comet- let kc_opts :: KC.Options- kc_opts = KC.Options { KC.prefix = "/blank", KC.verbose = if debug opts then 3 else 0 } --- KC.connect kc_opts $ \ kc_doc -> do- -- register the events we want to watch for- KC.send kc_doc $ T.unlines- [ "register(" <> T.pack(show nm) <> ");"- | nm <- events opts- ]-- queue <- atomically newTChan- _ <- forkIO $ forever $ do- val <- atomically $ readTChan $ KC.eventQueue $ kc_doc- case fromJSON val of- Success (event :: Event) -> do- atomically $ writeTChan queue event- _ -> return ()--- let cxt0 = DeviceContext kc_doc queue 300 300 1 locals-- -- A bit of bootstrapping- DeviceAttributes w h dpr <- send cxt0 device- -- print (DeviceAttributes w h dpr)-- let cxt1 = cxt0- { ctx_width = w- , ctx_height = h- , ctx_devicePixelRatio = dpr- }-- (actions $ cxt1) `catch` \ (e :: SomeException) -> do- print ("Exception in blank-canvas application:" :: String)- print e- throw e+ connectApp get "/" $ file $ dataDir ++ "/static/index.html" get "/jquery.js" $ file $ dataDir ++ "/static/jquery.js"@@ -248,7 +297,7 @@ db <- liftIO $ atomically $ readTVar $ locals if fileName `S.member` db then do- mime <- mimeTypes (T.unpack fileName)+ let mime = mimeType fileName Scotty.setHeader "Content-Type" $ LT.fromStrict $ mime file $ (root opts ++ "/" ++ T.unpack fileName) else do@@ -256,53 +305,74 @@ return () -- runSettings (setPort (port opts) $ setTimeout 5 $ defaultSettings ) app --- | Sends a set of Canvas commands to the canvas. Attempts+-- | Sends a set of canvas commands to the 'Canvas'. Attempts -- to common up as many commands as possible. Should not crash.- send :: DeviceContext -> Canvas a -> IO a-send cxt commands =- send' (deviceCanvasContext cxt) commands id+send _ (Return a) = return a+send cxt (Bind m k) | weakRemoteMonad cxt = send cxt m >>= send cxt . k+send cxt (With c (Bind m k)) | weakRemoteMonad cxt = send cxt (With c m) >>= send cxt . With c . k+send cxt (With _ (With c m)) | weakRemoteMonad cxt = send cxt (With c m)+send cxt commands = send' (deviceCanvasContext cxt) commands mempty where- sendBind :: CanvasContext -> Canvas a -> (a -> Canvas b) -> (String -> String) -> IO b- sendBind c (Return a) k cmds = send' c (k a) cmds- sendBind c (Bind m k1) k2 cmds = sendBind c m (\ r -> Bind (k1 r) k2) cmds- sendBind c (Method cmd) k cmds = send' c (k ()) (cmds . ((showJS c ++ ".") ++) . shows cmd . (";" ++))- sendBind c (Command cmd) k cmds = send' c (k ()) (cmds . shows cmd . (";" ++))- sendBind c (Query query) k cmds = sendQuery c query k cmds- sendBind c (With c' m) k cmds = send' c' (Bind m (With c . k)) cmds- sendBind c MyContext k cmds = send' c (k c) cmds+ sendBind :: CanvasContext -> Canvas a -> (a -> Canvas b) -> Builder -> IO b+ sendBind c (Return a) k cmds = send' c (k a) cmds+ sendBind c (Bind m k1) k2 cmds = sendBind c m (\ r -> Bind (k1 r) k2) cmds+ sendBind c (Method cmd) k cmds = send' c (k ()) (cmds <> jsCanvasContext c <> singleton '.' <> showb cmd <> singleton ';')+ sendBind c (Command cmd) k cmds = send' c (k ()) (cmds <> showb cmd <> singleton ';')+ sendBind c (Function func) k cmds = sendFunc c func k cmds+ sendBind c (Query query) k cmds = sendQuery c query k cmds+ sendBind c (With c' m) k cmds = send' c' (Bind m (With c . k)) cmds+ sendBind c MyContext k cmds = send' c (k c) cmds - sendQuery :: CanvasContext -> Query a -> (a -> Canvas b) -> (String -> String) -> IO b+ sendFunc :: CanvasContext -> Function a -> (a -> Canvas b) -> Builder -> IO b+ sendFunc c q@(CreateLinearGradient _) k cmds = sendGradient c q k cmds+ sendFunc c q@(CreateRadialGradient _) k cmds = sendGradient c q k cmds+ sendFunc c q@(CreatePattern _) k cmds = sendPattern c q k cmds++ sendGradient :: CanvasContext -> Function a -> (CanvasGradient -> Canvas b) -> Builder -> IO b+ sendGradient c q k cmds = do+ gId <- atomically getUniq+ send' c (k $ CanvasGradient gId) $ cmds <> "var gradient_"+ <> showb gId <> " = " <> jsCanvasContext c+ <> singleton '.' <> showb q <> singleton ';'++ sendPattern :: CanvasContext -> Function a -> (CanvasPattern -> Canvas b) -> Builder -> IO b+ sendPattern c q k cmds = do+ pId <- atomically getUniq+ send' c (k $ CanvasPattern pId) $ cmds <> "var pattern_"+ <> showb pId <> " = " <> jsCanvasContext c+ <> singleton '.' <> showb q <> singleton ';'++ fileQuery :: Text -> IO ()+ fileQuery url = do+ let url' = if "/" `T.isPrefixOf` url then T.tail url else url+ atomically $ do+ db <- readTVar (localFiles cxt)+ writeTVar (localFiles cxt) $ S.insert url' $ db++ sendQuery :: CanvasContext -> Query a -> (a -> Canvas b) -> Builder -> IO b sendQuery c query k cmds = do case query of- NewImage url -> do- let url' = if "/" `T.isPrefixOf` url then T.tail url else url- atomically $ do- db <- readTVar (localFiles cxt)- writeTVar (localFiles cxt) $ S.insert url' $ db+ NewImage url -> fileQuery url+ NewAudio url -> fileQuery url _ -> return () -- send the com uq <- atomically $ getUniq -- The query function returns a function takes the unique port number of the reply.- sendToCanvas cxt (cmds . ((show query ++ "(" ++ show uq ++ "," ++ showJS c ++ ");") ++))+ sendToCanvas cxt $ cmds <> showb query <> singleton '(' <> showb uq <> singleton ',' <> jsCanvasContext c <> ");" v <- KC.getReply (theComet cxt) uq case parse (parseQueryResult query) v of Error msg -> fail msg- Success a -> do- send' c (k a) id--+ Success a -> send' c (k a) mempty - send' :: CanvasContext -> Canvas a -> (String -> String) -> IO a--- Most of these can be factored out, except return+ send' :: CanvasContext -> Canvas a -> Builder -> IO a+ -- Most of these can be factored out, except return send' c (Bind m k) cmds = sendBind c m k cmds send' _ (With c m) cmds = send' c m cmds -- This is a bit of a hack send' _ (Return a) cmds = do@@ -310,7 +380,6 @@ return a send' c cmd cmds = sendBind c cmd Return cmds - local_only :: Middleware local_only = local $ responseLBS H.status403 [("Content-Type", "text/plain")] "local access only" @@ -325,24 +394,26 @@ writeTVar uniqVar (u + 1) return u -mimeTypes :: Monad m => FilePath -> m Text-mimeTypes filePath- | ".jpg" `L.isSuffixOf` filePath = return "image/jpeg"- | ".png" `L.isSuffixOf` filePath = return "image/png"- | ".gif" `L.isSuffixOf` filePath = return "image/gif"- | otherwise = fail $ "do not understand mime type for : " ++ show filePath+mimeType :: Text -> Text+mimeType filePath = go $ fileNameExtensions filePath+ where+ go [] = error $ "do not understand mime type for : " ++ show filePath+ go (e:es) = case M.lookup e defaultMimeMap of+ Nothing -> go es+ Just mt -> decodeUtf8 mt ------------------------------------------------- --- TODO: add extra mime types--+-- | Additional @blank-canvas@ settings. The defaults can be used by creating+-- 'Options' as a 'Num'. For example, @'blankCanvas' 3000@ uses the default 'Options'+-- on port 3000. data Options = Options- { port :: Int -- ^ which port do we issue the blank canvas using- , events :: [EventName] -- ^ which events does the canvas listen to- , debug :: Bool -- ^ turn on debugging (default False)- , root :: String -- ^ location of the static files (default .)- , middleware :: [Middleware] -- ^ extra middleware(s) to be executed. (default [local_only])+ { port :: Int -- ^ On which port do we issue @blank-canvas@?+ , events :: [EventName] -- ^ To which events does the canvas listen? Default: @[]@+ , debug :: Bool -- ^ Turn on debugging. Default: @False@+ , root :: String -- ^ Location of the static files. Default: @\".\"@+ , middleware :: [Middleware] -- ^ Extra middleware(s) to be executed. Default: @['local_only']@+ , weak :: Bool -- ^ use a weak monad, which may help debugging (default False) } instance Num Options where@@ -356,26 +427,85 @@ , debug = False , root = "." , middleware = [local_only]+ , weak = False } - ---------------------------------------------------- This is the monomorphic version, to stop "ambiguous" errors.+-- These are monomorphic versions of functions defined to curb type ambiguity errors. +-- | Sets the color used to fill a drawing (@\"black\"@ by default).+--+-- ==== __Examples__+--+-- @+-- 'fillStyle' \"red\"+-- 'fillStyle' \"#00FF00\"+-- @ fillStyle :: Text -> Canvas () fillStyle = Generated.fillStyle +-- | Sets the text context's font properties.+--+-- ==== __Examples__+--+-- @+-- 'font' \"40pt \'Gill Sans Extrabold\'\"+-- 'font' \"80% sans-serif\"+-- 'font' \"bold italic large serif\"+-- @+font :: Text -> Canvas ()+font = Generated.font++-- | Sets the color used for strokes (@\"black\"@ by default).+--+-- ==== __Examples__+--+-- @+-- 'strokeStyle' \"red\"+-- 'strokeStyle' \"#00FF00\"+-- @ strokeStyle :: Text -> Canvas () strokeStyle = Generated.strokeStyle +-- | Sets the color used for shadows.+--+-- ==== __Examples__+--+-- @+-- 'shadowColor' \"red\"+-- 'shadowColor' \"#00FF00\"+-- @ shadowColor :: Text -> Canvas () shadowColor = Generated.shadowColor -addColorStop :: (Double, Text) -> CanvasGradient -> Canvas ()+-- | Adds a color and stop position in a 'CanvasGradient'. A stop position is a+-- number between 0.0 and 1.0 that represents the position between start and stop+-- in a gradient.+--+-- ==== __Example__+--+-- @+-- grd <- 'createLinearGradient'(0, 0, 10, 10)+-- grd # 'addColorStop'(0, \"red\")+-- @+addColorStop :: (Interval, Text) -> CanvasGradient -> Canvas () addColorStop = Canvas.addColorStop +-- | Change the canvas cursor to the specified URL or keyword.+--+-- ==== __Examples__+--+-- @+-- cursor \"url(image.png), default\"+-- cursor \"crosshair\"+-- @+cursor :: Text -> Canvas ()+cursor = Canvas.cursor++-- | The height of an 'Image' in pixels. height :: (Image image, Num a) => image -> a height = JavaScript.height +-- | The width of an 'Image' in pixels. width :: (Image image, Num a) => image -> a width = JavaScript.width
Graphics/Blank/Canvas.hs view
@@ -1,27 +1,47 @@-{-# LANGUAGE TemplateHaskell, GADTs, KindSignatures, ScopedTypeVariables, OverloadedStrings, FlexibleInstances, OverlappingInstances #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TemplateHaskell #-} module Graphics.Blank.Canvas where -import Control.Applicative import Control.Monad (ap, liftM2) -import Graphics.Blank.Events-import Graphics.Blank.JavaScript- import Data.Aeson (FromJSON(..),Value(..),encode) import Data.Aeson.Types (Parser, (.:))-import Data.Char (chr)--import qualified Data.ByteString.Lazy as DBL import Data.Monoid-import qualified Data.Text as Text import Data.Text (Text)+import Data.Text.Lazy.Builder+import Data.Text.Lazy.Encoding (decodeUtf8) +import Graphics.Blank.Events+import Graphics.Blank.JavaScript+import Graphics.Blank.Types+import Graphics.Blank.Types.Cursor+import Graphics.Blank.Types.Font +import Prelude.Compat++import TextShow+import TextShow.TH (deriveTextShow)++data DeviceAttributes = DeviceAttributes Int Int Double deriving Show+$(deriveTextShow ''DeviceAttributes)++-- | The 'width' argument of 'TextMetrics' can trivially be projected out.+data TextMetrics = TextMetrics Double deriving Show+$(deriveTextShow ''TextMetrics)++-----------------------------------------------------------------------------+ data Canvas :: * -> * where Method :: Method -> Canvas () -- <context>.<method> Command :: Command -> Canvas () -- <command>- Query :: (Show a) => Query a -> Canvas a+ Function :: TextShow a => Function a -> Canvas a+ Query :: TextShow a => Query a -> Canvas a With :: CanvasContext -> Canvas a -> Canvas a MyContext :: Canvas CanvasContext Bind :: Canvas a -> (a -> Canvas b) -> Canvas b@@ -42,11 +62,10 @@ mappend = liftM2 mappend mempty = return mempty - -- HTML5 Canvas assignments: FillStyle, Font, GlobalAlpha, GlobalCompositeOperation, LineCap, LineJoin, LineWidth, MiterLimit, ShadowBlur, ShadowColor, ShadowOffsetX, ShadowOffsetY, StrokeStyle, TextAlign, TextBaseline data Method -- regular HTML5 canvas commands- = Arc (Double, Double, Double, Double, Double, Bool)+ = Arc (Double, Double, Double, Radians, Radians, Bool) | ArcTo (Double, Double, Double, Double, Double) | BeginPath | BezierCurveTo (Double, Double, Double, Double, Double, Double)@@ -58,8 +77,8 @@ | FillRect (Double, Double, Double, Double) | forall style . Style style => FillStyle style | FillText (Text, Double, Double)- | Font Text- | GlobalAlpha Double+ | forall canvasFont . CanvasFont canvasFont => Font canvasFont+ | GlobalAlpha Alpha | GlobalCompositeOperation Text | LineCap LineEndCap | LineJoin LineJoinCorner@@ -71,9 +90,9 @@ | QuadraticCurveTo (Double, Double, Double, Double) | Rect (Double, Double, Double, Double) | Restore- | Rotate Double+ | Rotate Radians | Save- | Scale (Double, Double)+ | Scale (Interval, Interval) | SetTransform (Double, Double, Double, Double, Double, Double) | ShadowBlur Double | forall canvasColor . CanvasColor canvasColor => ShadowColor canvasColor@@ -90,17 +109,21 @@ data Command = Trigger Event- | forall color . CanvasColor color => AddColorStop (Double, color) CanvasGradient+ | forall color . CanvasColor color => AddColorStop (Interval, color) CanvasGradient | forall msg . JSArg msg => Log msg | Eval Text instance Show Command where- show (Trigger e) = "Trigger(" ++ map (chr . fromEnum) (DBL.unpack (encode e)) ++ ")"- show (AddColorStop (off,rep) g)- = showJS g ++ ".addColorStop(" ++ showJS off ++ "," ++ jsStyle rep ++ ")"- show (Log msg) = "console.log(" ++ showJS msg ++ ")"- show (Eval cmd) = Text.unpack cmd -- no escaping or interpretation+ showsPrec p = showsPrec p . FromTextShow +instance TextShow Command where+ showb (Trigger e) = "Trigger(" <> (fromLazyText . decodeUtf8 $ encode e) <> singleton ')'+ showb (AddColorStop (off,rep) g) = jsCanvasGradient g <> ".addColorStop("+ <> jsDouble off <> singleton ',' <> jsCanvasColor rep+ <> singleton ')'+ showb (Log msg) = "console.log(" <> showbJS msg <> singleton ')'+ showb (Eval cmd) = fromText cmd -- no escaping or interpretation+ ----------------------------------------------------------------------------- -- | 'with' runs a set of canvas commands in the context@@ -108,21 +131,30 @@ with :: CanvasContext -> Canvas a -> Canvas a with = With --- | 'myCanvasContext' returns the current 'CanvasContent'.+-- | 'myCanvasContext' returns the current 'CanvasContext'. myCanvasContext :: Canvas CanvasContext myCanvasContext = MyContext ----------------------------------------------------------------------------- --- | trigger a specific named event, please.+-- | Triggers a specific named event. trigger :: Event -> Canvas () trigger = Command . Trigger --- | add a Color stop to a Canvas Gradient.-addColorStop :: CanvasColor color => (Double, color) -> CanvasGradient -> Canvas ()+-- | Adds a color and stop position in a 'CanvasGradient'. A stop position is a+-- number between 0.0 and 1.0 that represents the position between start and stop+-- in a gradient.+--+-- ==== __Example__+--+-- @+-- grd <- 'createLinearGradient'(0, 0, 10, 10)+-- grd # 'addColorStop'(0, 'red')+-- @+addColorStop :: CanvasColor color => (Interval, color) -> CanvasGradient -> Canvas () addColorStop (off,rep) = Command . AddColorStop (off,rep) --- | 'console_log' aids debugging by sending the argument to the browser console.log.+-- | 'console_log' aids debugging by sending the argument to the browser @console.log@. console_log :: JSArg msg => msg -> Canvas () console_log = Command . Log @@ -131,100 +163,205 @@ eval = Command . Eval ------------------------------------------------------------------------------data Query :: * -> * where- Device :: Query DeviceAttributes- ToDataURL :: Query Text- MeasureText :: Text -> Query TextMetrics- IsPointInPath :: (Double, Double) -> Query Bool- NewImage :: Text -> Query CanvasImage- CreateLinearGradient :: (Double, Double, Double, Double) -> Query CanvasGradient- CreateRadialGradient :: (Double, Double, Double, Double, Double, Double) -> Query CanvasGradient- CreatePattern :: Image image => (image, RepeatDirection) -> Query CanvasPattern- NewCanvas :: (Int, Int) -> Query CanvasContext- GetImageData :: (Double, Double, Double, Double) -> Query ImageData- Sync :: Query () -data DeviceAttributes = DeviceAttributes Int Int Double- deriving Show+data Function :: * -> * where+ CreateLinearGradient :: (Double,Double,Double,Double) -> Function CanvasGradient+ CreateRadialGradient :: (Double,Double,Double,Double,Double,Double) -> Function CanvasGradient+ CreatePattern :: Image image => (image, RepeatDirection) -> Function CanvasPattern --- | The 'width' argument of 'TextMetrics' can trivially be projected out.-data TextMetrics = TextMetrics Double- deriving Show +instance Show (Function a) where+ showsPrec p = showsPrec p . FromTextShow++instance TextShow (Function a) where+ showb (CreateLinearGradient (x0,y0,x1,y1)) = "createLinearGradient("+ <> jsDouble x0 <> singleton ',' <> jsDouble y0 <> singleton ','+ <> jsDouble x1 <> singleton ',' <> jsDouble y1 <> singleton ')'+ showb (CreateRadialGradient (x0,y0,r0,x1,y1,r1)) = "createRadialGradient("+ <> jsDouble x0 <> singleton ',' <> jsDouble y0 <> singleton ',' <> jsDouble r0 <> singleton ','+ <> jsDouble x1 <> singleton ',' <> jsDouble y1 <> singleton ',' <> jsDouble r1 <> singleton ')'+ showb (CreatePattern (img,dir)) = "createPattern("+ <> jsImage img <> singleton ',' <> jsRepeatDirection dir <> singleton ')'++-----------------------------------------------------------------------------++data Query :: * -> * where+ Device :: Query DeviceAttributes+ ToDataURL :: Query Text+ MeasureText :: Text -> Query TextMetrics+ IsPointInPath :: (Double, Double) -> Query Bool+ NewImage :: Text -> Query CanvasImage+ NewAudio :: Text -> Query CanvasAudio+ NewCanvas :: (Int, Int) -> Query CanvasContext+ GetImageData :: (Double, Double, Double, Double) -> Query ImageData+ Cursor :: CanvasCursor cursor => cursor -> Query ()+ Sync :: Query ()+ instance Show (Query a) where- show Device = "Device"- show ToDataURL = "ToDataURL"- show (MeasureText txt) = "MeasureText(" ++ showJS txt ++ ")"- show (IsPointInPath (x,y)) = "IsPointInPath(" ++ showJS x ++ "," ++ showJS y ++ ")"- show (NewImage url) = "NewImage(" ++ showJS url ++ ")"- show (CreateLinearGradient (x0,y0,x1,y1)) = "CreateLinearGradient(" ++ showJS x0 ++ "," ++ showJS y0 ++ "," ++ showJS x1 ++ "," ++ showJS y1 ++ ")"- show (CreateRadialGradient (x0,y0,r0,x1,y1,r1)) = "CreateRadialGradient(" ++ showJS x0 ++ "," ++ showJS y0 ++ "," ++ showJS r0 ++ "," ++ showJS x1 ++ "," ++ showJS y1 ++ "," ++ showJS r1 ++ ")"- show (CreatePattern (img,dir)) = "CreatePattern(" ++ jsImage img ++ "," ++ jsRepeatDirection dir ++ ")"- show (NewCanvas (x,y)) = "NewCanvas(" ++ showJS x ++ "," ++ showJS y ++ ")"- show (GetImageData (sx,sy,sw,sh))- = "GetImageData(" ++ showJS sx ++ "," ++ showJS sy ++ "," ++ showJS sw ++ "," ++ showJS sh ++ ")"- show Sync = "Sync"+ showsPrec p = showsPrec p . FromTextShow +instance TextShow (Query a) where+ showb Device = "Device"+ showb ToDataURL = "ToDataURL"+ showb (MeasureText txt) = "MeasureText(" <> jsText txt <> singleton ')'+ showb (IsPointInPath (x,y)) = "IsPointInPath(" <> jsDouble x <> singleton ','+ <> jsDouble y <> singleton ')'+ showb (NewImage url') = "NewImage(" <> jsText url' <> singleton ')'+ showb (NewAudio txt) = "NewAudio(" <> jsText txt <> singleton ')'++ showb (NewCanvas (x,y)) = "NewCanvas(" <> jsInt x <> singleton ','+ <> jsInt y <> singleton ')'+ showb (GetImageData (sx,sy,sw,sh)) = "GetImageData(" <> jsDouble sx <> singleton ','+ <> jsDouble sy <> singleton ','+ <> jsDouble sw <> singleton ','+ <> jsDouble sh <> singleton ')'+ showb (Cursor cur) = "Cursor(" <> jsCanvasCursor cur <> singleton ')'+ showb Sync = "Sync"+ -- This is how we take our value to bits parseQueryResult :: Query a -> Value -> Parser a-parseQueryResult (Device {}) o = uncurry3 DeviceAttributes <$> parseJSON o-parseQueryResult (ToDataURL {}) o = parseJSON o-parseQueryResult (MeasureText {}) (Object v) = TextMetrics <$> v .: "width"-parseQueryResult (IsPointInPath {}) o = parseJSON o-parseQueryResult (NewImage {}) o = uncurry3 CanvasImage <$> parseJSON o-parseQueryResult (CreateLinearGradient {}) o = CanvasGradient <$> parseJSON o-parseQueryResult (CreateRadialGradient {}) o = CanvasGradient <$> parseJSON o-parseQueryResult (CreatePattern {}) o = CanvasPattern <$> parseJSON o-parseQueryResult (NewCanvas {}) o = uncurry3 CanvasContext <$> parseJSON o+parseQueryResult (Device {}) o = uncurry3 DeviceAttributes <$> parseJSON o+parseQueryResult (ToDataURL {}) o = parseJSON o+parseQueryResult (MeasureText {}) (Object v) = TextMetrics <$> v .: "width"+parseQueryResult (IsPointInPath {}) o = parseJSON o+parseQueryResult (NewImage {}) o = uncurry3 CanvasImage <$> parseJSON o+parseQueryResult (NewAudio {}) o = uncurry CanvasAudio <$> parseJSON o+parseQueryResult (NewCanvas {}) o = uncurry3 CanvasContext <$> parseJSON o parseQueryResult (GetImageData {}) (Object o) = ImageData- <$> (o .: "width")- <*> (o .: "height")- <*> (o .: "data")-parseQueryResult (Sync {}) _ = return () -- we just accept anything; empty list sent-parseQueryResult _ _ = fail "no parse in blank-canvas server (internal error)"+ <$> (o .: "width")+ <*> (o .: "height")+ <*> (o .: "data")+parseQueryResult (Cursor {}) _ = return ()+parseQueryResult (Sync {}) _ = return () -- we just accept anything; empty list sent+parseQueryResult _ _ = fail "no parse in blank-canvas server (internal error)" -uncurry3 :: (t0 -> t1 -> t2 -> t3) -> (t0, t1, t2) -> t3+uncurry3 :: (a -> b -> c -> d) -> (a, b, c) -> d uncurry3 f (a,b,c) = f a b c device :: Canvas DeviceAttributes device = Query Device --- | Turn the canvas into a png data stream / data URL.+-- | Turn the canvas into a PNG data stream / data URL. -- -- > "data:image/png;base64,iVBORw0KGgo.." -- toDataURL :: () -> Canvas Text toDataURL () = Query ToDataURL +-- | Queries the measured width of the text argument.+--+-- ==== __Example__+--+-- @+-- 'TextMetrics' w <- 'measureText' \"Hello, World!\"+-- @ measureText :: Text -> Canvas TextMetrics measureText = Query . MeasureText +-- | @'isPointInPath'(x, y)@ queries whether point @(x, y)@ is within the current path.+--+-- ==== __Example__+--+-- @+-- 'rect'(10, 10, 100, 100)+-- 'stroke'()+-- b <- 'isPointInPath'(10, 10) -- b == True+-- @ isPointInPath :: (Double, Double) -> Canvas Bool isPointInPath = Query . IsPointInPath --- | 'image' takes a URL (perhaps a data URL), and returns the 'CanvasImage' handle,--- _after_ loading.--- The assumption is you are using local images, so loading should be near instant.+-- | 'newImage' takes a URL (perhaps a data URL), and returns the 'CanvasImage' handle+-- /after/ loading.+-- If you are using local images, loading should be near instant. newImage :: Text -> Canvas CanvasImage newImage = Query . NewImage +-- | 'newAudio' takes an URL to an audio file and returs the 'CanvasAudio' handle+-- /after/ loading.+-- If you are using local audio files, loading should be near instant.+newAudio :: Text -> Canvas CanvasAudio+newAudio = Query . NewAudio++-- | @'createLinearGradient'(x0, y0, x1, y1)@ creates a linear gradient along a line,+-- which can be used to fill other shapes.+--+-- * @x0@ is the starting x-coordinate of the gradient+--+-- * @y0@ is the starting y-coordinate of the gradient+--+-- * @x1@ is the ending y-coordinate of the gradient+--+-- * @y1@ is the ending y-coordinate of the gradient+--+-- ==== __Example__+--+-- @+-- grd <- 'createLinearGradient'(0, 0, 10, 10)+-- grd # 'addColorStop'(0, \"blue\")+-- grd # 'addColorStop'(1, \"red\")+-- 'fillStyle' grd+-- @ createLinearGradient :: (Double, Double, Double, Double) -> Canvas CanvasGradient-createLinearGradient = Query . CreateLinearGradient+createLinearGradient = Function . CreateLinearGradient +-- | @'createRadialGradient'(x0, y0, r0, x1, y1, r1)@ creates a radial gradient given+-- by the coordinates of two circles, which can be used to fill other shapes.+--+-- * @x0@ is the x-axis of the coordinate of the start circle+--+-- * @y0@ is the y-axis of the coordinate of the start circle+--+-- * @r0@ is the radius of the start circle+--+-- * @x1@ is the x-axis of the coordinate of the end circle+--+-- * @y1@ is the y-axis of the coordinate of the end circle+--+-- * @r1@ is the radius of the end circle+--+-- ==== __Example__+--+-- @+-- grd <- 'createRadialGradient'(100,100,100,100,100,0)+-- grd # 'addColorStop'(0, \"blue\")+-- grd # 'addColorStop'(1, \"red\")+-- 'fillStyle' grd+-- @ createRadialGradient :: (Double, Double, Double, Double, Double, Double) -> Canvas CanvasGradient-createRadialGradient = Query . CreateRadialGradient+createRadialGradient = Function . CreateRadialGradient +-- | Creates a pattern using a 'CanvasImage' and a 'RepeatDirection'.+--+-- ==== __Example__+--+-- @+-- img <- newImage \"cat.jpg\"+-- pat <- 'createPattern'(img, 'repeatX')+-- 'fillStyle' pat+-- @ createPattern :: (CanvasImage, RepeatDirection) -> Canvas CanvasPattern-createPattern = Query . CreatePattern+createPattern = Function . CreatePattern --- | Create a new, off-screen canvas buffer. Takes width and height.+-- | Create a new, off-screen canvas buffer. Takes width and height as arguments. newCanvas :: (Int, Int) -> Canvas CanvasContext newCanvas = Query . NewCanvas --- | Capture ImageDate from the Canvas.+-- | @'getImageData'(x, y, w, h)@ capture 'ImageData' from the rectangle with+-- upper-left corner @(x, y)@, width @w@, and height @h@. getImageData :: (Double, Double, Double, Double) -> Canvas ImageData getImageData = Query . GetImageData --- | Send all commands to the browser, wait for the browser to ack, then continue.+-- | Change the canvas cursor to the specified URL or keyword. +--+-- ==== __Examples__+--+-- @+-- cursor $ 'url' \"image.png\" 'default_'+-- cursor 'crosshair'+-- @+cursor :: CanvasCursor cursor => cursor -> Canvas ()+cursor = Query . Cursor++-- | Send all commands to the browser, wait for the browser to act, then continue. sync :: Canvas ()-sync = Query $ Sync+sync = Query Sync
+ Graphics/Blank/Cursor.hs view
@@ -0,0 +1,63 @@+{-|+Module: Graphics.Blank.Cursor+Copyright: (C) 2014-2015, The University of Kansas+License: BSD-style (see the file LICENSE)+Maintainer: Andy Gill+Stability: Beta+Portability: GHC++This module exposes an overloaded version of the 'cursor' function that can accept+a 'Cursor' ADT argument. This may be of interest if you desire stronger type safety+than @Text@-based cursors provide.++Note that this module's 'cursor' function conflicts with @cursor@ from+"Graphics.Blank". Make sure to hide @cursor@ from "Graphics.Blank" if you use+'cursor' from this module.+-}+module Graphics.Blank.Cursor (+ -- * Overloaded 'cursor'+ cursor+ , CanvasCursor(..)+ -- Cursors+ , Cursor(..)+ , auto+ , default_+ , none+ , contextMenu+ , help+ , pointer+ , progress+ , wait+ , cell+ , crosshair+ , text+ , verticalText+ , alias+ , copy+ , move+ , noDrop+ , notAllowed+ , allScroll+ , colResize+ , rowResize+ , nResize+ , eResize+ , sResize+ , wResize+ , neResize+ , nwResize+ , seResize+ , swResize+ , ewResize+ , nsResize+ , neswResize+ , nwseResize+ , zoomIn+ , zoomOut+ , grab+ , grabbing+ , url+ ) where++import Graphics.Blank.Canvas (cursor)+import Graphics.Blank.Types.Cursor
Graphics/Blank/DeviceContext.hs view
@@ -6,25 +6,29 @@ import Data.Set (Set) import Data.Monoid ((<>)) import Data.Text (Text)-import qualified Data.Text as T import Graphics.Blank.Events import Graphics.Blank.JavaScript -import qualified Web.Scotty.Comet as KC+import TextShow (Builder, toText) --- | 'Context' is our abstact handle into a specific 2d-context inside a browser.--- Note that the JavaScript API concepts of 2D-Context and Canvas--- are conflated in blank-canvas. Therefore, there is no 'getContext' method,--- rather 'getContext' is implied (when using 'send').+import qualified Web.Scotty.Comet as KC +-- | 'DeviceContext' is the abstract handle into a specific 2D context inside a browser.+-- Note that the JavaScript API concepts of+-- @<https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D CanvasRenderingContext2D>@ and+-- @<https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement HTMLCanvasElement>@+-- are conflated in @blank-canvas@. Therefore, there is no+-- @<https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/getContext getContext()>@ method;+-- rather, @getContext()@ is implied (when using 'send'). data DeviceContext = DeviceContext- { theComet :: KC.Document -- ^ The mechansims for sending commands+ { theComet :: KC.Document -- ^ The mechanisms for sending commands , eventQueue :: EventQueue -- ^ A single (typed) event queue , ctx_width :: !Int , ctx_height :: !Int , ctx_devicePixelRatio :: !Double , localFiles :: TVar (Set Text) -- ^ approved local files+ , weakRemoteMonad :: Bool -- ^ use a weak remote monad for debugging } instance Image DeviceContext where@@ -35,20 +39,21 @@ deviceCanvasContext :: DeviceContext -> CanvasContext deviceCanvasContext cxt = CanvasContext 0 (ctx_width cxt) (ctx_height cxt) --- ** 'devicePixelRatio' returns the Device Pixel Ratio as used. Typically, the browser ignore devicePixelRatio in the canvas,--- which can make fine details and text look fuzzy. Using the query "?hd" on the URL, blank-canvas attempts--- to use the native devicePixelRatio, and if successful, 'devicePixelRatio' will return a number other than 1.--- You can think of devicePixelRatio as the line width to use to make lines look one pixel wide.-+-- | 'devicePixelRatio' returns the device's pixel ratio as used. Typically, the+-- browser ignores @devicePixelRatio@ in the canvas, which can make fine details+-- and text look fuzzy. Using the query @?hd@ on the URL, @blank-canvas@ attempts+-- to use the native @devicePixelRatio@, and if successful, 'devicePixelRatio' will+-- return a number other than 1. You can think of 'devicePixelRatio' as the line+-- width to use to make lines look one pixel wide. devicePixelRatio :: DeviceContext -> Double devicePixelRatio = ctx_devicePixelRatio --- | internal command to send a message to the canvas.-sendToCanvas :: DeviceContext -> ShowS -> IO ()+-- | Internal command to send a message to the canvas.+sendToCanvas :: DeviceContext -> Builder -> IO () sendToCanvas cxt cmds = do- KC.send (theComet cxt) $ "try{" <> T.pack (cmds "}catch(e){alert('JavaScript Failure: '+e.message);}")+ KC.send (theComet cxt) . toText $ "try{" <> cmds <> "}catch(e){alert('JavaScript Failure: '+e.message);}" --- | wait for any event. blocks.+-- | Wait for any event. Blocks. wait :: DeviceContext -> IO Event wait c = atomically $ readTChan (eventQueue c)
Graphics/Blank/Events.hs view
@@ -1,22 +1,37 @@-{-# LANGUAGE ScopedTypeVariables, OverloadedStrings #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TemplateHaskell #-} module Graphics.Blank.Events where -import Control.Applicative ((<|>), (<$>), (<*>))+import Control.Applicative import Control.Concurrent.STM import Data.Aeson (FromJSON(..), Value(..), ToJSON(..)) import Data.Aeson.Types ((.:), (.=), object) import Data.Text (Text) --- | Basic Event from Browser; see <http://api.jquery.com/category/events/> for details.+import TextShow.TH (deriveTextShow)++-- | 'EventName' mirrors event names from jQuery, and uses lowercase.+-- Possible named events+-- +-- * @keypress@, @keydown@, @keyup@+-- * @mouseDown@, @mouseenter@, @mousemove@, @mouseout@, @mouseover@, @mouseup@+type EventName = Text++-- | 'EventQueue' is an STM channel ('TChan') of 'Event's.+-- Intentionally, 'EventQueue' is not abstract.+type EventQueue = TChan Event++-- | Basic event from browser. See <http://api.jquery.com/category/events/> for details. data Event = Event { eMetaKey :: Bool , ePageXY :: Maybe (Double, Double) , eType :: EventName -- "Describes the nature of the event." jquery , eWhich :: Maybe Int -- magic code for key presses }- deriving (Show)-+ deriving (Eq, Ord, Show)+$(deriveTextShow ''Event) instance FromJSON Event where parseJSON (Object v) = Event <$> ((v .: "metaKey") <|> return False)@@ -36,16 +51,3 @@ Nothing -> id Just w -> (:) ("which" .= w)) $ []---- | 'EventName' mirrors event names from jquery, and use lower case.--- Possible named events------ * keypress, keydown, keyup--- * mouseDown, mouseenter, mousemove, mouseout, mouseover, mouseup--- -type EventName = Text---- | EventQueue is a STM channel ('TChan') of 'Event's.--- Intentionally, 'EventQueue' is not abstract.-type EventQueue = TChan Event-
+ Graphics/Blank/Font.hs view
@@ -0,0 +1,86 @@+{-|+Module: Graphics.Blank.Font+Copyright: (C) 2014-2015, The University of Kansas+License: BSD-style (see the file LICENSE)+Maintainer: Andy Gill+Stability: Beta+Portability: GHC++This module exposes an overloaded version of the 'font' function that can accept+a 'Font' ADT argument. This may be of interest if you desire stronger type safety+than @Text@-based fonts provide.++Note that this module's 'font' function conflicts with @font@ from "Graphics.Blank".+Make sure to hide @font@ from "Graphics.Blank" if you use 'font' from this module.+-}+module Graphics.Blank.Font+ ( -- * Overloaded 'font'+ font+ , CanvasFont(..)+ -- * @font@+ , Font(..)+ , defFont+ , caption+ , icon+ , menu+ , messageBox+ , smallCaption+ , statusBar+ -- * @font-style@+ , FontStyle(..)+ , italic+ , oblique+ -- * @font-variant@+ , FontVariant(..)+ , smallCaps+ -- * @font-weight@+ , FontWeight(..)+ , bold+ , bolder+ , lighter+ -- * @font-size@+ , FontSize(..)+ , xxSmall+ , xSmall+ , small+ , medium+ , large+ , xLarge+ , xxLarge+ , larger+ , smaller+ -- * @line-height@+ , LineHeight(..)+ -- * @font-family@+ , FontFamily(..)+ , serif+ , sansSerif+ , monospace+ , cursive+ , fantasy+ -- * Normal values+ , NormalProperty(..)+ -- * Lengths+ , Length(..)+ , LengthProperty(..)+ , em+ , ex+ , ch+ , rem_+ , vh+ , vw+ , vmin+ , vmax+ , px+ , mm+ , cm+ , in_+ , pt+ , pc+ -- * Percentages+ , PercentageProperty(..)+ ) where++import Graphics.Blank.Generated (font)+import Graphics.Blank.Types.CSS+import Graphics.Blank.Types.Font
Graphics/Blank/GHCi.hs view
@@ -1,3 +1,14 @@+{-|+Module: Graphics.Blank.GHCi+Copyright: (C) 2014-2015, The University of Kansas+License: BSD-style (see the file LICENSE)+Maintainer: Andy Gill+Stability: Beta+Portability: GHC++The GHCi entry point for @blank-canvas@. Useful for sending multiple+commands to the same port.+-} module Graphics.Blank.GHCi (splatCanvas) where import Control.Concurrent@@ -8,15 +19,15 @@ import System.IO.Unsafe (unsafePerformIO) --- | splitCanvas is the GHCi entry point into blank-canvas.+-- | splitCanvas is the GHCi entry point into @blank-canvas@. -- A typical invocation would be---+-- -- >GHCi> import Graphics.Blank -- >GHCi> import Graphics.Blank.GHCi -- > -- >-- Adding commands to the canvas buffer -- >GHCi> splatCanvas 3000 $ ( .. canvas commands .. )---+-- -- The system remembers if it has been called on a specific port before, -- and if so, uses the previous session.
Graphics/Blank/Generated.hs view
@@ -1,73 +1,192 @@+{-# LANGUAGE OverloadedStrings #-} {-# OPTIONS_GHC -fno-warn-orphans #-} module Graphics.Blank.Generated where -import Graphics.Blank.Canvas-import Graphics.Blank.JavaScript-import Data.Text (Text)+import Data.Monoid ((<>))+import Data.Text (Text) +import Graphics.Blank.Canvas+import Graphics.Blank.JavaScript+import Graphics.Blank.Types+import Graphics.Blank.Types.Font++import TextShow (TextShow(..), FromTextShow(..), showb, singleton)+ instance Show Method where- show (Arc (a1,a2,a3,a4,a5,a6)) = "arc(" ++ jsDouble a1 ++ "," ++ jsDouble a2 ++ "," ++ jsDouble a3 ++ "," ++ jsDouble a4 ++ "," ++ jsDouble a5 ++ "," ++ jsBool a6 ++ ")"- show (ArcTo (a1,a2,a3,a4,a5)) = "arcTo(" ++ jsDouble a1 ++ "," ++ jsDouble a2 ++ "," ++ jsDouble a3 ++ "," ++ jsDouble a4 ++ "," ++ jsDouble a5 ++ ")"- show BeginPath = "beginPath()"- show (BezierCurveTo (a1,a2,a3,a4,a5,a6)) = "bezierCurveTo(" ++ jsDouble a1 ++ "," ++ jsDouble a2 ++ "," ++ jsDouble a3 ++ "," ++ jsDouble a4 ++ "," ++ jsDouble a5 ++ "," ++ jsDouble a6 ++ ")"- show (ClearRect (a1,a2,a3,a4)) = "clearRect(" ++ jsDouble a1 ++ "," ++ jsDouble a2 ++ "," ++ jsDouble a3 ++ "," ++ jsDouble a4 ++ ")"- show Clip = "clip()"- show ClosePath = "closePath()"- show (DrawImage (a1,a2)) = "drawImage(" ++ jsImage a1 ++ "," ++ jsList jsDouble a2 ++ ")"- show Fill = "fill()"- show (FillRect (a1,a2,a3,a4)) = "fillRect(" ++ jsDouble a1 ++ "," ++ jsDouble a2 ++ "," ++ jsDouble a3 ++ "," ++ jsDouble a4 ++ ")"- show (FillStyle (a1)) = "fillStyle = (" ++ jsStyle a1 ++ ")"- show (FillText (a1,a2,a3)) = "fillText(" ++ jsText a1 ++ "," ++ jsDouble a2 ++ "," ++ jsDouble a3 ++ ")"- show (Font (a1)) = "font = (" ++ jsText a1 ++ ")"- show (GlobalAlpha (a1)) = "globalAlpha = (" ++ jsDouble a1 ++ ")"- show (GlobalCompositeOperation (a1)) = "globalCompositeOperation = (" ++ jsText a1 ++ ")"- show (LineCap (a1)) = "lineCap = (" ++ jsLineEndCap a1 ++ ")"- show (LineJoin (a1)) = "lineJoin = (" ++ jsLineJoinCorner a1 ++ ")"- show (LineTo (a1,a2)) = "lineTo(" ++ jsDouble a1 ++ "," ++ jsDouble a2 ++ ")"- show (LineWidth (a1)) = "lineWidth = (" ++ jsDouble a1 ++ ")"- show (MiterLimit (a1)) = "miterLimit = (" ++ jsDouble a1 ++ ")"- show (MoveTo (a1,a2)) = "moveTo(" ++ jsDouble a1 ++ "," ++ jsDouble a2 ++ ")"- show (PutImageData (a1,a2)) = "putImageData(" ++ jsImageData a1 ++ "," ++ jsList jsDouble a2 ++ ")"- show (QuadraticCurveTo (a1,a2,a3,a4)) = "quadraticCurveTo(" ++ jsDouble a1 ++ "," ++ jsDouble a2 ++ "," ++ jsDouble a3 ++ "," ++ jsDouble a4 ++ ")"- show (Rect (a1,a2,a3,a4)) = "rect(" ++ jsDouble a1 ++ "," ++ jsDouble a2 ++ "," ++ jsDouble a3 ++ "," ++ jsDouble a4 ++ ")"- show Restore = "restore()"- show (Rotate (a1)) = "rotate(" ++ jsDouble a1 ++ ")"- show Save = "save()"- show (Scale (a1,a2)) = "scale(" ++ jsDouble a1 ++ "," ++ jsDouble a2 ++ ")"- show (SetTransform (a1,a2,a3,a4,a5,a6)) = "setTransform(" ++ jsDouble a1 ++ "," ++ jsDouble a2 ++ "," ++ jsDouble a3 ++ "," ++ jsDouble a4 ++ "," ++ jsDouble a5 ++ "," ++ jsDouble a6 ++ ")"- show (ShadowBlur (a1)) = "shadowBlur = (" ++ jsDouble a1 ++ ")"- show (ShadowColor (a1)) = "shadowColor = (" ++ jsCanvasColor a1 ++ ")"- show (ShadowOffsetX (a1)) = "shadowOffsetX = (" ++ jsDouble a1 ++ ")"- show (ShadowOffsetY (a1)) = "shadowOffsetY = (" ++ jsDouble a1 ++ ")"- show Stroke = "stroke()"- show (StrokeRect (a1,a2,a3,a4)) = "strokeRect(" ++ jsDouble a1 ++ "," ++ jsDouble a2 ++ "," ++ jsDouble a3 ++ "," ++ jsDouble a4 ++ ")"- show (StrokeStyle (a1)) = "strokeStyle = (" ++ jsStyle a1 ++ ")"- show (StrokeText (a1,a2,a3)) = "strokeText(" ++ jsText a1 ++ "," ++ jsDouble a2 ++ "," ++ jsDouble a3 ++ ")"- show (TextAlign (a1)) = "textAlign = (" ++ jsTextAnchorAlignment a1 ++ ")"- show (TextBaseline (a1)) = "textBaseline = (" ++ jsTextBaselineAlignment a1 ++ ")"- show (Transform (a1,a2,a3,a4,a5,a6)) = "transform(" ++ jsDouble a1 ++ "," ++ jsDouble a2 ++ "," ++ jsDouble a3 ++ "," ++ jsDouble a4 ++ "," ++ jsDouble a5 ++ "," ++ jsDouble a6 ++ ")"- show (Translate (a1,a2)) = "translate(" ++ jsDouble a1 ++ "," ++ jsDouble a2 ++ ")"+ showsPrec p = showsPrec p . FromTextShow +instance TextShow Method where+ showb (Arc (a1,a2,a3,a4,a5,a6)) = "arc("+ <> jsDouble a1 <> singleton ',' <> jsDouble a2 <> singleton ','+ <> jsDouble a3 <> singleton ',' <> jsDouble a4 <> singleton ','+ <> jsDouble a5 <> singleton ',' <> jsBool a6 <> singleton ')'+ showb (ArcTo (a1,a2,a3,a4,a5)) = "arcTo("+ <> jsDouble a1 <> singleton ',' <> jsDouble a2 <> singleton ',' <> jsDouble a3 <> singleton ','+ <> jsDouble a4 <> singleton ',' <> jsDouble a5 <> singleton ')'+ showb BeginPath = "beginPath()"+ showb (BezierCurveTo (a1,a2,a3,a4,a5,a6)) = "bezierCurveTo("+ <> jsDouble a1 <> singleton ',' <> jsDouble a2 <> singleton ','+ <> jsDouble a3 <> singleton ',' <> jsDouble a4 <> singleton ','+ <> jsDouble a5 <> singleton ',' <> jsDouble a6 <> singleton ')'+ showb (ClearRect (a1,a2,a3,a4)) = "clearRect("+ <> jsDouble a1 <> singleton ',' <> jsDouble a2 <> singleton ','+ <> jsDouble a3 <> singleton ',' <> jsDouble a4 <> singleton ')'+ showb Clip = "clip()"+ showb ClosePath = "closePath()"+ showb (DrawImage (a1,a2)) = "drawImage(" <> jsImage a1 <> singleton ',' <> jsList jsDouble a2 <> singleton ')'+ showb Fill = "fill()"+ showb (FillRect (a1,a2,a3,a4)) = "fillRect("+ <> jsDouble a1 <> singleton ',' <> jsDouble a2 <> singleton ','+ <> jsDouble a3 <> singleton ',' <> jsDouble a4 <> singleton ')'+ showb (FillStyle (a1)) = "fillStyle = (" <> jsStyle a1 <> singleton ')'+ showb (FillText (a1,a2,a3)) = "fillText(" <> jsText a1 <> singleton ',' <> jsDouble a2 <> singleton ',' <> jsDouble a3 <> singleton ')'+ showb (Font (a1)) = "font = (" <> jsCanvasFont a1 <> singleton ')'+ showb (GlobalAlpha (a1)) = "globalAlpha = (" <> jsDouble a1 <> singleton ')'+ showb (GlobalCompositeOperation (a1)) = "globalCompositeOperation = (" <> jsText a1 <> singleton ')'+ showb (LineCap (a1)) = "lineCap = (" <> jsLineEndCap a1 <> singleton ')'+ showb (LineJoin (a1)) = "lineJoin = (" <> jsLineJoinCorner a1 <> singleton ')'+ showb (LineTo (a1,a2)) = "lineTo(" <> jsDouble a1 <> singleton ',' <> jsDouble a2 <> singleton ')'+ showb (LineWidth (a1)) = "lineWidth = (" <> jsDouble a1 <> singleton ')'+ showb (MiterLimit (a1)) = "miterLimit = (" <> jsDouble a1 <> singleton ')'+ showb (MoveTo (a1,a2)) = "moveTo(" <> jsDouble a1 <> singleton ',' <> jsDouble a2 <> singleton ')'+ showb (PutImageData (a1,a2)) = "putImageData(" <> jsImageData a1 <> singleton ',' <> jsList jsDouble a2 <> singleton ')'+ showb (QuadraticCurveTo (a1,a2,a3,a4)) = "quadraticCurveTo("+ <> jsDouble a1 <> singleton ',' <> jsDouble a2 <> singleton ','+ <> jsDouble a3 <> singleton ',' <> jsDouble a4 <> singleton ')'+ showb (Rect (a1,a2,a3,a4)) = "rect("+ <> jsDouble a1 <> singleton ',' <> jsDouble a2 <> singleton ','+ <> jsDouble a3 <> singleton ',' <> jsDouble a4 <> singleton ')'+ showb Restore = "restore()"+ showb (Rotate (a1)) = "rotate(" <> jsDouble a1 <> singleton ')'+ showb Save = "save()"+ showb (Scale (a1,a2)) = "scale(" <> jsDouble a1 <> singleton ',' <> jsDouble a2 <> singleton ')'+ showb (SetTransform (a1,a2,a3,a4,a5,a6)) = "setTransform("+ <> jsDouble a1 <> singleton ',' <> jsDouble a2 <> singleton ','+ <> jsDouble a3 <> singleton ',' <> jsDouble a4 <> singleton ','+ <> jsDouble a5 <> singleton ',' <> jsDouble a6 <> singleton ')'+ showb (ShadowBlur (a1)) = "shadowBlur = (" <> jsDouble a1 <> singleton ')'+ showb (ShadowColor (a1)) = "shadowColor = (" <> jsCanvasColor a1 <> singleton ')'+ showb (ShadowOffsetX (a1)) = "shadowOffsetX = (" <> jsDouble a1 <> singleton ')'+ showb (ShadowOffsetY (a1)) = "shadowOffsetY = (" <> jsDouble a1 <> singleton ')'+ showb Stroke = "stroke()"+ showb (StrokeRect (a1,a2,a3,a4)) = "strokeRect("+ <> jsDouble a1 <> singleton ',' <> jsDouble a2 <> singleton ','+ <> jsDouble a3 <> singleton ',' <> jsDouble a4 <> singleton ')'+ showb (StrokeStyle (a1)) = "strokeStyle = (" <> jsStyle a1 <> singleton ')'+ showb (StrokeText (a1,a2,a3)) = "strokeText(" <> jsText a1 <> singleton ',' <> jsDouble a2 <> singleton ',' <> jsDouble a3 <> singleton ')'+ showb (TextAlign (a1)) = "textAlign = (" <> jsTextAnchorAlignment a1 <> singleton ')'+ showb (TextBaseline (a1)) = "textBaseline = (" <> jsTextBaselineAlignment a1 <> singleton ')'+ showb (Transform (a1,a2,a3,a4,a5,a6)) = "transform("+ <> jsDouble a1 <> singleton ',' <> jsDouble a2 <> singleton ','+ <> jsDouble a3 <> singleton ',' <> jsDouble a4 <> singleton ','+ <> jsDouble a5 <> singleton ',' <> jsDouble a6 <> singleton ')'+ showb (Translate (a1,a2)) = "translate(" <> jsDouble a1 <> singleton ',' <> jsDouble a2 <> singleton ')'+ -- DSL -arc :: (Double, Double, Double, Double, Double, Bool) -> Canvas ()+-- | @'arc'(x, y, r, sAngle, eAngle, cc)@ creates a circular arc, where+-- +-- * @x@ is the x-coordinate of the center of the circle+-- +-- * @y@ is the y-coordinate of the center of the circle+-- +-- * @r@ is the radius of the circle on which the arc is drawn+-- +-- * @sAngle@ is the starting angle (where @0@ at the 3 o'clock position of the circle)+-- +-- * @eAngle@ is the ending angle+-- +-- * @cc@ is the arc direction, where @True@ indicates counterclockwise and+-- @False@ indicates clockwise.+arc :: (Double, Double, Double, Radians, Radians, Bool) -> Canvas () arc = Method . Arc +-- | @'arcTo'(x1, y1, x2, y2, r)@ creates an arc between two tangents,+-- specified by two control points and a radius.+-- +-- * @x1@ is the x-coordinate of the first control point+-- +-- * @y1@ is the y-coordinate of the first control point+-- +-- * @x2@ is the x-coordinate of the second control point+-- +-- * @y2@ is the y-coordinate of the second control point+-- +-- * @r@ is the arc's radius arcTo :: (Double, Double, Double, Double, Double) -> Canvas () arcTo = Method . ArcTo +-- | Begins drawing a new path. This will empty the current list of subpaths.+--+-- ==== __Example__+-- +-- @+-- 'beginPath'()+-- 'moveTo'(20, 20)+-- 'lineTo'(200, 20)+-- 'stroke'()+-- @ beginPath :: () -> Canvas () beginPath () = Method BeginPath +-- | @'bezierCurveTo'(cp1x, cp1y, cp2x, cp2y x, y)@ adds a cubic Bézier curve to the path+-- (whereas 'quadraticCurveTo' adds a quadratic Bézier curve).+-- +-- * @cp1x@ is the x-coordinate of the first control point+-- +-- * @cp1y@ is the y-coordinate of the first control point+-- +-- * @cp2x@ is the x-coordinate of the second control point+-- +-- * @cp2y@ is the y-coordinate of the second control point+-- +-- * @x@ is the x-coordinate of the end point+-- +-- * @y@ is the y-coordinate of the end point bezierCurveTo :: (Double, Double, Double, Double, Double, Double) -> Canvas () bezierCurveTo = Method . BezierCurveTo +-- | @'clearRect'(x, y, w, h)@ clears all pixels within the rectangle with upper-left+-- corner @(x, y)@, width @w@, and height @h@ (i.e., sets the pixels to transparent black).+--+-- ==== __Example__+-- +-- @+-- 'fillStyle' \"red\"+-- 'fillRect'(0, 0, 300, 150)+-- 'clearRect'(20, 20, 100, 50)+-- @ clearRect :: (Double, Double, Double, Double) -> Canvas () clearRect = Method . ClearRect +-- | Turns the path currently being built into the current clipping path.+-- Anything drawn after 'clip' is called will only be visible if inside the new+-- clipping path. +--+-- ==== __Example__+-- +-- @+-- 'rect'(50, 20, 200, 120)+-- 'stroke'()+-- 'clip'()+-- 'fillStyle' \"red\"+-- 'fillRect'(0, 0, 150, 100)+-- @ clip :: () -> Canvas () clip () = Method Clip +-- | Creates a path from the current point back to the start, to close it.+--+-- ==== __Example__+-- +-- @+-- 'beginPath'()+-- 'moveTo'(20, 20)+-- 'lineTo'(200, 20)+-- 'lineTo'(120, 120)+-- 'closePath'()+-- 'stroke'()+-- @ closePath :: () -> Canvas () closePath () = Method ClosePath @@ -75,42 +194,130 @@ drawImage :: Image image => (image,[Double]) -> Canvas () drawImage = Method . DrawImage +-- | Fills the current path with the current 'fillStyle'.+--+-- ==== __Example__+-- +-- @+-- 'rect'(10, 10, 100, 100)+-- 'fill'()+-- @ fill :: () -> Canvas () fill () = Method Fill +-- | @'fillRect'(x, y, w, h)@ draws a filled rectangle with upper-left+-- corner @(x, y)@, width @w@, and height @h@ using the current 'fillStyle'.+--+-- ==== __Example__+-- +-- @+-- 'fillStyle' \"red\"+-- 'fillRect'(0, 0, 300, 150)+-- @ fillRect :: (Double, Double, Double, Double) -> Canvas () fillRect = Method . FillRect +-- | Sets the color, gradient, or pattern used to fill a drawing ('black' by default).+--+-- ==== __Examples__+-- +-- @+-- 'fillStyle' 'red'+-- +-- grd <- 'createLinearGradient'(0, 0, 10, 10)+-- 'fillStyle' grd+-- +-- img <- 'newImage' \"/myImage.jpg\"+-- pat <- 'createPattern'(img, 'Repeat')+-- 'fillStyle' pat+-- @ fillStyle :: Style style => style -> Canvas () fillStyle = Method . FillStyle +-- | @'fillText'(t, x, y)@ fills the text @t@ at position @(x, y)@+-- using the current 'fillStyle'.+--+-- ==== __Example__+-- +-- @+-- 'font' \"48px serif\"+-- 'fillText'(\"Hello, World!\", 50, 100)+-- @ fillText :: (Text, Double, Double) -> Canvas () fillText = Method . FillText -font :: Text -> Canvas ()+-- | Sets the text context's font properties.+--+-- ==== __Examples__+-- +-- @+-- 'font' ('defFont' "Gill Sans Extrabold") { 'fontSize' = 40 # 'pt' }+-- 'font' ('defFont' 'sansSerif') { 'fontSize' = 80 # 'percent' }+-- 'font' ('defFont' 'serif') {+-- 'fontWeight' = 'bold'+-- , 'fontStyle' = 'italic'+-- , 'fontSize' = 'large'+-- }+-- @+font :: CanvasFont canvasFont => canvasFont -> Canvas () font = Method . Font -globalAlpha :: Double -> Canvas ()+-- | Set the alpha value that is applied to shapes before they are drawn onto the canvas.+globalAlpha :: Alpha -> Canvas () globalAlpha = Method . GlobalAlpha +-- | Sets how new shapes should be drawn over existing shapes.+--+-- ==== __Examples__+-- +-- @+-- 'globalCompositeOperation' \"source-over\"+-- 'globalCompositeOperation' \"destination-atop\"+-- @ globalCompositeOperation :: Text -> Canvas () globalCompositeOperation = Method . GlobalCompositeOperation +-- | Sets the 'LineEndCap' to use when drawing the endpoints of lines. lineCap :: LineEndCap -> Canvas () lineCap = Method . LineCap +-- | Sets the 'LineJoinCorner' to use when drawing two connected lines. lineJoin :: LineJoinCorner -> Canvas () lineJoin = Method . LineJoin +-- | @'lineTo'(x, y)@ connects the last point in the subpath to the given @(x, y)@+-- coordinates (without actually drawing it).+--+-- ==== __Example__+-- +-- @+-- 'beginPath'()+-- 'moveTo'(50, 50)+-- 'lineTo'(200, 50)+-- 'stroke'()+-- @ lineTo :: (Double, Double) -> Canvas () lineTo = Method . LineTo +-- | Sets the thickness of lines in pixels (@1.0@ by default). lineWidth :: Double -> Canvas () lineWidth = Method . LineWidth +-- | Sets the maximum miter length (@10.0@ by default) to use when the+-- 'lineWidth' is 'miter'. miterLimit :: Double -> Canvas () miterLimit = Method . MiterLimit +-- | @'moveTo'(x, y)@ moves the starting point of a new subpath to the given @(x, y)@ coordinates.+--+-- ==== __Example__+-- +-- @+-- 'beginPath'()+-- 'moveTo'(50, 50)+-- 'lineTo'(200, 50)+-- 'stroke'()+-- @ moveTo :: (Double, Double) -> Canvas () moveTo = Method . MoveTo @@ -118,60 +325,190 @@ putImageData :: (ImageData, [Double]) -> Canvas () putImageData = Method . PutImageData +-- | @'quadraticCurveTo'(cpx, cpy, x, y)@ adds a quadratic Bézier curve to the path+-- (whereas 'bezierCurveTo' adds a cubic Bézier curve).+-- +-- * @cpx@ is the x-coordinate of the control point+-- +-- * @cpy@ is the y-coordinate of the control point+-- +-- * @x@ is the x-coordinate of the end point+-- +-- * @y@ is the y-coordinate of the end point quadraticCurveTo :: (Double, Double, Double, Double) -> Canvas () quadraticCurveTo = Method . QuadraticCurveTo +-- | @'rect'(x, y, w, h)@ creates a rectangle with an upper-left corner at position+-- @(x, y)@, width @w@, and height @h@ (where width and height are in pixels).+--+-- ==== __Example__+-- +-- @+-- 'rect'(10, 10, 100, 100)+-- 'fill'()+-- @ rect :: (Double, Double, Double, Double) -> Canvas () rect = Method . Rect +-- | Restores the most recently saved canvas by popping the top entry off of the+-- drawing state stack. If there is no state, do nothing. restore :: () -> Canvas () restore () = Method Restore -rotate :: Double -> Canvas ()+-- | Applies a rotation transformation to the canvas. When you call functions+-- such as 'fillRect' after 'rotate', the drawings will be rotated clockwise by+-- the angle given to 'rotate' (in radians).+--+-- ==== __Example__+-- +-- @+-- 'rotate' ('pi'/2) -- Rotate the canvas 90°+-- 'fillRect'(0, 0, 20, 10) -- Draw a 10x20 rectangle+-- @+rotate :: Radians -> Canvas () rotate = Method . Rotate +-- | Saves the entire canvas by pushing the current state onto a stack. save :: () -> Canvas () save () = Method Save -scale :: (Double, Double) -> Canvas ()+-- | Applies a scaling transformation to the canvas units, where the first argument+-- is the percent to scale horizontally, and the second argument is the percent to+-- scale vertically. By default, one canvas unit is one pixel.+--+-- ==== __Examples__+-- +-- @+-- 'scale'(0.5, 0.5) -- Halve the canvas units+-- 'fillRect'(0, 0, 20, 20) -- Draw a 10x10 square+-- 'scale'(-1, 1) -- Flip the context horizontally+-- @+scale :: (Interval, Interval) -> Canvas () scale = Method . Scale +-- | Resets the canvas's transformation matrix to the identity matrix,+-- then calls 'transform' with the given arguments. setTransform :: (Double, Double, Double, Double, Double, Double) -> Canvas () setTransform = Method . SetTransform +-- | Sets the blur level for shadows (@0.0@ by default). shadowBlur :: Double -> Canvas () shadowBlur = Method . ShadowBlur +-- | Sets the color used for shadows.+--+-- ==== __Examples__+-- +-- @+-- 'shadowColor' 'red'+-- 'shadowColor' $ 'rgb' 0 255 0+-- @ shadowColor :: CanvasColor canvasColor => canvasColor -> Canvas () shadowColor = Method . ShadowColor +-- | Sets the horizontal distance that a shadow will be offset (@0.0@ by default). shadowOffsetX :: Double -> Canvas () shadowOffsetX = Method . ShadowOffsetX +-- | Sets the vertical distance that a shadow will be offset (@0.0@ by default). shadowOffsetY :: Double -> Canvas () shadowOffsetY = Method . ShadowOffsetY +-- | Draws the current path's strokes with the current 'strokeStyle' ('black' by default).+--+-- ==== __Example__+-- +-- @+-- 'rect'(10, 10, 100, 100)+-- 'stroke'()+-- @ stroke :: () -> Canvas () stroke () = Method Stroke +-- | @'strokeRect'(x, y, w, h)@ draws a rectangle (no fill) with upper-left+-- corner @(x, y)@, width @w@, and height @h@ using the current 'strokeStyle'.+--+-- ==== __Example__+-- +-- @+-- 'strokeStyle' \"red\"+-- 'strokeRect'(0, 0, 300, 150)+-- @ strokeRect :: (Double, Double, Double, Double) -> Canvas () strokeRect = Method . StrokeRect +-- | Sets the color, gradient, or pattern used for strokes.+--+-- ==== __Examples__+-- +-- @+-- 'strokeStyle' 'red'+-- +-- grd <- 'createLinearGradient'(0, 0, 10, 10)+-- 'strokeStyle' grd+-- +-- img <- 'newImage' \"/myImage.jpg\"+-- pat <- 'createPattern'(img, 'Repeat')+-- 'strokeStyle' pat+-- @ strokeStyle :: Style style => style -> Canvas () strokeStyle = Method . StrokeStyle +-- | @'strokeText'(t, x, y)@ draws text @t@ (with no fill) at position @(x, y)@+-- using the current 'strokeStyle'.+--+-- ==== __Example__+-- +-- @+-- 'font' \"48px serif\"+-- 'strokeText'(\"Hello, World!\", 50, 100)+-- @ strokeText :: (Text,Double, Double) -> Canvas () strokeText = Method . StrokeText +-- | Sets the 'TextAnchorAlignment' to use when drawing text. textAlign :: TextAnchorAlignment -> Canvas () textAlign = Method . TextAlign +-- | Sets the 'TextBaselineAlignment' to use when drawing text. textBaseline :: TextBaselineAlignment -> Canvas () textBaseline = Method . TextBaseline +-- | Applies a transformation by multiplying a matrix to the canvas's+-- current transformation. If @'transform'(a, b, c, d, e, f)@ is called, the matrix+-- +-- @+-- ( a c e )+-- ( b d f )+-- ( 0 0 1 )+-- @+-- +-- is multiplied by the current transformation. The parameters are:+-- +-- * @a@ is the horizontal scaling+-- +-- * @b@ is the horizontal skewing+-- +-- * @c@ is the vertical skewing+-- +-- * @d@ is the vertical scaling+-- +-- * @e@ is the horizontal movement+-- +-- * @f@ is the vertical movement transform :: (Double, Double, Double, Double, Double, Double) -> Canvas () transform = Method . Transform +-- | Applies a translation transformation by remapping the origin (i.e., the (0,0)+-- position) on the canvas. When you call functions such as 'fillRect' after+-- 'translate', the values passed to 'translate' are added to the x- and+-- y-coordinate values. +--+-- ==== __Example__+-- +-- @+-- 'translate'(20, 20)+-- 'fillRect'(0, 0, 40, 40) -- Draw a 40x40 square, starting in position (20, 20)+-- @ translate :: (Double, Double) -> Canvas () translate = Method . Translate-
Graphics/Blank/JavaScript.hs view
@@ -1,55 +1,115 @@-{-# LANGUAGE FlexibleInstances, OverlappingInstances #-}-+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TemplateHaskell #-} module Graphics.Blank.JavaScript where -import Control.Applicative- import Data.Char (isControl, isAscii, ord) import Data.Colour import Data.Colour.SRGB import Data.Default.Class import Data.Ix+import Data.Monoid ((<>)) import Data.List import Data.String-import Data.Text (Text, unpack)+import Data.Text (Text)+import qualified Data.Text.Lazy as TL+import qualified Data.Text.Lazy.Builder as B (singleton)+import qualified Data.Vector.Unboxed as V+import Data.Vector.Unboxed (Vector, toList) import Data.Word (Word8) -import qualified Data.Vector.Unboxed as V-import Data.Vector.Unboxed (Vector)+import Graphics.Blank.Parser -import Numeric+import Prelude.Compat -import Text.ParserCombinators.ReadP (skipSpaces, string)-import Text.ParserCombinators.ReadPrec-import Text.Read+import Text.ParserCombinators.ReadP (choice, skipSpaces)+import Text.ParserCombinators.ReadPrec (lift)+import Text.Read (Read(..), parens, readListPrecDefault) +import TextShow+import TextShow.Data.Floating (showbFFloat)+import TextShow.Data.Integral (showbHex)+import TextShow.TH (deriveTextShow)+ ------------------------------------------------------------- --- TODO: close off+-- | A handle to an offscreen canvas. 'CanvasContext' cannot be destroyed.+data CanvasContext = CanvasContext Int Int Int deriving (Eq, Ord, Show)+$(deriveTextShow ''CanvasContext)++-- | A handle to a canvas image. 'CanvasImage's cannot be destroyed.+data CanvasImage = CanvasImage Int Int Int deriving (Eq, Ord, Show)+$(deriveTextShow ''CanvasImage)++-- | A handle to the a canvas gradient. 'CanvasGradient's cannot be destroyed.+newtype CanvasGradient = CanvasGradient Int deriving (Eq, Ord, Show)+$(deriveTextShow ''CanvasGradient)++-- | A handle to a canvas pattern. 'CanvasPattern's cannot be destroyed.+newtype CanvasPattern = CanvasPattern Int deriving (Eq, Ord, Show)+$(deriveTextShow ''CanvasPattern)++-- | A handle to a canvas audio. 'CanvasAudio's cannot be destroyed.+data CanvasAudio = CanvasAudio !Int !Double deriving (Eq, Ord, Show)+$(deriveTextShow ''CanvasAudio)++-------------------------------------------------------------++-- | 'ImageData' is a transliteration of JavaScript's+-- @<https://developer.mozilla.org/en-US/docs/Web/API/ImageData ImageData>@.+-- 'ImageData' consists of two 'Int's and one (unboxed) 'Vector' of 'Word8's.+-- @width@, @height@, and @data@ can be projected from 'ImageData',+-- 'Vector.length' can be used to find the @data@ length.+--+-- Note: 'ImageData' lives on the server, not the client.++data ImageData = ImageData !Int !Int !(Vector Word8) deriving (Eq, Ord, Show)++-- Defined manually to avoid an orphan T.Show (Vector a) instance+instance TextShow ImageData where+ showbPrec p (ImageData w h d) = showbParen (p > 10) $+ "ImageData " <> showbPrec 11 w <> showbSpace+ <> showbPrec 11 h <> showbSpace+ <> showbUnaryWith showbPrec "fromList" 11 (toList d)++-------------------------------------------------------------++-- | Class for JavaScript objects that represent images (including the canvas itself). class Image a where- jsImage :: a -> String- width :: Num b => a -> b- height :: Num b => a -> b+ jsImage :: a -> Builder+ width :: Num b => a -> b+ height :: Num b => a -> b instance Image CanvasImage where- jsImage = jsCanvasImage- width (CanvasImage _ w _) = fromIntegral w- height (CanvasImage _ _ h) = fromIntegral h+ jsImage = jsCanvasImage+ width (CanvasImage _ w _) = fromIntegral w+ height (CanvasImage _ _ h) = fromIntegral h -- The Image of a canvas is the the canvas context, not the DOM entry, so -- you need to indirect back to the DOM here. instance Image CanvasContext where- jsImage = (++ ".canvas") . jsCanvasContext- width (CanvasContext _ w _) = fromIntegral w- height (CanvasContext _ _ h) = fromIntegral h+ jsImage = (<> ".canvas") . jsCanvasContext+ width (CanvasContext _ w _) = fromIntegral w+ height (CanvasContext _ _ h) = fromIntegral h +class Audio a where+ jsAudio :: a -> Builder+ duration :: Fractional b => a -> b++instance Audio CanvasAudio where+ jsAudio = jsCanvasAudio+ duration (CanvasAudio _ d) = realToFrac d+ -- instance Element Video -- Not supported ----------------------------------------------------------------------------- --- TODO: close off+-- | A data type that can represent a style. That is, something with one or more+-- colors. class Style a where- jsStyle :: a -> String+ -- | Convert a value into a JavaScript string representing a style value.+ jsStyle :: a -> Builder instance Style Text where { jsStyle = jsText } instance Style CanvasGradient where { jsStyle = jsCanvasGradient }@@ -57,9 +117,10 @@ instance Style (Colour Double) where { jsStyle = jsColour } instance Style (AlphaColour Double) where { jsStyle = jsAlphaColour } +-- | A 'Style' containing exactly one color. class Style a => CanvasColor a -jsCanvasColor :: CanvasColor color => color -> String+jsCanvasColor :: CanvasColor color => color -> Builder jsCanvasColor = jsStyle instance CanvasColor Text@@ -68,30 +129,31 @@ ------------------------------------------------------------- --- | A handle to an offscreen canvas. CanvasContext can not be destroyed.-data CanvasContext = CanvasContext Int Int Int- deriving (Show,Eq,Ord)---- | A handle to the Image. CanvasImages can not be destroyed.-data CanvasImage = CanvasImage Int Int Int deriving (Show,Eq,Ord)---- | A handle to the CanvasGradient. CanvasGradients can not be destroyed.-newtype CanvasGradient = CanvasGradient Int deriving (Show,Eq,Ord)---- | A handle to the CanvasPattern. CanvasPatterns can not be destroyed.-newtype CanvasPattern = CanvasPattern Int deriving (Show,Eq,Ord)---------------------------------------------------------------- -- | The direction in which a 'CanvasPattern' repeats. data RepeatDirection = Repeat -- ^ The pattern repeats both horizontally- -- and vertically.+ -- and vertically (default). | RepeatX -- ^ The pattern repeats only horizontally. | RepeatY -- ^ The pattern repeats only vertically. | NoRepeat -- ^ The pattern displays only once and -- does not repeat.- deriving Eq+ deriving (Bounded, Enum, Eq, Ix, Ord) +-- | Shorthand for 'Repeat', with an underscore to distinguish it from 'repeat'.+repeat_ :: RepeatDirection+repeat_ = Repeat++-- | Shorthand for 'RepeatX'.+repeatX :: RepeatDirection+repeatX = RepeatX++-- | Shorthand for 'RepeatY'.+repeatY :: RepeatDirection+repeatY = RepeatY++-- | Shorthand for 'NoRepeat'.+noRepeat :: RepeatDirection+noRepeat = NoRepeat+ instance Default RepeatDirection where def = Repeat @@ -99,76 +161,109 @@ fromString = read instance Read RepeatDirection where- readPrec = parens . lift $ do- skipSpaces- (string "repeat" >> return Repeat)- <|> (string "repeat-x" >> return RepeatX)- <|> (string "repeat-y" >> return RepeatY)- <|> (string "no-repeat" >> return NoRepeat)+ readPrec = parens . lift $ do+ skipSpaces+ choice+ [ Repeat <$ stringCI "repeat"+ , RepeatX <$ stringCI "repeat-x"+ , RepeatY <$ stringCI "repeat-y"+ , NoRepeat <$ stringCI "no-repeat"+ ]+ readListPrec = readListPrecDefault instance Show RepeatDirection where- showsPrec _ rd = showString $ case rd of- Repeat -> "repeat"- RepeatX -> "repeat-x"- RepeatY -> "repeat-y"- NoRepeat -> "no-repeat"+ showsPrec p = showsPrec p . FromTextShow +instance TextShow RepeatDirection where+ showb Repeat = "repeat"+ showb RepeatX = "repeat-x"+ showb RepeatY = "repeat-y"+ showb NoRepeat = "no-repeat"+ -- | The style of the caps on the endpoints of a line.-data LineEndCap = ButtCap -- ^ Flat edges+data LineEndCap = ButtCap -- ^ Flat edges (default). | RoundCap -- ^ Semicircular end caps | SquareCap -- ^ Square end caps- deriving Eq+ deriving (Bounded, Enum, Eq, Ix, Ord) +-- | Shorthand for 'ButtCap'.+butt :: LineEndCap+butt = ButtCap++-- | Shorthand for 'SquareCap'.+square :: LineEndCap+square = SquareCap+ instance Default LineEndCap where- def = ButtCap+ def = ButtCap instance IsString LineEndCap where- fromString = read+ fromString = read instance Read LineEndCap where- readPrec = parens $ do- Ident s <- lexP- case s of- "butt" -> return ButtCap - "round" -> return RoundCap - "square" -> return SquareCap- _ -> pfail+ readPrec = parens . lift $ do+ skipSpaces+ choice+ [ ButtCap <$ stringCI "butt"+ , RoundCap <$ stringCI "round"+ , SquareCap <$ stringCI "square"+ ]+ readListPrec = readListPrecDefault +instance RoundProperty LineEndCap where+ round_ = RoundCap+ instance Show LineEndCap where- showsPrec _ le = showString $ case le of- ButtCap -> "butt"- RoundCap -> "round"- SquareCap -> "square"+ showsPrec p = showsPrec p . FromTextShow +instance TextShow LineEndCap where+ showb ButtCap = "butt"+ showb RoundCap = "round"+ showb SquareCap = "square"+ -- | The style of corner that is created when two lines join. data LineJoinCorner = BevelCorner -- ^ A filled triangle with a beveled edge -- connects two lines. | RoundCorner -- ^ A filled arc connects two lines. | MiterCorner -- ^ A filled triangle with a sharp edge- -- connects two lines.- deriving Eq+ -- connects two lines (default).+ deriving (Bounded, Enum, Eq, Ix, Ord) +-- | Shorthand for 'BevelCorner'.+bevel :: LineJoinCorner+bevel = BevelCorner++-- | Shorthand for 'MiterCorner'.+miter :: LineJoinCorner+miter = MiterCorner+ instance Default LineJoinCorner where- def = MiterCorner+ def = MiterCorner instance IsString LineJoinCorner where- fromString = read+ fromString = read instance Read LineJoinCorner where- readPrec = parens $ do- Ident s <- lexP- case s of- "bevel" -> return BevelCorner- "round" -> return RoundCorner- "miter" -> return MiterCorner- _ -> pfail+ readPrec = parens . lift $ do+ skipSpaces+ choice+ [ BevelCorner <$ stringCI "bevel"+ , RoundCorner <$ stringCI "round"+ , MiterCorner <$ stringCI "miter"+ ]+ readListPrec = readListPrecDefault +instance RoundProperty LineJoinCorner where+ round_ = RoundCorner+ instance Show LineJoinCorner where- showsPrec _ corner = showString $ case corner of- BevelCorner -> "bevel"- RoundCorner -> "round"- MiterCorner -> "miter"+ showsPrec p = showsPrec p . FromTextShow +instance TextShow LineJoinCorner where+ showb BevelCorner = "bevel"+ showb RoundCorner = "round"+ showb MiterCorner = "miter"+ -- | The anchor point for text in the current 'DeviceContext'. data TextAnchorAlignment = StartAnchor -- ^ The text is anchored at either its left edge -- (if the canvas is left-to-right) or its right@@ -179,33 +274,56 @@ | CenterAnchor -- ^ The text is anchored in its center. | LeftAnchor -- ^ The text is anchored at its left edge. | RightAnchor -- ^ the text is anchored at its right edge.- deriving Eq+ deriving (Bounded, Enum, Eq, Ix, Ord) +-- | Shorthand for 'StartAnchor'.+start :: TextAnchorAlignment+start = StartAnchor++-- | Shorthand for 'EndAnchor'.+end :: TextAnchorAlignment+end = EndAnchor++-- | Shorthand for 'CenterAnchor'.+center :: TextAnchorAlignment+center = CenterAnchor++-- | Shorthand for 'LeftAnchor'.+left :: TextAnchorAlignment+left = LeftAnchor++-- | Shorthand for 'RightAnchor'.+right :: TextAnchorAlignment+right = RightAnchor+ instance Default TextAnchorAlignment where- def = StartAnchor+ def = StartAnchor instance IsString TextAnchorAlignment where- fromString = read+ fromString = read instance Read TextAnchorAlignment where- readPrec = parens $ do- Ident s <- lexP- case s of- "start" -> return StartAnchor- "end" -> return EndAnchor- "center" -> return CenterAnchor- "left" -> return LeftAnchor- "right" -> return RightAnchor- _ -> pfail+ readPrec = parens . lift $ do+ skipSpaces+ choice+ [ StartAnchor <$ stringCI "start"+ , EndAnchor <$ stringCI "end"+ , CenterAnchor <$ stringCI "center"+ , LeftAnchor <$ stringCI "left"+ , RightAnchor <$ stringCI "right"+ ]+ readListPrec = readListPrecDefault instance Show TextAnchorAlignment where- showsPrec _ align = showString $ case align of- StartAnchor -> "start"- EndAnchor -> "end"- CenterAnchor -> "center"- LeftAnchor -> "left"- RightAnchor -> "right"+ showsPrec p = showsPrec p . FromTextShow +instance TextShow TextAnchorAlignment where+ showb StartAnchor = "start"+ showb EndAnchor = "end"+ showb CenterAnchor = "center"+ showb LeftAnchor = "left"+ showb RightAnchor = "right"+ -- | The baseline alignment used when drawing text in the current 'DeviceContext'. -- The baselines are ordered from highest ('Top') to lowest ('Bottom'). data TextBaselineAlignment = TopBaseline@@ -214,199 +332,244 @@ | AlphabeticBaseline | IdeographicBaseline | BottomBaseline- deriving (Bounded, Eq, Ix, Ord)+ deriving (Bounded, Enum, Eq, Ix, Ord) +-- | Shorthand for 'TopBaseline'.+top :: TextBaselineAlignment+top = TopBaseline++-- | Shorthand for 'HangingBaseline'.+hanging :: TextBaselineAlignment+hanging = HangingBaseline++-- | Shorthand for 'MiddleBaseline'.+middle :: TextBaselineAlignment+middle = MiddleBaseline++-- | Shorthand for 'AlphabeticBaseline'.+alphabetic :: TextBaselineAlignment+alphabetic = AlphabeticBaseline++-- | Shorthand for 'IdeographicBaseline'.+ideographic :: TextBaselineAlignment+ideographic = IdeographicBaseline++-- | Shorthand for 'BottomBaseline'.+bottom :: TextBaselineAlignment+bottom = BottomBaseline+ instance Default TextBaselineAlignment where- def = AlphabeticBaseline+ def = AlphabeticBaseline instance IsString TextBaselineAlignment where- fromString = read+ fromString = read instance Read TextBaselineAlignment where- readPrec = parens $ do- Ident s <- lexP- case s of- "top" -> return TopBaseline- "hanging" -> return HangingBaseline- "middle" -> return MiddleBaseline- "alphabetic" -> return AlphabeticBaseline- "ideographic" -> return IdeographicBaseline- "bottom" -> return BottomBaseline- _ -> pfail+ readPrec = parens . lift $ do+ skipSpaces+ choice+ [ TopBaseline <$ stringCI "top"+ , HangingBaseline <$ stringCI "hanging"+ , MiddleBaseline <$ stringCI "middle"+ , AlphabeticBaseline <$ stringCI "alphabetic"+ , IdeographicBaseline <$ stringCI "ideographic"+ , BottomBaseline <$ stringCI "bottom"+ ]+ readListPrec = readListPrecDefault instance Show TextBaselineAlignment where- showsPrec _ bl = showString $ case bl of- TopBaseline -> "top"- HangingBaseline -> "hanging"- MiddleBaseline -> "middle"- AlphabeticBaseline -> "alphabetic"- IdeographicBaseline -> "ideographic"- BottomBaseline -> "bottom"---------------------------------------------------------------+ showsPrec p = showsPrec p . FromTextShow --- | 'ImageData' is a transliteration of the JavaScript ImageData,--- There are two 'Int's, and one (unboxed) 'Vector' of 'Word8's.--- width, height, data can be projected from 'ImageData',--- 'Vector.length' can be used to find the length.------ Note: 'ImageData' lives on the server, not the client.+instance TextShow TextBaselineAlignment where+ showb TopBaseline = "top"+ showb HangingBaseline = "hanging"+ showb MiddleBaseline = "middle"+ showb AlphabeticBaseline = "alphabetic"+ showb IdeographicBaseline = "ideographic"+ showb BottomBaseline = "bottom" -data ImageData = ImageData !Int !Int !(Vector Word8) deriving (Show, Eq, Ord)+-- | Class for @round@ CSS property values.+class RoundProperty a where+ -- | Shorthand for 'RoundCap' or 'RoundCorner', with an underscore to+ -- distinguish it from 'round'.+ round_ :: a ------------------------------------------------------------- +-- | Class for Haskell data types which represent JavaScript data. class JSArg a where- showJS :: a -> String+ -- | Display a value as JavaScript data.+ showbJS :: a -> Builder instance JSArg (AlphaColour Double) where- showJS aCol- | a >= 1 = jsColour rgbCol- | a <= 0 = jsLiteralString "rgba(0,0,0,0)"- | otherwise = jsLiteralString $ "rgba("- ++ show r ++ ","- ++ show g ++ ","- ++ show b ++ ","- ++ jsDouble a ++ ")"- where- a = alphaChannel aCol- rgbCol = darken (recip a) $ aCol `over` black- RGB r g b = toSRGB24 rgbCol+ showbJS = jsAlphaColour -jsAlphaColour :: AlphaColour Double -> String-jsAlphaColour = showJS+jsAlphaColour :: AlphaColour Double -> Builder+jsAlphaColour aCol+ | a >= 1 = jsColour rgbCol+ | a <= 0 = jsLiteralBuilder "rgba(0,0,0,0)"+ | otherwise = jsLiteralBuilder $ "rgba("+ <> showb r <> B.singleton ','+ <> showb g <> B.singleton ','+ <> showb b <> B.singleton ','+ <> jsDouble a <> B.singleton ')'+ where+ a = alphaChannel aCol+ rgbCol = darken (recip a) $ aCol `over` black+ RGB r g b = toSRGB24 rgbCol instance JSArg Bool where- showJS True = "true"- showJS False = "false"+ showbJS = jsBool -jsBool :: Bool -> String-jsBool = showJS+jsBool :: Bool -> Builder+jsBool True = "true"+jsBool False = "false" +instance JSArg CanvasAudio where+ showbJS = jsCanvasAudio++jsCanvasAudio :: CanvasAudio -> Builder+jsCanvasAudio (CanvasAudio n _ ) = "sounds[" <> showb n <> B.singleton ']'+ instance JSArg CanvasContext where- showJS (CanvasContext n _ _) = "canvasbuffers[" ++ show n ++ "]"+ showbJS = jsCanvasContext -jsCanvasContext :: CanvasContext -> String-jsCanvasContext = showJS+jsCanvasContext :: CanvasContext -> Builder+jsCanvasContext (CanvasContext n _ _) = "canvasbuffers[" <> showb n <> B.singleton ']' instance JSArg CanvasImage where- showJS (CanvasImage n _ _) = "images[" ++ show n ++ "]"+ showbJS = jsCanvasImage -jsCanvasImage :: CanvasImage -> String-jsCanvasImage = showJS+jsCanvasImage :: CanvasImage -> Builder+jsCanvasImage (CanvasImage n _ _) = "images[" <> showb n <> B.singleton ']' instance JSArg CanvasGradient where- showJS (CanvasGradient n) = "gradients[" ++ show n ++ "]"+ showbJS = jsCanvasGradient -jsCanvasGradient :: CanvasGradient -> String-jsCanvasGradient = showJS+jsCanvasGradient :: CanvasGradient -> Builder+jsCanvasGradient (CanvasGradient n) = "gradient_" <> showb n instance JSArg CanvasPattern where- showJS (CanvasPattern n) = "patterns[" ++ show n ++ "]"+ showbJS = jsCanvasPattern -jsCanvasPattern :: CanvasPattern -> String-jsCanvasPattern = showJS+jsCanvasPattern :: CanvasPattern -> Builder+jsCanvasPattern (CanvasPattern n) = "pattern_" <> showb n instance JSArg (Colour Double) where- showJS = jsLiteralString . sRGB24show+ showbJS = jsColour -jsColour :: Colour Double -> String-jsColour = showJS+jsColour :: Colour Double -> Builder+jsColour = jsLiteralBuilder . sRGB24showb +-- | Convert a colour in hexadecimal 'Builder' form, e.g. \"#00aaff\"+sRGB24showb :: (Floating b, RealFrac b) => Colour b -> Builder+sRGB24showb c =+ B.singleton '#' <> showbHex2 r' <> showbHex2 g' <> showbHex2 b'+ where+ RGB r' g' b' = toSRGB24 c+ showbHex2 x | x <= 0xf = B.singleton '0' <> showbHex x+ | otherwise = showbHex x+ instance JSArg Double where- showJS a = showFFloat (Just 3) a ""+ showbJS = jsDouble -jsDouble :: Double -> String-jsDouble = showJS+jsDouble :: Double -> Builder+jsDouble = showbFFloat $ Just 3 instance JSArg ImageData where- showJS (ImageData w h d) = "ImageData(" ++ show w ++ "," ++ show h ++ ",[" ++ vs ++ "])"- where- vs = jsList show $ V.toList d+ showbJS = jsImageData -jsImageData :: ImageData -> String-jsImageData = showJS+jsImageData :: ImageData -> Builder+jsImageData (ImageData w h d) = "ImageData(" <> showb w+ <> B.singleton ',' <> showb h+ <> ",[" <> vs <> "])"+ where+ vs = jsList showb $ V.toList d instance JSArg Int where- showJS a = show a+ showbJS = jsInt +jsInt :: Int -> Builder+jsInt = showb+ instance JSArg LineEndCap where- showJS = jsLiteralString . show+ showbJS = jsLineEndCap -jsLineEndCap :: LineEndCap -> String-jsLineEndCap = showJS+jsLineEndCap :: LineEndCap -> Builder+jsLineEndCap = jsLiteralBuilder . showb instance JSArg LineJoinCorner where- showJS = jsLiteralString . show+ showbJS = jsLineJoinCorner -jsLineJoinCorner :: LineJoinCorner -> String-jsLineJoinCorner = showJS+jsLineJoinCorner :: LineJoinCorner -> Builder+jsLineJoinCorner = jsLiteralBuilder . showb -jsList :: (a -> String) -> [a] -> String-jsList js = concat . intersperse "," . map js+jsList :: (a -> Builder) -> [a] -> Builder+jsList js = mconcat . intersperse "," . map js instance JSArg RepeatDirection where- showJS = jsLiteralString . show+ showbJS = jsRepeatDirection -jsRepeatDirection :: RepeatDirection -> String-jsRepeatDirection = showJS+jsRepeatDirection :: RepeatDirection -> Builder+jsRepeatDirection = jsLiteralBuilder . showb instance JSArg Text where- showJS = jsLiteralString . unpack+ showbJS = jsText -jsText :: Text -> String-jsText = showJS+jsText :: Text -> Builder+jsText = jsLiteralBuilder . fromText instance JSArg TextAnchorAlignment where- showJS = jsLiteralString . show+ showbJS = jsTextAnchorAlignment -jsTextAnchorAlignment :: TextAnchorAlignment -> String-jsTextAnchorAlignment = showJS+jsTextAnchorAlignment :: TextAnchorAlignment -> Builder+jsTextAnchorAlignment = jsLiteralBuilder . showb instance JSArg TextBaselineAlignment where- showJS = jsLiteralString . show+ showbJS = jsTextBaselineAlignment -jsTextBaselineAlignment :: TextBaselineAlignment -> String-jsTextBaselineAlignment = showJS+jsTextBaselineAlignment :: TextBaselineAlignment -> Builder+jsTextBaselineAlignment = jsLiteralBuilder . showb --- The following was from our Sunroof compiler.+-- The following was adapted from our Sunroof compiler. -- ---------------------------------------------------------------- String Conversion Utilities: Haskell -> JS+-- Builder Conversion Utilities: Haskell -> JS -- ------------------------------------------------------------- --- | Transform a Haskell string into a string representing a JS string literal.-jsLiteralString :: String -> String-jsLiteralString = jsQuoteString . jsEscapeString+-- | Convert a 'Builder' to a representation as a JS string literal.+jsLiteralBuilder :: Builder -> Builder+jsLiteralBuilder = jsQuoteBuilder . jsEscapeBuilder --- | Add quotes to a string.-jsQuoteString :: String -> String-jsQuoteString s = "\"" ++ s ++ "\""+-- | Add quotes to a 'Builder'.+jsQuoteBuilder :: Builder -> Builder+jsQuoteBuilder b = B.singleton '"' <> b <> B.singleton '"' --- | Transform a character to a string that represents its JS+-- | Transform a character to a lazy 'TL.Text' that represents its JS -- unicode escape sequence.-jsUnicodeChar :: Char -> String+jsUnicodeChar :: Char -> TL.Text jsUnicodeChar c =- let hex = showHex (ord c) ""- in ('\\':'u': replicate (4 - length hex) '0') ++ hex+ let hex = toLazyText . showbHex $ ord c+ in "\\u" <> TL.replicate (4 - TL.length hex) (TL.singleton '0') <> hex +-- | Correctly replace a `Builder'`s characters by the JS escape sequences.+jsEscapeBuilder :: Builder -> Builder+jsEscapeBuilder = fromLazyText . TL.concatMap jsEscapeChar . toLazyText+ -- | Correctly replace Haskell characters by the JS escape sequences.-jsEscapeString :: String -> String-jsEscapeString [] = []-jsEscapeString (c:cs) = case c of- -- Backslash has to remain backslash in JS.- '\\' -> '\\' : '\\' : jsEscapeString cs- -- Special control sequences.- '\0' -> jsUnicodeChar '\0' ++ jsEscapeString cs -- Ambigous with numbers- '\a' -> jsUnicodeChar '\a' ++ jsEscapeString cs -- Non JS- '\b' -> '\\' : 'b' : jsEscapeString cs- '\f' -> '\\' : 'f' : jsEscapeString cs- '\n' -> '\\' : 'n' : jsEscapeString cs- '\r' -> '\\' : 'r' : jsEscapeString cs- '\t' -> '\\' : 't' : jsEscapeString cs- '\v' -> '\\' : 'v' : jsEscapeString cs- '\"' -> '\\' : '\"' : jsEscapeString cs- '\'' -> '\\' : '\'' : jsEscapeString cs- -- Non-control ASCII characters can remain as they are.- c' | not (isControl c') && isAscii c' -> c' : jsEscapeString cs- -- All other non ASCII signs are escaped to unicode.- c' -> jsUnicodeChar c' ++ jsEscapeString cs+jsEscapeChar :: Char -> TL.Text+jsEscapeChar '\\' = "\\\\"+-- Special control sequences.+jsEscapeChar '\0' = jsUnicodeChar '\0' -- Ambigous with numbers+jsEscapeChar '\a' = jsUnicodeChar '\a' -- Non JS+jsEscapeChar '\b' = "\\b"+jsEscapeChar '\f' = "\\f"+jsEscapeChar '\n' = "\\n"+jsEscapeChar '\r' = "\\r"+jsEscapeChar '\t' = "\\t"+jsEscapeChar '\v' = "\\v"+jsEscapeChar '\"' = "\\\""+jsEscapeChar '\'' = "\\'"+-- Non-control ASCII characters can remain as they are.+jsEscapeChar c' | not (isControl c') && isAscii c' = TL.singleton c'+-- All other non ASCII signs are escaped to unicode.+jsEscapeChar c' = jsUnicodeChar c'
+ Graphics/Blank/Parser.hs view
@@ -0,0 +1,82 @@+module Graphics.Blank.Parser where++import Control.Applicative hiding (many, optional)++import Data.Char+import Data.Ix+import Data.Functor (void)++import Text.ParserCombinators.ReadP+import Text.ParserCombinators.ReadPrec (ReadPrec, readPrec_to_P)++-- | @maybeRead p@ will either parse @p@ or return 'Nothing' without consuming any+-- input. Compare to 'option' from "Text.ParserCombinators.ReadP".+maybeRead :: ReadP a -> ReadP (Maybe a)+maybeRead p = (Just <$> p) <|> return Nothing++-- | @maybeReadPrec p@ will either parse @p@ or return 'Nothing' without consuming any+-- input. Compare to 'option' from "Text.ParserCombinators.ReadP".+maybeReadPrec :: ReadPrec a -> ReadPrec (Maybe a)+maybeReadPrec p = (Just <$> p) <|> return Nothing++-- | A case-insensitive version of 'string' from "Text.ParserCombinators.ReadP".+stringCI :: String -> ReadP String+stringCI this = look >>= scan this+ where+ scan :: String -> String -> ReadP String+ scan [] _ = return this+ scan (x:xs) (y:ys)+ | toLower x == toLower y = get *> scan xs ys+ scan _ _ = pfail++-- | Convert a 'ReadPrec' to a 'ReadP' (the converse of 'lift').+unlift :: ReadPrec a -> ReadP a+unlift = flip readPrec_to_P 0++-- | Equivalent to the function from @parsec@, but using 'ReadP'.+noneOf :: [Char] -> ReadP Char+noneOf cs = satisfy $ \c -> not $ elem c cs++-------------------------------------------------------------------------------+-- Parser combinators for CSS identifiers. Adapted from the hxt-css package.+-------------------------------------------------------------------------------++-- | Parses a CSS identifier.+cssIdent :: ReadP String+cssIdent = (:) <$> nmstart <*> many nmchar++-- | Parses the beginning character of a CSS identifier.+nmstart :: ReadP Char+nmstart = satisfy p <|> nonascii+ where+ p c = inRange ('a', 'z') c || inRange ('A', 'Z') c || c == '_'++-- | Parses a non-beginning CSS identifier character.+nmchar :: ReadP Char+nmchar = satisfy p <|> nonascii+ where+ p c = inRange ('a', 'z') c || inRange ('A', 'Z') c ||+ isDigit c || elem c "_-"++-- | Parses a CSS string literal.+stringLit :: ReadP String+stringLit = string1 <|> string2+ where+ string1 = char '"'+ *> many (noneOf "\n\r\f\\\"" <|> nl <|> nonascii)+ <* char '*'+ string2 = char '\''+ *> many (noneOf "\n\r\f\\'" <|> nl <|> nonascii)+ <* char '\''++-- | Parses a non-ASCII CSS character.+nonascii :: ReadP Char+nonascii = satisfy (> '\DEL')++-- | Parses a CSS-style newline.+nl :: ReadP Char+nl = choice+ [ void $ char '\n'+ , char '\r' >> optional (char '\n')+ , void $ char '\f'+ ] >> return '\n'
Graphics/Blank/Style.hs view
@@ -1,172 +1,193 @@+{-|+Module: Graphics.Blank.Style+Copyright: (C) 2014-2015, The University of Kansas+License: BSD-style (see the file LICENSE)+Maintainer: Andy Gill+Stability: Beta+Portability: GHC++This module exposes overloaded versions of @blank-canvas@ functions that take a+style or color as an argument, which may be of interest if you desire stronger+type safety than @Text@ provides.++Note that this module exports function names that conflict with "Graphics.Blank".+Make sure to hide any functions from "Graphics.Blank" that you use from this+module.+-} module Graphics.Blank.Style - ( -- * Overloaded versions of 'Canvas' functions- strokeStyle- , fillStyle- , shadowColor- , addColorStop- , Style(..)- , CanvasColor- -- * 'CanvasColor' creation- , Alpha- , Percentage- , rgb- , rgbPercent- , rgba- , rgbaPercent- , hsl- , hsla- -- * @colour@ reexports- , readColourName- , aliceblue- , antiquewhite- , aqua- , aquamarine- , azure- , beige- , bisque- , black- , blanchedalmond- , blue- , blueviolet- , brown- , burlywood- , cadetblue- , chartreuse- , chocolate- , coral- , cornflowerblue- , cornsilk- , crimson- , cyan- , darkblue- , darkcyan- , darkgoldenrod- , darkgray- , darkgreen- , darkgrey- , darkkhaki- , darkmagenta- , darkolivegreen- , darkorange- , darkorchid- , darkred- , darksalmon- , darkseagreen- , darkslateblue- , darkslategray- , darkslategrey- , darkturquoise- , darkviolet- , deeppink- , deepskyblue- , dimgray- , dimgrey- , dodgerblue- , firebrick- , floralwhite- , forestgreen- , fuchsia- , gainsboro- , ghostwhite- , gold- , goldenrod- , gray- , green- , grey- , greenyellow- , honeydew- , hotpink- , indianred- , indigo- , ivory- , khaki- , lavender- , lavenderblush- , lawngreen- , lemonchiffon- , lightblue- , lightcoral- , lightcyan- , lightgoldenrodyellow- , lightgray- , lightgreen- , lightgrey- , lightpink- , lightsalmon- , lightseagreen- , lightskyblue- , lightslategray- , lightslategrey- , lightsteelblue- , lightyellow- , lime- , limegreen- , linen- , magenta- , maroon- , mediumaquamarine- , mediumblue- , mediumorchid- , mediumpurple- , mediumseagreen- , mediumslateblue- , mediumspringgreen- , mediumturquoise- , mediumvioletred- , midnightblue- , mintcream- , mistyrose- , moccasin- , navajowhite- , navy- , oldlace- , olive- , olivedrab- , orange- , orangered- , orchid- , palegoldenrod- , palegreen- , paleturquoise- , palevioletred- , papayawhip- , peachpuff- , peru- , pink- , plum- , powderblue- , purple- , red- , rosybrown- , royalblue- , saddlebrown- , salmon- , sandybrown- , seagreen- , seashell- , sienna- , silver- , skyblue- , slateblue- , slategray- , slategrey- , snow- , springgreen- , steelblue- , tan- , teal- , thistle- , tomato- , turquoise- , violet- , wheat- , white- , whitesmoke- , yellow- , yellowgreen- , rebeccapurple- , transparent- ) where+ ( -- * Overloaded versions of 'Canvas' functions+ strokeStyle+ , fillStyle+ , shadowColor+ , addColorStop+ , Style(..)+ , CanvasColor+ -- * 'CanvasColor' creation+ , rgb+ , rgbPercent+ , rgba+ , rgbaPercent+ , hsl+ , hsla+ -- * @colour@ reexports+ -- ** 'Colour' and 'AlphaColour'+ , Colour+ , AlphaColour+ , transparent+ , readColourName+ -- ** CSS Level 1 colors+ , aqua+ , black+ , blue+ , fuchsia+ , gray+ , green+ , lime+ , maroon+ , navy+ , olive+ , purple+ , red+ , silver+ , teal+ , white+ , yellow+ -- ** CSS Level 2 color+ , orange+ -- ** CSS Color Module Level 3 colors+ , aliceblue+ , antiquewhite+ , aquamarine+ , azure+ , beige+ , bisque+ , blanchedalmond+ , blueviolet+ , brown+ , burlywood+ , cadetblue+ , chartreuse+ , chocolate+ , coral+ , cornflowerblue+ , cornsilk+ , crimson+ , cyan+ , darkblue+ , darkcyan+ , darkgoldenrod+ , darkgray+ , darkgreen+ , darkgrey+ , darkkhaki+ , darkmagenta+ , darkolivegreen+ , darkorange+ , darkorchid+ , darkred+ , darksalmon+ , darkseagreen+ , darkslateblue+ , darkslategray+ , darkslategrey+ , darkturquoise+ , darkviolet+ , deeppink+ , deepskyblue+ , dimgray+ , dimgrey+ , dodgerblue+ , firebrick+ , floralwhite+ , forestgreen+ , gainsboro+ , ghostwhite+ , gold+ , goldenrod+ , grey+ , greenyellow+ , honeydew+ , hotpink+ , indianred+ , indigo+ , ivory+ , khaki+ , lavender+ , lavenderblush+ , lawngreen+ , lemonchiffon+ , lightblue+ , lightcoral+ , lightcyan+ , lightgoldenrodyellow+ , lightgray+ , lightgreen+ , lightgrey+ , lightpink+ , lightsalmon+ , lightseagreen+ , lightskyblue+ , lightslategray+ , lightslategrey+ , lightsteelblue+ , lightyellow+ , limegreen+ , linen+ , magenta+ , mediumaquamarine+ , mediumblue+ , mediumorchid+ , mediumpurple+ , mediumseagreen+ , mediumslateblue+ , mediumspringgreen+ , mediumturquoise+ , mediumvioletred+ , midnightblue+ , mintcream+ , mistyrose+ , moccasin+ , navajowhite+ , oldlace+ , olivedrab+ , orangered+ , orchid+ , palegoldenrod+ , palegreen+ , paleturquoise+ , palevioletred+ , papayawhip+ , peachpuff+ , peru+ , pink+ , plum+ , powderblue+ , rosybrown+ , royalblue+ , saddlebrown+ , salmon+ , sandybrown+ , seagreen+ , seashell+ , sienna+ , skyblue+ , slateblue+ , slategray+ , slategrey+ , snow+ , springgreen+ , steelblue+ , tan+ , thistle+ , tomato+ , turquoise+ , violet+ , wheat+ , whitesmoke+ , yellowgreen+ -- ** CSS Color Module Level 4 color+ , rebeccapurple+ ) where import qualified Data.Colour as Colour import Data.Colour hiding (black, transparent)@@ -179,31 +200,21 @@ import Graphics.Blank.Canvas import Graphics.Blank.Generated import Graphics.Blank.JavaScript+import Graphics.Blank.Types import Prelude hiding (tan) --- |--- A value ranging from 0.0 to 1.0. A color with an alpha value of 0.0 is 'transparent',--- and a color with an alpha value of 1.0 is opaque.-type Alpha = Double---- | A value ranging from 0.0 to 100.0.-type Percentage = Double---- |--- Specifies a 'Colour' by its red, green, and blue components, where each component+-- | Specifies a 'Colour' by its red, green, and blue components, where each component -- is an integer between 0 and 255. rgb :: Word8 -> Word8 -> Word8 -> Colour Double rgb = sRGB24 --- |--- Specifies a 'Colour' by its red, green, and blue components, where each component--- is given by a percentage of 255.+-- | Specifies a 'Colour' by its red, green, and blue components, where each component+-- is given by a percentage (which should be between 0% to 100%) of 255. rgbPercent :: Percentage -> Percentage -> Percentage -> Colour Double rgbPercent r g b = sRGB (r/100) (g/100) (b/100) --- |--- Specifies an 'AlphaColour' by its RGB components and an alpha value.+-- | Specifies an 'AlphaColour' by its RGB components and an alpha value. -- -- @ -- 'rgba' r g b 0.0 = 'transparent'@@ -211,8 +222,8 @@ rgba :: Word8 -> Word8 -> Word8 -> Alpha -> AlphaColour Double rgba r g b = withOpacity $ rgb r g b --- |--- Specifies an 'AlphaColour' by its RGB component percentages and an alpha value.+-- | Specifies an 'AlphaColour' by its RGB component percentages (which should be+-- between 0% and 100%) and an alpha value. -- -- @ -- 'rgbaPercent' r g b 0.0 = 'transparent'@@ -220,19 +231,18 @@ rgbaPercent :: Percentage -> Percentage -> Percentage -> Alpha -> AlphaColour Double rgbaPercent r g b = withOpacity $ rgbPercent r g b --- |--- Specifies a 'Colour' by its hue (which ranges from 0° to 360°), saturation, and--- value.-hsl :: Int -> Percentage -> Percentage -> Colour Double+-- | Specifies a 'Colour' by its hue, saturation, and lightness value, where+-- saturation and lightness are percentages between 0% and 100%.+hsl :: Degrees -> Percentage -> Percentage -> Colour Double hsl h s l = uncurryRGB sRGB $ HSL.hsl (realToFrac h) (s/100) (l/100) -- |--- Specifies an 'AlphaColour' by its HSV values and an alpha value.+-- Specifies an 'AlphaColour' by its HSL values and an alpha value. -- -- @ -- 'hsla' h s v 0.0 = 'transparent' -- @-hsla :: Int -> Percentage -> Percentage -> Alpha -> AlphaColour Double+hsla :: Degrees -> Percentage -> Percentage -> Alpha -> AlphaColour Double hsla h s l = withOpacity $ hsl h s l -- |@@ -534,7 +544,7 @@ lightgreen :: Colour Double lightgreen = Names.lightgreen --- | @#D3D3D3@, @rgb(211, 211, 211)@, @hsl(0, 0%, 83%)@. Same as 'lightgrey'.+-- | @#D3D3D3@, @rgb(211, 211, 211)@, @hsl(0, 0%, 83%)@. Same as 'lightgray'. lightgrey :: Colour Double lightgrey = Names.lightgrey
+ Graphics/Blank/Types.hs view
@@ -0,0 +1,19 @@+module Graphics.Blank.Types where++-- | A value representing a percentage (e.g., @0.0@ represents 0%,+-- @100.0@ represents 100%, etc.).+type Percentage = Double++-- | A normalized percentage value (e.g., @0.0@ represent 0%, @1.0@+-- represents 100%, etc.).+type Interval = Double++-- | An interval representing a color's translucency. A color with an alpha value+-- of 0.0 is 'transparent', and a color with an alpha value of 1.0 is opaque.+type Alpha = Interval++-- | An angle type in which 360° represents one complete rotation.+type Degrees = Double++-- | An angle type in which 2π radians represents one complete rotation.+type Radians = Double
+ Graphics/Blank/Types/CSS.hs view
@@ -0,0 +1,155 @@+{-# LANGUAGE NoImplicitPrelude #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeSynonymInstances #-}+module Graphics.Blank.Types.CSS where++import Data.Monoid ((<>))+import Data.String++import Graphics.Blank.JavaScript+import Graphics.Blank.Parser+import Graphics.Blank.Types++import Prelude.Compat++import Text.ParserCombinators.ReadP (choice)+import Text.ParserCombinators.ReadPrec (lift)+import Text.Read (Read(..), readListPrecDefault)++import TextShow (TextShow(..), FromTextShow(..))++-- | Denotes CSS distance measurements, especially in the context of 'Font's.+data Length = Em { runLength :: Double } -- ^ The height of the current font.+ | Ex { runLength :: Double } -- ^ The height of the character @x@ (x-height) in the current font.+ | Ch { runLength :: Double } -- ^ The width of the character @0@ in the current font.+ | Rem { runLength :: Double } -- ^ The height of the font relative to the root element.+ | Vh { runLength :: Double } -- ^ One percent of the height of the viewport.+ | Vw { runLength :: Double } -- ^ One percent of the width of the viewport.+ | Vmin { runLength :: Double } -- ^ One percent of the minimum of the viewport height and width.+ | Vmax { runLength :: Double } -- ^ One percent of the maximum of the viewport height and width.+ | Px { runLength :: Double } -- ^ One device pixel (dot) of the display.+ | Mm { runLength :: Double } -- ^ One millimeter.+ | Cm { runLength :: Double } -- ^ One centimeter (10 millimeters).+ | In { runLength :: Double } -- ^ One inch (~2.54 centimeters).+ | Pt { runLength :: Double } -- ^ One point (1/72 inches).+ | Pc { runLength :: Double } -- ^ One pica (12 points).+ deriving (Eq, Ord)++-- | Designates CSS properties that can consist of a 'Length'.+class LengthProperty a where+ -- Create a CSS property value from a 'Length'.+ fromLength :: Length -> a++instance LengthProperty Length where+ fromLength = id++-- | Constructs a 'LengthProperty' value with 'Em' units.+em :: LengthProperty a => Double -> a+em = fromLength . Em++-- | Constructs a 'LengthProperty' value with 'Ex' units.+ex :: LengthProperty a => Double -> a+ex = fromLength . Ex++-- | Constructs a 'LengthProperty' value with 'Ch' units.+ch :: LengthProperty a => Double -> a+ch = fromLength . Ch++-- | Constructs a 'LengthProperty' value with 'Rem' units. 'rem_' has an underscore+-- to distinguish it from 'rem'.+rem_ :: LengthProperty a => Double -> a+rem_ = fromLength . Rem++-- | Constructs a 'LengthProperty' value with 'Vh' units.+vh :: LengthProperty a => Double -> a+vh = fromLength . Vh++-- | Constructs a 'LengthProperty' value with 'Vw' units.+vw :: LengthProperty a => Double -> a+vw = fromLength . Vw++-- | Constructs a 'LengthProperty' value with 'Em' units.+vmin :: LengthProperty a => Double -> a+vmin = fromLength . Vmin++-- | Constructs a 'LengthProperty' value with 'Vmax' units.+vmax :: LengthProperty a => Double -> a+vmax = fromLength . Vmax++-- | Constructs a 'LengthProperty' value with 'Px' units.+px :: LengthProperty a => Double -> a+px = fromLength . Px++-- | Constructs a 'LengthProperty' value with 'Mm' units.+mm :: LengthProperty a => Double -> a+mm = fromLength . Mm++-- | Constructs a 'LengthProperty' value with 'Cm' units.+cm :: LengthProperty a => Double -> a+cm = fromLength . Cm++-- | Constructs a 'LengthProperty' value with 'Im' units. This function has an+-- underscore to distinguish it from the Haskell keyword.+in_ :: LengthProperty a => Double -> a+in_ = fromLength . In++-- | Constructs a 'LengthProperty' value with 'Pt' units.+pt :: LengthProperty a => Double -> a+pt = fromLength . Pt++-- | Constructs a 'LengthProperty' value with 'Pc' units.+pc :: LengthProperty a => Double -> a+pc = fromLength . Pc++instance IsString Length where+ fromString = read++instance Read Length where+ readPrec = do+ d <- readPrec+ lift $ choice+ [ Em d <$ stringCI "em"+ , Ex d <$ stringCI "ex"+ , Ch d <$ stringCI "ch"+ , Rem d <$ stringCI "rem"+ , Vh d <$ stringCI "vh"+ , Vw d <$ stringCI "vw"+ , Vmin d <$ stringCI "vmin"+ , Vmax d <$ stringCI "vmax"+ , Px d <$ stringCI "px"+ , Mm d <$ stringCI "mm"+ , Cm d <$ stringCI "cm"+ , In d <$ stringCI "in"+ , Pt d <$ stringCI "pt"+ , Pc d <$ stringCI "pc"+ ]+ readListPrec = readListPrecDefault++instance Show Length where+ showsPrec p = showsPrec p . FromTextShow++instance TextShow Length where+ showb l = jsDouble (runLength l) <> showbUnits l+ where+ showbUnits (Em _) = "em"+ showbUnits (Ex _) = "ex"+ showbUnits (Ch _) = "ch"+ showbUnits (Rem _) = "rem"+ showbUnits (Vh _) = "vh"+ showbUnits (Vw _) = "vw"+ showbUnits (Vmin _) = "vmin"+ showbUnits (Vmax _) = "vmax"+ showbUnits (Px _) = "px"+ showbUnits (Mm _) = "mm"+ showbUnits (Cm _) = "cm"+ showbUnits (In _) = "in"+ showbUnits (Pt _) = "pt"+ showbUnits (Pc _) = "pc"++-- | Designates CSS properties that can consist of a 'Percentage'.+class PercentageProperty a where+ -- | Create a CSS property value from a 'Percentage'.+ percent :: Percentage -> a++instance PercentageProperty Percentage where+ percent = id
+ Graphics/Blank/Types/Cursor.hs view
@@ -0,0 +1,337 @@+{-# LANGUAGE NoImplicitPrelude #-}+{-# LANGUAGE OverloadedStrings #-}+module Graphics.Blank.Types.Cursor where++import Data.Monoid+import Data.String (IsString(..))+import qualified Data.Text as TS (Text)+import Data.Text (pack)++import Graphics.Blank.JavaScript+import Graphics.Blank.Parser (stringCI, unlift)++import Prelude.Compat++import Text.ParserCombinators.ReadP (ReadP, (<++), between, char,+ choice, munch, skipSpaces)+import Text.ParserCombinators.ReadPrec (lift)+import Text.Read (Read(..), readListPrecDefault)++import TextShow++-- | A data type that can represent a browser cursor.+class CanvasCursor a where+ -- | Convert a value into a JavaScript string representing a cursor value.+ jsCanvasCursor :: a -> Builder++instance CanvasCursor TS.Text where+ jsCanvasCursor = jsText++instance CanvasCursor Cursor where+ jsCanvasCursor = jsCursor++-- | Specified the mouse cursor's appearance in a web browser.+-- +-- Images by the Mozilla Developer Network are licensed under+-- <http://creativecommons.org/licenses/by-sa/2.5/ CC-BY-SA 2.5>.+data Cursor = Auto -- ^ The browser determines the cursor to display based on the+ -- current context.+ | Default -- ^ <<https://developer.mozilla.org/@api/deki/files/3438/=default.gif>>+ | None -- ^ No cursor is rendered.+ | ContextMenu -- ^ <<https://developer.mozilla.org/@api/deki/files/3461/=context-menu.png>>+ | Help -- ^ <<https://developer.mozilla.org/@api/deki/files/3442/=help.gif>>+ | Pointer -- ^ <<https://developer.mozilla.org/@api/deki/files/3449/=pointer.gif>>+ | Progress -- ^ <<https://developer.mozilla.org/@api/deki/files/3450/=progress.gif>>+ | Wait -- ^ <<https://developer.mozilla.org/@api/deki/files/3457/=wait.gif>>+ | Cell -- ^ <<https://developer.mozilla.org/@api/deki/files/3434/=cell.gif>>+ | Crosshair -- ^ <<https://developer.mozilla.org/@api/deki/files/3437/=crosshair.gif>>+ | Text -- ^ <<https://developer.mozilla.org/files/3809/text.gif>>+ | VerticalText -- ^ <<https://developer.mozilla.org/@api/deki/files/3456/=vertical-text.gif>>+ | Alias -- ^ <<https://developer.mozilla.org/@api/deki/files/3432/=alias.gif>>+ | Copy -- ^ <<https://developer.mozilla.org/@api/deki/files/3436/=copy.gif>>+ | Move -- ^ <<https://developer.mozilla.org/@api/deki/files/3443/=move.gif>>+ | NoDrop -- ^ <<https://developer.mozilla.org/@api/deki/files/3445/=no-drop.gif>>+ | NotAllowed -- ^ <<https://developer.mozilla.org/@api/deki/files/3446/=not-allowed.gif>>+ | AllScroll -- ^ <<https://developer.mozilla.org/@api/deki/files/3433/=all-scroll.gif>>+ | ColResize -- ^ <<https://developer.mozilla.org/@api/deki/files/3435/=col-resize.gif>>+ | RowResize -- ^ <<https://developer.mozilla.org/@api/deki/files/3451/=row-resize.gif>>+ | NResize -- ^ <<https://developer.mozilla.org/files/4083/n-resize.gif>>+ | EResize -- ^ <<https://developer.mozilla.org/files/4085/e-resize.gif>>+ | SResize -- ^ <<https://developer.mozilla.org/files/4087/s-resize.gif>>+ | WResize -- ^ <<https://developer.mozilla.org/files/4089/w-resize.gif>>+ | NEResize -- ^ <<https://developer.mozilla.org/files/4091/ne-resize.gif>>+ | NWResize -- ^ <<https://developer.mozilla.org/files/4093/nw-resize.gif>>+ | SEResize -- ^ <<https://developer.mozilla.org/files/4097/se-resize.gif>>+ | SWResize -- ^ <<https://developer.mozilla.org/files/4095/sw-resize.gif>>+ | EWResize -- ^ <<https://developer.mozilla.org/files/3806/3-resize.gif>>+ | NSResize -- ^ <<https://developer.mozilla.org/files/3808/6-resize.gif>>+ | NESWResize -- ^ <<https://developer.mozilla.org/files/3805/1-resize.gif>>+ | NWSEResize -- ^ <<https://developer.mozilla.org/files/3807/4-resize.gif>>+ | ZoomIn -- ^ <<https://developer.mozilla.org/@api/deki/files/3459/=zoom-in.gif>>+ | ZoomOut -- ^ <<https://developer.mozilla.org/@api/deki/files/3460/=zoom-out.gif>>+ | Grab -- ^ <<https://developer.mozilla.org/@api/deki/files/3440/=grab.gif>>+ | Grabbing -- ^ <<https://developer.mozilla.org/@api/deki/files/3441/=grabbing.gif>>+ | URL TS.Text Cursor+ -- ^ An image from a URL. Must be followed by another 'Cursor'.+ deriving (Eq, Ord)++instance IsString Cursor where+ fromString = read++instance JSArg Cursor where+ showbJS = jsCursor++jsCursor :: Cursor -> Builder+jsCursor = jsLiteralBuilder . showb++instance Read Cursor where+ readPrec = lift $ do+ skipSpaces+ choice+ [ Auto <$ stringCI "auto"+ , Default <$ stringCI "default"+ , None <$ stringCI "none"+ , ContextMenu <$ stringCI "context-menu"+ , Help <$ stringCI "help"+ , Pointer <$ stringCI "pointer"+ , Progress <$ stringCI "progress"+ , Wait <$ stringCI "wait"+ , Cell <$ stringCI "cell"+ , Crosshair <$ stringCI "crosshair"+ , Text <$ stringCI "text"+ , VerticalText <$ stringCI "vertical-text"+ , Alias <$ stringCI "alias"+ , Copy <$ stringCI "copy"+ , Move <$ stringCI "move"+ , NoDrop <$ stringCI "no-drop"+ , NotAllowed <$ stringCI "not-allowed"+ , AllScroll <$ stringCI "all-scroll"+ , ColResize <$ stringCI "col-resize"+ , RowResize <$ stringCI "row-resize"+ , NResize <$ stringCI "n-resize"+ , EResize <$ stringCI "e-resize"+ , SResize <$ stringCI "s-resize"+ , WResize <$ stringCI "w-resize"+ , NEResize <$ stringCI "ne-resize"+ , NWResize <$ stringCI "nw-resize"+ , SEResize <$ stringCI "se-resize"+ , SWResize <$ stringCI "sw-resize"+ , EWResize <$ stringCI "ew-resize"+ , NSResize <$ stringCI "ns-resize"+ , NESWResize <$ stringCI "nesw-resize"+ , NWSEResize <$ stringCI "nwse-resize"+ , ZoomIn <$ stringCI "zoom-in"+ , ZoomOut <$ stringCI "zoom-out"+ , Grab <$ stringCI "grab"+ , Grabbing <$ stringCI "grabbing"+ , do _ <- stringCI "url("+ let quoted quote = between (char quote) (char quote)+ url' <- quoted '"' (readURL $ Just '"')+ <++ quoted '\'' (readURL $ Just '\'')+ <++ readURL Nothing+ _ <- char ')'+ skipSpaces+ _ <- char ','+ URL url' <$> unlift readPrec+ ]+ + readListPrec = readListPrecDefault++readURL :: Maybe Char -> ReadP TS.Text+readURL mQuote = do+ url' <- case mQuote of+ Just quote -> munch (/= quote)+ Nothing -> munch (/= ')')+ return $ pack url'++instance Show Cursor where+ showsPrec p = showsPrec p . FromTextShow++instance TextShow Cursor where+ showb Auto = "auto"+ showb Default = "default"+ showb None = "none"+ showb ContextMenu = "context-menu"+ showb Help = "help"+ showb Pointer = "pointer"+ showb Progress = "progress"+ showb Wait = "wait"+ showb Cell = "cell"+ showb Crosshair = "crosshair"+ showb Text = "text"+ showb VerticalText = "vertical-text"+ showb Alias = "alias"+ showb Copy = "copy"+ showb Move = "move"+ showb NoDrop = "no-drop"+ showb NotAllowed = "not-allowed"+ showb AllScroll = "all-scroll"+ showb ColResize = "col-resize"+ showb RowResize = "row-resize"+ showb NResize = "n-resize"+ showb EResize = "e-resize"+ showb SResize = "s-resize"+ showb WResize = "w-resize"+ showb NEResize = "ne-resize"+ showb NWResize = "nw-resize"+ showb SEResize = "se-resize"+ showb SWResize = "sw-resize"+ showb EWResize = "ew-resize"+ showb NSResize = "ns-resize"+ showb NESWResize = "nesw-resize"+ showb NWSEResize = "nwse-resize"+ showb ZoomIn = "zoom-in"+ showb ZoomOut = "zoom-out"+ showb Grab = "grab"+ showb Grabbing = "grabbing"+ showb (URL url' cur) =+ "url(" <> jsLiteralBuilder (fromText url') <> "), " <> showb cur++-- | Shorthand for 'Auto'.+auto :: Cursor+auto = Auto++-- | Shorthand for 'Default', with an underscore to distinguish it from the+-- Haskell keyword @default@.+default_ :: Cursor+default_ = Default++-- | Shorthand for 'None'.+none :: Cursor+none = None++-- | Shorthand for 'ContextMenu'.+contextMenu :: Cursor+contextMenu = ContextMenu++-- | Shorthand for 'Help'.+help :: Cursor+help = Help++-- | Shorthand for 'Pointer'.+pointer :: Cursor+pointer = Pointer++-- | Shorthand for 'Progress'.+progress :: Cursor+progress = Progress++-- | Shorthand for 'Wait'.+wait :: Cursor+wait = Wait++-- | Shorthand for 'Cell'.+cell :: Cursor+cell = Cell++-- | Shorthand for 'Crosshair'.+crosshair :: Cursor+crosshair = Crosshair++-- | Shorthand for 'Text'.+text :: Cursor+text = Text++-- | Shorthand for 'VerticalText'.+verticalText :: Cursor+verticalText = VerticalText++-- | Shorthand for 'Alias'.+alias :: Cursor+alias = Alias++-- | Shorthand for 'Copy'.+copy :: Cursor+copy = Copy++-- | Shorthand for 'Move'.+move :: Cursor+move = Move++-- | Shorthand for 'NoDrop'.+noDrop :: Cursor+noDrop = NoDrop++-- | Shorthand for 'NotAllowed'.+notAllowed :: Cursor+notAllowed = NotAllowed++-- | Shorthand for 'AllScroll'.+allScroll :: Cursor+allScroll = AllScroll++-- | Shorthand for 'ColResize'.+colResize :: Cursor+colResize = ColResize++-- | Shorthand for 'RowResize'.+rowResize :: Cursor+rowResize = RowResize++-- | Shorthand for 'NResize'.+nResize :: Cursor+nResize = NResize++-- | Shorthand for 'EResize'.+eResize :: Cursor+eResize = EResize++-- | Shorthand for 'SResize'.+sResize :: Cursor+sResize = SResize++-- | Shorthand for 'WResize'.+wResize :: Cursor+wResize = WResize++-- | Shorthand for 'NEResize'.+neResize :: Cursor+neResize = NEResize++-- | Shorthand for 'NWResize'.+nwResize :: Cursor+nwResize = NWResize++-- | Shorthand for 'SEResize'.+seResize :: Cursor+seResize = SEResize++-- | Shorthand for 'SWResize'.+swResize :: Cursor+swResize = SWResize++-- | Shorthand for 'EWResize'.+ewResize :: Cursor+ewResize = ewResize++-- | Shorthand for 'NSResize'.+nsResize :: Cursor+nsResize = NSResize++-- | Shorthand for 'NESWResize'.+neswResize :: Cursor+neswResize = NESWResize++-- | Shorthand for 'NWSEResize'.+nwseResize :: Cursor+nwseResize = NWSEResize++-- | Shorthand for 'ZoomIn'.+zoomIn :: Cursor+zoomIn = ZoomIn++-- | Shorthand for 'ZoomOut'.+zoomOut :: Cursor+zoomOut = ZoomOut++-- | Shorthand for 'Grab'.+grab :: Cursor+grab = Grab++-- | Shorthand for 'Grabbing'.+grabbing :: Cursor+grabbing = Grabbing++-- | Shorthand for 'URL'.+url :: TS.Text -> Cursor -> Cursor+url = URL
+ Graphics/Blank/Types/Font.hs view
@@ -0,0 +1,679 @@+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE OverloadedStrings #-}+module Graphics.Blank.Types.Font where++import Control.Applicative+import Control.Monad++import Data.Char+import Data.Default.Class+import Data.Ix (Ix)+import Data.Maybe+import Data.Monoid+import Data.String+import qualified Data.Text as TS+import Data.Text (Text)+import qualified Data.Text.Lazy.Builder as B (singleton)++import Graphics.Blank.JavaScript+import Graphics.Blank.Parser+import Graphics.Blank.Types+import Graphics.Blank.Types.CSS++import qualified Text.ParserCombinators.ReadP as ReadP+import Text.ParserCombinators.ReadP hiding ((<++), choice, pfail)+import qualified Text.ParserCombinators.ReadPrec as ReadPrec+import Text.ParserCombinators.ReadPrec (ReadPrec, (<++), lift, pfail)+import Text.Read (Read(..), readListPrecDefault)++import TextShow (TextShow(..), Builder, FromTextShow(..), showbSpace)++-------------------------------------------------------------------------------++-- | A data type that can represent a browser font.+class CanvasFont a where+ -- | Convert a value into a JavaScript string representing a font value.+ jsCanvasFont :: a -> Builder++instance CanvasFont Text where+ jsCanvasFont = jsText++instance CanvasFont Font where+ jsCanvasFont = jsFont++-------------------------------------------------------------------------------++-- | A CSS-style font data type.+data Font = FontProperties+ { fontStyle :: FontStyle+ , fontVariant :: FontVariant+ , fontWeight :: FontWeight+ , fontSize :: FontSize+ , lineHeight :: LineHeight+ , fontFamily :: [FontFamily]+ } -- ^ A font specified by its individual longhand properties.+ | CaptionFont -- ^ The font used for captioned controls (e.g., buttons, drop-downs, etc.)+ | IconFont -- ^ The font used to label icons.+ | MenuFont -- ^ The font used in menus (e.g., dropdown menus and menu lists).+ | MessageBoxFont -- ^ The font used in dialog boxes.+ | SmallCaptionFont -- ^ The font used for labeling small controls.+ | StatusBarFont -- ^ The font used in window status bars.+ deriving (Eq, Ord)++-- |+-- Creates a new font from the 'FontFamily' list, using the 'Default' instances+-- for the other five longhand properties. If you only wish to change certain+-- properties and leave the others alone, this provides a convenient mechanism+-- for doing so:+-- +-- @+-- ('defFont' ["Gill Sans Extrabold", 'sansSerif']) {+-- 'fontStyle' = 'italic'+-- , 'fontSize' = 12 # 'px'+-- , 'lineHeight' = 14 # 'px'+-- }+-- @+defFont :: [FontFamily] -> Font+defFont = FontProperties def def def def def++-- | Shorthand for 'CaptionFont'.+caption :: Font+caption = CaptionFont++-- | Shorthand for 'IconFont'.+icon :: Font+icon = IconFont++-- | Shorthand for 'MenuFont'.+menu :: Font+menu = MenuFont++-- | Shorthand for 'MessageBoxFont'.+messageBox :: Font+messageBox = MessageBoxFont++-- | Shorthand for 'SmallCaptionFont'.+smallCaption :: Font+smallCaption = SmallCaptionFont++-- | Shorthand for 'StatusBarFont'.+statusBar :: Font+statusBar = StatusBarFont++instance IsString Font where+ fromString = read++instance JSArg Font where+ showbJS = jsFont++jsFont :: Font -> Builder+jsFont = jsLiteralBuilder . showb++instance Read Font where+ readPrec = do+ lift skipSpaces+ ReadPrec.choice+ [ CaptionFont <$ lift (stringCI "caption")+ , IconFont <$ lift (stringCI "icon")+ , MenuFont <$ lift (stringCI "menu")+ , MessageBoxFont <$ lift (stringCI "message-box")+ , SmallCaptionFont <$ lift (stringCI "small-caption")+ , StatusBarFont <$ lift (stringCI "status-bar")+ , readFontProperties Nothing Nothing Nothing+ ]+ readListPrec = readListPrecDefault++-- | Like 'Either', but with three possibilities instead of two.+data OneOfThree a b c = One a | Two b | Three c++-- |+-- The formal syntax for the font CSS property+-- (https://developer.mozilla.org/en-US/docs/Web/CSS/font#Syntax) is surprisingly complex.+-- It requires that font-style, font-variant, and font-weight must be defined, if any,+-- before the font-size value. Furthermore, each of those three properties may only be defined at+-- most once, and the relative order of the three does not matter. This is a tall order for the+-- Text.ParserCombinators modules, so we use a heavily monadic utility function to detect+-- make it easier to catch bad input. The three Maybe arguments each represent whether its+-- respective property has not (Nothing) or has (Just) been read. If it has been read, then+-- readFontProperties will not attempt to parse it again.+-- +-- readFontProperties will proceed to parse the remaining Font longhand properties once+-- either all three of the first properties have been parsed, or when it is unsuccessful at+-- parsing any of the first three properties.+readFontProperties :: Maybe FontStyle -> Maybe FontVariant -> Maybe FontWeight -> ReadPrec Font+readFontProperties style variant weight =+ -- If all three properties have been parsed, proceed to the remaining three properties.+ if isJust style && isJust variant && isJust weight+ then readFontProperties' style variant weight+ else do+ -- If the property has already been parsed, do not parse it again.+ let parseCheck :: Maybe a -> ReadPrec a -> ReadPrec a+ parseCheck mb parser = if isJust mb then pfail else parser+ + readStyle, readVariant, readWeight :: ReadPrec (OneOfThree FontStyle FontVariant FontWeight)+ readStyle = One <$> parseCheck style readPrec+ readVariant = Two <$> parseCheck variant readPrec+ readWeight = Three <$> parseCheck weight readPrec+ + -- First attempt to parse font-style, then font-variant, then font-weight (unless one+ -- of them has already been parsed, in which case skip to the next property parser.+ prop <- maybeReadPrec $ readStyle <++ readVariant <++ readWeight+ -- Check to see which property, if any, was parsed.+ case prop of+ Just (One style') -> do+ when (isJust style) pfail -- Safeguard to ensure a property is not parsed twice.+ readFontProperties (Just style') variant weight+ Just (Two variant') -> do+ when (isJust variant) pfail+ readFontProperties style (Just variant') weight+ Just (Three weight') -> do+ when (isJust weight) pfail+ readFontProperties style variant (Just weight')+ -- If no properties were parsed, proceed to the remaining three properties.+ Nothing -> readFontProperties' style variant weight++-- |+-- Parses the remaining three Font longhand properties (font-size, line-height, and+-- font-family). Make sure to also parse the forward slash, if any, that separates+-- the font-size and line-height properties.+readFontProperties' :: Maybe FontStyle -> Maybe FontVariant -> Maybe FontWeight -> ReadPrec Font+readFontProperties' mbStyle mbVariant mbWeight =+ FontProperties (fromMaybe def mbStyle) (fromMaybe def mbVariant) (fromMaybe def mbWeight)+ <$> readPrec+ <*> lift (option def $ skipSpaces *> char '/' *> unlift readPrec)+ <*> (lift (munch1 isSpace) *> readPrec)++instance Show Font where+ showsPrec p = showsPrec p . FromTextShow++instance TextShow Font where+ showb (FontProperties style variant weight size height' family)+ = showb style+ <> showbSpace+ <> showb variant+ <> showbSpace+ <> showb weight+ <> showbSpace+ <> showb size+ <> B.singleton '/'+ <> showb height'+ <> showbSpace+ <> showb family+ showb CaptionFont = "caption"+ showb IconFont = "icon"+ showb MenuFont = "menu"+ showb MessageBoxFont = "message-box"+ showb SmallCaptionFont = "small-caption"+ showb StatusBarFont = "status-bar"++-------------------------------------------------------------------------------++-- | Specifies if a 'Font' is italic or oblique.+data FontStyle = NormalStyle -- ^ Selects a font classified as normal (default).+ | ItalicStyle -- ^ Selects a font that is labeled italic, or if one is not available,+ -- one labeled oblique.+ | ObliqueStyle -- ^ Selects a font that is labeled oblique.+ deriving (Bounded, Enum, Eq, Ix, Ord)++-- | Shorthand for 'ItalicStyle'.+italic :: FontStyle+italic = ItalicStyle++-- | Shorthand for 'ObliqueStyle'.+oblique :: FontStyle+oblique = ObliqueStyle++instance Default FontStyle where+ def = NormalStyle++instance IsString FontStyle where+ fromString = read++instance NormalProperty FontStyle++instance Read FontStyle where+ readPrec = lift $ do+ skipSpaces+ ReadP.choice+ [ NormalStyle <$ stringCI "normal"+ , ItalicStyle <$ stringCI "italic"+ , ObliqueStyle <$ stringCI "oblique"+ ]+ readListPrec = readListPrecDefault++instance Show FontStyle where+ showsPrec p = showsPrec p . FromTextShow++instance TextShow FontStyle where+ showb NormalStyle = "normal"+ showb ItalicStyle = "italic"+ showb ObliqueStyle = "oblique"++-------------------------------------------------------------------------------++-- | Specifies the face of a 'Font'.+data FontVariant = NormalVariant -- ^ A normal font face (default).+ | SmallCapsVariant -- ^ A font face with small capital letters for lowercase characters.+ deriving (Bounded, Enum, Eq, Ix, Ord)++-- | Shorthand for 'SmallCapsVariant'.+smallCaps :: FontVariant+smallCaps = SmallCapsVariant++instance Default FontVariant where+ def = NormalVariant++instance IsString FontVariant where+ fromString = read++instance NormalProperty FontVariant++instance Read FontVariant where+ readPrec = lift $ do+ skipSpaces+ (NormalVariant <$ stringCI "normal") <|> (SmallCapsVariant <$ stringCI "small-caps")+ readListPrec = readListPrecDefault++instance Show FontVariant where+ showsPrec p = showsPrec p . FromTextShow++instance TextShow FontVariant where+ showb NormalVariant = "normal"+ showb SmallCapsVariant = "small-caps"++-------------------------------------------------------------------------------++-- |+-- Specifies the boldness of a 'Font'. Note that 'FontWeight' is an instance of+-- 'Num' so that the nine numeric weights can be used directly. For example:+-- +-- @+-- ('defFont' ['sansSerif']) { 'fontWeight' = 900 }+-- @+-- +-- Attempting to use a numeric weight other than the nine given will result in+-- a runtime error.+data FontWeight = NormalWeight -- ^ Default.+ | BoldWeight+ | BolderWeight+ | LighterWeight+ | Weight100+ | Weight200+ | Weight300+ | Weight400+ | Weight500+ | Weight600+ | Weight700+ | Weight800+ | Weight900+ deriving (Bounded, Enum, Eq, Ix, Ord)++-- | Shorthand for 'BoldWeight'.+bold :: FontWeight+bold = BoldWeight++-- | Shorthand for 'BolderWeight'.+bolder :: FontWeight+bolder = BolderWeight++-- | Shorthand for 'LighterWeight'.+lighter :: FontWeight+lighter = LighterWeight++fontWeightError :: a+fontWeightError = error "invalid font-weight operation"++instance Default FontWeight where+ def = NormalWeight++instance IsString FontWeight where+ fromString = read++instance NormalProperty FontWeight++instance Num FontWeight where+ (+) = fontWeightError+ (-) = fontWeightError+ (*) = fontWeightError+ abs = fontWeightError+ signum = fontWeightError+ fromInteger 100 = Weight100+ fromInteger 200 = Weight200+ fromInteger 300 = Weight300+ fromInteger 400 = Weight400+ fromInteger 500 = Weight500+ fromInteger 600 = Weight600+ fromInteger 700 = Weight700+ fromInteger 800 = Weight800+ fromInteger 900 = Weight900+ fromInteger _ = fontWeightError++instance Read FontWeight where+ readPrec = lift $ do+ skipSpaces+ ReadP.choice+ [ NormalWeight <$ stringCI "normal"+ , BoldWeight <$ stringCI "bold"+ , BolderWeight <$ stringCI "bolder"+ , LighterWeight <$ stringCI "lighter"+ , Weight100 <$ string "100"+ , Weight200 <$ string "200"+ , Weight300 <$ string "300"+ , Weight400 <$ string "400"+ , Weight500 <$ string "500"+ , Weight600 <$ string "600"+ , Weight700 <$ string "700"+ , Weight800 <$ string "800"+ , Weight900 <$ string "900"+ ]+ readListPrec = readListPrecDefault++instance Show FontWeight where+ showsPrec p = showsPrec p . FromTextShow++instance TextShow FontWeight where+ showb NormalWeight = "normal"+ showb BoldWeight = "bold"+ showb BolderWeight = "bolder"+ showb LighterWeight = "lighter"+ showb Weight100 = "100"+ showb Weight200 = "200"+ showb Weight300 = "300"+ showb Weight400 = "400"+ showb Weight500 = "500"+ showb Weight600 = "600"+ showb Weight700 = "700"+ showb Weight800 = "800"+ showb Weight900 = "900"++-------------------------------------------------------------------------------++-- | The desired height of 'Font' glyphs.+--+-- ==== __Examples__+--+-- @+-- ('defFont' ['sansSerif']) { 'fontSize' = 'xxSmall' }+-- ('defFont' ['sansSerif']) { 'fontSize' = 30 # 'pt' }+-- ('defFont' ['sansSerif']) { 'fontSize' = 50 # 'percent' }+-- @+data FontSize = XXSmallSize+ | XSmallSize+ | SmallSize+ | MediumSize -- ^ Default.+ | LargeSize+ | XLargeSize+ | XXLargeSize+ | LargerSize+ | SmallerSize+ | FontSizeLength Length+ | FontSizePercentage Percentage+ deriving (Eq, Ord)++-- | Shorthand for 'XXSmallSize'.+xxSmall :: FontSize+xxSmall = XXSmallSize++-- | Shorthand for 'XSmallSize'.+xSmall :: FontSize+xSmall = XSmallSize++-- | Shorthand for 'SmallSize'.+small :: FontSize+small = SmallSize++-- | Shorthand for 'MediumSize'.+medium :: FontSize+medium = MediumSize++-- | Shorthand for 'LargeSize'.+large :: FontSize+large = LargeSize++-- | Shorthand for 'XLargeSize'.+xLarge :: FontSize+xLarge = XLargeSize++-- | Shorthand for 'XXLargeSize'.+xxLarge :: FontSize+xxLarge = XXLargeSize++-- | Shorthand for 'LargerSize'.+larger :: FontSize+larger = LargerSize++-- | Shorthand for 'SmallerSize'.+smaller :: FontSize+smaller = SmallerSize++instance Default FontSize where+ def = MediumSize++instance IsString FontSize where+ fromString = read++instance LengthProperty FontSize where+ fromLength = FontSizeLength++instance PercentageProperty FontSize where+ percent = FontSizePercentage++instance Read FontSize where+ readPrec = do+ lift $ skipSpaces+ ReadPrec.choice+ [ XXSmallSize <$ lift (stringCI "xx-small")+ , XSmallSize <$ lift (stringCI "x-small")+ , SmallSize <$ lift (stringCI "small")+ , MediumSize <$ lift (stringCI "medium")+ , LargeSize <$ lift (stringCI "large")+ , XLargeSize <$ lift (stringCI "x-large")+ , XXLargeSize <$ lift (stringCI "xx-large")+ , LargerSize <$ lift (stringCI "larger")+ , SmallerSize <$ lift (stringCI "smaller")+ , FontSizeLength <$> readPrec+ , FontSizePercentage <$> readPrec <* lift (char '%')+ ]+ readListPrec = readListPrecDefault++instance Show FontSize where+ showsPrec p = showsPrec p . FromTextShow++instance TextShow FontSize where+ showb XXSmallSize = "xx-small"+ showb XSmallSize = "x-small"+ showb SmallSize = "small"+ showb MediumSize = "medium"+ showb LargeSize = "large"+ showb XLargeSize = "x-large"+ showb XXLargeSize = "xx-large"+ showb LargerSize = "larger"+ showb SmallerSize = "smaller"+ showb (FontSizeLength l) = showb l+ showb (FontSizePercentage p) = jsDouble p <> B.singleton '%'++-------------------------------------------------------------------------------++-- | The height of the line boxes in a 'Font'.+--+-- ==== __Examples__+--+-- @+-- ('defFont' ['sansSerif']) { 'lineHeight' = 'normal' }+-- ('defFont' ['sansSerif']) { 'lineHeight' = 50 }+-- ('defFont' ['sansSerif']) { 'lineHeight' = 30 # 'em' }+-- ('defFont' ['sansSerif']) { 'lineHeight' = 70 # 'percent' }+-- @+data LineHeight = NormalLineHeight -- ^ Default.+ | LineHeightNumber Double+ | LineHeightLength Length+ | LineHeightPercentage Percentage+ deriving (Eq, Ord)++lineHeightError :: a+lineHeightError = error "no arithmetic for line-height"++instance Default LineHeight where+ def = NormalLineHeight++instance Fractional LineHeight where+ (/) = lineHeightError+ recip = lineHeightError+ fromRational = LineHeightNumber . fromRational++instance IsString LineHeight where+ fromString = read++instance LengthProperty LineHeight where+ fromLength = LineHeightLength++instance NormalProperty LineHeight++instance Num LineHeight where+ (+) = lineHeightError+ (-) = lineHeightError+ (*) = lineHeightError+ abs = lineHeightError+ signum = lineHeightError+ fromInteger = LineHeightNumber . fromInteger++instance PercentageProperty LineHeight where+ percent = LineHeightPercentage++instance Read LineHeight where+ readPrec = do+ lift skipSpaces+ ReadPrec.choice+ [ NormalLineHeight <$ lift (stringCI "normal")+ , LineHeightNumber <$> readPrec+ , LineHeightLength <$> readPrec+ , LineHeightPercentage <$> readPrec <* lift (char '%')+ ]+ readListPrec = readListPrecDefault++instance Show LineHeight where+ showsPrec p = showsPrec p . FromTextShow++instance TextShow LineHeight where+ showb NormalLineHeight = "normal"+ showb (LineHeightNumber n) = jsDouble n+ showb (LineHeightLength l) = showb l+ showb (LineHeightPercentage p) = jsDouble p <> B.singleton '%'++-------------------------------------------------------------------------------++-- |+-- The name of a 'Font' family. Note that both 'FontFamily' and @['FontFamily']@+-- are instances of 'IsString', so it is possible to produce 'FontFamily' values+-- in several different ways. For example, these are all of type 'FontFamily':+-- +-- @+-- 'FontFamilyName' "Gill Sans Extrabold"+-- "Gill Sans Extrabold" :: 'FontFamily'+-- 'serif'+-- "serif" :: 'FontFamily'+-- @+-- +-- These are all of type @['FontFamily']@:+-- +-- @+-- ['FontFamilyName' \"Helvetica\", 'serif']+-- [\"Helvetica\", "serif"] :: ['FontFamily']+-- "Helvetica, serif" :: ['FontFamily']+-- @+data FontFamily = FontFamilyName Text -- ^ The name of a custom font family.+ | SerifFamily -- ^ A generic font family where glyphs have+ -- serifed endings.+ | SansSerifFamily -- ^ A generic font family where glyphs do not+ -- have serifed endings.+ | MonospaceFamily -- ^ A generic font family where all glyphs have+ -- the same fixed width.+ | CursiveFamily -- ^ A generic font family with cursive glyphs.+ | FantasyFamily -- ^ A generic font family where glyphs have+ -- decorative, playful representations.+ deriving (Eq, Ord)++-- | Shorthand for 'SerifFamily'.+serif :: FontFamily+serif = SerifFamily++-- | Shorthand for 'SansSerifFamily'.+sansSerif :: FontFamily+sansSerif = SansSerifFamily++-- | Shorthand for 'MonospaceFamily'.+monospace :: FontFamily+monospace = MonospaceFamily++-- | Shorthand for 'CursiveFamily'.+cursive :: FontFamily+cursive = CursiveFamily++-- | Shorthand for 'FantasyFamily'.+fantasy :: FontFamily+fantasy = FantasyFamily++instance IsString FontFamily where+ fromString = read++-- |+-- There are two separate 'IsString' instances for 'FontFamily' so that single font+-- families and lists of font families alike can be converted from string literals.+instance IsString [FontFamily] where+ fromString = read++instance Read FontFamily where+ readPrec = lift $ do+ skipSpaces+ ReadP.choice+ [ SerifFamily <$ stringCI "serif"+ , SansSerifFamily <$ stringCI "sans-serif"+ , MonospaceFamily <$ stringCI "monospace"+ , CursiveFamily <$ stringCI "cursive"+ , FantasyFamily <$ stringCI "fantasy"+ , let quoted quote = between (char quote) (char quote)+ in quoted '"' (readFontFamily $ Just '"')+ <|> quoted '\'' (readFontFamily $ Just '\'')+ <|> readFontFamily Nothing+ ]+ + -- readListPrec is overloaded so that it will read in a comma-separated list of+ -- family names not delimited by square brackets, as per the CSS syntax.+ readListPrec = lift . sepBy1 (unlift readPrec) $ skipSpaces *> char ','++readFontFamily :: Maybe Char -> ReadP FontFamily+readFontFamily mQuote = do+ name <- case mQuote of+ Just quote -> munch (/= quote)+ Nothing -> unwords <$> sepBy1 cssIdent (munch1 isSpace)+ return . FontFamilyName $ TS.pack name++instance Show FontFamily where+ showsPrec p = showsPrec p . FromTextShow+ showList = showsPrec 0 . FromTextShow++instance TextShow FontFamily where+ showb (FontFamilyName name) = showb name+ showb SerifFamily = "serif"+ showb SansSerifFamily = "sans-serif"+ showb MonospaceFamily = "monospace"+ showb CursiveFamily = "cursive"+ showb FantasyFamily = "fantasy"+ + -- Omit the square brackets when showing a list of font families so that+ -- it matches the CSS syntax.+ showbList = jsList showb++-------------------------------------------------------------------------------++-- | A convenient way to use the 'Default' normal value for several 'Font'+-- longhand properties.+class Default a => NormalProperty a where+ -- | The default value for a CSS property. For example, it can be used+ -- like this:+ -- + -- @+ -- ('defFont' ['sansSerif']) { 'lineHeight' = 'normal' }+ -- @+ normal :: a+ normal = def
Graphics/Blank/Utils.hs view
@@ -12,7 +12,6 @@ import Graphics.Blank.Generated import Graphics.Blank.JavaScript - -- | Clear the screen. Restores the default transformation matrix. clearCanvas :: Canvas () clearCanvas = do@@ -31,12 +30,12 @@ infixr 0 # -- | The @#@-operator is the Haskell analog to the @.@-operator--- in Javascript. Example:---+-- in JavaScript. Example:+-- -- > grd # addColorStop(0, "#8ED6FF");---+-- -- This can be seen as equivalent of @grd.addColorStop(0, "#8ED6FF")@.-(#) :: a -> (a -> Canvas b) -> Canvas b+(#) :: a -> (a -> b) -> b (#) obj act = act obj -- | Read a file, and generate a data URL.@@ -45,14 +44,13 @@ -- readDataURL :: Text -> FilePath -> IO Text readDataURL mime_type filePath = do- dat <- B.readFile filePath- return $ "data:" <> mime_type <> ";base64," <> decodeUtf8 (encode dat)+ dat <- B.readFile filePath+ return $ "data:" <> mime_type <> ";base64," <> decodeUtf8 (encode dat) --- | Find the mime type for a data URL.---+-- | Find the MIME type for a data URL.+-- -- > > dataURLMimeType "data:image/png;base64,iVBORw..." -- > "image/png"--- dataURLMimeType :: Text -> Text dataURLMimeType txt | dat /= "data" = error "dataURLMimeType: no 'data:'"@@ -64,7 +62,6 @@ (mime_type,rest2) = Text.span (/= ';') rest1 -- | Write a data URL to a given file.- writeDataURL :: FilePath -> Text -> IO () writeDataURL fileName = B.writeFile fileName@@ -98,7 +95,7 @@ putImageDataAt :: (ImageData, Double, Double) -> Canvas () putImageDataAt (imgData, dx, dy) = Method $ PutImageData (imgData, [dx, dy]) --- | Acts like 'putImageDataDirty', but with four extra 'Double' arguments that specify+-- | Acts like 'putImageDataAt', but with four extra 'Double' arguments that specify -- which region of the 'ImageData' (the dirty rectangle) should be drawn. The third -- and fourth 'Double's specify the dirty rectangle's x- and y- coordinates, and the -- fifth and sixth 'Double's specify the dirty rectangle's width and height.
LICENSE view
@@ -1,4 +1,4 @@-Copyright (c)2014, The University of Kansas+Copyright (c) 2014-2015, The University of Kansas All rights reserved.
README.md view
@@ -35,7 +35,7 @@ |-------|-------| | [Examples](https://github.com/ku-fpg/blank-canvas/wiki/Examples) | Various complete examples of using blank-canvas | | [Installation](https://github.com/ku-fpg/blank-canvas/wiki/Installation) | How to install blank-canvas |-| [Hackage](https://hackage.haskell.org/package/blank-canvas) | Current release is 0.4.0 |+| [Hackage](https://hackage.haskell.org/package/blank-canvas) | Current release is 0.5 | | [API](https://github.com/ku-fpg/blank-canvas/wiki/API) | Discussion of API, compared with the original JavaScript API | | [Canvas Examples](https://github.com/ku-fpg/blank-canvas/wiki/Canvas%20Examples) | Transliterated from <http://www.html5canvastutorials.com/> into Haskell and blank-canvas, with kind permission of Eric Rowell, author of the JavaScript HTML5 Canvas Tutorial. | | [FAQ](https://github.com/ku-fpg/blank-canvas/wiki/FAQ) | F.A.Q. |
blank-canvas.cabal view
@@ -1,17 +1,17 @@ Name: blank-canvas-Version: 0.5+Version: 0.6 Synopsis: HTML5 Canvas Graphics Library -Description: blank-canvas is a Haskell binding to the complete- HTML5 Canvas API. blank-canvas allows Haskell- users to write, in Haskell, interactive images- onto their web browsers. blank-canvas gives the- user a single full-window canvas, and provides+Description: @blank-canvas@ is a Haskell binding to the complete+ <https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API HTML5 Canvas API>.+ @blank-canvas@ allows Haskell users to write, in Haskell,+ interactive images onto their web browsers. @blank-canvas@+ gives the user a single full-window canvas, and provides many well-documented functions for rendering images. . @- {-# LANGUAGE OverloadedStrings #-}+ {-# LANGUAGE OverloadedStrings #-} module Main where import Graphics.Blank -- import the blank canvas .@@ -20,14 +20,14 @@     moveTo(50,50)     lineTo(200,100)     lineWidth 10-     strokeStyle "red"+     strokeStyle \"red\"     stroke() -- this draws the ink into the canvas @- .+ . <<https://github.com/ku-fpg/blank-canvas/wiki/images/Red_Line.png>>- .- For more details, read the <https://github.com/ku-fpg/blank-canvas/wiki blank-canvas wiki>.- .+ .+ For more details, read the <https://github.com/ku-fpg/blank-canvas/wiki blank-canvas wiki>.+ . License: BSD3 License-file: LICENSE Author: Andy Gill and Ryan Scott@@ -37,9 +37,9 @@ Bug-reports: https://github.com/ku-fpg/blank-canvas/issues Category: Graphics Build-type: Simple-Stability: beta+Stability: Beta Extra-source-files: README.md- Changelog.md+ Changelog.md Cabal-version: >= 1.10 data-files: static/index.html@@ -48,38 +48,123 @@ Library- Exposed-modules: Graphics.Blank,- Graphics.Blank.GHCi,+ Exposed-modules: Graphics.Blank+ Graphics.Blank.Cursor+ Graphics.Blank.Font+ Graphics.Blank.GHCi Graphics.Blank.Style- other-modules: Graphics.Blank.Canvas,- Graphics.Blank.DeviceContext,- Graphics.Blank.Events,- Graphics.Blank.Generated,- Graphics.Blank.JavaScript,- Graphics.Blank.Utils,+ other-modules: Graphics.Blank.Canvas+ Graphics.Blank.DeviceContext+ Graphics.Blank.Events+ Graphics.Blank.Generated+ Graphics.Blank.JavaScript+ Graphics.Blank.Parser+ Graphics.Blank.Types+ Graphics.Blank.Types.CSS+ Graphics.Blank.Types.Cursor+ Graphics.Blank.Types.Font+ Graphics.Blank.Utils Paths_blank_canvas default-language: Haskell2010- build-depends: aeson >= 0.7 && < 0.9,+ build-depends: aeson >= 0.7 && < 0.11, base64-bytestring == 1.0.*,- base >= 4.3.1 && < 4.8,+ base >= 4.6 && < 4.9,+ base-compat >= 0.8.1 && < 1, bytestring == 0.10.*,- colour >= 2.2 && < 3.0,+ colour >= 2.2 && < 3.0, containers == 0.5.*, data-default-class == 0.0.*,- http-types == 0.8.*,- kansas-comet == 0.3.*,- scotty >= 0.8 && < 0.10,- stm >= 2.2 && < 2.5,- text >= 1.1 && < 1.3,- transformers >= 0.3 && < 0.5,+ http-types >= 0.8 && < 0.10,+ mime-types >= 0.1.0.3 && < 0.2,+ kansas-comet >= 0.4 && < 0.5,+ scotty >= 0.10 && < 0.11,+ stm >= 2.2 && < 2.5,+ text >= 1.1 && < 1.3,+ text-show >= 2 && < 2.2,+ transformers >= 0.3 && < 0.5, wai == 3.*,- wai-extra >= 3.0.1 && < 3.1,+ wai-extra >= 3.0.1 && < 3.1, warp == 3.*,- vector >= 0.10 && < 0.11+ vector >= 0.10 && < 0.12 - GHC-options: -Wall -fno-warn-orphans -fno-warn-warnings-deprecations- GHC-prof-options: -Wall -fno-warn-orphans -fno-warn-warnings-deprecations -auto-all -fsimpl-tick-factor=100000+ GHC-options: -Wall+ GHC-prof-options: -Wall -fsimpl-tick-factor=100000+++test-suite wiki-suite+ build-depends: base >= 4.6 && < 4.9,+ blank-canvas == 0.6.*,+ containers == 0.5.*,+ process == 1.2.*,+ directory >= 1.2,+ shake >= 0.13,+ stm >= 2.2 && < 2.5,+ text >= 1.1 && < 1.3,+ time >= 1.4 && < 1.6,+ unix == 2.7.*,+ vector >= 0.10 && < 0.12++ default-language: Haskell2010+ GHC-options: -threaded -Wall+ main-is: Main.hs+ hs-source-dirs: wiki-suite+ type: exitcode-stdio-1.0+ other-modules: Arc+ Bezier_Curve+ Bounce+ Circle+ Clipping_Region+ Color_Fill+ Color_Square+ Custom_Shape+ Custom_Transform+ Draw_Canvas+ Draw_Device+ Draw_Image+ Favicon+ Font_Size_and_Style+ Get_Image_Data_URL+ Global_Alpha+ Global_Composite_Operations+ Grayscale+ Image_Crop+ Image_Loader+ Image_Size+ Is_Point_In_Path+ Key_Read+ Line+ Line_Cap+ Line_Color+ Line_Join+ Line_Width+ Linear_Gradient+ Load_Image_Data_URL+ Load_Image_Data_URL_2+ Miter_Limit+ Path+ Pattern+ Quadratic_Curve+ Radial_Gradient+ Rectangle+ Red_Line+ Rotate_Transform+ Rotating_Square+ Rounded_Corners+ Scale_Transform+ Semicircle+ Shadow+ Square+ Text_Align+ Text_Baseline+ Text_Color+ Text_Metrics+ Text_Stroke+ Text_Wrap+ Tic_Tac_Toe+ Translate_Transform+ Wiki+ source-repository head type: git
static/index.html view
@@ -11,7 +11,7 @@ <script type="text/javascript"> var session = 0; // global session number var images = [];- var gradients = [];+ var sounds = []; var patterns = []; var canvasbuffers = []; var c = null;@@ -57,29 +57,11 @@ } } - function CreateLinearGradient(x0,y0,x1,y1) {- return function (u,c) {- var count = gradients.push(c.createLinearGradient(x0,y0,x1,y1));- $.kc.reply(u,count - 1);- }- }-- function CreateRadialGradient(x0,y0,r0,x1,y1,r1) {- return function (u,c) {- var count = gradients.push(c.createRadialGradient(x0,y0,r0,x1,y1,r1));- $.kc.reply(u,count - 1);- }- }-- function CreatePattern(img,src) {- return function (u,c) {- var count = patterns.push(c.createPattern(img,src));- $.kc.reply(u,count - 1);- }- }- function MeasureText(txt) { return function (u,c) {+ // If we try return the object directly, via json, we+ // get different results on different browsers.+ // So we build an object explicity. $.kc.reply(u,{ width: c.measureText(txt).width }); } }@@ -103,7 +85,7 @@ return function (u,c) { var img = c.getImageData(sx,sy,sw,sh); var arr = [];- for(var i = 0;i < img.data.length;i++) {+ for (var i = 0;i < img.data.length;i++) { arr[i] = img.data[i]; } $.kc.reply(u,{ width: img.width@@ -113,28 +95,37 @@ } } + function Cursor(cur) {+ return function(u, c) {+ c.canvas.style.cursor = cur;+ $.kc.reply(u, []);+ };+ }+ function Sync(u,c) { $.kc.reply(u,[]); } function Trigger(e) {- var o = {};- o.metaKey = e.metaKey;+ var o = {};+ o.metaKey = e.metaKey; o.type = e.type;- if (e.pageXY != undefined) { - o.pageXY = e.pageXY; - }- if (e.pageX != undefined && e.pageY != undefined) {- o.pageXY = [e.pageX,e.pageY];+ if (e.pageXY != undefined) {+ o.pageXY = e.pageXY; }- if (e.which != undefined) { - o.which = e.which; + if (e.pageX != undefined && e.pageY != undefined) {+ o.pageXY = [e.pageX,e.pageY]; }+ if (e.which != undefined) {+ o.which = e.which;+ } $.kc.event(o); }+ function register(name) { $(document).bind(name,Trigger); }+ function redraw() { $.ajax({ url: "/canvas/" + session, type: "GET",@@ -152,6 +143,23 @@ } return imgData; }++ function NewAudio(src) {+ return function (u,c) {+ var audio = new Audio();+ var count = sounds.push(audio);+ // var dur = audio.duration;+ //+ // I commented this out because there's a chance that the browser won't+ // know the audio duration until it's "loaded" (i.e., it can be played+ // through). Further testing might be wise.+ audio.oncanplaythrough = function () {+ $.kc.reply(u,[count - 1,audio.duration]);+ };+ audio.onerror = function() { alert("Audio " + src + " not found.\nAdd as a URL to fix this."); };+ audio.src = src;+ }+ } $(document).ready(function() { // Make the canvas the size of the window
+ wiki-suite/Arc.hs view
@@ -0,0 +1,29 @@+{-# LANGUAGE OverloadedStrings #-}+module Arc where++import Graphics.Blank+import Wiki -- (578,200)++main :: IO ()+main = blankCanvas 3000 $ \ context -> do+ send context $ do+ let centerX = width context / 2;+ let centerY = height context / 2;+ let radius = 75;+ let startingAngle = 1.1 * pi+ let endingAngle = 1.9 * pi+ lineWidth 15+ strokeStyle "black"++ beginPath()+ arc(centerX - 50, centerY, radius, startingAngle, endingAngle, False)+ stroke()++ beginPath()+ strokeStyle "blue"+ arc(centerX + 50, centerY, radius, startingAngle, endingAngle, True)+ stroke()+++ wiki $ snapShot context "images/Arc.png"+ wiki $ close context
+ wiki-suite/Bezier_Curve.hs view
@@ -0,0 +1,20 @@+{-# LANGUAGE OverloadedStrings #-}+module Bezier_Curve where++import Graphics.Blank+import Wiki -- (578,200)++main :: IO ()+main = blankCanvas 3000 $ \ context -> do+ send context $ do+ beginPath()+ moveTo(188, 150)+ bezierCurveTo(140, 10, 388, 10, 388, 170)+ lineWidth 10+ -- line color+ strokeStyle "black"+ stroke()+++ wiki $ snapShot context "images/Bezier_Curve.png"+ wiki $ close context
+ wiki-suite/Bounce.hs view
@@ -0,0 +1,67 @@+{-# LANGUAGE OverloadedStrings #-}+module Bounce where++import Control.Concurrent+import Control.Monad -- wiki $+import Data.Text (Text)+import Graphics.Blank+import Wiki -- (512,384)++main :: IO ()+main = blankCanvas 3000 { events = ["mousedown"] } $ go++type Ball a = ((Double, Double), Double, a)++type Color = String++epoch :: [Ball ()]+epoch = []++type State = ([Ball Color])++showBall :: (Double, Double) -> Text -> Canvas ()+showBall (x,y) col = do+ beginPath()+ globalAlpha 0.5+ fillStyle col+ arc(x, y, 50, 0, pi*2, False)+ closePath()+ fill()++moveBall :: Ball a -> Ball a+moveBall ((x,y),d,a) = ((x,y+d),d+0.5,a)++go :: DeviceContext -> IO ()+go context = do+ let bounce :: Ball a -> Ball a+ bounce ((x,y),d,a)+ | y + 25 >= height context && d > 0 = ((x,y),-(d-0.5)*0.97,a)+ | otherwise = ((x,y),d,a)++ let loop (balls,cols) = do++ send context $ do+ clearCanvas+ sequence_+ [ showBall xy col+ | (xy,_,col) <- balls+ ]+ threadDelay (20 * 1000) ++ wiki $ counter (\ _ -> True) $ \ n -> do+ file <- wiki $ anim_png "Bounce"+ wiki $ when (n `mod` 43 == 0) $ send context $ trigger $ Event { eMetaKey = False, ePageXY = return(fromIntegral (100 + length balls * 45),20), eType = "mousedown", eWhich = Nothing}+ wiki $ when (n `mod` 3 == 0) $ snapShot context $ file+ wiki $ when (n >= 400) $ do { build_anim "Bounce" 7; close context }++ es <- flush context+ if (null es) then return () else print es++ let newBalls = [ ((x,y),0,head cols) + | Just (x,y) <- map ePageXY es+ ]++ loop (map bounce $ map moveBall $ balls ++ newBalls, tail cols)+++ loop ([((100,100),0,"blue")],cycle ["red","blue","green","orange","cyan"])
+ wiki-suite/Circle.hs view
@@ -0,0 +1,24 @@+{-# LANGUAGE OverloadedStrings #-}+module Circle where++import Graphics.Blank+import Wiki -- (578,200)++main :: IO ()+main = blankCanvas 3000 $ \ context -> do+ send context $ do+ let centerX = width context / 2+ let centerY = height context / 2+ let radius = 70++ beginPath()+ arc(centerX, centerY, radius, 0, 2 * pi, False)+ fillStyle "#8ED6FF"+ fill()+ lineWidth 5+ strokeStyle "black"+ stroke()+++ wiki $ snapShot context "images/Circle.png"+ wiki $ close context
+ wiki-suite/Clipping_Region.hs view
@@ -0,0 +1,55 @@+{-# LANGUAGE OverloadedStrings #-}+module Clipping_Region where++import Graphics.Blank+import Wiki -- (578,200)++main :: IO ()+main = blankCanvas 3000 $ \ context -> do+ send context $ do+ let x = width context / 2;+ let y = height context / 2;+ let radius = 75;+ let offset = 50;++ {-+ * save() allows us to save the canvas context before+ * defining the clipping region so that we can return+ * to the default state later on+ -}+ save();+ beginPath();+ arc(x, y, radius, 0, 2 * pi, False);+ clip();++ -- draw blue circle inside clipping region+ beginPath();+ arc(x - offset, y - offset, radius, 0, 2 * pi, False);+ fillStyle "blue";+ fill();++ -- draw yellow circle inside clipping region+ beginPath();+ arc(x + offset, y, radius, 0, 2 * pi, False);+ fillStyle "yellow";+ fill();++ -- draw red circle inside clipping region+ beginPath();+ arc(x, y + offset, radius, 0, 2 * pi, False);+ fillStyle "red";+ fill();++ {-+ * restore() restores the canvas context to its original state+ * before we defined the clipping region+ -}+ restore();+ beginPath();+ arc(x, y, radius, 0, 2 * pi, False);+ lineWidth 10;+ strokeStyle "blue";+ stroke();++ wiki $ snapShot context "images/Clipping_Region.png"+ wiki $ close context
+ wiki-suite/Color_Fill.hs view
@@ -0,0 +1,28 @@+{-# LANGUAGE OverloadedStrings #-}+module Color_Fill where++import Graphics.Blank+import Wiki -- (578,200)++main :: IO ()+main = blankCanvas 3000 $ \ context -> do+ send context $ do+ beginPath();+ moveTo(170, 80);+ bezierCurveTo(130, 100, 130, 150, 230, 150);+ bezierCurveTo(250, 180, 320, 180, 340, 150);+ bezierCurveTo(420, 150, 420, 120, 390, 100);+ bezierCurveTo(430, 40, 370, 30, 340, 50);+ bezierCurveTo(320, 5, 250, 20, 250, 50);+ bezierCurveTo(200, 5, 150, 20, 170, 80);++ -- complete custom shape+ closePath();+ lineWidth 5;+ fillStyle "#8ED6FF";+ fill();+ strokeStyle "blue";+ stroke();++ wiki $ snapShot context "images/Color_Fill.png"+ wiki $ close context
+ wiki-suite/Color_Square.hs view
@@ -0,0 +1,22 @@+{-# LANGUAGE OverloadedStrings #-}+module Color_Square where++import qualified Data.Vector.Unboxed as V+import Graphics.Blank+import Wiki -- (300,300)++main :: IO ()+main = blankCanvas 3000 $ \ context -> do+ let v = V.fromList+ $ concat+ $ [ [r,g,0,255]+ | r <- [0..255]+ , g <- [0..255]+ ]++ send context $ do+ putImageData (ImageData 256 256 v, [22,22])++ wiki $ snapShot context "images/Color_Square.png"+ wiki $ close context+
+ wiki-suite/Custom_Shape.hs view
@@ -0,0 +1,26 @@+{-# LANGUAGE OverloadedStrings #-}+module Custom_Shape where++import Graphics.Blank+import Wiki -- (578,200)++main :: IO ()+main = blankCanvas 3000 $ \ context -> do+ send context $ do+ beginPath();+ moveTo(170, 80);+ bezierCurveTo(130, 100, 130, 150, 230, 150);+ bezierCurveTo(250, 180, 320, 180, 340, 150);+ bezierCurveTo(420, 150, 420, 120, 390, 100);+ bezierCurveTo(430, 40, 370, 30, 340, 50);+ bezierCurveTo(320, 5, 250, 20, 250, 50);+ bezierCurveTo(200, 5, 150, 20, 170, 80);+ -- complete custom shape+ closePath();+ lineWidth 5;+ strokeStyle "blue";+ stroke();+++ wiki $ snapShot context "images/Custom_Shape.png"+ wiki $ close context
+ wiki-suite/Custom_Transform.hs view
@@ -0,0 +1,28 @@+{-# LANGUAGE OverloadedStrings #-}+module Custom_Transform where++import Graphics.Blank+import Wiki -- (578,200)++main :: IO ()+main = blankCanvas 3000 $ \ context -> do+ send context $ do+ let rectWidth = 150;+ let rectHeight = 75;++ -- translation matrix:+ -- 1 0 tx+ -- 0 1 ty+ -- 0 0 1+ let tx = width context / 2;+ let ty = height context / 2;++ -- apply custom transform+ transform(1, 0, 0, 1, tx, ty);++ fillStyle "blue";+ fillRect(rectWidth / (-2), rectHeight / (-2), rectWidth, rectHeight);+++ wiki $ snapShot context "images/Custom_Transform.png"+ wiki $ close context
+ wiki-suite/Draw_Canvas.hs view
@@ -0,0 +1,20 @@+{-# LANGUAGE OverloadedStrings #-}+module Draw_Canvas where++import Graphics.Blank -- import the blank canvas+import Wiki -- (600,200)++main :: IO ()+main = blankCanvas 3000 $ \ context -> do -- start blank canvas on port 3000+ send context $ do -- send commands to this specific context+ moveTo(50,50)+ lineTo(200,100)+ lineWidth 10+ strokeStyle "red"+ stroke() -- this draws the ink into the canvas++ me <- myCanvasContext+ drawImage(me,[50,50])++ wiki $ snapShot context "images/Draw_Canvas.png"+ wiki $ close context
+ wiki-suite/Draw_Device.hs view
@@ -0,0 +1,19 @@+{-# LANGUAGE OverloadedStrings #-}+module Draw_Device where++import Graphics.Blank -- import the blank canvas+import Wiki -- (600,200)++main :: IO ()+main = blankCanvas 3000 $ \ context -> do -- start blank canvas on port 3000+ send context $ do -- send commands to this specific context+ moveTo(50,50)+ lineTo(200,100)+ lineWidth 10+ strokeStyle "red"+ stroke() -- this draws the ink into the canvas++ drawImage(context,[50,50])++ wiki $ snapShot context "images/Draw_Device.png"+ wiki $ close context
+ wiki-suite/Draw_Image.hs view
@@ -0,0 +1,16 @@+{-# LANGUAGE OverloadedStrings #-}+module Draw_Image where++import Graphics.Blank+import Wiki -- (578,200)++main :: IO ()+main = do+ blankCanvas 3000 $ \ context -> do+ send context $ do+ img <- newImage "images/Haskell.jpg"+ drawImage(img,[69,50])+++ wiki $ snapShot context "images/Draw_Image.png"+ wiki $ close context
+ wiki-suite/Favicon.hs view
@@ -0,0 +1,37 @@+{-# LANGUAGE OverloadedStrings #-}+module Favicon where++import Graphics.Blank -- import the blank canvas+import Wiki -- (32,32)++main :: IO ()+main = blankCanvas 3000 $ \ context -> do -- start blank canvas on port 3000+ send context $ do -- send commands to this specific context++ beginPath()+ let relPath x xs = do+ beginPath()+ moveTo x+ sequence_ [ lineTo p | ss <- drop 2 $ scanl (flip (:)) [] (x:xs)+ , let p = (sum (map fst ss), sum (map snd ss))+ ]+ closePath()+ + translate(height context * 0.07,height context * 0.8)+ scale (height context / 20,-height context / 20)++ let shape x xs col = do+ relPath x xs+ lineWidth 0.5+ fillStyle col+ lineCap "round"+ fill() ++ shape (0,0) [(4,6),(-4,6),(3,0),(4,-6),(-4,-6)] "#E8000D" + shape (8,6) [(-4,6),(3,0),(8,-12),(-3,0),(-2.5,3.75),(-2.5,-3.75),(-3,0)] "#0022B4"+ shape (17,3.5) [(-3.5,0),(-8/6,12/6),(3.5+8/6,0)] "#FFC82D" + shape (17,6.5) [(-5.5,0),(-8/6,12/6),(5.5+8/6,0)] "#FFC82D" ++ -- wiki $ txt <- send context $ toDataURL() ; print txt+ wiki $ snapShot context "images/Favicon.png"+ wiki $ close context
+ wiki-suite/Font_Size_and_Style.hs view
@@ -0,0 +1,15 @@+{-# LANGUAGE OverloadedStrings #-}+module Font_Size_and_Style where++import Graphics.Blank+import Wiki -- (578,200)++main :: IO ()+main = blankCanvas 3000 $ \ context -> do+ send context $ do+ font "40pt Calibri"+ fillText("Hello World!", 150, 100)+++ wiki $ snapShot context "images/Font_Size_and_Style.png"+ wiki $ close context
+ wiki-suite/Get_Image_Data_URL.hs view
@@ -0,0 +1,31 @@+{-# LANGUAGE OverloadedStrings #-}+module Get_Image_Data_URL where++import qualified Data.Text as Text+import Graphics.Blank+import Wiki -- (578,350)++main :: IO ()+main = blankCanvas 3000 $ \ context -> do+ url <- send context $ do+ beginPath();+ moveTo(170, 80);+ bezierCurveTo(130, 100, 130, 150, 230, 150);+ bezierCurveTo(250, 180, 320, 180, 340, 150);+ bezierCurveTo(420, 150, 420, 120, 390, 100);+ bezierCurveTo(430, 40, 370, 30, 340, 50);+ bezierCurveTo(320, 5, 250, 20, 250, 50);+ bezierCurveTo(200, 5, 150, 20, 170, 80);+ -- complete custom shape+ closePath();+ lineWidth 5;+ strokeStyle "blue";+ stroke();+ toDataURL();++ send context $ do+ font "18pt Calibri"+ fillText(Text.pack $ show $ Text.take 50 $ url, 10, 300)++ wiki $ snapShot context "images/Get_Image_Data_URL.png"+ wiki $ close context
+ wiki-suite/Global_Alpha.hs view
@@ -0,0 +1,25 @@+{-# LANGUAGE OverloadedStrings #-}+module Global_Alpha where++import Graphics.Blank+import Wiki -- (578,200)++main :: IO ()+main = blankCanvas 3000 $ \ context -> do+ send context $ do+ -- draw blue rectangle+ beginPath();+ rect(200, 20, 100, 100);+ fillStyle "blue";+ fill();++ -- draw transparent red circle+ globalAlpha 0.5;+ beginPath();+ arc(320, 120, 60, 0, 2 * pi, False);+ fillStyle "red";+ fill();+++ wiki $ snapShot context "images/Global_Alpha.png"+ wiki $ close context
+ wiki-suite/Global_Composite_Operations.hs view
@@ -0,0 +1,70 @@+{-# LANGUAGE OverloadedStrings #-}+module Global_Composite_Operations where++import qualified Data.Text as Text+import Graphics.Blank+import Wiki -- (578,430)++main :: IO ()+main = blankCanvas 3000 $ \ context -> do+ send context $ do++ tempCanvas <- newCanvas (round (width context :: Double),+ round (height context :: Double))+ console_log tempCanvas+ (w,h) <- return (round (width context :: Double) :: Int,+ round (height context :: Double) :: Int)+ console_log $ Text.pack $ show $ (w,h)+ + let squareWidth = 55;+ let circleRadius = 35;+ let shapeOffset = 50;++ let compss = + [["source-atop", "source-in", "source-out", "source-over"]+ ,["destination-atop","destination-in","destination-out","destination-over"]+ ,["lighter","darker","xor","copy"]+ ]++ -- translate context to add 10px padding+ translate(10, 10);+++ sequence_ [+ do++ -- clear temp context+ with tempCanvas $ do+ save();++ clearRect(0, 0, width context, height context);+ -- draw rectangle (destination)+ beginPath();+ rect(0, 0, squareWidth, squareWidth);+ fillStyle "blue";+ fill();++ -- set global composite+ globalCompositeOperation thisOperation;++ -- draw circle (source)+ beginPath();+ arc(shapeOffset, shapeOffset, circleRadius, 0, 2 * pi, False);+ fillStyle "red";+ fill();++ restore();++ font "10pt Verdana";+ fillStyle "black";+ fillText(thisOperation, 0, squareWidth + 45);++ drawImage(tempCanvas, [x * 125, y * 125]);+++ | (comps,y) <- compss `zip` [0..]+ , (thisOperation,x) <- comps `zip` [0..]+ ]++ wiki $ snapShot context "images/Global_Composite_Operations.png"+ wiki $ close context
+ wiki-suite/Grayscale.hs view
@@ -0,0 +1,33 @@+{-# LANGUAGE OverloadedStrings #-}+module Grayscale where++import qualified Data.Vector.Unboxed as V+import Graphics.Blank+import Wiki -- (578,400)++main :: IO ()+main = do+ blankCanvas 3000 $ \ context -> do+ ImageData w h v <- send context $ do+ img <- newImage "images/House.jpg"+ drawImage(img,[50,50])+ getImageData(50,50, width img, height img)+ + let group4 (a:b:c:d:xs) = (a,b,c,d) : group4 xs+ group4 _ = []+ + let v' = V.fromList+ $ concat+ $ [ let brightness = round+ ( 0.34 * fromIntegral r + + 0.5 * fromIntegral g+ + 0.16 * fromIntegral b :: Double)+ in [brightness,brightness,brightness,a] + | (r,g,b,a) <- group4 $ V.toList $ v+ ]+ + send context $ do+ putImageData (ImageData w h v', [100,100])+ + wiki $ snapShot context "images/Grayscale.png"+ wiki $ close context
+ wiki-suite/Image_Crop.hs view
@@ -0,0 +1,16 @@+{-# LANGUAGE OverloadedStrings #-}+module Image_Crop where++import Graphics.Blank+import Wiki -- (578,200)++main :: IO ()+main = do+ blankCanvas 3000 $ \ context -> do+ send context $ do+ img <- newImage "images/Haskell.jpg"+ drawImage(img,[150,0,150,150,50,50,150,200])+ + + wiki $ snapShot context "images/Image_Crop.png"+ wiki $ close context
+ wiki-suite/Image_Loader.hs view
@@ -0,0 +1,18 @@+{-# LANGUAGE OverloadedStrings #-}+module Image_Loader where++import Graphics.Blank+import Wiki -- (578,400)++main :: IO ()+main = do+ blankCanvas 3000 $ \ context -> do+ send context $ do+ img1 <- newImage "images/Haskell.jpg"+ img2 <- newImage "images/House.jpg"+ drawImage(img1,[69,50,97,129])+ drawImage(img2,[200,50])+ + + wiki $ snapShot context "images/Image_Loader.png"+ wiki $ close context
+ wiki-suite/Image_Size.hs view
@@ -0,0 +1,16 @@+{-# LANGUAGE OverloadedStrings #-}+module Image_Size where++import Graphics.Blank+import Wiki -- (578,200)++main :: IO ()+main = do+ blankCanvas 3000 $ \ context -> do+ send context $ do+ img <- newImage "images/Haskell.jpg"+ drawImage(img,[69,50,97,129])+ + + wiki $ snapShot context "images/Image_Size.png"+ wiki $ close context
+ wiki-suite/Is_Point_In_Path.hs view
@@ -0,0 +1,29 @@+{-# LANGUAGE OverloadedStrings #-}+module Is_Point_In_Path where++import Graphics.Blank+import Wiki -- (400,400)++main :: IO ()+main = blankCanvas 3000 $ \ context -> do++ send context $ do+ strokeStyle "blue";+ beginPath();+ rect(100,100,200,200)+ cmds <- sequence [ do+ b <- isPointInPath (x,y)+ return $ do+ beginPath()+ fillStyle $ if b then "red" else "green"+ arc(x, y, 5, 0, pi*2, False)+ fill()+ | x <- take 8 [25,25+50..]+ , y <- take 8 [25,25+50..]+ ]+ stroke()+ -- Now draw the points+ sequence_ cmds++ wiki $ snapShot context "images/Is_Point_In_Path.png"+ wiki $ close context
+ wiki-suite/Key_Read.hs view
@@ -0,0 +1,52 @@+{-# LANGUAGE OverloadedStrings #-}+module Key_Read where++import Data.List (nub)+import Data.Monoid ((<>))+import qualified Data.Text as Text+-- import Debug.Trace+import Graphics.Blank+import Wiki -- (512,200)++data State = State+ { keys :: [Int] -- key *codes* for pressed keys+ , step :: Int+ }+ deriving Show++main :: IO ()+main = blankCanvas 3000 { events = ["keyup","keydown"] } $ \ context -> loop context (State [] 0)++loop :: DeviceContext -> State -> IO ()+loop context state = do+ send context $ do+ clearRect (0,0,width context,height context)+ lineWidth 1+ strokeStyle "red"+ font "30pt Calibri"+ fillText("Keys currently pressed: " <> Text.pack (show (keys state)),10,50)+ fillText("Counter: " <> Text.pack (show (step state)),10,150)++ control context state++control :: DeviceContext -> State -> IO ()+control context state = do+ wiki $ counter (\ _ -> True) $ \ n -> do+ file <- wiki $ anim_png "Key_Read"+ wiki $ snapShot context $ file+ wiki $ whenM (n == 1) $ ev context "keydown" 90+ wiki $ whenM (n == 2) $ ev context "keydown" 88+ wiki $ whenM (n == 3) $ ev context "keyup" 88+ wiki $ whenM (n == 4) $ ev context "keyup" 90+ wiki $ whenM (n == 5) $ do { build_anim "Key_Read" 100; close context }+ event <- wait context+ print event+ let down_keys = case (eType event,eWhich event) of+ ("keydown",Just c) -> [c]+ _ -> []+ let up_keys = case (eType event,eWhich event) of+ ("keyup",Just c) -> [c]+ _ -> []+ let current_keys = [ k | k <- nub (keys state ++ down_keys), not (k `elem` up_keys) ]+ let state' = state { step = step state + 1, keys = current_keys }+ loop context state'
+ wiki-suite/Line.hs view
@@ -0,0 +1,17 @@+{-# LANGUAGE OverloadedStrings #-}+module Line where++import Graphics.Blank+import Wiki -- (578,200)++main :: IO ()+main = blankCanvas 3000 $ \ context -> do+ send context $ do+ beginPath()+ moveTo(100,150)+ lineTo(450,50)+ stroke()+++ wiki $ snapShot context "images/Line.png"+ wiki $ close context
+ wiki-suite/Line_Cap.hs view
@@ -0,0 +1,23 @@+{-# LANGUAGE OverloadedStrings #-}+module Line_Cap where++import Graphics.Blank+import Wiki -- (578,200)++main :: IO ()+main = blankCanvas 3000 $ \ context -> do+ send context $ do+ sequence_+ [ do beginPath()+ moveTo(200, height context / 2 + n)+ lineTo(width context - 200, height context / 2 + n)+ lineWidth 20+ strokeStyle "#0000ff"+ lineCap cap+ stroke()+ | (cap,n) <- zip ["butt","round","square"] [-50,0,50]+ ]+++ wiki $ snapShot context "images/Line_Cap.png"+ wiki $ close context
+ wiki-suite/Line_Color.hs view
@@ -0,0 +1,18 @@+{-# LANGUAGE OverloadedStrings #-}+module Line_Color where++import Graphics.Blank+import Wiki -- (578,200)++main :: IO ()+main = blankCanvas 3000 $ \ context -> do+ send context $ do+ moveTo(100,150)+ lineTo(450,50)+ lineWidth 5+ strokeStyle "#ff0000"+ stroke()+++ wiki $ snapShot context "images/Line_Color.png"+ wiki $ close context
+ wiki-suite/Line_Join.hs view
@@ -0,0 +1,39 @@+{-# LANGUAGE OverloadedStrings #-}+module Line_Join where++import Graphics.Blank+import Wiki -- (578,200)++main :: IO ()+main = blankCanvas 3000 $ \ context -> do+ send context $ do+ + lineWidth 25;++ -- miter line join (left)+ beginPath();+ moveTo(99, 150);+ lineTo(149, 50);+ lineTo(199, 150);+ lineJoin "miter";+ stroke();++ -- round line join (middle)+ beginPath();+ moveTo(239, 150);+ lineTo(289, 50);+ lineTo(339, 150);+ lineJoin "round";+ stroke();++ -- bevel line join (right)+ beginPath();+ moveTo(379, 150);+ lineTo(429, 50);+ lineTo(479, 150);+ lineJoin "bevel";+ stroke();+ ++ wiki $ snapShot context "images/Line_Join.png"+ wiki $ close context
+ wiki-suite/Line_Width.hs view
@@ -0,0 +1,17 @@+{-# LANGUAGE OverloadedStrings #-}+module Line_Width where++import Graphics.Blank+import Wiki -- (578,200)++main :: IO ()+main = blankCanvas 3000 $ \ context -> do+ send context $ do+ moveTo(100,150)+ lineTo(450,50)+ lineWidth 15+ stroke()+++ wiki $ snapShot context "images/Line_Width.png"+ wiki $ close context
+ wiki-suite/Linear_Gradient.hs view
@@ -0,0 +1,22 @@+{-# LANGUAGE OverloadedStrings #-}+module Linear_Gradient where++import Graphics.Blank+import qualified Graphics.Blank.Style as Style+import Wiki -- (578,200)++main :: IO ()+main = blankCanvas 3000 $ \ context -> do+ send context $ do + rect(0, 0, width context, height context)+ grd <- createLinearGradient(0, 0, width context, height context)+ -- light blue+ grd # addColorStop(0, "#8ED6FF")+ -- dark blue+ grd # addColorStop(1, "#004CB3")+ Style.fillStyle grd;+ fill();+++ wiki $ snapShot context "images/Linear_Gradient.png"+ wiki $ close context
+ wiki-suite/Load_Image_Data_URL.hs view
@@ -0,0 +1,17 @@+{-# LANGUAGE OverloadedStrings #-}+module Load_Image_Data_URL where++import qualified Data.Text.IO as Text.IO+import Graphics.Blank+import Wiki -- (578,200)++main :: IO ()+main = do+ blankCanvas 3000 $ \ context -> do+ url <- Text.IO.readFile "data/dataURL.txt"+ send context $ do+ img <- newImage url+ drawImage (img,[0,0])+ + wiki $ snapShot context "images/Load_Image_Data_URL.png"+ wiki $ close context
+ wiki-suite/Load_Image_Data_URL_2.hs view
@@ -0,0 +1,15 @@+{-# LANGUAGE OverloadedStrings #-}+module Load_Image_Data_URL_2 where++import Graphics.Blank+import Wiki -- (578,200)++main :: IO ()+main = blankCanvas 3000 $ \ context -> do+ url <- readDataURL "image/jpeg" "images/Haskell.jpg"+ send context $ do+ img <- newImage url+ drawImage (img,[0,0])++ wiki $ snapShot context "images/Load_Image_Data_URL_2.png"+ wiki $ close context
+ wiki-suite/Main.hs view
@@ -0,0 +1,298 @@+-- Shake Generator for wiki pages+{-# LANGUAGE CPP #-}+module Main where++import Prelude hiding ((*>))++import Control.Concurrent++import Data.Char+import Data.List++import qualified Development.Shake as Shake+import Development.Shake hiding (doesFileExist)+import Development.Shake.FilePath++import System.Directory+import System.Environment+import System.IO+import System.Process++-- TO test: ghci wiki-suite/Draw_Canvas.hs -idist/build/autogen/:.:wiki-suite+import qualified Arc+import qualified Bezier_Curve+import qualified Bounce+import qualified Circle+import qualified Clipping_Region+import qualified Color_Fill+import qualified Color_Square+import qualified Custom_Shape+import qualified Draw_Canvas+import qualified Draw_Device+import qualified Draw_Image+import qualified Favicon+import qualified Font_Size_and_Style+import qualified Get_Image_Data_URL+import qualified Global_Alpha+import qualified Global_Composite_Operations+import qualified Grayscale+import qualified Image_Crop+import qualified Image_Loader+import qualified Image_Size+import qualified Is_Point_In_Path+import qualified Key_Read+import qualified Line+import qualified Line_Cap+import qualified Line_Color+import qualified Line_Join+import qualified Line_Width+import qualified Linear_Gradient+import qualified Load_Image_Data_URL+import qualified Load_Image_Data_URL_2+import qualified Miter_Limit+import qualified Path+import qualified Pattern+import qualified Quadratic_Curve+import qualified Radial_Gradient+import qualified Rectangle+import qualified Red_Line+import qualified Rotating_Square+import qualified Rounded_Corners+import qualified Semicircle+import qualified Shadow+import qualified Square+import qualified Text_Align+import qualified Text_Baseline+import qualified Text_Color+import qualified Text_Metrics+import qualified Text_Stroke+import qualified Text_Wrap+import qualified Tic_Tac_Toe+import qualified Translate_Transform+import qualified Scale_Transform+import qualified Rotate_Transform+import qualified Custom_Transform++import System.Environment++main :: IO ()+main = do + args <- getArgs + main2 args++main2 :: [String] -> IO ()+main2 ["Arc"] = Arc.main+main2 ["Bezier_Curve"] = Bezier_Curve.main+main2 ["Bounce"] = Bounce.main+main2 ["Circle"] = Circle.main+main2 ["Clipping_Region"] = Clipping_Region.main+main2 ["Color_Fill"] = Color_Fill.main+main2 ["Color_Square"] = Color_Square.main+main2 ["Custom_Shape"] = Custom_Shape.main+main2 ["Draw_Canvas"] = Draw_Canvas.main+main2 ["Draw_Device"] = Draw_Device.main+main2 ["Draw_Image"] = Draw_Image.main+main2 ["Favicon"] = Favicon.main+main2 ["Font_Size_and_Style"] = Font_Size_and_Style.main+main2 ["Get_Image_Data_URL"] = Get_Image_Data_URL.main+main2 ["Global_Alpha"] = Global_Alpha.main+main2 ["Global_Composite_Operations"] = Global_Composite_Operations.main+main2 ["Grayscale"] = Grayscale.main+main2 ["Image_Crop"] = Image_Crop.main+main2 ["Image_Loader"] = Image_Loader.main+main2 ["Miter_Limit"] = Miter_Limit.main+main2 ["Image_Size"] = Image_Size.main+main2 ["Is_Point_In_Path"] = Is_Point_In_Path.main+main2 ["Key_Read"] = Key_Read.main+main2 ["Line"] = Line.main+main2 ["Line_Cap"] = Line_Cap.main+main2 ["Line_Color"] = Line_Color.main+main2 ["Line_Join"] = Line_Join.main+main2 ["Line_Width"] = Line_Width.main+main2 ["Linear_Gradient"] = Linear_Gradient.main+main2 ["Load_Image_Data_URL"] = Load_Image_Data_URL.main+main2 ["Load_Image_Data_URL_2"] = Load_Image_Data_URL_2.main+main2 ["Path"] = Path.main+main2 ["Pattern"] = Pattern.main+main2 ["Quadratic_Curve"] = Quadratic_Curve.main+main2 ["Radial_Gradient"] = Radial_Gradient.main+main2 ["Rectangle"] = Rectangle.main+main2 ["Red_Line"] = Red_Line.main+main2 ["Rotating_Square"] = Rotating_Square.main+main2 ["Rounded_Corners"] = Rounded_Corners.main+main2 ["Semicircle"] = Semicircle.main+main2 ["Shadow"] = Shadow.main+main2 ["Square"] = Square.main+main2 ["Text_Align"] = Text_Align.main+main2 ["Text_Baseline"] = Text_Baseline.main+main2 ["Text_Color"] = Text_Color.main+main2 ["Text_Metrics"] = Text_Metrics.main+main2 ["Text_Stroke"] = Text_Stroke.main+main2 ["Text_Wrap"] = Text_Wrap.main+main2 ["Tic_Tac_Toe"] = Tic_Tac_Toe.main+main2 ["Translate_Transform"] = Translate_Transform.main+main2 ["Scale_Transform"] = Scale_Transform.main+main2 ["Rotate_Transform"] = Rotate_Transform.main+main2 ["Custom_Transform"] = Custom_Transform.main++main2 ["clean"] = do + _ <- createProcess $ shell "rm blank-canvas.wiki/images/*.png blank-canvas.wiki/images/*.gif blank-canvas.wiki/examples/*.hs"+ return ()+ +main2 args = shakeArgs shakeOptions $ do++ if null args then do+ want ["blank-canvas.wiki/images/" ++ nm ++ ".gif" | nm <- movies ]+ want ["blank-canvas.wiki/images/" ++ nm ++ ".png" | nm <- examples ++ tutorial]+ want ["blank-canvas.wiki/examples/" ++ nm ++ ".hs" | nm <- movies ++ examples ++ tutorial]+ want ["blank-canvas.wiki/" ++ toMinus nm ++ ".md" | nm <- movies ++ examples ++ tutorial]+ else return ()++ ["blank-canvas.wiki/images/*.png", "blank-canvas.wiki/images/*.gif"] |*> \out -> do+ let nm = takeBaseName out++ liftIO $ print (out,nm)++ let tmp = "tmp"++ liftIO $ createDirectoryIfMissing False tmp+ liftIO $ removeFiles tmp [nm ++ "*.png"]+ liftIO $ createDirectoryIfMissing False tmp++ need [ "blank-canvas.wiki/" ++ toMinus nm ++ ".md" ]+ let haskell_file = nm ++ ".hs"+ need [ "wiki-suite/" ++ haskell_file, "blank-canvas.wiki/examples/" ++ haskell_file ] + liftIO $ print nm++ txt <- readFile' $ "wiki-suite/" ++ haskell_file++ let (w,h) = head $+ [ case words ln of+ [_,_,_,n] -> read n+ _ -> (512,384)+ | ln <- lines txt + , "import" `isPrefixOf` ln && "Wiki" `isInfixOf` ln+ ] ++ [(512,384) :: (Int, Int)]+++ sequence_ [+ do (_,_,_,ghc) <- liftIO $ + createProcess (proc "./dist/build/wiki-suite/wiki-suite" [nm])++ -- wait a second, for things to start+ liftIO $ threadDelay (1 * 1000 * 1000)+ +#if defined(darwin_HOST_OS)+ command_ [] "/usr/bin/open" + ["-a"+ ,"/Applications/Google Chrome.app"+ ,"http://localhost:3000/?height=" ++ show (h) ++ "&width=" ++ show (w) ++ hd]+#else+ -- TODO: Figure out what Windows/MinTTY uses+ command_ [] "/usr/bin/xdg-open" ["http://localhost:3000/?height=" ++ show (h) ++ "&width=" ++ show (w) ++ hd]+#endif+ -- wait for haskell program to stop+ liftIO $ waitForProcess ghc | hd <- [("")] ++ if nm == "Text_Wrap" then [("&hd")] else [] ]+ return ()+++ "blank-canvas.wiki/examples/*.hs" *> \ out -> do+ liftIO $ print out+ let haskell_file = takeFileName out++ txt <- readFile' $ "./wiki-suite/" ++ haskell_file++ let new = reverse+ $ dropWhile (all isSpace)+ $ reverse+ [ if "module" `isPrefixOf` ln + then "module Main where"+ else ln+ | ln <- lines txt + , not ("wiki $" `isInfixOf` ln) -- remove the wiki stuff+ , not ("import" `isPrefixOf` ln && "Wiki" `isInfixOf` ln)+ ]++ writeFileChanged out (unlines $ map (untabify 0) new)++ "blank-canvas.wiki/*.md" *> \ out -> do+ b <- Shake.doesFileExist out+-- liftIO $ print b+ txts <- liftIO $ if b then do+ h <- openFile out ReadMode+ let loop = do+ b' <- hIsEOF h + if b'+ then return []+ else do+ ln <- hGetLine h+ lns <- loop+ return (ln : lns)+ txts <- loop + hClose h+ return txts+ else return [] +-- liftIO $ print txts++ let p = not . (code_header `isPrefixOf`)+ let textToKeep = takeWhile p txts++ let haskell_file = map (\ c -> if c == '-' then '_' else c) + $ replaceExtension (takeFileName out) ".hs"+++ liftIO $ print haskell_file+ txt <- readFile' $ "blank-canvas.wiki/examples/" ++ haskell_file ++ let new = unlines $+ [ t | t <- textToKeep + ] +++ [code_header] +++ lines txt +++ [code_footer]++-- liftIO $ putStrLn new++ writeFileChanged out new+++-- to clean: rm images/*png images/*gif examples/*hs+-- */++movies :: [String]+movies = ["Rotating_Square","Tic_Tac_Toe","Bounce","Key_Read","Square"]++examples :: [String]+examples = ["Red_Line","Favicon"]+ ++ ["Color_Square"] ++tutorial :: [String]+tutorial = ["Line", "Line_Width", "Line_Color", "Line_Cap","Miter_Limit"]+ ++ ["Arc","Quadratic_Curve","Bezier_Curve"]+ ++ ["Path","Line_Join","Rounded_Corners","Is_Point_In_Path"]+ ++ ["Custom_Shape","Rectangle","Circle","Semicircle"]+ ++ ["Color_Fill","Linear_Gradient","Radial_Gradient","Pattern"]+ ++ ["Draw_Image","Image_Size","Image_Crop","Image_Loader", "Draw_Canvas", "Draw_Device"]+ ++ ["Font_Size_and_Style","Text_Color","Text_Stroke","Text_Align","Text_Baseline","Text_Metrics","Text_Wrap"]+ ++ ["Translate_Transform","Scale_Transform","Rotate_Transform","Custom_Transform"]+ ++ ["Shadow","Global_Alpha","Clipping_Region","Global_Composite_Operations"]+ ++ ["Grayscale","Get_Image_Data_URL","Load_Image_Data_URL"]+ ++ ["Load_Image_Data_URL_2"]++wiki_dir :: String+wiki_dir = "."++toMinus :: String -> String+toMinus = map (\ c -> if c == '_' then '-' else c) +++untabify :: Int -> String -> String+untabify _ [] = []+untabify n (c:cs) | c == '\t' = let t = 8 - n `mod` 8 in take t (cycle " ") ++ untabify (n + t) cs+ | otherwise = c : untabify (n + 1) cs ++code_header :: String+code_header = "````Haskell"++code_footer :: String+code_footer = "````"
+ wiki-suite/Miter_Limit.hs view
@@ -0,0 +1,32 @@+{-# LANGUAGE OverloadedStrings #-}+module Miter_Limit where++import Graphics.Blank+import Wiki -- (578,200)++main :: IO ()+main = blankCanvas 3000 $ \ context -> do+ send context $ do+ clearRect(0,0,150,150);+ -- Draw guides+ strokeStyle "#09f";+ lineWidth 2;+ strokeRect(-5,50,160,50);+ + -- Set line styles+ strokeStyle "#000";+ lineWidth 10;+ + -- check input+ miterLimit 5;++ -- Draw lines+ beginPath()+ moveTo(0,100)+ sequence_ [ lineTo((fromIntegral i ** 1.5)*2,75+(if i `mod` 2 == 0 then 25 else -25))+ | i <- [0..20] :: [Int]+ ]+ stroke();++ wiki $ snapShot context "images/Miter_Limit.png"+ wiki $ close context
+ wiki-suite/Path.hs view
@@ -0,0 +1,27 @@+{-# LANGUAGE OverloadedStrings #-}+module Path where++import Graphics.Blank+import Wiki -- (578,200)++main :: IO ()+main = blankCanvas 3000 $ \ context -> do+ send context $ do+ beginPath()+ moveTo(100, 20)+ -- line 1+ lineTo(200, 160)+ -- quadratic curve+ quadraticCurveTo(230, 200, 250, 120)+ -- bezier curve+ bezierCurveTo(290, -40, 300, 200, 400, 150)+ -- line 2+ lineTo(500, 90)+ lineWidth 5+ strokeStyle "blue"+ stroke()++++ wiki $ snapShot context "images/Path.png"+ wiki $ close context
+ wiki-suite/Pattern.hs view
@@ -0,0 +1,19 @@+{-# LANGUAGE OverloadedStrings #-}+module Pattern where++import Graphics.Blank+import qualified Graphics.Blank.Style as Style+import Wiki -- (578,200)++main :: IO ()+main = blankCanvas 3000 $ \ context -> do+ send context $ do+ imageObj <- newImage "images/Haskell.jpg"+ pattern <- createPattern (imageObj,"repeat")+ rect(0, 0, width context, height context);+ Style.fillStyle pattern;+ fill();+++ wiki $ snapShot context "images/Pattern.png"+ wiki $ close context
+ wiki-suite/Quadratic_Curve.hs view
@@ -0,0 +1,20 @@+{-# LANGUAGE OverloadedStrings #-}+module Quadratic_Curve where++import Graphics.Blank+import Wiki -- (578,200)++main :: IO ()+main = blankCanvas 3000 $ \ context -> do+ send context $ do+ beginPath()+ moveTo(188, 150)+ quadraticCurveTo(288, 0, 388, 150)+ lineWidth 10+ -- line color+ strokeStyle "black"+ stroke()+++ wiki $ snapShot context "images/Quadratic_Curve.png"+ wiki $ close context
+ wiki-suite/Radial_Gradient.hs view
@@ -0,0 +1,22 @@+{-# LANGUAGE OverloadedStrings #-}+module Radial_Gradient where++import Graphics.Blank+import qualified Graphics.Blank.Style as Style+import Wiki -- (578,200)++main :: IO ()+main = blankCanvas 3000 $ \ context -> do+ send context $ do+ rect(0, 0, width context, height context)+ grd <- createRadialGradient (238, 50, 10, 238, 50, 300)+ -- light blue+ grd # addColorStop(0, "#8ED6FF")+ -- dark blue+ grd # addColorStop(1, "#004CB3")+ Style.fillStyle grd;+ fill();+++ wiki $ snapShot context "images/Radial_Gradient.png"+ wiki $ close context
+ wiki-suite/Rectangle.hs view
@@ -0,0 +1,20 @@+{-# LANGUAGE OverloadedStrings #-}+module Rectangle where++import Graphics.Blank+import Wiki -- (578,200)++main :: IO ()+main = blankCanvas 3000 $ \ context -> do+ send context $ do+ beginPath();+ rect(188, 50, 200, 100);+ fillStyle "yellow";+ fill();+ lineWidth 7;+ strokeStyle "black";+ stroke();+ ++ wiki $ snapShot context "images/Rectangle.png"+ wiki $ close context
+ wiki-suite/Red_Line.hs view
@@ -0,0 +1,17 @@+{-# LANGUAGE OverloadedStrings #-}+module Red_Line where++import Graphics.Blank -- import the blank canvas+import Wiki -- (600,200)++main :: IO ()+main = blankCanvas 3000 $ \ context -> do -- start blank canvas on port 3000+ send context $ do -- send commands to this specific context+ moveTo(50,50)+ lineTo(200,100)+ lineWidth 10+ strokeStyle "red"+ stroke() -- this draws the ink into the canvas++ wiki $ snapShot context "images/Red_Line.png"+ wiki $ close context
+ wiki-suite/Rotate_Transform.hs view
@@ -0,0 +1,20 @@+{-# LANGUAGE OverloadedStrings #-}+module Rotate_Transform where++import Graphics.Blank+import Wiki -- (578,200)++main :: IO ()+main = blankCanvas 3000 $ \ context -> do+ send context $ do+ let rectWidth = 150;+ let rectHeight = 75;+ translate(width context / 2, height context / 2);+ rotate (pi/4);++ fillStyle "blue";+ fillRect(rectWidth / (-2), rectHeight / (-2), rectWidth, rectHeight);+++ wiki $ snapShot context "images/Rotate_Transform.png"+ wiki $ close context
+ wiki-suite/Rotating_Square.hs view
@@ -0,0 +1,36 @@+{-# LANGUAGE OverloadedStrings #-}+module Rotating_Square where++import Control.Concurrent+import Graphics.Blank+import Wiki -- (384,384)++main :: IO ()+main = blankCanvas 3000 $ flip loop 0++loop :: DeviceContext -> Double -> IO ()+loop context n = do+ send context $ do+ clearRect (0,0,width context,height context)+ beginPath()+ save()+ translate (width context / 2,height context / 2)+ rotate (pi * n)+ beginPath()+ moveTo(-100,-100)+ lineTo(-100,100)+ lineTo(100,100)+ lineTo(100,-100)+ closePath()+ lineWidth 10+ strokeStyle "green"+ stroke()+ restore()+ threadDelay (20 * 1000)+ v <- wiki $ return (round (n*100) :: Int)+ wiki $ whenM (v `mod` 2 == 0) $ do+ file <- wiki $ anim_png "Rotating_Square"+ wiki $ snapShot context $ file+ wiki $ whenM (v == 48) $ do { build_anim "Rotating_Square" 5; close context }+ loop context (n + 0.01)+
+ wiki-suite/Rounded_Corners.hs view
@@ -0,0 +1,29 @@+{-# LANGUAGE OverloadedStrings #-}+module Rounded_Corners where++import Graphics.Blank+import Wiki -- (578,200)++main :: IO ()+main = blankCanvas 3000 $ \ context -> do+ send context $ do+ + lineWidth 25;++ let rectWidth = 200;+ let rectHeight = 100;+ let rectX = 189;+ let rectY = 50;+ let cornerRadius = 50;++ beginPath();+ moveTo(rectX, rectY);+ lineTo(rectX + rectWidth - cornerRadius, rectY);+ arcTo(rectX + rectWidth, rectY, rectX + rectWidth, rectY + cornerRadius, cornerRadius);+ lineTo(rectX + rectWidth, rectY + rectHeight);+ lineWidth 5;+ stroke();+++ wiki $ snapShot context "images/Rounded_Corners.png"+ wiki $ close context
+ wiki-suite/Scale_Transform.hs view
@@ -0,0 +1,20 @@+{-# LANGUAGE OverloadedStrings #-}+module Scale_Transform where++import Graphics.Blank+import Wiki -- (578,200)++main :: IO ()+main = blankCanvas 3000 $ \ context -> do+ send context $ do+ let rectWidth = 150;+ let rectHeight = 75;+ translate(width context / 2, height context / 2);+ scale(1, 0.5);++ fillStyle "blue";+ fillRect(rectWidth / (-2), rectHeight / (-2), rectWidth, rectHeight);+++ wiki $ snapShot context "images/Scale_Transform.png"+ wiki $ close context
+ wiki-suite/Semicircle.hs view
@@ -0,0 +1,21 @@+{-# LANGUAGE OverloadedStrings #-}+module Semicircle where++import Graphics.Blank+import Wiki -- (578,200)++main :: IO ()+main = blankCanvas 3000 $ \ context -> do+ send context $ do+ beginPath();+ arc(288, 75, 70, 0, pi, False);+ closePath();+ lineWidth 5;+ fillStyle "red";+ fill();+ strokeStyle "#550000";+ stroke();+++ wiki $ snapShot context "images/Semicircle.png"+ wiki $ close context
+ wiki-suite/Shadow.hs view
@@ -0,0 +1,20 @@+{-# LANGUAGE OverloadedStrings #-}+module Shadow where++import Graphics.Blank+import Wiki -- (578,200)++main :: IO ()+main = blankCanvas 3000 $ \ context -> do+ send context $ do+ rect(188, 40, 200, 100);+ fillStyle "red";+ shadowColor "#999";+ shadowBlur 20;+ shadowOffsetX 15;+ shadowOffsetY 15;+ fill()+++ wiki $ snapShot context "images/Shadow.png"+ wiki $ close context
+ wiki-suite/Square.hs view
@@ -0,0 +1,49 @@+{-# LANGUAGE OverloadedStrings #-}+module Square where++import Graphics.Blank+import Wiki -- (512,512)++main :: IO ()+main = blankCanvas 3000 { events = ["mousedown"] } $ \ context -> do+ let loop (x,y) (color:colors) = do+ send context $ saveRestore $ do+ translate (x,y)+ beginPath()+ moveTo(-100,-100)+ lineTo(-100,100)+ lineTo(100,100)+ lineTo(100,-100)+ closePath()+ lineWidth 10+ strokeStyle color+ stroke()+++ wiki $ counter (\ _ -> True) $ \ n -> do+ file <- wiki $ anim_png "Square"+ wiki $ snapShot context $ file+ let e (x',y') = wiki $ send context $ trigger $ Event {+ eMetaKey = False+ , ePageXY = return (x',y')+ , eType = "mousedown"+ , eWhich = Nothing+ }+ wiki $ whenM (n == 1) $ e (x-100,y+50)+ wiki $ whenM (n == 2) $ e (x+20, y-60)+ wiki $ whenM (n == 3) $ e (x+40, y+20)+ wiki $ whenM (n == 4) $ e (x+60, y-60)+ wiki $ whenM (n >= 5) $ do { build_anim "Square" 100; close context }++ event <- wait context+ case ePageXY event of+ Nothing -> loop (x,y) colors+ Just (x',y') -> loop (x',y') colors++ putStrLn "calling size" + loop (width context / 2,height context / 2)+ (cycle [ "#749871", "#1887f2", "#808080", "f01234"])++++
+ wiki-suite/Text_Align.hs view
@@ -0,0 +1,19 @@+{-# LANGUAGE OverloadedStrings #-}+module Text_Align where++import Graphics.Blank+import Wiki -- (578,200)++main :: IO ()+main = blankCanvas 3000 $ \ context -> do+ send context $ do+ let x = width context / 2+ let y = height context / 2+ font "30pt Calibri"+ textAlign "center"+ fillStyle "blue"+ fillText("Hello World!", x, y)+++ wiki $ snapShot context "images/Text_Align.png"+ wiki $ close context
+ wiki-suite/Text_Baseline.hs view
@@ -0,0 +1,20 @@+{-# LANGUAGE OverloadedStrings #-}+module Text_Baseline where++import Graphics.Blank+import Wiki -- (578,200)++main :: IO ()+main = blankCanvas 3000 $ \ context -> do+ send context $ do+ let x = width context / 2+ let y = height context / 2+ font "30pt Calibri"+ textAlign "center"+ textBaseline "middle"+ fillStyle "blue"+ fillText("Hello World!", x, y)+++ wiki $ snapShot context "images/Text_Baseline.png"+ wiki $ close context
+ wiki-suite/Text_Color.hs view
@@ -0,0 +1,16 @@+{-# LANGUAGE OverloadedStrings #-}+module Text_Color where++import Graphics.Blank+import Wiki -- (578,200)++main :: IO ()+main = blankCanvas 3000 $ \ context -> do+ send context $ do+ font "40pt Calibri"+ fillStyle "#0000ff"+ fillText("Hello World!", 150, 100)+++ wiki $ snapShot context "images/Text_Color.png"+ wiki $ close context
+ wiki-suite/Text_Metrics.hs view
@@ -0,0 +1,28 @@+{-# LANGUAGE OverloadedStrings #-}+module Text_Metrics where++import Data.Monoid+import qualified Data.Text as Text+import Graphics.Blank+import Wiki -- (578,200)++main :: IO ()+main = blankCanvas 3000 $ \ context -> do+ send context $ do + let x = width context / 2+ let y = height context / 2 - 10;+ let text = "Hello World!"+ font "30pt Calibri"+ textAlign "center"+ fillStyle "blue"+ fillText(text, x, y)++ TextMetrics w <- measureText text+ font "20pt Calibri"+ textAlign "center"+ fillStyle "#555"+ fillText("(" <> Text.pack (show w) <> "px wide)", x, y + 40)+++ wiki $ snapShot context "images/Text_Metrics.png"+ wiki $ close context
+ wiki-suite/Text_Stroke.hs view
@@ -0,0 +1,17 @@+{-# LANGUAGE OverloadedStrings #-}+module Text_Stroke where++import Graphics.Blank+import Wiki -- (578,200)++main :: IO ()+main = blankCanvas 3000 $ \ context -> do+ send context $ do+ font "60pt Calibri"+ lineWidth 3+ strokeStyle "blue"+ strokeText("Hello World!", 80, 110)+++ wiki $ snapShot context "images/Text_Stroke.png"+ wiki $ close context
+ wiki-suite/Text_Wrap.hs view
@@ -0,0 +1,34 @@+{-# LANGUAGE OverloadedStrings #-}+module Text_Wrap where++import Data.Monoid+import qualified Data.Text as Text+import Graphics.Blank+import Wiki -- (578,200)++main :: IO ()+main = blankCanvas 3000 $ \ context -> do+ let r = devicePixelRatio context+ send context $ do+ font $ Text.pack $ "lighter " ++ show (16 * r) ++ "pt Calibri"+ fillStyle "#000"+ let maxWidth = 400 * r+ wrapText 0 (Text.words message) ((width context - maxWidth) / 2) 60 maxWidth (25 * r)+ let ex = wiki $ if r > 1.5 then "@2x" else ""+ wiki $ snapShot context $ "images/Text_Wrap" ++ ex ++ ".png"+ wiki $ close context+ where++ message = "All the world's a stage, and all the men and women merely players. " <>+ "They have their exits and their entrances; And one man in his time plays many parts."++ wrapText _ [] _ _ _ _ = return ()+ wrapText wc text x y maxWidth lineHeight = do+ TextMetrics testWidth <- measureText $ Text.unwords $ take (wc+1) $ text+ if (testWidth > maxWidth && wc > 0) || length text <= wc+ then do fillText(Text.unwords $ take wc $ text,x,y)+ wrapText 0 (drop wc text) x (y + lineHeight) maxWidth lineHeight+ else do wrapText (wc+1) text x y maxWidth lineHeight+++
+ wiki-suite/Tic_Tac_Toe.hs view
@@ -0,0 +1,132 @@+{-# LANGUAGE OverloadedStrings #-}+module Tic_Tac_Toe where++import Control.Concurrent -- wiki $++import qualified Data.Map as Map+import Data.Map (Map)+import Data.Text (Text)++-- import Debug.Trace++import Graphics.Blank++import Wiki -- (512,384)++main :: IO ()+main = blankCanvas 3000 { events = ["mousedown"] } $ \ context -> loop context Map.empty X++data XO = X | O deriving (Eq,Ord,Show)++swap :: XO -> XO+swap X = O+swap O = X++loop :: DeviceContext -> Map (Int, Int) XO -> XO -> IO ()+loop context board turn = do+ sz <- send context $ do+ clearRect (0,0,width context,height context)+ beginPath()++ let sz = min (width context) (height context)+ save()+ translate (width context / 2,height context / 2)+ sequence_ [ do bigLine (-sz * 0.45,n) (sz * 0.45,n)+ bigLine (n,-sz * 0.45) (n,sz * 0.45)+ | n <- [-sz * 0.15,sz * 0.15]+ ]+++ sequence_ [ do save()+ translate (fromIntegral x * sz * 0.3,fromIntegral y * sz * 0.3)+ case Map.lookup (x,y) board of+ Just X -> drawX (sz * 0.1)+ Just O -> drawO (sz * 0.1)+ Nothing -> return ()+ restore()+ | x <- [-1,0,1]+ , y <- [-1,0,1]+ ]+ restore()+ return sz++ let pointToSq :: (Double, Double) -> Maybe (Int, Int)+ pointToSq (x,y) = do+ x' <- fd ((x - width context / 2) / sz)+ y' <- fd ((y - height context / 2) / sz)+ return (x',y')++ fd x = +-- trace (show ("fx",x,r)) $+ if r `elem` [-1..1] then Just (signum r) else Nothing+ where r = round (x * 3.3333)++ let press = (width context / 2 + fromIntegral x * (sz / 4),height context / 2 + fromIntegral y * (sz / 4)) -- wiki $+ where (x,y) = head [ ix | (ix,Nothing) -- wiki $+ <- [ ((x',y'),Map.lookup (x',y') board) -- wiki $+ | y' <- [-1,0,1] -- wiki $+ , x' <- [-1,0,1] -- wiki $+ ]] -- wiki $++ _ <- wiki $ forkIO $ send context $ trigger $ Event {+ eMetaKey = False+ , ePageXY = return $ press+ , eType = "keypress"+ , eWhich = Nothing+ }+ event <- wait context++ file <- wiki $ anim_png "Tic_Tac_Toe"+ wiki $ snapShot context $ file+ wiki $ whenM (Map.size board == 7) $ do { build_anim "Tic_Tac_Toe" 100; close context ; quit }++ print event+ case ePageXY event of+ -- if no mouse location, ignore, and redraw+ Nothing -> loop context board turn+ Just (x',y') -> case pointToSq (x',y') of+ Nothing -> loop context board turn+ Just pos -> case Map.lookup pos board of+ Nothing -> loop context+ (Map.insert pos turn board)+ (swap turn)+ -- already something here+ Just _ -> loop context board turn++xColor, oColor, boardColor :: Text+xColor = "#ff0000"+oColor = "#00a000"+boardColor = "#000080"++drawX :: Double -> Canvas ()+drawX size = do+ strokeStyle xColor+ lineCap "butt"+ beginPath()+ moveTo(-size,-size)+ lineTo(size,size)+ lineWidth 10+ stroke()+ beginPath()+ moveTo(-size,size)+ lineTo(size,-size)+ lineWidth 10+ stroke()++drawO :: Double -> Canvas ()+drawO radius = do+ beginPath()+ arc(0, 0, radius, 0, 2 * pi, False)+ lineWidth 10+ strokeStyle oColor+ stroke()++bigLine :: (Double, Double) -> (Double, Double) -> Canvas ()+bigLine (x,y) (x',y') = do+ beginPath()+ moveTo(x,y)+ lineTo(x',y')+ lineWidth 20+ strokeStyle boardColor+ lineCap "round"+ stroke()
+ wiki-suite/Translate_Transform.hs view
@@ -0,0 +1,18 @@+{-# LANGUAGE OverloadedStrings #-}+module Translate_Transform where++import Graphics.Blank+import Wiki -- (578,200)++main :: IO ()+main = blankCanvas 3000 $ \ context -> do+ send context $ do+ let rectWidth = 150;+ let rectHeight = 75;+ translate(width context / 2, height context / 2);+ fillStyle "blue";+ fillRect(rectWidth / (-2), rectHeight / (-2), rectWidth, rectHeight);+++ wiki $ snapShot context "images/Translate_Transform.png"+ wiki $ close context
+ wiki-suite/Wiki.hs view
@@ -0,0 +1,123 @@+{-# LANGUAGE OverloadedStrings #-}+-- wiki generator support++module Wiki where++import Control.Concurrent+import Control.Concurrent.STM+import qualified Control.Monad as M++import Data.Text (Text)+import Data.Time.Clock.POSIX++import Graphics.Blank++import System.Exit+import System.IO.Unsafe+import System.Posix.Process+import System.Process++import Text.Printf++-- import Trace.Hpc.Reflect+-- import Trace.Hpc.Tix++snapShot :: DeviceContext -> FilePath -> IO ()+snapShot context fileName = do+ txt <- send context $ do++ tempCanvas <- newCanvas (round (width context + 20 :: Double),+ round (height context + 20 :: Double))++ top' <- myCanvasContext++ with tempCanvas $ do+ -- print a border, because we can (looks better in wiki)++ save()+ beginPath()+ moveTo(1,1)+ lineTo(width context+1,1)+ lineTo(width context+1,height context+1)+ lineTo(1,height context+1)+ closePath()+ fillStyle "white"++ shadowOffsetX 10+ shadowOffsetY 10;+ shadowBlur 15;+ shadowColor "#999";++ fill()++ beginPath()+ moveTo(0.5,0.5)+ lineTo(width context+1.5,0.5)+ lineTo(width context+1.5,height context+1.5)+ lineTo(0.5,height context+1.5)+ closePath()++ shadowOffsetX 0+ shadowOffsetY 0+ shadowBlur 0+ lineWidth 1+ strokeStyle "black"++ stroke()++ restore()+ + drawImage(top',[1,1])+ toDataURL() -- of tempCanvas++ writeDataURL ("blank-canvas.wiki/" ++ fileName) txt++wiki :: a -> a+wiki = id++close :: DeviceContext -> IO ()+close context = do+-- n <- getPOSIXTime +-- Tix tix <- examineTix+-- let tix' = filter (\ t -> ("Graphics.Blank" `isPrefixOf` tixModuleName t)) +-- $ tix+-- writeFile ("tix/tix_" ++ printf "_%013d" (floor (fromRational (toRational n) * 1000) :: Integer) ++ ".tix") $ show $ Tix tix'+ send context $ eval "open(location, '_self').close()"+ threadDelay (1000 * 1000);+ putStrLn "dieing"+ p <- getProcessID + callProcess "kill" [show p]+ quit++quit :: IO a+quit = exitSuccess++whenM :: Monad m => Bool -> m () -> m ()+whenM = M.when++anim_png :: String -> IO String+anim_png nm = do+ n <- getPOSIXTime + return $ "tmp/" ++ nm ++ printf "_%013d" (floor (fromRational (toRational n) * 1000 :: Double) :: Integer) ++ ".png"++build_anim :: String -> Int -> IO ()+build_anim nm pz = do+ callCommand $ "convert -delay " ++ show pz ++ " -loop 0 -dispose background blank-canvas.wiki/tmp/" ++ nm ++ "_*.png blank-canvas.wiki/images/" ++ nm ++ ".gif"+ return ()+++{-# NOINLINE count #-}+count :: TVar Int+count = unsafePerformIO $ newTVarIO 1++counter :: (Int -> Bool) -> (Int -> IO ()) -> IO ()+counter p k = do+ n <- atomically $ do+ v <- readTVar count+ writeTVar count $! v + 1+ return v+ if p n then k n else return ()+++ev :: DeviceContext -> Text -> Int -> IO ()+ev context t c = send context $ trigger $ Event { eMetaKey = False, ePageXY = Nothing, eType = t, eWhich = Just c }