mod polygon_draw; use polygon_draw::Polygon; mod music; use music::Music; mod utils; use std::fs; use iced::{Element, window}; use iced::{ Length, Task, Theme, widget::{ Column, TextInput, button, canvas, column, container, pick_list, row, scrollable, slider, text, }, }; use std::f32::consts::PI; use std::time::Instant; use kira::{ AudioManager, AudioManagerSettings, DefaultBackend, sound::static_sound::StaticSoundData, }; fn main() -> iced::Result { iced::application("My App", MyApp::update, MyApp::view) .theme(|_| Theme::Dark) .subscription(MyApp::subscription) .run_with(MyApp::new) } #[derive(Debug, Clone)] enum Message { WindowEvent(window::Event), ButtonPressedIncrement, ButtonPressedDecrement, Tick, AddPolygon(String), ChangeTeta(usize, f32), Remove(usize), ChangeColor(usize, String), ChangeSound(usize, String), ToggleSavePanel, Save, Load, FileNameChanged(String), } struct MyApp { music: Music, time_last_frame: Instant, show_save_panel: bool, paused: bool, audio_manager: AudioManager, default_sound: StaticSoundData, all_sounds: Vec, all_saves: Vec, current_delta: f32, } impl MyApp { fn new() -> (Self, Task) { let manager = AudioManager::::new(AudioManagerSettings::default()) .expect("Error to load AudioManager"); let sound_data = StaticSoundData::from_file("assets/tick.ogg").expect("Fail to load audio"); ( Self { time_last_frame: Instant::now(), audio_manager: manager, default_sound: sound_data.clone(), show_save_panel: true, paused: false, all_sounds: load_path_sounds(), all_saves: load_path_saves(), music: Music::default(), current_delta: 0.0, }, Task::none(), ) } fn update(&mut self, message: Message) { match message { Message::WindowEvent(window::Event::Resized(size)) => { println!("Resize detected: {}x{}", size.width, size.height); } Message::ButtonPressedIncrement => self.music.nb_sec_for_rev += 0.5, Message::ButtonPressedDecrement => { if self.music.nb_sec_for_rev > 0.5 { self.music.nb_sec_for_rev -= 0.5; } } Message::AddPolygon(s) => { self.music.add_polygon(self.current_delta, s); } Message::Tick => { if !self.paused { 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(); } } Message::Remove(i) => { self.music.remove_polygon(self.current_delta, i - 1); } Message::ChangeTeta(i, teta) => { self.music.set_teta(self.current_delta, i, teta); } Message::ChangeColor(i, s) => { self.music.set_color(self.current_delta, i, s); } 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 => { let json = serde_json::to_string_pretty(&self.music).unwrap(); fs::write(format!("./saves/{0}", &self.music.file_name), json).unwrap(); } Message::Load => { let json = fs::read_to_string(format!("./saves/{0}", &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.current_delta); } 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, _ => {} } } fn view(&self) -> iced::Element { let txt_nb_rev = format!( "Number of second for revolution : {}", self.music.nb_sec_for_rev ); 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; i += 1; column![ row![ text(&polygon.name), button("Remove").on_press(Message::Remove(i)), pick_list( ["Black", "Blue", "Green", "Pink", "Yellow", "Cyan"] .map(|s| s.to_string()) .to_vec(), Some(&polygon.color_name), move |s| { Message::ChangeColor(current_index, s) } ), pick_list(entries.clone(), Some(&polygon.sound_name), move |s| { Message::ChangeSound(current_index, s) }), ] .spacing(20), slider(0.0..=2.0 * PI, polygon.global_teta, move |f| { Message::ChangeTeta(current_index, f) }) .step(PI / 84f32), // 84 | 4 for do PI / 4 ] .spacing(20) .into() }) .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)); 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(txt_nb_rev), row![ button("Increment").on_press(Message::ButtonPressedIncrement), button("Decrement").on_press(Message::ButtonPressedDecrement), ], text("Polygon options"), pick_list(all_options, Some("Choose polygon".to_string()), |s| { Message::AddPolygon(s) }), polygon_column.spacing(10), ] .spacing(10) .height(Length::FillPortion(1)) .width(Length::FillPortion(2)), ] .height(Length::FillPortion(2)) .spacing(20), row![text("futur time line")] .height(Length::FillPortion(1)) .width(Length::FillPortion(1)), ] .spacing(25) .padding(25) .into() } fn subscription(&self) -> iced::Subscription { iced::Subscription::batch([ window::events().map(|(_id, event)| Message::WindowEvent(event)), iced::time::every(std::time::Duration::from_millis(16)).map(|_| Message::Tick), ]) } } fn dummy_sound() -> StaticSoundData { StaticSoundData::from_file("assets/tick.ogg").expect("Fail to load audio") } fn dummy_instant() -> Instant { Instant::now() } fn dummy_audio_manager() -> AudioManager { AudioManager::::new(AudioManagerSettings::default()) .expect("Error to load AudioManager") } 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().to_string()) .collect() }