wire up App entry point state machine
This commit is contained in:
parent
a6d1018099
commit
afbfc59603
|
|
@ -64,13 +64,18 @@ pub enum Message {
|
||||||
|
|
||||||
pub struct App {
|
pub struct App {
|
||||||
pub state: AppState,
|
pub state: AppState,
|
||||||
|
pub global_config: crate::config::AudioOxideConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for App {
|
impl Default for App {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
let config = crate::first_run::load_or_initialize_config();
|
||||||
state: AppState::ProjectView(ProjectViewState::Splash),
|
let state = if config.first_run {
|
||||||
}
|
AppState::FirstRun { project_dir: config.project_dir.clone() }
|
||||||
|
} else {
|
||||||
|
AppState::ProjectView(ProjectViewState::Splash)
|
||||||
|
};
|
||||||
|
Self { state, global_config: config }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -79,12 +84,267 @@ pub fn main() -> iced::Result {
|
||||||
.run()
|
.run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn scan_recent_projects(project_dir: &PathBuf) -> Vec<ProjectInfo> {
|
||||||
|
let mut projects = Vec::new();
|
||||||
|
let entries = match std::fs::read_dir(project_dir) {
|
||||||
|
Ok(e) => e,
|
||||||
|
Err(_) => return projects,
|
||||||
|
};
|
||||||
|
for entry in entries.flatten() {
|
||||||
|
let path = entry.path();
|
||||||
|
if !path.is_dir() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let config_path = path.join("project.toml");
|
||||||
|
if !config_path.exists() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let name = path.file_name()
|
||||||
|
.and_then(|n| n.to_str())
|
||||||
|
.unwrap_or("Untitled")
|
||||||
|
.to_string();
|
||||||
|
let modified = std::fs::metadata(&config_path)
|
||||||
|
.and_then(|m| m.modified())
|
||||||
|
.ok()
|
||||||
|
.and_then(|t| {
|
||||||
|
let duration = t.duration_since(std::time::UNIX_EPOCH).ok()?;
|
||||||
|
chrono::DateTime::from_timestamp(duration.as_secs() as i64, 0)
|
||||||
|
.map(|dt| dt.naive_local())
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
projects.push(ProjectInfo { name, path, modified });
|
||||||
|
}
|
||||||
|
projects.sort_by(|a, b| b.modified.cmp(&a.modified));
|
||||||
|
projects
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_time_signature(s: &str) -> Option<(u8, u8)> {
|
||||||
|
let parts: Vec<&str> = s.split('/').collect();
|
||||||
|
if parts.len() == 2 {
|
||||||
|
let num = parts[0].trim().parse::<u8>().ok()?;
|
||||||
|
let den = parts[1].trim().parse::<u8>().ok()?;
|
||||||
|
Some((num, den))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_tempo_to_state(state: &mut AppState, bpm: f32) {
|
||||||
|
if let AppState::NewProject(np) = state {
|
||||||
|
np.config.tempo = bpm;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_time_signature_to_state(state: &mut AppState, num: u8, den: u8) {
|
||||||
|
if let AppState::NewProject(np) = state {
|
||||||
|
np.config.time_signature_numerator = num;
|
||||||
|
np.config.time_signature_denominator = den;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
fn update(&mut self, _message: Message) -> Task<Message> {
|
fn update(&mut self, message: Message) -> Task<Message> {
|
||||||
|
match message {
|
||||||
|
Message::ViewRecentProjects => {
|
||||||
|
let projects = scan_recent_projects(&self.global_config.project_dir);
|
||||||
|
self.state = AppState::ProjectView(ProjectViewState::Recent { projects });
|
||||||
|
}
|
||||||
|
Message::ViewFindProject => {
|
||||||
|
self.state = AppState::ProjectView(ProjectViewState::Find {
|
||||||
|
path_input: String::new(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Message::ViewNewProject => {
|
||||||
|
self.state = AppState::NewProject(crate::gui::new_project::State::default());
|
||||||
|
}
|
||||||
|
Message::ViewTimeUtility => {
|
||||||
|
let return_state = std::mem::replace(
|
||||||
|
&mut self.state,
|
||||||
|
AppState::ProjectView(ProjectViewState::Splash),
|
||||||
|
);
|
||||||
|
self.state = AppState::TimeUtility {
|
||||||
|
tapper_state: time_utility::State::default(),
|
||||||
|
return_state: Box::new(return_state),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Message::FindPathChanged(s) => {
|
||||||
|
if let AppState::ProjectView(ProjectViewState::Find { ref mut path_input }) = self.state {
|
||||||
|
*path_input = s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Message::OpenProject(path) => {
|
||||||
|
let (editor, task) = crate::editor::Editor::new(path);
|
||||||
|
self.state = AppState::Editor(editor);
|
||||||
|
return task.map(Message::EditorMessage);
|
||||||
|
}
|
||||||
|
Message::CreateProject => {
|
||||||
|
if let AppState::NewProject(ref np_state) = self.state {
|
||||||
|
let config = &np_state.config;
|
||||||
|
let project_dir = self.global_config.project_dir.join(&config.name);
|
||||||
|
let _ = std::fs::create_dir_all(&project_dir);
|
||||||
|
let config_path = project_dir.join("project.toml");
|
||||||
|
if let Ok(toml_str) = toml::to_string_pretty(config) {
|
||||||
|
let _ = std::fs::write(&config_path, toml_str);
|
||||||
|
}
|
||||||
|
let (editor, task) = crate::editor::Editor::new(project_dir);
|
||||||
|
self.state = AppState::Editor(editor);
|
||||||
|
return task.map(Message::EditorMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Message::ProjectNameChanged(s) => {
|
||||||
|
if let AppState::NewProject(ref mut np) = self.state {
|
||||||
|
np.config.name = s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message::SampleRateSelected(sr) => {
|
||||||
|
if let AppState::NewProject(ref mut np) = self.state {
|
||||||
|
np.config.sample_rate = sr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message::OutputBufferSizeSelected(bs) => {
|
||||||
|
if let AppState::NewProject(ref mut np) = self.state {
|
||||||
|
np.config.output_buffer_size = bs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message::InputBufferSizeSelected(bs) => {
|
||||||
|
if let AppState::NewProject(ref mut np) = self.state {
|
||||||
|
np.config.input_buffer_size = bs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message::AudioDeviceSelected(d) => {
|
||||||
|
if let AppState::NewProject(ref mut np) = self.state {
|
||||||
|
np.config.audio_device = d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message::InputDeviceSelected(d) => {
|
||||||
|
if let AppState::NewProject(ref mut np) = self.state {
|
||||||
|
np.config.audio_input_device = d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message::TempoChanged(t) => {
|
||||||
|
if let AppState::NewProject(ref mut np) = self.state {
|
||||||
|
np.config.tempo = t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message::TimeSignatureNumeratorChanged(s) => {
|
||||||
|
if let AppState::NewProject(ref mut np) = self.state {
|
||||||
|
if let Ok(v) = s.parse::<u8>() {
|
||||||
|
np.config.time_signature_numerator = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message::TimeSignatureDenominatorChanged(s) => {
|
||||||
|
if let AppState::NewProject(ref mut np) = self.state {
|
||||||
|
if let Ok(v) = s.parse::<u8>() {
|
||||||
|
np.config.time_signature_denominator = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Message::FirstRunProjectDirChanged(s) => {
|
||||||
|
if let AppState::FirstRun { ref mut project_dir } = self.state {
|
||||||
|
*project_dir = PathBuf::from(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message::FirstRunComplete => {
|
||||||
|
if let AppState::FirstRun { ref project_dir } = self.state {
|
||||||
|
self.global_config.first_run = false;
|
||||||
|
self.global_config.project_dir = project_dir.clone();
|
||||||
|
let _ = std::fs::create_dir_all(&self.global_config.project_dir);
|
||||||
|
crate::first_run::save_config(&self.global_config);
|
||||||
|
}
|
||||||
|
self.state = AppState::ProjectView(ProjectViewState::Splash);
|
||||||
|
}
|
||||||
|
|
||||||
|
Message::TimeUtilityTapPressed => {
|
||||||
|
if let AppState::TimeUtility { ref mut tapper_state, .. } = self.state {
|
||||||
|
time_utility::handle_tap_pressed(tapper_state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message::TimeUtilityTapReleased => {
|
||||||
|
if let AppState::TimeUtility { ref mut tapper_state, .. } = self.state {
|
||||||
|
return time_utility::handle_tap_released(tapper_state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message::RunTimeUtilityAnalysis => {
|
||||||
|
if let AppState::TimeUtility { ref mut tapper_state, .. } = self.state {
|
||||||
|
tapper_state.result = time_utility::run_analysis(&tapper_state.tap_events);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message::TimeUtilitySet(bpm) => {
|
||||||
|
if let Some(mut rs) = self.take_time_utility_return() {
|
||||||
|
apply_tempo_to_state(&mut rs, bpm as f32);
|
||||||
|
self.state = rs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message::TimeUtilitySetTimeSignature(sig) => {
|
||||||
|
if let Some(mut rs) = self.take_time_utility_return() {
|
||||||
|
if let Some((num, den)) = parse_time_signature(&sig) {
|
||||||
|
apply_time_signature_to_state(&mut rs, num, den);
|
||||||
|
}
|
||||||
|
self.state = rs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message::TimeUtilitySetBoth(bpm, sig) => {
|
||||||
|
if let Some(mut rs) = self.take_time_utility_return() {
|
||||||
|
apply_tempo_to_state(&mut rs, bpm as f32);
|
||||||
|
if let Some((num, den)) = parse_time_signature(&sig) {
|
||||||
|
apply_time_signature_to_state(&mut rs, num, den);
|
||||||
|
}
|
||||||
|
self.state = rs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message::TimeUtilityCancel => {
|
||||||
|
if let Some(rs) = self.take_time_utility_return() {
|
||||||
|
self.state = rs;
|
||||||
|
} else {
|
||||||
|
self.state = AppState::ProjectView(ProjectViewState::Splash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Message::EditorMessage(editor_msg) => {
|
||||||
|
if let AppState::Editor(ref mut editor) = self.state {
|
||||||
|
return editor.update(editor_msg).map(Message::EditorMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view(&self) -> Element<'_, Message> {
|
fn view(&self) -> Element<'_, Message> {
|
||||||
crate::gui::splash::view()
|
match &self.state {
|
||||||
|
AppState::ProjectView(ProjectViewState::Splash) => {
|
||||||
|
crate::gui::splash::view()
|
||||||
|
}
|
||||||
|
AppState::ProjectView(pv_state) => {
|
||||||
|
crate::gui::project_viewer::view(pv_state)
|
||||||
|
}
|
||||||
|
AppState::FirstRun { project_dir } => {
|
||||||
|
crate::gui::first_run_wizard::view(project_dir)
|
||||||
|
}
|
||||||
|
AppState::NewProject(np_state) => {
|
||||||
|
crate::gui::new_project::view(np_state)
|
||||||
|
}
|
||||||
|
AppState::TimeUtility { tapper_state, .. } => {
|
||||||
|
time_utility::view(tapper_state)
|
||||||
|
}
|
||||||
|
AppState::Editor(editor) => {
|
||||||
|
editor.view().map(Message::EditorMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn take_time_utility_return(&mut self) -> Option<AppState> {
|
||||||
|
let placeholder = AppState::ProjectView(ProjectViewState::Splash);
|
||||||
|
let old = std::mem::replace(&mut self.state, placeholder);
|
||||||
|
if let AppState::TimeUtility { return_state, .. } = old {
|
||||||
|
Some(*return_state)
|
||||||
|
} else {
|
||||||
|
self.state = old;
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue