|
|
|
@ -6,11 +6,13 @@ use eframe::egui;
|
|
|
|
|
use std::boxed::Box;
|
|
|
|
|
use std::collections::VecDeque;
|
|
|
|
|
use std::marker::PhantomData;
|
|
|
|
|
use symphonia::core::io::{MediaSource, MediaSourceStream};
|
|
|
|
|
|
|
|
|
|
struct PipeDash {
|
|
|
|
|
msg_queue: VecDeque<Message>,
|
|
|
|
|
selected_level: Option<usize>,
|
|
|
|
|
level_list: Vec<gd::OuterLevel>,
|
|
|
|
|
loaded_song: Option<Song>,
|
|
|
|
|
editor: Editor,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -29,13 +31,22 @@ enum Message {
|
|
|
|
|
struct Editor {
|
|
|
|
|
scroll_pos: f32,
|
|
|
|
|
pts_per_second: f32,
|
|
|
|
|
beats_per_bar: f32,
|
|
|
|
|
subdivisions: f32,
|
|
|
|
|
beat_rate: music::BeatRate,
|
|
|
|
|
time_signatures: music::TimeSignature,
|
|
|
|
|
data: GdlData,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct GdlData {
|
|
|
|
|
green_lines: music::Lines,
|
|
|
|
|
orange_lines: music::Lines,
|
|
|
|
|
yellow_lines: music::Lines,
|
|
|
|
|
beat_rate: music::BeatRate,
|
|
|
|
|
time_signatures: music::TimeSignature,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct Song {
|
|
|
|
|
name: String,
|
|
|
|
|
id: u32,
|
|
|
|
|
stream: MediaSourceStream,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct Orange;
|
|
|
|
@ -45,12 +56,31 @@ struct Green;
|
|
|
|
|
struct BeatRateWidget<'a>(&'a mut Editor);
|
|
|
|
|
struct TimeSignatureWidget<'a>(&'a mut Editor);
|
|
|
|
|
struct LinesWidget<'a, C: WithColor>(&'a mut Editor, PhantomData<C>);
|
|
|
|
|
struct WaveformWidget<'a>(&'a mut Editor);
|
|
|
|
|
|
|
|
|
|
trait WithColor {}
|
|
|
|
|
trait WithColor {
|
|
|
|
|
const COLOR: Color;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl WithColor for Orange {}
|
|
|
|
|
impl WithColor for Yellow {}
|
|
|
|
|
impl WithColor for Green {}
|
|
|
|
|
impl From<Color> for eframe::epaint::Color32 {
|
|
|
|
|
fn from(rhs: Color) -> Self {
|
|
|
|
|
match rhs {
|
|
|
|
|
Color::Green => Self::from_rgb(0, 255, 0),
|
|
|
|
|
Color::Orange => Self::from_rgb(255, 127, 0),
|
|
|
|
|
Color::Yellow => Self::from_rgb(255, 255, 0),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl WithColor for Orange {
|
|
|
|
|
const COLOR: Color = Color::Orange;
|
|
|
|
|
}
|
|
|
|
|
impl WithColor for Yellow {
|
|
|
|
|
const COLOR: Color = Color::Yellow;
|
|
|
|
|
}
|
|
|
|
|
impl WithColor for Green {
|
|
|
|
|
const COLOR: Color = Color::Green;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Editor {
|
|
|
|
|
pub fn beat_rate_widget(&mut self) -> BeatRateWidget {
|
|
|
|
@ -64,6 +94,10 @@ impl Editor {
|
|
|
|
|
pub fn lines_widget<C: WithColor>(&mut self) -> LinesWidget<C> {
|
|
|
|
|
LinesWidget(self, Default::default())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn waveform_widget(&mut self) -> WaveformWidget {
|
|
|
|
|
WaveformWidget(self)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'a> egui::Widget for BeatRateWidget<'a> {
|
|
|
|
@ -100,22 +134,58 @@ impl<'a> egui::Widget for TimeSignatureWidget<'a> {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'a, C: WithColor> egui::Widget for LinesWidget<'a, C> {
|
|
|
|
|
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<'a> egui::Widget for WaveformWidget<'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,
|
|
|
|
|
msg_queue: VecDeque::new(),
|
|
|
|
|
level_list: gd::OuterLevel::load_all(),
|
|
|
|
|
loaded_song: None,
|
|
|
|
|
editor: Editor {
|
|
|
|
|
scroll_pos: 0f32,
|
|
|
|
|
pts_per_second: 5f32,
|
|
|
|
|
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(),
|
|
|
|
|
data: GdlData {
|
|
|
|
|
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(),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@ -124,20 +194,25 @@ impl PipeDash {
|
|
|
|
|
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()
|
|
|
|
|
{
|
|
|
|
|
self.msg_queue.push_back(Message::LevelSelected(idx));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
ui.with_layout(egui::Layout::top_down(egui::Align::LEFT).with_main_justify(false), |ui| {
|
|
|
|
|
ui.with_layout(egui::Layout::top_down(egui::Align::LEFT).with_main_justify(true), |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()
|
|
|
|
|
{
|
|
|
|
|
self.msg_queue.push_back(Message::LevelSelected(idx));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
ui.button("Load Level");
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
@ -145,11 +220,27 @@ impl PipeDash {
|
|
|
|
|
fn center_panel(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
|
|
|
|
egui::CentralPanel::default().show(ctx, |ui| {
|
|
|
|
|
ui.vertical_centered_justified(|ui| {
|
|
|
|
|
ui.vertical(|ui| {
|
|
|
|
|
ui.label("Song name");
|
|
|
|
|
ui.label("Song id");
|
|
|
|
|
});
|
|
|
|
|
ui.label(
|
|
|
|
|
egui::RichText::new(match &self.loaded_song {
|
|
|
|
|
Some(song) => &song.name,
|
|
|
|
|
None => "No song loaded...",
|
|
|
|
|
})
|
|
|
|
|
.size(32.0),
|
|
|
|
|
);
|
|
|
|
|
ui.label(
|
|
|
|
|
egui::RichText::new(match &self.loaded_song {
|
|
|
|
|
Some(song) => song.id.to_string(),
|
|
|
|
|
None => "No song loaded...".into(),
|
|
|
|
|
})
|
|
|
|
|
.size(20.0),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
ui.add(self.editor.time_signature_widget());
|
|
|
|
|
ui.add(self.editor.beat_rate_widget());
|
|
|
|
|
ui.add(self.editor.lines_widget::<Green>());
|
|
|
|
|
ui.add(self.editor.lines_widget::<Yellow>());
|
|
|
|
|
ui.add(self.editor.lines_widget::<Orange>());
|
|
|
|
|
ui.add(self.editor.waveform_widget());
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|