formatting, finished timesignature type, starting to split main editor window into 6

main
aprzn 2 years ago
parent 40781f6f4b
commit d3b17e38f5

156
Cargo.lock generated

@ -835,6 +835,15 @@ dependencies = [
"bytemuck",
]
[[package]]
name = "encoding_rs"
version = "0.8.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394"
dependencies = [
"cfg-if",
]
[[package]]
name = "enumset"
version = "1.0.12"
@ -1772,6 +1781,7 @@ dependencies = [
"home",
"ordered-float",
"rodio",
"symphonia",
]
[[package]]
@ -2172,6 +2182,152 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "symphonia"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3671dd6f64f4f9d5c87179525054cfc1f60de23ba1f193bd6ceab812737403f1"
dependencies = [
"lazy_static",
"symphonia-bundle-flac",
"symphonia-bundle-mp3",
"symphonia-codec-adpcm",
"symphonia-codec-pcm",
"symphonia-codec-vorbis",
"symphonia-core",
"symphonia-format-mkv",
"symphonia-format-ogg",
"symphonia-format-wav",
"symphonia-metadata",
]
[[package]]
name = "symphonia-bundle-flac"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dc2deed3204967871ba60f913378f95820cb47a2fe9b2eef5a9eedb417dfdc8"
dependencies = [
"log",
"symphonia-core",
"symphonia-metadata",
"symphonia-utils-xiph",
]
[[package]]
name = "symphonia-bundle-mp3"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55a0846e7a2c9a8081ff799fc83a975170417ad2a143f644a77ec2e3e82a2b73"
dependencies = [
"bitflags",
"lazy_static",
"log",
"symphonia-core",
"symphonia-metadata",
]
[[package]]
name = "symphonia-codec-adpcm"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a5cfb8d4405e26eb9593157dc45b05e102b8d774b38ed2a95946d6bb9e26e3e"
dependencies = [
"log",
"symphonia-core",
]
[[package]]
name = "symphonia-codec-pcm"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cb9a9f0b9991cccf3217b74644af412d5d082a4815e5e2943f26e0ecabdf3c9"
dependencies = [
"log",
"symphonia-core",
]
[[package]]
name = "symphonia-codec-vorbis"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dfed6f7b6bfa21d7cef1acefc8eae5db80df1608a1aca91871b07cbd28d7b74"
dependencies = [
"log",
"symphonia-core",
"symphonia-utils-xiph",
]
[[package]]
name = "symphonia-core"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b9567e2d8a5f866b2f94f5d366d811e0c6826babcff6d37de9e1a6690d38869"
dependencies = [
"arrayvec 0.7.2",
"bitflags",
"bytemuck",
"lazy_static",
"log",
]
[[package]]
name = "symphonia-format-mkv"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bd22f2def8c8f078495ad66111648bfc7d5222ee33774f2077cb665588f3119"
dependencies = [
"lazy_static",
"log",
"symphonia-core",
"symphonia-metadata",
"symphonia-utils-xiph",
]
[[package]]
name = "symphonia-format-ogg"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "474df6e86b871dcb56913130bada1440245f483057c4a2d8a2981455494c4439"
dependencies = [
"log",
"symphonia-core",
"symphonia-metadata",
"symphonia-utils-xiph",
]
[[package]]
name = "symphonia-format-wav"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06679bd5646b3037300f88891dfc8a6e1cc4e1133206cc17a98e5d7c22f88296"
dependencies = [
"log",
"symphonia-core",
"symphonia-metadata",
]
[[package]]
name = "symphonia-metadata"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acd35c263223ef6161000be79b124a75de3e065eea563bf3ef169b3e94c7bb2e"
dependencies = [
"encoding_rs",
"lazy_static",
"log",
"symphonia-core",
]
[[package]]
name = "symphonia-utils-xiph"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce340a6c33ac06cb42de01220308ec056e8a2a3d5cc664aaf34567392557136b"
dependencies = [
"symphonia-core",
"symphonia-metadata",
]
[[package]]
name = "syn"
version = "1.0.107"

@ -14,3 +14,4 @@ flate2 = {version = "1.0.25"}
gd_plist = {git = "https://github.com/Syudagye/gd-plist.git", version = "1.4.0"}
chrono = {version = "0.4.23"}
ordered-float = {version = "3.4.0"}
symphonia = {version = "0.5.2", features = ["mp3"]}

