wip: can send messages and scripts over the IPC link.

This commit is contained in:
James Harton 2024-05-15 19:33:59 +12:00
parent a898d370ac
commit 244dedfb9b
Signed by: james
GPG key ID: 90E82DAA13F624F4
21 changed files with 1472 additions and 56 deletions

2
.gitignore vendored
View file

@ -24,3 +24,5 @@ scenic_driver_renderling-*.tar
# Temporary files, for example, from tests.
/tmp/
priv/__scenic

View file

@ -1,3 +1,3 @@
{
"cSpell.words": ["Renderling"]
"cSpell.words": ["centered", "Renderling"]
}

15
config/config.exs Normal file
View file

@ -0,0 +1,15 @@
import Config
if Mix.env() in [:dev, :test] do
config :scenic_driver_renderling, :viewport,
name: :main_viewport,
size: {700, 600},
default_scene: ExampleScene,
drivers: [
module: Scenic.Driver.Renderling.Window,
name: :local,
window: [resizeable: false, title: "Example Application"]
]
config :scenic, :assets, module: ExampleAssets
end

View file

@ -1,11 +1,74 @@
defmodule Scenic.Driver.Renderling.Window.Config do
defstruct [:title, :resizeable, :width, :height, :server_path]
defstruct name: nil,
limit_ms: nil,
layer: nil,
opacity: nil,
debug: false,
debugger: "",
debug_fps: 0,
antialias: true,
position: nil,
window: nil,
cursor: false,
key_map: Scenic.KeyMap.USEnglish,
on_close: :restart,
input_blacklist: [],
server_path: nil
defmodule Position do
defstruct scaled: false, centered: false, orientation: :normal
@type t :: %__MODULE__{
scaled: boolean,
centered: boolean,
orientation: :normal | :left | :right | :upside_down
}
def init(opts), do: struct(__MODULE__, opts)
end
defmodule Window do
defstruct title: "Scenic Window", resizeable: false, width: nil, height: nil
@type t :: %__MODULE__{
title: String.t(),
resizeable: false,
width: nil | non_neg_integer(),
height: nil | non_neg_integer()
}
def init(opts), do: struct(__MODULE__, opts)
end
@type t :: %__MODULE__{
title: String.t(),
resizeable: boolean,
width: non_neg_integer(),
height: non_neg_integer(),
server_path: String.t()
name: atom | String.t(),
limit_ms: non_neg_integer(),
layer: integer,
opacity: integer,
debug: boolean,
debugger: String.t(),
debug_fps: integer,
antialias: boolean,
position: Position.t(),
window: Window.t(),
cursor: boolean,
key_map: module,
on_close: :restart | :stop_driver | :stop_viewport | :stop_system | :halt_system,
input_blacklist: [String.t()]
}
def init(opts) do
attrs =
opts
|> Keyword.update(:position, Position.init([]), &Position.init/1)
|> Keyword.update(:window, Window.init([]), &Window.init/1)
|> Keyword.put(:server_path, compute_server_path())
struct(__MODULE__, attrs)
end
defp compute_server_path do
:scenic_driver_renderling
|> :code.priv_dir()
|> Path.join("native/renderling_window_server")
end
end

View file

@ -1,5 +1,11 @@
defmodule Scenic.Driver.Renderling.Window.Nif do
use Rustler, otp_app: :scenic_driver_renderling, crate: :renderling_window
def init(_cfg), do: :erlang.nif_error(:nif_not_loaded)
def init(_config), do: :erlang.nif_error(:nif_not_loaded)
def update_scene(_script, _script_id, _resource), do: :erlang.nif_error(:nif_not_loaded)
def reset_scene(_resource), do: :erlang.nif_error(:nif_not_loaded)
def del_scripts(_script_ids, _resource), do: :erlang.nif_error(:nif_not_loaded)
def clear_color(_color, _resource), do: :erlang.nif_error(:nif_not_loaded)
def terminate(_resource), do: :erlang.nif_error(:nif_not_loaded)
def request_input(_input_classes, _resource), do: :erlang.nif_error(:nif_not_loaded)
end

View file

@ -0,0 +1,117 @@
defmodule Scenic.Driver.Renderling.Window do
@moduledoc """
The Scenic for windowed applications.
"""
alias Scenic.ViewPort
alias Scenic.Driver.Renderling.Window.{Config, Nif}
use Scenic.Driver
require Logger
@default_limit 29
@default_layer 0
@default_opacity 255
@position_schema [
scaled: [type: :boolean, default: false],
centered: [type: :boolean, default: false],
orientation: [type: {:in, [:normal, :left, :right, :upside_down]}, default: :normal]
]
@window_schema [
title: [type: :string, default: "Scenic Window"],
resizeable: [type: :boolean, default: false]
]
@opts_schema [
name: [type: {:or, [:atom, :string]}],
limit_ms: [type: :non_neg_integer, default: @default_limit],
layer: [type: :integer, default: @default_layer],
opacity: [type: :integer, default: @default_opacity],
debug: [type: :boolean, default: false],
debugger: [type: :string, default: ""],
debug_fps: [type: :integer, default: 0],
antialias: [type: :boolean, default: true],
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
end

10
mix.exs
View file

@ -6,6 +6,7 @@ defmodule Scenic.Driver.Renderling.MixProject do
app: :scenic_driver_renderling,
version: "0.1.0",
elixir: "~> 1.16",
elixirc_paths: elixirc_paths(Mix.env()),
start_permanent: Mix.env() == :prod,
deps: deps()
]
@ -21,9 +22,12 @@ defmodule Scenic.Driver.Renderling.MixProject do
# Run "mix help deps" to learn about dependencies.
defp deps do
[
{:rustler, "~> 0.32", runtime: false}
# {:dep_from_hexpm, "~> 0.3.0"},
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
{:rustler, "~> 0.32", runtime: false},
{:nimble_options, ">= 0.0.0"},
{:scenic, "~> 0.11"}
]
end
defp elixirc_paths(env) when env in [:dev, :test], do: ["lib", "test/support"]
defp elixirc_paths(_), do: ["lib"]
end

