Basic fade in/out of gifs

This commit is contained in:
Luke D. Jones
2021-05-30 19:20:02 +12:00
parent b9c4ff9ca7
commit bb910344b8
22 changed files with 426 additions and 252 deletions

View File

@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Extra info output for gfx dev scan
- Extra info in log for G-Sync to help prevent user confusion around gfx switching
- Add GA503Q led modes
- Added ability to fade in/out gifs and images for anime. This does break anime configs. See manual for details.
# [3.6.1] - 2021-05-25
### Changed

6
Cargo.lock generated
View File

@@ -207,7 +207,7 @@ dependencies = [
[[package]]
name = "daemon"
version = "3.6.2"
version = "3.6.3"
dependencies = [
"env_logger",
"log",
@@ -232,7 +232,7 @@ dependencies = [
[[package]]
name = "daemon-user"
version = "1.1.1"
version = "1.2.0"
dependencies = [
"dirs 3.0.1",
"rog_anime",
@@ -863,7 +863,7 @@ checksum = "24d5f089152e60f62d28b835fbff2cd2e8dc0baf1ac13343bef92ab7eed84548"
[[package]]
name = "rog_anime"
version = "1.0.4"
version = "1.0.5"
dependencies = [
"gif",
"glam",

View File

@@ -269,6 +269,7 @@ Each object in the array can be one of:
<FLOAT>,
<FLOAT>
],
"time": <TIME>,
"brightness": <FLOAT>
}
},
@@ -322,6 +323,27 @@ A cycle is how many gif loops to run:
```json
"time": "Infinite",
```
`Fade` allows an image or gif to fade in and out, and remain at max brightness to n time:
```json
"time": {
"Fade": {
"fade_in": {
"secs": 2,
"nanos": 0
},
"show_for": {
"secs": 1,
"nanos": 0
},
"fade_out": {
"secs": 2,
"nanos": 0
}
}
},
```
`show_for` can be `null`, if it is `null` then the `show_for` becomes `gif_time_length - fade_in - fade_out`.
This is period for which the gif or image will be max brightness (as set).
**<INT>**

View File

@@ -1,6 +1,6 @@
use std::{env, path::Path, thread::sleep};
use rog_anime::{ActionData, AnimeAction, Sequences};
use rog_anime::{ActionData, ActionLoader, Sequences};
use rog_dbus::RogDbusClient;
fn main() {
@@ -17,7 +17,7 @@ fn main() {
let mut seq = Sequences::new();
seq.insert(
0,
&AnimeAction::AsusAnimation {
&ActionLoader::AsusAnimation {
file: path.into(),
time: rog_anime::AnimTime::Infinite,
brightness,

View File

@@ -161,7 +161,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
dbus.proxies().anime().set_led_power(anime_turn.into())?
}
if let Some(anime_boot) = cmd.boot {
dbus.proxies().anime().set_system_animations(anime_boot.into())?
dbus.proxies()
.anime()
.set_system_animations(anime_boot.into())?
}
if let Some(action) = cmd.command {
match action {

View File

@@ -1,6 +1,6 @@
[package]
name = "daemon-user"
version = "1.1.1"
version = "1.2.0"
authors = ["Luke D Jones <luke@ljones.dev>"]
edition = "2018"
description = "Usermode daemon for user settings, anime, per-key lighting"

View File

@@ -1,6 +1,5 @@
use rog_anime::{ActionData, AnimTime, AnimeAction, Sequences, Vec2};
use rog_anime::{ActionData, ActionLoader, AnimTime, Fade, Sequences, Vec2};
use rog_dbus::RogDbusClient;
//use crate::dbus::DbusEvents;
use serde_derive::{Deserialize, Serialize};
use std::time::Duration;
use std::{
@@ -17,6 +16,44 @@ use zvariant_derive::Type;
use crate::{error::Error, user_config::UserAnimeConfig};
#[derive(Debug, Clone, Deserialize, Serialize, Type)]
pub struct Timer {
type_of: TimeType,
/// If time type is Timer then this is milliseonds, otherwise it is animation loop count
count: u64,
/// Used only for `TimeType::Timer`, milliseonds to fade the image in for
fade_in: Option<u64>,
/// Used only for `TimeType::Timer`, milliseonds to fade the image out for
fade_out: Option<u64>,
}
impl From<Timer> for AnimTime {
fn from(time: Timer) -> Self {
match time.type_of {
TimeType::Timer => {
if time.fade_in.is_some() || time.fade_out.is_some() {
let fade_in = time
.fade_in
.map_or(Duration::from_secs(0), Duration::from_millis);
let fade_out = time
.fade_out
.map_or(Duration::from_secs(0), Duration::from_millis);
let show_for = if time.count != 0 {
Some(Duration::from_millis(time.count))
} else {
None
};
AnimTime::Fade(Fade::new(fade_in, show_for, fade_out))
} else {
AnimTime::Time(Duration::from_millis(time.count))
}
}
TimeType::Count => AnimTime::Count(time.count as u32),
TimeType::Infinite => AnimTime::Infinite,
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize, Type)]
pub enum TimeType {
Timer,
@@ -29,14 +66,14 @@ pub enum TimeType {
pub struct CtrlAnimeInner<'a> {
sequences: Sequences,
client: RogDbusClient<'a>,
do_early_return: &'a AtomicBool,
do_early_return: Arc<AtomicBool>,
}
impl<'a> CtrlAnimeInner<'static> {
pub fn new(
sequences: Sequences,
client: RogDbusClient<'static>,
do_early_return: &'static AtomicBool,
do_early_return: Arc<AtomicBool>,
) -> Result<Self, Error> {
Ok(Self {
sequences,
@@ -45,7 +82,7 @@ impl<'a> CtrlAnimeInner<'static> {
})
}
/// To be called on each main loop iteration to pump out commands to the anime
pub fn run(&self) -> Result<(), Error> {
pub fn run(&'a self) -> Result<(), Error> {
if self.do_early_return.load(Ordering::SeqCst) {
return Ok(());
}
@@ -53,32 +90,10 @@ impl<'a> CtrlAnimeInner<'static> {
for action in self.sequences.iter() {
match action {
ActionData::Animation(frames) => {
let mut count = 0;
let start = Instant::now();
'animation: loop {
for frame in frames.frames() {
if self.do_early_return.load(Ordering::SeqCst) {
return Ok(());
}
self.client
.proxies()
.anime()
.write(frame.frame().clone())
.unwrap();
if let AnimTime::Time(time) = frames.duration() {
if Instant::now().duration_since(start) > time {
break 'animation;
}
}
sleep(frame.delay());
}
if let AnimTime::Cycles(times) = frames.duration() {
count += 1;
if count >= times {
break 'animation;
}
}
}
rog_anime::run_animation(frames, self.do_early_return.clone(), &|output| {
self.client.proxies().anime().write(output).unwrap()
})
.unwrap();
}
ActionData::Image(image) => {
self.client
@@ -115,7 +130,7 @@ pub struct CtrlAnime<'a> {
client: RogDbusClient<'a>,
inner: Arc<Mutex<CtrlAnimeInner<'a>>>,
/// Must be the same Atomic as in CtrlAnimeInner
inner_early_return: &'a AtomicBool,
inner_early_return: Arc<AtomicBool>,
}
impl<'a> CtrlAnime<'static> {
@@ -123,7 +138,7 @@ impl<'a> CtrlAnime<'static> {
config: Arc<Mutex<UserAnimeConfig>>,
inner: Arc<Mutex<CtrlAnimeInner<'static>>>,
client: RogDbusClient<'static>,
inner_early_return: &'static AtomicBool,
inner_early_return: Arc<AtomicBool>,
) -> Result<Self, Error> {
Ok(CtrlAnime {
config,
@@ -159,18 +174,13 @@ impl CtrlAnime<'static> {
&mut self,
index: u32,
file: String,
time: TimeType,
count: u32,
time: Timer,
brightness: f32,
) -> zbus::fdo::Result<String> {
if let Ok(mut config) = self.config.try_lock() {
let time: AnimTime = match time {
TimeType::Timer => AnimTime::Time(Duration::from_millis(count as u64)),
TimeType::Count => AnimTime::Cycles(count),
TimeType::Infinite => AnimTime::Infinite,
};
let time: AnimTime = time.into();
let file = Path::new(&file);
let action = AnimeAction::AsusAnimation {
let action = ActionLoader::AsusAnimation {
file: file.into(),
brightness,
time,
@@ -205,19 +215,14 @@ impl CtrlAnime<'static> {
scale: f32,
angle: f32,
xy: (f32, f32),
time: TimeType,
count: u32,
time: Timer,
brightness: f32,
) -> zbus::fdo::Result<String> {
if let Ok(mut config) = self.config.try_lock() {
let time: AnimTime = match time {
TimeType::Timer => AnimTime::Time(Duration::from_millis(count as u64)),
TimeType::Count => AnimTime::Cycles(count),
TimeType::Infinite => AnimTime::Infinite,
};
let time: AnimTime = time.into();
let file = Path::new(&file);
let translation = Vec2::new(xy.0, xy.1);
let action = AnimeAction::ImageAnimation {
let action = ActionLoader::ImageAnimation {
file: file.into(),
scale,
angle,
@@ -248,6 +253,7 @@ impl CtrlAnime<'static> {
Err(zbus::fdo::Error::Failed("UserConfig lock fail".into()))
}
#[allow(clippy::too_many_arguments)]
pub fn insert_image(
&mut self,
index: u32,
@@ -255,16 +261,19 @@ impl CtrlAnime<'static> {
scale: f32,
angle: f32,
xy: (f32, f32),
time: Option<Timer>,
brightness: f32,
) -> zbus::fdo::Result<String> {
if let Ok(mut config) = self.config.try_lock() {
let file = Path::new(&file);
let action = AnimeAction::Image {
let time = time.map(|time| time.into());
let action = ActionLoader::Image {
file: file.into(),
scale,
angle,
translation: Vec2::new(xy.0, xy.1),
brightness,
time,
};
// Must make the inner run loop return early
@@ -291,7 +300,7 @@ impl CtrlAnime<'static> {
pub fn insert_pause(&mut self, index: u32, millis: u64) -> zbus::fdo::Result<String> {
if let Ok(mut config) = self.config.try_lock() {
let action = AnimeAction::Pause(Duration::from_millis(millis));
let action = ActionLoader::Pause(Duration::from_millis(millis));
// Must make the inner run loop return early
self.inner_early_return.store(true, Ordering::SeqCst);

View File

@@ -11,9 +11,6 @@ use zbus::{fdo, Connection};
use std::sync::atomic::AtomicBool;
/// The anime loop needs an atomic to make it exit early if required
static ANIME_INNER_EARLY_RETURN: AtomicBool = AtomicBool::new(false);
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!(" user daemon v{}", rog_user::VERSION);
println!(" rog-anime v{}", rog_anime::VERSION);
@@ -39,20 +36,16 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// Set up the anime data and run loop/thread
if supported.anime_ctrl.0 {
let early_return = Arc::new(AtomicBool::new(false));
// Inner behind mutex required for thread safety
let inner = Arc::new(Mutex::new(CtrlAnimeInner::new(
anime,
client,
&ANIME_INNER_EARLY_RETURN,
early_return.clone(),
)?));
// Need new client object for dbus control part
let (client, _) = RogDbusClient::new().unwrap();
let anime_control = CtrlAnime::new(
anime_config,
inner.clone(),
client,
&ANIME_INNER_EARLY_RETURN,
)?;
let anime_control = CtrlAnime::new(anime_config, inner.clone(), client, early_return)?;
anime_control.add_to_server(&mut server);
// Thread using inner
let _anime_thread = thread::Builder::new()

View File

@@ -4,7 +4,7 @@ use std::{
time::Duration,
};
use rog_anime::{AnimTime, AnimeAction, Sequences, Vec2};
use rog_anime::{ActionLoader, AnimTime, Fade, Sequences, Vec2};
use serde_derive::{Deserialize, Serialize};
use crate::error::Error;
@@ -12,7 +12,7 @@ use crate::error::Error;
#[derive(Debug, Deserialize, Serialize)]
pub struct UserAnimeConfig {
pub name: String,
pub anime: Vec<AnimeAction>,
pub anime: Vec<ActionLoader>,
}
impl UserAnimeConfig {
@@ -95,34 +95,47 @@ impl Default for UserAnimeConfig {
Self {
name: "default".to_string(),
anime: vec![
AnimeAction::AsusAnimation {
ActionLoader::AsusAnimation {
file: "/usr/share/asusd/anime/asus/rog/Sunset.gif".into(),
brightness: 0.5,
time: AnimTime::Cycles(1),
time: AnimTime::Fade(Fade::new(
Duration::from_secs(6),
None,
Duration::from_secs(3),
)),
},
AnimeAction::ImageAnimation {
ActionLoader::ImageAnimation {
file: "/usr/share/asusd/anime/custom/sonic-run.gif".into(),
scale: 0.9,
angle: 0.65,
translation: Vec2::default(),
brightness: 0.5,
time: AnimTime::Time(Duration::from_secs(5)),
time: AnimTime::Fade(Fade::new(
Duration::from_secs(2),
Some(Duration::from_secs(2)),
Duration::from_secs(2),
)),
},
AnimeAction::Image {
ActionLoader::Image {
file: "/usr/share/asusd/anime/custom/rust.png".into(),
scale: 1.0,
angle: 0.0,
translation: Vec2::default(),
time: Some(AnimTime::Fade(Fade::new(
Duration::from_secs(2),
Some(Duration::from_secs(1)),
Duration::from_secs(2),
))),
brightness: 0.6,
},
AnimeAction::Pause(Duration::from_secs(6)),
AnimeAction::ImageAnimation {
ActionLoader::Pause(Duration::from_secs(1)),
ActionLoader::ImageAnimation {
file: "/usr/share/asusd/anime/custom/sonic-wait.gif".into(),
scale: 0.9,
angle: 0.0,
translation: Vec2::new(3.0, 2.0),
brightness: 0.5,
time: AnimTime::Cycles(2),
time: AnimTime::Count(2),
},
],
}

View File

@@ -1,6 +1,6 @@
[package]
name = "daemon"
version = "3.6.2"
version = "3.6.3"
license = "MPL-2.0"
readme = "README.md"
authors = ["Luke <luke@ljones.dev>"]

View File

@@ -1,6 +1,7 @@
use crate::VERSION;
use log::{error, info, warn};
use rog_anime::{error::AnimeError, ActionData, AnimTime, AnimeAction, Vec2};
use rog_anime::Fade;
use rog_anime::{error::AnimeError, ActionData, ActionLoader, AnimTime, Vec2};
use serde_derive::{Deserialize, Serialize};
use std::fs::{File, OpenOptions};
use std::io::{Read, Write};
@@ -11,10 +12,10 @@ pub static ANIME_CACHE_PATH: &str = "/etc/asusd/anime-cache.conf";
#[derive(Deserialize, Serialize)]
pub struct AnimeConfigV341 {
pub system: Option<AnimeAction>,
pub boot: Option<AnimeAction>,
pub suspend: Option<AnimeAction>,
pub shutdown: Option<AnimeAction>,
pub system: Option<ActionLoader>,
pub boot: Option<ActionLoader>,
pub suspend: Option<ActionLoader>,
pub shutdown: Option<ActionLoader>,
}
impl AnimeConfigV341 {
@@ -49,10 +50,10 @@ impl AnimeConfigV341 {
#[derive(Deserialize, Serialize)]
pub struct AnimeConfigV352 {
pub system: Vec<AnimeAction>,
pub boot: Vec<AnimeAction>,
pub wake: Vec<AnimeAction>,
pub shutdown: Vec<AnimeAction>,
pub system: Vec<ActionLoader>,
pub boot: Vec<ActionLoader>,
pub wake: Vec<ActionLoader>,
pub shutdown: Vec<ActionLoader>,
pub brightness: f32,
}
@@ -110,10 +111,10 @@ impl AnimeConfigCached {
/// Config for base system actions for the anime display
#[derive(Deserialize, Serialize)]
pub struct AnimeConfig {
pub system: Vec<AnimeAction>,
pub boot: Vec<AnimeAction>,
pub wake: Vec<AnimeAction>,
pub shutdown: Vec<AnimeAction>,
pub system: Vec<ActionLoader>,
pub boot: Vec<ActionLoader>,
pub wake: Vec<ActionLoader>,
pub shutdown: Vec<ActionLoader>,
pub brightness: f32,
pub awake_enabled: bool,
pub boot_anim_enabled: bool,
@@ -165,8 +166,11 @@ impl AnimeConfig {
info!("Updated config version to: {}", VERSION);
return config;
}
warn!("Could not deserialise {}", ANIME_CONFIG_PATH);
panic!("Please remove {} then restart asusd", ANIME_CONFIG_PATH);
AnimeConfig::write_backup(buf);
warn!(
"Could not deserialise {}. Backed up as *-old",
ANIME_CONFIG_PATH
);
}
}
AnimeConfig::create_default(&mut file)
@@ -176,23 +180,31 @@ impl AnimeConfig {
// create a default config here
let config = AnimeConfig {
system: vec![],
boot: vec![AnimeAction::ImageAnimation {
boot: vec![ActionLoader::ImageAnimation {
file: "/usr/share/asusd/anime/custom/sonic-run.gif".into(),
scale: 0.9,
angle: 0.65,
translation: Vec2::default(),
brightness: 1.0,
time: AnimTime::Time(Duration::from_secs(5)),
time: AnimTime::Fade(Fade::new(
Duration::from_secs(2),
Some(Duration::from_secs(2)),
Duration::from_secs(2),
)),
}],
wake: vec![AnimeAction::ImageAnimation {
wake: vec![ActionLoader::ImageAnimation {
file: "/usr/share/asusd/anime/custom/sonic-run.gif".into(),
scale: 0.9,
angle: 0.65,
translation: Vec2::default(),
brightness: 1.0,
time: AnimTime::Time(Duration::from_secs(5)),
time: AnimTime::Fade(Fade::new(
Duration::from_secs(2),
Some(Duration::from_secs(2)),
Duration::from_secs(2),
)),
}],
shutdown: vec![AnimeAction::ImageAnimation {
shutdown: vec![ActionLoader::ImageAnimation {
file: "/usr/share/asusd/anime/custom/sonic-wait.gif".into(),
scale: 0.9,
angle: 0.0,
@@ -234,4 +246,12 @@ impl AnimeConfig {
file.write_all(json.as_bytes())
.unwrap_or_else(|err| error!("Could not write config: {}", err));
}
fn write_backup(buf: String) {
let mut path = ANIME_CONFIG_PATH.to_string();
path.push_str("-old");
let mut file = File::create(&path).expect("Couldn't overwrite config");
file.write_all(buf.as_bytes())
.unwrap_or_else(|err| error!("Could not write config: {}", err));
}
}

View File

@@ -7,6 +7,7 @@ use std::collections::BTreeMap;
use crate::config::Config;
/// for parsing old v3.1.7 config
#[allow(dead_code)]
#[derive(Deserialize)]
pub(crate) struct ConfigV317 {
pub gfx_mode: GfxVendors,
@@ -26,7 +27,7 @@ pub(crate) struct ConfigV317 {
impl ConfigV317 {
pub(crate) fn into_current(self) -> Config {
Config {
gfx_mode: GfxVendors::Hybrid,
gfx_mode: self.gfx_mode,
gfx_tmp_mode: None,
gfx_managed: self.gfx_managed,
gfx_vfio_enable: false,

View File

@@ -5,7 +5,7 @@ use rog_anime::{
pkt_for_apply, pkt_for_flush, pkt_for_set_boot, pkt_for_set_on, pkts_for_init, PROD_ID,
VENDOR_ID,
},
ActionData, AnimTime, AnimeDataBuffer, AnimePacketType, AnimePowerStates, ANIME_DATA_LEN,
ActionData, AnimeDataBuffer, AnimePacketType, AnimePowerStates, ANIME_DATA_LEN,
};
use rog_types::supported::AnimeSupportedFunctions;
use rusb::{Device, DeviceHandle};
@@ -13,7 +13,6 @@ use std::{
error::Error,
sync::{Arc, Mutex},
thread::sleep,
time::Instant,
};
use std::{
sync::atomic::{AtomicBool, Ordering},
@@ -144,31 +143,15 @@ impl CtrlAnime {
for action in actions.iter() {
match action {
ActionData::Animation(frames) => {
let mut count = 0;
let start = Instant::now();
'animation: loop {
for frame in frames.frames() {
if let Ok(lock) = inner.try_lock() {
lock.write_data_buffer(frame.frame().clone());
}
if let AnimTime::Time(time) = frames.duration() {
if Instant::now().duration_since(start) > time {
break 'animation;
}
}
sleep(frame.delay());
// Need to check for early exit condition here or it might run
// until end of gif or time
if thread_exit.load(Ordering::SeqCst) {
break 'main;
}
}
if let AnimTime::Cycles(times) = frames.duration() {
count += 1;
if count >= times {
break 'animation;
}
rog_anime::run_animation(frames, thread_exit.clone(), &|frame| {
if let Ok(lock) = inner.try_lock() {
lock.write_data_buffer(frame);
}
})
.unwrap();
if thread_exit.load(Ordering::SeqCst) {
break 'main;
}
}
ActionData::Image(image) => {

View File

@@ -1,4 +1,3 @@
use sysfs_class::RuntimePM;
use ::zbus::Connection;
use ctrl_gfx::error::GfxError;
use ctrl_gfx::*;
@@ -14,6 +13,7 @@ use std::{iter::FromIterator, thread::JoinHandle};
use std::{process::Command, thread::sleep, time::Duration};
use std::{str::FromStr, sync::mpsc};
use std::{sync::Arc, sync::Mutex};
use sysfs_class::RuntimePM;
use sysfs_class::{PciDevice, SysClass};
use system::{GraphicsDevice, PciBus};
@@ -67,8 +67,12 @@ impl CtrlGraphics {
let mut nvidia = Vec::new();
let mut other = Vec::new();
for dev in devs.iter() {
let c = dev.class().map_err(|err|{
error!("GFX: device error: {}, {}", dev.path().to_string_lossy(), err);
let c = dev.class().map_err(|err| {
error!(
"GFX: device error: {}, {}",
dev.path().to_string_lossy(),
err
);
err
})?;
if 0x03 == (c >> 16) & 0xFF {
@@ -408,8 +412,12 @@ impl CtrlGraphics {
// Make sure the power management is set to auto for nvidia devices
let devs = PciDevice::all()?;
for dev in devs.iter() {
let c = dev.class().map_err(|err|{
error!("GFX: device error: {}, {}", dev.path().to_string_lossy(), err);
let c = dev.class().map_err(|err| {
error!(
"GFX: device error: {}, {}",
dev.path().to_string_lossy(),
err
);
err
})?;
if 0x03 == (c >> 16) & 0xFF && dev.vendor()? == 0x10DE {

View File

@@ -89,11 +89,12 @@ impl CtrlFanAndCpu {
*existing = profile.clone();
existing.set_system_all()?;
} else {
config.power_profiles
config
.power_profiles
.insert(profile.name.clone(), profile.clone());
profile.set_system_all()?;
}
config.active_profile = profile.name.clone();
config.write();
}

View File

@@ -1,6 +1,6 @@
[package]
name = "rog_anime"
version = "1.0.4"
version = "1.0.5"
license = "MPL-2.0"
readme = "README.md"
authors = ["Luke <luke@ljones.dev>"]

View File

@@ -1,7 +1,18 @@
use std::{
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
thread::sleep,
time::{Duration, Instant},
};
use serde_derive::{Deserialize, Serialize};
#[cfg(feature = "dbus")]
use zvariant_derive::Type;
use crate::{error::AnimeError, AnimTime, AnimeGif};
/// The first 7 bytes of a USB packet are accounted for by `USB_PREFIX1` and `USB_PREFIX2`
const BLOCK_START: usize = 7;
/// *Not* inclusive, the byte before this is the final for each "pane"
@@ -79,3 +90,93 @@ impl From<AnimeDataBuffer> for AnimePacketType {
buffers
}
}
/// This runs the animations as a blocking loop by using the `callback` to write data
pub fn run_animation(
frames: &AnimeGif,
do_early_return: Arc<AtomicBool>,
callback: &dyn Fn(AnimeDataBuffer),
) -> Result<(), AnimeError> {
let mut count = 0;
let start = Instant::now();
let mut timed = false;
let mut run_time = frames.total_frame_time();
println!("Real gif run length = {:?}", run_time);
if let AnimTime::Fade(time) = frames.duration() {
if let Some(middle) = time.show_for() {
run_time = middle + time.total_fade_time();
}
timed = true;
} else if let AnimTime::Time(time) = frames.duration() {
run_time = time;
timed = true;
}
// After setting up all the data
let mut fade_in = Duration::from_millis(0);
let mut fade_out = Duration::from_millis(0);
let mut fade_in_step = 0.0;
let mut fade_in_accum = 0.0;
let mut fade_out_step = 0.0;
let mut fade_out_accum;
if let AnimTime::Fade(time) = frames.duration() {
fade_in = time.fade_in();
fade_out = time.fade_out();
fade_in_step = 1.0 / fade_in.as_secs_f32();
fade_out_step = 1.0 / fade_out.as_secs_f32();
if time.total_fade_time() > run_time {
println!("Total fade in/out time larger than gif run time. Setting fades to half");
fade_in = run_time / 2;
fade_in_step = 1.0 / (run_time / 2).as_secs_f32();
fade_out = run_time / 2;
fade_out_step = 1.0 / (run_time / 2).as_secs_f32();
}
}
'animation: loop {
for frame in frames.frames() {
let frame_start = Instant::now();
if do_early_return.load(Ordering::SeqCst) {
return Ok(());
}
let mut output = frame.frame().clone();
if let AnimTime::Fade(_) = frames.duration() {
if frame_start <= start + fade_in {
for pixel in output.get_mut() {
*pixel = (*pixel as f32 * fade_in_accum) as u8;
}
fade_in_accum = fade_in_step * (frame_start - start).as_secs_f32();
} else if frame_start > (start + run_time) - fade_out {
if run_time > (frame_start - start) {
fade_out_accum =
fade_out_step * (run_time - (frame_start - start)).as_secs_f32();
} else {
fade_out_accum = 0.0;
}
for pixel in output.get_mut() {
*pixel = (*pixel as f32 * fade_out_accum) as u8;
}
}
}
callback(output);
if timed && Instant::now().duration_since(start) > run_time {
break 'animation;
}
sleep(frame.delay());
}
if let AnimTime::Count(times) = frames.duration() {
count += 1;
if count >= times {
break 'animation;
}
}
}
Ok(())
}

View File

@@ -31,10 +31,12 @@ impl AnimeFrame {
pub enum AnimTime {
/// Time in milliseconds for animation to run
Time(Duration),
/// How many full animation loops to run
Cycles(u32),
/// How many full animation loops to run or how many seconds if image is static
Count(u32),
/// Run for infinite time
Infinite,
/// Fade in, play for, fade out
Fade(Fade),
}
impl Default for AnimTime {
@@ -44,6 +46,40 @@ impl Default for AnimTime {
}
}
/// Fancy brightness control: fade in/out, show at brightness for n time
#[derive(Debug, Copy, Clone, Deserialize, Serialize)]
pub struct Fade {
fade_in: Duration,
show_for: Option<Duration>,
fade_out: Duration,
}
impl Fade {
pub fn new(fade_in: Duration, show_for: Option<Duration>, fade_out: Duration) -> Self {
Self {
fade_in,
show_for,
fade_out,
}
}
pub fn fade_in(&self) -> Duration {
self.fade_in
}
pub fn show_for(&self) -> Option<Duration> {
self.show_for
}
pub fn fade_out(&self) -> Duration {
self.fade_out
}
pub fn total_fade_time(&self) -> Duration {
self.fade_in + self.fade_out
}
}
/// A gif animation. This is a collection of frames from the gif, and a duration
/// that the animation should be shown for.
#[derive(Debug, Clone, Deserialize, Serialize)]
@@ -67,6 +103,7 @@ impl AnimeGif {
let mut decoder = decoder.read_info(file)?;
let mut frames = Vec::with_capacity(decoder.buffer_size());
while let Some(frame) = decoder.read_next_frame()? {
let wait = frame.delay * 10;
if matches!(frame.dispose, gif::DisposalMethod::Background) {
@@ -162,6 +199,39 @@ impl AnimeGif {
Ok(Self(frames, duration))
}
/// Make a static gif out of a greyscale png. If no duration is specified then the default
/// will be 1 second long. If `AnimTime::Cycles` is specified for `duration` then this can
/// be considered how many seconds the image will show for.
#[inline]
pub fn create_png_static(
file_name: &Path,
scale: f32,
angle: f32,
translation: Vec2,
duration: AnimTime,
brightness: f32,
) -> Result<Self, AnimeError> {
let image = AnimeImage::from_png(file_name, scale, angle, translation, brightness)?;
let mut total = Duration::from_millis(1000);
if let AnimTime::Fade(fade) = duration {
total = fade.total_fade_time();
if let Some(middle) = fade.show_for {
total += middle;
}
}
// Make frame delay 30ms, and find frame count
let frame_count = total.as_millis() / 30;
let single = AnimeFrame {
data: <AnimeDataBuffer>::from(&image),
delay: Duration::from_millis(30),
};
let frames = vec![single; frame_count as usize];
Ok(Self(frames, duration))
}
/// Get a slice of the frames this gif has
#[inline]
pub fn frames(&self) -> &[AnimeFrame] {
@@ -173,4 +243,16 @@ impl AnimeGif {
pub fn duration(&self) -> AnimTime {
self.1
}
/// Get the frame count
pub fn frame_count(&self) -> usize {
self.0.len()
}
/// Get total gif time for one run
pub fn total_frame_time(&self) -> Duration {
let mut time = 0;
self.0.iter().for_each(|f| time += f.delay.as_millis());
Duration::from_millis(time as u64)
}
}

View File

@@ -1,7 +1,4 @@
use std::{
path::{Path, PathBuf},
time::Duration,
};
use std::{path::PathBuf, time::Duration};
use glam::Vec2;
use serde_derive::{Deserialize, Serialize};
@@ -11,14 +8,14 @@ use crate::{error::AnimeError, AnimTime, AnimeDataBuffer, AnimeGif, AnimeImage};
/// All the possible AniMe actions that can be used. This enum is intended to be
/// a helper for loading up `ActionData`.
#[derive(Debug, Clone, Deserialize, Serialize)]
pub enum AnimeAction {
pub enum ActionLoader {
/// Full gif sequence. Immutable.
AsusAnimation {
file: PathBuf,
time: AnimTime,
brightness: f32,
},
/// Basic image, can have properties changed
/// Animated gif. If the file is a png a static gif is created using the `time` properties
ImageAnimation {
file: PathBuf,
scale: f32,
@@ -32,6 +29,7 @@ pub enum AnimeAction {
scale: f32,
angle: f32,
translation: Vec2,
time: Option<AnimTime>,
brightness: f32,
},
/// A pause to be used between sequences
@@ -59,9 +57,9 @@ pub enum ActionData {
}
impl ActionData {
pub fn from_anime_action(action: &AnimeAction) -> Result<ActionData, AnimeError> {
pub fn from_anime_action(action: &ActionLoader) -> Result<ActionData, AnimeError> {
let a = match action {
AnimeAction::AsusAnimation {
ActionLoader::AsusAnimation {
file,
time: duration,
brightness,
@@ -70,33 +68,59 @@ impl ActionData {
*duration,
*brightness,
)?),
AnimeAction::ImageAnimation {
ActionLoader::ImageAnimation {
file,
scale,
angle,
translation,
time: duration,
brightness,
} => ActionData::Animation(AnimeGif::create_png_gif(
&file,
*scale,
*angle,
*translation,
*duration,
*brightness,
)?),
AnimeAction::Image {
} => {
if let Some(ext) = file.extension() {
if ext.to_string_lossy().to_lowercase() == "png" {
return Ok(ActionData::Animation(AnimeGif::create_png_static(
&file,
*scale,
*angle,
*translation,
*duration,
*brightness,
)?));
}
}
ActionData::Animation(AnimeGif::create_png_gif(
&file,
*scale,
*angle,
*translation,
*duration,
*brightness,
)?)
}
ActionLoader::Image {
file,
scale,
angle,
translation,
brightness,
time,
} => {
if let Some(time) = time {
return Ok(ActionData::Animation(AnimeGif::create_png_static(
&file,
*scale,
*angle,
*translation,
*time,
*brightness,
)?));
}
// If no time then create a plain static image
let image = AnimeImage::from_png(&file, *scale, *angle, *translation, *brightness)?;
let data = <AnimeDataBuffer>::from(&image);
ActionData::Image(Box::new(data))
}
AnimeAction::Pause(duration) => ActionData::Pause(*duration),
ActionLoader::Pause(duration) => ActionData::Pause(*duration),
};
Ok(a)
}
@@ -115,38 +139,8 @@ impl Sequences {
/// Use a base `AnimeAction` to generate the precomputed data and insert in to
/// the run buffer
#[inline]
pub fn insert(&mut self, index: usize, action: &AnimeAction) -> Result<(), AnimeError> {
match action {
AnimeAction::AsusAnimation {
file,
time: duration,
brightness,
} => self.insert_asus_gif(index, &file, *duration, *brightness)?,
AnimeAction::ImageAnimation {
file,
scale,
angle,
translation,
time: duration,
brightness,
} => self.insert_image_gif(
index,
&file,
*scale,
*angle,
*translation,
*duration,
*brightness,
)?,
AnimeAction::Image {
file,
scale,
angle,
translation,
brightness,
} => self.insert_png(index, &file, *scale, *angle, *translation, *brightness)?,
AnimeAction::Pause(duration) => self.insert_pause(index, *duration),
};
pub fn insert(&mut self, index: usize, action: &ActionLoader) -> Result<(), AnimeError> {
self.0.insert(index, ActionData::from_anime_action(action)?);
Ok(())
}
@@ -161,66 +155,6 @@ impl Sequences {
None
}
fn insert_asus_gif(
&mut self,
mut index: usize,
file: &Path,
duration: AnimTime,
brightness: f32,
) -> Result<(), AnimeError> {
if index > self.0.len() {
index = self.0.len() - 1;
}
let frames = AnimeGif::create_diagonal_gif(file, duration, brightness)?;
self.0.insert(index, ActionData::Animation(frames));
Ok(())
}
fn insert_png(
&mut self,
mut index: usize,
file: &Path,
scale: f32,
angle: f32,
translation: Vec2,
brightness: f32,
) -> Result<(), AnimeError> {
if index > self.0.len() {
index = self.0.len() - 1;
}
let image = AnimeImage::from_png(file, scale, angle, translation, brightness)?;
let data = <AnimeDataBuffer>::from(&image);
self.0.insert(index, ActionData::Image(Box::new(data)));
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn insert_image_gif(
&mut self,
mut index: usize,
file: &Path,
scale: f32,
angle: f32,
translation: Vec2,
duration: AnimTime,
brightness: f32,
) -> Result<(), AnimeError> {
if index > self.0.len() {
index = self.0.len() - 1;
}
let frames =
AnimeGif::create_png_gif(file, scale, angle, translation, duration, brightness)?;
self.0.insert(index, ActionData::Animation(frames));
Ok(())
}
fn insert_pause(&mut self, mut index: usize, duration: Duration) {
if index > self.0.len() {
index = self.0.len() - 1;
}
self.0.insert(index, ActionData::Pause(duration));
}
pub fn iter(&self) -> ActionIterator {
ActionIterator {
actions: &self,

View File

@@ -1,5 +1,5 @@
use std::sync::mpsc::Sender;
use rog_anime::{AnimeDataBuffer, AnimePowerStates};
use std::sync::mpsc::Sender;
use zbus::{dbus_proxy, Connection, Result};
#[dbus_proxy(

View File

@@ -19,7 +19,7 @@
//!
//! …consequently `zbus-xmlgen` did not generate code for the above interfaces.
use std::sync::{mpsc::Sender};
use std::sync::mpsc::Sender;
use zbus::{dbus_proxy, Connection, Result};

View File

@@ -1,7 +1,7 @@
use rog_aura::AuraModeNum;
use serde_derive::{Deserialize, Serialize};
use zvariant_derive::Type;
use std::fmt;
use zvariant_derive::Type;
#[derive(Serialize, Deserialize, Type, Debug)]
pub struct SupportedFunctions {
@@ -60,7 +60,11 @@ impl fmt::Display for AnimeSupportedFunctions {
impl fmt::Display for ChargeSupportedFunctions {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "Charge:")?;
writeln!(f, "\tBattery charge limit control: {}", self.charge_level_set)
writeln!(
f,
"\tBattery charge limit control: {}",
self.charge_level_set
)
}
}
impl fmt::Display for FanCpuSupportedFunctions {
@@ -86,4 +90,4 @@ impl fmt::Display for RogBiosSupportedFunctions {
writeln!(f, "\tPOST sound toggle: {}", self.post_sound_toggle)?;
writeln!(f, "\tDedicated GFX toggle: {}", self.dedicated_gfx_toggle)
}
}
}