formatting

note that i'm intentionally not using rustfmt. i've tried to like that
tool, but in the end i just really don't like it. too many edge cases
and subjectivity and not enough customization. which is probably the
intent. which makes me hate it that much more. fuck you, rustfmt.
This commit is contained in:
Gered 2023-03-02 15:10:27 -05:00
parent 43333687a8
commit eb6a363afd
56 changed files with 15746 additions and 15743 deletions

View file

@ -9,195 +9,193 @@ use libretrogd::utils::rnd_value;
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
struct AudioChannelStatus { struct AudioChannelStatus {
size: usize, size: usize,
position: usize, position: usize,
playing: bool playing: bool,
} }
fn load_and_convert_wav(path: &Path, target_spec: &AudioSpec) -> Result<AudioBuffer> { fn load_and_convert_wav(path: &Path, target_spec: &AudioSpec) -> Result<AudioBuffer> {
let sound = AudioBuffer::load_wav_file(path)?; let sound = AudioBuffer::load_wav_file(path)?;
let original_spec = *sound.spec(); let original_spec = *sound.spec();
let sound = sound.convert(target_spec)?; let sound = sound.convert(target_spec)?;
let final_spec = *sound.spec(); let final_spec = *sound.spec();
if original_spec != final_spec { if original_spec != final_spec {
println!("{:?} was converted from {:?} to {:?}", path, original_spec, final_spec); println!("{:?} was converted from {:?} to {:?}", path, original_spec, final_spec);
} else { } else {
println!("{:?} did not need to be converted from {:?}", path, original_spec); println!("{:?} did not need to be converted from {:?}", path, original_spec);
} }
Ok(sound) Ok(sound)
} }
pub struct SineWaveGenerator { pub struct SineWaveGenerator {
t: usize, t: usize,
} }
impl SineWaveGenerator { impl SineWaveGenerator {
pub fn new() -> Self { pub fn new() -> Self {
SineWaveGenerator { SineWaveGenerator {
t: 0 t: 0
} }
} }
} }
impl AudioGenerator for SineWaveGenerator { impl AudioGenerator for SineWaveGenerator {
fn gen_sample(&mut self, position: usize) -> Option<u8> { fn gen_sample(&mut self, position: usize) -> Option<u8> {
const MAX_TIME: usize = AUDIO_FREQUENCY_22KHZ as usize * 3; // 3 seconds const MAX_TIME: usize = AUDIO_FREQUENCY_22KHZ as usize * 3; // 3 seconds
if self.t < MAX_TIME { if self.t < MAX_TIME {
let sample = (self.t as f64 * 0.25).sin() * 80.0; let sample = (self.t as f64 * 0.25).sin() * 80.0;
self.t += 1; self.t += 1;
Some((sample + 128.0) as u8) Some((sample + 128.0) as u8)
} else { } else {
None None
} }
} }
} }
fn main() -> Result<()> { fn main() -> Result<()> {
let mut system = SystemBuilder::new().window_title("Audio Playback").vsync(true).build()?; let mut system = SystemBuilder::new().window_title("Audio Playback").vsync(true).build()?;
let mut using_queue_commands = false; let mut using_queue_commands = false;
let mut volume = 1.0; let mut volume = 1.0;
let sounds = [ let sounds = [
load_and_convert_wav(Path::new("./assets/pickup-coin.wav"), system.audio.spec())?, load_and_convert_wav(Path::new("./assets/pickup-coin.wav"), system.audio.spec())?,
load_and_convert_wav(Path::new("./assets/powerup.wav"), system.audio.spec())?, load_and_convert_wav(Path::new("./assets/powerup.wav"), system.audio.spec())?,
load_and_convert_wav(Path::new("./assets/explosion.wav"), system.audio.spec())?, load_and_convert_wav(Path::new("./assets/explosion.wav"), system.audio.spec())?,
load_and_convert_wav(Path::new("./assets/jump.wav"), system.audio.spec())?, load_and_convert_wav(Path::new("./assets/jump.wav"), system.audio.spec())?,
load_and_convert_wav(Path::new("./assets/laser-shoot.wav"), system.audio.spec())?, load_and_convert_wav(Path::new("./assets/laser-shoot.wav"), system.audio.spec())?,
]; ];
let mut statuses = [AudioChannelStatus { size: 0, position: 0, playing: false }; NUM_CHANNELS]; let mut statuses = [AudioChannelStatus { size: 0, position: 0, playing: false }; NUM_CHANNELS];
while !system.do_events() { while !system.do_events() {
if system.input_devices.keyboard.is_key_pressed(Scancode::Escape) { if system.input_devices.keyboard.is_key_pressed(Scancode::Escape) {
break; break;
} }
let mut audio_device = system.audio.lock(); let mut audio_device = system.audio.lock();
audio_device.volume = volume; audio_device.volume = volume;
if system.input_devices.keyboard.is_key_pressed(Scancode::Num1) { if system.input_devices.keyboard.is_key_pressed(Scancode::Num1) {
if using_queue_commands { if using_queue_commands {
system.audio_queue.play_buffer(&sounds[0], false); system.audio_queue.play_buffer(&sounds[0], false);
} else { } else {
audio_device.play_buffer(&sounds[0], false)?; audio_device.play_buffer(&sounds[0], false)?;
} }
} }
if system.input_devices.keyboard.is_key_pressed(Scancode::Num2) { if system.input_devices.keyboard.is_key_pressed(Scancode::Num2) {
if using_queue_commands { if using_queue_commands {
system.audio_queue.play_buffer(&sounds[1], false); system.audio_queue.play_buffer(&sounds[1], false);
} else { } else {
audio_device.play_buffer(&sounds[1], false)?; audio_device.play_buffer(&sounds[1], false)?;
} }
} }
if system.input_devices.keyboard.is_key_pressed(Scancode::Num3) { if system.input_devices.keyboard.is_key_pressed(Scancode::Num3) {
if using_queue_commands { if using_queue_commands {
system.audio_queue.play_buffer(&sounds[2], false); system.audio_queue.play_buffer(&sounds[2], false);
} else {
audio_device.play_buffer(&sounds[2], false)?;
}
}
} else { if system.input_devices.keyboard.is_key_pressed(Scancode::Num4) {
audio_device.play_buffer(&sounds[2], false)?; if using_queue_commands {
} system.audio_queue.play_buffer(&sounds[3], false);
} } else {
audio_device.play_buffer(&sounds[3], false)?;
}
}
if system.input_devices.keyboard.is_key_pressed(Scancode::Num4) { if system.input_devices.keyboard.is_key_pressed(Scancode::Num5) {
if using_queue_commands { if using_queue_commands {
system.audio_queue.play_buffer(&sounds[3], false); system.audio_queue.play_buffer(&sounds[4], false);
} else {
audio_device.play_buffer(&sounds[4], false)?;
}
}
} else { if system.input_devices.keyboard.is_key_pressed(Scancode::Num6) {
audio_device.play_buffer(&sounds[3], false)?; if using_queue_commands {
} system.audio_queue.play_generator(Box::new(SineWaveGenerator::new()), false);
} } else {
audio_device.play_generator(Box::new(SineWaveGenerator::new()), false);
}
}
if system.input_devices.keyboard.is_key_pressed(Scancode::Num5) { if system.input_devices.keyboard.is_key_pressed(Scancode::Num7) {
if using_queue_commands { let index = rnd_value(0, sounds.len() - 1);
system.audio_queue.play_buffer(&sounds[4], false); if using_queue_commands {
} else { system.audio_queue.play_buffer_on_channel(7, &sounds[index], false)?;
audio_device.play_buffer(&sounds[4], false)?; } else {
} audio_device.play_buffer_on_channel(7, &sounds[index], false)?;
} }
}
if system.input_devices.keyboard.is_key_pressed(Scancode::Num6) { if system.input_devices.keyboard.is_key_pressed(Scancode::S) {
if using_queue_commands { if using_queue_commands {
system.audio_queue.play_generator(Box::new(SineWaveGenerator::new()), false); system.audio_queue.stop_all();
} else { } else {
audio_device.play_generator(Box::new(SineWaveGenerator::new()), false); audio_device.stop_all();
} }
} }
if system.input_devices.keyboard.is_key_pressed(Scancode::Num7) { system.audio_queue.apply_to_device(&mut audio_device)?;
let index = rnd_value(0, sounds.len() - 1);
if using_queue_commands {
system.audio_queue.play_buffer_on_channel(7, &sounds[index], false)?;
} else {
audio_device.play_buffer_on_channel(7, &sounds[index], false)?;
}
}
if system.input_devices.keyboard.is_key_pressed(Scancode::S) { if system.input_devices.keyboard.is_key_pressed(Scancode::KpMinus) {
if using_queue_commands { volume -= 0.1;
system.audio_queue.stop_all(); }
} else { if system.input_devices.keyboard.is_key_pressed(Scancode::KpPlus) {
audio_device.stop_all(); volume += 0.1;
} }
} if system.input_devices.keyboard.is_key_pressed(Scancode::Q) {
using_queue_commands = !using_queue_commands;
}
system.audio_queue.apply_to_device(&mut audio_device)?; for index in 0..NUM_CHANNELS {
let channel = &audio_device[index];
let mut status = &mut statuses[index];
status.playing = channel.playing;
status.position = channel.position;
status.size = channel.data.len();
}
if system.input_devices.keyboard.is_key_pressed(Scancode::KpMinus) { drop(audio_device);
volume -= 0.1;
}
if system.input_devices.keyboard.is_key_pressed(Scancode::KpPlus) {
volume += 0.1;
}
if system.input_devices.keyboard.is_key_pressed(Scancode::Q) {
using_queue_commands = !using_queue_commands;
}
for index in 0..NUM_CHANNELS { system.video.clear(0);
let channel = &audio_device[index];
let mut status = &mut statuses[index];
status.playing = channel.playing;
status.position = channel.position;
status.size = channel.data.len();
}
drop(audio_device); system.video.print_string(&format!("Volume: {:2.2}", volume), 16, 16, FontRenderOpts::Color(10), &system.font);
system.video.print_string(
if using_queue_commands {
"Queueing Commands"
} else {
"Direct Commands"
},
160, 16, FontRenderOpts::Color(9), &system.font,
);
system.video.clear(0); system.video.print_string("Audio Channels", 16, 32, FontRenderOpts::Color(14), &system.font);
system.video.print_string(&format!("Volume: {:2.2}", volume), 16, 16, FontRenderOpts::Color(10), &system.font); let mut y = 48;
system.video.print_string( for index in 0..NUM_CHANNELS {
if using_queue_commands { let status = &statuses[index];
"Queueing Commands" system.video.print_string(
} else { &format!(
"Direct Commands" "channel {} - {} {}",
}, index,
160, 16, FontRenderOpts::Color(9), &system.font if status.playing { "playing" } else { "not playing" },
); if status.playing { String::from(format!("{} / {}", status.position, status.size)) } else { String::new() }
),
16, y,
FontRenderOpts::Color(15),
&system.font,
);
y += 16;
}
system.video.print_string("Audio Channels", 16, 32, FontRenderOpts::Color(14), &system.font); system.display()?;
}
let mut y = 48; Ok(())
for index in 0..NUM_CHANNELS {
let status = &statuses[index];
system.video.print_string(
&format!(
"channel {} - {} {}",
index,
if status.playing { "playing" } else { "not playing" },
if status.playing { String::from(format!("{} / {}", status.position, status.size)) } else { String::new() }
),
16, y,
FontRenderOpts::Color(15),
&system.font
);
y += 16;
}
system.display()?;
}
Ok(())
} }

View file

@ -14,105 +14,105 @@ const BALL_WIDTH: u32 = 8;
const BALL_HEIGHT: u32 = 8; const BALL_HEIGHT: u32 = 8;
struct Ball { struct Ball {
x: i32, x: i32,
y: i32, y: i32,
dir_x: i32, dir_x: i32,
dir_y: i32, dir_y: i32,
sprite: usize, sprite: usize,
} }
fn main() -> Result<()> { fn main() -> Result<()> {
let mut system = SystemBuilder::new() let mut system = SystemBuilder::new()
.window_title("Flying Balls!") .window_title("Flying Balls!")
.vsync(true) .vsync(true)
.build()?; .build()?;
let font = BitmaskFont::new_vga_font()?; let font = BitmaskFont::new_vga_font()?;
let (balls_bmp, balls_palette) = Bitmap::load_pcx_file(Path::new("./assets/balls.pcx"))?; let (balls_bmp, balls_palette) = Bitmap::load_pcx_file(Path::new("./assets/balls.pcx"))?;
system.palette = balls_palette.clone(); system.palette = balls_palette.clone();
let mut sprites = Vec::<Bitmap>::new(); let mut sprites = Vec::<Bitmap>::new();
let mut balls = Vec::<Ball>::new(); let mut balls = Vec::<Ball>::new();
for i in 0..NUM_BALL_SPRITES { for i in 0..NUM_BALL_SPRITES {
let mut sprite = Bitmap::new(BALL_WIDTH, BALL_HEIGHT)?; let mut sprite = Bitmap::new(BALL_WIDTH, BALL_HEIGHT)?;
sprite.blit_region( sprite.blit_region(
BlitMethod::Solid, BlitMethod::Solid,
&balls_bmp, &balls_bmp,
&Rect::new(i as i32 * BALL_WIDTH as i32, 0, BALL_WIDTH, BALL_HEIGHT), &Rect::new(i as i32 * BALL_WIDTH as i32, 0, BALL_WIDTH, BALL_HEIGHT),
0, 0,
0, 0,
); );
sprites.push(sprite); sprites.push(sprite);
} }
for _ in 0..NUM_BALLS { for _ in 0..NUM_BALLS {
let speed = rnd_value(1, 3); let speed = rnd_value(1, 3);
let ball = Ball { let ball = Ball {
x: rnd_value(0, SCREEN_WIDTH as i32 - 1), x: rnd_value(0, SCREEN_WIDTH as i32 - 1),
y: rnd_value(0, SCREEN_HEIGHT as i32 - 1), y: rnd_value(0, SCREEN_HEIGHT as i32 - 1),
dir_x: if rnd_value(0, 1) == 0 { -speed } else { speed }, dir_x: if rnd_value(0, 1) == 0 { -speed } else { speed },
dir_y: if rnd_value(0, 1) == 0 { -speed } else { speed }, dir_y: if rnd_value(0, 1) == 0 { -speed } else { speed },
sprite: rnd_value(0, NUM_BALL_SPRITES - 1), sprite: rnd_value(0, NUM_BALL_SPRITES - 1),
}; };
balls.push(ball); balls.push(ball);
} }
while !system.do_events() { while !system.do_events() {
if system.input_devices.keyboard.is_key_pressed(Scancode::Escape) { if system.input_devices.keyboard.is_key_pressed(Scancode::Escape) {
break; break;
} }
if system.input_devices.keyboard.is_key_up(Scancode::S) { if system.input_devices.keyboard.is_key_up(Scancode::S) {
for i in 0..NUM_BALLS { for i in 0..NUM_BALLS {
let ball = &mut balls[i]; let ball = &mut balls[i];
ball.x += ball.dir_x; ball.x += ball.dir_x;
ball.y += ball.dir_y; ball.y += ball.dir_y;
if ball.dir_x < 0 { if ball.dir_x < 0 {
if ball.x <= 0 { if ball.x <= 0 {
ball.dir_x = -ball.dir_x; ball.dir_x = -ball.dir_x;
ball.x = 0; ball.x = 0;
} }
} else { } else {
if ball.x >= (SCREEN_WIDTH - BALL_WIDTH) as i32 { if ball.x >= (SCREEN_WIDTH - BALL_WIDTH) as i32 {
ball.dir_x = -ball.dir_x; ball.dir_x = -ball.dir_x;
ball.x = (SCREEN_WIDTH - BALL_WIDTH) as i32; ball.x = (SCREEN_WIDTH - BALL_WIDTH) as i32;
} }
} }
if ball.dir_y < 0 { if ball.dir_y < 0 {
if ball.y <= 0 { if ball.y <= 0 {
ball.dir_y = -ball.dir_y; ball.dir_y = -ball.dir_y;
ball.y = 0; ball.y = 0;
} }
} else { } else {
if ball.y >= (SCREEN_HEIGHT - BALL_HEIGHT) as i32 { if ball.y >= (SCREEN_HEIGHT - BALL_HEIGHT) as i32 {
ball.dir_y = -ball.dir_y; ball.dir_y = -ball.dir_y;
ball.y = (SCREEN_HEIGHT - BALL_HEIGHT) as i32; ball.y = (SCREEN_HEIGHT - BALL_HEIGHT) as i32;
} }
} }
} }
} }
system.video.clear(2); system.video.clear(2);
system system
.video .video
.print_string("hello, world!", 10, 10, FontRenderOpts::Color(15), &font); .print_string("hello, world!", 10, 10, FontRenderOpts::Color(15), &font);
for i in 0..NUM_BALLS { for i in 0..NUM_BALLS {
system.video.blit( system.video.blit(
BlitMethod::Transparent(0), BlitMethod::Transparent(0),
&sprites[balls[i].sprite], &sprites[balls[i].sprite],
balls[i].x, balls[i].x,
balls[i].y, balls[i].y,
); );
} }
system.display()?; system.display()?;
} }
Ok(()) Ok(())
} }

View file

@ -27,236 +27,236 @@ pub struct Particle;
pub struct Color(u8); pub struct Color(u8);
pub struct LifeLeft { pub struct LifeLeft {
pub life: f32, pub life: f32,
pub initial: f32, pub initial: f32,
} }
pub struct LeavesTrail { pub struct LeavesTrail {
pub timer: f32, pub timer: f32,
} }
pub struct ColorByLifeTime(u8, u8, u8, u8, u8); pub struct ColorByLifeTime(u8, u8, u8, u8, u8);
pub enum Event { pub enum Event {
CollideAgainstEdge(EntityId), CollideAgainstEdge(EntityId),
Kill(EntityId), Kill(EntityId),
LeaveTrail(Vector2), LeaveTrail(Vector2),
} }
fn new_basic_particle_entity(entities: &mut Entities, x: f32, y: f32, color: u8, lifetime: f32, angle: f32, speed: i32) { fn new_basic_particle_entity(entities: &mut Entities, x: f32, y: f32, color: u8, lifetime: f32, angle: f32, speed: i32) {
let id = entities.new_entity(); let id = entities.new_entity();
entities.add_component(id, Particle); entities.add_component(id, Particle);
entities.add_component(id, Color(color)); entities.add_component(id, Color(color));
entities.add_component(id, LifeLeft { life: lifetime, initial: lifetime }); entities.add_component(id, LifeLeft { life: lifetime, initial: lifetime });
entities.add_component(id, Position(Vector2::new(x, y))); entities.add_component(id, Position(Vector2::new(x, y)));
entities.add_component(id, Velocity(Vector2::from_angle(angle) * speed as f32)); entities.add_component(id, Velocity(Vector2::from_angle(angle) * speed as f32));
} }
fn new_trail_particle_entity(entities: &mut Entities, x: f32, y: f32, lifetime: f32) { fn new_trail_particle_entity(entities: &mut Entities, x: f32, y: f32, lifetime: f32) {
let id = entities.new_entity(); let id = entities.new_entity();
entities.add_component(id, Particle); entities.add_component(id, Particle);
entities.add_component(id, ColorByLifeTime(33, 26, 21, 16, 10)); entities.add_component(id, ColorByLifeTime(33, 26, 21, 16, 10));
entities.add_component(id, LifeLeft { life: lifetime, initial: lifetime }); entities.add_component(id, LifeLeft { life: lifetime, initial: lifetime });
entities.add_component(id, Position(Vector2::new(x, y))); entities.add_component(id, Position(Vector2::new(x, y)));
} }
fn new_bounce_particles(entities: &mut Entities, x: f32, y: f32) { fn new_bounce_particles(entities: &mut Entities, x: f32, y: f32) {
for direction in 0..6 { for direction in 0..6 {
let angle = direction as f32 * (RADIANS_360 / 6.0); let angle = direction as f32 * (RADIANS_360 / 6.0);
new_basic_particle_entity( new_basic_particle_entity(
entities, entities,
x, x,
y, y,
BOUNCE_PARTICLE_COLOR, BOUNCE_PARTICLE_COLOR,
BOUNCE_PARTICLE_LIFETIME, BOUNCE_PARTICLE_LIFETIME,
angle, angle,
BOUNCE_PARTICLE_SPEED BOUNCE_PARTICLE_SPEED,
); );
} }
} }
fn new_ball_entity(entities: &mut Entities) { fn new_ball_entity(entities: &mut Entities) {
let id = entities.new_entity(); let id = entities.new_entity();
let x: i32 = rnd_value(SCREEN_LEFT as i32 + 1, SCREEN_RIGHT as i32 - BALL_SIZE - 1); let x: i32 = rnd_value(SCREEN_LEFT as i32 + 1, SCREEN_RIGHT as i32 - BALL_SIZE - 1);
let y: i32 = rnd_value(SCREEN_TOP as i32 + 1, SCREEN_BOTTOM as i32 - BALL_SIZE - 1); let y: i32 = rnd_value(SCREEN_TOP as i32 + 1, SCREEN_BOTTOM as i32 - BALL_SIZE - 1);
let speed = rnd_value(1, 3) * BALL_BASE_SPEED; let speed = rnd_value(1, 3) * BALL_BASE_SPEED;
let vx = if rnd_value(0, 1) == 0 { -speed } else { speed }; let vx = if rnd_value(0, 1) == 0 { -speed } else { speed };
let vy = if rnd_value(0, 1) == 0 { -speed } else { speed }; let vy = if rnd_value(0, 1) == 0 { -speed } else { speed };
let sprite_index = rnd_value(0, NUM_BALL_SPRITES - 1); let sprite_index = rnd_value(0, NUM_BALL_SPRITES - 1);
entities.add_component(id, Position(Vector2::new(x as f32, y as f32))); entities.add_component(id, Position(Vector2::new(x as f32, y as f32)));
entities.add_component(id, Velocity(Vector2::new(vx as f32, vy as f32))); entities.add_component(id, Velocity(Vector2::new(vx as f32, vy as f32)));
entities.add_component(id, SpriteIndex(sprite_index)); entities.add_component(id, SpriteIndex(sprite_index));
entities.add_component(id, BouncesAgainstEdge); entities.add_component(id, BouncesAgainstEdge);
entities.add_component(id, LeavesTrail { timer: BALL_TRAIL_PARTICLE_INTERVAL }); entities.add_component(id, LeavesTrail { timer: BALL_TRAIL_PARTICLE_INTERVAL });
} }
fn update_system_movement(context: &mut Context) { fn update_system_movement(context: &mut Context) {
let mut positions = context.entities.components_mut::<Position>().unwrap(); let mut positions = context.entities.components_mut::<Position>().unwrap();
let velocities = context.entities.components::<Velocity>(); let velocities = context.entities.components::<Velocity>();
for (entity, position) in positions.iter_mut() { for (entity, position) in positions.iter_mut() {
if let Some(velocity) = velocities.get(&entity) { if let Some(velocity) = velocities.get(&entity) {
position.0 += velocity.0 * context.delta; position.0 += velocity.0 * context.delta;
} }
} }
} }
fn update_system_collision(context: &mut Context) { fn update_system_collision(context: &mut Context) {
let bounceables = context.entities.components::<BouncesAgainstEdge>().unwrap(); let bounceables = context.entities.components::<BouncesAgainstEdge>().unwrap();
let mut positions = context.entities.components_mut::<Position>(); let mut positions = context.entities.components_mut::<Position>();
let mut velocities = context.entities.components_mut::<Velocity>(); let mut velocities = context.entities.components_mut::<Velocity>();
for (entity, _) in bounceables.iter() { for (entity, _) in bounceables.iter() {
let mut position = positions.get_mut(&entity).unwrap(); let mut position = positions.get_mut(&entity).unwrap();
let mut velocity = velocities.get_mut(&entity).unwrap(); let mut velocity = velocities.get_mut(&entity).unwrap();
let mut bounced = false; let mut bounced = false;
if position.0.x as i32 <= SCREEN_LEFT as i32 || position.0.x as i32 + BALL_SIZE >= SCREEN_RIGHT as i32 { if position.0.x as i32 <= SCREEN_LEFT as i32 || position.0.x as i32 + BALL_SIZE >= SCREEN_RIGHT as i32 {
position.0.x -= velocity.0.x * context.delta; position.0.x -= velocity.0.x * context.delta;
velocity.0.x = -velocity.0.x; velocity.0.x = -velocity.0.x;
bounced = true; bounced = true;
} }
if position.0.y as i32 <= SCREEN_TOP as i32 || position.0.y as i32 + BALL_SIZE >= SCREEN_BOTTOM as i32 { if position.0.y as i32 <= SCREEN_TOP as i32 || position.0.y as i32 + BALL_SIZE >= SCREEN_BOTTOM as i32 {
position.0.y -= velocity.0.y * context.delta; position.0.y -= velocity.0.y * context.delta;
velocity.0.y = -velocity.0.y; velocity.0.y = -velocity.0.y;
bounced = true; bounced = true;
} }
if bounced { if bounced {
context.event_publisher.queue(Event::CollideAgainstEdge(*entity)); context.event_publisher.queue(Event::CollideAgainstEdge(*entity));
} }
} }
} }
fn update_system_lifetime(context: &mut Context) { fn update_system_lifetime(context: &mut Context) {
let mut lifetimes = context.entities.components_mut::<LifeLeft>().unwrap(); let mut lifetimes = context.entities.components_mut::<LifeLeft>().unwrap();
for (entity, lifetime) in lifetimes.iter_mut() { for (entity, lifetime) in lifetimes.iter_mut() {
lifetime.life -= context.delta; lifetime.life -= context.delta;
if lifetime.life < 0.0 { if lifetime.life < 0.0 {
context.event_publisher.queue(Event::Kill(*entity)); context.event_publisher.queue(Event::Kill(*entity));
} }
} }
} }
fn update_system_leave_particle_trail(context: &mut Context) { fn update_system_leave_particle_trail(context: &mut Context) {
let mut leaves_trails = context.entities.components_mut::<LeavesTrail>().unwrap(); let mut leaves_trails = context.entities.components_mut::<LeavesTrail>().unwrap();
let positions = context.entities.components::<Position>(); let positions = context.entities.components::<Position>();
for (entity, leaves_trail) in leaves_trails.iter_mut() { for (entity, leaves_trail) in leaves_trails.iter_mut() {
leaves_trail.timer -= context.delta; leaves_trail.timer -= context.delta;
if leaves_trail.timer <= 0.0 { if leaves_trail.timer <= 0.0 {
leaves_trail.timer = BALL_TRAIL_PARTICLE_INTERVAL; leaves_trail.timer = BALL_TRAIL_PARTICLE_INTERVAL;
let position = positions.get(&entity).unwrap(); let position = positions.get(&entity).unwrap();
let mut trail_position = position.0; let mut trail_position = position.0;
trail_position.x += (BALL_SIZE / 2) as f32; trail_position.x += (BALL_SIZE / 2) as f32;
trail_position.y += (BALL_SIZE / 2) as f32; trail_position.y += (BALL_SIZE / 2) as f32;
context.event_publisher.queue(Event::LeaveTrail(trail_position)); context.event_publisher.queue(Event::LeaveTrail(trail_position));
} }
} }
} }
fn render_system_sprites(context: &mut Context) { fn render_system_sprites(context: &mut Context) {
let sprite_indices = context.entities.components::<SpriteIndex>().unwrap(); let sprite_indices = context.entities.components::<SpriteIndex>().unwrap();
let positions = context.entities.components::<Position>(); let positions = context.entities.components::<Position>();
for (entity, sprite_index) in sprite_indices.iter() { for (entity, sprite_index) in sprite_indices.iter() {
let position = positions.get(&entity).unwrap(); let position = positions.get(&entity).unwrap();
context.system.video.blit( context.system.video.blit(
BlitMethod::Transparent(0), BlitMethod::Transparent(0),
&context.sprites[sprite_index.0], &context.sprites[sprite_index.0],
position.0.x as i32, position.0.x as i32,
position.0.y as i32 position.0.y as i32,
); );
} }
} }
fn render_system_particles(context: &mut Context) { fn render_system_particles(context: &mut Context) {
let particles = context.entities.components::<Particle>().unwrap(); let particles = context.entities.components::<Particle>().unwrap();
let positions = context.entities.components::<Position>(); let positions = context.entities.components::<Position>();
let colors = context.entities.components::<Color>(); let colors = context.entities.components::<Color>();
let colors_by_lifetime = context.entities.components::<ColorByLifeTime>(); let colors_by_lifetime = context.entities.components::<ColorByLifeTime>();
let lifetimes = context.entities.components::<LifeLeft>(); let lifetimes = context.entities.components::<LifeLeft>();
for (entity, _) in particles.iter() { for (entity, _) in particles.iter() {
let position = positions.get(&entity).unwrap(); let position = positions.get(&entity).unwrap();
let pixel_color; let pixel_color;
if let Some(color) = colors.get(&entity) { if let Some(color) = colors.get(&entity) {
pixel_color = Some(color.0); pixel_color = Some(color.0);
} else if let Some(color_by_lifetime) = colors_by_lifetime.get(&entity) { } else if let Some(color_by_lifetime) = colors_by_lifetime.get(&entity) {
let lifetime = lifetimes.get(&entity).unwrap(); let lifetime = lifetimes.get(&entity).unwrap();
let percent_life = lifetime.life / lifetime.initial; let percent_life = lifetime.life / lifetime.initial;
pixel_color = Some(if percent_life >= 0.8 { pixel_color = Some(if percent_life >= 0.8 {
color_by_lifetime.0 color_by_lifetime.0
} else if percent_life >= 0.6 { } else if percent_life >= 0.6 {
color_by_lifetime.1 color_by_lifetime.1
} else if percent_life >= 0.4 { } else if percent_life >= 0.4 {
color_by_lifetime.2 color_by_lifetime.2
} else if percent_life >= 0.2 { } else if percent_life >= 0.2 {
color_by_lifetime.3 color_by_lifetime.3
} else { } else {
color_by_lifetime.4 color_by_lifetime.4
}); });
} else { } else {
pixel_color = None; pixel_color = None;
} }
if let Some(color) = pixel_color { if let Some(color) = pixel_color {
context.system.video.set_pixel(position.0.x as i32, position.0.y as i32, color); context.system.video.set_pixel(position.0.x as i32, position.0.y as i32, color);
} }
} }
} }
fn event_handler(event: &Event, context: &mut Context) -> bool { fn event_handler(event: &Event, context: &mut Context) -> bool {
match event { match event {
Event::Kill(entity) => { Event::Kill(entity) => {
context.entities.remove_entity(*entity); context.entities.remove_entity(*entity);
}, }
Event::CollideAgainstEdge(entity) => { Event::CollideAgainstEdge(entity) => {
let positions = context.entities.components::<Position>(); let positions = context.entities.components::<Position>();
let position = positions.get(entity).unwrap(); let position = positions.get(entity).unwrap();
let x = position.0.x + (BALL_SIZE / 2) as f32; let x = position.0.x + (BALL_SIZE / 2) as f32;
let y = position.0.y + (BALL_SIZE / 2) as f32; let y = position.0.y + (BALL_SIZE / 2) as f32;
drop(positions); drop(positions);
new_bounce_particles(&mut context.entities, x, y); new_bounce_particles(&mut context.entities, x, y);
}, }
Event::LeaveTrail(position) => { Event::LeaveTrail(position) => {
new_trail_particle_entity(&mut context.entities, position.x, position.y, TRAIL_PARTICLE_LIFETIME); new_trail_particle_entity(&mut context.entities, position.x, position.y, TRAIL_PARTICLE_LIFETIME);
} }
} }
false false
} }
pub fn init_entities(entities: &mut Entities) { pub fn init_entities(entities: &mut Entities) {
entities.init_components::<Position>(); entities.init_components::<Position>();
entities.init_components::<Velocity>(); entities.init_components::<Velocity>();
entities.init_components::<SpriteIndex>(); entities.init_components::<SpriteIndex>();
entities.init_components::<BouncesAgainstEdge>(); entities.init_components::<BouncesAgainstEdge>();
entities.init_components::<Particle>(); entities.init_components::<Particle>();
entities.init_components::<Color>(); entities.init_components::<Color>();
entities.init_components::<LifeLeft>(); entities.init_components::<LifeLeft>();
entities.init_components::<LeavesTrail>(); entities.init_components::<LeavesTrail>();
entities.init_components::<ColorByLifeTime>(); entities.init_components::<ColorByLifeTime>();
entities.remove_all_entities(); entities.remove_all_entities();
for _ in 0..NUM_BALLS { for _ in 0..NUM_BALLS {
new_ball_entity(entities); new_ball_entity(entities);
} }
} }
pub fn init_component_system(cs: &mut ComponentSystems<Context, Context>) { pub fn init_component_system(cs: &mut ComponentSystems<Context, Context>) {
cs.add_update_system(update_system_movement); cs.add_update_system(update_system_movement);
cs.add_update_system(update_system_collision); cs.add_update_system(update_system_collision);
cs.add_update_system(update_system_lifetime); cs.add_update_system(update_system_lifetime);
cs.add_update_system(update_system_leave_particle_trail); cs.add_update_system(update_system_leave_particle_trail);
cs.add_render_system(render_system_particles); cs.add_render_system(render_system_particles);
cs.add_render_system(render_system_sprites); cs.add_render_system(render_system_sprites);
} }
pub fn init_event_listeners(event_listeners: &mut EventListeners<Event, Context>) { pub fn init_event_listeners(event_listeners: &mut EventListeners<Event, Context>) {
event_listeners.add(event_handler); event_listeners.add(event_handler);
} }

View file

@ -12,27 +12,27 @@ mod entities;
mod states; mod states;
fn main() -> Result<()> { fn main() -> Result<()> {
let system = SystemBuilder::new().window_title("Flying Balls").vsync(true).build()?; let system = SystemBuilder::new().window_title("Flying Balls").vsync(true).build()?;
let mut game = Game::new(system)?; let mut game = Game::new(system)?;
let mut states = States::new(); let mut states = States::new();
states.push(SimulationState)?; states.push(SimulationState)?;
let tick_frequency = game.context.system.tick_frequency(); let tick_frequency = game.context.system.tick_frequency();
let mut last_ticks = game.context.system.ticks(); let mut last_ticks = game.context.system.ticks();
while !game.context.system.do_events() && !states.is_empty() { while !game.context.system.do_events() && !states.is_empty() {
let ticks = game.context.system.ticks(); let ticks = game.context.system.ticks();
let elapsed = ticks - last_ticks; let elapsed = ticks - last_ticks;
last_ticks = ticks; last_ticks = ticks;
game.context.delta = (elapsed as f64 / tick_frequency as f64) as f32; game.context.delta = (elapsed as f64 / tick_frequency as f64) as f32;
states.update(&mut game)?; states.update(&mut game)?;
game.context.system.video.clear(0); game.context.system.video.clear(0);
states.render(&mut game); states.render(&mut game);
game.context.system.display()?; game.context.system.display()?;
} }
Ok(()) Ok(())
} }

View file

@ -12,97 +12,97 @@ pub const NUM_BALLS: usize = 32;
pub const NUM_BALL_SPRITES: usize = 16; pub const NUM_BALL_SPRITES: usize = 16;
pub struct Context { pub struct Context {
pub delta: f32, pub delta: f32,
pub system: System, pub system: System,
pub font: BitmaskFont, pub font: BitmaskFont,
pub sprites: Vec<Bitmap>, pub sprites: Vec<Bitmap>,
pub entities: Entities, pub entities: Entities,
pub event_publisher: EventPublisher<Event>, pub event_publisher: EventPublisher<Event>,
} }
pub struct Game { pub struct Game {
pub context: Context, pub context: Context,
pub component_systems: ComponentSystems<Context, Context>, pub component_systems: ComponentSystems<Context, Context>,
pub event_listeners: EventListeners<Event, Context>, pub event_listeners: EventListeners<Event, Context>,
} }
impl Game { impl Game {
pub fn new(mut system: System) -> Result<Self> { pub fn new(mut system: System) -> Result<Self> {
let font = BitmaskFont::new_vga_font()?; let font = BitmaskFont::new_vga_font()?;
let (balls_bmp, balls_palette) = Bitmap::load_pcx_file(Path::new("./assets/balls.pcx"))?; let (balls_bmp, balls_palette) = Bitmap::load_pcx_file(Path::new("./assets/balls.pcx"))?;
system.palette = balls_palette.clone(); system.palette = balls_palette.clone();
let mut sprites = Vec::new(); let mut sprites = Vec::new();
for i in 0..NUM_BALL_SPRITES { for i in 0..NUM_BALL_SPRITES {
let mut sprite = Bitmap::new(BALL_SIZE as u32, BALL_SIZE as u32)?; let mut sprite = Bitmap::new(BALL_SIZE as u32, BALL_SIZE as u32)?;
sprite.blit_region( sprite.blit_region(
BlitMethod::Solid, BlitMethod::Solid,
&balls_bmp, &balls_bmp,
&Rect::new(i as i32 * BALL_SIZE as i32, 0, BALL_SIZE as u32, BALL_SIZE as u32), &Rect::new(i as i32 * BALL_SIZE as i32, 0, BALL_SIZE as u32, BALL_SIZE as u32),
0, 0,
0 0,
); );
sprites.push(sprite); sprites.push(sprite);
} }
let entities = Entities::new(); let entities = Entities::new();
let mut component_systems = ComponentSystems::new(); let mut component_systems = ComponentSystems::new();
let event_publisher = EventPublisher::new(); let event_publisher = EventPublisher::new();
let mut event_listeners = EventListeners::new(); let mut event_listeners = EventListeners::new();
init_component_system(&mut component_systems); init_component_system(&mut component_systems);
init_event_listeners(&mut event_listeners); init_event_listeners(&mut event_listeners);
Ok(Game { Ok(Game {
context: Context { context: Context {
delta: 0.0, delta: 0.0,
system, system,
font, font,
sprites, sprites,
entities, entities,
event_publisher event_publisher,
}, },
component_systems, component_systems,
event_listeners event_listeners,
}) })
} }
pub fn do_events(&mut self) { pub fn do_events(&mut self) {
self.event_listeners.take_queue_from(&mut self.context.event_publisher); self.event_listeners.take_queue_from(&mut self.context.event_publisher);
self.event_listeners.dispatch_queue(&mut self.context); self.event_listeners.dispatch_queue(&mut self.context);
} }
} }
pub struct SimulationState; pub struct SimulationState;
impl AppState<Game> for SimulationState { impl AppState<Game> for SimulationState {
fn update(&mut self, _state: State, context: &mut Game) -> Option<StateChange<Game>> { fn update(&mut self, _state: State, context: &mut Game) -> Option<StateChange<Game>> {
if context.context.system.input_devices.keyboard.is_key_up(Scancode::S) { if context.context.system.input_devices.keyboard.is_key_up(Scancode::S) {
context.do_events(); context.do_events();
context.component_systems.update(&mut context.context); context.component_systems.update(&mut context.context);
} }
if context.context.system.input_devices.keyboard.is_key_pressed(Scancode::Escape) { if context.context.system.input_devices.keyboard.is_key_pressed(Scancode::Escape) {
return Some(StateChange::Pop(1)); return Some(StateChange::Pop(1));
} }
None None
} }
fn render(&mut self, _state: State, context: &mut Game) { fn render(&mut self, _state: State, context: &mut Game) {
context.context.system.video.clear(2); context.context.system.video.clear(2);
context.component_systems.render(&mut context.context); context.component_systems.render(&mut context.context);
context.context.system.video.print_string("hello, world!", 10, 10, FontRenderOpts::Color(15), &context.context.font); context.context.system.video.print_string("hello, world!", 10, 10, FontRenderOpts::Color(15), &context.context.font);
} }
fn transition(&mut self, _state: State, _context: &mut Game) -> bool { fn transition(&mut self, _state: State, _context: &mut Game) -> bool {
true true
} }
fn state_change(&mut self, new_state: State, _old_state: State, context: &mut Game) { fn state_change(&mut self, new_state: State, _old_state: State, context: &mut Game) {
if new_state == State::Pending { if new_state == State::Pending {
init_entities(&mut context.context.entities); init_entities(&mut context.context.entities);
} }
} }
} }

View file

@ -6,88 +6,88 @@ use crate::entities::*;
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub enum Event { pub enum Event {
TurnAndMove(EntityId, Direction), TurnAndMove(EntityId, Direction),
MoveForward(EntityId), MoveForward(EntityId),
Remove(EntityId), Remove(EntityId),
RemoveAttachment(EntityId), RemoveAttachment(EntityId),
AnimationFinished(EntityId), AnimationFinished(EntityId),
Spawn(EntityId), Spawn(EntityId),
SpawnSlimeRandomly, SpawnSlimeRandomly,
SetActivity(EntityId, EntityActivity), SetActivity(EntityId, EntityActivity),
Attack(EntityId), Attack(EntityId),
Hit(EntityId, EntityId, i32, Vector2), Hit(EntityId, EntityId, i32, Vector2),
Kill(EntityId), Kill(EntityId),
Pickup(EntityId, EntityId), Pickup(EntityId, EntityId),
} }
fn event_handler(event: &Event, context: &mut Core) -> bool { fn event_handler(event: &Event, context: &mut Core) -> bool {
match event { match event {
Event::Remove(entity) => { Event::Remove(entity) => {
if context.entities.has_entity(*entity) { if context.entities.has_entity(*entity) {
remove_entity(&mut context.entities, *entity); remove_entity(&mut context.entities, *entity);
} }
}, }
Event::RemoveAttachment(entity) => { Event::RemoveAttachment(entity) => {
if context.entities.has_entity(*entity) { if context.entities.has_entity(*entity) {
remove_entity_attachment(&mut context.entities, *entity); remove_entity_attachment(&mut context.entities, *entity);
} }
} }
Event::TurnAndMove(entity, direction) => { Event::TurnAndMove(entity, direction) => {
if context.entities.has_entity(*entity) { if context.entities.has_entity(*entity) {
turn_and_move_entity(context, *entity, *direction); turn_and_move_entity(context, *entity, *direction);
} }
}, }
Event::MoveForward(entity) => { Event::MoveForward(entity) => {
if context.entities.has_entity(*entity) { if context.entities.has_entity(*entity) {
move_entity_forward(context, *entity); move_entity_forward(context, *entity);
} }
}, }
Event::Spawn(entity) => { Event::Spawn(entity) => {
// todo // todo
}, }
Event::AnimationFinished(entity) => { Event::AnimationFinished(entity) => {
if context.entities.has_entity(*entity) { if context.entities.has_entity(*entity) {
// if the entity's 'attack' animation just finished, move them back to 'idle' // if the entity's 'attack' animation just finished, move them back to 'idle'
let activities = context.entities.components::<Activity>(); let activities = context.entities.components::<Activity>();
if let Some(activity) = activities.get(entity) { if let Some(activity) = activities.get(entity) {
if activity.0 == EntityActivity::Attacking { if activity.0 == EntityActivity::Attacking {
drop(activities); drop(activities);
stop_attack(context, *entity); stop_attack(context, *entity);
} }
} }
} }
} }
Event::SpawnSlimeRandomly => { Event::SpawnSlimeRandomly => {
spawn_slime_randomly(context); spawn_slime_randomly(context);
}, }
Event::SetActivity(entity, activity) => { Event::SetActivity(entity, activity) => {
if context.entities.has_entity(*entity) { if context.entities.has_entity(*entity) {
set_entity_activity(&mut context.entities, *entity, *activity); set_entity_activity(&mut context.entities, *entity, *activity);
} }
}, }
Event::Attack(entity) => { Event::Attack(entity) => {
if context.entities.has_entity(*entity) { if context.entities.has_entity(*entity) {
attack(context, *entity); attack(context, *entity);
} }
}, }
Event::Hit(target, source, damage, damage_position) => { Event::Hit(target, source, damage, damage_position) => {
if context.entities.has_entity(*target) { if context.entities.has_entity(*target) {
hit_entity(context, *target, *source, *damage, *damage_position); hit_entity(context, *target, *source, *damage, *damage_position);
} }
}, }
Event::Kill(entity) => { Event::Kill(entity) => {
kill_entity(context, *entity); kill_entity(context, *entity);
}, }
Event::Pickup(picked_up_by, picked_up) => { Event::Pickup(picked_up_by, picked_up) => {
if context.entities.has_entity(*picked_up_by) && context.entities.has_entity(*picked_up) { if context.entities.has_entity(*picked_up_by) && context.entities.has_entity(*picked_up) {
pickup(context, *picked_up_by, *picked_up); pickup(context, *picked_up_by, *picked_up);
} }
} }
} }
false false
} }
pub fn init_events(event_listener: &mut EventListeners<Event, Core>) { pub fn init_events(event_listener: &mut EventListeners<Event, Core>) {
event_listener.clear(); event_listener.clear();
event_listener.add(event_handler); event_listener.add(event_handler);
} }

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -27,176 +27,176 @@ pub const TILE_WIDTH: u32 = 16;
pub const TILE_HEIGHT: u32 = 16; pub const TILE_HEIGHT: u32 = 16;
pub struct Core { pub struct Core {
pub delta: f32, pub delta: f32,
pub system: System, pub system: System,
pub font: BitmaskFont, pub font: BitmaskFont,
pub entities: Entities, pub entities: Entities,
pub event_publisher: EventPublisher<Event>, pub event_publisher: EventPublisher<Event>,
pub palette: Palette, pub palette: Palette,
pub fade_out_palette: Palette, pub fade_out_palette: Palette,
pub tiles: Rc<BitmapAtlas>, pub tiles: Rc<BitmapAtlas>,
pub hero_male: Rc<BitmapAtlas>, pub hero_male: Rc<BitmapAtlas>,
pub hero_female: Rc<BitmapAtlas>, pub hero_female: Rc<BitmapAtlas>,
pub green_slime: Rc<BitmapAtlas>, pub green_slime: Rc<BitmapAtlas>,
pub blue_slime: Rc<BitmapAtlas>, pub blue_slime: Rc<BitmapAtlas>,
pub orange_slime: Rc<BitmapAtlas>, pub orange_slime: Rc<BitmapAtlas>,
pub fist: Rc<BitmapAtlas>, pub fist: Rc<BitmapAtlas>,
pub sword: Rc<BitmapAtlas>, pub sword: Rc<BitmapAtlas>,
pub particles: Rc<BitmapAtlas>, pub particles: Rc<BitmapAtlas>,
pub items: Rc<BitmapAtlas>, pub items: Rc<BitmapAtlas>,
pub ui: Rc<BitmapAtlas>, pub ui: Rc<BitmapAtlas>,
pub tilemap: TileMap, pub tilemap: TileMap,
pub slime_activity_states: Rc<HashMap<EntityActivity, Rc<AnimationDef>>>, pub slime_activity_states: Rc<HashMap<EntityActivity, Rc<AnimationDef>>>,
pub hero_activity_states: Rc<HashMap<EntityActivity, Rc<AnimationDef>>>, pub hero_activity_states: Rc<HashMap<EntityActivity, Rc<AnimationDef>>>,
pub poof1_animation_def: Rc<AnimationDef>, pub poof1_animation_def: Rc<AnimationDef>,
pub poof2_animation_def: Rc<AnimationDef>, pub poof2_animation_def: Rc<AnimationDef>,
pub sparkles_animation_def: Rc<AnimationDef>, pub sparkles_animation_def: Rc<AnimationDef>,
pub sprite_render_list: Vec<(EntityId, Vector2, BlitMethod)>, pub sprite_render_list: Vec<(EntityId, Vector2, BlitMethod)>,
} }
impl CoreState for Core { impl CoreState for Core {
fn system(&self) -> &System { fn system(&self) -> &System {
&self.system &self.system
} }
fn system_mut(&mut self) -> &mut System { fn system_mut(&mut self) -> &mut System {
&mut self.system &mut self.system
} }
fn delta(&self) -> f32 { fn delta(&self) -> f32 {
self.delta self.delta
} }
fn set_delta(&mut self, delta: f32) { fn set_delta(&mut self, delta: f32) {
self.delta = delta; self.delta = delta;
} }
} }
impl CoreStateWithEvents<Event> for Core { impl CoreStateWithEvents<Event> for Core {
fn event_publisher(&mut self) -> &mut EventPublisher<Event> { fn event_publisher(&mut self) -> &mut EventPublisher<Event> {
&mut self.event_publisher &mut self.event_publisher
} }
} }
pub struct Support { pub struct Support {
pub component_systems: ComponentSystems<Core, Core>, pub component_systems: ComponentSystems<Core, Core>,
pub event_listeners: EventListeners<Event, Core>, pub event_listeners: EventListeners<Event, Core>,
} }
impl SupportSystems for Support {} impl SupportSystems for Support {}
impl SupportSystemsWithEvents<Event> for Support { impl SupportSystemsWithEvents<Event> for Support {
type ContextType = Core; type ContextType = Core;
fn event_listeners(&mut self) -> &mut EventListeners<Event, Self::ContextType> { fn event_listeners(&mut self) -> &mut EventListeners<Event, Self::ContextType> {
&mut self.event_listeners &mut self.event_listeners
} }
} }
pub struct Game { pub struct Game {
pub core: Core, pub core: Core,
pub support: Support, pub support: Support,
} }
impl AppContext for Game { impl AppContext for Game {
type CoreType = Core; type CoreType = Core;
type SupportType = Support; type SupportType = Support;
fn core(&mut self) -> &mut Self::CoreType { fn core(&mut self) -> &mut Self::CoreType {
&mut self.core &mut self.core
} }
fn support(&mut self) -> &mut Self::SupportType { fn support(&mut self) -> &mut Self::SupportType {
&mut self.support &mut self.support
} }
} }
impl Game { impl Game {
pub fn new(mut system: System) -> Result<Self> { pub fn new(mut system: System) -> Result<Self> {
let palette = load_palette(Path::new("./assets/db16.pal"))?; let palette = load_palette(Path::new("./assets/db16.pal"))?;
system.palette = palette.clone(); system.palette = palette.clone();
let font = load_font(Path::new("./assets/dp.fnt"))?; let font = load_font(Path::new("./assets/dp.fnt"))?;
let tiles = Rc::new(load_bitmap_atlas_autogrid(Path::new("./assets/tiles.pcx"))?); let tiles = Rc::new(load_bitmap_atlas_autogrid(Path::new("./assets/tiles.pcx"))?);
let hero_male = Rc::new(load_bitmap_atlas_autogrid(Path::new("./assets/hero_male.pcx"))?); let hero_male = Rc::new(load_bitmap_atlas_autogrid(Path::new("./assets/hero_male.pcx"))?);
let hero_female = Rc::new(load_bitmap_atlas_autogrid(Path::new("./assets/hero_female.pcx"))?); let hero_female = Rc::new(load_bitmap_atlas_autogrid(Path::new("./assets/hero_female.pcx"))?);
let green_slime = Rc::new(load_bitmap_atlas_autogrid(Path::new("./assets/green_slime.pcx"))?); let green_slime = Rc::new(load_bitmap_atlas_autogrid(Path::new("./assets/green_slime.pcx"))?);
let blue_slime = Rc::new(load_bitmap_atlas_autogrid(Path::new("./assets/blue_slime.pcx"))?); let blue_slime = Rc::new(load_bitmap_atlas_autogrid(Path::new("./assets/blue_slime.pcx"))?);
let orange_slime = Rc::new(load_bitmap_atlas_autogrid(Path::new("./assets/orange_slime.pcx"))?); let orange_slime = Rc::new(load_bitmap_atlas_autogrid(Path::new("./assets/orange_slime.pcx"))?);
let fist = Rc::new(load_bitmap_atlas_autogrid(Path::new("./assets/fist.pcx"))?); let fist = Rc::new(load_bitmap_atlas_autogrid(Path::new("./assets/fist.pcx"))?);
let sword = Rc::new(load_bitmap_atlas_autogrid(Path::new("./assets/sword.pcx"))?); let sword = Rc::new(load_bitmap_atlas_autogrid(Path::new("./assets/sword.pcx"))?);
let particles = Rc::new(load_bitmap_atlas_autogrid(Path::new("./assets/particles.pcx"))?); let particles = Rc::new(load_bitmap_atlas_autogrid(Path::new("./assets/particles.pcx"))?);
let items = Rc::new(load_bitmap_atlas_autogrid(Path::new("./assets/items.pcx"))?); let items = Rc::new(load_bitmap_atlas_autogrid(Path::new("./assets/items.pcx"))?);
let mut ui = load_bitmap_atlas(Path::new("./assets/ui.pcx"))?; let mut ui = load_bitmap_atlas(Path::new("./assets/ui.pcx"))?;
ui.add(Rect::new(0, 0, 16, 16))?; ui.add(Rect::new(0, 0, 16, 16))?;
ui.add(Rect::new(16, 0, 16, 16))?; ui.add(Rect::new(16, 0, 16, 16))?;
for i in 0..8 { for i in 0..8 {
ui.add(Rect::new(i * 8, 16, 8, 8))?; ui.add(Rect::new(i * 8, 16, 8, 8))?;
} }
let tilemap = TileMap::load_from(Path::new("./assets/title_screen.map.json"))?; let tilemap = TileMap::load_from(Path::new("./assets/title_screen.map.json"))?;
let entities = Entities::new(); let entities = Entities::new();
let component_systems = ComponentSystems::new(); let component_systems = ComponentSystems::new();
let event_publisher = EventPublisher::new(); let event_publisher = EventPublisher::new();
let event_listeners = EventListeners::new(); let event_listeners = EventListeners::new();
let slime_activity_states = HashMap::from([ let slime_activity_states = HashMap::from([
(EntityActivity::Idle, Rc::new(AnimationDef::new(&[1, 2], true, 1.0, Some(3)))), (EntityActivity::Idle, Rc::new(AnimationDef::new(&[1, 2], true, 1.0, Some(3)))),
(EntityActivity::Walking, Rc::new(AnimationDef::new(&[1, 0, 2, 0], true, 0.25, Some(3)))), (EntityActivity::Walking, Rc::new(AnimationDef::new(&[1, 0, 2, 0], true, 0.25, Some(3)))),
(EntityActivity::Attacking, Rc::new(AnimationDef::new(&[0], false, 0.3, Some(3)))), (EntityActivity::Attacking, Rc::new(AnimationDef::new(&[0], false, 0.3, Some(3)))),
(EntityActivity::Dead, Rc::new(AnimationDef::new(&[12], false, 1.0, None))), (EntityActivity::Dead, Rc::new(AnimationDef::new(&[12], false, 1.0, None))),
]); ]);
let hero_activity_states = HashMap::from([ let hero_activity_states = HashMap::from([
(EntityActivity::Idle, Rc::new(AnimationDef::new(&[0], true, 0.5, Some(4)))), (EntityActivity::Idle, Rc::new(AnimationDef::new(&[0], true, 0.5, Some(4)))),
(EntityActivity::Walking, Rc::new(AnimationDef::new(&[0, 1, 0, 2], true, 0.15, Some(4)))), (EntityActivity::Walking, Rc::new(AnimationDef::new(&[0, 1, 0, 2], true, 0.15, Some(4)))),
(EntityActivity::Attacking, Rc::new(AnimationDef::new(&[3], false, 0.3, Some(4)))), (EntityActivity::Attacking, Rc::new(AnimationDef::new(&[3], false, 0.3, Some(4)))),
(EntityActivity::Dead, Rc::new(AnimationDef::new(&[16], false, 1.0, None))), (EntityActivity::Dead, Rc::new(AnimationDef::new(&[16], false, 1.0, None))),
]); ]);
let poof1_animation_def = Rc::new(AnimationDef::new(&[0, 1, 2], false, 0.15, None)); let poof1_animation_def = Rc::new(AnimationDef::new(&[0, 1, 2], false, 0.15, None));
let poof2_animation_def = Rc::new(AnimationDef::new(&[3, 4, 5], false, 0.15, None)); let poof2_animation_def = Rc::new(AnimationDef::new(&[3, 4, 5], false, 0.15, None));
let sparkles_animation_def = Rc::new(AnimationDef::new(&[6, 7, 8, 9], false, 0.1, None)); let sparkles_animation_def = Rc::new(AnimationDef::new(&[6, 7, 8, 9], false, 0.1, None));
Ok(Game { Ok(Game {
core: Core { core: Core {
delta: 0.0, delta: 0.0,
system, system,
font, font,
entities, entities,
event_publisher, event_publisher,
palette, palette,
fade_out_palette: Palette::new_with_default(20, 12, 28), fade_out_palette: Palette::new_with_default(20, 12, 28),
tiles, tiles,
hero_male, hero_male,
hero_female, hero_female,
green_slime, green_slime,
blue_slime, blue_slime,
orange_slime, orange_slime,
fist, fist,
sword, sword,
particles, particles,
items, items,
ui: Rc::new(ui), ui: Rc::new(ui),
tilemap, tilemap,
slime_activity_states: Rc::new(slime_activity_states), slime_activity_states: Rc::new(slime_activity_states),
hero_activity_states: Rc::new(hero_activity_states), hero_activity_states: Rc::new(hero_activity_states),
poof1_animation_def, poof1_animation_def,
poof2_animation_def, poof2_animation_def,
sparkles_animation_def, sparkles_animation_def,
sprite_render_list: Vec::with_capacity(1024), sprite_render_list: Vec::with_capacity(1024),
}, },
support: Support { support: Support {
component_systems, component_systems,
event_listeners, event_listeners,
}, },
}) })
} }
} }
fn main() -> Result<()> { fn main() -> Result<()> {
let system = SystemBuilder::new().window_title("Slime Stabbing Simulator").vsync(true).build()?; let system = SystemBuilder::new().window_title("Slime Stabbing Simulator").vsync(true).build()?;
let game = Game::new(system)?; let game = Game::new(system)?;
main_loop(game, MainMenuState::new()).context("Main loop error") main_loop(game, MainMenuState::new()).context("Main loop error")
} }

View file

@ -11,195 +11,195 @@ use crate::Game;
use crate::support::*; use crate::support::*;
pub struct MainMenuState { pub struct MainMenuState {
fade: f32, fade: f32,
selection: i32, selection: i32,
} }
impl MainMenuState { impl MainMenuState {
pub fn new() -> Self { pub fn new() -> Self {
MainMenuState { MainMenuState {
fade: 0.0, fade: 0.0,
selection: 0, selection: 0,
} }
} }
} }
impl AppState<Game> for MainMenuState { impl AppState<Game> for MainMenuState {
fn update(&mut self, state: State, context: &mut Game) -> Option<StateChange<Game>> { fn update(&mut self, state: State, context: &mut Game) -> Option<StateChange<Game>> {
if state == State::Active { if state == State::Active {
if context.core.system.input_devices.keyboard.is_key_pressed(Scancode::Escape) { if context.core.system.input_devices.keyboard.is_key_pressed(Scancode::Escape) {
return Some(StateChange::Pop(1)); return Some(StateChange::Pop(1));
} }
if context.core.system.input_devices.keyboard.is_key_pressed(Scancode::Up) { if context.core.system.input_devices.keyboard.is_key_pressed(Scancode::Up) {
self.selection = (self.selection - 1).clamp(0, 1); self.selection = (self.selection - 1).clamp(0, 1);
} }
if context.core.system.input_devices.keyboard.is_key_pressed(Scancode::Down) { if context.core.system.input_devices.keyboard.is_key_pressed(Scancode::Down) {
self.selection = (self.selection + 1).clamp(0, 1); self.selection = (self.selection + 1).clamp(0, 1);
} }
if context.core.system.input_devices.keyboard.is_key_pressed(Scancode::Return) { if context.core.system.input_devices.keyboard.is_key_pressed(Scancode::Return) {
match self.selection { match self.selection {
0 => return Some(StateChange::Push(Box::new(GamePlayState::new()))), 0 => return Some(StateChange::Push(Box::new(GamePlayState::new()))),
1 => return Some(StateChange::Pop(1)), 1 => return Some(StateChange::Pop(1)),
_ => {} _ => {}
} }
} }
} }
context.support.do_events(&mut context.core); context.support.do_events(&mut context.core);
context.support.component_systems.update(&mut context.core); context.support.component_systems.update(&mut context.core);
None None
} }
fn render(&mut self, state: State, context: &mut Game) { fn render(&mut self, state: State, context: &mut Game) {
context.core.tilemap.draw(&mut context.core.system.video, &context.core.tiles, 0, 0); context.core.tilemap.draw(&mut context.core.system.video, &context.core.tiles, 0, 0);
context.support.component_systems.render(&mut context.core); context.support.component_systems.render(&mut context.core);
let x = 32; let x = 32;
let y = 160; let y = 160;
let width = 48; let width = 48;
let height = 40; let height = 40;
const SPACER: i32 = 8; const SPACER: i32 = 8;
draw_window(&mut context.core.system.video, &context.core.ui, x, y, x + width, y + height); draw_window(&mut context.core.system.video, &context.core.ui, x, y, x + width, y + height);
let selection_y = y + SPACER + (self.selection as i32 * 16); let selection_y = y + SPACER + (self.selection as i32 * 16);
context.core.system.video.print_string(">", x + SPACER, selection_y, FontRenderOpts::Color(15), &context.core.font); context.core.system.video.print_string(">", x + SPACER, selection_y, FontRenderOpts::Color(15), &context.core.font);
context.core.system.video.print_string("Play", x + SPACER + SPACER, y + SPACER, FontRenderOpts::Color(15), &context.core.font); context.core.system.video.print_string("Play", x + SPACER + SPACER, y + SPACER, FontRenderOpts::Color(15), &context.core.font);
context.core.system.video.print_string("Quit", x + SPACER + SPACER, y + SPACER + 16, FontRenderOpts::Color(15), &context.core.font); context.core.system.video.print_string("Quit", x + SPACER + SPACER, y + SPACER + 16, FontRenderOpts::Color(15), &context.core.font);
} }
fn transition(&mut self, state: State, context: &mut Game) -> bool { fn transition(&mut self, state: State, context: &mut Game) -> bool {
update_fade_transition(state, &mut self.fade, context.core.delta * 3.0, context) update_fade_transition(state, &mut self.fade, context.core.delta * 3.0, context)
} }
fn state_change(&mut self, new_state: State, old_state: State, context: &mut Game) { fn state_change(&mut self, new_state: State, old_state: State, context: &mut Game) {
match new_state { match new_state {
State::Pending | State::Resume => { State::Pending | State::Resume => {
init_everything(context, Path::new("./assets/title_screen.map.json"), 0.2, 1.0, 32); init_everything(context, Path::new("./assets/title_screen.map.json"), 0.2, 1.0, 32);
} }
State::TransitionIn => { State::TransitionIn => {
self.fade = 0.0; self.fade = 0.0;
} }
State::TransitionOut(_) => { State::TransitionOut(_) => {
self.fade = 1.0; self.fade = 1.0;
} }
State::Paused => { State::Paused => {
context.core.system.palette = context.core.palette.clone(); context.core.system.palette = context.core.palette.clone();
} }
_ => {} _ => {}
} }
} }
} }
pub struct GamePlayState { pub struct GamePlayState {
fade: f32, fade: f32,
in_menu: bool, in_menu: bool,
selection: i32, selection: i32,
} }
impl GamePlayState { impl GamePlayState {
pub fn new() -> Self { pub fn new() -> Self {
GamePlayState { GamePlayState {
fade: 0.0, fade: 0.0,
in_menu: false, in_menu: false,
selection: 0, selection: 0,
} }
} }
} }
impl AppState<Game> for GamePlayState { impl AppState<Game> for GamePlayState {
fn update(&mut self, state: State, context: &mut Game) -> Option<StateChange<Game>> { fn update(&mut self, state: State, context: &mut Game) -> Option<StateChange<Game>> {
if state == State::Active { if state == State::Active {
if self.in_menu { if self.in_menu {
if context.core.system.input_devices.keyboard.is_key_pressed(Scancode::Escape) { if context.core.system.input_devices.keyboard.is_key_pressed(Scancode::Escape) {
self.in_menu = false; self.in_menu = false;
} }
if context.core.system.input_devices.keyboard.is_key_pressed(Scancode::Up) { if context.core.system.input_devices.keyboard.is_key_pressed(Scancode::Up) {
self.selection = (self.selection - 1).clamp(0, 1); self.selection = (self.selection - 1).clamp(0, 1);
} }
if context.core.system.input_devices.keyboard.is_key_pressed(Scancode::Down) { if context.core.system.input_devices.keyboard.is_key_pressed(Scancode::Down) {
self.selection = (self.selection + 1).clamp(0, 1); self.selection = (self.selection + 1).clamp(0, 1);
} }
if context.core.system.input_devices.keyboard.is_key_pressed(Scancode::Return) { if context.core.system.input_devices.keyboard.is_key_pressed(Scancode::Return) {
match self.selection { match self.selection {
0 => self.in_menu = false, 0 => self.in_menu = false,
1 => return Some(StateChange::Pop(1)), 1 => return Some(StateChange::Pop(1)),
_ => {} _ => {}
} }
} }
} else { } else {
if context.core.system.input_devices.keyboard.is_key_pressed(Scancode::Escape) { if context.core.system.input_devices.keyboard.is_key_pressed(Scancode::Escape) {
self.in_menu = true; self.in_menu = true;
} }
if let Some((player_entity, _)) = context.core.entities.components::<Player>().single() { if let Some((player_entity, _)) = context.core.entities.components::<Player>().single() {
if context.core.system.input_devices.keyboard.is_key_down(Scancode::Up) { if context.core.system.input_devices.keyboard.is_key_down(Scancode::Up) {
context.core.event_publisher.queue(Event::TurnAndMove(*player_entity, Direction::North)); context.core.event_publisher.queue(Event::TurnAndMove(*player_entity, Direction::North));
} }
if context.core.system.input_devices.keyboard.is_key_down(Scancode::Down) { if context.core.system.input_devices.keyboard.is_key_down(Scancode::Down) {
context.core.event_publisher.queue(Event::TurnAndMove(*player_entity, Direction::South)); context.core.event_publisher.queue(Event::TurnAndMove(*player_entity, Direction::South));
} }
if context.core.system.input_devices.keyboard.is_key_down(Scancode::Left) { if context.core.system.input_devices.keyboard.is_key_down(Scancode::Left) {
context.core.event_publisher.queue(Event::TurnAndMove(*player_entity, Direction::West)); context.core.event_publisher.queue(Event::TurnAndMove(*player_entity, Direction::West));
} }
if context.core.system.input_devices.keyboard.is_key_down(Scancode::Right) { if context.core.system.input_devices.keyboard.is_key_down(Scancode::Right) {
context.core.event_publisher.queue(Event::TurnAndMove(*player_entity, Direction::East)); context.core.event_publisher.queue(Event::TurnAndMove(*player_entity, Direction::East));
} }
if context.core.system.input_devices.keyboard.is_key_pressed(Scancode::Space) { if context.core.system.input_devices.keyboard.is_key_pressed(Scancode::Space) {
context.core.event_publisher.queue(Event::Attack(*player_entity)); context.core.event_publisher.queue(Event::Attack(*player_entity));
} }
} }
} }
} }
context.support.do_events(&mut context.core); context.support.do_events(&mut context.core);
context.support.component_systems.update(&mut context.core); context.support.component_systems.update(&mut context.core);
None None
} }
fn render(&mut self, state: State, context: &mut Game) { fn render(&mut self, state: State, context: &mut Game) {
if let Some((_, camera)) = context.core.entities.components::<Camera>().single() { if let Some((_, camera)) = context.core.entities.components::<Camera>().single() {
context.core.tilemap.draw(&mut context.core.system.video, &context.core.tiles, camera.x, camera.y); context.core.tilemap.draw(&mut context.core.system.video, &context.core.tiles, camera.x, camera.y);
} }
context.support.component_systems.render(&mut context.core); context.support.component_systems.render(&mut context.core);
if self.in_menu { if self.in_menu {
let x = 32; let x = 32;
let y = 160; let y = 160;
let width = 80; let width = 80;
let height = 40; let height = 40;
const SPACER: i32 = 8; const SPACER: i32 = 8;
draw_window(&mut context.core.system.video, &context.core.ui, x, y, x + width, y + height); draw_window(&mut context.core.system.video, &context.core.ui, x, y, x + width, y + height);
let selection_y = y + SPACER + (self.selection as i32 * 16); let selection_y = y + SPACER + (self.selection as i32 * 16);
context.core.system.video.print_string(">", x + SPACER, selection_y, FontRenderOpts::Color(15), &context.core.font); context.core.system.video.print_string(">", x + SPACER, selection_y, FontRenderOpts::Color(15), &context.core.font);
context.core.system.video.print_string("Continue", x + SPACER + SPACER, y + SPACER, FontRenderOpts::Color(15), &context.core.font); context.core.system.video.print_string("Continue", x + SPACER + SPACER, y + SPACER, FontRenderOpts::Color(15), &context.core.font);
context.core.system.video.print_string("Quit", x + SPACER + SPACER, y + SPACER + 16, FontRenderOpts::Color(15), &context.core.font); context.core.system.video.print_string("Quit", x + SPACER + SPACER, y + SPACER + 16, FontRenderOpts::Color(15), &context.core.font);
} }
} }
fn transition(&mut self, state: State, context: &mut Game) -> bool { fn transition(&mut self, state: State, context: &mut Game) -> bool {
update_fade_transition(state, &mut self.fade, context.core.delta * 3.0, context) update_fade_transition(state, &mut self.fade, context.core.delta * 3.0, context)
} }
fn state_change(&mut self, new_state: State, old_state: State, context: &mut Game) { fn state_change(&mut self, new_state: State, old_state: State, context: &mut Game) {
match new_state { match new_state {
State::Pending => { State::Pending => {
init_everything(context, Path::new("./assets/arena.map.json"), 0.5, 2.0, 100); init_everything(context, Path::new("./assets/arena.map.json"), 0.5, 2.0, 100);
spawn_player_randomly(&mut context.core); spawn_player_randomly(&mut context.core);
}, }
State::TransitionIn => { State::TransitionIn => {
self.fade = 0.0; self.fade = 0.0;
} }
State::TransitionOut(_) => { State::TransitionOut(_) => {
self.fade = 1.0; self.fade = 1.0;
} }
_ => {} _ => {}
} }
} }
} }

View file

@ -8,76 +8,76 @@ use libretrogd::states::*;
use crate::{Game, TILE_HEIGHT, TILE_WIDTH}; use crate::{Game, TILE_HEIGHT, TILE_WIDTH};
pub fn load_palette(path: &Path) -> Result<Palette> { pub fn load_palette(path: &Path) -> Result<Palette> {
Palette::load_from_file(path, PaletteFormat::Vga).context(format!("Loading palette: {:?}", path)) Palette::load_from_file(path, PaletteFormat::Vga).context(format!("Loading palette: {:?}", path))
} }
pub fn load_font(path: &Path) -> Result<BitmaskFont> { pub fn load_font(path: &Path) -> Result<BitmaskFont> {
BitmaskFont::load_from_file(path).context(format!("Loading font: {:?}", path)) BitmaskFont::load_from_file(path).context(format!("Loading font: {:?}", path))
} }
pub fn load_bitmap_atlas_autogrid(path: &Path) -> Result<BitmapAtlas> { pub fn load_bitmap_atlas_autogrid(path: &Path) -> Result<BitmapAtlas> {
let (bmp, _) = Bitmap::load_file(path).context(format!("Loading bitmap atlas: {:?}", path))?; let (bmp, _) = Bitmap::load_file(path).context(format!("Loading bitmap atlas: {:?}", path))?;
let mut atlas = BitmapAtlas::new(bmp); let mut atlas = BitmapAtlas::new(bmp);
atlas.add_grid(TILE_WIDTH, TILE_HEIGHT)?; atlas.add_grid(TILE_WIDTH, TILE_HEIGHT)?;
Ok(atlas) Ok(atlas)
} }
pub fn load_bitmap_atlas(path: &Path) -> Result<BitmapAtlas> { pub fn load_bitmap_atlas(path: &Path) -> Result<BitmapAtlas> {
let (bmp, _) = Bitmap::load_file(path).context(format!("Loading bitmap atlas: {:?}", path))?; let (bmp, _) = Bitmap::load_file(path).context(format!("Loading bitmap atlas: {:?}", path))?;
let atlas = BitmapAtlas::new(bmp); let atlas = BitmapAtlas::new(bmp);
Ok(atlas) Ok(atlas)
} }
pub fn draw_window(dest: &mut Bitmap, ui: &BitmapAtlas, left: i32, top: i32, right: i32, bottom: i32) { pub fn draw_window(dest: &mut Bitmap, ui: &BitmapAtlas, left: i32, top: i32, right: i32, bottom: i32) {
dest.filled_rect(left + 8, top + 8, right - 8, bottom - 8, 1); dest.filled_rect(left + 8, top + 8, right - 8, bottom - 8, 1);
// corners // corners
dest.blit_region(BlitMethod::Transparent(0), &ui.bitmap(), &ui[2], left, top); dest.blit_region(BlitMethod::Transparent(0), &ui.bitmap(), &ui[2], left, top);
dest.blit_region(BlitMethod::Transparent(0), &ui.bitmap(), &ui[3], right - 8, top); dest.blit_region(BlitMethod::Transparent(0), &ui.bitmap(), &ui[3], right - 8, top);
dest.blit_region(BlitMethod::Transparent(0), &ui.bitmap(), &ui[4], left, bottom - 8); dest.blit_region(BlitMethod::Transparent(0), &ui.bitmap(), &ui[4], left, bottom - 8);
dest.blit_region(BlitMethod::Transparent(0), &ui.bitmap(), &ui[5], right - 8, bottom - 8); dest.blit_region(BlitMethod::Transparent(0), &ui.bitmap(), &ui[5], right - 8, bottom - 8);
// top and bottom edges // top and bottom edges
for i in 0..((right - left) / 8) - 2 { for i in 0..((right - left) / 8) - 2 {
let x = left + 8 + (i * 8); let x = left + 8 + (i * 8);
dest.blit_region(BlitMethod::Transparent(0), &ui.bitmap(), &ui[9], x, top); dest.blit_region(BlitMethod::Transparent(0), &ui.bitmap(), &ui[9], x, top);
dest.blit_region(BlitMethod::Transparent(0), &ui.bitmap(), &ui[8], x, bottom - 8); dest.blit_region(BlitMethod::Transparent(0), &ui.bitmap(), &ui[8], x, bottom - 8);
} }
// left and right edges // left and right edges
for i in 0..((bottom - top) / 8) - 2 { for i in 0..((bottom - top) / 8) - 2 {
let y = top + 8 + (i * 8); let y = top + 8 + (i * 8);
dest.blit_region(BlitMethod::Transparent(0), &ui.bitmap(), &ui[6], left, y); dest.blit_region(BlitMethod::Transparent(0), &ui.bitmap(), &ui[6], left, y);
dest.blit_region(BlitMethod::Transparent(0), &ui.bitmap(), &ui[7], right - 8, y); dest.blit_region(BlitMethod::Transparent(0), &ui.bitmap(), &ui[7], right - 8, y);
} }
} }
pub fn update_fade_transition(state: State, fade: &mut f32, delta: f32, context: &mut Game) -> bool { pub fn update_fade_transition(state: State, fade: &mut f32, delta: f32, context: &mut Game) -> bool {
match state { match state {
State::TransitionIn => { State::TransitionIn => {
*fade += delta; *fade += delta;
if *fade >= 1.0 { if *fade >= 1.0 {
*fade = 1.0; *fade = 1.0;
context.core.system.palette = context.core.palette.clone(); context.core.system.palette = context.core.palette.clone();
true true
} else { } else {
context.core.system.palette.lerp(0..=255, &context.core.fade_out_palette, &context.core.palette, *fade); context.core.system.palette.lerp(0..=255, &context.core.fade_out_palette, &context.core.palette, *fade);
false false
} }
}, }
State::TransitionOut(_) => { State::TransitionOut(_) => {
*fade -= delta; *fade -= delta;
if *fade <= 0.0 { if *fade <= 0.0 {
*fade = 0.0; *fade = 0.0;
context.core.system.palette = context.core.fade_out_palette.clone(); context.core.system.palette = context.core.fade_out_palette.clone();
true true
} else { } else {
context.core.system.palette.lerp(0..=255, &context.core.fade_out_palette, &context.core.palette, *fade); context.core.system.palette.lerp(0..=255, &context.core.fade_out_palette, &context.core.palette, *fade);
false false
} }
}, }
_ => { _ => {
true true
} }
} }
} }

View file

@ -17,111 +17,111 @@ pub const TILE_FLAG_SPAWNABLE: i32 = 1;
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct TileMap { pub struct TileMap {
width: u32, width: u32,
height: u32, height: u32,
layers: Vec<Box<[i32]>>, layers: Vec<Box<[i32]>>,
} }
impl TileMap { impl TileMap {
pub fn load_from(path: &Path) -> Result<Self> { pub fn load_from(path: &Path) -> Result<Self> {
let f = File::open(path)?; let f = File::open(path)?;
let reader = BufReader::new(f); let reader = BufReader::new(f);
serde_json::from_reader(reader).context(format!("Loading json tilemap: {:?}", path)) serde_json::from_reader(reader).context(format!("Loading json tilemap: {:?}", path))
} }
#[inline] #[inline]
pub fn width(&self) -> u32 { pub fn width(&self) -> u32 {
self.width self.width
} }
#[inline] #[inline]
pub fn height(&self) -> u32 { pub fn height(&self) -> u32 {
self.height self.height
} }
#[inline] #[inline]
pub fn index_to(&self, x: i32, y: i32) -> Option<usize> { pub fn index_to(&self, x: i32, y: i32) -> Option<usize> {
if x >= 0 && y >= 0 && x < self.width as i32 && y < self.height as i32 { if x >= 0 && y >= 0 && x < self.width as i32 && y < self.height as i32 {
Some(((y * self.width as i32) + x) as usize) Some(((y * self.width as i32) + x) as usize)
} else { } else {
None None
} }
} }
#[inline] #[inline]
pub fn lower(&self) -> &Box<[i32]> { pub fn lower(&self) -> &Box<[i32]> {
&self.layers[0] &self.layers[0]
} }
#[inline] #[inline]
pub fn upper(&self) -> &Box<[i32]> { pub fn upper(&self) -> &Box<[i32]> {
&self.layers[1] &self.layers[1]
} }
#[inline] #[inline]
pub fn collision(&self) -> &Box<[i32]> { pub fn collision(&self) -> &Box<[i32]> {
&self.layers[2] &self.layers[2]
} }
pub fn draw(&self, dest: &mut Bitmap, tiles: &BitmapAtlas, camera_x: i32, camera_y: i32) { pub fn draw(&self, dest: &mut Bitmap, tiles: &BitmapAtlas, camera_x: i32, camera_y: i32) {
let xt = camera_x / TILE_WIDTH as i32; let xt = camera_x / TILE_WIDTH as i32;
let yt = camera_y / TILE_HEIGHT as i32; let yt = camera_y / TILE_HEIGHT as i32;
let xp = camera_x % TILE_WIDTH as i32; let xp = camera_x % TILE_WIDTH as i32;
let yp = camera_y % TILE_HEIGHT as i32; let yp = camera_y % TILE_HEIGHT as i32;
for y in 0..=15 { for y in 0..=15 {
for x in 0..=20 { for x in 0..=20 {
if let Some(index) = self.index_to(x + xt, y + yt) { if let Some(index) = self.index_to(x + xt, y + yt) {
let xd = (x * TILE_WIDTH as i32) - xp; let xd = (x * TILE_WIDTH as i32) - xp;
let yd = (y * TILE_HEIGHT as i32) - yp; let yd = (y * TILE_HEIGHT as i32) - yp;
let lower = self.layers[0][index]; let lower = self.layers[0][index];
if lower >= 0 { if lower >= 0 {
dest.blit_region(BlitMethod::Solid, tiles.bitmap(), &tiles[lower as usize], xd, yd); dest.blit_region(BlitMethod::Solid, tiles.bitmap(), &tiles[lower as usize], xd, yd);
} }
let upper = self.layers[1][index]; let upper = self.layers[1][index];
if upper >= 0 { if upper >= 0 {
dest.blit_region(BlitMethod::Transparent(0), tiles.bitmap(), &tiles[upper as usize], xd, yd); dest.blit_region(BlitMethod::Transparent(0), tiles.bitmap(), &tiles[upper as usize], xd, yd);
} }
} }
} }
} }
} }
pub fn is_colliding(&self, rect: &Rect) -> bool { pub fn is_colliding(&self, rect: &Rect) -> bool {
let x1 = rect.x / TILE_WIDTH as i32; let x1 = rect.x / TILE_WIDTH as i32;
let y1 = rect.y / TILE_HEIGHT as i32; let y1 = rect.y / TILE_HEIGHT as i32;
let x2 = rect.right() / TILE_WIDTH as i32; let x2 = rect.right() / TILE_WIDTH as i32;
let y2 = rect.bottom() / TILE_HEIGHT as i32; let y2 = rect.bottom() / TILE_HEIGHT as i32;
for y in y1..=y2 { for y in y1..=y2 {
for x in x1..=x2 { for x in x1..=x2 {
match self.index_to(x, y) { match self.index_to(x, y) {
Some(index) => { Some(index) => {
if self.collision()[index] == TILE_FLAG_COLLISION { if self.collision()[index] == TILE_FLAG_COLLISION {
return true; return true;
} }
}, }
None => return true None => return true
} }
} }
} }
false false
} }
pub fn get_random_spawnable_coordinates(&self) -> (i32, i32) { pub fn get_random_spawnable_coordinates(&self) -> (i32, i32) {
// TODO: do this better // TODO: do this better
let mut x; let mut x;
let mut y; let mut y;
loop { loop {
x = rnd_value(0, self.width as i32 - 1); x = rnd_value(0, self.width as i32 - 1);
y = rnd_value(0, self.height as i32 - 1); y = rnd_value(0, self.height as i32 - 1);
if self.collision()[self.index_to(x, y).unwrap()] == TILE_FLAG_SPAWNABLE { if self.collision()[self.index_to(x, y).unwrap()] == TILE_FLAG_SPAWNABLE {
break; break;
} }
} }
(x, y) (x, y)
} }
} }

View file

@ -12,30 +12,30 @@ use libretrogd::utils::rnd_value;
////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////
pub enum Event { pub enum Event {
Remove(EntityId), Remove(EntityId),
SpawnPixel, SpawnPixel,
} }
pub fn event_listener(event: &Event, context: &mut Core) -> bool { pub fn event_listener(event: &Event, context: &mut Core) -> bool {
match event { match event {
Event::Remove(entity) => { Event::Remove(entity) => {
context.entities.remove_entity(*entity); context.entities.remove_entity(*entity);
true true
}, }
Event::SpawnPixel => { Event::SpawnPixel => {
let speed = rnd_value(1, 10) as f32 * 10.0; let speed = rnd_value(1, 10) as f32 * 10.0;
let angle = (rnd_value(0, 359) as f32).to_radians(); let angle = (rnd_value(0, 359) as f32).to_radians();
let x = (SCREEN_WIDTH / 2) as f32; let x = (SCREEN_WIDTH / 2) as f32;
let y = (SCREEN_HEIGHT / 2) as f32; let y = (SCREEN_HEIGHT / 2) as f32;
let color = rnd_value(0, 255); let color = rnd_value(0, 255);
let id = context.entities.new_entity(); let id = context.entities.new_entity();
context.entities.add_component(id, Position(Vector2::new(x, y))); context.entities.add_component(id, Position(Vector2::new(x, y)));
context.entities.add_component(id, Velocity(Vector2::from_angle(angle) * speed)); context.entities.add_component(id, Velocity(Vector2::from_angle(angle) * speed));
context.entities.add_component(id, Color(color)); context.entities.add_component(id, Color(color));
true true
}, }
_ => false _ => false
} }
} }
////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////
@ -49,32 +49,32 @@ pub struct Color(u8);
////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////
pub fn update_system_movement(context: &mut Core) { pub fn update_system_movement(context: &mut Core) {
let mut positions = context.entities.components_mut::<Position>().unwrap(); let mut positions = context.entities.components_mut::<Position>().unwrap();
let velocities = context.entities.components::<Velocity>().unwrap(); let velocities = context.entities.components::<Velocity>().unwrap();
for (entity, position) in positions.iter_mut() { for (entity, position) in positions.iter_mut() {
let velocity = velocities.get(entity).unwrap(); let velocity = velocities.get(entity).unwrap();
position.0 += velocity.0 * context.delta; position.0 += velocity.0 * context.delta;
} }
} }
pub fn update_system_remove_offscreen(context: &mut Core) { pub fn update_system_remove_offscreen(context: &mut Core) {
let positions = context.entities.components::<Position>().unwrap(); let positions = context.entities.components::<Position>().unwrap();
for (entity, position) in positions.iter() { for (entity, position) in positions.iter() {
if !context.system.video.is_xy_visible(position.0.x as i32, position.0.y as i32) { if !context.system.video.is_xy_visible(position.0.x as i32, position.0.y as i32) {
context.event_publisher.queue(Event::Remove(*entity)); context.event_publisher.queue(Event::Remove(*entity));
} }
} }
} }
pub fn render_system_pixels(context: &mut Core) { pub fn render_system_pixels(context: &mut Core) {
let positions = context.entities.components::<Position>().unwrap(); let positions = context.entities.components::<Position>().unwrap();
let colors = context.entities.components::<Color>().unwrap(); let colors = context.entities.components::<Color>().unwrap();
for (entity, position) in positions.iter() { for (entity, position) in positions.iter() {
let color = colors.get(entity).unwrap(); let color = colors.get(entity).unwrap();
context.system.video.set_pixel(position.0.x as i32, position.0.y as i32, color.0); context.system.video.set_pixel(position.0.x as i32, position.0.y as i32, color.0);
} }
} }
////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////
@ -82,150 +82,150 @@ pub fn render_system_pixels(context: &mut Core) {
pub struct DemoState; pub struct DemoState;
impl DemoState { impl DemoState {
fn init(&mut self, context: &mut App) { fn init(&mut self, context: &mut App) {
context.core.entities.init_components::<Position>(); context.core.entities.init_components::<Position>();
context.core.entities.init_components::<Velocity>(); context.core.entities.init_components::<Velocity>();
context.core.entities.init_components::<Color>(); context.core.entities.init_components::<Color>();
context.support.component_systems.reset(); context.support.component_systems.reset();
context.support.component_systems.add_update_system(update_system_movement); context.support.component_systems.add_update_system(update_system_movement);
context.support.component_systems.add_update_system(update_system_remove_offscreen); context.support.component_systems.add_update_system(update_system_remove_offscreen);
context.support.component_systems.add_render_system(render_system_pixels); context.support.component_systems.add_render_system(render_system_pixels);
context.support.event_listeners.clear(); context.support.event_listeners.clear();
context.support.event_listeners.add(event_listener); context.support.event_listeners.add(event_listener);
} }
} }
impl AppState<App> for DemoState { impl AppState<App> for DemoState {
fn update(&mut self, state: State, context: &mut App) -> Option<StateChange<App>> { fn update(&mut self, state: State, context: &mut App) -> Option<StateChange<App>> {
if state == State::Active { if state == State::Active {
if context.core.system.input_devices.keyboard.is_key_pressed(Scancode::Escape) { if context.core.system.input_devices.keyboard.is_key_pressed(Scancode::Escape) {
return Some(StateChange::Pop(1)) return Some(StateChange::Pop(1));
} }
} }
if rnd_value(0, 100) < 80 { if rnd_value(0, 100) < 80 {
context.core.event_publisher.queue(Event::SpawnPixel); context.core.event_publisher.queue(Event::SpawnPixel);
} }
context.support.do_events(&mut context.core); context.support.do_events(&mut context.core);
context.support.component_systems.update(&mut context.core); context.support.component_systems.update(&mut context.core);
None None
} }
fn render(&mut self, state: State, context: &mut App) { fn render(&mut self, state: State, context: &mut App) {
context.core.system.video.clear(0); context.core.system.video.clear(0);
context.support.component_systems.render(&mut context.core); context.support.component_systems.render(&mut context.core);
} }
fn transition(&mut self, state: State, context: &mut App) -> bool { fn transition(&mut self, state: State, context: &mut App) -> bool {
true true
} }
fn state_change(&mut self, new_state: State, old_state: State, context: &mut App) { fn state_change(&mut self, new_state: State, old_state: State, context: &mut App) {
match new_state { match new_state {
State::Pending => { State::Pending => {
self.init(context); self.init(context);
}, }
_ => {} _ => {}
} }
} }
} }
////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////
pub struct Core { pub struct Core {
pub delta: f32, pub delta: f32,
pub system: System, pub system: System,
pub entities: Entities, pub entities: Entities,
pub event_publisher: EventPublisher<Event>, pub event_publisher: EventPublisher<Event>,
} }
impl CoreState for Core { impl CoreState for Core {
fn system(&self) -> &System { fn system(&self) -> &System {
&self.system &self.system
} }
fn system_mut(&mut self) -> &mut System { fn system_mut(&mut self) -> &mut System {
&mut self.system &mut self.system
} }
fn delta(&self) -> f32 { fn delta(&self) -> f32 {
self.delta self.delta
} }
fn set_delta(&mut self, delta: f32) { fn set_delta(&mut self, delta: f32) {
self.delta = delta; self.delta = delta;
} }
} }
impl CoreStateWithEvents<Event> for Core { impl CoreStateWithEvents<Event> for Core {
fn event_publisher(&mut self) -> &mut EventPublisher<Event> { fn event_publisher(&mut self) -> &mut EventPublisher<Event> {
&mut self.event_publisher &mut self.event_publisher
} }
} }
pub struct Support { pub struct Support {
pub component_systems: ComponentSystems<Core, Core>, pub component_systems: ComponentSystems<Core, Core>,
pub event_listeners: EventListeners<Event, Core> pub event_listeners: EventListeners<Event, Core>,
} }
impl SupportSystems for Support {} impl SupportSystems for Support {}
impl SupportSystemsWithEvents<Event> for Support { impl SupportSystemsWithEvents<Event> for Support {
type ContextType = Core; type ContextType = Core;
fn event_listeners(&mut self) -> &mut EventListeners<Event, Self::ContextType> { fn event_listeners(&mut self) -> &mut EventListeners<Event, Self::ContextType> {
&mut self.event_listeners &mut self.event_listeners
} }
} }
pub struct App { pub struct App {
pub core: Core, pub core: Core,
pub support: Support, pub support: Support,
} }
impl AppContext for App { impl AppContext for App {
type CoreType = Core; type CoreType = Core;
type SupportType = Support; type SupportType = Support;
fn core(&mut self) -> &mut Core { fn core(&mut self) -> &mut Core {
&mut self.core &mut self.core
} }
fn support(&mut self) -> &mut Support { fn support(&mut self) -> &mut Support {
&mut self.support &mut self.support
} }
} }
impl App { impl App {
pub fn new(system: System) -> Result<Self> { pub fn new(system: System) -> Result<Self> {
let entities = Entities::new(); let entities = Entities::new();
let component_systems = ComponentSystems::new(); let component_systems = ComponentSystems::new();
let event_publisher = EventPublisher::new(); let event_publisher = EventPublisher::new();
let event_listeners = EventListeners::new(); let event_listeners = EventListeners::new();
Ok(App { Ok(App {
core: Core { core: Core {
delta: 0.0, delta: 0.0,
system, system,
entities, entities,
event_publisher, event_publisher,
}, },
support: Support { support: Support {
component_systems, component_systems,
event_listeners, event_listeners,
} },
}) })
} }
} }
////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////
fn main() -> Result<()> { fn main() -> Result<()> {
let system = SystemBuilder::new().window_title("Complicated Template").vsync(true).build()?; let system = SystemBuilder::new().window_title("Complicated Template").vsync(true).build()?;
let app = App::new(system)?; let app = App::new(system)?;
main_loop(app, DemoState).context("Main loop error") main_loop(app, DemoState).context("Main loop error")
} }

View file

@ -6,25 +6,25 @@ use libretrogd::system::*;
use libretrogd::utils::rnd_value; use libretrogd::utils::rnd_value;
fn main() -> Result<()> { fn main() -> Result<()> {
let mut system = SystemBuilder::new().window_title("Minimal Template").vsync(true).build()?; let mut system = SystemBuilder::new().window_title("Minimal Template").vsync(true).build()?;
let font = BitmaskFont::new_vga_font()?; let font = BitmaskFont::new_vga_font()?;
system.video.clear(0); system.video.clear(0);
system.video.print_string("Hello, world!", 20, 20, FontRenderOpts::Color(15), &font); system.video.print_string("Hello, world!", 20, 20, FontRenderOpts::Color(15), &font);
while !system.do_events() { while !system.do_events() {
if system.input_devices.keyboard.is_key_pressed(Scancode::Escape) { if system.input_devices.keyboard.is_key_pressed(Scancode::Escape) {
break; break;
} }
let x = rnd_value(0, SCREEN_RIGHT) as i32; let x = rnd_value(0, SCREEN_RIGHT) as i32;
let y = rnd_value(0, SCREEN_BOTTOM) as i32; let y = rnd_value(0, SCREEN_BOTTOM) as i32;
let color = rnd_value(0, 255); let color = rnd_value(0, 255);
system.video.set_pixel(x, y, color); system.video.set_pixel(x, y, color);
system.display()?; system.display()?;
} }
Ok(()) Ok(())
} }

View file

@ -1,26 +1,26 @@
use criterion::{black_box, criterion_group, criterion_main, Criterion}; use criterion::{black_box, Criterion, criterion_group, criterion_main};
use libretrogd::graphics::*;
use libretrogd::{SCREEN_HEIGHT, SCREEN_WIDTH}; use libretrogd::{SCREEN_HEIGHT, SCREEN_WIDTH};
use libretrogd::graphics::*;
pub fn criterion_benchmark(c: &mut Criterion) { pub fn criterion_benchmark(c: &mut Criterion) {
let mut source = Bitmap::new(SCREEN_WIDTH, SCREEN_HEIGHT).unwrap(); let mut source = Bitmap::new(SCREEN_WIDTH, SCREEN_HEIGHT).unwrap();
let mut dest = vec![0u32; (SCREEN_WIDTH * SCREEN_HEIGHT * 4) as usize].into_boxed_slice(); let mut dest = vec![0u32; (SCREEN_WIDTH * SCREEN_HEIGHT * 4) as usize].into_boxed_slice();
let palette = Palette::new_vga_palette().unwrap(); let palette = Palette::new_vga_palette().unwrap();
c.bench_function("deindex_bitmap_pixels", |b| { c.bench_function("deindex_bitmap_pixels", |b| {
b.iter(|| source.copy_as_argb_to(&mut dest, &palette)) b.iter(|| source.copy_as_argb_to(&mut dest, &palette))
}); });
c.bench_function("set_pixel", |b| { c.bench_function("set_pixel", |b| {
b.iter(|| source.set_pixel(black_box(100), black_box(100), black_box(42))) b.iter(|| source.set_pixel(black_box(100), black_box(100), black_box(42)))
}); });
c.bench_function("set_pixel_unchecked", |b| { c.bench_function("set_pixel_unchecked", |b| {
b.iter(|| unsafe { b.iter(|| unsafe {
source.set_pixel_unchecked(black_box(100), black_box(100), black_box(42)) source.set_pixel_unchecked(black_box(100), black_box(100), black_box(42))
}) })
}); });
} }
criterion_group!(benches, criterion_benchmark); criterion_group!(benches, criterion_benchmark);

View file

@ -1,498 +1,497 @@
use std::path::Path; use std::path::Path;
use criterion::{black_box, criterion_group, criterion_main, Criterion}; use criterion::{black_box, Criterion, criterion_group, criterion_main};
use libretrogd::graphics::*; use libretrogd::graphics::*;
use libretrogd::math::*; use libretrogd::math::*;
pub fn criterion_benchmark(c: &mut Criterion) { pub fn criterion_benchmark(c: &mut Criterion) {
let mut framebuffer = Bitmap::new(320, 240).unwrap(); let mut framebuffer = Bitmap::new(320, 240).unwrap();
let (bmp, _) = Bitmap::load_iff_file(Path::new("./test-assets/test-tiles.lbm")).unwrap(); let (bmp, _) = Bitmap::load_iff_file(Path::new("./test-assets/test-tiles.lbm")).unwrap();
let mut solid_bmp = Bitmap::new(16, 16).unwrap(); let mut solid_bmp = Bitmap::new(16, 16).unwrap();
solid_bmp.blit_region(BlitMethod::Solid, &bmp, &Rect::new(16, 16, 16, 16), 0, 0); solid_bmp.blit_region(BlitMethod::Solid, &bmp, &Rect::new(16, 16, 16, 16), 0, 0);
let mut trans_bmp = Bitmap::new(16, 16).unwrap(); let mut trans_bmp = Bitmap::new(16, 16).unwrap();
trans_bmp.blit_region(BlitMethod::Solid, &bmp, &Rect::new(160, 0, 16, 16), 0, 0); trans_bmp.blit_region(BlitMethod::Solid, &bmp, &Rect::new(160, 0, 16, 16), 0, 0);
////// //////
c.bench_function("blit_single_checked_solid", |b| { c.bench_function("blit_single_checked_solid", |b| {
b.iter(|| { b.iter(|| {
framebuffer.blit( framebuffer.blit(
black_box(BlitMethod::Solid), black_box(BlitMethod::Solid),
black_box(&solid_bmp), black_box(&solid_bmp),
black_box(100), black_box(100),
black_box(100), black_box(100),
) )
}) })
}); });
c.bench_function("blit_single_unchecked_solid", |b| { c.bench_function("blit_single_unchecked_solid", |b| {
b.iter(|| unsafe { b.iter(|| unsafe {
framebuffer.blit_unchecked( framebuffer.blit_unchecked(
black_box(BlitMethod::Solid), black_box(BlitMethod::Solid),
black_box(&solid_bmp), black_box(&solid_bmp),
black_box(100), black_box(100),
black_box(100), black_box(100),
) )
}) })
}); });
////// //////
c.bench_function("blit_single_checked_transparent", |b| { c.bench_function("blit_single_checked_transparent", |b| {
b.iter(|| { b.iter(|| {
framebuffer.blit( framebuffer.blit(
black_box(BlitMethod::Transparent(0)), black_box(BlitMethod::Transparent(0)),
black_box(&trans_bmp), black_box(&trans_bmp),
black_box(100), black_box(100),
black_box(100), black_box(100),
) )
}) })
}); });
c.bench_function("blit_single_unchecked_transparent", |b| { c.bench_function("blit_single_unchecked_transparent", |b| {
b.iter(|| unsafe { b.iter(|| unsafe {
framebuffer.blit_unchecked( framebuffer.blit_unchecked(
black_box(BlitMethod::Transparent(0)), black_box(BlitMethod::Transparent(0)),
black_box(&trans_bmp), black_box(&trans_bmp),
black_box(100), black_box(100),
black_box(100), black_box(100),
) )
}) })
}); });
////// //////
c.bench_function("blit_solid_flipped_not_flipped", |b| { c.bench_function("blit_solid_flipped_not_flipped", |b| {
b.iter(|| { b.iter(|| {
framebuffer.blit( framebuffer.blit(
black_box(BlitMethod::SolidFlipped { black_box(BlitMethod::SolidFlipped {
horizontal_flip: false, horizontal_flip: false,
vertical_flip: false vertical_flip: false,
}), }),
black_box(&solid_bmp), black_box(&solid_bmp),
black_box(100), black_box(100),
black_box(100), black_box(100),
) )
}) })
}); });
c.bench_function("blit_solid_flipped_horizontally", |b| { c.bench_function("blit_solid_flipped_horizontally", |b| {
b.iter(|| { b.iter(|| {
framebuffer.blit( framebuffer.blit(
black_box(BlitMethod::SolidFlipped { black_box(BlitMethod::SolidFlipped {
horizontal_flip: true, horizontal_flip: true,
vertical_flip: false vertical_flip: false,
}), }),
black_box(&solid_bmp), black_box(&solid_bmp),
black_box(100), black_box(100),
black_box(100), black_box(100),
) )
}) })
}); });
c.bench_function("blit_solid_flipped_vertically", |b| { c.bench_function("blit_solid_flipped_vertically", |b| {
b.iter(|| { b.iter(|| {
framebuffer.blit( framebuffer.blit(
black_box(BlitMethod::SolidFlipped { black_box(BlitMethod::SolidFlipped {
horizontal_flip: false, horizontal_flip: false,
vertical_flip: true vertical_flip: true,
}), }),
black_box(&solid_bmp), black_box(&solid_bmp),
black_box(100), black_box(100),
black_box(100), black_box(100),
) )
}) })
}); });
c.bench_function("blit_solid_flipped_horizontally_and_vertically", |b| { c.bench_function("blit_solid_flipped_horizontally_and_vertically", |b| {
b.iter(|| { b.iter(|| {
framebuffer.blit( framebuffer.blit(
black_box(BlitMethod::SolidFlipped { black_box(BlitMethod::SolidFlipped {
horizontal_flip: true, horizontal_flip: true,
vertical_flip: true vertical_flip: true,
}), }),
black_box(&solid_bmp), black_box(&solid_bmp),
black_box(100), black_box(100),
black_box(100), black_box(100),
) )
}) })
}); });
////// //////
c.bench_function("blit_transparent_flipped_not_flipped", |b| { c.bench_function("blit_transparent_flipped_not_flipped", |b| {
b.iter(|| { b.iter(|| {
framebuffer.blit( framebuffer.blit(
black_box(BlitMethod::TransparentFlipped { black_box(BlitMethod::TransparentFlipped {
transparent_color: 0, transparent_color: 0,
horizontal_flip: false, horizontal_flip: false,
vertical_flip: false vertical_flip: false,
}), }),
black_box(&trans_bmp), black_box(&trans_bmp),
black_box(100), black_box(100),
black_box(100), black_box(100),
) )
}) })
}); });
c.bench_function("blit_transparent_flipped_horizontally", |b| { c.bench_function("blit_transparent_flipped_horizontally", |b| {
b.iter(|| { b.iter(|| {
framebuffer.blit( framebuffer.blit(
black_box(BlitMethod::TransparentFlipped { black_box(BlitMethod::TransparentFlipped {
transparent_color: 0, transparent_color: 0,
horizontal_flip: true, horizontal_flip: true,
vertical_flip: false vertical_flip: false,
}), }),
black_box(&trans_bmp), black_box(&trans_bmp),
black_box(100), black_box(100),
black_box(100), black_box(100),
) )
}) })
}); });
c.bench_function("blit_transparent_flipped_vertically", |b| { c.bench_function("blit_transparent_flipped_vertically", |b| {
b.iter(|| { b.iter(|| {
framebuffer.blit( framebuffer.blit(
black_box(BlitMethod::TransparentFlipped { black_box(BlitMethod::TransparentFlipped {
transparent_color: 0, transparent_color: 0,
horizontal_flip: false, horizontal_flip: false,
vertical_flip: true vertical_flip: true,
}), }),
black_box(&trans_bmp), black_box(&trans_bmp),
black_box(100), black_box(100),
black_box(100), black_box(100),
) )
}) })
}); });
c.bench_function("blit_transparent_flipped_horizontally_and_vertically", |b| { c.bench_function("blit_transparent_flipped_horizontally_and_vertically", |b| {
b.iter(|| { b.iter(|| {
framebuffer.blit( framebuffer.blit(
black_box(BlitMethod::TransparentFlipped { black_box(BlitMethod::TransparentFlipped {
transparent_color: 0, transparent_color: 0,
horizontal_flip: true, horizontal_flip: true,
vertical_flip: true vertical_flip: true,
}), }),
black_box(&trans_bmp), black_box(&trans_bmp),
black_box(100), black_box(100),
black_box(100), black_box(100),
) )
}) })
}); });
////// //////
c.bench_function("blit_transparent_single_flipped_not_flipped", |b| { c.bench_function("blit_transparent_single_flipped_not_flipped", |b| {
b.iter(|| { b.iter(|| {
framebuffer.blit( framebuffer.blit(
black_box(BlitMethod::TransparentFlippedSingle { black_box(BlitMethod::TransparentFlippedSingle {
transparent_color: 0, transparent_color: 0,
horizontal_flip: false, horizontal_flip: false,
vertical_flip: false, vertical_flip: false,
draw_color: 17, draw_color: 17,
}), }),
black_box(&trans_bmp), black_box(&trans_bmp),
black_box(100), black_box(100),
black_box(100), black_box(100),
) )
}) })
}); });
c.bench_function("blit_transparent_single_flipped_horizontally", |b| { c.bench_function("blit_transparent_single_flipped_horizontally", |b| {
b.iter(|| { b.iter(|| {
framebuffer.blit( framebuffer.blit(
black_box(BlitMethod::TransparentFlippedSingle { black_box(BlitMethod::TransparentFlippedSingle {
transparent_color: 0, transparent_color: 0,
horizontal_flip: true, horizontal_flip: true,
vertical_flip: false, vertical_flip: false,
draw_color: 17, draw_color: 17,
}), }),
black_box(&trans_bmp), black_box(&trans_bmp),
black_box(100), black_box(100),
black_box(100), black_box(100),
) )
}) })
}); });
c.bench_function("blit_transparent_single_flipped_vertically", |b| { c.bench_function("blit_transparent_single_flipped_vertically", |b| {
b.iter(|| { b.iter(|| {
framebuffer.blit( framebuffer.blit(
black_box(BlitMethod::TransparentFlippedSingle { black_box(BlitMethod::TransparentFlippedSingle {
transparent_color: 0, transparent_color: 0,
horizontal_flip: false, horizontal_flip: false,
vertical_flip: true, vertical_flip: true,
draw_color: 17, draw_color: 17,
}), }),
black_box(&trans_bmp), black_box(&trans_bmp),
black_box(100), black_box(100),
black_box(100), black_box(100),
) )
}) })
}); });
c.bench_function("blit_transparent_single_flipped_horizontally_and_vertically", |b| { c.bench_function("blit_transparent_single_flipped_horizontally_and_vertically", |b| {
b.iter(|| { b.iter(|| {
framebuffer.blit( framebuffer.blit(
black_box(BlitMethod::TransparentFlippedSingle { black_box(BlitMethod::TransparentFlippedSingle {
transparent_color: 0, transparent_color: 0,
horizontal_flip: true, horizontal_flip: true,
vertical_flip: true, vertical_flip: true,
draw_color: 17, draw_color: 17,
}), }),
black_box(&trans_bmp), black_box(&trans_bmp),
black_box(100), black_box(100),
black_box(100), black_box(100),
) )
}) })
}); });
////// //////
c.bench_function("blit_transparent_single", |b| { c.bench_function("blit_transparent_single", |b| {
b.iter(|| { b.iter(|| {
framebuffer.blit( framebuffer.blit(
black_box(BlitMethod::TransparentSingle { black_box(BlitMethod::TransparentSingle {
transparent_color: 0, transparent_color: 0,
draw_color: 17, draw_color: 17,
}), }),
black_box(&trans_bmp), black_box(&trans_bmp),
black_box(100), black_box(100),
black_box(100), black_box(100),
) )
}) })
}); });
////// //////
c.bench_function("blit_transparent_offset", |b| { c.bench_function("blit_transparent_offset", |b| {
b.iter(|| { b.iter(|| {
framebuffer.blit( framebuffer.blit(
black_box(BlitMethod::TransparentOffset { black_box(BlitMethod::TransparentOffset {
transparent_color: 0, transparent_color: 0,
offset: 42, offset: 42,
}), }),
black_box(&trans_bmp), black_box(&trans_bmp),
black_box(100), black_box(100),
black_box(100), black_box(100),
) )
}) })
}); });
////// //////
c.bench_function("blit_transparent_offset_flipped_not_flipped", |b| { c.bench_function("blit_transparent_offset_flipped_not_flipped", |b| {
b.iter(|| { b.iter(|| {
framebuffer.blit( framebuffer.blit(
black_box(BlitMethod::TransparentFlippedOffset { black_box(BlitMethod::TransparentFlippedOffset {
transparent_color: 0, transparent_color: 0,
horizontal_flip: false, horizontal_flip: false,
vertical_flip: false, vertical_flip: false,
offset: 42, offset: 42,
}), }),
black_box(&trans_bmp), black_box(&trans_bmp),
black_box(100), black_box(100),
black_box(100), black_box(100),
) )
}) })
}); });
c.bench_function("blit_transparent_offset_flipped_horizontally", |b| { c.bench_function("blit_transparent_offset_flipped_horizontally", |b| {
b.iter(|| { b.iter(|| {
framebuffer.blit( framebuffer.blit(
black_box(BlitMethod::TransparentFlippedOffset { black_box(BlitMethod::TransparentFlippedOffset {
transparent_color: 0, transparent_color: 0,
horizontal_flip: true, horizontal_flip: true,
vertical_flip: false, vertical_flip: false,
offset: 42, offset: 42,
}), }),
black_box(&trans_bmp), black_box(&trans_bmp),
black_box(100), black_box(100),
black_box(100), black_box(100),
) )
}) })
}); });
c.bench_function("blit_transparent_offset_flipped_vertically", |b| { c.bench_function("blit_transparent_offset_flipped_vertically", |b| {
b.iter(|| { b.iter(|| {
framebuffer.blit( framebuffer.blit(
black_box(BlitMethod::TransparentFlippedOffset { black_box(BlitMethod::TransparentFlippedOffset {
transparent_color: 0, transparent_color: 0,
horizontal_flip: false, horizontal_flip: false,
vertical_flip: true, vertical_flip: true,
offset: 42, offset: 42,
}), }),
black_box(&trans_bmp), black_box(&trans_bmp),
black_box(100), black_box(100),
black_box(100), black_box(100),
) )
}) })
}); });
c.bench_function("blit_transparent_offset_flipped_horizontally_and_vertically", |b| { c.bench_function("blit_transparent_offset_flipped_horizontally_and_vertically", |b| {
b.iter(|| { b.iter(|| {
framebuffer.blit( framebuffer.blit(
black_box(BlitMethod::TransparentFlippedOffset { black_box(BlitMethod::TransparentFlippedOffset {
transparent_color: 0, transparent_color: 0,
horizontal_flip: true, horizontal_flip: true,
vertical_flip: true, vertical_flip: true,
offset: 42, offset: 42,
}), }),
black_box(&trans_bmp), black_box(&trans_bmp),
black_box(100), black_box(100),
black_box(100), black_box(100),
) )
}) })
}); });
////// //////
c.bench_function("blit_solid_offset", |b| { c.bench_function("blit_solid_offset", |b| {
b.iter(|| { b.iter(|| {
framebuffer.blit( framebuffer.blit(
black_box(BlitMethod::SolidOffset(42)), black_box(BlitMethod::SolidOffset(42)),
black_box(&solid_bmp), black_box(&solid_bmp),
black_box(100), black_box(100),
black_box(100), black_box(100),
) )
}) })
}); });
////// //////
c.bench_function("blit_solid_offset_flipped_not_flipped", |b| { c.bench_function("blit_solid_offset_flipped_not_flipped", |b| {
b.iter(|| { b.iter(|| {
framebuffer.blit( framebuffer.blit(
black_box(BlitMethod::SolidFlippedOffset { black_box(BlitMethod::SolidFlippedOffset {
horizontal_flip: false, horizontal_flip: false,
vertical_flip: false, vertical_flip: false,
offset: 42, offset: 42,
}), }),
black_box(&solid_bmp), black_box(&solid_bmp),
black_box(100), black_box(100),
black_box(100), black_box(100),
) )
}) })
}); });
c.bench_function("blit_solid_offset_flipped_horizontally", |b| { c.bench_function("blit_solid_offset_flipped_horizontally", |b| {
b.iter(|| { b.iter(|| {
framebuffer.blit( framebuffer.blit(
black_box(BlitMethod::SolidFlippedOffset { black_box(BlitMethod::SolidFlippedOffset {
horizontal_flip: true, horizontal_flip: true,
vertical_flip: false, vertical_flip: false,
offset: 42, offset: 42,
}), }),
black_box(&solid_bmp), black_box(&solid_bmp),
black_box(100), black_box(100),
black_box(100), black_box(100),
) )
}) })
}); });
c.bench_function("blit_solid_offset_flipped_vertically", |b| { c.bench_function("blit_solid_offset_flipped_vertically", |b| {
b.iter(|| { b.iter(|| {
framebuffer.blit( framebuffer.blit(
black_box(BlitMethod::SolidFlippedOffset { black_box(BlitMethod::SolidFlippedOffset {
horizontal_flip: false, horizontal_flip: false,
vertical_flip: true, vertical_flip: true,
offset: 42, offset: 42,
}), }),
black_box(&solid_bmp), black_box(&solid_bmp),
black_box(100), black_box(100),
black_box(100), black_box(100),
) )
}) })
}); });
c.bench_function("blit_solid_offset_flipped_horizontally_and_vertically", |b| { c.bench_function("blit_solid_offset_flipped_horizontally_and_vertically", |b| {
b.iter(|| { b.iter(|| {
framebuffer.blit( framebuffer.blit(
black_box(BlitMethod::SolidFlippedOffset { black_box(BlitMethod::SolidFlippedOffset {
horizontal_flip: true, horizontal_flip: true,
vertical_flip: true, vertical_flip: true,
offset: 42, offset: 42,
}), }),
black_box(&solid_bmp), black_box(&solid_bmp),
black_box(100), black_box(100),
black_box(100), black_box(100),
) )
}) })
}); });
////// //////
c.bench_function("blit_rotozoom", |b| { c.bench_function("blit_rotozoom", |b| {
b.iter(|| { b.iter(|| {
framebuffer.blit( framebuffer.blit(
black_box(BlitMethod::RotoZoom { black_box(BlitMethod::RotoZoom {
angle: 73.0f32.to_radians(), angle: 73.0f32.to_radians(),
scale_x: 1.2, scale_x: 1.2,
scale_y: 0.8, scale_y: 0.8,
}), }),
black_box(&solid_bmp), black_box(&solid_bmp),
black_box(100), black_box(100),
black_box(100), black_box(100),
) )
}) })
}); });
////// //////
c.bench_function("blit_rotozoom_offset", |b| { c.bench_function("blit_rotozoom_offset", |b| {
b.iter(|| { b.iter(|| {
framebuffer.blit( framebuffer.blit(
black_box(BlitMethod::RotoZoomOffset { black_box(BlitMethod::RotoZoomOffset {
angle: 73.0f32.to_radians(), angle: 73.0f32.to_radians(),
scale_x: 1.2, scale_x: 1.2,
scale_y: 0.8, scale_y: 0.8,
offset: 42, offset: 42,
}), }),
black_box(&solid_bmp), black_box(&solid_bmp),
black_box(100), black_box(100),
black_box(100), black_box(100),
) )
}) })
}); });
////// //////
c.bench_function("blit_rotozoom_transparent", |b| { c.bench_function("blit_rotozoom_transparent", |b| {
b.iter(|| { b.iter(|| {
framebuffer.blit( framebuffer.blit(
black_box(BlitMethod::RotoZoomTransparent { black_box(BlitMethod::RotoZoomTransparent {
angle: 73.0f32.to_radians(), angle: 73.0f32.to_radians(),
scale_x: 1.2, scale_x: 1.2,
scale_y: 0.8, scale_y: 0.8,
transparent_color: 0, transparent_color: 0,
}), }),
black_box(&trans_bmp), black_box(&trans_bmp),
black_box(100), black_box(100),
black_box(100), black_box(100),
) )
}) })
}); });
////// //////
c.bench_function("blit_rotozoom_offset_transparent", |b| {
b.iter(|| {
framebuffer.blit(
black_box(BlitMethod::RotoZoomTransparentOffset {
angle: 73.0f32.to_radians(),
scale_x: 1.2,
scale_y: 0.8,
transparent_color: 0,
offset: 42,
}),
black_box(&trans_bmp),
black_box(100),
black_box(100),
)
})
});
c.bench_function("blit_rotozoom_offset_transparent", |b| {
b.iter(|| {
framebuffer.blit(
black_box(BlitMethod::RotoZoomTransparentOffset {
angle: 73.0f32.to_radians(),
scale_x: 1.2,
scale_y: 0.8,
transparent_color: 0,
offset: 42,
}),
black_box(&trans_bmp),
black_box(100),
black_box(100),
)
})
});
} }
criterion_group!(benches, criterion_benchmark); criterion_group!(benches, criterion_benchmark);

View file

@ -8,19 +8,19 @@ pub static SMALL_GIF_FILE_BYTES: &[u8] = include_bytes!("../test-assets/test.gif
pub static LARGE_GIF_FILE_BYTES: &[u8] = include_bytes!("../test-assets/test_image.gif"); pub static LARGE_GIF_FILE_BYTES: &[u8] = include_bytes!("../test-assets/test_image.gif");
pub fn criterion_benchmark(c: &mut Criterion) { pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("loading_small_gif", |b| { c.bench_function("loading_small_gif", |b| {
b.iter(|| { b.iter(|| {
let mut reader = Cursor::new(SMALL_GIF_FILE_BYTES); let mut reader = Cursor::new(SMALL_GIF_FILE_BYTES);
Bitmap::load_gif_bytes(black_box(&mut reader)).unwrap(); Bitmap::load_gif_bytes(black_box(&mut reader)).unwrap();
}) })
}); });
c.bench_function("loading_large_gif", |b| { c.bench_function("loading_large_gif", |b| {
b.iter(|| { b.iter(|| {
let mut reader = Cursor::new(LARGE_GIF_FILE_BYTES); let mut reader = Cursor::new(LARGE_GIF_FILE_BYTES);
Bitmap::load_gif_bytes(black_box(&mut reader)).unwrap(); Bitmap::load_gif_bytes(black_box(&mut reader)).unwrap();
}) })
}); });
} }
criterion_group!(benches, criterion_benchmark); criterion_group!(benches, criterion_benchmark);

View file

@ -6,64 +6,64 @@ pub mod wav;
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum AudioBufferError { pub enum AudioBufferError {
#[error("Error during format conversion: {0}")] #[error("Error during format conversion: {0}")]
ConversionError(String), ConversionError(String),
} }
/// Holds audio sample data that can be played via [`AudioDevice`]. /// Holds audio sample data that can be played via [`AudioDevice`].
#[derive(Clone, Eq, PartialEq)] #[derive(Clone, Eq, PartialEq)]
pub struct AudioBuffer { pub struct AudioBuffer {
spec: AudioSpec, spec: AudioSpec,
pub data: Vec<u8>, pub data: Vec<u8>,
} }
impl std::fmt::Debug for AudioBuffer { impl std::fmt::Debug for AudioBuffer {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AudioBuffer") f.debug_struct("AudioBuffer")
.field("spec", &self.spec) .field("spec", &self.spec)
.field("data.len()", &self.data.len()) .field("data.len()", &self.data.len())
.finish_non_exhaustive() .finish_non_exhaustive()
} }
} }
impl AudioBuffer { impl AudioBuffer {
/// Creates and returns a new, empty, [`AudioBuffer`] that will hold audio sample data in the /// Creates and returns a new, empty, [`AudioBuffer`] that will hold audio sample data in the
/// spec/format given. /// spec/format given.
pub fn new(spec: AudioSpec) -> Self { pub fn new(spec: AudioSpec) -> Self {
AudioBuffer { AudioBuffer {
spec, spec,
data: Vec::new(), data: Vec::new(),
} }
} }
/// Returns the spec of the audio sample data that this buffer contains. /// Returns the spec of the audio sample data that this buffer contains.
#[inline] #[inline]
pub fn spec(&self) -> &AudioSpec { pub fn spec(&self) -> &AudioSpec {
&self.spec &self.spec
} }
/// Converts the audio sample data in this buffer to the spec given, returning the newly /// Converts the audio sample data in this buffer to the spec given, returning the newly
/// converted buffer. /// converted buffer.
pub fn convert(self, to_spec: &AudioSpec) -> Result<Self, AudioBufferError> { pub fn convert(self, to_spec: &AudioSpec) -> Result<Self, AudioBufferError> {
if self.spec == *to_spec { if self.spec == *to_spec {
Ok(self) Ok(self)
} else { } else {
let converter = sdl2::audio::AudioCVT::new( let converter = sdl2::audio::AudioCVT::new(
self.spec.format(), self.spec.format(),
self.spec.channels(), self.spec.channels(),
self.spec.frequency() as i32, self.spec.frequency() as i32,
to_spec.format(), to_spec.format(),
to_spec.channels(), to_spec.channels(),
to_spec.frequency() as i32, to_spec.frequency() as i32,
); );
match converter { match converter {
Ok(converter) => { Ok(converter) => {
let mut result = AudioBuffer::new(*to_spec); let mut result = AudioBuffer::new(*to_spec);
result.data = converter.convert(self.data); result.data = converter.convert(self.data);
Ok(result) Ok(result)
} }
Err(string) => Err(AudioBufferError::ConversionError(string)), Err(string) => Err(AudioBufferError::ConversionError(string)),
} }
} }
} }
} }

View file

@ -12,322 +12,322 @@ use crate::utils::io::StreamSize;
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum WavError { pub enum WavError {
#[error("Bad or unsupported WAV file: {0}")] #[error("Bad or unsupported WAV file: {0}")]
BadFile(String), BadFile(String),
#[error("WAV I/O error")] #[error("WAV I/O error")]
IOError(#[from] std::io::Error), IOError(#[from] std::io::Error),
} }
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
struct ChunkId { struct ChunkId {
id: [u8; 4], id: [u8; 4],
} }
impl ChunkId { impl ChunkId {
pub fn read<T: Read>(reader: &mut T) -> Result<Self, WavError> { pub fn read<T: Read>(reader: &mut T) -> Result<Self, WavError> {
let mut id = [0u8; 4]; let mut id = [0u8; 4];
reader.read_exact(&mut id)?; reader.read_exact(&mut id)?;
Ok(ChunkId { id }) Ok(ChunkId { id })
} }
#[allow(dead_code)] #[allow(dead_code)]
pub fn write<T: Write>(&self, writer: &mut T) -> Result<(), WavError> { pub fn write<T: Write>(&self, writer: &mut T) -> Result<(), WavError> {
writer.write_all(&self.id)?; writer.write_all(&self.id)?;
Ok(()) Ok(())
} }
} }
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
struct SubChunkHeader { struct SubChunkHeader {
chunk_id: ChunkId, chunk_id: ChunkId,
size: u32, size: u32,
} }
impl SubChunkHeader { impl SubChunkHeader {
pub fn read<T: ReadBytesExt>(reader: &mut T) -> Result<Self, WavError> { pub fn read<T: ReadBytesExt>(reader: &mut T) -> Result<Self, WavError> {
let chunk_id = ChunkId::read(reader)?; let chunk_id = ChunkId::read(reader)?;
let size = reader.read_u32::<LittleEndian>()?; let size = reader.read_u32::<LittleEndian>()?;
Ok(SubChunkHeader { chunk_id, size }) Ok(SubChunkHeader { chunk_id, size })
} }
#[allow(dead_code)] #[allow(dead_code)]
pub fn write<T: WriteBytesExt>(&self, writer: &mut T) -> Result<(), WavError> { pub fn write<T: WriteBytesExt>(&self, writer: &mut T) -> Result<(), WavError> {
self.chunk_id.write(writer)?; self.chunk_id.write(writer)?;
writer.write_u32::<LittleEndian>(self.size)?; writer.write_u32::<LittleEndian>(self.size)?;
Ok(()) Ok(())
} }
} }
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
struct WavHeader { struct WavHeader {
file_chunk: SubChunkHeader, file_chunk: SubChunkHeader,
file_container_id: ChunkId, file_container_id: ChunkId,
} }
impl WavHeader { impl WavHeader {
pub fn read<T: ReadBytesExt>(reader: &mut T) -> Result<Self, WavError> { pub fn read<T: ReadBytesExt>(reader: &mut T) -> Result<Self, WavError> {
let file_chunk = SubChunkHeader::read(reader)?; let file_chunk = SubChunkHeader::read(reader)?;
let file_container_id = ChunkId::read(reader)?; let file_container_id = ChunkId::read(reader)?;
Ok(WavHeader { Ok(WavHeader {
file_chunk, file_chunk,
file_container_id, file_container_id,
}) })
} }
#[allow(dead_code)] #[allow(dead_code)]
pub fn write<T: WriteBytesExt>(&self, writer: &mut T) -> Result<(), WavError> { pub fn write<T: WriteBytesExt>(&self, writer: &mut T) -> Result<(), WavError> {
self.file_chunk.write(writer)?; self.file_chunk.write(writer)?;
self.file_container_id.write(writer)?; self.file_container_id.write(writer)?;
Ok(()) Ok(())
} }
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
#[allow(dead_code)] #[allow(dead_code)]
struct FormatChunk { struct FormatChunk {
compression_code: u16, compression_code: u16,
channels: u16, channels: u16,
frequency: u32, frequency: u32,
bytes_per_second: u32, bytes_per_second: u32,
block_alignment: u16, block_alignment: u16,
bits_per_sample: u16, bits_per_sample: u16,
additional_data_length: u16, additional_data_length: u16,
additional_data: Option<Box<[u8]>>, additional_data: Option<Box<[u8]>>,
} }
impl FormatChunk { impl FormatChunk {
pub fn read<T: ReadBytesExt>( pub fn read<T: ReadBytesExt>(
reader: &mut T, reader: &mut T,
chunk_header: &SubChunkHeader, chunk_header: &SubChunkHeader,
) -> Result<Self, WavError> { ) -> Result<Self, WavError> {
let compression_code = reader.read_u16::<LittleEndian>()?; let compression_code = reader.read_u16::<LittleEndian>()?;
let channels = reader.read_u16::<LittleEndian>()?; let channels = reader.read_u16::<LittleEndian>()?;
let frequency = reader.read_u32::<LittleEndian>()?; let frequency = reader.read_u32::<LittleEndian>()?;
let bytes_per_second = reader.read_u32::<LittleEndian>()?; let bytes_per_second = reader.read_u32::<LittleEndian>()?;
let block_alignment = reader.read_u16::<LittleEndian>()?; let block_alignment = reader.read_u16::<LittleEndian>()?;
let bits_per_sample = reader.read_u16::<LittleEndian>()?; let bits_per_sample = reader.read_u16::<LittleEndian>()?;
let additional_data_length; let additional_data_length;
let additional_data; let additional_data;
if chunk_header.size > 16 { if chunk_header.size > 16 {
additional_data_length = reader.read_u16::<LittleEndian>()?; additional_data_length = reader.read_u16::<LittleEndian>()?;
let mut buffer = vec![0u8; additional_data_length as usize]; let mut buffer = vec![0u8; additional_data_length as usize];
reader.read(&mut buffer)?; reader.read(&mut buffer)?;
additional_data = Some(buffer.into_boxed_slice()); additional_data = Some(buffer.into_boxed_slice());
} else { } else {
additional_data_length = 0; additional_data_length = 0;
additional_data = None; additional_data = None;
} }
Ok(FormatChunk { Ok(FormatChunk {
compression_code, compression_code,
channels, channels,
frequency, frequency,
bytes_per_second, bytes_per_second,
block_alignment, block_alignment,
bits_per_sample, bits_per_sample,
additional_data_length, additional_data_length,
additional_data, additional_data,
}) })
} }
#[allow(dead_code)] #[allow(dead_code)]
pub fn write<T: WriteBytesExt>(&self, writer: &mut T) -> Result<(), WavError> { pub fn write<T: WriteBytesExt>(&self, writer: &mut T) -> Result<(), WavError> {
writer.write_u16::<LittleEndian>(self.compression_code)?; writer.write_u16::<LittleEndian>(self.compression_code)?;
writer.write_u16::<LittleEndian>(self.channels)?; writer.write_u16::<LittleEndian>(self.channels)?;
writer.write_u32::<LittleEndian>(self.frequency)?; writer.write_u32::<LittleEndian>(self.frequency)?;
writer.write_u32::<LittleEndian>(self.bytes_per_second)?; writer.write_u32::<LittleEndian>(self.bytes_per_second)?;
writer.write_u16::<LittleEndian>(self.block_alignment)?; writer.write_u16::<LittleEndian>(self.block_alignment)?;
writer.write_u16::<LittleEndian>(self.bits_per_sample)?; writer.write_u16::<LittleEndian>(self.bits_per_sample)?;
if self.additional_data_length > 0 { if self.additional_data_length > 0 {
writer.write_u16::<LittleEndian>(self.additional_data_length)?; writer.write_u16::<LittleEndian>(self.additional_data_length)?;
writer.write_all(&self.additional_data.as_ref().unwrap())?; writer.write_all(&self.additional_data.as_ref().unwrap())?;
} }
Ok(()) Ok(())
} }
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct DataChunk { struct DataChunk {
data: Box<[u8]>, data: Box<[u8]>,
} }
impl DataChunk { impl DataChunk {
pub fn read<T: ReadBytesExt>( pub fn read<T: ReadBytesExt>(
reader: &mut T, reader: &mut T,
chunk_header: &SubChunkHeader, chunk_header: &SubChunkHeader,
is_probably_naive_file: bool, is_probably_naive_file: bool,
) -> Result<Self, WavError> { ) -> Result<Self, WavError> {
let mut buffer; let mut buffer;
if is_probably_naive_file { if is_probably_naive_file {
// in this scenario, we have doubts about the chunk size being recorded correctly // in this scenario, we have doubts about the chunk size being recorded correctly
// (the tool that created this file may have used a buggy calculation). we assume that // (the tool that created this file may have used a buggy calculation). we assume that
// in this case, this wav file is probably written in a "naive" manner and likely only // in this case, this wav file is probably written in a "naive" manner and likely only
// contains "fmt" and "data" chunks with the "data" chunk being at the end of the file. // contains "fmt" and "data" chunks with the "data" chunk being at the end of the file.
// if this assumption is correct, then we can just read everything until EOF here as // if this assumption is correct, then we can just read everything until EOF here as
// the "data" chunk contents and that should be ok (assuming this file isn't corrupt // the "data" chunk contents and that should be ok (assuming this file isn't corrupt
// anyway). // anyway).
buffer = Vec::new(); buffer = Vec::new();
reader.read_to_end(&mut buffer)?; reader.read_to_end(&mut buffer)?;
} else { } else {
// alternatively, this scenario means we are assuming the file was written out more // alternatively, this scenario means we are assuming the file was written out more
// properly and we will assume the chunk size is correct and read that many bytes. // properly and we will assume the chunk size is correct and read that many bytes.
// this is best if there is the possibility that there are more chunks than just "fmt" // this is best if there is the possibility that there are more chunks than just "fmt"
// and "data" in this wav file and maybe they are in a different order, etc. // and "data" in this wav file and maybe they are in a different order, etc.
// it is important to note that this seems to be an uncommon case for wav files. most // it is important to note that this seems to be an uncommon case for wav files. most
// wav files seem to be written in a fairly "naive" manner. // wav files seem to be written in a fairly "naive" manner.
buffer = vec![0u8; chunk_header.size as usize]; buffer = vec![0u8; chunk_header.size as usize];
reader.read_exact(&mut buffer)?; reader.read_exact(&mut buffer)?;
} }
Ok(DataChunk { Ok(DataChunk {
data: buffer.into_boxed_slice(), data: buffer.into_boxed_slice(),
}) })
} }
#[allow(dead_code)] #[allow(dead_code)]
pub fn write<T: WriteBytesExt>(&self, writer: &mut T) -> Result<(), WavError> { pub fn write<T: WriteBytesExt>(&self, writer: &mut T) -> Result<(), WavError> {
writer.write_all(self.data.as_ref())?; writer.write_all(self.data.as_ref())?;
Ok(()) Ok(())
} }
} }
impl AudioBuffer { impl AudioBuffer {
/// Loads the bytes of a WAV file into an [`AudioBuffer`]. The returned buffer will be in its /// Loads the bytes of a WAV file into an [`AudioBuffer`]. The returned buffer will be in its
/// original format and may need to be converted before it can be played. /// original format and may need to be converted before it can be played.
pub fn load_wav_bytes<T: ReadBytesExt + Seek>(reader: &mut T) -> Result<AudioBuffer, WavError> { pub fn load_wav_bytes<T: ReadBytesExt + Seek>(reader: &mut T) -> Result<AudioBuffer, WavError> {
let file_size = reader.stream_size()?; let file_size = reader.stream_size()?;
let header = WavHeader::read(reader)?; let header = WavHeader::read(reader)?;
if header.file_chunk.chunk_id.id != *b"RIFF" { if header.file_chunk.chunk_id.id != *b"RIFF" {
return Err(WavError::BadFile(String::from( return Err(WavError::BadFile(String::from(
"Unexpected RIFF chunk id, probably not a WAV file", "Unexpected RIFF chunk id, probably not a WAV file",
))); )));
} }
if header.file_container_id.id != *b"WAVE" { if header.file_container_id.id != *b"WAVE" {
return Err(WavError::BadFile(String::from( return Err(WavError::BadFile(String::from(
"Unexpected RIFF container id, probably not a WAV file", "Unexpected RIFF container id, probably not a WAV file",
))); )));
} }
// some tools like sfxr and jsfxr incorrectly calculate data sizes, seemingly using a // some tools like sfxr and jsfxr incorrectly calculate data sizes, seemingly using a
// hardcoded 16-bit sample size in their calculations even when the file being created has // hardcoded 16-bit sample size in their calculations even when the file being created has
// 8-bit sample sizes. this means the chunk size here and the "data" chunk size will be // 8-bit sample sizes. this means the chunk size here and the "data" chunk size will be
// larger than they should be for the _actual_ data present in the file. // larger than they should be for the _actual_ data present in the file.
// of course, if the chunk size here is wrong, it could also mean a corrupt file. but we'll // of course, if the chunk size here is wrong, it could also mean a corrupt file. but we'll
// proceed regardless, with the assumption that an incorrect size here probably means that // proceed regardless, with the assumption that an incorrect size here probably means that
// the file was created using these semi-broken tools and should act accordingly later on. // the file was created using these semi-broken tools and should act accordingly later on.
// if the file is actually corrupt (maybe truncated accidentally or something), then we'll // if the file is actually corrupt (maybe truncated accidentally or something), then we'll
// hit an EOF earlier than expected somewhere too ... // hit an EOF earlier than expected somewhere too ...
let is_probably_naive_file = file_size - 8 != header.file_chunk.size as u64; let is_probably_naive_file = file_size - 8 != header.file_chunk.size as u64;
let mut format: Option<FormatChunk> = None; let mut format: Option<FormatChunk> = None;
let mut data: Option<DataChunk> = None; let mut data: Option<DataChunk> = None;
loop { loop {
let chunk_header = match SubChunkHeader::read(reader) { let chunk_header = match SubChunkHeader::read(reader) {
Ok(header) => header, Ok(header) => header,
Err(WavError::IOError(io_error)) Err(WavError::IOError(io_error))
if io_error.kind() == io::ErrorKind::UnexpectedEof => if io_error.kind() == io::ErrorKind::UnexpectedEof =>
{ {
break; break;
} }
Err(err) => return Err(err), Err(err) => return Err(err),
}; };
let chunk_data_position = reader.stream_position()?; let chunk_data_position = reader.stream_position()?;
// read only the chunks we recognize / care about // read only the chunks we recognize / care about
if chunk_header.chunk_id.id == *b"fmt " { if chunk_header.chunk_id.id == *b"fmt " {
format = Some(FormatChunk::read(reader, &chunk_header)?); format = Some(FormatChunk::read(reader, &chunk_header)?);
if format.as_ref().unwrap().compression_code != 1 { if format.as_ref().unwrap().compression_code != 1 {
return Err(WavError::BadFile(String::from( return Err(WavError::BadFile(String::from(
"Only PCM format WAV files are supported", "Only PCM format WAV files are supported",
))); )));
} }
if format.as_ref().unwrap().bits_per_sample != 8 && if format.as_ref().unwrap().bits_per_sample != 8 &&
format.as_ref().unwrap().bits_per_sample != 16 { format.as_ref().unwrap().bits_per_sample != 16 {
return Err(WavError::BadFile(String::from( return Err(WavError::BadFile(String::from(
"Only 8-bit and 16-bit sample WAV files are supported", "Only 8-bit and 16-bit sample WAV files are supported",
))); )));
} }
} else if chunk_header.chunk_id.id == *b"data" { } else if chunk_header.chunk_id.id == *b"data" {
data = Some(DataChunk::read(reader, &chunk_header, is_probably_naive_file)?); data = Some(DataChunk::read(reader, &chunk_header, is_probably_naive_file)?);
} }
// move to the start of the next chunk (possibly skipping over the current chunk if we // move to the start of the next chunk (possibly skipping over the current chunk if we
// didn't recognize it above ...) // didn't recognize it above ...)
reader.seek(SeekFrom::Start( reader.seek(SeekFrom::Start(
chunk_data_position + chunk_header.size as u64, chunk_data_position + chunk_header.size as u64,
))?; ))?;
} }
// all done reading the file, now convert the read data into an AudioBuffer ... // all done reading the file, now convert the read data into an AudioBuffer ...
let mut audio_buffer; let mut audio_buffer;
if let Some(format) = format { if let Some(format) = format {
let sample_format = match format.bits_per_sample { let sample_format = match format.bits_per_sample {
8 => AudioFormat::U8, 8 => AudioFormat::U8,
16 => AudioFormat::S16LSB, 16 => AudioFormat::S16LSB,
// this shouldn't be able to happen given the above checks when reading the // this shouldn't be able to happen given the above checks when reading the
// "fmt" chunk // "fmt" chunk
_ => return Err(WavError::BadFile(String::from("Unsupported sample bit size."))) _ => return Err(WavError::BadFile(String::from("Unsupported sample bit size.")))
}; };
let spec = AudioSpec::new(format.frequency, format.channels as u8, sample_format); let spec = AudioSpec::new(format.frequency, format.channels as u8, sample_format);
audio_buffer = AudioBuffer::new(spec); audio_buffer = AudioBuffer::new(spec);
} else { } else {
return Err(WavError::BadFile(String::from("No 'fmt ' chunk was found"))); return Err(WavError::BadFile(String::from("No 'fmt ' chunk was found")));
} }
if let Some(data) = data { if let Some(data) = data {
audio_buffer.data = data.data.into_vec(); audio_buffer.data = data.data.into_vec();
} else { } else {
return Err(WavError::BadFile(String::from("No 'data' chunk was found"))); return Err(WavError::BadFile(String::from("No 'data' chunk was found")));
} }
Ok(audio_buffer) Ok(audio_buffer)
} }
/// Loads a WAV file into an [`AudioBuffer`]. The returned buffer will be in its original /// Loads a WAV file into an [`AudioBuffer`]. The returned buffer will be in its original
/// format and may need to be converted before it can be played. /// format and may need to be converted before it can be played.
pub fn load_wav_file(path: &Path) -> Result<AudioBuffer, WavError> { pub fn load_wav_file(path: &Path) -> Result<AudioBuffer, WavError> {
let f = File::open(path)?; let f = File::open(path)?;
let mut reader = BufReader::new(f); let mut reader = BufReader::new(f);
Self::load_wav_bytes(&mut reader) Self::load_wav_bytes(&mut reader)
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::audio::*; use crate::audio::*;
use super::*; use super::*;
#[test] #[test]
pub fn load_wav_file() -> Result<(), WavError> { pub fn load_wav_file() -> Result<(), WavError> {
let wav_buffer = AudioBuffer::load_wav_file(Path::new("./test-assets/22khz_8bit_1ch.wav"))?; let wav_buffer = AudioBuffer::load_wav_file(Path::new("./test-assets/22khz_8bit_1ch.wav"))?;
assert_eq!(AUDIO_FREQUENCY_22KHZ, wav_buffer.spec().frequency()); assert_eq!(AUDIO_FREQUENCY_22KHZ, wav_buffer.spec().frequency());
assert_eq!(1, wav_buffer.spec().channels()); assert_eq!(1, wav_buffer.spec().channels());
assert_eq!(AudioFormat::U8, wav_buffer.spec.format); assert_eq!(AudioFormat::U8, wav_buffer.spec.format);
assert_eq!(11248, wav_buffer.data.len()); assert_eq!(11248, wav_buffer.data.len());
let wav_buffer = AudioBuffer::load_wav_file(Path::new("./test-assets/44khz_8bit_1ch.wav"))?; let wav_buffer = AudioBuffer::load_wav_file(Path::new("./test-assets/44khz_8bit_1ch.wav"))?;
assert_eq!(AUDIO_FREQUENCY_44KHZ, wav_buffer.spec().frequency()); assert_eq!(AUDIO_FREQUENCY_44KHZ, wav_buffer.spec().frequency());
assert_eq!(1, wav_buffer.spec().channels()); assert_eq!(1, wav_buffer.spec().channels());
assert_eq!(AudioFormat::U8, wav_buffer.spec.format); assert_eq!(AudioFormat::U8, wav_buffer.spec.format);
assert_eq!(22496, wav_buffer.data.len()); assert_eq!(22496, wav_buffer.data.len());
let wav_buffer = AudioBuffer::load_wav_file(Path::new("./test-assets/22khz_16bit_1ch.wav"))?; let wav_buffer = AudioBuffer::load_wav_file(Path::new("./test-assets/22khz_16bit_1ch.wav"))?;
assert_eq!(AUDIO_FREQUENCY_22KHZ, wav_buffer.spec().frequency()); assert_eq!(AUDIO_FREQUENCY_22KHZ, wav_buffer.spec().frequency());
assert_eq!(1, wav_buffer.spec().channels()); assert_eq!(1, wav_buffer.spec().channels());
assert_eq!(AudioFormat::S16LSB, wav_buffer.spec.format); assert_eq!(AudioFormat::S16LSB, wav_buffer.spec.format);
assert_eq!(22496, wav_buffer.data.len()); assert_eq!(22496, wav_buffer.data.len());
let wav_buffer = AudioBuffer::load_wav_file(Path::new("./test-assets/44khz_16bit_1ch.wav"))?; let wav_buffer = AudioBuffer::load_wav_file(Path::new("./test-assets/44khz_16bit_1ch.wav"))?;
assert_eq!(AUDIO_FREQUENCY_44KHZ, wav_buffer.spec().frequency()); assert_eq!(AUDIO_FREQUENCY_44KHZ, wav_buffer.spec().frequency());
assert_eq!(1, wav_buffer.spec().channels()); assert_eq!(1, wav_buffer.spec().channels());
assert_eq!(AudioFormat::S16LSB, wav_buffer.spec.format); assert_eq!(AudioFormat::S16LSB, wav_buffer.spec.format);
assert_eq!(44992, wav_buffer.data.len()); assert_eq!(44992, wav_buffer.data.len());
Ok(()) Ok(())
} }
} }

View file

@ -8,180 +8,180 @@ use crate::audio::*;
/// Represents a "channel" of audio playback that will be mixed together with all of the other /// Represents a "channel" of audio playback that will be mixed together with all of the other
/// actively playing audio channels to get the final audio playback. /// actively playing audio channels to get the final audio playback.
pub struct AudioChannel { pub struct AudioChannel {
/// Whether the channel is currently playing or not. /// Whether the channel is currently playing or not.
pub playing: bool, pub playing: bool,
/// Whether this channel is playing on a loop or not. If not, once the end of the [`data`] /// Whether this channel is playing on a loop or not. If not, once the end of the [`data`]
/// buffer is reached, or the [`AudioGenerator::gen_sample`] method returns `None`, playback /// buffer is reached, or the [`AudioGenerator::gen_sample`] method returns `None`, playback
/// on this channel will automatically stop and [`playing`] will be changed to `false`. /// on this channel will automatically stop and [`playing`] will be changed to `false`.
pub loops: bool, pub loops: bool,
/// The audio data buffer (samples) that this channel will play from, **only** if [`generator`] /// The audio data buffer (samples) that this channel will play from, **only** if [`generator`]
/// is `None`. /// is `None`.
pub data: Vec<u8>, pub data: Vec<u8>,
/// An [`AudioGenerator`] instance that will be used to dynamically generate audio data to play /// An [`AudioGenerator`] instance that will be used to dynamically generate audio data to play
/// on this channel _instead of_ playing from [`data`]. Set this to `None` to play from audio /// on this channel _instead of_ playing from [`data`]. Set this to `None` to play from audio
/// data in [`data`] instead. /// data in [`data`] instead.
pub generator: Option<Box<dyn AudioGenerator>>, pub generator: Option<Box<dyn AudioGenerator>>,
/// The volume level to play this channel at. 1.0 is "normal", 0.0 is completely silent. /// The volume level to play this channel at. 1.0 is "normal", 0.0 is completely silent.
pub volume: f32, pub volume: f32,
/// The current playback position (index). 0 is the start of playback. The end position is /// The current playback position (index). 0 is the start of playback. The end position is
/// either the (current) size of the [`data`] buffer or dependant on the implementation of this /// either the (current) size of the [`data`] buffer or dependant on the implementation of this
/// channel's current [`generator`] if not `None`. /// channel's current [`generator`] if not `None`.
pub position: usize, pub position: usize,
} }
impl std::fmt::Debug for AudioChannel { impl std::fmt::Debug for AudioChannel {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AudioChannel") f.debug_struct("AudioChannel")
.field("playing", &self.playing) .field("playing", &self.playing)
.field("loops", &self.loops) .field("loops", &self.loops)
.field("data.len()", &self.data.len()) .field("data.len()", &self.data.len())
.field("generator", match self.generator { .field("generator", match self.generator {
Some(..) => &"Some(..)", Some(..) => &"Some(..)",
None => &"None", None => &"None",
}) })
.field("volume", &self.volume) .field("volume", &self.volume)
.field("position", &self.position) .field("position", &self.position)
.finish_non_exhaustive() .finish_non_exhaustive()
} }
} }
impl AudioChannel { impl AudioChannel {
pub fn new() -> Self { pub fn new() -> Self {
AudioChannel { AudioChannel {
playing: false, playing: false,
loops: false, loops: false,
volume: 1.0, volume: 1.0,
position: 0, position: 0,
generator: None, generator: None,
data: Vec::new(), data: Vec::new(),
} }
} }
/// Returns the audio sample for the given position, or `None` if that position is invalid. /// Returns the audio sample for the given position, or `None` if that position is invalid.
#[inline] #[inline]
fn data_at(&mut self, position: usize) -> Option<u8> { fn data_at(&mut self, position: usize) -> Option<u8> {
if let Some(generator) = &mut self.generator { if let Some(generator) = &mut self.generator {
generator.gen_sample(position) generator.gen_sample(position)
} else { } else {
self.data.get(position).copied() self.data.get(position).copied()
} }
} }
/// Returns the next sample from this channel's buffer. If this channel's buffer is done /// Returns the next sample from this channel's buffer. If this channel's buffer is done
/// playing or there is no buffer data at all, `None` is returned. If the next sample was /// playing or there is no buffer data at all, `None` is returned. If the next sample was
/// successfully loaded from the buffer, the channel's current position is advanced by 1. /// successfully loaded from the buffer, the channel's current position is advanced by 1.
/// ///
/// The returned sample will be a byte value, but in an `i16` with the buffer's original `u8` /// The returned sample will be a byte value, but in an `i16` with the buffer's original `u8`
/// value centered around 0 (meaning the returned sample will be within the range -128 to 127 /// value centered around 0 (meaning the returned sample will be within the range -128 to 127
/// instead of 0 to 255). /// instead of 0 to 255).
#[inline] #[inline]
fn next_sample(&mut self) -> Option<i16> { fn next_sample(&mut self) -> Option<i16> {
if let Some(sample) = self.data_at(self.position) { if let Some(sample) = self.data_at(self.position) {
self.position += 1; self.position += 1;
Some(sample as i16 - 128) Some(sample as i16 - 128)
} else { } else {
None None
} }
} }
/// Samples the channel's current audio buffer, advancing the position within that buffer by 1. /// Samples the channel's current audio buffer, advancing the position within that buffer by 1.
/// The channel will automatically stop playing when the end of the buffer is reached and if /// The channel will automatically stop playing when the end of the buffer is reached and if
/// the channel is not set to loop. `None` is returned if no data can be read from the buffer /// the channel is not set to loop. `None` is returned if no data can be read from the buffer
/// for any reason, or if the channel is not currently playing. /// for any reason, or if the channel is not currently playing.
/// ///
/// The returned sample will be a byte value, but in an `i16` with the buffer's original `u8` /// The returned sample will be a byte value, but in an `i16` with the buffer's original `u8`
/// value centered around 0 (meaning the returned sample will be within the range -128 to 127 /// value centered around 0 (meaning the returned sample will be within the range -128 to 127
/// instead of 0 to 255). /// instead of 0 to 255).
#[inline] #[inline]
pub fn sample(&mut self) -> Option<i16> { pub fn sample(&mut self) -> Option<i16> {
if !self.playing { if !self.playing {
return None; return None;
} }
if let Some(sample) = self.next_sample() { if let Some(sample) = self.next_sample() {
Some((sample as f32 * self.volume) as i16) Some((sample as f32 * self.volume) as i16)
} else { } else {
if self.loops { if self.loops {
self.position = 0; self.position = 0;
None None
} else { } else {
self.stop(); self.stop();
None None
} }
} }
} }
/// Resets the audio channel to a "blank slate", clearing the audio buffer, setting no current /// Resets the audio channel to a "blank slate", clearing the audio buffer, setting no current
/// audio generator, and turning playback off. /// audio generator, and turning playback off.
#[inline] #[inline]
pub fn reset(&mut self) { pub fn reset(&mut self) {
self.data.clear(); self.data.clear();
self.generator = None; self.generator = None;
self.position = 0; self.position = 0;
self.playing = false; self.playing = false;
} }
/// Copies the data from the given audio buffer into this channel's buffer (clearing it first, /// Copies the data from the given audio buffer into this channel's buffer (clearing it first,
/// and extending the size of the buffer if necessary) and then begins playback from position 0. /// and extending the size of the buffer if necessary) and then begins playback from position 0.
/// This also sets the associated [`generator`] to `None`. /// This also sets the associated [`generator`] to `None`.
#[inline] #[inline]
pub fn play_buffer(&mut self, buffer: &AudioBuffer, loops: bool) { pub fn play_buffer(&mut self, buffer: &AudioBuffer, loops: bool) {
self.data.clear(); self.data.clear();
self.data.extend(&buffer.data); self.data.extend(&buffer.data);
self.generator = None; self.generator = None;
self.position = 0; self.position = 0;
self.playing = true; self.playing = true;
self.loops = loops; self.loops = loops;
} }
/// Begins playback on this channel from the given [`AudioGenerator`] instance from position 0. /// Begins playback on this channel from the given [`AudioGenerator`] instance from position 0.
/// This also clears the existing audio buffer contents. /// This also clears the existing audio buffer contents.
#[inline] #[inline]
pub fn play_generator(&mut self, generator: Box<dyn AudioGenerator>, loops: bool) { pub fn play_generator(&mut self, generator: Box<dyn AudioGenerator>, loops: bool) {
self.data.clear(); self.data.clear();
self.generator = Some(generator); self.generator = Some(generator);
self.position = 0; self.position = 0;
self.playing = true; self.playing = true;
self.loops = loops; self.loops = loops;
} }
/// Returns true if this channel has something that can be played back currently. /// Returns true if this channel has something that can be played back currently.
#[inline] #[inline]
pub fn is_playable(&self) -> bool { pub fn is_playable(&self) -> bool {
!self.data.is_empty() || self.generator.is_some() !self.data.is_empty() || self.generator.is_some()
} }
/// Begins playback on this channel, only if playback is currently possible with its current /// Begins playback on this channel, only if playback is currently possible with its current
/// state (if it has some sample data in the buffer or if an [`AudioGenerator`] is set). /// state (if it has some sample data in the buffer or if an [`AudioGenerator`] is set).
/// Resets the position to 0 if playback is started and returns true, otherwise returns false. /// Resets the position to 0 if playback is started and returns true, otherwise returns false.
#[inline] #[inline]
pub fn play(&mut self, loops: bool) -> bool { pub fn play(&mut self, loops: bool) -> bool {
if self.is_playable() { if self.is_playable() {
self.position = 0; self.position = 0;
self.playing = true; self.playing = true;
self.loops = loops; self.loops = loops;
true true
} else { } else {
false false
} }
} }
/// Stops playback on this channel. /// Stops playback on this channel.
#[inline] #[inline]
pub fn stop(&mut self) { pub fn stop(&mut self) {
self.playing = false; self.playing = false;
} }
} }
////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum AudioDeviceError { pub enum AudioDeviceError {
#[error("That buffer's AudioSpec does not match the device's AudioSpec")] #[error("That buffer's AudioSpec does not match the device's AudioSpec")]
AudioSpecMismatch, AudioSpecMismatch,
#[error("The channel index {0} is out of range")] #[error("The channel index {0} is out of range")]
ChannelIndexOutOfRange(usize), ChannelIndexOutOfRange(usize),
} }
/// Represents the audio device and performs mixing of all of the [`AudioChannel`]s that are /// Represents the audio device and performs mixing of all of the [`AudioChannel`]s that are
@ -189,214 +189,214 @@ pub enum AudioDeviceError {
/// [`Audio::lock`]. /// [`Audio::lock`].
#[derive(Debug)] #[derive(Debug)]
pub struct AudioDevice { pub struct AudioDevice {
spec: AudioSpec, spec: AudioSpec,
channels: Vec<AudioChannel>, channels: Vec<AudioChannel>,
pub volume: f32, pub volume: f32,
} }
/// SDL audio callback implementation which performs audio mixing, generating the final sample data /// SDL audio callback implementation which performs audio mixing, generating the final sample data
/// that will be played by the system's audio device. /// that will be played by the system's audio device.
impl AudioCallback for AudioDevice { impl AudioCallback for AudioDevice {
type Channel = u8; type Channel = u8;
fn callback(&mut self, out: &mut [u8]) { fn callback(&mut self, out: &mut [u8]) {
for dest in out.iter_mut() { for dest in out.iter_mut() {
let mut sample: i16 = 0; let mut sample: i16 = 0;
for channel in self.channels.iter_mut() { for channel in self.channels.iter_mut() {
if let Some(this_sample) = channel.sample() { if let Some(this_sample) = channel.sample() {
sample += this_sample; sample += this_sample;
} }
} }
sample = ((sample as f32) * self.volume) as i16; sample = ((sample as f32) * self.volume) as i16;
*dest = (sample.clamp(-128, 127) + 128) as u8; *dest = (sample.clamp(-128, 127) + 128) as u8;
} }
} }
} }
impl AudioDevice { impl AudioDevice {
/// Creates a new [`AudioDevice`] instance, using the given spec as its playback format. /// Creates a new [`AudioDevice`] instance, using the given spec as its playback format.
pub fn new(spec: AudioSpec) -> Self { pub fn new(spec: AudioSpec) -> Self {
let mut channels = Vec::new(); let mut channels = Vec::new();
for _ in 0..NUM_CHANNELS { for _ in 0..NUM_CHANNELS {
channels.push(AudioChannel::new()); channels.push(AudioChannel::new());
} }
AudioDevice { AudioDevice {
spec, spec,
channels, channels,
volume: 1.0, volume: 1.0,
} }
} }
/// Returns the spec that this device is currently set to play. All audio to be played via /// Returns the spec that this device is currently set to play. All audio to be played via
/// this device must be pre-converted to match this spec! /// this device must be pre-converted to match this spec!
#[inline] #[inline]
pub fn spec(&self) -> &AudioSpec { pub fn spec(&self) -> &AudioSpec {
&self.spec &self.spec
} }
/// Returns true if any of the audio channels are currently playing, false otherwise. /// Returns true if any of the audio channels are currently playing, false otherwise.
#[inline] #[inline]
pub fn is_playing(&self) -> bool { pub fn is_playing(&self) -> bool {
self.channels.iter().any(|channel| channel.playing) self.channels.iter().any(|channel| channel.playing)
} }
/// Stops the specified channel's playback, or does nothing if that channel was not currently /// Stops the specified channel's playback, or does nothing if that channel was not currently
/// playing. This does not affect the channel's other state (data buffer, etc). /// playing. This does not affect the channel's other state (data buffer, etc).
pub fn stop_channel(&mut self, channel_index: usize) -> Result<(), AudioDeviceError> { pub fn stop_channel(&mut self, channel_index: usize) -> Result<(), AudioDeviceError> {
if channel_index >= NUM_CHANNELS { if channel_index >= NUM_CHANNELS {
Err(AudioDeviceError::ChannelIndexOutOfRange(channel_index)) Err(AudioDeviceError::ChannelIndexOutOfRange(channel_index))
} else { } else {
self.channels[channel_index].stop(); self.channels[channel_index].stop();
Ok(()) Ok(())
} }
} }
/// Stops playback of all channels. /// Stops playback of all channels.
pub fn stop_all(&mut self) { pub fn stop_all(&mut self) {
for channel in self.channels.iter_mut() { for channel in self.channels.iter_mut() {
channel.stop(); channel.stop();
} }
} }
/// Tries to play the given [`AudioBuffer`] on the first channel found that is not already /// Tries to play the given [`AudioBuffer`] on the first channel found that is not already
/// playing. If a free channel is found, playback will be started by copying the buffer's /// playing. If a free channel is found, playback will be started by copying the buffer's
/// contents to the channel. The index of the channel is returned. If playback was not started /// contents to the channel. The index of the channel is returned. If playback was not started
/// because no channel is free currently, then `None` is returned. /// because no channel is free currently, then `None` is returned.
pub fn play_buffer( pub fn play_buffer(
&mut self, &mut self,
buffer: &AudioBuffer, buffer: &AudioBuffer,
loops: bool, loops: bool,
) -> Result<Option<usize>, AudioDeviceError> { ) -> Result<Option<usize>, AudioDeviceError> {
if *buffer.spec() != self.spec { if *buffer.spec() != self.spec {
Err(AudioDeviceError::AudioSpecMismatch) Err(AudioDeviceError::AudioSpecMismatch)
} else { } else {
if let Some((index, channel)) = self.stopped_channels_iter_mut().enumerate().next() { if let Some((index, channel)) = self.stopped_channels_iter_mut().enumerate().next() {
channel.play_buffer(buffer, loops); channel.play_buffer(buffer, loops);
Ok(Some(index)) Ok(Some(index))
} else { } else {
Ok(None) Ok(None)
} }
} }
} }
/// Plays the given [`AudioBuffer`] on the specified channel. Whatever that channel was playing /// Plays the given [`AudioBuffer`] on the specified channel. Whatever that channel was playing
/// will be interrupted and replaced with a copy of the given buffer's data. /// will be interrupted and replaced with a copy of the given buffer's data.
pub fn play_buffer_on_channel( pub fn play_buffer_on_channel(
&mut self, &mut self,
channel_index: usize, channel_index: usize,
buffer: &AudioBuffer, buffer: &AudioBuffer,
loops: bool, loops: bool,
) -> Result<(), AudioDeviceError> { ) -> Result<(), AudioDeviceError> {
if *buffer.spec() != self.spec { if *buffer.spec() != self.spec {
Err(AudioDeviceError::AudioSpecMismatch) Err(AudioDeviceError::AudioSpecMismatch)
} else if channel_index >= NUM_CHANNELS { } else if channel_index >= NUM_CHANNELS {
Err(AudioDeviceError::ChannelIndexOutOfRange(channel_index)) Err(AudioDeviceError::ChannelIndexOutOfRange(channel_index))
} else { } else {
self.channels[channel_index].play_buffer(buffer, loops); self.channels[channel_index].play_buffer(buffer, loops);
Ok(()) Ok(())
} }
} }
/// Tries to play the given [`AudioGenerator`] on the first channel found that is not already /// Tries to play the given [`AudioGenerator`] on the first channel found that is not already
/// playing. If a free channel is found, playback will be started and the index of the channel /// playing. If a free channel is found, playback will be started and the index of the channel
/// will be returned. If playback was not started because no channel is free currently, then /// will be returned. If playback was not started because no channel is free currently, then
/// `None` is returned. /// `None` is returned.
pub fn play_generator( pub fn play_generator(
&mut self, &mut self,
generator: Box<dyn AudioGenerator>, generator: Box<dyn AudioGenerator>,
loops: bool, loops: bool,
) -> Result<Option<usize>, AudioDeviceError> { ) -> Result<Option<usize>, AudioDeviceError> {
if let Some((index, channel)) = self.stopped_channels_iter_mut().enumerate().next() { if let Some((index, channel)) = self.stopped_channels_iter_mut().enumerate().next() {
channel.play_generator(generator, loops); channel.play_generator(generator, loops);
Ok(Some(index)) Ok(Some(index))
} else { } else {
Ok(None) Ok(None)
} }
} }
/// Plays the given [`AudioGenerator`] on the specified channel. Whatever that channel was /// Plays the given [`AudioGenerator`] on the specified channel. Whatever that channel was
/// playing will be interrupted and replaced. /// playing will be interrupted and replaced.
pub fn play_generator_on_channel( pub fn play_generator_on_channel(
&mut self, &mut self,
channel_index: usize, channel_index: usize,
generator: Box<dyn AudioGenerator>, generator: Box<dyn AudioGenerator>,
loops: bool, loops: bool,
) -> Result<(), AudioDeviceError> { ) -> Result<(), AudioDeviceError> {
if channel_index >= NUM_CHANNELS { if channel_index >= NUM_CHANNELS {
Err(AudioDeviceError::ChannelIndexOutOfRange(channel_index)) Err(AudioDeviceError::ChannelIndexOutOfRange(channel_index))
} else { } else {
self.channels[channel_index].play_generator(generator, loops); self.channels[channel_index].play_generator(generator, loops);
Ok(()) Ok(())
} }
} }
/// Returns an iterator of any [`AudioChannel`]s that are currently playing. /// Returns an iterator of any [`AudioChannel`]s that are currently playing.
#[inline] #[inline]
pub fn playing_channels_iter(&mut self) -> impl Iterator<Item = &AudioChannel> { pub fn playing_channels_iter(&mut self) -> impl Iterator<Item=&AudioChannel> {
self.channels.iter().filter(|channel| channel.playing) self.channels.iter().filter(|channel| channel.playing)
} }
/// Returns an iterator of mutable [`AudioChannel`]s that are currently playing. /// Returns an iterator of mutable [`AudioChannel`]s that are currently playing.
#[inline] #[inline]
pub fn playing_channels_iter_mut(&mut self) -> impl Iterator<Item = &mut AudioChannel> { pub fn playing_channels_iter_mut(&mut self) -> impl Iterator<Item=&mut AudioChannel> {
self.channels.iter_mut().filter(|channel| channel.playing) self.channels.iter_mut().filter(|channel| channel.playing)
} }
/// Returns an iterator of [`AudioChannel`]s that are not currently playing. /// Returns an iterator of [`AudioChannel`]s that are not currently playing.
#[inline] #[inline]
pub fn stopped_channels_iter(&mut self) -> impl Iterator<Item = &AudioChannel> { pub fn stopped_channels_iter(&mut self) -> impl Iterator<Item=&AudioChannel> {
self.channels.iter().filter(|channel| !channel.playing) self.channels.iter().filter(|channel| !channel.playing)
} }
/// Returns an iterator of mutable [`AudioChannel`]s that are not currently playing. /// Returns an iterator of mutable [`AudioChannel`]s that are not currently playing.
#[inline] #[inline]
pub fn stopped_channels_iter_mut(&mut self) -> impl Iterator<Item = &mut AudioChannel> { pub fn stopped_channels_iter_mut(&mut self) -> impl Iterator<Item=&mut AudioChannel> {
self.channels.iter_mut().filter(|channel| !channel.playing) self.channels.iter_mut().filter(|channel| !channel.playing)
} }
/// Returns an iterator of all [`AudioChannel`]s. /// Returns an iterator of all [`AudioChannel`]s.
#[inline] #[inline]
pub fn channels_iter(&mut self) -> impl Iterator<Item = &AudioChannel> { pub fn channels_iter(&mut self) -> impl Iterator<Item=&AudioChannel> {
self.channels.iter() self.channels.iter()
} }
/// Returns an iterator of all [`AudioChannel`]s as mutable references. /// Returns an iterator of all [`AudioChannel`]s as mutable references.
#[inline] #[inline]
pub fn channels_iter_mut(&mut self) -> impl Iterator<Item = &mut AudioChannel> { pub fn channels_iter_mut(&mut self) -> impl Iterator<Item=&mut AudioChannel> {
self.channels.iter_mut() self.channels.iter_mut()
} }
/// Returns a reference to the specified [`AudioChannel`] or `None` if the index specified /// Returns a reference to the specified [`AudioChannel`] or `None` if the index specified
/// is not valid. /// is not valid.
#[inline] #[inline]
pub fn get(&self, index: usize) -> Option<&AudioChannel> { pub fn get(&self, index: usize) -> Option<&AudioChannel> {
self.channels.get(index) self.channels.get(index)
} }
/// Returns a mutable reference to the specified [`AudioChannel`] or `None` if the index /// Returns a mutable reference to the specified [`AudioChannel`] or `None` if the index
/// specified is not valid. /// specified is not valid.
#[inline] #[inline]
pub fn get_mut(&mut self, index: usize) -> Option<&mut AudioChannel> { pub fn get_mut(&mut self, index: usize) -> Option<&mut AudioChannel> {
self.channels.get_mut(index) self.channels.get_mut(index)
} }
} }
impl Index<usize> for AudioDevice { impl Index<usize> for AudioDevice {
type Output = AudioChannel; type Output = AudioChannel;
/// Returns a reference to the specified [`AudioChannel`] or panics if the index specified is /// Returns a reference to the specified [`AudioChannel`] or panics if the index specified is
/// not valid. /// not valid.
#[inline] #[inline]
fn index(&self, index: usize) -> &Self::Output { fn index(&self, index: usize) -> &Self::Output {
self.get(index).unwrap() self.get(index).unwrap()
} }
} }
impl IndexMut<usize> for AudioDevice { impl IndexMut<usize> for AudioDevice {
/// Returns a mutable reference to the specified [`AudioChannel`] or panics if the index /// Returns a mutable reference to the specified [`AudioChannel`] or panics if the index
/// specified is not valid. /// specified is not valid.
#[inline] #[inline]
fn index_mut(&mut self, index: usize) -> &mut Self::Output { fn index_mut(&mut self, index: usize) -> &mut Self::Output {
self.get_mut(index).unwrap() self.get_mut(index).unwrap()
} }
} }

View file

@ -32,43 +32,43 @@ pub const TARGET_AUDIO_CHANNELS: u8 = 1;
/// to know what format an audio buffer is in and to specify conversion formats, etc. /// to know what format an audio buffer is in and to specify conversion formats, etc.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct AudioSpec { pub struct AudioSpec {
frequency: u32, frequency: u32,
channels: u8, channels: u8,
format: AudioFormat, format: AudioFormat,
} }
impl AudioSpec { impl AudioSpec {
/// Creates a new `AudioSpec` with the properties specified. /// Creates a new `AudioSpec` with the properties specified.
/// ///
/// # Arguments /// # Arguments
/// ///
/// * `frequency`: the frequency of the audio /// * `frequency`: the frequency of the audio
/// * `channels`: the number of channels of the audio (e.g. 1 = mono, 2 = stereo, etc) /// * `channels`: the number of channels of the audio (e.g. 1 = mono, 2 = stereo, etc)
/// * `format`: indicates the format of the bytes making up the audio buffer. /// * `format`: indicates the format of the bytes making up the audio buffer.
pub fn new(frequency: u32, channels: u8, format: AudioFormat) -> Self { pub fn new(frequency: u32, channels: u8, format: AudioFormat) -> Self {
AudioSpec { AudioSpec {
frequency, frequency,
channels, channels,
format, format,
} }
} }
#[inline] #[inline]
pub fn frequency(&self) -> u32 { pub fn frequency(&self) -> u32 {
self.frequency self.frequency
} }
#[inline] #[inline]
pub fn channels(&self) -> u8 { pub fn channels(&self) -> u8 {
self.channels self.channels
} }
/// An SDL2 [`sdl2::audio::AudioFormat`] value indicating the audio format of the bytes making /// An SDL2 [`sdl2::audio::AudioFormat`] value indicating the audio format of the bytes making
/// up an audio buffer. /// up an audio buffer.
#[inline] #[inline]
pub fn format(&self) -> AudioFormat { pub fn format(&self) -> AudioFormat {
self.format self.format
} }
} }
////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////
@ -77,104 +77,104 @@ impl AudioSpec {
/// Used to implement custom/dynamic audio generation. /// Used to implement custom/dynamic audio generation.
pub trait AudioGenerator: Send { pub trait AudioGenerator: Send {
/// Generates and returns the sample for the given playback position. `None` is returned if /// Generates and returns the sample for the given playback position. `None` is returned if
/// there is no sample for that position (e.g. it might be past the "end"). /// there is no sample for that position (e.g. it might be past the "end").
fn gen_sample(&mut self, position: usize) -> Option<u8>; fn gen_sample(&mut self, position: usize) -> Option<u8>;
} }
////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum AudioError { pub enum AudioError {
#[error("Failed to open audio device for playback: {0}")] #[error("Failed to open audio device for playback: {0}")]
OpenDeviceFailed(String), OpenDeviceFailed(String),
} }
/// Top-level abstraction over the system's audio output device. To play audio or change other /// Top-level abstraction over the system's audio output device. To play audio or change other
/// playback properties, you will need to lock the audio device via [`Audio::lock`] to obtain an /// playback properties, you will need to lock the audio device via [`Audio::lock`] to obtain an
/// [`AudioDevice`]. /// [`AudioDevice`].
pub struct Audio { pub struct Audio {
spec: AudioSpec, spec: AudioSpec,
sdl_audio_device: sdl2::audio::AudioDevice<AudioDevice>, sdl_audio_device: sdl2::audio::AudioDevice<AudioDevice>,
} }
impl std::fmt::Debug for Audio { impl std::fmt::Debug for Audio {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Audio") f.debug_struct("Audio")
.field("spec", &self.spec) .field("spec", &self.spec)
.finish_non_exhaustive() .finish_non_exhaustive()
} }
} }
impl Audio { impl Audio {
/// Creates a new [`Audio`] instance, wrapping the given SDL [`sdl2::audio::AudioSubsystem`]. /// Creates a new [`Audio`] instance, wrapping the given SDL [`sdl2::audio::AudioSubsystem`].
/// The `desired_spec` given specifies the target audio playback format. /// The `desired_spec` given specifies the target audio playback format.
/// ///
/// Ideally, you should not be creating an instance of this yourself and should just use the /// Ideally, you should not be creating an instance of this yourself and should just use the
/// one provided by [`crate::system::System`]. /// one provided by [`crate::system::System`].
pub fn new( pub fn new(
desired_spec: AudioSpecDesired, desired_spec: AudioSpecDesired,
sdl_audio_subsystem: &AudioSubsystem, sdl_audio_subsystem: &AudioSubsystem,
) -> Result<Self, AudioError> { ) -> Result<Self, AudioError> {
let mut spec = None; let mut spec = None;
let sdl_audio_device = let sdl_audio_device =
match sdl_audio_subsystem.open_playback(None, &desired_spec, |opened_spec| { match sdl_audio_subsystem.open_playback(None, &desired_spec, |opened_spec| {
let our_spec = AudioSpec::new( let our_spec = AudioSpec::new(
opened_spec.freq as u32, opened_spec.freq as u32,
opened_spec.channels, opened_spec.channels,
opened_spec.format, opened_spec.format,
); );
spec = Some(our_spec); spec = Some(our_spec);
AudioDevice::new(our_spec) AudioDevice::new(our_spec)
}) { }) {
Ok(audio_device) => audio_device, Ok(audio_device) => audio_device,
Err(error) => return Err(AudioError::OpenDeviceFailed(error)), Err(error) => return Err(AudioError::OpenDeviceFailed(error)),
}; };
if let Some(spec) = spec { if let Some(spec) = spec {
Ok(Audio { Ok(Audio {
spec, spec,
sdl_audio_device, sdl_audio_device,
}) })
} else { } else {
Err(AudioError::OpenDeviceFailed(String::from( Err(AudioError::OpenDeviceFailed(String::from(
"Device initialization failed to set AudioSpec", "Device initialization failed to set AudioSpec",
))) )))
} }
} }
/// Returns current audio device's audio specification/format for playback. All [`AudioBuffer`]s /// Returns current audio device's audio specification/format for playback. All [`AudioBuffer`]s
/// that are to be used for playback must be converted to match this before they can be played. /// that are to be used for playback must be converted to match this before they can be played.
#[inline] #[inline]
pub fn spec(&self) -> &AudioSpec { pub fn spec(&self) -> &AudioSpec {
&self.spec &self.spec
} }
/// Returns the current status of the audio device (e.g. whether it is paused, stopped, etc). /// Returns the current status of the audio device (e.g. whether it is paused, stopped, etc).
#[inline] #[inline]
pub fn status(&self) -> sdl2::audio::AudioStatus { pub fn status(&self) -> sdl2::audio::AudioStatus {
self.sdl_audio_device.status() self.sdl_audio_device.status()
} }
/// Pauses all audio playback. /// Pauses all audio playback.
#[inline] #[inline]
pub fn pause(&mut self) { pub fn pause(&mut self) {
self.sdl_audio_device.pause() self.sdl_audio_device.pause()
} }
/// Resumes all audio playback. /// Resumes all audio playback.
#[inline] #[inline]
pub fn resume(&mut self) { pub fn resume(&mut self) {
self.sdl_audio_device.resume() self.sdl_audio_device.resume()
} }
/// Locks the audio device so that new audio data can be provided or playback altered. A /// Locks the audio device so that new audio data can be provided or playback altered. A
/// [`AudioDevice`] instance is returned on successful lock which can be used to interact with /// [`AudioDevice`] instance is returned on successful lock which can be used to interact with
/// the actual system's audio playback. The audio device is unlocked once this instance is /// the actual system's audio playback. The audio device is unlocked once this instance is
/// dropped. Ideally, you will want to keep the audio device for **as _short_ a time as /// dropped. Ideally, you will want to keep the audio device for **as _short_ a time as
/// possible!** /// possible!**
#[inline] #[inline]
pub fn lock(&mut self) -> sdl2::audio::AudioDeviceLockGuard<AudioDevice> { pub fn lock(&mut self) -> sdl2::audio::AudioDeviceLockGuard<AudioDevice> {
self.sdl_audio_device.lock() self.sdl_audio_device.lock()
} }
} }

View file

@ -4,82 +4,82 @@ use std::rc::Rc;
use crate::audio::*; use crate::audio::*;
pub enum AudioCommand { pub enum AudioCommand {
StopChannel(usize), StopChannel(usize),
StopAllChannels, StopAllChannels,
PlayBuffer { PlayBuffer {
buffer: AudioBuffer, buffer: AudioBuffer,
loops: bool, loops: bool,
}, },
PlayRcBuffer { PlayRcBuffer {
buffer: Rc<AudioBuffer>, buffer: Rc<AudioBuffer>,
loops: bool, loops: bool,
}, },
PlayBufferOnChannel { PlayBufferOnChannel {
channel: usize, channel: usize,
buffer: AudioBuffer, buffer: AudioBuffer,
loops: bool, loops: bool,
}, },
PlayRcBufferOnChannel { PlayRcBufferOnChannel {
channel: usize, channel: usize,
buffer: Rc<AudioBuffer>, buffer: Rc<AudioBuffer>,
loops: bool, loops: bool,
}, },
PlayGenerator { PlayGenerator {
generator: Box<dyn AudioGenerator>, generator: Box<dyn AudioGenerator>,
loops: bool, loops: bool,
}, },
PlayGeneratorOnChannel { PlayGeneratorOnChannel {
channel: usize, channel: usize,
generator: Box<dyn AudioGenerator>, generator: Box<dyn AudioGenerator>,
loops: bool, loops: bool,
}, },
} }
impl std::fmt::Debug for AudioCommand { impl std::fmt::Debug for AudioCommand {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
use AudioCommand::*; use AudioCommand::*;
match self { match self {
StopChannel(n) => write!(f, "StopChannel({})", n), StopChannel(n) => write!(f, "StopChannel({})", n),
StopAllChannels => write!(f, "StopAllChannels"), StopAllChannels => write!(f, "StopAllChannels"),
PlayBuffer { buffer, loops } => { PlayBuffer { buffer, loops } => {
f.debug_struct("PlayBuffer") f.debug_struct("PlayBuffer")
.field("buffer", buffer) .field("buffer", buffer)
.field("loops", loops) .field("loops", loops)
.finish() .finish()
}, }
PlayRcBuffer { buffer, loops } => { PlayRcBuffer { buffer, loops } => {
f.debug_struct("PlayRcBuffer") f.debug_struct("PlayRcBuffer")
.field("buffer", buffer) .field("buffer", buffer)
.field("loops", loops) .field("loops", loops)
.finish() .finish()
}, }
PlayBufferOnChannel { channel, buffer, loops } => { PlayBufferOnChannel { channel, buffer, loops } => {
f.debug_struct("PlayBufferOnChannel") f.debug_struct("PlayBufferOnChannel")
.field("channel", channel) .field("channel", channel)
.field("buffer", buffer) .field("buffer", buffer)
.field("loops", loops) .field("loops", loops)
.finish() .finish()
}, }
PlayRcBufferOnChannel { channel, buffer, loops } => { PlayRcBufferOnChannel { channel, buffer, loops } => {
f.debug_struct("PlayRcBufferOnChannel") f.debug_struct("PlayRcBufferOnChannel")
.field("channel", channel) .field("channel", channel)
.field("buffer", buffer) .field("buffer", buffer)
.field("loops", loops) .field("loops", loops)
.finish() .finish()
}, }
PlayGenerator { loops, .. } => { PlayGenerator { loops, .. } => {
f.debug_struct("PlayGenerator") f.debug_struct("PlayGenerator")
.field("loops", loops) .field("loops", loops)
.finish_non_exhaustive() .finish_non_exhaustive()
}, }
PlayGeneratorOnChannel { channel, loops, .. } => { PlayGeneratorOnChannel { channel, loops, .. } => {
f.debug_struct("PlayGeneratorOnChannel") f.debug_struct("PlayGeneratorOnChannel")
.field("channel", channel) .field("channel", channel)
.field("loops", loops) .field("loops", loops)
.finish_non_exhaustive() .finish_non_exhaustive()
}, }
} }
} }
} }
/// A convenience abstraction that can be used to queue up commands to be issued to an /// A convenience abstraction that can be used to queue up commands to be issued to an
@ -89,199 +89,199 @@ impl std::fmt::Debug for AudioCommand {
/// manner. /// manner.
#[derive(Debug)] #[derive(Debug)]
pub struct AudioQueue { pub struct AudioQueue {
spec: AudioSpec, spec: AudioSpec,
commands: VecDeque<AudioCommand>, commands: VecDeque<AudioCommand>,
} }
impl AudioQueue { impl AudioQueue {
/// Creates and returns a new [`AudioQueue`] instance. /// Creates and returns a new [`AudioQueue`] instance.
pub fn new(audio: &Audio) -> Self { pub fn new(audio: &Audio) -> Self {
AudioQueue { AudioQueue {
spec: audio.spec, spec: audio.spec,
commands: VecDeque::new(), commands: VecDeque::new(),
} }
} }
/// Returns the spec that this queue is currently set to play. All audio to be played via /// Returns the spec that this queue is currently set to play. All audio to be played via
/// this queue must be pre-converted to match this spec! This spec is a copy of the one that /// this queue must be pre-converted to match this spec! This spec is a copy of the one that
/// was obtained from the [`Audio`] instance used to create this [`AudioQueue`]. /// was obtained from the [`Audio`] instance used to create this [`AudioQueue`].
#[inline] #[inline]
pub fn spec(&self) -> &AudioSpec { pub fn spec(&self) -> &AudioSpec {
&self.spec &self.spec
} }
/// Queues a stop command for the given channel. /// Queues a stop command for the given channel.
pub fn stop_channel(&mut self, channel_index: usize) -> Result<(), AudioDeviceError> { pub fn stop_channel(&mut self, channel_index: usize) -> Result<(), AudioDeviceError> {
if channel_index >= NUM_CHANNELS { if channel_index >= NUM_CHANNELS {
Err(AudioDeviceError::ChannelIndexOutOfRange(channel_index)) Err(AudioDeviceError::ChannelIndexOutOfRange(channel_index))
} else { } else {
self.commands.push_back(AudioCommand::StopChannel(channel_index)); self.commands.push_back(AudioCommand::StopChannel(channel_index));
Ok(()) Ok(())
} }
} }
/// Queues a command that will stop playback on all channels. /// Queues a command that will stop playback on all channels.
pub fn stop_all(&mut self) { pub fn stop_all(&mut self) {
self.commands.push_back(AudioCommand::StopAllChannels); self.commands.push_back(AudioCommand::StopAllChannels);
} }
/// Queues a command to play a copy of the given [`AudioBuffer`]'s data. The buffer will be /// Queues a command to play a copy of the given [`AudioBuffer`]'s data. The buffer will be
/// played on the first channel found that is not already playing. If all channels are already /// played on the first channel found that is not already playing. If all channels are already
/// playing, then nothing will be done. /// playing, then nothing will be done.
pub fn play_buffer( pub fn play_buffer(
&mut self, &mut self,
buffer: &AudioBuffer, buffer: &AudioBuffer,
loops: bool, loops: bool,
) -> Result<(), AudioDeviceError> { ) -> Result<(), AudioDeviceError> {
if *buffer.spec() != self.spec { if *buffer.spec() != self.spec {
Err(AudioDeviceError::AudioSpecMismatch) Err(AudioDeviceError::AudioSpecMismatch)
} else { } else {
self.commands.push_back(AudioCommand::PlayBuffer { self.commands.push_back(AudioCommand::PlayBuffer {
buffer: buffer.clone(), buffer: buffer.clone(),
loops, loops,
}); });
Ok(()) Ok(())
} }
} }
/// Queues a command to play the given [`AudioBuffer`]'s data. The buffer will be played on /// Queues a command to play the given [`AudioBuffer`]'s data. The buffer will be played on
/// the first channel found that is not already playing. If all channels are already playing, /// the first channel found that is not already playing. If all channels are already playing,
/// then nothing will be done. This method is more performant than [`AudioQueue::play_buffer`], /// then nothing will be done. This method is more performant than [`AudioQueue::play_buffer`],
/// as that method will always immediately copy the given buffer to create the queued command. /// as that method will always immediately copy the given buffer to create the queued command.
pub fn play_buffer_rc( pub fn play_buffer_rc(
&mut self, &mut self,
buffer: Rc<AudioBuffer>, buffer: Rc<AudioBuffer>,
loops: bool, loops: bool,
) -> Result<(), AudioDeviceError> { ) -> Result<(), AudioDeviceError> {
if *buffer.spec() != self.spec { if *buffer.spec() != self.spec {
Err(AudioDeviceError::AudioSpecMismatch) Err(AudioDeviceError::AudioSpecMismatch)
} else { } else {
self.commands.push_back(AudioCommand::PlayRcBuffer { self.commands.push_back(AudioCommand::PlayRcBuffer {
buffer, buffer,
loops, loops,
}); });
Ok(()) Ok(())
} }
} }
/// Queues a command to play a copy of the given [`AudioBuffer`]'s data on the channel /// Queues a command to play a copy of the given [`AudioBuffer`]'s data on the channel
/// specified. Whatever that channel was playing will be interrupted to begin playing this /// specified. Whatever that channel was playing will be interrupted to begin playing this
/// buffer. /// buffer.
pub fn play_buffer_on_channel( pub fn play_buffer_on_channel(
&mut self, &mut self,
channel_index: usize, channel_index: usize,
buffer: &AudioBuffer, buffer: &AudioBuffer,
loops: bool, loops: bool,
) -> Result<(), AudioDeviceError> { ) -> Result<(), AudioDeviceError> {
if *buffer.spec() != self.spec { if *buffer.spec() != self.spec {
Err(AudioDeviceError::AudioSpecMismatch) Err(AudioDeviceError::AudioSpecMismatch)
} else if channel_index >= NUM_CHANNELS { } else if channel_index >= NUM_CHANNELS {
Err(AudioDeviceError::ChannelIndexOutOfRange(channel_index)) Err(AudioDeviceError::ChannelIndexOutOfRange(channel_index))
} else { } else {
self.commands.push_back(AudioCommand::PlayBufferOnChannel { self.commands.push_back(AudioCommand::PlayBufferOnChannel {
channel: channel_index, channel: channel_index,
buffer: buffer.clone(), buffer: buffer.clone(),
loops, loops,
}); });
Ok(()) Ok(())
} }
} }
/// Queues a command to play the given [`AudioBuffer`]'s data on the channel specified. Whatever /// Queues a command to play the given [`AudioBuffer`]'s data on the channel specified. Whatever
/// that channel was playing will be interrupted to begin playing this buffer. This method is /// that channel was playing will be interrupted to begin playing this buffer. This method is
/// more performant than [`AudioQueue::play_buffer_on_channel`], as that method will always /// more performant than [`AudioQueue::play_buffer_on_channel`], as that method will always
/// immediately copy the given buffer to create the queued command. /// immediately copy the given buffer to create the queued command.
pub fn play_buffer_rc_on_channel( pub fn play_buffer_rc_on_channel(
&mut self, &mut self,
channel_index: usize, channel_index: usize,
buffer: Rc<AudioBuffer>, buffer: Rc<AudioBuffer>,
loops: bool, loops: bool,
) -> Result<(), AudioDeviceError> { ) -> Result<(), AudioDeviceError> {
if *buffer.spec() != self.spec { if *buffer.spec() != self.spec {
Err(AudioDeviceError::AudioSpecMismatch) Err(AudioDeviceError::AudioSpecMismatch)
} else if channel_index >= NUM_CHANNELS { } else if channel_index >= NUM_CHANNELS {
Err(AudioDeviceError::ChannelIndexOutOfRange(channel_index)) Err(AudioDeviceError::ChannelIndexOutOfRange(channel_index))
} else { } else {
self.commands.push_back(AudioCommand::PlayRcBufferOnChannel { self.commands.push_back(AudioCommand::PlayRcBufferOnChannel {
channel: channel_index, channel: channel_index,
buffer, buffer,
loops, loops,
}); });
Ok(()) Ok(())
} }
} }
/// Queues a command to play the given [`AudioGenerator`] on the first channel found that is /// Queues a command to play the given [`AudioGenerator`] on the first channel found that is
/// not already playing. If all channels are already playing, then nothing will be done. /// not already playing. If all channels are already playing, then nothing will be done.
pub fn play_generator( pub fn play_generator(
&mut self, &mut self,
generator: Box<dyn AudioGenerator>, generator: Box<dyn AudioGenerator>,
loops: bool, loops: bool,
) -> Result<(), AudioDeviceError> { ) -> Result<(), AudioDeviceError> {
self.commands.push_back(AudioCommand::PlayGenerator { generator, loops }); self.commands.push_back(AudioCommand::PlayGenerator { generator, loops });
Ok(()) Ok(())
} }
/// Queues a command to play the given [`AudioGenerator`] on the channel specified. Whatever /// Queues a command to play the given [`AudioGenerator`] on the channel specified. Whatever
/// that channel was playing will be interrupted to begin playing this generator. /// that channel was playing will be interrupted to begin playing this generator.
pub fn play_generator_on_channel( pub fn play_generator_on_channel(
&mut self, &mut self,
channel_index: usize, channel_index: usize,
generator: Box<dyn AudioGenerator>, generator: Box<dyn AudioGenerator>,
loops: bool, loops: bool,
) -> Result<(), AudioDeviceError> { ) -> Result<(), AudioDeviceError> {
self.commands.push_back(AudioCommand::PlayGeneratorOnChannel { self.commands.push_back(AudioCommand::PlayGeneratorOnChannel {
channel: channel_index, channel: channel_index,
generator, generator,
loops, loops,
}); });
Ok(()) Ok(())
} }
/// Flushes the queued commands, issuing them in the same order they were created, to the /// Flushes the queued commands, issuing them in the same order they were created, to the
/// given [`AudioDevice`]. /// given [`AudioDevice`].
pub fn apply_to_device(&mut self, device: &mut AudioDevice) -> Result<(), AudioDeviceError> { pub fn apply_to_device(&mut self, device: &mut AudioDevice) -> Result<(), AudioDeviceError> {
loop { loop {
if let Some(command) = self.commands.pop_front() { if let Some(command) = self.commands.pop_front() {
use AudioCommand::*; use AudioCommand::*;
match command { match command {
StopChannel(channel_index) => { StopChannel(channel_index) => {
device.stop_channel(channel_index)?; device.stop_channel(channel_index)?;
}, }
StopAllChannels => { StopAllChannels => {
device.stop_all(); device.stop_all();
}, }
PlayBuffer { buffer, loops } => { PlayBuffer { buffer, loops } => {
device.play_buffer(&buffer, loops)?; device.play_buffer(&buffer, loops)?;
} }
PlayRcBuffer { buffer, loops } => { PlayRcBuffer { buffer, loops } => {
device.play_buffer(&buffer, loops)?; device.play_buffer(&buffer, loops)?;
}, }
PlayBufferOnChannel { channel, buffer, loops } => { PlayBufferOnChannel { channel, buffer, loops } => {
device.play_buffer_on_channel(channel, &buffer, loops)?; device.play_buffer_on_channel(channel, &buffer, loops)?;
} }
PlayRcBufferOnChannel { channel, buffer, loops } => { PlayRcBufferOnChannel { channel, buffer, loops } => {
device.play_buffer_on_channel(channel, &buffer, loops)?; device.play_buffer_on_channel(channel, &buffer, loops)?;
}, }
PlayGenerator { generator, loops } => { PlayGenerator { generator, loops } => {
device.play_generator(generator, loops)?; device.play_generator(generator, loops)?;
}, }
PlayGeneratorOnChannel { channel, generator, loops } => { PlayGeneratorOnChannel { channel, generator, loops } => {
device.play_generator_on_channel(channel, generator, loops)?; device.play_generator_on_channel(channel, generator, loops)?;
}, }
} }
} else { } else {
return Ok(()) return Ok(());
} }
} }
} }
/// Flushes the queued commands, issuing them in the same order they were created, to the /// Flushes the queued commands, issuing them in the same order they were created, to the
/// given [`Audio`] instance. This method automatically handles obtaining a locked /// given [`Audio`] instance. This method automatically handles obtaining a locked
/// [`AudioDevice`], and so is a bit more convenient to use if you don't actually need to /// [`AudioDevice`], and so is a bit more convenient to use if you don't actually need to
/// interact with the [`AudioDevice`] itself in your code. /// interact with the [`AudioDevice`] itself in your code.
pub fn apply(&mut self, audio: &mut Audio) -> Result<(), AudioDeviceError> { pub fn apply(&mut self, audio: &mut Audio) -> Result<(), AudioDeviceError> {
let mut device = audio.lock(); let mut device = audio.lock();
self.apply_to_device(&mut device) self.apply_to_device(&mut device)
} }
} }

View file

@ -186,9 +186,9 @@ pub fn main_loop<ContextType, State>(
mut app: ContextType, mut app: ContextType,
initial_state: State, initial_state: State,
) -> Result<(), MainLoopError> ) -> Result<(), MainLoopError>
where where
ContextType: AppContext, ContextType: AppContext,
State: AppState<ContextType> + 'static, State: AppState<ContextType> + 'static,
{ {
let mut states = States::new(); let mut states = States::new();
states.push(initial_state)?; states.push(initial_state)?;

File diff suppressed because it is too large Load diff

View file

@ -10,47 +10,47 @@ pub type ListenerFn<EventType, ContextType> = fn(event: &EventType, &mut Context
/// instance. The `EventType` here should usually be an application-specific "events" enum. /// instance. The `EventType` here should usually be an application-specific "events" enum.
#[derive(Clone)] #[derive(Clone)]
pub struct EventPublisher<EventType> { pub struct EventPublisher<EventType> {
queue: VecDeque<EventType>, queue: VecDeque<EventType>,
} }
impl<EventType> std::fmt::Debug for EventPublisher<EventType> { impl<EventType> std::fmt::Debug for EventPublisher<EventType> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("EventPublisher") f.debug_struct("EventPublisher")
.field("queue.len()", &self.queue.len()) .field("queue.len()", &self.queue.len())
.finish_non_exhaustive() .finish_non_exhaustive()
} }
} }
impl<EventType> EventPublisher<EventType> { impl<EventType> EventPublisher<EventType> {
pub fn new() -> Self { pub fn new() -> Self {
EventPublisher { EventPublisher {
queue: VecDeque::new(), queue: VecDeque::new(),
} }
} }
/// Returns the number of events that have been queued. /// Returns the number of events that have been queued.
#[inline] #[inline]
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
self.queue.len() self.queue.len()
} }
/// Clears the current event queue. The events will not be processed/handled. /// Clears the current event queue. The events will not be processed/handled.
#[inline] #[inline]
pub fn clear(&mut self) { pub fn clear(&mut self) {
self.queue.clear(); self.queue.clear();
} }
/// Pushes the given event to the back of the queue. /// Pushes the given event to the back of the queue.
#[inline] #[inline]
pub fn queue(&mut self, event: EventType) { pub fn queue(&mut self, event: EventType) {
self.queue.push_back(event); self.queue.push_back(event);
} }
fn take_queue(&mut self, destination: &mut VecDeque<EventType>) { fn take_queue(&mut self, destination: &mut VecDeque<EventType>) {
destination.clear(); destination.clear();
destination.append(&mut self.queue); destination.append(&mut self.queue);
self.clear(); self.clear();
} }
} }
/// A manager for application event listeners/handlers that can dispatch events queued up by a /// A manager for application event listeners/handlers that can dispatch events queued up by a
@ -63,280 +63,278 @@ impl<EventType> EventPublisher<EventType> {
/// want available in all of your event listener/handler functions. /// want available in all of your event listener/handler functions.
#[derive(Clone)] #[derive(Clone)]
pub struct EventListeners<EventType, ContextType> { pub struct EventListeners<EventType, ContextType> {
listeners: Vec<ListenerFn<EventType, ContextType>>, listeners: Vec<ListenerFn<EventType, ContextType>>,
dispatch_queue: VecDeque<EventType>, dispatch_queue: VecDeque<EventType>,
} }
impl<EventType, ContextType> std::fmt::Debug for EventListeners<EventType, ContextType> { impl<EventType, ContextType> std::fmt::Debug for EventListeners<EventType, ContextType> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("EventListeners") f.debug_struct("EventListeners")
.field("listeners.len()", &self.listeners.len()) .field("listeners.len()", &self.listeners.len())
.field("dispatch_queue.len()", &self.dispatch_queue.len()) .field("dispatch_queue.len()", &self.dispatch_queue.len())
.finish_non_exhaustive() .finish_non_exhaustive()
} }
} }
impl<EventType, ContextType> EventListeners<EventType, ContextType> { impl<EventType, ContextType> EventListeners<EventType, ContextType> {
pub fn new() -> Self { pub fn new() -> Self {
EventListeners { EventListeners {
listeners: Vec::new(), listeners: Vec::new(),
dispatch_queue: VecDeque::new(), dispatch_queue: VecDeque::new(),
} }
} }
/// Returns the number of event listeners/handlers registered with this manager. /// Returns the number of event listeners/handlers registered with this manager.
#[inline] #[inline]
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
self.listeners.len() self.listeners.len()
} }
/// Unregisters all event listeners/managers previously registered with this manager. /// Unregisters all event listeners/managers previously registered with this manager.
#[inline] #[inline]
pub fn clear(&mut self) { pub fn clear(&mut self) {
self.listeners.clear(); self.listeners.clear();
} }
/// Adds/Registers the given event listener/handler function with this manager so that /// Adds/Registers the given event listener/handler function with this manager so that
/// it will be called during dispatching of events. Returns true if the function was added. /// it will be called during dispatching of events. Returns true if the function was added.
pub fn add(&mut self, listener: ListenerFn<EventType, ContextType>) -> bool { pub fn add(&mut self, listener: ListenerFn<EventType, ContextType>) -> bool {
// HACK?: most advice i've seen right now for comparing function pointers suggests doing // HACK?: most advice i've seen right now for comparing function pointers suggests doing
// this, but i've also come across comments suggesting there are times where this // this, but i've also come across comments suggesting there are times where this
// might not be foolproof? (e.g. where generics or lifetimes come into play ... ) // might not be foolproof? (e.g. where generics or lifetimes come into play ... )
if self.listeners.iter().any(|&l| l as usize == listener as usize) { if self.listeners.iter().any(|&l| l as usize == listener as usize) {
false // don't add a duplicate listener false // don't add a duplicate listener
} else { } else {
self.listeners.push(listener); self.listeners.push(listener);
true true
} }
} }
/// Removes/Unregisters the specified event listener/handler function from this manager. /// Removes/Unregisters the specified event listener/handler function from this manager.
pub fn remove(&mut self, listener: ListenerFn<EventType, ContextType>) -> bool { pub fn remove(&mut self, listener: ListenerFn<EventType, ContextType>) -> bool {
let before_size = self.listeners.len(); let before_size = self.listeners.len();
// HACK?: comparing function pointers -- see above "HACK?" comment. same concern here. // HACK?: comparing function pointers -- see above "HACK?" comment. same concern here.
self.listeners.retain(|&l| l as usize != listener as usize); self.listeners.retain(|&l| l as usize != listener as usize);
// return true if the listener was removed // return true if the listener was removed
return before_size != self.listeners.len() return before_size != self.listeners.len();
} }
/// Moves the queue from the given [`EventPublisher`] to this manager in preparation for /// Moves the queue from the given [`EventPublisher`] to this manager in preparation for
/// dispatching the queued events via [`EventListeners::dispatch_queue`]. After calling this, /// dispatching the queued events via [`EventListeners::dispatch_queue`]. After calling this,
/// the [`EventPublisher`]'s queue will be empty. /// the [`EventPublisher`]'s queue will be empty.
pub fn take_queue_from(&mut self, publisher: &mut EventPublisher<EventType>) -> usize { pub fn take_queue_from(&mut self, publisher: &mut EventPublisher<EventType>) -> usize {
publisher.take_queue(&mut self.dispatch_queue); publisher.take_queue(&mut self.dispatch_queue);
self.dispatch_queue.len() self.dispatch_queue.len()
} }
/// Dispatches the previous obtained event queue (via a call to
/// [`EventListeners::take_queue_from`]) to all of the registered event listeners/handlers,
/// passing each of them the given context argument. Not all of the event listeners/handlers
/// will necessarily be called for each event being dispatched depending on which ones handled
/// which events.
pub fn dispatch_queue(&mut self, context: &mut ContextType) {
while let Some(event) = self.dispatch_queue.pop_front() {
for listener in &self.listeners {
if listener(&event, context) {
break;
}
}
}
}
/// Dispatches the previous obtained event queue (via a call to
/// [`EventListeners::take_queue_from`]) to all of the registered event listeners/handlers,
/// passing each of them the given context argument. Not all of the event listeners/handlers
/// will necessarily be called for each event being dispatched depending on which ones handled
/// which events.
pub fn dispatch_queue(&mut self, context: &mut ContextType) {
while let Some(event) = self.dispatch_queue.pop_front() {
for listener in &self.listeners {
if listener(&event, context) {
break;
}
}
}
}
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
#[derive(Debug, Eq, PartialEq, Copy, Clone)] #[derive(Debug, Eq, PartialEq, Copy, Clone)]
enum TestEvent { enum TestEvent {
Dummy, Dummy,
Foobar(i32), Foobar(i32),
Message(&'static str), Message(&'static str),
} }
struct DummyContext; struct DummyContext;
struct TestContext { struct TestContext {
pub count: i32, pub count: i32,
pub events: Vec<TestEvent>, pub events: Vec<TestEvent>,
} }
impl TestContext { impl TestContext {
pub fn new() -> Self { pub fn new() -> Self {
TestContext { count: 0, events: Vec::new() } TestContext { count: 0, events: Vec::new() }
} }
} }
fn dummy_listener(_event: &TestEvent, _context: &mut DummyContext) -> bool { fn dummy_listener(_event: &TestEvent, _context: &mut DummyContext) -> bool {
false false
} }
fn other_dummy_listener(_event: &TestEvent, _context: &mut DummyContext) -> bool { fn other_dummy_listener(_event: &TestEvent, _context: &mut DummyContext) -> bool {
false false
} }
fn event_logger(event: &TestEvent, context: &mut TestContext) -> bool { fn event_logger(event: &TestEvent, context: &mut TestContext) -> bool {
context.events.push(*event); context.events.push(*event);
false false
} }
fn event_counter(_event: &TestEvent, context: &mut TestContext) -> bool { fn event_counter(_event: &TestEvent, context: &mut TestContext) -> bool {
context.count += 1; context.count += 1;
false false
} }
fn message_filter(event: &TestEvent, _context: &mut TestContext) -> bool { fn message_filter(event: &TestEvent, _context: &mut TestContext) -> bool {
match event { match event {
TestEvent::Message(s) => { TestEvent::Message(s) => {
if *s == "filter" { if *s == "filter" {
true // means event was handled, and no subsequent listeners should be called true // means event was handled, and no subsequent listeners should be called
} else { } else {
false false
} }
}, }
_ => false _ => false
} }
} }
#[test] #[test]
pub fn adding_and_removing_listeners() { pub fn adding_and_removing_listeners() {
let mut listeners = EventListeners::<TestEvent, DummyContext>::new(); let mut listeners = EventListeners::<TestEvent, DummyContext>::new();
// add and remove // add and remove
assert_eq!(0, listeners.len()); assert_eq!(0, listeners.len());
assert!(listeners.add(dummy_listener)); assert!(listeners.add(dummy_listener));
assert_eq!(1, listeners.len()); assert_eq!(1, listeners.len());
assert!(!listeners.add(dummy_listener)); assert!(!listeners.add(dummy_listener));
assert_eq!(1, listeners.len()); assert_eq!(1, listeners.len());
assert!(listeners.remove(dummy_listener)); assert!(listeners.remove(dummy_listener));
assert_eq!(0, listeners.len()); assert_eq!(0, listeners.len());
assert!(!listeners.remove(dummy_listener)); assert!(!listeners.remove(dummy_listener));
assert_eq!(0, listeners.len()); assert_eq!(0, listeners.len());
// add and remove multiple // add and remove multiple
assert!(listeners.add(dummy_listener)); assert!(listeners.add(dummy_listener));
assert_eq!(1, listeners.len()); assert_eq!(1, listeners.len());
assert!(listeners.add(other_dummy_listener)); assert!(listeners.add(other_dummy_listener));
assert_eq!(2, listeners.len()); assert_eq!(2, listeners.len());
assert!(listeners.remove(dummy_listener)); assert!(listeners.remove(dummy_listener));
assert_eq!(1, listeners.len()); assert_eq!(1, listeners.len());
assert!(!listeners.remove(dummy_listener)); assert!(!listeners.remove(dummy_listener));
assert_eq!(1, listeners.len()); assert_eq!(1, listeners.len());
assert!(listeners.remove(other_dummy_listener)); assert!(listeners.remove(other_dummy_listener));
assert_eq!(0, listeners.len()); assert_eq!(0, listeners.len());
// clear all // clear all
assert!(listeners.add(dummy_listener)); assert!(listeners.add(dummy_listener));
assert!(listeners.add(other_dummy_listener)); assert!(listeners.add(other_dummy_listener));
assert_eq!(2, listeners.len()); assert_eq!(2, listeners.len());
listeners.clear(); listeners.clear();
assert_eq!(0, listeners.len()); assert_eq!(0, listeners.len());
} }
#[test] #[test]
pub fn queueing_events() { pub fn queueing_events() {
use TestEvent::*; use TestEvent::*;
let mut publisher = EventPublisher::<TestEvent>::new(); let mut publisher = EventPublisher::<TestEvent>::new();
assert_eq!(0, publisher.len()); assert_eq!(0, publisher.len());
publisher.queue(Dummy); publisher.queue(Dummy);
assert_eq!(1, publisher.len()); assert_eq!(1, publisher.len());
publisher.queue(Foobar(1)); publisher.queue(Foobar(1));
assert_eq!(2, publisher.len()); assert_eq!(2, publisher.len());
publisher.queue(Foobar(2)); publisher.queue(Foobar(2));
assert_eq!(3, publisher.len()); assert_eq!(3, publisher.len());
let mut queue = VecDeque::<TestEvent>::new(); let mut queue = VecDeque::<TestEvent>::new();
publisher.take_queue(&mut queue); publisher.take_queue(&mut queue);
assert_eq!(0, publisher.len()); assert_eq!(0, publisher.len());
assert_eq!(Dummy, queue.pop_front().unwrap()); assert_eq!(Dummy, queue.pop_front().unwrap());
assert_eq!(Foobar(1), queue.pop_front().unwrap()); assert_eq!(Foobar(1), queue.pop_front().unwrap());
assert_eq!(Foobar(2), queue.pop_front().unwrap()); assert_eq!(Foobar(2), queue.pop_front().unwrap());
assert!(queue.pop_front().is_none()); assert!(queue.pop_front().is_none());
publisher.queue(Dummy); publisher.queue(Dummy);
assert_eq!(1, publisher.len()); assert_eq!(1, publisher.len());
publisher.clear(); publisher.clear();
assert_eq!(0, publisher.len()); assert_eq!(0, publisher.len());
let mut queue = VecDeque::<TestEvent>::new(); let mut queue = VecDeque::<TestEvent>::new();
publisher.take_queue(&mut queue); publisher.take_queue(&mut queue);
assert_eq!(0, publisher.len()); assert_eq!(0, publisher.len());
assert_eq!(0, queue.len()); assert_eq!(0, queue.len());
} }
#[test] #[test]
pub fn listeners_receive_events() { pub fn listeners_receive_events() {
use TestEvent::*; use TestEvent::*;
let mut listeners = EventListeners::<TestEvent, TestContext>::new(); let mut listeners = EventListeners::<TestEvent, TestContext>::new();
assert!(listeners.add(event_logger)); assert!(listeners.add(event_logger));
let mut publisher = EventPublisher::<TestEvent>::new(); let mut publisher = EventPublisher::<TestEvent>::new();
publisher.queue(Dummy); publisher.queue(Dummy);
publisher.queue(Foobar(1)); publisher.queue(Foobar(1));
publisher.queue(Dummy); publisher.queue(Dummy);
publisher.queue(Foobar(42)); publisher.queue(Foobar(42));
assert_eq!(4, listeners.take_queue_from(&mut publisher)); assert_eq!(4, listeners.take_queue_from(&mut publisher));
let mut context = TestContext::new(); let mut context = TestContext::new();
assert!(context.events.is_empty()); assert!(context.events.is_empty());
assert_eq!(0, context.count); assert_eq!(0, context.count);
listeners.dispatch_queue(&mut context); listeners.dispatch_queue(&mut context);
assert!(!context.events.is_empty()); assert!(!context.events.is_empty());
assert_eq!(0, context.count); assert_eq!(0, context.count);
assert_eq!( assert_eq!(
vec![Dummy, Foobar(1), Dummy, Foobar(42)], vec![Dummy, Foobar(1), Dummy, Foobar(42)],
context.events context.events
); );
let mut context = TestContext::new(); let mut context = TestContext::new();
assert!(context.events.is_empty()); assert!(context.events.is_empty());
assert_eq!(0, context.count); assert_eq!(0, context.count);
listeners.dispatch_queue(&mut context); listeners.dispatch_queue(&mut context);
assert!(context.events.is_empty()); assert!(context.events.is_empty());
assert!(listeners.add(event_counter)); assert!(listeners.add(event_counter));
publisher.queue(Foobar(10)); publisher.queue(Foobar(10));
publisher.queue(Foobar(20)); publisher.queue(Foobar(20));
publisher.queue(Dummy); publisher.queue(Dummy);
listeners.take_queue_from(&mut publisher); listeners.take_queue_from(&mut publisher);
let mut context = TestContext::new(); let mut context = TestContext::new();
listeners.dispatch_queue(&mut context); listeners.dispatch_queue(&mut context);
assert!(!context.events.is_empty()); assert!(!context.events.is_empty());
assert_eq!(3, context.count); assert_eq!(3, context.count);
assert_eq!( assert_eq!(
vec![Foobar(10), Foobar(20), Dummy], vec![Foobar(10), Foobar(20), Dummy],
context.events context.events
); );
} }
#[test] #[test]
pub fn listener_filtering() { pub fn listener_filtering() {
use TestEvent::*; use TestEvent::*;
let mut listeners = EventListeners::<TestEvent, TestContext>::new(); let mut listeners = EventListeners::<TestEvent, TestContext>::new();
assert!(listeners.add(message_filter)); assert!(listeners.add(message_filter));
assert!(listeners.add(event_logger)); assert!(listeners.add(event_logger));
assert!(listeners.add(event_counter)); assert!(listeners.add(event_counter));
let mut publisher = EventPublisher::<TestEvent>::new(); let mut publisher = EventPublisher::<TestEvent>::new();
publisher.queue(Message("hello")); publisher.queue(Message("hello"));
publisher.queue(Dummy); publisher.queue(Dummy);
publisher.queue(Message("filter")); publisher.queue(Message("filter"));
publisher.queue(Foobar(3)); publisher.queue(Foobar(3));
assert_eq!(4, listeners.take_queue_from(&mut publisher)); assert_eq!(4, listeners.take_queue_from(&mut publisher));
let mut context = TestContext::new(); let mut context = TestContext::new();
assert!(context.events.is_empty()); assert!(context.events.is_empty());
assert_eq!(0, context.count); assert_eq!(0, context.count);
listeners.dispatch_queue(&mut context); listeners.dispatch_queue(&mut context);
assert!(!context.events.is_empty()); assert!(!context.events.is_empty());
assert_eq!(3, context.count); assert_eq!(3, context.count);
assert_eq!( assert_eq!(
vec![Message("hello"), Dummy, Foobar(3)], vec![Message("hello"), Dummy, Foobar(3)],
context.events context.events
); );
}
}
} }

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -10,340 +10,340 @@ use crate::utils::bytes::ReadFixedLengthByteArray;
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum PcxError { pub enum PcxError {
#[error("Bad or unsupported PCX file: {0}")] #[error("Bad or unsupported PCX file: {0}")]
BadFile(String), BadFile(String),
#[error("PCX palette data error")] #[error("PCX palette data error")]
BadPalette(#[from] PaletteError), BadPalette(#[from] PaletteError),
#[error("PCX I/O error")] #[error("PCX I/O error")]
IOError(#[from] std::io::Error), IOError(#[from] std::io::Error),
} }
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
#[repr(packed)] #[repr(packed)]
struct PcxHeader { struct PcxHeader {
manufacturer: u8, manufacturer: u8,
version: u8, version: u8,
encoding: u8, encoding: u8,
bpp: u8, bpp: u8,
x1: u16, x1: u16,
y1: u16, y1: u16,
x2: u16, x2: u16,
y2: u16, y2: u16,
horizontal_dpi: u16, horizontal_dpi: u16,
vertical_dpi: u16, vertical_dpi: u16,
ega_palette: [u8; 48], ega_palette: [u8; 48],
reserved: u8, reserved: u8,
num_color_planes: u8, num_color_planes: u8,
bytes_per_line: u16, bytes_per_line: u16,
palette_type: u16, palette_type: u16,
horizontal_size: u16, horizontal_size: u16,
vertical_size: u16, vertical_size: u16,
padding: [u8; 54], padding: [u8; 54],
} }
impl PcxHeader { impl PcxHeader {
pub fn read<T: ReadBytesExt>(reader: &mut T) -> Result<Self, PcxError> { pub fn read<T: ReadBytesExt>(reader: &mut T) -> Result<Self, PcxError> {
Ok(PcxHeader { Ok(PcxHeader {
manufacturer: reader.read_u8()?, manufacturer: reader.read_u8()?,
version: reader.read_u8()?, version: reader.read_u8()?,
encoding: reader.read_u8()?, encoding: reader.read_u8()?,
bpp: reader.read_u8()?, bpp: reader.read_u8()?,
x1: reader.read_u16::<LittleEndian>()?, x1: reader.read_u16::<LittleEndian>()?,
y1: reader.read_u16::<LittleEndian>()?, y1: reader.read_u16::<LittleEndian>()?,
x2: reader.read_u16::<LittleEndian>()?, x2: reader.read_u16::<LittleEndian>()?,
y2: reader.read_u16::<LittleEndian>()?, y2: reader.read_u16::<LittleEndian>()?,
horizontal_dpi: reader.read_u16::<LittleEndian>()?, horizontal_dpi: reader.read_u16::<LittleEndian>()?,
vertical_dpi: reader.read_u16::<LittleEndian>()?, vertical_dpi: reader.read_u16::<LittleEndian>()?,
ega_palette: reader.read_bytes()?, ega_palette: reader.read_bytes()?,
reserved: reader.read_u8()?, reserved: reader.read_u8()?,
num_color_planes: reader.read_u8()?, num_color_planes: reader.read_u8()?,
bytes_per_line: reader.read_u16::<LittleEndian>()?, bytes_per_line: reader.read_u16::<LittleEndian>()?,
palette_type: reader.read_u16::<LittleEndian>()?, palette_type: reader.read_u16::<LittleEndian>()?,
horizontal_size: reader.read_u16::<LittleEndian>()?, horizontal_size: reader.read_u16::<LittleEndian>()?,
vertical_size: reader.read_u16::<LittleEndian>()?, vertical_size: reader.read_u16::<LittleEndian>()?,
padding: reader.read_bytes()?, padding: reader.read_bytes()?,
}) })
} }
pub fn write<T: WriteBytesExt>(&self, writer: &mut T) -> Result<(), PcxError> { pub fn write<T: WriteBytesExt>(&self, writer: &mut T) -> Result<(), PcxError> {
writer.write_u8(self.manufacturer)?; writer.write_u8(self.manufacturer)?;
writer.write_u8(self.version)?; writer.write_u8(self.version)?;
writer.write_u8(self.encoding)?; writer.write_u8(self.encoding)?;
writer.write_u8(self.bpp)?; writer.write_u8(self.bpp)?;
writer.write_u16::<LittleEndian>(self.x1)?; writer.write_u16::<LittleEndian>(self.x1)?;
writer.write_u16::<LittleEndian>(self.y1)?; writer.write_u16::<LittleEndian>(self.y1)?;
writer.write_u16::<LittleEndian>(self.x2)?; writer.write_u16::<LittleEndian>(self.x2)?;
writer.write_u16::<LittleEndian>(self.y2)?; writer.write_u16::<LittleEndian>(self.y2)?;
writer.write_u16::<LittleEndian>(self.horizontal_dpi)?; writer.write_u16::<LittleEndian>(self.horizontal_dpi)?;
writer.write_u16::<LittleEndian>(self.vertical_dpi)?; writer.write_u16::<LittleEndian>(self.vertical_dpi)?;
writer.write_all(&self.ega_palette)?; writer.write_all(&self.ega_palette)?;
writer.write_u8(self.reserved)?; writer.write_u8(self.reserved)?;
writer.write_u8(self.num_color_planes)?; writer.write_u8(self.num_color_planes)?;
writer.write_u16::<LittleEndian>(self.bytes_per_line)?; writer.write_u16::<LittleEndian>(self.bytes_per_line)?;
writer.write_u16::<LittleEndian>(self.palette_type)?; writer.write_u16::<LittleEndian>(self.palette_type)?;
writer.write_u16::<LittleEndian>(self.horizontal_size)?; writer.write_u16::<LittleEndian>(self.horizontal_size)?;
writer.write_u16::<LittleEndian>(self.vertical_size)?; writer.write_u16::<LittleEndian>(self.vertical_size)?;
writer.write_all(&self.padding)?; writer.write_all(&self.padding)?;
Ok(()) Ok(())
} }
} }
fn write_pcx_data<T: WriteBytesExt>( fn write_pcx_data<T: WriteBytesExt>(
writer: &mut T, writer: &mut T,
run_count: u8, run_count: u8,
pixel: u8, pixel: u8,
) -> Result<(), PcxError> { ) -> Result<(), PcxError> {
if (run_count > 1) || ((pixel & 0xc0) == 0xc0) { if (run_count > 1) || ((pixel & 0xc0) == 0xc0) {
writer.write_u8(0xc0 | run_count)?; writer.write_u8(0xc0 | run_count)?;
} }
writer.write_u8(pixel)?; writer.write_u8(pixel)?;
Ok(()) Ok(())
} }
impl Bitmap { impl Bitmap {
pub fn load_pcx_bytes<T: ReadBytesExt + Seek>( pub fn load_pcx_bytes<T: ReadBytesExt + Seek>(
reader: &mut T, reader: &mut T,
) -> Result<(Bitmap, Palette), PcxError> { ) -> Result<(Bitmap, Palette), PcxError> {
let header = PcxHeader::read(reader)?; let header = PcxHeader::read(reader)?;
if header.manufacturer != 10 { if header.manufacturer != 10 {
return Err(PcxError::BadFile(String::from( return Err(PcxError::BadFile(String::from(
"Unexpected header.manufacturer value, probably not a PCX file", "Unexpected header.manufacturer value, probably not a PCX file",
))); )));
} }
if header.version != 5 { if header.version != 5 {
return Err(PcxError::BadFile(String::from( return Err(PcxError::BadFile(String::from(
"Only version 5 PCX files are supported", "Only version 5 PCX files are supported",
))); )));
} }
if header.encoding != 1 { if header.encoding != 1 {
return Err(PcxError::BadFile(String::from( return Err(PcxError::BadFile(String::from(
"Only RLE-compressed PCX files are supported", "Only RLE-compressed PCX files are supported",
))); )));
} }
if header.bpp != 8 { if header.bpp != 8 {
return Err(PcxError::BadFile(String::from( return Err(PcxError::BadFile(String::from(
"Only 8-bit indexed (256 color palette) PCX files are supported", "Only 8-bit indexed (256 color palette) PCX files are supported",
))); )));
} }
if header.x2 == 0 || header.y2 == 0 { if header.x2 == 0 || header.y2 == 0 {
return Err(PcxError::BadFile(String::from( return Err(PcxError::BadFile(String::from(
"Invalid PCX image dimensions", "Invalid PCX image dimensions",
))); )));
} }
// read the PCX file's pixel data into a bitmap // read the PCX file's pixel data into a bitmap
let width = (header.x2 + 1) as u32; let width = (header.x2 + 1) as u32;
let height = (header.y2 + 1) as u32; let height = (header.y2 + 1) as u32;
let mut bmp = Bitmap::new(width, height).unwrap(); let mut bmp = Bitmap::new(width, height).unwrap();
let mut writer = Cursor::new(bmp.pixels_mut()); let mut writer = Cursor::new(bmp.pixels_mut());
for _y in 0..height { for _y in 0..height {
// read the next scanline's worth of pixels from the PCX file // read the next scanline's worth of pixels from the PCX file
let mut x: u32 = 0; let mut x: u32 = 0;
while x < (header.bytes_per_line as u32) { while x < (header.bytes_per_line as u32) {
let mut data: u8; let mut data: u8;
let mut count: u32; let mut count: u32;
// read pixel or RLE count // read pixel or RLE count
data = reader.read_u8()?; data = reader.read_u8()?;
if (data & 0xc0) == 0xc0 { if (data & 0xc0) == 0xc0 {
// it was an RLE count, actual pixel is the next byte ... // it was an RLE count, actual pixel is the next byte ...
count = (data & 0x3f) as u32; count = (data & 0x3f) as u32;
data = reader.read_u8()?; data = reader.read_u8()?;
} else { } else {
// it was just a single pixel // it was just a single pixel
count = 1; count = 1;
} }
// write the current pixel value 'data' to the bitmap 'count' number of times // write the current pixel value 'data' to the bitmap 'count' number of times
while count > 0 { while count > 0 {
if x <= width { if x <= width {
writer.write_u8(data)?; writer.write_u8(data)?;
} else { } else {
writer.seek(SeekFrom::Current(1))?; writer.seek(SeekFrom::Current(1))?;
} }
x += 1; x += 1;
count -= 1; count -= 1;
} }
} }
} }
// now read the palette data located at the end of the PCX file // now read the palette data located at the end of the PCX file
// palette data should be for 256 colors, 3 bytes per color = 768 bytes // palette data should be for 256 colors, 3 bytes per color = 768 bytes
// the palette is preceded by a single byte, 0x0c, which we will also validate // the palette is preceded by a single byte, 0x0c, which we will also validate
reader.seek(SeekFrom::End(-769))?; reader.seek(SeekFrom::End(-769))?;
let palette_marker = reader.read_u8()?; let palette_marker = reader.read_u8()?;
if palette_marker != 0x0c { if palette_marker != 0x0c {
return Err(PcxError::BadFile(String::from( return Err(PcxError::BadFile(String::from(
"Palette not found at end of file", "Palette not found at end of file",
))); )));
} }
let palette = Palette::load_from_bytes(reader, PaletteFormat::Normal)?; let palette = Palette::load_from_bytes(reader, PaletteFormat::Normal)?;
Ok((bmp, palette)) Ok((bmp, palette))
} }
pub fn load_pcx_file(path: &Path) -> Result<(Bitmap, Palette), PcxError> { pub fn load_pcx_file(path: &Path) -> Result<(Bitmap, Palette), PcxError> {
let f = File::open(path)?; let f = File::open(path)?;
let mut reader = BufReader::new(f); let mut reader = BufReader::new(f);
Self::load_pcx_bytes(&mut reader) Self::load_pcx_bytes(&mut reader)
} }
pub fn to_pcx_bytes<T: WriteBytesExt>( pub fn to_pcx_bytes<T: WriteBytesExt>(
&self, &self,
writer: &mut T, writer: &mut T,
palette: &Palette, palette: &Palette,
) -> Result<(), PcxError> { ) -> Result<(), PcxError> {
let header = PcxHeader { let header = PcxHeader {
manufacturer: 10, manufacturer: 10,
version: 5, version: 5,
encoding: 1, encoding: 1,
bpp: 8, bpp: 8,
x1: 0, x1: 0,
y1: 0, y1: 0,
x2: self.right() as u16, x2: self.right() as u16,
y2: self.bottom() as u16, y2: self.bottom() as u16,
horizontal_dpi: 320, horizontal_dpi: 320,
vertical_dpi: 200, vertical_dpi: 200,
ega_palette: [0u8; 48], ega_palette: [0u8; 48],
reserved: 0, reserved: 0,
num_color_planes: 1, num_color_planes: 1,
bytes_per_line: self.width() as u16, bytes_per_line: self.width() as u16,
palette_type: 1, palette_type: 1,
horizontal_size: self.width() as u16, horizontal_size: self.width() as u16,
vertical_size: self.height() as u16, vertical_size: self.height() as u16,
padding: [0u8; 54], padding: [0u8; 54],
}; };
header.write(writer)?; header.write(writer)?;
let pixels = self.pixels(); let pixels = self.pixels();
let mut i = 0; let mut i = 0;
for _y in 0..=self.bottom() { for _y in 0..=self.bottom() {
// write one scanline at a time. breaking runs that could have continued across // write one scanline at a time. breaking runs that could have continued across
// scanlines in the process, as per the pcx standard // scanlines in the process, as per the pcx standard
let mut run_count = 0; let mut run_count = 0;
let mut run_pixel = 0; let mut run_pixel = 0;
for _x in 0..=self.right() { for _x in 0..=self.right() {
let pixel = pixels[i]; let pixel = pixels[i];
i += 1; i += 1;
if run_count == 0 { if run_count == 0 {
run_count = 1; run_count = 1;
run_pixel = pixel; run_pixel = pixel;
} else { } else {
if (pixel != run_pixel) || (run_count >= 63) { if (pixel != run_pixel) || (run_count >= 63) {
write_pcx_data(writer, run_count, run_pixel)?; write_pcx_data(writer, run_count, run_pixel)?;
run_count = 1; run_count = 1;
run_pixel = pixel; run_pixel = pixel;
} else { } else {
run_count += 1; run_count += 1;
} }
} }
} }
// end the scanline, writing out whatever run we might have had going // end the scanline, writing out whatever run we might have had going
write_pcx_data(writer, run_count, run_pixel)?; write_pcx_data(writer, run_count, run_pixel)?;
} }
// marker for beginning of palette data // marker for beginning of palette data
writer.write_u8(0xc)?; writer.write_u8(0xc)?;
for i in 0..=255 { for i in 0..=255 {
let argb = palette[i]; let argb = palette[i];
let (r, g, b) = from_rgb32(argb); let (r, g, b) = from_rgb32(argb);
writer.write_u8(r)?; writer.write_u8(r)?;
writer.write_u8(g)?; writer.write_u8(g)?;
writer.write_u8(b)?; writer.write_u8(b)?;
} }
Ok(()) Ok(())
} }
pub fn to_pcx_file(&self, path: &Path, palette: &Palette) -> Result<(), PcxError> { pub fn to_pcx_file(&self, path: &Path, palette: &Palette) -> Result<(), PcxError> {
let f = File::create(path)?; let f = File::create(path)?;
let mut writer = BufWriter::new(f); let mut writer = BufWriter::new(f);
self.to_pcx_bytes(&mut writer, palette) self.to_pcx_bytes(&mut writer, palette)
} }
} }
#[cfg(test)] #[cfg(test)]
pub mod tests { pub mod tests {
use tempfile::TempDir; use tempfile::TempDir;
use super::*; use super::*;
pub static TEST_BMP_PIXELS_RAW: &[u8] = include_bytes!("../../../test-assets/test_bmp_pixels_raw.bin"); pub static TEST_BMP_PIXELS_RAW: &[u8] = include_bytes!("../../../test-assets/test_bmp_pixels_raw.bin");
pub static TEST_LARGE_BMP_PIXELS_RAW: &[u8] = include_bytes!("../../../test-assets/test_large_bmp_pixels_raw.bin"); pub static TEST_LARGE_BMP_PIXELS_RAW: &[u8] = include_bytes!("../../../test-assets/test_large_bmp_pixels_raw.bin");
pub static TEST_LARGE_BMP_PIXELS_RAW_2: &[u8] = include_bytes!("../../../test-assets/test_large_bmp_pixels_raw2.bin"); pub static TEST_LARGE_BMP_PIXELS_RAW_2: &[u8] = include_bytes!("../../../test-assets/test_large_bmp_pixels_raw2.bin");
#[test] #[test]
pub fn load_and_save() -> Result<(), PcxError> { pub fn load_and_save() -> Result<(), PcxError> {
let dp2_palette = let dp2_palette =
Palette::load_from_file(Path::new("./test-assets/dp2.pal"), PaletteFormat::Normal) Palette::load_from_file(Path::new("./test-assets/dp2.pal"), PaletteFormat::Normal)
.unwrap(); .unwrap();
let tmp_dir = TempDir::new()?; let tmp_dir = TempDir::new()?;
let (bmp, palette) = Bitmap::load_pcx_file(Path::new("./test-assets/test.pcx"))?; let (bmp, palette) = Bitmap::load_pcx_file(Path::new("./test-assets/test.pcx"))?;
assert_eq!(16, bmp.width()); assert_eq!(16, bmp.width());
assert_eq!(16, bmp.height()); assert_eq!(16, bmp.height());
assert_eq!(bmp.pixels(), TEST_BMP_PIXELS_RAW); assert_eq!(bmp.pixels(), TEST_BMP_PIXELS_RAW);
assert_eq!(palette, dp2_palette); assert_eq!(palette, dp2_palette);
let save_path = tmp_dir.path().join("test_save.pcx"); let save_path = tmp_dir.path().join("test_save.pcx");
bmp.to_pcx_file(&save_path, &palette)?; bmp.to_pcx_file(&save_path, &palette)?;
let (reloaded_bmp, reloaded_palette) = Bitmap::load_pcx_file(&save_path)?; let (reloaded_bmp, reloaded_palette) = Bitmap::load_pcx_file(&save_path)?;
assert_eq!(16, reloaded_bmp.width()); assert_eq!(16, reloaded_bmp.width());
assert_eq!(16, reloaded_bmp.height()); assert_eq!(16, reloaded_bmp.height());
assert_eq!(reloaded_bmp.pixels(), TEST_BMP_PIXELS_RAW); assert_eq!(reloaded_bmp.pixels(), TEST_BMP_PIXELS_RAW);
assert_eq!(reloaded_palette, dp2_palette); assert_eq!(reloaded_palette, dp2_palette);
Ok(()) Ok(())
} }
#[test] #[test]
pub fn load_and_save_larger_image() -> Result<(), PcxError> { pub fn load_and_save_larger_image() -> Result<(), PcxError> {
let tmp_dir = TempDir::new()?; let tmp_dir = TempDir::new()?;
// first image // first image
let (bmp, palette) = Bitmap::load_pcx_file(Path::new("./test-assets/test_image.pcx"))?; let (bmp, palette) = Bitmap::load_pcx_file(Path::new("./test-assets/test_image.pcx"))?;
assert_eq!(320, bmp.width()); assert_eq!(320, bmp.width());
assert_eq!(200, bmp.height()); assert_eq!(200, bmp.height());
assert_eq!(bmp.pixels(), TEST_LARGE_BMP_PIXELS_RAW); assert_eq!(bmp.pixels(), TEST_LARGE_BMP_PIXELS_RAW);
let save_path = tmp_dir.path().join("test_save.pcx"); let save_path = tmp_dir.path().join("test_save.pcx");
bmp.to_pcx_file(&save_path, &palette)?; bmp.to_pcx_file(&save_path, &palette)?;
let (reloaded_bmp, _) = Bitmap::load_pcx_file(&save_path)?; let (reloaded_bmp, _) = Bitmap::load_pcx_file(&save_path)?;
assert_eq!(320, reloaded_bmp.width()); assert_eq!(320, reloaded_bmp.width());
assert_eq!(200, reloaded_bmp.height()); assert_eq!(200, reloaded_bmp.height());
assert_eq!(reloaded_bmp.pixels(), TEST_LARGE_BMP_PIXELS_RAW); assert_eq!(reloaded_bmp.pixels(), TEST_LARGE_BMP_PIXELS_RAW);
// second image // second image
let (bmp, palette) = Bitmap::load_pcx_file(Path::new("./test-assets/test_image2.pcx"))?; let (bmp, palette) = Bitmap::load_pcx_file(Path::new("./test-assets/test_image2.pcx"))?;
assert_eq!(320, bmp.width()); assert_eq!(320, bmp.width());
assert_eq!(200, bmp.height()); assert_eq!(200, bmp.height());
assert_eq!(bmp.pixels(), TEST_LARGE_BMP_PIXELS_RAW_2); assert_eq!(bmp.pixels(), TEST_LARGE_BMP_PIXELS_RAW_2);
let save_path = tmp_dir.path().join("test_save_2.pcx"); let save_path = tmp_dir.path().join("test_save_2.pcx");
bmp.to_pcx_file(&save_path, &palette)?; bmp.to_pcx_file(&save_path, &palette)?;
let (reloaded_bmp, _) = Bitmap::load_pcx_file(&save_path)?; let (reloaded_bmp, _) = Bitmap::load_pcx_file(&save_path)?;
assert_eq!(320, reloaded_bmp.width()); assert_eq!(320, reloaded_bmp.width());
assert_eq!(200, reloaded_bmp.height()); assert_eq!(200, reloaded_bmp.height());
assert_eq!(reloaded_bmp.pixels(), TEST_LARGE_BMP_PIXELS_RAW_2); assert_eq!(reloaded_bmp.pixels(), TEST_LARGE_BMP_PIXELS_RAW_2);
Ok(()) Ok(())
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -7,181 +7,181 @@ use crate::math::*;
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum BitmapAtlasError { pub enum BitmapAtlasError {
#[error("Region is out of bounds for the Bitmap used by the BitmapAtlas")] #[error("Region is out of bounds for the Bitmap used by the BitmapAtlas")]
OutOfBounds, OutOfBounds,
} }
#[derive(Debug, Clone, Eq, PartialEq)] #[derive(Debug, Clone, Eq, PartialEq)]
pub struct BitmapAtlas { pub struct BitmapAtlas {
bitmap: Bitmap, bitmap: Bitmap,
bounds: Rect, bounds: Rect,
tiles: Vec<Rect>, tiles: Vec<Rect>,
} }
impl BitmapAtlas { impl BitmapAtlas {
pub fn new(bitmap: Bitmap) -> BitmapAtlas { pub fn new(bitmap: Bitmap) -> BitmapAtlas {
let bounds = bitmap.full_bounds(); let bounds = bitmap.full_bounds();
BitmapAtlas { BitmapAtlas {
bitmap, bitmap,
bounds, bounds,
tiles: Vec::new(), tiles: Vec::new(),
} }
} }
pub fn add(&mut self, rect: Rect) -> Result<usize, BitmapAtlasError> { pub fn add(&mut self, rect: Rect) -> Result<usize, BitmapAtlasError> {
if !self.bounds.contains_rect(&rect) { if !self.bounds.contains_rect(&rect) {
return Err(BitmapAtlasError::OutOfBounds); return Err(BitmapAtlasError::OutOfBounds);
} }
self.tiles.push(rect); self.tiles.push(rect);
Ok(self.tiles.len() - 1) Ok(self.tiles.len() - 1)
} }
pub fn add_grid( pub fn add_grid(
&mut self, &mut self,
tile_width: u32, tile_width: u32,
tile_height: u32, tile_height: u32,
) -> Result<usize, BitmapAtlasError> { ) -> Result<usize, BitmapAtlasError> {
if self.bounds.width < tile_width || self.bounds.height < tile_height { if self.bounds.width < tile_width || self.bounds.height < tile_height {
return Err(BitmapAtlasError::OutOfBounds); return Err(BitmapAtlasError::OutOfBounds);
} }
for yt in 0..(self.bounds.height / tile_height) { for yt in 0..(self.bounds.height / tile_height) {
for xt in 0..(self.bounds.width) / tile_width { for xt in 0..(self.bounds.width) / tile_width {
let x = xt * tile_width; let x = xt * tile_width;
let y = yt * tile_height; let y = yt * tile_height;
let rect = Rect::new(x as i32, y as i32, tile_width, tile_height); let rect = Rect::new(x as i32, y as i32, tile_width, tile_height);
self.tiles.push(rect); self.tiles.push(rect);
} }
} }
Ok(self.tiles.len() - 1) Ok(self.tiles.len() - 1)
} }
pub fn add_custom_grid( pub fn add_custom_grid(
&mut self, &mut self,
start_x: u32, start_x: u32,
start_y: u32, start_y: u32,
tile_width: u32, tile_width: u32,
tile_height: u32, tile_height: u32,
x_tiles: u32, x_tiles: u32,
y_tiles: u32, y_tiles: u32,
border: u32, border: u32,
) -> Result<usize, BitmapAtlasError> { ) -> Result<usize, BitmapAtlasError> {
// figure out of the grid properties given would result in us creating any // figure out of the grid properties given would result in us creating any
// rects that lie out of the bounds of this bitmap // rects that lie out of the bounds of this bitmap
let grid_region = Rect::new( let grid_region = Rect::new(
start_x as i32, start_x as i32,
start_y as i32, start_y as i32,
(tile_width + border) * x_tiles + border, (tile_width + border) * x_tiles + border,
(tile_height + border) * y_tiles + border, (tile_height + border) * y_tiles + border,
); );
if !self.bounds.contains_rect(&grid_region) { if !self.bounds.contains_rect(&grid_region) {
return Err(BitmapAtlasError::OutOfBounds); return Err(BitmapAtlasError::OutOfBounds);
} }
// all good! now create all the tiles needed for the grid specified // all good! now create all the tiles needed for the grid specified
for yt in 0..y_tiles { for yt in 0..y_tiles {
for xt in 0..x_tiles { for xt in 0..x_tiles {
let x = start_x + (tile_width + border) * xt; let x = start_x + (tile_width + border) * xt;
let y = start_y + (tile_height + border) * yt; let y = start_y + (tile_height + border) * yt;
let rect = Rect::new(x as i32, y as i32, tile_width, tile_height); let rect = Rect::new(x as i32, y as i32, tile_width, tile_height);
self.tiles.push(rect); self.tiles.push(rect);
} }
} }
Ok(self.tiles.len() - 1) Ok(self.tiles.len() - 1)
} }
pub fn clear(&mut self) {
self.tiles.clear()
}
#[inline] pub fn clear(&mut self) {
pub fn len(&self) -> usize { self.tiles.clear()
self.tiles.len() }
}
#[inline] #[inline]
pub fn get(&self, index: usize) -> Option<&Rect> { pub fn len(&self) -> usize {
self.tiles.get(index) self.tiles.len()
} }
#[inline] #[inline]
pub fn bitmap(&self) -> &Bitmap { pub fn get(&self, index: usize) -> Option<&Rect> {
&self.bitmap self.tiles.get(index)
} }
#[inline]
pub fn bitmap(&self) -> &Bitmap {
&self.bitmap
}
} }
impl Index<usize> for BitmapAtlas { impl Index<usize> for BitmapAtlas {
type Output = Rect; type Output = Rect;
#[inline] #[inline]
fn index(&self, index: usize) -> &Self::Output { fn index(&self, index: usize) -> &Self::Output {
self.get(index).unwrap() self.get(index).unwrap()
} }
} }
#[cfg(test)] #[cfg(test)]
pub mod tests { pub mod tests {
use claim::*; use claim::*;
use super::*; use super::*;
#[test] #[test]
pub fn adding_rects() { pub fn adding_rects() {
let bmp = Bitmap::new(64, 64).unwrap(); let bmp = Bitmap::new(64, 64).unwrap();
let mut atlas = BitmapAtlas::new(bmp); let mut atlas = BitmapAtlas::new(bmp);
let rect = Rect::new(0, 0, 16, 16); let rect = Rect::new(0, 0, 16, 16);
assert_eq!(0, atlas.add(rect.clone()).unwrap()); assert_eq!(0, atlas.add(rect.clone()).unwrap());
assert_eq!(rect, atlas[0]); assert_eq!(rect, atlas[0]);
assert_eq!(1, atlas.len()); assert_eq!(1, atlas.len());
let rect = Rect::new(16, 0, 16, 16); let rect = Rect::new(16, 0, 16, 16);
assert_eq!(1, atlas.add(rect.clone()).unwrap()); assert_eq!(1, atlas.add(rect.clone()).unwrap());
assert_eq!(rect, atlas[1]); assert_eq!(rect, atlas[1]);
assert_eq!(2, atlas.len()); assert_eq!(2, atlas.len());
assert_matches!( assert_matches!(
atlas.add(Rect::new(56, 0, 16, 16)), atlas.add(Rect::new(56, 0, 16, 16)),
Err(BitmapAtlasError::OutOfBounds) Err(BitmapAtlasError::OutOfBounds)
); );
assert_eq!(2, atlas.len()); assert_eq!(2, atlas.len());
assert_matches!( assert_matches!(
atlas.add(Rect::new(-8, 4, 16, 16)), atlas.add(Rect::new(-8, 4, 16, 16)),
Err(BitmapAtlasError::OutOfBounds) Err(BitmapAtlasError::OutOfBounds)
); );
assert_eq!(2, atlas.len()); assert_eq!(2, atlas.len());
assert_matches!( assert_matches!(
atlas.add(Rect::new(0, 0, 128, 128)), atlas.add(Rect::new(0, 0, 128, 128)),
Err(BitmapAtlasError::OutOfBounds) Err(BitmapAtlasError::OutOfBounds)
); );
assert_eq!(2, atlas.len()); assert_eq!(2, atlas.len());
} }
#[test] #[test]
pub fn adding_grid() { pub fn adding_grid() {
let bmp = Bitmap::new(64, 64).unwrap(); let bmp = Bitmap::new(64, 64).unwrap();
let mut atlas = BitmapAtlas::new(bmp); let mut atlas = BitmapAtlas::new(bmp);
assert_eq!(3, atlas.add_custom_grid(0, 0, 8, 8, 2, 2, 0).unwrap()); assert_eq!(3, atlas.add_custom_grid(0, 0, 8, 8, 2, 2, 0).unwrap());
assert_eq!(4, atlas.len()); assert_eq!(4, atlas.len());
assert_eq!(Rect::new(0, 0, 8, 8), atlas[0]); assert_eq!(Rect::new(0, 0, 8, 8), atlas[0]);
assert_eq!(Rect::new(8, 0, 8, 8), atlas[1]); assert_eq!(Rect::new(8, 0, 8, 8), atlas[1]);
assert_eq!(Rect::new(0, 8, 8, 8), atlas[2]); assert_eq!(Rect::new(0, 8, 8, 8), atlas[2]);
assert_eq!(Rect::new(8, 8, 8, 8), atlas[3]); assert_eq!(Rect::new(8, 8, 8, 8), atlas[3]);
atlas.clear(); atlas.clear();
assert_eq!(0, atlas.len()); assert_eq!(0, atlas.len());
assert_eq!(3, atlas.add_custom_grid(0, 0, 4, 8, 2, 2, 1).unwrap()); assert_eq!(3, atlas.add_custom_grid(0, 0, 4, 8, 2, 2, 1).unwrap());
assert_eq!(4, atlas.len()); assert_eq!(4, atlas.len());
assert_eq!(Rect::new(0, 0, 4, 8), atlas[0]); assert_eq!(Rect::new(0, 0, 4, 8), atlas[0]);
assert_eq!(Rect::new(5, 0, 4, 8), atlas[1]); assert_eq!(Rect::new(5, 0, 4, 8), atlas[1]);
assert_eq!(Rect::new(0, 9, 4, 8), atlas[2]); assert_eq!(Rect::new(0, 9, 4, 8), atlas[2]);
assert_eq!(Rect::new(5, 9, 4, 8), atlas[3]); assert_eq!(Rect::new(5, 9, 4, 8), atlas[3]);
} }
} }

View file

@ -12,14 +12,14 @@ use crate::utils::bytes::ReadFixedLengthByteArray;
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum BlendMapError { pub enum BlendMapError {
#[error("Source color {0} is out of range for this BlendMap")] #[error("Source color {0} is out of range for this BlendMap")]
InvalidSourceColor(u8), InvalidSourceColor(u8),
#[error("Bad or unsupported BlendMap file: {0}")] #[error("Bad or unsupported BlendMap file: {0}")]
BadFile(String), BadFile(String),
#[error("BlendMap I/O error")] #[error("BlendMap I/O error")]
IOError(#[from] std::io::Error), IOError(#[from] std::io::Error),
} }
/// A lookup table used by [`BlendMap`]s. This table stores destination color to blend color /// A lookup table used by [`BlendMap`]s. This table stores destination color to blend color
@ -39,378 +39,378 @@ pub type BlendMapping = [u8; 256];
/// source color, it will have 256 destination to blended color mappings. /// source color, it will have 256 destination to blended color mappings.
#[derive(Clone, Eq, PartialEq)] #[derive(Clone, Eq, PartialEq)]
pub struct BlendMap { pub struct BlendMap {
start_color: u8, start_color: u8,
end_color: u8, end_color: u8,
mapping: Box<[BlendMapping]>, mapping: Box<[BlendMapping]>,
} }
impl std::fmt::Debug for BlendMap { impl std::fmt::Debug for BlendMap {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("BlendMap") f.debug_struct("BlendMap")
.field("start_color", &self.start_color) .field("start_color", &self.start_color)
.field("end_color", &self.end_color) .field("end_color", &self.end_color)
.finish_non_exhaustive() .finish_non_exhaustive()
} }
} }
impl BlendMap { impl BlendMap {
/// Creates and returns a new [`BlendMap`] with source color mappings for the given inclusive /// Creates and returns a new [`BlendMap`] with source color mappings for the given inclusive
/// range only. The `start_color` and `end_color` may also be equal to create a blend map with /// range only. The `start_color` and `end_color` may also be equal to create a blend map with
/// only a single source color mapping. /// only a single source color mapping.
pub fn new(start_color: u8, end_color: u8) -> Self { pub fn new(start_color: u8, end_color: u8) -> Self {
let (start_color, end_color) = if start_color > end_color { let (start_color, end_color) = if start_color > end_color {
(end_color, start_color) (end_color, start_color)
} else { } else {
(start_color, end_color) (start_color, end_color)
}; };
let num_colors = (end_color - start_color) as usize + 1; let num_colors = (end_color - start_color) as usize + 1;
BlendMap { BlendMap {
start_color, start_color,
end_color, end_color,
mapping: vec![[0u8; 256]; num_colors].into_boxed_slice(), mapping: vec![[0u8; 256]; num_colors].into_boxed_slice(),
} }
} }
/// Creates and returns a new [`BlendMap`] with a single source color mapping which maps to /// Creates and returns a new [`BlendMap`] with a single source color mapping which maps to
/// a table pre-calculated for the given palette based on the color gradient specified. The /// a table pre-calculated for the given palette based on the color gradient specified. The
/// resulting blend map can be used to create simple "colorization" overlay effects, which look /// resulting blend map can be used to create simple "colorization" overlay effects, which look
/// like a simple translucency effect. The starting color in the gradient is used as the source /// like a simple translucency effect. The starting color in the gradient is used as the source
/// color mapping in the returned blend map. /// color mapping in the returned blend map.
pub fn new_colorized_map(gradient_start: u8, gradient_end: u8, palette: &Palette) -> Self { pub fn new_colorized_map(gradient_start: u8, gradient_end: u8, palette: &Palette) -> Self {
let (gradient_start, gradient_end) = if gradient_start > gradient_end { let (gradient_start, gradient_end) = if gradient_start > gradient_end {
(gradient_end, gradient_start) (gradient_end, gradient_start)
} else { } else {
(gradient_start, gradient_end) (gradient_start, gradient_end)
}; };
let gradient_size = gradient_end - gradient_start + 1; let gradient_size = gradient_end - gradient_start + 1;
let source_color = gradient_start; let source_color = gradient_start;
let mut blend_map = Self::new(source_color, source_color); let mut blend_map = Self::new(source_color, source_color);
for idx in 0..=255 { for idx in 0..=255 {
let (r, g, b) = from_rgb32(palette[idx]); let (r, g, b) = from_rgb32(palette[idx]);
let lit = (luminance(r, g, b) * 255.0) as u8; let lit = (luminance(r, g, b) * 255.0) as u8;
blend_map.set_mapping( blend_map.set_mapping(
source_color, source_color,
idx as u8, idx as u8,
(gradient_size - 1) - (lit / (256 / gradient_size as u32) as u8) + source_color (gradient_size - 1) - (lit / (256 / gradient_size as u32) as u8) + source_color,
).unwrap(); ).unwrap();
} }
blend_map blend_map
} }
/// Creates and returns a new [`BlendMap`] which can be used to blend source colors together /// Creates and returns a new [`BlendMap`] which can be used to blend source colors together
/// with the destination using a colorization effect based on a function providing a custom /// with the destination using a colorization effect based on a function providing a custom
/// calculation combining the source and destination color luminance values to return a weight /// calculation combining the source and destination color luminance values to return a weight
/// into the gradient range given. /// into the gradient range given.
pub fn new_colored_luminance_map( pub fn new_colored_luminance_map(
gradient_start: u8, gradient_start: u8,
gradient_end: u8, gradient_end: u8,
palette: &Palette, palette: &Palette,
f: impl Fn(f32, f32) -> f32 f: impl Fn(f32, f32) -> f32,
) -> BlendMap { ) -> BlendMap {
let (gradient_start, gradient_end) = if gradient_start > gradient_end { let (gradient_start, gradient_end) = if gradient_start > gradient_end {
(gradient_end, gradient_start) (gradient_end, gradient_start)
} else { } else {
(gradient_start, gradient_end) (gradient_start, gradient_end)
}; };
let gradient_size = gradient_end - gradient_start + 1; let gradient_size = gradient_end - gradient_start + 1;
let mut blend_map = BlendMap::new(0, 255); let mut blend_map = BlendMap::new(0, 255);
for source_color in 0..=255 { for source_color in 0..=255 {
let (r, g, b) = from_rgb32(palette[source_color]); let (r, g, b) = from_rgb32(palette[source_color]);
let source_luminance = luminance(r, g, b); let source_luminance = luminance(r, g, b);
for dest_color in 0..=255 { for dest_color in 0..=255 {
let (r, g, b) = from_rgb32(palette[dest_color]); let (r, g, b) = from_rgb32(palette[dest_color]);
let destination_luminance = luminance(r, g, b); let destination_luminance = luminance(r, g, b);
let weight = (f(source_luminance, destination_luminance) * 255.0) as u8; let weight = (f(source_luminance, destination_luminance) * 255.0) as u8;
blend_map.set_mapping( blend_map.set_mapping(
source_color, source_color,
dest_color, dest_color,
(gradient_size - 1).wrapping_sub(weight / (256 / gradient_size as u32) as u8) + gradient_start (gradient_size - 1).wrapping_sub(weight / (256 / gradient_size as u32) as u8) + gradient_start,
).unwrap(); ).unwrap();
} }
} }
blend_map blend_map
} }
/// Creates and returns a new [`BlendMap`] which can be used to blend all 256 colors together /// Creates and returns a new [`BlendMap`] which can be used to blend all 256 colors together
/// with every other color, weighting the blending based on the ratios given where 0.0 will /// with every other color, weighting the blending based on the ratios given where 0.0 will
/// result in that component being totally transparent and 1.0, totally opaque. /// result in that component being totally transparent and 1.0, totally opaque.
/// ///
/// This method is SLOW! It is computing 65536 different blend colors by searching the given /// This method is SLOW! It is computing 65536 different blend colors by searching the given
/// palette for the closest RGB match between two colors. /// palette for the closest RGB match between two colors.
/// ///
/// Because simple palette searches are being used to build the blending table, results will /// Because simple palette searches are being used to build the blending table, results will
/// vary palette to palette. There will not always be a perfect blend color available. /// vary palette to palette. There will not always be a perfect blend color available.
pub fn new_translucency_map(blend_r: f32, blend_g: f32, blend_b: f32, palette: &Palette) -> Self { pub fn new_translucency_map(blend_r: f32, blend_g: f32, blend_b: f32, palette: &Palette) -> Self {
let mut blend_map = BlendMap::new(0, 255); let mut blend_map = BlendMap::new(0, 255);
for source in 0..=255 { for source in 0..=255 {
let (source_r, source_g, source_b) = from_rgb32(palette[source]); let (source_r, source_g, source_b) = from_rgb32(palette[source]);
let mapping = blend_map.get_mapping_mut(source).unwrap(); let mapping = blend_map.get_mapping_mut(source).unwrap();
for dest in 0..=255 { for dest in 0..=255 {
let (dest_r, dest_g, dest_b) = from_rgb32(palette[dest]); let (dest_r, dest_g, dest_b) = from_rgb32(palette[dest]);
let find_r = lerp(dest_r as f32, source_r as f32, blend_r) as u8; let find_r = lerp(dest_r as f32, source_r as f32, blend_r) as u8;
let find_g = lerp(dest_g as f32, source_g as f32, blend_g) as u8; let find_g = lerp(dest_g as f32, source_g as f32, blend_g) as u8;
let find_b = lerp(dest_b as f32, source_b as f32, blend_b) as u8; let find_b = lerp(dest_b as f32, source_b as f32, blend_b) as u8;
let result_c = palette.find_color(find_r, find_g, find_b); let result_c = palette.find_color(find_r, find_g, find_b);
mapping[dest as usize] = result_c; mapping[dest as usize] = result_c;
} }
} }
blend_map blend_map
} }
/// The beginning source color that is mapped in this blend map. /// The beginning source color that is mapped in this blend map.
#[inline] #[inline]
pub fn start_color(&self) -> u8 { pub fn start_color(&self) -> u8 {
self.start_color self.start_color
} }
/// The ending source color that is mapped in this blend map. /// The ending source color that is mapped in this blend map.
#[inline] #[inline]
pub fn end_color(&self) -> u8 { pub fn end_color(&self) -> u8 {
self.end_color self.end_color
} }
/// Returns true if the given source color is mapped in this blend map. /// Returns true if the given source color is mapped in this blend map.
#[inline] #[inline]
pub fn is_mapped(&self, color: u8) -> bool { pub fn is_mapped(&self, color: u8) -> bool {
color >= self.start_color && color <= self.end_color color >= self.start_color && color <= self.end_color
} }
#[inline] #[inline]
fn get_mapping_index(&self, color: u8) -> Option<usize> { fn get_mapping_index(&self, color: u8) -> Option<usize> {
if color >= self.start_color && color <= self.end_color { if color >= self.start_color && color <= self.end_color {
let index = (color - self.start_color) as usize; let index = (color - self.start_color) as usize;
Some(index) Some(index)
} else { } else {
None None
} }
} }
/// Returns a reference to the destination-to-blend color mapping table for the given source /// Returns a reference to the destination-to-blend color mapping table for the given source
/// color. Returns `None` if the specified source color is not in this blend map. /// color. Returns `None` if the specified source color is not in this blend map.
#[inline] #[inline]
pub fn get_mapping(&self, color: u8) -> Option<&BlendMapping> { pub fn get_mapping(&self, color: u8) -> Option<&BlendMapping> {
if let Some(index) = self.get_mapping_index(color) { if let Some(index) = self.get_mapping_index(color) {
// safety: index cannot be outside 0-255 since color and start_color are both u8 // safety: index cannot be outside 0-255 since color and start_color are both u8
unsafe { Some(self.mapping.get_unchecked(index)) } unsafe { Some(self.mapping.get_unchecked(index)) }
} else { } else {
None None
} }
} }
/// Returns a mutable reference to the destination-to-blend color mapping table for the given /// Returns a mutable reference to the destination-to-blend color mapping table for the given
/// source color. Returns `None` if the specified source color is not in this blend map. /// source color. Returns `None` if the specified source color is not in this blend map.
#[inline] #[inline]
pub fn get_mapping_mut(&mut self, color: u8) -> Option<&mut BlendMapping> { pub fn get_mapping_mut(&mut self, color: u8) -> Option<&mut BlendMapping> {
if let Some(index) = self.get_mapping_index(color) { if let Some(index) = self.get_mapping_index(color) {
// safety: index cannot be outside 0-255 since color and start_color are both u8 // safety: index cannot be outside 0-255 since color and start_color are both u8
unsafe { Some(self.mapping.get_unchecked_mut(index)) } unsafe { Some(self.mapping.get_unchecked_mut(index)) }
} else { } else {
None None
} }
} }
/// Sets the blend color mapping for the given source color and destination color combination. /// Sets the blend color mapping for the given source color and destination color combination.
pub fn set_mapping(&mut self, source_color: u8, dest_color: u8, blended_color: u8) -> Result<(), BlendMapError> { pub fn set_mapping(&mut self, source_color: u8, dest_color: u8, blended_color: u8) -> Result<(), BlendMapError> {
if let Some(mapping) = self.get_mapping_mut(source_color) { if let Some(mapping) = self.get_mapping_mut(source_color) {
mapping[dest_color as usize] = blended_color; mapping[dest_color as usize] = blended_color;
Ok(()) Ok(())
} else { } else {
Err(BlendMapError::InvalidSourceColor(source_color)) Err(BlendMapError::InvalidSourceColor(source_color))
} }
} }
/// Sets a series of blend color mappings for the given source color and starting from a base /// Sets a series of blend color mappings for the given source color and starting from a base
/// destination color. /// destination color.
pub fn set_mappings<const N: usize>(&mut self, source_color: u8, base_dest_color: u8, mappings: [u8; N]) -> Result<(), BlendMapError> { pub fn set_mappings<const N: usize>(&mut self, source_color: u8, base_dest_color: u8, mappings: [u8; N]) -> Result<(), BlendMapError> {
if let Some(mapping) = self.get_mapping_mut(source_color) { if let Some(mapping) = self.get_mapping_mut(source_color) {
assert!((base_dest_color as usize + N - 1) <= 255, "mappings array is too big for the remaining colors available"); assert!((base_dest_color as usize + N - 1) <= 255, "mappings array is too big for the remaining colors available");
for index in 0..N { for index in 0..N {
mapping[index + base_dest_color as usize] = mappings[index]; mapping[index + base_dest_color as usize] = mappings[index];
} }
Ok(()) Ok(())
} else { } else {
Err(BlendMapError::InvalidSourceColor(source_color)) Err(BlendMapError::InvalidSourceColor(source_color))
} }
} }
/// Returns the blend color for the given source and destination colors. If the source color /// Returns the blend color for the given source and destination colors. If the source color
/// is not in this blend map, `None` is returned. /// is not in this blend map, `None` is returned.
#[inline] #[inline]
pub fn blend(&self, source_color: u8, dest_color: u8) -> Option<u8> { pub fn blend(&self, source_color: u8, dest_color: u8) -> Option<u8> {
if let Some(mapping) = self.get_mapping(source_color) { if let Some(mapping) = self.get_mapping(source_color) {
Some(mapping[dest_color as usize]) Some(mapping[dest_color as usize])
} else { } else {
None None
} }
} }
pub fn load_from_file(path: &Path) -> Result<Self, BlendMapError> { pub fn load_from_file(path: &Path) -> Result<Self, BlendMapError> {
let f = File::open(path)?; let f = File::open(path)?;
let mut reader = BufReader::new(f); let mut reader = BufReader::new(f);
Self::load_from_bytes(&mut reader) Self::load_from_bytes(&mut reader)
} }
pub fn load_from_bytes<T: ReadBytesExt>(reader: &mut T) -> Result<Self, BlendMapError> { pub fn load_from_bytes<T: ReadBytesExt>(reader: &mut T) -> Result<Self, BlendMapError> {
let ident: [u8; 4] = reader.read_bytes()?; let ident: [u8; 4] = reader.read_bytes()?;
if ident != *b"BMap" { if ident != *b"BMap" {
return Err(BlendMapError::BadFile(String::from("Unrecognized header"))); return Err(BlendMapError::BadFile(String::from("Unrecognized header")));
} }
let start_color = reader.read_u8()?; let start_color = reader.read_u8()?;
let end_color = reader.read_u8()?; let end_color = reader.read_u8()?;
let num_maps = end_color as usize - start_color as usize + 1; let num_maps = end_color as usize - start_color as usize + 1;
let mut maps = Vec::with_capacity(num_maps); let mut maps = Vec::with_capacity(num_maps);
for _ in 0..num_maps { for _ in 0..num_maps {
let map: BlendMapping = reader.read_bytes()?; let map: BlendMapping = reader.read_bytes()?;
maps.push(map); maps.push(map);
} }
Ok(BlendMap { Ok(BlendMap {
start_color, start_color,
end_color, end_color,
mapping: maps.into_boxed_slice() mapping: maps.into_boxed_slice(),
}) })
} }
pub fn to_file(&self, path: &Path) -> Result<(), BlendMapError> { pub fn to_file(&self, path: &Path) -> Result<(), BlendMapError> {
let f = File::create(path)?; let f = File::create(path)?;
let mut writer = BufWriter::new(f); let mut writer = BufWriter::new(f);
self.to_bytes(&mut writer) self.to_bytes(&mut writer)
} }
pub fn to_bytes<T: WriteBytesExt>(&self, writer: &mut T) -> Result<(), BlendMapError> { pub fn to_bytes<T: WriteBytesExt>(&self, writer: &mut T) -> Result<(), BlendMapError> {
writer.write_all(b"BMap")?; writer.write_all(b"BMap")?;
writer.write_u8(self.start_color)?; writer.write_u8(self.start_color)?;
writer.write_u8(self.end_color)?; writer.write_u8(self.end_color)?;
for map in self.mapping.iter() { for map in self.mapping.iter() {
writer.write_all(map)?; writer.write_all(map)?;
} }
Ok(()) Ok(())
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use claim::*; use claim::*;
use tempfile::TempDir; use tempfile::TempDir;
use super::*; use super::*;
#[test] #[test]
pub fn create() -> Result<(), BlendMapError> { pub fn create() -> Result<(), BlendMapError> {
let blend_map = BlendMap::new(10, 12); let blend_map = BlendMap::new(10, 12);
assert_eq!(10, blend_map.start_color()); assert_eq!(10, blend_map.start_color());
assert_eq!(12, blend_map.end_color()); assert_eq!(12, blend_map.end_color());
assert!(blend_map.is_mapped(10)); assert!(blend_map.is_mapped(10));
assert!(blend_map.is_mapped(11)); assert!(blend_map.is_mapped(11));
assert!(blend_map.is_mapped(12)); assert!(blend_map.is_mapped(12));
assert!(!blend_map.is_mapped(9)); assert!(!blend_map.is_mapped(9));
assert!(!blend_map.is_mapped(13)); assert!(!blend_map.is_mapped(13));
assert_some!(blend_map.get_mapping(10)); assert_some!(blend_map.get_mapping(10));
assert_some!(blend_map.get_mapping(11)); assert_some!(blend_map.get_mapping(11));
assert_some!(blend_map.get_mapping(12)); assert_some!(blend_map.get_mapping(12));
assert_none!(blend_map.get_mapping(9)); assert_none!(blend_map.get_mapping(9));
assert_none!(blend_map.get_mapping(13)); assert_none!(blend_map.get_mapping(13));
let blend_map = BlendMap::new(12, 10); let blend_map = BlendMap::new(12, 10);
assert_eq!(10, blend_map.start_color()); assert_eq!(10, blend_map.start_color());
assert_eq!(12, blend_map.end_color()); assert_eq!(12, blend_map.end_color());
assert!(blend_map.is_mapped(10)); assert!(blend_map.is_mapped(10));
assert!(blend_map.is_mapped(11)); assert!(blend_map.is_mapped(11));
assert!(blend_map.is_mapped(12)); assert!(blend_map.is_mapped(12));
assert!(!blend_map.is_mapped(9)); assert!(!blend_map.is_mapped(9));
assert!(!blend_map.is_mapped(13)); assert!(!blend_map.is_mapped(13));
assert_some!(blend_map.get_mapping(10)); assert_some!(blend_map.get_mapping(10));
assert_some!(blend_map.get_mapping(11)); assert_some!(blend_map.get_mapping(11));
assert_some!(blend_map.get_mapping(12)); assert_some!(blend_map.get_mapping(12));
assert_none!(blend_map.get_mapping(9)); assert_none!(blend_map.get_mapping(9));
assert_none!(blend_map.get_mapping(13)); assert_none!(blend_map.get_mapping(13));
let blend_map = BlendMap::new(130, 130); let blend_map = BlendMap::new(130, 130);
assert_eq!(130, blend_map.start_color()); assert_eq!(130, blend_map.start_color());
assert_eq!(130, blend_map.end_color()); assert_eq!(130, blend_map.end_color());
assert!(blend_map.is_mapped(130)); assert!(blend_map.is_mapped(130));
assert!(!blend_map.is_mapped(129)); assert!(!blend_map.is_mapped(129));
assert!(!blend_map.is_mapped(131)); assert!(!blend_map.is_mapped(131));
assert_some!(blend_map.get_mapping(130)); assert_some!(blend_map.get_mapping(130));
assert_none!(blend_map.get_mapping(129)); assert_none!(blend_map.get_mapping(129));
assert_none!(blend_map.get_mapping(131)); assert_none!(blend_map.get_mapping(131));
Ok(()) Ok(())
} }
#[test] #[test]
pub fn mapping() -> Result<(), BlendMapError> { pub fn mapping() -> Result<(), BlendMapError> {
let mut blend_map = BlendMap::new(16, 31); let mut blend_map = BlendMap::new(16, 31);
assert_none!(blend_map.blend(15, 0)); assert_none!(blend_map.blend(15, 0));
assert_eq!(Some(0), blend_map.blend(16, 0)); assert_eq!(Some(0), blend_map.blend(16, 0));
assert_eq!(Some(0), blend_map.blend(16, 1)); assert_eq!(Some(0), blend_map.blend(16, 1));
assert_ok!(blend_map.set_mapping(16, 0, 116)); assert_ok!(blend_map.set_mapping(16, 0, 116));
assert_eq!(Some(116), blend_map.blend(16, 0)); assert_eq!(Some(116), blend_map.blend(16, 0));
assert_eq!(Some(0), blend_map.blend(16, 1)); assert_eq!(Some(0), blend_map.blend(16, 1));
let mapping = blend_map.get_mapping(16).unwrap(); let mapping = blend_map.get_mapping(16).unwrap();
assert_eq!(116, mapping[0]); assert_eq!(116, mapping[0]);
assert_eq!(0, mapping[1]); assert_eq!(0, mapping[1]);
assert_eq!(Some(0), blend_map.blend(17, 0)); assert_eq!(Some(0), blend_map.blend(17, 0));
assert_ok!(blend_map.set_mapping(17, 0, 117)); assert_ok!(blend_map.set_mapping(17, 0, 117));
assert_eq!(Some(117), blend_map.blend(17, 0)); assert_eq!(Some(117), blend_map.blend(17, 0));
let mapping = blend_map.get_mapping_mut(17).unwrap(); let mapping = blend_map.get_mapping_mut(17).unwrap();
assert_eq!(117, mapping[0]); assert_eq!(117, mapping[0]);
mapping[0] = 217; mapping[0] = 217;
assert_eq!(Some(217), blend_map.blend(17, 0)); assert_eq!(Some(217), blend_map.blend(17, 0));
assert_matches!( assert_matches!(
blend_map.set_mapping(64, 1, 2), blend_map.set_mapping(64, 1, 2),
Err(BlendMapError::InvalidSourceColor(64)) Err(BlendMapError::InvalidSourceColor(64))
); );
Ok(()) Ok(())
} }
#[test] #[test]
pub fn bulk_mappings() -> Result<(), BlendMapError> { pub fn bulk_mappings() -> Result<(), BlendMapError> {
let mut blend_map = BlendMap::new(0, 7); let mut blend_map = BlendMap::new(0, 7);
let mapping = blend_map.get_mapping(2).unwrap(); let mapping = blend_map.get_mapping(2).unwrap();
assert_eq!([0, 0, 0, 0, 0, 0, 0, 0], mapping[0..8]); assert_eq!([0, 0, 0, 0, 0, 0, 0, 0], mapping[0..8]);
assert_ok!(blend_map.set_mappings(2, 4, [1, 2, 3, 4, 5, 6, 7, 8])); assert_ok!(blend_map.set_mappings(2, 4, [1, 2, 3, 4, 5, 6, 7, 8]));
let mapping = blend_map.get_mapping(2).unwrap(); let mapping = blend_map.get_mapping(2).unwrap();
assert_eq!( assert_eq!(
[0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0], [0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0],
mapping[0..16] mapping[0..16]
); );
Ok(()) Ok(())
} }
#[test] #[test]
fn load_and_save() -> Result<(), BlendMapError> { fn load_and_save() -> Result<(), BlendMapError> {
let tmp_dir = TempDir::new()?; let tmp_dir = TempDir::new()?;
let mut blend_map = BlendMap::new(2, 3); let mut blend_map = BlendMap::new(2, 3);
for i in 0..=255 { for i in 0..=255 {
blend_map.set_mapping(2, i, i)?; blend_map.set_mapping(2, i, i)?;
blend_map.set_mapping(3, i, 255 - i)?; blend_map.set_mapping(3, i, 255 - i)?;
} }
let save_path = tmp_dir.path().join("test_blend_map.blendmap"); let save_path = tmp_dir.path().join("test_blend_map.blendmap");
blend_map.to_file(&save_path)?; blend_map.to_file(&save_path)?;
let loaded_blend_map = BlendMap::load_from_file(&save_path)?; let loaded_blend_map = BlendMap::load_from_file(&save_path)?;
assert!(blend_map == loaded_blend_map, "loaded BlendMap is not the same as the original"); assert!(blend_map == loaded_blend_map, "loaded BlendMap is not the same as the original");
Ok(()) Ok(())
} }
} }

View file

@ -17,263 +17,263 @@ pub const CHAR_FIXED_WIDTH: usize = 8;
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum FontError { pub enum FontError {
#[error("Invalid font file: {0}")] #[error("Invalid font file: {0}")]
InvalidFile(String), InvalidFile(String),
#[error("Font I/O error")] #[error("Font I/O error")]
IOError(#[from] std::io::Error), IOError(#[from] std::io::Error),
} }
#[derive(Debug, Copy, Clone, Eq, PartialEq)] #[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum FontRenderOpts { pub enum FontRenderOpts {
Color(u8), Color(u8),
None, None,
} }
pub trait Character { pub trait Character {
fn bounds(&self) -> &Rect; fn bounds(&self) -> &Rect;
fn draw(&self, dest: &mut Bitmap, x: i32, y: i32, opts: FontRenderOpts); fn draw(&self, dest: &mut Bitmap, x: i32, y: i32, opts: FontRenderOpts);
} }
pub trait Font { pub trait Font {
type CharacterType: Character; type CharacterType: Character;
fn character(&self, ch: char) -> &Self::CharacterType; fn character(&self, ch: char) -> &Self::CharacterType;
fn space_width(&self) -> u8; fn space_width(&self) -> u8;
fn line_height(&self) -> u8; fn line_height(&self) -> u8;
fn measure(&self, text: &str, opts: FontRenderOpts) -> (u32, u32); fn measure(&self, text: &str, opts: FontRenderOpts) -> (u32, u32);
} }
#[derive(Debug, Clone, Eq, PartialEq)] #[derive(Debug, Clone, Eq, PartialEq)]
pub struct BitmaskCharacter { pub struct BitmaskCharacter {
bytes: [u8; CHAR_HEIGHT], bytes: [u8; CHAR_HEIGHT],
bounds: Rect, bounds: Rect,
} }
impl Character for BitmaskCharacter { impl Character for BitmaskCharacter {
#[inline] #[inline]
fn bounds(&self) -> &Rect { fn bounds(&self) -> &Rect {
&self.bounds &self.bounds
} }
fn draw(&self, dest: &mut Bitmap, x: i32, y: i32, opts: FontRenderOpts) { fn draw(&self, dest: &mut Bitmap, x: i32, y: i32, opts: FontRenderOpts) {
// out of bounds check // out of bounds check
if ((x + self.bounds.width as i32) < dest.clip_region().x) if ((x + self.bounds.width as i32) < dest.clip_region().x)
|| ((y + self.bounds.height as i32) < dest.clip_region().y) || ((y + self.bounds.height as i32) < dest.clip_region().y)
|| (x >= dest.clip_region().right()) || (x >= dest.clip_region().right())
|| (y >= dest.clip_region().bottom()) || (y >= dest.clip_region().bottom())
{ {
return; return;
} }
let color = match opts { let color = match opts {
FontRenderOpts::Color(color) => color, FontRenderOpts::Color(color) => color,
_ => 0, _ => 0,
}; };
// TODO: i'm sure this can be optimized, lol // TODO: i'm sure this can be optimized, lol
for char_y in 0..self.bounds.height as usize { for char_y in 0..self.bounds.height as usize {
let mut bit_mask = 0x80; let mut bit_mask = 0x80;
for char_x in 0..self.bounds.width as usize { for char_x in 0..self.bounds.width as usize {
if self.bytes[char_y] & bit_mask > 0 { if self.bytes[char_y] & bit_mask > 0 {
dest.set_pixel(x + char_x as i32, y + char_y as i32, color); dest.set_pixel(x + char_x as i32, y + char_y as i32, color);
} }
bit_mask >>= 1; bit_mask >>= 1;
} }
} }
} }
} }
#[derive(Clone, Eq, PartialEq)] #[derive(Clone, Eq, PartialEq)]
pub struct BitmaskFont { pub struct BitmaskFont {
characters: Box<[BitmaskCharacter]>, characters: Box<[BitmaskCharacter]>,
line_height: u8, line_height: u8,
space_width: u8, space_width: u8,
} }
impl std::fmt::Debug for BitmaskFont { impl std::fmt::Debug for BitmaskFont {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("BitmaskFont") f.debug_struct("BitmaskFont")
.field("line_height", &self.line_height) .field("line_height", &self.line_height)
.field("space_width", &self.space_width) .field("space_width", &self.space_width)
.field("characters.len()", &self.characters.len()) .field("characters.len()", &self.characters.len())
.finish() .finish()
} }
} }
impl BitmaskFont { impl BitmaskFont {
pub fn new_vga_font() -> Result<BitmaskFont, FontError> { pub fn new_vga_font() -> Result<BitmaskFont, FontError> {
BitmaskFont::load_from_bytes(&mut Cursor::new(VGA_FONT_BYTES)) BitmaskFont::load_from_bytes(&mut Cursor::new(VGA_FONT_BYTES))
} }
pub fn load_from_file(path: &Path) -> Result<BitmaskFont, FontError> { pub fn load_from_file(path: &Path) -> Result<BitmaskFont, FontError> {
let f = File::open(path)?; let f = File::open(path)?;
let mut reader = BufReader::new(f); let mut reader = BufReader::new(f);
BitmaskFont::load_from_bytes(&mut reader) BitmaskFont::load_from_bytes(&mut reader)
} }
pub fn load_from_bytes<T: ReadBytesExt>(reader: &mut T) -> Result<BitmaskFont, FontError> { pub fn load_from_bytes<T: ReadBytesExt>(reader: &mut T) -> Result<BitmaskFont, FontError> {
let mut characters: Vec<BitmaskCharacter> = Vec::with_capacity(NUM_CHARS); let mut characters: Vec<BitmaskCharacter> = Vec::with_capacity(NUM_CHARS);
// read character bitmap data // read character bitmap data
for _ in 0..NUM_CHARS { for _ in 0..NUM_CHARS {
let mut buffer = [0u8; CHAR_HEIGHT]; let mut buffer = [0u8; CHAR_HEIGHT];
reader.read_exact(&mut buffer)?; reader.read_exact(&mut buffer)?;
let character = BitmaskCharacter { let character = BitmaskCharacter {
bytes: buffer, bytes: buffer,
// bounds are filled in below. ugh. // bounds are filled in below. ugh.
bounds: Rect { bounds: Rect {
x: 0, x: 0,
y: 0, y: 0,
width: 0, width: 0,
height: 0, height: 0,
}, },
}; };
characters.push(character); characters.push(character);
} }
// read character widths (used for rendering) // read character widths (used for rendering)
for i in 0..NUM_CHARS { for i in 0..NUM_CHARS {
characters[i].bounds.width = reader.read_u8()? as u32; characters[i].bounds.width = reader.read_u8()? as u32;
} }
// read global font height (used for rendering) // read global font height (used for rendering)
let line_height = reader.read_u8()?; let line_height = reader.read_u8()?;
for i in 0..NUM_CHARS { for i in 0..NUM_CHARS {
characters[i].bounds.height = line_height as u32; characters[i].bounds.height = line_height as u32;
} }
let space_width = characters[' ' as usize].bounds.width as u8; let space_width = characters[' ' as usize].bounds.width as u8;
Ok(BitmaskFont { Ok(BitmaskFont {
characters: characters.into_boxed_slice(), characters: characters.into_boxed_slice(),
line_height, line_height,
space_width, space_width,
}) })
} }
pub fn to_file(&self, path: &Path) -> Result<(), FontError> { pub fn to_file(&self, path: &Path) -> Result<(), FontError> {
let f = File::create(path)?; let f = File::create(path)?;
let mut writer = BufWriter::new(f); let mut writer = BufWriter::new(f);
self.to_bytes(&mut writer) self.to_bytes(&mut writer)
} }
pub fn to_bytes<T: WriteBytesExt>(&self, writer: &mut T) -> Result<(), FontError> { pub fn to_bytes<T: WriteBytesExt>(&self, writer: &mut T) -> Result<(), FontError> {
// write character bitmap data // write character bitmap data
for i in 0..NUM_CHARS { for i in 0..NUM_CHARS {
writer.write_all(&self.characters[i].bytes)?; writer.write_all(&self.characters[i].bytes)?;
} }
// write character widths // write character widths
for i in 0..NUM_CHARS { for i in 0..NUM_CHARS {
writer.write_u8(self.characters[i].bounds.width as u8)?; writer.write_u8(self.characters[i].bounds.width as u8)?;
} }
// write global font height // write global font height
writer.write_u8(self.line_height)?; writer.write_u8(self.line_height)?;
Ok(()) Ok(())
} }
} }
impl Font for BitmaskFont { impl Font for BitmaskFont {
type CharacterType = BitmaskCharacter; type CharacterType = BitmaskCharacter;
#[inline] #[inline]
fn character(&self, ch: char) -> &Self::CharacterType { fn character(&self, ch: char) -> &Self::CharacterType {
&self.characters[ch as usize] &self.characters[ch as usize]
} }
#[inline] #[inline]
fn space_width(&self) -> u8 { fn space_width(&self) -> u8 {
self.space_width self.space_width
} }
#[inline] #[inline]
fn line_height(&self) -> u8 { fn line_height(&self) -> u8 {
self.line_height self.line_height
} }
fn measure(&self, text: &str, _opts: FontRenderOpts) -> (u32, u32) { fn measure(&self, text: &str, _opts: FontRenderOpts) -> (u32, u32) {
if text.is_empty() { if text.is_empty() {
return (0, 0); return (0, 0);
} }
let mut height = 0; let mut height = 0;
let mut width = 0; let mut width = 0;
let mut x = 0; let mut x = 0;
// trimming whitespace off the end because it won't be rendered (since it's whitespace) // trimming whitespace off the end because it won't be rendered (since it's whitespace)
// and thus, won't contribute to visible rendered output (what we're measuring) // and thus, won't contribute to visible rendered output (what we're measuring)
for ch in text.trim_end().chars() { for ch in text.trim_end().chars() {
match ch { match ch {
'\n' => { '\n' => {
if x == 0 { if x == 0 {
height += self.line_height as u32; height += self.line_height as u32;
} }
width = std::cmp::max(width, x); width = std::cmp::max(width, x);
x = 0; x = 0;
}, }
'\r' => (), '\r' => (),
ch => { ch => {
if x == 0 { if x == 0 {
height += self.line_height as u32; height += self.line_height as u32;
} }
x += self.character(ch).bounds().width; x += self.character(ch).bounds().width;
} }
} }
} }
width = std::cmp::max(width, x); width = std::cmp::max(width, x);
(width, height) (width, height)
} }
} }
#[cfg(test)] #[cfg(test)]
pub mod tests { pub mod tests {
use super::*; use super::*;
#[test] #[test]
pub fn load_font() -> Result<(), FontError> { pub fn load_font() -> Result<(), FontError> {
let font = BitmaskFont::load_from_file(Path::new("./assets/vga.fnt"))?; let font = BitmaskFont::load_from_file(Path::new("./assets/vga.fnt"))?;
assert_eq!(256, font.characters.len()); assert_eq!(256, font.characters.len());
assert_eq!(CHAR_FIXED_WIDTH as u8, font.space_width); assert_eq!(CHAR_FIXED_WIDTH as u8, font.space_width);
for character in font.characters.iter() { for character in font.characters.iter() {
assert_eq!(CHAR_FIXED_WIDTH as u8, character.bounds.width as u8); assert_eq!(CHAR_FIXED_WIDTH as u8, character.bounds.width as u8);
assert_eq!(CHAR_HEIGHT, character.bytes.len()); assert_eq!(CHAR_HEIGHT, character.bytes.len());
} }
Ok(()) Ok(())
} }
#[test] #[test]
pub fn measure_text() -> Result<(), FontError> { pub fn measure_text() -> Result<(), FontError> {
{ {
let font = BitmaskFont::load_from_file(Path::new("./assets/vga.fnt"))?; let font = BitmaskFont::load_from_file(Path::new("./assets/vga.fnt"))?;
assert_eq!((40, 8), font.measure("Hello", FontRenderOpts::None)); assert_eq!((40, 8), font.measure("Hello", FontRenderOpts::None));
assert_eq!((40, 16), font.measure("Hello\nthere", FontRenderOpts::None)); assert_eq!((40, 16), font.measure("Hello\nthere", FontRenderOpts::None));
assert_eq!((88, 24), font.measure("longer line\nshort\nthe end", FontRenderOpts::None)); assert_eq!((88, 24), font.measure("longer line\nshort\nthe end", FontRenderOpts::None));
assert_eq!((0, 0), font.measure("", FontRenderOpts::None)); assert_eq!((0, 0), font.measure("", FontRenderOpts::None));
assert_eq!((0, 0), font.measure(" ", FontRenderOpts::None)); assert_eq!((0, 0), font.measure(" ", FontRenderOpts::None));
assert_eq!((40, 16), font.measure("\nhello", FontRenderOpts::None)); assert_eq!((40, 16), font.measure("\nhello", FontRenderOpts::None));
assert_eq!((0, 0), font.measure("\n", FontRenderOpts::None)); assert_eq!((0, 0), font.measure("\n", FontRenderOpts::None));
assert_eq!((40, 8), font.measure("hello\n", FontRenderOpts::None)); assert_eq!((40, 8), font.measure("hello\n", FontRenderOpts::None));
assert_eq!((40, 24), font.measure("hello\n\nthere", FontRenderOpts::None)); assert_eq!((40, 24), font.measure("hello\n\nthere", FontRenderOpts::None));
} }
{ {
let font = BitmaskFont::load_from_file(Path::new("./test-assets/small.fnt"))?; let font = BitmaskFont::load_from_file(Path::new("./test-assets/small.fnt"))?;
assert_eq!((22, 7), font.measure("Hello", FontRenderOpts::None)); assert_eq!((22, 7), font.measure("Hello", FontRenderOpts::None));
assert_eq!((24, 14), font.measure("Hello\nthere", FontRenderOpts::None)); assert_eq!((24, 14), font.measure("Hello\nthere", FontRenderOpts::None));
assert_eq!((50, 21), font.measure("longer line\nshort\nthe end", FontRenderOpts::None)); assert_eq!((50, 21), font.measure("longer line\nshort\nthe end", FontRenderOpts::None));
assert_eq!((0, 0), font.measure("", FontRenderOpts::None)); assert_eq!((0, 0), font.measure("", FontRenderOpts::None));
assert_eq!((0, 0), font.measure(" ", FontRenderOpts::None)); assert_eq!((0, 0), font.measure(" ", FontRenderOpts::None));
assert_eq!((21, 14), font.measure("\nhello", FontRenderOpts::None)); assert_eq!((21, 14), font.measure("\nhello", FontRenderOpts::None));
assert_eq!((0, 0), font.measure("\n", FontRenderOpts::None)); assert_eq!((0, 0), font.measure("\n", FontRenderOpts::None));
assert_eq!((21, 7), font.measure("hello\n", FontRenderOpts::None)); assert_eq!((21, 7), font.measure("hello\n", FontRenderOpts::None));
assert_eq!((24, 21), font.measure("hello\n\nthere", FontRenderOpts::None)); assert_eq!((24, 21), font.measure("hello\n\nthere", FontRenderOpts::None));
} }
Ok(()) Ok(())
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -12,33 +12,33 @@ pub mod system;
pub mod utils; pub mod utils;
pub const LOW_RES: bool = if cfg!(feature = "low_res") { pub const LOW_RES: bool = if cfg!(feature = "low_res") {
true true
} else { } else {
false false
}; };
pub const WIDE_SCREEN: bool = if cfg!(feature = "wide") { pub const WIDE_SCREEN: bool = if cfg!(feature = "wide") {
true true
} else { } else {
false false
}; };
pub const SCREEN_WIDTH: u32 = if cfg!(feature = "low_res") { pub const SCREEN_WIDTH: u32 = if cfg!(feature = "low_res") {
if cfg!(feature = "wide") { if cfg!(feature = "wide") {
214 214
} else { } else {
160 160
} }
} else { } else {
if cfg!(feature = "wide") { if cfg!(feature = "wide") {
428 428
} else { } else {
320 320
} }
}; };
pub const SCREEN_HEIGHT: u32 = if cfg!(feature = "low_res") { pub const SCREEN_HEIGHT: u32 = if cfg!(feature = "low_res") {
120 120
} else { } else {
240 240
}; };
pub const SCREEN_TOP: u32 = 0; pub const SCREEN_TOP: u32 = 0;
@ -47,9 +47,9 @@ pub const SCREEN_RIGHT: u32 = SCREEN_WIDTH - 1;
pub const SCREEN_BOTTOM: u32 = SCREEN_HEIGHT - 1; pub const SCREEN_BOTTOM: u32 = SCREEN_HEIGHT - 1;
pub const DEFAULT_SCALE_FACTOR: u32 = if cfg!(feature = "low_res") { pub const DEFAULT_SCALE_FACTOR: u32 = if cfg!(feature = "low_res") {
6 6
} else { } else {
3 3
}; };
pub const NUM_COLORS: usize = 256; // i mean ... the number of colors is really defined by the size of u8 ... pub const NUM_COLORS: usize = 256; // i mean ... the number of colors is really defined by the size of u8 ...

View file

@ -3,111 +3,111 @@ use crate::math::*;
/// Represents a 2D circle, using integer center coordinates and radius. /// Represents a 2D circle, using integer center coordinates and radius.
#[derive(Debug, Copy, Clone, Eq, PartialEq)] #[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct Circle { pub struct Circle {
pub x: i32, pub x: i32,
pub y: i32, pub y: i32,
pub radius: u32, pub radius: u32,
} }
impl Circle { impl Circle {
#[inline] #[inline]
pub fn new(x: i32, y: i32, radius: u32) -> Self { pub fn new(x: i32, y: i32, radius: u32) -> Self {
Circle { x, y, radius } Circle { x, y, radius }
} }
pub fn new_encapsulating(points: &[Vector2]) -> Option<Circle> { pub fn new_encapsulating(points: &[Vector2]) -> Option<Circle> {
if points.is_empty() { if points.is_empty() {
return None; return None;
} }
let mut min_x = points[0].x; let mut min_x = points[0].x;
let mut min_y = points[0].y; let mut min_y = points[0].y;
let mut max_x = min_x; let mut max_x = min_x;
let mut max_y = min_y; let mut max_y = min_y;
for i in 0..points.len() { for i in 0..points.len() {
let point = &points[i]; let point = &points[i];
min_x = point.x.min(min_x); min_x = point.x.min(min_x);
min_y = point.y.min(min_y); min_y = point.y.min(min_y);
max_x = point.x.max(max_x); max_x = point.x.max(max_x);
max_y = point.y.max(max_y); max_y = point.y.max(max_y);
} }
let radius = distance_between(min_x, min_y, max_x, max_y) * 0.5; let radius = distance_between(min_x, min_y, max_x, max_y) * 0.5;
let center_x = (max_x - min_x) / 2.0; let center_x = (max_x - min_x) / 2.0;
let center_y = (max_y - min_y) / 2.0; let center_y = (max_y - min_y) / 2.0;
Some(Circle { Some(Circle {
x: center_x as i32, x: center_x as i32,
y: center_y as i32, y: center_y as i32,
radius: radius as u32, radius: radius as u32,
}) })
} }
/// Calculates the diameter of the circle. /// Calculates the diameter of the circle.
#[inline] #[inline]
pub fn diameter(&self) -> u32 { pub fn diameter(&self) -> u32 {
self.radius * 2 self.radius * 2
} }
/// Returns true if the given point is contained within the bounds of this circle. /// Returns true if the given point is contained within the bounds of this circle.
pub fn contains_point(&self, x: i32, y: i32) -> bool { pub fn contains_point(&self, x: i32, y: i32) -> bool {
let distance_squared = let distance_squared =
distance_squared_between(self.x as f32, self.y as f32, x as f32, y as f32); distance_squared_between(self.x as f32, self.y as f32, x as f32, y as f32);
let radius_squared = (self.radius * self.radius) as f32; let radius_squared = (self.radius * self.radius) as f32;
distance_squared <= radius_squared distance_squared <= radius_squared
} }
/// Returns true if the given circle at least partially overlaps the bounds of this circle. /// Returns true if the given circle at least partially overlaps the bounds of this circle.
pub fn overlaps(&self, other: &Circle) -> bool { pub fn overlaps(&self, other: &Circle) -> bool {
let distance_squared = let distance_squared =
distance_squared_between(self.x as f32, self.y as f32, other.x as f32, other.y as f32); distance_squared_between(self.x as f32, self.y as f32, other.x as f32, other.y as f32);
let minimum_distance_squared = let minimum_distance_squared =
((self.radius + other.radius) * (self.radius + other.radius)) as f32; ((self.radius + other.radius) * (self.radius + other.radius)) as f32;
distance_squared <= minimum_distance_squared distance_squared <= minimum_distance_squared
} }
} }
#[cfg(test)] #[cfg(test)]
pub mod tests { pub mod tests {
use super::*; use super::*;
#[test] #[test]
pub fn test_new() { pub fn test_new() {
let c = Circle::new(1, 2, 4); let c = Circle::new(1, 2, 4);
assert_eq!(1, c.x); assert_eq!(1, c.x);
assert_eq!(2, c.y); assert_eq!(2, c.y);
assert_eq!(4, c.radius); assert_eq!(4, c.radius);
} }
#[test] #[test]
pub fn test_diameter() { pub fn test_diameter() {
let c = Circle::new(4, 4, 3); let c = Circle::new(4, 4, 3);
assert_eq!(6, c.diameter()); assert_eq!(6, c.diameter());
} }
#[test] #[test]
pub fn test_contains_point() { pub fn test_contains_point() {
let c = Circle::new(1, 1, 6); let c = Circle::new(1, 1, 6);
assert!(c.contains_point(4, 4)); assert!(c.contains_point(4, 4));
assert!(!c.contains_point(8, 4)); assert!(!c.contains_point(8, 4));
let c = Circle::new(0, 1, 2); let c = Circle::new(0, 1, 2);
assert!(!c.contains_point(3, 3)); assert!(!c.contains_point(3, 3));
assert!(c.contains_point(0, 0)); assert!(c.contains_point(0, 0));
} }
#[test] #[test]
pub fn test_overlaps() { pub fn test_overlaps() {
let a = Circle::new(3, 4, 5); let a = Circle::new(3, 4, 5);
let b = Circle::new(14, 18, 8); let b = Circle::new(14, 18, 8);
assert!(!a.overlaps(&b)); assert!(!a.overlaps(&b));
let a = Circle::new(2, 3, 12); let a = Circle::new(2, 3, 12);
let b = Circle::new(15, 28, 10); let b = Circle::new(15, 28, 10);
assert!(!a.overlaps(&b)); assert!(!a.overlaps(&b));
let a = Circle::new(-10, 8, 30); let a = Circle::new(-10, 8, 30);
let b = Circle::new(14, -24, 10); let b = Circle::new(14, -24, 10);
assert!(a.overlaps(&b)); assert!(a.overlaps(&b));
} }
} }

View file

@ -5,414 +5,414 @@ use crate::math::*;
/// Represents a 3x3 column-major matrix and provides common methods for matrix math. /// Represents a 3x3 column-major matrix and provides common methods for matrix math.
#[derive(Debug, Copy, Clone, PartialEq)] #[derive(Debug, Copy, Clone, PartialEq)]
pub struct Matrix3x3 { pub struct Matrix3x3 {
pub m: [f32; 9], pub m: [f32; 9],
} }
impl Matrix3x3 { impl Matrix3x3 {
pub const M11: usize = 0; pub const M11: usize = 0;
pub const M12: usize = 3; pub const M12: usize = 3;
pub const M13: usize = 6; pub const M13: usize = 6;
pub const M21: usize = 1; pub const M21: usize = 1;
pub const M22: usize = 4; pub const M22: usize = 4;
pub const M23: usize = 7; pub const M23: usize = 7;
pub const M31: usize = 2; pub const M31: usize = 2;
pub const M32: usize = 5; pub const M32: usize = 5;
pub const M33: usize = 8; pub const M33: usize = 8;
pub const IDENTITY: Matrix3x3 = Matrix3x3 { pub const IDENTITY: Matrix3x3 = Matrix3x3 {
m: [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0], m: [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0],
}; };
/// Returns a new identity matrix. /// Returns a new identity matrix.
#[inline] #[inline]
pub fn identity() -> Matrix3x3 { pub fn identity() -> Matrix3x3 {
Matrix3x3::IDENTITY Matrix3x3::IDENTITY
} }
/// Creates a new matrix with the specified elements. /// Creates a new matrix with the specified elements.
#[rustfmt::skip] #[rustfmt::skip]
#[inline] #[inline]
pub fn new( pub fn new(
m11: f32, m12: f32, m13: f32, m11: f32, m12: f32, m13: f32,
m21: f32, m22: f32, m23: f32, m21: f32, m22: f32, m23: f32,
m31: f32, m32: f32, m33: f32, m31: f32, m32: f32, m33: f32,
) -> Matrix3x3 { ) -> Matrix3x3 {
Matrix3x3 { Matrix3x3 {
m: [ m: [
m11, m21, m31, m11, m21, m31,
m12, m22, m32, m12, m22, m32,
m13, m23, m33 m13, m23, m33
], ],
} }
} }
/// Creates a new rotation matrix from a set of euler angles. /// Creates a new rotation matrix from a set of euler angles.
/// ///
/// # Arguments /// # Arguments
/// ///
/// * `x`: the x angle (in radians) /// * `x`: the x angle (in radians)
/// * `y`: the y angle (in radians) /// * `y`: the y angle (in radians)
/// * `z`: the z angle (in radians) /// * `z`: the z angle (in radians)
pub fn from_euler_angles(x: f32, y: f32, z: f32) -> Matrix3x3 { pub fn from_euler_angles(x: f32, y: f32, z: f32) -> Matrix3x3 {
let rotate_z = Matrix3x3::new_rotation_z(z); let rotate_z = Matrix3x3::new_rotation_z(z);
let rotate_y = Matrix3x3::new_rotation_y(y); let rotate_y = Matrix3x3::new_rotation_y(y);
let rotate_x = Matrix3x3::new_rotation_x(x); let rotate_x = Matrix3x3::new_rotation_x(x);
// "right-to-left" column-major matrix concatenation // "right-to-left" column-major matrix concatenation
rotate_z * rotate_y * rotate_x rotate_z * rotate_y * rotate_x
} }
/// Creates a new rotation matrix for rotation around the x axis. /// Creates a new rotation matrix for rotation around the x axis.
/// ///
/// # Arguments /// # Arguments
/// ///
/// * `radians`: angle to rotate the x axis around (in radians) /// * `radians`: angle to rotate the x axis around (in radians)
#[rustfmt::skip] #[rustfmt::skip]
#[inline] #[inline]
pub fn new_rotation_x(radians: f32) -> Matrix3x3 { pub fn new_rotation_x(radians: f32) -> Matrix3x3 {
let (s, c) = radians.sin_cos(); let (s, c) = radians.sin_cos();
Matrix3x3::new( Matrix3x3::new(
1.0, 0.0, 0.0, 1.0, 0.0, 0.0,
0.0, c, -s, 0.0, c, -s,
0.0, s, c 0.0, s, c,
) )
} }
/// Creates a new rotation matrix for rotation around the y axis. /// Creates a new rotation matrix for rotation around the y axis.
/// ///
/// # Arguments /// # Arguments
/// ///
/// * `radians`: angle to rotate the y axis around (in radians) /// * `radians`: angle to rotate the y axis around (in radians)
#[rustfmt::skip] #[rustfmt::skip]
#[inline] #[inline]
pub fn new_rotation_y(radians: f32) -> Matrix3x3 { pub fn new_rotation_y(radians: f32) -> Matrix3x3 {
let (s, c) = radians.sin_cos(); let (s, c) = radians.sin_cos();
Matrix3x3::new( Matrix3x3::new(
c, 0.0, s, c, 0.0, s,
0.0, 1.0, 0.0, 0.0, 1.0, 0.0,
-s, 0.0, c -s, 0.0, c,
) )
} }
/// Creates a new rotation matrix for rotation around the z axis. /// Creates a new rotation matrix for rotation around the z axis.
/// ///
/// # Arguments /// # Arguments
/// ///
/// * `radians`: angle to rotate the z axis around (in radians) /// * `radians`: angle to rotate the z axis around (in radians)
#[rustfmt::skip] #[rustfmt::skip]
#[inline] #[inline]
pub fn new_rotation_z(radians: f32) -> Matrix3x3 { pub fn new_rotation_z(radians: f32) -> Matrix3x3 {
let (s, c) = radians.sin_cos(); let (s, c) = radians.sin_cos();
Matrix3x3::new( Matrix3x3::new(
c, -s, 0.0, c, -s, 0.0,
s, c, 0.0, s, c, 0.0,
0.0, 0.0, 1.0 0.0, 0.0, 1.0,
) )
} }
/// Creates a translation matrix. For use with 2D coordinates only. /// Creates a translation matrix. For use with 2D coordinates only.
/// ///
/// # Arguments /// # Arguments
/// ///
/// * `x`: the amount to translate on the x axis /// * `x`: the amount to translate on the x axis
/// * `y`: the amount to translate on the y axis /// * `y`: the amount to translate on the y axis
#[rustfmt::skip] #[rustfmt::skip]
#[inline] #[inline]
pub fn new_2d_translation(x: f32, y: f32) -> Matrix3x3 { pub fn new_2d_translation(x: f32, y: f32) -> Matrix3x3 {
Matrix3x3::new( Matrix3x3::new(
1.0, 0.0, 0.0, 1.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0, 1.0, 0.0,
x, y, 1.0 x, y, 1.0,
) )
} }
/// Creates a scaling matrix from scaling factors for each axis. For use with 2D coordinates /// Creates a scaling matrix from scaling factors for each axis. For use with 2D coordinates
/// only. /// only.
/// ///
/// # Arguments /// # Arguments
/// ///
/// * `x`: the scale factor for the x axis /// * `x`: the scale factor for the x axis
/// * `y`: the scale factor for the y axis /// * `y`: the scale factor for the y axis
#[rustfmt::skip] #[rustfmt::skip]
#[inline] #[inline]
pub fn new_2d_scaling(x: f32, y: f32) -> Matrix3x3 { pub fn new_2d_scaling(x: f32, y: f32) -> Matrix3x3 {
Matrix3x3::new( Matrix3x3::new(
x, 0.0, 0.0, x, 0.0, 0.0,
0.0, y, 0.0, 0.0, y, 0.0,
0.0, 0.0, 1.0 0.0, 0.0, 1.0,
) )
} }
/// Creates a new rotation matrix. For use with 2D coordinates only. /// Creates a new rotation matrix. For use with 2D coordinates only.
/// ///
/// # Arguments /// # Arguments
/// ///
/// * `radians`: angle to rotate by (in radians) /// * `radians`: angle to rotate by (in radians)
#[inline(always)] #[inline(always)]
pub fn new_2d_rotation(radians: f32) -> Matrix3x3 { pub fn new_2d_rotation(radians: f32) -> Matrix3x3 {
Matrix3x3::new_rotation_z(radians) Matrix3x3::new_rotation_z(radians)
} }
/// Calculates the determinant of this matrix. /// Calculates the determinant of this matrix.
#[rustfmt::skip] #[rustfmt::skip]
#[inline] #[inline]
pub fn determinant(&self) -> f32 { pub fn determinant(&self) -> f32 {
self.m[Matrix3x3::M11] * self.m[Matrix3x3::M22] * self.m[Matrix3x3::M33] + self.m[Matrix3x3::M11] * self.m[Matrix3x3::M22] * self.m[Matrix3x3::M33] +
self.m[Matrix3x3::M12] * self.m[Matrix3x3::M23] * self.m[Matrix3x3::M31] + self.m[Matrix3x3::M12] * self.m[Matrix3x3::M23] * self.m[Matrix3x3::M31] +
self.m[Matrix3x3::M13] * self.m[Matrix3x3::M21] * self.m[Matrix3x3::M32] - self.m[Matrix3x3::M13] * self.m[Matrix3x3::M21] * self.m[Matrix3x3::M32] -
self.m[Matrix3x3::M11] * self.m[Matrix3x3::M23] * self.m[Matrix3x3::M32] - self.m[Matrix3x3::M11] * self.m[Matrix3x3::M23] * self.m[Matrix3x3::M32] -
self.m[Matrix3x3::M12] * self.m[Matrix3x3::M21] * self.m[Matrix3x3::M33] - self.m[Matrix3x3::M12] * self.m[Matrix3x3::M21] * self.m[Matrix3x3::M33] -
self.m[Matrix3x3::M13] * self.m[Matrix3x3::M22] * self.m[Matrix3x3::M31] self.m[Matrix3x3::M13] * self.m[Matrix3x3::M22] * self.m[Matrix3x3::M31]
} }
/// Calculates the inverse of this matrix. /// Calculates the inverse of this matrix.
#[rustfmt::skip] #[rustfmt::skip]
pub fn invert(&self) -> Matrix3x3 { pub fn invert(&self) -> Matrix3x3 {
let d = self.determinant(); let d = self.determinant();
if nearly_equal(d, 0.0, 0.000001) { if nearly_equal(d, 0.0, 0.000001) {
Matrix3x3::IDENTITY Matrix3x3::IDENTITY
} else { } else {
let d = 1.0 / d; let d = 1.0 / d;
Matrix3x3 { Matrix3x3 {
m: [ m: [
d * (self.m[Matrix3x3::M22] * self.m[Matrix3x3::M33] - self.m[Matrix3x3::M32] * self.m[Matrix3x3::M23]), d * (self.m[Matrix3x3::M22] * self.m[Matrix3x3::M33] - self.m[Matrix3x3::M32] * self.m[Matrix3x3::M23]),
d * (self.m[Matrix3x3::M31] * self.m[Matrix3x3::M23] - self.m[Matrix3x3::M21] * self.m[Matrix3x3::M33]), d * (self.m[Matrix3x3::M31] * self.m[Matrix3x3::M23] - self.m[Matrix3x3::M21] * self.m[Matrix3x3::M33]),
d * (self.m[Matrix3x3::M21] * self.m[Matrix3x3::M32] - self.m[Matrix3x3::M31] * self.m[Matrix3x3::M22]), d * (self.m[Matrix3x3::M21] * self.m[Matrix3x3::M32] - self.m[Matrix3x3::M31] * self.m[Matrix3x3::M22]),
d * (self.m[Matrix3x3::M32] * self.m[Matrix3x3::M13] - self.m[Matrix3x3::M12] * self.m[Matrix3x3::M33]), d * (self.m[Matrix3x3::M32] * self.m[Matrix3x3::M13] - self.m[Matrix3x3::M12] * self.m[Matrix3x3::M33]),
d * (self.m[Matrix3x3::M11] * self.m[Matrix3x3::M33] - self.m[Matrix3x3::M31] * self.m[Matrix3x3::M13]), d * (self.m[Matrix3x3::M11] * self.m[Matrix3x3::M33] - self.m[Matrix3x3::M31] * self.m[Matrix3x3::M13]),
d * (self.m[Matrix3x3::M31] * self.m[Matrix3x3::M12] - self.m[Matrix3x3::M11] * self.m[Matrix3x3::M32]), d * (self.m[Matrix3x3::M31] * self.m[Matrix3x3::M12] - self.m[Matrix3x3::M11] * self.m[Matrix3x3::M32]),
d * (self.m[Matrix3x3::M12] * self.m[Matrix3x3::M23] - self.m[Matrix3x3::M22] * self.m[Matrix3x3::M13]), d * (self.m[Matrix3x3::M12] * self.m[Matrix3x3::M23] - self.m[Matrix3x3::M22] * self.m[Matrix3x3::M13]),
d * (self.m[Matrix3x3::M21] * self.m[Matrix3x3::M13] - self.m[Matrix3x3::M11] * self.m[Matrix3x3::M23]), d * (self.m[Matrix3x3::M21] * self.m[Matrix3x3::M13] - self.m[Matrix3x3::M11] * self.m[Matrix3x3::M23]),
d * (self.m[Matrix3x3::M11] * self.m[Matrix3x3::M22] - self.m[Matrix3x3::M21] * self.m[Matrix3x3::M12]), d * (self.m[Matrix3x3::M11] * self.m[Matrix3x3::M22] - self.m[Matrix3x3::M21] * self.m[Matrix3x3::M12]),
] ]
} }
} }
} }
/// Calculates the transpose of this matrix. /// Calculates the transpose of this matrix.
#[inline] #[inline]
pub fn transpose(&self) -> Matrix3x3 { pub fn transpose(&self) -> Matrix3x3 {
Matrix3x3::new( Matrix3x3::new(
self.m[Matrix3x3::M11], self.m[Matrix3x3::M11],
self.m[Matrix3x3::M21], self.m[Matrix3x3::M21],
self.m[Matrix3x3::M31], self.m[Matrix3x3::M31],
self.m[Matrix3x3::M12], self.m[Matrix3x3::M12],
self.m[Matrix3x3::M22], self.m[Matrix3x3::M22],
self.m[Matrix3x3::M32], self.m[Matrix3x3::M32],
self.m[Matrix3x3::M13], self.m[Matrix3x3::M13],
self.m[Matrix3x3::M23], self.m[Matrix3x3::M23],
self.m[Matrix3x3::M33], self.m[Matrix3x3::M33],
) )
} }
/// Sets all of the elements of this matrix. /// Sets all of the elements of this matrix.
#[inline] #[inline]
pub fn set( pub fn set(
&mut self, &mut self,
m11: f32, m11: f32,
m12: f32, m12: f32,
m13: f32, m13: f32,
m21: f32, m21: f32,
m22: f32, m22: f32,
m23: f32, m23: f32,
m31: f32, m31: f32,
m32: f32, m32: f32,
m33: f32, m33: f32,
) { ) {
self.m[Matrix3x3::M11] = m11; self.m[Matrix3x3::M11] = m11;
self.m[Matrix3x3::M12] = m12; self.m[Matrix3x3::M12] = m12;
self.m[Matrix3x3::M13] = m13; self.m[Matrix3x3::M13] = m13;
self.m[Matrix3x3::M21] = m21; self.m[Matrix3x3::M21] = m21;
self.m[Matrix3x3::M22] = m22; self.m[Matrix3x3::M22] = m22;
self.m[Matrix3x3::M23] = m23; self.m[Matrix3x3::M23] = m23;
self.m[Matrix3x3::M31] = m31; self.m[Matrix3x3::M31] = m31;
self.m[Matrix3x3::M32] = m32; self.m[Matrix3x3::M32] = m32;
self.m[Matrix3x3::M33] = m33; self.m[Matrix3x3::M33] = m33;
} }
} }
impl Mul for Matrix3x3 { impl Mul for Matrix3x3 {
type Output = Self; type Output = Self;
#[rustfmt::skip] #[rustfmt::skip]
#[inline] #[inline]
fn mul(self, rhs: Self) -> Self::Output { fn mul(self, rhs: Self) -> Self::Output {
Matrix3x3::new( Matrix3x3::new(
self.m[Matrix3x3::M11] * rhs.m[Matrix3x3::M11] + self.m[Matrix3x3::M12] * rhs.m[Matrix3x3::M21] + self.m[Matrix3x3::M13] * rhs.m[Matrix3x3::M31], self.m[Matrix3x3::M11] * rhs.m[Matrix3x3::M11] + self.m[Matrix3x3::M12] * rhs.m[Matrix3x3::M21] + self.m[Matrix3x3::M13] * rhs.m[Matrix3x3::M31],
self.m[Matrix3x3::M11] * rhs.m[Matrix3x3::M12] + self.m[Matrix3x3::M12] * rhs.m[Matrix3x3::M22] + self.m[Matrix3x3::M13] * rhs.m[Matrix3x3::M32], self.m[Matrix3x3::M11] * rhs.m[Matrix3x3::M12] + self.m[Matrix3x3::M12] * rhs.m[Matrix3x3::M22] + self.m[Matrix3x3::M13] * rhs.m[Matrix3x3::M32],
self.m[Matrix3x3::M11] * rhs.m[Matrix3x3::M13] + self.m[Matrix3x3::M12] * rhs.m[Matrix3x3::M23] + self.m[Matrix3x3::M13] * rhs.m[Matrix3x3::M33], self.m[Matrix3x3::M11] * rhs.m[Matrix3x3::M13] + self.m[Matrix3x3::M12] * rhs.m[Matrix3x3::M23] + self.m[Matrix3x3::M13] * rhs.m[Matrix3x3::M33],
self.m[Matrix3x3::M21] * rhs.m[Matrix3x3::M11] + self.m[Matrix3x3::M22] * rhs.m[Matrix3x3::M21] + self.m[Matrix3x3::M23] * rhs.m[Matrix3x3::M31], self.m[Matrix3x3::M21] * rhs.m[Matrix3x3::M11] + self.m[Matrix3x3::M22] * rhs.m[Matrix3x3::M21] + self.m[Matrix3x3::M23] * rhs.m[Matrix3x3::M31],
self.m[Matrix3x3::M21] * rhs.m[Matrix3x3::M12] + self.m[Matrix3x3::M22] * rhs.m[Matrix3x3::M22] + self.m[Matrix3x3::M23] * rhs.m[Matrix3x3::M32], self.m[Matrix3x3::M21] * rhs.m[Matrix3x3::M12] + self.m[Matrix3x3::M22] * rhs.m[Matrix3x3::M22] + self.m[Matrix3x3::M23] * rhs.m[Matrix3x3::M32],
self.m[Matrix3x3::M21] * rhs.m[Matrix3x3::M13] + self.m[Matrix3x3::M22] * rhs.m[Matrix3x3::M23] + self.m[Matrix3x3::M23] * rhs.m[Matrix3x3::M33], self.m[Matrix3x3::M21] * rhs.m[Matrix3x3::M13] + self.m[Matrix3x3::M22] * rhs.m[Matrix3x3::M23] + self.m[Matrix3x3::M23] * rhs.m[Matrix3x3::M33],
self.m[Matrix3x3::M31] * rhs.m[Matrix3x3::M11] + self.m[Matrix3x3::M32] * rhs.m[Matrix3x3::M21] + self.m[Matrix3x3::M33] * rhs.m[Matrix3x3::M31], self.m[Matrix3x3::M31] * rhs.m[Matrix3x3::M11] + self.m[Matrix3x3::M32] * rhs.m[Matrix3x3::M21] + self.m[Matrix3x3::M33] * rhs.m[Matrix3x3::M31],
self.m[Matrix3x3::M31] * rhs.m[Matrix3x3::M12] + self.m[Matrix3x3::M32] * rhs.m[Matrix3x3::M22] + self.m[Matrix3x3::M33] * rhs.m[Matrix3x3::M32], self.m[Matrix3x3::M31] * rhs.m[Matrix3x3::M12] + self.m[Matrix3x3::M32] * rhs.m[Matrix3x3::M22] + self.m[Matrix3x3::M33] * rhs.m[Matrix3x3::M32],
self.m[Matrix3x3::M31] * rhs.m[Matrix3x3::M13] + self.m[Matrix3x3::M32] * rhs.m[Matrix3x3::M23] + self.m[Matrix3x3::M33] * rhs.m[Matrix3x3::M33] self.m[Matrix3x3::M31] * rhs.m[Matrix3x3::M13] + self.m[Matrix3x3::M32] * rhs.m[Matrix3x3::M23] + self.m[Matrix3x3::M33] * rhs.m[Matrix3x3::M33],
) )
} }
} }
impl MulAssign for Matrix3x3 { impl MulAssign for Matrix3x3 {
#[rustfmt::skip] #[rustfmt::skip]
#[inline] #[inline]
fn mul_assign(&mut self, rhs: Self) { fn mul_assign(&mut self, rhs: Self) {
self.set( self.set(
self.m[Matrix3x3::M11] * rhs.m[Matrix3x3::M11] + self.m[Matrix3x3::M12] * rhs.m[Matrix3x3::M21] + self.m[Matrix3x3::M13] * rhs.m[Matrix3x3::M31], self.m[Matrix3x3::M11] * rhs.m[Matrix3x3::M11] + self.m[Matrix3x3::M12] * rhs.m[Matrix3x3::M21] + self.m[Matrix3x3::M13] * rhs.m[Matrix3x3::M31],
self.m[Matrix3x3::M11] * rhs.m[Matrix3x3::M12] + self.m[Matrix3x3::M12] * rhs.m[Matrix3x3::M22] + self.m[Matrix3x3::M13] * rhs.m[Matrix3x3::M32], self.m[Matrix3x3::M11] * rhs.m[Matrix3x3::M12] + self.m[Matrix3x3::M12] * rhs.m[Matrix3x3::M22] + self.m[Matrix3x3::M13] * rhs.m[Matrix3x3::M32],
self.m[Matrix3x3::M11] * rhs.m[Matrix3x3::M13] + self.m[Matrix3x3::M12] * rhs.m[Matrix3x3::M23] + self.m[Matrix3x3::M13] * rhs.m[Matrix3x3::M33], self.m[Matrix3x3::M11] * rhs.m[Matrix3x3::M13] + self.m[Matrix3x3::M12] * rhs.m[Matrix3x3::M23] + self.m[Matrix3x3::M13] * rhs.m[Matrix3x3::M33],
self.m[Matrix3x3::M21] * rhs.m[Matrix3x3::M11] + self.m[Matrix3x3::M22] * rhs.m[Matrix3x3::M21] + self.m[Matrix3x3::M23] * rhs.m[Matrix3x3::M31], self.m[Matrix3x3::M21] * rhs.m[Matrix3x3::M11] + self.m[Matrix3x3::M22] * rhs.m[Matrix3x3::M21] + self.m[Matrix3x3::M23] * rhs.m[Matrix3x3::M31],
self.m[Matrix3x3::M21] * rhs.m[Matrix3x3::M12] + self.m[Matrix3x3::M22] * rhs.m[Matrix3x3::M22] + self.m[Matrix3x3::M23] * rhs.m[Matrix3x3::M32], self.m[Matrix3x3::M21] * rhs.m[Matrix3x3::M12] + self.m[Matrix3x3::M22] * rhs.m[Matrix3x3::M22] + self.m[Matrix3x3::M23] * rhs.m[Matrix3x3::M32],
self.m[Matrix3x3::M21] * rhs.m[Matrix3x3::M13] + self.m[Matrix3x3::M22] * rhs.m[Matrix3x3::M23] + self.m[Matrix3x3::M23] * rhs.m[Matrix3x3::M33], self.m[Matrix3x3::M21] * rhs.m[Matrix3x3::M13] + self.m[Matrix3x3::M22] * rhs.m[Matrix3x3::M23] + self.m[Matrix3x3::M23] * rhs.m[Matrix3x3::M33],
self.m[Matrix3x3::M31] * rhs.m[Matrix3x3::M11] + self.m[Matrix3x3::M32] * rhs.m[Matrix3x3::M21] + self.m[Matrix3x3::M33] * rhs.m[Matrix3x3::M31], self.m[Matrix3x3::M31] * rhs.m[Matrix3x3::M11] + self.m[Matrix3x3::M32] * rhs.m[Matrix3x3::M21] + self.m[Matrix3x3::M33] * rhs.m[Matrix3x3::M31],
self.m[Matrix3x3::M31] * rhs.m[Matrix3x3::M12] + self.m[Matrix3x3::M32] * rhs.m[Matrix3x3::M22] + self.m[Matrix3x3::M33] * rhs.m[Matrix3x3::M32], self.m[Matrix3x3::M31] * rhs.m[Matrix3x3::M12] + self.m[Matrix3x3::M32] * rhs.m[Matrix3x3::M22] + self.m[Matrix3x3::M33] * rhs.m[Matrix3x3::M32],
self.m[Matrix3x3::M31] * rhs.m[Matrix3x3::M13] + self.m[Matrix3x3::M32] * rhs.m[Matrix3x3::M23] + self.m[Matrix3x3::M33] * rhs.m[Matrix3x3::M33] self.m[Matrix3x3::M31] * rhs.m[Matrix3x3::M13] + self.m[Matrix3x3::M32] * rhs.m[Matrix3x3::M23] + self.m[Matrix3x3::M33] * rhs.m[Matrix3x3::M33],
) )
} }
} }
impl Mul<Vector2> for Matrix3x3 { impl Mul<Vector2> for Matrix3x3 {
type Output = Vector2; type Output = Vector2;
#[rustfmt::skip] #[rustfmt::skip]
#[inline] #[inline]
fn mul(self, rhs: Vector2) -> Self::Output { fn mul(self, rhs: Vector2) -> Self::Output {
Vector2 { Vector2 {
x: rhs.x * self.m[Matrix3x3::M11] + rhs.y * self.m[Matrix3x3::M12] + self.m[Matrix3x3::M13] + self.m[Matrix3x3::M31], x: rhs.x * self.m[Matrix3x3::M11] + rhs.y * self.m[Matrix3x3::M12] + self.m[Matrix3x3::M13] + self.m[Matrix3x3::M31],
y: rhs.x * self.m[Matrix3x3::M21] + rhs.y * self.m[Matrix3x3::M22] + self.m[Matrix3x3::M23] + self.m[Matrix3x3::M32] y: rhs.x * self.m[Matrix3x3::M21] + rhs.y * self.m[Matrix3x3::M22] + self.m[Matrix3x3::M23] + self.m[Matrix3x3::M32],
} }
} }
} }
#[cfg(test)] #[cfg(test)]
pub mod tests { pub mod tests {
use super::*; use super::*;
#[test] #[test]
pub fn test_new() { pub fn test_new() {
let m = Matrix3x3::new(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0); let m = Matrix3x3::new(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0);
assert_eq!(1.0, m.m[Matrix3x3::M11]); assert_eq!(1.0, m.m[Matrix3x3::M11]);
assert_eq!(2.0, m.m[Matrix3x3::M12]); assert_eq!(2.0, m.m[Matrix3x3::M12]);
assert_eq!(3.0, m.m[Matrix3x3::M13]); assert_eq!(3.0, m.m[Matrix3x3::M13]);
assert_eq!(4.0, m.m[Matrix3x3::M21]); assert_eq!(4.0, m.m[Matrix3x3::M21]);
assert_eq!(5.0, m.m[Matrix3x3::M22]); assert_eq!(5.0, m.m[Matrix3x3::M22]);
assert_eq!(6.0, m.m[Matrix3x3::M23]); assert_eq!(6.0, m.m[Matrix3x3::M23]);
assert_eq!(7.0, m.m[Matrix3x3::M31]); assert_eq!(7.0, m.m[Matrix3x3::M31]);
assert_eq!(8.0, m.m[Matrix3x3::M32]); assert_eq!(8.0, m.m[Matrix3x3::M32]);
assert_eq!(9.0, m.m[Matrix3x3::M33]); assert_eq!(9.0, m.m[Matrix3x3::M33]);
} }
#[test] #[test]
pub fn test_set() { pub fn test_set() {
let mut m = Matrix3x3 { m: [0.0; 9] }; let mut m = Matrix3x3 { m: [0.0; 9] };
m.set(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0); m.set(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0);
assert_eq!(1.0, m.m[Matrix3x3::M11]); assert_eq!(1.0, m.m[Matrix3x3::M11]);
assert_eq!(2.0, m.m[Matrix3x3::M12]); assert_eq!(2.0, m.m[Matrix3x3::M12]);
assert_eq!(3.0, m.m[Matrix3x3::M13]); assert_eq!(3.0, m.m[Matrix3x3::M13]);
assert_eq!(4.0, m.m[Matrix3x3::M21]); assert_eq!(4.0, m.m[Matrix3x3::M21]);
assert_eq!(5.0, m.m[Matrix3x3::M22]); assert_eq!(5.0, m.m[Matrix3x3::M22]);
assert_eq!(6.0, m.m[Matrix3x3::M23]); assert_eq!(6.0, m.m[Matrix3x3::M23]);
assert_eq!(7.0, m.m[Matrix3x3::M31]); assert_eq!(7.0, m.m[Matrix3x3::M31]);
assert_eq!(8.0, m.m[Matrix3x3::M32]); assert_eq!(8.0, m.m[Matrix3x3::M32]);
assert_eq!(9.0, m.m[Matrix3x3::M33]); assert_eq!(9.0, m.m[Matrix3x3::M33]);
} }
#[test] #[test]
pub fn test_identity() { pub fn test_identity() {
let m = Matrix3x3::identity(); let m = Matrix3x3::identity();
assert_eq!(1.0, m.m[Matrix3x3::M11]); assert_eq!(1.0, m.m[Matrix3x3::M11]);
assert_eq!(0.0, m.m[Matrix3x3::M12]); assert_eq!(0.0, m.m[Matrix3x3::M12]);
assert_eq!(0.0, m.m[Matrix3x3::M13]); assert_eq!(0.0, m.m[Matrix3x3::M13]);
assert_eq!(0.0, m.m[Matrix3x3::M21]); assert_eq!(0.0, m.m[Matrix3x3::M21]);
assert_eq!(1.0, m.m[Matrix3x3::M22]); assert_eq!(1.0, m.m[Matrix3x3::M22]);
assert_eq!(0.0, m.m[Matrix3x3::M23]); assert_eq!(0.0, m.m[Matrix3x3::M23]);
assert_eq!(0.0, m.m[Matrix3x3::M31]); assert_eq!(0.0, m.m[Matrix3x3::M31]);
assert_eq!(0.0, m.m[Matrix3x3::M32]); assert_eq!(0.0, m.m[Matrix3x3::M32]);
assert_eq!(1.0, m.m[Matrix3x3::M33]); assert_eq!(1.0, m.m[Matrix3x3::M33]);
} }
#[rustfmt::skip] #[rustfmt::skip]
#[test] #[test]
pub fn test_transpose() { pub fn test_transpose() {
let m = Matrix3x3::new( let m = Matrix3x3::new(
1.0, 2.0, 3.0, 1.0, 2.0, 3.0,
4.0, 5.0, 6.0, 4.0, 5.0, 6.0,
7.0, 8.0, 9.0 7.0, 8.0, 9.0,
); );
let t = m.transpose(); let t = m.transpose();
assert_eq!(1.0, t.m[Matrix3x3::M11]); assert_eq!(1.0, t.m[Matrix3x3::M11]);
assert_eq!(4.0, t.m[Matrix3x3::M12]); assert_eq!(4.0, t.m[Matrix3x3::M12]);
assert_eq!(7.0, t.m[Matrix3x3::M13]); assert_eq!(7.0, t.m[Matrix3x3::M13]);
assert_eq!(2.0, t.m[Matrix3x3::M21]); assert_eq!(2.0, t.m[Matrix3x3::M21]);
assert_eq!(5.0, t.m[Matrix3x3::M22]); assert_eq!(5.0, t.m[Matrix3x3::M22]);
assert_eq!(8.0, t.m[Matrix3x3::M23]); assert_eq!(8.0, t.m[Matrix3x3::M23]);
assert_eq!(3.0, t.m[Matrix3x3::M31]); assert_eq!(3.0, t.m[Matrix3x3::M31]);
assert_eq!(6.0, t.m[Matrix3x3::M32]); assert_eq!(6.0, t.m[Matrix3x3::M32]);
assert_eq!(9.0, t.m[Matrix3x3::M33]); assert_eq!(9.0, t.m[Matrix3x3::M33]);
} }
#[test] #[test]
pub fn test_mul() { pub fn test_mul() {
let a = Matrix3x3::new(12.0, 8.0, 4.0, 3.0, 17.0, 14.0, 9.0, 8.0, 10.0); let a = Matrix3x3::new(12.0, 8.0, 4.0, 3.0, 17.0, 14.0, 9.0, 8.0, 10.0);
let b = Matrix3x3::new(5.0, 19.0, 3.0, 6.0, 15.0, 9.0, 7.0, 8.0, 16.0); let b = Matrix3x3::new(5.0, 19.0, 3.0, 6.0, 15.0, 9.0, 7.0, 8.0, 16.0);
let c = a * b; let c = a * b;
assert!(nearly_equal(136.0, c.m[Matrix3x3::M11], 0.001)); assert!(nearly_equal(136.0, c.m[Matrix3x3::M11], 0.001));
assert!(nearly_equal(380.0, c.m[Matrix3x3::M12], 0.001)); assert!(nearly_equal(380.0, c.m[Matrix3x3::M12], 0.001));
assert!(nearly_equal(172.0, c.m[Matrix3x3::M13], 0.001)); assert!(nearly_equal(172.0, c.m[Matrix3x3::M13], 0.001));
assert!(nearly_equal(215.0, c.m[Matrix3x3::M21], 0.001)); assert!(nearly_equal(215.0, c.m[Matrix3x3::M21], 0.001));
assert!(nearly_equal(424.0, c.m[Matrix3x3::M22], 0.001)); assert!(nearly_equal(424.0, c.m[Matrix3x3::M22], 0.001));
assert!(nearly_equal(386.0, c.m[Matrix3x3::M23], 0.001)); assert!(nearly_equal(386.0, c.m[Matrix3x3::M23], 0.001));
assert!(nearly_equal(163.0, c.m[Matrix3x3::M31], 0.001)); assert!(nearly_equal(163.0, c.m[Matrix3x3::M31], 0.001));
assert!(nearly_equal(371.0, c.m[Matrix3x3::M32], 0.001)); assert!(nearly_equal(371.0, c.m[Matrix3x3::M32], 0.001));
assert!(nearly_equal(259.0, c.m[Matrix3x3::M33], 0.001)); assert!(nearly_equal(259.0, c.m[Matrix3x3::M33], 0.001));
let mut a = Matrix3x3::new(12.0, 8.0, 4.0, 3.0, 17.0, 14.0, 9.0, 8.0, 10.0); let mut a = Matrix3x3::new(12.0, 8.0, 4.0, 3.0, 17.0, 14.0, 9.0, 8.0, 10.0);
let b = Matrix3x3::new(5.0, 19.0, 3.0, 6.0, 15.0, 9.0, 7.0, 8.0, 16.0); let b = Matrix3x3::new(5.0, 19.0, 3.0, 6.0, 15.0, 9.0, 7.0, 8.0, 16.0);
a *= b; a *= b;
assert!(nearly_equal(136.0, a.m[Matrix3x3::M11], 0.001)); assert!(nearly_equal(136.0, a.m[Matrix3x3::M11], 0.001));
assert!(nearly_equal(380.0, a.m[Matrix3x3::M12], 0.001)); assert!(nearly_equal(380.0, a.m[Matrix3x3::M12], 0.001));
assert!(nearly_equal(172.0, a.m[Matrix3x3::M13], 0.001)); assert!(nearly_equal(172.0, a.m[Matrix3x3::M13], 0.001));
assert!(nearly_equal(215.0, a.m[Matrix3x3::M21], 0.001)); assert!(nearly_equal(215.0, a.m[Matrix3x3::M21], 0.001));
assert!(nearly_equal(424.0, a.m[Matrix3x3::M22], 0.001)); assert!(nearly_equal(424.0, a.m[Matrix3x3::M22], 0.001));
assert!(nearly_equal(386.0, a.m[Matrix3x3::M23], 0.001)); assert!(nearly_equal(386.0, a.m[Matrix3x3::M23], 0.001));
assert!(nearly_equal(163.0, a.m[Matrix3x3::M31], 0.001)); assert!(nearly_equal(163.0, a.m[Matrix3x3::M31], 0.001));
assert!(nearly_equal(371.0, a.m[Matrix3x3::M32], 0.001)); assert!(nearly_equal(371.0, a.m[Matrix3x3::M32], 0.001));
assert!(nearly_equal(259.0, a.m[Matrix3x3::M33], 0.001)); assert!(nearly_equal(259.0, a.m[Matrix3x3::M33], 0.001));
} }
#[test] #[test]
pub fn test_2d_translation() { pub fn test_2d_translation() {
let v = Vector2::new(10.2, 5.7); let v = Vector2::new(10.2, 5.7);
let m = Matrix3x3::new_2d_translation(2.0, 3.0); let m = Matrix3x3::new_2d_translation(2.0, 3.0);
let t = m * v; let t = m * v;
assert!(nearly_equal(12.2, t.x, 0.001)); assert!(nearly_equal(12.2, t.x, 0.001));
assert!(nearly_equal(8.7, t.y, 0.001)); assert!(nearly_equal(8.7, t.y, 0.001));
} }
#[test] #[test]
pub fn test_2d_scaling() { pub fn test_2d_scaling() {
let v = Vector2::new(10.2, 5.7); let v = Vector2::new(10.2, 5.7);
let m = Matrix3x3::new_2d_scaling(3.0, 4.0); let m = Matrix3x3::new_2d_scaling(3.0, 4.0);
let t = m * v; let t = m * v;
assert!(nearly_equal(30.6, t.x, 0.001)); assert!(nearly_equal(30.6, t.x, 0.001));
assert!(nearly_equal(22.8, t.y, 0.001)); assert!(nearly_equal(22.8, t.y, 0.001));
} }
#[test] #[test]
pub fn test_2d_rotation() { pub fn test_2d_rotation() {
let v = Vector2::new(0.0, 5.0); let v = Vector2::new(0.0, 5.0);
let m = Matrix3x3::new_2d_rotation(RADIANS_90); let m = Matrix3x3::new_2d_rotation(RADIANS_90);
let t = m * v; let t = m * v;
assert!(nearly_equal(-5.0, t.x, 0.001)); assert!(nearly_equal(-5.0, t.x, 0.001));
assert!(nearly_equal(0.0, t.y, 0.001)); assert!(nearly_equal(0.0, t.y, 0.001));
} }
#[test] #[test]
pub fn test_2d_combined_transform() { pub fn test_2d_combined_transform() {
let a = Matrix3x3::new_2d_translation(-2.0, 0.0); let a = Matrix3x3::new_2d_translation(-2.0, 0.0);
let b = Matrix3x3::new_2d_rotation(RADIANS_180); let b = Matrix3x3::new_2d_rotation(RADIANS_180);
let m = a * b; let m = a * b;
let v = Vector2::new(0.0, 5.0); let v = Vector2::new(0.0, 5.0);
let t = m * v; let t = m * v;
assert!(nearly_equal(2.0, t.x, 0.001)); assert!(nearly_equal(2.0, t.x, 0.001));
assert!(nearly_equal(-5.0, t.y, 0.001)); assert!(nearly_equal(-5.0, t.y, 0.001));
} }
} }

View file

@ -10,9 +10,12 @@ pub mod matrix3x3;
pub mod rect; pub mod rect;
pub mod vector2; pub mod vector2;
pub const PI: f32 = std::f32::consts::PI; // 180 degrees pub const PI: f32 = std::f32::consts::PI;
pub const HALF_PI: f32 = PI / 2.0; // 90 degrees // 180 degrees
pub const QUARTER_PI: f32 = PI / 4.0; // 45 degrees pub const HALF_PI: f32 = PI / 2.0;
// 90 degrees
pub const QUARTER_PI: f32 = PI / 4.0;
// 45 degrees
pub const TWO_PI: f32 = PI * 2.0; // 360 degrees pub const TWO_PI: f32 = PI * 2.0; // 360 degrees
pub const RADIANS_0: f32 = 0.0; pub const RADIANS_0: f32 = 0.0;
@ -40,8 +43,8 @@ pub const RIGHT: f32 = RADIANS_0;
/// precision of the provided epsilon value. /// precision of the provided epsilon value.
#[inline] #[inline]
pub fn nearly_equal(a: f32, b: f32, epsilon: f32) -> bool { pub fn nearly_equal(a: f32, b: f32, epsilon: f32) -> bool {
// HACK/TODO: this is a shitty way to do this. but it's "good enough" for me ... // HACK/TODO: this is a shitty way to do this. but it's "good enough" for me ...
(a - b).abs() <= epsilon (a - b).abs() <= epsilon
} }
/// Linearly interpolates between two values. /// Linearly interpolates between two values.
@ -53,10 +56,10 @@ pub fn nearly_equal(a: f32, b: f32, epsilon: f32) -> bool {
/// * `t`: the amount to interpolate between the two values, specified as a fraction /// * `t`: the amount to interpolate between the two values, specified as a fraction
#[inline] #[inline]
pub fn lerp<N>(a: N, b: N, t: f32) -> N pub fn lerp<N>(a: N, b: N, t: f32) -> N
where where
N: Copy + Add<Output = N> + Sub<Output = N> + Mul<f32, Output = N>, N: Copy + Add<Output=N> + Sub<Output=N> + Mul<f32, Output=N>,
{ {
a + (b - a) * t a + (b - a) * t
} }
/// Given a linearly interpolated value and the original range (high and low) of the linear /// Given a linearly interpolated value and the original range (high and low) of the linear
@ -69,10 +72,10 @@ where
/// * `lerp_result`: the interpolated value between the range given /// * `lerp_result`: the interpolated value between the range given
#[inline] #[inline]
pub fn inverse_lerp<N>(a: N, b: N, lerp_result: N) -> f32 pub fn inverse_lerp<N>(a: N, b: N, lerp_result: N) -> f32
where where
N: Copy + Sub<Output = N> + Div<N, Output = f32>, N: Copy + Sub<Output=N> + Div<N, Output=f32>,
{ {
(lerp_result - a) / (b - a) (lerp_result - a) / (b - a)
} }
/// Interpolates between two values using a cubic equation. /// Interpolates between two values using a cubic equation.
@ -84,11 +87,11 @@ where
/// * `t`: the amount to interpolate between the two values, specified as a fraction /// * `t`: the amount to interpolate between the two values, specified as a fraction
#[inline] #[inline]
pub fn smooth_lerp<N>(a: N, b: N, t: f32) -> N pub fn smooth_lerp<N>(a: N, b: N, t: f32) -> N
where where
N: Copy + Add<Output = N> + Sub<Output = N> + Mul<f32, Output = N>, N: Copy + Add<Output=N> + Sub<Output=N> + Mul<f32, Output=N>,
{ {
let t = t.clamp(0.0, 1.0); let t = t.clamp(0.0, 1.0);
lerp(a, b, (t * t) * (3.0 - (2.0 * t))) lerp(a, b, (t * t) * (3.0 - (2.0 * t)))
} }
/// Re-scales a given value from an old min/max range to a new and different min/max range such /// Re-scales a given value from an old min/max range to a new and different min/max range such
@ -104,174 +107,174 @@ where
/// * `new_max`: new max value (high end of range) /// * `new_max`: new max value (high end of range)
#[inline] #[inline]
pub fn scale_range<N>(value: N, old_min: N, old_max: N, new_min: N, new_max: N) -> N pub fn scale_range<N>(value: N, old_min: N, old_max: N, new_min: N, new_max: N) -> N
where where
N: Copy + Add<Output = N> + Sub<Output = N> + Mul<Output = N> + Div<Output = N>, N: Copy + Add<Output=N> + Sub<Output=N> + Mul<Output=N> + Div<Output=N>,
{ {
(new_max - new_min) * (value - old_min) / (old_max - old_min) + new_min (new_max - new_min) * (value - old_min) / (old_max - old_min) + new_min
} }
/// Calculates the angle (in radians) between the two points. /// Calculates the angle (in radians) between the two points.
#[inline] #[inline]
pub fn angle_between(x1: f32, y1: f32, x2: f32, y2: f32) -> f32 { pub fn angle_between(x1: f32, y1: f32, x2: f32, y2: f32) -> f32 {
let delta_x = x2 - x1; let delta_x = x2 - x1;
let delta_y = y2 - y1; let delta_y = y2 - y1;
delta_y.atan2(delta_x) delta_y.atan2(delta_x)
} }
/// Returns the X and Y point of a normalized 2D vector that points in the same direction as /// Returns the X and Y point of a normalized 2D vector that points in the same direction as
/// the given angle. /// the given angle.
#[inline] #[inline]
pub fn angle_to_direction(radians: f32) -> (f32, f32) { pub fn angle_to_direction(radians: f32) -> (f32, f32) {
let x = radians.cos(); let x = radians.cos();
let y = radians.sin(); let y = radians.sin();
(x, y) (x, y)
} }
/// Calculates the squared distance between two 2D points. /// Calculates the squared distance between two 2D points.
#[inline] #[inline]
pub fn distance_squared_between(x1: f32, y1: f32, x2: f32, y2: f32) -> f32 { pub fn distance_squared_between(x1: f32, y1: f32, x2: f32, y2: f32) -> f32 {
(x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1) (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)
} }
/// Calculates the distance between two 2D points. /// Calculates the distance between two 2D points.
#[inline] #[inline]
pub fn distance_between(x1: f32, y1: f32, x2: f32, y2: f32) -> f32 { pub fn distance_between(x1: f32, y1: f32, x2: f32, y2: f32) -> f32 {
distance_squared_between(x1, y1, x2, y2).sqrt() distance_squared_between(x1, y1, x2, y2).sqrt()
} }
pub trait NearlyEqual { pub trait NearlyEqual {
type Output; type Output;
/// Returns true if the two f32 values are "close enough" to be considered equal using the /// Returns true if the two f32 values are "close enough" to be considered equal using the
/// precision of the provided epsilon value. /// precision of the provided epsilon value.
fn nearly_equal(self, other: Self::Output, epsilon: f32) -> bool; fn nearly_equal(self, other: Self::Output, epsilon: f32) -> bool;
} }
impl NearlyEqual for f32 { impl NearlyEqual for f32 {
type Output = f32; type Output = f32;
#[inline(always)] #[inline(always)]
fn nearly_equal(self, other: Self::Output, epsilon: f32) -> bool { fn nearly_equal(self, other: Self::Output, epsilon: f32) -> bool {
nearly_equal(self, other, epsilon) nearly_equal(self, other, epsilon)
} }
} }
pub trait WrappingRadians { pub trait WrappingRadians {
type Type; type Type;
/// Adds two angle values in radians together returning the result. The addition will wrap /// Adds two angle values in radians together returning the result. The addition will wrap
/// around so that the returned result always lies within 0 -> 2π radians (0 -> 360 degrees). /// around so that the returned result always lies within 0 -> 2π radians (0 -> 360 degrees).
fn wrapping_radians_add(self, other: Self::Type) -> Self::Type; fn wrapping_radians_add(self, other: Self::Type) -> Self::Type;
/// Subtracts two angle values in radians returning the result. The subtraction will wrap /// Subtracts two angle values in radians returning the result. The subtraction will wrap
/// around so that the returned result always lies within 0 -> 2π radians (0 -> 360 degrees). /// around so that the returned result always lies within 0 -> 2π radians (0 -> 360 degrees).
fn wrapping_radians_sub(self, other: Self::Type) -> Self::Type; fn wrapping_radians_sub(self, other: Self::Type) -> Self::Type;
} }
impl WrappingRadians for f32 { impl WrappingRadians for f32 {
type Type = f32; type Type = f32;
fn wrapping_radians_add(self, other: Self::Type) -> Self::Type { fn wrapping_radians_add(self, other: Self::Type) -> Self::Type {
let result = self + other; let result = self + other;
if result < RADIANS_0 { if result < RADIANS_0 {
result + RADIANS_360 result + RADIANS_360
} else if result >= RADIANS_360 { } else if result >= RADIANS_360 {
result - RADIANS_360 result - RADIANS_360
} else { } else {
result result
} }
} }
fn wrapping_radians_sub(self, other: Self::Type) -> Self::Type { fn wrapping_radians_sub(self, other: Self::Type) -> Self::Type {
let result = self - other; let result = self - other;
if result < RADIANS_0 { if result < RADIANS_0 {
result + RADIANS_360 result + RADIANS_360
} else if result >= RADIANS_360 { } else if result >= RADIANS_360 {
result - RADIANS_360 result - RADIANS_360
} else { } else {
result result
} }
} }
} }
#[cfg(test)] #[cfg(test)]
pub mod tests { pub mod tests {
use super::*; use super::*;
#[test] #[test]
pub fn test_nearly_equal() { pub fn test_nearly_equal() {
assert!(nearly_equal(4.0, 4.0, 0.1)); assert!(nearly_equal(4.0, 4.0, 0.1));
assert!(4.0f32.nearly_equal(4.0, 0.1)); assert!(4.0f32.nearly_equal(4.0, 0.1));
assert!(nearly_equal(0.1 + 0.2, 0.3, 0.01)); assert!(nearly_equal(0.1 + 0.2, 0.3, 0.01));
assert!(!nearly_equal(1.0001, 1.0005, 0.0001)); assert!(!nearly_equal(1.0001, 1.0005, 0.0001));
} }
#[test] #[test]
pub fn test_lerp() { pub fn test_lerp() {
assert!(nearly_equal(15.0, lerp(10.0, 20.0, 0.5), 0.0001)); assert!(nearly_equal(15.0, lerp(10.0, 20.0, 0.5), 0.0001));
assert!(nearly_equal(10.0, lerp(10.0, 20.0, 0.0), 0.0001)); assert!(nearly_equal(10.0, lerp(10.0, 20.0, 0.0), 0.0001));
assert!(nearly_equal(20.0, lerp(10.0, 20.0, 1.0), 0.0001)); assert!(nearly_equal(20.0, lerp(10.0, 20.0, 1.0), 0.0001));
} }
#[test] #[test]
pub fn test_inverse_lerp() { pub fn test_inverse_lerp() {
assert_eq!(0.5, inverse_lerp(10.0, 20.0, 15.0f32)) assert_eq!(0.5, inverse_lerp(10.0, 20.0, 15.0f32))
} }
#[test] #[test]
pub fn test_angle_between() { pub fn test_angle_between() {
let angle = angle_between(20.0, 20.0, 10.0, 10.0); let angle = angle_between(20.0, 20.0, 10.0, 10.0);
assert!(nearly_equal(-RADIANS_135, angle, 0.0001)); assert!(nearly_equal(-RADIANS_135, angle, 0.0001));
let angle = angle_between(0.0, 0.0, 10.0, 10.0); let angle = angle_between(0.0, 0.0, 10.0, 10.0);
assert!(nearly_equal(RADIANS_45, angle, 0.0001)); assert!(nearly_equal(RADIANS_45, angle, 0.0001));
let angle = angle_between(5.0, 5.0, 5.0, 5.0); let angle = angle_between(5.0, 5.0, 5.0, 5.0);
assert!(nearly_equal(0.0, angle, 0.0001)); assert!(nearly_equal(0.0, angle, 0.0001));
} }
#[test] #[test]
pub fn test_angle_to_direction() { pub fn test_angle_to_direction() {
let (x, y) = angle_to_direction(RADIANS_0); let (x, y) = angle_to_direction(RADIANS_0);
assert!(nearly_equal(x, 1.0, 0.000001)); assert!(nearly_equal(x, 1.0, 0.000001));
assert!(nearly_equal(y, 0.0, 0.000001)); assert!(nearly_equal(y, 0.0, 0.000001));
let (x, y) = angle_to_direction(RADIANS_45); let (x, y) = angle_to_direction(RADIANS_45);
assert!(nearly_equal(x, 0.707106, 0.000001)); assert!(nearly_equal(x, 0.707106, 0.000001));
assert!(nearly_equal(y, 0.707106, 0.000001)); assert!(nearly_equal(y, 0.707106, 0.000001));
let (x, y) = angle_to_direction(RADIANS_225); let (x, y) = angle_to_direction(RADIANS_225);
assert!(nearly_equal(x, -0.707106, 0.000001)); assert!(nearly_equal(x, -0.707106, 0.000001));
assert!(nearly_equal(y, -0.707106, 0.000001)); assert!(nearly_equal(y, -0.707106, 0.000001));
let (x, y) = angle_to_direction(UP); let (x, y) = angle_to_direction(UP);
assert!(nearly_equal(x, 0.0, 0.000001)); assert!(nearly_equal(x, 0.0, 0.000001));
assert!(nearly_equal(y, -1.0, 0.000001)); assert!(nearly_equal(y, -1.0, 0.000001));
let (x, y) = angle_to_direction(DOWN); let (x, y) = angle_to_direction(DOWN);
assert!(nearly_equal(x, 0.0, 0.000001)); assert!(nearly_equal(x, 0.0, 0.000001));
assert!(nearly_equal(y, 1.0, 0.000001)); assert!(nearly_equal(y, 1.0, 0.000001));
let (x, y) = angle_to_direction(LEFT); let (x, y) = angle_to_direction(LEFT);
assert!(nearly_equal(x, -1.0, 0.000001)); assert!(nearly_equal(x, -1.0, 0.000001));
assert!(nearly_equal(y, 0.0, 0.000001)); assert!(nearly_equal(y, 0.0, 0.000001));
let (x, y) = angle_to_direction(RIGHT); let (x, y) = angle_to_direction(RIGHT);
assert!(nearly_equal(x, 1.0, 0.000001)); assert!(nearly_equal(x, 1.0, 0.000001));
assert!(nearly_equal(y, 0.0, 0.000001)); assert!(nearly_equal(y, 0.0, 0.000001));
} }
#[test] #[test]
pub fn test_distance_between() { pub fn test_distance_between() {
let x1 = -2.0; let x1 = -2.0;
let y1 = 1.0; let y1 = 1.0;
let x2 = 4.0; let x2 = 4.0;
let y2 = 3.0; let y2 = 3.0;
let distance_squared = distance_squared_between(x1, y1, x2, y2); let distance_squared = distance_squared_between(x1, y1, x2, y2);
let distance = distance_between(x1, y1, x2, y2); let distance = distance_between(x1, y1, x2, y2);
assert!(nearly_equal(distance_squared, 40.0000, 0.0001)); assert!(nearly_equal(distance_squared, 40.0000, 0.0001));
assert!(nearly_equal(distance, 6.3245, 0.0001)); assert!(nearly_equal(distance, 6.3245, 0.0001));
} }
#[test] #[test]
pub fn test_wrapping_radians() { pub fn test_wrapping_radians() {
assert!(nearly_equal(RADIANS_90, RADIANS_45.wrapping_radians_add(RADIANS_45), 0.0001)); assert!(nearly_equal(RADIANS_90, RADIANS_45.wrapping_radians_add(RADIANS_45), 0.0001));
assert!(nearly_equal(RADIANS_90, RADIANS_180.wrapping_radians_sub(RADIANS_90), 0.0001)); assert!(nearly_equal(RADIANS_90, RADIANS_180.wrapping_radians_sub(RADIANS_90), 0.0001));
assert!(nearly_equal(RADIANS_45, RADIANS_315.wrapping_radians_add(RADIANS_90), 0.0001)); assert!(nearly_equal(RADIANS_45, RADIANS_315.wrapping_radians_add(RADIANS_90), 0.0001));
assert!(nearly_equal(RADIANS_315, RADIANS_90.wrapping_radians_sub(RADIANS_135), 0.0001)); assert!(nearly_equal(RADIANS_315, RADIANS_90.wrapping_radians_sub(RADIANS_135), 0.0001));
} }
} }

View file

@ -1,284 +1,284 @@
/// Represents a 2D rectangle, using integer coordinates and dimensions. /// Represents a 2D rectangle, using integer coordinates and dimensions.
#[derive(Debug, Copy, Clone, Eq, PartialEq)] #[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct Rect { pub struct Rect {
pub x: i32, pub x: i32,
pub y: i32, pub y: i32,
pub width: u32, pub width: u32,
pub height: u32, pub height: u32,
} }
impl Rect { impl Rect {
#[inline] #[inline]
pub fn new(x: i32, y: i32, width: u32, height: u32) -> Rect { pub fn new(x: i32, y: i32, width: u32, height: u32) -> Rect {
Rect { Rect {
x, x,
y, y,
width, width,
height, height,
} }
} }
/// Creates a new rect from the specified coordinates. Automatically determines if the /// Creates a new rect from the specified coordinates. Automatically determines if the
/// coordinates provided are swapped (where the right/bottom coordinate is provided before the /// coordinates provided are swapped (where the right/bottom coordinate is provided before the
/// left/top). All of the coordinates are inclusive. /// left/top). All of the coordinates are inclusive.
/// ///
/// # Arguments /// # Arguments
/// ///
/// * `left`: the left x coordinate /// * `left`: the left x coordinate
/// * `top`: the top y coordinate /// * `top`: the top y coordinate
/// * `right`: the right x coordinate /// * `right`: the right x coordinate
/// * `bottom`: the bottom y coordinate /// * `bottom`: the bottom y coordinate
pub fn from_coords(left: i32, top: i32, right: i32, bottom: i32) -> Rect { pub fn from_coords(left: i32, top: i32, right: i32, bottom: i32) -> Rect {
let x; let x;
let y; let y;
let width; let width;
let height; let height;
if left <= right { if left <= right {
x = left; x = left;
width = (right - left).abs() + 1; width = (right - left).abs() + 1;
} else { } else {
x = right; x = right;
width = (left - right).abs() + 1; width = (left - right).abs() + 1;
} }
if top <= bottom { if top <= bottom {
y = top; y = top;
height = (bottom - top).abs() + 1; height = (bottom - top).abs() + 1;
} else { } else {
y = bottom; y = bottom;
height = (top - bottom).abs() + 1; height = (top - bottom).abs() + 1;
} }
Rect { Rect {
x, x,
y, y,
width: width as u32, width: width as u32,
height: height as u32, height: height as u32,
} }
} }
pub fn set_from_coords(&mut self, left: i32, top: i32, right: i32, bottom: i32) { pub fn set_from_coords(&mut self, left: i32, top: i32, right: i32, bottom: i32) {
if left <= right { if left <= right {
self.x = left; self.x = left;
self.width = ((right - left).abs() + 1) as u32; self.width = ((right - left).abs() + 1) as u32;
} else { } else {
self.x = right; self.x = right;
self.width = ((left - right).abs() + 1) as u32; self.width = ((left - right).abs() + 1) as u32;
} }
if top <= bottom { if top <= bottom {
self.y = top; self.y = top;
self.height = ((bottom - top).abs() + 1) as u32; self.height = ((bottom - top).abs() + 1) as u32;
} else { } else {
self.y = bottom; self.y = bottom;
self.height = ((top - bottom).abs() + 1) as u32; self.height = ((top - bottom).abs() + 1) as u32;
} }
} }
/// Calculates the right-most x coordinate contained by this rect. /// Calculates the right-most x coordinate contained by this rect.
#[inline] #[inline]
pub fn right(&self) -> i32 { pub fn right(&self) -> i32 {
if self.width > 0 { if self.width > 0 {
self.x + self.width as i32 - 1 self.x + self.width as i32 - 1
} else { } else {
self.x self.x
} }
} }
/// Calculates the bottom-most y coordinate contained by this rect. /// Calculates the bottom-most y coordinate contained by this rect.
#[inline] #[inline]
pub fn bottom(&self) -> i32 { pub fn bottom(&self) -> i32 {
if self.height > 0 { if self.height > 0 {
self.y + self.height as i32 - 1 self.y + self.height as i32 - 1
} else { } else {
self.y self.y
} }
} }
/// Returns true if the given point is contained within the bounds of this rect. /// Returns true if the given point is contained within the bounds of this rect.
pub fn contains_point(&self, x: i32, y: i32) -> bool { pub fn contains_point(&self, x: i32, y: i32) -> bool {
(self.x <= x) && (self.right() >= x) && (self.y <= y) && (self.bottom() >= y) (self.x <= x) && (self.right() >= x) && (self.y <= y) && (self.bottom() >= y)
} }
/// Returns true if the given rect is contained completely within the bounds of this rect. /// Returns true if the given rect is contained completely within the bounds of this rect.
pub fn contains_rect(&self, other: &Rect) -> bool { pub fn contains_rect(&self, other: &Rect) -> bool {
(other.x >= self.x && other.x < self.right()) (other.x >= self.x && other.x < self.right())
&& (other.right() > self.x && other.right() <= self.right()) && (other.right() > self.x && other.right() <= self.right())
&& (other.y >= self.y && other.y < self.bottom()) && (other.y >= self.y && other.y < self.bottom())
&& (other.bottom() > self.y && other.bottom() <= self.bottom()) && (other.bottom() > self.y && other.bottom() <= self.bottom())
} }
/// Returns true if the given rect at least partially overlaps the bounds of this rect. /// Returns true if the given rect at least partially overlaps the bounds of this rect.
pub fn overlaps(&self, other: &Rect) -> bool { pub fn overlaps(&self, other: &Rect) -> bool {
(self.x <= other.right()) (self.x <= other.right())
&& (self.right() >= other.x) && (self.right() >= other.x)
&& (self.y <= other.bottom()) && (self.y <= other.bottom())
&& (self.bottom() >= other.y) && (self.bottom() >= other.y)
} }
pub fn clamp_to(&mut self, other: &Rect) -> bool { pub fn clamp_to(&mut self, other: &Rect) -> bool {
if !self.overlaps(other) { if !self.overlaps(other) {
// not possible to clamp this rect to the other rect as they do not overlap at all // not possible to clamp this rect to the other rect as they do not overlap at all
false false
} else { } else {
// the rects at least partially overlap, so we will clamp this rect to the overlapping // the rects at least partially overlap, so we will clamp this rect to the overlapping
// region of the other rect // region of the other rect
let mut x1 = self.x; let mut x1 = self.x;
let mut y1 = self.y; let mut y1 = self.y;
let mut x2 = self.right(); let mut x2 = self.right();
let mut y2 = self.bottom(); let mut y2 = self.bottom();
if y1 < other.y { if y1 < other.y {
y1 = other.y; y1 = other.y;
} }
if y1 > other.bottom() { if y1 > other.bottom() {
y1 = other.bottom(); y1 = other.bottom();
} }
if y2 < other.y { if y2 < other.y {
y2 = other.y; y2 = other.y;
} }
if y2 > other.bottom() { if y2 > other.bottom() {
y2 = other.bottom(); y2 = other.bottom();
} }
if x1 < other.x { if x1 < other.x {
x1 = other.x; x1 = other.x;
} }
if x1 > other.right() { if x1 > other.right() {
x1 = other.right(); x1 = other.right();
} }
if x2 < other.x { if x2 < other.x {
x2 = other.x; x2 = other.x;
} }
if x2 > other.right() { if x2 > other.right() {
x2 = other.right(); x2 = other.right();
} }
self.set_from_coords(x1, y1, x2, y2); self.set_from_coords(x1, y1, x2, y2);
true true
} }
} }
} }
#[cfg(test)] #[cfg(test)]
pub mod tests { pub mod tests {
use super::*; use super::*;
#[test] #[test]
pub fn right_and_left() { pub fn right_and_left() {
let rect = Rect { let rect = Rect {
x: 5, x: 5,
y: 6, y: 6,
width: 16, width: 16,
height: 12, height: 12,
}; };
assert_eq!(20, rect.right()); assert_eq!(20, rect.right());
assert_eq!(17, rect.bottom()); assert_eq!(17, rect.bottom());
let rect = Rect { let rect = Rect {
x: -11, x: -11,
y: -25, y: -25,
width: 16, width: 16,
height: 12, height: 12,
}; };
assert_eq!(4, rect.right()); assert_eq!(4, rect.right());
assert_eq!(-14, rect.bottom()); assert_eq!(-14, rect.bottom());
} }
#[test] #[test]
pub fn create_from_coords() { pub fn create_from_coords() {
let rect = Rect::from_coords(10, 15, 20, 30); let rect = Rect::from_coords(10, 15, 20, 30);
assert_eq!(10, rect.x); assert_eq!(10, rect.x);
assert_eq!(15, rect.y); assert_eq!(15, rect.y);
assert_eq!(11, rect.width); assert_eq!(11, rect.width);
assert_eq!(16, rect.height); assert_eq!(16, rect.height);
assert_eq!(20, rect.right()); assert_eq!(20, rect.right());
assert_eq!(30, rect.bottom()); assert_eq!(30, rect.bottom());
let rect = Rect::from_coords(-5, -13, 6, -2); let rect = Rect::from_coords(-5, -13, 6, -2);
assert_eq!(-5, rect.x); assert_eq!(-5, rect.x);
assert_eq!(-13, rect.y); assert_eq!(-13, rect.y);
assert_eq!(12, rect.width); assert_eq!(12, rect.width);
assert_eq!(12, rect.height); assert_eq!(12, rect.height);
assert_eq!(6, rect.right()); assert_eq!(6, rect.right());
assert_eq!(-2, rect.bottom()); assert_eq!(-2, rect.bottom());
} }
#[test] #[test]
pub fn create_from_coords_with_swapped_order() { pub fn create_from_coords_with_swapped_order() {
let rect = Rect::from_coords(20, 30, 10, 15); let rect = Rect::from_coords(20, 30, 10, 15);
assert_eq!(10, rect.x); assert_eq!(10, rect.x);
assert_eq!(15, rect.y); assert_eq!(15, rect.y);
assert_eq!(11, rect.width); assert_eq!(11, rect.width);
assert_eq!(16, rect.height); assert_eq!(16, rect.height);
assert_eq!(20, rect.right()); assert_eq!(20, rect.right());
assert_eq!(30, rect.bottom()); assert_eq!(30, rect.bottom());
let rect = Rect::from_coords(6, -2, -5, -13); let rect = Rect::from_coords(6, -2, -5, -13);
assert_eq!(-5, rect.x); assert_eq!(-5, rect.x);
assert_eq!(-13, rect.y); assert_eq!(-13, rect.y);
assert_eq!(12, rect.width); assert_eq!(12, rect.width);
assert_eq!(12, rect.height); assert_eq!(12, rect.height);
assert_eq!(6, rect.right()); assert_eq!(6, rect.right());
assert_eq!(-2, rect.bottom()); assert_eq!(-2, rect.bottom());
} }
#[test] #[test]
pub fn test_contains_point() { pub fn test_contains_point() {
let r = Rect::from_coords(10, 10, 20, 20); let r = Rect::from_coords(10, 10, 20, 20);
assert!(r.contains_point(10, 10)); assert!(r.contains_point(10, 10));
assert!(r.contains_point(15, 15)); assert!(r.contains_point(15, 15));
assert!(r.contains_point(20, 20)); assert!(r.contains_point(20, 20));
assert!(!r.contains_point(12, 30)); assert!(!r.contains_point(12, 30));
assert!(!r.contains_point(8, 12)); assert!(!r.contains_point(8, 12));
assert!(!r.contains_point(25, 16)); assert!(!r.contains_point(25, 16));
assert!(!r.contains_point(17, 4)); assert!(!r.contains_point(17, 4));
} }
#[test] #[test]
pub fn test_contains_rect() { pub fn test_contains_rect() {
let r = Rect::from_coords(10, 10, 20, 20); let r = Rect::from_coords(10, 10, 20, 20);
assert!(r.contains_rect(&Rect::from_coords(12, 12, 15, 15))); assert!(r.contains_rect(&Rect::from_coords(12, 12, 15, 15)));
assert!(r.contains_rect(&Rect::from_coords(10, 10, 15, 15))); assert!(r.contains_rect(&Rect::from_coords(10, 10, 15, 15)));
assert!(r.contains_rect(&Rect::from_coords(15, 15, 20, 20))); assert!(r.contains_rect(&Rect::from_coords(15, 15, 20, 20)));
assert!(r.contains_rect(&Rect::from_coords(10, 12, 20, 15))); assert!(r.contains_rect(&Rect::from_coords(10, 12, 20, 15)));
assert!(r.contains_rect(&Rect::from_coords(12, 10, 15, 20))); assert!(r.contains_rect(&Rect::from_coords(12, 10, 15, 20)));
assert!(!r.contains_rect(&Rect::from_coords(5, 5, 15, 15))); assert!(!r.contains_rect(&Rect::from_coords(5, 5, 15, 15)));
assert!(!r.contains_rect(&Rect::from_coords(15, 15, 25, 25))); assert!(!r.contains_rect(&Rect::from_coords(15, 15, 25, 25)));
assert!(!r.contains_rect(&Rect::from_coords(2, 2, 8, 4))); assert!(!r.contains_rect(&Rect::from_coords(2, 2, 8, 4)));
assert!(!r.contains_rect(&Rect::from_coords(12, 21, 18, 25))); assert!(!r.contains_rect(&Rect::from_coords(12, 21, 18, 25)));
assert!(!r.contains_rect(&Rect::from_coords(22, 12, 32, 17))); assert!(!r.contains_rect(&Rect::from_coords(22, 12, 32, 17)));
} }
#[test] #[test]
pub fn test_overlaps() { pub fn test_overlaps() {
let r = Rect::from_coords(10, 10, 20, 20); let r = Rect::from_coords(10, 10, 20, 20);
assert!(r.overlaps(&Rect::from_coords(12, 12, 15, 15))); assert!(r.overlaps(&Rect::from_coords(12, 12, 15, 15)));
assert!(r.overlaps(&Rect::from_coords(10, 10, 15, 15))); assert!(r.overlaps(&Rect::from_coords(10, 10, 15, 15)));
assert!(r.overlaps(&Rect::from_coords(15, 15, 20, 20))); assert!(r.overlaps(&Rect::from_coords(15, 15, 20, 20)));
assert!(r.overlaps(&Rect::from_coords(10, 12, 20, 15))); assert!(r.overlaps(&Rect::from_coords(10, 12, 20, 15)));
assert!(r.overlaps(&Rect::from_coords(12, 10, 15, 20))); assert!(r.overlaps(&Rect::from_coords(12, 10, 15, 20)));
assert!(r.overlaps(&Rect::from_coords(12, 5, 18, 10))); assert!(r.overlaps(&Rect::from_coords(12, 5, 18, 10)));
assert!(r.overlaps(&Rect::from_coords(13, 20, 16, 25))); assert!(r.overlaps(&Rect::from_coords(13, 20, 16, 25)));
assert!(r.overlaps(&Rect::from_coords(5, 12, 10, 18))); assert!(r.overlaps(&Rect::from_coords(5, 12, 10, 18)));
assert!(r.overlaps(&Rect::from_coords(20, 13, 25, 16))); assert!(r.overlaps(&Rect::from_coords(20, 13, 25, 16)));
assert!(r.overlaps(&Rect::from_coords(5, 5, 15, 15))); assert!(r.overlaps(&Rect::from_coords(5, 5, 15, 15)));
assert!(r.overlaps(&Rect::from_coords(15, 15, 25, 25))); assert!(r.overlaps(&Rect::from_coords(15, 15, 25, 25)));
assert!(!r.overlaps(&Rect::from_coords(2, 2, 8, 4))); assert!(!r.overlaps(&Rect::from_coords(2, 2, 8, 4)));
assert!(!r.overlaps(&Rect::from_coords(12, 21, 18, 25))); assert!(!r.overlaps(&Rect::from_coords(12, 21, 18, 25)));
assert!(!r.overlaps(&Rect::from_coords(22, 12, 32, 17))); assert!(!r.overlaps(&Rect::from_coords(22, 12, 32, 17)));
assert!(!r.overlaps(&Rect::from_coords(12, 5, 18, 9))); assert!(!r.overlaps(&Rect::from_coords(12, 5, 18, 9)));
assert!(!r.overlaps(&Rect::from_coords(13, 21, 16, 25))); assert!(!r.overlaps(&Rect::from_coords(13, 21, 16, 25)));
assert!(!r.overlaps(&Rect::from_coords(5, 12, 9, 18))); assert!(!r.overlaps(&Rect::from_coords(5, 12, 9, 18)));
assert!(!r.overlaps(&Rect::from_coords(21, 13, 25, 16))); assert!(!r.overlaps(&Rect::from_coords(21, 13, 25, 16)));
} }
} }

View file

@ -5,478 +5,478 @@ use crate::math::*;
/// Represents a 2D vector and provides common methods for vector math. /// Represents a 2D vector and provides common methods for vector math.
#[derive(Debug, Copy, Clone, PartialEq)] #[derive(Debug, Copy, Clone, PartialEq)]
pub struct Vector2 { pub struct Vector2 {
pub x: f32, pub x: f32,
pub y: f32, pub y: f32,
} }
impl Vector2 { impl Vector2 {
pub const ZERO: Vector2 = Vector2 { x: 0.0, y: 0.0 }; pub const ZERO: Vector2 = Vector2 { x: 0.0, y: 0.0 };
pub const UP: Vector2 = Vector2 { x: 0.0, y: -1.0 }; pub const UP: Vector2 = Vector2 { x: 0.0, y: -1.0 };
pub const DOWN: Vector2 = Vector2 { x: 0.0, y: 1.0 }; pub const DOWN: Vector2 = Vector2 { x: 0.0, y: 1.0 };
pub const LEFT: Vector2 = Vector2 { x: -1.0, y: 0.0 }; pub const LEFT: Vector2 = Vector2 { x: -1.0, y: 0.0 };
pub const RIGHT: Vector2 = Vector2 { x: 1.0, y: 0.0 }; pub const RIGHT: Vector2 = Vector2 { x: 1.0, y: 0.0 };
pub const X_AXIS: Vector2 = Vector2 { x: 1.0, y: 0.0 }; pub const X_AXIS: Vector2 = Vector2 { x: 1.0, y: 0.0 };
pub const Y_AXIS: Vector2 = Vector2 { x: 0.0, y: 1.0 }; pub const Y_AXIS: Vector2 = Vector2 { x: 0.0, y: 1.0 };
/// Creates a vector with the specified X and Y components. /// Creates a vector with the specified X and Y components.
#[inline] #[inline]
pub fn new(x: f32, y: f32) -> Vector2 { pub fn new(x: f32, y: f32) -> Vector2 {
Vector2 { x, y } Vector2 { x, y }
} }
/// Creates a normalized vector that points in the same direction as the given angle. /// Creates a normalized vector that points in the same direction as the given angle.
#[inline] #[inline]
pub fn from_angle(radians: f32) -> Vector2 { pub fn from_angle(radians: f32) -> Vector2 {
let (x, y) = angle_to_direction(radians); let (x, y) = angle_to_direction(radians);
Vector2 { x, y } Vector2 { x, y }
} }
/// Calculates the distance between this and another vector. /// Calculates the distance between this and another vector.
#[inline] #[inline]
pub fn distance(&self, other: &Vector2) -> f32 { pub fn distance(&self, other: &Vector2) -> f32 {
self.distance_squared(other).sqrt() self.distance_squared(other).sqrt()
} }
/// Calculates the squared distance between this and another vector. /// Calculates the squared distance between this and another vector.
#[inline] #[inline]
pub fn distance_squared(&self, other: &Vector2) -> f32 { pub fn distance_squared(&self, other: &Vector2) -> f32 {
(other.x - self.x) * (other.x - self.x) + (other.y - self.y) * (other.y - self.y) (other.x - self.x) * (other.x - self.x) + (other.y - self.y) * (other.y - self.y)
} }
/// Calculates the dot product of this and another vector. /// Calculates the dot product of this and another vector.
#[inline] #[inline]
pub fn dot(&self, other: &Vector2) -> f32 { pub fn dot(&self, other: &Vector2) -> f32 {
(self.x * other.x) + (self.y * other.y) (self.x * other.x) + (self.y * other.y)
} }
/// Calculates the length (a.k.a. magnitude) of this vector. /// Calculates the length (a.k.a. magnitude) of this vector.
#[inline] #[inline]
pub fn length(&self) -> f32 { pub fn length(&self) -> f32 {
self.length_squared().sqrt() self.length_squared().sqrt()
} }
/// Calculates the squared length of this vector. /// Calculates the squared length of this vector.
#[inline] #[inline]
pub fn length_squared(&self) -> f32 { pub fn length_squared(&self) -> f32 {
(self.x * self.x) + (self.y * self.y) (self.x * self.x) + (self.y * self.y)
} }
/// Returns a normalized vector from this vector. /// Returns a normalized vector from this vector.
pub fn normalize(&self) -> Vector2 { pub fn normalize(&self) -> Vector2 {
let inverse_length = 1.0 / self.length(); let inverse_length = 1.0 / self.length();
Vector2 { Vector2 {
x: self.x * inverse_length, x: self.x * inverse_length,
y: self.y * inverse_length, y: self.y * inverse_length,
} }
} }
/// Returns an extended (or shrunk) vector from this vector, where the returned vector will /// Returns an extended (or shrunk) vector from this vector, where the returned vector will
/// have a length exactly matching the specified length, but will retain the same direction. /// have a length exactly matching the specified length, but will retain the same direction.
pub fn extend(&self, length: f32) -> Vector2 { pub fn extend(&self, length: f32) -> Vector2 {
*self * (length / self.length()) *self * (length / self.length())
} }
/// Returns the angle (in radians) equivalent to the direction of this vector. /// Returns the angle (in radians) equivalent to the direction of this vector.
#[inline] #[inline]
pub fn angle(&self) -> f32 { pub fn angle(&self) -> f32 {
self.y.atan2(self.x) self.y.atan2(self.x)
} }
/// Calculates the angle (in radians) between the this and another vector. /// Calculates the angle (in radians) between the this and another vector.
#[inline] #[inline]
pub fn angle_between(&self, other: &Vector2) -> f32 { pub fn angle_between(&self, other: &Vector2) -> f32 {
angle_between(self.x, self.y, other.x, other.y) angle_between(self.x, self.y, other.x, other.y)
} }
/// Returns true if this vector is nearly equal to the zero vector (0.0, 0.0). /// Returns true if this vector is nearly equal to the zero vector (0.0, 0.0).
#[inline] #[inline]
pub fn almost_zero(&self, epsilon: f32) -> bool { pub fn almost_zero(&self, epsilon: f32) -> bool {
self.nearly_equal(Vector2::ZERO, epsilon) self.nearly_equal(Vector2::ZERO, epsilon)
} }
} }
impl Neg for Vector2 { impl Neg for Vector2 {
type Output = Self; type Output = Self;
#[inline] #[inline]
fn neg(self) -> Self::Output { fn neg(self) -> Self::Output {
Vector2 { Vector2 {
x: -self.x, x: -self.x,
y: -self.y, y: -self.y,
} }
} }
} }
impl Add for Vector2 { impl Add for Vector2 {
type Output = Self; type Output = Self;
#[inline] #[inline]
fn add(self, rhs: Self) -> Self::Output { fn add(self, rhs: Self) -> Self::Output {
Vector2 { Vector2 {
x: self.x + rhs.x, x: self.x + rhs.x,
y: self.y + rhs.y, y: self.y + rhs.y,
} }
} }
} }
impl AddAssign for Vector2 { impl AddAssign for Vector2 {
#[inline] #[inline]
fn add_assign(&mut self, rhs: Self) { fn add_assign(&mut self, rhs: Self) {
self.x += rhs.x; self.x += rhs.x;
self.y += rhs.y; self.y += rhs.y;
} }
} }
impl Sub for Vector2 { impl Sub for Vector2 {
type Output = Self; type Output = Self;
#[inline] #[inline]
fn sub(self, rhs: Self) -> Self::Output { fn sub(self, rhs: Self) -> Self::Output {
Vector2 { Vector2 {
x: self.x - rhs.x, x: self.x - rhs.x,
y: self.y - rhs.y, y: self.y - rhs.y,
} }
} }
} }
impl SubAssign for Vector2 { impl SubAssign for Vector2 {
#[inline] #[inline]
fn sub_assign(&mut self, rhs: Self) { fn sub_assign(&mut self, rhs: Self) {
self.x -= rhs.x; self.x -= rhs.x;
self.y -= rhs.y; self.y -= rhs.y;
} }
} }
impl Mul for Vector2 { impl Mul for Vector2 {
type Output = Self; type Output = Self;
#[inline] #[inline]
fn mul(self, rhs: Self) -> Self::Output { fn mul(self, rhs: Self) -> Self::Output {
Vector2 { Vector2 {
x: self.x * rhs.x, x: self.x * rhs.x,
y: self.y * rhs.y, y: self.y * rhs.y,
} }
} }
} }
impl MulAssign for Vector2 { impl MulAssign for Vector2 {
#[inline] #[inline]
fn mul_assign(&mut self, rhs: Self) { fn mul_assign(&mut self, rhs: Self) {
self.x *= rhs.x; self.x *= rhs.x;
self.y *= rhs.y; self.y *= rhs.y;
} }
} }
impl Div for Vector2 { impl Div for Vector2 {
type Output = Self; type Output = Self;
#[inline] #[inline]
fn div(self, rhs: Self) -> Self::Output { fn div(self, rhs: Self) -> Self::Output {
Vector2 { Vector2 {
x: self.x / rhs.x, x: self.x / rhs.x,
y: self.y / rhs.y, y: self.y / rhs.y,
} }
} }
} }
impl DivAssign for Vector2 { impl DivAssign for Vector2 {
#[inline] #[inline]
fn div_assign(&mut self, rhs: Self) { fn div_assign(&mut self, rhs: Self) {
self.x /= rhs.x; self.x /= rhs.x;
self.y /= rhs.y; self.y /= rhs.y;
} }
} }
impl Mul<f32> for Vector2 { impl Mul<f32> for Vector2 {
type Output = Self; type Output = Self;
#[inline] #[inline]
fn mul(self, rhs: f32) -> Self::Output { fn mul(self, rhs: f32) -> Self::Output {
Vector2 { Vector2 {
x: self.x * rhs, x: self.x * rhs,
y: self.y * rhs, y: self.y * rhs,
} }
} }
} }
impl MulAssign<f32> for Vector2 { impl MulAssign<f32> for Vector2 {
#[inline] #[inline]
fn mul_assign(&mut self, rhs: f32) { fn mul_assign(&mut self, rhs: f32) {
self.x *= rhs; self.x *= rhs;
self.y *= rhs; self.y *= rhs;
} }
} }
impl Div<f32> for Vector2 { impl Div<f32> for Vector2 {
type Output = Self; type Output = Self;
#[inline] #[inline]
fn div(self, rhs: f32) -> Self::Output { fn div(self, rhs: f32) -> Self::Output {
Vector2 { Vector2 {
x: self.x / rhs, x: self.x / rhs,
y: self.y / rhs, y: self.y / rhs,
} }
} }
} }
impl DivAssign<f32> for Vector2 { impl DivAssign<f32> for Vector2 {
#[inline] #[inline]
fn div_assign(&mut self, rhs: f32) { fn div_assign(&mut self, rhs: f32) {
self.x /= rhs; self.x /= rhs;
self.y /= rhs; self.y /= rhs;
} }
} }
impl NearlyEqual for Vector2 { impl NearlyEqual for Vector2 {
type Output = Self; type Output = Self;
#[inline(always)] #[inline(always)]
fn nearly_equal(self, other: Self::Output, epsilon: f32) -> bool { fn nearly_equal(self, other: Self::Output, epsilon: f32) -> bool {
nearly_equal(self.x, other.x, epsilon) && nearly_equal(self.y, other.y, epsilon) nearly_equal(self.x, other.x, epsilon) && nearly_equal(self.y, other.y, epsilon)
} }
} }
impl MulAssign<Matrix3x3> for Vector2 { impl MulAssign<Matrix3x3> for Vector2 {
#[rustfmt::skip] #[rustfmt::skip]
#[inline] #[inline]
fn mul_assign(&mut self, rhs: Matrix3x3) { fn mul_assign(&mut self, rhs: Matrix3x3) {
let x = self.x * rhs.m[Matrix3x3::M11] + self.y * rhs.m[Matrix3x3::M12] + rhs.m[Matrix3x3::M13] + rhs.m[Matrix3x3::M31]; let x = self.x * rhs.m[Matrix3x3::M11] + self.y * rhs.m[Matrix3x3::M12] + rhs.m[Matrix3x3::M13] + rhs.m[Matrix3x3::M31];
let y = self.x * rhs.m[Matrix3x3::M21] + self.y * rhs.m[Matrix3x3::M22] + rhs.m[Matrix3x3::M23] + rhs.m[Matrix3x3::M32]; let y = self.x * rhs.m[Matrix3x3::M21] + self.y * rhs.m[Matrix3x3::M22] + rhs.m[Matrix3x3::M23] + rhs.m[Matrix3x3::M32];
self.x = x; self.x = x;
self.y = y; self.y = y;
} }
} }
#[cfg(test)] #[cfg(test)]
pub mod tests { pub mod tests {
use super::*; use super::*;
#[test] #[test]
pub fn test_new() { pub fn test_new() {
let v = Vector2::new(3.0, 7.0); let v = Vector2::new(3.0, 7.0);
assert!(nearly_equal(v.x, 3.0, 0.0001)); assert!(nearly_equal(v.x, 3.0, 0.0001));
assert!(nearly_equal(v.y, 7.0, 0.0001)); assert!(nearly_equal(v.y, 7.0, 0.0001));
} }
#[test] #[test]
pub fn test_neg() { pub fn test_neg() {
let v = Vector2 { x: 1.0, y: 2.0 }; let v = Vector2 { x: 1.0, y: 2.0 };
let neg = -v; let neg = -v;
assert!(nearly_equal(neg.x, -1.0, 0.0001)); assert!(nearly_equal(neg.x, -1.0, 0.0001));
assert!(nearly_equal(neg.y, -2.0, 0.0001)); assert!(nearly_equal(neg.y, -2.0, 0.0001));
} }
#[test] #[test]
pub fn test_add() { pub fn test_add() {
let a = Vector2 { x: 3.0, y: 4.0 }; let a = Vector2 { x: 3.0, y: 4.0 };
let b = Vector2 { x: 1.0, y: 2.0 }; let b = Vector2 { x: 1.0, y: 2.0 };
let c = a + b; let c = a + b;
assert!(nearly_equal(c.x, 4.0, 0.0001)); assert!(nearly_equal(c.x, 4.0, 0.0001));
assert!(nearly_equal(c.y, 6.0, 0.0001)); assert!(nearly_equal(c.y, 6.0, 0.0001));
let mut a = Vector2 { x: 3.0, y: 4.0 }; let mut a = Vector2 { x: 3.0, y: 4.0 };
let b = Vector2 { x: 1.0, y: 2.0 }; let b = Vector2 { x: 1.0, y: 2.0 };
a += b; a += b;
assert!(nearly_equal(a.x, 4.0, 0.0001)); assert!(nearly_equal(a.x, 4.0, 0.0001));
assert!(nearly_equal(a.y, 6.0, 0.0001)); assert!(nearly_equal(a.y, 6.0, 0.0001));
} }
#[test] #[test]
pub fn test_sub() { pub fn test_sub() {
let a = Vector2 { x: 3.0, y: 4.0 }; let a = Vector2 { x: 3.0, y: 4.0 };
let b = Vector2 { x: 1.0, y: 2.0 }; let b = Vector2 { x: 1.0, y: 2.0 };
let c = a - b; let c = a - b;
assert!(nearly_equal(c.x, 2.0, 0.0001)); assert!(nearly_equal(c.x, 2.0, 0.0001));
assert!(nearly_equal(c.y, 2.0, 0.0001)); assert!(nearly_equal(c.y, 2.0, 0.0001));
let mut a = Vector2 { x: 3.0, y: 4.0 }; let mut a = Vector2 { x: 3.0, y: 4.0 };
let b = Vector2 { x: 1.0, y: 2.0 }; let b = Vector2 { x: 1.0, y: 2.0 };
a -= b; a -= b;
assert!(nearly_equal(a.x, 2.0, 0.0001)); assert!(nearly_equal(a.x, 2.0, 0.0001));
assert!(nearly_equal(a.y, 2.0, 0.0001)); assert!(nearly_equal(a.y, 2.0, 0.0001));
} }
#[test] #[test]
pub fn test_mul() { pub fn test_mul() {
let a = Vector2 { x: 2.5, y: 6.0 }; let a = Vector2 { x: 2.5, y: 6.0 };
let b = Vector2 { x: 1.25, y: 2.0 }; let b = Vector2 { x: 1.25, y: 2.0 };
let c = a * b; let c = a * b;
assert!(nearly_equal(c.x, 3.125, 0.0001)); assert!(nearly_equal(c.x, 3.125, 0.0001));
assert!(nearly_equal(c.y, 12.0, 0.0001)); assert!(nearly_equal(c.y, 12.0, 0.0001));
let mut a = Vector2 { x: 2.5, y: 6.0 }; let mut a = Vector2 { x: 2.5, y: 6.0 };
let b = Vector2 { x: 1.25, y: 2.0 }; let b = Vector2 { x: 1.25, y: 2.0 };
a *= b; a *= b;
assert!(nearly_equal(a.x, 3.125, 0.0001)); assert!(nearly_equal(a.x, 3.125, 0.0001));
assert!(nearly_equal(a.y, 12.0, 0.0001)); assert!(nearly_equal(a.y, 12.0, 0.0001));
} }
#[test] #[test]
pub fn test_div() { pub fn test_div() {
let a = Vector2 { x: 2.5, y: 6.0 }; let a = Vector2 { x: 2.5, y: 6.0 };
let b = Vector2 { x: 1.25, y: 2.0 }; let b = Vector2 { x: 1.25, y: 2.0 };
let c = a / b; let c = a / b;
assert!(nearly_equal(c.x, 2.0, 0.0001)); assert!(nearly_equal(c.x, 2.0, 0.0001));
assert!(nearly_equal(c.y, 3.0, 0.0001)); assert!(nearly_equal(c.y, 3.0, 0.0001));
let mut a = Vector2 { x: 2.5, y: 6.0 }; let mut a = Vector2 { x: 2.5, y: 6.0 };
let b = Vector2 { x: 1.25, y: 2.0 }; let b = Vector2 { x: 1.25, y: 2.0 };
a /= b; a /= b;
assert!(nearly_equal(a.x, 2.0, 0.0001)); assert!(nearly_equal(a.x, 2.0, 0.0001));
assert!(nearly_equal(a.y, 3.0, 0.0001)); assert!(nearly_equal(a.y, 3.0, 0.0001));
} }
#[test] #[test]
pub fn test_scalar_mul() { pub fn test_scalar_mul() {
let a = Vector2 { x: 1.0, y: 2.0 }; let a = Vector2 { x: 1.0, y: 2.0 };
let b = a * 2.0; let b = a * 2.0;
assert!(nearly_equal(b.x, 2.0, 0.0001)); assert!(nearly_equal(b.x, 2.0, 0.0001));
assert!(nearly_equal(b.y, 4.0, 0.0001)); assert!(nearly_equal(b.y, 4.0, 0.0001));
let mut a = Vector2 { x: 1.0, y: 2.0 }; let mut a = Vector2 { x: 1.0, y: 2.0 };
a *= 2.0; a *= 2.0;
assert!(nearly_equal(b.x, 2.0, 0.0001)); assert!(nearly_equal(b.x, 2.0, 0.0001));
assert!(nearly_equal(b.y, 4.0, 0.0001)); assert!(nearly_equal(b.y, 4.0, 0.0001));
} }
#[test] #[test]
pub fn test_scalar_div() { pub fn test_scalar_div() {
let a = Vector2 { x: 1.0, y: 2.0 }; let a = Vector2 { x: 1.0, y: 2.0 };
let b = a / 2.0; let b = a / 2.0;
assert!(nearly_equal(b.x, 0.5, 0.0001)); assert!(nearly_equal(b.x, 0.5, 0.0001));
assert!(nearly_equal(b.y, 1.0, 0.0001)); assert!(nearly_equal(b.y, 1.0, 0.0001));
let mut a = Vector2 { x: 1.0, y: 2.0 }; let mut a = Vector2 { x: 1.0, y: 2.0 };
a /= 2.0; a /= 2.0;
assert!(nearly_equal(b.x, 0.5, 0.0001)); assert!(nearly_equal(b.x, 0.5, 0.0001));
assert!(nearly_equal(b.y, 1.0, 0.0001)); assert!(nearly_equal(b.y, 1.0, 0.0001));
} }
#[test] #[test]
pub fn test_nearly_equal() { pub fn test_nearly_equal() {
let a = Vector2 { x: 3.4, y: -7.1 }; let a = Vector2 { x: 3.4, y: -7.1 };
let b = Vector2 { x: 3.5, y: -7.1 }; let b = Vector2 { x: 3.5, y: -7.1 };
assert!(!a.nearly_equal(b, 0.0001)); assert!(!a.nearly_equal(b, 0.0001));
let a = Vector2 { x: 2.0, y: 4.0 }; let a = Vector2 { x: 2.0, y: 4.0 };
let b = Vector2 { x: 2.0, y: 4.0 }; let b = Vector2 { x: 2.0, y: 4.0 };
assert!(a.nearly_equal(b, 0.0001)); assert!(a.nearly_equal(b, 0.0001));
} }
#[test] #[test]
pub fn test_length() { pub fn test_length() {
let v = Vector2 { x: 6.0, y: 8.0 }; let v = Vector2 { x: 6.0, y: 8.0 };
let length_squared = v.length_squared(); let length_squared = v.length_squared();
let length = v.length(); let length = v.length();
assert!(nearly_equal(length_squared, 100.0, 0.0001)); assert!(nearly_equal(length_squared, 100.0, 0.0001));
assert!(nearly_equal(length, 10.0, 0.0001)); assert!(nearly_equal(length, 10.0, 0.0001));
} }
#[test] #[test]
pub fn test_dot() { pub fn test_dot() {
let a = Vector2 { x: -6.0, y: 8.0 }; let a = Vector2 { x: -6.0, y: 8.0 };
let b = Vector2 { x: 5.0, y: 12.0 }; let b = Vector2 { x: 5.0, y: 12.0 };
let dot = a.dot(&b); let dot = a.dot(&b);
assert!(nearly_equal(dot, 66.0, 0.0001)); assert!(nearly_equal(dot, 66.0, 0.0001));
let a = Vector2 { x: -12.0, y: 16.0 }; let a = Vector2 { x: -12.0, y: 16.0 };
let b = Vector2 { x: 12.0, y: 9.0 }; let b = Vector2 { x: 12.0, y: 9.0 };
let dot = a.dot(&b); let dot = a.dot(&b);
assert!(nearly_equal(dot, 0.0, 0.0001)); assert!(nearly_equal(dot, 0.0, 0.0001));
} }
#[test] #[test]
pub fn test_distance() { pub fn test_distance() {
let a = Vector2 { x: 1.0, y: 1.0 }; let a = Vector2 { x: 1.0, y: 1.0 };
let b = Vector2 { x: 1.0, y: 3.0 }; let b = Vector2 { x: 1.0, y: 3.0 };
let distance_squared = a.distance_squared(&b); let distance_squared = a.distance_squared(&b);
let distance = a.distance(&b); let distance = a.distance(&b);
assert!(nearly_equal(distance_squared, 4.0, 0.0001)); assert!(nearly_equal(distance_squared, 4.0, 0.0001));
assert!(nearly_equal(distance, 2.0, 0.0001)); assert!(nearly_equal(distance, 2.0, 0.0001));
} }
#[test] #[test]
pub fn test_normalize() { pub fn test_normalize() {
let v = Vector2 { x: 3.0, y: 4.0 }; let v = Vector2 { x: 3.0, y: 4.0 };
let normalized = v.normalize(); let normalized = v.normalize();
assert!(nearly_equal(normalized.x, 0.6, 0.0001)); assert!(nearly_equal(normalized.x, 0.6, 0.0001));
assert!(nearly_equal(normalized.y, 0.8, 0.0001)); assert!(nearly_equal(normalized.y, 0.8, 0.0001));
} }
#[test] #[test]
pub fn test_extend() { pub fn test_extend() {
let v = Vector2 { x: 10.0, y: 1.0 }; let v = Vector2 { x: 10.0, y: 1.0 };
let extended = v.extend(2.0); let extended = v.extend(2.0);
assert!(nearly_equal(extended.x, 1.990, 0.0001)); assert!(nearly_equal(extended.x, 1.990, 0.0001));
assert!(nearly_equal(extended.y, 0.199, 0.0001)); assert!(nearly_equal(extended.y, 0.199, 0.0001));
} }
#[test] #[test]
#[rustfmt::skip] #[rustfmt::skip]
pub fn test_angle() { pub fn test_angle() {
assert!(nearly_equal(RADIANS_0, Vector2::new(5.0, 0.0).angle(), 0.0001)); assert!(nearly_equal(RADIANS_0, Vector2::new(5.0, 0.0).angle(), 0.0001));
assert!(nearly_equal(RADIANS_45, Vector2::new(5.0, 5.0).angle(), 0.0001)); assert!(nearly_equal(RADIANS_45, Vector2::new(5.0, 5.0).angle(), 0.0001));
assert!(nearly_equal(RADIANS_90, Vector2::new(0.0, 5.0).angle(), 0.0001)); assert!(nearly_equal(RADIANS_90, Vector2::new(0.0, 5.0).angle(), 0.0001));
assert!(nearly_equal(RADIANS_135, Vector2::new(-5.0, 5.0).angle(), 0.0001)); assert!(nearly_equal(RADIANS_135, Vector2::new(-5.0, 5.0).angle(), 0.0001));
assert!(nearly_equal(RADIANS_180, Vector2::new(-5.0, 0.0).angle(), 0.0001)); assert!(nearly_equal(RADIANS_180, Vector2::new(-5.0, 0.0).angle(), 0.0001));
assert!(nearly_equal(-RADIANS_135, Vector2::new(-5.0, -5.0).angle(), 0.0001)); assert!(nearly_equal(-RADIANS_135, Vector2::new(-5.0, -5.0).angle(), 0.0001));
assert!(nearly_equal(-RADIANS_90, Vector2::new(0.0, -5.0).angle(), 0.0001)); assert!(nearly_equal(-RADIANS_90, Vector2::new(0.0, -5.0).angle(), 0.0001));
assert!(nearly_equal(-RADIANS_45, Vector2::new(5.0, -5.0).angle(), 0.0001)); assert!(nearly_equal(-RADIANS_45, Vector2::new(5.0, -5.0).angle(), 0.0001));
assert!(nearly_equal(crate::math::UP, Vector2::UP.angle(), 0.0001));
assert!(nearly_equal(crate::math::DOWN, Vector2::DOWN.angle(), 0.0001));
assert!(nearly_equal(crate::math::LEFT, Vector2::LEFT.angle(), 0.0001));
assert!(nearly_equal(crate::math::RIGHT, Vector2::RIGHT.angle(), 0.0001));
}
#[test] assert!(nearly_equal(crate::math::UP, Vector2::UP.angle(), 0.0001));
pub fn test_from_angle() { assert!(nearly_equal(crate::math::DOWN, Vector2::DOWN.angle(), 0.0001));
let v = Vector2::from_angle(RADIANS_0); assert!(nearly_equal(crate::math::LEFT, Vector2::LEFT.angle(), 0.0001));
assert!(nearly_equal(v.x, 1.0, 0.000001)); assert!(nearly_equal(crate::math::RIGHT, Vector2::RIGHT.angle(), 0.0001));
assert!(nearly_equal(v.y, 0.0, 0.000001)); }
let v = Vector2::from_angle(RADIANS_45);
assert!(nearly_equal(v.x, 0.707106, 0.000001));
assert!(nearly_equal(v.y, 0.707106, 0.000001));
let v = Vector2::from_angle(RADIANS_225);
assert!(nearly_equal(v.x, -0.707106, 0.000001));
assert!(nearly_equal(v.y, -0.707106, 0.000001));
let v = Vector2::from_angle(UP); #[test]
assert!(v.nearly_equal(Vector2::UP, 0.000001)); pub fn test_from_angle() {
let v = Vector2::from_angle(DOWN); let v = Vector2::from_angle(RADIANS_0);
assert!(v.nearly_equal(Vector2::DOWN, 0.000001)); assert!(nearly_equal(v.x, 1.0, 0.000001));
let v = Vector2::from_angle(LEFT); assert!(nearly_equal(v.y, 0.0, 0.000001));
assert!(v.nearly_equal(Vector2::LEFT, 0.000001)); let v = Vector2::from_angle(RADIANS_45);
let v = Vector2::from_angle(RIGHT); assert!(nearly_equal(v.x, 0.707106, 0.000001));
assert!(v.nearly_equal(Vector2::RIGHT, 0.000001)); assert!(nearly_equal(v.y, 0.707106, 0.000001));
} let v = Vector2::from_angle(RADIANS_225);
assert!(nearly_equal(v.x, -0.707106, 0.000001));
assert!(nearly_equal(v.y, -0.707106, 0.000001));
#[test] let v = Vector2::from_angle(UP);
pub fn test_angle_between() { assert!(v.nearly_equal(Vector2::UP, 0.000001));
let a = Vector2::new(20.0, 20.0); let v = Vector2::from_angle(DOWN);
let b = Vector2::new(10.0, 10.0); assert!(v.nearly_equal(Vector2::DOWN, 0.000001));
let angle = a.angle_between(&b); let v = Vector2::from_angle(LEFT);
assert!(nearly_equal(-RADIANS_135, angle, 0.0001)); assert!(v.nearly_equal(Vector2::LEFT, 0.000001));
let v = Vector2::from_angle(RIGHT);
assert!(v.nearly_equal(Vector2::RIGHT, 0.000001));
}
let a = Vector2::new(0.0, 0.0); #[test]
let b = Vector2::new(10.0, 10.0); pub fn test_angle_between() {
let angle = a.angle_between(&b); let a = Vector2::new(20.0, 20.0);
assert!(nearly_equal(RADIANS_45, angle, 0.0001)); let b = Vector2::new(10.0, 10.0);
let angle = a.angle_between(&b);
assert!(nearly_equal(-RADIANS_135, angle, 0.0001));
let a = Vector2::new(5.0, 5.0); let a = Vector2::new(0.0, 0.0);
let b = Vector2::new(5.0, 5.0); let b = Vector2::new(10.0, 10.0);
let angle = a.angle_between(&b); let angle = a.angle_between(&b);
assert!(nearly_equal(0.0, angle, 0.0001)); assert!(nearly_equal(RADIANS_45, angle, 0.0001));
}
#[test] let a = Vector2::new(5.0, 5.0);
pub fn test_lerp() { let b = Vector2::new(5.0, 5.0);
let a = Vector2 { x: 5.0, y: 1.0 }; let angle = a.angle_between(&b);
let b = Vector2 { x: 10.0, y: 2.0 }; assert!(nearly_equal(0.0, angle, 0.0001));
let c = lerp(a, b, 0.5); }
assert!(nearly_equal(c.x, 7.5, 0.0001));
assert!(nearly_equal(c.y, 1.5, 0.0001)); #[test]
} pub fn test_lerp() {
let a = Vector2 { x: 5.0, y: 1.0 };
let b = Vector2 { x: 10.0, y: 2.0 };
let c = lerp(a, b, 0.5);
assert!(nearly_equal(c.x, 7.5, 0.0001));
assert!(nearly_equal(c.y, 1.5, 0.0001));
}
} }

File diff suppressed because it is too large Load diff

View file

@ -16,44 +16,44 @@ use crate::system::MouseEvent::MouseButtonUp;
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum WindowEvent { pub enum WindowEvent {
Shown, Shown,
Hidden, Hidden,
Exposed, Exposed,
Moved(i32, i32), Moved(i32, i32),
Resized(i32, i32), Resized(i32, i32),
SizeChanged(i32, i32), SizeChanged(i32, i32),
Minimized, Minimized,
Maximized, Maximized,
Restored, Restored,
Enter, Enter,
Leave, Leave,
FocusGained, FocusGained,
FocusLost, FocusLost,
Close, Close,
// for sdl2::event::WindowEvent enum values we haven't mapped / don't care about (yet?) // for sdl2::event::WindowEvent enum values we haven't mapped / don't care about (yet?)
Unimplemented, Unimplemented,
} }
impl From<sdl2::event::WindowEvent> for WindowEvent { impl From<sdl2::event::WindowEvent> for WindowEvent {
fn from(value: sdl2::event::WindowEvent) -> Self { fn from(value: sdl2::event::WindowEvent) -> Self {
match value { match value {
sdl2::event::WindowEvent::Shown => WindowEvent::Shown, sdl2::event::WindowEvent::Shown => WindowEvent::Shown,
sdl2::event::WindowEvent::Hidden => WindowEvent::Hidden, sdl2::event::WindowEvent::Hidden => WindowEvent::Hidden,
sdl2::event::WindowEvent::Exposed => WindowEvent::Exposed, sdl2::event::WindowEvent::Exposed => WindowEvent::Exposed,
sdl2::event::WindowEvent::Moved(x, y) => WindowEvent::Moved(x, y), sdl2::event::WindowEvent::Moved(x, y) => WindowEvent::Moved(x, y),
sdl2::event::WindowEvent::Resized(width, height) => WindowEvent::Resized(width, height), sdl2::event::WindowEvent::Resized(width, height) => WindowEvent::Resized(width, height),
sdl2::event::WindowEvent::SizeChanged(width, height) => WindowEvent::SizeChanged(width, height), sdl2::event::WindowEvent::SizeChanged(width, height) => WindowEvent::SizeChanged(width, height),
sdl2::event::WindowEvent::Minimized => WindowEvent::Minimized, sdl2::event::WindowEvent::Minimized => WindowEvent::Minimized,
sdl2::event::WindowEvent::Maximized => WindowEvent::Maximized, sdl2::event::WindowEvent::Maximized => WindowEvent::Maximized,
sdl2::event::WindowEvent::Restored => WindowEvent::Restored, sdl2::event::WindowEvent::Restored => WindowEvent::Restored,
sdl2::event::WindowEvent::Enter => WindowEvent::Enter, sdl2::event::WindowEvent::Enter => WindowEvent::Enter,
sdl2::event::WindowEvent::Leave => WindowEvent::Leave, sdl2::event::WindowEvent::Leave => WindowEvent::Leave,
sdl2::event::WindowEvent::FocusGained => WindowEvent::FocusGained, sdl2::event::WindowEvent::FocusGained => WindowEvent::FocusGained,
sdl2::event::WindowEvent::FocusLost => WindowEvent::FocusLost, sdl2::event::WindowEvent::FocusLost => WindowEvent::FocusLost,
sdl2::event::WindowEvent::Close => WindowEvent::Close, sdl2::event::WindowEvent::Close => WindowEvent::Close,
_ => WindowEvent::Unimplemented, _ => WindowEvent::Unimplemented,
} }
} }
} }
bitflags! { bitflags! {
@ -76,156 +76,156 @@ bitflags! {
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum KeyboardEvent { pub enum KeyboardEvent {
KeyUp { KeyUp {
keycode: Option<Keycode>, keycode: Option<Keycode>,
scancode: Option<Scancode>, scancode: Option<Scancode>,
keymod: KeyModifiers, keymod: KeyModifiers,
repeat: bool, repeat: bool,
}, },
KeyDown { KeyDown {
keycode: Option<Keycode>, keycode: Option<Keycode>,
scancode: Option<Scancode>, scancode: Option<Scancode>,
keymod: KeyModifiers, keymod: KeyModifiers,
repeat: bool, repeat: bool,
}, },
} }
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum MouseEvent { pub enum MouseEvent {
MouseMotion { MouseMotion {
x: i32, x: i32,
y: i32, y: i32,
x_delta: i32, x_delta: i32,
y_delta: i32, y_delta: i32,
buttons: MouseButtons, buttons: MouseButtons,
}, },
MouseButtonDown { MouseButtonDown {
x: i32, x: i32,
y: i32, y: i32,
button: MouseButton, button: MouseButton,
clicks: u8, clicks: u8,
}, },
MouseButtonUp { MouseButtonUp {
x: i32, x: i32,
y: i32, y: i32,
button: MouseButton, button: MouseButton,
clicks: u8, clicks: u8,
}, },
} }
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum SystemEvent { pub enum SystemEvent {
Quit, Quit,
AppTerminating, AppTerminating,
AppLowMemory, AppLowMemory,
AppWillEnterBackground, AppWillEnterBackground,
AppDidEnterBackground, AppDidEnterBackground,
AppWillEnterForeground, AppWillEnterForeground,
AppDidEnterForeground, AppDidEnterForeground,
Window(WindowEvent), Window(WindowEvent),
Keyboard(KeyboardEvent), Keyboard(KeyboardEvent),
Mouse(MouseEvent), Mouse(MouseEvent),
// for the many sdl2::event::Event enum values that we don't are about quite yet ... // for the many sdl2::event::Event enum values that we don't are about quite yet ...
Unimplemented, Unimplemented,
} }
impl From<sdl2::event::Event> for SystemEvent { impl From<sdl2::event::Event> for SystemEvent {
fn from(value: sdl2::event::Event) -> Self { fn from(value: sdl2::event::Event) -> Self {
match value { match value {
sdl2::event::Event::Quit { .. } => SystemEvent::Quit, sdl2::event::Event::Quit { .. } => SystemEvent::Quit,
sdl2::event::Event::AppTerminating { .. } => SystemEvent::AppTerminating, sdl2::event::Event::AppTerminating { .. } => SystemEvent::AppTerminating,
sdl2::event::Event::AppLowMemory { .. } => SystemEvent::AppLowMemory, sdl2::event::Event::AppLowMemory { .. } => SystemEvent::AppLowMemory,
sdl2::event::Event::AppWillEnterBackground { .. } => SystemEvent::AppWillEnterBackground, sdl2::event::Event::AppWillEnterBackground { .. } => SystemEvent::AppWillEnterBackground,
sdl2::event::Event::AppDidEnterBackground { .. } => SystemEvent::AppDidEnterBackground, sdl2::event::Event::AppDidEnterBackground { .. } => SystemEvent::AppDidEnterBackground,
sdl2::event::Event::AppWillEnterForeground { .. } => SystemEvent::AppWillEnterForeground, sdl2::event::Event::AppWillEnterForeground { .. } => SystemEvent::AppWillEnterForeground,
sdl2::event::Event::AppDidEnterForeground { .. } => SystemEvent::AppDidEnterForeground, sdl2::event::Event::AppDidEnterForeground { .. } => SystemEvent::AppDidEnterForeground,
sdl2::event::Event::Window { win_event, .. } => SystemEvent::Window(win_event.into()), sdl2::event::Event::Window { win_event, .. } => SystemEvent::Window(win_event.into()),
sdl2::event::Event::KeyDown { keycode, scancode, keymod, repeat, .. } => { sdl2::event::Event::KeyDown { keycode, scancode, keymod, repeat, .. } => {
SystemEvent::Keyboard(KeyboardEvent::KeyDown { SystemEvent::Keyboard(KeyboardEvent::KeyDown {
keycode: keycode.map(|keycode| keycode.into()), keycode: keycode.map(|keycode| keycode.into()),
scancode: scancode.map(|scancode| scancode.into()), scancode: scancode.map(|scancode| scancode.into()),
keymod: KeyModifiers::from_bits_truncate(keymod.bits()), keymod: KeyModifiers::from_bits_truncate(keymod.bits()),
repeat repeat,
}) })
}, }
sdl2::event::Event::KeyUp { keycode, scancode, keymod, repeat, .. } => { sdl2::event::Event::KeyUp { keycode, scancode, keymod, repeat, .. } => {
SystemEvent::Keyboard(KeyboardEvent::KeyUp { SystemEvent::Keyboard(KeyboardEvent::KeyUp {
keycode: keycode.map(|keycode| keycode.into()), keycode: keycode.map(|keycode| keycode.into()),
scancode: scancode.map(|scancode| scancode.into()), scancode: scancode.map(|scancode| scancode.into()),
keymod: KeyModifiers::from_bits_truncate(keymod.bits()), keymod: KeyModifiers::from_bits_truncate(keymod.bits()),
repeat repeat,
}) })
} }
sdl2::event::Event::MouseMotion { mousestate, x, y, xrel, yrel, .. } => { sdl2::event::Event::MouseMotion { mousestate, x, y, xrel, yrel, .. } => {
SystemEvent::Mouse(MouseEvent::MouseMotion { SystemEvent::Mouse(MouseEvent::MouseMotion {
x, x,
y, y,
x_delta: xrel, x_delta: xrel,
y_delta: yrel, y_delta: yrel,
buttons: MouseButtons::from_bits_truncate(mousestate.to_sdl_state()), buttons: MouseButtons::from_bits_truncate(mousestate.to_sdl_state()),
}) })
}, }
sdl2::event::Event::MouseButtonDown { mouse_btn, clicks, x, y, .. } => { sdl2::event::Event::MouseButtonDown { mouse_btn, clicks, x, y, .. } => {
SystemEvent::Mouse(MouseEvent::MouseButtonDown { SystemEvent::Mouse(MouseEvent::MouseButtonDown {
x, x,
y, y,
clicks, clicks,
button: mouse_btn.into(), button: mouse_btn.into(),
}) })
}, }
sdl2::event::Event::MouseButtonUp { mouse_btn, clicks, x, y, .. } => { sdl2::event::Event::MouseButtonUp { mouse_btn, clicks, x, y, .. } => {
SystemEvent::Mouse(MouseButtonUp { SystemEvent::Mouse(MouseButtonUp {
x, x,
y, y,
clicks, clicks,
button: mouse_btn.into(), button: mouse_btn.into(),
}) })
}, }
_ => SystemEvent::Unimplemented, _ => SystemEvent::Unimplemented,
} }
} }
} }
/// Common trait for implementing a handler of [`SystemEvent`]s that are polled during the /// Common trait for implementing a handler of [`SystemEvent`]s that are polled during the
/// application's main loop. /// application's main loop.
pub trait SystemEventHandler { pub trait SystemEventHandler {
/// Processes the data from the given [`SystemEvent`]. Returns true if the processing actually /// Processes the data from the given [`SystemEvent`]. Returns true if the processing actually
/// recognized the passed event and handled it, or false if the event was ignored. /// recognized the passed event and handled it, or false if the event was ignored.
fn handle_event(&mut self, event: &SystemEvent) -> bool; fn handle_event(&mut self, event: &SystemEvent) -> bool;
} }
/// An interator for SDL2 system events, polled via [`SystemEventPump`]. /// An interator for SDL2 system events, polled via [`SystemEventPump`].
pub struct SystemEventIterator<'a> { pub struct SystemEventIterator<'a> {
iter: sdl2::event::EventPollIterator<'a>, iter: sdl2::event::EventPollIterator<'a>,
} }
impl Iterator for SystemEventIterator<'_> { impl Iterator for SystemEventIterator<'_> {
type Item = SystemEvent; type Item = SystemEvent;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
self.iter.next().map(|e| e.into()) self.iter.next().map(|e| e.into())
} }
} }
/// Provides an event pump iterator that wraps over SDL2 events, allowing applications to respond /// Provides an event pump iterator that wraps over SDL2 events, allowing applications to respond
/// to all events each frame as [`SystemEvent`] instances. /// to all events each frame as [`SystemEvent`] instances.
pub struct SystemEventPump { pub struct SystemEventPump {
sdl_event_pump: sdl2::EventPump, sdl_event_pump: sdl2::EventPump,
} }
impl SystemEventPump { impl SystemEventPump {
pub fn from(pump: sdl2::EventPump) -> Self { pub fn from(pump: sdl2::EventPump) -> Self {
SystemEventPump { SystemEventPump {
sdl_event_pump: pump, sdl_event_pump: pump,
} }
} }
/// Returns an iterator over [`SystemEvent`]s that have been generated since the last time /// Returns an iterator over [`SystemEvent`]s that have been generated since the last time
/// events were polled (usually, in the previous frame). /// events were polled (usually, in the previous frame).
pub fn poll_iter(&mut self) -> SystemEventIterator { pub fn poll_iter(&mut self) -> SystemEventIterator {
self.sdl_event_pump.pump_events(); self.sdl_event_pump.pump_events();
SystemEventIterator { iter: self.sdl_event_pump.poll_iter() } SystemEventIterator { iter: self.sdl_event_pump.poll_iter() }
} }
} }

View file

@ -3,481 +3,481 @@
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
#[repr(i32)] #[repr(i32)]
pub enum Keycode { pub enum Keycode {
Backspace = sdl2::keyboard::Keycode::Backspace as i32, Backspace = sdl2::keyboard::Keycode::Backspace as i32,
Tab = sdl2::keyboard::Keycode::Tab as i32, Tab = sdl2::keyboard::Keycode::Tab as i32,
Return = sdl2::keyboard::Keycode::Return as i32, Return = sdl2::keyboard::Keycode::Return as i32,
Escape = sdl2::keyboard::Keycode::Escape as i32, Escape = sdl2::keyboard::Keycode::Escape as i32,
Space = sdl2::keyboard::Keycode::Space as i32, Space = sdl2::keyboard::Keycode::Space as i32,
Exclaim = sdl2::keyboard::Keycode::Exclaim as i32, Exclaim = sdl2::keyboard::Keycode::Exclaim as i32,
Quotedbl = sdl2::keyboard::Keycode::Quotedbl as i32, Quotedbl = sdl2::keyboard::Keycode::Quotedbl as i32,
Hash = sdl2::keyboard::Keycode::Hash as i32, Hash = sdl2::keyboard::Keycode::Hash as i32,
Dollar = sdl2::keyboard::Keycode::Dollar as i32, Dollar = sdl2::keyboard::Keycode::Dollar as i32,
Percent = sdl2::keyboard::Keycode::Percent as i32, Percent = sdl2::keyboard::Keycode::Percent as i32,
Ampersand = sdl2::keyboard::Keycode::Ampersand as i32, Ampersand = sdl2::keyboard::Keycode::Ampersand as i32,
Quote = sdl2::keyboard::Keycode::Quote as i32, Quote = sdl2::keyboard::Keycode::Quote as i32,
LeftParen = sdl2::keyboard::Keycode::LeftParen as i32, LeftParen = sdl2::keyboard::Keycode::LeftParen as i32,
RightParen = sdl2::keyboard::Keycode::RightParen as i32, RightParen = sdl2::keyboard::Keycode::RightParen as i32,
Asterisk = sdl2::keyboard::Keycode::Asterisk as i32, Asterisk = sdl2::keyboard::Keycode::Asterisk as i32,
Plus = sdl2::keyboard::Keycode::Plus as i32, Plus = sdl2::keyboard::Keycode::Plus as i32,
Comma = sdl2::keyboard::Keycode::Comma as i32, Comma = sdl2::keyboard::Keycode::Comma as i32,
Minus = sdl2::keyboard::Keycode::Minus as i32, Minus = sdl2::keyboard::Keycode::Minus as i32,
Period = sdl2::keyboard::Keycode::Period as i32, Period = sdl2::keyboard::Keycode::Period as i32,
Slash = sdl2::keyboard::Keycode::Slash as i32, Slash = sdl2::keyboard::Keycode::Slash as i32,
Num0 = sdl2::keyboard::Keycode::Num0 as i32, Num0 = sdl2::keyboard::Keycode::Num0 as i32,
Num1 = sdl2::keyboard::Keycode::Num1 as i32, Num1 = sdl2::keyboard::Keycode::Num1 as i32,
Num2 = sdl2::keyboard::Keycode::Num2 as i32, Num2 = sdl2::keyboard::Keycode::Num2 as i32,
Num3 = sdl2::keyboard::Keycode::Num3 as i32, Num3 = sdl2::keyboard::Keycode::Num3 as i32,
Num4 = sdl2::keyboard::Keycode::Num4 as i32, Num4 = sdl2::keyboard::Keycode::Num4 as i32,
Num5 = sdl2::keyboard::Keycode::Num5 as i32, Num5 = sdl2::keyboard::Keycode::Num5 as i32,
Num6 = sdl2::keyboard::Keycode::Num6 as i32, Num6 = sdl2::keyboard::Keycode::Num6 as i32,
Num7 = sdl2::keyboard::Keycode::Num7 as i32, Num7 = sdl2::keyboard::Keycode::Num7 as i32,
Num8 = sdl2::keyboard::Keycode::Num8 as i32, Num8 = sdl2::keyboard::Keycode::Num8 as i32,
Num9 = sdl2::keyboard::Keycode::Num9 as i32, Num9 = sdl2::keyboard::Keycode::Num9 as i32,
Colon = sdl2::keyboard::Keycode::Colon as i32, Colon = sdl2::keyboard::Keycode::Colon as i32,
Semicolon = sdl2::keyboard::Keycode::Semicolon as i32, Semicolon = sdl2::keyboard::Keycode::Semicolon as i32,
Less = sdl2::keyboard::Keycode::Less as i32, Less = sdl2::keyboard::Keycode::Less as i32,
Equals = sdl2::keyboard::Keycode::Equals as i32, Equals = sdl2::keyboard::Keycode::Equals as i32,
Greater = sdl2::keyboard::Keycode::Greater as i32, Greater = sdl2::keyboard::Keycode::Greater as i32,
Question = sdl2::keyboard::Keycode::Question as i32, Question = sdl2::keyboard::Keycode::Question as i32,
At = sdl2::keyboard::Keycode::At as i32, At = sdl2::keyboard::Keycode::At as i32,
LeftBracket = sdl2::keyboard::Keycode::LeftBracket as i32, LeftBracket = sdl2::keyboard::Keycode::LeftBracket as i32,
Backslash = sdl2::keyboard::Keycode::Backslash as i32, Backslash = sdl2::keyboard::Keycode::Backslash as i32,
RightBracket = sdl2::keyboard::Keycode::RightBracket as i32, RightBracket = sdl2::keyboard::Keycode::RightBracket as i32,
Caret = sdl2::keyboard::Keycode::Caret as i32, Caret = sdl2::keyboard::Keycode::Caret as i32,
Underscore = sdl2::keyboard::Keycode::Underscore as i32, Underscore = sdl2::keyboard::Keycode::Underscore as i32,
Backquote = sdl2::keyboard::Keycode::Backquote as i32, Backquote = sdl2::keyboard::Keycode::Backquote as i32,
A = sdl2::keyboard::Keycode::A as i32, A = sdl2::keyboard::Keycode::A as i32,
B = sdl2::keyboard::Keycode::B as i32, B = sdl2::keyboard::Keycode::B as i32,
C = sdl2::keyboard::Keycode::C as i32, C = sdl2::keyboard::Keycode::C as i32,
D = sdl2::keyboard::Keycode::D as i32, D = sdl2::keyboard::Keycode::D as i32,
E = sdl2::keyboard::Keycode::E as i32, E = sdl2::keyboard::Keycode::E as i32,
F = sdl2::keyboard::Keycode::F as i32, F = sdl2::keyboard::Keycode::F as i32,
G = sdl2::keyboard::Keycode::G as i32, G = sdl2::keyboard::Keycode::G as i32,
H = sdl2::keyboard::Keycode::H as i32, H = sdl2::keyboard::Keycode::H as i32,
I = sdl2::keyboard::Keycode::I as i32, I = sdl2::keyboard::Keycode::I as i32,
J = sdl2::keyboard::Keycode::J as i32, J = sdl2::keyboard::Keycode::J as i32,
K = sdl2::keyboard::Keycode::K as i32, K = sdl2::keyboard::Keycode::K as i32,
L = sdl2::keyboard::Keycode::L as i32, L = sdl2::keyboard::Keycode::L as i32,
M = sdl2::keyboard::Keycode::M as i32, M = sdl2::keyboard::Keycode::M as i32,
N = sdl2::keyboard::Keycode::N as i32, N = sdl2::keyboard::Keycode::N as i32,
O = sdl2::keyboard::Keycode::O as i32, O = sdl2::keyboard::Keycode::O as i32,
P = sdl2::keyboard::Keycode::P as i32, P = sdl2::keyboard::Keycode::P as i32,
Q = sdl2::keyboard::Keycode::Q as i32, Q = sdl2::keyboard::Keycode::Q as i32,
R = sdl2::keyboard::Keycode::R as i32, R = sdl2::keyboard::Keycode::R as i32,
S = sdl2::keyboard::Keycode::S as i32, S = sdl2::keyboard::Keycode::S as i32,
T = sdl2::keyboard::Keycode::T as i32, T = sdl2::keyboard::Keycode::T as i32,
U = sdl2::keyboard::Keycode::U as i32, U = sdl2::keyboard::Keycode::U as i32,
V = sdl2::keyboard::Keycode::V as i32, V = sdl2::keyboard::Keycode::V as i32,
W = sdl2::keyboard::Keycode::W as i32, W = sdl2::keyboard::Keycode::W as i32,
X = sdl2::keyboard::Keycode::X as i32, X = sdl2::keyboard::Keycode::X as i32,
Y = sdl2::keyboard::Keycode::Y as i32, Y = sdl2::keyboard::Keycode::Y as i32,
Z = sdl2::keyboard::Keycode::Z as i32, Z = sdl2::keyboard::Keycode::Z as i32,
Delete = sdl2::keyboard::Keycode::Delete as i32, Delete = sdl2::keyboard::Keycode::Delete as i32,
CapsLock = sdl2::keyboard::Keycode::CapsLock as i32, CapsLock = sdl2::keyboard::Keycode::CapsLock as i32,
F1 = sdl2::keyboard::Keycode::F1 as i32, F1 = sdl2::keyboard::Keycode::F1 as i32,
F2 = sdl2::keyboard::Keycode::F2 as i32, F2 = sdl2::keyboard::Keycode::F2 as i32,
F3 = sdl2::keyboard::Keycode::F3 as i32, F3 = sdl2::keyboard::Keycode::F3 as i32,
F4 = sdl2::keyboard::Keycode::F4 as i32, F4 = sdl2::keyboard::Keycode::F4 as i32,
F5 = sdl2::keyboard::Keycode::F5 as i32, F5 = sdl2::keyboard::Keycode::F5 as i32,
F6 = sdl2::keyboard::Keycode::F6 as i32, F6 = sdl2::keyboard::Keycode::F6 as i32,
F7 = sdl2::keyboard::Keycode::F7 as i32, F7 = sdl2::keyboard::Keycode::F7 as i32,
F8 = sdl2::keyboard::Keycode::F8 as i32, F8 = sdl2::keyboard::Keycode::F8 as i32,
F9 = sdl2::keyboard::Keycode::F9 as i32, F9 = sdl2::keyboard::Keycode::F9 as i32,
F10 = sdl2::keyboard::Keycode::F10 as i32, F10 = sdl2::keyboard::Keycode::F10 as i32,
F11 = sdl2::keyboard::Keycode::F11 as i32, F11 = sdl2::keyboard::Keycode::F11 as i32,
F12 = sdl2::keyboard::Keycode::F12 as i32, F12 = sdl2::keyboard::Keycode::F12 as i32,
PrintScreen = sdl2::keyboard::Keycode::PrintScreen as i32, PrintScreen = sdl2::keyboard::Keycode::PrintScreen as i32,
ScrollLock = sdl2::keyboard::Keycode::ScrollLock as i32, ScrollLock = sdl2::keyboard::Keycode::ScrollLock as i32,
Pause = sdl2::keyboard::Keycode::Pause as i32, Pause = sdl2::keyboard::Keycode::Pause as i32,
Insert = sdl2::keyboard::Keycode::Insert as i32, Insert = sdl2::keyboard::Keycode::Insert as i32,
Home = sdl2::keyboard::Keycode::Home as i32, Home = sdl2::keyboard::Keycode::Home as i32,
PageUp = sdl2::keyboard::Keycode::PageUp as i32, PageUp = sdl2::keyboard::Keycode::PageUp as i32,
End = sdl2::keyboard::Keycode::End as i32, End = sdl2::keyboard::Keycode::End as i32,
PageDown = sdl2::keyboard::Keycode::PageDown as i32, PageDown = sdl2::keyboard::Keycode::PageDown as i32,
Right = sdl2::keyboard::Keycode::Right as i32, Right = sdl2::keyboard::Keycode::Right as i32,
Left = sdl2::keyboard::Keycode::Left as i32, Left = sdl2::keyboard::Keycode::Left as i32,
Down = sdl2::keyboard::Keycode::Down as i32, Down = sdl2::keyboard::Keycode::Down as i32,
Up = sdl2::keyboard::Keycode::Up as i32, Up = sdl2::keyboard::Keycode::Up as i32,
NumLockClear = sdl2::keyboard::Keycode::NumLockClear as i32, NumLockClear = sdl2::keyboard::Keycode::NumLockClear as i32,
KpDivide = sdl2::keyboard::Keycode::KpDivide as i32, KpDivide = sdl2::keyboard::Keycode::KpDivide as i32,
KpMultiply = sdl2::keyboard::Keycode::KpMultiply as i32, KpMultiply = sdl2::keyboard::Keycode::KpMultiply as i32,
KpMinus = sdl2::keyboard::Keycode::KpMinus as i32, KpMinus = sdl2::keyboard::Keycode::KpMinus as i32,
KpPlus = sdl2::keyboard::Keycode::KpPlus as i32, KpPlus = sdl2::keyboard::Keycode::KpPlus as i32,
KpEnter = sdl2::keyboard::Keycode::KpEnter as i32, KpEnter = sdl2::keyboard::Keycode::KpEnter as i32,
Kp1 = sdl2::keyboard::Keycode::Kp1 as i32, Kp1 = sdl2::keyboard::Keycode::Kp1 as i32,
Kp2 = sdl2::keyboard::Keycode::Kp2 as i32, Kp2 = sdl2::keyboard::Keycode::Kp2 as i32,
Kp3 = sdl2::keyboard::Keycode::Kp3 as i32, Kp3 = sdl2::keyboard::Keycode::Kp3 as i32,
Kp4 = sdl2::keyboard::Keycode::Kp4 as i32, Kp4 = sdl2::keyboard::Keycode::Kp4 as i32,
Kp5 = sdl2::keyboard::Keycode::Kp5 as i32, Kp5 = sdl2::keyboard::Keycode::Kp5 as i32,
Kp6 = sdl2::keyboard::Keycode::Kp6 as i32, Kp6 = sdl2::keyboard::Keycode::Kp6 as i32,
Kp7 = sdl2::keyboard::Keycode::Kp7 as i32, Kp7 = sdl2::keyboard::Keycode::Kp7 as i32,
Kp8 = sdl2::keyboard::Keycode::Kp8 as i32, Kp8 = sdl2::keyboard::Keycode::Kp8 as i32,
Kp9 = sdl2::keyboard::Keycode::Kp9 as i32, Kp9 = sdl2::keyboard::Keycode::Kp9 as i32,
Kp0 = sdl2::keyboard::Keycode::Kp0 as i32, Kp0 = sdl2::keyboard::Keycode::Kp0 as i32,
KpPeriod = sdl2::keyboard::Keycode::KpPeriod as i32, KpPeriod = sdl2::keyboard::Keycode::KpPeriod as i32,
Application = sdl2::keyboard::Keycode::Application as i32, Application = sdl2::keyboard::Keycode::Application as i32,
Power = sdl2::keyboard::Keycode::Power as i32, Power = sdl2::keyboard::Keycode::Power as i32,
KpEquals = sdl2::keyboard::Keycode::KpEquals as i32, KpEquals = sdl2::keyboard::Keycode::KpEquals as i32,
F13 = sdl2::keyboard::Keycode::F13 as i32, F13 = sdl2::keyboard::Keycode::F13 as i32,
F14 = sdl2::keyboard::Keycode::F14 as i32, F14 = sdl2::keyboard::Keycode::F14 as i32,
F15 = sdl2::keyboard::Keycode::F15 as i32, F15 = sdl2::keyboard::Keycode::F15 as i32,
F16 = sdl2::keyboard::Keycode::F16 as i32, F16 = sdl2::keyboard::Keycode::F16 as i32,
F17 = sdl2::keyboard::Keycode::F17 as i32, F17 = sdl2::keyboard::Keycode::F17 as i32,
F18 = sdl2::keyboard::Keycode::F18 as i32, F18 = sdl2::keyboard::Keycode::F18 as i32,
F19 = sdl2::keyboard::Keycode::F19 as i32, F19 = sdl2::keyboard::Keycode::F19 as i32,
F20 = sdl2::keyboard::Keycode::F20 as i32, F20 = sdl2::keyboard::Keycode::F20 as i32,
F21 = sdl2::keyboard::Keycode::F21 as i32, F21 = sdl2::keyboard::Keycode::F21 as i32,
F22 = sdl2::keyboard::Keycode::F22 as i32, F22 = sdl2::keyboard::Keycode::F22 as i32,
F23 = sdl2::keyboard::Keycode::F23 as i32, F23 = sdl2::keyboard::Keycode::F23 as i32,
F24 = sdl2::keyboard::Keycode::F24 as i32, F24 = sdl2::keyboard::Keycode::F24 as i32,
Execute = sdl2::keyboard::Keycode::Execute as i32, Execute = sdl2::keyboard::Keycode::Execute as i32,
Help = sdl2::keyboard::Keycode::Help as i32, Help = sdl2::keyboard::Keycode::Help as i32,
Menu = sdl2::keyboard::Keycode::Menu as i32, Menu = sdl2::keyboard::Keycode::Menu as i32,
Select = sdl2::keyboard::Keycode::Select as i32, Select = sdl2::keyboard::Keycode::Select as i32,
Stop = sdl2::keyboard::Keycode::Stop as i32, Stop = sdl2::keyboard::Keycode::Stop as i32,
Again = sdl2::keyboard::Keycode::Again as i32, Again = sdl2::keyboard::Keycode::Again as i32,
Undo = sdl2::keyboard::Keycode::Undo as i32, Undo = sdl2::keyboard::Keycode::Undo as i32,
Cut = sdl2::keyboard::Keycode::Cut as i32, Cut = sdl2::keyboard::Keycode::Cut as i32,
Copy = sdl2::keyboard::Keycode::Copy as i32, Copy = sdl2::keyboard::Keycode::Copy as i32,
Paste = sdl2::keyboard::Keycode::Paste as i32, Paste = sdl2::keyboard::Keycode::Paste as i32,
Find = sdl2::keyboard::Keycode::Find as i32, Find = sdl2::keyboard::Keycode::Find as i32,
Mute = sdl2::keyboard::Keycode::Mute as i32, Mute = sdl2::keyboard::Keycode::Mute as i32,
VolumeUp = sdl2::keyboard::Keycode::VolumeUp as i32, VolumeUp = sdl2::keyboard::Keycode::VolumeUp as i32,
VolumeDown = sdl2::keyboard::Keycode::VolumeDown as i32, VolumeDown = sdl2::keyboard::Keycode::VolumeDown as i32,
KpComma = sdl2::keyboard::Keycode::KpComma as i32, KpComma = sdl2::keyboard::Keycode::KpComma as i32,
KpEqualsAS400 = sdl2::keyboard::Keycode::KpEqualsAS400 as i32, KpEqualsAS400 = sdl2::keyboard::Keycode::KpEqualsAS400 as i32,
AltErase = sdl2::keyboard::Keycode::AltErase as i32, AltErase = sdl2::keyboard::Keycode::AltErase as i32,
Sysreq = sdl2::keyboard::Keycode::Sysreq as i32, Sysreq = sdl2::keyboard::Keycode::Sysreq as i32,
Cancel = sdl2::keyboard::Keycode::Cancel as i32, Cancel = sdl2::keyboard::Keycode::Cancel as i32,
Clear = sdl2::keyboard::Keycode::Clear as i32, Clear = sdl2::keyboard::Keycode::Clear as i32,
Prior = sdl2::keyboard::Keycode::Prior as i32, Prior = sdl2::keyboard::Keycode::Prior as i32,
Return2 = sdl2::keyboard::Keycode::Return2 as i32, Return2 = sdl2::keyboard::Keycode::Return2 as i32,
Separator = sdl2::keyboard::Keycode::Separator as i32, Separator = sdl2::keyboard::Keycode::Separator as i32,
Out = sdl2::keyboard::Keycode::Out as i32, Out = sdl2::keyboard::Keycode::Out as i32,
Oper = sdl2::keyboard::Keycode::Oper as i32, Oper = sdl2::keyboard::Keycode::Oper as i32,
ClearAgain = sdl2::keyboard::Keycode::ClearAgain as i32, ClearAgain = sdl2::keyboard::Keycode::ClearAgain as i32,
CrSel = sdl2::keyboard::Keycode::CrSel as i32, CrSel = sdl2::keyboard::Keycode::CrSel as i32,
ExSel = sdl2::keyboard::Keycode::ExSel as i32, ExSel = sdl2::keyboard::Keycode::ExSel as i32,
Kp00 = sdl2::keyboard::Keycode::Kp00 as i32, Kp00 = sdl2::keyboard::Keycode::Kp00 as i32,
Kp000 = sdl2::keyboard::Keycode::Kp000 as i32, Kp000 = sdl2::keyboard::Keycode::Kp000 as i32,
ThousandsSeparator = sdl2::keyboard::Keycode::ThousandsSeparator as i32, ThousandsSeparator = sdl2::keyboard::Keycode::ThousandsSeparator as i32,
DecimalSeparator = sdl2::keyboard::Keycode::DecimalSeparator as i32, DecimalSeparator = sdl2::keyboard::Keycode::DecimalSeparator as i32,
CurrencyUnit = sdl2::keyboard::Keycode::CurrencyUnit as i32, CurrencyUnit = sdl2::keyboard::Keycode::CurrencyUnit as i32,
CurrencySubUnit = sdl2::keyboard::Keycode::CurrencySubUnit as i32, CurrencySubUnit = sdl2::keyboard::Keycode::CurrencySubUnit as i32,
KpLeftParen = sdl2::keyboard::Keycode::KpLeftParen as i32, KpLeftParen = sdl2::keyboard::Keycode::KpLeftParen as i32,
KpRightParen = sdl2::keyboard::Keycode::KpRightParen as i32, KpRightParen = sdl2::keyboard::Keycode::KpRightParen as i32,
KpLeftBrace = sdl2::keyboard::Keycode::KpLeftBrace as i32, KpLeftBrace = sdl2::keyboard::Keycode::KpLeftBrace as i32,
KpRightBrace = sdl2::keyboard::Keycode::KpRightBrace as i32, KpRightBrace = sdl2::keyboard::Keycode::KpRightBrace as i32,
KpTab = sdl2::keyboard::Keycode::KpTab as i32, KpTab = sdl2::keyboard::Keycode::KpTab as i32,
KpBackspace = sdl2::keyboard::Keycode::KpBackspace as i32, KpBackspace = sdl2::keyboard::Keycode::KpBackspace as i32,
KpA = sdl2::keyboard::Keycode::KpA as i32, KpA = sdl2::keyboard::Keycode::KpA as i32,
KpB = sdl2::keyboard::Keycode::KpB as i32, KpB = sdl2::keyboard::Keycode::KpB as i32,
KpC = sdl2::keyboard::Keycode::KpC as i32, KpC = sdl2::keyboard::Keycode::KpC as i32,
KpD = sdl2::keyboard::Keycode::KpD as i32, KpD = sdl2::keyboard::Keycode::KpD as i32,
KpE = sdl2::keyboard::Keycode::KpE as i32, KpE = sdl2::keyboard::Keycode::KpE as i32,
KpF = sdl2::keyboard::Keycode::KpF as i32, KpF = sdl2::keyboard::Keycode::KpF as i32,
KpXor = sdl2::keyboard::Keycode::KpXor as i32, KpXor = sdl2::keyboard::Keycode::KpXor as i32,
KpPower = sdl2::keyboard::Keycode::KpPower as i32, KpPower = sdl2::keyboard::Keycode::KpPower as i32,
KpPercent = sdl2::keyboard::Keycode::KpPercent as i32, KpPercent = sdl2::keyboard::Keycode::KpPercent as i32,
KpLess = sdl2::keyboard::Keycode::KpLess as i32, KpLess = sdl2::keyboard::Keycode::KpLess as i32,
KpGreater = sdl2::keyboard::Keycode::KpGreater as i32, KpGreater = sdl2::keyboard::Keycode::KpGreater as i32,
KpAmpersand = sdl2::keyboard::Keycode::KpAmpersand as i32, KpAmpersand = sdl2::keyboard::Keycode::KpAmpersand as i32,
KpDblAmpersand = sdl2::keyboard::Keycode::KpDblAmpersand as i32, KpDblAmpersand = sdl2::keyboard::Keycode::KpDblAmpersand as i32,
KpVerticalBar = sdl2::keyboard::Keycode::KpVerticalBar as i32, KpVerticalBar = sdl2::keyboard::Keycode::KpVerticalBar as i32,
KpDblVerticalBar = sdl2::keyboard::Keycode::KpDblVerticalBar as i32, KpDblVerticalBar = sdl2::keyboard::Keycode::KpDblVerticalBar as i32,
KpColon = sdl2::keyboard::Keycode::KpColon as i32, KpColon = sdl2::keyboard::Keycode::KpColon as i32,
KpHash = sdl2::keyboard::Keycode::KpHash as i32, KpHash = sdl2::keyboard::Keycode::KpHash as i32,
KpSpace = sdl2::keyboard::Keycode::KpSpace as i32, KpSpace = sdl2::keyboard::Keycode::KpSpace as i32,
KpAt = sdl2::keyboard::Keycode::KpAt as i32, KpAt = sdl2::keyboard::Keycode::KpAt as i32,
KpExclam = sdl2::keyboard::Keycode::KpExclam as i32, KpExclam = sdl2::keyboard::Keycode::KpExclam as i32,
KpMemStore = sdl2::keyboard::Keycode::KpMemStore as i32, KpMemStore = sdl2::keyboard::Keycode::KpMemStore as i32,
KpMemRecall = sdl2::keyboard::Keycode::KpMemRecall as i32, KpMemRecall = sdl2::keyboard::Keycode::KpMemRecall as i32,
KpMemClear = sdl2::keyboard::Keycode::KpMemClear as i32, KpMemClear = sdl2::keyboard::Keycode::KpMemClear as i32,
KpMemAdd = sdl2::keyboard::Keycode::KpMemAdd as i32, KpMemAdd = sdl2::keyboard::Keycode::KpMemAdd as i32,
KpMemSubtract = sdl2::keyboard::Keycode::KpMemSubtract as i32, KpMemSubtract = sdl2::keyboard::Keycode::KpMemSubtract as i32,
KpMemMultiply = sdl2::keyboard::Keycode::KpMemMultiply as i32, KpMemMultiply = sdl2::keyboard::Keycode::KpMemMultiply as i32,
KpMemDivide = sdl2::keyboard::Keycode::KpMemDivide as i32, KpMemDivide = sdl2::keyboard::Keycode::KpMemDivide as i32,
KpPlusMinus = sdl2::keyboard::Keycode::KpPlusMinus as i32, KpPlusMinus = sdl2::keyboard::Keycode::KpPlusMinus as i32,
KpClear = sdl2::keyboard::Keycode::KpClear as i32, KpClear = sdl2::keyboard::Keycode::KpClear as i32,
KpClearEntry = sdl2::keyboard::Keycode::KpClearEntry as i32, KpClearEntry = sdl2::keyboard::Keycode::KpClearEntry as i32,
KpBinary = sdl2::keyboard::Keycode::KpBinary as i32, KpBinary = sdl2::keyboard::Keycode::KpBinary as i32,
KpOctal = sdl2::keyboard::Keycode::KpOctal as i32, KpOctal = sdl2::keyboard::Keycode::KpOctal as i32,
KpDecimal = sdl2::keyboard::Keycode::KpDecimal as i32, KpDecimal = sdl2::keyboard::Keycode::KpDecimal as i32,
KpHexadecimal = sdl2::keyboard::Keycode::KpHexadecimal as i32, KpHexadecimal = sdl2::keyboard::Keycode::KpHexadecimal as i32,
LCtrl = sdl2::keyboard::Keycode::LCtrl as i32, LCtrl = sdl2::keyboard::Keycode::LCtrl as i32,
LShift = sdl2::keyboard::Keycode::LShift as i32, LShift = sdl2::keyboard::Keycode::LShift as i32,
LAlt = sdl2::keyboard::Keycode::LAlt as i32, LAlt = sdl2::keyboard::Keycode::LAlt as i32,
LGui = sdl2::keyboard::Keycode::LGui as i32, LGui = sdl2::keyboard::Keycode::LGui as i32,
RCtrl = sdl2::keyboard::Keycode::RCtrl as i32, RCtrl = sdl2::keyboard::Keycode::RCtrl as i32,
RShift = sdl2::keyboard::Keycode::RShift as i32, RShift = sdl2::keyboard::Keycode::RShift as i32,
RAlt = sdl2::keyboard::Keycode::RAlt as i32, RAlt = sdl2::keyboard::Keycode::RAlt as i32,
RGui = sdl2::keyboard::Keycode::RGui as i32, RGui = sdl2::keyboard::Keycode::RGui as i32,
Mode = sdl2::keyboard::Keycode::Mode as i32, Mode = sdl2::keyboard::Keycode::Mode as i32,
AudioNext = sdl2::keyboard::Keycode::AudioNext as i32, AudioNext = sdl2::keyboard::Keycode::AudioNext as i32,
AudioPrev = sdl2::keyboard::Keycode::AudioPrev as i32, AudioPrev = sdl2::keyboard::Keycode::AudioPrev as i32,
AudioStop = sdl2::keyboard::Keycode::AudioStop as i32, AudioStop = sdl2::keyboard::Keycode::AudioStop as i32,
AudioPlay = sdl2::keyboard::Keycode::AudioPlay as i32, AudioPlay = sdl2::keyboard::Keycode::AudioPlay as i32,
AudioMute = sdl2::keyboard::Keycode::AudioMute as i32, AudioMute = sdl2::keyboard::Keycode::AudioMute as i32,
MediaSelect = sdl2::keyboard::Keycode::MediaSelect as i32, MediaSelect = sdl2::keyboard::Keycode::MediaSelect as i32,
Www = sdl2::keyboard::Keycode::Www as i32, Www = sdl2::keyboard::Keycode::Www as i32,
Mail = sdl2::keyboard::Keycode::Mail as i32, Mail = sdl2::keyboard::Keycode::Mail as i32,
Calculator = sdl2::keyboard::Keycode::Calculator as i32, Calculator = sdl2::keyboard::Keycode::Calculator as i32,
Computer = sdl2::keyboard::Keycode::Computer as i32, Computer = sdl2::keyboard::Keycode::Computer as i32,
AcSearch = sdl2::keyboard::Keycode::AcSearch as i32, AcSearch = sdl2::keyboard::Keycode::AcSearch as i32,
AcHome = sdl2::keyboard::Keycode::AcHome as i32, AcHome = sdl2::keyboard::Keycode::AcHome as i32,
AcBack = sdl2::keyboard::Keycode::AcBack as i32, AcBack = sdl2::keyboard::Keycode::AcBack as i32,
AcForward = sdl2::keyboard::Keycode::AcForward as i32, AcForward = sdl2::keyboard::Keycode::AcForward as i32,
AcStop = sdl2::keyboard::Keycode::AcStop as i32, AcStop = sdl2::keyboard::Keycode::AcStop as i32,
AcRefresh = sdl2::keyboard::Keycode::AcRefresh as i32, AcRefresh = sdl2::keyboard::Keycode::AcRefresh as i32,
AcBookmarks = sdl2::keyboard::Keycode::AcBookmarks as i32, AcBookmarks = sdl2::keyboard::Keycode::AcBookmarks as i32,
BrightnessDown = sdl2::keyboard::Keycode::BrightnessDown as i32, BrightnessDown = sdl2::keyboard::Keycode::BrightnessDown as i32,
BrightnessUp = sdl2::keyboard::Keycode::BrightnessUp as i32, BrightnessUp = sdl2::keyboard::Keycode::BrightnessUp as i32,
DisplaySwitch = sdl2::keyboard::Keycode::DisplaySwitch as i32, DisplaySwitch = sdl2::keyboard::Keycode::DisplaySwitch as i32,
KbdIllumToggle = sdl2::keyboard::Keycode::KbdIllumToggle as i32, KbdIllumToggle = sdl2::keyboard::Keycode::KbdIllumToggle as i32,
KbdIllumDown = sdl2::keyboard::Keycode::KbdIllumDown as i32, KbdIllumDown = sdl2::keyboard::Keycode::KbdIllumDown as i32,
KbdIllumUp = sdl2::keyboard::Keycode::KbdIllumUp as i32, KbdIllumUp = sdl2::keyboard::Keycode::KbdIllumUp as i32,
Eject = sdl2::keyboard::Keycode::Eject as i32, Eject = sdl2::keyboard::Keycode::Eject as i32,
Sleep = sdl2::keyboard::Keycode::Sleep as i32, Sleep = sdl2::keyboard::Keycode::Sleep as i32,
} }
impl From<sdl2::keyboard::Keycode> for Keycode { impl From<sdl2::keyboard::Keycode> for Keycode {
fn from(value: sdl2::keyboard::Keycode) -> Self { fn from(value: sdl2::keyboard::Keycode) -> Self {
match value { match value {
sdl2::keyboard::Keycode::Backspace => Keycode::Backspace, sdl2::keyboard::Keycode::Backspace => Keycode::Backspace,
sdl2::keyboard::Keycode::Tab => Keycode::Tab, sdl2::keyboard::Keycode::Tab => Keycode::Tab,
sdl2::keyboard::Keycode::Return => Keycode::Return, sdl2::keyboard::Keycode::Return => Keycode::Return,
sdl2::keyboard::Keycode::Escape => Keycode::Escape, sdl2::keyboard::Keycode::Escape => Keycode::Escape,
sdl2::keyboard::Keycode::Space => Keycode::Space, sdl2::keyboard::Keycode::Space => Keycode::Space,
sdl2::keyboard::Keycode::Exclaim => Keycode::Exclaim, sdl2::keyboard::Keycode::Exclaim => Keycode::Exclaim,
sdl2::keyboard::Keycode::Quotedbl => Keycode::Quotedbl, sdl2::keyboard::Keycode::Quotedbl => Keycode::Quotedbl,
sdl2::keyboard::Keycode::Hash => Keycode::Hash, sdl2::keyboard::Keycode::Hash => Keycode::Hash,
sdl2::keyboard::Keycode::Dollar => Keycode::Dollar, sdl2::keyboard::Keycode::Dollar => Keycode::Dollar,
sdl2::keyboard::Keycode::Percent => Keycode::Percent, sdl2::keyboard::Keycode::Percent => Keycode::Percent,
sdl2::keyboard::Keycode::Ampersand => Keycode::Ampersand, sdl2::keyboard::Keycode::Ampersand => Keycode::Ampersand,
sdl2::keyboard::Keycode::Quote => Keycode::Quote, sdl2::keyboard::Keycode::Quote => Keycode::Quote,
sdl2::keyboard::Keycode::LeftParen => Keycode::LeftParen, sdl2::keyboard::Keycode::LeftParen => Keycode::LeftParen,
sdl2::keyboard::Keycode::RightParen => Keycode::RightParen, sdl2::keyboard::Keycode::RightParen => Keycode::RightParen,
sdl2::keyboard::Keycode::Asterisk => Keycode::Asterisk, sdl2::keyboard::Keycode::Asterisk => Keycode::Asterisk,
sdl2::keyboard::Keycode::Plus => Keycode::Plus, sdl2::keyboard::Keycode::Plus => Keycode::Plus,
sdl2::keyboard::Keycode::Comma => Keycode::Comma, sdl2::keyboard::Keycode::Comma => Keycode::Comma,
sdl2::keyboard::Keycode::Minus => Keycode::Minus, sdl2::keyboard::Keycode::Minus => Keycode::Minus,
sdl2::keyboard::Keycode::Period => Keycode::Period, sdl2::keyboard::Keycode::Period => Keycode::Period,
sdl2::keyboard::Keycode::Slash => Keycode::Slash, sdl2::keyboard::Keycode::Slash => Keycode::Slash,
sdl2::keyboard::Keycode::Num0 => Keycode::Num0, sdl2::keyboard::Keycode::Num0 => Keycode::Num0,
sdl2::keyboard::Keycode::Num1 => Keycode::Num1, sdl2::keyboard::Keycode::Num1 => Keycode::Num1,
sdl2::keyboard::Keycode::Num2 => Keycode::Num2, sdl2::keyboard::Keycode::Num2 => Keycode::Num2,
sdl2::keyboard::Keycode::Num3 => Keycode::Num3, sdl2::keyboard::Keycode::Num3 => Keycode::Num3,
sdl2::keyboard::Keycode::Num4 => Keycode::Num4, sdl2::keyboard::Keycode::Num4 => Keycode::Num4,
sdl2::keyboard::Keycode::Num5 => Keycode::Num5, sdl2::keyboard::Keycode::Num5 => Keycode::Num5,
sdl2::keyboard::Keycode::Num6 => Keycode::Num6, sdl2::keyboard::Keycode::Num6 => Keycode::Num6,
sdl2::keyboard::Keycode::Num7 => Keycode::Num7, sdl2::keyboard::Keycode::Num7 => Keycode::Num7,
sdl2::keyboard::Keycode::Num8 => Keycode::Num8, sdl2::keyboard::Keycode::Num8 => Keycode::Num8,
sdl2::keyboard::Keycode::Num9 => Keycode::Num9, sdl2::keyboard::Keycode::Num9 => Keycode::Num9,
sdl2::keyboard::Keycode::Colon => Keycode::Colon, sdl2::keyboard::Keycode::Colon => Keycode::Colon,
sdl2::keyboard::Keycode::Semicolon => Keycode::Semicolon, sdl2::keyboard::Keycode::Semicolon => Keycode::Semicolon,
sdl2::keyboard::Keycode::Less => Keycode::Less, sdl2::keyboard::Keycode::Less => Keycode::Less,
sdl2::keyboard::Keycode::Equals => Keycode::Equals, sdl2::keyboard::Keycode::Equals => Keycode::Equals,
sdl2::keyboard::Keycode::Greater => Keycode::Greater, sdl2::keyboard::Keycode::Greater => Keycode::Greater,
sdl2::keyboard::Keycode::Question => Keycode::Question, sdl2::keyboard::Keycode::Question => Keycode::Question,
sdl2::keyboard::Keycode::At => Keycode::At, sdl2::keyboard::Keycode::At => Keycode::At,
sdl2::keyboard::Keycode::LeftBracket => Keycode::LeftBracket, sdl2::keyboard::Keycode::LeftBracket => Keycode::LeftBracket,
sdl2::keyboard::Keycode::Backslash => Keycode::Backslash, sdl2::keyboard::Keycode::Backslash => Keycode::Backslash,
sdl2::keyboard::Keycode::RightBracket => Keycode::RightBracket, sdl2::keyboard::Keycode::RightBracket => Keycode::RightBracket,
sdl2::keyboard::Keycode::Caret => Keycode::Caret, sdl2::keyboard::Keycode::Caret => Keycode::Caret,
sdl2::keyboard::Keycode::Underscore => Keycode::Underscore, sdl2::keyboard::Keycode::Underscore => Keycode::Underscore,
sdl2::keyboard::Keycode::Backquote => Keycode::Backquote, sdl2::keyboard::Keycode::Backquote => Keycode::Backquote,
sdl2::keyboard::Keycode::A => Keycode::A, sdl2::keyboard::Keycode::A => Keycode::A,
sdl2::keyboard::Keycode::B => Keycode::B, sdl2::keyboard::Keycode::B => Keycode::B,
sdl2::keyboard::Keycode::C => Keycode::C, sdl2::keyboard::Keycode::C => Keycode::C,
sdl2::keyboard::Keycode::D => Keycode::D, sdl2::keyboard::Keycode::D => Keycode::D,
sdl2::keyboard::Keycode::E => Keycode::E, sdl2::keyboard::Keycode::E => Keycode::E,
sdl2::keyboard::Keycode::F => Keycode::F, sdl2::keyboard::Keycode::F => Keycode::F,
sdl2::keyboard::Keycode::G => Keycode::G, sdl2::keyboard::Keycode::G => Keycode::G,
sdl2::keyboard::Keycode::H => Keycode::H, sdl2::keyboard::Keycode::H => Keycode::H,
sdl2::keyboard::Keycode::I => Keycode::I, sdl2::keyboard::Keycode::I => Keycode::I,
sdl2::keyboard::Keycode::J => Keycode::J, sdl2::keyboard::Keycode::J => Keycode::J,
sdl2::keyboard::Keycode::K => Keycode::K, sdl2::keyboard::Keycode::K => Keycode::K,
sdl2::keyboard::Keycode::L => Keycode::L, sdl2::keyboard::Keycode::L => Keycode::L,
sdl2::keyboard::Keycode::M => Keycode::M, sdl2::keyboard::Keycode::M => Keycode::M,
sdl2::keyboard::Keycode::N => Keycode::N, sdl2::keyboard::Keycode::N => Keycode::N,
sdl2::keyboard::Keycode::O => Keycode::O, sdl2::keyboard::Keycode::O => Keycode::O,
sdl2::keyboard::Keycode::P => Keycode::P, sdl2::keyboard::Keycode::P => Keycode::P,
sdl2::keyboard::Keycode::Q => Keycode::Q, sdl2::keyboard::Keycode::Q => Keycode::Q,
sdl2::keyboard::Keycode::R => Keycode::R, sdl2::keyboard::Keycode::R => Keycode::R,
sdl2::keyboard::Keycode::S => Keycode::S, sdl2::keyboard::Keycode::S => Keycode::S,
sdl2::keyboard::Keycode::T => Keycode::T, sdl2::keyboard::Keycode::T => Keycode::T,
sdl2::keyboard::Keycode::U => Keycode::U, sdl2::keyboard::Keycode::U => Keycode::U,
sdl2::keyboard::Keycode::V => Keycode::V, sdl2::keyboard::Keycode::V => Keycode::V,
sdl2::keyboard::Keycode::W => Keycode::W, sdl2::keyboard::Keycode::W => Keycode::W,
sdl2::keyboard::Keycode::X => Keycode::X, sdl2::keyboard::Keycode::X => Keycode::X,
sdl2::keyboard::Keycode::Y => Keycode::Y, sdl2::keyboard::Keycode::Y => Keycode::Y,
sdl2::keyboard::Keycode::Z => Keycode::Z, sdl2::keyboard::Keycode::Z => Keycode::Z,
sdl2::keyboard::Keycode::Delete => Keycode::Delete, sdl2::keyboard::Keycode::Delete => Keycode::Delete,
sdl2::keyboard::Keycode::CapsLock => Keycode::CapsLock, sdl2::keyboard::Keycode::CapsLock => Keycode::CapsLock,
sdl2::keyboard::Keycode::F1 => Keycode::F1, sdl2::keyboard::Keycode::F1 => Keycode::F1,
sdl2::keyboard::Keycode::F2 => Keycode::F2, sdl2::keyboard::Keycode::F2 => Keycode::F2,
sdl2::keyboard::Keycode::F3 => Keycode::F3, sdl2::keyboard::Keycode::F3 => Keycode::F3,
sdl2::keyboard::Keycode::F4 => Keycode::F4, sdl2::keyboard::Keycode::F4 => Keycode::F4,
sdl2::keyboard::Keycode::F5 => Keycode::F5, sdl2::keyboard::Keycode::F5 => Keycode::F5,
sdl2::keyboard::Keycode::F6 => Keycode::F6, sdl2::keyboard::Keycode::F6 => Keycode::F6,
sdl2::keyboard::Keycode::F7 => Keycode::F7, sdl2::keyboard::Keycode::F7 => Keycode::F7,
sdl2::keyboard::Keycode::F8 => Keycode::F8, sdl2::keyboard::Keycode::F8 => Keycode::F8,
sdl2::keyboard::Keycode::F9 => Keycode::F9, sdl2::keyboard::Keycode::F9 => Keycode::F9,
sdl2::keyboard::Keycode::F10 => Keycode::F10, sdl2::keyboard::Keycode::F10 => Keycode::F10,
sdl2::keyboard::Keycode::F11 => Keycode::F11, sdl2::keyboard::Keycode::F11 => Keycode::F11,
sdl2::keyboard::Keycode::F12 => Keycode::F12, sdl2::keyboard::Keycode::F12 => Keycode::F12,
sdl2::keyboard::Keycode::PrintScreen => Keycode::PrintScreen, sdl2::keyboard::Keycode::PrintScreen => Keycode::PrintScreen,
sdl2::keyboard::Keycode::ScrollLock => Keycode::ScrollLock, sdl2::keyboard::Keycode::ScrollLock => Keycode::ScrollLock,
sdl2::keyboard::Keycode::Pause => Keycode::Pause, sdl2::keyboard::Keycode::Pause => Keycode::Pause,
sdl2::keyboard::Keycode::Insert => Keycode::Insert, sdl2::keyboard::Keycode::Insert => Keycode::Insert,
sdl2::keyboard::Keycode::Home => Keycode::Home, sdl2::keyboard::Keycode::Home => Keycode::Home,
sdl2::keyboard::Keycode::PageUp => Keycode::PageUp, sdl2::keyboard::Keycode::PageUp => Keycode::PageUp,
sdl2::keyboard::Keycode::End => Keycode::End, sdl2::keyboard::Keycode::End => Keycode::End,
sdl2::keyboard::Keycode::PageDown => Keycode::PageDown, sdl2::keyboard::Keycode::PageDown => Keycode::PageDown,
sdl2::keyboard::Keycode::Right => Keycode::Right, sdl2::keyboard::Keycode::Right => Keycode::Right,
sdl2::keyboard::Keycode::Left => Keycode::Left, sdl2::keyboard::Keycode::Left => Keycode::Left,
sdl2::keyboard::Keycode::Down => Keycode::Down, sdl2::keyboard::Keycode::Down => Keycode::Down,
sdl2::keyboard::Keycode::Up => Keycode::Up, sdl2::keyboard::Keycode::Up => Keycode::Up,
sdl2::keyboard::Keycode::NumLockClear => Keycode::NumLockClear, sdl2::keyboard::Keycode::NumLockClear => Keycode::NumLockClear,
sdl2::keyboard::Keycode::KpDivide => Keycode::KpDivide, sdl2::keyboard::Keycode::KpDivide => Keycode::KpDivide,
sdl2::keyboard::Keycode::KpMultiply => Keycode::KpMultiply, sdl2::keyboard::Keycode::KpMultiply => Keycode::KpMultiply,
sdl2::keyboard::Keycode::KpMinus => Keycode::KpMinus, sdl2::keyboard::Keycode::KpMinus => Keycode::KpMinus,
sdl2::keyboard::Keycode::KpPlus => Keycode::KpPlus, sdl2::keyboard::Keycode::KpPlus => Keycode::KpPlus,
sdl2::keyboard::Keycode::KpEnter => Keycode::KpEnter, sdl2::keyboard::Keycode::KpEnter => Keycode::KpEnter,
sdl2::keyboard::Keycode::Kp1 => Keycode::Kp1, sdl2::keyboard::Keycode::Kp1 => Keycode::Kp1,
sdl2::keyboard::Keycode::Kp2 => Keycode::Kp2, sdl2::keyboard::Keycode::Kp2 => Keycode::Kp2,
sdl2::keyboard::Keycode::Kp3 => Keycode::Kp3, sdl2::keyboard::Keycode::Kp3 => Keycode::Kp3,
sdl2::keyboard::Keycode::Kp4 => Keycode::Kp4, sdl2::keyboard::Keycode::Kp4 => Keycode::Kp4,
sdl2::keyboard::Keycode::Kp5 => Keycode::Kp5, sdl2::keyboard::Keycode::Kp5 => Keycode::Kp5,
sdl2::keyboard::Keycode::Kp6 => Keycode::Kp6, sdl2::keyboard::Keycode::Kp6 => Keycode::Kp6,
sdl2::keyboard::Keycode::Kp7 => Keycode::Kp7, sdl2::keyboard::Keycode::Kp7 => Keycode::Kp7,
sdl2::keyboard::Keycode::Kp8 => Keycode::Kp8, sdl2::keyboard::Keycode::Kp8 => Keycode::Kp8,
sdl2::keyboard::Keycode::Kp9 => Keycode::Kp9, sdl2::keyboard::Keycode::Kp9 => Keycode::Kp9,
sdl2::keyboard::Keycode::Kp0 => Keycode::Kp0, sdl2::keyboard::Keycode::Kp0 => Keycode::Kp0,
sdl2::keyboard::Keycode::KpPeriod => Keycode::KpPeriod, sdl2::keyboard::Keycode::KpPeriod => Keycode::KpPeriod,
sdl2::keyboard::Keycode::Application => Keycode::Application, sdl2::keyboard::Keycode::Application => Keycode::Application,
sdl2::keyboard::Keycode::Power => Keycode::Power, sdl2::keyboard::Keycode::Power => Keycode::Power,
sdl2::keyboard::Keycode::KpEquals => Keycode::KpEquals, sdl2::keyboard::Keycode::KpEquals => Keycode::KpEquals,
sdl2::keyboard::Keycode::F13 => Keycode::F13, sdl2::keyboard::Keycode::F13 => Keycode::F13,
sdl2::keyboard::Keycode::F14 => Keycode::F14, sdl2::keyboard::Keycode::F14 => Keycode::F14,
sdl2::keyboard::Keycode::F15 => Keycode::F15, sdl2::keyboard::Keycode::F15 => Keycode::F15,
sdl2::keyboard::Keycode::F16 => Keycode::F16, sdl2::keyboard::Keycode::F16 => Keycode::F16,
sdl2::keyboard::Keycode::F17 => Keycode::F17, sdl2::keyboard::Keycode::F17 => Keycode::F17,
sdl2::keyboard::Keycode::F18 => Keycode::F18, sdl2::keyboard::Keycode::F18 => Keycode::F18,
sdl2::keyboard::Keycode::F19 => Keycode::F19, sdl2::keyboard::Keycode::F19 => Keycode::F19,
sdl2::keyboard::Keycode::F20 => Keycode::F20, sdl2::keyboard::Keycode::F20 => Keycode::F20,
sdl2::keyboard::Keycode::F21 => Keycode::F21, sdl2::keyboard::Keycode::F21 => Keycode::F21,
sdl2::keyboard::Keycode::F22 => Keycode::F22, sdl2::keyboard::Keycode::F22 => Keycode::F22,
sdl2::keyboard::Keycode::F23 => Keycode::F23, sdl2::keyboard::Keycode::F23 => Keycode::F23,
sdl2::keyboard::Keycode::F24 => Keycode::F24, sdl2::keyboard::Keycode::F24 => Keycode::F24,
sdl2::keyboard::Keycode::Execute => Keycode::Execute, sdl2::keyboard::Keycode::Execute => Keycode::Execute,
sdl2::keyboard::Keycode::Help => Keycode::Help, sdl2::keyboard::Keycode::Help => Keycode::Help,
sdl2::keyboard::Keycode::Menu => Keycode::Menu, sdl2::keyboard::Keycode::Menu => Keycode::Menu,
sdl2::keyboard::Keycode::Select => Keycode::Select, sdl2::keyboard::Keycode::Select => Keycode::Select,
sdl2::keyboard::Keycode::Stop => Keycode::Stop, sdl2::keyboard::Keycode::Stop => Keycode::Stop,
sdl2::keyboard::Keycode::Again => Keycode::Again, sdl2::keyboard::Keycode::Again => Keycode::Again,
sdl2::keyboard::Keycode::Undo => Keycode::Undo, sdl2::keyboard::Keycode::Undo => Keycode::Undo,
sdl2::keyboard::Keycode::Cut => Keycode::Cut, sdl2::keyboard::Keycode::Cut => Keycode::Cut,
sdl2::keyboard::Keycode::Copy => Keycode::Copy, sdl2::keyboard::Keycode::Copy => Keycode::Copy,
sdl2::keyboard::Keycode::Paste => Keycode::Paste, sdl2::keyboard::Keycode::Paste => Keycode::Paste,
sdl2::keyboard::Keycode::Find => Keycode::Find, sdl2::keyboard::Keycode::Find => Keycode::Find,
sdl2::keyboard::Keycode::Mute => Keycode::Mute, sdl2::keyboard::Keycode::Mute => Keycode::Mute,
sdl2::keyboard::Keycode::VolumeUp => Keycode::VolumeUp, sdl2::keyboard::Keycode::VolumeUp => Keycode::VolumeUp,
sdl2::keyboard::Keycode::VolumeDown => Keycode::VolumeDown, sdl2::keyboard::Keycode::VolumeDown => Keycode::VolumeDown,
sdl2::keyboard::Keycode::KpComma => Keycode::KpComma, sdl2::keyboard::Keycode::KpComma => Keycode::KpComma,
sdl2::keyboard::Keycode::KpEqualsAS400 => Keycode::KpEqualsAS400, sdl2::keyboard::Keycode::KpEqualsAS400 => Keycode::KpEqualsAS400,
sdl2::keyboard::Keycode::AltErase => Keycode::AltErase, sdl2::keyboard::Keycode::AltErase => Keycode::AltErase,
sdl2::keyboard::Keycode::Sysreq => Keycode::Sysreq, sdl2::keyboard::Keycode::Sysreq => Keycode::Sysreq,
sdl2::keyboard::Keycode::Cancel => Keycode::Cancel, sdl2::keyboard::Keycode::Cancel => Keycode::Cancel,
sdl2::keyboard::Keycode::Clear => Keycode::Clear, sdl2::keyboard::Keycode::Clear => Keycode::Clear,
sdl2::keyboard::Keycode::Prior => Keycode::Prior, sdl2::keyboard::Keycode::Prior => Keycode::Prior,
sdl2::keyboard::Keycode::Return2 => Keycode::Return2, sdl2::keyboard::Keycode::Return2 => Keycode::Return2,
sdl2::keyboard::Keycode::Separator => Keycode::Separator, sdl2::keyboard::Keycode::Separator => Keycode::Separator,
sdl2::keyboard::Keycode::Out => Keycode::Out, sdl2::keyboard::Keycode::Out => Keycode::Out,
sdl2::keyboard::Keycode::Oper => Keycode::Oper, sdl2::keyboard::Keycode::Oper => Keycode::Oper,
sdl2::keyboard::Keycode::ClearAgain => Keycode::ClearAgain, sdl2::keyboard::Keycode::ClearAgain => Keycode::ClearAgain,
sdl2::keyboard::Keycode::CrSel => Keycode::CrSel, sdl2::keyboard::Keycode::CrSel => Keycode::CrSel,
sdl2::keyboard::Keycode::ExSel => Keycode::ExSel, sdl2::keyboard::Keycode::ExSel => Keycode::ExSel,
sdl2::keyboard::Keycode::Kp00 => Keycode::Kp00, sdl2::keyboard::Keycode::Kp00 => Keycode::Kp00,
sdl2::keyboard::Keycode::Kp000 => Keycode::Kp000, sdl2::keyboard::Keycode::Kp000 => Keycode::Kp000,
sdl2::keyboard::Keycode::ThousandsSeparator => Keycode::ThousandsSeparator, sdl2::keyboard::Keycode::ThousandsSeparator => Keycode::ThousandsSeparator,
sdl2::keyboard::Keycode::DecimalSeparator => Keycode::DecimalSeparator, sdl2::keyboard::Keycode::DecimalSeparator => Keycode::DecimalSeparator,
sdl2::keyboard::Keycode::CurrencyUnit => Keycode::CurrencyUnit, sdl2::keyboard::Keycode::CurrencyUnit => Keycode::CurrencyUnit,
sdl2::keyboard::Keycode::CurrencySubUnit => Keycode::CurrencySubUnit, sdl2::keyboard::Keycode::CurrencySubUnit => Keycode::CurrencySubUnit,
sdl2::keyboard::Keycode::KpLeftParen => Keycode::KpLeftParen, sdl2::keyboard::Keycode::KpLeftParen => Keycode::KpLeftParen,
sdl2::keyboard::Keycode::KpRightParen => Keycode::KpRightParen, sdl2::keyboard::Keycode::KpRightParen => Keycode::KpRightParen,
sdl2::keyboard::Keycode::KpLeftBrace => Keycode::KpLeftBrace, sdl2::keyboard::Keycode::KpLeftBrace => Keycode::KpLeftBrace,
sdl2::keyboard::Keycode::KpRightBrace => Keycode::KpRightBrace, sdl2::keyboard::Keycode::KpRightBrace => Keycode::KpRightBrace,
sdl2::keyboard::Keycode::KpTab => Keycode::KpTab, sdl2::keyboard::Keycode::KpTab => Keycode::KpTab,
sdl2::keyboard::Keycode::KpBackspace => Keycode::KpBackspace, sdl2::keyboard::Keycode::KpBackspace => Keycode::KpBackspace,
sdl2::keyboard::Keycode::KpA => Keycode::KpA, sdl2::keyboard::Keycode::KpA => Keycode::KpA,
sdl2::keyboard::Keycode::KpB => Keycode::KpB, sdl2::keyboard::Keycode::KpB => Keycode::KpB,
sdl2::keyboard::Keycode::KpC => Keycode::KpC, sdl2::keyboard::Keycode::KpC => Keycode::KpC,
sdl2::keyboard::Keycode::KpD => Keycode::KpD, sdl2::keyboard::Keycode::KpD => Keycode::KpD,
sdl2::keyboard::Keycode::KpE => Keycode::KpE, sdl2::keyboard::Keycode::KpE => Keycode::KpE,
sdl2::keyboard::Keycode::KpF => Keycode::KpF, sdl2::keyboard::Keycode::KpF => Keycode::KpF,
sdl2::keyboard::Keycode::KpXor => Keycode::KpXor, sdl2::keyboard::Keycode::KpXor => Keycode::KpXor,
sdl2::keyboard::Keycode::KpPower => Keycode::KpPower, sdl2::keyboard::Keycode::KpPower => Keycode::KpPower,
sdl2::keyboard::Keycode::KpPercent => Keycode::KpPercent, sdl2::keyboard::Keycode::KpPercent => Keycode::KpPercent,
sdl2::keyboard::Keycode::KpLess => Keycode::KpLess, sdl2::keyboard::Keycode::KpLess => Keycode::KpLess,
sdl2::keyboard::Keycode::KpGreater => Keycode::KpGreater, sdl2::keyboard::Keycode::KpGreater => Keycode::KpGreater,
sdl2::keyboard::Keycode::KpAmpersand => Keycode::KpAmpersand, sdl2::keyboard::Keycode::KpAmpersand => Keycode::KpAmpersand,
sdl2::keyboard::Keycode::KpDblAmpersand => Keycode::KpDblAmpersand, sdl2::keyboard::Keycode::KpDblAmpersand => Keycode::KpDblAmpersand,
sdl2::keyboard::Keycode::KpVerticalBar => Keycode::KpVerticalBar, sdl2::keyboard::Keycode::KpVerticalBar => Keycode::KpVerticalBar,
sdl2::keyboard::Keycode::KpDblVerticalBar => Keycode::KpDblVerticalBar, sdl2::keyboard::Keycode::KpDblVerticalBar => Keycode::KpDblVerticalBar,
sdl2::keyboard::Keycode::KpColon => Keycode::KpColon, sdl2::keyboard::Keycode::KpColon => Keycode::KpColon,
sdl2::keyboard::Keycode::KpHash => Keycode::KpHash, sdl2::keyboard::Keycode::KpHash => Keycode::KpHash,
sdl2::keyboard::Keycode::KpSpace => Keycode::KpSpace, sdl2::keyboard::Keycode::KpSpace => Keycode::KpSpace,
sdl2::keyboard::Keycode::KpAt => Keycode::KpAt, sdl2::keyboard::Keycode::KpAt => Keycode::KpAt,
sdl2::keyboard::Keycode::KpExclam => Keycode::KpExclam, sdl2::keyboard::Keycode::KpExclam => Keycode::KpExclam,
sdl2::keyboard::Keycode::KpMemStore => Keycode::KpMemStore, sdl2::keyboard::Keycode::KpMemStore => Keycode::KpMemStore,
sdl2::keyboard::Keycode::KpMemRecall => Keycode::KpMemRecall, sdl2::keyboard::Keycode::KpMemRecall => Keycode::KpMemRecall,
sdl2::keyboard::Keycode::KpMemClear => Keycode::KpMemClear, sdl2::keyboard::Keycode::KpMemClear => Keycode::KpMemClear,
sdl2::keyboard::Keycode::KpMemAdd => Keycode::KpMemAdd, sdl2::keyboard::Keycode::KpMemAdd => Keycode::KpMemAdd,
sdl2::keyboard::Keycode::KpMemSubtract => Keycode::KpMemSubtract, sdl2::keyboard::Keycode::KpMemSubtract => Keycode::KpMemSubtract,
sdl2::keyboard::Keycode::KpMemMultiply => Keycode::KpMemMultiply, sdl2::keyboard::Keycode::KpMemMultiply => Keycode::KpMemMultiply,
sdl2::keyboard::Keycode::KpMemDivide => Keycode::KpMemDivide, sdl2::keyboard::Keycode::KpMemDivide => Keycode::KpMemDivide,
sdl2::keyboard::Keycode::KpPlusMinus => Keycode::KpPlusMinus, sdl2::keyboard::Keycode::KpPlusMinus => Keycode::KpPlusMinus,
sdl2::keyboard::Keycode::KpClear => Keycode::KpClear, sdl2::keyboard::Keycode::KpClear => Keycode::KpClear,
sdl2::keyboard::Keycode::KpClearEntry => Keycode::KpClearEntry, sdl2::keyboard::Keycode::KpClearEntry => Keycode::KpClearEntry,
sdl2::keyboard::Keycode::KpBinary => Keycode::KpBinary, sdl2::keyboard::Keycode::KpBinary => Keycode::KpBinary,
sdl2::keyboard::Keycode::KpOctal => Keycode::KpOctal, sdl2::keyboard::Keycode::KpOctal => Keycode::KpOctal,
sdl2::keyboard::Keycode::KpDecimal => Keycode::KpDecimal, sdl2::keyboard::Keycode::KpDecimal => Keycode::KpDecimal,
sdl2::keyboard::Keycode::KpHexadecimal => Keycode::KpHexadecimal, sdl2::keyboard::Keycode::KpHexadecimal => Keycode::KpHexadecimal,
sdl2::keyboard::Keycode::LCtrl => Keycode::LCtrl, sdl2::keyboard::Keycode::LCtrl => Keycode::LCtrl,
sdl2::keyboard::Keycode::LShift => Keycode::LShift, sdl2::keyboard::Keycode::LShift => Keycode::LShift,
sdl2::keyboard::Keycode::LAlt => Keycode::LAlt, sdl2::keyboard::Keycode::LAlt => Keycode::LAlt,
sdl2::keyboard::Keycode::LGui => Keycode::LGui, sdl2::keyboard::Keycode::LGui => Keycode::LGui,
sdl2::keyboard::Keycode::RCtrl => Keycode::RCtrl, sdl2::keyboard::Keycode::RCtrl => Keycode::RCtrl,
sdl2::keyboard::Keycode::RShift => Keycode::RShift, sdl2::keyboard::Keycode::RShift => Keycode::RShift,
sdl2::keyboard::Keycode::RAlt => Keycode::RAlt, sdl2::keyboard::Keycode::RAlt => Keycode::RAlt,
sdl2::keyboard::Keycode::RGui => Keycode::RGui, sdl2::keyboard::Keycode::RGui => Keycode::RGui,
sdl2::keyboard::Keycode::Mode => Keycode::Mode, sdl2::keyboard::Keycode::Mode => Keycode::Mode,
sdl2::keyboard::Keycode::AudioNext => Keycode::AudioNext, sdl2::keyboard::Keycode::AudioNext => Keycode::AudioNext,
sdl2::keyboard::Keycode::AudioPrev => Keycode::AudioPrev, sdl2::keyboard::Keycode::AudioPrev => Keycode::AudioPrev,
sdl2::keyboard::Keycode::AudioStop => Keycode::AudioStop, sdl2::keyboard::Keycode::AudioStop => Keycode::AudioStop,
sdl2::keyboard::Keycode::AudioPlay => Keycode::AudioPlay, sdl2::keyboard::Keycode::AudioPlay => Keycode::AudioPlay,
sdl2::keyboard::Keycode::AudioMute => Keycode::AudioMute, sdl2::keyboard::Keycode::AudioMute => Keycode::AudioMute,
sdl2::keyboard::Keycode::MediaSelect => Keycode::MediaSelect, sdl2::keyboard::Keycode::MediaSelect => Keycode::MediaSelect,
sdl2::keyboard::Keycode::Www => Keycode::Www, sdl2::keyboard::Keycode::Www => Keycode::Www,
sdl2::keyboard::Keycode::Mail => Keycode::Mail, sdl2::keyboard::Keycode::Mail => Keycode::Mail,
sdl2::keyboard::Keycode::Calculator => Keycode::Calculator, sdl2::keyboard::Keycode::Calculator => Keycode::Calculator,
sdl2::keyboard::Keycode::Computer => Keycode::Computer, sdl2::keyboard::Keycode::Computer => Keycode::Computer,
sdl2::keyboard::Keycode::AcSearch => Keycode::AcSearch, sdl2::keyboard::Keycode::AcSearch => Keycode::AcSearch,
sdl2::keyboard::Keycode::AcHome => Keycode::AcHome, sdl2::keyboard::Keycode::AcHome => Keycode::AcHome,
sdl2::keyboard::Keycode::AcBack => Keycode::AcBack, sdl2::keyboard::Keycode::AcBack => Keycode::AcBack,
sdl2::keyboard::Keycode::AcForward => Keycode::AcForward, sdl2::keyboard::Keycode::AcForward => Keycode::AcForward,
sdl2::keyboard::Keycode::AcStop => Keycode::AcStop, sdl2::keyboard::Keycode::AcStop => Keycode::AcStop,
sdl2::keyboard::Keycode::AcRefresh => Keycode::AcRefresh, sdl2::keyboard::Keycode::AcRefresh => Keycode::AcRefresh,
sdl2::keyboard::Keycode::AcBookmarks => Keycode::AcBookmarks, sdl2::keyboard::Keycode::AcBookmarks => Keycode::AcBookmarks,
sdl2::keyboard::Keycode::BrightnessDown => Keycode::BrightnessDown, sdl2::keyboard::Keycode::BrightnessDown => Keycode::BrightnessDown,
sdl2::keyboard::Keycode::BrightnessUp => Keycode::BrightnessUp, sdl2::keyboard::Keycode::BrightnessUp => Keycode::BrightnessUp,
sdl2::keyboard::Keycode::DisplaySwitch => Keycode::DisplaySwitch, sdl2::keyboard::Keycode::DisplaySwitch => Keycode::DisplaySwitch,
sdl2::keyboard::Keycode::KbdIllumToggle => Keycode::KbdIllumToggle, sdl2::keyboard::Keycode::KbdIllumToggle => Keycode::KbdIllumToggle,
sdl2::keyboard::Keycode::KbdIllumDown => Keycode::KbdIllumDown, sdl2::keyboard::Keycode::KbdIllumDown => Keycode::KbdIllumDown,
sdl2::keyboard::Keycode::KbdIllumUp => Keycode::KbdIllumUp, sdl2::keyboard::Keycode::KbdIllumUp => Keycode::KbdIllumUp,
sdl2::keyboard::Keycode::Eject => Keycode::Eject, sdl2::keyboard::Keycode::Eject => Keycode::Eject,
sdl2::keyboard::Keycode::Sleep => Keycode::Sleep, sdl2::keyboard::Keycode::Sleep => Keycode::Sleep,
} }
} }
} }

View file

@ -19,85 +19,85 @@ const MAX_KEYS: usize = 256;
/// [`System`]: crate::System /// [`System`]: crate::System
#[derive(Debug)] #[derive(Debug)]
pub struct Keyboard { pub struct Keyboard {
keyboard: [ButtonState; MAX_KEYS], // Box<[ButtonState]>, keyboard: [ButtonState; MAX_KEYS], // Box<[ButtonState]>,
} }
impl Keyboard { impl Keyboard {
pub fn new() -> Keyboard { pub fn new() -> Keyboard {
Keyboard { Keyboard {
keyboard: [ButtonState::Idle; MAX_KEYS], keyboard: [ButtonState::Idle; MAX_KEYS],
} }
/* /*
Keyboard { Keyboard {
keyboard: vec![ButtonState::Idle; 256].into_boxed_slice(), keyboard: vec![ButtonState::Idle; 256].into_boxed_slice(),
} }
*/ */
} }
/// Returns true if the given key was just pressed or is being held down. /// Returns true if the given key was just pressed or is being held down.
#[inline] #[inline]
pub fn is_key_down(&self, scancode: Scancode) -> bool { pub fn is_key_down(&self, scancode: Scancode) -> bool {
matches!( matches!(
self.keyboard[scancode as usize], self.keyboard[scancode as usize],
ButtonState::Pressed | ButtonState::Held ButtonState::Pressed | ButtonState::Held
) )
} }
/// Returns true if the given key was not just pressed and is not being held down. /// Returns true if the given key was not just pressed and is not being held down.
#[inline] #[inline]
pub fn is_key_up(&self, scancode: Scancode) -> bool { pub fn is_key_up(&self, scancode: Scancode) -> bool {
matches!( matches!(
self.keyboard[scancode as usize], self.keyboard[scancode as usize],
ButtonState::Released | ButtonState::Idle ButtonState::Released | ButtonState::Idle
) )
} }
/// Returns true if the given key was just pressed (not being held down, yet). /// Returns true if the given key was just pressed (not being held down, yet).
#[inline] #[inline]
pub fn is_key_pressed(&self, scancode: Scancode) -> bool { pub fn is_key_pressed(&self, scancode: Scancode) -> bool {
self.keyboard[scancode as usize] == ButtonState::Pressed self.keyboard[scancode as usize] == ButtonState::Pressed
} }
/// Returns true if the given key was just released. /// Returns true if the given key was just released.
#[inline] #[inline]
pub fn is_key_released(&self, scancode: Scancode) -> bool { pub fn is_key_released(&self, scancode: Scancode) -> bool {
self.keyboard[scancode as usize] == ButtonState::Released self.keyboard[scancode as usize] == ButtonState::Released
} }
} }
impl InputDevice for Keyboard { impl InputDevice for Keyboard {
fn update(&mut self) { fn update(&mut self) {
for state in self.keyboard.iter_mut() { for state in self.keyboard.iter_mut() {
*state = match *state { *state = match *state {
ButtonState::Pressed => ButtonState::Held, ButtonState::Pressed => ButtonState::Held,
ButtonState::Released => ButtonState::Idle, ButtonState::Released => ButtonState::Idle,
otherwise => otherwise, otherwise => otherwise,
}; };
} }
} }
} }
impl SystemEventHandler for Keyboard { impl SystemEventHandler for Keyboard {
fn handle_event(&mut self, event: &SystemEvent) -> bool { fn handle_event(&mut self, event: &SystemEvent) -> bool {
match event { match event {
SystemEvent::Keyboard(KeyboardEvent::KeyDown { scancode, .. }) => { SystemEvent::Keyboard(KeyboardEvent::KeyDown { scancode, .. }) => {
if let Some(scancode) = scancode { if let Some(scancode) = scancode {
let state = &mut self.keyboard[*scancode as usize]; let state = &mut self.keyboard[*scancode as usize];
*state = match *state { *state = match *state {
ButtonState::Pressed => ButtonState::Held, ButtonState::Pressed => ButtonState::Held,
ButtonState::Held => ButtonState::Held, ButtonState::Held => ButtonState::Held,
_ => ButtonState::Pressed, _ => ButtonState::Pressed,
}; };
} }
true true
} }
SystemEvent::Keyboard(KeyboardEvent::KeyUp { scancode, .. }) => { SystemEvent::Keyboard(KeyboardEvent::KeyUp { scancode, .. }) => {
if let Some(scancode) = scancode { if let Some(scancode) = scancode {
self.keyboard[*scancode as usize] = ButtonState::Released; self.keyboard[*scancode as usize] = ButtonState::Released;
} }
true true
} }
_ => false, _ => false,
} }
} }
} }

View file

@ -3,493 +3,493 @@
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
#[repr(i32)] #[repr(i32)]
pub enum Scancode { pub enum Scancode {
A = sdl2::keyboard::Scancode::A as i32, A = sdl2::keyboard::Scancode::A as i32,
B = sdl2::keyboard::Scancode::B as i32, B = sdl2::keyboard::Scancode::B as i32,
C = sdl2::keyboard::Scancode::C as i32, C = sdl2::keyboard::Scancode::C as i32,
D = sdl2::keyboard::Scancode::D as i32, D = sdl2::keyboard::Scancode::D as i32,
E = sdl2::keyboard::Scancode::E as i32, E = sdl2::keyboard::Scancode::E as i32,
F = sdl2::keyboard::Scancode::F as i32, F = sdl2::keyboard::Scancode::F as i32,
G = sdl2::keyboard::Scancode::G as i32, G = sdl2::keyboard::Scancode::G as i32,
H = sdl2::keyboard::Scancode::H as i32, H = sdl2::keyboard::Scancode::H as i32,
I = sdl2::keyboard::Scancode::I as i32, I = sdl2::keyboard::Scancode::I as i32,
J = sdl2::keyboard::Scancode::J as i32, J = sdl2::keyboard::Scancode::J as i32,
K = sdl2::keyboard::Scancode::K as i32, K = sdl2::keyboard::Scancode::K as i32,
L = sdl2::keyboard::Scancode::L as i32, L = sdl2::keyboard::Scancode::L as i32,
M = sdl2::keyboard::Scancode::M as i32, M = sdl2::keyboard::Scancode::M as i32,
N = sdl2::keyboard::Scancode::N as i32, N = sdl2::keyboard::Scancode::N as i32,
O = sdl2::keyboard::Scancode::O as i32, O = sdl2::keyboard::Scancode::O as i32,
P = sdl2::keyboard::Scancode::P as i32, P = sdl2::keyboard::Scancode::P as i32,
Q = sdl2::keyboard::Scancode::Q as i32, Q = sdl2::keyboard::Scancode::Q as i32,
R = sdl2::keyboard::Scancode::R as i32, R = sdl2::keyboard::Scancode::R as i32,
S = sdl2::keyboard::Scancode::S as i32, S = sdl2::keyboard::Scancode::S as i32,
T = sdl2::keyboard::Scancode::T as i32, T = sdl2::keyboard::Scancode::T as i32,
U = sdl2::keyboard::Scancode::U as i32, U = sdl2::keyboard::Scancode::U as i32,
V = sdl2::keyboard::Scancode::V as i32, V = sdl2::keyboard::Scancode::V as i32,
W = sdl2::keyboard::Scancode::W as i32, W = sdl2::keyboard::Scancode::W as i32,
X = sdl2::keyboard::Scancode::X as i32, X = sdl2::keyboard::Scancode::X as i32,
Y = sdl2::keyboard::Scancode::Y as i32, Y = sdl2::keyboard::Scancode::Y as i32,
Z = sdl2::keyboard::Scancode::Z as i32, Z = sdl2::keyboard::Scancode::Z as i32,
Num1 = sdl2::keyboard::Scancode::Num1 as i32, Num1 = sdl2::keyboard::Scancode::Num1 as i32,
Num2 = sdl2::keyboard::Scancode::Num2 as i32, Num2 = sdl2::keyboard::Scancode::Num2 as i32,
Num3 = sdl2::keyboard::Scancode::Num3 as i32, Num3 = sdl2::keyboard::Scancode::Num3 as i32,
Num4 = sdl2::keyboard::Scancode::Num4 as i32, Num4 = sdl2::keyboard::Scancode::Num4 as i32,
Num5 = sdl2::keyboard::Scancode::Num5 as i32, Num5 = sdl2::keyboard::Scancode::Num5 as i32,
Num6 = sdl2::keyboard::Scancode::Num6 as i32, Num6 = sdl2::keyboard::Scancode::Num6 as i32,
Num7 = sdl2::keyboard::Scancode::Num7 as i32, Num7 = sdl2::keyboard::Scancode::Num7 as i32,
Num8 = sdl2::keyboard::Scancode::Num8 as i32, Num8 = sdl2::keyboard::Scancode::Num8 as i32,
Num9 = sdl2::keyboard::Scancode::Num9 as i32, Num9 = sdl2::keyboard::Scancode::Num9 as i32,
Num0 = sdl2::keyboard::Scancode::Num0 as i32, Num0 = sdl2::keyboard::Scancode::Num0 as i32,
Return = sdl2::keyboard::Scancode::Return as i32, Return = sdl2::keyboard::Scancode::Return as i32,
Escape = sdl2::keyboard::Scancode::Escape as i32, Escape = sdl2::keyboard::Scancode::Escape as i32,
Backspace = sdl2::keyboard::Scancode::Backspace as i32, Backspace = sdl2::keyboard::Scancode::Backspace as i32,
Tab = sdl2::keyboard::Scancode::Tab as i32, Tab = sdl2::keyboard::Scancode::Tab as i32,
Space = sdl2::keyboard::Scancode::Space as i32, Space = sdl2::keyboard::Scancode::Space as i32,
Minus = sdl2::keyboard::Scancode::Minus as i32, Minus = sdl2::keyboard::Scancode::Minus as i32,
Equals = sdl2::keyboard::Scancode::Equals as i32, Equals = sdl2::keyboard::Scancode::Equals as i32,
LeftBracket = sdl2::keyboard::Scancode::LeftBracket as i32, LeftBracket = sdl2::keyboard::Scancode::LeftBracket as i32,
RightBracket = sdl2::keyboard::Scancode::RightBracket as i32, RightBracket = sdl2::keyboard::Scancode::RightBracket as i32,
Backslash = sdl2::keyboard::Scancode::Backslash as i32, Backslash = sdl2::keyboard::Scancode::Backslash as i32,
NonUsHash = sdl2::keyboard::Scancode::NonUsHash as i32, NonUsHash = sdl2::keyboard::Scancode::NonUsHash as i32,
Semicolon = sdl2::keyboard::Scancode::Semicolon as i32, Semicolon = sdl2::keyboard::Scancode::Semicolon as i32,
Apostrophe = sdl2::keyboard::Scancode::Apostrophe as i32, Apostrophe = sdl2::keyboard::Scancode::Apostrophe as i32,
Grave = sdl2::keyboard::Scancode::Grave as i32, Grave = sdl2::keyboard::Scancode::Grave as i32,
Comma = sdl2::keyboard::Scancode::Comma as i32, Comma = sdl2::keyboard::Scancode::Comma as i32,
Period = sdl2::keyboard::Scancode::Period as i32, Period = sdl2::keyboard::Scancode::Period as i32,
Slash = sdl2::keyboard::Scancode::Slash as i32, Slash = sdl2::keyboard::Scancode::Slash as i32,
CapsLock = sdl2::keyboard::Scancode::CapsLock as i32, CapsLock = sdl2::keyboard::Scancode::CapsLock as i32,
F1 = sdl2::keyboard::Scancode::F1 as i32, F1 = sdl2::keyboard::Scancode::F1 as i32,
F2 = sdl2::keyboard::Scancode::F2 as i32, F2 = sdl2::keyboard::Scancode::F2 as i32,
F3 = sdl2::keyboard::Scancode::F3 as i32, F3 = sdl2::keyboard::Scancode::F3 as i32,
F4 = sdl2::keyboard::Scancode::F4 as i32, F4 = sdl2::keyboard::Scancode::F4 as i32,
F5 = sdl2::keyboard::Scancode::F5 as i32, F5 = sdl2::keyboard::Scancode::F5 as i32,
F6 = sdl2::keyboard::Scancode::F6 as i32, F6 = sdl2::keyboard::Scancode::F6 as i32,
F7 = sdl2::keyboard::Scancode::F7 as i32, F7 = sdl2::keyboard::Scancode::F7 as i32,
F8 = sdl2::keyboard::Scancode::F8 as i32, F8 = sdl2::keyboard::Scancode::F8 as i32,
F9 = sdl2::keyboard::Scancode::F9 as i32, F9 = sdl2::keyboard::Scancode::F9 as i32,
F10 = sdl2::keyboard::Scancode::F10 as i32, F10 = sdl2::keyboard::Scancode::F10 as i32,
F11 = sdl2::keyboard::Scancode::F11 as i32, F11 = sdl2::keyboard::Scancode::F11 as i32,
F12 = sdl2::keyboard::Scancode::F12 as i32, F12 = sdl2::keyboard::Scancode::F12 as i32,
PrintScreen = sdl2::keyboard::Scancode::PrintScreen as i32, PrintScreen = sdl2::keyboard::Scancode::PrintScreen as i32,
ScrollLock = sdl2::keyboard::Scancode::ScrollLock as i32, ScrollLock = sdl2::keyboard::Scancode::ScrollLock as i32,
Pause = sdl2::keyboard::Scancode::Pause as i32, Pause = sdl2::keyboard::Scancode::Pause as i32,
Insert = sdl2::keyboard::Scancode::Insert as i32, Insert = sdl2::keyboard::Scancode::Insert as i32,
Home = sdl2::keyboard::Scancode::Home as i32, Home = sdl2::keyboard::Scancode::Home as i32,
PageUp = sdl2::keyboard::Scancode::PageUp as i32, PageUp = sdl2::keyboard::Scancode::PageUp as i32,
Delete = sdl2::keyboard::Scancode::Delete as i32, Delete = sdl2::keyboard::Scancode::Delete as i32,
End = sdl2::keyboard::Scancode::End as i32, End = sdl2::keyboard::Scancode::End as i32,
PageDown = sdl2::keyboard::Scancode::PageDown as i32, PageDown = sdl2::keyboard::Scancode::PageDown as i32,
Right = sdl2::keyboard::Scancode::Right as i32, Right = sdl2::keyboard::Scancode::Right as i32,
Left = sdl2::keyboard::Scancode::Left as i32, Left = sdl2::keyboard::Scancode::Left as i32,
Down = sdl2::keyboard::Scancode::Down as i32, Down = sdl2::keyboard::Scancode::Down as i32,
Up = sdl2::keyboard::Scancode::Up as i32, Up = sdl2::keyboard::Scancode::Up as i32,
NumLockClear = sdl2::keyboard::Scancode::NumLockClear as i32, NumLockClear = sdl2::keyboard::Scancode::NumLockClear as i32,
KpDivide = sdl2::keyboard::Scancode::KpDivide as i32, KpDivide = sdl2::keyboard::Scancode::KpDivide as i32,
KpMultiply = sdl2::keyboard::Scancode::KpMultiply as i32, KpMultiply = sdl2::keyboard::Scancode::KpMultiply as i32,
KpMinus = sdl2::keyboard::Scancode::KpMinus as i32, KpMinus = sdl2::keyboard::Scancode::KpMinus as i32,
KpPlus = sdl2::keyboard::Scancode::KpPlus as i32, KpPlus = sdl2::keyboard::Scancode::KpPlus as i32,
KpEnter = sdl2::keyboard::Scancode::KpEnter as i32, KpEnter = sdl2::keyboard::Scancode::KpEnter as i32,
Kp1 = sdl2::keyboard::Scancode::Kp1 as i32, Kp1 = sdl2::keyboard::Scancode::Kp1 as i32,
Kp2 = sdl2::keyboard::Scancode::Kp2 as i32, Kp2 = sdl2::keyboard::Scancode::Kp2 as i32,
Kp3 = sdl2::keyboard::Scancode::Kp3 as i32, Kp3 = sdl2::keyboard::Scancode::Kp3 as i32,
Kp4 = sdl2::keyboard::Scancode::Kp4 as i32, Kp4 = sdl2::keyboard::Scancode::Kp4 as i32,
Kp5 = sdl2::keyboard::Scancode::Kp5 as i32, Kp5 = sdl2::keyboard::Scancode::Kp5 as i32,
Kp6 = sdl2::keyboard::Scancode::Kp6 as i32, Kp6 = sdl2::keyboard::Scancode::Kp6 as i32,
Kp7 = sdl2::keyboard::Scancode::Kp7 as i32, Kp7 = sdl2::keyboard::Scancode::Kp7 as i32,
Kp8 = sdl2::keyboard::Scancode::Kp8 as i32, Kp8 = sdl2::keyboard::Scancode::Kp8 as i32,
Kp9 = sdl2::keyboard::Scancode::Kp9 as i32, Kp9 = sdl2::keyboard::Scancode::Kp9 as i32,
Kp0 = sdl2::keyboard::Scancode::Kp0 as i32, Kp0 = sdl2::keyboard::Scancode::Kp0 as i32,
KpPeriod = sdl2::keyboard::Scancode::KpPeriod as i32, KpPeriod = sdl2::keyboard::Scancode::KpPeriod as i32,
NonUsBackslash = sdl2::keyboard::Scancode::NonUsBackslash as i32, NonUsBackslash = sdl2::keyboard::Scancode::NonUsBackslash as i32,
Application = sdl2::keyboard::Scancode::Application as i32, Application = sdl2::keyboard::Scancode::Application as i32,
Power = sdl2::keyboard::Scancode::Power as i32, Power = sdl2::keyboard::Scancode::Power as i32,
KpEquals = sdl2::keyboard::Scancode::KpEquals as i32, KpEquals = sdl2::keyboard::Scancode::KpEquals as i32,
F13 = sdl2::keyboard::Scancode::F13 as i32, F13 = sdl2::keyboard::Scancode::F13 as i32,
F14 = sdl2::keyboard::Scancode::F14 as i32, F14 = sdl2::keyboard::Scancode::F14 as i32,
F15 = sdl2::keyboard::Scancode::F15 as i32, F15 = sdl2::keyboard::Scancode::F15 as i32,
F16 = sdl2::keyboard::Scancode::F16 as i32, F16 = sdl2::keyboard::Scancode::F16 as i32,
F17 = sdl2::keyboard::Scancode::F17 as i32, F17 = sdl2::keyboard::Scancode::F17 as i32,
F18 = sdl2::keyboard::Scancode::F18 as i32, F18 = sdl2::keyboard::Scancode::F18 as i32,
F19 = sdl2::keyboard::Scancode::F19 as i32, F19 = sdl2::keyboard::Scancode::F19 as i32,
F20 = sdl2::keyboard::Scancode::F20 as i32, F20 = sdl2::keyboard::Scancode::F20 as i32,
F21 = sdl2::keyboard::Scancode::F21 as i32, F21 = sdl2::keyboard::Scancode::F21 as i32,
F22 = sdl2::keyboard::Scancode::F22 as i32, F22 = sdl2::keyboard::Scancode::F22 as i32,
F23 = sdl2::keyboard::Scancode::F23 as i32, F23 = sdl2::keyboard::Scancode::F23 as i32,
F24 = sdl2::keyboard::Scancode::F24 as i32, F24 = sdl2::keyboard::Scancode::F24 as i32,
Execute = sdl2::keyboard::Scancode::Execute as i32, Execute = sdl2::keyboard::Scancode::Execute as i32,
Help = sdl2::keyboard::Scancode::Help as i32, Help = sdl2::keyboard::Scancode::Help as i32,
Menu = sdl2::keyboard::Scancode::Menu as i32, Menu = sdl2::keyboard::Scancode::Menu as i32,
Select = sdl2::keyboard::Scancode::Select as i32, Select = sdl2::keyboard::Scancode::Select as i32,
Stop = sdl2::keyboard::Scancode::Stop as i32, Stop = sdl2::keyboard::Scancode::Stop as i32,
Again = sdl2::keyboard::Scancode::Again as i32, Again = sdl2::keyboard::Scancode::Again as i32,
Undo = sdl2::keyboard::Scancode::Undo as i32, Undo = sdl2::keyboard::Scancode::Undo as i32,
Cut = sdl2::keyboard::Scancode::Cut as i32, Cut = sdl2::keyboard::Scancode::Cut as i32,
Copy = sdl2::keyboard::Scancode::Copy as i32, Copy = sdl2::keyboard::Scancode::Copy as i32,
Paste = sdl2::keyboard::Scancode::Paste as i32, Paste = sdl2::keyboard::Scancode::Paste as i32,
Find = sdl2::keyboard::Scancode::Find as i32, Find = sdl2::keyboard::Scancode::Find as i32,
Mute = sdl2::keyboard::Scancode::Mute as i32, Mute = sdl2::keyboard::Scancode::Mute as i32,
VolumeUp = sdl2::keyboard::Scancode::VolumeUp as i32, VolumeUp = sdl2::keyboard::Scancode::VolumeUp as i32,
VolumeDown = sdl2::keyboard::Scancode::VolumeDown as i32, VolumeDown = sdl2::keyboard::Scancode::VolumeDown as i32,
KpComma = sdl2::keyboard::Scancode::KpComma as i32, KpComma = sdl2::keyboard::Scancode::KpComma as i32,
KpEqualsAS400 = sdl2::keyboard::Scancode::KpEqualsAS400 as i32, KpEqualsAS400 = sdl2::keyboard::Scancode::KpEqualsAS400 as i32,
International1 = sdl2::keyboard::Scancode::International1 as i32, International1 = sdl2::keyboard::Scancode::International1 as i32,
International2 = sdl2::keyboard::Scancode::International2 as i32, International2 = sdl2::keyboard::Scancode::International2 as i32,
International3 = sdl2::keyboard::Scancode::International3 as i32, International3 = sdl2::keyboard::Scancode::International3 as i32,
International4 = sdl2::keyboard::Scancode::International4 as i32, International4 = sdl2::keyboard::Scancode::International4 as i32,
International5 = sdl2::keyboard::Scancode::International5 as i32, International5 = sdl2::keyboard::Scancode::International5 as i32,
International6 = sdl2::keyboard::Scancode::International6 as i32, International6 = sdl2::keyboard::Scancode::International6 as i32,
International7 = sdl2::keyboard::Scancode::International7 as i32, International7 = sdl2::keyboard::Scancode::International7 as i32,
International8 = sdl2::keyboard::Scancode::International8 as i32, International8 = sdl2::keyboard::Scancode::International8 as i32,
International9 = sdl2::keyboard::Scancode::International9 as i32, International9 = sdl2::keyboard::Scancode::International9 as i32,
Lang1 = sdl2::keyboard::Scancode::Lang1 as i32, Lang1 = sdl2::keyboard::Scancode::Lang1 as i32,
Lang2 = sdl2::keyboard::Scancode::Lang2 as i32, Lang2 = sdl2::keyboard::Scancode::Lang2 as i32,
Lang3 = sdl2::keyboard::Scancode::Lang3 as i32, Lang3 = sdl2::keyboard::Scancode::Lang3 as i32,
Lang4 = sdl2::keyboard::Scancode::Lang4 as i32, Lang4 = sdl2::keyboard::Scancode::Lang4 as i32,
Lang5 = sdl2::keyboard::Scancode::Lang5 as i32, Lang5 = sdl2::keyboard::Scancode::Lang5 as i32,
Lang6 = sdl2::keyboard::Scancode::Lang6 as i32, Lang6 = sdl2::keyboard::Scancode::Lang6 as i32,
Lang7 = sdl2::keyboard::Scancode::Lang7 as i32, Lang7 = sdl2::keyboard::Scancode::Lang7 as i32,
Lang8 = sdl2::keyboard::Scancode::Lang8 as i32, Lang8 = sdl2::keyboard::Scancode::Lang8 as i32,
Lang9 = sdl2::keyboard::Scancode::Lang9 as i32, Lang9 = sdl2::keyboard::Scancode::Lang9 as i32,
AltErase = sdl2::keyboard::Scancode::AltErase as i32, AltErase = sdl2::keyboard::Scancode::AltErase as i32,
SysReq = sdl2::keyboard::Scancode::SysReq as i32, SysReq = sdl2::keyboard::Scancode::SysReq as i32,
Cancel = sdl2::keyboard::Scancode::Cancel as i32, Cancel = sdl2::keyboard::Scancode::Cancel as i32,
Clear = sdl2::keyboard::Scancode::Clear as i32, Clear = sdl2::keyboard::Scancode::Clear as i32,
Prior = sdl2::keyboard::Scancode::Prior as i32, Prior = sdl2::keyboard::Scancode::Prior as i32,
Return2 = sdl2::keyboard::Scancode::Return2 as i32, Return2 = sdl2::keyboard::Scancode::Return2 as i32,
Separator = sdl2::keyboard::Scancode::Separator as i32, Separator = sdl2::keyboard::Scancode::Separator as i32,
Out = sdl2::keyboard::Scancode::Out as i32, Out = sdl2::keyboard::Scancode::Out as i32,
Oper = sdl2::keyboard::Scancode::Oper as i32, Oper = sdl2::keyboard::Scancode::Oper as i32,
ClearAgain = sdl2::keyboard::Scancode::ClearAgain as i32, ClearAgain = sdl2::keyboard::Scancode::ClearAgain as i32,
CrSel = sdl2::keyboard::Scancode::CrSel as i32, CrSel = sdl2::keyboard::Scancode::CrSel as i32,
ExSel = sdl2::keyboard::Scancode::ExSel as i32, ExSel = sdl2::keyboard::Scancode::ExSel as i32,
Kp00 = sdl2::keyboard::Scancode::Kp00 as i32, Kp00 = sdl2::keyboard::Scancode::Kp00 as i32,
Kp000 = sdl2::keyboard::Scancode::Kp000 as i32, Kp000 = sdl2::keyboard::Scancode::Kp000 as i32,
ThousandsSeparator = sdl2::keyboard::Scancode::ThousandsSeparator as i32, ThousandsSeparator = sdl2::keyboard::Scancode::ThousandsSeparator as i32,
DecimalSeparator = sdl2::keyboard::Scancode::DecimalSeparator as i32, DecimalSeparator = sdl2::keyboard::Scancode::DecimalSeparator as i32,
CurrencyUnit = sdl2::keyboard::Scancode::CurrencyUnit as i32, CurrencyUnit = sdl2::keyboard::Scancode::CurrencyUnit as i32,
CurrencySubUnit = sdl2::keyboard::Scancode::CurrencySubUnit as i32, CurrencySubUnit = sdl2::keyboard::Scancode::CurrencySubUnit as i32,
KpLeftParen = sdl2::keyboard::Scancode::KpLeftParen as i32, KpLeftParen = sdl2::keyboard::Scancode::KpLeftParen as i32,
KpRightParen = sdl2::keyboard::Scancode::KpRightParen as i32, KpRightParen = sdl2::keyboard::Scancode::KpRightParen as i32,
KpLeftBrace = sdl2::keyboard::Scancode::KpLeftBrace as i32, KpLeftBrace = sdl2::keyboard::Scancode::KpLeftBrace as i32,
KpRightBrace = sdl2::keyboard::Scancode::KpRightBrace as i32, KpRightBrace = sdl2::keyboard::Scancode::KpRightBrace as i32,
KpTab = sdl2::keyboard::Scancode::KpTab as i32, KpTab = sdl2::keyboard::Scancode::KpTab as i32,
KpBackspace = sdl2::keyboard::Scancode::KpBackspace as i32, KpBackspace = sdl2::keyboard::Scancode::KpBackspace as i32,
KpA = sdl2::keyboard::Scancode::KpA as i32, KpA = sdl2::keyboard::Scancode::KpA as i32,
KpB = sdl2::keyboard::Scancode::KpB as i32, KpB = sdl2::keyboard::Scancode::KpB as i32,
KpC = sdl2::keyboard::Scancode::KpC as i32, KpC = sdl2::keyboard::Scancode::KpC as i32,
KpD = sdl2::keyboard::Scancode::KpD as i32, KpD = sdl2::keyboard::Scancode::KpD as i32,
KpE = sdl2::keyboard::Scancode::KpE as i32, KpE = sdl2::keyboard::Scancode::KpE as i32,
KpF = sdl2::keyboard::Scancode::KpF as i32, KpF = sdl2::keyboard::Scancode::KpF as i32,
KpXor = sdl2::keyboard::Scancode::KpXor as i32, KpXor = sdl2::keyboard::Scancode::KpXor as i32,
KpPower = sdl2::keyboard::Scancode::KpPower as i32, KpPower = sdl2::keyboard::Scancode::KpPower as i32,
KpPercent = sdl2::keyboard::Scancode::KpPercent as i32, KpPercent = sdl2::keyboard::Scancode::KpPercent as i32,
KpLess = sdl2::keyboard::Scancode::KpLess as i32, KpLess = sdl2::keyboard::Scancode::KpLess as i32,
KpGreater = sdl2::keyboard::Scancode::KpGreater as i32, KpGreater = sdl2::keyboard::Scancode::KpGreater as i32,
KpAmpersand = sdl2::keyboard::Scancode::KpAmpersand as i32, KpAmpersand = sdl2::keyboard::Scancode::KpAmpersand as i32,
KpDblAmpersand = sdl2::keyboard::Scancode::KpDblAmpersand as i32, KpDblAmpersand = sdl2::keyboard::Scancode::KpDblAmpersand as i32,
KpVerticalBar = sdl2::keyboard::Scancode::KpVerticalBar as i32, KpVerticalBar = sdl2::keyboard::Scancode::KpVerticalBar as i32,
KpDblVerticalBar = sdl2::keyboard::Scancode::KpDblVerticalBar as i32, KpDblVerticalBar = sdl2::keyboard::Scancode::KpDblVerticalBar as i32,
KpColon = sdl2::keyboard::Scancode::KpColon as i32, KpColon = sdl2::keyboard::Scancode::KpColon as i32,
KpHash = sdl2::keyboard::Scancode::KpHash as i32, KpHash = sdl2::keyboard::Scancode::KpHash as i32,
KpSpace = sdl2::keyboard::Scancode::KpSpace as i32, KpSpace = sdl2::keyboard::Scancode::KpSpace as i32,
KpAt = sdl2::keyboard::Scancode::KpAt as i32, KpAt = sdl2::keyboard::Scancode::KpAt as i32,
KpExclam = sdl2::keyboard::Scancode::KpExclam as i32, KpExclam = sdl2::keyboard::Scancode::KpExclam as i32,
KpMemStore = sdl2::keyboard::Scancode::KpMemStore as i32, KpMemStore = sdl2::keyboard::Scancode::KpMemStore as i32,
KpMemRecall = sdl2::keyboard::Scancode::KpMemRecall as i32, KpMemRecall = sdl2::keyboard::Scancode::KpMemRecall as i32,
KpMemClear = sdl2::keyboard::Scancode::KpMemClear as i32, KpMemClear = sdl2::keyboard::Scancode::KpMemClear as i32,
KpMemAdd = sdl2::keyboard::Scancode::KpMemAdd as i32, KpMemAdd = sdl2::keyboard::Scancode::KpMemAdd as i32,
KpMemSubtract = sdl2::keyboard::Scancode::KpMemSubtract as i32, KpMemSubtract = sdl2::keyboard::Scancode::KpMemSubtract as i32,
KpMemMultiply = sdl2::keyboard::Scancode::KpMemMultiply as i32, KpMemMultiply = sdl2::keyboard::Scancode::KpMemMultiply as i32,
KpMemDivide = sdl2::keyboard::Scancode::KpMemDivide as i32, KpMemDivide = sdl2::keyboard::Scancode::KpMemDivide as i32,
KpPlusMinus = sdl2::keyboard::Scancode::KpPlusMinus as i32, KpPlusMinus = sdl2::keyboard::Scancode::KpPlusMinus as i32,
KpClear = sdl2::keyboard::Scancode::KpClear as i32, KpClear = sdl2::keyboard::Scancode::KpClear as i32,
KpClearEntry = sdl2::keyboard::Scancode::KpClearEntry as i32, KpClearEntry = sdl2::keyboard::Scancode::KpClearEntry as i32,
KpBinary = sdl2::keyboard::Scancode::KpBinary as i32, KpBinary = sdl2::keyboard::Scancode::KpBinary as i32,
KpOctal = sdl2::keyboard::Scancode::KpOctal as i32, KpOctal = sdl2::keyboard::Scancode::KpOctal as i32,
KpDecimal = sdl2::keyboard::Scancode::KpDecimal as i32, KpDecimal = sdl2::keyboard::Scancode::KpDecimal as i32,
KpHexadecimal = sdl2::keyboard::Scancode::KpHexadecimal as i32, KpHexadecimal = sdl2::keyboard::Scancode::KpHexadecimal as i32,
LCtrl = sdl2::keyboard::Scancode::LCtrl as i32, LCtrl = sdl2::keyboard::Scancode::LCtrl as i32,
LShift = sdl2::keyboard::Scancode::LShift as i32, LShift = sdl2::keyboard::Scancode::LShift as i32,
LAlt = sdl2::keyboard::Scancode::LAlt as i32, LAlt = sdl2::keyboard::Scancode::LAlt as i32,
LGui = sdl2::keyboard::Scancode::LGui as i32, LGui = sdl2::keyboard::Scancode::LGui as i32,
RCtrl = sdl2::keyboard::Scancode::RCtrl as i32, RCtrl = sdl2::keyboard::Scancode::RCtrl as i32,
RShift = sdl2::keyboard::Scancode::RShift as i32, RShift = sdl2::keyboard::Scancode::RShift as i32,
RAlt = sdl2::keyboard::Scancode::RAlt as i32, RAlt = sdl2::keyboard::Scancode::RAlt as i32,
RGui = sdl2::keyboard::Scancode::RGui as i32, RGui = sdl2::keyboard::Scancode::RGui as i32,
Mode = sdl2::keyboard::Scancode::Mode as i32, Mode = sdl2::keyboard::Scancode::Mode as i32,
AudioNext = sdl2::keyboard::Scancode::AudioNext as i32, AudioNext = sdl2::keyboard::Scancode::AudioNext as i32,
AudioPrev = sdl2::keyboard::Scancode::AudioPrev as i32, AudioPrev = sdl2::keyboard::Scancode::AudioPrev as i32,
AudioStop = sdl2::keyboard::Scancode::AudioStop as i32, AudioStop = sdl2::keyboard::Scancode::AudioStop as i32,
AudioPlay = sdl2::keyboard::Scancode::AudioPlay as i32, AudioPlay = sdl2::keyboard::Scancode::AudioPlay as i32,
AudioMute = sdl2::keyboard::Scancode::AudioMute as i32, AudioMute = sdl2::keyboard::Scancode::AudioMute as i32,
MediaSelect = sdl2::keyboard::Scancode::MediaSelect as i32, MediaSelect = sdl2::keyboard::Scancode::MediaSelect as i32,
Www = sdl2::keyboard::Scancode::Www as i32, Www = sdl2::keyboard::Scancode::Www as i32,
Mail = sdl2::keyboard::Scancode::Mail as i32, Mail = sdl2::keyboard::Scancode::Mail as i32,
Calculator = sdl2::keyboard::Scancode::Calculator as i32, Calculator = sdl2::keyboard::Scancode::Calculator as i32,
Computer = sdl2::keyboard::Scancode::Computer as i32, Computer = sdl2::keyboard::Scancode::Computer as i32,
AcSearch = sdl2::keyboard::Scancode::AcSearch as i32, AcSearch = sdl2::keyboard::Scancode::AcSearch as i32,
AcHome = sdl2::keyboard::Scancode::AcHome as i32, AcHome = sdl2::keyboard::Scancode::AcHome as i32,
AcBack = sdl2::keyboard::Scancode::AcBack as i32, AcBack = sdl2::keyboard::Scancode::AcBack as i32,
AcForward = sdl2::keyboard::Scancode::AcForward as i32, AcForward = sdl2::keyboard::Scancode::AcForward as i32,
AcStop = sdl2::keyboard::Scancode::AcStop as i32, AcStop = sdl2::keyboard::Scancode::AcStop as i32,
AcRefresh = sdl2::keyboard::Scancode::AcRefresh as i32, AcRefresh = sdl2::keyboard::Scancode::AcRefresh as i32,
AcBookmarks = sdl2::keyboard::Scancode::AcBookmarks as i32, AcBookmarks = sdl2::keyboard::Scancode::AcBookmarks as i32,
BrightnessDown = sdl2::keyboard::Scancode::BrightnessDown as i32, BrightnessDown = sdl2::keyboard::Scancode::BrightnessDown as i32,
BrightnessUp = sdl2::keyboard::Scancode::BrightnessUp as i32, BrightnessUp = sdl2::keyboard::Scancode::BrightnessUp as i32,
DisplaySwitch = sdl2::keyboard::Scancode::DisplaySwitch as i32, DisplaySwitch = sdl2::keyboard::Scancode::DisplaySwitch as i32,
KbdIllumToggle = sdl2::keyboard::Scancode::KbdIllumToggle as i32, KbdIllumToggle = sdl2::keyboard::Scancode::KbdIllumToggle as i32,
KbdIllumDown = sdl2::keyboard::Scancode::KbdIllumDown as i32, KbdIllumDown = sdl2::keyboard::Scancode::KbdIllumDown as i32,
KbdIllumUp = sdl2::keyboard::Scancode::KbdIllumUp as i32, KbdIllumUp = sdl2::keyboard::Scancode::KbdIllumUp as i32,
Eject = sdl2::keyboard::Scancode::Eject as i32, Eject = sdl2::keyboard::Scancode::Eject as i32,
Sleep = sdl2::keyboard::Scancode::Sleep as i32, Sleep = sdl2::keyboard::Scancode::Sleep as i32,
App1 = sdl2::keyboard::Scancode::App1 as i32, App1 = sdl2::keyboard::Scancode::App1 as i32,
App2 = sdl2::keyboard::Scancode::App2 as i32, App2 = sdl2::keyboard::Scancode::App2 as i32,
Num = sdl2::keyboard::Scancode::Num as i32, Num = sdl2::keyboard::Scancode::Num as i32,
} }
impl From<sdl2::keyboard::Scancode> for Scancode { impl From<sdl2::keyboard::Scancode> for Scancode {
fn from(value: sdl2::keyboard::Scancode) -> Self { fn from(value: sdl2::keyboard::Scancode) -> Self {
match value { match value {
sdl2::keyboard::Scancode::A => Scancode::A, sdl2::keyboard::Scancode::A => Scancode::A,
sdl2::keyboard::Scancode::B => Scancode::B, sdl2::keyboard::Scancode::B => Scancode::B,
sdl2::keyboard::Scancode::C => Scancode::C, sdl2::keyboard::Scancode::C => Scancode::C,
sdl2::keyboard::Scancode::D => Scancode::D, sdl2::keyboard::Scancode::D => Scancode::D,
sdl2::keyboard::Scancode::E => Scancode::E, sdl2::keyboard::Scancode::E => Scancode::E,
sdl2::keyboard::Scancode::F => Scancode::F, sdl2::keyboard::Scancode::F => Scancode::F,
sdl2::keyboard::Scancode::G => Scancode::G, sdl2::keyboard::Scancode::G => Scancode::G,
sdl2::keyboard::Scancode::H => Scancode::H, sdl2::keyboard::Scancode::H => Scancode::H,
sdl2::keyboard::Scancode::I => Scancode::I, sdl2::keyboard::Scancode::I => Scancode::I,
sdl2::keyboard::Scancode::J => Scancode::J, sdl2::keyboard::Scancode::J => Scancode::J,
sdl2::keyboard::Scancode::K => Scancode::K, sdl2::keyboard::Scancode::K => Scancode::K,
sdl2::keyboard::Scancode::L => Scancode::L, sdl2::keyboard::Scancode::L => Scancode::L,
sdl2::keyboard::Scancode::M => Scancode::M, sdl2::keyboard::Scancode::M => Scancode::M,
sdl2::keyboard::Scancode::N => Scancode::N, sdl2::keyboard::Scancode::N => Scancode::N,
sdl2::keyboard::Scancode::O => Scancode::O, sdl2::keyboard::Scancode::O => Scancode::O,
sdl2::keyboard::Scancode::P => Scancode::P, sdl2::keyboard::Scancode::P => Scancode::P,
sdl2::keyboard::Scancode::Q => Scancode::Q, sdl2::keyboard::Scancode::Q => Scancode::Q,
sdl2::keyboard::Scancode::R => Scancode::R, sdl2::keyboard::Scancode::R => Scancode::R,
sdl2::keyboard::Scancode::S => Scancode::S, sdl2::keyboard::Scancode::S => Scancode::S,
sdl2::keyboard::Scancode::T => Scancode::T, sdl2::keyboard::Scancode::T => Scancode::T,
sdl2::keyboard::Scancode::U => Scancode::U, sdl2::keyboard::Scancode::U => Scancode::U,
sdl2::keyboard::Scancode::V => Scancode::V, sdl2::keyboard::Scancode::V => Scancode::V,
sdl2::keyboard::Scancode::W => Scancode::W, sdl2::keyboard::Scancode::W => Scancode::W,
sdl2::keyboard::Scancode::X => Scancode::X, sdl2::keyboard::Scancode::X => Scancode::X,
sdl2::keyboard::Scancode::Y => Scancode::Y, sdl2::keyboard::Scancode::Y => Scancode::Y,
sdl2::keyboard::Scancode::Z => Scancode::Z, sdl2::keyboard::Scancode::Z => Scancode::Z,
sdl2::keyboard::Scancode::Num1 => Scancode::Num1, sdl2::keyboard::Scancode::Num1 => Scancode::Num1,
sdl2::keyboard::Scancode::Num2 => Scancode::Num2, sdl2::keyboard::Scancode::Num2 => Scancode::Num2,
sdl2::keyboard::Scancode::Num3 => Scancode::Num3, sdl2::keyboard::Scancode::Num3 => Scancode::Num3,
sdl2::keyboard::Scancode::Num4 => Scancode::Num4, sdl2::keyboard::Scancode::Num4 => Scancode::Num4,
sdl2::keyboard::Scancode::Num5 => Scancode::Num5, sdl2::keyboard::Scancode::Num5 => Scancode::Num5,
sdl2::keyboard::Scancode::Num6 => Scancode::Num6, sdl2::keyboard::Scancode::Num6 => Scancode::Num6,
sdl2::keyboard::Scancode::Num7 => Scancode::Num7, sdl2::keyboard::Scancode::Num7 => Scancode::Num7,
sdl2::keyboard::Scancode::Num8 => Scancode::Num8, sdl2::keyboard::Scancode::Num8 => Scancode::Num8,
sdl2::keyboard::Scancode::Num9 => Scancode::Num9, sdl2::keyboard::Scancode::Num9 => Scancode::Num9,
sdl2::keyboard::Scancode::Num0 => Scancode::Num0, sdl2::keyboard::Scancode::Num0 => Scancode::Num0,
sdl2::keyboard::Scancode::Return => Scancode::Return, sdl2::keyboard::Scancode::Return => Scancode::Return,
sdl2::keyboard::Scancode::Escape => Scancode::Escape, sdl2::keyboard::Scancode::Escape => Scancode::Escape,
sdl2::keyboard::Scancode::Backspace => Scancode::Backspace, sdl2::keyboard::Scancode::Backspace => Scancode::Backspace,
sdl2::keyboard::Scancode::Tab => Scancode::Tab, sdl2::keyboard::Scancode::Tab => Scancode::Tab,
sdl2::keyboard::Scancode::Space => Scancode::Space, sdl2::keyboard::Scancode::Space => Scancode::Space,
sdl2::keyboard::Scancode::Minus => Scancode::Minus, sdl2::keyboard::Scancode::Minus => Scancode::Minus,
sdl2::keyboard::Scancode::Equals => Scancode::Equals, sdl2::keyboard::Scancode::Equals => Scancode::Equals,
sdl2::keyboard::Scancode::LeftBracket => Scancode::LeftBracket, sdl2::keyboard::Scancode::LeftBracket => Scancode::LeftBracket,
sdl2::keyboard::Scancode::RightBracket => Scancode::RightBracket, sdl2::keyboard::Scancode::RightBracket => Scancode::RightBracket,
sdl2::keyboard::Scancode::Backslash => Scancode::Backslash, sdl2::keyboard::Scancode::Backslash => Scancode::Backslash,
sdl2::keyboard::Scancode::NonUsHash => Scancode::NonUsHash, sdl2::keyboard::Scancode::NonUsHash => Scancode::NonUsHash,
sdl2::keyboard::Scancode::Semicolon => Scancode::Semicolon, sdl2::keyboard::Scancode::Semicolon => Scancode::Semicolon,
sdl2::keyboard::Scancode::Apostrophe => Scancode::Apostrophe, sdl2::keyboard::Scancode::Apostrophe => Scancode::Apostrophe,
sdl2::keyboard::Scancode::Grave => Scancode::Grave, sdl2::keyboard::Scancode::Grave => Scancode::Grave,
sdl2::keyboard::Scancode::Comma => Scancode::Comma, sdl2::keyboard::Scancode::Comma => Scancode::Comma,
sdl2::keyboard::Scancode::Period => Scancode::Period, sdl2::keyboard::Scancode::Period => Scancode::Period,
sdl2::keyboard::Scancode::Slash => Scancode::Slash, sdl2::keyboard::Scancode::Slash => Scancode::Slash,
sdl2::keyboard::Scancode::CapsLock => Scancode::CapsLock, sdl2::keyboard::Scancode::CapsLock => Scancode::CapsLock,
sdl2::keyboard::Scancode::F1 => Scancode::F1, sdl2::keyboard::Scancode::F1 => Scancode::F1,
sdl2::keyboard::Scancode::F2 => Scancode::F2, sdl2::keyboard::Scancode::F2 => Scancode::F2,
sdl2::keyboard::Scancode::F3 => Scancode::F3, sdl2::keyboard::Scancode::F3 => Scancode::F3,
sdl2::keyboard::Scancode::F4 => Scancode::F4, sdl2::keyboard::Scancode::F4 => Scancode::F4,
sdl2::keyboard::Scancode::F5 => Scancode::F5, sdl2::keyboard::Scancode::F5 => Scancode::F5,
sdl2::keyboard::Scancode::F6 => Scancode::F6, sdl2::keyboard::Scancode::F6 => Scancode::F6,
sdl2::keyboard::Scancode::F7 => Scancode::F7, sdl2::keyboard::Scancode::F7 => Scancode::F7,
sdl2::keyboard::Scancode::F8 => Scancode::F8, sdl2::keyboard::Scancode::F8 => Scancode::F8,
sdl2::keyboard::Scancode::F9 => Scancode::F9, sdl2::keyboard::Scancode::F9 => Scancode::F9,
sdl2::keyboard::Scancode::F10 => Scancode::F10, sdl2::keyboard::Scancode::F10 => Scancode::F10,
sdl2::keyboard::Scancode::F11 => Scancode::F11, sdl2::keyboard::Scancode::F11 => Scancode::F11,
sdl2::keyboard::Scancode::F12 => Scancode::F12, sdl2::keyboard::Scancode::F12 => Scancode::F12,
sdl2::keyboard::Scancode::PrintScreen => Scancode::PrintScreen, sdl2::keyboard::Scancode::PrintScreen => Scancode::PrintScreen,
sdl2::keyboard::Scancode::ScrollLock => Scancode::ScrollLock, sdl2::keyboard::Scancode::ScrollLock => Scancode::ScrollLock,
sdl2::keyboard::Scancode::Pause => Scancode::Pause, sdl2::keyboard::Scancode::Pause => Scancode::Pause,
sdl2::keyboard::Scancode::Insert => Scancode::Insert, sdl2::keyboard::Scancode::Insert => Scancode::Insert,
sdl2::keyboard::Scancode::Home => Scancode::Home, sdl2::keyboard::Scancode::Home => Scancode::Home,
sdl2::keyboard::Scancode::PageUp => Scancode::PageUp, sdl2::keyboard::Scancode::PageUp => Scancode::PageUp,
sdl2::keyboard::Scancode::Delete => Scancode::Delete, sdl2::keyboard::Scancode::Delete => Scancode::Delete,
sdl2::keyboard::Scancode::End => Scancode::End, sdl2::keyboard::Scancode::End => Scancode::End,
sdl2::keyboard::Scancode::PageDown => Scancode::PageDown, sdl2::keyboard::Scancode::PageDown => Scancode::PageDown,
sdl2::keyboard::Scancode::Right => Scancode::Right, sdl2::keyboard::Scancode::Right => Scancode::Right,
sdl2::keyboard::Scancode::Left => Scancode::Left, sdl2::keyboard::Scancode::Left => Scancode::Left,
sdl2::keyboard::Scancode::Down => Scancode::Down, sdl2::keyboard::Scancode::Down => Scancode::Down,
sdl2::keyboard::Scancode::Up => Scancode::Up, sdl2::keyboard::Scancode::Up => Scancode::Up,
sdl2::keyboard::Scancode::NumLockClear => Scancode::NumLockClear, sdl2::keyboard::Scancode::NumLockClear => Scancode::NumLockClear,
sdl2::keyboard::Scancode::KpDivide => Scancode::KpDivide, sdl2::keyboard::Scancode::KpDivide => Scancode::KpDivide,
sdl2::keyboard::Scancode::KpMultiply => Scancode::KpMultiply, sdl2::keyboard::Scancode::KpMultiply => Scancode::KpMultiply,
sdl2::keyboard::Scancode::KpMinus => Scancode::KpMinus, sdl2::keyboard::Scancode::KpMinus => Scancode::KpMinus,
sdl2::keyboard::Scancode::KpPlus => Scancode::KpPlus, sdl2::keyboard::Scancode::KpPlus => Scancode::KpPlus,
sdl2::keyboard::Scancode::KpEnter => Scancode::KpEnter, sdl2::keyboard::Scancode::KpEnter => Scancode::KpEnter,
sdl2::keyboard::Scancode::Kp1 => Scancode::Kp1, sdl2::keyboard::Scancode::Kp1 => Scancode::Kp1,
sdl2::keyboard::Scancode::Kp2 => Scancode::Kp2, sdl2::keyboard::Scancode::Kp2 => Scancode::Kp2,
sdl2::keyboard::Scancode::Kp3 => Scancode::Kp3, sdl2::keyboard::Scancode::Kp3 => Scancode::Kp3,
sdl2::keyboard::Scancode::Kp4 => Scancode::Kp4, sdl2::keyboard::Scancode::Kp4 => Scancode::Kp4,
sdl2::keyboard::Scancode::Kp5 => Scancode::Kp5, sdl2::keyboard::Scancode::Kp5 => Scancode::Kp5,
sdl2::keyboard::Scancode::Kp6 => Scancode::Kp6, sdl2::keyboard::Scancode::Kp6 => Scancode::Kp6,
sdl2::keyboard::Scancode::Kp7 => Scancode::Kp7, sdl2::keyboard::Scancode::Kp7 => Scancode::Kp7,
sdl2::keyboard::Scancode::Kp8 => Scancode::Kp8, sdl2::keyboard::Scancode::Kp8 => Scancode::Kp8,
sdl2::keyboard::Scancode::Kp9 => Scancode::Kp9, sdl2::keyboard::Scancode::Kp9 => Scancode::Kp9,
sdl2::keyboard::Scancode::Kp0 => Scancode::Kp0, sdl2::keyboard::Scancode::Kp0 => Scancode::Kp0,
sdl2::keyboard::Scancode::KpPeriod => Scancode::KpPeriod, sdl2::keyboard::Scancode::KpPeriod => Scancode::KpPeriod,
sdl2::keyboard::Scancode::NonUsBackslash => Scancode::NonUsBackslash, sdl2::keyboard::Scancode::NonUsBackslash => Scancode::NonUsBackslash,
sdl2::keyboard::Scancode::Application => Scancode::Application, sdl2::keyboard::Scancode::Application => Scancode::Application,
sdl2::keyboard::Scancode::Power => Scancode::Power, sdl2::keyboard::Scancode::Power => Scancode::Power,
sdl2::keyboard::Scancode::KpEquals => Scancode::KpEquals, sdl2::keyboard::Scancode::KpEquals => Scancode::KpEquals,
sdl2::keyboard::Scancode::F13 => Scancode::F13, sdl2::keyboard::Scancode::F13 => Scancode::F13,
sdl2::keyboard::Scancode::F14 => Scancode::F14, sdl2::keyboard::Scancode::F14 => Scancode::F14,
sdl2::keyboard::Scancode::F15 => Scancode::F15, sdl2::keyboard::Scancode::F15 => Scancode::F15,
sdl2::keyboard::Scancode::F16 => Scancode::F16, sdl2::keyboard::Scancode::F16 => Scancode::F16,
sdl2::keyboard::Scancode::F17 => Scancode::F17, sdl2::keyboard::Scancode::F17 => Scancode::F17,
sdl2::keyboard::Scancode::F18 => Scancode::F18, sdl2::keyboard::Scancode::F18 => Scancode::F18,
sdl2::keyboard::Scancode::F19 => Scancode::F19, sdl2::keyboard::Scancode::F19 => Scancode::F19,
sdl2::keyboard::Scancode::F20 => Scancode::F20, sdl2::keyboard::Scancode::F20 => Scancode::F20,
sdl2::keyboard::Scancode::F21 => Scancode::F21, sdl2::keyboard::Scancode::F21 => Scancode::F21,
sdl2::keyboard::Scancode::F22 => Scancode::F22, sdl2::keyboard::Scancode::F22 => Scancode::F22,
sdl2::keyboard::Scancode::F23 => Scancode::F23, sdl2::keyboard::Scancode::F23 => Scancode::F23,
sdl2::keyboard::Scancode::F24 => Scancode::F24, sdl2::keyboard::Scancode::F24 => Scancode::F24,
sdl2::keyboard::Scancode::Execute => Scancode::Execute, sdl2::keyboard::Scancode::Execute => Scancode::Execute,
sdl2::keyboard::Scancode::Help => Scancode::Help, sdl2::keyboard::Scancode::Help => Scancode::Help,
sdl2::keyboard::Scancode::Menu => Scancode::Menu, sdl2::keyboard::Scancode::Menu => Scancode::Menu,
sdl2::keyboard::Scancode::Select => Scancode::Select, sdl2::keyboard::Scancode::Select => Scancode::Select,
sdl2::keyboard::Scancode::Stop => Scancode::Stop, sdl2::keyboard::Scancode::Stop => Scancode::Stop,
sdl2::keyboard::Scancode::Again => Scancode::Again, sdl2::keyboard::Scancode::Again => Scancode::Again,
sdl2::keyboard::Scancode::Undo => Scancode::Undo, sdl2::keyboard::Scancode::Undo => Scancode::Undo,
sdl2::keyboard::Scancode::Cut => Scancode::Cut, sdl2::keyboard::Scancode::Cut => Scancode::Cut,
sdl2::keyboard::Scancode::Copy => Scancode::Copy, sdl2::keyboard::Scancode::Copy => Scancode::Copy,
sdl2::keyboard::Scancode::Paste => Scancode::Paste, sdl2::keyboard::Scancode::Paste => Scancode::Paste,
sdl2::keyboard::Scancode::Find => Scancode::Find, sdl2::keyboard::Scancode::Find => Scancode::Find,
sdl2::keyboard::Scancode::Mute => Scancode::Mute, sdl2::keyboard::Scancode::Mute => Scancode::Mute,
sdl2::keyboard::Scancode::VolumeUp => Scancode::VolumeUp, sdl2::keyboard::Scancode::VolumeUp => Scancode::VolumeUp,
sdl2::keyboard::Scancode::VolumeDown => Scancode::VolumeDown, sdl2::keyboard::Scancode::VolumeDown => Scancode::VolumeDown,
sdl2::keyboard::Scancode::KpComma => Scancode::KpComma, sdl2::keyboard::Scancode::KpComma => Scancode::KpComma,
sdl2::keyboard::Scancode::KpEqualsAS400 => Scancode::KpEqualsAS400, sdl2::keyboard::Scancode::KpEqualsAS400 => Scancode::KpEqualsAS400,
sdl2::keyboard::Scancode::International1 => Scancode::International1, sdl2::keyboard::Scancode::International1 => Scancode::International1,
sdl2::keyboard::Scancode::International2 => Scancode::International2, sdl2::keyboard::Scancode::International2 => Scancode::International2,
sdl2::keyboard::Scancode::International3 => Scancode::International3, sdl2::keyboard::Scancode::International3 => Scancode::International3,
sdl2::keyboard::Scancode::International4 => Scancode::International4, sdl2::keyboard::Scancode::International4 => Scancode::International4,
sdl2::keyboard::Scancode::International5 => Scancode::International5, sdl2::keyboard::Scancode::International5 => Scancode::International5,
sdl2::keyboard::Scancode::International6 => Scancode::International6, sdl2::keyboard::Scancode::International6 => Scancode::International6,
sdl2::keyboard::Scancode::International7 => Scancode::International7, sdl2::keyboard::Scancode::International7 => Scancode::International7,
sdl2::keyboard::Scancode::International8 => Scancode::International8, sdl2::keyboard::Scancode::International8 => Scancode::International8,
sdl2::keyboard::Scancode::International9 => Scancode::International9, sdl2::keyboard::Scancode::International9 => Scancode::International9,
sdl2::keyboard::Scancode::Lang1 => Scancode::Lang1, sdl2::keyboard::Scancode::Lang1 => Scancode::Lang1,
sdl2::keyboard::Scancode::Lang2 => Scancode::Lang2, sdl2::keyboard::Scancode::Lang2 => Scancode::Lang2,
sdl2::keyboard::Scancode::Lang3 => Scancode::Lang3, sdl2::keyboard::Scancode::Lang3 => Scancode::Lang3,
sdl2::keyboard::Scancode::Lang4 => Scancode::Lang4, sdl2::keyboard::Scancode::Lang4 => Scancode::Lang4,
sdl2::keyboard::Scancode::Lang5 => Scancode::Lang5, sdl2::keyboard::Scancode::Lang5 => Scancode::Lang5,
sdl2::keyboard::Scancode::Lang6 => Scancode::Lang6, sdl2::keyboard::Scancode::Lang6 => Scancode::Lang6,
sdl2::keyboard::Scancode::Lang7 => Scancode::Lang7, sdl2::keyboard::Scancode::Lang7 => Scancode::Lang7,
sdl2::keyboard::Scancode::Lang8 => Scancode::Lang8, sdl2::keyboard::Scancode::Lang8 => Scancode::Lang8,
sdl2::keyboard::Scancode::Lang9 => Scancode::Lang9, sdl2::keyboard::Scancode::Lang9 => Scancode::Lang9,
sdl2::keyboard::Scancode::AltErase => Scancode::AltErase, sdl2::keyboard::Scancode::AltErase => Scancode::AltErase,
sdl2::keyboard::Scancode::SysReq => Scancode::SysReq, sdl2::keyboard::Scancode::SysReq => Scancode::SysReq,
sdl2::keyboard::Scancode::Cancel => Scancode::Cancel, sdl2::keyboard::Scancode::Cancel => Scancode::Cancel,
sdl2::keyboard::Scancode::Clear => Scancode::Clear, sdl2::keyboard::Scancode::Clear => Scancode::Clear,
sdl2::keyboard::Scancode::Prior => Scancode::Prior, sdl2::keyboard::Scancode::Prior => Scancode::Prior,
sdl2::keyboard::Scancode::Return2 => Scancode::Return2, sdl2::keyboard::Scancode::Return2 => Scancode::Return2,
sdl2::keyboard::Scancode::Separator => Scancode::Separator, sdl2::keyboard::Scancode::Separator => Scancode::Separator,
sdl2::keyboard::Scancode::Out => Scancode::Out, sdl2::keyboard::Scancode::Out => Scancode::Out,
sdl2::keyboard::Scancode::Oper => Scancode::Oper, sdl2::keyboard::Scancode::Oper => Scancode::Oper,
sdl2::keyboard::Scancode::ClearAgain => Scancode::ClearAgain, sdl2::keyboard::Scancode::ClearAgain => Scancode::ClearAgain,
sdl2::keyboard::Scancode::CrSel => Scancode::CrSel, sdl2::keyboard::Scancode::CrSel => Scancode::CrSel,
sdl2::keyboard::Scancode::ExSel => Scancode::ExSel, sdl2::keyboard::Scancode::ExSel => Scancode::ExSel,
sdl2::keyboard::Scancode::Kp00 => Scancode::Kp00, sdl2::keyboard::Scancode::Kp00 => Scancode::Kp00,
sdl2::keyboard::Scancode::Kp000 => Scancode::Kp000, sdl2::keyboard::Scancode::Kp000 => Scancode::Kp000,
sdl2::keyboard::Scancode::ThousandsSeparator => Scancode::ThousandsSeparator, sdl2::keyboard::Scancode::ThousandsSeparator => Scancode::ThousandsSeparator,
sdl2::keyboard::Scancode::DecimalSeparator => Scancode::DecimalSeparator, sdl2::keyboard::Scancode::DecimalSeparator => Scancode::DecimalSeparator,
sdl2::keyboard::Scancode::CurrencyUnit => Scancode::CurrencyUnit, sdl2::keyboard::Scancode::CurrencyUnit => Scancode::CurrencyUnit,
sdl2::keyboard::Scancode::CurrencySubUnit => Scancode::CurrencySubUnit, sdl2::keyboard::Scancode::CurrencySubUnit => Scancode::CurrencySubUnit,
sdl2::keyboard::Scancode::KpLeftParen => Scancode::KpLeftParen, sdl2::keyboard::Scancode::KpLeftParen => Scancode::KpLeftParen,
sdl2::keyboard::Scancode::KpRightParen => Scancode::KpRightParen, sdl2::keyboard::Scancode::KpRightParen => Scancode::KpRightParen,
sdl2::keyboard::Scancode::KpLeftBrace => Scancode::KpLeftBrace, sdl2::keyboard::Scancode::KpLeftBrace => Scancode::KpLeftBrace,
sdl2::keyboard::Scancode::KpRightBrace => Scancode::KpRightBrace, sdl2::keyboard::Scancode::KpRightBrace => Scancode::KpRightBrace,
sdl2::keyboard::Scancode::KpTab => Scancode::KpTab, sdl2::keyboard::Scancode::KpTab => Scancode::KpTab,
sdl2::keyboard::Scancode::KpBackspace => Scancode::KpBackspace, sdl2::keyboard::Scancode::KpBackspace => Scancode::KpBackspace,
sdl2::keyboard::Scancode::KpA => Scancode::KpA, sdl2::keyboard::Scancode::KpA => Scancode::KpA,
sdl2::keyboard::Scancode::KpB => Scancode::KpB, sdl2::keyboard::Scancode::KpB => Scancode::KpB,
sdl2::keyboard::Scancode::KpC => Scancode::KpC, sdl2::keyboard::Scancode::KpC => Scancode::KpC,
sdl2::keyboard::Scancode::KpD => Scancode::KpD, sdl2::keyboard::Scancode::KpD => Scancode::KpD,
sdl2::keyboard::Scancode::KpE => Scancode::KpE, sdl2::keyboard::Scancode::KpE => Scancode::KpE,
sdl2::keyboard::Scancode::KpF => Scancode::KpF, sdl2::keyboard::Scancode::KpF => Scancode::KpF,
sdl2::keyboard::Scancode::KpXor => Scancode::KpXor, sdl2::keyboard::Scancode::KpXor => Scancode::KpXor,
sdl2::keyboard::Scancode::KpPower => Scancode::KpPower, sdl2::keyboard::Scancode::KpPower => Scancode::KpPower,
sdl2::keyboard::Scancode::KpPercent => Scancode::KpPercent, sdl2::keyboard::Scancode::KpPercent => Scancode::KpPercent,
sdl2::keyboard::Scancode::KpLess => Scancode::KpLess, sdl2::keyboard::Scancode::KpLess => Scancode::KpLess,
sdl2::keyboard::Scancode::KpGreater => Scancode::KpGreater, sdl2::keyboard::Scancode::KpGreater => Scancode::KpGreater,
sdl2::keyboard::Scancode::KpAmpersand => Scancode::KpAmpersand, sdl2::keyboard::Scancode::KpAmpersand => Scancode::KpAmpersand,
sdl2::keyboard::Scancode::KpDblAmpersand => Scancode::KpDblAmpersand, sdl2::keyboard::Scancode::KpDblAmpersand => Scancode::KpDblAmpersand,
sdl2::keyboard::Scancode::KpVerticalBar => Scancode::KpVerticalBar, sdl2::keyboard::Scancode::KpVerticalBar => Scancode::KpVerticalBar,
sdl2::keyboard::Scancode::KpDblVerticalBar => Scancode::KpDblVerticalBar, sdl2::keyboard::Scancode::KpDblVerticalBar => Scancode::KpDblVerticalBar,
sdl2::keyboard::Scancode::KpColon => Scancode::KpColon, sdl2::keyboard::Scancode::KpColon => Scancode::KpColon,
sdl2::keyboard::Scancode::KpHash => Scancode::KpHash, sdl2::keyboard::Scancode::KpHash => Scancode::KpHash,
sdl2::keyboard::Scancode::KpSpace => Scancode::KpSpace, sdl2::keyboard::Scancode::KpSpace => Scancode::KpSpace,
sdl2::keyboard::Scancode::KpAt => Scancode::KpAt, sdl2::keyboard::Scancode::KpAt => Scancode::KpAt,
sdl2::keyboard::Scancode::KpExclam => Scancode::KpExclam, sdl2::keyboard::Scancode::KpExclam => Scancode::KpExclam,
sdl2::keyboard::Scancode::KpMemStore => Scancode::KpMemStore, sdl2::keyboard::Scancode::KpMemStore => Scancode::KpMemStore,
sdl2::keyboard::Scancode::KpMemRecall => Scancode::KpMemRecall, sdl2::keyboard::Scancode::KpMemRecall => Scancode::KpMemRecall,
sdl2::keyboard::Scancode::KpMemClear => Scancode::KpMemClear, sdl2::keyboard::Scancode::KpMemClear => Scancode::KpMemClear,
sdl2::keyboard::Scancode::KpMemAdd => Scancode::KpMemAdd, sdl2::keyboard::Scancode::KpMemAdd => Scancode::KpMemAdd,
sdl2::keyboard::Scancode::KpMemSubtract => Scancode::KpMemSubtract, sdl2::keyboard::Scancode::KpMemSubtract => Scancode::KpMemSubtract,
sdl2::keyboard::Scancode::KpMemMultiply => Scancode::KpMemMultiply, sdl2::keyboard::Scancode::KpMemMultiply => Scancode::KpMemMultiply,
sdl2::keyboard::Scancode::KpMemDivide => Scancode::KpMemDivide, sdl2::keyboard::Scancode::KpMemDivide => Scancode::KpMemDivide,
sdl2::keyboard::Scancode::KpPlusMinus => Scancode::KpPlusMinus, sdl2::keyboard::Scancode::KpPlusMinus => Scancode::KpPlusMinus,
sdl2::keyboard::Scancode::KpClear => Scancode::KpClear, sdl2::keyboard::Scancode::KpClear => Scancode::KpClear,
sdl2::keyboard::Scancode::KpClearEntry => Scancode::KpClearEntry, sdl2::keyboard::Scancode::KpClearEntry => Scancode::KpClearEntry,
sdl2::keyboard::Scancode::KpBinary => Scancode::KpBinary, sdl2::keyboard::Scancode::KpBinary => Scancode::KpBinary,
sdl2::keyboard::Scancode::KpOctal => Scancode::KpOctal, sdl2::keyboard::Scancode::KpOctal => Scancode::KpOctal,
sdl2::keyboard::Scancode::KpDecimal => Scancode::KpDecimal, sdl2::keyboard::Scancode::KpDecimal => Scancode::KpDecimal,
sdl2::keyboard::Scancode::KpHexadecimal => Scancode::KpHexadecimal, sdl2::keyboard::Scancode::KpHexadecimal => Scancode::KpHexadecimal,
sdl2::keyboard::Scancode::LCtrl => Scancode::LCtrl, sdl2::keyboard::Scancode::LCtrl => Scancode::LCtrl,
sdl2::keyboard::Scancode::LShift => Scancode::LShift, sdl2::keyboard::Scancode::LShift => Scancode::LShift,
sdl2::keyboard::Scancode::LAlt => Scancode::LAlt, sdl2::keyboard::Scancode::LAlt => Scancode::LAlt,
sdl2::keyboard::Scancode::LGui => Scancode::LGui, sdl2::keyboard::Scancode::LGui => Scancode::LGui,
sdl2::keyboard::Scancode::RCtrl => Scancode::RCtrl, sdl2::keyboard::Scancode::RCtrl => Scancode::RCtrl,
sdl2::keyboard::Scancode::RShift => Scancode::RShift, sdl2::keyboard::Scancode::RShift => Scancode::RShift,
sdl2::keyboard::Scancode::RAlt => Scancode::RAlt, sdl2::keyboard::Scancode::RAlt => Scancode::RAlt,
sdl2::keyboard::Scancode::RGui => Scancode::RGui, sdl2::keyboard::Scancode::RGui => Scancode::RGui,
sdl2::keyboard::Scancode::Mode => Scancode::Mode, sdl2::keyboard::Scancode::Mode => Scancode::Mode,
sdl2::keyboard::Scancode::AudioNext => Scancode::AudioNext, sdl2::keyboard::Scancode::AudioNext => Scancode::AudioNext,
sdl2::keyboard::Scancode::AudioPrev => Scancode::AudioPrev, sdl2::keyboard::Scancode::AudioPrev => Scancode::AudioPrev,
sdl2::keyboard::Scancode::AudioStop => Scancode::AudioStop, sdl2::keyboard::Scancode::AudioStop => Scancode::AudioStop,
sdl2::keyboard::Scancode::AudioPlay => Scancode::AudioPlay, sdl2::keyboard::Scancode::AudioPlay => Scancode::AudioPlay,
sdl2::keyboard::Scancode::AudioMute => Scancode::AudioMute, sdl2::keyboard::Scancode::AudioMute => Scancode::AudioMute,
sdl2::keyboard::Scancode::MediaSelect => Scancode::MediaSelect, sdl2::keyboard::Scancode::MediaSelect => Scancode::MediaSelect,
sdl2::keyboard::Scancode::Www => Scancode::Www, sdl2::keyboard::Scancode::Www => Scancode::Www,
sdl2::keyboard::Scancode::Mail => Scancode::Mail, sdl2::keyboard::Scancode::Mail => Scancode::Mail,
sdl2::keyboard::Scancode::Calculator => Scancode::Calculator, sdl2::keyboard::Scancode::Calculator => Scancode::Calculator,
sdl2::keyboard::Scancode::Computer => Scancode::Computer, sdl2::keyboard::Scancode::Computer => Scancode::Computer,
sdl2::keyboard::Scancode::AcSearch => Scancode::AcSearch, sdl2::keyboard::Scancode::AcSearch => Scancode::AcSearch,
sdl2::keyboard::Scancode::AcHome => Scancode::AcHome, sdl2::keyboard::Scancode::AcHome => Scancode::AcHome,
sdl2::keyboard::Scancode::AcBack => Scancode::AcBack, sdl2::keyboard::Scancode::AcBack => Scancode::AcBack,
sdl2::keyboard::Scancode::AcForward => Scancode::AcForward, sdl2::keyboard::Scancode::AcForward => Scancode::AcForward,
sdl2::keyboard::Scancode::AcStop => Scancode::AcStop, sdl2::keyboard::Scancode::AcStop => Scancode::AcStop,
sdl2::keyboard::Scancode::AcRefresh => Scancode::AcRefresh, sdl2::keyboard::Scancode::AcRefresh => Scancode::AcRefresh,
sdl2::keyboard::Scancode::AcBookmarks => Scancode::AcBookmarks, sdl2::keyboard::Scancode::AcBookmarks => Scancode::AcBookmarks,
sdl2::keyboard::Scancode::BrightnessDown => Scancode::BrightnessDown, sdl2::keyboard::Scancode::BrightnessDown => Scancode::BrightnessDown,
sdl2::keyboard::Scancode::BrightnessUp => Scancode::BrightnessUp, sdl2::keyboard::Scancode::BrightnessUp => Scancode::BrightnessUp,
sdl2::keyboard::Scancode::DisplaySwitch => Scancode::DisplaySwitch, sdl2::keyboard::Scancode::DisplaySwitch => Scancode::DisplaySwitch,
sdl2::keyboard::Scancode::KbdIllumToggle => Scancode::KbdIllumToggle, sdl2::keyboard::Scancode::KbdIllumToggle => Scancode::KbdIllumToggle,
sdl2::keyboard::Scancode::KbdIllumDown => Scancode::KbdIllumDown, sdl2::keyboard::Scancode::KbdIllumDown => Scancode::KbdIllumDown,
sdl2::keyboard::Scancode::KbdIllumUp => Scancode::KbdIllumUp, sdl2::keyboard::Scancode::KbdIllumUp => Scancode::KbdIllumUp,
sdl2::keyboard::Scancode::Eject => Scancode::Eject, sdl2::keyboard::Scancode::Eject => Scancode::Eject,
sdl2::keyboard::Scancode::Sleep => Scancode::Sleep, sdl2::keyboard::Scancode::Sleep => Scancode::Sleep,
sdl2::keyboard::Scancode::App1 => Scancode::App1, sdl2::keyboard::Scancode::App1 => Scancode::App1,
sdl2::keyboard::Scancode::App2 => Scancode::App2, sdl2::keyboard::Scancode::App2 => Scancode::App2,
sdl2::keyboard::Scancode::Num => Scancode::Num, sdl2::keyboard::Scancode::Num => Scancode::Num,
} }
} }
} }

View file

@ -5,41 +5,41 @@ pub mod mouse;
#[derive(Clone, Copy, Eq, PartialEq, Debug)] #[derive(Clone, Copy, Eq, PartialEq, Debug)]
pub enum ButtonState { pub enum ButtonState {
Idle, Idle,
Pressed, Pressed,
Held, Held,
Released, Released,
} }
/// Common trait for input device implementations. /// Common trait for input device implementations.
pub trait InputDevice { pub trait InputDevice {
/// Performs internal house-keeping necessary for properly reporting the current state of this /// Performs internal house-keeping necessary for properly reporting the current state of this
/// input device. Normally this should be called on the device before all of this frame's /// input device. Normally this should be called on the device before all of this frame's
/// input events have been processed via `handle_event`. /// input events have been processed via `handle_event`.
fn update(&mut self); fn update(&mut self);
} }
/// Container for all available input devices available for applications to use. /// Container for all available input devices available for applications to use.
pub struct InputDevices { pub struct InputDevices {
pub keyboard: keyboard::Keyboard, pub keyboard: keyboard::Keyboard,
pub mouse: mouse::Mouse, pub mouse: mouse::Mouse,
} }
impl InputDevice for InputDevices { impl InputDevice for InputDevices {
fn update(&mut self) { fn update(&mut self) {
self.keyboard.update(); self.keyboard.update();
self.mouse.update(); self.mouse.update();
} }
} }
impl SystemEventHandler for InputDevices { impl SystemEventHandler for InputDevices {
fn handle_event(&mut self, event: &SystemEvent) -> bool { fn handle_event(&mut self, event: &SystemEvent) -> bool {
if self.keyboard.handle_event(event) { if self.keyboard.handle_event(event) {
return true; return true;
} }
if self.mouse.handle_event(event) { if self.mouse.handle_event(event) {
return true; return true;
} }
false false
} }
} }

View file

@ -13,23 +13,23 @@ bitflags! {
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
#[repr(u8)] #[repr(u8)]
pub enum MouseButton { pub enum MouseButton {
Unknown = 0, Unknown = 0,
Left = sdl2::mouse::MouseButton::Left as u8, Left = sdl2::mouse::MouseButton::Left as u8,
Middle = sdl2::mouse::MouseButton::Middle as u8, Middle = sdl2::mouse::MouseButton::Middle as u8,
Right = sdl2::mouse::MouseButton::Right as u8, Right = sdl2::mouse::MouseButton::Right as u8,
X1 = sdl2::mouse::MouseButton::X1 as u8, X1 = sdl2::mouse::MouseButton::X1 as u8,
X2 = sdl2::mouse::MouseButton::X2 as u8, X2 = sdl2::mouse::MouseButton::X2 as u8,
} }
impl From<sdl2::mouse::MouseButton> for MouseButton { impl From<sdl2::mouse::MouseButton> for MouseButton {
fn from(value: sdl2::mouse::MouseButton) -> Self { fn from(value: sdl2::mouse::MouseButton) -> Self {
match value { match value {
sdl2::mouse::MouseButton::Unknown => MouseButton::Unknown, sdl2::mouse::MouseButton::Unknown => MouseButton::Unknown,
sdl2::mouse::MouseButton::Left => MouseButton::Left, sdl2::mouse::MouseButton::Left => MouseButton::Left,
sdl2::mouse::MouseButton::Middle => MouseButton::Middle, sdl2::mouse::MouseButton::Middle => MouseButton::Middle,
sdl2::mouse::MouseButton::Right => MouseButton::Right, sdl2::mouse::MouseButton::Right => MouseButton::Right,
sdl2::mouse::MouseButton::X1 => MouseButton::X1, sdl2::mouse::MouseButton::X1 => MouseButton::X1,
sdl2::mouse::MouseButton::X2 => MouseButton::X2 sdl2::mouse::MouseButton::X2 => MouseButton::X2
} }
} }
} }

View file

@ -17,22 +17,22 @@ const DEFAULT_MOUSE_CURSOR_HEIGHT: usize = 16;
#[rustfmt::skip] #[rustfmt::skip]
const DEFAULT_MOUSE_CURSOR: [u8; DEFAULT_MOUSE_CURSOR_WIDTH * DEFAULT_MOUSE_CURSOR_HEIGHT] = [ const DEFAULT_MOUSE_CURSOR: [u8; DEFAULT_MOUSE_CURSOR_WIDTH * DEFAULT_MOUSE_CURSOR_HEIGHT] = [
0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00,0x0f,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 0x00, 0x0f, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00,0x0f,0x0f,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 0x00, 0x0f, 0x0f, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00,0x0f,0x0f,0x0f,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 0x00, 0x0f, 0x0f, 0x0f, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00,0x0f,0x0f,0x0f,0x0f,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 0x00, 0x0f, 0x0f, 0x0f, 0x0f, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00,0x0f,0x0f,0x0f,0x0f,0x0f,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 0x00, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 0x00, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 0x00, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x00,0xff,0xff,0xff,0xff,0xff,0xff, 0x00, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00,0x0f,0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff, 0x00, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00,0x0f,0x0f,0x00,0x0f,0x0f,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 0x00, 0x0f, 0x0f, 0x00, 0x0f, 0x0f, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00,0x0f,0x00,0x00,0x00,0x0f,0x0f,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x0f, 0x0f, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00,0x00,0xff,0xff,0x00,0x0f,0x0f,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x0f, 0x0f, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff,0xff,0xff,0xff,0xff,0x00,0x0f,0x0f,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x0f, 0x0f, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff,0xff,0xff,0xff,0xff,0x00,0x0f,0x0f,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x0f, 0x0f, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
]; ];
/// Holds the current state of the mouse. /// Holds the current state of the mouse.
@ -44,287 +44,287 @@ const DEFAULT_MOUSE_CURSOR: [u8; DEFAULT_MOUSE_CURSOR_WIDTH * DEFAULT_MOUSE_CURS
/// [`System`]: crate::System /// [`System`]: crate::System
#[derive(Debug)] #[derive(Debug)]
pub struct Mouse { pub struct Mouse {
x: i32, x: i32,
y: i32, y: i32,
x_delta: i32, x_delta: i32,
y_delta: i32, y_delta: i32,
buttons: [ButtonState; MAX_BUTTONS], buttons: [ButtonState; MAX_BUTTONS],
cursor: Bitmap, cursor: Bitmap,
cursor_background: Bitmap, cursor_background: Bitmap,
cursor_hotspot_x: u32, cursor_hotspot_x: u32,
cursor_hotspot_y: u32, cursor_hotspot_y: u32,
cursor_enabled: bool, cursor_enabled: bool,
} }
impl Mouse { impl Mouse {
pub fn new() -> Mouse { pub fn new() -> Mouse {
let (cursor, cursor_background, cursor_hotspot_x, cursor_hotspot_y) = let (cursor, cursor_background, cursor_hotspot_x, cursor_hotspot_y) =
Self::get_default_mouse_cursor(); Self::get_default_mouse_cursor();
Mouse { Mouse {
x: 0, x: 0,
y: 0, y: 0,
x_delta: 0, x_delta: 0,
y_delta: 0, y_delta: 0,
buttons: [ButtonState::Idle; MAX_BUTTONS], buttons: [ButtonState::Idle; MAX_BUTTONS],
cursor, cursor,
cursor_background, cursor_background,
cursor_hotspot_x, cursor_hotspot_x,
cursor_hotspot_y, cursor_hotspot_y,
cursor_enabled: false, cursor_enabled: false,
} }
} }
/// Returns the current x coordinate of the mouse cursor. /// Returns the current x coordinate of the mouse cursor.
#[inline] #[inline]
pub fn x(&self) -> i32 { pub fn x(&self) -> i32 {
self.x self.x
} }
/// Returns the current y coordinate of the mouse cursor. /// Returns the current y coordinate of the mouse cursor.
#[inline] #[inline]
pub fn y(&self) -> i32 { pub fn y(&self) -> i32 {
self.y self.y
} }
/// Returns the amount of pixels along the x-axis that the mouse cursor moved since the last /// Returns the amount of pixels along the x-axis that the mouse cursor moved since the last
/// time that the mouse state was updated. /// time that the mouse state was updated.
#[inline] #[inline]
pub fn x_delta(&self) -> i32 { pub fn x_delta(&self) -> i32 {
self.x_delta self.x_delta
} }
/// Returns the amount of pixels along the y-axis that the mouse cursor moved since the last /// Returns the amount of pixels along the y-axis that the mouse cursor moved since the last
/// time that the mouse state was updated. /// time that the mouse state was updated.
#[inline] #[inline]
pub fn y_delta(&self) -> i32 { pub fn y_delta(&self) -> i32 {
self.y_delta self.y_delta
} }
/// Returns true if the given button was just pressed or is being held down. /// Returns true if the given button was just pressed or is being held down.
#[inline] #[inline]
pub fn is_button_down(&self, button: usize) -> bool { pub fn is_button_down(&self, button: usize) -> bool {
matches!( matches!(
self.buttons[button], self.buttons[button],
ButtonState::Pressed | ButtonState::Held ButtonState::Pressed | ButtonState::Held
) )
} }
/// Returns true if the given button was not just pressed and is not being held down. /// Returns true if the given button was not just pressed and is not being held down.
#[inline] #[inline]
pub fn is_button_up(&self, button: usize) -> bool { pub fn is_button_up(&self, button: usize) -> bool {
matches!( matches!(
self.buttons[button], self.buttons[button],
ButtonState::Released | ButtonState::Idle ButtonState::Released | ButtonState::Idle
) )
} }
/// Returns true if the given button was just pressed (not being held down, yet). /// Returns true if the given button was just pressed (not being held down, yet).
#[inline] #[inline]
pub fn is_button_pressed(&self, button: usize) -> bool { pub fn is_button_pressed(&self, button: usize) -> bool {
self.buttons[button] == ButtonState::Pressed self.buttons[button] == ButtonState::Pressed
} }
/// Returns true if the given button was just released. /// Returns true if the given button was just released.
#[inline] #[inline]
pub fn is_button_released(&self, button: usize) -> bool { pub fn is_button_released(&self, button: usize) -> bool {
self.buttons[button] == ButtonState::Released self.buttons[button] == ButtonState::Released
} }
/// Returns a reference to the current mouse cursor bitmap. /// Returns a reference to the current mouse cursor bitmap.
#[inline] #[inline]
pub fn cursor_bitmap(&self) -> &Bitmap { pub fn cursor_bitmap(&self) -> &Bitmap {
&self.cursor &self.cursor
} }
/// Returns the current mouse cursor's "hotspot" x coordinate. /// Returns the current mouse cursor's "hotspot" x coordinate.
#[inline] #[inline]
pub fn cursor_hotspot_x(&self) -> u32 { pub fn cursor_hotspot_x(&self) -> u32 {
self.cursor_hotspot_x self.cursor_hotspot_x
} }
/// Returns the current mouse cursor's "hotspot" y coordinate. /// Returns the current mouse cursor's "hotspot" y coordinate.
#[inline] #[inline]
pub fn cursor_hotspot_y(&self) -> u32 { pub fn cursor_hotspot_y(&self) -> u32 {
self.cursor_hotspot_y self.cursor_hotspot_y
} }
/// Returns true if mouse cursor bitmap rendering is enabled. /// Returns true if mouse cursor bitmap rendering is enabled.
#[inline] #[inline]
pub fn is_cursor_enabled(&self) -> bool { pub fn is_cursor_enabled(&self) -> bool {
self.cursor_enabled self.cursor_enabled
} }
/// Enables or disables mouse cursor bitmap rendering. /// Enables or disables mouse cursor bitmap rendering.
#[inline] #[inline]
pub fn enable_cursor(&mut self, enable: bool) { pub fn enable_cursor(&mut self, enable: bool) {
self.cursor_enabled = enable; self.cursor_enabled = enable;
} }
/// Sets the [`Bitmap`] used to display the mouse cursor and the "hotspot" coordinate. The /// Sets the [`Bitmap`] used to display the mouse cursor and the "hotspot" coordinate. The
/// bitmap provided here should be set up to use color 255 as the transparent color. /// bitmap provided here should be set up to use color 255 as the transparent color.
/// ///
/// # Arguments /// # Arguments
/// ///
/// * `cursor`: the bitmap to be used to display the mouse cursor on screen /// * `cursor`: the bitmap to be used to display the mouse cursor on screen
/// * `hotspot_x`: the "hotspot" x coordinate /// * `hotspot_x`: the "hotspot" x coordinate
/// * `hotspot_y`: the "hotspot" y coordinate. /// * `hotspot_y`: the "hotspot" y coordinate.
pub fn set_mouse_cursor(&mut self, cursor: Bitmap, hotspot_x: u32, hotspot_y: u32) { pub fn set_mouse_cursor(&mut self, cursor: Bitmap, hotspot_x: u32, hotspot_y: u32) {
self.cursor = cursor; self.cursor = cursor;
self.cursor_background = Bitmap::new(self.cursor.width(), self.cursor.height()).unwrap(); self.cursor_background = Bitmap::new(self.cursor.width(), self.cursor.height()).unwrap();
self.cursor_hotspot_x = hotspot_x; self.cursor_hotspot_x = hotspot_x;
self.cursor_hotspot_y = hotspot_y; self.cursor_hotspot_y = hotspot_y;
} }
/// Resets the mouse cursor bitmap and "hotspot" coordinate back to the default settings. /// Resets the mouse cursor bitmap and "hotspot" coordinate back to the default settings.
pub fn set_default_mouse_cursor(&mut self) { pub fn set_default_mouse_cursor(&mut self) {
let (cursor, background, hotspot_x, hotspot_y) = Self::get_default_mouse_cursor(); let (cursor, background, hotspot_x, hotspot_y) = Self::get_default_mouse_cursor();
self.cursor = cursor; self.cursor = cursor;
self.cursor_background = background; self.cursor_background = background;
self.cursor_hotspot_x = hotspot_x; self.cursor_hotspot_x = hotspot_x;
self.cursor_hotspot_y = hotspot_y; self.cursor_hotspot_y = hotspot_y;
} }
fn get_default_mouse_cursor() -> (Bitmap, Bitmap, u32, u32) { fn get_default_mouse_cursor() -> (Bitmap, Bitmap, u32, u32) {
let mut cursor = Bitmap::new( let mut cursor = Bitmap::new(
DEFAULT_MOUSE_CURSOR_WIDTH as u32, DEFAULT_MOUSE_CURSOR_WIDTH as u32,
DEFAULT_MOUSE_CURSOR_HEIGHT as u32, DEFAULT_MOUSE_CURSOR_HEIGHT as u32,
) )
.unwrap(); .unwrap();
cursor.pixels_mut().copy_from_slice(&DEFAULT_MOUSE_CURSOR); cursor.pixels_mut().copy_from_slice(&DEFAULT_MOUSE_CURSOR);
let cursor_background = Bitmap::new(cursor.width(), cursor.height()).unwrap(); let cursor_background = Bitmap::new(cursor.width(), cursor.height()).unwrap();
( (
cursor, cursor,
cursor_background, cursor_background,
DEFAULT_MOUSE_CURSOR_HOTSPOT_X, DEFAULT_MOUSE_CURSOR_HOTSPOT_X,
DEFAULT_MOUSE_CURSOR_HOTSPOT_Y, DEFAULT_MOUSE_CURSOR_HOTSPOT_Y,
) )
} }
#[inline] #[inline]
fn get_cursor_render_position(&self) -> (i32, i32) { fn get_cursor_render_position(&self) -> (i32, i32) {
( (
self.x - self.cursor_hotspot_x as i32, self.x - self.cursor_hotspot_x as i32,
self.y - self.cursor_hotspot_y as i32, self.y - self.cursor_hotspot_y as i32,
) )
} }
/// Renders the mouse cursor bitmap onto the destination bitmap at the mouse's current /// Renders the mouse cursor bitmap onto the destination bitmap at the mouse's current
/// position. The destination bitmap specified is assumed to be the [`System`]'s video /// position. The destination bitmap specified is assumed to be the [`System`]'s video
/// backbuffer bitmap. The background on the destination bitmap is saved internally and a /// backbuffer bitmap. The background on the destination bitmap is saved internally and a
/// subsequent call to [`Mouse::hide_cursor`] will restore the background. /// subsequent call to [`Mouse::hide_cursor`] will restore the background.
/// ///
/// If mouse cursor rendering is not currently enabled, this method does nothing. /// If mouse cursor rendering is not currently enabled, this method does nothing.
/// ///
/// Applications will not normally need to call this method, as if mouse cursor rendering is /// Applications will not normally need to call this method, as if mouse cursor rendering is
/// enabled, this will be automatically handled by [`System::display`]. /// enabled, this will be automatically handled by [`System::display`].
/// ///
/// [`System`]: crate::System /// [`System`]: crate::System
/// [`System::display`]: crate::System::display /// [`System::display`]: crate::System::display
pub fn render_cursor(&mut self, dest: &mut Bitmap) { pub fn render_cursor(&mut self, dest: &mut Bitmap) {
if !self.cursor_enabled { if !self.cursor_enabled {
return; return;
} }
let (x, y) = self.get_cursor_render_position(); let (x, y) = self.get_cursor_render_position();
// preserve existing background first // preserve existing background first
self.cursor_background.blit_region( self.cursor_background.blit_region(
BlitMethod::Solid, BlitMethod::Solid,
&dest, &dest,
&Rect::new(x, y, self.cursor.width(), self.cursor.height()), &Rect::new(x, y, self.cursor.width(), self.cursor.height()),
0, 0,
0, 0,
); );
dest.blit(BlitMethod::Transparent(255), &self.cursor, x, y); dest.blit(BlitMethod::Transparent(255), &self.cursor, x, y);
} }
/// Restores the original destination bitmap contents where the mouse cursor bitmap was /// Restores the original destination bitmap contents where the mouse cursor bitmap was
/// rendered to during the previous call to [`Mouse::render_cursor`]. The destination bitmap /// rendered to during the previous call to [`Mouse::render_cursor`]. The destination bitmap
/// specified is assumed to be the [`System`]'s video backbuffer bitmap. /// specified is assumed to be the [`System`]'s video backbuffer bitmap.
/// ///
/// If mouse cursor rendering is not currently enabled, this method does nothing. /// If mouse cursor rendering is not currently enabled, this method does nothing.
/// ///
/// Applications will not normally need to call this method, as if mouse cursor rendering is /// Applications will not normally need to call this method, as if mouse cursor rendering is
/// enabled, this will be automatically handled by [`System::display`]. /// enabled, this will be automatically handled by [`System::display`].
/// ///
/// [`System`]: crate::System /// [`System`]: crate::System
/// [`System::display`]: crate::System::display /// [`System::display`]: crate::System::display
pub fn hide_cursor(&mut self, dest: &mut Bitmap) { pub fn hide_cursor(&mut self, dest: &mut Bitmap) {
if !self.cursor_enabled { if !self.cursor_enabled {
return; return;
} }
let (x, y) = self.get_cursor_render_position(); let (x, y) = self.get_cursor_render_position();
dest.blit(BlitMethod::Solid, &self.cursor_background, x, y); dest.blit(BlitMethod::Solid, &self.cursor_background, x, y);
} }
fn update_button_state(&mut self, button: u32, is_pressed: bool) { fn update_button_state(&mut self, button: u32, is_pressed: bool) {
let button_state = &mut self.buttons[button as usize]; let button_state = &mut self.buttons[button as usize];
*button_state = if is_pressed { *button_state = if is_pressed {
match *button_state { match *button_state {
ButtonState::Pressed => ButtonState::Held, ButtonState::Pressed => ButtonState::Held,
ButtonState::Held => ButtonState::Held, ButtonState::Held => ButtonState::Held,
_ => ButtonState::Pressed, _ => ButtonState::Pressed,
} }
} else { } else {
match *button_state { match *button_state {
ButtonState::Pressed | ButtonState::Held => ButtonState::Released, ButtonState::Pressed | ButtonState::Held => ButtonState::Released,
ButtonState::Released => ButtonState::Idle, ButtonState::Released => ButtonState::Idle,
ButtonState::Idle => ButtonState::Idle, ButtonState::Idle => ButtonState::Idle,
} }
} }
} }
} }
impl InputDevice for Mouse { impl InputDevice for Mouse {
fn update(&mut self) { fn update(&mut self) {
self.x_delta = 0; self.x_delta = 0;
self.y_delta = 0; self.y_delta = 0;
for state in self.buttons.iter_mut() { for state in self.buttons.iter_mut() {
*state = match *state { *state = match *state {
ButtonState::Pressed => ButtonState::Held, ButtonState::Pressed => ButtonState::Held,
ButtonState::Released => ButtonState::Idle, ButtonState::Released => ButtonState::Idle,
otherwise => otherwise, otherwise => otherwise,
} }
} }
} }
} }
impl SystemEventHandler for Mouse { impl SystemEventHandler for Mouse {
fn handle_event(&mut self, event: &SystemEvent) -> bool { fn handle_event(&mut self, event: &SystemEvent) -> bool {
match event { match event {
SystemEvent::Mouse(MouseEvent::MouseMotion { SystemEvent::Mouse(MouseEvent::MouseMotion {
x, x,
y, y,
x_delta, x_delta,
y_delta, y_delta,
buttons, buttons,
}) => { }) => {
self.x = *x; self.x = *x;
self.y = *y; self.y = *y;
self.x_delta = *x_delta; self.x_delta = *x_delta;
self.y_delta = *y_delta; self.y_delta = *y_delta;
self.update_button_state(MouseButton::Left as u32, buttons.contains(MouseButtons::LEFT_BUTTON)); self.update_button_state(MouseButton::Left as u32, buttons.contains(MouseButtons::LEFT_BUTTON));
self.update_button_state(MouseButton::Middle as u32, buttons.contains(MouseButtons::MIDDLE_BUTTON)); self.update_button_state(MouseButton::Middle as u32, buttons.contains(MouseButtons::MIDDLE_BUTTON));
self.update_button_state(MouseButton::Right as u32, buttons.contains(MouseButtons::RIGHT_BUTTON)); self.update_button_state(MouseButton::Right as u32, buttons.contains(MouseButtons::RIGHT_BUTTON));
self.update_button_state(MouseButton::X1 as u32, buttons.contains(MouseButtons::X1)); self.update_button_state(MouseButton::X1 as u32, buttons.contains(MouseButtons::X1));
self.update_button_state(MouseButton::X2 as u32, buttons.contains(MouseButtons::X2)); self.update_button_state(MouseButton::X2 as u32, buttons.contains(MouseButtons::X2));
true true
} }
SystemEvent::Mouse(MouseEvent::MouseButtonDown { button, .. }) => { SystemEvent::Mouse(MouseEvent::MouseButtonDown { button, .. }) => {
self.update_button_state(*button as u32, true); self.update_button_state(*button as u32, true);
true true
} }
SystemEvent::Mouse(MouseEvent::MouseButtonUp { button, .. }) => { SystemEvent::Mouse(MouseEvent::MouseButtonUp { button, .. }) => {
self.update_button_state(*button as u32, false); self.update_button_state(*button as u32, false);
true true
} }
_ => false, _ => false,
} }
} }
} }

View file

@ -20,329 +20,329 @@ pub mod event;
pub mod input_devices; pub mod input_devices;
fn is_x11_compositor_skipping_problematic() -> bool { fn is_x11_compositor_skipping_problematic() -> bool {
/* /*
this is _probably_ a bit of a hack. this is _probably_ a bit of a hack.
currently on linux systems, SDL2 (2.0.8+), tries to "skip" (disable) the X11 server currently on linux systems, SDL2 (2.0.8+), tries to "skip" (disable) the X11 server
compositor when starting up. this is to reduce/remove any added latency from the SDL program compositor when starting up. this is to reduce/remove any added latency from the SDL program
that is usually introduced by the compositor when it is enabled for the window. if SDL did that is usually introduced by the compositor when it is enabled for the window. if SDL did
disable the compositor in this manner, it will re-enable it when SDL shuts down. the disable the compositor in this manner, it will re-enable it when SDL shuts down. the
intention is for the compositor to be disabled for just the SDL window(s) only and to affect intention is for the compositor to be disabled for just the SDL window(s) only and to affect
nothing else running concurrently. nothing else running concurrently.
this works great for several desktop environments, but it unfortunately has a global effect this works great for several desktop environments, but it unfortunately has a global effect
on KDE/Kwin, where users may notice a visible screen flicker, other concurrently running on KDE/Kwin, where users may notice a visible screen flicker, other concurrently running
applications may exhibit visual artifacts/weirdness, and (all?) other application windows applications may exhibit visual artifacts/weirdness, and (all?) other application windows
while the SDL app is running will also have the compositor disabled for them too. while the SDL app is running will also have the compositor disabled for them too.
not great! this function is a quick, hacky, and probably-not-bullet-proof method to detect not great! this function is a quick, hacky, and probably-not-bullet-proof method to detect
if KDE/Kwin is the current desktop environment. in the future other known problem if KDE/Kwin is the current desktop environment. in the future other known problem
configurations could be added here and/or this could/should be updated with a better method configurations could be added here and/or this could/should be updated with a better method
to check for this. to check for this.
*/ */
match std::env::consts::OS { match std::env::consts::OS {
"linux"|"freebsd"|"netbsd"|"openbsd" => { "linux" | "freebsd" | "netbsd" | "openbsd" => {
match std::env::var("XDG_SESSION_DESKTOP") { match std::env::var("XDG_SESSION_DESKTOP") {
Ok(value) => value.eq_ignore_ascii_case("KDE"), Ok(value) => value.eq_ignore_ascii_case("KDE"),
Err(_) => false Err(_) => false
} }
}, }
_ => false, _ => false,
} }
} }
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum SystemError { pub enum SystemError {
#[error("System init error: {0}")] #[error("System init error: {0}")]
InitError(String), InitError(String),
#[error("System display error: {0}")] #[error("System display error: {0}")]
DisplayError(String), DisplayError(String),
#[error("System audio error: {0}")] #[error("System audio error: {0}")]
AudioError(#[from] crate::audio::AudioError), AudioError(#[from] crate::audio::AudioError),
} }
/// Builder for configuring and constructing an instance of [`System`]. /// Builder for configuring and constructing an instance of [`System`].
#[derive(Debug)] #[derive(Debug)]
pub struct SystemBuilder { pub struct SystemBuilder {
window_title: String, window_title: String,
vsync: bool, vsync: bool,
target_framerate: Option<u32>, target_framerate: Option<u32>,
initial_scale_factor: u32, initial_scale_factor: u32,
resizable: bool, resizable: bool,
show_mouse: bool, show_mouse: bool,
relative_mouse_scaling: bool, relative_mouse_scaling: bool,
integer_scaling: bool, integer_scaling: bool,
skip_x11_compositor: bool, skip_x11_compositor: bool,
} }
impl SystemBuilder { impl SystemBuilder {
/// Returns a new [`SystemBuilder`] with a default configuration. /// Returns a new [`SystemBuilder`] with a default configuration.
pub fn new() -> SystemBuilder { pub fn new() -> SystemBuilder {
SystemBuilder { SystemBuilder {
window_title: String::new(), window_title: String::new(),
vsync: false, vsync: false,
target_framerate: None, target_framerate: None,
initial_scale_factor: DEFAULT_SCALE_FACTOR, initial_scale_factor: DEFAULT_SCALE_FACTOR,
resizable: true, resizable: true,
show_mouse: false, show_mouse: false,
relative_mouse_scaling: true, relative_mouse_scaling: true,
integer_scaling: false, integer_scaling: false,
skip_x11_compositor: !is_x11_compositor_skipping_problematic(), skip_x11_compositor: !is_x11_compositor_skipping_problematic(),
} }
} }
/// Set the window title for the [`System`] to be built. /// Set the window title for the [`System`] to be built.
pub fn window_title(&mut self, window_title: &str) -> &mut SystemBuilder { pub fn window_title(&mut self, window_title: &str) -> &mut SystemBuilder {
self.window_title = window_title.to_string(); self.window_title = window_title.to_string();
self self
} }
/// Enables or disables V-Sync for the [`System`] to be built. Enabling V-sync automatically /// Enables or disables V-Sync for the [`System`] to be built. Enabling V-sync automatically
/// disables `target_framerate`. /// disables `target_framerate`.
pub fn vsync(&mut self, enable: bool) -> &mut SystemBuilder { pub fn vsync(&mut self, enable: bool) -> &mut SystemBuilder {
self.vsync = enable; self.vsync = enable;
self.target_framerate = None; self.target_framerate = None;
self self
} }
/// Sets a target framerate for the [`System`] being built to run at. This is intended to be /// Sets a target framerate for the [`System`] being built to run at. This is intended to be
/// used when V-sync is not desired, so setting a target framerate automatically disables /// used when V-sync is not desired, so setting a target framerate automatically disables
/// `vsync`. /// `vsync`.
pub fn target_framerate(&mut self, target_framerate: u32) -> &mut SystemBuilder { pub fn target_framerate(&mut self, target_framerate: u32) -> &mut SystemBuilder {
self.target_framerate = Some(target_framerate); self.target_framerate = Some(target_framerate);
self.vsync = false; self.vsync = false;
self self
} }
/// Sets an integer scaling factor for the [`System`] being built to up-scale the virtual /// Sets an integer scaling factor for the [`System`] being built to up-scale the virtual
/// framebuffer to when displaying it on screen. /// framebuffer to when displaying it on screen.
pub fn scale_factor(&mut self, scale_factor: u32) -> &mut SystemBuilder { pub fn scale_factor(&mut self, scale_factor: u32) -> &mut SystemBuilder {
self.initial_scale_factor = scale_factor; self.initial_scale_factor = scale_factor;
self self
} }
/// Sets whether the window will be resizable by the user for the [`System`] being built. /// Sets whether the window will be resizable by the user for the [`System`] being built.
pub fn resizable(&mut self, enable: bool) -> &mut SystemBuilder { pub fn resizable(&mut self, enable: bool) -> &mut SystemBuilder {
self.resizable = enable; self.resizable = enable;
self self
} }
/// Enables or disables mouse cursor display by the operating system when the cursor is over /// Enables or disables mouse cursor display by the operating system when the cursor is over
/// the window for the [`System`] being built. Disable this if you intend to render your own /// the window for the [`System`] being built. Disable this if you intend to render your own
/// custom mouse cursor. /// custom mouse cursor.
pub fn show_mouse(&mut self, enable: bool) -> &mut SystemBuilder { pub fn show_mouse(&mut self, enable: bool) -> &mut SystemBuilder {
self.show_mouse = enable; self.show_mouse = enable;
self self
} }
/// Enables or disables automatic DPI scaling of mouse relative movement values (delta values) /// Enables or disables automatic DPI scaling of mouse relative movement values (delta values)
/// available via the [`Mouse`] input device. /// available via the [`Mouse`] input device.
pub fn relative_mouse_scaling(&mut self, enable: bool) -> &mut SystemBuilder { pub fn relative_mouse_scaling(&mut self, enable: bool) -> &mut SystemBuilder {
self.relative_mouse_scaling = enable; self.relative_mouse_scaling = enable;
self self
} }
/// Enables or disables restricting the final rendered output to always be integer scaled, /// Enables or disables restricting the final rendered output to always be integer scaled,
/// even if that result will not fully fill the area of the window. /// even if that result will not fully fill the area of the window.
pub fn integer_scaling(&mut self, enable: bool) -> &mut SystemBuilder { pub fn integer_scaling(&mut self, enable: bool) -> &mut SystemBuilder {
self.integer_scaling = enable; self.integer_scaling = enable;
self self
} }
/// Enables or disables skipping the X11 server compositor on Linux systems only. This can be /// Enables or disables skipping the X11 server compositor on Linux systems only. This can be
/// set to manually control the underlying SDL hint that is used to control this setting. The /// set to manually control the underlying SDL hint that is used to control this setting. The
/// default setting that [`SystemBuilder`] configures is to follow the SDL default, except where /// default setting that [`SystemBuilder`] configures is to follow the SDL default, except where
/// the setting affects the system globally (in certain desktop environments, e.g. KDE/Kwin) /// the setting affects the system globally (in certain desktop environments, e.g. KDE/Kwin)
/// which may be undesired by end-users, at the cost of some additional input latency. /// which may be undesired by end-users, at the cost of some additional input latency.
pub fn skip_x11_compositor(&mut self, enable: bool) -> &mut SystemBuilder { pub fn skip_x11_compositor(&mut self, enable: bool) -> &mut SystemBuilder {
self.skip_x11_compositor = enable; self.skip_x11_compositor = enable;
self self
} }
/// Builds and returns a [`System`] from the current configuration. /// Builds and returns a [`System`] from the current configuration.
pub fn build(&self) -> Result<System, SystemError> { pub fn build(&self) -> Result<System, SystemError> {
// todo: maybe let this be customized in the future, or at least halved so a 160x120 mode can be available ... ? // todo: maybe let this be customized in the future, or at least halved so a 160x120 mode can be available ... ?
let screen_width = SCREEN_WIDTH; let screen_width = SCREEN_WIDTH;
let screen_height = SCREEN_HEIGHT; let screen_height = SCREEN_HEIGHT;
let texture_pixel_size = 4; // 32-bit ARGB format let texture_pixel_size = 4; // 32-bit ARGB format
sdl2::hint::set( sdl2::hint::set(
"SDL_MOUSE_RELATIVE_SCALING", "SDL_MOUSE_RELATIVE_SCALING",
if self.relative_mouse_scaling { if self.relative_mouse_scaling {
"1" "1"
} else { } else {
"0" "0"
}, },
); );
sdl2::hint::set( sdl2::hint::set(
"SDL_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR", "SDL_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR",
if self.skip_x11_compositor { if self.skip_x11_compositor {
"1" "1"
} else { } else {
"0" "0"
} },
); );
// build all the individual SDL subsystems // build all the individual SDL subsystems
let sdl_context = match sdl2::init() { let sdl_context = match sdl2::init() {
Ok(sdl_context) => sdl_context, Ok(sdl_context) => sdl_context,
Err(message) => return Err(SystemError::InitError(message)), Err(message) => return Err(SystemError::InitError(message)),
}; };
let sdl_timer_subsystem = match sdl_context.timer() { let sdl_timer_subsystem = match sdl_context.timer() {
Ok(timer_subsystem) => timer_subsystem, Ok(timer_subsystem) => timer_subsystem,
Err(message) => return Err(SystemError::InitError(message)), Err(message) => return Err(SystemError::InitError(message)),
}; };
let sdl_video_subsystem = match sdl_context.video() { let sdl_video_subsystem = match sdl_context.video() {
Ok(video_subsystem) => video_subsystem, Ok(video_subsystem) => video_subsystem,
Err(message) => return Err(SystemError::InitError(message)), Err(message) => return Err(SystemError::InitError(message)),
}; };
let sdl_event_pump = match sdl_context.event_pump() { let sdl_event_pump = match sdl_context.event_pump() {
Ok(event_pump) => event_pump, Ok(event_pump) => event_pump,
Err(message) => return Err(SystemError::InitError(message)), Err(message) => return Err(SystemError::InitError(message)),
}; };
let sdl_audio_subsystem = match sdl_context.audio() { let sdl_audio_subsystem = match sdl_context.audio() {
Ok(audio_subsystem) => audio_subsystem, Ok(audio_subsystem) => audio_subsystem,
Err(message) => return Err(SystemError::InitError(message)), Err(message) => return Err(SystemError::InitError(message)),
}; };
// create the window // create the window
let window_width = screen_width * self.initial_scale_factor; let window_width = screen_width * self.initial_scale_factor;
let window_height = screen_height * self.initial_scale_factor; let window_height = screen_height * self.initial_scale_factor;
let mut window_builder = &mut (sdl_video_subsystem.window( let mut window_builder = &mut (sdl_video_subsystem.window(
self.window_title.as_str(), self.window_title.as_str(),
window_width, window_width,
window_height, window_height,
)); ));
if self.resizable { if self.resizable {
window_builder = window_builder.resizable(); window_builder = window_builder.resizable();
} }
let sdl_window = match window_builder.build() { let sdl_window = match window_builder.build() {
Ok(window) => window, Ok(window) => window,
Err(error) => return Err(SystemError::InitError(error.to_string())), Err(error) => return Err(SystemError::InitError(error.to_string())),
}; };
sdl_context.mouse().show_cursor(self.show_mouse); sdl_context.mouse().show_cursor(self.show_mouse);
// turn the window into a canvas (under the hood, an SDL Renderer that owns the window) // turn the window into a canvas (under the hood, an SDL Renderer that owns the window)
let mut canvas_builder = sdl_window.into_canvas(); let mut canvas_builder = sdl_window.into_canvas();
if self.vsync { if self.vsync {
canvas_builder = canvas_builder.present_vsync(); canvas_builder = canvas_builder.present_vsync();
} }
let mut sdl_canvas = match canvas_builder.build() { let mut sdl_canvas = match canvas_builder.build() {
Ok(canvas) => canvas, Ok(canvas) => canvas,
Err(error) => return Err(SystemError::InitError(error.to_string())), Err(error) => return Err(SystemError::InitError(error.to_string())),
}; };
if let Err(error) = sdl_canvas.set_logical_size(screen_width, screen_height) { if let Err(error) = sdl_canvas.set_logical_size(screen_width, screen_height) {
return Err(SystemError::InitError(error.to_string())); return Err(SystemError::InitError(error.to_string()));
}; };
// TODO: newer versions of rust-sdl2 support this directly off the WindowCanvas struct // TODO: newer versions of rust-sdl2 support this directly off the WindowCanvas struct
unsafe { unsafe {
sdl2::sys::SDL_RenderSetIntegerScale( sdl2::sys::SDL_RenderSetIntegerScale(
sdl_canvas.raw(), sdl_canvas.raw(),
if self.integer_scaling { if self.integer_scaling {
sdl2::sys::SDL_bool::SDL_TRUE sdl2::sys::SDL_bool::SDL_TRUE
} else { } else {
sdl2::sys::SDL_bool::SDL_FALSE sdl2::sys::SDL_bool::SDL_FALSE
}, },
); );
} }
// create an SDL texture which we will be uploading to every frame to display the // create an SDL texture which we will be uploading to every frame to display the
// application's framebuffer // application's framebuffer
let sdl_texture = match sdl_canvas.create_texture_streaming( let sdl_texture = match sdl_canvas.create_texture_streaming(
Some(PixelFormatEnum::ARGB8888), Some(PixelFormatEnum::ARGB8888),
screen_width, screen_width,
screen_height, screen_height,
) { ) {
Ok(texture) => texture, Ok(texture) => texture,
Err(error) => return Err(SystemError::InitError(error.to_string())), Err(error) => return Err(SystemError::InitError(error.to_string())),
}; };
let sdl_texture_pitch = (sdl_texture.query().width * texture_pixel_size) as usize; let sdl_texture_pitch = (sdl_texture.query().width * texture_pixel_size) as usize;
// create a raw 32-bit RGBA buffer that will be used as the temporary source for // create a raw 32-bit RGBA buffer that will be used as the temporary source for
// SDL texture uploads each frame. necessary as applications are dealing with 8-bit indexed // SDL texture uploads each frame. necessary as applications are dealing with 8-bit indexed
// bitmaps, not 32-bit RGBA pixels, so this temporary buffer is where we convert the final // bitmaps, not 32-bit RGBA pixels, so this temporary buffer is where we convert the final
// application framebuffer to 32-bit RGBA pixels before it is uploaded to the SDL texture // application framebuffer to 32-bit RGBA pixels before it is uploaded to the SDL texture
let texture_pixels_size = (screen_width * screen_height * texture_pixel_size) as usize; let texture_pixels_size = (screen_width * screen_height * texture_pixel_size) as usize;
let texture_pixels = vec![0u32; texture_pixels_size].into_boxed_slice(); let texture_pixels = vec![0u32; texture_pixels_size].into_boxed_slice();
// create the Bitmap object that will be exposed to the application acting as the system // create the Bitmap object that will be exposed to the application acting as the system
// backbuffer // backbuffer
let framebuffer = match Bitmap::new(SCREEN_WIDTH, SCREEN_HEIGHT) { let framebuffer = match Bitmap::new(SCREEN_WIDTH, SCREEN_HEIGHT) {
Ok(bmp) => bmp, Ok(bmp) => bmp,
Err(error) => return Err(SystemError::InitError(error.to_string())), Err(error) => return Err(SystemError::InitError(error.to_string())),
}; };
// create the default palette, initialized to the VGA default palette. also exposed to the // create the default palette, initialized to the VGA default palette. also exposed to the
// application for manipulation // application for manipulation
let palette = match Palette::new_vga_palette() { let palette = match Palette::new_vga_palette() {
Ok(palette) => palette, Ok(palette) => palette,
Err(error) => return Err(SystemError::InitError(error.to_string())), Err(error) => return Err(SystemError::InitError(error.to_string())),
}; };
// create the default font, initialized to the VGA BIOS default font. // create the default font, initialized to the VGA BIOS default font.
let font = match BitmaskFont::new_vga_font() { let font = match BitmaskFont::new_vga_font() {
Ok(font) => font, Ok(font) => font,
Err(error) => return Err(SystemError::InitError(error.to_string())), Err(error) => return Err(SystemError::InitError(error.to_string())),
}; };
let audio_spec = AudioSpecDesired { let audio_spec = AudioSpecDesired {
freq: Some(TARGET_AUDIO_FREQUENCY as i32), freq: Some(TARGET_AUDIO_FREQUENCY as i32),
channels: Some(TARGET_AUDIO_CHANNELS), channels: Some(TARGET_AUDIO_CHANNELS),
samples: None, samples: None,
}; };
let mut audio = Audio::new(audio_spec, &sdl_audio_subsystem)?; let mut audio = Audio::new(audio_spec, &sdl_audio_subsystem)?;
audio.resume(); audio.resume();
let audio_queue = AudioQueue::new(&audio); let audio_queue = AudioQueue::new(&audio);
let event_pump = SystemEventPump::from(sdl_event_pump); let event_pump = SystemEventPump::from(sdl_event_pump);
// create input device objects, exposed to the application // create input device objects, exposed to the application
let keyboard = Keyboard::new(); let keyboard = Keyboard::new();
let mouse = Mouse::new(); let mouse = Mouse::new();
let input_devices = InputDevices { let input_devices = InputDevices {
keyboard, keyboard,
mouse, mouse,
}; };
Ok(System { Ok(System {
sdl_context, sdl_context,
sdl_audio_subsystem, sdl_audio_subsystem,
sdl_video_subsystem, sdl_video_subsystem,
sdl_timer_subsystem, sdl_timer_subsystem,
sdl_canvas, sdl_canvas,
sdl_texture, sdl_texture,
sdl_texture_pitch, sdl_texture_pitch,
texture_pixels, texture_pixels,
audio, audio,
audio_queue, audio_queue,
video: framebuffer, video: framebuffer,
palette, palette,
font, font,
input_devices, input_devices,
event_pump, event_pump,
target_framerate: self.target_framerate, target_framerate: self.target_framerate,
target_framerate_delta: None, target_framerate_delta: None,
next_tick: 0, next_tick: 0,
}) })
} }
} }
/// Holds all primary structures necessary for interacting with the operating system and for /// Holds all primary structures necessary for interacting with the operating system and for
@ -350,200 +350,200 @@ impl SystemBuilder {
/// "virtual machine" exposed by this library. /// "virtual machine" exposed by this library.
#[allow(dead_code)] #[allow(dead_code)]
pub struct System { pub struct System {
sdl_context: Sdl, sdl_context: Sdl,
sdl_audio_subsystem: AudioSubsystem, sdl_audio_subsystem: AudioSubsystem,
sdl_video_subsystem: VideoSubsystem, sdl_video_subsystem: VideoSubsystem,
sdl_timer_subsystem: TimerSubsystem, sdl_timer_subsystem: TimerSubsystem,
sdl_canvas: WindowCanvas, sdl_canvas: WindowCanvas,
sdl_texture: Texture, sdl_texture: Texture,
sdl_texture_pitch: usize, sdl_texture_pitch: usize,
texture_pixels: Box<[u32]>, texture_pixels: Box<[u32]>,
target_framerate: Option<u32>, target_framerate: Option<u32>,
target_framerate_delta: Option<i64>, target_framerate_delta: Option<i64>,
next_tick: i64, next_tick: i64,
/// An [`Audio`] instance that allows interacting with the system's audio output device. /// An [`Audio`] instance that allows interacting with the system's audio output device.
pub audio: Audio, pub audio: Audio,
/// An [`AudioQueue`] instance that can queue up playback/stop commands to be issued to the /// An [`AudioQueue`] instance that can queue up playback/stop commands to be issued to the
/// system's [`Audio`] instance a bit more flexibly. If you use this, your application must /// system's [`Audio`] instance a bit more flexibly. If you use this, your application must
/// manually call [`AudioQueue::apply`] or [`AudioQueue::apply_to_device`] in your loop to /// manually call [`AudioQueue::apply`] or [`AudioQueue::apply_to_device`] in your loop to
/// flush the queued commands, otherwise this queue will not do anything. /// flush the queued commands, otherwise this queue will not do anything.
pub audio_queue: AudioQueue, pub audio_queue: AudioQueue,
/// The primary backbuffer [`Bitmap`] that will be rendered to the screen whenever /// The primary backbuffer [`Bitmap`] that will be rendered to the screen whenever
/// [`System::display`] is called. Regardless of the actual window size, this bitmap is always /// [`System::display`] is called. Regardless of the actual window size, this bitmap is always
/// [`SCREEN_WIDTH`]x[`SCREEN_HEIGHT`] pixels in size. /// [`SCREEN_WIDTH`]x[`SCREEN_HEIGHT`] pixels in size.
pub video: Bitmap, pub video: Bitmap,
/// The [`Palette`] that will be used in conjunction with the `video` backbuffer to /// The [`Palette`] that will be used in conjunction with the `video` backbuffer to
/// render the final output to the screen whenever [`System::display`] is called. /// render the final output to the screen whenever [`System::display`] is called.
pub palette: Palette, pub palette: Palette,
/// A pre-loaded [`Font`] that can be used for text rendering. /// A pre-loaded [`Font`] that can be used for text rendering.
pub font: BitmaskFont, pub font: BitmaskFont,
/// Contains instances representing the current state of the input devices available. /// Contains instances representing the current state of the input devices available.
/// To ensure these are updated each frame, ensure that you are either calling /// To ensure these are updated each frame, ensure that you are either calling
/// [`System::do_events`] or manually implementing an event polling loop which calls /// [`System::do_events`] or manually implementing an event polling loop which calls
/// [`InputDevices::update`] and [`InputDevices::handle_event`]. /// [`InputDevices::update`] and [`InputDevices::handle_event`].
pub input_devices: InputDevices, pub input_devices: InputDevices,
pub event_pump: SystemEventPump, pub event_pump: SystemEventPump,
} }
impl std::fmt::Debug for System { impl std::fmt::Debug for System {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("System") f.debug_struct("System")
.field("audio", &self.audio) .field("audio", &self.audio)
.field("audio_queue", &self.audio_queue) .field("audio_queue", &self.audio_queue)
.field("video", &self.video) .field("video", &self.video)
.field("palette", &self.palette) .field("palette", &self.palette)
.field("font", &self.font) .field("font", &self.font)
//.field("keyboard", &self.keyboard) //.field("keyboard", &self.keyboard)
//.field("mouse", &self.mouse) //.field("mouse", &self.mouse)
.field("target_framerate", &self.target_framerate) .field("target_framerate", &self.target_framerate)
.field("target_framerate_delta", &self.target_framerate_delta) .field("target_framerate_delta", &self.target_framerate_delta)
.field("next_tick", &self.next_tick) .field("next_tick", &self.next_tick)
.finish_non_exhaustive() .finish_non_exhaustive()
} }
} }
impl System { impl System {
/// Takes the `video` backbuffer bitmap and `palette` and renders it to the window, up-scaled /// Takes the `video` backbuffer bitmap and `palette` and renders it to the window, up-scaled
/// to fill the window (preserving aspect ratio of course). If V-sync is enabled, this method /// to fill the window (preserving aspect ratio of course). If V-sync is enabled, this method
/// will block to wait for V-sync. Otherwise, if a target framerate was configured a delay /// will block to wait for V-sync. Otherwise, if a target framerate was configured a delay
/// might be used to try to meet that framerate. /// might be used to try to meet that framerate.
pub fn display(&mut self) -> Result<(), SystemError> { pub fn display(&mut self) -> Result<(), SystemError> {
self.input_devices.mouse.render_cursor(&mut self.video); self.input_devices.mouse.render_cursor(&mut self.video);
// convert application framebuffer to 32-bit RGBA pixels, and then upload it to the SDL // convert application framebuffer to 32-bit RGBA pixels, and then upload it to the SDL
// texture so it will be displayed on screen // texture so it will be displayed on screen
self.video self.video
.copy_as_argb_to(&mut self.texture_pixels, &self.palette); .copy_as_argb_to(&mut self.texture_pixels, &self.palette);
let texture_pixels = self.texture_pixels.as_byte_slice(); let texture_pixels = self.texture_pixels.as_byte_slice();
if let Err(error) = self if let Err(error) = self
.sdl_texture .sdl_texture
.update(None, texture_pixels, self.sdl_texture_pitch) .update(None, texture_pixels, self.sdl_texture_pitch)
{ {
return Err(SystemError::DisplayError(error.to_string())); return Err(SystemError::DisplayError(error.to_string()));
} }
self.sdl_canvas.clear(); self.sdl_canvas.clear();
if let Err(error) = self.sdl_canvas.copy(&self.sdl_texture, None, None) { if let Err(error) = self.sdl_canvas.copy(&self.sdl_texture, None, None) {
return Err(SystemError::DisplayError(error)); return Err(SystemError::DisplayError(error));
} }
self.sdl_canvas.present(); self.sdl_canvas.present();
self.input_devices.mouse.hide_cursor(&mut self.video); self.input_devices.mouse.hide_cursor(&mut self.video);
// if a specific target framerate is desired, apply some loop timing/delay to achieve it // if a specific target framerate is desired, apply some loop timing/delay to achieve it
// TODO: do this better. delaying when running faster like this is a poor way to do this.. // TODO: do this better. delaying when running faster like this is a poor way to do this..
if let Some(target_framerate) = self.target_framerate { if let Some(target_framerate) = self.target_framerate {
if self.target_framerate_delta.is_some() { if self.target_framerate_delta.is_some() {
// normal path for every other loop iteration except the first // normal path for every other loop iteration except the first
let delay = self.next_tick - self.ticks() as i64; let delay = self.next_tick - self.ticks() as i64;
if delay < 0 { if delay < 0 {
// this loop iteration took too long, no need to delay // this loop iteration took too long, no need to delay
self.next_tick -= delay; self.next_tick -= delay;
} else { } else {
// this loop iteration completed before next_tick time, delay by the remainder // this loop iteration completed before next_tick time, delay by the remainder
// time period so we're running at about the desired framerate // time period so we're running at about the desired framerate
self.delay(((delay * 1000) / self.tick_frequency() as i64) as u32); self.delay(((delay * 1000) / self.tick_frequency() as i64) as u32);
} }
} else { } else {
// this branch will occur on the first main loop iteration. we use the fact that // this branch will occur on the first main loop iteration. we use the fact that
// target_framerate_delta was not yet set to avoid doing any delay on the first // target_framerate_delta was not yet set to avoid doing any delay on the first
// loop, just in case there was some other processing between the System struct // loop, just in case there was some other processing between the System struct
// being created and the actual beginning of the first loop ... // being created and the actual beginning of the first loop ...
self.target_framerate_delta = self.target_framerate_delta =
Some((self.tick_frequency() / target_framerate as u64) as i64); Some((self.tick_frequency() / target_framerate as u64) as i64);
} }
// expected time for the next display() call to happen by // expected time for the next display() call to happen by
self.next_tick = (self.ticks() as i64) + self.target_framerate_delta.unwrap(); self.next_tick = (self.ticks() as i64) + self.target_framerate_delta.unwrap();
} }
Ok(()) Ok(())
} }
/// Checks for and responds to all SDL2 events waiting in the queue. Each event is passed to /// Checks for and responds to all SDL2 events waiting in the queue. Each event is passed to
/// all [`InputDevice`]'s automatically to ensure input device state is up to date. Returns /// all [`InputDevice`]'s automatically to ensure input device state is up to date. Returns
/// true if a [`SystemEvent::Quit`] event is encountered, in which case, the application /// true if a [`SystemEvent::Quit`] event is encountered, in which case, the application
/// should quit. Otherwise, returns false. /// should quit. Otherwise, returns false.
/// ///
/// ```no_run /// ```no_run
/// use libretrogd::system::*; /// use libretrogd::system::*;
/// ///
/// let mut system = SystemBuilder::new().window_title("Example").build().unwrap(); /// let mut system = SystemBuilder::new().window_title("Example").build().unwrap();
/// ///
/// while !system.do_events() { /// while !system.do_events() {
/// // ... the body of your main loop here ... /// // ... the body of your main loop here ...
/// } /// }
/// ``` /// ```
/// ///
/// If your application needs to react to [`SystemEvent`]s, then instead of using /// If your application needs to react to [`SystemEvent`]s, then instead of using
/// [`System::do_events`], you should instead manually take care of event polling in your /// [`System::do_events`], you should instead manually take care of event polling in your
/// main loop. For example: /// main loop. For example:
/// ///
/// ```no_run /// ```no_run
/// use libretrogd::system::*; /// use libretrogd::system::*;
/// ///
/// let mut system = SystemBuilder::new().window_title("Example").build().unwrap(); /// let mut system = SystemBuilder::new().window_title("Example").build().unwrap();
/// ///
/// 'mainloop: loop { /// 'mainloop: loop {
/// system.input_devices.update(); /// system.input_devices.update();
/// for event in system.event_pump.poll_iter() { /// for event in system.event_pump.poll_iter() {
/// system.input_devices.handle_event(&event); /// system.input_devices.handle_event(&event);
/// match event { /// match event {
/// SystemEvent::Quit => { /// SystemEvent::Quit => {
/// break 'mainloop /// break 'mainloop
/// }, /// },
/// _ => {}, /// _ => {},
/// } /// }
/// } /// }
/// ///
/// // ...the rest of the body of your main loop here ... /// // ...the rest of the body of your main loop here ...
/// } /// }
/// ``` /// ```
pub fn do_events(&mut self) -> bool { pub fn do_events(&mut self) -> bool {
let mut should_quit = false; let mut should_quit = false;
self.input_devices.update(); self.input_devices.update();
for event in self.event_pump.poll_iter() { for event in self.event_pump.poll_iter() {
self.input_devices.handle_event(&event); self.input_devices.handle_event(&event);
if event == SystemEvent::Quit { if event == SystemEvent::Quit {
should_quit = true; should_quit = true;
} }
} }
should_quit should_quit
} }
/// Convenience method that applies any [`AudioBuffer`]s that may have been queued up on /// Convenience method that applies any [`AudioBuffer`]s that may have been queued up on
/// [`System::audio_queue`] to the system audio device so that they will be played. Do not /// [`System::audio_queue`] to the system audio device so that they will be played. Do not
/// call this when you already have an active lock on an [`AudioDevice`]. /// call this when you already have an active lock on an [`AudioDevice`].
pub fn apply_audio_queue(&mut self) -> Result<(), AudioDeviceError> { pub fn apply_audio_queue(&mut self) -> Result<(), AudioDeviceError> {
self.audio_queue.apply(&mut self.audio) self.audio_queue.apply(&mut self.audio)
} }
pub fn ticks(&self) -> u64 { pub fn ticks(&self) -> u64 {
self.sdl_timer_subsystem.performance_counter() self.sdl_timer_subsystem.performance_counter()
} }
pub fn tick_frequency(&self) -> u64 { pub fn tick_frequency(&self) -> u64 {
self.sdl_timer_subsystem.performance_frequency() self.sdl_timer_subsystem.performance_frequency()
} }
/// Returns the number of milliseconds elapsed since SDL was initialized. /// Returns the number of milliseconds elapsed since SDL was initialized.
pub fn millis(&self) -> u32 { pub fn millis(&self) -> u32 {
self.sdl_timer_subsystem.ticks() self.sdl_timer_subsystem.ticks()
} }
/// Delays (blocks) for about the number of milliseconds specified. /// Delays (blocks) for about the number of milliseconds specified.
pub fn delay(&mut self, millis: u32) { pub fn delay(&mut self, millis: u32) {
self.sdl_timer_subsystem.delay(millis); self.sdl_timer_subsystem.delay(millis);
} }
} }

View file

@ -1,12 +1,12 @@
pub trait ReadFixedLengthByteArray { pub trait ReadFixedLengthByteArray {
fn read_bytes<const N: usize>(&mut self) -> Result<[u8; N], std::io::Error>; fn read_bytes<const N: usize>(&mut self) -> Result<[u8; N], std::io::Error>;
} }
impl<T: std::io::Read> ReadFixedLengthByteArray for T { impl<T: std::io::Read> ReadFixedLengthByteArray for T {
fn read_bytes<const N: usize>(&mut self) -> Result<[u8; N], std::io::Error> { fn read_bytes<const N: usize>(&mut self) -> Result<[u8; N], std::io::Error> {
assert_ne!(N, 0); assert_ne!(N, 0);
let mut array = [0u8; N]; let mut array = [0u8; N];
self.read_exact(&mut array)?; self.read_exact(&mut array)?;
Ok(array) Ok(array)
} }
} }

View file

@ -3,20 +3,20 @@ use std::io::{Error, SeekFrom};
/// Provides a convenience method for determining the total size of a stream. This is provided /// Provides a convenience method for determining the total size of a stream. This is provided
/// as a temporary alternative to [std::io::Seek::stream_len] which is currently marked unstable. /// as a temporary alternative to [std::io::Seek::stream_len] which is currently marked unstable.
pub trait StreamSize { pub trait StreamSize {
fn stream_size(&mut self) -> Result<u64, std::io::Error>; fn stream_size(&mut self) -> Result<u64, std::io::Error>;
} }
impl<T: std::io::Read + std::io::Seek> StreamSize for T { impl<T: std::io::Read + std::io::Seek> StreamSize for T {
fn stream_size(&mut self) -> Result<u64, Error> { fn stream_size(&mut self) -> Result<u64, Error> {
let old_pos = self.stream_position()?; let old_pos = self.stream_position()?;
let len = self.seek(SeekFrom::End(0))?; let len = self.seek(SeekFrom::End(0))?;
// Avoid seeking a third time when we were already at the end of the // Avoid seeking a third time when we were already at the end of the
// stream. The branch is usually way cheaper than a seek operation. // stream. The branch is usually way cheaper than a seek operation.
if old_pos != len { if old_pos != len {
self.seek(SeekFrom::Start(old_pos))?; self.seek(SeekFrom::Start(old_pos))?;
} }
Ok(len) Ok(len)
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -10,31 +10,31 @@ pub mod lzwgif;
pub mod packbits; pub mod packbits;
pub fn rnd_value<N: SampleUniform + PartialOrd>(low: N, high: N) -> N { pub fn rnd_value<N: SampleUniform + PartialOrd>(low: N, high: N) -> N {
rand::thread_rng().gen_range(low..=high) rand::thread_rng().gen_range(low..=high)
} }
/// Returns the absolute difference between two unsigned values. This is just here as a temporary /// Returns the absolute difference between two unsigned values. This is just here as a temporary
/// alternative to the `abs_diff` method currently provided by Rust but that is marked unstable. /// alternative to the `abs_diff` method currently provided by Rust but that is marked unstable.
#[inline] #[inline]
pub fn abs_diff<N: Unsigned + PartialOrd>(a: N, b: N) -> N { pub fn abs_diff<N: Unsigned + PartialOrd>(a: N, b: N) -> N {
if a < b { if a < b {
b - a b - a
} else { } else {
a - b a - b
} }
} }
pub trait AsAny { pub trait AsAny {
fn as_any(&self) -> &dyn Any; fn as_any(&self) -> &dyn Any;
fn as_any_mut(&mut self) -> &mut dyn Any; fn as_any_mut(&mut self) -> &mut dyn Any;
} }
impl<A: Any> AsAny for A { impl<A: Any> AsAny for A {
fn as_any(&self) -> &dyn Any { fn as_any(&self) -> &dyn Any {
self as &dyn Any self as &dyn Any
} }
fn as_any_mut(&mut self) -> &mut dyn Any { fn as_any_mut(&mut self) -> &mut dyn Any {
self as &mut dyn Any self as &mut dyn Any
} }
} }

View file

@ -3,224 +3,224 @@ use thiserror::Error;
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum PackBitsError { pub enum PackBitsError {
#[error("PackBits I/O error")] #[error("PackBits I/O error")]
IOError(#[from] std::io::Error), IOError(#[from] std::io::Error),
} }
enum PackMode { enum PackMode {
Dump, Dump,
Run, Run,
} }
pub fn pack_bits<S, D>(src: &mut S, dest: &mut D, src_length: usize) -> Result<(), PackBitsError> pub fn pack_bits<S, D>(src: &mut S, dest: &mut D, src_length: usize) -> Result<(), PackBitsError>
where where
S: ReadBytesExt, S: ReadBytesExt,
D: WriteBytesExt, D: WriteBytesExt,
{ {
const MIN_RUN: usize = 3; const MIN_RUN: usize = 3;
const MAX_RUN: usize = 128; const MAX_RUN: usize = 128;
const MAX_BUFFER: usize = 128; const MAX_BUFFER: usize = 128;
if src_length == 0 { if src_length == 0 {
return Ok(()); return Ok(());
} }
let mut bytes_left = src_length; let mut bytes_left = src_length;
let mut buffer = [0u8; (MAX_RUN * 2)]; let mut buffer = [0u8; (MAX_RUN * 2)];
// read the first byte from the source, just to start things off before we get into the loop // read the first byte from the source, just to start things off before we get into the loop
buffer[0] = src.read_u8()?; buffer[0] = src.read_u8()?;
bytes_left -= 1; bytes_left -= 1;
let mut mode = PackMode::Dump; let mut mode = PackMode::Dump;
let mut run_end = 1; // 1 because we already read the first byte into the buffer let mut run_end = 1; // 1 because we already read the first byte into the buffer
let mut run_start = 0; let mut run_start = 0;
let mut previous_byte = buffer[0]; let mut previous_byte = buffer[0];
while bytes_left > 0 { while bytes_left > 0 {
let byte = src.read_u8()?; let byte = src.read_u8()?;
buffer[run_end] = byte; buffer[run_end] = byte;
run_end += 1; run_end += 1;
match mode { match mode {
// "dump" mode. keep collecting any bytes and write them as-is until we detect the // "dump" mode. keep collecting any bytes and write them as-is until we detect the
// start of a run of identical bytes // start of a run of identical bytes
PackMode::Dump => { PackMode::Dump => {
if run_end > MAX_BUFFER { if run_end > MAX_BUFFER {
// we need to flush the temp buffer to the destination // we need to flush the temp buffer to the destination
dest.write_u8((run_end - 2) as u8)?; dest.write_u8((run_end - 2) as u8)?;
dest.write_all(&buffer[0..run_end])?; dest.write_all(&buffer[0..run_end])?;
buffer[0] = byte; buffer[0] = byte;
run_end = 1; run_end = 1;
run_start = 0; run_start = 0;
} else if byte == previous_byte { } else if byte == previous_byte {
// detected the start of a run of identical bytes // detected the start of a run of identical bytes
if (run_end - run_start) >= MIN_RUN { if (run_end - run_start) >= MIN_RUN {
if run_start > 0 { if run_start > 0 {
// we've found a run, flush the buffer we have currently so we can // we've found a run, flush the buffer we have currently so we can
// start tracking the length of this run // start tracking the length of this run
dest.write_u8((run_start - 1) as u8)?; dest.write_u8((run_start - 1) as u8)?;
dest.write_all(&buffer[0..run_start])?; dest.write_all(&buffer[0..run_start])?;
} }
mode = PackMode::Run; mode = PackMode::Run;
} else if run_start == 0 { } else if run_start == 0 {
mode = PackMode::Run; mode = PackMode::Run;
} }
} else { } else {
run_start = run_end - 1; run_start = run_end - 1;
} }
} }
// "run" mode. keep counting up bytes as long as they are identical to each other. when // "run" mode. keep counting up bytes as long as they are identical to each other. when
// we find the end of a run, write out the run info and switch back to dump mode // we find the end of a run, write out the run info and switch back to dump mode
PackMode::Run => { PackMode::Run => {
// check for the end of a run of identical bytes // check for the end of a run of identical bytes
if (byte != previous_byte) || ((run_end - run_start) > MAX_RUN) { if (byte != previous_byte) || ((run_end - run_start) > MAX_RUN) {
// the identical byte run has ended, write it out to the destination // the identical byte run has ended, write it out to the destination
// (this is just two bytes, the count and the actual byte) // (this is just two bytes, the count and the actual byte)
dest.write_i8(-((run_end - run_start - 2) as i8))?; dest.write_i8(-((run_end - run_start - 2) as i8))?;
dest.write_u8(previous_byte)?; dest.write_u8(previous_byte)?;
// clear the temp buffer for our switch back to "dump" mode // clear the temp buffer for our switch back to "dump" mode
buffer[0] = byte; buffer[0] = byte;
run_end = 1; run_end = 1;
run_start = 0; run_start = 0;
mode = PackMode::Dump; mode = PackMode::Dump;
} }
} }
}; };
previous_byte = byte; previous_byte = byte;
bytes_left -= 1; bytes_left -= 1;
} }
// the source bytes have all been read, but we still might have to flush the temp buffer // the source bytes have all been read, but we still might have to flush the temp buffer
// out to the destination, or finish writing out a run of identical bytes that was at the very // out to the destination, or finish writing out a run of identical bytes that was at the very
// end of the source // end of the source
match mode { match mode {
PackMode::Dump => { PackMode::Dump => {
dest.write_u8((run_end - 1) as u8)?; dest.write_u8((run_end - 1) as u8)?;
dest.write_all(&buffer[0..run_end])?; dest.write_all(&buffer[0..run_end])?;
} }
PackMode::Run => { PackMode::Run => {
dest.write_i8(-((run_end - run_start - 1) as i8))?; dest.write_i8(-((run_end - run_start - 1) as i8))?;
dest.write_u8(previous_byte)?; dest.write_u8(previous_byte)?;
} }
}; };
Ok(()) Ok(())
} }
pub fn unpack_bits<S, D>( pub fn unpack_bits<S, D>(
src: &mut S, src: &mut S,
dest: &mut D, dest: &mut D,
unpacked_length: usize, unpacked_length: usize,
) -> Result<(), PackBitsError> ) -> Result<(), PackBitsError>
where where
S: ReadBytesExt, S: ReadBytesExt,
D: WriteBytesExt, D: WriteBytesExt,
{ {
let mut buffer = [0u8; 128]; let mut buffer = [0u8; 128];
let mut bytes_written = 0; let mut bytes_written = 0;
while bytes_written < unpacked_length { while bytes_written < unpacked_length {
// read the next "code" byte that determines how to process the subsequent byte(s) // read the next "code" byte that determines how to process the subsequent byte(s)
let byte = src.read_u8()?; let byte = src.read_u8()?;
if byte > 128 { if byte > 128 {
// 129-255 = repeat the next byte 257-n times // 129-255 = repeat the next byte 257-n times
let run_length = (257 - byte as u32) as usize; let run_length = (257 - byte as u32) as usize;
// read the next byte from the source and repeat it the specified number of times // read the next byte from the source and repeat it the specified number of times
let byte = src.read_u8()?; let byte = src.read_u8()?;
buffer.fill(byte); buffer.fill(byte);
dest.write_all(&buffer[0..run_length])?; dest.write_all(&buffer[0..run_length])?;
bytes_written += run_length; bytes_written += run_length;
} else if byte < 128 { } else if byte < 128 {
// 0-128 = copy next n-1 bytes from src to dest as-is // 0-128 = copy next n-1 bytes from src to dest as-is
let run_length = (byte + 1) as usize; let run_length = (byte + 1) as usize;
src.read_exact(&mut buffer[0..run_length])?; src.read_exact(&mut buffer[0..run_length])?;
dest.write_all(&buffer[0..run_length])?; dest.write_all(&buffer[0..run_length])?;
bytes_written += run_length; bytes_written += run_length;
} }
// note that byte == 128 is a no-op (does it even appear in any files ???) // note that byte == 128 is a no-op (does it even appear in any files ???)
} }
Ok(()) Ok(())
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::io::Cursor; use std::io::Cursor;
use super::*; use super::*;
struct TestData<'a> { struct TestData<'a> {
packed: &'a [u8], packed: &'a [u8],
unpacked: &'a [u8], unpacked: &'a [u8],
} }
static TEST_DATA: &[TestData] = &[ static TEST_DATA: &[TestData] = &[
TestData { TestData {
packed: &[ packed: &[
0xfe, 0xaa, 0x02, 0x80, 0x00, 0x2a, 0xfd, 0xaa, 0x03, 0x80, 0x00, 0x2a, 0x22, 0xf7, 0xfe, 0xaa, 0x02, 0x80, 0x00, 0x2a, 0xfd, 0xaa, 0x03, 0x80, 0x00, 0x2a, 0x22, 0xf7,
0xaa, 0xaa,
], ],
unpacked: &[ unpacked: &[
0xaa, 0xaa, 0xaa, 0x80, 0x00, 0x2a, 0xaa, 0xaa, 0xaa, 0xaa, 0x80, 0x00, 0x2a, 0x22, 0xaa, 0xaa, 0xaa, 0x80, 0x00, 0x2a, 0xaa, 0xaa, 0xaa, 0xaa, 0x80, 0x00, 0x2a, 0x22,
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
], ],
}, },
TestData { TestData {
packed: &[0x00, 0xaa], packed: &[0x00, 0xaa],
unpacked: &[0xaa], unpacked: &[0xaa],
}, },
TestData { TestData {
packed: &[0xf9, 0xaa], packed: &[0xf9, 0xaa],
unpacked: &[0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa], unpacked: &[0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa],
}, },
TestData { TestData {
packed: &[0xf9, 0xaa, 0x00, 0xbb], packed: &[0xf9, 0xaa, 0x00, 0xbb],
unpacked: &[0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xbb], unpacked: &[0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xbb],
}, },
TestData { TestData {
packed: &[0x07, 0xa0, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8], packed: &[0x07, 0xa0, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8],
unpacked: &[0xa0, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8], unpacked: &[0xa0, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8],
}, },
TestData { TestData {
packed: &[0x08, 0xa0, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa8], packed: &[0x08, 0xa0, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa8],
unpacked: &[0xa0, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa8], unpacked: &[0xa0, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa8],
}, },
TestData { TestData {
packed: &[0x06, 0xa0, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xfe, 0xa8], packed: &[0x06, 0xa0, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xfe, 0xa8],
unpacked: &[0xa0, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa8, 0xa8], unpacked: &[0xa0, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa8, 0xa8],
}, },
]; ];
#[test] #[test]
fn packs() -> Result<(), PackBitsError> { fn packs() -> Result<(), PackBitsError> {
for TestData { packed, unpacked } in TEST_DATA { for TestData { packed, unpacked } in TEST_DATA {
let mut src = Cursor::new(*unpacked); let mut src = Cursor::new(*unpacked);
let mut dest = vec![0u8; 0]; let mut dest = vec![0u8; 0];
pack_bits(&mut src, &mut dest, unpacked.len())?; pack_bits(&mut src, &mut dest, unpacked.len())?;
assert_eq!(dest, *packed); assert_eq!(dest, *packed);
} }
Ok(()) Ok(())
} }
#[test] #[test]
fn unpacks() -> Result<(), PackBitsError> { fn unpacks() -> Result<(), PackBitsError> {
for TestData { packed, unpacked } in TEST_DATA { for TestData { packed, unpacked } in TEST_DATA {
let mut src = Cursor::new(*packed); let mut src = Cursor::new(*packed);
let mut dest = vec![0u8; 0]; let mut dest = vec![0u8; 0];
unpack_bits(&mut src, &mut dest, unpacked.len())?; unpack_bits(&mut src, &mut dest, unpacked.len())?;
assert_eq!(dest, *unpacked); assert_eq!(dest, *unpacked);
} }
Ok(()) Ok(())
} }
} }

File diff suppressed because it is too large Load diff