93 lines
2.6 KiB
Rust
93 lines
2.6 KiB
Rust
|
use std::io::Read;
|
||
|
|
||
|
use xcursor::{
|
||
|
parser::{parse_xcursor, Image},
|
||
|
CursorTheme,
|
||
|
};
|
||
|
|
||
|
static FALLBACK_CURSOR_DATA: &[u8] = include_bytes!("../resources/cursor.rgba");
|
||
|
|
||
|
pub struct Cursor {
|
||
|
icons: Vec<Image>,
|
||
|
size: u32,
|
||
|
}
|
||
|
|
||
|
impl Cursor {
|
||
|
pub fn load(log: &::slog::Logger) -> Cursor {
|
||
|
let name = std::env::var("XCURSOR_THEME")
|
||
|
.ok()
|
||
|
.unwrap_or_else(|| "default".into());
|
||
|
let size = std::env::var("XCURSOR_SIZE")
|
||
|
.ok()
|
||
|
.and_then(|s| s.parse().ok())
|
||
|
.unwrap_or(24);
|
||
|
|
||
|
let theme = CursorTheme::load(&name);
|
||
|
let icons = load_icon(&theme)
|
||
|
.map_err(|err| slog::warn!(log, "Unable to load xcursor: {}, using fallback cursor", err))
|
||
|
.unwrap_or_else(|_| {
|
||
|
vec![Image {
|
||
|
size: 32,
|
||
|
width: 64,
|
||
|
height: 64,
|
||
|
xhot: 1,
|
||
|
yhot: 1,
|
||
|
delay: 1,
|
||
|
pixels_rgba: Vec::from(FALLBACK_CURSOR_DATA),
|
||
|
pixels_argb: vec![], //unused
|
||
|
}]
|
||
|
});
|
||
|
|
||
|
Cursor { icons, size }
|
||
|
}
|
||
|
|
||
|
pub fn get_image(&self, scale: u32, millis: u32) -> Image {
|
||
|
let size = self.size * scale;
|
||
|
frame(millis, size, &self.icons)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fn nearest_images(size: u32, images: &[Image]) -> impl Iterator<Item = &Image> {
|
||
|
// Follow the nominal size of the cursor to choose the nearest
|
||
|
let nearest_image = images
|
||
|
.iter()
|
||
|
.min_by_key(|image| (size as i32 - image.size as i32).abs())
|
||
|
.unwrap();
|
||
|
|
||
|
images
|
||
|
.iter()
|
||
|
.filter(move |image| image.width == nearest_image.width && image.height == nearest_image.height)
|
||
|
}
|
||
|
|
||
|
fn frame(mut millis: u32, size: u32, images: &[Image]) -> Image {
|
||
|
let total = nearest_images(size, images).fold(0, |acc, image| acc + image.delay);
|
||
|
millis %= total;
|
||
|
|
||
|
for img in nearest_images(size, images) {
|
||
|
if millis < img.delay {
|
||
|
return img.clone();
|
||
|
}
|
||
|
millis -= img.delay;
|
||
|
}
|
||
|
|
||
|
unreachable!()
|
||
|
}
|
||
|
|
||
|
#[derive(thiserror::Error, Debug)]
|
||
|
enum Error {
|
||
|
#[error("Theme has no default cursor")]
|
||
|
NoDefaultCursor,
|
||
|
#[error("Error opening xcursor file: {0}")]
|
||
|
File(#[from] std::io::Error),
|
||
|
#[error("Failed to parse XCursor file")]
|
||
|
Parse,
|
||
|
}
|
||
|
|
||
|
fn load_icon(theme: &CursorTheme) -> Result<Vec<Image>, Error> {
|
||
|
let icon_path = theme.load_icon("default").ok_or(Error::NoDefaultCursor)?;
|
||
|
let mut cursor_file = std::fs::File::open(&icon_path)?;
|
||
|
let mut cursor_data = Vec::new();
|
||
|
cursor_file.read_to_end(&mut cursor_data)?;
|
||
|
parse_xcursor(&cursor_data).ok_or(Error::Parse)
|
||
|
}
|