diff --git a/Cargo.toml b/Cargo.toml index 7cb086a..e407675 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ desktop = [ "eframe", "egui_ratatui", "soft_ratatui", + "image", ] [dependencies] @@ -56,6 +57,7 @@ egui = { version = "0.33", optional = true } eframe = { version = "0.33", optional = true } egui_ratatui = { version = "2.1", optional = true } soft_ratatui = { version = "0.1.3", features = ["unicodefonts"], optional = true } +image = { version = "0.25", default-features = false, features = ["png"], optional = true } [profile.release] opt-level = 3 diff --git a/README.md b/README.md index a7d9485..a3e4c1d 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,7 @@ +

+ Cagire +

+ # Cagire A Forth Music Sequencer. diff --git a/cagire_pixel.png b/cagire_pixel.png new file mode 100644 index 0000000..f7a4b23 Binary files /dev/null and b/cagire_pixel.png differ diff --git a/src/bin/desktop.rs b/src/bin/desktop.rs index dd1a32f..298985c 100644 --- a/src/bin/desktop.rs +++ b/src/bin/desktop.rs @@ -145,7 +145,6 @@ struct CagireDesktop { _stream: Option, _analysis_handle: Option, current_font: FontChoice, - pending_font: Option, } impl CagireDesktop { @@ -270,7 +269,6 @@ impl CagireDesktop { _stream: stream, _analysis_handle: analysis_handle, current_font, - pending_font: None, } } @@ -302,7 +300,7 @@ impl CagireDesktop { } self.app.audio.config.sample_count = restart_samples.len(); - self.audio_sample_pos.store(0, Ordering::Relaxed); + self.audio_sample_pos.store(0, Ordering::Release); match build_stream( &new_config, @@ -372,14 +370,6 @@ impl CagireDesktop { impl eframe::App for CagireDesktop { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { - if let Some(font) = self.pending_font.take() { - self.terminal = create_terminal(font); - self.current_font = font; - let mut settings = Settings::load(); - settings.display.font = font.to_setting().to_string(); - settings.save(); - } - self.handle_audio_restart(); self.update_metrics(); @@ -412,7 +402,7 @@ impl eframe::App for CagireDesktop { } let current_font = self.current_font; - let mut pending_font = None; + let mut new_font = None; egui::CentralPanel::default() .frame(egui::Frame::NONE.fill(egui::Color32::BLACK)) @@ -429,7 +419,6 @@ impl eframe::App for CagireDesktop { ui.add(self.terminal.backend_mut()); - // Create a click-sensing overlay for context menu let response = ui.interact( ui.max_rect(), egui::Id::new("terminal_context"), @@ -440,7 +429,7 @@ impl eframe::App for CagireDesktop { for choice in FontChoice::ALL { let selected = current_font == choice; if ui.selectable_label(selected, choice.label()).clicked() { - pending_font = Some(choice); + new_font = Some(choice); ui.close(); } } @@ -448,8 +437,12 @@ impl eframe::App for CagireDesktop { }); }); - if pending_font.is_some() { - self.pending_font = pending_font; + if let Some(font) = new_font { + self.terminal = create_terminal(font); + self.current_font = font; + let mut settings = Settings::load(); + settings.display.font = font.to_setting().to_string(); + settings.save(); } ctx.request_repaint_after(Duration::from_millis( @@ -465,50 +458,19 @@ impl eframe::App for CagireDesktop { } fn load_icon() -> egui::IconData { - let size = 64u32; - let mut rgba = vec![0u8; (size * size * 4) as usize]; + const ICON_BYTES: &[u8] = include_bytes!("../../cagire_pixel.png"); - for y in 0..size { - for x in 0..size { - let idx = ((y * size + x) * 4) as usize; - let cx = x as f32 - size as f32 / 2.0; - let cy = y as f32 - size as f32 / 2.0; - let dist = (cx * cx + cy * cy).sqrt(); - let radius = size as f32 / 2.0 - 2.0; + let img = image::load_from_memory(ICON_BYTES) + .expect("Failed to load embedded icon") + .resize(64, 64, image::imageops::FilterType::Lanczos3) + .into_rgba8(); - if dist < radius { - let angle = cy.atan2(cx); - let normalized = (angle + std::f32::consts::PI) / (2.0 * std::f32::consts::PI); - - if normalized > 0.1 && normalized < 0.9 { - let inner_radius = radius * 0.5; - if dist > inner_radius { - rgba[idx] = 80; - rgba[idx + 1] = 160; - rgba[idx + 2] = 200; - rgba[idx + 3] = 255; - } else { - rgba[idx] = 30; - rgba[idx + 1] = 60; - rgba[idx + 2] = 80; - rgba[idx + 3] = 255; - } - } else { - rgba[idx] = 30; - rgba[idx + 1] = 30; - rgba[idx + 2] = 40; - rgba[idx + 3] = 255; - } - } else { - rgba[idx + 3] = 0; - } - } - } + let (width, height) = img.dimensions(); egui::IconData { - rgba, - width: size, - height: size, + rgba: img.into_raw(), + width, + height, } } diff --git a/src/engine/sequencer.rs b/src/engine/sequencer.rs index e3eb134..35be218 100644 --- a/src/engine/sequencer.rs +++ b/src/engine/sequencer.rs @@ -165,7 +165,9 @@ impl SequencerHandle { pub fn shutdown(self) { let _ = self.cmd_tx.send(SeqCommand::Shutdown); - let _ = self.thread.join(); + if let Err(e) = self.thread.join() { + eprintln!("Sequencer thread panicked: {e:?}"); + } } } @@ -873,7 +875,7 @@ fn sequencer_loop( let tempo = state.tempo(); let sr = sample_rate.load(Ordering::Relaxed) as f64; - let audio_samples = audio_sample_pos.load(Ordering::Relaxed); + let audio_samples = audio_sample_pos.load(Ordering::Acquire); let engine_time = if sr > 0.0 { audio_samples as f64 / sr } else { diff --git a/src/settings.rs b/src/settings.rs index 425bce4..f8daa38 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -101,7 +101,9 @@ impl Settings { } pub fn save(&self) { - let _ = confy::store(APP_NAME, None, self); + if let Err(e) = confy::store(APP_NAME, None, self) { + eprintln!("Failed to save settings: {e}"); + } } }