@ -1,11 +1,11 @@
use std::path::PathBuf;
use std::fs::File;
use std::io::{Read, Cursor};
use base64::engine::{general_purpose::URL_SAFE, Engine};
use eframe::egui::TextFormat;
use eframe::epaint::text::LayoutJob;
use flate2::read::GzDecoder;
use gd_plist::Value;
use eframe::epaint::text::LayoutJob;
use eframe::egui::TextFormat;
use std::fs::File;
use std::io::{Cursor, Read};
use std::path::PathBuf;
struct User {
name: String,
@ -13,9 +13,9 @@ struct User {
}
enum Song {
Official{id: i32 /*k8*/},
Newgrounds{id: i32 /*k45*/},
Unknown
Official { id: i32 /*k8*/ },
Newgrounds { id: i32 /*k45*/ },
Unknown,
}
struct Level {
@ -32,8 +32,15 @@ pub struct OuterLevel {
impl OuterLevel {
pub fn load_all() -> Vec<OuterLevel> {
let plist = get_local_level_plist();
let levels: Vec<OuterLevel> = plist.as_dictionary().and_then(|dict| dict.get("LLM_01")).unwrap()
.as_dictionary().unwrap().into_iter().filter(|(key, _)| key.as_str() != "_isArr").map(|(_, val)| {
let levels: Vec<OuterLevel> = plist
.as_dictionary()
.and_then(|dict| dict.get("LLM_01"))
.unwrap()
.as_dictionary()
.unwrap()
.into_iter()
.filter(|(key, _)| key.as_str() != "_isArr")
.map(|(_, val)| {
let mut builder = LevelBuilder::new();
let props = val.as_dictionary().unwrap();
if let Some(title) = props.get("k2") {
@ -43,7 +50,8 @@ impl OuterLevel {
builder.with_revision(rev.as_signed_integer().unwrap().into());
}
builder.build_outer_level().unwrap()
}).collect();
})
.collect();
levels
}
@ -52,17 +60,21 @@ impl OuterLevel {
Some(rev) => {
let mut job = LayoutJob::default();
job.append(&format!("{} ", self.name), 0f32, TextFormat::default());
job.append(&format!("(rev {})", rev), 0f32, TextFormat {
job.append(
&format!("(rev {})", rev),
0f32,
TextFormat {
italics: true,
..Default::default()
});
job
},
);
job
}
None => {
let mut job = LayoutJob::default();
job.append(&self.name, 0f32, TextFormat::default());
job
},
}
}
}
}
@ -70,10 +82,24 @@ impl OuterLevel {
pub fn gd_path() -> PathBuf {
let mut path_buf = home::home_dir().unwrap();
#[cfg(unix)]
path_buf.extend([".local", "share", "Steam", "steamapps",
"compatdata", "322170", "pfx", "drive_c",
"users", "steamuser", "AppData", "Local",
"GeometryDash"].iter());
path_buf.extend(
[
".local",
"share",
"Steam",
"steamapps",
"compatdata",
"322170",
"pfx",
"drive_c",
"users",
"steamuser",
"AppData",
"Local",
"GeometryDash",
]
.iter(),
);
#[cfg(windows)]
path_buf.extend(["AppData", "Local", "GeometryDash"].iter());
path_buf
@ -87,12 +113,18 @@ struct LevelBuilder {
impl Default for LevelBuilder {
fn default() -> Self {
Self {name: None, song: None, revision: None}
Self {
name: None,
song: None,
revision: None,
}
}
}
impl LevelBuilder {
fn new() -> Self {Self::default()}
fn new() -> Self {
Self::default()
}
fn with_name(&mut self, name: String) {
self.name = Some(name);
@ -112,7 +144,10 @@ impl LevelBuilder {
name: Some(name),
song: Some(song),
revision,
} => Some(Level{song, outer: OuterLevel {name, revision}}),
} => Some(Level {
song,
outer: OuterLevel { name, revision },
}),
_ => None,
}
}
@ -123,7 +158,7 @@ impl LevelBuilder {
name: Some(name),
revision,
..
} => Some(OuterLevel {name, revision}),
} => Some(OuterLevel { name, revision }),
_ => None,
}
}
@ -131,12 +166,17 @@ impl LevelBuilder {
fn get_local_level_plist() -> Value {
let raw_save_data = {
let mut save_file = File::open(gd_path().join("CCLocalLevels.dat")).expect("No save file found!");
let mut save_file =
File::open(gd_path().join("CCLocalLevels.dat")).expect("No save file found!");
let mut sd = Vec::new();
save_file.read_to_end(&mut sd).unwrap();
sd
};
let data_post_xor: Vec<u8> = raw_save_data.iter().map(|b| b ^ 11).filter(|&b| b != 0u8).collect();
let data_post_xor: Vec<u8> = raw_save_data
.iter()
.map(|b| b ^ 11)
.filter(|&b| b != 0u8)
.collect();
let data_post_b64 = URL_SAFE.decode(data_post_xor).unwrap();
let mut decoder = GzDecoder::<&[u8]>::new(data_post_b64.as_ref());
let mut plist = String::new();
@ -145,4 +185,3 @@ fn get_local_level_plist() -> Value {
}
Value::from_reader(Cursor::new(plist)).unwrap()
}

@ -5,12 +5,11 @@ use eframe;
use eframe::egui;
use std::boxed::Box;
use std::collections::VecDeque;
use std::marker::PhantomData;
struct PipeDash {
msg_queue: VecDeque<Message>,
selected_level: Option<usize>,
selected_color: Option<Color>,
level_list: Vec<gd::OuterLevel>,
editor: Editor,
}
@ -24,7 +23,6 @@ enum Color {
#[derive(Debug)]
enum Message {
ColorSelected(Color),
LevelSelected(usize),
}
@ -34,38 +32,78 @@ struct Editor {
beats_per_bar: f32,
subdivisions: f32,
beat_rate: music::BeatRate,
time_signatures: music::TimeSignature,
green_lines: music::Lines,
orange_lines: music::Lines,
yellow_lines: music::Lines,
}
type EditorWidget<'a> = &'a mut Editor;
struct Orange;
struct Yellow;
struct Green;
struct BeatRateWidget<'a>(&'a mut Editor);
struct TimeSignatureWidget<'a>(&'a mut Editor);
struct LinesWidget<'a, C: WithColor>(&'a mut Editor, PhantomData<C>);
trait WithColor {}
impl WithColor for Orange {}
impl WithColor for Yellow {}
impl WithColor for Green {}
impl Editor {
pub fn widget(&mut self) -> EditorWidget {
self
pub fn beat_rate_widget(&mut self) -> BeatRateWidget {
BeatRateWidget(self)
}
pub fn time_signature_widget(&mut self) -> TimeSignatureWidget {
TimeSignatureWidget(self)
}
pub fn lines_widget<C: WithColor>(&mut self) -> LinesWidget<C> {
LinesWidget(self, Default::default())
}
}
impl<'a> egui::Widget for EditorWidget<'a> {
impl<'a> egui::Widget for BeatRateWidget<'a> {
fn ui(self, ui: &mut egui::Ui) -> egui::Response {
// 1. choose size
let max_rect = ui.max_rect();
let preferred_size = max_rect.size();
let preferred_size = egui::Vec2::new(max_rect.size().x, 60.0);
// 2. allocate space
let (rect, res) = ui.allocate_exact_size(preferred_size, egui::Sense::click_and_drag());
// 3. handle interactions
// 4. draw widget
if ui.is_rect_visible(rect) {
ui.painter().rect_filled(rect, 0f32, eframe::epaint::Color32::from_gray(0));
ui.painter()
.rect_filled(rect, 0f32, eframe::epaint::Color32::from_gray(0));
}
res
}
}
impl<'a> egui::Widget for TimeSignatureWidget<'a> {
fn ui(self, ui: &mut egui::Ui) -> egui::Response {
// 1. choose size
let max_rect = ui.max_rect();
let preferred_size = egui::Vec2::new(max_rect.size().x, 60.0);
// 2. allocate space
let (rect, res) = ui.allocate_exact_size(preferred_size, egui::Sense::click_and_drag());
// 3. handle interactions
// 4. draw widget
if ui.is_rect_visible(rect) {
ui.painter()
.rect_filled(rect, 0f32, eframe::epaint::Color32::from_gray(0));
}
res
}
}
impl PipeDash {
fn new(_cc: &eframe::CreationContext) -> Self {
Self {
selected_level: None,
selected_color: None,
msg_queue: VecDeque::new(),
level_list: gd::OuterLevel::load_all(),
editor: Editor {
@ -74,16 +112,28 @@ impl PipeDash {
beats_per_bar: 4.0,
subdivisions: 4.0,
beat_rate: music::StaticBeatRate::from_bpm(120f32).into(),
}
time_signatures: music::StaticTimeSignature::new(4, 4).into(),
green_lines: music::Lines::new(),
orange_lines: music::Lines::new(),
yellow_lines: music::Lines::new(),
},
}
}
fn side_panel(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
egui::SidePanel::left("level_picker").default_width(100f32).show(ctx, |ui| {
egui::SidePanel::left("level_picker")
.default_width(100f32)
.show(ctx, |ui| {
egui::ScrollArea::vertical().show(ui, |ui| {
ui.with_layout(egui::Layout::top_down_justified(egui::Align::Min), |ui| {
for (idx, level) in self.level_list.iter().enumerate() {
if ui.selectable_label(self.selected_level == Some(idx), level.display_name()).clicked() {
if ui
.selectable_label(
self.selected_level == Some(idx),
level.display_name(),
)
.clicked()
{
self.msg_queue.push_back(Message::LevelSelected(idx));
}
}
@ -93,30 +143,13 @@ impl PipeDash {
}
fn center_panel(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
use Message::*;
use Color::*;
egui::CentralPanel::default().show(ctx, |ui| {
ui.vertical_centered_justified(|ui| {
ui.horizontal_top(|ui| {
ui.vertical(|ui| {
ui.label("Song name");
ui.label("Song id");
});
ui.with_layout(egui::Layout::right_to_left(egui::Align::TOP), |ui| {
ui.vertical(|ui| {
if ui.selectable_label(self.selected_color == Some(Orange), "orange").clicked() {
self.msg_queue.push_back(ColorSelected(Orange));
}
if ui.selectable_label(self.selected_color == Some(Yellow), "yellow").clicked() {
self.msg_queue.push_back(ColorSelected(Yellow));
}
if ui.selectable_label(self.selected_color == Some(Green), "green").clicked() {
self.msg_queue.push_back(ColorSelected(Green));
}
});
})
});
ui.add(self.editor.widget());
ui.add(self.editor.beat_rate_widget());
});
});
}
@ -125,12 +158,9 @@ impl PipeDash {
for message in self.msg_queue.drain(..) {
println!("{:?}", message);
match message {
Message::ColorSelected(color) => {
self.selected_color = Some(color);
},
Message::LevelSelected(idx) => {
self.selected_level = Some(idx);
},
}
}
}
}
@ -152,4 +182,3 @@ fn main() {
let opts = eframe::NativeOptions::default();
eframe::run_native("PipeDash", opts, Box::new(|cc| Box::new(PipeDash::new(cc))));
}

@ -1,6 +1,6 @@
use chrono::Duration;
use std::collections::BTreeMap;
use ordered_float::OrderedFloat as Float;
use std::collections::{BTreeMap, BTreeSet};
pub type BeatPosition = Float<f32>;
@ -25,6 +25,10 @@ pub struct StaticTimeSignature {
denominator: u32,
}
#[derive(Default)]
pub struct Lines {
positions: BTreeSet<BeatPosition>,
}
impl StaticBeatRate {
pub fn from_bpm(bpm: f32) -> Self {
@ -48,9 +52,15 @@ impl BeatRate {
if &pos < first_change {
self.initial
} else {
*self.changes.iter().rev().find(|&el| el.0 <= &pos).unwrap().1
*self
.changes
.iter()
.rev()
.find(|&el| el.0 <= &pos)
.unwrap()
.1
}
}
},
None => self.initial,
}
}
@ -62,9 +72,16 @@ impl BeatRate {
/// Changes: when the time signature changes, the bar immediately resets
impl StaticTimeSignature {
pub fn new(numerator: u32, denominator: u32) -> Self {Self {numerator, denominator}}
pub fn new(numerator: u32, denominator: u32) -> Self {
Self {
numerator,
denominator,
}
}
fn beats_per_bar(&self) -> BeatPosition {(self.numerator as f32).into()}
fn beats_per_bar(&self) -> BeatPosition {
(self.numerator as f32).into()
}
}
impl From<StaticTimeSignature> for TimeSignature {
@ -87,28 +104,52 @@ impl TimeSignature {
if &pos < first_change {
self.initial
} else {
*self.changes.iter().rev().find(|&el| el.0 <= &pos).unwrap().1
*self
.changes
.iter()
.rev()
.find(|&el| el.0 <= &pos)
.unwrap()
.1
}
}
},
none => self.initial,
}
}
pub fn position_in_bar(&self, pos: BeatPosition) -> BeatPosition {
match self.changes.first_key_value() {
Some((first_change, first_change_sig)) => {
Some((first_change, _)) => {
if &pos < first_change {
pos % self.initial.beats_per_bar()
} else {
let (signature_start_point, signature) = self.changes.iter().rev().find(|&el| el.0 <= &pos).unwrap();
let (signature_start_point, signature) =
self.changes.iter().rev().find(|&el| el.0 <= &pos).unwrap();
(pos - signature_start_point) % signature.beats_per_bar()
}
},
}
None => pos % self.initial.beats_per_bar(),
}
}
}
impl Lines {
pub fn new() -> Self {
Self::default()
}
pub fn insert(&mut self, pos: BeatPosition) -> bool {
self.positions.insert(pos)
}
pub fn remove(&mut self, pos: BeatPosition) -> bool {
self.positions.remove(&pos)
}
pub fn get_positions(&mut self) -> &BTreeSet<BeatPosition> {
&self.positions
}
}
#[cfg(test)]
mod tests {

Loading…
Cancel
Save