View file

@ -1,5 +1,11 @@
%{
"elixir_make": {:hex, :elixir_make, "0.7.8", "505026f266552ee5aabca0b9f9c229cbb496c689537c9f922f3eb5431157efc7", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.0", [hex: :certifi, repo: "hexpm", optional: true]}], "hexpm", "7a71945b913d37ea89b06966e1342c85cfe549b15e6d6d081e8081c493062c07"},
"ex_image_info": {:hex, :ex_image_info, "0.2.4", "610002acba43520a9b1cf1421d55812bde5b8a8aeaf1fe7b1f8823e84e762adb", [:mix], [], "hexpm", "fd1a7e02664e3b14dfd3b231d22fdd48bd3dd694c4773e6272b3a6228f1106bc"},
"font_metrics": {:hex, :font_metrics, "0.5.1", "10ce0b8b1bf092a2d3d307e05a7c433787ae8ca7cd1f3cf959995a628809a994", [:mix], [{:nimble_options, "~> 0.3", [hex: :nimble_options, repo: "hexpm", optional: false]}], "hexpm", "192e4288772839ae4dadccb0f5b1d5c89b73a0c3961ccea14b6181fdcd535e54"},
"jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"},
"nimble_options": {:hex, :nimble_options, "0.5.2", "42703307b924880f8c08d97719da7472673391905f528259915782bb346e0a1b", [:mix], [], "hexpm", "4da7f904b915fd71db549bcdc25f8d56f378ef7ae07dc1d372cbe72ba950dce0"},
"rustler": {:hex, :rustler, "0.32.1", "f4cf5a39f9e85d182c0a3f75fa15b5d0add6542ab0bf9ceac6b4023109ebd3fc", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:toml, "~> 0.6", [hex: :toml, repo: "hexpm", optional: false]}], "hexpm", "b96be75526784f86f6587f051bc8d6f4eaff23d6e0f88dbcfe4d5871f52946f7"},
"scenic": {:hex, :scenic, "0.11.2", "5d29aac74ee85899651716af47f72d167d064939fc2b4e514cd7c43810293cda", [:make, :mix], [{:elixir_make, "~> 0.7.7", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:ex_image_info, "~> 0.2.4", [hex: :ex_image_info, repo: "hexpm", optional: false]}, {:font_metrics, "~> 0.5.0", [hex: :font_metrics, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.3.4 or ~> 0.4.0 or ~> 0.5.0 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:truetype_metrics, "~> 0.6", [hex: :truetype_metrics, repo: "hexpm", optional: false]}], "hexpm", "6f846cfe8457163d28ad6b8b147d251bd15524c249ddad3e75608cbdb739beaa"},
"toml": {:hex, :toml, "0.7.0", "fbcd773caa937d0c7a02c301a1feea25612720ac3fa1ccb8bfd9d30d822911de", [:mix], [], "hexpm", "0690246a2478c1defd100b0c9b89b4ea280a22be9a7b313a8a058a2408a2fa70"},
"truetype_metrics": {:hex, :truetype_metrics, "0.6.1", "9119a04dc269dd8f63e85e12e4098f711cb7c5204a420f4896f40667b9e064f6", [:mix], [{:font_metrics, "~> 0.5", [hex: :font_metrics, repo: "hexpm", optional: false]}], "hexpm", "5711d4a3e4fc92eb073326fbe54208925d35168dc9b288c331ee666a8a84759b"},
}

View file

@ -0,0 +1,13 @@
{
"cSpell.words": [
"Bezier",
"centered",
"cmds",
"Codepoint",
"Miter",
"Rect",
"Renderling",
"Rrect",
"Stdio"
]
}

View file

@ -2044,6 +2044,7 @@ dependencies = [
"lazy_static",
"rustler_codegen",
"rustler_sys",
"serde",
]
[[package]]

View file

@ -14,11 +14,11 @@ anyhow = "1"
clap = {version = "4", features = ["derive"]}
ipc-channel = "0.18.0"
renderling = "0.4.1"
rustler = "0.32.1"
rustler = {version = "0.32.1", features = ["serde", "nif_version_2_16"]}
serde = {version = "1", features = ["derive"]}
wgpu = "0.20.0"
winit = "0.30.0"
[[bin]]
name = "server"
name = "renderling_window_server"
path = "src/server.rs"

View file

