merge integration

This commit is contained in:
jess 2026-03-31 15:57:33 -07:00
commit e2dadf1044
1 changed files with 265 additions and 5 deletions

View File

@ -64,13 +64,18 @@ pub enum Message {
pub struct App {
pub state: AppState,
pub global_config: crate::config::AudioOxideConfig,
}
impl Default for App {
fn default() -> Self {
Self {
state: AppState::ProjectView(ProjectViewState::Splash),
}
let config = crate::first_run::load_or_initialize_config();
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()
}
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 {
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()
}
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
}
}
}