mod polygon_draw; use iced::advanced::graphics::core::SmolStr; use iced::keyboard::{Key, Modifiers}; use polygon_draw::Polygon; mod music; use music::Music; mod message; use message::Message; mod utils; use utils::{is_delta_format_valid, str_to_sec}; use std::fs; use iced::widget::{TextInput, column, text}; use iced::{ Color, Length, Padding, Task, Theme, keyboard, widget::{Column, button, canvas, container, pick_list, row, scrollable, slider}, }; use iced::{Element, Font, Subscription}; use iced_aw::widget::color_picker; use std::f32::consts::PI; use std::time::Instant; use kira::{ AudioManager, AudioManagerSettings, DefaultBackend, sound::static_sound::StaticSoundData, }; const FONT_BYTES: &[u8] = include_bytes!("../fonts/EnvyCodeRNerdFontMono-Regular.ttf"); const FONT: Font = Font::with_name("EnvyCodeR Nerd Font Mono"); use crate::utils::delta_to_string; 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("My App", MyApp::update, MyApp::view) .theme(move |_| polytheme.clone()) .font(iced_fonts::REQUIRED_FONT_BYTES) .font(FONT_BYTES) .default_font(FONT) .antialiasing(true) .subscription(MyApp::subscription) .run_with(MyApp::new) } struct MyApp { music: Music, time_last_frame: Instant, show_save_panel: bool, paused: bool, audio_manager: AudioManager, all_sounds: Vec, all_saves: Vec, current_delta: f32, str_music_length: String, str_time: String, can_unpaused: bool, } impl MyApp { fn new() -> (Self, Task) { let manager = AudioManager::::new(AudioManagerSettings::default()) .expect("Error to load AudioManager"); //let font_bytes = include_bytes!("../fonts/"); ( Self { time_last_frame: Instant::now(), audio_manager: manager, show_save_panel: false, 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, }, Task::none(), ) } fn update(&mut self, message: Message) { match message { Message::AddPolygon(s) => { self.music.add_polygon(self.current_delta, s); } 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) => { self.music.remove_polygon(self.current_delta, i); } Message::ChangeTeta(i, teta) => { self.music.set_teta(self.current_delta, i, teta); } Message::ChangeSound(i, s) => { let sound = StaticSoundData::from_file(format!("./assets/{s}")) .expect("Fail to load audio"); self.music.set_sound(self.current_delta, i, sound, s); } Message::Save => { dbg!("Save file"); 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(); } 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(); } Err(e) => { eprintln!("Error, no saves with this name to load, {e} "); } } } Message::ToggleSavePanel => self.show_save_panel = !self.show_save_panel, 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.music.length = sec; } } 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.update(Message::ChangeDelta(str_to_sec(&s))); } self.str_time = s; } Message::AddPoint => { self.music.add_point(self.current_delta); } Message::RemovePoint => { self.music.remove_point(self.current_delta); } Message::ClickedOnTimeLine(f) => { self.update(Message::ChangeDelta(f)); } Message::SlidePointLeft => { self.music.slide_to_left(self.current_delta); self.music.fix_teta(self.current_delta); self.update_canvas_if_paused(); } Message::SlidePointRight => { self.music.slide_to_right(self.current_delta); self.music.fix_teta(self.current_delta); self.update_canvas_if_paused(); } 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())) } } 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.music.nb_sec_for_rev = val } } 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) => { self.music.set_color(self.current_delta, i, color); self.music.set_color_picker(self.current_delta, i, false); self.can_unpaused = true; } Message::None => {} } } fn view(&self) -> iced::Element { let mut i = 0; let entries = self.all_sounds.clone(); //Create all polygon options let polygon_rows: Vec> = self .music .current_frame(self.current_delta) .polygons .iter() .map(|polygon| { let current_index = i; let but = button(text("").size(20).center()).on_press(Message::SubmitColor(i)); let c = column![ row![ text(&polygon.name).size(24), button(text("").size(20)).on_press(Message::Remove(i)), color_picker( polygon.show_color_picker, polygon.color, but, Message::CancelColor(i), move |color| Message::ChooseColor(i, color) ), pick_list(entries.clone(), Some(&polygon.sound_name), move |s| { Message::ChangeSound(current_index, s) }) .text_size(20), ] .spacing(20), row![ TextInput::new("90", &polygon.global_teta.to_degrees().floor().to_string()) .on_input(move |new_value| Message::ChangeDegree( current_index, new_value )) .width(Length::FillPortion(1)), row![ slider(0.0..=2.0 * PI, polygon.global_teta, move |f| { Message::ChangeTeta(current_index, f) }) .step(2. * PI / 10_000.) .width(Length::FillPortion(9)) ] .padding(Padding::from(16)), ] .spacing(5), ] .spacing(10) .into(); i += 1; c }) .collect(); let ngon_options: Vec = (5..=42).map(|sides| format!("Ngon{sides}")).collect(); let all_options: Vec = [ "Segment", "Triangle", "Square", "Nr6In30", "Nr7In30", "Nr8In30", "Nr9In30", "Nr8In42", "Nr9In42", "Nr10aIn42", "Nr10bIn42", ] .iter() .map(|s| s.to_string()) .chain(ngon_options) .collect(); let polygon_column = scrollable(Column::with_children(polygon_rows).spacing(24)); let mut save_panel: Vec> = vec![ button("Toggle Save Panel") .on_press(Message::ToggleSavePanel) .into(), ]; if self.show_save_panel { save_panel.push( TextInput::new("Name File", &self.music.file_name) .on_input(|new_value| Message::FileNameChanged(new_value)) .into(), ); save_panel.push(button("Save").on_press(Message::Save).into()); save_panel.push( pick_list( self.all_saves.clone(), Some(&self.music.file_name), move |s| Message::FileNameChanged(s), ) .into(), ); save_panel.push(button("Load").on_press(Message::Load).into()); } column![ text("Polymusic").size(32.0), row(save_panel).spacing(20), row![ container( canvas(self.music.current_frame(self.current_delta)) .height(Length::FillPortion(1)) .width(Length::FillPortion(1)) ), column![ text("Polygon options").size(26), pick_list(all_options, Some("Choose polygon".to_string()), |s| { Message::AddPolygon(s) }) .text_size(18), polygon_column, ] .spacing(10) .height(Length::FillPortion(1)) .width(Length::FillPortion(2)), ] .height(Length::FillPortion(2)) .spacing(20), column![ row![ button(text(if self.paused { "󰐊" } else { "󰏤" }).size(28).center()) .on_press(if self.can_unpaused { Message::TogglePaused } else { Message::None }) .width(Length::FillPortion(1)), row![ TextInput::new("MM:SS:CS", &self.str_time) .on_input(|new_value| Message::ChangeDeltaString(new_value)) .size(28), text("/").size(30), TextInput::new("MM:SS:CS", &self.str_music_length) .on_input(|new_value| Message::LengthChange(new_value)) .size(28), ] .width(Length::FillPortion(10)), TextInput::new("1.0", &format!("{:.1} sec/rev", &self.music.nb_sec_for_rev)) .on_input(|new_value| Message::ChangeNbPerSec(new_value)) .size(28) .width(Length::FillPortion(2)), button(text("").size(28).center()) .on_press(Message::SlidePointLeft) .width(Length::FillPortion(1)), button(text("").size(28).center()) .on_press(Message::AddPoint) .width(Length::FillPortion(1)), button(text("").size(28).center()) .on_press(Message::SlidePointRight) .width(Length::FillPortion(1)), button(text("").size(28).center()) .on_press(Message::RemovePoint) .width(Length::FillPortion(1)), ] .spacing(20), column![ /* slider(0.0..=self.music.length, self.current_delta, move |f| { Message::ChangeDelta(f) }) .step(&self.music.length / 10_000.),*/ canvas(&self.music) .height(Length::FillPortion(1)) .width(Length::FillPortion(1)) ] .spacing(0), ] .spacing(20) .height(Length::FillPortion(1)) .width(Length::FillPortion(1)), ] .spacing(25) .padding(25) .into() } fn subscription(&self) -> iced::Subscription { if self.paused { Subscription::none() } else { Subscription::batch(vec![ iced::time::every(std::time::Duration::from_millis(16)).map(|_| Message::Tick), keyboard::events().map(|event| { match event { keyboard::Event::KeyPressed { key_code, .. } => { Message::KeyPressed(key_code) } _ => Message::Ignored, // Vous devrez ajouter cette variante à votre enum Message } }), ]) } } fn update_canvas_if_paused(&mut self) { if self.paused { self.update(Message::TogglePaused); self.update(Message::Tick); self.update(Message::TogglePaused); } } } 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' !"); 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() }