#
# Created by Boyd Multerer on 2021-02-03.
# Copyright © 2021 Kry10 Limited. All rights reserved.
#
defmodule Scenic.Primitive.Script do
@moduledoc """
A reference to a draw script.
The `Script` primitive is used to refer to a script that you created
and loaded into the ViewPort separately from the graph. This script also
has full access to the `Scenic.Script` API.
For example, the check mark shape in the `Checkbox` control is a draw
script that is reference by the checkbox control's graph. A graph
can reference the same script multiple times, which is very efficient
as the script is only sent to the drivers once.
If the graph is modified later, then any scripts it references will not
need to be resent to the drivers. This is an isolation of concerns. The same
is true in reverse. If you rebuild a script and send it to the
`ViewPort`, the script will be sent to the drivers, but any graphs that
reference it do not need to be.
## `Script` vs. `Path`
Both the `Path` and the `Script` primitives use the `Scenic.Script` to create scripts that
are sent to the driver for drawing. The difference is that a Path is far more limited
in what it can do, and is inserted inline with the compiled graph that created it.
The script primitive, on the other hand, has full access to the API set of
`Scenic.Script` and accesses scripts by reference.
The inline vs. reference difference is important. A simple path will consume
fewer resources. BUT it will cause the entire graph to be recompiled and resent
to the driver if you change it.
A script primitive references a script that you create separately from the
graph. This means that any changes to the graph (such as an animation) will
NOT need to recompile or resend the script.
## Usage
You should add/modify primitives via the helper functions in
[`Scenic.Primitives`](Scenic.Primitives.html#script/3)
This example is based on the check mark script from the Checkbox control.
```elixir
alias Scenic.Script
# build the checkmark script
my_script =
Script.start()
|> Script.push_state()
|> Script.join(:round)
|> Script.stroke_width(3)
|> Script.stroke_color(:light_blue)
|> Script.begin_path()
|> Script.move_to(0, 8)
|> Script.line_to(5, 13)
|> Script.line_to(12, 1)
|> Script.stroke_path()
|> Script.pop_state()
|> Script.finish()
# push the script to the ViewPort
scene = push_script(scene, my_script, "My Script")
# refer to the script in a graph, and position it
graph
|> script("My Script", translate: {3, 2})
```
"""
use Scenic.Primitive
alias Scenic.Script
alias Scenic.Primitive
alias Scenic.Primitive.Style
@type styles_t :: [:hidden | :scissor]
@styles [:hidden, :scissor]
@impl Primitive
@spec validate(script_id :: Scenic.Script.id()) ::
{:ok, script_id :: Scenic.Script.id()} | {:error, String.t()}
def validate(id) when is_bitstring(id) do
{:ok, id}
end
def validate(data) do
{
:error,
"""
#{IO.ANSI.red()}Invalid Script ID
Received: #{inspect(data)}
#{IO.ANSI.yellow()}
The specification for a Script primitive is teh ID of a script that is pushed in to
a viewport at some other time. This ID can be a pid, an atom, a string or a reference.
You can refer to a script before it is pushed, but it will not draw until it is pushed
to the viewport.#{IO.ANSI.default_color()}
"""
}
end
# --------------------------------------------------------
# filter and gather styles
@doc """
Returns a list of styles recognized by this primitive.
"""
@impl Primitive
@spec valid_styles() :: styles_t()
def valid_styles(), do: @styles
# --------------------------------------------------------
# compiling a script 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 Script is a special case and is handled in Scenic.Graph.Compiler"
end
end