@ -0,0 +1,407 @@
use rustler::{NifStruct, NifTaggedEnum, NifTuple, NifUnitEnum, NifUntaggedEnum, Term};
/// Structures received from BEAM.
#[derive(NifUnitEnum, Debug, Clone)]
pub enum WindowOrientation {
Normal,
Left,
Right,
UpsideDown,
}
#[derive(NifStruct, Debug, Clone)]
#[module = "Scenic.Driver.Renderling.Window.Config.Position"]
pub struct WindowPosition {
pub scaled: bool,
pub centered: bool,
pub orientation: WindowOrientation,
}
#[derive(NifStruct, Debug, Clone)]
#[module = "Scenic.Driver.Renderling.Window.Config.Window"]
pub struct WindowConfig {
pub title: String,
pub resizeable: bool,
pub width: u32,
pub height: u32,
}
#[derive(NifUnitEnum, Debug, Clone)]
pub enum OnCloseAction {
Restart,
StopDriver,
StopViewport,
StopSystem,
HaltSystem,
}
#[derive(NifStruct, Debug, Clone)]
#[module = "Scenic.Driver.Renderling.Window.Config"]
pub struct DriverConfig {
pub limit_ms: u32,
pub layer: u32,
pub opacity: u32,
pub debug: bool,
pub debugger: String,
pub debug_fps: u32,
pub antialias: bool,
pub position: WindowPosition,
pub window: WindowConfig,
pub cursor: bool,
pub on_close: OnCloseAction,
pub input_blacklist: Vec<String>,
pub server_path: String,
}
#[derive(NifTaggedEnum, Debug, Clone)]
pub enum Color {
ColorG(u8),
ColorGa((u8, u8)),
ColorRgb((u8, u8, u8)),
ColorRgba((u8, u8, u8, u8)),
ColorHsv((f32, f32, f32)),
ColorHsl((f32, f32, f32)),
}
#[derive(NifTuple, Debug, Clone)]
pub struct Pos(pub u32, pub u32);
#[derive(NifUnitEnum, Debug, Clone)]
pub enum TextAlign {
Left,
Center,
Right,
}
#[derive(NifUnitEnum, Debug, Clone)]
pub enum TextBase {
Top,
Middle,
Alphabetic,
Bottom,
}
#[derive(NifUnitEnum, Debug, Clone)]
pub enum FillStroke {
Fill,
Stroke,
FillStroke,
}
#[derive(NifTuple, Debug, Clone)]
pub struct DrawLine {
pub x0: u32,
pub y0: u32,
pub x1: u32,
pub y1: u32,
pub fill_stroke: FillStroke,
}
#[derive(NifTuple, Debug, Clone)]
pub struct DrawQuad {
pub x0: u32,
pub y0: u32,
pub x1: u32,
pub y1: u32,
pub x2: u32,
pub y2: u32,
pub x3: u32,
pub y3: u32,
pub fill_stroke: FillStroke,
}
#[derive(NifTuple, Debug, Clone)]
pub struct Quad {
pub x0: u32,
pub y0: u32,
pub x1: u32,
pub y1: u32,
pub x2: u32,
pub y2: u32,
pub x3: u32,
pub y3: u32,
}
#[derive(NifTuple, Debug, Clone)]
pub struct DrawRect {
pub width: u32,
pub height: u32,
pub fill_stroke: FillStroke,
}
#[derive(NifTuple, Debug, Clone)]
pub struct Rect {
pub width: u32,
pub height: u32,
}
#[derive(NifTuple, Debug, Clone)]
pub struct DrawRrect {
pub width: u32,
pub height: u32,
pub radius: u32,
pub fill_stroke: FillStroke,
}
#[derive(NifTuple, Debug, Clone)]
pub struct Rrect {
pub width: u32,
pub height: u32,
pub radius: u32,
}
#[derive(NifTuple, Debug, Clone)]
pub struct DrawSector {
pub radius: u32,
pub radians: f32,
pub fill_stroke: FillStroke,
}
#[derive(NifTuple, Debug, Clone)]
pub struct Sector {
pub radius: u32,
pub radians: f32,
}
#[derive(NifTuple, Debug, Clone)]
pub struct DrawArc {
pub radius: u32,
pub radians: f32,
pub fill_stroke: FillStroke,
}
#[derive(NifTuple, Debug, Clone)]
pub struct DrawCircle {
pub radius: u32,
pub fill_stroke: FillStroke,
}
#[derive(NifTuple, Debug, Clone)]
pub struct Circle {
pub radius: u32,
}
#[derive(NifTuple, Debug, Clone)]
pub struct DrawEllipse {
pub radius0: u32,
pub radius1: u32,
pub fill_stroke: FillStroke,
}
#[derive(NifTuple, Debug, Clone)]
pub struct Ellipse {
pub radius0: u32,
pub radius1: u32,
}
#[derive(NifTuple, Debug, Clone)]
pub struct SpriteDrawCmd {
pub src_xy: Pos,
pub src_wh: Pos,
pub dst_xy: Pos,
pub dst_wh: Pos,
}
#[derive(NifTuple, Debug, Clone)]
pub struct DrawSprite {
pub src_id: String,
pub cmds: Vec<SpriteDrawCmd>,
}
#[derive(NifTuple, Debug, Clone)]
pub struct DrawTriangle {
pub x0: u32,
pub y0: u32,
pub x1: u32,
pub y1: u32,
pub x2: u32,
pub y2: u32,
pub fill_stroke: FillStroke,
}
#[derive(NifTuple, Debug, Clone)]
pub struct Triangle {
pub x0: u32,
pub y0: u32,
pub x1: u32,
pub y1: u32,
pub x2: u32,
pub y2: u32,
}
#[derive(NifTuple, Debug, Clone)]
pub struct ArcTo {
pub x0: u32,
pub y0: u32,
pub x1: u32,
pub y1: u32,
pub radius: u32,
}
#[derive(NifTuple, Debug, Clone)]
pub struct BezierTo {
pub cp1x: u32,
pub cp1y: u32,
pub cp2x: u32,
pub cp2y: u32,
pub x: u32,
pub y: u32,
}
#[derive(NifTuple, Debug, Clone)]
pub struct QuadraticTo {
pub cpx: u32,
pub cpy: u32,
pub x: u32,
pub y: u32,
}
#[derive(NifTuple, Debug, Clone)]
pub struct Scale {
pub x: f32,
pub y: f32,
}
#[derive(NifTuple, Debug, Clone)]
pub struct Transform {
pub a: f32,
pub b: f32,
pub c: f32,
pub d: f32,
pub e: f32,
pub f: f32,
}
#[derive(NifTuple, Debug, Clone)]
pub struct FillLinear {
pub start_x: u32,
pub start_y: u32,
pub end_x: u32,
pub end_y: u32,
pub color_start: Color,
pub color_end: Color,
}
#[derive(NifTuple, Debug, Clone)]
pub struct FillRadial {
pub center_x: u32,
pub center_y: u32,
pub inner_radius: u32,
pub outer_radius: u32,
pub color_start: Color,
pub color_end: Color,
}
#[derive(NifTuple, Debug, Clone)]
pub struct StrokeLinear {
pub start_x: u32,
pub start_u: u32,
pub end_x: u32,
pub end_y: u32,
pub color_start: Color,
pub color_end: Color,
}
#[derive(NifTuple, Debug, Clone)]
pub struct StrokeRadial {
pub center_x: u32,
pub center_y: u32,
pub inner_radius: u32,
pub outer_radius: u32,
pub color_start: Color,
pub color_end: Color,
}
#[derive(NifUnitEnum, Debug, Clone)]
pub enum Cap {
Butt,
Round,
Square,
}
#[derive(NifUnitEnum, Debug, Clone)]
pub enum Join {
Bevel,
Round,
Miter,
}
#[derive(NifTuple, Debug, Clone)]
pub struct Scissor {
pub width: u32,
pub height: u32,
}
#[derive(NifTaggedEnum, Debug, Clone)]
pub enum ScriptItem {
PushState,
PopState,
PushPopState,
Clear(Color),
DrawLine(DrawLine),
DrawQuad(DrawQuad),
DrawRect(DrawRect),
DrawRrect(DrawRrect),
DrawSector(DrawSector),
DrawArc(DrawArc),
DrawCircle(DrawCircle),
DrawEllipse(DrawEllipse),
DrawSprites(DrawSprite),
DrawText(String),
DrawTriangle(DrawTriangle),
Script(String),
BeginPath,
ClosePath,
FillPath,
StrokePath,
MoveTo(Pos),
LineTo(Pos),
ArcTo(ArcTo),
BezierTo(BezierTo),
QuadraticTo(QuadraticTo),
Quad(Quad),
Rect(Rect),
Rrect(Rrect),
Sector(Sector),
Circle(Circle),
Ellipse(Ellipse),
Triangle(Triangle),
Scale(Scale),
Rotate(f32),
Translate(Pos),
Transform(Transform),
FillColor(Color),
FillLinear(FillLinear),
FillRadial(FillRadial),
FillImage(String),
FillStream(String),
StrokeColor(Color),
StrokeLinear(StrokeLinear),
StrokeRadial(StrokeRadial),
StrokeImage(String),
StrokeStream(String),
StrokeWidth(u32),
Cap(Cap),
Join(Join),
MiterLimit(u32),
Scissor(Scissor),
Font(String),
FontSize(u32),
TextAlign(TextAlign),
TextBase(TextBase),
}
#[derive(NifUnitEnum, Debug, Clone)]
pub enum InputClass {
CursorButton,
CursorScroll,
CursorPos,
Codepoint,
Key,
Viewport,
Relative,
Led,
Switch,
}

