{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeFamilies #-} -- | Main functions for creating SVG dom elements via Reflex module Reflex.Dom.Widget.SVG ( AsSVGTag (..) , BasicSVG (..) , BasicInner (..) , SVG_Root (..) , CanBeNested , SVGEl (..) , svg_ , svgBasicDyn , svgBasicDyn_ , svgElDynAttr' , svgElDynAttr_ ) where import Control.Monad.Fix (MonadFix) import Data.Text (Text) import Reflex (Dynamic, MonadHold) import qualified Reflex as R import Reflex.Dom.Core (Element, EventResult, DomBuilderSpace, DomBuilder, PostBuild) import qualified Reflex.Dom.Core as RD import Data.Map (Map) import Reflex.Dom.Widget.SVG.Types (SVG_El, makeSVGProps) -- | Lawless class to provide a constraint indicating that a given type is capable -- of being represented by a SVG XML Tag. , , , etc. class AsSVGTag s where svgTagName :: s -> Text instance AsSVGTag BasicSVG where svgTagName Rectangle = "rect" svgTagName Circle = "circle" svgTagName Ellipse = "ellipse" svgTagName Path = "path" svgTagName Line = "line" svgTagName PolyLine = "polyline" svgTagName Polygon = "polygon" instance AsSVGTag BasicInner where svgTagName Animate = "animate" instance AsSVGTag SVG_Root where svgTagName SVG_Root = "svg" -- | The SVG Root element: "" data SVG_Root = SVG_Root -- | The basic SVG shapes. data BasicSVG = Rectangle | Circle | Ellipse | Path | Line | PolyLine | Polygon deriving (Show, Eq) -- | The simplest inner element for a basic shape, the "" tag. data BasicInner = Animate deriving (Eq, Ord) -- | Create a relationship between a set of SVG tags that can be nested inside a -- different set of SVG tags. Currently this just creates the relationship -- between the "" tag and the basic shapes ("", "", etc). type family CanBeNested a :: * type instance CanBeNested BasicSVG = BasicInner -- | This represents an SVG element, containing both the raw Reflex.Dom @El@ type -- and a @Dynamic@ of all of the children that are nested in this element. data SVGEl t m a = SVGEl { _svgEl_el :: Element EventResult (DomBuilderSpace m) t , _svgEl_children :: Dynamic t (Map (CanBeNested a) (Element EventResult (DomBuilderSpace m) t)) } svgXMLNamespace :: Text svgXMLNamespace = "http://www.w3.org/2000/svg" -- | This is for creating a SVG element with @Dynamic@ attributes, and ensuring we -- use the right namespace so the browser actually picks up on it. The name -- space in use is "http://www.w3.org/2000/svg". svgElDynAttr' :: forall t m a e. ( DomBuilder t m , PostBuild t m , AsSVGTag e ) => e -> Dynamic t (Map Text Text) -> m a -> m (Element EventResult (DomBuilderSpace m) t, a) svgElDynAttr' = RD.elDynAttrNS' (Just svgXMLNamespace) . svgTagName -- | As per @svgElDynAttr'@, but does not have any children. svgElDynAttr_ :: forall t m e. ( DomBuilder t m , PostBuild t m , AsSVGTag e ) => e -> Dynamic t (Map Text Text) -> m (Element EventResult (DomBuilderSpace m) t) svgElDynAttr_ t dAttrs = fst <$> RD.elDynAttrNS' (Just svgXMLNamespace) (svgTagName t) dAttrs RD.blank -- | Create the Root SVG element. -- -- Note that there are not restrictions on the inner element, apart from the -- return type being of @m (SVGEl t a)@. So you are free to place whatever you -- like in there, but bear in mind that the browser rules for SVG are still in -- play. So text inputs etc, won't work. svg_ :: ( DomBuilder t m , PostBuild t m , R.Reflex t , AsSVGTag a ) => Dynamic t SVG_El -> m ( SVGEl t m a ) -> m ( Element EventResult (DomBuilderSpace m) t, SVGEl t m a) svg_ dAttrs = svgElDynAttr' SVG_Root (makeSVGProps <$> dAttrs) -- | Create a SVG element that has dynamic attributes and contains children that -- are acceptable children for this element. "" as a Basic Shape can only -- contain "" elements, for example. -- -- The SVG element will have some @Dynamic@ properties and a function that -- allows these properties to be converted into a @Map Text Text@, inline with -- other Reflex.Dom widgets. svgBasicDyn :: ( DomBuilder t m , PostBuild t m , MonadFix m , MonadHold t m , AsSVGTag s , AsSVGTag (CanBeNested s) , Ord (CanBeNested s) ) => s -> ( p -> Map Text Text ) -> Dynamic t p -> Dynamic t ( Map (CanBeNested s) (Map Text Text) ) -> m ( SVGEl t m s ) svgBasicDyn t propFn dProps dInnerElMap = fmap ( uncurry SVGEl ) . svgElDynAttr' t (propFn <$> dProps) $ RD.listWithKey dInnerElMap (\innerS dInnerAttrs -> fst <$> svgElDynAttr' innerS dInnerAttrs RD.blank) -- | As per the @svgBasicDyn@ function, except with no inner elements. svgBasicDyn_ :: ( DomBuilder t m , PostBuild t m , MonadFix m , MonadHold t m , AsSVGTag s , AsSVGTag (CanBeNested s) , Ord (CanBeNested s) ) => s -> ( p -> Map Text Text ) -> Dynamic t p -> m ( SVGEl t m s ) svgBasicDyn_ t propFn dProps = svgBasicDyn t propFn dProps (pure mempty)