# `Drafter.Style`
[🔗](https://github.com/jaman/drafter/blob/main/lib/drafter/style.ex#L1)

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.

# `border_style`

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

# `color`

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

# `dimension`

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

# `margin`

```elixir
@type margin() :: padding()
```

# `padding`

```elixir
@type padding() ::
  non_neg_integer()
  | {non_neg_integer(), non_neg_integer()}
  | {non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer()}
```

# `rgb`

```elixir
@type rgb() :: {non_neg_integer(), non_neg_integer(), non_neg_integer()}
```

# `rgba`

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

# `t`

```elixir
@type t() :: %{
  optional(:color) =&gt; color(),
  optional(:background) =&gt; color(),
  optional(:bold) =&gt; boolean(),
  optional(:dim) =&gt; boolean(),
  optional(:italic) =&gt; boolean(),
  optional(:underline) =&gt; boolean(),
  optional(:reverse) =&gt; boolean(),
  optional(:padding) =&gt; padding(),
  optional(:padding_top) =&gt; non_neg_integer(),
  optional(:padding_right) =&gt; non_neg_integer(),
  optional(:padding_bottom) =&gt; non_neg_integer(),
  optional(:padding_left) =&gt; non_neg_integer(),
  optional(:margin) =&gt; margin(),
  optional(:margin_top) =&gt; non_neg_integer(),
  optional(:margin_right) =&gt; non_neg_integer(),
  optional(:margin_bottom) =&gt; non_neg_integer(),
  optional(:margin_left) =&gt; non_neg_integer(),
  optional(:width) =&gt; dimension(),
  optional(:height) =&gt; dimension(),
  optional(:min_width) =&gt; non_neg_integer(),
  optional(:max_width) =&gt; non_neg_integer(),
  optional(:min_height) =&gt; non_neg_integer(),
  optional(:max_height) =&gt; non_neg_integer(),
  optional(:border) =&gt; border_style(),
  optional(:border_color) =&gt; color(),
  optional(:text_align) =&gt; :left | :center | :right,
  optional(:text_wrap) =&gt; :none | :char | :word,
  optional(:text_overflow) =&gt; :clip | :ellipsis,
  optional(:visibility) =&gt; :visible | :hidden,
  optional(:opacity) =&gt; float()
}
```

# `adjust`

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

# `darken`

```elixir
@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`

```elixir
@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`

```elixir
@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`

```elixir
@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`

```elixir
@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`

```elixir
@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`

```elixir
@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`

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

# `new`

```elixir
@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:t/0`.

## Examples

    iex> Drafter.Style.new(%{bold: true, nonsense: 1})
    %{bold: true}

    iex> Drafter.Style.new()
    %{}

# `normalize_class`

```elixir
@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`

```elixir
@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`

```elixir
@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`

```elixir
@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`

```elixir
@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`

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

---

*Consult [api-reference.md](api-reference.md) for complete listing*
