{-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE OverloadedStrings #-} {- | A 'ColorString' is a multicolored 'Text'.-} module Imj.Graphics.Text.ColorString ( -- * Type ColorString(..) -- * Constructors {- | 'colored' creates a 'ColorString' using the specified foreground color on /black/ background, wherease 'colored'' allows you to chose both the background and the foreground colors. And since 'ColorString' is 'Monoid', we can write: @ str = colored \"Hello\" white <> colored \" World\" yellow @ -} , colored , colored' -- * Utilities , countChars -- * Reexports , LayeredColor(..) ) where import Imj.Prelude import Data.String(IsString(..)) import Data.Text( Text, pack, unpack, length ) import qualified Data.List as List(length) import Imj.Graphics.Class.DiscreteInterpolation import Imj.Graphics.Color.Types import Imj.Graphics.Text.ColorString.Interpolation import Imj.Util newtype ColorString = ColorString [(Text, LayeredColor)] deriving(Show) instance IsString ColorString where fromString str = ColorString [(pack str, onBlack white)] -- TODO maybe it would be faster to have a representation with Array (Char, LayeredColor) -- (ie the result of simplify) -- | First interpolating characters, then color. instance DiscreteDistance ColorString where distance c1 c2 = let colorDist (_, color) (_, color') = distance color color' n1 = countChars c1 n2 = countChars c2 s1 = simplify c1 s2 = simplify c2 (c1', remaining) = interpolateChars s1 s2 countTextChanges s1' = assert (remaining == 0) c1' l = zipWith colorDist s1' s2 -- since color interpolation happends AFTER char changes, -- we compare colors with result of char interpolation colorDistance = if null l then 1 else maximum l toString = map fst str1 = toString s1 str2 = toString s2 lPref = List.length $ commonPrefix str1 str2 lSuff = List.length $ commonSuffix (drop lPref str1) (drop lPref str2) countTextChanges = max n1 n2 - (lPref + lSuff) in colorDistance + countTextChanges -- | First interpolating characters, then color. instance DiscreteInterpolation ColorString where interpolate c1 c2 i = let c2' = simplify c2 (c1', remaining) = interpolateChars (simplify c1) c2' i in ColorString $ map (\(char,color) -> (pack [char], color)) $ if remaining >= 0 then c1' else interpolateColors c1' c2' (negate remaining) interpolateColors :: [(Char, LayeredColor)] -- ^ from ->[(Char, LayeredColor)] -- ^ to -> Int -- ^ progress -> [(Char, LayeredColor)] interpolateColors c1 c2 i = let z (_, color) (char, color') = (char, interpolate color color' i) in zipWith z c1 c2 -- | Maps a 'ColorString' to a list of 'Char' and 'LayeredColor'. -- It is used to simplify the implementation of some interpolation algorithms simplify :: ColorString -> [(Char, LayeredColor)] simplify (ColorString []) = [] simplify (ColorString l@(_:_)) = let (txt, color) = head l in map (\c -> (c,color)) (unpack txt) ++ simplify (ColorString $ tail l) colored' :: Text -> LayeredColor -> ColorString colored' t c = ColorString [(t, c)] colored :: Text -> Color8 Foreground -> ColorString colored t c = colored' t $ onBlack c -- | Counts the chars in the 'ColorString' countChars :: ColorString -> Int countChars (ColorString cs) = sum $ map (Data.Text.length . fst) cs instance Monoid ColorString where mempty = ColorString [("", onBlack white)] mappend (ColorString x) (ColorString y) = ColorString $ x ++ y