Scenic.Driver behaviour (Scenic v0.11.0-beta.0) View Source

The main module for drawing and user input.

Note: The driver model has completely changed in v0.11.

Drivers make up the bottom layer of the Scenic architectural stack. They draw everything on the screen and originate the raw user input. In general, different drawing targets will need different drivers.

The driver interface provides a great deal of flexibility, but is more advanced than writing scenes. You can write drivers that only provide user input, only draw scripts, or do both. There is no assumption at all as to what the output target or user source is.

Starting Drivers

Drivers are always managed by a ViewPort that hosts them.

The most common way to instantiate a driver is set it up in the config of a ViewPort when it starts up.

config :my_app, :viewport,
  size: {800, 600},
  name: :main_viewport,
  theme: :dark,
  default_scene: MyApp.Scene.MainScene,
  drivers: [
    [
      module: Scenic.Driver.Local,
      name: :local_driver,
      window: [title: "My Application", resizeable: false]
    ],
    [
      module: MyApp.Driver.MyDriver,
      my_param: "abc"
    ],
  ]

In the example above, two drivers are configured to be started when the :main_viewport starts up. Both drivers drivers will be running at the same time and will receive the same messages from the ViewPort.

Drivers can be dynamically started on a ViewPort using Scenic.ViewPort.start_driver/2. They can be dyncamically stopped on a ViewPort using Scenic.ViewPort.stop_driver/2.

Drivers can also define their own configuration options. See the documentation for the driver you are interested in starting to see the available options.

Messages from the ViewPort

The way a ViewPort communicates with it's drivers is by sending them a set of well-known messages. These are picked up by the Driver module and sent to your driver through callbacks.

CallbackDescription
reset_scene/1The ViewPort context has been reset. The driver can clean up all scripts and cached media
request_input/2The ViewPort is requesting user inputs from the keys list
update_scene/2The scripts identified by ids have been update and should be processed
del_scripts/2The script identified by id has been deleted and can be cleaned up
clear_color/2The background clear color has been updated

Handling Updates

The main drawing related task of a Driver is to receive the update_scene/2 callback and then draw the updated scripts to the screen, or whatever output medium the driver supports.

In a very simple example, it would look something like this.

def update_scene( ids, driver ) do
  Enum.each( ids, fn(id) ->
    with {:ok, script} <- ViewPort.get_script_by_id(vp, id) do
      script
      |> Scenic.Script.serialize()
      |> my_render_script(driver)
    end
  end)
  {:ok, driver}
end

The above example is overly simple and just there to get you started. Note that only the ids of the scripts are sent. You still need to fish them out of the ViewPort

User Input

User input events are created whenever a driver has events to share. The request_input/2 callback indicates to you which input events are currently being listened to. This is a guide that you can use or not. You can send any valid input event at any time, the ViewPort will simply ignore those that aren't being listened to.

In this example, there is some source of user input that casts messages to our driver.

def handle_cast( {:my_cursor_press, button, xy}, driver ) do
  send_input(driver, {:cursor_button, {button, 1, [], xy}} )
  { :noreply, driver }
end

No matter what type of input you are sending, it will be checked to make sure it conforms the known input types.

Updated Input Formats

There are several changes to the input formats in version v0.11.

Button indicators are now atoms that conform to standard linux-like buttons. This allows for more buttons on a mouse like device. Do not assume these are the only buttons that can be sent in a :cursor_button message.

New ButtonOld Button
:btn_left:left
:btn_right:right

Press and Release messages were atoms and are now numbers. This is to support multi-state buttons and joysticks and the like. Turns out that some buttons are pressure sensitive and can have a range of values.

New ActionOld Action
0:release
1:press

Modifier keys were previously a number and you would have had to dig into the GLFW documentation to see how to interpret it. That was both unintuitive and I wanted to make it more source independent. So now it is a list of atoms. If the atom you are interested is in the list, then it is pressed.

[:ctrl, :shift, :alt, :meta]

Test if a modifier key is pressed using the Enum.member?/2 function.

mods = [:ctrl, :shift]
Enum.member?( mods, :shift )

Link to this section Summary

Callbacks

Called when the background color has changed.

Called when a script has been deleted and can be cleaned up.

Initialize a driver process.

Called when requested input types have changed.

Called when the scene has been reset.

Called when the scene has been updated.

Validate the options passed to a Driver.

Functions

Convenience function to assign a list of values into a driver struct.

Convenience function to assign a value into a driver struct.

Convenience function to assign a list of new values into a driver struct.

Convenience function to assign a new values into a driver struct.

Convenience function to fetch an assigned value out of a driver struct.

Convenience function to get an assigned value out of a driver struct.

Request a scene_updated call.

Send input from the driver.

Set or clear the busy flag.

Link to this section Types

Specs

response_opts() :: [timeout() | :hibernate | {:continue, term()}]

