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
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}");
+ }
}
}