diff --git a/.gitignore b/.gitignore index b77faa4..a904d9a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,8 @@ examples/colors +examples/styles examples/animals examples/tui-menu generated-docs/ -format.sh \ No newline at end of file +format.sh diff --git a/examples/styles.roc b/examples/styles.roc new file mode 100644 index 0000000..6795cf2 --- /dev/null +++ b/examples/styles.roc @@ -0,0 +1,19 @@ +app "styles" + packages { + cli: "https://github.com/roc-lang/basic-cli/releases/download/0.8.1/x8URkvfyi9I0QhmVG98roKBUs_AZRkLFwFJVJ3942YA.tar.br", + ansi: "../package/main.roc", + } + imports [cli.Stdout, ansi.Core] + provides [main] to cli + +main = + [ + "Bold On" |> Core.withStyle [Bold On] |> Core.withStyle [Default], + "Italicized On" |> Core.withStyle [Italicized On] |> Core.withStyle [Default], + "Strikethrough On" |> Core.withStyle [Strikethrough On] |> Core.withStyle [Default], + "Underlined On" |> Core.withStyle [Underlined On] |> Core.withStyle [Default], + "Combination" |> Core.withStyle [Bold On, Italicized On, Strikethrough On, Underlined On], + "This should have the last style", + ] + |> Str.joinWith "\n" + |> Stdout.line diff --git a/examples/tui-menu.roc b/examples/tui-menu.roc index e73082d..a9b9c93 100644 --- a/examples/tui-menu.roc +++ b/examples/tui-menu.roc @@ -1,6 +1,6 @@ app "tui-menu" packages { - pf: "https://github.com/roc-lang/basic-cli/releases/download/0.7.0/bkGby8jb0tmZYsy2hg1E_B2QrCgcSTxdUlHtETwm5m4.tar.br", + pf: "https://github.com/roc-lang/basic-cli/releases/download/0.8.1/x8URkvfyi9I0QhmVG98roKBUs_AZRkLFwFJVJ3942YA.tar.br", ansi: "../package/main.roc", } imports [ @@ -8,7 +8,7 @@ app "tui-menu" pf.Stdin, pf.Tty, pf.Task.{ Task }, - ansi.Core.{ Color, Input, ScreenSize, Position, DrawFn }, + ansi.Core.{ Control, Color, Input, ScreenSize, Position, DrawFn }, pf.Utc.{ Utc }, ] provides [main] to pf @@ -151,7 +151,7 @@ getTerminalSize : Task ScreenSize [] getTerminalSize = # Move the cursor to bottom right corner of terminal - cmd = [SetCursor { row: 999, col: 999 }, GetCursor] |> List.map Core.toStr |> Str.joinWith "" + cmd = [MoveCursor (To { row: 999, col: 999 }), GetCursor] |> List.map Control |> List.map Core.toStr |> Str.joinWith "" {} <- Stdout.write cmd |> Task.await # Read the cursor position @@ -167,7 +167,7 @@ homeScreen = \model -> Core.drawText " Choose your Thing" { r: 1, c: 1, fg: Green }, Core.drawText "RUN" { r: 2, c: 11, fg: Blue }, Core.drawText "QUIT" { r: 2, c: 26, fg: Red }, - Core.drawText " ENTER TO RUN, ESCAPE TO QUIT" { r: 2, c: 1, fg: Gray }, + Core.drawText " ENTER TO RUN, ESCAPE TO QUIT" { r: 2, c: 1, fg: White }, Core.drawBox { r: 0, c: 0, w: model.screen.width, h: model.screen.height }, ], { selected, s, row } <- model |> mapSelected |> List.map @@ -185,7 +185,7 @@ confirmScreen = \state -> [ Core.drawText " Would you like to do something?" { r: 1, c: 1, fg: Yellow }, Core.drawText "CONFIRM" { r: 2, c: 11, fg: Blue }, Core.drawText "RETURN" { r: 2, c: 30, fg: Red }, - Core.drawText " ENTER TO CONFIRM, ESCAPE TO RETURN" { r: 2, c: 1, fg: Gray }, + Core.drawText " ENTER TO CONFIRM, ESCAPE TO RETURN" { r: 2, c: 1, fg: White }, Core.drawText " count: TBC" { r: 3, c: 1 }, Core.drawText " speed: TBC" { r: 4, c: 1 }, Core.drawText " size: TBC" { r: 5, c: 1 }, @@ -196,7 +196,7 @@ debugScreen : Model -> List DrawFn debugScreen = \state -> cursorStr = "CURSOR R$(Num.toStr state.cursor.row), C$(Num.toStr state.cursor.col)" screenStr = "SCREEN H$(Num.toStr state.screen.height), W$(Num.toStr state.screen.width)" - inputDelatStr = "DELTA $(Num.toStr (Utc.deltaAsMillis state.prevDraw state.currDraw)) millis" + inputDeltaStr = "DELTA $(Num.toStr (Utc.deltaAsMillis state.prevDraw state.currDraw)) millis" lastInput = state.inputs |> List.last @@ -206,9 +206,9 @@ debugScreen = \state -> [ Core.drawText lastInput { r: state.screen.height - 5, c: 1, fg: Magenta }, - Core.drawText inputDelatStr { r: state.screen.height - 4, c: 1, fg: Magenta }, + Core.drawText inputDeltaStr { r: state.screen.height - 4, c: 1, fg: Magenta }, Core.drawText cursorStr { r: state.screen.height - 3, c: 1, fg: Magenta }, Core.drawText screenStr { r: state.screen.height - 2, c: 1, fg: Magenta }, - Core.drawVLine { r: 1, c: state.screen.width // 2, len: state.screen.height, fg: Gray }, - Core.drawHLine { c: 1, r: state.screen.height // 2, len: state.screen.width, fg: Gray }, + Core.drawVLine { r: 1, c: state.screen.width // 2, len: state.screen.height, fg: White }, + Core.drawHLine { c: 1, r: state.screen.height // 2, len: state.screen.width, fg: White }, ] diff --git a/package/Core.roc b/package/Core.roc index ed0563b..e918566 100644 --- a/package/Core.roc +++ b/package/Core.roc @@ -1,9 +1,14 @@ interface Core exposes [ # ANSI - Code, + Escape, + Control, toStr, + # Style + Style, + withStyle, + # Color Color, withFg, @@ -30,23 +35,43 @@ interface Core imports [] ## [ANSI Escape Codes](https://en.wikipedia.org/wiki/ANSI_escape_code) -Code : [ + +Escape : [ Reset, + Control Control, +] + +Control : [ + MoveCursor + [ + Step [Up, Down, Left, Right] I32, + To { row : I32, col : I32 }, + Home, + Line [Next, Prev] I32, + ], + Erase [Display [ToEnd, ToStart, All], Line [ToEnd, ToStart, All]], ClearScreen, GetCursor, - SetCursor { row : I32, col : I32 }, + Scroll [Up, Down] I32, + Style Style, SetFgColor Color, SetBgColor Color, - EraseToEnd, - EraseFromStart, - EraseLine, - MoveCursorHome, - MoveCursor [Up, Down, Left, Right] I32, - MoveCursorNextLine, - MoveCursorPrevLine, +] + +Style : [ + Default, + Bold [On, Off], + Italicized [On, Off], + Strikethrough [On, Off], + Underlined [On, Off], + Color (ColorName, ColorScheme, ColorIntensity), ] ## 8-bit colors supported on *most* modern terminal emulators +ColorName : [Black, Red, Green, Yellow, Blue, Magenta, Cyan, White, Default] +ColorScheme : [Fg, Bg] +ColorIntensity : [Regular, Bright] # for terminals which support axiterm specification + Color : [ Black, Red, @@ -55,97 +80,178 @@ Color : [ Blue, Magenta, Cyan, - Gray, + White, BrightBlack, # for terminals which support axiterm specification - BrightRed, # for terminals which support axiterm specification - BrightGreen, # for terminals which support axiterm specification - BrightYellow, # for terminals which support axiterm specification - BrightBlue, # for terminals which support axiterm specification - BrightMagenta, # for terminals which support axiterm specification - BrightCyan, # for terminals which support axiterm specification - BrightWhite, # for terminals which support axiterm specification + BrightRed, + BrightGreen, + BrightYellow, + BrightBlue, + BrightMagenta, + BrightCyan, + BrightWhite, Default, ] -# ESC character -esc : Str -esc = "\u(001b)" - -toStr : Code -> Str -toStr = \code -> - when code is - Reset -> "$(esc)c" - ClearScreen -> "$(esc)[3J" - GetCursor -> "$(esc)[6n" - SetCursor { row, col } -> "$(esc)[$(Num.toStr row);$(Num.toStr col)H" +toStr = \x -> "\u(001b)$(escapeToStrHelp x)" + +escapeToStrHelp : Escape -> Str +escapeToStrHelp = \escape -> + when escape is + Reset -> "c" + Control control -> "[$(controlToStrHelp control)" + +controlToStrHelp : Control -> Str +controlToStrHelp = \control -> + when control is + GetCursor -> "6n" + MoveCursor x -> + when x is + Step direction steps -> + when direction is + Up -> "$(Num.toStr steps)A" + Down -> "$(Num.toStr steps)B" + Right -> "$(Num.toStr steps)C" + Left -> "$(Num.toStr steps)D" + + Line direction lines -> + when direction is + Next -> "$(Num.toStr lines)E" + Prev -> "$(Num.toStr lines)F" + + Home -> "H" + To { row, col } -> "$(Num.toStr row);$(Num.toStr col)H" + + Erase x -> + when x is + Display d -> + when d is + ToEnd -> "0J" + ToStart -> "1J" + All -> "2J" + + Line l -> + when l is + ToEnd -> "0K" + ToStart -> "1K" + All -> "2K" + + ClearScreen -> "3J" + Scroll direction lines -> + when direction is + Up -> "$(Num.toStr lines)S" + Down -> "$(Num.toStr lines)T" + SetFgColor color -> fromFgColor color SetBgColor color -> fromBgColor color - EraseToEnd -> "$(esc)[0K" - EraseFromStart -> "$(esc)[1K" - EraseLine -> "$(esc)[2K" - MoveCursorHome -> "$(esc)[H" - MoveCursorNextLine -> "$(esc)[1E" - MoveCursorPrevLine -> "$(esc)[1F" - MoveCursor direction steps -> - when direction is - Up -> "$(esc)[$(Num.toStr steps)A" - Down -> "$(esc)[$(Num.toStr steps)B" - Right -> "$(esc)[$(Num.toStr steps)C" - Left -> "$(esc)[$(Num.toStr steps)D" + Style style -> "$(Num.toStr (styleToStrHelp style))m" + +styleToStrHelp = \style -> + when style is + Default -> 0 + Bold b -> + when b is + On -> 1 + Off -> 22 + + Italicized i -> + when i is + On -> 3 + Off -> 23 + + Strikethrough s -> + when s is + On -> 9 + Off -> 29 + + Underlined u -> + when u is + On -> 4 + Off -> 24 + + Color (name, scheme, intensity) -> + fromColorIntensityScheme intensity scheme + |> Num.add (fromColorName name) + +fromColorName : ColorName -> U8 +fromColorName = \name -> + when name is + Black -> 0 + Red -> 1 + Green -> 2 + Yellow -> 3 + Blue -> 4 + Magenta -> 5 + Cyan -> 6 + White -> 7 + Default -> 9 + +fromColorIntensityScheme : ColorIntensity, ColorScheme -> U8 +fromColorIntensityScheme = \intensity, scheme -> + when (intensity, scheme) is + (Regular, Fg) -> 30 + (Regular, Bg) -> 40 + (Bright, Fg) -> 90 + (Bright, Bg) -> 100 fromFgColor : Color -> Str fromFgColor = \color -> when color is - Black -> "$(esc)[30m" - Red -> "$(esc)[31m" - Green -> "$(esc)[32m" - Yellow -> "$(esc)[33m" - Blue -> "$(esc)[34m" - Magenta -> "$(esc)[35m" - Cyan -> "$(esc)[36m" - Gray -> "$(esc)[37m" - Default -> "$(esc)[39m" - BrightBlack -> "$(esc)[90m" - BrightRed -> "$(esc)[91m" - BrightGreen -> "$(esc)[92m" - BrightYellow -> "$(esc)[93m" - BrightBlue -> "$(esc)[94m" - BrightMagenta -> "$(esc)[95m" - BrightCyan -> "$(esc)[96m" - BrightWhite -> "$(esc)[97m" + Black -> "30m" + Red -> "31m" + Green -> "32m" + Yellow -> "33m" + Blue -> "34m" + Magenta -> "35m" + Cyan -> "36m" + White -> "37m" + Default -> "39m" + BrightBlack -> "90m" + BrightRed -> "91m" + BrightGreen -> "92m" + BrightYellow -> "93m" + BrightBlue -> "94m" + BrightMagenta -> "95m" + BrightCyan -> "96m" + BrightWhite -> "97m" fromBgColor : Color -> Str fromBgColor = \color -> when color is - Black -> "$(esc)[40m" - Red -> "$(esc)[41m" - Green -> "$(esc)[42m" - Yellow -> "$(esc)[43m" - Blue -> "$(esc)[44m" - Magenta -> "$(esc)[45m" - Cyan -> "$(esc)[46m" - Gray -> "$(esc)[47m" - Default -> "$(esc)[49m" - BrightBlack -> "$(esc)[100m" - BrightRed -> "$(esc)[101m" - BrightGreen -> "$(esc)[102m" - BrightYellow -> "$(esc)[103m" - BrightBlue -> "$(esc)[104m" - BrightMagenta -> "$(esc)[105m" - BrightCyan -> "$(esc)[106m" - BrightWhite -> "$(esc)[107m" + Black -> "40m" + Red -> "41m" + Green -> "42m" + Yellow -> "43m" + Blue -> "44m" + Magenta -> "45m" + Cyan -> "46m" + White -> "47m" + Default -> "49m" + BrightBlack -> "100m" + BrightRed -> "101m" + BrightGreen -> "102m" + BrightYellow -> "103m" + BrightBlue -> "104m" + BrightMagenta -> "105m" + BrightCyan -> "106m" + BrightWhite -> "107m" + +## Adds style to a Str and then resets to Default +withStyle : Str, List Style -> Str +withStyle = \str, styles -> "$(styles |> List.map Style |> List.map Control |> List.map toStr |> Str.joinWith (""))$(str)" + +resetStyle = toStr (Control (Style (Default))) ## Adds foreground color formatting to a Str and then resets to Default withFg : Str, Color -> Str -withFg = \str, color -> "$(toStr (SetFgColor color))$(str)$(esc)[0m" +withFg = \str, color -> "$(toStr (Control (SetFgColor color)))$(str)$(resetStyle)" ## Adds background color formatting to a Str and then resets to Default withBg : Str, Color -> Str -withBg = \str, color -> "$(toStr (SetBgColor color))$(str)$(esc)[0m" +withBg = \str, color -> "$(toStr (Control (SetBgColor color)))$(str)$(resetStyle)" ## Adds color formatting to a Str and then resets to Default withColor : Str, { fg : Color, bg : Color } -> Str -withColor = \str, colors -> "$(toStr (SetFgColor colors.fg))$(toStr (SetBgColor colors.bg))$(str)$(esc)[0m" +withColor = \str, colors -> "$(toStr (Control (SetFgColor colors.fg)))$(toStr (Control (SetBgColor colors.bg)))$(str)$(resetStyle)" Key : [ Up, @@ -599,7 +705,7 @@ joinPixelRow = \{ char, fg, bg, lines }, pixelRow, row -> line = rowStrs |> Str.joinWith "" # Set cursor at the start of line we want to draw - |> Str.withPrefix (toStr (SetCursor { row: Num.toI32 (row + 1), col: 0 })) + |> Str.withPrefix (toStr (Control (MoveCursor (To { row: Num.toI32 (row + 1), col: 0 })))) { char: " ", fg: prev.fg, bg: prev.bg, lines: List.append lines line } @@ -608,13 +714,13 @@ joinPixels = \{ rowStrs, prev }, curr -> pixelStr = # Prepend an ASCII escape ONLY if there is a change between pixels curr.char - |> \str -> if curr.fg != prev.fg then Str.concat (toStr (SetFgColor curr.fg)) str else str - |> \str -> if curr.bg != prev.bg then Str.concat (toStr (SetBgColor curr.bg)) str else str + |> \str -> if curr.fg != prev.fg then Str.concat (toStr (Control (SetFgColor curr.fg))) str else str + |> \str -> if curr.bg != prev.bg then Str.concat (toStr (Control (SetBgColor curr.bg))) str else str { rowStrs: List.append rowStrs pixelStr, prev: curr } drawBox : { r : I32, c : I32, w : I32, h : I32, fg ? Color, bg ? Color, char ? Str } -> DrawFn -drawBox = \{ r, c, w, h, fg ? Gray, bg ? Default, char ? "#" } -> \_, { row, col } -> +drawBox = \{ r, c, w, h, fg ? White, bg ? Default, char ? "#" } -> \_, { row, col } -> startRow = r endRow = (r + h) @@ -647,10 +753,8 @@ drawHLine = \{ r, c, len, fg ? Default, bg ? Default, char ? "-" } -> \_, { row, Err {} drawCursor : { fg ? Color, bg ? Color, char ? Str } -> DrawFn -drawCursor = \{ fg ? Default, bg ? Gray, char ? " " } -> \cursor, { row, col } -> - if - (row == cursor.row) && (col == cursor.col) - then +drawCursor = \{ fg ? Default, bg ? White, char ? " " } -> \cursor, { row, col } -> + if (row == cursor.row) && (col == cursor.col) then Ok { char, fg, bg } else Err {}