View file

@ -1,63 +1,36 @@
mod elixir_types;
mod message;
use crate::message::{IpcExchange, Message};
use crate::elixir_types::{Color, DriverConfig, InputClass, ScriptItem};
use ipc_channel::ipc::{IpcOneShotServer, IpcReceiver, IpcSender};
use rustler::types::OwnedBinary;
use rustler::{Atom, Env, Error, NifStruct, ResourceArc, Term};
use std::env;
use std::io::stdout;
use rustler::{Env, NifResult, ResourceArc, Term};
use std::process::{Child, Command, Stdio};
use std::sync::Mutex;
#[derive(NifStruct)]
#[module = "Scenic.Driver.Renderling.Window.Config"]
pub struct WindowConfig {
pub title: String,
pub resizeable: bool,
pub width: u32,
pub height: u32,
pub server_path: String,
}
use std::thread::sleep;
struct IpcResourceInner {
ipc_channel_name: String,
sender: IpcSender<Message>,
receiver: IpcReceiver<Message>,
sender: IpcSender<message::Message>,
receiver: IpcReceiver<message::Message>,
handle: Child,
}
impl Drop for IpcResource {
fn drop(&mut self) {
println!("Shutting down server.");
let mut mutex = self.0.lock().unwrap();
// mutex.sender.send(Message::Shutdown).unwrap();
mutex.handle.try_wait().unwrap();
}
}
struct IpcResource(Mutex<IpcResourceInner>);
#[rustler::nif]
fn init(cfg: WindowConfig) -> ResourceArc<IpcResource> {
let (ipc, ipc_channel_name) = IpcOneShotServer::<IpcExchange>::new().unwrap();
fn init(cfg: DriverConfig) -> ResourceArc<IpcResource> {
let (ipc, ipc_channel_name) = IpcOneShotServer::<message::IpcExchange>::new().unwrap();
let handle = Command::new(cfg.server_path)
let handle = Command::new(cfg.server_path.clone())
.arg("--ipc-channel")
.arg(&ipc_channel_name)
.stdout(Stdio::inherit())
.spawn()
.unwrap();
let (_, IpcExchange { sender, receiver }) = ipc.accept().unwrap();
let (_, message::IpcExchange { sender, receiver }) = ipc.accept().unwrap();
sender
.send(Message::Init {
title: cfg.title,
resizeable: cfg.resizeable,
width: cfg.width,
height: cfg.height,
})
.unwrap();
sender.send(cfg.into()).unwrap();
ResourceArc::new(IpcResource(Mutex::new(IpcResourceInner {
ipc_channel_name,
@ -67,6 +40,94 @@ fn init(cfg: WindowConfig) -> ResourceArc<IpcResource> {
})))
}
#[rustler::nif]
fn update_scene(script: Term, script_id: String, resource: ResourceArc<IpcResource>) {
let script = script
.into_list_iterator()
.unwrap()
.map(|term| {
term.decode::<ScriptItem>()
.map(|script_item| script_item.into())
})
.collect::<NifResult<Vec<message::ScriptItem>>>()
.unwrap();
let mutex = resource.0.lock().unwrap();
mutex
.sender
.send(message::Message::UpdateScene(script_id, script))
.unwrap();
}
#[rustler::nif]
fn clear_color(color: Color, resource: ResourceArc<IpcResource>) {
let mutex = resource.0.lock().unwrap();
mutex
.sender
.send(message::Message::ClearColor(color.into()))
.unwrap();
}
#[rustler::nif]
fn reset_scene(resource: ResourceArc<IpcResource>) {
let mutex = resource.0.lock().unwrap();
mutex.sender.send(message::Message::ResetScene).unwrap();
}
#[rustler::nif]
fn del_scripts(ids: Term, resource: ResourceArc<IpcResource>) {
let ids = ids
.into_list_iterator()
.unwrap()
.map(|x| x.decode::<String>())
.collect::<NifResult<Vec<String>>>()
.unwrap();
let mutex = resource.0.lock().unwrap();
mutex
.sender
.send(message::Message::DeleteScripts(ids))
.unwrap();
}
#[rustler::nif]
fn request_input(classes: Term, resource: ResourceArc<IpcResource>) {
let classes = classes
.into_list_iterator()
.unwrap()
.map(|term| {
term.decode::<InputClass>()
.map(|input_class| input_class.into())
})
.collect::<NifResult<Vec<message::InputClass>>>()
.unwrap();
let mutex = resource.0.lock().unwrap();
mutex
.sender
.send(message::Message::RequestInput(classes))
.unwrap();
}
#[rustler::nif]
fn terminate(resource: ResourceArc<IpcResource>) {
println!("Shutting down server.");
let mut mutex = resource.0.lock().unwrap();
if let None = mutex.handle.try_wait().unwrap() {
mutex.sender.send(message::Message::Shutdown).unwrap();
drop(mutex);
std::thread::spawn(move || {
// We give the server 100 millis to shut down and then we just kill
// it.
sleep(std::time::Duration::from_micros(100));
let mut mutex = resource.0.lock().unwrap();
if let None = mutex.handle.try_wait().unwrap() {
mutex.handle.kill().unwrap();
}
});
}
}
fn load(env: Env, _: Term) -> bool {
rustler::resource!(IpcResource, env);
true
@ -74,6 +135,13 @@ fn load(env: Env, _: Term) -> bool {
rustler::init!(
"Elixir.Scenic.Driver.Renderling.Window.Nif",
[init],
[
init,
update_scene,
reset_scene,
del_scripts,
clear_color,
terminate
],
load = load
);

View file

@ -1,15 +1,655 @@
use crate::elixir_types;
use ipc_channel::ipc::{IpcReceiver, IpcSender};
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum Orientation {
Normal,
Left,
Right,
UpsideDown,
}
impl From<elixir_types::WindowOrientation> for Orientation {
fn from(orientation: elixir_types::WindowOrientation) -> Self {
match orientation {
elixir_types::WindowOrientation::Normal => Orientation::Normal,
elixir_types::WindowOrientation::Left => Orientation::Left,
elixir_types::WindowOrientation::Right => Orientation::Right,
elixir_types::WindowOrientation::UpsideDown => Orientation::UpsideDown,
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum OnClose {
Restart,
StopDriver,
StopViewport,
StopSystem,
HaltSystem,
}
impl From<elixir_types::OnCloseAction> for OnClose {
fn from(on_close: elixir_types::OnCloseAction) -> Self {
match on_close {
elixir_types::OnCloseAction::Restart => OnClose::Restart,
elixir_types::OnCloseAction::StopDriver => OnClose::StopDriver,
elixir_types::OnCloseAction::StopViewport => OnClose::StopViewport,
elixir_types::OnCloseAction::StopSystem => OnClose::StopSystem,
elixir_types::OnCloseAction::HaltSystem => OnClose::HaltSystem,
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum Color {
Gray(u8),
GrayAlpha(u8, u8),
Rgb(u8, u8, u8),
RgbA(u8, u8, u8, u8),
Hsv(f32, f32, f32),
Hsl(f32, f32, f32),
}
impl From<elixir_types::Color> for Color {
fn from(color: elixir_types::Color) -> Self {
match color {
elixir_types::Color::ColorG(g) => Color::Gray(g),
elixir_types::Color::ColorGa((g, a)) => Color::GrayAlpha(g, a),
elixir_types::Color::ColorRgb((r, g, b)) => Color::Rgb(r, g, b),
elixir_types::Color::ColorRgba((r, g, b, a)) => Color::RgbA(r, g, b, a),
elixir_types::Color::ColorHsv((h, s, v)) => Color::Hsv(h, s, v),
elixir_types::Color::ColorHsl((h, s, l)) => Color::Hsl(h, s, l),
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum InputClass {
CursorButton,
CursorScroll,
CursorPos,
Codepoint,
Key,
Viewport,
Relative,
Led,
Switch,
}
impl From<elixir_types::InputClass> for InputClass {
fn from(input_class: elixir_types::InputClass) -> Self {
match input_class {
elixir_types::InputClass::CursorButton => InputClass::CursorButton,
elixir_types::InputClass::CursorScroll => InputClass::CursorScroll,
elixir_types::InputClass::CursorPos => InputClass::CursorPos,
elixir_types::InputClass::Codepoint => InputClass::Codepoint,
elixir_types::InputClass::Key => InputClass::Key,
elixir_types::InputClass::Viewport => InputClass::Viewport,
elixir_types::InputClass::Relative => InputClass::Relative,
elixir_types::InputClass::Led => InputClass::Led,
elixir_types::InputClass::Switch => InputClass::Switch,
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum FillStroke {
Fill,
Stroke,
FillStroke,
}
impl From<elixir_types::FillStroke> for FillStroke {
fn from(fill_stroke: elixir_types::FillStroke) -> Self {
match fill_stroke {
elixir_types::FillStroke::Fill => FillStroke::Fill,
elixir_types::FillStroke::FillStroke => FillStroke::FillStroke,
elixir_types::FillStroke::Stroke => FillStroke::Stroke,
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct SpriteDrawCmd {
src_x: u32,
src_y: u32,
src_w: u32,
src_h: u32,
dst_x: u32,
dst_y: u32,
dst_w: u32,
dst_h: u32,
}
impl From<elixir_types::SpriteDrawCmd> for SpriteDrawCmd {
fn from(cmd: elixir_types::SpriteDrawCmd) -> Self {
SpriteDrawCmd {
src_x: cmd.src_xy.0,
src_y: cmd.src_xy.1,
src_w: cmd.src_wh.0,
src_h: cmd.src_wh.1,
dst_x: cmd.dst_xy.0,
dst_y: cmd.dst_xy.1,
dst_w: cmd.dst_wh.0,
dst_h: cmd.dst_wh.1,
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum Cap {
Butt,
Round,
Square,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum Join {
Bevel,
Round,
Miter,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum TextAlign {
Left,
Center,
Right,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum TextBase {
Top,
Middle,
Alphabetic,
Bottom,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum ScriptItem {
PushState,
PopState,
PushPopState,
Clear(Color),
DrawLine {
x0: u32,
y0: u32,
x1: u32,
y1: u32,
fill_stroke: FillStroke,
},
DrawQuad {
x0: u32,
y0: u32,
x1: u32,
y1: u32,
x2: u32,
y2: u32,
x3: u32,
y3: u32,
fill_stroke: FillStroke,
},
DrawRect {
width: u32,
height: u32,
fill_stroke: FillStroke,
},
DrawRRect {
width: u32,
height: u32,
radius: u32,
fill_stroke: FillStroke,
},
DrawSector {
radius: u32,
radians: f32,
fill_stroke: FillStroke,
},
DrawArc {
radius: u32,
radians: f32,
fill_stroke: FillStroke,
},
DrawCircle {
radius: u32,
fill_stroke: FillStroke,
},
DrawEllipse {
radius0: u32,
radius1: u32,
fill_stroke: FillStroke,
},
DrawSprite {
src_id: String,
cmds: Vec<SpriteDrawCmd>,
},
DrawText(String),
DrawTriangle {
x0: u32,
y0: u32,
x1: u32,
y1: u32,
x2: u32,
y2: u32,
fill_stroke: FillStroke,
},
Script(String),
BeginPath,
ClosePath,
FillPath,
StrokePath,
MoveTo {
x: u32,
y: u32,
},
LineTo {
x: u32,
y: u32,
},
ArcTo {
x0: u32,
y0: u32,
x1: u32,
y1: u32,
radius: u32,
},
BezierTo {
cp1x: u32,
cp1y: u32,
cp2x: u32,
cp2y: u32,
x: u32,
y: u32,
},
QuadraticTo {
cpx: u32,
cpy: u32,
x: u32,
y: u32,
},
Quad {
x0: u32,
y0: u32,
x1: u32,
y1: u32,
x2: u32,
y2: u32,
x3: u32,
y3: u32,
},
Rect {
width: u32,
height: u32,
},
RRect {
width: u32,
height: u32,
radius: u32,
},
Sector {
radius: u32,
radians: f32,
},
Circle {
radius: u32,
},
Ellipse {
radius0: u32,
radius1: u32,
},
Triangle {
x0: u32,
y0: u32,
x1: u32,
y1: u32,
x2: u32,
y2: u32,
},
Scale {
x: f32,
y: f32,
},
Rotate {
radians: f32,
},
Translate {
x: u32,
y: u32,
},
Transform {
a: f32,
b: f32,
c: f32,
d: f32,
e: f32,
f: f32,
},
FillColor(Color),
FillLinear {
start_x: u32,
start_y: u32,
end_x: u32,
end_y: u32,
color_start: Color,
color_end: Color,
},
FillRadial {
center_x: u32,
center_y: u32,
inner_radius: u32,
outer_radius: u32,
color_start: Color,
color_end: Color,
},
FillImage(String),
StrokeColor(Color),
StrokeLinear {
start_x: u32,
start_u: u32,
end_x: u32,
end_y: u32,
color_start: Color,
color_end: Color,
},
StrokeRadial {
center_x: u32,
center_y: u32,
inner_radius: u32,
outer_radius: u32,
color_start: Color,
color_end: Color,
},
StrokeImage(String),
StrokeStream(String),
StrokeWidth(u32),
Cap(Cap),
Join(Join),
MiterLimit(u32),
Scissor {
width: u32,
height: u32,
},
Font(String),
FontSize(u32),
TextAlign(TextAlign),
TextBase(TextBase),
}
impl From<elixir_types::ScriptItem> for ScriptItem {
fn from(script_item: elixir_types::ScriptItem) -> Self {
match script_item {
elixir_types::ScriptItem::PushState => ScriptItem::PushState,
elixir_types::ScriptItem::PopState => ScriptItem::PopState,
elixir_types::ScriptItem::PushPopState => ScriptItem::PushPopState,
elixir_types::ScriptItem::Clear(color) => ScriptItem::Clear(color.into()),
elixir_types::ScriptItem::DrawLine(dl) => ScriptItem::DrawLine {
x0: dl.x0,
y0: dl.y0,
x1: dl.x1,
y1: dl.y1,
fill_stroke: dl.fill_stroke.into(),
},
elixir_types::ScriptItem::DrawQuad(dq) => ScriptItem::DrawQuad {
x0: dq.x0,
y0: dq.y0,
x1: dq.x1,
y1: dq.y1,
x2: dq.x2,
y2: dq.y2,
x3: dq.x3,
y3: dq.y3,
fill_stroke: dq.fill_stroke.into(),
},
elixir_types::ScriptItem::DrawRect(r) => ScriptItem::DrawRect {
width: r.width,
height: r.height,
fill_stroke: r.fill_stroke.into(),
},
elixir_types::ScriptItem::DrawRrect(r) => ScriptItem::DrawRRect {
width: r.width,
height: r.height,
radius: r.radius,
fill_stroke: r.fill_stroke.into(),
},
elixir_types::ScriptItem::DrawSector(sector) => ScriptItem::DrawSector {
radius: sector.radius,
radians: sector.radians,
fill_stroke: sector.fill_stroke.into(),
},
elixir_types::ScriptItem::DrawArc(arc) => ScriptItem::DrawArc {
radius: arc.radius,
radians: arc.radians,
fill_stroke: arc.fill_stroke.into(),
},
elixir_types::ScriptItem::DrawCircle(c) => ScriptItem::DrawCircle {
radius: c.radius,
fill_stroke: c.fill_stroke.into(),
},
elixir_types::ScriptItem::DrawEllipse(e) => ScriptItem::DrawEllipse {
radius0: e.radius0,
radius1: e.radius1,
fill_stroke: e.fill_stroke.into(),
},
elixir_types::ScriptItem::DrawSprites(s) => ScriptItem::DrawSprite {
src_id: s.src_id,
cmds: s
.cmds
.iter()
.map(|x| x.clone().into())
.collect::<Vec<SpriteDrawCmd>>(),
},
elixir_types::ScriptItem::DrawText(text) => ScriptItem::DrawText(text),
elixir_types::ScriptItem::DrawTriangle(t) => ScriptItem::DrawTriangle {
x0: t.x0,
y0: t.y0,
x1: t.x1,
y1: t.y1,
x2: t.x2,
y2: t.y2,
fill_stroke: t.fill_stroke.into(),
},
elixir_types::ScriptItem::Script(id) => ScriptItem::Script(id),
elixir_types::ScriptItem::BeginPath => ScriptItem::BeginPath,
elixir_types::ScriptItem::ClosePath => ScriptItem::ClosePath,
elixir_types::ScriptItem::FillPath => ScriptItem::FillPath,
elixir_types::ScriptItem::StrokePath => ScriptItem::StrokePath,
elixir_types::ScriptItem::MoveTo(p) => ScriptItem::MoveTo { x: p.0, y: p.1 },
elixir_types::ScriptItem::LineTo(p) => ScriptItem::LineTo { x: p.0, y: p.1 },
elixir_types::ScriptItem::ArcTo(a) => ScriptItem::ArcTo {
x0: a.x0,
y0: a.y0,
x1: a.x1,
y1: a.y1,
radius: a.radius,
},
elixir_types::ScriptItem::BezierTo(b) => ScriptItem::BezierTo {
cp1x: b.cp1x,
cp1y: b.cp1y,
cp2x: b.cp2x,
cp2y: b.cp2y,
x: b.x,
y: b.y,
},
elixir_types::ScriptItem::QuadraticTo(q) => ScriptItem::QuadraticTo {
cpx: q.cpx,
cpy: q.cpy,
x: q.x,
y: q.y,
},
elixir_types::ScriptItem::Quad(q) => ScriptItem::Quad {
x0: q.x0,
y0: q.y0,
x1: q.x1,
y1: q.y1,
x2: q.x2,
y2: q.y2,
x3: q.x3,
y3: q.y3,
},
elixir_types::ScriptItem::Rect(r) => ScriptItem::Rect {
width: r.width,
height: r.height,
},
elixir_types::ScriptItem::Rrect(r) => ScriptItem::RRect {
width: r.width,
height: r.height,
radius: r.radius,
},
elixir_types::ScriptItem::Sector(s) => ScriptItem::Sector {
radius: s.radius,
radians: s.radians,
},
elixir_types::ScriptItem::Circle(c) => ScriptItem::Circle { radius: c.radius },
elixir_types::ScriptItem::Ellipse(e) => ScriptItem::Ellipse {
radius0: e.radius0,
radius1: e.radius1,
},
elixir_types::ScriptItem::Triangle(t) => ScriptItem::Triangle {
x0: t.x0,
y0: t.y0,
x1: t.x1,
y1: t.y1,
x2: t.x2,
y2: t.y2,
},
elixir_types::ScriptItem::Scale(s) => ScriptItem::Scale { x: s.x, y: s.y },
elixir_types::ScriptItem::Rotate(r) => ScriptItem::Rotate { radians: r },
elixir_types::ScriptItem::Translate(p) => ScriptItem::Translate { x: p.0, y: p.1 },
elixir_types::ScriptItem::Transform(t) => ScriptItem::Transform {
a: t.a,
b: t.b,
c: t.c,
d: t.d,
e: t.e,
f: t.f,
},
elixir_types::ScriptItem::FillColor(c) => ScriptItem::FillColor(c.into()),
elixir_types::ScriptItem::FillLinear(l) => ScriptItem::FillLinear {
start_x: l.start_x,
start_y: l.start_y,
end_x: l.end_x,
end_y: l.end_y,
color_start: l.color_start.into(),
color_end: l.color_end.into(),
},
elixir_types::ScriptItem::FillRadial(r) => ScriptItem::FillRadial {
center_x: r.center_x,
center_y: r.center_y,
inner_radius: r.inner_radius,
outer_radius: r.outer_radius,
color_start: r.color_start.into(),
color_end: r.color_end.into(),
},
elixir_types::ScriptItem::FillImage(i) => ScriptItem::FillImage(i),
elixir_types::ScriptItem::StrokeColor(c) => ScriptItem::StrokeColor(c.into()),
elixir_types::ScriptItem::StrokeLinear(l) => ScriptItem::StrokeLinear {
start_x: l.start_x,
start_u: l.start_u,
end_x: l.end_x,
end_y: l.end_y,
color_start: l.color_start.into(),
color_end: l.color_end.into(),
},
elixir_types::ScriptItem::StrokeRadial(r) => ScriptItem::StrokeRadial {
center_x: r.center_x,
center_y: r.center_y,
inner_radius: r.inner_radius,
outer_radius: r.outer_radius,
color_start: r.color_start.into(),
color_end: r.color_end.into(),
},
elixir_types::ScriptItem::StrokeImage(s) => ScriptItem::StrokeImage(s),
elixir_types::ScriptItem::StrokeStream(s) => ScriptItem::StrokeStream(s),
elixir_types::ScriptItem::StrokeWidth(w) => ScriptItem::StrokeWidth(w),
elixir_types::ScriptItem::Cap(elixir_types::Cap::Butt) => ScriptItem::Cap(Cap::Butt),
elixir_types::ScriptItem::Cap(elixir_types::Cap::Round) => ScriptItem::Cap(Cap::Round),
elixir_types::ScriptItem::Cap(elixir_types::Cap::Square) => {
ScriptItem::Cap(Cap::Square)
}
elixir_types::ScriptItem::Join(elixir_types::Join::Bevel) => {
ScriptItem::Join(Join::Bevel)
}
elixir_types::ScriptItem::Join(elixir_types::Join::Round) => {
ScriptItem::Join(Join::Round)
}
elixir_types::ScriptItem::Join(elixir_types::Join::Miter) => {
ScriptItem::Join(Join::Miter)
}
elixir_types::ScriptItem::MiterLimit(l) => ScriptItem::MiterLimit(l),
elixir_types::ScriptItem::Scissor(s) => ScriptItem::Scissor {
width: s.width,
height: s.height,
},
elixir_types::ScriptItem::Font(s) => ScriptItem::Font(s),
elixir_types::ScriptItem::FontSize(u32) => ScriptItem::FontSize(u32),
elixir_types::ScriptItem::TextAlign(elixir_types::TextAlign::Left) => {
ScriptItem::TextAlign(TextAlign::Left)
}
elixir_types::ScriptItem::TextAlign(elixir_types::TextAlign::Center) => {
ScriptItem::TextAlign(TextAlign::Center)
}
elixir_types::ScriptItem::TextAlign(elixir_types::TextAlign::Right) => {
ScriptItem::TextAlign(TextAlign::Right)
}
elixir_types::ScriptItem::TextBase(elixir_types::TextBase::Top) => {
ScriptItem::TextBase(TextBase::Top)
}
elixir_types::ScriptItem::TextBase(elixir_types::TextBase::Middle) => {
ScriptItem::TextBase(TextBase::Middle)
}
elixir_types::ScriptItem::TextBase(elixir_types::TextBase::Alphabetic) => {
ScriptItem::TextBase(TextBase::Alphabetic)
}
elixir_types::ScriptItem::TextBase(elixir_types::TextBase::Bottom) => {
ScriptItem::TextBase(TextBase::Bottom)
}
_ => unimplemented!(),
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum Message {
Shutdown,
ResetScene,
Init {
opacity: u32,
debug: bool,
antialias: bool,
centered: bool,
scaled: bool,
orientation: Orientation,
title: String,
resizeable: bool,
width: u32,
height: u32,
on_close: OnClose,
},
ClearColor(Color),
DeleteScripts(Vec<String>),
RequestInput(Vec<InputClass>),
UpdateScene(String, Vec<ScriptItem>),
}
impl From<elixir_types::DriverConfig> for Message {
fn from(driver_config: elixir_types::DriverConfig) -> Self {
Message::Init {
opacity: driver_config.opacity,
debug: driver_config.debug,
antialias: driver_config.antialias,
centered: driver_config.position.centered,
scaled: driver_config.position.scaled,
orientation: driver_config.position.orientation.into(),
title: driver_config.window.title,
resizeable: driver_config.window.resizeable,
width: driver_config.window.width,
height: driver_config.window.height,
on_close: driver_config.on_close.into(),
}
}
}
#[derive(Debug, Serialize, Deserialize)]

View file

@ -1,12 +1,16 @@
mod elixir_types;
mod message;
use std::process::exit;
use crate::message::{IpcExchange, Message};
use clap::Parser;
use ipc_channel::ipc;
use ipc_channel::ipc::IpcSender;
use message::{OnClose, Orientation};
use winit::application::ApplicationHandler;
use winit::dpi::LogicalSize;
use winit::dpi::{LogicalPosition, LogicalSize};
use winit::event::WindowEvent;
use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::keyboard::{Key, ModifiersState};
@ -34,7 +38,7 @@ fn main() -> anyhow::Result<()> {
let sender = IpcSender::connect(args.ipc_channel)?;
let (c2s_send, c2s_recv) = ipc::channel()?;
let (s2c_send, s2c_recv) = ipc::channel()?;
let (_s2c_send, s2c_recv) = ipc::channel()?;
sender.send(IpcExchange {
sender: c2s_send,
receiver: s2c_recv,
@ -48,7 +52,12 @@ fn main() -> anyhow::Result<()> {
std::thread::spawn(move || loop {
let event = c2s_recv.recv().unwrap();
event_loop_proxy.send_event(event).unwrap();
match event {
Message::Shutdown => exit(0),
Message::Init { .. } => unreachable!(),
_ => event_loop_proxy.send_event(event).unwrap(),
};
});
let mut state = Application::new(msg);
@ -59,10 +68,17 @@ fn main() -> anyhow::Result<()> {
}
struct Application {
opacity: u32,
debug: bool,
antialias: bool,
centered: bool,
scaled: bool,
orientation: Orientation,
title: String,
resizeable: bool,
width: u32,
height: u32,
on_close: OnClose,
window: Option<Window>,
}
@ -70,15 +86,29 @@ impl Application {
fn new(event: Message) -> Self {
match event {
Message::Init {
opacity,
debug,
antialias,
centered,
scaled,
orientation,
title,
resizeable,
width,
height,
on_close,
} => Application {
opacity,
debug,
antialias,
centered,
scaled,
orientation,
title,
resizeable,
width,
height,
on_close,
window: None,
},
_ => unreachable!(),
@ -104,7 +134,16 @@ impl ApplicationHandler<Message> for Application {
let window_attributes = Window::default_attributes()
.with_title(self.title.clone())
.with_resizable(self.resizeable)
.with_inner_size(LogicalSize::new(self.width, self.height));
.with_inner_size(LogicalSize::new(self.width as f32, self.height as f32));
let window_attributes = if self.centered {
let display_size = event_loop.primary_monitor().unwrap().size();
let center_x = (display_size.width as f32 / 2.0) - (self.width as f32 / 2.0);
let center_y = (display_size.height as f32 / 2.0) - (self.height as f32 / 2.0);
window_attributes.with_position(LogicalPosition::new(center_x, center_y))
} else {
window_attributes
};
self.window = event_loop.create_window(window_attributes).ok();
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

16
test/support/example.ex Normal file
View file

@ -0,0 +1,16 @@
defmodule Example do
@default_viewport [
name: :main_viewport,
size: {640, 480},
default_scene: ExampleScene,
drivers: [
[
module: Scenic.Driver.Renderling.Window,
name: :window,
window: [resizeable: false, title: "Example Window"]
]
]
]
def start_link(viewport \\ @default_viewport), do: Scenic.start_link([viewport])
end

View file

@ -0,0 +1,3 @@
defmodule ExampleAssets do
use Scenic.Assets.Static, otp_app: :scenic_driver_renderling, alias: []
end

View file

@ -0,0 +1,16 @@
defmodule ExampleScene do
@moduledoc false
use Scenic.Scene
alias Scenic.Graph
import Scenic.Primitives
@graph Graph.build()
|> text("Hello, World!", font_size: 22, translate: {20, 80})
@doc false
@impl true
def init(scene, _args, _opts) do
{:ok, push_graph(scene, @graph)}
end
end