# `Owl.Spinner`
[🔗](https://github.com/fuelen/owl/blob/v0.13.1/lib/owl/spinner.ex#L1)

A spinner widget.

Simply run any long-running task using `run/2`:

    Owl.Spinner.run(
      fn -> Process.sleep(5_000) end,
      labels: [ok: "Done", error: "Failed", processing: "Please wait..."]
    )

Multiple spinners can be run simultaneously:

    long_running_tasks =
      Enum.map([9000, 8000, 4000, 6000], fn delay ->
        fn -> Process.sleep(delay) end
      end)

    long_running_tasks
    |> Task.async_stream(&Owl.Spinner.run/1, timeout: :infinity)
    |> Stream.run()

Multi-line frames are supported as well:

    Owl.Spinner.run(fn -> Process.sleep(5_000) end,
      frames: [
        processing: [
          "╔════╤╤╤╤════╗\n║    │││ \\   ║\n║    │││  O  ║\n║    OOO     ║",
          "╔════╤╤╤╤════╗\n║    ││││    ║\n║    ││││    ║\n║    OOOO    ║",
          "╔════╤╤╤╤════╗\n║   / │││    ║\n║  O  │││    ║\n║     OOO    ║",
          "╔════╤╤╤╤════╗\n║    ││││    ║\n║    ││││    ║\n║    OOOO    ║"
        ]
      ]
    )

### Where can I get alternative frames?

* https://github.com/blackode/elixir_cli_spinners/blob/master/lib/cli_spinners/spinners.ex
* https://www.google.com/search?q=ascii+spinners

# `frame`

```elixir
@type frame() :: Owl.Data.t()
```

# `id`

```elixir
@type id() :: any()
```

# `label`

```elixir
@type label() :: Owl.Data.t()
```

# `run`

```elixir
@spec run(process_function :: (-&gt; :ok | :error | {:ok, value} | {:error, reason}),
  refresh_every: non_neg_integer(),
  frames: [ok: frame(), error: frame(), processing: [frame()]],
  labels: [
    ok: label() | (nil | value -&gt; label() | nil) | nil,
    error: label() | (nil | reason -&gt; label()) | nil,
    processing: label() | nil
  ],
  live_screen_server: GenServer.server()
) :: :ok | :error | {:ok, value} | {:error, reason}
when value: any(), reason: any()
```

Runs a spinner during execution of `process_function` and returns its result.

The spinner is started and automatically stopped after the function returns, regardless of whether there was an error when executing the function.
It is a wrapper around `start/1` and `stop/1`. The only downside of `run/2` is that it is not possible to update
a label while `process_function` is executing.

If function returns `:ok` or `{:ok, value}` then spinner will be stopped with `:ok` resolution.

If function returns `:error` or `{:error, reason}` then spinner will be stopped with `:error` resolution.

## Options

* `:refresh_every` - period of changing frames. Defaults to `100`.
* `:frames` - allows to set frames for different states of spinner:
  * `:processing` - list of frames which are rendered until spinner is stopped.
  Defaults to `["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]`.
  * `:ok` - frame that is rendered when spinner is stopped with `:ok` resolution.
  Defaults to `Owl.Data.tag("✔", :green)`.
  * `:error` - frame that is rendered when spinner is stopped with `:error` resolution.
  Defaults to `Owl.Data.tag("✖", :red)`.
* `:labels` - allows to set labels for different states of spinner:
  * `:processing` - label that is rendered during processing. Cannot be changed during execution of `process_function`.
  Defaults to `nil`.
  * `:ok` - label that is rendered when spinner is stopped with `:ok` resolution. A function with arity 1 can be
  passed in order to format a label based on result of `process_function`.
  Defaults to `nil`.
  * `:error` - label that is rendered when spinner is stopped with `:error` resolution. A function with arity 1
  can be passed in order to format a label based on result of `process_function`.
  Defaults to `nil`.
* `:live_screen_server` - a reference to `Owl.LiveScreen` server. Defaults to `Owl.LiveScreen`.

## Examples

    Owl.Spinner.run(fn -> Process.sleep(5_000) end)
    => :ok

    Owl.Spinner.run(fn -> Process.sleep(5_000) end,
      frames: [
        # an ASCII fish going back and forth
        processing: [
          ">))'>",
          "    >))'>",
          "        >))'>",
          "    <'((<",
          "<'((<"
        ]
      ]
    )
    => :ok

    Owl.Spinner.run(
      fn ->
        Process.sleep(5_000)
        {:error, :oops}
      end,
      labels: [
        error: fn reason -> "Failed: #{inspect(reason)}" end,
        processing: "Processing..."
      ]
    )
    => {:error, :oops}

# `start`

```elixir
@spec start(
  id: id(),
  frames: [ok: frame(), error: frame(), processing: [frame()]],
  labels: [ok: label() | nil, error: label() | nil, processing: label() | nil],
  refresh_every: non_neg_integer(),
  live_screen_server: GenServer.server()
) :: DynamicSupervisor.on_start_child()
```

Starts a new spinner.

Must be stopped manually by calling `stop/1`.

## Options

* `:id` - an id of the spinner. Required.
* `:refresh_every` - period of changing frames. Defaults to `100`.
* `:frames` - allows to set frames for different states of spinner:
  * `:processing` - list of frames which are rendered until spinner is stopped.
  Defaults to `["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]`.
  * `:ok` - frame that is rendered when spinner is stopped with `:ok` resolution.
  Defaults to `Owl.Data.tag("✔", :green)`.
  * `:error` - frame that is rendered when spinner is stopped with `:error` resolution.
  Defaults to `Owl.Data.tag("✖", :red)`.
* `:labels` - allows to set labels for different states of spinner:
  * `:processing` - label that is rendered during processing. Can be changed with `update_label/1`.
  Defaults to `nil`.
  * `:ok` - label that is rendered when spinner is stopped with `:ok` resolution.
  Defaults to `nil`.
  * `:error` - label that is rendered when spinner is stopped with `:error` resolution.
  Defaults to `nil`.
* `:live_screen_server` - a reference to `Owl.LiveScreen` server. Defaults to `Owl.LiveScreen`.

## Example

    Owl.Spinner.start(id: :my_spinner)
    Process.sleep(1000)
    Owl.Spinner.stop(id: :my_spinner, resolution: :ok)

# `stop`

```elixir
@spec stop(id: id(), resolution: :ok | :error, label: label()) :: :ok
```

Stops the spinner.

## Options

* `:id` - an id of the spinner. Required.
* `:resolution` - an atom `:ok` or `:error`. Determines frame and label for final rendering. Required.
* `:label` - a label for final rendering. If not set, then values that are set on spinner start will be used.

## Example

    Owl.Spinner.stop(id: :my_spinner, resolution: :ok)

# `update_label`

```elixir
@spec update_label(id: id(), label: label()) :: :ok
```

Updates a label of the running spinner.

Overrides a value that is set for `:processing` state on start.

## Options

* `:id` - an id of the spinner. Required.
* `:label` - a new value of the label. Required.

## Example

    Owl.Spinner.start(id: :my_spinner)
    Owl.Spinner.update_label(id: :my_spinner, label: "Downloading files...")
    Process.sleep(1000)
    Owl.Spinner.update_label(id: :my_spinner, label: "Checking signatures...")
    Process.sleep(1000)
    Owl.Spinner.stop(id: :my_spinner, resolution: :ok, label: "Done")

---

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