SnapFramework.Scene behaviour (SnapFramework v0.1.0-alpha.5) View Source

Overview

SnapFramework.Scene aims to make creating Scenic scenes easier and comes with a lot of convenient features. See Scenic.Scene docs for more on scenes.

In order to use this module you will first need a template. Templates are just basic EEx files. See an example template below

<%= graph font_size: 20 %>

<%= primitive Scenic.Primitive.Text,
    "selected value #{@dropdown_value}",
    id: :dropdown_value_text,
    translate: {20, 80}
%>

<%= component Scenic.Component.Dropdown, {
        @dropdown_opts,
        @dropdown_value
    },
    id: :dropdown,
    translate: {20, 20}
%>

We always start the template off with <%= graph %>. This tells the compiler to build the Scenic graph. After that we begin inserting our primitives or components. The above is equivilent to writing the following -

Scenic.Graph.build()
|> Scenic.Primitive.Text.add_to_graph("selected value #{scene.assigns.dropdown_value}", id: :dropdown_value_text, translate: {20, 80})
|> Scenic.Component.Dropdown({scene.assigns.dropdown_opts, scene.assigns.dropdown_value}, id: :dropdown, translate: {20, 20})

Now that we have a template we can use the template in a scene.

defmodule Example.Scene.MyScene do
  use SnapFramework.Scene,
    name: :my_scene,
    template: "lib/scenes/my_scene.eex",
    controller: :none,
    assigns: [
      dropdown_opts: [
        {"Dashboard", :dashboard},
        {"Controls", :controls},
        {"Primitives", :primitives}
      ],
      dropdown_value: :dashboard,
    ]
end

Having just the above should be enough to get the scene rendering. But as you can see selecting a new dropdown doesn't update the text component text like the template implies that it should.

To update a graph SnapFramework has the use_effect macro. This macro comes with a Scene or Component. Let's update the above code to catch the event from the dropdown and update the text.

defmodule Example.Scene.MyScene do
  use SnapFramework.Scene,
    name: :my_scene,
    template: "lib/scenes/my_scene.eex",
    controller: Example.Scene.MySceneController,
    assigns: [
      dropdown_opts: [
        {"Dashboard", :dashboard},
        {"Controls", :controls},
        {"Primitives", :primitives}
      ],
      dropdown_value: :dashboard,
    ]

  use_effect [assigns: [dropdown_value: :any]], [
    run: [:on_dropdown_value_change],
  ]

  def process_event({:value_changed, :dropdown, value}, _, scene) do
    {:noreply, assign(scene, dropdown_value: value)}
  end
end

Last but not least the controller module.

defmodule Examples.Scene.MySceneController do
  import Scenic.Primitives, only: [text: 3]
  alias Scenic.Graph

  def on_dropdown_value_change(scene) do
    graph =
      scene.assigns.graph
      |> Graph.modify(:dropdown_value_text, &text(&1, "selected value #{scene.assigns.dropdown_value}", []))

    Scenic.Scene.assign(scene, graph: graph)
  end
end

That is the basics to using SnapFramework.Scene. It essentially consist of three pieces, a template, a controller, and a scene module that glues them together.

Setup and Mounted Callbacks

If you need to do some special setup, like request input, subscribe to a PubSub service, or add some runtime assigns. You can do that in the setup callback. It gives you the scene struct and should return a scene struct.

The setup callback run before the template is compiled. So any added or modified assigns will be included in the template. The graph however is not included on the scene yet.

defmodule Example.Scene.MyScene do
  use SnapFramework.Scene,
    name: :my_scene,
    template: "lib/scenes/my_scene.eex",
    controller: Example.Scene.MySceneController,
    assigns: [
      dropdown_opts: [
        {"Dashboard", :dashboard},
        {"Controls", :controls},
        {"Primitives", :primitives}
      ],
      dropdown_value: :dashboard,
    ]

  use_effect [assigns: [dropdown_value: :any]], [
    run: [:on_dropdown_value_change],
  ]

  def setup(scene) do
    Scenic.PubSub.subscribe(:pubsub_service)

    assign(scene, new_assign: true)
  end

  def process_event({:value_changed, :dropdown, value}, _, scene) do
    {:noreply, assign(scene, dropdown_value: value)}
  end
end

If you need to do something after the graph is compile, you can use the mounted callback. Like the setup callback it gives you the scene, and should return a scene.

