refactor: extract renderer from scenic_window_server.

This commit is contained in:
James Harton 2024-05-17 12:26:03 +12:00
parent 7a812e8e63
commit 78244ffdda
Signed by: james
GPG key ID: 90E82DAA13F624F4
22 changed files with 4178 additions and 1331 deletions

View file

@ -58,6 +58,6 @@ defmodule Scenic.Driver.Renderling.Window.Config do
defp compute_server_path do
:scenic_driver_renderling
|> :code.priv_dir()
|> Path.join("native/renderling_window_server")
|> Path.join("native/scenic_window_server")
end
end

View file

@ -1,5 +1,5 @@
defmodule Scenic.Driver.Renderling.Window.Nif do
use Rustler, otp_app: :scenic_driver_renderling, crate: :renderling_window
use Rustler, otp_app: :scenic_driver_renderling, crate: :scenic_window
def init(_config), do: :erlang.nif_error(:nif_not_loaded)
def update_scene(_script, _script_id, _resource), do: :erlang.nif_error(:nif_not_loaded)

View file

@ -10,9 +10,11 @@ defmodule Scenic.Driver.Renderling.Window do
require Logger
@position_schema [
scaled: [type: :boolean, default: false],
centered: [type: :boolean, default: false],
orientation: [type: {:in, [:normal, :left, :right, :upside_down]}, default: :normal]
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 [
@ -22,7 +24,6 @@ defmodule Scenic.Driver.Renderling.Window do
@opts_schema [
name: [type: {:or, [:atom, :string]}],
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],

View file

@ -1,760 +0,0 @@
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, Eq, PartialEq, Hash)]
pub enum ModifierKey {
Meta,
Alt,
Ctrl,
Shift,
CapsLock,
NumLock,
ScrollLock,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct Point {
pub x: u32,
pub y: u32,
}
impl From<winit::dpi::PhysicalPosition<f64>> for Point {
fn from(pos: winit::dpi::PhysicalPosition<f64>) -> Self {
Point {
x: pos.x.round() as u32,
y: pos.y.round() as u32,
}
}
}
impl From<winit::dpi::PhysicalSize<u32>> for Point {
fn from(pos: winit::dpi::PhysicalSize<u32>) -> Self {
Point {
x: pos.width,
y: pos.height,
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum ViewPortEvent {
Enter,
Exit,
Reshape,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum ElementState {
Pressed,
Released,
}
impl From<winit::event::ElementState> for ElementState {
fn from(state: winit::event::ElementState) -> Self {
match state {
winit::event::ElementState::Pressed => ElementState::Pressed,
winit::event::ElementState::Released => ElementState::Released,
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum InputEvent {
CodePoint {
codepoint: u32,
modifiers: Vec<ModifierKey>,
},
Key {
key: winit::keyboard::KeyCode,
state: ElementState,
modifiers: Vec<ModifierKey>,
},
CursorButton {
button: winit::event::MouseButton,
state: ElementState,
modifiers: Vec<ModifierKey>,
position: Point,
},
CursorScroll {
offset: Point,
position: Point,
},
CursorPos {
position: Point,
},
ViewPort {
event: ViewPortEvent,
position: Point,
},
Relative {
position: Point,
},
Led {
id: String,
value: u32,
},
Switch {
id: String,
value: u32,
},
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum ShutdownReason {
Normal,
Terminate,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum Message {
Shutdown(ShutdownReason),
ResetScene,
Init {
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>),
InputEvent(InputEvent),
}
impl From<elixir_types::DriverConfig> for Message {
fn from(driver_config: elixir_types::DriverConfig) -> Self {
Message::Init {
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)]
pub struct IpcExchange {
pub sender: IpcSender<Message>,
pub receiver: IpcReceiver<Message>,
}

View file

@ -1,119 +0,0 @@
use std::collections::HashMap;
use renderling::{
camera::Camera,
math::{UVec2, Vec4},
slab::Hybrid,
stage::Stage,
Context,
};
use crate::message::{Color, ScriptItem};
pub struct Renderer {
pub stage: Stage,
pub ctx: Context,
camera: Hybrid<Camera>,
scripts: HashMap<String, Vec<ScriptItem>>,
}
impl Renderer {
pub fn new(ctx: Context) -> Self {
let mut stage = ctx
.new_stage()
.with_lighting(false)
.with_bloom(false)
.with_background_color(Vec4::new(1.0, 0.0, 0.0, 1.0));
let size = ctx.get_size();
let camera = stage.new_value(Camera::default_ortho2d(size.x as f32, size.y as f32));
let scripts = HashMap::default();
Self {
stage,
camera,
ctx,
scripts,
}
}
pub fn set_size(&mut self, width: u32, height: u32) {
self.ctx.set_size(UVec2::new(width, height));
}
pub fn redraw(&mut self) {
self.ctx.get_device().poll(wgpu::Maintain::Wait);
}
pub fn render_frame(&mut self) {
let frame = self.ctx.get_next_frame().unwrap();
self.stage.render(&frame.view());
frame.present();
}
pub fn clear_color(&mut self, color: Color) {
self.stage.set_background_color(color);
}
pub fn reset_scene(&mut self) {
// ?
}
pub fn update_scene(&mut self, script_id: String, script: Vec<ScriptItem>) {
self.scripts.insert(script_id, script);
}
pub fn delete_scripts(&mut self, script_ids: Vec<String>) {
for script_id in script_ids {
self.scripts.remove(&script_id);
}
}
}
impl Into<Vec4> for Color {
fn into(self) -> Vec4 {
match self {
Color::Gray(g) => {
let c = g as f32 / 255 as f32;
Vec4::new(c, c, c, 1.0)
}
Color::GrayAlpha(g, a) => {
let c = g as f32 / 255 as f32;
let a = a as f32 / 255 as f32;
Vec4::new(c, c, c, a)
}
Color::Rgb(r, g, b) => {
let r = r as f32 / 255 as f32;
let g = g as f32 / 255 as f32;
let b = b as f32 / 255 as f32;
Vec4::new(r, g, b, 1.0)
}
Color::RgbA(r, g, b, a) => {
let r = r as f32 / 255 as f32;
let g = g as f32 / 255 as f32;
let b = b as f32 / 255 as f32;
let a = a as f32 / 255 as f32;
Vec4::new(r, g, b, a)
}
Color::Hsv(h, s, v) => {
let (r, g, b) = hsv::hsv_to_rgb(h as f64, s as f64, v as f64);
let r = r as f32 / 255 as f32;
let g = g as f32 / 255 as f32;
let b = b as f32 / 255 as f32;
Vec4::new(r, g, b, 1.0)
}
Color::Hsl(h, s, l) => {
let (r, g, b) = hsl::HSL {
h: h as f64,
s: s as f64,
l: l as f64,
}
.to_rgb();
let r = r as f32 / 255 as f32;
let g = g as f32 / 255 as f32;
let b = b as f32 / 255 as f32;
Vec4::new(r, g, b, 1.0)
}
}
}
}

View file

@ -1,407 +0,0 @@
mod elixir_types;
mod message;
mod render;
use std::collections::HashSet;
use std::process::exit;
use std::sync::Arc;
use crate::message::{IpcExchange, Message};
use clap::Parser;
use ipc_channel::ipc;
use ipc_channel::ipc::IpcSender;
use message::{InputClass, ModifierKey, OnClose, Orientation};
use winit::application::ApplicationHandler;
use winit::dpi::{LogicalPosition, LogicalSize};
use winit::event::{ElementState, KeyEvent, WindowEvent};
use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::keyboard::{Key, NamedKey, PhysicalKey};
use winit::window::{Window, WindowId};
use renderling::{math::UVec2, Context};
use crate::render::Renderer;
#[cfg(macos_platform)]
use winit::platform::macos::{OptionAsAlt, WindowAttributesExtMacOS, WindowExtMacOS};
#[cfg(any(x11_platform, wayland_platform))]
use winit::platform::startup_notify::{
self, EventLoopExtStartupNotify, WindowAttributesExtStartupNotify, WindowExtStartupNotify,
};
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct CmdArgs {
#[arg(long)]
ipc_channel: String,
}
fn main() -> anyhow::Result<()> {
let args = CmdArgs::parse();
println!("Server: Started.");
let sender = IpcSender::connect(args.ipc_channel)?;
let (c2s_send, c2s_recv) = ipc::channel()?;
let (s2c_send, s2c_recv) = ipc::channel()?;
sender.send(IpcExchange {
sender: c2s_send,
receiver: s2c_recv,
})?;
let msg = c2s_recv.recv()?;
if let &Message::Init { .. } = &msg {
let event_loop = EventLoop::<Message>::with_user_event().build()?;
let event_loop_proxy = event_loop.create_proxy();
std::thread::spawn(move || loop {
let event = c2s_recv.recv().unwrap();
match event {
Message::Init { .. } => unreachable!(),
Message::Shutdown(message::ShutdownReason::Normal) => exit(0),
Message::Shutdown(message::ShutdownReason::Terminate) => exit(1),
_ => event_loop_proxy.send_event(event).unwrap(),
};
});
let mut state = Application::new(msg, s2c_send);
event_loop.run_app(&mut state).map_err(Into::into)
} else {
panic!("Server: Invalid message received {:?}", msg);
}
}
struct Application {
antialias: bool,
centered: bool,
scaled: bool,
orientation: Orientation,
title: String,
resizeable: bool,
width: u32,
height: u32,
on_close: OnClose,
window: Option<Arc<Window>>,
renderer: Option<Renderer>,
input_classes: Vec<InputClass>,
sender: IpcSender<Message>,
current_modifier_keys: HashSet<message::ModifierKey>,
last_cursor_pos: message::Point,
}
impl Application {
fn new(event: Message, sender: IpcSender<Message>) -> Self {
match event {
Message::Init {
antialias,
centered,
scaled,
orientation,
title,
resizeable,
width,
height,
on_close,
} => Application {
antialias,
centered,
scaled,
orientation,
title,
resizeable,
width,
height,
on_close,
window: None,
input_classes: vec![
InputClass::CursorButton,
InputClass::CursorScroll,
InputClass::CursorPos,
InputClass::Codepoint,
InputClass::Key,
InputClass::Viewport,
InputClass::Relative,
InputClass::Led,
InputClass::Switch,
],
sender,
current_modifier_keys: HashSet::default(),
last_cursor_pos: message::Point { x: 0, y: 0 },
renderer: None,
},
_ => unreachable!(),
}
}
}
impl ApplicationHandler<Message> for Application {
fn user_event(&mut self, _event_loop: &ActiveEventLoop, event: Message) {
if let Some(renderer) = &mut self.renderer {
match event {
Message::ClearColor(color) => renderer.clear_color(color),
Message::ResetScene => renderer.reset_scene(),
Message::UpdateScene(script_id, script) => renderer.update_scene(script_id, script),
Message::DeleteScripts(script_ids) => renderer.delete_scripts(script_ids),
_ => {
println!("unhandled message event {:?}", event)
}
};
}
}
fn window_event(
&mut self,
_event_loop: &ActiveEventLoop,
_window_id: WindowId,
event: WindowEvent,
) {
match event {
// Keep a record of which modifier keys are pressed at any given time.
WindowEvent::KeyboardInput { event, .. } => {
match event {
KeyEvent {
logical_key: Key::Named(NamedKey::Meta),
state: ElementState::Pressed,
..
} => self.current_modifier_keys.insert(ModifierKey::Meta),
KeyEvent {
logical_key: Key::Named(NamedKey::Meta),
state: ElementState::Released,
..
} => self.current_modifier_keys.remove(&ModifierKey::Meta),
KeyEvent {
logical_key: Key::Named(NamedKey::Alt),
state: ElementState::Pressed,
..
} => self.current_modifier_keys.insert(ModifierKey::Alt),
KeyEvent {
logical_key: Key::Named(NamedKey::Alt),
state: ElementState::Released,
..
} => self.current_modifier_keys.remove(&ModifierKey::Alt),
KeyEvent {
logical_key: Key::Named(NamedKey::Control),
state: ElementState::Pressed,
..
} => self.current_modifier_keys.insert(ModifierKey::Ctrl),
KeyEvent {
logical_key: Key::Named(NamedKey::Control),
state: ElementState::Released,
..
} => self.current_modifier_keys.remove(&ModifierKey::Ctrl),
KeyEvent {
logical_key: Key::Named(NamedKey::Shift),
state: ElementState::Pressed,
..
} => self.current_modifier_keys.insert(ModifierKey::Shift),
KeyEvent {
logical_key: Key::Named(NamedKey::Shift),
state: ElementState::Released,
..
} => self.current_modifier_keys.remove(&ModifierKey::Shift),
KeyEvent {
logical_key: Key::Named(NamedKey::CapsLock),
state: ElementState::Pressed,
..
} => self.current_modifier_keys.insert(ModifierKey::CapsLock),
KeyEvent {
logical_key: Key::Named(NamedKey::CapsLock),
state: ElementState::Released,
..
} => self.current_modifier_keys.remove(&ModifierKey::CapsLock),
KeyEvent {
logical_key: Key::Named(NamedKey::NumLock),
state: ElementState::Pressed,
..
} => self.current_modifier_keys.insert(ModifierKey::NumLock),
KeyEvent {
logical_key: Key::Named(NamedKey::NumLock),
state: ElementState::Released,
..
} => self.current_modifier_keys.remove(&ModifierKey::NumLock),
KeyEvent {
logical_key: Key::Named(NamedKey::ScrollLock),
state: ElementState::Pressed,
..
} => self.current_modifier_keys.insert(ModifierKey::ScrollLock),
KeyEvent {
logical_key: Key::Named(NamedKey::ScrollLock),
state: ElementState::Released,
..
} => self.current_modifier_keys.remove(&ModifierKey::ScrollLock),
_ => true,
};
if let PhysicalKey::Code(keycode) = event.physical_key {
self.sender
.send(Message::InputEvent(message::InputEvent::Key {
key: keycode,
state: event.state.into(),
modifiers: self
.current_modifier_keys
.iter()
.cloned()
.collect::<Vec<ModifierKey>>(),
}))
.unwrap();
}
if let Some(text) = event.text {
let codepoint = text.chars().next().unwrap();
self.sender
.send(Message::InputEvent(message::InputEvent::CodePoint {
codepoint: codepoint as u32,
modifiers: self
.current_modifier_keys
.iter()
.cloned()
.collect::<Vec<ModifierKey>>(),
}))
.unwrap();
}
}
WindowEvent::CloseRequested => {
self.sender
.send(Message::Shutdown(message::ShutdownReason::Normal))
.unwrap();
}
WindowEvent::Destroyed => {
self.sender
.send(Message::Shutdown(message::ShutdownReason::Terminate))
.unwrap();
}
WindowEvent::CursorMoved { position, .. } => {
let pos: message::Point = position.into();
if pos != self.last_cursor_pos {
self.last_cursor_pos = pos.clone();
self.sender
.send(Message::InputEvent(message::InputEvent::CursorPos {
position: pos,
}))
.unwrap();
}
}
WindowEvent::MouseInput { state, button, .. } => {
self.sender
.send(Message::InputEvent(message::InputEvent::CursorButton {
button: button,
state: state.into(),
modifiers: self
.current_modifier_keys
.iter()
.cloned()
.collect::<Vec<ModifierKey>>(),
position: self.last_cursor_pos.clone(),
}))
.unwrap();
}
WindowEvent::Touch(touch) => match touch.phase {
winit::event::TouchPhase::Started => {
self.sender
.send(Message::InputEvent(message::InputEvent::CursorButton {
button: winit::event::MouseButton::Left,
state: message::ElementState::Pressed,
modifiers: self
.current_modifier_keys
.iter()
.cloned()
.collect::<Vec<ModifierKey>>(),
position: self.last_cursor_pos.clone(),
}))
.unwrap();
}
winit::event::TouchPhase::Ended => {
self.sender
.send(Message::InputEvent(message::InputEvent::CursorButton {
button: winit::event::MouseButton::Left,
state: message::ElementState::Released,
modifiers: self
.current_modifier_keys
.iter()
.cloned()
.collect::<Vec<ModifierKey>>(),
position: self.last_cursor_pos.clone(),
}))
.unwrap();
}
_ => (),
},
WindowEvent::CursorEntered { .. } => {
self.sender
.send(Message::InputEvent(message::InputEvent::ViewPort {
event: message::ViewPortEvent::Enter,
position: self.last_cursor_pos.clone(),
}))
.unwrap();
}
WindowEvent::CursorLeft { .. } => {
self.sender
.send(Message::InputEvent(message::InputEvent::ViewPort {
event: message::ViewPortEvent::Exit,
position: self.last_cursor_pos.clone(),
}))
.unwrap();
}
WindowEvent::Resized(size) => {
self.sender
.send(Message::InputEvent(message::InputEvent::ViewPort {
event: message::ViewPortEvent::Reshape,
position: size.into(),
}))
.unwrap();
if let Some(renderer) = &mut self.renderer {
renderer.set_size(size.width, size.height);
}
}
WindowEvent::RedrawRequested => {
if let Some(renderer) = &mut self.renderer {
renderer.redraw();
}
}
_ => println!("unhandled window event {:?}", event),
}
}
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
let window_attributes = Window::default_attributes()
.with_title(self.title.clone())
.with_resizable(self.resizeable)
.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
};
let window = event_loop
.create_window(window_attributes)
.map(|window| Arc::new(window))
.unwrap();
let context = Context::from_window(window.clone());
self.window = Some(window);
self.renderer = Some(Renderer::new(context));
}
fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) {
if let Some(renderer) = &mut self.renderer {
renderer.render_frame();
}
}
}

View file

@ -0,0 +1,6 @@
{
"cSpell.words": [
"consts",
"renderling"
]
}

2758
native/scenic_renderer/Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,11 @@
[package]
edition = "2021"
name = "scenic_renderer"
version = "0.1.0"
[dependencies]
hsl = "0.1.1"
hsv = "0.1.1"
renderling = "0.4.2"
serde = {version = "1", features = ["derive"]}
wgpu = {version = "0.19"}

View file

@ -0,0 +1,99 @@
pub mod script;
use script::{Color, Script};
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, f32::consts::PI};
use renderling::{
camera::Camera,
math::{Mat4, UVec2, Vec4},
slab::Hybrid,
stage::Stage,
};
pub use renderling::Context;
pub struct Renderer {
pub stage: Stage,
pub ctx: Context,
camera: Hybrid<Camera>,
scripts: HashMap<String, Script>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Orientation {
Normal,
Left,
Right,
UpsideDown,
}
impl Renderer {
pub fn new(ctx: Context) -> Self {
let mut stage = ctx
.new_stage()
.with_lighting(false)
.with_bloom(false)
.with_background_color(Vec4::new(0.0, 0.0, 0.0, 1.0));
let size = ctx.get_size();
let camera = stage.new_value(Camera::default_ortho2d(size.x as f32, size.y as f32));
let scripts = HashMap::default();
Self {
stage,
camera,
ctx,
scripts,
}
}
pub fn set_orientation(&mut self, orientation: Orientation) {
match orientation {
Orientation::Normal => self
.camera
.modify(|c| c.set_view(Mat4::from_rotation_z(0.0))),
Orientation::Left => self
.camera
.modify(|c| c.set_view(Mat4::from_rotation_z(PI / -2.0))),
Orientation::Right => self
.camera
.modify(|c| c.set_view(Mat4::from_rotation_z(PI / 2.0))),
Orientation::UpsideDown => self
.camera
.modify(|c| c.set_view(Mat4::from_rotation_z(PI))),
}
}
pub fn set_size(&mut self, width: u32, height: u32) {
self.ctx.set_size(UVec2::new(width, height));
}
pub fn redraw(&mut self) {
self.ctx.get_device().poll(wgpu::Maintain::Wait);
}
pub fn render_frame(&mut self) {
let frame = self.ctx.get_next_frame().unwrap();
self.stage.render(&frame.view());
frame.present();
}
pub fn clear_color(&mut self, color: Color) {
self.stage.set_background_color(color);
}
pub fn reset_scene(&mut self) {
// ?
}
pub fn update_scene(&mut self, script_id: String, script: Script) {
self.scripts.insert(script_id, script);
}
pub fn delete_scripts(&mut self, script_ids: Vec<String>) {
for script_id in script_ids {
self.scripts.remove(&script_id);
}
}
}

View file

@ -0,0 +1,327 @@
use renderling::math::Vec4;
use serde::{Deserialize, Serialize};
#[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<Color> for Vec4 {
fn from(val: Color) -> Self {
match val {
Color::Gray(g) => {
let c = g as f32 / 255_f32;
Vec4::new(c, c, c, 1.0)
}
Color::GrayAlpha(g, a) => {
let c = g as f32 / 255_f32;
let a = a as f32 / 255_f32;
Vec4::new(c, c, c, a)
}
Color::Rgb(r, g, b) => {
let r = r as f32 / 255_f32;
let g = g as f32 / 255_f32;
let b = b as f32 / 255_f32;
Vec4::new(r, g, b, 1.0)
}
Color::RgbA(r, g, b, a) => {
let r = r as f32 / 255_f32;
let g = g as f32 / 255_f32;
let b = b as f32 / 255_f32;
let a = a as f32 / 255_f32;
Vec4::new(r, g, b, a)
}
Color::Hsv(h, s, v) => {
let (r, g, b) = hsv::hsv_to_rgb(h as f64, s as f64, v as f64);
let r = r as f32 / 255_f32;
let g = g as f32 / 255_f32;
let b = b as f32 / 255_f32;
Vec4::new(r, g, b, 1.0)
}
Color::Hsl(h, s, l) => {
let (r, g, b) = hsl::HSL {
h: h as f64,
s: s as f64,
l: l as f64,
}
.to_rgb();
let r = r as f32 / 255_f32;
let g = g as f32 / 255_f32;
let b = b as f32 / 255_f32;
Vec4::new(r, g, b, 1.0)
}
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum FillStroke {
Fill,
Stroke,
FillStroke,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct SpriteDrawCmd {
pub src_x: u32,
pub src_y: u32,
pub src_w: u32,
pub src_h: u32,
pub dst_x: u32,
pub dst_y: u32,
pub dst_w: u32,
pub dst_h: u32,
}
#[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),
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Script(Vec<ScriptItem>);
impl From<Vec<ScriptItem>> for Script {
fn from(vec: Vec<ScriptItem>) -> Script {
Script(vec)
}
}

1
native/scenic_window/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

View file

@ -4,6 +4,7 @@
"centered",
"cmds",
"Codepoint",
"Fullscreen",
"Miter",
"Rect",
"Renderling",

View file

@ -1847,22 +1847,6 @@ dependencies = [
"winit",
]
[[package]]
name = "renderling_window"
version = "0.1.0"
dependencies = [
"anyhow",
"clap",
"hsl",
"hsv",
"ipc-channel",
"renderling",
"rustler",
"serde",
"wgpu",
"winit",
]
[[package]]
name = "rustc-hash"
version = "1.1.0"
@ -1931,6 +1915,30 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "scenic_renderer"
version = "0.1.0"
dependencies = [
"hsl",
"hsv",
"renderling",
"serde",
"wgpu",
]
[[package]]
name = "scenic_window"
version = "0.1.0"
dependencies = [
"anyhow",
"clap",
"ipc-channel",
"rustler",
"scenic_renderer",
"serde",
"winit",
]
[[package]]
name = "scoped-tls"
version = "1.0.1"

View file

@ -1,26 +1,23 @@
[package]
authors = []
edition = "2021"
name = "renderling_window"
name = "scenic_window"
version = "0.1.0"
[lib]
crate-type = ["cdylib"]
name = "renderling_window"
name = "scenic_window"
path = "src/lib.rs"
[dependencies]
anyhow = "1"
clap = {version = "4", features = ["derive"]}
hsl = "0.1.1"
hsv = "0.1.1"
ipc-channel = "0.18.0"
renderling = "0.4.2"
rustler = {version = "0.32.1", features = ["serde", "nif_version_2_16"]}
scenic_renderer = {path = "../scenic_renderer"}
serde = {version = "1", features = ["derive"]}
wgpu = {version = "0.19"}
winit = {version = "0.30.0", features = ["serde"]}
[[bin]]
name = "renderling_window_server"
name = "scenic_window_server"
path = "src/server.rs"

View file

@ -1,7 +1,8 @@
//! Type definitions for anything going into or coming out of the BEAM.
use crate::message;
use rustler::{NifStruct, NifTaggedEnum, NifTuple, NifUnitEnum};
/// Structures received from BEAM.
use scenic_renderer::script;
#[derive(NifUnitEnum, Debug, Clone)]
pub enum WindowOrientation {
@ -11,12 +12,25 @@ pub enum WindowOrientation {
UpsideDown,
}
impl From<WindowOrientation> for scenic_renderer::Orientation {
fn from(orientation: WindowOrientation) -> Self {
match orientation {
WindowOrientation::Normal => scenic_renderer::Orientation::Normal,
WindowOrientation::Left => scenic_renderer::Orientation::Left,
WindowOrientation::Right => scenic_renderer::Orientation::Right,
WindowOrientation::UpsideDown => scenic_renderer::Orientation::UpsideDown,
}
}
}
#[derive(NifStruct, Debug, Clone)]
#[module = "Scenic.Driver.Renderling.Window.Config.Position"]
pub struct WindowPosition {
pub scaled: bool,
pub centered: bool,
pub orientation: WindowOrientation,
pub maximised: bool,
pub full_screen: bool,
}
#[derive(NifStruct, Debug, Clone)]
@ -49,6 +63,7 @@ pub struct DriverConfig {
pub server_path: String,
}
#[allow(clippy::enum_variant_names)]
#[derive(NifTaggedEnum, Debug, Clone)]
pub enum Color {
ColorG(u8),
@ -59,6 +74,19 @@ pub enum Color {
ColorHsl((f32, f32, f32)),
}
impl From<Color> for script::Color {
fn from(color: Color) -> Self {
match color {
Color::ColorG(g) => script::Color::Gray(g),
Color::ColorGa((g, a)) => script::Color::GrayAlpha(g, a),
Color::ColorRgb((r, g, b)) => script::Color::Rgb(r, g, b),
Color::ColorRgba((r, g, b, a)) => script::Color::RgbA(r, g, b, a),
Color::ColorHsv((h, s, v)) => script::Color::Hsv(h, s, v),
Color::ColorHsl((h, s, l)) => script::Color::Hsl(h, s, l),
}
}
}
#[derive(NifTuple, Debug, Clone)]
pub struct Pos(pub u32, pub u32);
@ -77,6 +105,7 @@ pub enum TextBase {
Bottom,
}
#[allow(clippy::enum_variant_names)]
#[derive(NifUnitEnum, Debug, Clone)]
pub enum FillStroke {
Fill,
@ -84,6 +113,16 @@ pub enum FillStroke {
FillStroke,
}
impl From<FillStroke> for script::FillStroke {
fn from(fill_stroke: FillStroke) -> Self {
match fill_stroke {
FillStroke::Fill => script::FillStroke::Fill,
FillStroke::FillStroke => script::FillStroke::FillStroke,
FillStroke::Stroke => script::FillStroke::Stroke,
}
}
}
#[derive(NifTuple, Debug, Clone)]
pub struct DrawLine {
pub x0: u32,
@ -198,6 +237,21 @@ pub struct SpriteDrawCmd {
pub dst_wh: Pos,
}
impl From<SpriteDrawCmd> for script::SpriteDrawCmd {
fn from(cmd: SpriteDrawCmd) -> Self {
script::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(NifTuple, Debug, Clone)]
pub struct DrawSprite {
pub src_id: String,
@ -388,6 +442,232 @@ pub enum ScriptItem {
TextBase(TextBase),
}
impl From<ScriptItem> for script::ScriptItem {
fn from(script_item: ScriptItem) -> Self {
match script_item {
ScriptItem::PushState => script::ScriptItem::PushState,
ScriptItem::PopState => script::ScriptItem::PopState,
ScriptItem::PushPopState => script::ScriptItem::PushPopState,
ScriptItem::Clear(color) => script::ScriptItem::Clear(color.into()),
ScriptItem::DrawLine(dl) => script::ScriptItem::DrawLine {
x0: dl.x0,
y0: dl.y0,
x1: dl.x1,
y1: dl.y1,
fill_stroke: dl.fill_stroke.into(),
},
ScriptItem::DrawQuad(dq) => script::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(),
},
ScriptItem::DrawRect(r) => script::ScriptItem::DrawRect {
width: r.width,
height: r.height,
fill_stroke: r.fill_stroke.into(),
},
ScriptItem::DrawRrect(r) => script::ScriptItem::DrawRRect {
width: r.width,
height: r.height,
radius: r.radius,
fill_stroke: r.fill_stroke.into(),
},
ScriptItem::DrawSector(sector) => script::ScriptItem::DrawSector {
radius: sector.radius,
radians: sector.radians,
fill_stroke: sector.fill_stroke.into(),
},
ScriptItem::DrawArc(arc) => script::ScriptItem::DrawArc {
radius: arc.radius,
radians: arc.radians,
fill_stroke: arc.fill_stroke.into(),
},
ScriptItem::DrawCircle(c) => script::ScriptItem::DrawCircle {
radius: c.radius,
fill_stroke: c.fill_stroke.into(),
},
ScriptItem::DrawEllipse(e) => script::ScriptItem::DrawEllipse {
radius0: e.radius0,
radius1: e.radius1,
fill_stroke: e.fill_stroke.into(),
},
ScriptItem::DrawSprites(s) => script::ScriptItem::DrawSprite {
src_id: s.src_id,
cmds: s
.cmds
.iter()
.map(|x| x.clone().into())
.collect::<Vec<script::SpriteDrawCmd>>(),
},
ScriptItem::DrawText(text) => script::ScriptItem::DrawText(text),
ScriptItem::DrawTriangle(t) => script::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(),
},
ScriptItem::Script(id) => script::ScriptItem::Script(id),
ScriptItem::BeginPath => script::ScriptItem::BeginPath,
ScriptItem::ClosePath => script::ScriptItem::ClosePath,
ScriptItem::FillPath => script::ScriptItem::FillPath,
ScriptItem::StrokePath => script::ScriptItem::StrokePath,
ScriptItem::MoveTo(p) => script::ScriptItem::MoveTo { x: p.0, y: p.1 },
ScriptItem::LineTo(p) => script::ScriptItem::LineTo { x: p.0, y: p.1 },
ScriptItem::ArcTo(a) => script::ScriptItem::ArcTo {
x0: a.x0,
y0: a.y0,
x1: a.x1,
y1: a.y1,
radius: a.radius,
},
ScriptItem::BezierTo(b) => script::ScriptItem::BezierTo {
cp1x: b.cp1x,
cp1y: b.cp1y,
cp2x: b.cp2x,
cp2y: b.cp2y,
x: b.x,
y: b.y,
},
ScriptItem::QuadraticTo(q) => script::ScriptItem::QuadraticTo {
cpx: q.cpx,
cpy: q.cpy,
x: q.x,
y: q.y,
},
ScriptItem::Quad(q) => script::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,
},
ScriptItem::Rect(r) => script::ScriptItem::Rect {
width: r.width,
height: r.height,
},
ScriptItem::Rrect(r) => script::ScriptItem::RRect {
width: r.width,
height: r.height,
radius: r.radius,
},
ScriptItem::Sector(s) => script::ScriptItem::Sector {
radius: s.radius,
radians: s.radians,
},
ScriptItem::Circle(c) => script::ScriptItem::Circle { radius: c.radius },
ScriptItem::Ellipse(e) => script::ScriptItem::Ellipse {
radius0: e.radius0,
radius1: e.radius1,
},
ScriptItem::Triangle(t) => script::ScriptItem::Triangle {
x0: t.x0,
y0: t.y0,
x1: t.x1,
y1: t.y1,
x2: t.x2,
y2: t.y2,
},
ScriptItem::Scale(s) => script::ScriptItem::Scale { x: s.x, y: s.y },
ScriptItem::Rotate(r) => script::ScriptItem::Rotate { radians: r },
ScriptItem::Translate(p) => script::ScriptItem::Translate { x: p.0, y: p.1 },
ScriptItem::Transform(t) => script::ScriptItem::Transform {
a: t.a,
b: t.b,
c: t.c,
d: t.d,
e: t.e,
f: t.f,
},
ScriptItem::FillColor(c) => script::ScriptItem::FillColor(c.into()),
ScriptItem::FillLinear(l) => script::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(),
},
ScriptItem::FillRadial(r) => script::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(),
},
ScriptItem::FillImage(i) => script::ScriptItem::FillImage(i),
ScriptItem::StrokeColor(c) => script::ScriptItem::StrokeColor(c.into()),
ScriptItem::StrokeLinear(l) => script::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(),
},
ScriptItem::StrokeRadial(r) => script::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(),
},
ScriptItem::StrokeImage(s) => script::ScriptItem::StrokeImage(s),
ScriptItem::StrokeStream(s) => script::ScriptItem::StrokeStream(s),
ScriptItem::StrokeWidth(w) => script::ScriptItem::StrokeWidth(w),
ScriptItem::Cap(Cap::Butt) => script::ScriptItem::Cap(script::Cap::Butt),
ScriptItem::Cap(Cap::Round) => script::ScriptItem::Cap(script::Cap::Round),
ScriptItem::Cap(Cap::Square) => script::ScriptItem::Cap(script::Cap::Square),
ScriptItem::Join(Join::Bevel) => script::ScriptItem::Join(script::Join::Bevel),
ScriptItem::Join(Join::Round) => script::ScriptItem::Join(script::Join::Round),
ScriptItem::Join(Join::Miter) => script::ScriptItem::Join(script::Join::Miter),
ScriptItem::MiterLimit(l) => script::ScriptItem::MiterLimit(l),
ScriptItem::Scissor(s) => script::ScriptItem::Scissor {
width: s.width,
height: s.height,
},
ScriptItem::Font(s) => script::ScriptItem::Font(s),
ScriptItem::FontSize(u32) => script::ScriptItem::FontSize(u32),
ScriptItem::TextAlign(TextAlign::Left) => {
script::ScriptItem::TextAlign(script::TextAlign::Left)
}
ScriptItem::TextAlign(TextAlign::Center) => {
script::ScriptItem::TextAlign(script::TextAlign::Center)
}
ScriptItem::TextAlign(TextAlign::Right) => {
script::ScriptItem::TextAlign(script::TextAlign::Right)
}
ScriptItem::TextBase(TextBase::Top) => {
script::ScriptItem::TextBase(script::TextBase::Top)
}
ScriptItem::TextBase(TextBase::Middle) => {
script::ScriptItem::TextBase(script::TextBase::Middle)
}
ScriptItem::TextBase(TextBase::Alphabetic) => {
script::ScriptItem::TextBase(script::TextBase::Alphabetic)
}
ScriptItem::TextBase(TextBase::Bottom) => {
script::ScriptItem::TextBase(script::TextBase::Bottom)
}
_ => unimplemented!(),
}
}
}
#[derive(NifUnitEnum, Debug, Clone)]
pub enum InputClass {
CursorButton,
@ -467,6 +747,7 @@ impl From<message::ElementState> for ElementState {
}
}
#[allow(clippy::enum_variant_names)]
#[derive(NifUnitEnum, Debug, Clone)]
pub enum Key {
KeySpace,
@ -757,6 +1038,7 @@ impl From<winit::keyboard::KeyCode> for Key {
}
}
#[allow(clippy::enum_variant_names)]
#[derive(NifUnitEnum, Debug, Clone)]
pub enum MouseButton {
BtnLeft,
@ -849,16 +1131,7 @@ pub enum ShutdownReason {
}
#[derive(NifTuple, Debug, Clone)]
pub struct ShutdownMessage(ShutdownAtom, ShutdownReason);
impl ShutdownMessage {
pub fn normal() -> Self {
ShutdownMessage(ShutdownAtom::Shutdown, ShutdownReason::Normal)
}
pub fn terminate() -> Self {
ShutdownMessage(ShutdownAtom::Shutdown, ShutdownReason::Terminate)
}
}
pub struct ShutdownMessage(pub ShutdownAtom, pub ShutdownReason);
impl From<message::ShutdownReason> for ShutdownMessage {
fn from(reason: message::ShutdownReason) -> Self {

View file

@ -6,6 +6,7 @@ use crate::elixir_types::{
};
use ipc_channel::ipc::{IpcOneShotServer, IpcSender};
use rustler::{Encoder, Env, NifResult, OwnedEnv, ResourceArc, Term};
use scenic_renderer::script;
use std::process::{Child, Command, Stdio};
use std::sync::Mutex;
use std::thread::sleep;
@ -59,7 +60,13 @@ fn init(env: Env<'_>, cfg: DriverConfig) -> ResourceArc<IpcResource> {
} else {
let mut msg_env = OwnedEnv::new();
msg_env
.send_and_clear(&pid, |env| ShutdownMessage::terminate().encode(env))
.send_and_clear(&pid, |env| {
ShutdownMessage(
elixir_types::ShutdownAtom::Shutdown,
elixir_types::ShutdownReason::Terminate,
)
.encode(env)
})
.unwrap();
}
});
@ -76,13 +83,13 @@ fn update_scene(script: Term, script_id: String, resource: ResourceArc<IpcResour
term.decode::<ScriptItem>()
.map(|script_item| script_item.into())
})
.collect::<NifResult<Vec<message::ScriptItem>>>()
.collect::<NifResult<Vec<script::ScriptItem>>>()
.unwrap();
let mutex = resource.0.lock().unwrap();
mutex
.sender
.send(message::Message::UpdateScene(script_id, script))
.send(message::Message::UpdateScene(script_id, script.into()))
.unwrap();
}
@ -140,7 +147,7 @@ fn request_input(classes: Term, resource: ResourceArc<IpcResource>) {
fn terminate(resource: ResourceArc<IpcResource>) {
println!("Shutting down server.");
let mut mutex = resource.0.lock().unwrap();
if let None = mutex.handle.try_wait().unwrap() {
if mutex.handle.try_wait().unwrap().is_none() {
mutex
.sender
.send(message::Message::Shutdown(message::ShutdownReason::Normal))
@ -151,7 +158,7 @@ fn terminate(resource: ResourceArc<IpcResource>) {
// it.
sleep(std::time::Duration::from_micros(100));
let mut mutex = resource.0.lock().unwrap();
if let None = mutex.handle.try_wait().unwrap() {
if mutex.handle.try_wait().unwrap().is_none() {
mutex.handle.kill().unwrap();
}
});

View file

@ -0,0 +1,180 @@
use crate::elixir_types;
use ipc_channel::ipc::{IpcReceiver, IpcSender};
use scenic_renderer::script::{Color, Script};
use scenic_renderer::Orientation;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Hash)]
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, Eq, PartialEq, Hash)]
pub enum ModifierKey {
Meta,
Alt,
Ctrl,
Shift,
CapsLock,
NumLock,
ScrollLock,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct Point {
pub x: u32,
pub y: u32,
}
impl From<winit::dpi::PhysicalPosition<f64>> for Point {
fn from(pos: winit::dpi::PhysicalPosition<f64>) -> Self {
Point {
x: pos.x.round() as u32,
y: pos.y.round() as u32,
}
}
}
impl From<winit::dpi::PhysicalSize<u32>> for Point {
fn from(pos: winit::dpi::PhysicalSize<u32>) -> Self {
Point {
x: pos.width,
y: pos.height,
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum ViewPortEvent {
Enter,
Exit,
Reshape,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum ElementState {
Pressed,
Released,
}
impl From<winit::event::ElementState> for ElementState {
fn from(state: winit::event::ElementState) -> Self {
match state {
winit::event::ElementState::Pressed => ElementState::Pressed,
winit::event::ElementState::Released => ElementState::Released,
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum InputEvent {
CodePoint {
codepoint: u32,
modifiers: Vec<ModifierKey>,
},
Key {
key: winit::keyboard::KeyCode,
state: ElementState,
modifiers: Vec<ModifierKey>,
},
CursorButton {
button: winit::event::MouseButton,
state: ElementState,
modifiers: Vec<ModifierKey>,
position: Point,
},
CursorScroll {
offset: Point,
position: Point,
},
CursorPos {
position: Point,
},
ViewPort {
event: ViewPortEvent,
position: Point,
},
Relative {
position: Point,
},
Led {
id: String,
value: u32,
},
Switch {
id: String,
value: u32,
},
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum ShutdownReason {
Normal,
Terminate,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum Message {
Shutdown(ShutdownReason),
ResetScene,
Init {
centered: bool,
full_screen: bool,
height: u32,
maximised: bool,
orientation: Orientation,
resizeable: bool,
title: String,
width: u32,
},
ClearColor(Color),
DeleteScripts(Vec<String>),
RequestInput(Vec<InputClass>),
UpdateScene(String, Script),
InputEvent(InputEvent),
}
impl From<elixir_types::DriverConfig> for Message {
fn from(driver_config: elixir_types::DriverConfig) -> Self {
Message::Init {
centered: driver_config.position.centered,
full_screen: driver_config.position.full_screen,
height: driver_config.window.height,
maximised: driver_config.position.maximised,
orientation: driver_config.position.orientation.into(),
resizeable: driver_config.window.resizeable,
title: driver_config.window.title,
width: driver_config.window.width,
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct IpcExchange {
pub sender: IpcSender<Message>,
pub receiver: IpcReceiver<Message>,
}

View file

@ -0,0 +1,464 @@
//! scenic_window_server
//!
//! Some platforms require that windows must be opened from a processes "main
//! thread" so we can't just start using `winit` from our Erlang NIFs.
//!
//! This module becomes a separate binary that is spawned by our NIFs and uses
//! `ipc_channel` to efficiently send messages between the two. The messages
//! are defined in the `messages` module.
mod elixir_types;
mod message;
use std::collections::HashSet;
use std::process::exit;
use std::sync::Arc;
use crate::message::{IpcExchange, Message};
use clap::Parser;
use ipc_channel::ipc;
use ipc_channel::ipc::IpcSender;
use message::{InputClass, ModifierKey};
use winit::application::ApplicationHandler;
use winit::dpi::{LogicalPosition, LogicalSize};
use winit::event::{ElementState, KeyEvent, WindowEvent};
use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::keyboard::{Key, NamedKey, PhysicalKey};
use winit::window::{self, Window, WindowAttributes, WindowId};
use scenic_renderer::{Context, Orientation, Renderer};
#[cfg(macos_platform)]
use winit::platform::macos::{OptionAsAlt, WindowAttributesExtMacOS, WindowExtMacOS};
#[cfg(any(x11_platform, wayland_platform))]
use winit::platform::startup_notify::{
self, EventLoopExtStartupNotify, WindowAttributesExtStartupNotify, WindowExtStartupNotify,
};
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct CmdArgs {
#[arg(long)]
ipc_channel: String,
}
fn main() -> anyhow::Result<()> {
let args = CmdArgs::parse();
println!("Server: Started.");
let sender = IpcSender::connect(args.ipc_channel)?;
let (c2s_send, c2s_recv) = ipc::channel()?;
let (s2c_send, s2c_recv) = ipc::channel()?;
sender.send(IpcExchange {
sender: c2s_send,
receiver: s2c_recv,
})?;
let msg = c2s_recv.recv()?;
if let &Message::Init { .. } = &msg {
let event_loop = EventLoop::<Message>::with_user_event().build()?;
let event_loop_proxy = event_loop.create_proxy();
std::thread::spawn(move || loop {
let event = c2s_recv.recv().unwrap();
match event {
Message::Init { .. } => unreachable!(),
Message::Shutdown(message::ShutdownReason::Normal) => exit(0),
Message::Shutdown(message::ShutdownReason::Terminate) => exit(1),
_ => event_loop_proxy.send_event(event).unwrap(),
};
});
let mut state = Application::new(msg, s2c_send);
event_loop.run_app(&mut state).map_err(Into::into)
} else {
panic!("Server: Invalid message received {:?}", msg);
}
}
#[derive(Clone)]
struct InitialWindowConfig {
centered: bool,
full_screen: bool,
height: u32,
maximised: bool,
orientation: Orientation,
resizeable: bool,
title: String,
width: u32,
}
struct Application {
initial_config: Option<InitialWindowConfig>,
current_modifier_keys: HashSet<message::ModifierKey>,
input_classes: HashSet<InputClass>,
last_cursor_pos: message::Point,
renderer: Option<Renderer>,
sender: IpcSender<Message>,
window: Option<Arc<Window>>,
}
impl Application {
fn new(event: Message, sender: IpcSender<Message>) -> Self {
match event {
Message::Init {
centered,
full_screen,
height,
maximised,
orientation,
resizeable,
title,
width,
} => Application {
initial_config: Some(InitialWindowConfig {
centered,
full_screen,
height,
maximised,
orientation,
resizeable,
title,
width,
}),
window: None,
input_classes: [
InputClass::CursorButton,
InputClass::CursorScroll,
InputClass::CursorPos,
InputClass::Codepoint,
InputClass::Key,
InputClass::Viewport,
InputClass::Relative,
InputClass::Led,
InputClass::Switch,
]
.iter()
.cloned()
.collect::<HashSet<InputClass>>(),
sender,
current_modifier_keys: HashSet::default(),
last_cursor_pos: message::Point { x: 0, y: 0 },
renderer: None,
},
_ => unreachable!(),
}
}
}
impl ApplicationHandler<Message> for Application {
fn user_event(&mut self, _event_loop: &ActiveEventLoop, event: Message) {
if let Some(renderer) = &mut self.renderer {
match event {
Message::ClearColor(color) => renderer.clear_color(color),
Message::ResetScene => renderer.reset_scene(),
Message::UpdateScene(script_id, script) => renderer.update_scene(script_id, script),
Message::DeleteScripts(script_ids) => renderer.delete_scripts(script_ids),
Message::RequestInput(input_classes) => {
self.input_classes = input_classes
.iter()
.cloned()
.collect::<HashSet<InputClass>>();
}
_ => {
println!("unhandled message event {:?}", event)
}
};
}
}
fn window_event(
&mut self,
_event_loop: &ActiveEventLoop,
_window_id: WindowId,
event: WindowEvent,
) {
match event {
// Keep a record of which modifier keys are pressed at any given time.
WindowEvent::KeyboardInput { event, .. } => {
if self.input_classes.contains(&InputClass::Key) {
match event {
KeyEvent {
logical_key: Key::Named(NamedKey::Meta),
state: ElementState::Pressed,
..
} => self.current_modifier_keys.insert(ModifierKey::Meta),
KeyEvent {
logical_key: Key::Named(NamedKey::Meta),
state: ElementState::Released,
..
} => self.current_modifier_keys.remove(&ModifierKey::Meta),
KeyEvent {
logical_key: Key::Named(NamedKey::Alt),
state: ElementState::Pressed,
..
} => self.current_modifier_keys.insert(ModifierKey::Alt),
KeyEvent {
logical_key: Key::Named(NamedKey::Alt),
state: ElementState::Released,
..
} => self.current_modifier_keys.remove(&ModifierKey::Alt),
KeyEvent {
logical_key: Key::Named(NamedKey::Control),
state: ElementState::Pressed,
..
} => self.current_modifier_keys.insert(ModifierKey::Ctrl),
KeyEvent {
logical_key: Key::Named(NamedKey::Control),
state: ElementState::Released,
..
} => self.current_modifier_keys.remove(&ModifierKey::Ctrl),
KeyEvent {
logical_key: Key::Named(NamedKey::Shift),
state: ElementState::Pressed,
..
} => self.current_modifier_keys.insert(ModifierKey::Shift),
KeyEvent {
logical_key: Key::Named(NamedKey::Shift),
state: ElementState::Released,
..
} => self.current_modifier_keys.remove(&ModifierKey::Shift),
KeyEvent {
logical_key: Key::Named(NamedKey::CapsLock),
state: ElementState::Pressed,
..
} => self.current_modifier_keys.insert(ModifierKey::CapsLock),
KeyEvent {
logical_key: Key::Named(NamedKey::CapsLock),
state: ElementState::Released,
..
} => self.current_modifier_keys.remove(&ModifierKey::CapsLock),
KeyEvent {
logical_key: Key::Named(NamedKey::NumLock),
state: ElementState::Pressed,
..
} => self.current_modifier_keys.insert(ModifierKey::NumLock),
KeyEvent {
logical_key: Key::Named(NamedKey::NumLock),
state: ElementState::Released,
..
} => self.current_modifier_keys.remove(&ModifierKey::NumLock),
KeyEvent {
logical_key: Key::Named(NamedKey::ScrollLock),
state: ElementState::Pressed,
..
} => self.current_modifier_keys.insert(ModifierKey::ScrollLock),
KeyEvent {
logical_key: Key::Named(NamedKey::ScrollLock),
state: ElementState::Released,
..
} => self.current_modifier_keys.remove(&ModifierKey::ScrollLock),
_ => true,
};
if let PhysicalKey::Code(keycode) = event.physical_key {
self.sender
.send(Message::InputEvent(message::InputEvent::Key {
key: keycode,
state: event.state.into(),
modifiers: self
.current_modifier_keys
.iter()
.cloned()
.collect::<Vec<ModifierKey>>(),
}))
.unwrap();
}
if let Some(text) = event.text {
let codepoint = text.chars().next().unwrap();
self.sender
.send(Message::InputEvent(message::InputEvent::CodePoint {
codepoint: codepoint as u32,
modifiers: self
.current_modifier_keys
.iter()
.cloned()
.collect::<Vec<ModifierKey>>(),
}))
.unwrap();
}
}
}
WindowEvent::CloseRequested => {
self.sender
.send(Message::Shutdown(message::ShutdownReason::Normal))
.unwrap();
}
WindowEvent::Destroyed => {
self.sender
.send(Message::Shutdown(message::ShutdownReason::Terminate))
.unwrap();
}
WindowEvent::CursorMoved { position, .. } => {
let pos: message::Point = position.into();
if pos != self.last_cursor_pos {
self.last_cursor_pos = pos.clone();
if self.input_classes.contains(&InputClass::CursorPos) {
self.sender
.send(Message::InputEvent(message::InputEvent::CursorPos {
position: pos,
}))
.unwrap();
}
}
}
WindowEvent::MouseInput { state, button, .. } => {
if self.input_classes.contains(&InputClass::CursorButton) {
self.sender
.send(Message::InputEvent(message::InputEvent::CursorButton {
button,
state: state.into(),
modifiers: self
.current_modifier_keys
.iter()
.cloned()
.collect::<Vec<ModifierKey>>(),
position: self.last_cursor_pos.clone(),
}))
.unwrap();
}
}
WindowEvent::Touch(touch) => {
if self.input_classes.contains(&InputClass::CursorButton) {
match touch.phase {
winit::event::TouchPhase::Started => {
self.sender
.send(Message::InputEvent(message::InputEvent::CursorButton {
button: winit::event::MouseButton::Left,
state: message::ElementState::Pressed,
modifiers: self
.current_modifier_keys
.iter()
.cloned()
.collect::<Vec<ModifierKey>>(),
position: self.last_cursor_pos.clone(),
}))
.unwrap();
}
winit::event::TouchPhase::Ended => {
self.sender
.send(Message::InputEvent(message::InputEvent::CursorButton {
button: winit::event::MouseButton::Left,
state: message::ElementState::Released,
modifiers: self
.current_modifier_keys
.iter()
.cloned()
.collect::<Vec<ModifierKey>>(),
position: self.last_cursor_pos.clone(),
}))
.unwrap();
}
_ => (),
}
}
}
WindowEvent::CursorEntered { .. } => {
if self.input_classes.contains(&InputClass::Viewport) {
self.sender
.send(Message::InputEvent(message::InputEvent::ViewPort {
event: message::ViewPortEvent::Enter,
position: self.last_cursor_pos.clone(),
}))
.unwrap();
}
}
WindowEvent::CursorLeft { .. } => {
if self.input_classes.contains(&InputClass::Viewport) {
self.sender
.send(Message::InputEvent(message::InputEvent::ViewPort {
event: message::ViewPortEvent::Exit,
position: self.last_cursor_pos.clone(),
}))
.unwrap();
}
}
WindowEvent::Resized(size) => {
if self.input_classes.contains(&InputClass::Viewport) {
self.sender
.send(Message::InputEvent(message::InputEvent::ViewPort {
event: message::ViewPortEvent::Reshape,
position: size.into(),
}))
.unwrap();
}
if let Some(renderer) = &mut self.renderer {
renderer.set_size(size.width, size.height);
}
}
WindowEvent::RedrawRequested => {
if let Some(renderer) = &mut self.renderer {
renderer.redraw();
}
}
_ => println!("unhandled window event {:?}", event),
}
}
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
let config = self.initial_config.clone().unwrap();
self.initial_config = None;
let full_screen = if config.full_screen {
Some(window::Fullscreen::Borderless(None))
} else {
None
};
let window_attributes = Window::default_attributes()
.with_title(config.title.clone())
.with_resizable(config.resizeable)
.with_inner_size(LogicalSize::new(config.width as f32, config.height as f32))
.with_maximized(config.maximised)
.with_fullscreen(full_screen);
let window_attributes = maybe_center(window_attributes, &config, event_loop);
let window = event_loop
.create_window(window_attributes)
.map(Arc::new)
.unwrap();
let context = Context::from_window(window.clone());
self.window = Some(window);
let mut renderer = Renderer::new(context);
renderer.set_orientation(config.orientation);
self.renderer = Some(renderer);
}
fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) {
if let Some(renderer) = &mut self.renderer {
renderer.render_frame();
}
}
}
fn maybe_center(
window_attributes: WindowAttributes,
config: &InitialWindowConfig,
event_loop: &ActiveEventLoop,
) -> WindowAttributes {
if config.centered {
let display_size = event_loop.primary_monitor().unwrap().size();
let center_x = (display_size.width as f32 / 2.0) - (config.width as f32 / 2.0);
let center_y = (display_size.height as f32 / 2.0) - (config.height as f32 / 2.0);
window_attributes.with_position(LogicalPosition::new(center_x, center_y))
} else {
window_attributes
}
}