use speedy2d::color::Color;
use speedy2d::dimen::Vector2;
use speedy2d::font::Font;
use speedy2d::image::{ImageDataType, ImageSmoothingMode};
use speedy2d::shape::Rectangle;
use speedy2d::window::WindowHelper;
use speedy2d::Graphics2D;
use crate::helpers::Entry;
use crate::helpers::event::{Event, EventType};
use crate::kfn_data::KfnData;
use crate::kfn_ini::eff::Action;
mod window_handler;
mod text_buffer;
mod user_interactions;
use text_buffer::TextBuffer;
#[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,
text_buffer_vec: Vec<TextBuffer>,
time: TimeKeeper,
receiver: crossbeam::channel::Receiver<Event>,
sender: crossbeam::channel::Sender<String>,
paused: bool,
diag: (bool, Diagnostics),
}
#[derive(Debug, Clone)]
struct ScreenBuffer {
background: Event,
tint: Color,
buffered_image: (String, Vec<u8>),
resized: bool
}
#[derive(Debug, Clone)]
struct TimeKeeper {
start_time: std::time::Instant,
offset: std::time::Duration,
}
#[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!(
"./fonts/NotoSansJP-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,
buffered_image: (String::new(), Vec::new()),
resized: false
},
text_buffer_vec: Vec::new(),
time: TimeKeeper {
start_time: std::time::Instant::now(),
offset: std::time::Duration::from_millis(0)
},
receiver,
sender,
paused: false,
diag,
}
}
fn set_background(&mut 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 {
if &background_entry.filename != &self.screen_buffer.buffered_image.0 || self.screen_buffer.resized {
self.screen_buffer.resized = false;
self.screen_buffer.buffered_image.0 = background_entry.filename;
self.screen_buffer.buffered_image.1 = 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),
&self.screen_buffer.buffered_image.1).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 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,
}
)
})
}
for eff_num in 0..self.data.song.effs.len() {
let eff = self.data.song.effs[eff_num].clone();
let eff_id = eff.id;
if eff_id == 51 {
continue;
}
let mut current_buffer = TextBuffer {
eff_num,
text_events: Vec::new(),
font: Font::new(include_bytes!("fonts/NotoSansJP-Regular.ttf")).unwrap(),
font_size: 70.0,
inactive_color: speedy2d::color::Color::WHITE,
inactive_outline_color: speedy2d::color::Color::BLACK,
active_color: speedy2d::color::Color::YELLOW,
active_outline_color: speedy2d::color::Color::BLACK,
outline_weight: 5,
};
if let Some(inactive_color) = &self.data.song.effs[eff_num].initial_inactive_color {
let s: Vec<String> = inactive_color.to_owned().trim().split("").map(|s| s.to_string()).collect();
let r = u8::from_str_radix(&(s[2].clone() + &s[3]).to_ascii_lowercase(), 16).unwrap();
let g = u8::from_str_radix(&(s[4].clone() + &s[5]).to_ascii_lowercase(), 16).unwrap();
let b = u8::from_str_radix(&(s[6].clone() + &s[7]).to_ascii_lowercase(), 16).unwrap();
let a = u8::from_str_radix(&(s[8].clone() + &s[9]).to_ascii_lowercase(), 16).unwrap();
let hex = speedy2d::color::Color::from_int_rgba(r, g, b, a);
current_buffer.inactive_color = hex;
}
if let Some(active_color) = &self.data.song.effs[eff_num].initial_active_color {
let s: Vec<String> = active_color.to_owned().trim().split("").map(|s| s.to_string()).collect();
let r = u8::from_str_radix(&(s[2].clone() + &s[3]).to_ascii_lowercase(), 16).unwrap();
let g = u8::from_str_radix(&(s[4].clone() + &s[5]).to_ascii_lowercase(), 16).unwrap();
let b = u8::from_str_radix(&(s[6].clone() + &s[7]).to_ascii_lowercase(), 16).unwrap();
let a = u8::from_str_radix(&(s[8].clone() + &s[9]).to_ascii_lowercase(), 16).unwrap();
let hex = speedy2d::color::Color::from_int_rgba(r, g, b, a);
current_buffer.active_color = hex;
}
if let Some(font) = &self.data.song.effs[eff_num].initial_font {
dbg!(&font.0);
current_buffer.font = Font::new(&self.data.get_entry_by_name(&font.0).unwrap().file_bin).unwrap();
}
self.text_buffer_vec.push(current_buffer);
}
}
}