Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9f59e68b2c | |||
| 607cf17888 | |||
| 984cec5741 | |||
| 8eb84f6780 | |||
| 719f0c1332 | |||
| 064190d1a7 | |||
| a426c7bb98 | |||
| 440032ffdb | |||
| 7047679ace | |||
| 68f8cff151 | |||
| e85ed8371d | |||
| bf2266955b | |||
| 4d31708481 | |||
| 8f850e9323 | |||
| 51757c3922 | |||
| 50251b3053 | |||
| 00ea324d48 | |||
| 1192c22691 | |||
| 01ad529024 | |||
| 44b96aef15 | |||
| 27529e4fc5 | |||
| a56f5ddba4 |
164
Cargo.lock
generated
164
Cargo.lock
generated
@@ -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"
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
name = "polygomusic"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
build = "build.rs"
|
||||
|
||||
[dependencies]
|
||||
iced = { version = "0.13.1", features = ["canvas", "tokio"] }
|
||||
@@ -10,3 +11,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"
|
||||
|
||||
12
README.md
12
README.md
@@ -1,4 +1,9 @@
|
||||
# Polymusic
|
||||
Polymusic is an innovative music application inspired
|
||||
by two academic research papers that explore sound
|
||||
generation through balanced polygons. By leveraging
|
||||
geometric principles, Polymusic produces harmonious
|
||||
and rich audio textures, creating a unique auditory experience.
|
||||
|
||||
## Instalation
|
||||
|
||||
@@ -19,3 +24,10 @@ The `assets` folder contains all the sounds you can play. You can add more.
|
||||
- FLAC
|
||||
- WAV
|
||||
|
||||
|
||||
## To Do List
|
||||
- [ ] Custom Theme
|
||||
- [ ] RGB
|
||||
- [ ] Edit angle by degree
|
||||
- [ ] Sound on folder
|
||||
- [ ] Global sound
|
||||
|
||||
BIN
assets/A_LA.ogg
BIN
assets/A_LA.ogg
Binary file not shown.
BIN
assets/B_SI.ogg
BIN
assets/B_SI.ogg
Binary file not shown.
BIN
assets/C_DO.ogg
BIN
assets/C_DO.ogg
Binary file not shown.
BIN
assets/D_RE.ogg
BIN
assets/D_RE.ogg
Binary file not shown.
BIN
assets/E_MI.ogg
BIN
assets/E_MI.ogg
Binary file not shown.
BIN
assets/F_FA.ogg
BIN
assets/F_FA.ogg
Binary file not shown.
BIN
assets/G_SOL.ogg
BIN
assets/G_SOL.ogg
Binary file not shown.
BIN
assets/Z_ah.ogg
BIN
assets/Z_ah.ogg
Binary file not shown.
BIN
assets/a-3.mp3
Normal file
BIN
assets/a-3.mp3
Normal file
Binary file not shown.
BIN
assets/a-4.mp3
Normal file
BIN
assets/a-4.mp3
Normal file
Binary file not shown.
BIN
assets/a-5.mp3
Normal file
BIN
assets/a-5.mp3
Normal file
Binary file not shown.
BIN
assets/a3.mp3
Normal file
BIN
assets/a3.mp3
Normal file
Binary file not shown.
BIN
assets/a4.mp3
Normal file
BIN
assets/a4.mp3
Normal file
Binary file not shown.
BIN
assets/a5.mp3
Normal file
BIN
assets/a5.mp3
Normal file
Binary file not shown.
BIN
assets/b3.mp3
Normal file
BIN
assets/b3.mp3
Normal file
Binary file not shown.
BIN
assets/b4.mp3
Normal file
BIN
assets/b4.mp3
Normal file
Binary file not shown.
BIN
assets/b5.mp3
Normal file
BIN
assets/b5.mp3
Normal file
Binary file not shown.
BIN
assets/c-3.mp3
Normal file
BIN
assets/c-3.mp3
Normal file
Binary file not shown.
BIN
assets/c-4.mp3
Normal file
BIN
assets/c-4.mp3
Normal file
Binary file not shown.
BIN
assets/c-5.mp3
Normal file
BIN
assets/c-5.mp3
Normal file
Binary file not shown.
BIN
assets/c3.mp3
Normal file
BIN
assets/c3.mp3
Normal file
Binary file not shown.
BIN
assets/c4.mp3
Normal file
BIN
assets/c4.mp3
Normal file
Binary file not shown.
BIN
assets/c5.mp3
Normal file
BIN
assets/c5.mp3
Normal file
Binary file not shown.
BIN
assets/c6.mp3
Normal file
BIN
assets/c6.mp3
Normal file
Binary file not shown.
BIN
assets/d-3.mp3
Normal file
BIN
assets/d-3.mp3
Normal file
Binary file not shown.
BIN
assets/d-4.mp3
Normal file
BIN
assets/d-4.mp3
Normal file
Binary file not shown.
BIN
assets/d-5.mp3
Normal file
BIN
assets/d-5.mp3
Normal file
Binary file not shown.
BIN
assets/d3.mp3
Normal file
BIN
assets/d3.mp3
Normal file
Binary file not shown.
BIN
assets/d4.mp3
Normal file
BIN
assets/d4.mp3
Normal file
Binary file not shown.
BIN
assets/d5.mp3
Normal file
BIN
assets/d5.mp3
Normal file
Binary file not shown.
BIN
assets/e3.mp3
Normal file
BIN
assets/e3.mp3
Normal file
Binary file not shown.
BIN
assets/e4.mp3
Normal file
BIN
assets/e4.mp3
Normal file
Binary file not shown.
BIN
assets/e5.mp3
Normal file
BIN
assets/e5.mp3
Normal file
Binary file not shown.
BIN
assets/f-3.mp3
Normal file
BIN
assets/f-3.mp3
Normal file
Binary file not shown.
BIN
assets/f-4.mp3
Normal file
BIN
assets/f-4.mp3
Normal file
Binary file not shown.
BIN
assets/f-5.mp3
Normal file
BIN
assets/f-5.mp3
Normal file
Binary file not shown.
BIN
assets/f3.mp3
Normal file
BIN
assets/f3.mp3
Normal file
Binary file not shown.
BIN
assets/f4.mp3
Normal file
BIN
assets/f4.mp3
Normal file
Binary file not shown.
BIN
assets/f5.mp3
Normal file
BIN
assets/f5.mp3
Normal file
Binary file not shown.
BIN
assets/g-3.mp3
Normal file
BIN
assets/g-3.mp3
Normal file
Binary file not shown.
BIN
assets/g-4.mp3
Normal file
BIN
assets/g-4.mp3
Normal file
Binary file not shown.
BIN
assets/g-5.mp3
Normal file
BIN
assets/g-5.mp3
Normal file
Binary file not shown.
BIN
assets/g3.mp3
Normal file
BIN
assets/g3.mp3
Normal file
Binary file not shown.
BIN
assets/g4.mp3
Normal file
BIN
assets/g4.mp3
Normal file
Binary file not shown.
BIN
assets/g5.mp3
Normal file
BIN
assets/g5.mp3
Normal file
Binary file not shown.
39
build.rs
Normal file
39
build.rs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
fonts/EnvyCodeRNerdFontMono-Regular.ttf
Normal file
BIN
fonts/EnvyCodeRNerdFontMono-Regular.ttf
Normal file
Binary file not shown.
300
src/main.rs
300
src/main.rs
@@ -4,61 +4,61 @@ use polygon_draw::Polygon;
|
||||
mod music;
|
||||
use music::Music;
|
||||
|
||||
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::{Element, Font, Subscription};
|
||||
use iced::{
|
||||
Font,
|
||||
widget::{
|
||||
TextInput, column, text,
|
||||
text_input::{Icon, Side},
|
||||
},
|
||||
};
|
||||
use iced::{
|
||||
Length, Task, Theme,
|
||||
Color, Length, Task, Theme,
|
||||
widget::{Column, button, canvas, container, pick_list, row, scrollable, slider},
|
||||
};
|
||||
use iced_aw::widget::color_picker;
|
||||
|
||||
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 {
|
||||
let polytheme: Theme = {
|
||||
let back = Color::from_rgb8(39, 63, 79);
|
||||
let text = Color::from_rgb8(0xD3, 0xD4, 0xD9);
|
||||
let prim = Color::from_rgb8(230, 82, 31);
|
||||
let succ = Color::from_rgb8(0xFF, 0xF9, 0xFB);
|
||||
let dan = Color::from_rgb8(0xBB, 0x0A, 0x21);
|
||||
|
||||
Theme::custom(
|
||||
String::from("PolyTheme"),
|
||||
iced::theme::Palette {
|
||||
background: back,
|
||||
text: text,
|
||||
primary: prim,
|
||||
success: succ,
|
||||
danger: dan,
|
||||
},
|
||||
)
|
||||
};
|
||||
iced::application("My App", MyApp::update, MyApp::view)
|
||||
.theme(|_| Theme::Dark)
|
||||
.theme(move |_| polytheme.clone())
|
||||
.font(iced_fonts::REQUIRED_FONT_BYTES)
|
||||
.font(FONT_BYTES)
|
||||
.default_font(FONT)
|
||||
.antialiasing(true)
|
||||
.subscription(MyApp::subscription)
|
||||
.run_with(MyApp::new)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum Message {
|
||||
ButtonPressedIncrement,
|
||||
ButtonPressedDecrement,
|
||||
Tick,
|
||||
AddPolygon(String),
|
||||
ChangeTeta(usize, f32),
|
||||
Remove(usize),
|
||||
ChangeColor(usize, String),
|
||||
ChangeSound(usize, String),
|
||||
ToggleSavePanel,
|
||||
Save,
|
||||
Load,
|
||||
FileNameChanged(String),
|
||||
TogglePaused,
|
||||
SetMusicLength,
|
||||
LengthChange(String),
|
||||
ChangeDelta(f32),
|
||||
AddPoint,
|
||||
}
|
||||
|
||||
struct MyApp {
|
||||
music: Music,
|
||||
time_last_frame: Instant,
|
||||
@@ -69,12 +69,16 @@ struct MyApp {
|
||||
all_saves: Vec<String>,
|
||||
current_delta: f32,
|
||||
str_music_length: String,
|
||||
str_time: String,
|
||||
}
|
||||
|
||||
impl MyApp {
|
||||
fn new() -> (Self, Task<Message>) {
|
||||
let manager = AudioManager::<DefaultBackend>::new(AudioManagerSettings::default())
|
||||
.expect("Error to load AudioManager");
|
||||
|
||||
//let font_bytes = include_bytes!("../fonts/");
|
||||
|
||||
(
|
||||
Self {
|
||||
time_last_frame: Instant::now(),
|
||||
@@ -86,6 +90,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,17 +117,16 @@ 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) => {
|
||||
self.music.remove_polygon(self.current_delta, i - 1);
|
||||
self.music.remove_polygon(self.current_delta, i);
|
||||
}
|
||||
Message::ChangeTeta(i, teta) => {
|
||||
self.music.set_teta(self.current_delta, i, teta);
|
||||
}
|
||||
Message::ChangeColor(i, s) => {
|
||||
self.music.set_color(self.current_delta, i, s);
|
||||
}
|
||||
|
||||
Message::ChangeSound(i, s) => {
|
||||
let sound = StaticSoundData::from_file(format!("./assets/{s}"))
|
||||
.expect("Fail to load audio");
|
||||
@@ -139,7 +143,7 @@ impl MyApp {
|
||||
Ok(j) => {
|
||||
let decoded: Music = serde_json::from_str(&j).unwrap();
|
||||
self.music = decoded;
|
||||
self.music.update_frame(self.current_delta);
|
||||
self.music.update_frame();
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Error, no saves with this name to load, {e} ");
|
||||
@@ -148,41 +152,99 @@ 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);
|
||||
}
|
||||
Message::RemovePoint => {
|
||||
self.music.remove_point(self.current_delta);
|
||||
}
|
||||
Message::ClickedOnTimeLine(f) => {
|
||||
self.update(Message::ChangeDelta(f));
|
||||
}
|
||||
Message::SlidePointLeft => {
|
||||
self.music.slide_to_left(self.current_delta);
|
||||
self.music.fix_teta(self.current_delta);
|
||||
self.update_canvas_if_paused();
|
||||
}
|
||||
Message::SlidePointRight => {
|
||||
self.music.slide_to_right(self.current_delta);
|
||||
self.music.fix_teta(self.current_delta);
|
||||
self.update_canvas_if_paused();
|
||||
}
|
||||
Message::ChangeDegree(i, s) => {
|
||||
let mut mut_s = s;
|
||||
if mut_s.len() == 0 {
|
||||
mut_s = "0".to_string();
|
||||
}
|
||||
match mut_s.parse::<f32>() {
|
||||
Ok(val) => {
|
||||
if val <= 360. {
|
||||
self.update(Message::ChangeTeta(i, (val).to_radians()))
|
||||
}
|
||||
}
|
||||
Err(_) => {}
|
||||
}
|
||||
}
|
||||
Message::ChangeNbPerSec(s) => {
|
||||
let mut_s = s.trim_end_matches(" rev/sec");
|
||||
if mut_s.len() != s.len() {
|
||||
match mut_s.parse::<f32>() {
|
||||
Ok(val) => {
|
||||
let val = (val * 10.).floor() / 10.;
|
||||
if val >= 1. {
|
||||
self.music.nb_sec_for_rev = val
|
||||
}
|
||||
}
|
||||
Err(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::CancelColor(i) => {
|
||||
self.music.set_color_picker(self.current_delta, i, false);
|
||||
self.paused = false;
|
||||
}
|
||||
Message::SubmitColor(i) => {
|
||||
if !self.paused {
|
||||
self.update(Message::TogglePaused);
|
||||
}
|
||||
self.music.set_color_picker(self.current_delta, i, true);
|
||||
}
|
||||
Message::ChooseColor(i, color) => {
|
||||
self.music.set_color(self.current_delta, i, color);
|
||||
self.music.set_color_picker(self.current_delta, i, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn view(&self) -> iced::Element<Message> {
|
||||
let txt_nb_rev = if self.music.nb_sec_for_rev % 1. != 0.0 {
|
||||
format!("{} sec/revolution", self.music.nb_sec_for_rev)
|
||||
} else {
|
||||
format!("{}.0 sec/revolution", self.music.nb_sec_for_rev)
|
||||
};
|
||||
|
||||
let mut i = 0;
|
||||
let entries = self.all_sounds.clone();
|
||||
//Create all polygon options
|
||||
@@ -193,30 +255,44 @@ impl MyApp {
|
||||
.iter()
|
||||
.map(|polygon| {
|
||||
let current_index = i;
|
||||
i += 1;
|
||||
column![
|
||||
let but = button(text("").size(20).center()).on_press(Message::SubmitColor(i));
|
||||
let c = 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) }
|
||||
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)),
|
||||
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
|
||||
.step(PI / 84f32) // 84 | 4 for do PI / 4
|
||||
.width(Length::FillPortion(9))
|
||||
.height(32),
|
||||
]
|
||||
.spacing(10),
|
||||
]
|
||||
.spacing(10)
|
||||
.into()
|
||||
.into();
|
||||
i += 1;
|
||||
c
|
||||
})
|
||||
.collect();
|
||||
let ngon_options: Vec<String> = (5..=42).map(|sides| format!("Ngon{sides}")).collect();
|
||||
@@ -264,29 +340,8 @@ 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),
|
||||
]
|
||||
.spacing(20),
|
||||
row![
|
||||
container(
|
||||
canvas(self.music.current_frame(self.current_delta))
|
||||
@@ -294,10 +349,11 @@ impl MyApp {
|
||||
.width(Length::FillPortion(1))
|
||||
),
|
||||
column![
|
||||
text("Polygon options"),
|
||||
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)
|
||||
@@ -308,27 +364,50 @@ 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(text(if self.paused { "" } else { "" }).size(28).center())
|
||||
.on_press(Message::TogglePaused)
|
||||
.width(Length::FillPortion(1)),
|
||||
row![
|
||||
TextInput::new("MM:SS:CS", &self.str_time)
|
||||
.on_input(|new_value| Message::ChangeDeltaString(new_value))
|
||||
.size(28),
|
||||
text("/").size(30),
|
||||
TextInput::new("MM:SS:CS", &self.str_music_length)
|
||||
.on_input(|new_value| Message::LengthChange(new_value))
|
||||
.size(28),
|
||||
]
|
||||
.width(Length::FillPortion(10)),
|
||||
TextInput::new("1.0", &format!("{:.1} rev/sec", &self.music.nb_sec_for_rev))
|
||||
.on_input(|new_value| Message::ChangeNbPerSec(new_value))
|
||||
.size(28)
|
||||
.width(Length::FillPortion(2)),
|
||||
button(text("").size(28).center())
|
||||
.on_press(Message::SlidePointLeft)
|
||||
.width(Length::FillPortion(1)),
|
||||
button(text("").size(28).center())
|
||||
.on_press(Message::AddPoint)
|
||||
.width(Length::FillPortion(1)),
|
||||
button(text("").size(28).center())
|
||||
.on_press(Message::SlidePointRight)
|
||||
.width(Length::FillPortion(1)),
|
||||
button(text("").size(28).center())
|
||||
.on_press(Message::RemovePoint)
|
||||
.width(Length::FillPortion(1)),
|
||||
]
|
||||
.spacing(20),
|
||||
column![
|
||||
/*
|
||||
slider(0.0..=self.music.length, self.current_delta, move |f| {
|
||||
Message::ChangeDelta(f)
|
||||
})
|
||||
.step(&self.music.length / 10_000.),
|
||||
.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)),
|
||||
]
|
||||
@@ -337,15 +416,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<Message> {
|
||||
iced::Subscription::batch([
|
||||
//window::events().map(|(_id, event)| Message::WindowEvent(event)),
|
||||
iced::time::every(std::time::Duration::from_millis(16)).map(|_| Message::Tick),
|
||||
])
|
||||
if self.paused {
|
||||
Subscription::none() // ➝ désactive toutes les subscriptions
|
||||
} else {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
29
src/message.rs
Normal file
29
src/message.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
use iced::Color;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Message {
|
||||
ChangeNbPerSec(String),
|
||||
Tick,
|
||||
AddPolygon(String),
|
||||
ChangeTeta(usize, f32),
|
||||
Remove(usize),
|
||||
ChangeSound(usize, String),
|
||||
ToggleSavePanel,
|
||||
Save,
|
||||
Load,
|
||||
FileNameChanged(String),
|
||||
TogglePaused,
|
||||
LengthChange(String),
|
||||
ChangeDelta(f32),
|
||||
AddPoint,
|
||||
RemovePoint,
|
||||
ClickedOnTimeLine(f32),
|
||||
ChangeDeltaString(String),
|
||||
SlidePointLeft,
|
||||
SlidePointRight,
|
||||
ChangeDegree(usize, String),
|
||||
|
||||
ChooseColor(usize, Color),
|
||||
CancelColor(usize),
|
||||
SubmitColor(usize),
|
||||
}
|
||||
188
src/music.rs
188
src/music.rs
@@ -1,15 +1,22 @@
|
||||
use crate::message::Message;
|
||||
use crate::utils::string_to_polygon;
|
||||
use crate::{polygon_draw::*, utils::string_to_color};
|
||||
use crate::polygon_draw::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use kira::{AudioManager, sound::static_sound::StaticSoundData};
|
||||
|
||||
use iced::mouse;
|
||||
use iced::Vector;
|
||||
use iced::event::Status;
|
||||
use iced::mouse::Cursor;
|
||||
use iced::widget::canvas::{Event, Geometry};
|
||||
use iced::{Size, mouse};
|
||||
|
||||
use iced::widget::canvas;
|
||||
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 {
|
||||
@@ -35,6 +42,17 @@ impl Music {
|
||||
&mut self.poly_frame.last_mut().unwrap().1
|
||||
}
|
||||
}
|
||||
fn find_index_frame(&mut self, delta: f32) -> usize {
|
||||
if let Some(i) = self
|
||||
.poly_frame
|
||||
.windows(2)
|
||||
.position(|w| w[0].0 <= delta && delta < w[1].0)
|
||||
{
|
||||
i
|
||||
} else {
|
||||
self.poly_frame.len() - 1
|
||||
}
|
||||
}
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~PUBLIC~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
pub fn current_frame(&self, delta: f32) -> &PolygonFrame {
|
||||
if let Some(i) = self
|
||||
@@ -59,8 +77,10 @@ impl Music {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_frame(&mut self, delta: f32) {
|
||||
self.find_poly_frame(delta).update();
|
||||
pub fn update_frame(&mut self) {
|
||||
for (_, p) in &mut self.poly_frame {
|
||||
p.update();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fix_teta(&mut self, delta: f32) {
|
||||
@@ -103,10 +123,14 @@ impl Music {
|
||||
self.find_poly_frame(delta).polygons[index].global_teta = teta;
|
||||
}
|
||||
|
||||
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) {
|
||||
let current_frame = self.find_poly_frame(delta);
|
||||
current_frame.polygons[index].color = string_to_color(&color_name);
|
||||
current_frame.polygons[index].color_name = color_name;
|
||||
current_frame.polygons[index].color = color;
|
||||
}
|
||||
|
||||
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~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -119,6 +143,12 @@ impl Music {
|
||||
self.poly_frame
|
||||
.insert(pos, (delta, self.current_frame(delta).clone()));
|
||||
}
|
||||
pub fn remove_point(&mut self, delta: f32) {
|
||||
let i = self.find_index_frame(delta);
|
||||
if i != 0 {
|
||||
self.poly_frame.remove(i);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_polygon(&mut self, delta: f32, polygon_name: String) {
|
||||
let current_frame = self.find_poly_frame(delta);
|
||||
@@ -129,47 +159,86 @@ 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<Message> canvas::Program<Message> for Music {
|
||||
impl canvas::Program<Message> for Music {
|
||||
// No internal state
|
||||
type State = ();
|
||||
type State = bool;
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
_state: &(),
|
||||
_state: &Self::State,
|
||||
renderer: &Renderer,
|
||||
_theme: &Theme,
|
||||
bounds: Rectangle,
|
||||
_cursor: mouse::Cursor,
|
||||
) -> 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 mut toggle_color = true;
|
||||
let padding = 8.;
|
||||
let w = bounds.width - (padding * 2.);
|
||||
for (delta, _) in &self.poly_frame {
|
||||
let x = delta / self.length * w + padding;
|
||||
frame.fill_rectangle(
|
||||
iced::Point { x: x, y: 0.0 },
|
||||
frame.size(),
|
||||
if toggle_color {
|
||||
Color::from_rgb(0.3, 0.3, 0.3)
|
||||
} 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()
|
||||
for (delta, polyframe) in &self.poly_frame {
|
||||
let x = delta / self.length * w + 8.;
|
||||
let mut back_frame = canvas::Frame::new(
|
||||
renderer,
|
||||
Size {
|
||||
width: bounds.width,
|
||||
height: bounds.height,
|
||||
},
|
||||
);
|
||||
|
||||
let x = self.current_delta / self.length * w + padding;
|
||||
frame.stroke_rectangle(
|
||||
back_frame.fill_rectangle(
|
||||
iced::Point { x: x, y: 0.0 },
|
||||
frame.size(),
|
||||
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: 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.;
|
||||
let mut frame_cursor = canvas::Frame::new(renderer, bounds.size());
|
||||
frame_cursor.stroke_rectangle(
|
||||
iced::Point::new(x, 0.),
|
||||
iced::Size {
|
||||
width: 0.,
|
||||
@@ -177,12 +246,63 @@ impl<Message> canvas::Program<Message> for Music {
|
||||
},
|
||||
Stroke {
|
||||
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()
|
||||
},
|
||||
);
|
||||
frame_cursor.stroke_rectangle(
|
||||
iced::Point { x: 0.0, y: 0.0 },
|
||||
frame.size(),
|
||||
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
|
||||
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(
|
||||
&self,
|
||||
state: &mut Self::State,
|
||||
event: Event,
|
||||
bounds: Rectangle,
|
||||
cursor: Cursor,
|
||||
) -> (Status, Option<Message>) {
|
||||
//eprintln!("event = {:?}", event);
|
||||
if let Event::Mouse(mouse_event) = event {
|
||||
match mouse_event {
|
||||
mouse::Event::ButtonPressed(mouse::Button::Left) => {
|
||||
*state = true;
|
||||
if let Some(position) = cursor.position_in(bounds) {
|
||||
let pos_x = (position.x - 8.0) / (bounds.width - 16.);
|
||||
let delta = (pos_x * self.length).clamp(0., self.length);
|
||||
return (
|
||||
Status::Captured,
|
||||
Some(crate::message::Message::ClickedOnTimeLine(delta)),
|
||||
);
|
||||
}
|
||||
}
|
||||
mouse::Event::ButtonReleased(mouse::Button::Left) => *state = false,
|
||||
mouse::Event::CursorMoved { position: _ } => {
|
||||
if let Some(position) = cursor.position_in(bounds)
|
||||
&& *state
|
||||
{
|
||||
let pos_x = (position.x - 8.0) / (bounds.width - 16.);
|
||||
let delta = (pos_x * self.length).clamp(0., self.length);
|
||||
return (Status::Captured, Some(Message::ClickedOnTimeLine(delta)));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
(Status::Ignored, None)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,12 @@ use crate::utils::string_to_color;
|
||||
|
||||
use std::f32::consts::PI;
|
||||
|
||||
use iced::Size;
|
||||
use iced::Vector;
|
||||
use iced::mouse;
|
||||
use iced::widget::canvas;
|
||||
use iced::widget::canvas::Stroke;
|
||||
use iced::widget::canvas::Style;
|
||||
use iced::{Color, Rectangle, Renderer, Theme};
|
||||
use iced::widget::canvas::{Frame, Stroke, Style};
|
||||
use iced::{Color, Point, Rectangle, Renderer, Theme};
|
||||
use kira::sound::static_sound::StaticSoundData;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -49,6 +49,65 @@ impl PolygonFrame {
|
||||
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 {
|
||||
@@ -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 mut vec = Vector::new(0.0, -radius);
|
||||
let c = frame.center();
|
||||
frame.fill_rectangle(
|
||||
iced::Point { x: 0., y: 0. },
|
||||
frame.size(),
|
||||
Color::from_rgb8(27, 60, 83),
|
||||
);
|
||||
// Draw Circle
|
||||
let circle = canvas::Path::circle(frame.center(), radius);
|
||||
frame.stroke(
|
||||
&circle,
|
||||
Stroke {
|
||||
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()
|
||||
},
|
||||
);
|
||||
@@ -100,7 +164,7 @@ impl<Message> canvas::Program<Message> for PolygonFrame {
|
||||
}
|
||||
|
||||
// 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));
|
||||
|
||||
// Draw the frame
|
||||
@@ -109,6 +173,7 @@ impl<Message> canvas::Program<Message> for PolygonFrame {
|
||||
frame.size(),
|
||||
Stroke {
|
||||
width: 16.0,
|
||||
style: Style::Solid(Color::from_rgb8(207, 74, 28)),
|
||||
..Stroke::default()
|
||||
},
|
||||
);
|
||||
@@ -129,6 +194,7 @@ pub struct Polygon {
|
||||
#[serde(skip)]
|
||||
pub color: Color,
|
||||
pub color_name: String,
|
||||
pub show_color_picker: bool,
|
||||
}
|
||||
#[warn(dead_code)]
|
||||
impl Polygon {
|
||||
@@ -153,61 +219,55 @@ impl Polygon {
|
||||
}
|
||||
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);
|
||||
for i in 0..n_side {
|
||||
v.push((i as f32 * 2.0 * PI) / n_side as f32);
|
||||
}
|
||||
Polygon {
|
||||
global_teta: teta,
|
||||
points_teta: v,
|
||||
sound: sound,
|
||||
sound_name: "tick.ogg".to_string(),
|
||||
color_name: "Black".to_string(),
|
||||
name: "".to_string(),
|
||||
color: Color::BLACK,
|
||||
let mut p = Polygon::default(sound);
|
||||
p.points_teta = v;
|
||||
p
|
||||
}
|
||||
}
|
||||
pub fn segment(teta: f32, sound: StaticSoundData) -> Self {
|
||||
Polygon::n_gon(teta, 2, sound)
|
||||
pub fn segment(sound: StaticSoundData) -> Self {
|
||||
Polygon::n_gon(2, sound)
|
||||
}
|
||||
|
||||
pub fn triangle(teta: f32, sound: StaticSoundData) -> Self {
|
||||
Polygon::n_gon(teta, 3, sound)
|
||||
pub fn triangle(sound: StaticSoundData) -> Self {
|
||||
Polygon::n_gon(3, sound)
|
||||
}
|
||||
|
||||
pub fn square(teta: f32, sound: StaticSoundData) -> Self {
|
||||
Polygon::n_gon(teta, 4, sound)
|
||||
pub fn square(sound: StaticSoundData) -> Self {
|
||||
Polygon::n_gon(4, sound)
|
||||
}
|
||||
|
||||
pub fn nr_6_in_30(teta: f32, sound: StaticSoundData) -> Self {
|
||||
Polygon {
|
||||
sound: sound,
|
||||
name: "".to_string(),
|
||||
color: Color::BLACK,
|
||||
global_teta: teta,
|
||||
sound_name: "tick.ogg".to_string(),
|
||||
color_name: "Black".to_string(),
|
||||
points_teta: vec![
|
||||
pub fn nr_6_in_30(sound: StaticSoundData) -> Self {
|
||||
let mut p = Polygon::default(sound);
|
||||
p.points_teta = vec![
|
||||
2.0 * 5.0 * PI / 30.0,
|
||||
2.0 * 6.0 * PI / 30.0,
|
||||
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,
|
||||
],
|
||||
];
|
||||
p
|
||||
}
|
||||
}
|
||||
pub fn nr_7_in_30(teta: f32, sound: StaticSoundData) -> Self {
|
||||
Polygon {
|
||||
sound: sound,
|
||||
name: "".to_string(),
|
||||
color: Color::BLACK,
|
||||
sound_name: "tick.ogg".to_string(),
|
||||
color_name: "Black".to_string(),
|
||||
global_teta: teta,
|
||||
points_teta: vec![
|
||||
pub fn nr_7_in_30(sound: StaticSoundData) -> Self {
|
||||
let mut p = Polygon::default(sound);
|
||||
p.points_teta = vec![
|
||||
0.0,
|
||||
2.0 * 6.0 * PI / 30.0,
|
||||
2.0 * 7.0 * PI / 30.0,
|
||||
@@ -215,18 +275,12 @@ impl Polygon {
|
||||
2.0 * 17.0 * PI / 30.0,
|
||||
2.0 * 23.0 * PI / 30.0,
|
||||
2.0 * 24.0 * PI / 30.0,
|
||||
],
|
||||
];
|
||||
p
|
||||
}
|
||||
}
|
||||
pub fn nr_8_in_30(teta: f32, sound: StaticSoundData) -> Self {
|
||||
Polygon {
|
||||
sound: sound,
|
||||
name: "".to_string(),
|
||||
color: Color::BLACK,
|
||||
sound_name: "tick.ogg".to_string(),
|
||||
color_name: "Black".to_string(),
|
||||
global_teta: teta,
|
||||
points_teta: vec![
|
||||
pub fn nr_8_in_30(sound: StaticSoundData) -> Self {
|
||||
let mut p = Polygon::default(sound);
|
||||
p.points_teta = vec![
|
||||
2.0 * PI / 30.0,
|
||||
2.0 * 5.0 * PI / 30.0,
|
||||
2.0 * 11.0 * PI / 30.0,
|
||||
@@ -235,18 +289,12 @@ impl Polygon {
|
||||
2.0 * 19.0 * PI / 30.0,
|
||||
2.0 * 25.0 * PI / 30.0,
|
||||
2.0 * 29.0 * PI / 30.0,
|
||||
],
|
||||
];
|
||||
p
|
||||
}
|
||||
}
|
||||
pub fn nr_9_in_30(teta: f32, sound: StaticSoundData) -> Self {
|
||||
Polygon {
|
||||
sound: sound,
|
||||
name: "".to_string(),
|
||||
color: Color::BLACK,
|
||||
sound_name: "tick.ogg".to_string(),
|
||||
color_name: "Black".to_string(),
|
||||
global_teta: teta,
|
||||
points_teta: vec![
|
||||
pub fn nr_9_in_30(sound: StaticSoundData) -> Self {
|
||||
let mut p = Polygon::default(sound);
|
||||
p.points_teta = vec![
|
||||
0.0,
|
||||
2.0 * PI / 30.0,
|
||||
2.0 * 7.0 * PI / 30.0,
|
||||
@@ -256,18 +304,12 @@ impl Polygon {
|
||||
2.0 * 19.0 * PI / 30.0,
|
||||
2.0 * 23.0 * PI / 30.0,
|
||||
2.0 * 29.0 * PI / 30.0,
|
||||
],
|
||||
];
|
||||
p
|
||||
}
|
||||
}
|
||||
pub fn nr_8_in_42(teta: f32, sound: StaticSoundData) -> Self {
|
||||
Polygon {
|
||||
sound: sound,
|
||||
name: "".to_string(),
|
||||
color: Color::BLACK,
|
||||
sound_name: "tick.ogg".to_string(),
|
||||
color_name: "Black".to_string(),
|
||||
global_teta: teta,
|
||||
points_teta: vec![
|
||||
pub fn nr_8_in_42(sound: StaticSoundData) -> Self {
|
||||
let mut p = Polygon::default(sound);
|
||||
p.points_teta = vec![
|
||||
2.0 * 3.0 * PI / 42.0,
|
||||
2.0 * 9.0 * PI / 42.0,
|
||||
2.0 * 14.0 * PI / 42.0,
|
||||
@@ -276,18 +318,12 @@ impl Polygon {
|
||||
2.0 * 28.0 * PI / 42.0,
|
||||
2.0 * 33.0 * PI / 42.0,
|
||||
2.0 * 39.0 * PI / 42.0,
|
||||
],
|
||||
];
|
||||
p
|
||||
}
|
||||
}
|
||||
pub fn nr_9_in_42(teta: f32, sound: StaticSoundData) -> Self {
|
||||
Polygon {
|
||||
sound: sound,
|
||||
name: "".to_string(),
|
||||
color: Color::BLACK,
|
||||
sound_name: "tick.ogg".to_string(),
|
||||
color_name: "Black".to_string(),
|
||||
global_teta: teta,
|
||||
points_teta: vec![
|
||||
pub fn nr_9_in_42(sound: StaticSoundData) -> Self {
|
||||
let mut p = Polygon::default(sound);
|
||||
p.points_teta = vec![
|
||||
0.0,
|
||||
2.0 * 6.0 * PI / 42.0,
|
||||
2.0 * 11.0 * PI / 42.0,
|
||||
@@ -297,18 +333,12 @@ impl Polygon {
|
||||
2.0 * 30.0 * PI / 42.0,
|
||||
2.0 * 31.0 * PI / 42.0,
|
||||
2.0 * 36.0 * PI / 42.0,
|
||||
],
|
||||
];
|
||||
p
|
||||
}
|
||||
}
|
||||
pub fn nr_10a_in_42(teta: f32, sound: StaticSoundData) -> Self {
|
||||
Polygon {
|
||||
sound: sound,
|
||||
name: "".to_string(),
|
||||
color: Color::BLACK,
|
||||
sound_name: "tick.ogg".to_string(),
|
||||
color_name: "Black".to_string(),
|
||||
global_teta: teta,
|
||||
points_teta: vec![
|
||||
pub fn nr_10a_in_42(sound: StaticSoundData) -> Self {
|
||||
let mut p = Polygon::default(sound);
|
||||
p.points_teta = vec![
|
||||
2.0 * 6.0 * PI / 42.0,
|
||||
2.0 * 7.0 * PI / 42.0,
|
||||
2.0 * 11.0 * PI / 42.0,
|
||||
@@ -319,18 +349,12 @@ impl Polygon {
|
||||
2.0 * 31.0 * PI / 42.0,
|
||||
2.0 * 35.0 * PI / 42.0,
|
||||
2.0 * 36.0 * PI / 42.0,
|
||||
],
|
||||
];
|
||||
p
|
||||
}
|
||||
}
|
||||
pub fn nr_10b_in_42(teta: f32, sound: StaticSoundData) -> Self {
|
||||
Polygon {
|
||||
sound: sound,
|
||||
name: "".to_string(),
|
||||
color: Color::BLACK,
|
||||
sound_name: "tick.ogg".to_string(),
|
||||
color_name: "Black".to_string(),
|
||||
global_teta: teta,
|
||||
points_teta: vec![
|
||||
pub fn nr_10b_in_42(sound: StaticSoundData) -> Self {
|
||||
let mut p = Polygon::default(sound);
|
||||
p.points_teta = vec![
|
||||
0.0,
|
||||
2.0 * 1.0 * PI / 42.0,
|
||||
2.0 * 5.0 * PI / 42.0,
|
||||
@@ -341,8 +365,8 @@ impl Polygon {
|
||||
2.0 * 29.0 * PI / 42.0,
|
||||
2.0 * 30.0 * PI / 42.0,
|
||||
2.0 * 41.0 * PI / 42.0,
|
||||
],
|
||||
}
|
||||
];
|
||||
p
|
||||
}
|
||||
}
|
||||
fn dummy_sound() -> StaticSoundData {
|
||||
|
||||
34
src/utils.rs
34
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: AsRef<str>>(s: S) -> Color {
|
||||
match s.as_ref() {
|
||||
"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;
|
||||
if s.starts_with("Ngon") {
|
||||
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 {
|
||||
poly = Polygon::n_gon(0.0, 0, dummy_sound());
|
||||
poly = Polygon::n_gon(0, dummy_sound());
|
||||
}
|
||||
} else {
|
||||
match s {
|
||||
"Segment" => {
|
||||
poly = Polygon::segment(0.0, dummy_sound());
|
||||
poly = Polygon::segment(dummy_sound());
|
||||
}
|
||||
"Triangle" => {
|
||||
poly = Polygon::triangle(0.0, dummy_sound());
|
||||
poly = Polygon::triangle(dummy_sound());
|
||||
}
|
||||
"Square" => {
|
||||
poly = Polygon::square(0.0, dummy_sound());
|
||||
poly = Polygon::square(dummy_sound());
|
||||
}
|
||||
"Nr6In30" => {
|
||||
poly = Polygon::nr_6_in_30(0.0, dummy_sound());
|
||||
poly = Polygon::nr_6_in_30(dummy_sound());
|
||||
}
|
||||
"Nr7In30" => {
|
||||
poly = Polygon::nr_7_in_30(0.0, dummy_sound());
|
||||
poly = Polygon::nr_7_in_30(dummy_sound());
|
||||
}
|
||||
"Nr8In30" => {
|
||||
poly = Polygon::nr_8_in_30(0.0, dummy_sound());
|
||||
poly = Polygon::nr_8_in_30(dummy_sound());
|
||||
}
|
||||
"Nr9In30" => {
|
||||
poly = Polygon::nr_9_in_30(0.0, dummy_sound());
|
||||
poly = Polygon::nr_9_in_30(dummy_sound());
|
||||
}
|
||||
"Nr8In42" => {
|
||||
poly = Polygon::nr_8_in_42(0.0, dummy_sound());
|
||||
poly = Polygon::nr_8_in_42(dummy_sound());
|
||||
}
|
||||
"Nr9In42" => {
|
||||
poly = Polygon::nr_9_in_42(0.0, dummy_sound());
|
||||
poly = Polygon::nr_9_in_42(dummy_sound());
|
||||
}
|
||||
"Nr10aIn42" => {
|
||||
poly = Polygon::nr_10a_in_42(0.0, dummy_sound());
|
||||
poly = Polygon::nr_10a_in_42(dummy_sound());
|
||||
}
|
||||
"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();
|
||||
|
||||
Reference in New Issue
Block a user