lib/scenic/primitive/group.ex

#
#  Created by Boyd Multerer on 2017-05-06.
#  Copyright © 2017-2021 Kry10 Limited. All rights reserved.
#

defmodule Scenic.Primitive.Group do
  @moduledoc """
  A container to hold other primitives.

  Any styles placed on a group will be inherited by the primitives in the 
  group. Any transforms placed on a group will be multiplied into the transforms
  in the primitives in the group.

  ## Data

  `uids`

  The data for an arc is a list of internal uids for the primitives it contains.

  You will not typically add these ids yourself. You should use the helper functions
  with a callback to do that for you. See Usage below.

  ## Styles

  The group is special in that it accepts all styles and transforms, even if they
  are non-standard. These are then inherited by any primitives, including SceneRefs.

  Any styles you place on the group itself will be inherited by the primitives
  contained in the group. However, these styles will not be inherited by any
  component in the group.

  ## Transforms

  If you add a transform to a group, then everything in the group will also be
  moved by that transform, including child components. This is a very handy way
  to create some UI, then position, scale, or rotate it as needed without having
  to adjust the inner elements.

  ## Usage

  You should add/modify primitives via the helper functions in
  [`Scenic.Primitives`](Scenic.Primitives.html#group/3)

  ```elixir
  graph
    |> group( fn(g) ->
      g
      |> rect( {200, 100}, fill: :blue )
      |> test( "In a Group", fill: :yellow, translate: {20, 40} )
    end,
    translate: {100, 100},
    font: :roboto
  )
  ```

  """

  use Scenic.Primitive
  alias Scenic.Script
  alias Scenic.Primitive
  alias Scenic.Primitive.Style

  #  import IEx

  @type t :: [pos_integer]
  @type styles_t :: [:hidden | :scissor | atom]

  @styles [:hidden, :scissor]

  # ============================================================================
  # data verification and serialization

  @impl Primitive
  @spec validate(ids :: [pos_integer]) ::
          {:ok, ids :: [pos_integer]} | {:error, String.t()}
  def validate(ids) when is_list(ids) do
    case Enum.all?(ids, fn n -> is_integer(n) && n >= 0 end) do
      true -> {:ok, ids}
      false -> err_validation(ids)
    end
  end

  def validate(data), do: err_validation(data)

  defp err_validation(data) do
    {
      :error,
      """
      #{IO.ANSI.red()}Invalid Group specification
      Received: #{inspect(data)}
      #{IO.ANSI.yellow()}
      The data for an Group is a list of primitive ids.#{IO.ANSI.default_color()}
      """
    }
  end

  # --------------------------------------------------------
  @doc """
  Returns a list of styles recognized by this primitive.
  """
  @impl Primitive
  @spec valid_styles() :: [:hidden, ...]
  def valid_styles(), do: @styles

  # --------------------------------------------------------
  # compiling a group is a special case and is handled in Scenic.Graph.Compiler
  @doc false
  @impl Primitive
  @spec compile(primitive :: Primitive.t(), styles :: Style.t()) :: Script.t()
  def compile(%Primitive{module: __MODULE__}, _styles) do
    raise "compiling a group is a special case and is handled in Scenic.Graph.Compiler"
  end

  # ============================================================================
  # apis to manipulate the list of child ids

  # ----------------------------------------------------------------------------
  def insert_at(%Primitive{module: __MODULE__, data: uid_list} = p, index, uid) do
    Map.put(
      p,
      :data,
      List.insert_at(uid_list, index, uid)
    )
  end

  # ----------------------------------------------------------------------------
  def delete(%Primitive{module: __MODULE__, data: uid_list} = p, uid) do
    Map.put(
      p,
      :data,
      Enum.reject(uid_list, fn xid -> xid == uid end)
    )
  end

  # ----------------------------------------------------------------------------
  def increment(%Primitive{module: __MODULE__, data: uid_list} = p, offset) do
    Map.put(
      p,
      :data,
      Enum.map(uid_list, fn xid -> xid + offset end)
    )
  end
end