Styling

Styling in the terminal has historically been quite confusing. Due to terminals being very old, there have been added multiple ways of styling, without the ability to remove legacy styling options due to backwards compatibility.

See also

A list for all styling nodes can be found here: Nodes List.

3 Color Formats

4 bit color

One consequence of terminal history is that there are three ways of doing colors. The oldest, and most widely supported way is the 4 bit colors. Colors specified with this format render differently depending on terminal theme! This color format is accessible with the Color4 enum. Below follows a chart with all colors available with this enum.

BLACK   BRIGHT_BLACK  
RED     BRIGHT_RED    
GREEN   BRIGHT_GREEN  
YELLOW  BRIGHT_YELLOW 
BLUE    BRIGHT_BLUE   
MAGENTA BRIGHT_MAGENTA
CYAN    BRIGHT_CYAN   
WHITE   BRIGHT_WHITE  
RESET

Important

Color4 has a RESET attribute. It represents your terminal’s default foreground or backround color.

XTERM-256

Another widely supported color format is XTERM-256 (8 bit colors) This format is also very widely supported. To access this format you just simply use an int anywhere a color is needed. Which integer corresponds to which color can be viewed in the figure below.

Regular and bright colors are rendered differently depending on terminal theme
Regular:     0    1    2    3    4    5    6    7 
Bright:      8    9   10   11   12   13   14   15 

Color Cube
  16   17   18   19   20   21   22   23   24   25   26   27   28   29   30   31   32   33 
  52   53   54   55   56   57   58   59   60   61   62   63   64   65   66   67   68   69 
  88   89   90   91   92   93   94   95   96   97   98   99  100  101  102  103  104  105 
 124  125  126  127  128  129  130  131  132  133  134  135  136  137  138  139  140  141 
 160  161  162  163  164  165  166  167  168  169  170  171  172  173  174  175  176  177 
 196  197  198  199  200  201  202  203  204  205  206  207  208  209  210  211  212  213 
  34   35   36   37   38   39   40   41   42   43   44   45   46   47   48   49   50   51 
  70   71   72   73   74   75   76   77   78   79   80   81   82   83   84   85   86   87 
 106  107  108  109  110  111  112  113  114  115  116  117  118  119  120  121  122  123 
 142  143  144  145  146  147  148  149  150  151  152  153  154  155  156  157  158  159 
 178  179  180  181  182  183  184  185  186  187  188  189  190  191  192  193  194  195 
 214  215  216  217  218  219  220  221  222  223  224  225  226  227  228  229  230  231 

Color ramps, black and white intentionally excluded
 232  233  234  235  236  237  238  239  240  241  242  243 
 244  245  246  247  248  249  250  251  252  253  254  255 

Tip

Colors represented by integers 0 through 15 are exactly the same colors as in Color4 enum (except RESET). That enum is actually an IntEnum which means that it’s members are treated as integers. (RESET member is represented as -1)

True Color

Lastly, there is true color (24 bit or rgb color). This color format can be accessed via rgb(), hsl() or hex() functions that create a Color24 object that stores the color. This is the color format that the support is somewhat lacking. Most notably, the curses renderer does not support it.

See also

Different renderers are discussed in I/O Overview.

Color Downgrading

If you are using a color format that is not supported by the renderer (or terminal), it will be automatically downgraded to a supported format, with the new colors trying to match the original as closely as possible.

Styling elements

Functui uses StyleRule objects to represent styles rules. These objects contains a foreground and background color as well as a bunch of StyleAttr flags to represent style attributes like bold or italic. Color can be represented either by an int (for 4 and 8 bit colors) or a Color24 (for 24 bit colors). To use a style rule on a layout you can use push_rule.

from functui import *  # for Rect, StyleRule, StyleAttr, Color4 and rgb
from functui.common import *  # for text, push_rule and border
from functui.io.ansi import layout_to_str