Specs

t() :: %Scenic.Driver{
  assigns: map(),
  busy: boolean(),
  clear_color: Scenic.Color.rgba(),
  dirty_ids: list(),
  gated: boolean(),
  input_buffer: %{
    required(Scenic.ViewPort.Input.class()) => Scenic.ViewPort.Input.t()
  },
  input_limited: boolean(),
  limit_ms: integer(),
  module: atom(),
  pid: pid(),
  requested_inputs: [Scenic.ViewPort.Input.class()],
  update_ready: boolean(),
  update_requested: boolean(),
  viewport: Scenic.ViewPort.t()
}

Link to this section Callbacks

Link to this callback

clear_color(color, driver)

View Source (optional)

Specs

clear_color(color :: Scenic.Color.t(), driver :: t()) :: {:ok, t()}

Called when the background color has changed.

The color is provided.

This callback is optional.

Link to this callback

del_scripts(script_ids, driver)

View Source (optional)

Specs

del_scripts(script_ids :: [Scenic.Script.id()], driver :: t()) :: {:ok, t()}

Called when a script has been deleted and can be cleaned up.

The deleted id is provided.

This callback is optional.

Specs

init(driver :: t(), opts :: Keyword.t()) :: {:ok, t()}

Initialize a driver process.

The ViewPort and an options list for the driver are passed in. Just like initializing any GenServer process, it should return {:ok, state}

Link to this callback

request_input(input, driver)

View Source (optional)

Specs

request_input(input :: [Scenic.ViewPort.Input.class()], driver :: t()) ::
  {:ok, t()}

Called when requested input types have changed.

This informs your driver that the requested input types for the application have changed. This is useful if you want to reduce the amount of data being transferred between your input source (which might be expensive...) and the driver.

This callback is optional. If you ignore it and send all input events, then only the ones being listened to will be processed.

Link to this callback

reset_scene(driver)

View Source (optional)

Specs

reset_scene(driver :: t()) :: {:ok, t()}

Called when the scene has been reset.

This is an opportunity for your driver to clear state that may no longer be relevant. This is typically scripts, inputs, media, etc.

Link to this callback

update_scene(script_ids, driver)

View Source (optional)

Specs

update_scene(script_ids :: [Scenic.Script.id()], driver :: t()) :: {:ok, t()}

Called when the scene has been updated.

The list of ids is the set of script ids that have changed and should be updated.

Note that the list may be empty if you have requested an update via the request_update/1 function.

This callback is optional.

Specs

validate_opts(opts :: Keyword.t()) ::
  {:ok, any()}
  | {:error, String.t()}
  | {:error, NimbleOptions.ValidationError.t()}

Validate the options passed to a Driver.

The list of options for a driver are passed in as opts. If you decide then are good, return them, or a transformed set of them as {:ok, opts}

If they are invalid, return either one of:

  • {:error, String.t()}
  • {:error, NimbleOptions.ValidationError.t()}

Scenic uses NimbleOptions internally for options validation, so NimbleOptions errors are supported.

Link to this section Functions

Link to this function

assign(driver, key_list)

View Source

Specs

assign(driver :: t(), key_list :: Keyword.t()) :: t()

Convenience function to assign a list of values into a driver struct.

Link to this function

assign(driver, key, value)

View Source

Specs

assign(driver :: t(), key :: any(), value :: any()) :: t()

Convenience function to assign a value into a driver struct.

Link to this function

assign_new(driver, key_list)

View Source

Specs

assign_new(driver :: t(), key_list :: Keyword.t()) :: t()

Convenience function to assign a list of new values into a driver struct.

Only values that do not already exist will be assigned

Link to this function

assign_new(driver, key, value)

View Source

Specs

assign_new(driver :: t(), key :: any(), value :: any()) :: t()

Convenience function to assign a new values into a driver struct.

The value will only be assigned if it does not already exist in the struct.

Specs

fetch(driver :: t(), key :: any()) :: {:ok, any()} | :error

Convenience function to fetch an assigned value out of a driver struct.

Link to this function

get(driver, key, default \\ nil)

View Source

Specs

get(driver :: t(), key :: any(), default :: any()) :: any()

Convenience function to get an assigned value out of a driver struct.

Request a scene_updated call.

This is used when scripts are updated. Some drivers use it to batch updates into a single atomic operation. This call is rate limited by limit_ms.

Specs

send_input(driver :: t(), input :: Scenic.ViewPort.Input.t()) :: t()

Send input from the driver.

Send input from the driver to its ViewPort. :cursor_pos and :cursor_scroll input will be buffered/rate limited according the driver's :limit_ms setting.

Specs

set_busy(driver :: t(), flag :: boolean()) :: t()

Set or clear the busy flag.

When the busy flag is set, put_script messages will be consolidated until cleared.

Link to this function

terminate(reason, driver)

View Source