mirror of
https://github.com/Zenithsiz/zsw.git
synced 2026-02-03 17:52:15 +00:00
Durations are now parsed and displayed with possible hour, minute and second markers.
This commit is contained in:
parent
cac7059747
commit
310bdab36f
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -4865,6 +4865,7 @@ dependencies = [
|
||||
"pin-project",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_with",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
@ -4,6 +4,6 @@ display = "cross-screen"
|
||||
[panels.shader]
|
||||
type = "fade"
|
||||
playlists = ["example1"]
|
||||
duration = 5.0
|
||||
fade_duration = 1.0
|
||||
duration = "5s"
|
||||
fade_duration = "1s"
|
||||
fade = "basic"
|
||||
|
||||
@ -4,8 +4,8 @@ display = "multiple"
|
||||
[panels.shader]
|
||||
type = "fade"
|
||||
playlists = ["example1"]
|
||||
duration = 5.0
|
||||
fade_duration = 1.0
|
||||
duration = "1h1m1.1s"
|
||||
fade_duration = "1m1.1s"
|
||||
fade = "out"
|
||||
strength = 1.0
|
||||
|
||||
@ -16,7 +16,7 @@ display = "quarter"
|
||||
[panels.shader]
|
||||
type = "fade"
|
||||
playlists = ["example1"]
|
||||
duration = 5.0
|
||||
fade_duration = 1.0
|
||||
duration = "5s"
|
||||
fade_duration = "1s"
|
||||
fade = "white"
|
||||
strength = 1.0
|
||||
|
||||
@ -4,6 +4,6 @@ display = "full"
|
||||
[panels.shader]
|
||||
type = "fade"
|
||||
playlists = ["example1"]
|
||||
duration = 5.0
|
||||
fade_duration = 1.0
|
||||
duration = "5s"
|
||||
fade_duration = "1s"
|
||||
fade = "basic"
|
||||
|
||||
@ -13,6 +13,7 @@ image = { workspace = true }
|
||||
pin-project = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
serde_with = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
|
||||
84
zsw-util/src/duration_display.rs
Normal file
84
zsw-util/src/duration_display.rs
Normal file
@ -0,0 +1,84 @@
|
||||
//! Duration display
|
||||
|
||||
// Imports
|
||||
use {
|
||||
crate::AppError,
|
||||
app_error::Context,
|
||||
core::{fmt, str::FromStr, time::Duration},
|
||||
};
|
||||
|
||||
/// Duration
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[derive(serde_with::SerializeDisplay)]
|
||||
#[derive(serde_with::DeserializeFromStr)]
|
||||
pub struct DurationDisplay(pub Duration);
|
||||
|
||||
impl FromStr for DurationDisplay {
|
||||
type Err = AppError;
|
||||
|
||||
fn from_str(mut s: &str) -> Result<Self, Self::Err> {
|
||||
let mut time = Duration::ZERO;
|
||||
|
||||
// Remove any hours from the time
|
||||
if let Some((hours, rest)) = s.split_once('h') {
|
||||
let hours = hours
|
||||
.parse::<u64>()
|
||||
.with_context(|| format!("Expected an integer before `h`, found {hours:?}"))?;
|
||||
time += Duration::from_hours(hours);
|
||||
s = rest;
|
||||
}
|
||||
|
||||
// Remove any minutes from the time
|
||||
if let Some((mins, rest)) = s.split_once('m') {
|
||||
let mins = mins
|
||||
.parse::<u64>()
|
||||
.with_context(|| format!("Expected an integer before `m`, found {mins:?}"))?;
|
||||
time += Duration::from_mins(mins);
|
||||
s = rest;
|
||||
}
|
||||
|
||||
// Then remove any trailing `s` the user might have added
|
||||
let secs = s.strip_suffix('s').unwrap_or(s);
|
||||
|
||||
// And parse the rest as seconds (may be empty)
|
||||
let secs = match secs {
|
||||
"" => 0.0,
|
||||
_ => secs
|
||||
.parse::<f64>()
|
||||
.with_context(|| format!("Expected a number of seconds, found {secs:?}"))?,
|
||||
};
|
||||
time += Duration::from_secs_f64(secs);
|
||||
|
||||
Ok(Self(time))
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for DurationDisplay {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let hours = self.0.as_secs() / 3600;
|
||||
if hours != 0 {
|
||||
write!(f, "{hours}h")?;
|
||||
}
|
||||
|
||||
let mins = (self.0 - Duration::from_hours(hours)).as_secs() / 60;
|
||||
if mins != 0 {
|
||||
write!(f, "{mins}m")?;
|
||||
}
|
||||
|
||||
let secs = (self.0 - Duration::from_hours(hours) - Duration::from_mins(mins)).as_secs_f64();
|
||||
if secs != 0.0 || (hours == 0 && mins == 0) {
|
||||
// TODO: Find some other way of having variable precision (up to millisecond)
|
||||
let mut secs = format!("{secs:.3}");
|
||||
while secs.ends_with('0') {
|
||||
_ = secs.pop();
|
||||
}
|
||||
if secs.ends_with('.') {
|
||||
_ = secs.pop();
|
||||
}
|
||||
secs.push('s');
|
||||
f.pad(&secs)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@ -24,6 +24,7 @@
|
||||
)]
|
||||
|
||||
// Modules
|
||||
pub mod duration_display;
|
||||
pub mod loadable;
|
||||
mod rect;
|
||||
mod tuple_collect_res;
|
||||
@ -32,6 +33,7 @@ pub mod walk_dir;
|
||||
|
||||
// Exports
|
||||
pub use {
|
||||
duration_display::DurationDisplay,
|
||||
loadable::Loadable,
|
||||
rect::Rect,
|
||||
tuple_collect_res::{TupleCollectRes1, TupleCollectRes2, TupleCollectRes3, TupleCollectRes4, TupleCollectRes5},
|
||||
|
||||
@ -76,8 +76,8 @@ impl Profiles {
|
||||
ser::ProfilePanelShader::Fade(shader) =>
|
||||
ProfilePanelShader::Fade(ProfilePanelFadeShader {
|
||||
playlists: shader.playlists.into_iter().map(PlaylistName::from).collect(),
|
||||
duration: shader.duration,
|
||||
fade_duration: shader.fade_duration,
|
||||
duration: shader.duration.0,
|
||||
fade_duration: shader.fade_duration.0,
|
||||
inner: match shader.inner {
|
||||
ser::ProfilePanelFadeShaderInner::Basic =>
|
||||
ProfilePanelFadeShaderInner::Basic,
|
||||
|
||||
@ -1,12 +1,7 @@
|
||||
//! Serialized profile
|
||||
|
||||
// TODO: Allow deserializing durations from strings such as "500ms", "5s", "1m2s", etc.
|
||||
|
||||
// Imports
|
||||
use {
|
||||
core::time::Duration,
|
||||
serde_with::{DurationSecondsWithFrac, serde_as},
|
||||
};
|
||||
use zsw_util::DurationDisplay;
|
||||
|
||||
/// Profile
|
||||
#[derive(Debug)]
|
||||
@ -45,16 +40,11 @@ pub struct ProfilePanelNoneShader {
|
||||
|
||||
/// Panel shader fade
|
||||
#[derive(Debug)]
|
||||
#[serde_as]
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct ProfilePanelFadeShader {
|
||||
pub playlists: Vec<String>,
|
||||
|
||||
#[serde_as(as = "DurationSecondsWithFrac<f64>")]
|
||||
pub duration: Duration,
|
||||
|
||||
#[serde_as(as = "DurationSecondsWithFrac<f64>")]
|
||||
pub fade_duration: Duration,
|
||||
pub playlists: Vec<String>,
|
||||
pub duration: DurationDisplay,
|
||||
pub fade_duration: DurationDisplay,
|
||||
|
||||
/// Inner
|
||||
#[serde(flatten)]
|
||||
|
||||
@ -10,12 +10,12 @@ mod panels;
|
||||
// Imports
|
||||
use {
|
||||
crate::{AppEvent, display::Displays, panel::Panel},
|
||||
core::{ops::RangeInclusive, time::Duration},
|
||||
core::{ops::RangeInclusive, str::FromStr, time::Duration},
|
||||
egui::{Widget, mutex::Mutex},
|
||||
std::{path::Path, sync::Arc},
|
||||
strum::IntoEnumIterator,
|
||||
winit::{dpi::LogicalPosition, event_loop::EventLoopProxy},
|
||||
zsw_util::{AppError, Rect},
|
||||
zsw_util::{AppError, DurationDisplay, Rect},
|
||||
zsw_wgpu::Wgpu,
|
||||
};
|
||||
|
||||
@ -124,7 +124,8 @@ fn draw_duration(ui: &mut egui::Ui, duration: &mut Duration, range: RangeInclusi
|
||||
let start = range.start().as_secs_f32();
|
||||
let end = range.end().as_secs_f32();
|
||||
egui::Slider::new(&mut secs, start..=end)
|
||||
.suffix("s")
|
||||
.custom_formatter(|secs, _| DurationDisplay(Duration::from_secs_f64(secs)).to_string())
|
||||
.custom_parser(|s| DurationDisplay::from_str(s).ok().map(|d| d.0.as_secs_f64()))
|
||||
.clamping(egui::SliderClamping::Edits)
|
||||
.ui(ui);
|
||||
*duration = Duration::from_secs_f32(secs);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user