use speedy2d::color::Color;
use speedy2d::dimen::Vector2;
use speedy2d::font::{Font, TextLayout, TextOptions};
use speedy2d::image::{ImageDataType, ImageSmoothingMode};
use speedy2d::shape::Rectangle;
use speedy2d::window::{WindowHandler, WindowHelper, WindowStartupInfo};
use speedy2d::Graphics2D;
use crate::helpers::Entry;
use crate::helpers::event::{Event, EventType};
use crate::kfn_data::KfnData;
use crate::kfn_ini::eff::Action;
#[derive(Debug, Clone)]
pub struct KfnPlayer {
pub data: KfnData,
pub window_size: Vector2<u32>,
curr_background_entry: Entry,
_event_list: Vec<Event>,
event_queue: Vec<Event>,
screen_buffer: ScreenBuffer,
receiver: crossbeam::channel::Receiver<Event>,
sender: crossbeam::channel::Sender<String>,
paused: bool,
diag: (bool, Diagnostics),
}
#[derive(Debug, Clone)]
struct ScreenBuffer {
background: Event,
tint: Color,
}
#[derive(Debug, Clone)]
struct Diagnostics {
counter: usize,
frame_count: u32,
last_update: std::time::Instant,
fps: f32,
draw_time: f32,
font: Font,
}
impl KfnPlayer {
pub fn new(data: KfnData, window_size: (u32, u32), event_list: Vec<Event>, receiver: crossbeam::channel::Receiver<Event>, sender: crossbeam::channel::Sender<String>) -> Self {
let diag = (true, Diagnostics {
counter: 0,
frame_count: 0,
last_update: std::time::Instant::now(),
font: Font::new(include_bytes!(
"/usr/share/fonts/noto/NotoSans-Regular.ttf"
))
.unwrap(),
fps: 0.0,
draw_time: 0.0,
});
Self {
data,
window_size: Vector2::from((window_size.0, window_size.1)),
curr_background_entry: Entry::default(),
_event_list: event_list,
event_queue: Vec::new(),
screen_buffer: ScreenBuffer { background: Event::default(), tint: speedy2d::color::Color::WHITE },
receiver,
sender,
paused: false,
diag,
}
}
fn set_background(&self, entry_name: &str, graphics: &mut Graphics2D) {
graphics.clear_screen(Color::BLACK);
match self.data.get_entry_by_name(entry_name) {
Some(background_entry) => {
if background_entry != self.curr_background_entry {
let raw_image = image::load_from_memory(&background_entry.file_bin)
.expect("Invalid image file.")
.resize_to_fill(self.window_size.x, self.window_size.y, image::imageops::FilterType::Triangle)
.into_rgb8().into_raw();
let image = graphics.create_image_from_raw_pixels(
ImageDataType::RGB,
ImageSmoothingMode::NearestNeighbor,
(self.window_size.x, self.window_size.y),
&raw_image).unwrap();
let rect = Rectangle::new(
Vector2::new(0.0, 0.0),
Vector2::new(self.window_size.x as f32, self.window_size.y as f32),
);
graphics.draw_rectangle_image_tinted(rect, self.screen_buffer.tint, &image);
}
},
None => {
},
}
}
fn draw_screen_buffer(&mut self, _helper: &mut WindowHelper<()>, graphics: &mut Graphics2D) {
let bg = self.screen_buffer.background.event_type.clone();
match bg {
EventType::Background(ae) => {
match ae.action {
Action::ChgBgImg(bg_entry) => {
self.set_background(&bg_entry, graphics);
}
_ => ()
}
}
_ => ()
}
}
fn play_pause(&mut self) {
if self.paused {
self.sender.send("RESUME".to_string()).unwrap();
self.paused = false;
println!("KFN-PLAYER: RESUME signal sent.")
} else {
self.sender.send("PAUSE".to_string()).unwrap();
self.paused = true;
println!("KFN-PLAYER: PAUSE signal sent.")
}
}
fn change_track(&mut self) {
self.sender.send("CH_TRACK".to_string()).unwrap();
}
fn set_initial_state(&mut self) {
if let Some(initial_bg) = self.data.song.effs[0].initial_lib_image.clone() {
self.event_queue.push(Event {
time: 0,
event_type: EventType::Background(crate::kfn_ini::eff::AnimEntry {
action: Action::ChgBgImg(initial_bg), effect: None, trans_time: 0.0, trans_type: crate::kfn_ini::eff::TransType::None })
})
}
}
}
impl WindowHandler for KfnPlayer {
fn on_resize(
&mut self,
_helper: &mut WindowHelper<()>,
size_pixels: Vector2<u32>
) {
self.window_size.x = size_pixels.x;
self.window_size.y = size_pixels.y;
}
fn on_start(&mut self, helper: &mut WindowHelper<()>, _info: WindowStartupInfo) {
helper.set_resizable(true);
helper.set_icon_from_rgba_pixels(
image::open("src/icons/icon32x32.png").unwrap().into_bytes(), (32, 32)).unwrap();
self.set_initial_state();
}
fn on_draw(&mut self, helper: &mut WindowHelper<()>, graphics: &mut Graphics2D) {
let draw_start = std::time::Instant::now();
let text =
&self.diag.1.font.layout_text(
&std::format!(
"Frame: {}, FPS: {:.0}, frame draw time: {:.0} ms",
self.diag.1.counter,
self.diag.1.fps,
self.diag.1.draw_time/1000.0),
42.0,
TextOptions::new());
if !self.paused {
graphics.clear_screen(speedy2d::color::Color::BLACK);
while !self.receiver.is_empty() {
match self.receiver.try_recv() {
Ok(event_recv) => {
println!("{} received", event_recv.time);
dbg!(&event_recv);
self.event_queue.push(event_recv);
},
Err(_e) => {
},
};
}
while self.event_queue.len() != 0 {
if let Some(event) = self.event_queue.pop() {
match &event.event_type {
EventType::Background(ae) => {
match &ae.action {
Action::ChgBgImg(_) => {
self.screen_buffer.background = event.clone();
},
Action::ChgColImageColor(target_color) => {
let rgb_hex = &target_color.to_owned()[0..7];
let alpha_hex = u32::from_str_radix(&target_color.to_owned()[7..9], 16).unwrap() as f32;
let rgba_color = colorsys::Rgb::from_hex_str(rgb_hex).unwrap();
let color = speedy2d::color::Color::from_rgba(
(rgba_color.red()/255.0) as f32,
(rgba_color.green()/255.0) as f32,
(rgba_color.blue()/255.0) as f32,
(alpha_hex/255.0) as f32);
self.screen_buffer.tint = color;
}
_ => ()
}
},
_ => ()
}
}
}
self.draw_screen_buffer(helper, graphics);
if self.diag.0 {
graphics.draw_text((0.0, 0.0), speedy2d::color::Color::RED, &text);
}
}
helper.request_redraw();
self.diag.1.draw_time = draw_start.elapsed().as_secs_f32() * 1000.0 * 1000.0;
self.diag.1.counter += 1;
self.diag.1.frame_count += 1;
self.diag.1.fps = 1.0 / (draw_start - self.diag.1.last_update).as_secs_f32();
self.diag.1.last_update = draw_start;
}
fn on_mouse_button_down(&mut self, _helper: &mut WindowHelper<()>, _button: speedy2d::window::MouseButton) {
self.play_pause();
}
fn on_keyboard_char(
&mut self,
_helper: &mut WindowHelper<()>,
unicode_codepoint: char
) {
if unicode_codepoint == 'k' {
println!("KFN-PLAYER: CH_TRACK signal sent.");
self.change_track();
}
}
}