defmodule Example.Scene.MyScene do
  use SnapFramework.Scene,
    name: :my_scene,
    template: "lib/scenes/my_scene.eex",
    controller: Example.Scene.MySceneController,
    assigns: [
      dropdown_opts: [
        {"Dashboard", :dashboard},
        {"Controls", :controls},
        {"Primitives", :primitives}
      ],
      dropdown_value: :dashboard,
    ]

  use_effect [assigns: [dropdown_value: :any]], [
    run: [:on_dropdown_value_change],
  ]

  def setup(scene) do
    Scenic.PubSub.subscribe(:pubsub_service)

    assign(scene, new_assign: true)
  end

  def mounted(%{assigns: %{graph: graph}} = scene) do
    # do something with the graph
  end

  def process_event({:value_changed, :dropdown, value}, _, scene) do
    {:noreply, assign(scene, dropdown_value: value)}
  end
end

Link to this section Summary

Callbacks

Called after graph is compiled. If you need to do any post setup changes on your graph do that here.

Called when a scene receives a call message. The returned state is diffed, and effects are run.

Called when a scene receives a cast message. The returned state is diffed, and effects are run.

Called when a scene receives an event message. The returned state is diffed, and effects are run.

Called when a scene receives a message. The returned state is diffed, and effects are run.

Called when a scene receives an input messsage. The returned state is diffed, and effects are run.

Called when a scene receives an update message. Use this to update data and options on your state. The returned state is diffed, and effects are run.

Called when a first starts, before the graph is compiled. Use this to do any startup logic. Any assigns set or updated here will be included in the compiled graph. If you need to subscribe to a PubSub service do that here.

Functions

The use effect macro glues your scene module to your controller. Whenever you return a scene from the on of the process functions, it matches against your effect registery. If a match is found it then runs the specified controller function and pushed your graph if your graph has changed

The watch macro will recompile the template with the most up to date assigns, whenever one of the watched keys changes. This macro is not recommended for large scene or components.

Link to this section Callbacks

Specs

mounted(Scenic.Scene.t()) :: Scenic.Scene.t()

Called after graph is compiled. If you need to do any post setup changes on your graph do that here.

Link to this callback

process_call(term, from, t)

View Source

Specs

process_call(term(), GenServer.from(), Scenic.Scene.t()) ::
  {atom(), term(), Scenic.Scene.t()} | {atom(), Scenic.Scene.t()}

Called when a scene receives a call message. The returned state is diffed, and effects are run.

Specs

process_cast(any(), Scenic.Scene.t()) :: {atom(), Scenic.Scene.t()}

Called when a scene receives a cast message. The returned state is diffed, and effects are run.

Link to this callback

process_event(term, pid, t)

View Source

Specs

process_event(term(), pid(), Scenic.Scene.t()) ::
  {atom(), Scenic.Scene.t()}
  | {atom(), Scenic.Scene.t(), list()}
  | {atom(), term(), Scenic.Scene.t()}
  | {atom(), term(), Scenic.Scene.t(), list()}

Called when a scene receives an event message. The returned state is diffed, and effects are run.

Specs

process_info(any(), Scenic.Scene.t()) :: {atom(), Scenic.Scene.t()}

Called when a scene receives a message. The returned state is diffed, and effects are run.

Link to this callback

process_input(term, term, t)

View Source

Specs

process_input(term(), term(), Scenic.Scene.t()) :: {atom(), Scenic.Scene.t()}

Called when a scene receives an input messsage. The returned state is diffed, and effects are run.

Link to this callback

process_update(term, t, t)

View Source

Specs

process_update(term(), List.t(), Scenic.Scene.t()) :: {atom(), Scenic.Scene.t()}

Called when a scene receives an update message. Use this to update data and options on your state. The returned state is diffed, and effects are run.

Specs

Called when a first starts, before the graph is compiled. Use this to do any startup logic. Any assigns set or updated here will be included in the compiled graph. If you need to subscribe to a PubSub service do that here.

Link to this section Functions

Link to this macro

use_effect(list, actions)

View Source (macro)

The use effect macro glues your scene module to your controller. Whenever you return a scene from the on of the process functions, it matches against your effect registery. If a match is found it then runs the specified controller function and pushed your graph if your graph has changed

use_effect [assigns: [dropdown_value: :any]], [
    run: [:on_dropdown_value_change],
  ]

The watch macro will recompile the template with the most up to date assigns, whenever one of the watched keys changes. This macro is not recommended for large scene or components.

use_effect is always preferred.

watch [:dropdown_value]