added basic animation

This commit is contained in:
2025-05-31 15:36:20 -05:00
parent 61157a2402
commit eced2a561c
4 changed files with 47 additions and 32 deletions
+1
View File
@@ -27,6 +27,7 @@ rand = "0.9.1"
tokio = { version = "1.45.1", features = ["macros", "parking_lot", "rt-multi-thread", "time"] } tokio = { version = "1.45.1", features = ["macros", "parking_lot", "rt-multi-thread", "time"] }
zip = { version = "4.0.0", features = ["zstd"] } zip = { version = "4.0.0", features = ["zstd"] }
anyhow = "1.0.98" anyhow = "1.0.98"
futures = "0.3.31"
[features] [features]
# this feature is used for production builds or when `devPath` points to the filesystem # this feature is used for production builds or when `devPath` points to the filesystem
+35 -28
View File
@@ -1,11 +1,9 @@
use anyhow::Result; use anyhow::Result;
use futures::FutureExt;
use image::{ImageFormat, RgbaImage}; use image::{ImageFormat, RgbaImage};
use std::{env, fs, future::pending, sync::Arc, time::Duration}; use std::{env, fs, sync::Arc, time::Duration};
use tokio::time::interval; use tokio::time::interval;
use tokio::{ use tokio::{select, time::Interval};
select,
time::{Instant, Interval},
};
use zip::ZipArchive; use zip::ZipArchive;
use crate::{ use crate::{
@@ -16,27 +14,20 @@ use crate::{
#[derive(Debug, Default)] #[derive(Debug, Default)]
struct AnimationState { struct AnimationState {
cursor_index: Option<usize>, cursor_index: Option<usize>,
loaded_image: Option<RgbaImage>, frame_timer: Option<Interval>,
loaded_particles: Option<Vec<RgbaImage>>, frame_index: usize,
} loaded_image: Vec<RgbaImage>,
loaded_particles: Vec<Vec<RgbaImage>>,
async fn pend_optional_interval(t: &mut Option<Interval>) -> Instant {
if let Some(t) = t {
t.tick().await
} else {
pending::<Instant>().await
}
} }
#[tokio::main] #[tokio::main]
pub async fn animate_loop(cursors: Arc<Vec<Cursor>>) -> Result<()> { pub async fn animate_loop(cursors: Arc<Vec<Cursor>>) -> Result<()> {
let mut cmds = CURSOR_CMD.subscribe(); let mut cmds = CURSOR_CMD.subscribe();
let mut selected: Option<usize> = None; let mut selected: Option<usize> = None;
let start = Instant::now();
let mut animation_state = AnimationState::default(); let mut animation_state = AnimationState::default();
let mut animation_interval: Option<Interval> = None; let mut animation_interval = interval(Duration::from_secs_f32(1.0 / 30.0));
loop { loop {
select! { select! {
@@ -48,11 +39,6 @@ pub async fn animate_loop(cursors: Arc<Vec<Cursor>>) -> Result<()> {
if i < cursors.len() { if i < cursors.len() {
selected = Some(i); selected = Some(i);
} }
if cursors[i].animation_speed > 0.0 {
animation_interval = Some(interval(Duration::from_secs_f32(1.0/cursors[i].animation_speed)));
}else{
animation_interval = None;
}
} }
crate::CursorCmd::Next => { crate::CursorCmd::Next => {
selected = Some(selected.map(|x| (x + 1) % cursors.len()).unwrap_or(0)) selected = Some(selected.map(|x| (x + 1) % cursors.len()).unwrap_or(0))
@@ -67,9 +53,7 @@ pub async fn animate_loop(cursors: Arc<Vec<Cursor>>) -> Result<()> {
CURSOR_CHANGED.send(selected.unwrap_or(0)).ok(); CURSOR_CHANGED.send(selected.unwrap_or(0)).ok();
} }
_ = pend_optional_interval(&mut animation_interval) => { _ = animation_interval.tick() => {}
println!("interval {:?}", start.elapsed());
}
} }
update_cursor(&mut animation_state, selected, &cursors).await?; update_cursor(&mut animation_state, selected, &cursors).await?;
@@ -94,6 +78,10 @@ fn load(path: &str) -> Option<RgbaImage> {
) )
} }
fn load_frames(frames: &Vec<String>) -> Vec<RgbaImage> {
frames.iter().map(|x| load(x)).flatten().collect()
}
async fn update_cursor( async fn update_cursor(
animation_state: &mut AnimationState, animation_state: &mut AnimationState,
selected: Option<usize>, selected: Option<usize>,
@@ -104,19 +92,38 @@ async fn update_cursor(
animation_state.cursor_index = selected; animation_state.cursor_index = selected;
if let Some(selected) = selected { if let Some(selected) = selected {
animation_state.loaded_image = load(&cursors[selected].path); if cursors[selected].animation_speed > 0.0 {
animation_state.frame_timer = Some(interval(Duration::from_secs_f32(
1.0 / cursors[selected].animation_speed,
)))
}
animation_state.loaded_image = load_frames(&cursors[selected].frames);
animation_state.loaded_particles = cursors[selected] animation_state.loaded_particles = cursors[selected]
.particles .particles
.iter() .iter()
.map(|x| load(&x.path)) .map(|x| load_frames(&x.frames))
.collect(); .collect();
if let Some(i) = &animation_state.loaded_image { if let Some(i) = animation_state.loaded_image.get(0) {
set_system_cursor(i)?; set_system_cursor(i)?;
} }
} else { } else {
restore_default_cursor()?; restore_default_cursor()?;
} }
} else {
if let Some(frame_timer) = &mut animation_state.frame_timer {
if frame_timer.tick().now_or_never().is_some() && animation_state.loaded_image.len() > 0
{
animation_state.frame_index += 1;
animation_state.frame_index %= animation_state.loaded_image.len();
if let Some(i) = animation_state
.loaded_image
.get(animation_state.frame_index)
{
set_system_cursor(i)?;
}
}
}
} }
Ok(()) Ok(())
+9 -2
View File
@@ -1,14 +1,21 @@
[ [
{ {
"name": "white", "name": "white",
"path": "icon.png",
"frames": ["icon.png"],
"animation_speed": 0.0, "animation_speed": 0.0,
"particles": [] "particles": []
}, },
{ {
"name": "red", "name": "red",
"path": "red.png", "frames": ["red.png"],
"animation_speed": 0.0, "animation_speed": 0.0,
"particles": [] "particles": []
},
{
"name": "flashing",
"frames": ["red.png", "icon.png"],
"animation_speed": 30.0,
"particles": []
} }
] ]
+2 -2
View File
@@ -47,7 +47,7 @@ impl RandomRange {
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ParticleConfig { pub struct ParticleConfig {
path: String, frames: Vec<String>,
animation_speed: f32, animation_speed: f32,
gravity_x: RandomRange, gravity_x: RandomRange,
gravity_y: RandomRange, gravity_y: RandomRange,
@@ -61,7 +61,7 @@ pub struct ParticleConfig {
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Cursor { pub struct Cursor {
name: String, name: String,
path: String, frames: Vec<String>,
animation_speed: f32, animation_speed: f32,
particles: Vec<ParticleConfig>, particles: Vec<ParticleConfig>,
} }