diff --git a/src/gui.rs b/src/gui.rs index 4b15c90..f0d9c03 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -3,11 +3,11 @@ use crate::message::Message; use iced::alignment::{Horizontal, Vertical}; use iced::widget::{TextInput, column, text}; +use iced::{Element, Theme}; use iced::{ Length, Padding, widget::{Column, Container, button, canvas, container, pick_list, row, scrollable, slider}, }; -use iced::{Element, Theme}; use iced_aw::menu::{self, Item}; use iced_aw::menu_bar; use iced_aw::widget::color_picker; @@ -115,7 +115,9 @@ pub fn music_view(app: &MyApp) -> iced::Element { menu_tpl_1(vec![ Item::new("󰐊 SPACE"), Item::new("󰆓 CTRL+S"), - Item::new("Start ") + Item::new("󰈼 ARROW UP"), + Item::new("󰕌 CTRL+Z"), + Item::new("󰑎 CTRL+Y"), ]) )) .draw_path(menu::DrawPath::Backdrop) diff --git a/src/history.rs b/src/history.rs new file mode 100644 index 0000000..a02bfd1 --- /dev/null +++ b/src/history.rs @@ -0,0 +1,53 @@ +use crate::{message::Message, music::Music}; + +#[derive(Clone, Debug)] +struct HistoricItem { + old: Message, + new: Message, + delta: f32, +} + +#[derive(Debug)] +pub struct Historic { + past: Vec, + future: Vec, + pub ignore_add: bool, +} + +impl Historic { + pub fn new() -> Self { + Historic { + past: vec![], + future: vec![], + ignore_add: false, + } + } + + pub fn add(&mut self, old: Message, new: Message, delta: f32) { + if !self.ignore_add { + self.future = vec![]; + self.past.push(HistoricItem { + old: old, + new: new, + delta: delta, + }); + } + } + pub fn undo(&mut self) -> Option<(Message, f32)> { + if let Some(item) = self.past.pop() { + self.future.push(item.clone()); + Some((item.old.clone(), item.delta)) + } else { + None + } + } + + pub fn redo(&mut self) -> Option<(Message, f32)> { + if let Some(item) = self.future.pop() { + self.past.push(item.clone()); + Some((item.new.clone(), item.delta)) + } else { + None + } + } +} diff --git a/src/main.rs b/src/main.rs index 0725f61..70ae8bd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,9 @@ use polygon_draw::Polygon; mod music; use music::Music; +mod history; +use history::Historic; + mod message; use message::Message; @@ -76,6 +79,7 @@ struct MyApp { mode_file_load: bool, file_name_to_creat: String, show_warning_message_creat: bool, + historic: Historic, } impl MyApp { @@ -83,8 +87,6 @@ impl MyApp { let manager = AudioManager::::new(AudioManagerSettings::default()) .expect("Error to load AudioManager"); - //let font_bytes = include_bytes!("../fonts/"); - ( Self { time_last_frame: Instant::now(), @@ -101,6 +103,7 @@ impl MyApp { mode_file_load: true, file_name_to_creat: "Default File Name".to_string(), show_warning_message_creat: false, + historic: Historic::new(), }, Task::none(), ) @@ -108,8 +111,13 @@ impl MyApp { fn update(&mut self, message: Message) { match message { Message::AddPolygon(s) => { - self.music.add_polygon(self.current_delta, 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::Tick => { if self.current_delta >= self.music.length { @@ -126,19 +134,36 @@ impl MyApp { } } Message::Remove(i) => { - self.music.remove_polygon(self.current_delta, i); + let name = self.music.remove_polygon(self.current_delta, i); self.already_save = false; + self.historic.add( + Message::AddPolygon(name), + Message::Remove(i), + self.current_delta, + ); } Message::ChangeTeta(i, teta) => { - self.music.set_teta(self.current_delta, 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"); - self.music.set_sound(self.current_delta, i, sound, s); + 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(); @@ -176,6 +201,11 @@ impl MyApp { 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; } @@ -202,13 +232,24 @@ impl MyApp { } 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)); @@ -218,12 +259,22 @@ impl MyApp { self.music.slide_to_left(self.current_delta); self.music.fix_teta(self.current_delta); self.update_canvas_if_paused(); + self.historic.add( + Message::SlidePointRight, + 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::SlidePointLeft, + Message::SlidePointRight, + self.current_delta, + ); } Message::ChangeDegree(i, s) => { let mut mut_s = s; @@ -247,6 +298,14 @@ impl MyApp { 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; } @@ -267,14 +326,19 @@ impl MyApp { self.music.set_color_picker(self.current_delta, i, true); } Message::ChooseColor(i, color) => { - self.music.set_color(self.current_delta, 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::None => {} 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; @@ -284,6 +348,23 @@ impl MyApp { 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 => {} } } @@ -297,6 +378,22 @@ impl MyApp { 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), diff --git a/src/message.rs b/src/message.rs index 7fbfd3f..1b1eb57 100644 --- a/src/message.rs +++ b/src/message.rs @@ -7,27 +7,32 @@ pub enum Message { SetFileNameCreat(String), GoToLoadView, ForceToQuit, - ChangeNbPerSec(String), Tick, - AddPolygon(String), - ChangeTeta(usize, f32), - Remove(usize), - ChangeSound(usize, String), Save, Load, FileNameChanged(String), TogglePaused, - LengthChange(String), ChangeDelta(f32), - AddPoint, - RemovePoint, ClickedOnTimeLine(f32), ChangeDeltaString(String), + + ChangeNbPerSec(String), + AddPolygon(String), + ChangeTeta(usize, f32), + Remove(usize), + ChangeSound(usize, String), + LengthChange(String), + AddPoint, + RemovePoint, SlidePointLeft, SlidePointRight, ChangeDegree(usize, String), + ReAddPoint, ChooseColor(usize, Color), CancelColor(usize), SubmitColor(usize), + + Undo, + Redo, } diff --git a/src/music.rs b/src/music.rs index ae2d706..a8193b2 100644 --- a/src/music.rs +++ b/src/music.rs @@ -31,6 +31,8 @@ pub struct Music { teta: f32, #[serde(skip)] pub current_delta: f32, + #[serde(skip)] + point_removed: Vec<(f32, PolygonFrame)>, } impl Music { @@ -77,6 +79,7 @@ impl Music { length: 60.0, teta: 0., current_delta: 0., + point_removed: vec![], } } @@ -116,19 +119,25 @@ impl Music { index: usize, sound: StaticSoundData, sound_name: String, - ) { + ) -> String { let current_frame = self.find_poly_frame(delta); current_frame.polygons[index].sound = sound; + let out = current_frame.polygons[index].sound_name.clone(); current_frame.polygons[index].sound_name = sound_name; + out } - pub fn set_teta(&mut self, delta: f32, index: usize, teta: f32) { + pub fn set_teta(&mut self, delta: f32, index: usize, teta: f32) -> f32 { + let out = self.find_poly_frame(delta).polygons[index].global_teta; self.find_poly_frame(delta).polygons[index].global_teta = teta; + out } - pub fn set_color(&mut self, delta: f32, index: usize, color: Color) { + pub fn set_color(&mut self, delta: f32, index: usize, color: Color) -> Color { let current_frame = self.find_poly_frame(delta); + let out = current_frame.polygons[index].color.clone(); current_frame.polygons[index].color = color; + out } pub fn set_color_picker(&mut self, delta: f32, i: usize, b: bool) { @@ -146,9 +155,19 @@ impl Music { self.poly_frame .insert(pos, (delta, self.current_frame(delta).clone())); } + pub fn add_point_old(&mut self) { + if let Some(pair) = self.point_removed.pop() { + let pos = self + .poly_frame + .binary_search_by(|(d, _)| d.partial_cmp(&pair.0).unwrap()) + .unwrap_or_else(|e| e); + self.poly_frame.insert(pos, pair); + } + } pub fn remove_point(&mut self, delta: f32) { let i = self.find_index_frame(delta); if i != 0 { + self.point_removed.push(self.poly_frame[i].clone()); self.poly_frame.remove(i); } } @@ -159,8 +178,15 @@ impl Music { current_frame.polygons.push(poly); } - pub fn remove_polygon(&mut self, delta: f32, i: usize) { - self.find_poly_frame(delta).polygons.remove(i); + pub fn remove_polygon(&mut self, delta: f32, i: usize) -> String { + let pf = self.find_poly_frame(delta); + let mut i = i; + if i == usize::MAX { + i = pf.polygons.len() - 1 + } + let out = pf.polygons[i].name.clone(); + pf.polygons.remove(i); + out } pub fn slide_to_left(&mut self, delta: f32) {