Files
feresaria/compositor/src/focus_effect.rs
T

157 lines
5.0 KiB
Rust

use std::time::Duration;
use crate::config::FocusEffectSettings;
use ferese_layout::WindowId;
pub(crate) fn target(settings: FocusEffectSettings, active: Option<WindowId>, window: WindowId) -> f64 {
if !settings.has_effects() || active == Some(window) {
1.0
} else {
0.0
}
}
impl FocusEffectSettings {
pub(crate) fn has_effects(self) -> bool {
self.enabled && (self.active_opacity != self.inactive_opacity || self.inactive_dim != 0.0)
}
pub(crate) fn resolve(self, progress: f64, has_active: bool, overview_opacity: f32) -> (f32, f64) {
if !self.enabled {
return (1.0, 0.0);
}
let progress = progress.clamp(0.0, 1.0);
let alpha = self.inactive_opacity + (self.active_opacity - self.inactive_opacity) * progress;
let dim = if has_active {
self.inactive_dim * (1.0 - progress) * (1.0 - f64::from(overview_opacity))
} else {
0.0
};
(alpha as f32, dim)
}
}
/// A bounded, interruption-safe opacity transition; it never overshoots.
#[derive(Clone, Debug)]
pub(crate) struct BoundedFade {
pub current: f64,
start: f64,
target: f64,
elapsed: f64,
}
impl BoundedFade {
pub(crate) fn needs_update(&self, target: f64) -> bool {
self.target != target || self.current != target
}
pub(crate) fn is_animating(&self) -> bool {
self.current != self.target
}
pub fn new(value: f64) -> Self {
Self {
current: value,
start: value,
target: value,
elapsed: 0.0,
}
}
pub fn advance(&mut self, target: f64, delta: Duration, duration_ms: f64) -> bool {
if self.target != target {
self.start = self.current;
self.target = target;
self.elapsed = 0.0;
}
if duration_ms == 0.0 {
self.current = target;
return false;
}
self.elapsed += delta.as_secs_f64() * 1000.0;
let progress = (self.elapsed / duration_ms).min(1.0);
if progress == 1.0 {
self.current = target;
return false;
}
let eased = progress * progress * (3.0 - 2.0 * progress);
self.current = self.start + (target - self.start) * eased;
self.current != target
}
/// The target changed at this tick, not at the beginning of a potentially
/// long idle interval. Do not spend that old elapsed time on a new fade.
pub fn advance_visual(&mut self, target: f64, delta: Duration, duration_ms: f64) -> bool {
self.advance(
target,
if self.target != target { Duration::ZERO } else { delta },
duration_ms,
)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn settings() -> FocusEffectSettings {
FocusEffectSettings {
enabled: true,
blur: true,
active_opacity: 1.0,
inactive_opacity: 0.8,
inactive_dim: 0.2,
duration_ms: 150.0,
}
}
#[test]
fn disabled_effect_bypasses_opacity_and_dimming_without_changing_settings() {
let mut effect = settings();
effect.enabled = false;
assert!(!effect.has_effects());
for progress in [0.0, 0.5, 1.0] {
assert_eq!(effect.resolve(progress, true, 0.0), (1.0, 0.0));
}
assert_eq!(target(effect, Some(WindowId(1)), WindowId(2)), 1.0);
effect.enabled = true;
assert_eq!(effect.resolve(0.0, true, 0.0), (0.8, 0.2));
}
#[test]
fn opacity_and_dimming_share_one_interruption_safe_transition() {
let mut focus = BoundedFade::new(0.0);
assert!(focus.advance_visual(1.0, Duration::from_secs(30), 150.0));
assert_eq!(focus.current, 0.0);
focus.advance_visual(1.0, Duration::from_millis(75), 150.0);
let (alpha, dim) = settings().resolve(focus.current, true, 0.0);
assert!((alpha - 0.9).abs() < 1e-6);
assert!((dim - 0.1).abs() < 1e-9);
let before = focus.current;
focus.advance_visual(0.0, Duration::from_secs(30), 150.0);
assert_eq!(focus.current, before);
assert!(!focus.advance_visual(0.0, Duration::from_millis(150), 150.0));
assert_eq!(settings().resolve(focus.current, true, 0.0), (0.8, 0.2));
}
#[test]
fn activation_is_independent_of_overview_and_no_active_window() {
assert_eq!(target(settings(), Some(WindowId(1)), WindowId(1)), 1.0);
assert_eq!(target(settings(), Some(WindowId(1)), WindowId(2)), 0.0);
assert_eq!(target(settings(), None, WindowId(2)), 0.0);
assert_eq!(settings().resolve(0.0, true, 1.0), (0.8, 0.0));
assert_eq!(settings().resolve(0.0, false, 0.0), (0.8, 0.0));
assert_eq!(settings().resolve(0.0, true, 0.5), (0.8, 0.1));
}
#[test]
fn reduced_motion_and_zero_duration_snap() {
let mut focus = BoundedFade::new(0.0);
assert!(!focus.advance_visual(1.0, Duration::ZERO, 0.0));
assert_eq!(settings().resolve(focus.current, true, 0.0), (1.0, 0.0));
}
}