From becc95172950ff4f1f6f7cfefd5b8d0954e4ea32 Mon Sep 17 00:00:00 2001 From: Christopher Serr Date: Mon, 1 Aug 2022 14:10:33 +0200 Subject: [PATCH] Implement Timer Delta Background --- src/component/detailed_timer/mod.rs | 14 ++-- src/component/timer.rs | 64 ++++++++++++++++++- src/layout/parser/detailed_timer.rs | 6 +- src/layout/parser/mod.rs | 44 ++++++++++++- src/layout/parser/timer.rs | 4 +- src/settings/value.rs | 28 +++++++- src/util/clear_vec.rs | 4 +- ...round.lsl => WithTimerDeltaBackground.lsl} | 12 ++-- tests/layout_files/dark.lsl | 2 +- tests/layout_files/mod.rs | 2 +- tests/layout_files/subsplits.lsl | 8 +-- tests/layout_parsing.rs | 4 +- tests/rendering.rs | 33 ++++++++-- 13 files changed, 190 insertions(+), 35 deletions(-) rename tests/layout_files/{WithTimerGradientBackground.lsl => WithTimerDeltaBackground.lsl} (92%) diff --git a/src/component/detailed_timer/mod.rs b/src/component/detailed_timer/mod.rs index 97e4e082..66452cba 100644 --- a/src/component/detailed_timer/mod.rs +++ b/src/component/detailed_timer/mod.rs @@ -39,7 +39,7 @@ pub struct Component { #[serde(default)] pub struct Settings { /// The background shown behind the component. - pub background: Gradient, + pub background: timer::DeltaGradient, /// The first comparison to show the segment time of. If it's not specified, /// the current comparison is used. pub comparison1: Option, @@ -114,7 +114,7 @@ fn update_comparison( impl Default for Settings { fn default() -> Self { Settings { - background: Gradient::Transparent, + background: timer::DeltaGradient::from(Gradient::Transparent), comparison1: None, comparison2: Some(String::from(best_segments::NAME)), hide_second_comparison: false, @@ -271,11 +271,11 @@ impl Component { .update_with(current_split.filter(|_| display_icon).map(Segment::icon)) .map(Into::into); - state.background = self.settings.background; - self.timer .update_state(&mut state.timer, timer, layout_settings); + state.background = state.timer.background; + self.segment_timer .update_state(&mut state.segment_timer, timer, layout_settings); @@ -374,7 +374,11 @@ impl Component { /// the setting provided is out of bounds. pub fn set_value(&mut self, index: usize, value: Value) { match index { - 0 => self.settings.background = value.into(), + 0 => { + let value = value.into(); + self.settings.background = value; + self.settings.timer.background = value; + } 1 => { let value = value.into(); self.settings.timer.timing_method = value; diff --git a/src/component/timer.rs b/src/component/timer.rs index ef22fb6d..5baf3f08 100644 --- a/src/component/timer.rs +++ b/src/component/timer.rs @@ -24,12 +24,67 @@ pub struct Component { settings: Settings, } +/// Represents the possible backgrounds for a timer. +/// Right now these are just gradients, and gradients with delta, +/// however in the future timers may support other types of backgrounds.] +#[derive(Copy, Clone, PartialEq, Serialize, Deserialize)] +pub enum DeltaGradient { + /// A normal gradient of some kind + Gradient(Gradient), + /// Delta based plain color + DeltaPlain, + /// Delta based gradient, Vertical + DeltaVertical, + /// Delta based gradient, horizontal + DeltaHorizontal, +} + +impl From for DeltaGradient { + fn from(g: Gradient) -> Self { + Self::Gradient(g) + } +} + +impl Default for DeltaGradient { + fn default() -> Self { + Self::Gradient(Gradient::default()) + } +} + +impl DeltaGradient { + /// Converts the DeltaGradient to a normal gradient for purposes of rendering + /// + /// # Arguments + /// * `delta` - the color used to represent the timer's current state + pub fn gradient(&self, delta: Color) -> Gradient { + let [h, s, v, a] = delta.to_hsva(); + + match self { + DeltaGradient::Gradient(g) => *g, + DeltaGradient::DeltaVertical => { + let color_a = Color::hsva(h, s * 0.5, v * 0.25, a * (1.0 / 6.0)); + let color_b = Color::hsva(h, s * 0.5, v * 0.25, a); + + Gradient::Vertical(color_a, color_b) + } + DeltaGradient::DeltaPlain => { + Gradient::Plain(Color::hsva(h, s * 0.5, v * 0.25, a * (7.0 / 12.0))) + } + DeltaGradient::DeltaHorizontal => { + let color_a = Color::hsva(h, s * 0.5, v * 0.25, a * (1.0 / 6.0)); + let color_b = Color::hsva(h, s * 0.5, v * 0.25, a); + + Gradient::Horizontal(color_a, color_b) + } + } + } +} /// The Settings for this component. #[derive(Clone, Serialize, Deserialize)] #[serde(default)] pub struct Settings { /// The background shown behind the component. - pub background: Gradient, + pub background: DeltaGradient, /// Specifies the Timing Method to use. If set to `None` the Timing Method /// of the Timer is used for showing the time. Otherwise the Timing Method /// provided is used. @@ -58,7 +113,7 @@ pub struct Settings { impl Default for Settings { fn default() -> Self { Self { - background: Gradient::Transparent, + background: DeltaGradient::default(), timing_method: None, height: 60, color_override: None, @@ -222,7 +277,10 @@ impl Component { (visual_color, visual_color) }; - state.background = self.settings.background; + state.background = self + .settings + .background + .gradient(semantic_color.visualize(layout_settings)); state.time.clear(); let _ = write!( diff --git a/src/layout/parser/detailed_timer.rs b/src/layout/parser/detailed_timer.rs index 95791ffa..3dff8aaa 100644 --- a/src/layout/parser/detailed_timer.rs +++ b/src/layout/parser/detailed_timer.rs @@ -1,6 +1,7 @@ use super::{ accuracy, color, comparison_override, end_tag, parse_bool, parse_children, text_parsed, - timer_format, timing_method_override, translate_size, GradientBuilder, Result, + timer_format, timing_method_override, translate_size, DeltaGradientKind, GradientBuilder, + Result, }; use crate::{timing::formatter::DigitsFormat, util::xml::Reader}; @@ -8,7 +9,7 @@ pub use crate::component::detailed_timer::Component; pub fn settings(reader: &mut Reader<'_>, component: &mut Component) -> Result<()> { let mut settings = component.settings().clone(); - let mut background_builder = GradientBuilder::new(); + let mut background_builder = GradientBuilder::::new_gradient_type(); let mut timer_override_color = false; let (mut total_height, mut segment_timer_ratio) = (65u32, 0.4); @@ -91,6 +92,7 @@ pub fn settings(reader: &mut Reader<'_>, component: &mut Component) -> Result<() settings.timer.color_override = None; } settings.background = background_builder.build(); + settings.timer.background = settings.background; settings.segment_timer.height = (total_height as f32 * segment_timer_ratio) as u32; settings.timer.height = total_height - settings.segment_timer.height; diff --git a/src/layout/parser/mod.rs b/src/layout/parser/mod.rs index bde451c8..1c345225 100644 --- a/src/layout/parser/mod.rs +++ b/src/layout/parser/mod.rs @@ -2,7 +2,7 @@ use super::{Component, Layout, LayoutDirection}; use crate::{ - component::separator, + component::{separator, timer::DeltaGradient}, platform::{math::f32::powf, prelude::*}, settings::{ Alignment, Color, Font, FontStretch, FontStyle, FontWeight, Gradient, ListGradient, @@ -107,6 +107,16 @@ enum GradientKind { Horizontal, } +enum DeltaGradientKind { + Transparent, + Plain, + Vertical, + Horizontal, + PlainWithDeltaColor, + VerticalWithDeltaColor, + HorizontalWithDeltaColor, +} + enum ListGradientKind { Same(GradientKind), Alternating, @@ -119,6 +129,38 @@ trait GradientType: Sized { fn build(self, first: Color, second: Color) -> Self::Built; } +impl GradientType for DeltaGradientKind { + type Built = DeltaGradient; + + fn default() -> Self { + DeltaGradientKind::Transparent + } + + fn parse(kind: &str) -> Result { + match kind { + "Plain" => Ok(DeltaGradientKind::Plain), + "PlainWithDeltaColor" => Ok(DeltaGradientKind::PlainWithDeltaColor), + "Vertical" => Ok(DeltaGradientKind::Vertical), + "VerticalWithDeltaColor" => Ok(DeltaGradientKind::VerticalWithDeltaColor), + "Horizontal" => Ok(DeltaGradientKind::Horizontal), + "HorizontalWithDeltaColor" => Ok(DeltaGradientKind::HorizontalWithDeltaColor), + _ => Err(Error::ParseGradientType), + } + } + + fn build(self, first: Color, second: Color) -> Self::Built { + match self { + DeltaGradientKind::Transparent => Gradient::Transparent.into(), + DeltaGradientKind::Plain => Gradient::Plain(first).into(), + DeltaGradientKind::Vertical => Gradient::Vertical(first, second).into(), + DeltaGradientKind::Horizontal => Gradient::Horizontal(first, second).into(), + DeltaGradientKind::PlainWithDeltaColor => DeltaGradient::DeltaPlain, + DeltaGradientKind::VerticalWithDeltaColor => DeltaGradient::DeltaVertical, + DeltaGradientKind::HorizontalWithDeltaColor => DeltaGradient::DeltaHorizontal, + } + } +} + impl GradientType for GradientKind { type Built = Gradient; fn default() -> Self { diff --git a/src/layout/parser/timer.rs b/src/layout/parser/timer.rs index 49e2c827..5ee91af0 100644 --- a/src/layout/parser/timer.rs +++ b/src/layout/parser/timer.rs @@ -1,6 +1,6 @@ use super::{ accuracy, color, end_tag, parse_bool, parse_children, text_parsed, timer_format, - timing_method_override, translate_size, GradientBuilder, Result, + timing_method_override, translate_size, DeltaGradientKind, GradientBuilder, Result, }; use crate::util::xml::Reader; @@ -8,7 +8,7 @@ pub use crate::component::timer::Component; pub fn settings(reader: &mut Reader<'_>, component: &mut Component) -> Result<()> { let settings = component.settings_mut(); - let mut background_builder = GradientBuilder::new(); + let mut background_builder = GradientBuilder::::new_gradient_type(); let mut override_color = false; parse_children(reader, |reader, tag, _| { diff --git a/src/settings/value.rs b/src/settings/value.rs index ef80d53b..55cbb6f2 100644 --- a/src/settings/value.rs +++ b/src/settings/value.rs @@ -1,5 +1,8 @@ use crate::{ - component::splits::{ColumnStartWith, ColumnUpdateTrigger, ColumnUpdateWith}, + component::{ + splits::{ColumnStartWith, ColumnUpdateTrigger, ColumnUpdateWith}, + timer::DeltaGradient, + }, hotkey::KeyCode, layout::LayoutDirection, platform::prelude::*, @@ -57,6 +60,9 @@ pub enum Value { /// A value describing a font to use. `None` if a default font should be /// used. Font(Option), + /// A gradient that may or may not take one of it's colors from the current + /// delta. + DeltaGradient(DeltaGradient), } impl From for Value { @@ -173,6 +179,12 @@ impl From> for Value { } } +impl From for Value { + fn from(x: DeltaGradient) -> Self { + Value::DeltaGradient(x) + } +} + /// The Error type for values that couldn't be converted. #[derive(Debug, snafu::Snafu)] pub enum Error { @@ -345,6 +357,14 @@ impl Value { _ => Err(Error::WrongType), } } + + /// Tries to convert the value into a delta gradient. + pub fn into_delta_gradient(self) -> Result { + match self { + Value::DeltaGradient(v) => Ok(v), + _ => Err(Error::WrongType), + } + } } impl From for bool { @@ -460,3 +480,9 @@ impl From for Option { value.into_font().unwrap() } } + +impl From for DeltaGradient { + fn from(value: Value) -> Self { + value.into_delta_gradient().unwrap() + } +} diff --git a/src/util/clear_vec.rs b/src/util/clear_vec.rs index a3d4c11a..7fb72fce 100644 --- a/src/util/clear_vec.rs +++ b/src/util/clear_vec.rs @@ -109,14 +109,14 @@ impl FromIterator for ClearVec { } } -impl<'a, T: Clear> Index for ClearVec { +impl Index for ClearVec { type Output = T; fn index(&self, index: usize) -> &Self::Output { &self.vec[index] } } -impl<'a, T: Clear> IndexMut for ClearVec { +impl IndexMut for ClearVec { fn index_mut(&mut self, index: usize) -> &mut Self::Output { &mut self.vec[index] } diff --git a/tests/layout_files/WithTimerGradientBackground.lsl b/tests/layout_files/WithTimerDeltaBackground.lsl similarity index 92% rename from tests/layout_files/WithTimerGradientBackground.lsl rename to tests/layout_files/WithTimerDeltaBackground.lsl index 35d68219..f63153aa 100644 --- a/tests/layout_files/WithTimerGradientBackground.lsl +++ b/tests/layout_files/WithTimerDeltaBackground.lsl @@ -24,9 +24,9 @@ FFFFFF00 00000000 80000000 - - - + + + False True True @@ -67,7 +67,7 @@ 1.6 FF0B365F FF153574 - 19 + 5 1 False False @@ -123,7 +123,7 @@ FFAAAAAA 00000000 FF222222 - PlainWithDeltaColor + HorizontalWithDeltaColor False Current Timing Method 35 @@ -147,4 +147,4 @@ - \ No newline at end of file + diff --git a/tests/layout_files/dark.lsl b/tests/layout_files/dark.lsl index b3884065..f3d0aa25 100644 --- a/tests/layout_files/dark.lsl +++ b/tests/layout_files/dark.lsl @@ -236,4 +236,4 @@ - \ No newline at end of file + diff --git a/tests/layout_files/mod.rs b/tests/layout_files/mod.rs index 06ee80a9..b0f6bc16 100644 --- a/tests/layout_files/mod.rs +++ b/tests/layout_files/mod.rs @@ -4,4 +4,4 @@ pub const ALL: &str = include_str!("All.lsl"); pub const DARK: &str = include_str!("dark.lsl"); pub const SUBSPLITS: &str = include_str!("subsplits.lsl"); pub const WSPLIT: &str = include_str!("WSplit.lsl"); -pub const WITH_TIMER_GRADIENT_BACKGROUND: &str = include_str!("WithTimerGradientBackground.lsl"); +pub const WITH_TIMER_DELTA_BACKGROUND: &str = include_str!("WithTimerDeltaBackground.lsl"); diff --git a/tests/layout_files/subsplits.lsl b/tests/layout_files/subsplits.lsl index 308548b7..c26f2cbd 100644 --- a/tests/layout_files/subsplits.lsl +++ b/tests/layout_files/subsplits.lsl @@ -24,9 +24,9 @@ FF7A7A7A 00000000 80000000 - - - + + + True True False @@ -297,4 +297,4 @@ - \ No newline at end of file + diff --git a/tests/layout_parsing.rs b/tests/layout_parsing.rs index ed3b4735..b2b03e63 100644 --- a/tests/layout_parsing.rs +++ b/tests/layout_parsing.rs @@ -31,9 +31,7 @@ mod parse { #[test] fn with_timer_delta_background() { - livesplit(layout_files::WITH_TIMER_GRADIENT_BACKGROUND); - // FIXME: Add a rendering test to render out the gradient once we have - // support for this. + livesplit(layout_files::WITH_TIMER_DELTA_BACKGROUND); } #[test] diff --git a/tests/rendering.rs b/tests/rendering.rs index 697c6753..105bc5c0 100644 --- a/tests/rendering.rs +++ b/tests/rendering.rs @@ -68,6 +68,31 @@ fn wsplit() { ); } +#[test] +fn timer_delta_background() { + let run = lss(run_files::LIVESPLIT_1_0); + let mut timer = Timer::new(run).unwrap(); + let mut layout = lsl(layout_files::WITH_TIMER_DELTA_BACKGROUND); + tests_helper::start_run(&mut timer); + tests_helper::make_progress_run_with_splits_opt(&mut timer, &[Some(5.0), None, Some(10.0)]); + + check_dims( + &layout.state(&timer.snapshot()), + [250, 300], + "a+nRyOCfXU0=", + "timer_delta_background_ahead", + ); + + timer.reset(true); + + check_dims( + &layout.state(&timer.snapshot()), + [250, 300], + "a+nZyeGfW80=", + "timer_delta_background_stopped", + ); +} + #[test] fn all_components() { let mut layout = lsl(layout_files::ALL); @@ -223,7 +248,7 @@ fn get_comparison_tolerance() -> u32 { // tolerance that is greater than 0. // FIXME: We use SSE as an approximation for the cfg because MMX isn't supported by Rust yet. if cfg!(all(target_arch = "x86", not(target_feature = "sse"))) { - 2 + 3 } else { 0 } @@ -276,9 +301,9 @@ fn check_dims( for (x, y, Rgba([r, g, b, a])) in expected_image.enumerate_pixels_mut() { if x < hash_image.width() && y < hash_image.height() { let img_hash::image::Rgba([r2, g2, b2, a2]) = hash_image.get_pixel(x, y); - *r = (*r as i16).wrapping_sub(*r2 as i16).abs() as u8; - *g = (*g as i16).wrapping_sub(*g2 as i16).abs() as u8; - *b = (*b as i16).wrapping_sub(*b2 as i16).abs() as u8; + *r = (*r as i16).wrapping_sub(*r2 as i16).unsigned_abs() as u8; + *g = (*g as i16).wrapping_sub(*g2 as i16).unsigned_abs() as u8; + *b = (*b as i16).wrapping_sub(*b2 as i16).unsigned_abs() as u8; *a = (*a).max(*a2); } }