Drafter.Style (drafter v0.3.2)

Copy Markdown View Source

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

border_style()

@type border_style() :: :none | :solid | :dashed | :double | :rounded | :heavy

color()

@type color() :: rgb() | rgba() | String.t() | atom()

dimension()

@type dimension() ::
  non_neg_integer() | :auto | {:percent, number()} | {:fr, number()}

margin()

@type margin() :: padding()

padding()

rgb()

rgba()

@type rgba() :: {:rgba, rgb(), float()}

t()

@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

adjust(arg, adjustment)

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}

darken(color, amount)

@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}

get(style, property, default \\ nil)

@spec get(t(), atom(), term()) :: term()

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

get_margin(style)

@spec get_margin(map()) :: {integer(), integer(), integer(), integer()}

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}

get_padding(style)

@spec get_padding(map()) :: {integer(), integer(), integer(), integer()}

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}

lighten(color, amount)

@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}

merge(styles)

@spec merge([t()]) :: t()

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([])
%{}

merge(base, override)

@spec merge(t(), t() | nil) :: t()

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}

mix(arg1, arg2, ratio \\ 0.5)

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}

new(props \\ %{})

@spec new(map()) :: t()

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()
%{}

normalize_class(class)

@spec normalize_class(String.t() | atom()) :: atom()

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

normalize_classes(classes)

@spec normalize_classes([String.t() | atom()] | String.t() | atom()) :: [atom()]

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([])
[]

put(style, property, value)

@spec put(t(), atom(), term()) :: t()

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)
%{}

resolve_color(hex_color, theme)

@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.

to_segment_style(style, theme \\ nil)

@spec to_segment_style(map(), map() | nil) :: map()

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(%{})
%{}

with_alpha(arg, alpha)

@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.