From e5b3831c3a276ebaec249c04ac8edf3eee604bf3 Mon Sep 17 00:00:00 2001 From: Dukantic Date: Fri, 18 Jul 2025 17:51:17 +0200 Subject: [PATCH] fix --- Cargo.lock | 24 ++- Cargo.toml | 1 + "\\" | 479 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 23 ++- 4 files changed, 512 insertions(+), 15 deletions(-) create mode 100644 "\\" diff --git a/Cargo.lock b/Cargo.lock index b033d39..225ca6b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -431,6 +431,15 @@ dependencies = [ "piper", ] +[[package]] +name = "borsh" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8646f98db542e39fc66e68a20b2144f6a732636df7c2354e74645faaa433ce" +dependencies = [ + "cfg_aliases 0.2.1", +] + [[package]] name = "bumpalo" version = "3.17.0" @@ -1638,7 +1647,7 @@ dependencies = [ "once_cell", "palette", "rustc-hash 2.1.1", - "smol_str", + "smol_str 0.2.2", "thiserror", "web-time", ] @@ -2834,6 +2843,7 @@ dependencies = [ "regex", "serde", "serde_json", + "smol_str 0.3.2", "tokio", ] @@ -3352,6 +3362,16 @@ dependencies = [ "serde", ] +[[package]] +name = "smol_str" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9676b89cd56310a87b93dec47b11af744f34d5fc9f367b829474eec0a891350d" +dependencies = [ + "borsh", + "serde", +] + [[package]] name = "softbuffer" version = "0.4.6" @@ -4647,7 +4667,7 @@ dependencies = [ "rustix 0.38.44", "sctk-adwaita", "smithay-client-toolkit", - "smol_str", + "smol_str 0.2.2", "tracing", "unicode-segmentation", "wasm-bindgen", diff --git a/Cargo.toml b/Cargo.toml index 199b31b..748f0b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,3 +13,4 @@ serde_json = "1.0.140" regex = "1.11.1" iced_aw = {version = "0.12.2", default-features = true} iced_fonts = "0.2.1" +smol_str = "0.3.2" diff --git "a/\\" "b/\\" new file mode 100644 index 0000000..fce0a5b --- /dev/null +++ "b/\\" @@ -0,0 +1,479 @@ +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() +} diff --git a/src/main.rs b/src/main.rs index 9ea75d6..4d8e3a6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,5 @@ mod polygon_draw; -use iced::advanced::graphics::core::SmolStr; -use iced::keyboard::{Key, Modifiers}; +use iced::advanced::subscription; use polygon_draw::Polygon; mod music; @@ -14,12 +13,17 @@ use utils::{is_delta_format_valid, str_to_sec}; use std::fs; +use smol_str; +use smol_str::SmolStr; + use iced::widget::{TextInput, column, text}; use iced::{ - Color, Length, Padding, Task, Theme, keyboard, + Color, Event, Length, Padding, Task, Theme, + event::{self, Status}, + keyboard::{Event::KeyPressed, Key, key::Named}, widget::{Column, button, canvas, container, pick_list, row, scrollable, slider}, }; -use iced::{Element, Font, Subscription}; +use iced::{Element, Font, Subscription, keyboard}; use iced_aw::widget::color_picker; use std::f32::consts::PI; @@ -428,15 +432,8 @@ impl MyApp { if self.paused { Subscription::none() } else { - Subscription::batch(vec![ - iced::time::every(std::time::Duration::from_millis(16)).map(|_| Message::Tick), - iced::keyboard::on_key_press(|key: Key, modifiers: Modifiers| { - println!("Key pressed: {:?}, Modifiers: {:?}", key, modifiers); - match (key, modifiers) { - (Key::Character(c), m) if m.control() && c == "s" => Some(Message::Save), - _ => None, - } - }), + iced::Subscription::batch([ + iced::time::every(std::time::Duration::from_millis(16)).map(|_| Message::Tick) ]) } }