liquidrust 0.2.2

A simple Rust application for displaying information and setting RGB colors for the Corsair H115i RGB PRO XT AIO.
Documentation
use crate::hid::write_to_device;
use hidapi::HidDevice;
use regex::Regex;
use std::thread::sleep;
use std::time::Duration;
pub const LED_COUNT: usize = 16;

fn hsl_to_rgb(h: f64, s: f64, l: f64) -> (u8, u8, u8) {
  let a = s * f64::min(l, 1.0 - l);
  let f = |n: f64| {
    let k = (n + h / 30.0) % 12.0;
    l - a * f64::max(-1.0, f64::min(f64::min(k - 3.0, 9.0 - k), 1.0))
  };
  let r = (f(0.0) * 255.0).round() as u8;
  let g = (f(8.0) * 255.0).round() as u8;
  let b = (f(4.0) * 255.0).round() as u8;
  (r, g, b)
}

fn rgb_to_hex(r: u8, g: u8, b: u8) -> String {
  format!("{:02x}{:02x}{:02x}", r, g, b)
}

pub fn u32_to_rgb(color: u32) -> (u8, u8, u8) {
  let r = ((color >> 16) & 0xff) as u8;
  let g = ((color >> 8) & 0xff) as u8;
  let b = (color & 0xff) as u8;
  (r, g, b)
}

fn interpolate(start: f64, end: f64, factor: f64) -> f64 {
  start + (end - start) * factor
}

fn interpolate_color(start: (u8, u8, u8), end: (u8, u8, u8), factor: f64) -> (u8, u8, u8) {
  let r = interpolate(start.0 as f64, end.0 as f64, factor).round() as u8;
  let g = interpolate(start.1 as f64, end.1 as f64, factor).round() as u8;
  let b = interpolate(start.2 as f64, end.2 as f64, factor).round() as u8;
  (r, g, b)
}

pub fn rainbow() -> Vec<String> {
  let mut colors = Vec::new();
  for i in 0..LED_COUNT {
    let hue = (i as f64 / LED_COUNT as f64) * 360.0;
    let (r, g, b) = hsl_to_rgb(hue, 1.0, 0.5);
    colors.push(rgb_to_hex(r, g, b));
  }
  colors
}

pub fn gradient(start_color: u32, end_color: u32) -> Vec<String> {
  let start_color = u32_to_rgb(start_color);
  let end_color = u32_to_rgb(end_color);
  let mut colors = Vec::new();
  for i in 0..(LED_COUNT / 2) {
    let factor = i as f64 / ((LED_COUNT / 2) as f64 - 1.0);
    let (r, g, b) = interpolate_color(start_color, end_color, factor);
    colors.push(rgb_to_hex(r, g, b));
  }
  let inverse_colors: Vec<String> = colors.iter().rev().map(|c| c.to_string()).collect();
  colors.extend(inverse_colors);
  colors
}

pub fn parse_color(color_str: &str) -> Result<u32, String> {
  let valid_color = Regex::new(r"^#?([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$").unwrap();
  if !valid_color.is_match(color_str) {
    return Err("Invalid hex color".to_string());
  }
  let mut color_str = color_str.trim_start_matches('#').to_string();
  if color_str.len() == 3 {
    color_str = color_str
      .chars()
      .flat_map(|c| std::iter::repeat(c).take(2))
      .collect();
  }
  u32::from_str_radix(&color_str, 16).map_err(|_| "Failed to parse hex color".to_string())
}

pub fn set_color(device: &HidDevice, color: u32) {
  let mut data = [0u8; 60];
  let (r, g, b) = u32_to_rgb(color);
  for i in 0..LED_COUNT {
    data[(i * 3) + 1] = g;
    data[(i * 3) + 2] = r;
    data[(i * 3) + 3] = b;
  }
  write_to_device(device, 0b100, None, Some(&data));
  sleep(Duration::from_millis(5));
}

pub fn set_colors(device: &HidDevice, colors: Vec<String>) {
  let mut data = [0u8; 60];
  let colors = colors;
  for i in 0..LED_COUNT {
    let color = parse_color(&colors[i]).unwrap();
    let (r, g, b) = u32_to_rgb(color);
    data[(i * 3) + 1] = g;
    data[(i * 3) + 2] = r;
    data[(i * 3) + 3] = b;
  }
  write_to_device(device, 0b100, None, Some(&data));
  sleep(Duration::from_millis(5));
}