historic , undo and redo
This commit is contained in:
@@ -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<Message> {
|
||||
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)
|
||||
|
||||
53
src/history.rs
Normal file
53
src/history.rs
Normal file
@@ -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<HistoricItem>,
|
||||
future: Vec<HistoricItem>,
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
113
src/main.rs
113
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::<DefaultBackend>::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<Message> {
|
||||
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),
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
36
src/music.rs
36
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) {
|
||||
|
||||
Reference in New Issue
Block a user