View Source Scenic.Driver behaviour (Scenic v0.11.2)
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
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 dynamically 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
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.
Callback | Description |
---|---|
reset_scene/1 | The ViewPort context has been reset. The driver can clean up all scripts and cached media |
request_input/2 | The ViewPort is requesting user inputs from the keys list |
update_scene/2 | The scripts identified by ids have been update and should be processed |
del_scripts/2 | The script identified by id has been deleted and can be cleaned up |
clear_color/2 | The background clear color has been updated |
handling-updates
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
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
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 Button | Old 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 Action | Old 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 or map 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.
Validate driver configuration
Link to this section Types
@type 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
@callback 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.
@callback 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.
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}
@callback 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.
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.
@callback 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.
@callback 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
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 or map of new values into a driver struct.
Only values that do not already exist will be assigned
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.
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.
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.
@spec 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.
Set or clear the busy flag.
When the busy flag is set, put_script messages will be consolidated until cleared.
Validate driver configuration
Used primarily for dynamic view port creation