mod polygon_draw; use iced::keyboard::Modifiers; use polygon_draw::Polygon; mod music; use music::Music; mod color; mod history; use history::Historic; mod message; use message::Message; mod utils; use utils::{delta_to_string, is_delta_format_valid, str_to_sec}; use std::fs; mod gui; use gui::{load_file_view, music_view}; use iced::Font; use iced::Theme; use iced::{ event::{self, Status}, keyboard::{key::Named, Key}, Color, Event, Task, }; use std::time::Instant; use kira::{ sound::static_sound::StaticSoundData, AudioManager, AudioManagerSettings, DefaultBackend, }; const FONT_BYTES: &[u8] = include_bytes!("../fonts/EnvyCodeRNerdFontMono-Regular.ttf"); const FONT: Font = Font::with_name("EnvyCodeR Nerd Font Mono"); fn main() -> iced::Result { let polytheme: Theme = { let back = Color::from_rgb8(39, 63, 79); let text = Color::from_rgb8(0xD3, 0xD4, 0xD9); let prim = Color::from_rgb8(230, 82, 31); let succ = Color::from_rgb8(0xFF, 0xF9, 0xFB); let dan = Color::from_rgb8(0xBB, 0x0A, 0x21); Theme::custom( String::from("PolyTheme"), iced::theme::Palette { background: back, text: text, primary: prim, success: succ, danger: dan, }, ) }; iced::application("Polymusic", Polymusic::update, Polymusic::view) .theme(move |_| polytheme.clone()) .font(iced_fonts::REQUIRED_FONT_BYTES) .font(FONT_BYTES) .default_font(FONT) .antialiasing(true) .subscription(Polymusic::subscription) .run_with(Polymusic::new) } struct Polymusic { music: Music, time_last_frame: Instant, paused: bool, audio_manager: AudioManager, all_sounds: Vec, all_saves: Vec, current_delta: f32, str_music_length: String, str_time: String, can_unpaused: bool, already_save: bool, mode_file_load: bool, file_name_to_creat: String, show_warning_message_creat: bool, historic: Historic, } impl Polymusic { fn new() -> (Self, Task) { let manager = AudioManager::::new(AudioManagerSettings::default()) .expect("Error to load AudioManager"); ( Self { time_last_frame: Instant::now(), audio_manager: manager, paused: true, all_sounds: load_path_sounds(), all_saves: load_path_saves(), music: Music::default(), current_delta: 0.0, str_music_length: "01:00:00".to_string(), str_time: "00:00:00".to_string(), can_unpaused: true, already_save: true, mode_file_load: true, file_name_to_creat: "Default File Name".to_string(), show_warning_message_creat: false, historic: Historic::new(), }, Task::none(), ) } fn update(&mut self, message: Message) { match message { Message::AddPolygon(s) => { self.music.add_polygon(self.current_delta, s.clone()); self.already_save = false; self.historic.add( Message::Remove(usize::MAX), Message::AddPolygon(s), self.current_delta, ); } Message::ReAddPolygon(poly) => { self.music.add_polygon_from(self.current_delta, poly); } Message::Tick => { if self.current_delta >= self.music.length { self.paused = true } if !self.paused { self.music.current_delta = self.current_delta; let time_btw = Instant::now().duration_since(self.time_last_frame); self.current_delta += time_btw.as_millis() as f32 / 1000.0; self.music .apply_tick(self.current_delta, time_btw, &mut self.audio_manager); self.time_last_frame = Instant::now(); self.str_time = delta_to_string(self.current_delta); } } Message::Remove(i) => { let poly = self.music.remove_polygon(self.current_delta, i); self.already_save = false; self.historic.add( Message::ReAddPolygon(poly), Message::Remove(i), self.current_delta, ); } Message::ChangeTeta(i, teta) => { let old_teta = self.music.set_teta(self.current_delta, i, teta); self.already_save = false; self.historic.add( Message::ChangeTeta(i, old_teta), Message::ChangeTeta(i, teta), self.current_delta, ); } Message::ChangeSound(i, s) => { let sound = StaticSoundData::from_file(format!("./assets/{s}")) .expect("Fail to load audio"); let old_sound = self .music .set_sound(self.current_delta, i, sound, s.clone()); self.already_save = false; self.historic.add( Message::ChangeSound(i, old_sound), Message::ChangeSound(i, s), self.current_delta, ); } Message::Save => { let json = serde_json::to_string_pretty(&self.music).unwrap(); fs::write(format!("./saves/{0}.pmx", &self.music.file_name), json).unwrap(); self.all_saves = load_path_saves(); self.already_save = true; } Message::Load => { let json = fs::read_to_string(format!("./saves/{0}.pmx", &self.music.file_name)); match json { Ok(j) => { let decoded: Music = serde_json::from_str(&j).unwrap(); self.music = decoded; self.music.update_frame(); self.mode_file_load = false; } Err(e) => { eprintln!("Error, no saves with this name to load, {e} "); } } } Message::SetFileNameCreat(s) => self.file_name_to_creat = s, Message::CreatFile => { if self.all_saves.contains(&self.file_name_to_creat) { self.show_warning_message_creat = true; } else { self.mode_file_load = false; self.music = Music::default(); self.music.file_name = self.file_name_to_creat.clone(); self.update(Message::Save); } } Message::FileNameChanged(s) => self.music.file_name = s, Message::LengthChange(s) => { if is_delta_format_valid(&s) { let sec = str_to_sec(&s); if sec > 0. { self.historic.add( Message::LengthChange(delta_to_string(self.music.length)), Message::LengthChange(s.clone()), self.current_delta, ); self.music.length = sec; self.already_save = false; } } self.str_music_length = s; } Message::TogglePaused => { self.paused = !self.paused; if !self.paused { self.time_last_frame = Instant::now(); } } Message::ChangeDelta(f) => { self.current_delta = f; self.music.fix_teta(self.current_delta); // update the red dot on canvas self.update_canvas_if_paused(); } Message::ChangeDeltaString(s) => { if is_delta_format_valid(&s) { self.already_save = false; self.update(Message::ChangeDelta(str_to_sec(&s))); } self.str_time = s; } Message::ReAddPoint => { self.already_save = false; self.music.add_point_old(); } Message::AddPoint => { self.already_save = false; self.music.add_point(self.current_delta); self.historic .add(Message::RemovePoint, Message::AddPoint, self.current_delta); } Message::RemovePoint => { self.already_save = false; self.music.remove_point(self.current_delta); self.historic.add( Message::ReAddPoint, Message::RemovePoint, self.current_delta, ); } Message::ClickedOnTimeLine(f) => { self.update(Message::ChangeDelta(f)); } Message::SlidePointLeft => { self.already_save = false; self.music.slide_to_left(self.current_delta); self.music.fix_teta(self.current_delta); self.update_canvas_if_paused(); self.historic.add( Message::SlidePointLeft, Message::SlidePointLeft, self.current_delta, ); } Message::SlidePointRight => { self.already_save = false; self.music.slide_to_right(self.current_delta); self.music.fix_teta(self.current_delta); self.update_canvas_if_paused(); self.historic.add( Message::SlidePointRight, Message::SlidePointRight, self.current_delta, ); } Message::ChangeDegree(i, s) => { let mut mut_s = s; if mut_s.len() == 0 { mut_s = "0".to_string(); } match mut_s.parse::() { Ok(val) => { if val >= 0. && val <= 360. { self.update(Message::ChangeTeta(i, (val).to_radians())); self.already_save = false; } } Err(_) => {} } } Message::ChangeNbPerSec(s) => { let mut_s = s.trim_end_matches(" sec/rev"); if mut_s.len() != s.len() { match mut_s.parse::() { Ok(val) => { let val = (val * 10.).floor() / 10.; if val >= 1. && val < 1000. { self.historic.add( Message::ChangeNbPerSec(format!( "{:.1} sec/rev", self.music.nb_sec_for_rev )), Message::ChangeNbPerSec(s.clone()), self.current_delta, ); self.music.nb_sec_for_rev = val; self.already_save = false; } } Err(_) => {} } } } Message::CancelColor(i) => { self.music.set_color_picker(self.current_delta, i, false); self.can_unpaused = true; } Message::SubmitColor(i) => { if !self.paused { self.update(Message::TogglePaused); } self.can_unpaused = false; self.music.set_color_picker(self.current_delta, i, true); } Message::ChooseColor(i, color) => { let old_color = self.music.set_color(self.current_delta, i, color); self.music.set_color_picker(self.current_delta, i, false); self.can_unpaused = true; self.already_save = false; self.historic.add( Message::ChooseColor(i, old_color), Message::ChooseColor(i, color), self.current_delta, ); } Message::GoToLoadView => { if self.already_save { self.historic = Historic::new(); self.paused = true; self.file_name_to_creat = "Default File Name".to_string(); self.show_warning_message_creat = false; self.mode_file_load = true } } Message::ForceToQuit => { self.already_save = true; } Message::Undo => { if let Some(item) = self.historic.undo() { self.historic.ignore_add = true; self.update(Message::ChangeDelta(item.1)); self.update(item.0); self.historic.ignore_add = false; } } Message::Redo => { if let Some(item) = self.historic.redo() { self.historic.ignore_add = true; self.update(Message::ChangeDelta(item.1)); self.update(item.0); self.historic.ignore_add = false; } } Message::None => {} } } fn view(&self) -> iced::Element { if !self.mode_file_load { music_view(self) } else { load_file_view(self) } } fn subscription(&self) -> iced::Subscription { let subscr_key = event::listen_with(|event, status, _| match (event, status) { ( Event::Keyboard(iced::keyboard::Event::KeyPressed { key: Key::Character(c), modifiers: Modifiers::CTRL, .. }), Status::Ignored, ) if c.as_ref() == "z" => Some(Message::Undo), ( Event::Keyboard(iced::keyboard::Event::KeyPressed { key: Key::Character(c), modifiers: Modifiers::CTRL, .. }), Status::Ignored, ) if c.as_ref() == "y" => Some(Message::Redo), ( Event::Keyboard(iced::keyboard::Event::KeyPressed { key: Key::Character(c), modifiers: Modifiers::CTRL, .. }), Status::Ignored, ) if c.as_ref() == "s" => Some(Message::Save), ( Event::Keyboard(iced::keyboard::Event::KeyPressed { key: Key::Named(Named::Space), .. }), Status::Ignored, ) => Some(Message::TogglePaused), ( Event::Keyboard(iced::keyboard::Event::KeyPressed { key: Key::Named(Named::ArrowUp), .. }), Status::Ignored, ) => Some(Message::ChangeDelta(0.0)), ( Event::Keyboard(iced::keyboard::Event::KeyPressed { key: Key::Character(c), modifiers: Modifiers::CTRL, .. }), Status::Ignored, ) if c.as_ref() == "f" => Some(Message::ForceToQuit), _ => None, }); if self.paused { subscr_key } else { iced::Subscription::batch([ subscr_key, iced::time::every(std::time::Duration::from_millis(16)).map(|_| Message::Tick), ]) } } fn update_canvas_if_paused(&mut self) { if self.paused { self.update(Message::TogglePaused); self.update(Message::Tick); self.update(Message::TogglePaused); self.update(Message::None); } } } fn load_path_sounds() -> Vec { let mut entries: Vec = fs::read_dir("./assets") .unwrap() .filter_map(|res| res.ok()) .map(|e| e.path().file_name().unwrap().to_str().unwrap().to_string()) .collect(); entries.sort(); entries } fn load_path_saves() -> Vec { fs::create_dir_all("./saves").expect("fail to creat 'saves' !"); let mut saves: Vec = fs::read_dir("./saves") .unwrap() .filter_map(|res| res.ok()) .map(|e| { e.path() .file_name() .unwrap() .to_str() .unwrap() .trim_end_matches(".pmx") .to_string() }) .collect(); saves.sort(); saves }