26 Commits

Author SHA1 Message Date
607f65c913 cargo fix 2025-07-19 18:56:22 +02:00
82ca2ced24 historic , undo and redo 2025-07-19 18:56:06 +02:00
bb64de1955 remove to do list and add information 2025-07-19 17:08:35 +02:00
54949a9db0 remove file not used 2025-07-19 17:03:14 +02:00
d841c9fdb4 zoom on time line 2025-07-19 13:34:15 +02:00
ad6d09b8a5 cargo fix 2025-07-19 01:18:50 +02:00
e7b1000205 load menu and save carfully 2025-07-19 01:18:34 +02:00
625828f7ed save 2025-07-18 20:33:00 +02:00
5cd8bf90c7 fix cargo 2025-07-18 20:18:22 +02:00
fc54c4b9ee menu bar 2025-07-18 19:51:28 +02:00
46861440fd fix by cargo 2025-07-18 18:22:19 +02:00
fdfea98c50 shortcut 2025-07-18 18:21:36 +02:00
a512eff61d fix 2025-07-18 17:51:36 +02:00
bbeff369e8 fix 2025-07-18 17:51:17 +02:00
3788c3e631 fix 2025-07-17 22:18:44 +02:00
2e61e5b629 bug fix 2025-07-10 20:21:27 +02:00
ea708dfe85 fix 2025-07-10 15:26:15 +02:00
9f59e68b2c fix by cargo 2025-07-10 15:09:45 +02:00
607cf17888 add color picker and better buttons 2025-07-10 15:07:52 +02:00
984cec5741 antialiazing 2025-07-10 00:33:29 +02:00
8eb84f6780 del notused font 2025-07-10 00:26:31 +02:00
719f0c1332 fonts and glowup 2025-07-10 00:25:42 +02:00
064190d1a7 add small canvas on time line 2025-07-09 20:50:39 +02:00
a426c7bb98 colorfull 2025-07-09 18:07:53 +02:00
440032ffdb better color 2025-07-09 17:20:00 +02:00
7047679ace add to do list 2025-07-09 15:24:20 +02:00
12 changed files with 1298 additions and 459 deletions

188
Cargo.lock generated
View File

