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"] }
|
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
|
||||||
|
|||||||
@@ -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(())
|
||||||
|
|||||||
@@ -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": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -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>,
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user