{-# LANGUAGE Strict #-}

{- |
Module      : Granite.Render.Scene
Copyright   : (c) 2025
License     : MIT
Maintainer  : mschavinda@gmail.com

Backend-agnostic IR. Coordinates are in logical pixels; the terminal
backend maps them to character cells via 'pxPerChar' / 'pxPerLine'
(Braille gives 2×4 sub-cell resolution).
-}
module Granite.Render.Scene (
    Point (..),
    Rect (..),
    Mark (..),
    Scene (..),
    Style (..),
    defaultStyle,
    TextStyle (..),
    defaultTextStyle,
    TextAnchor (..),
    pxPerChar,
    pxPerLine,
) where

import Data.Text (Text)
import Granite.Color (Color (..))

data Point = Point !Double !Double
    deriving (Point -> Point -> Bool
(Point -> Point -> Bool) -> (Point -> Point -> Bool) -> Eq Point
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Point -> Point -> Bool
== :: Point -> Point -> Bool
$c/= :: Point -> Point -> Bool
/= :: Point -> Point -> Bool
Eq, Int -> Point -> ShowS
[Point] -> ShowS
Point -> String
(Int -> Point -> ShowS)
-> (Point -> String) -> ([Point] -> ShowS) -> Show Point
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Point -> ShowS
showsPrec :: Int -> Point -> ShowS
$cshow :: Point -> String
show :: Point -> String
$cshowList :: [Point] -> ShowS
showList :: [Point] -> ShowS
Show)

-- | Axis-aligned rectangle by top-left + size.
data Rect = Rect
    { Rect -> Double
rectX :: !Double
    , Rect -> Double
rectY :: !Double
    , Rect -> Double
rectW :: !Double
    , Rect -> Double
rectH :: !Double
    }
    deriving (Rect -> Rect -> Bool
(Rect -> Rect -> Bool) -> (Rect -> Rect -> Bool) -> Eq Rect
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Rect -> Rect -> Bool
== :: Rect -> Rect -> Bool
$c/= :: Rect -> Rect -> Bool
/= :: Rect -> Rect -> Bool
Eq, Int -> Rect -> ShowS
[Rect] -> ShowS
Rect -> String
(Int -> Rect -> ShowS)
-> (Rect -> String) -> ([Rect] -> ShowS) -> Show Rect
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Rect -> ShowS
showsPrec :: Int -> Rect -> ShowS
$cshow :: Rect -> String
show :: Rect -> String
$cshowList :: [Rect] -> ShowS
showList :: [Rect] -> ShowS
Show)

-- | 'Nothing' on fill or stroke means "don't paint that side".
data Style = Style
    { Style -> Maybe Color
styleFill :: !(Maybe Color)
    , Style -> Maybe Color
styleStroke :: !(Maybe Color)
    , Style -> Double
styleStrokeWidth :: !Double
    , Style -> Double
styleFillOpacity :: !Double
    }
    deriving (Style -> Style -> Bool
(Style -> Style -> Bool) -> (Style -> Style -> Bool) -> Eq Style
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Style -> Style -> Bool
== :: Style -> Style -> Bool
$c/= :: Style -> Style -> Bool
/= :: Style -> Style -> Bool
Eq, Int -> Style -> ShowS
[Style] -> ShowS
Style -> String
(Int -> Style -> ShowS)
-> (Style -> String) -> ([Style] -> ShowS) -> Show Style
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Style -> ShowS
showsPrec :: Int -> Style -> ShowS
$cshow :: Style -> String
show :: Style -> String
$cshowList :: [Style] -> ShowS
showList :: [Style] -> ShowS
Show)

defaultStyle :: Style
defaultStyle :: Style
defaultStyle = Maybe Color -> Maybe Color -> Double -> Double -> Style
Style Maybe Color
forall a. Maybe a
Nothing Maybe Color
forall a. Maybe a
Nothing Double
1 Double
1

data TextAnchor = AnchorStart | AnchorMiddle | AnchorEnd
    deriving (TextAnchor -> TextAnchor -> Bool
(TextAnchor -> TextAnchor -> Bool)
-> (TextAnchor -> TextAnchor -> Bool) -> Eq TextAnchor
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: TextAnchor -> TextAnchor -> Bool
== :: TextAnchor -> TextAnchor -> Bool
$c/= :: TextAnchor -> TextAnchor -> Bool
/= :: TextAnchor -> TextAnchor -> Bool
Eq, Int -> TextAnchor -> ShowS
[TextAnchor] -> ShowS
TextAnchor -> String
(Int -> TextAnchor -> ShowS)
-> (TextAnchor -> String)
-> ([TextAnchor] -> ShowS)
-> Show TextAnchor
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> TextAnchor -> ShowS
showsPrec :: Int -> TextAnchor -> ShowS
$cshow :: TextAnchor -> String
show :: TextAnchor -> String
$cshowList :: [TextAnchor] -> ShowS
showList :: [TextAnchor] -> ShowS
Show)

data TextStyle = TextStyle
    { TextStyle -> Color
textFill :: !Color
    , TextStyle -> Double
textSize :: !Double
    , TextStyle -> TextAnchor
textAnchor :: !TextAnchor
    , TextStyle -> Double
textRotate :: !Double
    -- ^ Rotation in degrees about the anchor point; 0 = upright.
    , TextStyle -> Maybe Text
textTitle :: !(Maybe Text)
    -- ^ A @\<title\>@ tooltip (the untruncated label); 'Nothing' = none.
    }
    deriving (TextStyle -> TextStyle -> Bool
(TextStyle -> TextStyle -> Bool)
-> (TextStyle -> TextStyle -> Bool) -> Eq TextStyle
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: TextStyle -> TextStyle -> Bool
== :: TextStyle -> TextStyle -> Bool
$c/= :: TextStyle -> TextStyle -> Bool
/= :: TextStyle -> TextStyle -> Bool
Eq, Int -> TextStyle -> ShowS
[TextStyle] -> ShowS
TextStyle -> String
(Int -> TextStyle -> ShowS)
-> (TextStyle -> String)
-> ([TextStyle] -> ShowS)
-> Show TextStyle
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> TextStyle -> ShowS
showsPrec :: Int -> TextStyle -> ShowS
$cshow :: TextStyle -> String
show :: TextStyle -> String
$cshowList :: [TextStyle] -> ShowS
showList :: [TextStyle] -> ShowS
Show)

defaultTextStyle :: TextStyle
defaultTextStyle :: TextStyle
defaultTextStyle = Color -> Double -> TextAnchor -> Double -> Maybe Text -> TextStyle
TextStyle Color
Default Double
11 TextAnchor
AnchorStart Double
0 Maybe Text
forall a. Maybe a
Nothing

{- | Primitive drawable marks. 'MArc' takes (center, radius, a0, a1)
in radians CCW from +x. 'MPolygon' is a closed filled polyline.
'MAxisLine' is an axis-aligned 1-px line that the terminal backend
writes as box-drawing chars (@│@ / @─@ / @┼@) for crisp axes;
everything else is rasterised through Braille.
-}
data Mark
    = MCircle !Point !Double !Style
    | MRect !Rect !Style
    | MPolyline ![Point] !Style
    | MPolygon ![Point] !Style
    | MPath !Text !Style
    | MText !Point !Text !TextStyle
    | MArc !Point !Double !Double !Double !Style
    | MAxisLine !Point !Point !Style
    | MGroup ![Mark]
    deriving (Mark -> Mark -> Bool
(Mark -> Mark -> Bool) -> (Mark -> Mark -> Bool) -> Eq Mark
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Mark -> Mark -> Bool
== :: Mark -> Mark -> Bool
$c/= :: Mark -> Mark -> Bool
/= :: Mark -> Mark -> Bool
Eq, Int -> Mark -> ShowS
[Mark] -> ShowS
Mark -> String
(Int -> Mark -> ShowS)
-> (Mark -> String) -> ([Mark] -> ShowS) -> Show Mark
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Mark -> ShowS
showsPrec :: Int -> Mark -> ShowS
$cshow :: Mark -> String
show :: Mark -> String
$cshowList :: [Mark] -> ShowS
showList :: [Mark] -> ShowS
Show)

data Scene = Scene
    { Scene -> Double
sceneWidth :: !Double
    , Scene -> Double
sceneHeight :: !Double
    , Scene -> [Mark]
sceneMarks :: ![Mark]
    }
    deriving (Scene -> Scene -> Bool
(Scene -> Scene -> Bool) -> (Scene -> Scene -> Bool) -> Eq Scene
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Scene -> Scene -> Bool
== :: Scene -> Scene -> Bool
$c/= :: Scene -> Scene -> Bool
/= :: Scene -> Scene -> Bool
Eq, Int -> Scene -> ShowS
[Scene] -> ShowS
Scene -> String
(Int -> Scene -> ShowS)
-> (Scene -> String) -> ([Scene] -> ShowS) -> Show Scene
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Scene -> ShowS
showsPrec :: Int -> Scene -> ShowS
$cshow :: Scene -> String
show :: Scene -> String
$cshowList :: [Scene] -> ShowS
showList :: [Scene] -> ShowS
Show)

pxPerChar :: Double
pxPerChar :: Double
pxPerChar = Double
10

pxPerLine :: Double
pxPerLine :: Double
pxPerLine = Double
16