Reforactor
This commit is contained in:
parent
2609d8c00d
commit
ee64cc77bc
|
@ -1,6 +1,6 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"lib",
|
"core",
|
||||||
"desktop",
|
"desktop",
|
||||||
"web"
|
"web"
|
||||||
]
|
]
|
||||||
|
|
|
@ -4,15 +4,18 @@ version = "0.1.0"
|
||||||
authors = ["Mike Welsh <mwelsh@gmail.com>"]
|
authors = ["Mike Welsh <mwelsh@gmail.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[features]
|
|
||||||
default = ["console_error_panic_hook", "console_log"]
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bacon_rajan_cc = "0.2"
|
bacon_rajan_cc = "0.2"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
url = "1.7.2"
|
url = "1.7.2"
|
||||||
svg = "0.5.12"
|
svg = "0.5.12"
|
||||||
swf = { git = "https://github.com/Herschel/swf-rs", version = "*" }
|
swf = { git = "https://github.com/Herschel/swf-rs", version = "*" }
|
||||||
|
js-sys = "0.3.19"
|
||||||
|
wasm-bindgen = "0.2"
|
||||||
|
|
||||||
|
[dependencies.web-sys]
|
||||||
|
version = "0.3.19"
|
||||||
|
features = ["CanvasRenderingContext2d", "HtmlCanvasElement", "HtmlImageElement"]
|
||||||
|
|
||||||
# Desktop dependencies
|
# Desktop dependencies
|
||||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||||
|
@ -21,13 +24,3 @@ glium = "0.24"
|
||||||
glutin = "0.20"
|
glutin = "0.20"
|
||||||
winit = "0.19.1"
|
winit = "0.19.1"
|
||||||
|
|
||||||
# Wasm32 dependencies
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
|
||||||
console_error_panic_hook = { version = "0.1.1", optional = true }
|
|
||||||
console_log = { version = "0.1", optional = true }
|
|
||||||
js-sys = "0.3.19"
|
|
||||||
wasm-bindgen = "0.2"
|
|
||||||
|
|
||||||
[dependencies.web-sys]
|
|
||||||
version = "0.3.19"
|
|
||||||
features = ["CanvasRenderingContext2d", "Document", "Element", "HtmlCanvasElement", "Performance", "HtmlImageElement", "Window"]
|
|
|
@ -0,0 +1 @@
|
||||||
|
pub mod render;
|
|
@ -1,10 +1,12 @@
|
||||||
pub mod common;
|
pub mod common;
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub mod glium;
|
pub mod glium;
|
||||||
|
pub mod null;
|
||||||
pub mod shape_utils;
|
pub mod shape_utils;
|
||||||
#[cfg(target_arch = "wasm32")]
|
|
||||||
pub mod web_canvas;
|
pub mod web_canvas;
|
||||||
|
|
||||||
|
pub use null::NullRenderer;
|
||||||
|
|
||||||
use self::common::ShapeHandle;
|
use self::common::ShapeHandle;
|
||||||
use crate::{matrix::Matrix, Color};
|
use crate::{matrix::Matrix, Color};
|
||||||
|
|
||||||
|
@ -12,7 +14,7 @@ pub trait RenderBackend {
|
||||||
fn register_shape(&mut self, shape: &swf::Shape) -> common::ShapeHandle;
|
fn register_shape(&mut self, shape: &swf::Shape) -> common::ShapeHandle;
|
||||||
|
|
||||||
fn begin_frame(&mut self);
|
fn begin_frame(&mut self);
|
||||||
fn clear(&mut self, color: crate::Color);
|
fn clear(&mut self, color: Color);
|
||||||
fn render_shape(&mut self, shape: ShapeHandle, matrix: &Matrix);
|
fn render_shape(&mut self, shape: ShapeHandle, matrix: &Matrix);
|
||||||
fn end_frame(&mut self);
|
fn end_frame(&mut self);
|
||||||
}
|
}
|
|
@ -1,9 +1,8 @@
|
||||||
use super::{common::ShapeHandle, shape_utils, RenderBackend};
|
use super::{common::ShapeHandle, RenderBackend};
|
||||||
use crate::backend::ui::glutin::GlutinBackend;
|
|
||||||
use crate::{matrix::Matrix, Color};
|
use crate::{matrix::Matrix, Color};
|
||||||
use glium::{implement_vertex, uniform, Display, Frame, Surface};
|
use glium::{implement_vertex, uniform, Display, Frame, Surface};
|
||||||
use lyon::tessellation::geometry_builder::{BuffersBuilder, VertexBuffers, VertexConstructor};
|
use glutin::WindowedContext;
|
||||||
use lyon::tessellation::FillVertex;
|
use lyon::tessellation::geometry_builder::{BuffersBuilder, VertexBuffers};
|
||||||
use lyon::{path::PathEvent, tessellation, tessellation::FillTessellator};
|
use lyon::{path::PathEvent, tessellation, tessellation::FillTessellator};
|
||||||
use std::collections::{HashMap, VecDeque};
|
use std::collections::{HashMap, VecDeque};
|
||||||
use swf::{FillStyle, LineStyle};
|
use swf::{FillStyle, LineStyle};
|
||||||
|
@ -16,8 +15,10 @@ pub struct GliumRenderBackend {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GliumRenderBackend {
|
impl GliumRenderBackend {
|
||||||
pub fn new(ui: &mut GlutinBackend) -> Result<GliumRenderBackend, Box<std::error::Error>> {
|
pub fn new(
|
||||||
let display = Display::from_gl_window(ui.take_context())?;
|
windowed_context: WindowedContext,
|
||||||
|
) -> Result<GliumRenderBackend, Box<std::error::Error>> {
|
||||||
|
let display = Display::from_gl_window(windowed_context)?;
|
||||||
|
|
||||||
let shader_program =
|
let shader_program =
|
||||||
glium::Program::from_source(&display, VERTEX_SHADER, FRAGMENT_SHADER, None)?;
|
glium::Program::from_source(&display, VERTEX_SHADER, FRAGMENT_SHADER, None)?;
|
||||||
|
@ -42,7 +43,7 @@ impl RenderBackend for GliumRenderBackend {
|
||||||
let mut fill_tess = FillTessellator::new();
|
let mut fill_tess = FillTessellator::new();
|
||||||
|
|
||||||
for (cmd, path) in paths {
|
for (cmd, path) in paths {
|
||||||
if let &PathCommandType::Stroke(_) = &cmd {
|
if let PathCommandType::Stroke(_) = cmd {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let color = match cmd {
|
let color = match cmd {
|
||||||
|
@ -188,7 +189,7 @@ fn swf_shape_to_lyon_paths(
|
||||||
let mut prev;
|
let mut prev;
|
||||||
use lyon::geom::{LineSegment, QuadraticBezierSegment};
|
use lyon::geom::{LineSegment, QuadraticBezierSegment};
|
||||||
for cmd in cmds {
|
for cmd in cmds {
|
||||||
if let PathCommandType::Fill(fill_style) = &cmd.command_type {
|
if let PathCommandType::Fill(_fill_style) = &cmd.command_type {
|
||||||
let mut out_path = vec![PathEvent::MoveTo(point(cmd.path.start.0, cmd.path.start.1))];
|
let mut out_path = vec![PathEvent::MoveTo(point(cmd.path.start.0, cmd.path.start.1))];
|
||||||
prev = point(cmd.path.start.0, cmd.path.start.1);
|
prev = point(cmd.path.start.0, cmd.path.start.1);
|
||||||
for edge in cmd.path.edges {
|
for edge in cmd.path.edges {
|
||||||
|
@ -260,7 +261,7 @@ fn get_paths(shape: &swf::Shape) -> impl Iterator<Item = PathCommand> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(ref new_styles) = style_change.new_styles {
|
if let Some(ref new_styles) = style_change.new_styles {
|
||||||
for (id, paths) in paths {
|
for (_id, paths) in paths {
|
||||||
for path in paths.open_paths {
|
for path in paths.open_paths {
|
||||||
out.push(PathCommand {
|
out.push(PathCommand {
|
||||||
command_type: paths.command_type.clone(),
|
command_type: paths.command_type.clone(),
|
|
@ -0,0 +1,14 @@
|
||||||
|
use super::{common::ShapeHandle, RenderBackend};
|
||||||
|
use crate::{matrix::Matrix, Color};
|
||||||
|
|
||||||
|
pub struct NullRenderer;
|
||||||
|
|
||||||
|
impl RenderBackend for NullRenderer {
|
||||||
|
fn register_shape(&mut self, _shape: &swf::Shape) -> ShapeHandle {
|
||||||
|
ShapeHandle(0)
|
||||||
|
}
|
||||||
|
fn begin_frame(&mut self) {}
|
||||||
|
fn end_frame(&mut self) {}
|
||||||
|
fn clear(&mut self, _color: Color) {}
|
||||||
|
fn render_shape(&mut self, _shape: ShapeHandle, _matrix: &Matrix) {}
|
||||||
|
}
|
|
@ -227,7 +227,7 @@ pub fn swf_shape_to_svg(shape: &Shape) -> String {
|
||||||
line_style.color.r, line_style.color.g, line_style.color.b, line_style.color.a
|
line_style.color.r, line_style.color.g, line_style.color.b, line_style.color.a
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.set("width", line_style.width as f32 / 20.0);
|
.set("width", f32::from(line_style.width) / 20.0);
|
||||||
|
|
||||||
let mut data = Data::new();
|
let mut data = Data::new();
|
||||||
for subpath in &stroke.subpaths {
|
for subpath in &stroke.subpaths {
|
|
@ -0,0 +1,95 @@
|
||||||
|
use super::{common::ShapeHandle, RenderBackend};
|
||||||
|
use crate::{matrix::Matrix, Color};
|
||||||
|
use log::info;
|
||||||
|
use wasm_bindgen::JsCast;
|
||||||
|
use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement, HtmlImageElement};
|
||||||
|
|
||||||
|
pub struct WebCanvasRenderBackend {
|
||||||
|
context: CanvasRenderingContext2d,
|
||||||
|
width: f64,
|
||||||
|
height: f64,
|
||||||
|
|
||||||
|
shapes: Vec<ShapeData>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ShapeData {
|
||||||
|
image: HtmlImageElement,
|
||||||
|
x_min: f64,
|
||||||
|
y_min: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WebCanvasRenderBackend {
|
||||||
|
pub fn new(canvas: HtmlCanvasElement) -> Result<Self, Box<std::error::Error>> {
|
||||||
|
let width = canvas.width();
|
||||||
|
let height = canvas.height();
|
||||||
|
let context: CanvasRenderingContext2d = canvas
|
||||||
|
.get_context("2d")
|
||||||
|
.map_err(|_| "Could not create context")?
|
||||||
|
.ok_or("Could not create context")?
|
||||||
|
.dyn_into()
|
||||||
|
.map_err(|_| "Expected CanvasRenderingContext2d")?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
context,
|
||||||
|
width: width.into(),
|
||||||
|
height: height.into(),
|
||||||
|
shapes: vec![],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderBackend for WebCanvasRenderBackend {
|
||||||
|
fn register_shape(&mut self, shape: &swf::Shape) -> ShapeHandle {
|
||||||
|
let handle = ShapeHandle(self.shapes.len());
|
||||||
|
|
||||||
|
let image = HtmlImageElement::new().unwrap();
|
||||||
|
|
||||||
|
use url::percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET};
|
||||||
|
let svg = super::shape_utils::swf_shape_to_svg(&shape);
|
||||||
|
let svg_encoded = format!(
|
||||||
|
"data:image/svg+xml,{}",
|
||||||
|
utf8_percent_encode(&svg, DEFAULT_ENCODE_SET)
|
||||||
|
);
|
||||||
|
info!("{}", svg_encoded);
|
||||||
|
image.set_src(&svg_encoded);
|
||||||
|
|
||||||
|
self.shapes.push(ShapeData {
|
||||||
|
image,
|
||||||
|
x_min: shape.shape_bounds.x_min.into(),
|
||||||
|
y_min: shape.shape_bounds.y_min.into(),
|
||||||
|
});
|
||||||
|
|
||||||
|
handle
|
||||||
|
}
|
||||||
|
|
||||||
|
fn begin_frame(&mut self) {
|
||||||
|
self.context.reset_transform().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end_frame(&mut self) {
|
||||||
|
// Noop
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear(&mut self, color: Color) {
|
||||||
|
let color = format!("rgb({}, {}, {}", color.r, color.g, color.b);
|
||||||
|
self.context.set_fill_style(&color.into());
|
||||||
|
self.context.fill_rect(0.0, 0.0, self.width, self.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_shape(&mut self, shape: ShapeHandle, matrix: &Matrix) {
|
||||||
|
let shape = &self.shapes[shape.0];
|
||||||
|
self.context
|
||||||
|
.set_transform(
|
||||||
|
matrix.a.into(),
|
||||||
|
matrix.b.into(),
|
||||||
|
matrix.c.into(),
|
||||||
|
matrix.d.into(),
|
||||||
|
matrix.tx.into(),
|
||||||
|
matrix.ty.into(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
self.context
|
||||||
|
.draw_image_with_html_image_element(&shape.image, shape.x_min, shape.y_min)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
mod backend;
|
pub mod backend;
|
||||||
mod character;
|
mod character;
|
||||||
mod color_transform;
|
mod color_transform;
|
||||||
mod display_object;
|
mod display_object;
|
|
@ -59,7 +59,7 @@ impl MovieClip {
|
||||||
context: &mut UpdateContext,
|
context: &mut UpdateContext,
|
||||||
) {
|
) {
|
||||||
use swf::PlaceObjectAction;
|
use swf::PlaceObjectAction;
|
||||||
let mut character = match place_object.action {
|
let character = match place_object.action {
|
||||||
PlaceObjectAction::Place(id) => {
|
PlaceObjectAction::Place(id) => {
|
||||||
// TODO(Herschel): Behavior when character doesn't exist/isn't a DisplayObject?
|
// TODO(Herschel): Behavior when character doesn't exist/isn't a DisplayObject?
|
||||||
let character =
|
let character =
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::backend::{render::RenderBackend, ui::UiBackend};
|
use crate::backend::render::RenderBackend;
|
||||||
use crate::color_transform::ColorTransformStack;
|
use crate::color_transform::ColorTransformStack;
|
||||||
use crate::display_object::DisplayObject;
|
use crate::display_object::DisplayObject;
|
||||||
use crate::library::Library;
|
use crate::library::Library;
|
||||||
|
@ -25,8 +25,6 @@ pub struct Player {
|
||||||
|
|
||||||
render_context: RenderContext,
|
render_context: RenderContext,
|
||||||
|
|
||||||
ui: Box<UiBackend>,
|
|
||||||
|
|
||||||
library: Library,
|
library: Library,
|
||||||
stage: Cc<RefCell<Stage>>,
|
stage: Cc<RefCell<Stage>>,
|
||||||
|
|
||||||
|
@ -35,21 +33,23 @@ pub struct Player {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Player {
|
impl Player {
|
||||||
pub fn new(swf_data: Vec<u8>) -> Result<Player, Box<std::error::Error>> {
|
pub fn new(
|
||||||
Self::new_internal(swf_data)
|
renderer: Box<RenderBackend>,
|
||||||
|
swf_data: Vec<u8>,
|
||||||
|
) -> Result<Player, Box<std::error::Error>> {
|
||||||
|
Self::new_internal(renderer, swf_data)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_internal(swf_data: Vec<u8>) -> Result<Player, Box<std::error::Error>> {
|
fn new_internal(
|
||||||
|
renderer: Box<RenderBackend>,
|
||||||
|
swf_data: Vec<u8>,
|
||||||
|
) -> Result<Player, Box<std::error::Error>> {
|
||||||
let (swf, tag_stream) = swf::read::read_swf_header_decompressed(&swf_data[..]).unwrap();
|
let (swf, tag_stream) = swf::read::read_swf_header_decompressed(&swf_data[..]).unwrap();
|
||||||
info!("{}x{}", swf.stage_size.x_max, swf.stage_size.y_max);
|
info!("{}x{}", swf.stage_size.x_max, swf.stage_size.y_max);
|
||||||
|
|
||||||
let (ui, renderer) = crate::backend::build()?;
|
|
||||||
|
|
||||||
Ok(Player {
|
Ok(Player {
|
||||||
tag_stream,
|
tag_stream,
|
||||||
|
|
||||||
ui,
|
|
||||||
|
|
||||||
render_context: RenderContext {
|
render_context: RenderContext {
|
||||||
renderer,
|
renderer,
|
||||||
matrix_stack: MatrixStack::new(),
|
matrix_stack: MatrixStack::new(),
|
||||||
|
@ -64,22 +64,6 @@ impl Player {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn play(&mut self) {
|
|
||||||
use std::time::{Duration, Instant};
|
|
||||||
|
|
||||||
let mut time = Instant::now();
|
|
||||||
|
|
||||||
while self.ui.poll_events() {
|
|
||||||
let new_time = Instant::now();
|
|
||||||
let dt = new_time.duration_since(time).as_millis();
|
|
||||||
time = new_time;
|
|
||||||
|
|
||||||
self.tick(dt as f64);
|
|
||||||
|
|
||||||
std::thread::sleep(Duration::from_millis(1000 / 60));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn tick(&mut self, dt: f64) {
|
pub fn tick(&mut self, dt: f64) {
|
||||||
self.frame_accumulator += dt;
|
self.frame_accumulator += dt;
|
||||||
let frame_time = 1000.0 / self.frame_rate;
|
let frame_time = 1000.0 / self.frame_rate;
|
|
@ -5,5 +5,6 @@ authors = ["Mike Welsh <mwelsh@gmail.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
fluster_core = { path = "../lib" }
|
fluster_core = { path = "../core" }
|
||||||
|
glutin = "0.20"
|
||||||
structopt = "0.2.15"
|
structopt = "0.2.15"
|
|
@ -1,21 +1,74 @@
|
||||||
use fluster_core::Player;
|
use fluster_core::{backend::render::glium::GliumRenderBackend, Player};
|
||||||
|
use glutin::{ContextBuilder, Event, EventsLoop, WindowBuilder, WindowEvent};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
use structopt::StructOpt;
|
||||||
|
|
||||||
|
#[derive(StructOpt, Debug)]
|
||||||
|
#[structopt(name = "basic")]
|
||||||
|
struct Opt {
|
||||||
|
#[structopt(name = "FILE", parse(from_os_str))]
|
||||||
|
input_path: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
|
||||||
#[allow(dead_code)]
|
|
||||||
fn main() {
|
fn main() {
|
||||||
use std::path::PathBuf;
|
|
||||||
use structopt::StructOpt;
|
|
||||||
|
|
||||||
#[derive(StructOpt, Debug)]
|
|
||||||
#[structopt(name = "basic")]
|
|
||||||
struct Opt {
|
|
||||||
#[structopt(name = "FILE", parse(from_os_str))]
|
|
||||||
input_path: PathBuf,
|
|
||||||
}
|
|
||||||
|
|
||||||
let opt = Opt::from_args();
|
let opt = Opt::from_args();
|
||||||
|
|
||||||
let swf_data = std::fs::read(opt.input_path).unwrap();
|
let ret = run_player(opt.input_path);
|
||||||
let mut player = Player::new(swf_data).unwrap();
|
|
||||||
player.play();
|
if let Err(e) = ret {
|
||||||
|
eprintln!("Fatal error:\n{}", e);
|
||||||
|
std::process::exit(-1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn run_player(input_path: PathBuf) -> Result<(), Box<std::error::Error>> {
|
||||||
|
let swf_data = std::fs::read(input_path)?;
|
||||||
|
|
||||||
|
let mut events_loop = EventsLoop::new();
|
||||||
|
let window_builder = WindowBuilder::new();
|
||||||
|
let windowed_context = ContextBuilder::new().build_windowed(window_builder, &events_loop)?;
|
||||||
|
let renderer = GliumRenderBackend::new(windowed_context)?;
|
||||||
|
let mut player = Player::new(Box::new(renderer), swf_data)?;
|
||||||
|
|
||||||
|
let mut time = Instant::now();
|
||||||
|
loop {
|
||||||
|
// Poll UI events
|
||||||
|
let mut request_close = false;
|
||||||
|
events_loop.poll_events(|event| match event {
|
||||||
|
Event::WindowEvent {
|
||||||
|
event: WindowEvent::CloseRequested,
|
||||||
|
..
|
||||||
|
} => request_close = true,
|
||||||
|
_ => (),
|
||||||
|
});
|
||||||
|
|
||||||
|
if request_close {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let new_time = Instant::now();
|
||||||
|
let dt = new_time.duration_since(time).as_millis();
|
||||||
|
time = new_time;
|
||||||
|
|
||||||
|
player.tick(dt as f64);
|
||||||
|
|
||||||
|
std::thread::sleep(Duration::from_millis(1000 / 60));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// impl UiBackend for GlutinBackend {
|
||||||
|
// fn poll_events(&mut self) -> bool {
|
||||||
|
// let mut request_close = false;
|
||||||
|
// self.events_loop.poll_events(|event| match event {
|
||||||
|
// Event::WindowEvent {
|
||||||
|
// event: WindowEvent::CloseRequested,
|
||||||
|
// ..
|
||||||
|
// } => request_close = true,
|
||||||
|
// _ => (),
|
||||||
|
// });
|
||||||
|
|
||||||
|
// !request_close
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
|
@ -1,36 +0,0 @@
|
||||||
pub mod render;
|
|
||||||
pub mod ui;
|
|
||||||
|
|
||||||
use render::RenderBackend;
|
|
||||||
use ui::UiBackend;
|
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
|
||||||
pub fn build() -> Result<(Box<UiBackend>, Box<RenderBackend>), Box<std::error::Error>> {
|
|
||||||
let mut ui = ui::glutin::GlutinBackend::new(500, 500)?;
|
|
||||||
let renderer = render::glium::GliumRenderBackend::new(&mut ui)?;
|
|
||||||
Ok((Box::new(ui), Box::new(renderer)))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
|
||||||
pub fn build() -> Result<(Box<UiBackend>, Box<RenderBackend>), Box<std::error::Error>> {
|
|
||||||
use wasm_bindgen::JsCast;
|
|
||||||
|
|
||||||
let window = web_sys::window().ok_or("Expected window")?;
|
|
||||||
let document = window.document().ok_or("Expected document")?;
|
|
||||||
let canvas: web_sys::HtmlCanvasElement = document
|
|
||||||
.get_element_by_id("fluster-canvas")
|
|
||||||
.ok_or("Missing element")?
|
|
||||||
.dyn_into()
|
|
||||||
.map_err(|_| "Not a canvas")?;
|
|
||||||
let (width, height) = (f64::from(canvas.width()), f64::from(canvas.height()));
|
|
||||||
let context: web_sys::CanvasRenderingContext2d = canvas
|
|
||||||
.get_context("2d")
|
|
||||||
.map_err(|_| "Unable to make canvas context")?
|
|
||||||
.ok_or("Unable to make canvas context")?
|
|
||||||
.dyn_into()
|
|
||||||
.map_err(|_| "Not a CanvasRenderingContext2d")?;
|
|
||||||
|
|
||||||
let mut ui = ui::web_canvas::WebCanvasBackend::new(canvas)?;
|
|
||||||
let renderer = render::web_canvas::WebCanvasRenderBackend::new(context, width, height);
|
|
||||||
Ok((Box::new(ui), Box::new(renderer)))
|
|
||||||
}
|
|
|
@ -1,69 +0,0 @@
|
||||||
use super::{common::ShapeHandle, RenderBackend};
|
|
||||||
use crate::{Color, matrix::Matrix};
|
|
||||||
use log::info;
|
|
||||||
use web_sys::{CanvasRenderingContext2d, HtmlImageElement};
|
|
||||||
|
|
||||||
pub struct WebCanvasRenderBackend {
|
|
||||||
context: CanvasRenderingContext2d,
|
|
||||||
width: f64,
|
|
||||||
height: f64,
|
|
||||||
|
|
||||||
shapes: Vec<ShapeData>,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ShapeData {
|
|
||||||
image: HtmlImageElement,
|
|
||||||
x_min: f64,
|
|
||||||
y_min: f64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WebCanvasRenderBackend {
|
|
||||||
pub fn new(context: CanvasRenderingContext2d, width: f64, height: f64) -> Self {
|
|
||||||
Self {
|
|
||||||
context,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
shapes: vec![],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RenderBackend for WebCanvasRenderBackend {
|
|
||||||
fn register_shape(&mut self, shape: &swf::Shape) -> ShapeHandle {
|
|
||||||
let handle = ShapeHandle(self.shapes.len());
|
|
||||||
|
|
||||||
let image = HtmlImageElement::new().unwrap();
|
|
||||||
|
|
||||||
use url::percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET};
|
|
||||||
let svg = super::shape_utils::swf_shape_to_svg(&shape);
|
|
||||||
let svg_encoded = format!("data:image/svg+xml,{}", utf8_percent_encode(&svg, DEFAULT_ENCODE_SET));
|
|
||||||
info!("{}", svg_encoded);
|
|
||||||
image.set_src(&svg_encoded);
|
|
||||||
|
|
||||||
self.shapes.push(ShapeData{
|
|
||||||
image, x_min: shape.shape_bounds.x_min.into(), y_min: shape.shape_bounds.y_min.into()
|
|
||||||
});
|
|
||||||
|
|
||||||
handle
|
|
||||||
}
|
|
||||||
|
|
||||||
fn begin_frame(&mut self) {
|
|
||||||
self.context.reset_transform();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn end_frame(&mut self) {
|
|
||||||
// Noop
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clear(&mut self, color: Color) {
|
|
||||||
let color = format!("rgb({}, {}, {}", color.r, color.g, color.b);
|
|
||||||
self.context.set_fill_style(&color.into());
|
|
||||||
self.context.fill_rect(0.0, 0.0, self.width, self.height);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_shape(&mut self, shape: ShapeHandle, matrix: &Matrix) {
|
|
||||||
let shape = &self.shapes[shape.0];
|
|
||||||
self.context.set_transform(matrix.a.into(), matrix.b.into(), matrix.c.into(), matrix.d.into(), matrix.tx.into(), matrix.ty.into()).unwrap();
|
|
||||||
self.context.draw_image_with_html_image_element(&shape.image, shape.x_min, shape.y_min).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
|
||||||
pub mod glutin;
|
|
||||||
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
|
||||||
pub mod web_canvas;
|
|
||||||
|
|
||||||
pub trait UiBackend {
|
|
||||||
fn poll_events(&mut self) -> bool;
|
|
||||||
}
|
|
|
@ -1,38 +0,0 @@
|
||||||
use super::UiBackend;
|
|
||||||
use glutin::{ContextBuilder, Event, EventsLoop, WindowBuilder, WindowEvent, WindowedContext};
|
|
||||||
|
|
||||||
pub struct GlutinBackend {
|
|
||||||
context: Option<WindowedContext>,
|
|
||||||
events_loop: EventsLoop,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GlutinBackend {
|
|
||||||
pub fn new(width: u32, height: u32) -> Result<GlutinBackend, Box<std::error::Error>> {
|
|
||||||
let events_loop = EventsLoop::new();
|
|
||||||
let window_builder = WindowBuilder::new().with_dimensions((width, height).into());
|
|
||||||
let context = ContextBuilder::new().build_windowed(window_builder, &events_loop)?;
|
|
||||||
Ok(GlutinBackend {
|
|
||||||
context: Some(context),
|
|
||||||
events_loop,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn take_context(&mut self) -> WindowedContext {
|
|
||||||
self.context.take().unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UiBackend for GlutinBackend {
|
|
||||||
fn poll_events(&mut self) -> bool {
|
|
||||||
let mut request_close = false;
|
|
||||||
self.events_loop.poll_events(|event| match event {
|
|
||||||
Event::WindowEvent {
|
|
||||||
event: WindowEvent::CloseRequested,
|
|
||||||
..
|
|
||||||
} => request_close = true,
|
|
||||||
_ => (),
|
|
||||||
});
|
|
||||||
|
|
||||||
!request_close
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
use super::UiBackend;
|
|
||||||
use web_sys::HtmlCanvasElement;
|
|
||||||
|
|
||||||
pub struct WebCanvasBackend {
|
|
||||||
canvas: HtmlCanvasElement,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WebCanvasBackend {
|
|
||||||
pub fn new(canvas: HtmlCanvasElement) -> Result<Self, Box<std::error::Error>> {
|
|
||||||
use log::Level;
|
|
||||||
console_log::init_with_level(Level::Trace)?;
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
canvas
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UiBackend for WebCanvasBackend {
|
|
||||||
fn poll_events(&mut self) -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -7,12 +7,19 @@ edition = "2018"
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["cdylib", "rlib"]
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["console_error_panic_hook", "console_log"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
console_error_panic_hook = { version = "0.1.1", optional = true }
|
console_error_panic_hook = { version = "0.1.1", optional = true }
|
||||||
console_log = { version = "0.1", optional = true }
|
console_log = { version = "0.1", optional = true }
|
||||||
fluster_core = { path = "../lib" }
|
fluster_core = { path = "../core" }
|
||||||
js-sys = "0.3.19"
|
js-sys = "0.3.19"
|
||||||
wasm-bindgen = "0.2"
|
wasm-bindgen = "0.2"
|
||||||
|
|
||||||
|
[dependencies.web-sys]
|
||||||
|
version = "0.3.19"
|
||||||
|
features = ["CanvasRenderingContext2d", "HtmlCanvasElement"]
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
wasm-bindgen-test = "0.2"
|
wasm-bindgen-test = "0.2"
|
|
@ -1,20 +1,33 @@
|
||||||
|
use fluster_core::backend::render::web_canvas::WebCanvasRenderBackend;
|
||||||
use js_sys::Uint8Array;
|
use js_sys::Uint8Array;
|
||||||
use wasm_bindgen::{prelude::*, JsValue};
|
use wasm_bindgen::{prelude::*, JsValue};
|
||||||
|
use web_sys::HtmlCanvasElement;
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub struct Player(fluster_core::Player);
|
pub struct Player(fluster_core::Player);
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
impl Player {
|
impl Player {
|
||||||
pub fn new(swf_data: Uint8Array) -> Result<Player, JsValue> {
|
pub fn new(canvas: HtmlCanvasElement, swf_data: Uint8Array) -> Result<Player, JsValue> {
|
||||||
let mut data = vec![0; swf_data.length() as usize];
|
Player::new_internal(canvas, swf_data).map_err(|_| "Error creating player".into())
|
||||||
swf_data.copy_to(&mut data[..]);
|
|
||||||
|
|
||||||
let player = fluster_core::Player::new(data).map_err(|_| JsValue::null())?;
|
|
||||||
Ok(Player(player))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tick(&mut self, dt: f64) {
|
pub fn tick(&mut self, dt: f64) {
|
||||||
self.0.tick(dt);
|
self.0.tick(dt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Player {
|
||||||
|
fn new_internal(
|
||||||
|
canvas: HtmlCanvasElement,
|
||||||
|
swf_data: Uint8Array,
|
||||||
|
) -> Result<Player, Box<std::error::Error>> {
|
||||||
|
let mut data = vec![0; swf_data.length() as usize];
|
||||||
|
swf_data.copy_to(&mut data[..]);
|
||||||
|
|
||||||
|
let renderer = WebCanvasRenderBackend::new(canvas)?;
|
||||||
|
|
||||||
|
let player = fluster_core::Player::new(Box::new(renderer), data)?;
|
||||||
|
Ok(Player(player))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Player } from "fluster";
|
import { Player } from "../pkg/fluster";
|
||||||
|
|
||||||
let fileInput = document.getElementById("file-input");
|
let fileInput = document.getElementById("file-input");
|
||||||
fileInput.addEventListener("change", fileSelected, false);
|
fileInput.addEventListener("change", fileSelected, false);
|
||||||
|
@ -20,8 +20,7 @@ let timestamp = 0;
|
||||||
function playSwf(swfData) {
|
function playSwf(swfData) {
|
||||||
let canvas = document.getElementById("fluster-canvas");
|
let canvas = document.getElementById("fluster-canvas");
|
||||||
if (swfData && canvas) {
|
if (swfData && canvas) {
|
||||||
let data = new Uint8Array(swfData);
|
player = Player.new(canvas, new Uint8Array(swfData));
|
||||||
player = Player.new(data);
|
|
||||||
timestamp = performance.now();
|
timestamp = performance.now();
|
||||||
window.requestAnimationFrame(tickPlayer);
|
window.requestAnimationFrame(tickPlayer);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue