diff --git a/Cargo.lock b/Cargo.lock index 531b6a4..b033d39 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -121,6 +121,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + [[package]] name = "android_system_properties" version = "0.1.5" @@ -352,7 +358,7 @@ dependencies = [ "bitflags 2.9.1", "cexpr", "clang-sys", - "itertools", + "itertools 0.13.0", "proc-macro2", "quote", "regex", @@ -533,6 +539,20 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chrono" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link", +] + [[package]] name = "clang-sys" version = "1.8.1" @@ -1548,6 +1568,30 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" +[[package]] +name = "iana-time-zone" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core 0.61.2", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "iced" version = "0.13.1" @@ -1562,6 +1606,23 @@ dependencies = [ "thiserror", ] +[[package]] +name = "iced_aw" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "582c517a94ce3205da98e9c10b26bb71aa36b7d7d084441d826dc912711d1bac" +dependencies = [ + "cfg-if", + "chrono", + "getrandom 0.3.3", + "iced", + "iced_fonts 0.1.1", + "itertools 0.14.0", + "num-format", + "num-traits", + "web-time", +] + [[package]] name = "iced_core" version = "0.13.2" @@ -1582,6 +1643,25 @@ dependencies = [ "web-time", ] +[[package]] +name = "iced_fonts" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df7deb0800a850ee25c8a42559f72c0f249e577feb3aad37b9b65dc1e517e52a" +dependencies = [ + "iced_core", +] + +[[package]] +name = "iced_fonts" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7db9d45e87bc3ac22ff82e2d9dea9139f1c055137f1e0f39df3d68110fcee7a9" +dependencies = [ + "iced_core", + "iced_widget", +] + [[package]] name = "iced_futures" version = "0.13.2" @@ -1757,6 +1837,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.15" @@ -2176,6 +2265,16 @@ dependencies = [ "syn 2.0.101", ] +[[package]] +name = "num-format" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3" +dependencies = [ + "arrayvec", + "itoa", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -2729,6 +2828,8 @@ name = "polygomusic" version = "0.1.0" dependencies = [ "iced", + "iced_aw", + "iced_fonts 0.2.1", "kira", "regex", "serde", @@ -4170,10 +4271,51 @@ version = "0.54.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65" dependencies = [ - "windows-result", + "windows-result 0.1.2", "windows-targets 0.52.6", ] +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result 0.3.4", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + [[package]] name = "windows-result" version = "0.1.2" @@ -4183,6 +4325,24 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.45.0" diff --git a/Cargo.toml b/Cargo.toml index f2e3604..712c6bf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,3 +10,5 @@ tokio = {version = "1.45.1", features = ["time"]} serde = { version = "1.0", features = ["derive"] } serde_json = "1.0.140" regex = "1.11.1" +iced_aw = {version = "0.12.2", default-features = true} +iced_fonts = "0.2.1" diff --git a/fonts/EnvyCodeRNerdFont-Bold.ttf b/fonts/EnvyCodeRNerdFont-Bold.ttf new file mode 100644 index 0000000..21cc296 Binary files /dev/null and b/fonts/EnvyCodeRNerdFont-Bold.ttf differ diff --git a/fonts/EnvyCodeRNerdFont-Italic.ttf b/fonts/EnvyCodeRNerdFont-Italic.ttf new file mode 100644 index 0000000..b05ba03 Binary files /dev/null and b/fonts/EnvyCodeRNerdFont-Italic.ttf differ diff --git a/fonts/EnvyCodeRNerdFont-Regular.ttf b/fonts/EnvyCodeRNerdFont-Regular.ttf new file mode 100644 index 0000000..ac7a510 Binary files /dev/null and b/fonts/EnvyCodeRNerdFont-Regular.ttf differ diff --git a/fonts/EnvyCodeRNerdFontMono-Bold.ttf b/fonts/EnvyCodeRNerdFontMono-Bold.ttf new file mode 100644 index 0000000..badd20b Binary files /dev/null and b/fonts/EnvyCodeRNerdFontMono-Bold.ttf differ diff --git a/fonts/EnvyCodeRNerdFontMono-Italic.ttf b/fonts/EnvyCodeRNerdFontMono-Italic.ttf new file mode 100644 index 0000000..20b1e3d Binary files /dev/null and b/fonts/EnvyCodeRNerdFontMono-Italic.ttf differ diff --git a/fonts/EnvyCodeRNerdFontMono-Regular.ttf b/fonts/EnvyCodeRNerdFontMono-Regular.ttf new file mode 100644 index 0000000..b89c960 Binary files /dev/null and b/fonts/EnvyCodeRNerdFontMono-Regular.ttf differ diff --git a/fonts/EnvyCodeRNerdFontPropo-Bold.ttf b/fonts/EnvyCodeRNerdFontPropo-Bold.ttf new file mode 100644 index 0000000..24609e0 Binary files /dev/null and b/fonts/EnvyCodeRNerdFontPropo-Bold.ttf differ diff --git a/fonts/EnvyCodeRNerdFontPropo-Italic.ttf b/fonts/EnvyCodeRNerdFontPropo-Italic.ttf new file mode 100644 index 0000000..d7b65f3 Binary files /dev/null and b/fonts/EnvyCodeRNerdFontPropo-Italic.ttf differ diff --git a/fonts/EnvyCodeRNerdFontPropo-Regular.ttf b/fonts/EnvyCodeRNerdFontPropo-Regular.ttf new file mode 100644 index 0000000..f1b888e Binary files /dev/null and b/fonts/EnvyCodeRNerdFontPropo-Regular.ttf differ diff --git a/src/main.rs b/src/main.rs index 4d0e37b..cbc3ab3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,30 +8,25 @@ mod message; use message::Message; mod utils; -use utils::str_to_sec; +use utils::{is_delta_format_valid, str_to_sec}; use std::fs; -use iced::Element; +use iced::widget::{TextInput, column, text}; +use iced::{Application, Element, Font, font}; use iced::{ Color, Length, Task, Theme, widget::{Column, button, canvas, container, pick_list, row, scrollable, slider}, }; -use iced::{ - Font, - widget::{ - TextInput, column, text, - text_input::{Icon, Side}, - }, -}; -use regex::Regex; 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 { @@ -55,6 +50,8 @@ fn main() -> iced::Result { }; iced::application("My App", MyApp::update, MyApp::view) .theme(move |_| polytheme.clone()) + .font(FONT_BYTES) + .default_font(FONT) .subscription(MyApp::subscription) .run_with(MyApp::new) } @@ -69,12 +66,16 @@ struct MyApp { all_saves: Vec, current_delta: f32, str_music_length: String, + str_time: String, } 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(), @@ -86,6 +87,7 @@ impl MyApp { music: Music::default(), current_delta: 0.0, str_music_length: "01:00:00".to_string(), + str_time: "00:00:00".to_string(), }, Task::none(), ) @@ -112,6 +114,7 @@ impl MyApp { 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) => { @@ -148,27 +151,33 @@ impl MyApp { } Message::ToggleSavePanel => self.show_save_panel = !self.show_save_panel, Message::FileNameChanged(s) => self.music.file_name = s, - Message::LengthChange(s) => self.str_music_length = 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::SetMusicLength => { - if self.is_length_valid() { - self.music.length = str_to_sec(&self.str_music_length) - } - } + Message::ChangeDelta(f) => { self.current_delta = f; self.music.fix_teta(self.current_delta); // update the red dot on canvas - if self.paused { - self.update(Message::TogglePaused); - self.update(Message::Tick); - self.update(Message::TogglePaused); + 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); @@ -179,6 +188,16 @@ impl MyApp { 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(); + } } } @@ -202,7 +221,7 @@ impl MyApp { i += 1; column![ row![ - text(&polygon.name), + text(&polygon.name).font(FONT), button("Remove").on_press(Message::Remove(i)), pick_list( ["Black", "Blue", "Green", "Pink", "Yellow", "Cyan"] @@ -270,24 +289,9 @@ impl MyApp { save_panel.push(button("Load").on_press(Message::Load).into()); } column![ - text(&self.music.file_name).size(32.0), + text("Polymusic").size(32.0), row(save_panel).spacing(20), row![ - text("Music Length").size(20), - TextInput::new("MM:SS:CS", &self.str_music_length) - .on_input(|new_value| Message::LengthChange(new_value)) - .icon(Icon { - font: Font::DEFAULT, - code_point: if self.is_length_valid() { - '\u{2705}' - } else { - '\u{274C}' - }, - size: None, - spacing: 10., - side: Side::Left, - }), - button("Valid").on_press(Message::SetMusicLength), text(txt_nb_rev).size(20), button("Increment").on_press(Message::ButtonPressedIncrement), button("Decrement").on_press(Message::ButtonPressedDecrement), @@ -314,15 +318,19 @@ impl MyApp { .spacing(20), column![ row![ - button("Toggle Play").on_press(Message::TogglePaused), - text(format!( - "{}/{}", - delta_to_string(self.current_delta), - delta_to_string(self.music.length) - )) - .size(20.0), - button("Add Point").on_press(Message::AddPoint), - button("Remove Point").on_press(Message::RemovePoint), + button(text(if self.paused { "󰐊" } else { "󰏤" }).size(28)) + .on_press(Message::TogglePaused), + 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), + button(text("").size(28)).on_press(Message::AddPoint), + button(text("").size(28)).on_press(Message::RemovePoint), + button(text("").size(28)).on_press(Message::SlidePointLeft), + button(text("").size(28)).on_press(Message::SlidePointRight), ] .spacing(20), column![ @@ -346,16 +354,20 @@ impl MyApp { .into() } - fn is_length_valid(&self) -> bool { - let re = Regex::new(r"^\d{2}:\d{2}:\d{2}$").unwrap(); - re.is_match(self.str_music_length.as_str()) - } 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 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 { diff --git a/src/message.rs b/src/message.rs index 6184463..84aa945 100644 --- a/src/message.rs +++ b/src/message.rs @@ -13,10 +13,12 @@ pub enum Message { Load, FileNameChanged(String), TogglePaused, - SetMusicLength, LengthChange(String), ChangeDelta(f32), AddPoint, RemovePoint, ClickedOnTimeLine(f32), + ChangeDeltaString(String), + SlidePointLeft, + SlidePointRight, } diff --git a/src/music.rs b/src/music.rs index 49443b4..0863593 100644 --- a/src/music.rs +++ b/src/music.rs @@ -18,6 +18,7 @@ use iced::widget::canvas::Stroke; use iced::widget::canvas::Style; use iced::{Color, Rectangle, Renderer, Theme}; use std::f32::consts::PI; +use std::mem::swap; use std::time::Duration; #[derive(Serialize, Deserialize)] pub struct Music { @@ -156,6 +157,21 @@ impl Music { pub fn remove_polygon(&mut self, delta: f32, i: usize) { self.find_poly_frame(delta).polygons.remove(i); } + + pub fn slide_to_left(&mut self, delta: f32) { + let i = self.find_index_frame(delta); + if i > 0 { + let (left, right) = self.poly_frame.split_at_mut(i); + swap(&mut left[i - 1].1, &mut right[0].1); + } + } + pub fn slide_to_right(&mut self, delta: f32) { + let i = self.find_index_frame(delta); + if i < self.poly_frame.len() - 1 { + let (left, right) = self.poly_frame.split_at_mut(i + 1); + swap(&mut left[i].1, &mut right[0].1); + } + } } impl canvas::Program for Music { // No internal state @@ -171,7 +187,7 @@ impl canvas::Program for Music { ) -> Vec { let mut geo_small_frame: Vec = vec![]; let mut geo_cursor: Vec = vec![]; - let mut frame = canvas::Frame::new(renderer, bounds.size()); + let frame = canvas::Frame::new(renderer, bounds.size()); let mut toggle_color = true; let padding = 8.; let w = bounds.width - (padding * 2.); diff --git a/src/utils.rs b/src/utils.rs index a720411..47f4545 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,6 +1,12 @@ use crate::Polygon; use iced::Color; use kira::sound::static_sound::StaticSoundData; +use regex::Regex; + +pub fn is_delta_format_valid(str: &str) -> bool { + let re = Regex::new(r"^\d{1,2}:\d{1,2}:\d{1,2}$").unwrap(); + re.is_match(str) +} pub fn string_to_color>(s: S) -> Color { match s.as_ref() { "Green" => Color::from_rgb(0.0, 1.0, 0.0),