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. .. seealso:: A list for all styling nodes can be found here: :doc:`nodes`. 3 Color Formats --------------- .. _color4: 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 :obj:`~functui.classes.Color4` enum. Below follows a chart with all colors available with this enum. .. raw:: html
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.
.. _color8:
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
:obj:`int` anywhere a color is needed. Which integer corresponds to which color
can be viewed in the figure below.
.. raw:: html
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 :obj:`~functui.classes.Color4` enum (except ``RESET``). That enum is
actually an :obj:`~enum.IntEnum` which means that it's members are treated
as integers. (``RESET`` member is represented as -1)
.. _color24:
True Color
~~~~~~~~~~
Lastly, there is true color (24 bit or rgb color). This color format can be
accessed via :func:`~functui.classes.rgb`, :func:`~functui.classes.hsl` or
:func:`~functui.classes.hex` functions that create a
:obj:`~functui.classes.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.
.. seealso::
Different renderers are discussed in :doc:`io`.
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 :obj:`~functui.classes.StyleRule` objects to represent styles
rules. These objects contains a foreground and background color as well as a
bunch of :obj:`~functui.classes.StyleAttr` flags to represent style attributes
like bold or italic. Color can be represented either by an :obj:`int` (for 4
and 8 bit colors) or a :obj:`~functui.classes.Color24` (for 24 bit colors). To
use a style rule on a layout you can use :obj:`~functui.common.push_rule`.
.. code-block:: py
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:
.. raw:: html
┌──────────────────┐
│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
:obj:`~functui.common.bg_fill`.
.. code-block:: py
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:
.. raw:: html
┌──────────────────┐
│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.
.. code-block:: py
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
:obj:`~functui.common.fg` and :obj:`~functui.common.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
:obj:`~functui.common.bold` and :obj:`~functui.common.italic`.
.. code-block:: py
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:
.. raw:: html
┌────────────────────────────┐
│This text is red and bold │
└────────────────────────────┘
:obj:`~functui.common.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 :obj:`~functui.common.styled` to wrap nodes you want to style.
.. code-block:: py
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:
.. raw:: html
┌────────────────────────────┐
│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 :obj:`~functui.rich_text.rich_text` node or
:obj:`~functui.rich_text.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 :obj:`~functui.rich_text.span` and specify a
:obj:`~functui.classes.style_rule`.
.. code-block:: py
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:
.. raw:: html
┌──────────────────┐
│Some of this text │
│will be styled. │
│Also, it is │
│possible to nest │
│spans! │
│ │
│ │
│ │
└──────────────────┘