3

I know that the Brick and the VTY hackage do not support escape sequences. VTY only supports 240 colors.

Is there any workaround to use true RGB colors and not mess up the layout?

This is an example I made, but I can't get the border right:

module BrickTest where

import Brick                (simpleMain, Widget, str)
import Brick.Widgets.Border (border)
import Text.Printf          (printf)

main :: IO ()
main = simpleMain $ colorWidget (255, 0, 0)

type RGB = (Int, Int, Int)

colorWidget :: RGB -> Widget ()
colorWidget (r, g, b) = border $ str (prefix ++ "a" ++ postfix)
    where
        prefix = printf "\ESC[38;2;%d;%d;%dm" r g b
        postfix = "\ESC[0m"

output:

┌──────────────────┐
│a│
└──────────────────┘

terminal screenshot

duplode
  • 33,731
  • 7
  • 79
  • 150
birneee
  • 623
  • 4
  • 10
  • Brick supports ANSI terminal escape codes, and Vty as well: https://hackage.haskell.org/package/vty-5.29/docs/Graphics-Vty-Attributes-Color.html#t:Color (this is used in Brick with https://hackage.haskell.org/package/brick-0.55/docs/Brick-Themes.html#t:CustomAttr). The ISO colors are used exactly to get rid of the programmer specifying ANSI terminal escape sequences themselves. – Willem Van Onsem Aug 30 '20 at 21:21
  • @WillemVanOnsem but only 240 colors are supported and not 256^3 colors, which is supported by most modern terminal applications. – birneee Aug 30 '20 at 21:31
  • When you can add your own escape codes, you can tell the terminal to do whatever it supports. – Carl Aug 30 '20 at 22:05
  • @Carl escape codes can be inserted, but it always messes up the layout. Because the mentioned hackages do not support escape sequences. That's why I try to find a workaround. – birneee Aug 30 '20 at 23:42

1 Answers1

3

I found a workaround. I managed to implement a function zeroWidthStr that can print any string, and Brick handles it as if it has width 0. But I can't really explain how this is working internally, and it might have some other side effects.

module BrickTest where

import           Brick                       (Widget, raw, simpleMain, str,
                                              (<+>))
import           Brick.Widgets.Border        (border)
import           Data.List                   (intercalate)
import           Data.Text.Lazy              (pack)
import           Graphics.Vty                (defAttr)
import           Graphics.Vty.Image.Internal (Image (HorizText))
import           Text.Printf                 (printf)

main :: IO ()
main = simpleMain $ colorWidget (255, 0, 0)

type RGB = (Int, Int, Int)

colorWidget :: RGB -> Widget ()
colorWidget (r, g, b) = border $ prefix <+> str "a" <+> postfix
    where
        prefix = zeroWidthStr $ printf "\ESC[38;2;%d;%d;%dm" r g b
        postfix = zeroWidthStr $ "\ESC[0m"

zeroWidthStr :: String -> Widget ()
-- | workaround to print any string in terminal, and hackage Brick (vty) handles it as if it has width 0
zeroWidthStr str = raw image
    where
        image = HorizText defAttr (pack modStr) 0 0
        modStr = str ++ repeatN "\ESC\ESCa" (length str)
        repeatN :: String -> Int -> String
        repeatN str n = intercalate "" $ take n $ repeat str

output:

terminal screenshot

birneee
  • 623
  • 4
  • 10
  • I think you can use `raw` with `HorizText`, but instead of this `modStr` stuff, use `HorizText defAttr (pack str) outputWidth charWidth` where `str` contains the escape sequences, but `outputWidth` and `charWidth` are computed from `str` with the escape sequences *stripped*, using e.g. `stripEscapes = Graphics.Vty.Image.safe_wcswidth . Data.Text.unpack . Data.String.AnsiEscapeCodes.Strip.Text.stripAnsiEscapeCodes . Data.Text.pack`. Note `charWidth` should be ≥1. I think that’s more semantically appropriate too, as all you’re really doing is setting the display width per the visible chars. – Jon Purdy Aug 31 '20 at 19:31
  • Sorry, that should be `safe_wcswidth . stripEscapes` where `stripEscapes = …` – Jon Purdy Aug 31 '20 at 21:25
  • @JonPurdy do you mean like this? `outputWidth = stripEscapes str; charWidth = outputWidth; stripEscapes = safeWcswidth . unpack . stripAnsiEscapeCodes . pack` – birneee Sep 02 '20 at 16:55
  • I mean `stripEscapes = unpack . stripAnsiEscapeCodes . pack` (or you could add a version that doesn’t have to go through `Text`) and `outputWidth = safe_wcswidth (stripEscapes str)`. But of course you could have the function both strip the escapes and compute the width, and just call it something else, like `getOutputWidth`. – Jon Purdy Sep 02 '20 at 17:25