added basic animation
This commit is contained in:
@@ -27,6 +27,7 @@ rand = "0.9.1"
|
||||
tokio = { version = "1.45.1", features = ["macros", "parking_lot", "rt-multi-thread", "time"] }
|
||||
zip = { version = "4.0.0", features = ["zstd"] }
|
||||
anyhow = "1.0.98"
|
||||
futures = "0.3.31"
|
||||
|
||||
[features]
|
||||
# this feature is used for production builds or when `devPath` points to the filesystem
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
use anyhow::Result;
|
||||
use futures::FutureExt;
|
||||
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::{
|
||||
select,
|
||||
time::{Instant, Interval},
|
||||
};
|
||||
use tokio::{select, time::Interval};
|
||||
use zip::ZipArchive;
|
||||
|
||||
use crate::{
|
||||
@@ -16,27 +14,20 @@ use crate::{
|
||||
#[derive(Debug, Default)]
|
||||
struct AnimationState {
|
||||
cursor_index: Option<usize>,
|
||||
loaded_image: Option<RgbaImage>,
|
||||
loaded_particles: Option<Vec<RgbaImage>>,
|
||||
}
|
||||
|
||||
async fn pend_optional_interval(t: &mut Option<Interval>) -> Instant {
|
||||
if let Some(t) = t {
|
||||
t.tick().await
|
||||
} else {
|
||||
pending::<Instant>().await
|
||||
}
|
||||
frame_timer: Option<Interval>,
|
||||
frame_index: usize,
|
||||
loaded_image: Vec<RgbaImage>,
|
||||
loaded_particles: Vec<Vec<RgbaImage>>,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
pub async fn animate_loop(cursors: Arc<Vec<Cursor>>) -> Result<()> {
|
||||
let mut cmds = CURSOR_CMD.subscribe();
|
||||
let mut selected: Option<usize> = None;
|
||||
let start = Instant::now();
|
||||
|
||||
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 {
|
||||
select! {
|
||||
@@ -48,11 +39,6 @@ pub async fn animate_loop(cursors: Arc<Vec<Cursor>>) -> Result<()> {
|
||||
if i < cursors.len() {
|
||||
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 => {
|
||||
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();
|
||||
}
|
||||
|
||||
_ = pend_optional_interval(&mut animation_interval) => {
|
||||
println!("interval {:?}", start.elapsed());
|
||||
}
|
||||
_ = animation_interval.tick() => {}
|
||||
}
|
||||
|
||||
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(
|
||||
animation_state: &mut AnimationState,
|
||||
selected: Option<usize>,
|
||||
@@ -104,19 +92,38 @@ async fn update_cursor(
|
||||
animation_state.cursor_index = 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]
|
||||
.particles
|
||||
.iter()
|
||||
.map(|x| load(&x.path))
|
||||
.map(|x| load_frames(&x.frames))
|
||||
.collect();
|
||||
|
||||
if let Some(i) = &animation_state.loaded_image {
|
||||
if let Some(i) = animation_state.loaded_image.get(0) {
|
||||
set_system_cursor(i)?;
|
||||
}
|
||||
} else {
|
||||
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(())
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
[
|
||||
{
|
||||
"name": "white",
|
||||
"path": "icon.png",
|
||||
|
||||
"frames": ["icon.png"],
|
||||
"animation_speed": 0.0,
|
||||
"particles": []
|
||||
},
|
||||
{
|
||||
"name": "red",
|
||||
"path": "red.png",
|
||||
"frames": ["red.png"],
|
||||
"animation_speed": 0.0,
|
||||
"particles": []
|
||||
},
|
||||
{
|
||||
"name": "flashing",
|
||||
"frames": ["red.png", "icon.png"],
|
||||
"animation_speed": 30.0,
|
||||
"particles": []
|
||||
}
|
||||
]
|
||||
@@ -47,7 +47,7 @@ impl RandomRange {
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ParticleConfig {
|
||||
path: String,
|
||||
frames: Vec<String>,
|
||||
animation_speed: f32,
|
||||
gravity_x: RandomRange,
|
||||
gravity_y: RandomRange,
|
||||
@@ -61,7 +61,7 @@ pub struct ParticleConfig {
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Cursor {
|
||||
name: String,
|
||||
path: String,
|
||||
frames: Vec<String>,
|
||||
animation_speed: f32,
|
||||
particles: Vec<ParticleConfig>,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user