Color and style utilities for widget rendering.
Provides RGB color manipulation (lighten, darken, blend, interpolate),
type definitions shared across the rendering pipeline, ANSI escape
sequence helpers, and normalisation of the :class option every widget takes.
Summary
Functions
Lighten by a positive adjustment or darken by a negative one.
Subtract amount from each channel, flooring at 0.
Reads property from style, returning default when absent.
Margin from a style map, as {top, right, bottom, left}.
Padding from a style map, as {top, right, bottom, left}.
Add amount to each channel, capping at 255.
Merges a list of style maps left to right, so later entries win.
Merges override onto base, with override winning on conflicts.
Blend two colours channel by channel.
Builds a style map from props, dropping every key that is not a recognised
style property.
The atom form of a single CSS class name.
The atom list form of a widget's :class option.
Sets property to value, silently ignoring properties that are not recognised.
Resolve any colour form to an {r, g, b} triple, or nil.
Converts a style map to the segment style map used by the rendering pipeline.
Flatten a colour against the current theme's background at alpha opacity.
Types
@type border_style() :: :none | :solid | :dashed | :double | :rounded | :heavy
@type dimension() :: non_neg_integer() | :auto | {:percent, number()} | {:fr, number()}
@type margin() :: padding()
@type padding() :: non_neg_integer() | {non_neg_integer(), non_neg_integer()} | {non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer()}
@type rgb() :: {non_neg_integer(), non_neg_integer(), non_neg_integer()}
@type t() :: %{ optional(:color) => color(), optional(:background) => color(), optional(:bold) => boolean(), optional(:dim) => boolean(), optional(:italic) => boolean(), optional(:underline) => boolean(), optional(:reverse) => boolean(), optional(:padding) => padding(), optional(:padding_top) => non_neg_integer(), optional(:padding_right) => non_neg_integer(), optional(:padding_bottom) => non_neg_integer(), optional(:padding_left) => non_neg_integer(), optional(:margin) => margin(), optional(:margin_top) => non_neg_integer(), optional(:margin_right) => non_neg_integer(), optional(:margin_bottom) => non_neg_integer(), optional(:margin_left) => non_neg_integer(), optional(:width) => dimension(), optional(:height) => dimension(), optional(:min_width) => non_neg_integer(), optional(:max_width) => non_neg_integer(), optional(:min_height) => non_neg_integer(), optional(:max_height) => non_neg_integer(), optional(:border) => border_style(), optional(:border_color) => color(), optional(:text_align) => :left | :center | :right, optional(:text_wrap) => :none | :char | :word, optional(:text_overflow) => :clip | :ellipsis, optional(:visibility) => :visible | :hidden, optional(:opacity) => float() }
Functions
@spec adjust({integer(), integer(), integer()}, integer()) :: {non_neg_integer(), non_neg_integer(), non_neg_integer()}
Lighten by a positive adjustment or darken by a negative one.
Takes an {r, g, b} triple only; a slot atom or anything else raises
FunctionClauseError, unlike darken/2 and lighten/2.
Examples
iex> Drafter.Style.adjust({100, 100, 100}, 20)
{120, 120, 120}
iex> Drafter.Style.adjust({100, 100, 100}, -20)
{80, 80, 80}
@spec darken(term(), term()) :: {non_neg_integer(), non_neg_integer(), non_neg_integer()}
Subtract amount from each channel, flooring at 0.
A theme slot atom is resolved first, which needs a running theme manager. A slot
that does not resolve gives {30, 30, 30}, as does any other input — including a
non-integer amount.
Examples
iex> Drafter.Style.darken({100, 50, 10}, 20)
{80, 30, 0}
iex> Drafter.Style.darken("not a colour", 20)
{30, 30, 30}
Reads property from style, returning default when absent.
default is nil when omitted.
Examples
iex> Drafter.Style.get(%{bold: true}, :bold)
true
iex> Drafter.Style.get(%{}, :bold)
nil
iex> Drafter.Style.get(%{}, :bold, false)
false
Margin from a style map, as {top, right, bottom, left}.
Shares the shorthand forms of get_padding/1, reading :margin and the individual
:margin_top, :margin_right, :margin_bottom and :margin_left keys.
Examples
iex> Drafter.Style.get_margin(%{margin: {1, 2}})
{1, 2, 1, 2}
iex> Drafter.Style.get_margin(%{})
{0, 0, 0, 0}
Padding from a style map, as {top, right, bottom, left}.
:padding wins when set: an integer applies to all four sides, a {v, h} pair to
two each, a four-tuple is taken as written. Without it, the individual
:padding_top, :padding_right, :padding_bottom and :padding_left keys are
read, each defaulting to 0.
Examples
iex> Drafter.Style.get_padding(%{padding: 2})
{2, 2, 2, 2}
iex> Drafter.Style.get_padding(%{padding_left: 3})
{0, 0, 0, 3}
iex> Drafter.Style.get_padding(%{})
{0, 0, 0, 0}
@spec lighten(term(), term()) :: {non_neg_integer(), non_neg_integer(), non_neg_integer()}
Add amount to each channel, capping at 255.
As darken/2, but the fallback for anything unresolvable is {100, 100, 100}.
Examples
iex> Drafter.Style.lighten({100, 50, 250}, 20)
{120, 70, 255}
iex> Drafter.Style.lighten("not a colour", 20)
{100, 100, 100}
Merges a list of style maps left to right, so later entries win.
Examples
iex> Drafter.Style.merge([%{bold: true}, %{color: :red}, %{bold: false}])
%{bold: false, color: :red}
iex> Drafter.Style.merge([])
%{}
Merges override onto base, with override winning on conflicts.
A nil override returns base unchanged. Neither side is filtered against the
recognised property list.
Examples
iex> Drafter.Style.merge(%{bold: true, color: :red}, %{color: :blue})
%{bold: true, color: :blue}
iex> Drafter.Style.merge(%{bold: true}, nil)
%{bold: true}
@spec mix( {integer(), integer(), integer()}, {integer(), integer(), integer()}, float() ) :: {non_neg_integer(), non_neg_integer(), non_neg_integer()}
Blend two colours channel by channel.
ratio is the weight given to the second colour and defaults to 0.5. It must
be a float; an integer raises FunctionClauseError.
Examples
iex> Drafter.Style.mix({0, 0, 0}, {100, 200, 255})
{50, 100, 128}
iex> Drafter.Style.mix({0, 0, 0}, {100, 200, 255}, 0.25)
{25, 50, 64}
Builds a style map from props, dropping every key that is not a recognised
style property.
Defaults to %{} when called with no argument. The recognised keys are exactly
the optional keys of t/0.
Examples
iex> Drafter.Style.new(%{bold: true, nonsense: 1})
%{bold: true}
iex> Drafter.Style.new()
%{}
The atom form of a single CSS class name.
An atom is returned as given. A string becomes the existing atom of that name where there is one, and a new atom otherwise, so a class named only in a stylesheet still resolves.
iex> Drafter.Style.normalize_class(:primary)
:primary
iex> Drafter.Style.normalize_class("primary")
:primary
The atom list form of a widget's :class option.
Accepts a list, a single class, a string or an atom, and always returns a list.
iex> Drafter.Style.normalize_classes(["primary", :large])
[:primary, :large]
iex> Drafter.Style.normalize_classes("primary")
[:primary]
iex> Drafter.Style.normalize_classes([])
[]
Sets property to value, silently ignoring properties that are not recognised.
Examples
iex> Drafter.Style.put(%{}, :bold, true)
%{bold: true}
iex> Drafter.Style.put(%{}, :nonsense, true)
%{}
@spec resolve_color(term(), Drafter.Theme.t() | map() | nil) :: {non_neg_integer(), non_neg_integer(), non_neg_integer()} | nil
Resolve any colour form to an {r, g, b} triple, or nil.
Accepts an {r, g, b} triple (returned as is), {:rgba, {r, g, b}, alpha}, a CSS
"#rrggbb", "rgb(...)" or "rgba(...)" string, a theme slot name as an atom, or
that slot name as a string. Alpha is flattened by mixing against the theme's
:background, falling back to black.
theme may be nil, in which case atom slot names resolve against
Drafter.ThemeManager.get_current_theme/0 — which requires a running theme manager.
Unparseable strings and unknown slot names return nil.
Converts a style map to the segment style map used by the rendering pipeline.
Reads the foreground from :fg falling back to :color, and the background
from :bg falling back to :background; both are passed through
resolve_color/2. Copies :bold, :dim, :italic, :underline and
:reverse through unchanged. Keys whose resolved value is nil are omitted
entirely, so the result contains only the attributes that were actually set.
theme defaults to nil, in which case atom colour names resolve against
Drafter.ThemeManager.get_current_theme/0.
Examples
iex> Drafter.Style.to_segment_style(%{fg: {1, 2, 3}, bold: true})
%{fg: {1, 2, 3}, bold: true}
iex> Drafter.Style.to_segment_style(%{})
%{}
@spec with_alpha({integer(), integer(), integer()}, float()) :: {non_neg_integer(), non_neg_integer(), non_neg_integer()}
Flatten a colour against the current theme's background at alpha opacity.
alpha must be a float in 0.0..1.0. Resolves :background through the running
theme manager, falling back to black when it has none.