@@ -121,6 +121,12 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04"
[[package]]
name = "android-tzdata"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
[[package]] [[package]]
name = "android_system_properties" name = "android_system_properties"
version = "0.1.5" version = "0.1.5"
@@ -352,7 +358,7 @@ dependencies = [
"bitflags 2.9.1", "bitflags 2.9.1",
"cexpr", "cexpr",
"clang-sys", "clang-sys",
"itertools", "itertools 0.13.0",
"proc-macro2", "proc-macro2",
"quote", "quote",
"regex", "regex",
@@ -425,6 +431,15 @@ dependencies = [
"piper", "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]] [[package]]
name = "bumpalo" name = "bumpalo"
version = "3.17.0" version = "3.17.0"
@@ -533,6 +548,20 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" 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]] [[package]]
name = "clang-sys" name = "clang-sys"
version = "1.8.1" version = "1.8.1"
@@ -1548,6 +1577,30 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" 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]] [[package]]
name = "iced" name = "iced"
version = "0.13.1" version = "0.13.1"
@@ -1562,6 +1615,23 @@ dependencies = [
"thiserror", "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]] [[package]]
name = "iced_core" name = "iced_core"
version = "0.13.2" version = "0.13.2"
@@ -1577,11 +1647,30 @@ dependencies = [
"once_cell", "once_cell",
"palette", "palette",
"rustc-hash 2.1.1", "rustc-hash 2.1.1",
"smol_str", "smol_str 0.2.2",
"thiserror", "thiserror",
"web-time", "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]] [[package]]
name = "iced_futures" name = "iced_futures"
version = "0.13.2" version = "0.13.2"
@@ -1757,6 +1846,15 @@ dependencies = [
"either", "either",
] ]
[[package]]
name = "itertools"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
dependencies = [
"either",
]
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.15" version = "1.0.15"
@@ -2176,6 +2274,16 @@ dependencies = [
"syn 2.0.101", "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]] [[package]]
name = "num-traits" name = "num-traits"
version = "0.2.19" version = "0.2.19"
@@ -2729,10 +2837,13 @@ name = "polygomusic"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"iced", "iced",
"iced_aw",
"iced_fonts 0.2.1",
"kira", "kira",
"regex", "regex",
"serde", "serde",
"serde_json", "serde_json",
"smol_str 0.3.2",
"tokio", "tokio",
] ]
@@ -3251,6 +3362,16 @@ dependencies = [
"serde", "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]] [[package]]
name = "softbuffer" name = "softbuffer"
version = "0.4.6" version = "0.4.6"
@@ -4170,10 +4291,51 @@ version = "0.54.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65" checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65"
dependencies = [ dependencies = [
"windows-result", "windows-result 0.1.2",
"windows-targets 0.52.6", "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]] [[package]]
name = "windows-result" name = "windows-result"
version = "0.1.2" version = "0.1.2"
@@ -4183,6 +4345,24 @@ dependencies = [
"windows-targets 0.52.6", "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]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.45.0" version = "0.45.0"
@@ -4487,7 +4667,7 @@ dependencies = [
"rustix 0.38.44", "rustix 0.38.44",
"sctk-adwaita", "sctk-adwaita",
"smithay-client-toolkit", "smithay-client-toolkit",
"smol_str", "smol_str 0.2.2",
"tracing", "tracing",
"unicode-segmentation", "unicode-segmentation",
"wasm-bindgen", "wasm-bindgen",

View File

@@ -2,6 +2,7 @@
name = "polygomusic" name = "polygomusic"
version = "0.1.0" version = "0.1.0"
edition = "2024" edition = "2024"
build = "build.rs"
[dependencies] [dependencies]
iced = { version = "0.13.1", features = ["canvas", "tokio"] } iced = { version = "0.13.1", features = ["canvas", "tokio"] }
@@ -10,3 +11,6 @@ tokio = {version = "1.45.1", features = ["time"]}
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.140" serde_json = "1.0.140"
regex = "1.11.1" regex = "1.11.1"
iced_aw = {version = "0.12.2", default-features = true}
iced_fonts = "0.2.1"
smol_str = "0.3.2"

View File

@@ -6,6 +6,7 @@ Polymusic is an innovative music application inspired
and rich audio textures, creating a unique auditory experience. and rich audio textures, creating a unique auditory experience.
## Instalation ## Instalation
### With source code
>Install Rust and Cargo >Install Rust and Cargo
>https://www.rust-lang.org/tools/install >https://www.rust-lang.org/tools/install
@@ -13,6 +14,9 @@ Polymusic is an innovative music application inspired
``` ```
cargo run cargo run
``` ```
### Release
Release is available on gitlab. Download windows ou linux version.
Execute `Polymusic` or `Polymusic.exe`
## Files ## Files

39
build.rs Normal file
View File

@@ -0,0 +1,39 @@
use std::env;
use std::fs;
use std::path::Path;
fn main() {
println!("cargo:rerun-if-changed=assets");
println!("cargo:rerun-if-changed=fonts");
// Chemin vers target/debug ou target/release
let out_dir = env::var("OUT_DIR").unwrap();
let target_dir = Path::new(&out_dir)
.ancestors()
.nth(3) // On remonte pour arriver dans target/debug ou target/release
.unwrap();
// Copie assets/
let _ = fs::remove_dir_all(target_dir.join("assets"));
fs::create_dir_all(target_dir.join("assets")).unwrap();
copy_dir("assets", target_dir.join("assets"));
// Copie fonts/
let _ = fs::remove_dir_all(target_dir.join("fonts"));
fs::create_dir_all(target_dir.join("fonts")).unwrap();
copy_dir("fonts", target_dir.join("fonts"));
}
fn copy_dir(src: impl AsRef<Path>, dst: impl AsRef<Path>) {
for entry in fs::read_dir(src).unwrap() {
let entry = entry.unwrap();
let ty = entry.file_type().unwrap();
let dst_path = dst.as_ref().join(entry.file_name());
if ty.is_dir() {
fs::create_dir_all(&dst_path).unwrap();
copy_dir(entry.path(), &dst_path);
} else {
fs::copy(entry.path(), &dst_path).unwrap();
}
}
}

Binary file not shown.

257
src/gui.rs Normal file
View File

@@ -0,0 +1,257 @@
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_aw::menu::{self, Item};
use iced_aw::menu_bar;
use iced_aw::widget::color_picker;
use iced_aw::widget::menu::Menu;
use std::f32::consts::PI;
use crate::MyApp;
pub fn music_view(app: &MyApp) -> iced::Element<Message> {
let mut i = 0;
let entries = app.all_sounds.clone();
//Create all polygon options
let polygon_rows: Vec<Element<Message>> = app
.music
.current_frame(app.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<String> = (5..=42).map(|sides| format!("Ngon{sides}")).collect();
let all_options: Vec<String> = [
"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 menu_tpl_1 = |items| Menu::new(items).max_width(200.0).offset(15.0).spacing(5.0);
let save_menu = menu_tpl_1(vec![
Item::new(button("Save File").on_press(Message::Save)),
Item::new(button("Load Menu").on_press(Message::GoToLoadView).style(
|theme: &Theme, status| {
let palette = theme.extended_palette();
match status {
button::Status::Active => {
if !app.already_save {
button::Style::default().with_background(palette.danger.strong.color)
} else {
button::primary(theme, status)
}
}
_ => button::primary(theme, status),
}
},
)),
Item::new("CTRL+F for quit without save."),
]);
column![
menu_bar!((text("File").size(20), save_menu)(
text("Shortcuts").size(20),
menu_tpl_1(vec![
Item::new("󰐊 SPACE"),
Item::new("󰆓 CTRL+S"),
Item::new("󰈼 ARROW UP"),
Item::new("󰕌 CTRL+Z"),
Item::new("󰑎 CTRL+Y"),
])
))
.draw_path(menu::DrawPath::Backdrop)
.spacing(20),
text(&app.music.file_name)
.width(Length::FillPortion(1))
.size(32),
row![
container(
canvas(app.music.current_frame(app.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 app.paused { "󰐊" } else { "󰏤" }).size(28).center())
.on_press(if app.can_unpaused {
Message::TogglePaused
} else {
Message::None
})
.width(Length::FillPortion(1)),
row![
TextInput::new("MM:SS:CS", &app.str_time)
.on_input(|new_value| Message::ChangeDeltaString(new_value))
.size(28),
text("/").size(30),
TextInput::new("MM:SS:CS", &app.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", &app.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..=app.music.length, self.current_delta, move |f| {
Message::ChangeDelta(f)
})
.step(&app.music.length / 10_000.),*/
canvas(&app.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()
}
pub fn load_file_view(app: &MyApp) -> iced::Element<Message> {
Container::new(
column![
text("Polymusic").size(42),
row![
column![
TextInput::new("Name File", &app.file_name_to_creat)
.on_input(|new_value| Message::SetFileNameCreat(new_value)),
button("Create File").on_press(Message::CreatFile),
text(if app.show_warning_message_creat {
"Warning: File already exists. Delete it or change the file name!"
} else {
""
})
.style(|theme: &Theme| {
let palette = theme.extended_palette();
text::Style {
color: Some(palette.danger.strong.color),
}
},)
]
.spacing(10)
.width(200)
.height(200)
.align_x(Horizontal::Center),
column![
pick_list(
app.all_saves.clone(),
Some(&app.music.file_name),
move |s| Message::FileNameChanged(s),
),
button("Load File").on_press(Message::Load),
text("")
]
.spacing(10)
.width(200)
.height(200)
.align_x(Horizontal::Center)
]
.spacing(32)
.align_y(Vertical::Center),
]
.spacing(64)
.align_x(Horizontal::Center),
)
.width(Length::Fill)
.height(Length::Fill)
.center_x(Length::Fill)
.center_y(Length::Fill)
.into()
}

53
src/history.rs Normal file
View File

@@ -0,0 +1,53 @@
use crate::message::Message;
#[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
}
}
}

View File

@@ -1,43 +1,65 @@
mod polygon_draw; mod polygon_draw;
use iced::widget::canvas::Program; use iced::keyboard::Modifiers;
use polygon_draw::Polygon; use polygon_draw::Polygon;
mod music; mod music;
use music::Music; use music::Music;
mod history;
use history::Historic;
mod message; mod message;
use message::Message; use message::Message;
mod utils; mod utils;
use utils::str_to_sec; use utils::{delta_to_string, is_delta_format_valid, str_to_sec};
use std::fs; use std::fs;
use iced::Element; mod gui;
use gui::{load_file_view, music_view};
use iced::Font;
use iced::Theme;
use iced::{ use iced::{
Font, Color, Event, Task,
widget::{ event::{self, Status},
TextInput, column, text, keyboard::{Key, key::Named},
text_input::{Icon, Side},
},
};
use iced::{
Length, Task, Theme,
widget::{Column, button, canvas, container, pick_list, row, scrollable, slider},
}; };
use regex::Regex;
use std::f32::consts::PI;
use std::time::Instant; use std::time::Instant;
use kira::{ use kira::{
AudioManager, AudioManagerSettings, DefaultBackend, sound::static_sound::StaticSoundData, 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 { 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) iced::application("My App", MyApp::update, MyApp::view)
.theme(|_| Theme::Dark) .theme(move |_| polytheme.clone())
.font(iced_fonts::REQUIRED_FONT_BYTES)
.font(FONT_BYTES)
.default_font(FONT)
.antialiasing(true)
.subscription(MyApp::subscription) .subscription(MyApp::subscription)
.run_with(MyApp::new) .run_with(MyApp::new)
} }
@@ -45,44 +67,57 @@ fn main() -> iced::Result {
struct MyApp { struct MyApp {
music: Music, music: Music,
time_last_frame: Instant, time_last_frame: Instant,
show_save_panel: bool,
paused: bool, paused: bool,
audio_manager: AudioManager, audio_manager: AudioManager,
all_sounds: Vec<String>, all_sounds: Vec<String>,
all_saves: Vec<String>, all_saves: Vec<String>,
current_delta: f32, current_delta: f32,
str_music_length: String, str_music_length: String,
str_time: String,
can_unpaused: bool,
already_save: bool,
mode_file_load: bool,
file_name_to_creat: String,
show_warning_message_creat: bool,
historic: Historic,
} }
impl MyApp { impl MyApp {
fn new() -> (Self, Task<Message>) { fn new() -> (Self, Task<Message>) {
let manager = AudioManager::<DefaultBackend>::new(AudioManagerSettings::default()) let manager = AudioManager::<DefaultBackend>::new(AudioManagerSettings::default())
.expect("Error to load AudioManager"); .expect("Error to load AudioManager");
( (
Self { Self {
time_last_frame: Instant::now(), time_last_frame: Instant::now(),
audio_manager: manager, audio_manager: manager,
show_save_panel: false,
paused: true, paused: true,
all_sounds: load_path_sounds(), all_sounds: load_path_sounds(),
all_saves: load_path_saves(), all_saves: load_path_saves(),
music: Music::default(), music: Music::default(),
current_delta: 0.0, current_delta: 0.0,
str_music_length: "01:00:00".to_string(), str_music_length: "01:00:00".to_string(),
str_time: "00:00:00".to_string(),
can_unpaused: true,
already_save: true,
mode_file_load: true,
file_name_to_creat: "Default File Name".to_string(),
show_warning_message_creat: false,
historic: Historic::new(),
}, },
Task::none(), Task::none(),
) )
} }
fn update(&mut self, message: Message) { fn update(&mut self, message: Message) {
match message { match message {
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) => { 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 => { Message::Tick => {
if self.current_delta >= self.music.length { if self.current_delta >= self.music.length {
@@ -95,26 +130,46 @@ impl MyApp {
self.music self.music
.apply_tick(self.current_delta, time_btw, &mut self.audio_manager); .apply_tick(self.current_delta, time_btw, &mut self.audio_manager);
self.time_last_frame = Instant::now(); self.time_last_frame = Instant::now();
self.str_time = delta_to_string(self.current_delta);
} }
} }
Message::Remove(i) => { Message::Remove(i) => {
self.music.remove_polygon(self.current_delta, i - 1); 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) => { 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;
Message::ChangeColor(i, s) => { self.historic.add(
self.music.set_color(self.current_delta, i, s); Message::ChangeTeta(i, old_teta),
Message::ChangeTeta(i, teta),
self.current_delta,
);
} }
Message::ChangeSound(i, s) => { Message::ChangeSound(i, s) => {
let sound = StaticSoundData::from_file(format!("./assets/{s}")) let sound = StaticSoundData::from_file(format!("./assets/{s}"))
.expect("Fail to load audio"); .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 => { Message::Save => {
let json = serde_json::to_string_pretty(&self.music).unwrap(); let json = serde_json::to_string_pretty(&self.music).unwrap();
fs::write(format!("./saves/{0}.pmx", &self.music.file_name), json).unwrap(); fs::write(format!("./saves/{0}.pmx", &self.music.file_name), json).unwrap();
self.all_saves = load_path_saves(); self.all_saves = load_path_saves();
self.already_save = true;
} }
Message::Load => { Message::Load => {
let json = fs::read_to_string(format!("./saves/{0}.pmx", &self.music.file_name)); let json = fs::read_to_string(format!("./saves/{0}.pmx", &self.music.file_name));
@@ -123,221 +178,271 @@ impl MyApp {
let decoded: Music = serde_json::from_str(&j).unwrap(); let decoded: Music = serde_json::from_str(&j).unwrap();
self.music = decoded; self.music = decoded;
self.music.update_frame(); self.music.update_frame();
self.mode_file_load = false;
} }
Err(e) => { Err(e) => {
eprintln!("Error, no saves with this name to load, {e} "); eprintln!("Error, no saves with this name to load, {e} ");
} }
} }
} }
Message::ToggleSavePanel => self.show_save_panel = !self.show_save_panel, Message::SetFileNameCreat(s) => self.file_name_to_creat = s,
Message::CreatFile => {
if self.all_saves.contains(&self.file_name_to_creat) {
self.show_warning_message_creat = true;
} else {
self.mode_file_load = false;
self.music = Music::default();
self.music.file_name = self.file_name_to_creat.clone();
self.update(Message::Save);
}
}
Message::FileNameChanged(s) => self.music.file_name = s, 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.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;
}
}
self.str_music_length = s;
}
Message::TogglePaused => { Message::TogglePaused => {
self.paused = !self.paused; self.paused = !self.paused;
if !self.paused { if !self.paused {
self.time_last_frame = Instant::now(); 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) => { Message::ChangeDelta(f) => {
self.current_delta = f; self.current_delta = f;
self.music.fix_teta(self.current_delta); self.music.fix_teta(self.current_delta);
// update the red dot on canvas // update the red dot on canvas
if self.paused { self.update_canvas_if_paused();
self.update(Message::TogglePaused); }
self.update(Message::Tick); Message::ChangeDeltaString(s) => {
self.update(Message::TogglePaused); if is_delta_format_valid(&s) {
self.already_save = false;
self.update(Message::ChangeDelta(str_to_sec(&s)));
} }
self.str_time = s;
}
Message::ReAddPoint => {
self.already_save = false;
self.music.add_point_old();
} }
Message::AddPoint => { Message::AddPoint => {
self.already_save = false;
self.music.add_point(self.current_delta); self.music.add_point(self.current_delta);
self.historic
.add(Message::RemovePoint, Message::AddPoint, self.current_delta);
} }
Message::RemovePoint => { Message::RemovePoint => {
self.already_save = false;
self.music.remove_point(self.current_delta); self.music.remove_point(self.current_delta);
self.historic.add(
Message::ReAddPoint,
Message::RemovePoint,
self.current_delta,
);
} }
Message::ClickedOnTimeLine(f) => { Message::ClickedOnTimeLine(f) => {
self.update(Message::ChangeDelta(f)); self.update(Message::ChangeDelta(f));
} }
Message::SlidePointLeft => {
self.already_save = false;
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;
if mut_s.len() == 0 {
mut_s = "0".to_string();
}
match mut_s.parse::<f32>() {
Ok(val) => {
if val >= 0. && val <= 360. {
self.update(Message::ChangeTeta(i, (val).to_radians()));
self.already_save = false;
}
}
Err(_) => {}
}
}
Message::ChangeNbPerSec(s) => {
let mut_s = s.trim_end_matches(" sec/rev");
if mut_s.len() != s.len() {
match mut_s.parse::<f32>() {
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;
}
}
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) => {
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::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;
self.mode_file_load = true
}
}
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 => {}
} }
} }
fn view(&self) -> iced::Element<Message> { fn view(&self) -> iced::Element<Message> {
let txt_nb_rev = if self.music.nb_sec_for_rev % 1. != 0.0 { if !self.mode_file_load {
format!("{} sec/revolution", self.music.nb_sec_for_rev) music_view(self)
} else { } else {
format!("{}.0 sec/revolution", self.music.nb_sec_for_rev) load_file_view(self)
};
let mut i = 0;
let entries = self.all_sounds.clone();
//Create all polygon options
let polygon_rows: Vec<Element<Message>> = 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(10)
.into()
})
.collect();
let ngon_options: Vec<String> = (5..=42).map(|sides| format!("Ngon{sides}")).collect();
let all_options: Vec<String> = [
"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(20));
let mut save_panel: Vec<Element<Message>> = 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(&self.music.file_name).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),
]
.spacing(20),
row![
container(
canvas(self.music.current_frame(self.current_delta))
.height(Length::FillPortion(1))
.width(Length::FillPortion(1))
),
column![
text("Polygon options"),
pick_list(all_options, Some("Choose polygon".to_string()), |s| {
Message::AddPolygon(s)
}),
polygon_column,
]
.spacing(10)
.height(Length::FillPortion(1))
.width(Length::FillPortion(2)),
]
.height(Length::FillPortion(2))
.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),
]
.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 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<Message> { fn subscription(&self) -> iced::Subscription<Message> {
iced::Subscription::batch([ let subscr_key = event::listen_with(|event, status, _| match (event, status) {
//window::events().map(|(_id, event)| Message::WindowEvent(event)), (
iced::time::every(std::time::Duration::from_millis(16)).map(|_| Message::Tick), 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),
modifiers: Modifiers::CTRL,
..
}),
Status::Ignored,
) if c.as_ref() == "s" => Some(Message::Save),
(
Event::Keyboard(iced::keyboard::Event::KeyPressed {
key: Key::Named(Named::Space),
..
}),
Status::Ignored,
) => Some(Message::TogglePaused),
(
Event::Keyboard(iced::keyboard::Event::KeyPressed {
key: Key::Named(Named::ArrowUp),
..
}),
Status::Ignored,
) => Some(Message::ChangeDelta(0.0)),
(
Event::Keyboard(iced::keyboard::Event::KeyPressed {
key: Key::Character(c),
modifiers: Modifiers::CTRL,
..
}),
Status::Ignored,
) if c.as_ref() == "f" => Some(Message::ForceToQuit),
_ => None,
});
if self.paused {
subscr_key
} else {
iced::Subscription::batch([
subscr_key,
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);
}
} }
} }
@@ -353,7 +458,7 @@ fn load_path_sounds() -> Vec<String> {
fn load_path_saves() -> Vec<String> { fn load_path_saves() -> Vec<String> {
fs::create_dir_all("./saves").expect("fail to creat 'saves' !"); fs::create_dir_all("./saves").expect("fail to creat 'saves' !");
fs::read_dir("./saves") let mut saves: Vec<String> = fs::read_dir("./saves")
.unwrap() .unwrap()
.filter_map(|res| res.ok()) .filter_map(|res| res.ok())
.map(|e| { .map(|e| {
@@ -365,5 +470,7 @@ fn load_path_saves() -> Vec<String> {
.trim_end_matches(".pmx") .trim_end_matches(".pmx")
.to_string() .to_string()
}) })
.collect() .collect();
saves.sort();
saves
} }

View File

@@ -1,22 +1,38 @@
use iced::Color;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum Message { pub enum Message {
ButtonPressedIncrement, None,
ButtonPressedDecrement, CreatFile,
SetFileNameCreat(String),
GoToLoadView,
ForceToQuit,
Tick, Tick,
AddPolygon(String),
ChangeTeta(usize, f32),
Remove(usize),
ChangeColor(usize, String),
ChangeSound(usize, String),
ToggleSavePanel,
Save, Save,
Load, Load,
FileNameChanged(String), FileNameChanged(String),
TogglePaused, TogglePaused,
SetMusicLength,
LengthChange(String),
ChangeDelta(f32), ChangeDelta(f32),
ClickedOnTimeLine(f32),
ChangeDeltaString(String),
ChangeNbPerSec(String),
AddPolygon(String),
ChangeTeta(usize, f32),
Remove(usize),
ChangeSound(usize, String),
LengthChange(String),
AddPoint, AddPoint,
RemovePoint, RemovePoint,
ClickedOnTimeLine(f32), SlidePointLeft,
SlidePointRight,
ChangeDegree(usize, String),
ReAddPoint,
ChooseColor(usize, Color),
CancelColor(usize),
SubmitColor(usize),
Undo,
Redo,
} }

View File

@@ -1,20 +1,25 @@
use crate::message::Message; use crate::message::Message;
use crate::polygon_draw::*;
use crate::utils::string_to_polygon; use crate::utils::string_to_polygon;
use crate::{polygon_draw::*, utils::string_to_color};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use kira::{AudioManager, sound::static_sound::StaticSoundData}; use kira::{AudioManager, sound::static_sound::StaticSoundData};
use iced::event::Status; use iced::event::Status;
use iced::mouse::Cursor; use iced::mouse::Cursor;
use iced::widget::canvas::Event; use iced::widget::canvas::{Event, Geometry};
use iced::{mouse, padding}; use iced::{
Size,
mouse::{self, ScrollDelta},
};
use iced::{Vector, keyboard};
use iced::widget::canvas; use iced::widget::canvas;
use iced::widget::canvas::Stroke; use iced::widget::canvas::Stroke;
use iced::widget::canvas::Style; use iced::widget::canvas::Style;
use iced::{Color, Rectangle, Renderer, Theme}; use iced::{Color, Rectangle, Renderer, Theme};
use std::f32::consts::PI; use std::f32::consts::PI;
use std::mem::swap;
use std::time::Duration; use std::time::Duration;
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct Music { pub struct Music {
@@ -26,6 +31,8 @@ pub struct Music {
teta: f32, teta: f32,
#[serde(skip)] #[serde(skip)]
pub current_delta: f32, pub current_delta: f32,
#[serde(skip)]
point_removed: Vec<(f32, PolygonFrame)>,
} }
impl Music { impl Music {
@@ -68,10 +75,11 @@ impl Music {
Music { Music {
poly_frame: vec![(0.0, PolygonFrame::default())], poly_frame: vec![(0.0, PolygonFrame::default())],
nb_sec_for_rev: 1.0, nb_sec_for_rev: 1.0,
file_name: "Default Name".to_string(), file_name: "Default File Name".to_string(),
length: 60.0, length: 60.0,
teta: 0., teta: 0.,
current_delta: 0., current_delta: 0.,
point_removed: vec![],
} }
} }
@@ -111,20 +119,30 @@ impl Music {
index: usize, index: usize,
sound: StaticSoundData, sound: StaticSoundData,
sound_name: String, sound_name: String,
) { ) -> String {
let current_frame = self.find_poly_frame(delta); let current_frame = self.find_poly_frame(delta);
current_frame.polygons[index].sound = sound; current_frame.polygons[index].sound = sound;
let out = current_frame.polygons[index].sound_name.clone();
current_frame.polygons[index].sound_name = sound_name; 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; self.find_poly_frame(delta).polygons[index].global_teta = teta;
out
} }
pub fn set_color(&mut self, delta: f32, index: usize, color_name: String) { pub fn set_color(&mut self, delta: f32, index: usize, color: Color) -> Color {
let current_frame = self.find_poly_frame(delta); let current_frame = self.find_poly_frame(delta);
current_frame.polygons[index].color = string_to_color(&color_name); let out = current_frame.polygons[index].color.clone();
current_frame.polygons[index].color_name = color_name; current_frame.polygons[index].color = color;
out
}
pub fn set_color_picker(&mut self, delta: f32, i: usize, b: bool) {
let current_frame = self.find_poly_frame(delta);
current_frame.polygons[i].show_color_picker = b;
} }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ADD/REMOVE~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ADD/REMOVE~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -137,9 +155,19 @@ impl Music {
self.poly_frame self.poly_frame
.insert(pos, (delta, self.current_frame(delta).clone())); .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) { pub fn remove_point(&mut self, delta: f32) {
let i = self.find_index_frame(delta); let i = self.find_index_frame(delta);
if i != 0 { if i != 0 {
self.point_removed.push(self.poly_frame[i].clone());
self.poly_frame.remove(i); self.poly_frame.remove(i);
} }
} }
@@ -150,50 +178,108 @@ impl Music {
current_frame.polygons.push(poly); current_frame.polygons.push(poly);
} }
pub fn remove_polygon(&mut self, delta: f32, i: usize) { pub fn remove_polygon(&mut self, delta: f32, i: usize) -> String {
self.find_poly_frame(delta).polygons.remove(i); 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) {
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);
}
} }
} }
#[derive(Default, Debug)]
pub struct StateMusic {
mouse_left: bool,
ctrl: bool,
zoom: f32,
offset: f32,
}
impl canvas::Program<Message> for Music { impl canvas::Program<Message> for Music {
// No internal state // No internal state
type State = bool; type State = StateMusic;
fn draw( fn draw(
&self, &self,
_state: &Self::State, state: &Self::State,
renderer: &Renderer, renderer: &Renderer,
_theme: &Theme, _theme: &Theme,
bounds: Rectangle, bounds: Rectangle,
_cursor: mouse::Cursor, _cursor: mouse::Cursor,
) -> Vec<canvas::Geometry> { ) -> Vec<canvas::Geometry> {
let mut frame = canvas::Frame::new(renderer, bounds.size()); let mut geo_small_frame: Vec<Geometry> = vec![];
let mut geo_cursor: Vec<Geometry> = vec![];
let frame = canvas::Frame::new(renderer, bounds.size());
let global_width = bounds.width * state.zoom;
let mut toggle_color = true; let mut toggle_color = true;
let padding = 8.; let padding = 8.;
let w = bounds.width - (padding * 2.); let w = global_width - (padding * 2.);
for (delta, _) in &self.poly_frame { for (delta, polyframe) in &self.poly_frame {
let x = delta / self.length * w + 8.; let x = delta / self.length * w + 8. - state.offset;
frame.fill_rectangle( let mut back_frame = canvas::Frame::new(
iced::Point { x: x, y: 0.0 }, renderer,
frame.size(), Size {
if toggle_color { width: global_width.clamp(0.0, bounds.width),
Color::from_rgb(0.3, 0.3, 0.3) height: bounds.height,
} else {
Color::from_rgb(0.1, 0.1, 0.1)
}, },
); );
toggle_color = !toggle_color;
}
frame.stroke_rectangle(
iced::Point { x: 0.0, y: 0.0 },
frame.size(),
Stroke {
width: 16.0,
..Stroke::default()
},
);
let x = self.current_delta / self.length * w + 8.; back_frame.fill_rectangle(
frame.stroke_rectangle( iced::Point { x: x, y: 0.0 },
Size {
width: global_width,
height: bounds.height,
},
if toggle_color {
Color::from_rgb8(27, 60, 83)
} else {
Color::from_rgb8(69, 104, 130)
},
);
geo_small_frame.push(back_frame.into_geometry());
toggle_color = !toggle_color;
let mut small_frame = canvas::Frame::new(
renderer,
Size {
width: global_width.clamp(0.0, bounds.width),
height: bounds.height,
},
);
small_frame.translate(Vector {
x: x + (bounds.height / 10.),
y: (bounds.height / 10.),
});
polyframe.draw_in_frame(
&mut small_frame,
Size {
width: (8. * bounds.height) / 10.,
height: (8. * bounds.height) / 10.,
},
);
geo_small_frame.push(small_frame.into_geometry());
}
let x = self.current_delta / self.length * w + 8. - state.offset;
let mut frame_cursor = canvas::Frame::new(renderer, bounds.size());
frame_cursor.stroke_rectangle(
iced::Point::new(x, 0.), iced::Point::new(x, 0.),
iced::Size { iced::Size {
width: 0., width: 0.,
@@ -201,13 +287,33 @@ impl canvas::Program<Message> for Music {
}, },
Stroke { Stroke {
width: 4.0, width: 4.0,
style: Style::Solid(Color::from_rgb(1.0, 0.0, 0.0)), style: Style::Solid(Color::from_rgba(1.0, 0.0, 0.0, 1.)),
..Stroke::default() ..Stroke::default()
}, },
); );
frame_cursor.stroke_rectangle(
iced::Point {
x: -state.offset,
y: 0.0,
},
iced::Size {
width: global_width,
height: bounds.height,
},
Stroke {
width: 16.0,
style: Style::Solid(Color::from_rgb8(207, 74, 28)),
..Stroke::default()
},
);
geo_cursor.push(frame_cursor.into_geometry());
//vec_geo.push(frame.into_geometry());
// Then, we produce the geometry // Then, we produce the geometry
vec![frame.into_geometry()] let mut out = vec![frame.into_geometry()];
out.append(&mut geo_small_frame);
out.append(&mut geo_cursor);
out
} }
fn update( fn update(
&self, &self,
@@ -217,12 +323,21 @@ impl canvas::Program<Message> for Music {
cursor: Cursor, cursor: Cursor,
) -> (Status, Option<Message>) { ) -> (Status, Option<Message>) {
//eprintln!("event = {:?}", event); //eprintln!("event = {:?}", event);
if let Event::Keyboard(keyboard_event) = &event {
match keyboard_event {
keyboard::Event::ModifiersChanged(m) => {
state.ctrl = m.control();
}
_ => (),
}
}
if let Event::Mouse(mouse_event) = event { if let Event::Mouse(mouse_event) = event {
match mouse_event { match mouse_event {
mouse::Event::ButtonPressed(mouse::Button::Left) => { mouse::Event::ButtonPressed(mouse::Button::Left) => {
*state = true; state.mouse_left = true;
if let Some(position) = cursor.position_in(bounds) { if let Some(position) = cursor.position_in(bounds) {
let pos_x = (position.x - 8.0) / (bounds.width - 16.); let pos_x =
(position.x + state.offset - 8.0) / (bounds.width * state.zoom - 16.);
let delta = (pos_x * self.length).clamp(0., self.length); let delta = (pos_x * self.length).clamp(0., self.length);
return ( return (
Status::Captured, Status::Captured,
@@ -230,19 +345,52 @@ impl canvas::Program<Message> for Music {
); );
} }
} }
mouse::Event::ButtonReleased(mouse::Button::Left) => *state = false, mouse::Event::ButtonReleased(mouse::Button::Left) => state.mouse_left = false,
mouse::Event::CursorMoved { position: _ } => { mouse::Event::CursorMoved { position: _ } => {
if let Some(position) = cursor.position_in(bounds) if let Some(position) = cursor.position_in(bounds)
&& *state && state.mouse_left
{ {
let pos_x = (position.x - 8.0) / (bounds.width - 16.); let pos_x =
(position.x + state.offset - 8.0) / (bounds.width * state.zoom - 16.);
let delta = (pos_x * self.length).clamp(0., self.length); let delta = (pos_x * self.length).clamp(0., self.length);
return (Status::Captured, Some(Message::ClickedOnTimeLine(delta))); return (Status::Captured, Some(Message::ClickedOnTimeLine(delta)));
} }
} }
mouse::Event::WheelScrolled { delta: d } => {
if state.ctrl {
match d {
ScrollDelta::Lines { x: _, y } => {
let before = bounds.width * state.zoom;
state.zoom += y / 10.;
state.offset = state.offset * (bounds.width * state.zoom) / before;
}
ScrollDelta::Pixels { x: _, y } => {
let before = bounds.width * state.zoom;
state.zoom += y / 10.;
state.offset = state.offset * (bounds.width * state.zoom) / before;
}
}
} else {
match d {
ScrollDelta::Lines { x: _, y } => {
state.offset -= y * 32. * state.zoom;
}
ScrollDelta::Pixels { x: _, y } => {
state.offset -= y * 32. * state.zoom;
}
}
}
}
_ => {} _ => {}
} }
} }
state.zoom = state.zoom.clamp(1.0, 10.);
if state.offset + bounds.width >= bounds.width * state.zoom {
state.offset = bounds.width * state.zoom - bounds.width
}
if state.offset <= 0. {
state.offset = 0.
}
(Status::Ignored, None) (Status::Ignored, None)
} }

View File

@@ -2,12 +2,12 @@ use crate::utils::string_to_color;
use std::f32::consts::PI; use std::f32::consts::PI;
use iced::Size;
use iced::Vector; use iced::Vector;
use iced::mouse; use iced::mouse;
use iced::widget::canvas; use iced::widget::canvas;
use iced::widget::canvas::Stroke; use iced::widget::canvas::{Frame, Stroke, Style};
use iced::widget::canvas::Style; use iced::{Color, Point, Rectangle, Renderer, Theme};
use iced::{Color, Rectangle, Renderer, Theme};
use kira::sound::static_sound::StaticSoundData; use kira::sound::static_sound::StaticSoundData;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -49,6 +49,65 @@ impl PolygonFrame {
polygons: vec![], polygons: vec![],
} }
} }
pub fn draw_in_frame(&self, frame: &mut Frame, size: Size) {
let radius = size.height / 2.0 - (size.height / 10.);
let mut vec = Vector::new(0.0, -radius);
let c = Point {
x: size.width / 2.,
y: size.height / 2.,
};
// Draw Circle
let circle = canvas::Path::circle(c, radius);
frame.stroke(
&circle,
Stroke {
width: 6.0,
style: Style::Solid(Color::from_rgba8(210, 193, 182, 0.25)),
..Stroke::default()
},
);
// Draw all polygons by there points
for poly in &self.polygons {
let l = poly.points_teta.len();
let mut line: canvas::Path;
let mut t1: f32;
let mut t2: f32;
let mut color: Color;
for i in 0..l {
t1 = poly.points_teta[i] + poly.global_teta;
t2 = poly.points_teta[(i + 1) % l] + poly.global_teta;
line = canvas::Path::line(c + vec.rotate(t1), c + vec.rotate(t2));
color = poly.color.clone();
color.a = 0.25;
frame.stroke(
&line,
Stroke {
width: 4.0,
style: Style::Solid(poly.color.clone()),
..Stroke::default()
},
);
}
}
/*
// Draw the red dot on the current position
let dot = canvas::Path::circle(frame.center() + vec.rotate(self.teta), 10.0);
frame.fill(&dot, Color::from_rgb(1.0, 0.0, 0.0));
*/
// Draw the frame
/*
frame.stroke_rectangle(
iced::Point { x: 0.0, y: 0.0 },
size,
Stroke {
width: 8.0,
style: Style::Solid(Color::from_rgb8(207, 74, 28)),
..Stroke::default()
},
);*/
}
} }
impl<Message> canvas::Program<Message> for PolygonFrame { impl<Message> canvas::Program<Message> for PolygonFrame {
@@ -67,13 +126,18 @@ impl<Message> canvas::Program<Message> for PolygonFrame {
let radius = frame.size().width.min(frame.size().height) / 2.0 - 32.0; let radius = frame.size().width.min(frame.size().height) / 2.0 - 32.0;
let mut vec = Vector::new(0.0, -radius); let mut vec = Vector::new(0.0, -radius);
let c = frame.center(); let c = frame.center();
frame.fill_rectangle(
iced::Point { x: 0., y: 0. },
frame.size(),
Color::from_rgb8(27, 60, 83),
);
// Draw Circle // Draw Circle
let circle = canvas::Path::circle(frame.center(), radius); let circle = canvas::Path::circle(frame.center(), radius);
frame.stroke( frame.stroke(
&circle, &circle,
Stroke { Stroke {
width: 6.0, width: 6.0,
style: Style::Solid(Color::from_rgba(0.0, 0.0, 0.0, 1.0)), style: Style::Solid(Color::from_rgb8(210, 193, 182)),
..Stroke::default() ..Stroke::default()
}, },
); );
@@ -100,7 +164,7 @@ impl<Message> canvas::Program<Message> for PolygonFrame {
} }
// Draw the red dot on the current position // Draw the red dot on the current position
let dot = canvas::Path::circle(frame.center() + vec.rotate(self.teta), 5.0); let dot = canvas::Path::circle(frame.center() + vec.rotate(self.teta), 10.0);
frame.fill(&dot, Color::from_rgb(1.0, 0.0, 0.0)); frame.fill(&dot, Color::from_rgb(1.0, 0.0, 0.0));
// Draw the frame // Draw the frame
@@ -109,6 +173,7 @@ impl<Message> canvas::Program<Message> for PolygonFrame {
frame.size(), frame.size(),
Stroke { Stroke {
width: 16.0, width: 16.0,
style: Style::Solid(Color::from_rgb8(207, 74, 28)),
..Stroke::default() ..Stroke::default()
}, },
); );
@@ -129,6 +194,8 @@ pub struct Polygon {
#[serde(skip)] #[serde(skip)]
pub color: Color, pub color: Color,
pub color_name: String, pub color_name: String,
#[serde(skip)]
pub show_color_picker: bool,
} }
#[warn(dead_code)] #[warn(dead_code)]
impl Polygon { impl Polygon {
@@ -153,196 +220,154 @@ impl Polygon {
} }
sound_to_play sound_to_play
} }
pub fn default(sound: StaticSoundData) -> Self {
Polygon {
global_teta: 0.,
points_teta: vec![],
sound: sound,
sound_name: "tick.ogg".to_string(),
color_name: "Black".to_string(),
name: "".to_string(),
color: Color::BLACK,
show_color_picker: false,
}
}
pub fn n_gon(teta: f32, n_side: u8, sound: StaticSoundData) -> Self { pub fn n_gon(n_side: u8, sound: StaticSoundData) -> Self {
let mut v: Vec<f32> = Vec::with_capacity(n_side as usize); let mut v: Vec<f32> = Vec::with_capacity(n_side as usize);
for i in 0..n_side { for i in 0..n_side {
v.push((i as f32 * 2.0 * PI) / n_side as f32); v.push((i as f32 * 2.0 * PI) / n_side as f32);
} }
Polygon { let mut p = Polygon::default(sound);
global_teta: teta, p.points_teta = v;
points_teta: v, p
sound: sound,
sound_name: "tick.ogg".to_string(),
color_name: "Black".to_string(),
name: "".to_string(),
color: Color::BLACK,
}
} }
pub fn segment(teta: f32, sound: StaticSoundData) -> Self { pub fn segment(sound: StaticSoundData) -> Self {
Polygon::n_gon(teta, 2, sound) Polygon::n_gon(2, sound)
} }
pub fn triangle(teta: f32, sound: StaticSoundData) -> Self { pub fn triangle(sound: StaticSoundData) -> Self {
Polygon::n_gon(teta, 3, sound) Polygon::n_gon(3, sound)
} }
pub fn square(teta: f32, sound: StaticSoundData) -> Self { pub fn square(sound: StaticSoundData) -> Self {
Polygon::n_gon(teta, 4, sound) Polygon::n_gon(4, sound)
} }
pub fn nr_6_in_30(teta: f32, sound: StaticSoundData) -> Self { pub fn nr_6_in_30(sound: StaticSoundData) -> Self {
Polygon { let mut p = Polygon::default(sound);
sound: sound, p.points_teta = vec![
name: "".to_string(), 2.0 * 5.0 * PI / 30.0,
color: Color::BLACK, 2.0 * 6.0 * PI / 30.0,
global_teta: teta, 2.0 * 12.0 * PI / 30.0,
sound_name: "tick.ogg".to_string(), 2.0 * 18.0 * PI / 30.0,
color_name: "Black".to_string(), 2.0 * 24.0 * PI / 30.0,
points_teta: vec![ 2.0 * 25.0 * PI / 30.0,
2.0 * 5.0 * PI / 30.0, ];
2.0 * 6.0 * PI / 30.0, p
2.0 * 12.0 * PI / 30.0,
2.0 * 18.0 * PI / 30.0,
2.0 * 24.0 * PI / 30.0,
2.0 * 25.0 * PI / 30.0,
],
}
} }
pub fn nr_7_in_30(teta: f32, sound: StaticSoundData) -> Self { pub fn nr_7_in_30(sound: StaticSoundData) -> Self {
Polygon { let mut p = Polygon::default(sound);
sound: sound, p.points_teta = vec![
name: "".to_string(), 0.0,
color: Color::BLACK, 2.0 * 6.0 * PI / 30.0,
sound_name: "tick.ogg".to_string(), 2.0 * 7.0 * PI / 30.0,
color_name: "Black".to_string(), 2.0 * 13.0 * PI / 30.0,
global_teta: teta, 2.0 * 17.0 * PI / 30.0,
points_teta: vec![ 2.0 * 23.0 * PI / 30.0,
0.0, 2.0 * 24.0 * PI / 30.0,
2.0 * 6.0 * PI / 30.0, ];
2.0 * 7.0 * PI / 30.0, p
2.0 * 13.0 * PI / 30.0,
2.0 * 17.0 * PI / 30.0,
2.0 * 23.0 * PI / 30.0,
2.0 * 24.0 * PI / 30.0,
],
}
} }
pub fn nr_8_in_30(teta: f32, sound: StaticSoundData) -> Self { pub fn nr_8_in_30(sound: StaticSoundData) -> Self {
Polygon { let mut p = Polygon::default(sound);
sound: sound, p.points_teta = vec![
name: "".to_string(), 2.0 * PI / 30.0,
color: Color::BLACK, 2.0 * 5.0 * PI / 30.0,
sound_name: "tick.ogg".to_string(), 2.0 * 11.0 * PI / 30.0,
color_name: "Black".to_string(), 2.0 * 12.0 * PI / 30.0,
global_teta: teta, 2.0 * 18.0 * PI / 30.0,
points_teta: vec![ 2.0 * 19.0 * PI / 30.0,
2.0 * PI / 30.0, 2.0 * 25.0 * PI / 30.0,
2.0 * 5.0 * PI / 30.0, 2.0 * 29.0 * PI / 30.0,
2.0 * 11.0 * PI / 30.0, ];
2.0 * 12.0 * PI / 30.0, p
2.0 * 18.0 * PI / 30.0,
2.0 * 19.0 * PI / 30.0,
2.0 * 25.0 * PI / 30.0,
2.0 * 29.0 * PI / 30.0,
],
}
} }
pub fn nr_9_in_30(teta: f32, sound: StaticSoundData) -> Self { pub fn nr_9_in_30(sound: StaticSoundData) -> Self {
Polygon { let mut p = Polygon::default(sound);
sound: sound, p.points_teta = vec![
name: "".to_string(), 0.0,
color: Color::BLACK, 2.0 * PI / 30.0,
sound_name: "tick.ogg".to_string(), 2.0 * 7.0 * PI / 30.0,
color_name: "Black".to_string(), 2.0 * 11.0 * PI / 30.0,
global_teta: teta, 2.0 * 13.0 * PI / 30.0,
points_teta: vec![ 2.0 * 17.0 * PI / 30.0,
0.0, 2.0 * 19.0 * PI / 30.0,
2.0 * PI / 30.0, 2.0 * 23.0 * PI / 30.0,
2.0 * 7.0 * PI / 30.0, 2.0 * 29.0 * PI / 30.0,
2.0 * 11.0 * PI / 30.0, ];
2.0 * 13.0 * PI / 30.0, p
2.0 * 17.0 * PI / 30.0,
2.0 * 19.0 * PI / 30.0,
2.0 * 23.0 * PI / 30.0,
2.0 * 29.0 * PI / 30.0,
],
}
} }
pub fn nr_8_in_42(teta: f32, sound: StaticSoundData) -> Self { pub fn nr_8_in_42(sound: StaticSoundData) -> Self {
Polygon { let mut p = Polygon::default(sound);
sound: sound, p.points_teta = vec![
name: "".to_string(), 2.0 * 3.0 * PI / 42.0,
color: Color::BLACK, 2.0 * 9.0 * PI / 42.0,
sound_name: "tick.ogg".to_string(), 2.0 * 14.0 * PI / 42.0,
color_name: "Black".to_string(), 2.0 * 15.0 * PI / 42.0,
global_teta: teta, 2.0 * 27.0 * PI / 42.0,
points_teta: vec![ 2.0 * 28.0 * PI / 42.0,
2.0 * 3.0 * PI / 42.0, 2.0 * 33.0 * PI / 42.0,
2.0 * 9.0 * PI / 42.0, 2.0 * 39.0 * PI / 42.0,
2.0 * 14.0 * PI / 42.0, ];
2.0 * 15.0 * PI / 42.0, p
2.0 * 27.0 * PI / 42.0,
2.0 * 28.0 * PI / 42.0,
2.0 * 33.0 * PI / 42.0,
2.0 * 39.0 * PI / 42.0,
],
}
} }
pub fn nr_9_in_42(teta: f32, sound: StaticSoundData) -> Self { pub fn nr_9_in_42(sound: StaticSoundData) -> Self {
Polygon { let mut p = Polygon::default(sound);
sound: sound, p.points_teta = vec![
name: "".to_string(), 0.0,
color: Color::BLACK, 2.0 * 6.0 * PI / 42.0,
sound_name: "tick.ogg".to_string(), 2.0 * 11.0 * PI / 42.0,
color_name: "Black".to_string(), 2.0 * 12.0 * PI / 42.0,
global_teta: teta, 2.0 * 17.0 * PI / 42.0,
points_teta: vec![ 2.0 * 25.0 * PI / 42.0,
0.0, 2.0 * 30.0 * PI / 42.0,
2.0 * 6.0 * PI / 42.0, 2.0 * 31.0 * PI / 42.0,
2.0 * 11.0 * PI / 42.0, 2.0 * 36.0 * PI / 42.0,
2.0 * 12.0 * PI / 42.0, ];
2.0 * 17.0 * PI / 42.0, p
2.0 * 25.0 * PI / 42.0,
2.0 * 30.0 * PI / 42.0,
2.0 * 31.0 * PI / 42.0,
2.0 * 36.0 * PI / 42.0,
],
}
} }
pub fn nr_10a_in_42(teta: f32, sound: StaticSoundData) -> Self { pub fn nr_10a_in_42(sound: StaticSoundData) -> Self {
Polygon { let mut p = Polygon::default(sound);
sound: sound, p.points_teta = vec![
name: "".to_string(), 2.0 * 6.0 * PI / 42.0,
color: Color::BLACK, 2.0 * 7.0 * PI / 42.0,
sound_name: "tick.ogg".to_string(), 2.0 * 11.0 * PI / 42.0,
color_name: "Black".to_string(), 2.0 * 12.0 * PI / 42.0,
global_teta: teta, 2.0 * 17.0 * PI / 42.0,
points_teta: vec![ 2.0 * 25.0 * PI / 42.0,
2.0 * 6.0 * PI / 42.0, 2.0 * 30.0 * PI / 42.0,
2.0 * 7.0 * PI / 42.0, 2.0 * 31.0 * PI / 42.0,
2.0 * 11.0 * PI / 42.0, 2.0 * 35.0 * PI / 42.0,
2.0 * 12.0 * PI / 42.0, 2.0 * 36.0 * PI / 42.0,
2.0 * 17.0 * PI / 42.0, ];
2.0 * 25.0 * PI / 42.0, p
2.0 * 30.0 * PI / 42.0,
2.0 * 31.0 * PI / 42.0,
2.0 * 35.0 * PI / 42.0,
2.0 * 36.0 * PI / 42.0,
],
}
} }
pub fn nr_10b_in_42(teta: f32, sound: StaticSoundData) -> Self { pub fn nr_10b_in_42(sound: StaticSoundData) -> Self {
Polygon { let mut p = Polygon::default(sound);
sound: sound, p.points_teta = vec![
name: "".to_string(), 0.0,
color: Color::BLACK, 2.0 * 1.0 * PI / 42.0,
sound_name: "tick.ogg".to_string(), 2.0 * 5.0 * PI / 42.0,
color_name: "Black".to_string(), 2.0 * 13.0 * PI / 42.0,
global_teta: teta, 2.0 * 18.0 * PI / 42.0,
points_teta: vec![ 2.0 * 19.0 * PI / 42.0,
0.0, 2.0 * 24.0 * PI / 42.0,
2.0 * 1.0 * PI / 42.0, 2.0 * 29.0 * PI / 42.0,
2.0 * 5.0 * PI / 42.0, 2.0 * 30.0 * PI / 42.0,
2.0 * 13.0 * PI / 42.0, 2.0 * 41.0 * PI / 42.0,
2.0 * 18.0 * PI / 42.0, ];
2.0 * 19.0 * PI / 42.0, p
2.0 * 24.0 * PI / 42.0,
2.0 * 29.0 * PI / 42.0,
2.0 * 30.0 * PI / 42.0,
2.0 * 41.0 * PI / 42.0,
],
}
} }
} }
fn dummy_sound() -> StaticSoundData { fn dummy_sound() -> StaticSoundData {

View File

@@ -1,6 +1,12 @@
use crate::Polygon; use crate::Polygon;
use iced::Color; use iced::Color;
use kira::sound::static_sound::StaticSoundData; 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: AsRef<str>>(s: S) -> Color { pub fn string_to_color<S: AsRef<str>>(s: S) -> Color {
match s.as_ref() { match s.as_ref() {
"Green" => Color::from_rgb(0.0, 1.0, 0.0), "Green" => Color::from_rgb(0.0, 1.0, 0.0),
@@ -43,46 +49,46 @@ pub fn string_to_polygon<S: AsRef<str>>(str: S) -> Polygon {
let mut poly: Polygon; let mut poly: Polygon;
if s.starts_with("Ngon") { if s.starts_with("Ngon") {
if let Ok(sides) = s.trim_start_matches("Ngon").parse::<u8>() { if let Ok(sides) = s.trim_start_matches("Ngon").parse::<u8>() {
poly = Polygon::n_gon(0.0, sides, dummy_sound()); poly = Polygon::n_gon(sides, dummy_sound());
} else { } else {
poly = Polygon::n_gon(0.0, 0, dummy_sound()); poly = Polygon::n_gon(0, dummy_sound());
} }
} else { } else {
match s { match s {
"Segment" => { "Segment" => {
poly = Polygon::segment(0.0, dummy_sound()); poly = Polygon::segment(dummy_sound());
} }
"Triangle" => { "Triangle" => {
poly = Polygon::triangle(0.0, dummy_sound()); poly = Polygon::triangle(dummy_sound());
} }
"Square" => { "Square" => {
poly = Polygon::square(0.0, dummy_sound()); poly = Polygon::square(dummy_sound());
} }
"Nr6In30" => { "Nr6In30" => {
poly = Polygon::nr_6_in_30(0.0, dummy_sound()); poly = Polygon::nr_6_in_30(dummy_sound());
} }
"Nr7In30" => { "Nr7In30" => {
poly = Polygon::nr_7_in_30(0.0, dummy_sound()); poly = Polygon::nr_7_in_30(dummy_sound());
} }
"Nr8In30" => { "Nr8In30" => {
poly = Polygon::nr_8_in_30(0.0, dummy_sound()); poly = Polygon::nr_8_in_30(dummy_sound());
} }
"Nr9In30" => { "Nr9In30" => {
poly = Polygon::nr_9_in_30(0.0, dummy_sound()); poly = Polygon::nr_9_in_30(dummy_sound());
} }
"Nr8In42" => { "Nr8In42" => {
poly = Polygon::nr_8_in_42(0.0, dummy_sound()); poly = Polygon::nr_8_in_42(dummy_sound());
} }
"Nr9In42" => { "Nr9In42" => {
poly = Polygon::nr_9_in_42(0.0, dummy_sound()); poly = Polygon::nr_9_in_42(dummy_sound());
} }
"Nr10aIn42" => { "Nr10aIn42" => {
poly = Polygon::nr_10a_in_42(0.0, dummy_sound()); poly = Polygon::nr_10a_in_42(dummy_sound());
} }
"Nr10bIn42" => { "Nr10bIn42" => {
poly = Polygon::nr_10b_in_42(0.0, dummy_sound()); poly = Polygon::nr_10b_in_42(dummy_sound());
} }
_ => poly = Polygon::n_gon(0.0, 0, dummy_sound()), _ => poly = Polygon::n_gon(0, dummy_sound()),
} }
} }
poly.name = s.to_string(); poly.name = s.to_string();