mirror of
https://github.com/rgleichman/glance.git
synced 2024-11-30 14:22:20 +03:00
338 lines
12 KiB
Haskell
338 lines
12 KiB
Haskell
{-# LANGUAGE NoMonomorphismRestriction, FlexibleContexts, TypeFamilies, RankNTypes, PartialTypeSignatures #-}
|
|
module Icons
|
|
(
|
|
Icon(..),
|
|
apply0NDia,
|
|
iconToDiagram,
|
|
nameDiagram,
|
|
textBox,
|
|
enclosure,
|
|
lambdaRegion,
|
|
resultIcon,
|
|
guardIcon,
|
|
caseIcon,
|
|
defaultLineWidth,
|
|
ColorStyle(..),
|
|
colorScheme
|
|
) where
|
|
|
|
import Diagrams.Prelude
|
|
-- import Diagrams.Backend.SVG(B)
|
|
import Diagrams.TwoD.Text(Text)
|
|
import Data.Typeable(Typeable)
|
|
|
|
import Types(Icon(..))
|
|
import Util(fromMaybeError)
|
|
|
|
-- COLO(U)RS --
|
|
colorScheme :: (Floating a, Ord a) => ColorStyle a
|
|
colorScheme = colorOnBlackScheme
|
|
|
|
data ColorStyle a = ColorStyle {
|
|
backgroundC :: Colour a,
|
|
lineC :: Colour a,
|
|
textBoxTextC :: Colour a,
|
|
textBoxC :: Colour a,
|
|
apply0C :: Colour a,
|
|
apply1C :: Colour a,
|
|
boolC :: Colour a,
|
|
lamArgResC :: Colour a,
|
|
regionPerimC :: Colour a,
|
|
caseRhsC :: Colour a,
|
|
patternC :: Colour a
|
|
}
|
|
|
|
colorOnBlackScheme :: (Floating a, Ord a) => ColorStyle a
|
|
colorOnBlackScheme = ColorStyle {
|
|
backgroundC = black,
|
|
lineC = white,
|
|
textBoxTextC = white,
|
|
textBoxC = white,
|
|
apply0C = red,
|
|
apply1C = cyan,
|
|
boolC = orange,
|
|
lamArgResC = lime,
|
|
regionPerimC = white,
|
|
caseRhsC = slightlyGreenYellow,
|
|
patternC = lightMagenta
|
|
}
|
|
where
|
|
slightlyGreenYellow = sRGB24 212 255 0
|
|
lightMagenta = sRGB24 255 94 255
|
|
|
|
whiteOnBlackScheme :: (Floating a, Ord a) => ColorStyle a
|
|
whiteOnBlackScheme = ColorStyle {
|
|
backgroundC = black,
|
|
lineC = white,
|
|
textBoxTextC = white,
|
|
textBoxC = white,
|
|
apply0C = white,
|
|
apply1C = white,
|
|
boolC = white,
|
|
lamArgResC = white,
|
|
regionPerimC = white,
|
|
caseRhsC = white,
|
|
patternC = white
|
|
}
|
|
|
|
-- Use this to test that all of the colors use the colorScheme
|
|
randomColorScheme :: (Floating a, Ord a) => ColorStyle a
|
|
randomColorScheme = ColorStyle {
|
|
backgroundC = darkorchid,
|
|
lineC = yellow,
|
|
textBoxTextC = blue,
|
|
textBoxC = magenta,
|
|
apply0C = orange,
|
|
apply1C = green,
|
|
boolC = lightpink,
|
|
lamArgResC = red,
|
|
regionPerimC = cyan,
|
|
caseRhsC = red,
|
|
patternC = olive
|
|
}
|
|
|
|
lineCol :: (Floating a, Ord a) => Colour a
|
|
lineCol = lineC colorScheme
|
|
|
|
-- FUNCTIONS --
|
|
-- Optimization: The apply0NDia's can be memoized.
|
|
iconToDiagram ::
|
|
(RealFloat n, Typeable n, Renderable (Path V2 n) b,
|
|
Renderable (Text n) b) =>
|
|
Icon -> [(Name, QDiagram b V2 n Any)] -> QDiagram b V2 n Any
|
|
iconToDiagram (Apply0NIcon n) _ = apply0NDia n
|
|
iconToDiagram ResultIcon _ = resultIcon
|
|
iconToDiagram BranchIcon _ = branchIcon
|
|
iconToDiagram (TextBoxIcon s) _ = textBox s
|
|
iconToDiagram (GuardIcon n) _ = guardIcon n
|
|
iconToDiagram (CaseIcon n) _ = caseIcon n
|
|
iconToDiagram CaseResultIcon _ = caseResult
|
|
iconToDiagram (LambdaRegionIcon n diagramName) nameToSubdiagramMap =
|
|
lambdaRegion n dia
|
|
where
|
|
dia = fromMaybeError "iconToDiagram: subdiagram not found" $ lookup diagramName nameToSubdiagramMap
|
|
|
|
-- | Names the diagram and puts all sub-names in the namespace of the top level name.
|
|
nameDiagram :: (Floating n, Ord n, Semigroup m, Metric v, IsName nm) => nm -> QDiagram b v n m -> QDiagram b v n m
|
|
nameDiagram name dia = named name (name .>> dia)
|
|
|
|
-- | Make an port with an integer name. Always use <> to add a ports (not === or |||)
|
|
--- since mempty has no size and will not be placed where you want it.
|
|
makePort :: (Floating n, Ord n, Semigroup m, Metric v) => Int -> QDiagram b v n m
|
|
makePort x = mempty # named x
|
|
--makePort x = circle 0.2 # fc green # named x
|
|
--makePort x = textBox (show x) # fc green # named x
|
|
|
|
|
|
--makePortDiagrams :: [P2 Double] -> Diagram B
|
|
--makePortDiagrams ::(Monoid a, Semigroup a, HasOrigin a, b ~ N a) => [P2 b] -> GeneralDiagram a
|
|
makePortDiagrams ::
|
|
(Floating n, Ord n, Semigroup m, Metric v) =>
|
|
[Point v n] -> QDiagram b v n m
|
|
makePortDiagrams points =
|
|
atPoints points (map makePort ([0,1..] :: [Int]))
|
|
|
|
-- CONSTANTS --
|
|
defaultLineWidth :: (Fractional a) => a
|
|
defaultLineWidth = 0.15
|
|
|
|
-- APPLY0 ICON --
|
|
circleRadius :: (Fractional a) => a
|
|
circleRadius = 0.5
|
|
|
|
type GeneralDiagram b = (Transformable b, RealFloat (N b), Typeable (N b), HasStyle b, TrailLike b, V b ~ V2) => b
|
|
|
|
resultCircle ::
|
|
(RealFloat (N b), Typeable (N b), Transformable b, HasStyle b,
|
|
TrailLike b, V b ~ V2) =>
|
|
b
|
|
resultCircle = circle circleRadius # fc (apply0C colorScheme) # lw none
|
|
|
|
apply0Triangle ::
|
|
(Typeable (N b), Transformable b, HasStyle b, TrailLike b,
|
|
V b ~ V2) =>
|
|
b
|
|
apply0Triangle = eqTriangle (2 * circleRadius) # rotateBy (-1/12) # fc (apply0C colorScheme) # lw none
|
|
|
|
apply0Line ::
|
|
(Typeable (N b), HasStyle b, TrailLike b, V b ~ V2) => b
|
|
apply0Line = rect defaultLineWidth (2 * circleRadius) # fc lineCol # lw none
|
|
|
|
--apply0Dia :: (Juxtaposable a, Semigroup a) => GeneralDiagram a
|
|
apply0Dia ::
|
|
(RealFloat n, Typeable n, Monoid m, Semigroup m,
|
|
TrailLike (QDiagram b V2 n m)) =>
|
|
QDiagram b V2 n m
|
|
apply0Dia = (resultCircle ||| apply0Line ||| apply0Triangle) <> makePortDiagrams apply0PortLocations # centerXY
|
|
|
|
apply0PortLocations :: Floating a => [P2 a]
|
|
apply0PortLocations = map p2 [
|
|
(circleRadius + defaultLineWidth + triangleWidth, 0),
|
|
(-circleRadius,0),
|
|
(lineCenter,-circleRadius)
|
|
--(lineCenter,circleRadius),
|
|
]
|
|
where
|
|
triangleWidth = circleRadius * sqrt 3
|
|
lineCenter = circleRadius + (defaultLineWidth / 2.0)
|
|
|
|
-- apply0N Icon--
|
|
-- | apply0N port locations:
|
|
-- Port 0: Function
|
|
-- Port 1: Result
|
|
-- Ports 2,3..: Arguments
|
|
apply0NDia ::
|
|
(RealFloat n, Typeable n, Monoid m, Semigroup m,
|
|
TrailLike (QDiagram b V2 n m)) =>
|
|
Int -> QDiagram b V2 n m
|
|
apply0NDia 1 = apply0Dia
|
|
apply0NDia n = finalDia # centerXY where
|
|
seperation = circleRadius * 1.5
|
|
portCircle = circle (circleRadius * 0.5) # fc lineCol # lw none
|
|
trianglePortsCircle = hcat [
|
|
reflectX apply0Triangle,
|
|
hcat $ take n $ map (\x -> makePort x <> portCircle <> strutX seperation) [2,3..],
|
|
makePort 1 <> alignR (circle circleRadius # fc (apply0C colorScheme) # lwG defaultLineWidth # lc (apply0C colorScheme))
|
|
]
|
|
allPorts = makePort 0 <> alignL trianglePortsCircle
|
|
topAndBottomLineWidth = width allPorts - circleRadius
|
|
topAndBottomLine = hrule topAndBottomLineWidth # lc (apply0C colorScheme) # lwG defaultLineWidth # alignL
|
|
finalDia = topAndBottomLine === allPorts === topAndBottomLine
|
|
|
|
-- TEXT ICON --
|
|
textBoxFontSize :: (Num a) => a
|
|
textBoxFontSize = 1
|
|
monoLetterWidthToHeightFraction :: (Fractional a) => a
|
|
monoLetterWidthToHeightFraction = 0.61
|
|
textBoxHeightFactor :: (Fractional a) => a
|
|
textBoxHeightFactor = 1.1
|
|
|
|
textBox ::
|
|
(RealFloat n, Typeable n, Renderable (Path V2 n) b,
|
|
Renderable (Text n) b) =>
|
|
String -> QDiagram b V2 n Any
|
|
textBox = coloredTextBox (textBoxTextC colorScheme) $ opaque (textBoxC colorScheme)
|
|
|
|
-- Since the normal SVG text has no size, some hackery is needed to determine
|
|
-- the size of the text's bounding box.
|
|
coloredTextBox ::
|
|
(RealFloat n, Typeable n, Renderable (Path V2 n) b,
|
|
Renderable (Diagrams.TwoD.Text.Text n) b) =>
|
|
Colour Double
|
|
-> AlphaColour Double -> String -> QDiagram b V2 n Any
|
|
coloredTextBox textColor boxColor t =
|
|
text t # fc textColor # font "freemono" # bold # fontSize (local textBoxFontSize)
|
|
<> rect rectangleWidth (textBoxFontSize * textBoxHeightFactor) # lcA boxColor # lwG (0.6 * defaultLineWidth)
|
|
where
|
|
rectangleWidth = textBoxFontSize * monoLetterWidthToHeightFraction
|
|
* fromIntegral (length t)
|
|
+ (textBoxFontSize * 0.2)
|
|
|
|
-- ENCLOSING REGION --
|
|
enclosure ::
|
|
(Floating n, Ord n, Typeable n, Monoid m, Semigroup m,
|
|
TrailLike (QDiagram b V2 n m)) =>
|
|
QDiagram b V2 n m -> QDiagram b V2 n m
|
|
enclosure dia = dia <> boundingRect (dia # frame 0.5) # lc (regionPerimC colorScheme) # lwG defaultLineWidth
|
|
|
|
-- LAMBDA ICON --
|
|
-- Don't use === here to put the port under the text box since mempty will stay
|
|
-- at the origin of the text box.
|
|
lambdaIcon ::
|
|
(RealFloat n, Typeable n, Renderable (Path V2 n) b,
|
|
Renderable (Diagrams.TwoD.Text.Text n) b) =>
|
|
Int -> QDiagram b V2 n Any
|
|
lambdaIcon x = coloredTextBox (lamArgResC colorScheme) transparent "λ" # alignB <> makePort x
|
|
|
|
-- LAMBDA REGION --
|
|
|
|
-- | lambdaRegion takes as an argument the numbers of parameters to the lambda,
|
|
-- and draws the diagram inside a region with the lambda icons on top.
|
|
lambdaRegion ::
|
|
(RealFloat n, Typeable n, Renderable (Path V2 n) b,
|
|
Renderable (Diagrams.TwoD.Text.Text n) b) =>
|
|
Int -> QDiagram b V2 n Any -> QDiagram b V2 n Any
|
|
lambdaRegion n dia =
|
|
centerXY $ lambdaIcons # centerX === (enclosure dia # centerX)
|
|
where lambdaIcons = hsep 0.4 (take n (map lambdaIcon [0,1..]))
|
|
|
|
-- RESULT ICON --
|
|
resultIcon ::
|
|
(Typeable (N b), HasStyle b, TrailLike b, V b ~ V2) => b
|
|
resultIcon = unitSquare # lw none # fc (lamArgResC colorScheme)
|
|
|
|
-- BRANCH ICON --
|
|
branchIcon :: GeneralDiagram a
|
|
branchIcon = circle circleRadius # fc lineCol # lc lineCol # lw none
|
|
|
|
-- GUARD ICON --
|
|
guardSize :: (Fractional a) => a
|
|
guardSize = 0.7
|
|
|
|
guardTriangle ::
|
|
(Floating n, Ord n, Typeable n, Monoid m, Semigroup m,
|
|
TrailLike (QDiagram b V2 n m)) =>
|
|
Int -> QDiagram b V2 n m
|
|
guardTriangle x =
|
|
((triangleAndPort ||| (hrule (guardSize * 0.8) # lwG defaultLineWidth)) # alignR) <> makePort x # alignL
|
|
where
|
|
triangleAndPort = polygon (with & polyType .~ PolySides [90 @@ deg, 45 @@ deg] [guardSize, guardSize])
|
|
# rotateBy (1/8) # lwG defaultLineWidth # alignT # alignR
|
|
|
|
guardLBracket ::
|
|
(RealFloat n, Typeable n, Renderable (Path V2 n) b) =>
|
|
Int -> QDiagram b V2 n Any
|
|
guardLBracket x = ell # alignT # alignL <> makePort x
|
|
where
|
|
ellShape = fromOffsets $ map r2 [(0, guardSize), (-guardSize,0)]
|
|
ell = ellShape # strokeLine # lc (boolC colorScheme) # lwG defaultLineWidth # lineJoin LineJoinRound
|
|
|
|
generalGuardIcon ::
|
|
(RealFloat n, Typeable n, Renderable (Path V2 n) b) =>
|
|
Colour Double -> (Int -> QDiagram b V2 n Any) -> QDiagram b V2 n Any -> Int -> QDiagram b V2 n Any
|
|
generalGuardIcon triangleColor lBracket bottomDia n = centerXY $ alignT (bottomDia <> makePort 1) <> alignB (bigVerticalLine <> guardDia <> makePort 0)
|
|
where
|
|
--guardTriangles = vsep 0.4 (take n (map guardTriangle [0,1..]))
|
|
trianglesWithPorts = map guardTriangle [2,4..]
|
|
lBrackets = map lBracket [3, 5..]
|
|
trianglesAndBrackets =
|
|
zipWith zipper trianglesWithPorts lBrackets
|
|
zipper thisTriangle lBrack = verticalLine === ((lBrack # extrudeRight guardSize) # alignR <> (thisTriangle # alignL # lc triangleColor))
|
|
where
|
|
verticalLine = strutY 0.4
|
|
guardDia = vcat (take n trianglesAndBrackets # alignT)
|
|
bigVerticalLine = vrule (height guardDia) # lc triangleColor # lwG defaultLineWidth # alignT
|
|
|
|
-- | The ports of the guard icon are as follows:
|
|
-- Port 0: Top result port
|
|
-- Port 1: Bottom result port
|
|
-- Ports 3,5...: The left ports for the booleans
|
|
-- Ports 2,4...: The right ports for the values
|
|
guardIcon ::
|
|
(RealFloat n, Typeable n, Renderable (Path V2 n) b) =>
|
|
Int -> QDiagram b V2 n Any
|
|
guardIcon = generalGuardIcon lineCol guardLBracket mempty
|
|
|
|
-- TODO Improve design to be more than a circle.
|
|
caseResult :: (RealFloat n,
|
|
Typeable n,
|
|
Renderable (Path V2 n) b) => QDiagram b V2 n Any
|
|
caseResult = circle (circleRadius * 0.7) # fc caseCColor # lc caseCColor # lw none where
|
|
caseCColor = caseRhsC colorScheme
|
|
|
|
caseC :: (RealFloat n,
|
|
Typeable n,
|
|
Renderable (Path V2 n) b) => Int -> QDiagram b V2 n Any
|
|
caseC n = caseResult <> makePort n
|
|
|
|
|
|
-- | The ports of the case icon are as follows:
|
|
-- Port 0: Top result port
|
|
-- Port 1: Bottom result port
|
|
-- Ports 3,5...: The left ports for the results
|
|
-- Ports 2,4...: The right ports for the patterns
|
|
caseIcon ::(RealFloat n,
|
|
Typeable n,
|
|
Renderable (Path V2 n) b) => Int -> QDiagram b V2 n Any
|
|
caseIcon = generalGuardIcon (patternC colorScheme) caseC caseResult
|