style_rule = StyleRule(
    fg=Color4.BRIGHT_YELLOW,
    bg=rgb(10, 134, 143),
    add_attrs=StyleAttr.ITALIC | StyleAttr.BOLD
    # StyleAttr is a flag which means you
    # can use | to combine muliple attributes.
)

# use style on a layout
layout = text("styled_text") | push_rule(style_rule) | border

print(layout_to_str(layout, Rect(20, 3)))

Expected Output:

┌──────────────────┐
│styled_text       │
└──────────────────┘

Important

As you may notice, only the text was styled even though technically the text node is taking up all of the available space inside the border. This is due to styles being applied only to “printed” characters, rather than the whole area a node takes up. To apply style to the whole node, use bg_fill.

from functui import *
from functui.common import *
from functui.io.ansi import layout_to_str

style_rule = StyleRule(
    fg=Color4.BRIGHT_YELLOW,
    bg=rgb(10, 134, 143),
    add_attrs=StyleAttr.ITALIC | StyleAttr.BOLD
    # StyleAttr is a flag which means you
    # can use | to combine muliple attributes.
)

layout = text("styled_text") | bg_fill | push_rule(style_rule) | border
# bg_fill here ----------------^^^^^^^

print(layout_to_str(layout, Rect(20, 3)))

Expected Output:

┌──────────────────┐
│styled_text       │
└──────────────────┘

rule_*

In some cases though, this syntax of defining style rules can be quite tedious, especially if you only want to define a few style attributes. For those cases, there are numerous rule_* constants and functions.

from functui import *
from functui.common import *

style_rule = rule_fg(Color4.RED) | rule_bold | rule_italic

# much less visual noise when defining styles inline
layout = text("foo") | styled(border, rule_fg(Color4.RED))

Convenient Styling Nodes

Finally, it’s not always that you want to create a new style rule. Especially when it comes to quick prototyping, there are cases when you just want to apply one style and don’t need the ability to reuse it. For those cases use fg and bg wrapper nodes for adding color to foreground and background respectively. To apply styles to a layout there are wrapper nodes that are named as style attributes. For example bold and italic.

from functui import *
from functui.common import *

layout = text("This text is red and bold") | fg(Color4.RED) | bold | border
print(layout_to_str(layout, Rect(30, 3)))

Expected output:

┌────────────────────────────┐
│This text is red and bold   │
└────────────────────────────┘

styled

Notice how these wrapper nodes style all of their descendants with the specified style. If you want to style only certain wrapper nodes (for example a border), you can use styled to wrap nodes you want to style.

from functui import *
from functui.common import *

style_rule = rule_fg(rgb(0, 255, 255))

layout = text("This text is not styled.\nBut the border around it is.")\
    | styled(border, style_rule)

print(layout_to_str(layout, Rect(30, 4)))

Expected output:

┌────────────────────────────┐
│This text is not styled.    │
│But the border around it is.│
└────────────────────────────┘

Rich Text

Sometimes there may be a need for multiple styles in the same paragraph. This can be done with the rich_text node or adaptive_text if you also want that paragraph to be responsive to screen size changes. To style only a part of the paragraph, wrap that part in a span and specify a style_rule.

from functui import *
from functui.common import *
from functui.rich_text import adaptive_text, rich_text, span
from functui.io.ansi import layout_to_str


layout = adaptive_text(
    "Some of this ",
    span("text", rule=rule_fg(Color4.BRIGHT_RED)),
    " will be ",
    span("styled. ", rule=rule_italic),
    span(
        "Also, it is possible to ",
        span("nest",rule=rule_fg(Color4.BRIGHT_BLACK) | rule_bold),
        " spans!",
        rule=rule_bg(Color4.BLUE)
    )
) | border

print(layout_to_str(layout, Rect(20, 10)))

Expected Output:

┌──────────────────┐
│Some of this text │
│will be styled.   │
│Also, it is       │
│possible to nest  │
│spans!            │
│                  │
│                  │
│                  │
└──────────────────┘