use delegate::delegate;
use dirs;
pub use pixels::Error;
use pixels::{Pixels, SurfaceTexture};
use png::Encoder;
use std::collections::{HashMap, HashSet};
use std::marker::PhantomData;
use std::rc::Rc;
use std::sync::Arc;
use std::sync::mpsc;
use std::time::{Instant, SystemTime, UNIX_EPOCH};
use winit::{
application::ApplicationHandler,
dpi::LogicalSize,
event::{Modifiers, MouseButton, WindowEvent},
event_loop::{ControlFlow, EventLoop},
keyboard::{Key, ModifiersKeyState},
window::{CursorIcon, Window, WindowId},
};
const DEFAULT_WIDTH: u32 = 1080;
const DEFAULT_HEIGHT: u32 = 700;
const DEFAULT_TITLE: &str = "Artimate";
#[derive(Debug)]
pub struct Config {
pub width: u32,
pub height: u32,
pub no_loop: bool,
pub frames: Option<u32>,
pub cursor_visible: bool,
pub frames_to_save: u32,
pub window_title: String,
}
impl Config {
pub fn new(
width: u32,
height: u32,
no_loop: bool,
cursor_visible: bool,
frames_to_save: u32,
) -> Self {
Self {
width,
height,
no_loop,
frames: None,
cursor_visible,
frames_to_save,
window_title: DEFAULT_TITLE.to_string(),
}
}
pub fn with_dims(width: u32, height: u32) -> Self {
Self::new(width, height, false, true, 0)
}
pub fn wh(&self) -> (u32, u32) {
(self.width, self.height)
}
pub fn wh_f32(&self) -> (f32, f32) {
(self.width as f32, self.height as f32)
}
pub fn w_f32(&self) -> f32 {
self.width as f32
}
pub fn h_f32(&self) -> f32 {
self.height as f32
}
pub fn set_frames_to_save(self, frames_to_save: u32) -> Self {
Self {
frames_to_save,
..self
}
}
pub fn set_cursor_visibility(self, cursor_visible: bool) -> Self {
Self {
cursor_visible,
..self
}
}
pub fn no_loop(self) -> Self {
Self {
no_loop: true,
..self
}
}
pub fn set_frames(self, frames: u32) -> Self {
Self {
frames: Some(frames),
..self
}
}
pub fn set_title(self, title: &str) -> Self {
Self {
window_title: title.to_string(),
..self
}
}
}
impl Default for Config {
fn default() -> Self {
Self::new(DEFAULT_WIDTH, DEFAULT_HEIGHT, false, true, 0)
}
}
pub struct SketchMode;
pub struct AppMode;
pub struct App<Mode = SketchMode, M = ()> {
pub model: M,
pub config: Config,
pub update: Option<fn(&App<Mode, M>, M) -> M>,
pub draw: fn(&App<Mode, M>, &M) -> Vec<u8>,
pub time: f32,
pub start_time: Instant,
pub frame_count: u32,
window: Option<Arc<Window>>,
pixels: Option<Pixels<'static>>,
pub mouse_position: (f32, f32),
frame_sender: Option<mpsc::Sender<(Vec<u8>, String, u32, u32)>>,
key_handlers: HashMap<Key, Rc<dyn Fn(&mut App<Mode, M>)>>,
mouse_handlers: HashMap<MouseButton, Rc<dyn Fn(&mut App<Mode, M>)>>,
key_press_handlers: HashMap<Key, Rc<dyn Fn(&mut App<Mode, M>)>>,
key_release_handlers: HashMap<Key, Rc<dyn Fn(&mut App<Mode, M>)>>,
keys_down: HashSet<Key>,
modifiers: Modifiers,
_mode: PhantomData<Mode>,
}
fn setup_frame_sender() -> Option<mpsc::Sender<(Vec<u8>, String, u32, u32)>> {
let (tx, rx) = mpsc::channel();
std::thread::spawn(move || {
while let Ok((frame_data, filename, width, height)) = rx.recv() {
if let Err(err) = save_frame(frame_data, filename, width, height) {
eprintln!("Failed to save frame: {}", err);
}
}
});
Some(tx)
}
fn save_frame(
frame_data: Vec<u8>,
filename: String,
width: u32,
height: u32,
) -> Result<(), Box<dyn std::error::Error>> {
let file = std::fs::File::create(&filename)?;
let mut encoder = Encoder::new(file, width, height);
encoder.set_color(png::ColorType::Rgba);
encoder.set_depth(png::BitDepth::Eight);
let mut writer = encoder.write_header()?;
writer.write_image_data(&frame_data)?;
Ok(())
}
impl App<SketchMode> {
pub fn sketch(config: Config, draw: fn(&App<SketchMode, ()>, &()) -> Vec<u8>) -> Self {
let maybe_tx = if config.frames_to_save > 0 {
setup_frame_sender()
} else {
None
};
Self {
model: (),
config,
update: None,
draw,
time: 0.0,
frame_count: 0,
window: None,
pixels: None,
start_time: Instant::now(),
mouse_position: (0.0, 0.0),
frame_sender: maybe_tx,
key_handlers: HashMap::new(),
mouse_handlers: HashMap::new(),
key_press_handlers: HashMap::new(),
key_release_handlers: HashMap::new(),
keys_down: HashSet::new(),
modifiers: Modifiers::default(),
_mode: PhantomData,
}
}
}
impl<M> App<AppMode, M>
where
M: Clone,
{
pub fn app(
model: M,
config: Config,
update: fn(&App<AppMode, M>, M) -> M,
draw: fn(&App<AppMode, M>, &M) -> Vec<u8>,
) -> Self {
let maybe_tx = if config.frames_to_save > 0 {
setup_frame_sender()
} else {
None
};
Self {
model,
config,
update: Some(update),
draw,
time: 0.0,
frame_count: 0,
window: None,
pixels: None,
start_time: Instant::now(),
mouse_position: (0.0, 0.0),
frame_sender: maybe_tx,
key_handlers: HashMap::new(),
mouse_handlers: HashMap::new(),
key_press_handlers: HashMap::new(),
key_release_handlers: HashMap::new(),
keys_down: HashSet::new(),
modifiers: Modifiers::default(),
_mode: PhantomData,
}
}
}
impl<Mode, M> App<Mode, M>
where
M: Clone,
{
pub fn run(&mut self) -> Result<(), Error> {
let event_loop = EventLoop::new().unwrap();
event_loop.set_control_flow(ControlFlow::Poll);
let now = Instant::now();
let res = event_loop.run_app(self);
println!();
println!(
"Average FPS: {}",
self.frame_count as f32 / now.elapsed().as_secs_f32(),
);
println!("Frame count: {}", self.frame_count,);
println!("Elapsed time: {} seconds", now.elapsed().as_secs_f32(),);
res.map_err(|e| Error::UserDefined(Box::new(e)))
}
pub fn mouse_x(&self) -> f32 {
self.mouse_position.0
}
pub fn mouse_y(&self) -> f32 {
self.mouse_position.1
}
delegate! {
to self.config {
pub fn wh(&self) -> (u32, u32);
pub fn wh_f32(&self) -> (f32, f32);
pub fn w_f32(&self) -> f32;
pub fn h_f32(&self) -> f32;
}
}
pub fn set_frames_to_save(mut self, frames_to_save: u32) -> Self {
self.config = self.config.set_frames_to_save(frames_to_save);
self
}
pub fn set_cursor_visibility(mut self, cursor_visible: bool) -> Self {
self.config = self.config.set_cursor_visibility(cursor_visible);
self
}
pub fn no_loop(mut self) -> Self {
self.config = self.config.no_loop();
self
}
pub fn set_frames(mut self, frames: u32) -> Self {
self.config = self.config.set_frames(frames);
self
}
pub fn set_title(self, title: &str) -> Self {
Self {
config: self.config.set_title(title),
..self
}
}
pub fn on_key_held<F>(&mut self, key: Key, handler: F)
where
F: Fn(&mut App<Mode, M>) + 'static,
{
self.key_handlers.insert(key, Rc::new(handler));
}
pub fn on_key_press<F>(&mut self, key: Key, handler: F)
where
F: Fn(&mut App<Mode, M>) + 'static,
{
self.key_press_handlers.insert(key, Rc::new(handler));
}
pub fn on_key_release<F>(&mut self, key: Key, handler: F)
where
F: Fn(&mut App<Mode, M>) + 'static,
{
self.key_release_handlers.insert(key, Rc::new(handler));
}
pub fn on_mouse_press<F>(&mut self, button: MouseButton, handler: F)
where
F: Fn(&mut App<Mode, M>) + 'static,
{
self.mouse_handlers.insert(button, Rc::new(handler));
}
fn handle_keyboard_input(
&mut self,
event: winit::event::KeyEvent,
_event_loop: &winit::event_loop::ActiveEventLoop,
) {
match event.state {
winit::event::ElementState::Pressed => {
self.keys_down.insert(event.logical_key.clone());
if let Some(handler) = self.key_press_handlers.get(&event.logical_key).cloned() {
handler(self);
self.window.as_ref().unwrap().request_redraw();
}
}
winit::event::ElementState::Released => {
self.keys_down.remove(&event.logical_key);
if let Some(handler) = self.key_release_handlers.get(&event.logical_key).cloned() {
handler(self);
self.window.as_ref().unwrap().request_redraw();
}
}
}
if event.state == winit::event::ElementState::Pressed {
if let Some(handler) = self.key_handlers.get(&event.logical_key).cloned() {
handler(self);
self.window.as_ref().unwrap().request_redraw();
}
}
}
fn handle_mouse_input(&mut self, button: MouseButton) {
let handler = self.mouse_handlers.get(&button).cloned();
if let Some(handler) = handler {
handler(self);
self.window.as_ref().unwrap().request_redraw();
}
}
}
impl<Mode, M> ApplicationHandler for App<Mode, M>
where
M: Clone,
{
fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
let size = LogicalSize::new(self.config.width as f64, self.config.height as f64);
self.window.get_or_insert_with(|| {
Arc::new(event_loop
.create_window(
Window::default_attributes()
.with_title(self.config.window_title.clone())
.with_inner_size(size)
.with_min_inner_size(size),
)
.unwrap())
});
}
fn window_event(
&mut self,
event_loop: &winit::event_loop::ActiveEventLoop,
_window_id: WindowId,
event: WindowEvent,
) {
let window = self.window.as_ref().unwrap();
let window_size = window.inner_size();
self.time = self.start_time.elapsed().as_secs_f32();
match event {
WindowEvent::CloseRequested => {
println!("Close Requested");
event_loop.exit();
}
WindowEvent::ModifiersChanged(new_mods) => {
self.modifiers = new_mods; }
WindowEvent::KeyboardInput { event, .. } => {
if event.state == winit::event::ElementState::Pressed {
if let Key::Character(ref text) = event.logical_key {
if text == "s" {
if self.modifiers.lsuper_state() == ModifiersKeyState::Pressed
|| self.modifiers.rsuper_state() == ModifiersKeyState::Pressed
{
let draw_result = (self.draw)(&self, &self.model);
if let Some(pixels) = self.pixels.as_mut() {
pixels.frame_mut().copy_from_slice(draw_result.as_ref());
let frame_data: Vec<u8> = pixels.frame().to_vec();
if let Some(downloads_dir) = dirs::download_dir() {
let output_dir = downloads_dir.join("artmate");
if let Err(err) = std::fs::create_dir_all(&output_dir) {
eprintln!("Failed to create frames directory: {}", err);
} else {
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
let filename = output_dir
.join(format!("artmate_{}.png", timestamp));
save_frame(
frame_data,
filename.to_string_lossy().to_string(),
self.config.width,
self.config.height,
)
.unwrap();
}
}
}
}
}
}
}
self.handle_keyboard_input(event, event_loop);
}
WindowEvent::MouseInput { button, state, .. } => {
if state == winit::event::ElementState::Pressed {
self.handle_mouse_input(button);
}
}
WindowEvent::CursorMoved { position, .. } => {
if let Some(window) = &self.window {
let scale_factor = window.scale_factor();
let logical_position = position.to_logical(scale_factor);
self.mouse_position = (logical_position.x, logical_position.y);
}
}
WindowEvent::CursorEntered { .. } => {
if let Some(window) = &self.window {
if self.config.cursor_visible {
window.set_cursor(CursorIcon::Crosshair);
} else {
window.set_cursor_visible(false);
}
}
}
WindowEvent::CursorLeft { .. } => {
if let Some(window) = &self.window {
window.set_cursor(CursorIcon::Default);
window.set_cursor_visible(true);
}
}
WindowEvent::RedrawRequested => {
self.pixels.get_or_insert_with(|| {
let surface_texture =
SurfaceTexture::new(window_size.width, window_size.height, window.clone());
Pixels::new(self.config.width, self.config.height, surface_texture).unwrap()
});
let draw_result = (self.draw)(&self, &self.model);
if let Some(pixels) = self.pixels.as_mut() {
pixels.frame_mut().copy_from_slice(draw_result.as_ref());
if self.frame_count < self.config.frames_to_save {
if let Some(sender) = &self.frame_sender {
let frame_data: Vec<u8> = pixels.frame().to_vec();
if let Some(downloads_dir) = dirs::download_dir() {
let output_dir = downloads_dir.join("frames");
if let Err(err) = std::fs::create_dir_all(&output_dir) {
eprintln!("Failed to create frames directory: {}", err);
} else {
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
let filename = output_dir.join(format!(
"frame_{}_{:04}.png",
timestamp, self.frame_count
));
if let Err(err) = sender.send((
frame_data,
filename.to_string_lossy().to_string(),
self.config.width,
self.config.height,
)) {
eprintln!("Failed to send frame data: {}", err);
}
}
}
}
}
if let Err(_err) = pixels.render() {
event_loop.exit();
return;
}
}
if let Some(update) = self.update {
self.model = update(&self, self.model.clone());
}
if !self.config.no_loop {
if let Some(frames) = self.config.frames {
if self.frame_count < frames {
window.request_redraw();
}
} else {
window.request_redraw();
}
}
self.frame_count += 1;
}
_ => (),
}
}
}