{-# LANGUAGE GADTs #-}
module Graphics.Vty.Span
( SpanOp(..)
, columnsToCharOffset
, spanOpHasWidth
, SpanOps
, spanOpsAffectedColumns
, splitOpsAt
, dropOps
, DisplayOps
, displayOpsRows
, displayOpsColumns
, affectedRegion
)
where
import Graphics.Vty.Attributes (Attr)
import Graphics.Vty.Image
import Graphics.Vty.Image.Internal ( clipText )
import qualified Data.Text.Lazy as TL
import Data.Vector (Vector)
import qualified Data.Vector as Vector
data SpanOp =
TextSpan
{ SpanOp -> Attr
textSpanAttr :: !Attr
, SpanOp -> Int
textSpanOutputWidth :: !Int
, SpanOp -> Int
textSpanCharWidth :: !Int
, SpanOp -> Text
textSpanText :: TL.Text
}
| Skip !Int
| RowEnd !Int
deriving SpanOp -> SpanOp -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: SpanOp -> SpanOp -> Bool
$c/= :: SpanOp -> SpanOp -> Bool
== :: SpanOp -> SpanOp -> Bool
$c== :: SpanOp -> SpanOp -> Bool
Eq
type SpanOps = Vector SpanOp
dropOps :: Int -> SpanOps -> SpanOps
dropOps :: Int -> SpanOps -> SpanOps
dropOps Int
w = forall a b. (a, b) -> b
snd forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> SpanOps -> (SpanOps, SpanOps)
splitOpsAt Int
w
splitOpsAt :: Int -> SpanOps -> (SpanOps, SpanOps)
splitOpsAt :: Int -> SpanOps -> (SpanOps, SpanOps)
splitOpsAt = Int -> SpanOps -> (SpanOps, SpanOps)
splitOpsAt'
where
splitOpsAt' :: Int -> SpanOps -> (SpanOps, SpanOps)
splitOpsAt' Int
0 SpanOps
ops = (forall a. Vector a
Vector.empty, SpanOps
ops)
splitOpsAt' Int
remainingColumns SpanOps
ops = case forall a. Vector a -> a
Vector.head SpanOps
ops of
t :: SpanOp
t@(TextSpan {}) -> if Int
remainingColumns forall a. Ord a => a -> a -> Bool
>= SpanOp -> Int
textSpanOutputWidth SpanOp
t
then let (SpanOps
pre,SpanOps
post) = Int -> SpanOps -> (SpanOps, SpanOps)
splitOpsAt' (Int
remainingColumns forall a. Num a => a -> a -> a
- SpanOp -> Int
textSpanOutputWidth SpanOp
t)
(forall a. Vector a -> Vector a
Vector.tail SpanOps
ops)
in (forall a. a -> Vector a -> Vector a
Vector.cons SpanOp
t SpanOps
pre, SpanOps
post)
else let preTxt :: Text
preTxt = Text -> Int -> Int -> Text
clipText (SpanOp -> Text
textSpanText SpanOp
t) Int
0 Int
remainingColumns
preOp :: SpanOp
preOp = TextSpan { textSpanAttr :: Attr
textSpanAttr = SpanOp -> Attr
textSpanAttr SpanOp
t
, textSpanOutputWidth :: Int
textSpanOutputWidth = Int
remainingColumns
, textSpanCharWidth :: Int
textSpanCharWidth = forall a b. (Integral a, Num b) => a -> b
fromIntegral forall a b. (a -> b) -> a -> b
$! Text -> Int64
TL.length Text
preTxt
, textSpanText :: Text
textSpanText = Text
preTxt
}
postWidth :: Int
postWidth = SpanOp -> Int
textSpanOutputWidth SpanOp
t forall a. Num a => a -> a -> a
- Int
remainingColumns
postTxt :: Text
postTxt = Text -> Int -> Int -> Text
clipText (SpanOp -> Text
textSpanText SpanOp
t) Int
remainingColumns Int
postWidth
postOp :: SpanOp
postOp = TextSpan { textSpanAttr :: Attr
textSpanAttr = SpanOp -> Attr
textSpanAttr SpanOp
t
, textSpanOutputWidth :: Int
textSpanOutputWidth = Int
postWidth
, textSpanCharWidth :: Int
textSpanCharWidth = forall a b. (Integral a, Num b) => a -> b
fromIntegral forall a b. (a -> b) -> a -> b
$! Text -> Int64
TL.length Text
postTxt
, textSpanText :: Text
textSpanText = Text
postTxt
}
in ( forall a. a -> Vector a
Vector.singleton SpanOp
preOp
, forall a. a -> Vector a -> Vector a
Vector.cons SpanOp
postOp (forall a. Vector a -> Vector a
Vector.tail SpanOps
ops)
)
Skip Int
w -> if Int
remainingColumns forall a. Ord a => a -> a -> Bool
>= Int
w
then let (SpanOps
pre,SpanOps
post) = Int -> SpanOps -> (SpanOps, SpanOps)
splitOpsAt' (Int
remainingColumns forall a. Num a => a -> a -> a
- Int
w) (forall a. Vector a -> Vector a
Vector.tail SpanOps
ops)
in (forall a. a -> Vector a -> Vector a
Vector.cons (Int -> SpanOp
Skip Int
w) SpanOps
pre, SpanOps
post)
else ( forall a. a -> Vector a
Vector.singleton forall a b. (a -> b) -> a -> b
$ Int -> SpanOp
Skip Int
remainingColumns
, forall a. a -> Vector a -> Vector a
Vector.cons (Int -> SpanOp
Skip (Int
w forall a. Num a => a -> a -> a
- Int
remainingColumns)) (forall a. Vector a -> Vector a
Vector.tail SpanOps
ops)
)
RowEnd Int
_ -> forall a. HasCallStack => [Char] -> a
error [Char]
"cannot split ops containing a row end"
type DisplayOps = Vector SpanOps
instance Show SpanOp where
show :: SpanOp -> [Char]
show (TextSpan Attr
attr Int
ow Int
cw Text
_) = [Char]
"TextSpan(" forall a. [a] -> [a] -> [a]
++ forall a. Show a => a -> [Char]
show Attr
attr forall a. [a] -> [a] -> [a]
++ [Char]
")(" forall a. [a] -> [a] -> [a]
++ forall a. Show a => a -> [Char]
show Int
ow forall a. [a] -> [a] -> [a]
++ [Char]
", " forall a. [a] -> [a] -> [a]
++ forall a. Show a => a -> [Char]
show Int
cw forall a. [a] -> [a] -> [a]
++ [Char]
")"
show (Skip Int
ow) = [Char]
"Skip(" forall a. [a] -> [a] -> [a]
++ forall a. Show a => a -> [Char]
show Int
ow forall a. [a] -> [a] -> [a]
++ [Char]
")"
show (RowEnd Int
ow) = [Char]
"RowEnd(" forall a. [a] -> [a] -> [a]
++ forall a. Show a => a -> [Char]
show Int
ow forall a. [a] -> [a] -> [a]
++ [Char]
")"
displayOpsColumns :: DisplayOps -> Int
displayOpsColumns :: DisplayOps -> Int
displayOpsColumns DisplayOps
ops
| forall a. Vector a -> Int
Vector.length DisplayOps
ops forall a. Eq a => a -> a -> Bool
== Int
0 = Int
0
| Bool
otherwise = forall a. Vector a -> Int
Vector.length forall a b. (a -> b) -> a -> b
$ forall a. Vector a -> a
Vector.head DisplayOps
ops
displayOpsRows :: DisplayOps -> Int
displayOpsRows :: DisplayOps -> Int
displayOpsRows = forall a. Vector a -> Int
Vector.length
affectedRegion :: DisplayOps -> DisplayRegion
affectedRegion :: DisplayOps -> DisplayRegion
affectedRegion DisplayOps
ops = (DisplayOps -> Int
displayOpsColumns DisplayOps
ops, DisplayOps -> Int
displayOpsRows DisplayOps
ops)
spanOpsAffectedColumns :: SpanOps -> Int
spanOpsAffectedColumns :: SpanOps -> Int
spanOpsAffectedColumns SpanOps
inOps = forall a b. (a -> b -> a) -> a -> Vector b -> a
Vector.foldl' Int -> SpanOp -> Int
spanOpsAffectedColumns' Int
0 SpanOps
inOps
where
spanOpsAffectedColumns' :: Int -> SpanOp -> Int
spanOpsAffectedColumns' Int
t (TextSpan Attr
_ Int
w Int
_ Text
_ ) = Int
t forall a. Num a => a -> a -> a
+ Int
w
spanOpsAffectedColumns' Int
t (Skip Int
w) = Int
t forall a. Num a => a -> a -> a
+ Int
w
spanOpsAffectedColumns' Int
t (RowEnd Int
w) = Int
t forall a. Num a => a -> a -> a
+ Int
w
spanOpHasWidth :: SpanOp -> Maybe (Int, Int)
spanOpHasWidth :: SpanOp -> Maybe DisplayRegion
spanOpHasWidth (TextSpan Attr
_ Int
ow Int
cw Text
_) = forall a. a -> Maybe a
Just (Int
cw, Int
ow)
spanOpHasWidth (Skip Int
ow) = forall a. a -> Maybe a
Just (Int
ow,Int
ow)
spanOpHasWidth (RowEnd Int
ow) = forall a. a -> Maybe a
Just (Int
ow,Int
ow)
columnsToCharOffset :: Int -> SpanOp -> Int
columnsToCharOffset :: Int -> SpanOp -> Int
columnsToCharOffset Int
cx (TextSpan Attr
_ Int
_ Int
_ Text
utf8Str) =
Text -> Int
wctlwidth (Int64 -> Text -> Text
TL.take (forall a b. (Integral a, Num b) => a -> b
fromIntegral Int
cx) Text
utf8Str)
columnsToCharOffset Int
cx (Skip Int
_) = Int
cx
columnsToCharOffset Int
cx (RowEnd Int
_) = Int
cx