Add janky spinner

This commit is contained in:
2025-06-13 23:06:45 +02:00
parent 83ad2068e0
commit 2a830f0539

View File

@ -2,11 +2,14 @@ use std::{
fs,
path::PathBuf,
sync::{Arc, mpsc},
thread::JoinHandle,
time::{Duration, Instant},
};
use crate::{file_editor::FileEditor, preferences::Preferences, util::GuiSender};
use egui::{
Align, Button, Color32, FontData, FontDefinitions, PointerButton, RichText, ScrollArea, Stroke,
Ui,
};
#[derive(serde::Deserialize, serde::Serialize)]
@ -18,6 +21,8 @@ pub struct App {
actions_tx: mpsc::Sender<Action>,
#[serde(skip)]
actions_rx: mpsc::Receiver<Action>,
#[serde(skip)]
jobs: Jobs,
tabs: Vec<(TabId, Tab)>,
open_tab_index: Option<usize>,
@ -25,6 +30,33 @@ pub struct App {
next_tab_id: TabId,
}
pub struct Jobs {
handles: Vec<JoinHandle<()>>,
actions_tx: mpsc::Sender<Action>,
}
impl Jobs {
fn start(&mut self, ui: &mut Ui, job: impl FnOnce() -> Option<Action> + Send + 'static) {
let ctx = ui.ctx().clone();
let actions_tx = self.actions_tx.clone();
self.handles.push(std::thread::spawn(move || {
// start rendering the spinner thingy
ctx.request_repaint();
let start = Instant::now();
if let Some(action) = job() {
let _ = actions_tx.send(action);
ctx.request_repaint();
};
// Make sure that task takes at least 250ms to run, so that the spinner won't blink
let sleep_for = Duration::from_millis(250).saturating_sub(start.elapsed());
std::thread::sleep(sleep_for);
}));
}
}
#[derive(serde::Deserialize, serde::Serialize)]
enum Tab {
File(FileEditor),
@ -55,8 +87,12 @@ impl Default for App {
let (actions_tx, actions_rx) = mpsc::channel();
Self {
preferences: Preferences::default(),
actions_tx,
actions_tx: actions_tx.clone(/* this is silly, i know */),
actions_rx,
jobs: Jobs {
handles: Default::default(),
actions_tx,
},
tabs: vec![(1, Tab::File(FileEditor::new("note.md")))],
open_tab_index: None,
next_tab_id: 2,
@ -178,6 +214,8 @@ impl eframe::App for App {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
self.preferences.apply(ctx);
self.jobs.handles.retain(|job| !job.is_finished());
while let Ok(action) = self.actions_rx.try_recv() {
self.handle_action(action);
}
@ -205,22 +243,15 @@ impl eframe::App for App {
#[cfg(not(target_arch = "wasm32"))]
if ui.button("Open File").clicked() {
let actions_tx = self.actions_tx(ui.ctx());
std::thread::spawn(move || {
let file = rfd::FileDialog::new().pick_file();
self.jobs.start(ui, move || {
let file_path = rfd::FileDialog::new().pick_file()?;
let Some(file_path) = file else { return };
let text = match fs::read_to_string(&file_path) {
Ok(text) => text,
Err(e) => {
log::error!("Failed to read {file_path:?}: {e}");
return;
}
};
let text = fs::read_to_string(&file_path)
.inspect_err(|e| log::error!("Failed to read {file_path:?}: {e}"))
.ok()?;
let editor = FileEditor::from_file(file_path, &text);
let _ = actions_tx.send(Action::OpenFile(editor));
Some(Action::OpenFile(editor))
});
}
@ -255,10 +286,11 @@ impl eframe::App for App {
if let Some((file_path, file_editor)) = open_file_with_path {
let text = file_editor.to_string();
let file_path = file_path.to_owned();
std::thread::spawn(move || {
self.jobs.start(ui, move || {
if let Err(e) = fs::write(file_path, text.as_bytes()) {
log::error!("{e}");
};
None
});
}
}
@ -268,21 +300,17 @@ impl eframe::App for App {
.add_enabled(open_file.is_some(), Button::new("Save As"))
.clicked()
{
let actions_tx = self.actions_tx(ui.ctx());
let (tab_id, editor) =
open_file.expect("We checked that open_file is_some");
let text = editor.to_string();
std::thread::spawn(move || {
let Some(file_path) = rfd::FileDialog::new().save_file() else {
return;
};
self.jobs.start(ui, move || {
let file_path = rfd::FileDialog::new().save_file()?;
if let Err(e) = fs::write(&file_path, text.as_bytes()) {
log::error!("{e}");
return;
};
fs::write(&file_path, text.as_bytes())
.inspect_err(|e| log::error!("{e}"))
.ok()?;
let _ = actions_tx.send(Action::MoveFile(tab_id, file_path));
Some(Action::MoveFile(tab_id, file_path))
});
}
@ -297,6 +325,10 @@ impl eframe::App for App {
}
});
if !self.jobs.handles.is_empty() {
ui.spinner();
}
ui.add_space(16.0);
ui.add_space(16.0);