wip2
This commit is contained in:
@@ -0,0 +1,27 @@
|
|||||||
|
use eframe::egui::{Context, Window};
|
||||||
|
use crate::gui::Showable;
|
||||||
|
|
||||||
|
/// A struct to represent a simple messagebox, like for an error or something.
|
||||||
|
/// Contains a string (the message) and a bool which is cleared when the window is
|
||||||
|
/// closed.
|
||||||
|
pub struct Dialog(pub String, pub bool);
|
||||||
|
|
||||||
|
impl<T> Showable<T> for Dialog {
|
||||||
|
fn show(&mut self, ctx: &Context, _state: &T) {
|
||||||
|
let win = Window::new("Hey!").resizable(false).scroll([false, false]);
|
||||||
|
let mut open = true;
|
||||||
|
win.open(&mut open).show(ctx, |ui| {
|
||||||
|
ui.label(&self.0);
|
||||||
|
if ui.button("Okay").clicked() {
|
||||||
|
self.1 = false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if !open { self.1 = false }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: AsRef<str>> From<T> for Dialog {
|
||||||
|
fn from(value: T) -> Self {
|
||||||
|
Self(value.as_ref().to_string(), true)
|
||||||
|
}
|
||||||
|
}
|
||||||
+44
-41
@@ -1,7 +1,8 @@
|
|||||||
use std::ops::RangeInclusive;
|
use std::ops::RangeInclusive;
|
||||||
use eframe::egui;
|
use eframe::egui;
|
||||||
use eframe::egui::{Color32, Context, Id, PointerButton, Pos2, Rangef, RichText, Sense, Ui, Vec2, Widget};
|
use eframe::egui::{Color32, Context, PointerButton, Pos2, Rangef, Sense, Ui, Vec2};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use crate::gui::Showable;
|
||||||
use crate::noise::NoteType;
|
use crate::noise::NoteType;
|
||||||
use crate::scale::Scale;
|
use crate::scale::Scale;
|
||||||
use crate::tenori::LOOP_LENGTH;
|
use crate::tenori::LOOP_LENGTH;
|
||||||
@@ -40,46 +41,6 @@ impl Grid {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn show(&mut self, ctx: &Context, cursor: f32) {
|
|
||||||
let win = egui::Window::new(&self.id).resizable(false).scroll([false, false]);
|
|
||||||
win.show(ctx, |ui| {
|
|
||||||
egui::MenuBar::new().ui(ui, |ui| {
|
|
||||||
if ui.button("Clear").clicked() {
|
|
||||||
self.notes = vec![false; (LOOP_LENGTH * LOOP_LENGTH) as usize]
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.menu_button("Scale...", |ui| {
|
|
||||||
if ui.button(Scale::CMajor.label_text(self.scale)).clicked() {
|
|
||||||
self.scale = Scale::CMajor
|
|
||||||
}
|
|
||||||
if ui.button(Scale::CMinor.label_text(self.scale)).clicked() {
|
|
||||||
self.scale = Scale::CMinor
|
|
||||||
}
|
|
||||||
if ui.button(Scale::Chromatic.label_text(self.scale)).clicked() {
|
|
||||||
self.scale = Scale::Chromatic
|
|
||||||
}
|
|
||||||
if ui.button(Scale::Pentatonic.label_text(self.scale)).clicked() {
|
|
||||||
self.scale = Scale::Pentatonic
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
egui::MenuBar::new().ui(ui, |ui| {
|
|
||||||
ui.label("Volume");
|
|
||||||
ui.add(egui::Slider::new(&mut self.volume, RangeInclusive::new(0.0, 2.0)).show_value(false));
|
|
||||||
|
|
||||||
ui.label("Length");
|
|
||||||
ui.add(egui::Slider::new(&mut self.length, RangeInclusive::new(0, 2000)).show_value(false));
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
egui::Frame::new().inner_margin(3).show(ui, |ui| {
|
|
||||||
self.draw_grid(ui, cursor)
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw_grid(&mut self, ui: &mut Ui, cursor: f32) {
|
fn draw_grid(&mut self, ui: &mut Ui, cursor: f32) {
|
||||||
let dim = 20.0 * LOOP_LENGTH as f32;
|
let dim = 20.0 * LOOP_LENGTH as f32;
|
||||||
let (rect, response) = ui.allocate_exact_size(Vec2::new(dim, dim), Sense::click_and_drag());
|
let (rect, response) = ui.allocate_exact_size(Vec2::new(dim, dim), Sense::click_and_drag());
|
||||||
@@ -127,3 +88,45 @@ impl Grid {
|
|||||||
notes
|
notes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Showable<f32> for Grid {
|
||||||
|
fn show(&mut self, ctx: &Context, cursor: &f32) {
|
||||||
|
let win = egui::Window::new(&self.id).resizable(false).scroll([false, false]);
|
||||||
|
win.show(ctx, |ui| {
|
||||||
|
egui::MenuBar::new().ui(ui, |ui| {
|
||||||
|
if ui.button("Clear").clicked() {
|
||||||
|
self.notes = vec![false; (LOOP_LENGTH * LOOP_LENGTH) as usize]
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.menu_button("Scale...", |ui| {
|
||||||
|
if ui.button(Scale::CMajor.label_text(self.scale)).clicked() {
|
||||||
|
self.scale = Scale::CMajor
|
||||||
|
}
|
||||||
|
if ui.button(Scale::CMinor.label_text(self.scale)).clicked() {
|
||||||
|
self.scale = Scale::CMinor
|
||||||
|
}
|
||||||
|
if ui.button(Scale::Chromatic.label_text(self.scale)).clicked() {
|
||||||
|
self.scale = Scale::Chromatic
|
||||||
|
}
|
||||||
|
if ui.button(Scale::Pentatonic.label_text(self.scale)).clicked() {
|
||||||
|
self.scale = Scale::Pentatonic
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
egui::MenuBar::new().ui(ui, |ui| {
|
||||||
|
ui.label("Volume");
|
||||||
|
ui.add(egui::Slider::new(&mut self.volume, RangeInclusive::new(0.0, 2.0)).show_value(false));
|
||||||
|
|
||||||
|
ui.label("Length");
|
||||||
|
ui.add(egui::Slider::new(&mut self.length, RangeInclusive::new(0, 2000)).show_value(false));
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
egui::Frame::new().inner_margin(3).show(ui, |ui| {
|
||||||
|
self.draw_grid(ui, *cursor)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
+67
-15
@@ -1,33 +1,35 @@
|
|||||||
use std::fs;
|
use std::fs;
|
||||||
use std::ops::RangeInclusive;
|
use std::ops::RangeInclusive;
|
||||||
|
use std::path::Path;
|
||||||
use eframe::egui;
|
use eframe::egui;
|
||||||
use eframe::egui::{Context, TopBottomPanel};
|
use eframe::egui::{Context, TopBottomPanel};
|
||||||
use crate::noise::NoteType;
|
use crate::noise::NoteType;
|
||||||
use crate::saveload::PersistedTenori;
|
use crate::saveload::PersistedTenori;
|
||||||
use crate::Tenori;
|
use crate::Tenori;
|
||||||
|
|
||||||
|
/// A trait for things that can be shown in a gui, given a Context.
|
||||||
|
pub trait Showable<T> {
|
||||||
|
fn show(&mut self, ctx: &Context, state: &T);
|
||||||
|
}
|
||||||
|
|
||||||
impl Tenori {
|
impl Tenori {
|
||||||
pub fn menu(&mut self, ctx: &Context) {
|
fn menu(&mut self, ctx: &Context) {
|
||||||
TopBottomPanel::top("menu_panel").show(ctx, |ui| {
|
TopBottomPanel::top("menu_panel").show(ctx, |ui| {
|
||||||
egui::MenuBar::new().ui(ui, |ui| {
|
egui::MenuBar::new().ui(ui, |ui| {
|
||||||
ui.menu_button("File", |ui| {
|
ui.menu_button("File", |ui| {
|
||||||
if ui.button("Save").clicked() &&
|
if ui.button("Load").clicked() && let Err(s) = self.load_from_file() {
|
||||||
let Ok(serialized) = toml::to_string(&PersistedTenori::from(&*self)) &&
|
self.dialogs.push(s.into())
|
||||||
let Some(path) = rfd::FileDialog::new().add_filter("Tenori files", &["tenori"]).set_file_name("song.tenori").save_file() {
|
}
|
||||||
match fs::write(path, serialized) {
|
|
||||||
Ok(_) => {}
|
if ui.add_enabled(self.default_filename.is_some(), egui::Button::new("Save")).clicked() {
|
||||||
Err(e) => { dbg!(e); }
|
let path = self.default_filename.as_ref().unwrap_or_else(|| unreachable!());
|
||||||
|
if let Err(s) = self.save_to_file(path) {
|
||||||
|
self.dialogs.push(s.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ui.button("Load").clicked() &&
|
if ui.button("Save As...").clicked() && let Err(s) = self.save_as() {
|
||||||
let Some(path) = rfd::FileDialog::new().add_filter("Tenori files", &["tenori"]).pick_file() &&
|
self.dialogs.push(s.into())
|
||||||
let Ok(serialized) = fs::read_to_string(path) &&
|
|
||||||
let Ok(persisted) = toml::from_str::<PersistedTenori>(serialized.as_str()) {
|
|
||||||
self.grids = persisted.grids;
|
|
||||||
self.tempo = persisted.tempo;
|
|
||||||
self.playing = false;
|
|
||||||
self.timer = 0.0;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -60,4 +62,54 @@ impl Tenori {
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn display_dialogs(&mut self, ctx: &Context) {
|
||||||
|
for d in self.dialogs.iter_mut() {
|
||||||
|
d.show(ctx, &());
|
||||||
|
}
|
||||||
|
self.dialogs.retain(|d| d.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn save_as(&self) -> Result<(), String> {
|
||||||
|
if let Some(path) = rfd::FileDialog::new()
|
||||||
|
.add_filter("Tenori files", &["tenori"])
|
||||||
|
.set_file_name("song.tenori").save_file() {
|
||||||
|
return self.save_to_file(path)
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn save_to_file<P: AsRef<Path>>(&self, filename: P) -> Result<(), String> {
|
||||||
|
let serialized = toml::to_string(&PersistedTenori::from(&*self)).map_err(|e| e.to_string())?;
|
||||||
|
fs::write(filename, serialized).map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_from_file(&mut self) -> Result<(), String> {
|
||||||
|
if let Some(path) = rfd::FileDialog::new()
|
||||||
|
.add_filter("Tenori files", &["tenori"])
|
||||||
|
.pick_file() {
|
||||||
|
let serialized = fs::read_to_string(path).map_err(|e| e.to_string())?;
|
||||||
|
let persisted = toml::from_str::<PersistedTenori>(serialized.as_str()).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
self.grids = persisted.grids;
|
||||||
|
self.tempo = persisted.tempo;
|
||||||
|
self.playing = false;
|
||||||
|
self.timer = 0.0;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_grids(&mut self, ctx: &Context, cursor: &f32) {
|
||||||
|
for g in self.grids.iter_mut() {
|
||||||
|
g.show(ctx, cursor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Showable<f32> for Tenori {
|
||||||
|
fn show(&mut self, ctx: &Context, cursor: &f32) {
|
||||||
|
self.menu(ctx);
|
||||||
|
self.display_grids(ctx, cursor);
|
||||||
|
self.display_dialogs(ctx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+5
-6
@@ -4,10 +4,12 @@ mod grid;
|
|||||||
mod noise;
|
mod noise;
|
||||||
mod scale;
|
mod scale;
|
||||||
mod saveload;
|
mod saveload;
|
||||||
|
mod dialog;
|
||||||
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use eframe::{App, Frame};
|
use eframe::{App, Frame};
|
||||||
use eframe::egui::Context;
|
use eframe::egui::Context;
|
||||||
|
use crate::gui::Showable;
|
||||||
use crate::tenori::Tenori;
|
use crate::tenori::Tenori;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
@@ -19,15 +21,12 @@ async fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl App for Tenori {
|
impl App for Tenori {
|
||||||
fn update(&mut self, ctx: &Context, frame: &mut Frame) {
|
fn update(&mut self, ctx: &Context, _frame: &mut Frame) {
|
||||||
let play = self.tick();
|
let play = self.tick();
|
||||||
let cursor = self.ratio();
|
let cursor = self.ratio();
|
||||||
self.menu(ctx);
|
|
||||||
for g in self.grids.iter_mut() {
|
|
||||||
g.show(ctx, cursor)
|
|
||||||
}
|
|
||||||
|
|
||||||
for d in self.dialogs
|
self.show(ctx, &cursor);
|
||||||
|
|
||||||
if play {
|
if play {
|
||||||
for note in self.notes_for_beat() {
|
for note in self.notes_for_beat() {
|
||||||
self.play(note)
|
self.play(note)
|
||||||
|
|||||||
+8
-6
@@ -1,6 +1,7 @@
|
|||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
use rodio::OutputStream;
|
use rodio::OutputStream;
|
||||||
use crate::grid::Grid;
|
use crate::grid::Grid;
|
||||||
|
use crate::dialog::Dialog;
|
||||||
use crate::noise::{Note, NoteType};
|
use crate::noise::{Note, NoteType};
|
||||||
|
|
||||||
pub const LOOP_LENGTH: u32 = 16;
|
pub const LOOP_LENGTH: u32 = 16;
|
||||||
@@ -27,8 +28,12 @@ pub struct Tenori {
|
|||||||
// The audio output stream to which we will play notes
|
// The audio output stream to which we will play notes
|
||||||
output_stream: OutputStream,
|
output_stream: OutputStream,
|
||||||
|
|
||||||
// Error dialogs we're currently showing
|
/// Error dialogs we're currently showing
|
||||||
dialogs: Vec<String>
|
pub dialogs: Vec<Dialog>,
|
||||||
|
|
||||||
|
/// If present, we can save to this file without asking the
|
||||||
|
/// user to select a file first.
|
||||||
|
pub default_filename: Option<String>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Tenori {
|
impl Default for Tenori {
|
||||||
@@ -44,6 +49,7 @@ impl Default for Tenori {
|
|||||||
grids: vec![],
|
grids: vec![],
|
||||||
window_counter: 0,
|
window_counter: 0,
|
||||||
dialogs: vec![],
|
dialogs: vec![],
|
||||||
|
default_filename: None,
|
||||||
output_stream
|
output_stream
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -73,10 +79,6 @@ impl Tenori {
|
|||||||
self.beat() != old_beat
|
self.beat() != old_beat
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn display_dialogs(&mut self) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Which beat (0..loop_length) we're on
|
/// Which beat (0..loop_length) we're on
|
||||||
pub fn beat(&self) -> u32 {
|
pub fn beat(&self) -> u32 {
|
||||||
self.timer.floor() as u32
|
self.timer.floor() as u32
|
||||||
|
|||||||
Reference in New Issue
Block a user