defmodule Scenic.Driver.Renderling.Window do @moduledoc """ The Scenic for windowed applications. """ alias Scenic.{ViewPort, ViewPort.Input} alias Scenic.Driver.Renderling.Window.{Config, Nif} use Scenic.Driver require Logger @position_schema [ centered: [type: :boolean, default: false], full_screen: [type: :boolean, default: false], maximized: [type: :boolean, default: false], orientation: [type: {:in, [:normal, :left, :right, :upside_down]}, default: :normal], scaled: [type: :boolean, default: false] ] @window_schema [ title: [type: :string, default: "Scenic Window"], resizeable: [type: :boolean, default: false] ] @opts_schema [ name: [type: {:or, [:atom, :string]}], position: [type: :keyword_list, keys: @position_schema, default: []], window: [type: :keyword_list, keys: @window_schema, default: []], cursor: [type: :boolean, default: false], key_map: [type: :atom, default: Scenic.KeyMap.USEnglish], on_close: [ type: {:or, [:mfa, {:in, [:restart, :stop_driver, :stop_viewport, :stop_system, :halt_system]}]}, default: :restart ], input_blacklist: [type: {:list, :string}, default: []] ] @doc false @impl Scenic.Driver def validate_opts(opts), do: NimbleOptions.validate(opts, @opts_schema) @doc false @impl true def init(driver, opts) do {width, height} = driver.viewport.size config = opts |> Keyword.update( :window, [width: width, height: height], &Keyword.merge(&1, width: width, height: height) ) |> Config.init() driver = assign(driver, config: config, server: Nif.init(config)) Process.flag(:trap_exit, true) Logger.info("#{inspect(__MODULE__)}: start #{inspect(opts)}, pid: #{inspect(self())}") {:ok, driver} end @doc false @impl true def update_scene(ids, driver) do Enum.reduce_while(ids, {:ok, driver}, fn id, {:ok, driver} -> case ViewPort.get_script(driver.viewport, id) do {:ok, script} -> Nif.update_scene(script, id, driver.assigns.server) {:cont, {:ok, driver}} {:error, reason} -> {:halt, {:error, reason}} end end) end @doc false @impl true def reset_scene(driver) do Nif.reset_scene(driver.assigns.server) {:ok, driver} end @doc false @impl true def del_scripts(ids, driver) do Nif.del_scripts(ids, driver.assigns.server) {:ok, driver} end @doc false @impl true def clear_color(color, driver) do Nif.clear_color(color, driver.assigns.server) {:ok, driver} end @doc false @impl GenServer def terminate(_, driver) do Nif.terminate(driver.assigns.server) end @doc false @impl GenServer def handle_info({:shutdown, reason}, driver) do Logger.debug(fn -> "Received shutdown from `scenic_window_server`: #{inspect(reason)}" end) case driver.assigns.config.on_close do :restart -> {:stop, :normal, driver} :stop_driver -> ViewPort.stop_driver(driver.viewport, self()) {:stop, :normal, driver} :stop_viewport -> ViewPort.stop(driver.viewport) {:stop, :normal, driver} :stop_system -> System.stop(0) {:stop, :normal, driver} :halt_system -> System.halt(0) {:stop, :normal, driver} {module, _fun, 1} -> module.fun(:shutdown) {:stop, :normal, driver} end end def handle_info({:codepoint, codepoint, mods}, driver) do Input.send(driver.viewport, {:codepoint, {<>, mods}}) {:noreply, driver} end def handle_info({:key, key, :pressed, mods}, driver) do Input.send(driver.viewport, {:key, {key, 1, mods}}) {:noreply, driver} end def handle_info({:key, key, :released, mods}, driver) do Input.send(driver.viewport, {:key, {key, 0, mods}}) {:noreply, driver} end def handle_info({:cursor_button, btn, :pressed, mods, pos}, driver) do Input.send(driver.viewport, {:cursor_button, {btn, 1, mods, pos}}) {:noreply, driver} end def handle_info({:cursor_button, btn, :released, mods, pos}, driver) do Input.send(driver.viewport, {:cursor_button, {btn, 0, mods, pos}}) {:noreply, driver} end def handle_info({:cursor_scroll, offset, pos}, driver) do Input.send(driver.viewport, {:cursor_scroll, offset, pos}) {:noreply, driver} end def handle_info({:cursor_pos, pos}, driver) do Input.send(driver.viewport, {:cursor_pos, pos}) {:noreply, driver} end def handle_info({:viewport, event, pos}, driver) when event in [:enter, :exit, :reshape] do Input.send(driver.viewport, {:viewport, {event, pos}}) {:noreply, driver} end def handle_info({:relative, pos}, driver) do Input.send(driver.viewport, {:relative, pos}) {:noreply, driver} end def handle_info(msg, driver) do Logger.debug(fn -> "Unhandled message #{inspect(msg)}" end) {:noreply, driver} end end