View Source Scenic.Component behaviour (Scenic v0.11.2)
A Component is Scene that is optimized to be used as a child of another scene.
These are typically controls that you want to define once and use in multiple places.
standard-components
Standard Components
Scenic includes a several standard components that you can use in your scenes. These were chosen to be in the main library because:
- They are used frequently
- Their use promotes a certain amount of "common" look and feel
All of these components are typically added/modified via the helper functions in the
Scenic.Components
module.
Helper | Component Module | Description |
---|---|---|
button/3 | Scenic.Component.Button | A simple button |
checkbox/3 | Scenic.Component.Input.Checkbox | A boolean checkbox control |
dropdown/3 | Scenic.Component.Input.Dropdown | A menu-like dropdown control |
radio_group/3 | Scenic.Component.Input.RadioGroup | A group of radio controls |
slider/3 | Scenic.Component.Input.Slider | A slider ranging from one value to another |
text_field/3 | Scenic.Component.Input.TextField | A text input field. |
toggle/3 | Scenic.Component.Input.Toggle | A boolean toggle control. |
defmodule MyApp.Scene.MyScene do
use Scenic.Scene
import Scenic.Components
@impl Scenic.Scene
def init(scene, text, opts) do
graph =
Scenic.Graph.build()
|> button( "Press Me", id: :press_me )
|> slider( {{0,100}, 0}, id: :slide_me )
{ :ok, push_graph(scene, graph) }
end
end
creating-custom-components
Creating Custom Components
Creating a custom component that you can use in your scenes is just like creating a scene with an extra validation function. This validation function is used when the graph that uses your component is built in order to make sure it uses data that conforms to what your component expects.
defmodule MyApp.Component.Fancy do
use Scenic.Component
@impl Scenic.Component
def validate(data) when is_bitstring(data), do: {:ok, data}
def validate(_), do: {:error, "Descriptive error message goes here."}
@impl Scenic.Scene
def init(scene, data, opts) do
{ :ok, scene }
end
end
generating-sending-events
Generating/Sending Events
Communication from a component to it's parent is usually done via event messages. Scenic knows how to route events to a component's parent. If that parent doesn't handle it, then it is automatically routed to the parent's parent. If it gets all the way to the ViewPort itself, then it is ignored.
defmodule MyApp.Component.Fancy do
# ... validate, and other setup ...
@impl Scenic.Scene
def init(scene, data, opts) do
# setup and push a graph here...
{ :ok, assign(scene, id: opts[:id] }
end
@impl Scenic.Scene
def handle_input( {:cursor_button, {0, :release, _, _}}, :btn,
%Scene{assigns: %{id: id}} = scene
) do
:ok = send_parent_event( scene, {:click, id} )
{ :noreply, scene }
end
end
Notice how the component saved the original id
that was passed in to the init
function via
the opts
list. This is then used to identify the click to the parent. This is a common pattern.
optional-fetch-put-handlers
Optional: Fetch/Put Handlers
If you would like the parent scene to be able to query your component's state without waiting for the component to send events, you can optionally implement the following handle_call functions.
This is an "informal" spec... You don't have to implement it, but it is nice when you do.
defmodule MyApp.Component.Fancy do
use Scenic.Component
# ... init, validate, and other functions ...
def handle_call(:fetch, _, %{assigns: %{value: value}} = scene) do
{ :reply, {:ok, value}, scene }
end
def handle_call({:put, value}, _, scene) when is_bitstring(value) do
{ :reply, :ok, assign(scene, value: value) }
end
def handle_call({:put, _}, _, scene) do
{:reply, {:error, :invalid}, scene}
end
end
To make the above example more practical, you would probably also modify and push a graph when
handling the :put
message. See the code for the standard input components for deeper examples.
optional-has_children-false
Optional: has_children: false
If you know for certain that your component will not itself use any components, you can
set :has_children
to false
like this.
defmodule MyApp.Component.Fancy do
use Scenic.Component, has_children: false
# ...
end
When :has_children
is set to false
, no DynamicSupervisor
is started to manage the
scene's children, overall resource use is improved, and startup time is faster. You will not,
however, be able to nested components in any scene where :has_children
is false
.
For example, the Scenic.Component.Button
component sets :has_children
to false
.
This option is available for any Scene, not just components.
Link to this section Summary
Callbacks
Add this component to a Graph.
Compute the bounding box of the component.
Provide a default pin for this component.
Validate that the data for a component is correctly formed.
Link to this section Callbacks
@callback add_to_graph(graph :: Scenic.Graph.t(), data :: any(), opts :: Keyword.t()) :: Scenic.Graph.t()
Add this component to a Graph.
A standard add_to_graph/3
is automatically added to your component. Override this
callback if you want to customize it.
@callback bounds(data :: any(), styles :: map()) :: Scenic.Graph.bounds()
Compute the bounding box of the component.
This function can be called outside of the context of a running component. The box should be computed as if it was running with the given data and styles.
@callback default_pin(data :: any(), styles :: map()) :: Scenic.Math.vector_2()
Provide a default pin for this component.
If this callback is not implemented, then the default pin will be {0,0}.
Validate that the data for a component is correctly formed.
This callback is required.