mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-22 02:01:55 +07:00
Refactor animation timing to use lazy clocks
This commit is contained in:
@@ -1,8 +1,6 @@
|
|||||||
use std::f32::consts::{FRAC_PI_2, PI};
|
use std::f32::consts::{FRAC_PI_2, PI};
|
||||||
use std::sync::atomic::Ordering;
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use niri::animation::ANIMATION_SLOWDOWN;
|
|
||||||
use niri::render_helpers::border::BorderRenderElement;
|
use niri::render_helpers::border::BorderRenderElement;
|
||||||
use niri_config::{Color, CornerRadius, GradientInterpolation};
|
use niri_config::{Color, CornerRadius, GradientInterpolation};
|
||||||
use smithay::backend::renderer::element::RenderElement;
|
use smithay::backend::renderer::element::RenderElement;
|
||||||
@@ -31,20 +29,13 @@ impl TestCase for GradientAngle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn advance_animations(&mut self, current_time: Duration) {
|
fn advance_animations(&mut self, current_time: Duration) {
|
||||||
let mut delta = if self.prev_time.is_zero() {
|
let delta = if self.prev_time.is_zero() {
|
||||||
Duration::ZERO
|
Duration::ZERO
|
||||||
} else {
|
} else {
|
||||||
current_time.saturating_sub(self.prev_time)
|
current_time.saturating_sub(self.prev_time)
|
||||||
};
|
};
|
||||||
self.prev_time = current_time;
|
self.prev_time = current_time;
|
||||||
|
|
||||||
let slowdown = ANIMATION_SLOWDOWN.load(Ordering::SeqCst);
|
|
||||||
if slowdown == 0. {
|
|
||||||
delta = Duration::ZERO
|
|
||||||
} else {
|
|
||||||
delta = delta.div_f64(slowdown);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.angle += delta.as_secs_f32() * PI;
|
self.angle += delta.as_secs_f32() * PI;
|
||||||
|
|
||||||
if self.angle >= PI * 2. {
|
if self.angle >= PI * 2. {
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
use std::f32::consts::{FRAC_PI_4, PI};
|
use std::f32::consts::{FRAC_PI_4, PI};
|
||||||
use std::sync::atomic::Ordering;
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use niri::animation::ANIMATION_SLOWDOWN;
|
|
||||||
use niri::layout::focus_ring::FocusRing;
|
use niri::layout::focus_ring::FocusRing;
|
||||||
use niri::render_helpers::border::BorderRenderElement;
|
use niri::render_helpers::border::BorderRenderElement;
|
||||||
use niri_config::{Color, CornerRadius, FloatOrInt, GradientInterpolation};
|
use niri_config::{Color, CornerRadius, FloatOrInt, GradientInterpolation};
|
||||||
@@ -43,20 +41,13 @@ impl TestCase for GradientArea {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn advance_animations(&mut self, current_time: Duration) {
|
fn advance_animations(&mut self, current_time: Duration) {
|
||||||
let mut delta = if self.prev_time.is_zero() {
|
let delta = if self.prev_time.is_zero() {
|
||||||
Duration::ZERO
|
Duration::ZERO
|
||||||
} else {
|
} else {
|
||||||
current_time.saturating_sub(self.prev_time)
|
current_time.saturating_sub(self.prev_time)
|
||||||
};
|
};
|
||||||
self.prev_time = current_time;
|
self.prev_time = current_time;
|
||||||
|
|
||||||
let slowdown = ANIMATION_SLOWDOWN.load(Ordering::SeqCst);
|
|
||||||
if slowdown == 0. {
|
|
||||||
delta = Duration::ZERO
|
|
||||||
} else {
|
|
||||||
delta = delta.div_f64(slowdown);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.progress += delta.as_secs_f32() * PI;
|
self.progress += delta.as_secs_f32() * PI;
|
||||||
|
|
||||||
if self.progress >= PI * 2. {
|
if self.progress >= PI * 2. {
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ impl Layout {
|
|||||||
let mut layout = niri::layout::Layout::with_options(clock.clone(), options);
|
let mut layout = niri::layout::Layout::with_options(clock.clone(), options);
|
||||||
layout.add_output(output.clone());
|
layout.add_output(output.clone());
|
||||||
|
|
||||||
let start_time = clock.now();
|
let start_time = clock.now_unadjusted();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
output,
|
output,
|
||||||
@@ -207,24 +207,25 @@ impl TestCase for Layout {
|
|||||||
self.layout.are_animations_ongoing(Some(&self.output)) || !self.steps.is_empty()
|
self.layout.are_animations_ongoing(Some(&self.output)) || !self.steps.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn advance_animations(&mut self, current_time: Duration) {
|
fn advance_animations(&mut self, _current_time: Duration) {
|
||||||
|
let now_unadjusted = self.clock.now_unadjusted();
|
||||||
let run = self
|
let run = self
|
||||||
.steps
|
.steps
|
||||||
.keys()
|
.keys()
|
||||||
.copied()
|
.copied()
|
||||||
.filter(|delay| self.start_time + *delay <= current_time)
|
.filter(|delay| self.start_time + *delay <= now_unadjusted)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
for delay in &run {
|
for delay in &run {
|
||||||
let now = self.start_time + *delay;
|
let now = self.start_time + *delay;
|
||||||
self.clock.set_time_override(Some(now));
|
self.clock.set_unadjusted(now);
|
||||||
self.layout.advance_animations(now);
|
self.layout.advance_animations();
|
||||||
|
|
||||||
let f = self.steps.remove(delay).unwrap();
|
let f = self.steps.remove(delay).unwrap();
|
||||||
f(self);
|
f(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.clock.set_time_override(None);
|
self.clock.set_unadjusted(now_unadjusted);
|
||||||
self.layout.advance_animations(current_time);
|
self.layout.advance_animations();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(
|
fn render(
|
||||||
|
|||||||
@@ -90,8 +90,8 @@ impl TestCase for Tile {
|
|||||||
self.tile.are_animations_ongoing()
|
self.tile.are_animations_ongoing()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn advance_animations(&mut self, current_time: Duration) {
|
fn advance_animations(&mut self, _current_time: Duration) {
|
||||||
self.tile.advance_animations(current_time);
|
self.tile.advance_animations();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(
|
fn render(
|
||||||
|
|||||||
@@ -2,15 +2,11 @@
|
|||||||
extern crate tracing;
|
extern crate tracing;
|
||||||
|
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::sync::atomic::Ordering;
|
|
||||||
|
|
||||||
use adw::prelude::{AdwApplicationWindowExt, NavigationPageExt};
|
use adw::prelude::{AdwApplicationWindowExt, NavigationPageExt};
|
||||||
use cases::Args;
|
use cases::Args;
|
||||||
use gtk::prelude::{
|
use gtk::prelude::{ApplicationExt, ApplicationExtManual, BoxExt, GtkWindowExt, WidgetExt};
|
||||||
AdjustmentExt, ApplicationExt, ApplicationExtManual, BoxExt, GtkWindowExt, WidgetExt,
|
|
||||||
};
|
|
||||||
use gtk::{gdk, gio, glib};
|
use gtk::{gdk, gio, glib};
|
||||||
use niri::animation::ANIMATION_SLOWDOWN;
|
|
||||||
use smithay_view::SmithayView;
|
use smithay_view::SmithayView;
|
||||||
use tracing_subscriber::EnvFilter;
|
use tracing_subscriber::EnvFilter;
|
||||||
|
|
||||||
@@ -66,20 +62,23 @@ fn on_startup(_app: &adw::Application) {
|
|||||||
|
|
||||||
fn build_ui(app: &adw::Application) {
|
fn build_ui(app: &adw::Application) {
|
||||||
let stack = gtk::Stack::new();
|
let stack = gtk::Stack::new();
|
||||||
|
let anim_adjustment = gtk::Adjustment::new(1., 0., 10., 0.1, 0.5, 0.);
|
||||||
|
|
||||||
struct S {
|
struct S {
|
||||||
stack: gtk::Stack,
|
stack: gtk::Stack,
|
||||||
|
anim_adjustment: gtk::Adjustment,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl S {
|
impl S {
|
||||||
fn add<T: TestCase + 'static>(&self, make: impl Fn(Args) -> T + 'static, title: &str) {
|
fn add<T: TestCase + 'static>(&self, make: impl Fn(Args) -> T + 'static, title: &str) {
|
||||||
let view = SmithayView::new(make);
|
let view = SmithayView::new(make, &self.anim_adjustment);
|
||||||
self.stack.add_titled(&view, None, title);
|
self.stack.add_titled(&view, None, title);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let s = S {
|
let s = S {
|
||||||
stack: stack.clone(),
|
stack: stack.clone(),
|
||||||
|
anim_adjustment: anim_adjustment.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
s.add(Window::freeform, "Freeform Window");
|
s.add(Window::freeform, "Freeform Window");
|
||||||
@@ -133,9 +132,6 @@ fn build_ui(app: &adw::Application) {
|
|||||||
|
|
||||||
let content_headerbar = adw::HeaderBar::new();
|
let content_headerbar = adw::HeaderBar::new();
|
||||||
|
|
||||||
let anim_adjustment = gtk::Adjustment::new(1., 0., 10., 0.1, 0.5, 0.);
|
|
||||||
anim_adjustment
|
|
||||||
.connect_value_changed(|adj| ANIMATION_SLOWDOWN.store(adj.value(), Ordering::SeqCst));
|
|
||||||
let anim_scale = gtk::Scale::new(gtk::Orientation::Horizontal, Some(&anim_adjustment));
|
let anim_scale = gtk::Scale::new(gtk::Orientation::Horizontal, Some(&anim_adjustment));
|
||||||
anim_scale.set_hexpand(true);
|
anim_scale.set_hexpand(true);
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use gtk::glib;
|
use gtk::glib;
|
||||||
|
use gtk::prelude::*;
|
||||||
use gtk::subclass::prelude::*;
|
use gtk::subclass::prelude::*;
|
||||||
use smithay::utils::Size;
|
use smithay::utils::Size;
|
||||||
|
|
||||||
@@ -7,13 +8,13 @@ use crate::cases::{Args, TestCase};
|
|||||||
mod imp {
|
mod imp {
|
||||||
use std::cell::{Cell, OnceCell, RefCell};
|
use std::cell::{Cell, OnceCell, RefCell};
|
||||||
use std::ptr::null;
|
use std::ptr::null;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use anyhow::{ensure, Context};
|
use anyhow::{ensure, Context};
|
||||||
use gtk::gdk;
|
use gtk::gdk;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use niri::animation::Clock;
|
use niri::animation::Clock;
|
||||||
use niri::render_helpers::{resources, shaders};
|
use niri::render_helpers::{resources, shaders};
|
||||||
use niri::utils::get_monotonic_time;
|
|
||||||
use smithay::backend::egl::ffi::egl;
|
use smithay::backend::egl::ffi::egl;
|
||||||
use smithay::backend::egl::EGLContext;
|
use smithay::backend::egl::EGLContext;
|
||||||
use smithay::backend::renderer::gles::GlesRenderer;
|
use smithay::backend::renderer::gles::GlesRenderer;
|
||||||
@@ -31,7 +32,7 @@ mod imp {
|
|||||||
renderer: RefCell<Option<Result<GlesRenderer, ()>>>,
|
renderer: RefCell<Option<Result<GlesRenderer, ()>>>,
|
||||||
pub make_test_case: OnceCell<DynMakeTestCase>,
|
pub make_test_case: OnceCell<DynMakeTestCase>,
|
||||||
test_case: RefCell<Option<Box<dyn TestCase>>>,
|
test_case: RefCell<Option<Box<dyn TestCase>>>,
|
||||||
clock: RefCell<Clock>,
|
pub clock: RefCell<Clock>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[glib::object_subclass]
|
#[glib::object_subclass]
|
||||||
@@ -127,6 +128,10 @@ mod imp {
|
|||||||
|
|
||||||
let size = self.size.get();
|
let size = self.size.get();
|
||||||
|
|
||||||
|
let frame_clock = self.obj().frame_clock().unwrap();
|
||||||
|
let time = Duration::from_micros(frame_clock.frame_time() as u64);
|
||||||
|
self.clock.borrow_mut().set_unadjusted(time);
|
||||||
|
|
||||||
// Create the test case if missing.
|
// Create the test case if missing.
|
||||||
let mut case = self.test_case.borrow_mut();
|
let mut case = self.test_case.borrow_mut();
|
||||||
let case = case.get_or_insert_with(|| {
|
let case = case.get_or_insert_with(|| {
|
||||||
@@ -138,7 +143,7 @@ mod imp {
|
|||||||
make(args)
|
make(args)
|
||||||
});
|
});
|
||||||
|
|
||||||
case.advance_animations(get_monotonic_time());
|
case.advance_animations(self.clock.borrow_mut().now());
|
||||||
|
|
||||||
let rect: Rectangle<i32, Physical> = Rectangle::from_loc_and_size((0, 0), size);
|
let rect: Rectangle<i32, Physical> = Rectangle::from_loc_and_size((0, 0), size);
|
||||||
|
|
||||||
@@ -238,13 +243,33 @@ glib::wrapper! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SmithayView {
|
impl SmithayView {
|
||||||
pub fn new<T: TestCase + 'static>(make_test_case: impl Fn(Args) -> T + 'static) -> Self {
|
pub fn new<T: TestCase + 'static>(
|
||||||
|
make_test_case: impl Fn(Args) -> T + 'static,
|
||||||
|
anim_adjustment: >k::Adjustment,
|
||||||
|
) -> Self {
|
||||||
let obj: Self = glib::Object::builder().build();
|
let obj: Self = glib::Object::builder().build();
|
||||||
|
|
||||||
let make = move |args| Box::new(make_test_case(args)) as Box<dyn TestCase>;
|
let make = move |args| Box::new(make_test_case(args)) as Box<dyn TestCase>;
|
||||||
let make_test_case = Box::new(make) as _;
|
let make_test_case = Box::new(make) as _;
|
||||||
let _ = obj.imp().make_test_case.set(make_test_case);
|
let _ = obj.imp().make_test_case.set(make_test_case);
|
||||||
|
|
||||||
|
anim_adjustment.connect_value_changed({
|
||||||
|
let obj = obj.downgrade();
|
||||||
|
move |adj| {
|
||||||
|
if let Some(obj) = obj.upgrade() {
|
||||||
|
let mut clock = obj.imp().clock.borrow_mut();
|
||||||
|
let instantly = adj.value() == 0.0;
|
||||||
|
let rate = if instantly {
|
||||||
|
1.0
|
||||||
|
} else {
|
||||||
|
1.0 / adj.value().max(0.001)
|
||||||
|
};
|
||||||
|
clock.set_rate(rate);
|
||||||
|
clock.set_complete_instantly(instantly);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
obj
|
obj
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+176
-15
@@ -1,41 +1,202 @@
|
|||||||
use std::cell::Cell;
|
use std::cell::RefCell;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use crate::utils::get_monotonic_time;
|
use crate::utils::get_monotonic_time;
|
||||||
|
|
||||||
/// Clock that can have its time value overridden.
|
/// Shareable lazy clock that can change rate.
|
||||||
///
|
///
|
||||||
/// Can be cloned to share the same clock.
|
/// The clock will fetch the time once and then retain it until explicitly cleared with
|
||||||
|
/// [`Clock::clear`].
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default, Clone)]
|
||||||
pub struct Clock {
|
pub struct Clock {
|
||||||
time_override: Rc<Cell<Option<Duration>>>,
|
inner: Rc<RefCell<AdjustableClock>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
struct LazyClock {
|
||||||
|
time: Option<Duration>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clock that can adjust its rate.
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct AdjustableClock {
|
||||||
|
inner: LazyClock,
|
||||||
|
current_time: Duration,
|
||||||
|
last_seen_time: Duration,
|
||||||
|
rate: f64,
|
||||||
|
complete_instantly: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clock {
|
impl Clock {
|
||||||
/// Creates a new [`Clock`] with time override in place.
|
/// Creates a new clock with the given time.
|
||||||
pub fn with_override(time: Duration) -> Self {
|
pub fn with_time(time: Duration) -> Self {
|
||||||
|
let clock = AdjustableClock::new(LazyClock::with_time(time));
|
||||||
Self {
|
Self {
|
||||||
time_override: Rc::new(Cell::new(Some(time))),
|
inner: Rc::new(RefCell::new(clock)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the current time override.
|
/// Returns the current time.
|
||||||
pub fn set_time_override(&mut self, time: Option<Duration>) {
|
pub fn now(&self) -> Duration {
|
||||||
self.time_override.set(time);
|
self.inner.borrow_mut().now()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the current time.
|
/// Returns the underlying time not adjusted for rate change.
|
||||||
#[inline]
|
pub fn now_unadjusted(&self) -> Duration {
|
||||||
pub fn now(&self) -> Duration {
|
self.inner.borrow_mut().inner.now()
|
||||||
self.time_override.get().unwrap_or_else(get_monotonic_time)
|
}
|
||||||
|
|
||||||
|
/// Sets the unadjusted clock time.
|
||||||
|
pub fn set_unadjusted(&mut self, time: Duration) {
|
||||||
|
self.inner.borrow_mut().inner.set(time);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clears the stored time so it's re-fetched again next.
|
||||||
|
pub fn clear(&mut self) {
|
||||||
|
self.inner.borrow_mut().inner.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the clock rate.
|
||||||
|
pub fn rate(&self) -> f64 {
|
||||||
|
self.inner.borrow().rate()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the clock rate.
|
||||||
|
pub fn set_rate(&mut self, rate: f64) {
|
||||||
|
self.inner.borrow_mut().set_rate(rate);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether animations should complete instantly.
|
||||||
|
pub fn should_complete_instantly(&self) -> bool {
|
||||||
|
self.inner.borrow().should_complete_instantly()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets whether animations should complete instantly.
|
||||||
|
pub fn set_complete_instantly(&mut self, value: bool) {
|
||||||
|
self.inner.borrow_mut().set_complete_instantly(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for Clock {
|
impl PartialEq for Clock {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
Rc::ptr_eq(&self.time_override, &other.time_override)
|
Rc::ptr_eq(&self.inner, &other.inner)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Eq for Clock {}
|
impl Eq for Clock {}
|
||||||
|
|
||||||
|
impl LazyClock {
|
||||||
|
pub fn with_time(time: Duration) -> Self {
|
||||||
|
Self { time: Some(time) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear(&mut self) {
|
||||||
|
self.time = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set(&mut self, time: Duration) {
|
||||||
|
self.time = Some(time);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn now(&mut self) -> Duration {
|
||||||
|
*self.time.get_or_insert_with(get_monotonic_time)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AdjustableClock {
|
||||||
|
pub fn new(mut inner: LazyClock) -> Self {
|
||||||
|
let time = inner.now();
|
||||||
|
Self {
|
||||||
|
inner,
|
||||||
|
current_time: time,
|
||||||
|
last_seen_time: time,
|
||||||
|
rate: 1.,
|
||||||
|
complete_instantly: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rate(&self) -> f64 {
|
||||||
|
self.rate
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_rate(&mut self, rate: f64) {
|
||||||
|
self.rate = rate.clamp(0., 1000.);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn should_complete_instantly(&self) -> bool {
|
||||||
|
self.complete_instantly
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_complete_instantly(&mut self, value: bool) {
|
||||||
|
self.complete_instantly = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn now(&mut self) -> Duration {
|
||||||
|
let time = self.inner.now();
|
||||||
|
|
||||||
|
if self.last_seen_time == time {
|
||||||
|
return self.current_time;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.last_seen_time < time {
|
||||||
|
let delta = time - self.last_seen_time;
|
||||||
|
let delta = delta.mul_f64(self.rate);
|
||||||
|
self.current_time = self.current_time.saturating_add(delta);
|
||||||
|
} else {
|
||||||
|
let delta = self.last_seen_time - time;
|
||||||
|
let delta = delta.mul_f64(self.rate);
|
||||||
|
self.current_time = self.current_time.saturating_sub(delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.last_seen_time = time;
|
||||||
|
self.current_time
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for AdjustableClock {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new(LazyClock::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn frozen_clock() {
|
||||||
|
let mut clock = Clock::with_time(Duration::ZERO);
|
||||||
|
assert_eq!(clock.now(), Duration::ZERO);
|
||||||
|
|
||||||
|
clock.set_unadjusted(Duration::from_millis(100));
|
||||||
|
assert_eq!(clock.now(), Duration::from_millis(100));
|
||||||
|
|
||||||
|
clock.set_unadjusted(Duration::from_millis(200));
|
||||||
|
assert_eq!(clock.now(), Duration::from_millis(200));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rate_change() {
|
||||||
|
let mut clock = Clock::with_time(Duration::ZERO);
|
||||||
|
clock.set_rate(0.5);
|
||||||
|
|
||||||
|
clock.set_unadjusted(Duration::from_millis(100));
|
||||||
|
assert_eq!(clock.now_unadjusted(), Duration::from_millis(100));
|
||||||
|
assert_eq!(clock.now(), Duration::from_millis(50));
|
||||||
|
|
||||||
|
clock.set_unadjusted(Duration::from_millis(200));
|
||||||
|
assert_eq!(clock.now_unadjusted(), Duration::from_millis(200));
|
||||||
|
assert_eq!(clock.now(), Duration::from_millis(100));
|
||||||
|
|
||||||
|
clock.set_unadjusted(Duration::from_millis(150));
|
||||||
|
assert_eq!(clock.now_unadjusted(), Duration::from_millis(150));
|
||||||
|
assert_eq!(clock.now(), Duration::from_millis(75));
|
||||||
|
|
||||||
|
clock.set_rate(2.0);
|
||||||
|
|
||||||
|
clock.set_unadjusted(Duration::from_millis(250));
|
||||||
|
assert_eq!(clock.now_unadjusted(), Duration::from_millis(250));
|
||||||
|
assert_eq!(clock.now(), Duration::from_millis(275));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+33
-102
@@ -2,7 +2,6 @@ use std::time::Duration;
|
|||||||
|
|
||||||
use keyframe::functions::{EaseOutCubic, EaseOutQuad};
|
use keyframe::functions::{EaseOutCubic, EaseOutQuad};
|
||||||
use keyframe::EasingFunction;
|
use keyframe::EasingFunction;
|
||||||
use portable_atomic::{AtomicF64, Ordering};
|
|
||||||
|
|
||||||
mod spring;
|
mod spring;
|
||||||
pub use spring::{Spring, SpringParams};
|
pub use spring::{Spring, SpringParams};
|
||||||
@@ -10,8 +9,6 @@ pub use spring::{Spring, SpringParams};
|
|||||||
mod clock;
|
mod clock;
|
||||||
pub use clock::Clock;
|
pub use clock::Clock;
|
||||||
|
|
||||||
pub static ANIMATION_SLOWDOWN: AtomicF64 = AtomicF64::new(1.);
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Animation {
|
pub struct Animation {
|
||||||
from: f64,
|
from: f64,
|
||||||
@@ -24,7 +21,7 @@ pub struct Animation {
|
|||||||
/// Best effort; not always exactly precise.
|
/// Best effort; not always exactly precise.
|
||||||
clamped_duration: Duration,
|
clamped_duration: Duration,
|
||||||
start_time: Duration,
|
start_time: Duration,
|
||||||
current_time: Duration,
|
clock: Clock,
|
||||||
kind: Kind,
|
kind: Kind,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,23 +47,16 @@ pub enum Curve {
|
|||||||
|
|
||||||
impl Animation {
|
impl Animation {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
current_time: Duration,
|
clock: Clock,
|
||||||
from: f64,
|
from: f64,
|
||||||
to: f64,
|
to: f64,
|
||||||
initial_velocity: f64,
|
initial_velocity: f64,
|
||||||
config: niri_config::Animation,
|
config: niri_config::Animation,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
// Scale the velocity by slowdown to keep the touchpad gestures feeling right.
|
// Scale the velocity by rate to keep the touchpad gestures feeling right.
|
||||||
let initial_velocity = initial_velocity * ANIMATION_SLOWDOWN.load(Ordering::Relaxed);
|
let initial_velocity = initial_velocity / clock.rate().max(0.001);
|
||||||
|
|
||||||
let mut rv = Self::ease(
|
let mut rv = Self::ease(clock, from, to, initial_velocity, 0, Curve::EaseOutCubic);
|
||||||
current_time,
|
|
||||||
from,
|
|
||||||
to,
|
|
||||||
initial_velocity,
|
|
||||||
0,
|
|
||||||
Curve::EaseOutCubic,
|
|
||||||
);
|
|
||||||
if config.off {
|
if config.off {
|
||||||
rv.is_off = true;
|
rv.is_off = true;
|
||||||
return rv;
|
return rv;
|
||||||
@@ -85,7 +75,6 @@ impl Animation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let start_time = self.start_time;
|
let start_time = self.start_time;
|
||||||
let current_time = self.current_time;
|
|
||||||
|
|
||||||
match config.kind {
|
match config.kind {
|
||||||
niri_config::AnimationKind::Spring(p) => {
|
niri_config::AnimationKind::Spring(p) => {
|
||||||
@@ -97,11 +86,11 @@ impl Animation {
|
|||||||
initial_velocity: self.initial_velocity,
|
initial_velocity: self.initial_velocity,
|
||||||
params,
|
params,
|
||||||
};
|
};
|
||||||
*self = Self::spring(current_time, spring);
|
*self = Self::spring(self.clock.clone(), spring);
|
||||||
}
|
}
|
||||||
niri_config::AnimationKind::Easing(p) => {
|
niri_config::AnimationKind::Easing(p) => {
|
||||||
*self = Self::ease(
|
*self = Self::ease(
|
||||||
current_time,
|
self.clock.clone(),
|
||||||
self.from,
|
self.from,
|
||||||
self.to,
|
self.to,
|
||||||
self.initial_velocity,
|
self.initial_velocity,
|
||||||
@@ -112,27 +101,20 @@ impl Animation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.start_time = start_time;
|
self.start_time = start_time;
|
||||||
self.current_time = current_time;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Restarts the animation using the previous config.
|
/// Restarts the animation using the previous config.
|
||||||
pub fn restarted(
|
pub fn restarted(&self, from: f64, to: f64, initial_velocity: f64) -> Self {
|
||||||
&self,
|
|
||||||
current_time: Duration,
|
|
||||||
from: f64,
|
|
||||||
to: f64,
|
|
||||||
initial_velocity: f64,
|
|
||||||
) -> Self {
|
|
||||||
if self.is_off {
|
if self.is_off {
|
||||||
return self.clone();
|
return self.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scale the velocity by slowdown to keep the touchpad gestures feeling right.
|
// Scale the velocity by rate to keep the touchpad gestures feeling right.
|
||||||
let initial_velocity = initial_velocity * ANIMATION_SLOWDOWN.load(Ordering::Relaxed);
|
let initial_velocity = initial_velocity / self.clock.rate().max(0.001);
|
||||||
|
|
||||||
match self.kind {
|
match self.kind {
|
||||||
Kind::Easing { curve } => Self::ease(
|
Kind::Easing { curve } => Self::ease(
|
||||||
current_time,
|
self.clock.clone(),
|
||||||
from,
|
from,
|
||||||
to,
|
to,
|
||||||
initial_velocity,
|
initial_velocity,
|
||||||
@@ -146,7 +128,7 @@ impl Animation {
|
|||||||
initial_velocity: self.initial_velocity,
|
initial_velocity: self.initial_velocity,
|
||||||
params: spring.params,
|
params: spring.params,
|
||||||
};
|
};
|
||||||
Self::spring(current_time, spring)
|
Self::spring(self.clock.clone(), spring)
|
||||||
}
|
}
|
||||||
Kind::Deceleration {
|
Kind::Deceleration {
|
||||||
initial_velocity,
|
initial_velocity,
|
||||||
@@ -154,7 +136,7 @@ impl Animation {
|
|||||||
} => {
|
} => {
|
||||||
let threshold = 0.001; // FIXME
|
let threshold = 0.001; // FIXME
|
||||||
Self::decelerate(
|
Self::decelerate(
|
||||||
current_time,
|
self.clock.clone(),
|
||||||
from,
|
from,
|
||||||
initial_velocity,
|
initial_velocity,
|
||||||
deceleration_rate,
|
deceleration_rate,
|
||||||
@@ -165,7 +147,7 @@ impl Animation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn ease(
|
pub fn ease(
|
||||||
current_time: Duration,
|
clock: Clock,
|
||||||
from: f64,
|
from: f64,
|
||||||
to: f64,
|
to: f64,
|
||||||
initial_velocity: f64,
|
initial_velocity: f64,
|
||||||
@@ -183,13 +165,13 @@ impl Animation {
|
|||||||
duration,
|
duration,
|
||||||
// Our current curves never overshoot.
|
// Our current curves never overshoot.
|
||||||
clamped_duration: duration,
|
clamped_duration: duration,
|
||||||
start_time: current_time,
|
start_time: clock.now(),
|
||||||
current_time,
|
clock,
|
||||||
kind,
|
kind,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn spring(current_time: Duration, spring: Spring) -> Self {
|
pub fn spring(clock: Clock, spring: Spring) -> Self {
|
||||||
let _span = tracy_client::span!("Animation::spring");
|
let _span = tracy_client::span!("Animation::spring");
|
||||||
|
|
||||||
let duration = spring.duration();
|
let duration = spring.duration();
|
||||||
@@ -203,14 +185,14 @@ impl Animation {
|
|||||||
is_off: false,
|
is_off: false,
|
||||||
duration,
|
duration,
|
||||||
clamped_duration,
|
clamped_duration,
|
||||||
start_time: current_time,
|
start_time: clock.now(),
|
||||||
current_time,
|
clock,
|
||||||
kind,
|
kind,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn decelerate(
|
pub fn decelerate(
|
||||||
current_time: Duration,
|
clock: Clock,
|
||||||
from: f64,
|
from: f64,
|
||||||
initial_velocity: f64,
|
initial_velocity: f64,
|
||||||
deceleration_rate: f64,
|
deceleration_rate: f64,
|
||||||
@@ -238,77 +220,26 @@ impl Animation {
|
|||||||
is_off: false,
|
is_off: false,
|
||||||
duration,
|
duration,
|
||||||
clamped_duration: duration,
|
clamped_duration: duration,
|
||||||
start_time: current_time,
|
start_time: clock.now(),
|
||||||
current_time,
|
clock,
|
||||||
kind,
|
kind,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_current_time(&mut self, time: Duration) {
|
|
||||||
if self.duration.is_zero() {
|
|
||||||
self.current_time = time;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let end_time = self.start_time + self.duration;
|
|
||||||
if end_time <= self.current_time {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let slowdown = ANIMATION_SLOWDOWN.load(Ordering::Relaxed);
|
|
||||||
if slowdown <= f64::EPSILON {
|
|
||||||
// Zero slowdown will cause the animation to end right away.
|
|
||||||
self.current_time = end_time;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We can't change current_time (since the incoming time values are always real-time), so
|
|
||||||
// apply the slowdown by shifting the start time to compensate.
|
|
||||||
if self.current_time <= time {
|
|
||||||
let delta = time - self.current_time;
|
|
||||||
|
|
||||||
let max_delta = end_time - self.current_time;
|
|
||||||
let min_slowdown = delta.as_secs_f64() / max_delta.as_secs_f64();
|
|
||||||
if slowdown <= min_slowdown {
|
|
||||||
// Our slowdown value will cause the animation to end right away.
|
|
||||||
self.current_time = end_time;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let adjusted_delta = delta.div_f64(slowdown);
|
|
||||||
if adjusted_delta >= delta {
|
|
||||||
self.start_time -= adjusted_delta - delta;
|
|
||||||
} else {
|
|
||||||
self.start_time += delta - adjusted_delta;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let delta = self.current_time - time;
|
|
||||||
|
|
||||||
let min_slowdown = delta.as_secs_f64() / self.current_time.as_secs_f64();
|
|
||||||
if slowdown <= min_slowdown {
|
|
||||||
// Current time was about to jump to before the animation had started; let's just
|
|
||||||
// cancel the animation in this case.
|
|
||||||
self.current_time = end_time;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let adjusted_delta = delta.div_f64(slowdown);
|
|
||||||
if adjusted_delta >= delta {
|
|
||||||
self.start_time += adjusted_delta - delta;
|
|
||||||
} else {
|
|
||||||
self.start_time -= delta - adjusted_delta;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.current_time = time;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_done(&self) -> bool {
|
pub fn is_done(&self) -> bool {
|
||||||
self.current_time >= self.start_time + self.duration
|
if self.clock.should_complete_instantly() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.clock.now() >= self.start_time + self.duration
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_clamped_done(&self) -> bool {
|
pub fn is_clamped_done(&self) -> bool {
|
||||||
self.current_time >= self.start_time + self.clamped_duration
|
if self.clock.should_complete_instantly() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.clock.now() >= self.start_time + self.clamped_duration
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn value(&self) -> f64 {
|
pub fn value(&self) -> f64 {
|
||||||
@@ -316,7 +247,7 @@ impl Animation {
|
|||||||
return self.to;
|
return self.to;
|
||||||
}
|
}
|
||||||
|
|
||||||
let passed = self.current_time.saturating_sub(self.start_time);
|
let passed = self.clock.now().saturating_sub(self.start_time);
|
||||||
|
|
||||||
match self.kind {
|
match self.kind {
|
||||||
Kind::Easing { curve } => {
|
Kind::Easing { curve } => {
|
||||||
|
|||||||
+2
-5
@@ -83,11 +83,8 @@ impl State {
|
|||||||
{
|
{
|
||||||
let _span = tracy_client::span!("process_input_event");
|
let _span = tracy_client::span!("process_input_event");
|
||||||
|
|
||||||
// A bit of a hack, but animation end runs some logic (i.e. workspace clean-up) and it
|
// Make sure some logic like workspace clean-up has a chance to run before doing actions.
|
||||||
// doesn't always trigger due to damage, etc. So run it here right before it might prove
|
self.niri.advance_animations();
|
||||||
// important. Besides, animations affect the input, so it's best to have up-to-date values
|
|
||||||
// here.
|
|
||||||
self.niri.advance_animations(get_monotonic_time());
|
|
||||||
|
|
||||||
if self.niri.monitors_active {
|
if self.niri.monitors_active {
|
||||||
// Notify the idle-notifier of activity.
|
// Notify the idle-notifier of activity.
|
||||||
|
|||||||
@@ -314,6 +314,9 @@ async fn process(ctx: &ClientCtx, request: Request) -> Reply {
|
|||||||
|
|
||||||
let action = niri_config::Action::from(action);
|
let action = niri_config::Action::from(action);
|
||||||
ctx.event_loop.insert_idle(move |state| {
|
ctx.event_loop.insert_idle(move |state| {
|
||||||
|
// Make sure some logic like workspace clean-up has a chance to run before doing
|
||||||
|
// actions.
|
||||||
|
state.niri.advance_animations();
|
||||||
state.do_action(action, false);
|
state.do_action(action, false);
|
||||||
let _ = tx.send_blocking(());
|
let _ = tx.send_blocking(());
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use anyhow::Context as _;
|
use anyhow::Context as _;
|
||||||
use glam::{Mat3, Vec2};
|
use glam::{Mat3, Vec2};
|
||||||
@@ -138,15 +137,15 @@ impl ClosingWindow {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn advance_animations(&mut self, current_time: Duration) {
|
pub fn advance_animations(&mut self) {
|
||||||
match &mut self.anim_state {
|
match &mut self.anim_state {
|
||||||
AnimationState::Waiting { blocker, anim } => {
|
AnimationState::Waiting { blocker, anim } => {
|
||||||
if blocker.state() != BlockerState::Pending {
|
if blocker.state() != BlockerState::Pending {
|
||||||
let anim = anim.restarted(current_time, 0., 1., 0.);
|
let anim = anim.restarted(0., 1., 0.);
|
||||||
self.anim_state = AnimationState::Animating(anim);
|
self.anim_state = AnimationState::Animating(anim);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AnimationState::Animating(anim) => anim.set_current_time(current_time),
|
AnimationState::Animating(_anim) => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+19
-9
@@ -219,6 +219,8 @@ pub struct Layout<W: LayoutElement> {
|
|||||||
interactive_move: Option<InteractiveMoveState<W>>,
|
interactive_move: Option<InteractiveMoveState<W>>,
|
||||||
/// Clock for driving animations.
|
/// Clock for driving animations.
|
||||||
clock: Clock,
|
clock: Clock,
|
||||||
|
/// Time that we last updated render elements for.
|
||||||
|
update_render_elements_time: Duration,
|
||||||
/// Configurable properties of the layout.
|
/// Configurable properties of the layout.
|
||||||
options: Rc<Options>,
|
options: Rc<Options>,
|
||||||
}
|
}
|
||||||
@@ -447,6 +449,7 @@ impl<W: LayoutElement> Layout<W> {
|
|||||||
last_active_workspace_id: HashMap::new(),
|
last_active_workspace_id: HashMap::new(),
|
||||||
interactive_move: None,
|
interactive_move: None,
|
||||||
clock,
|
clock,
|
||||||
|
update_render_elements_time: Duration::ZERO,
|
||||||
options: Rc::new(options),
|
options: Rc::new(options),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -468,6 +471,7 @@ impl<W: LayoutElement> Layout<W> {
|
|||||||
last_active_workspace_id: HashMap::new(),
|
last_active_workspace_id: HashMap::new(),
|
||||||
interactive_move: None,
|
interactive_move: None,
|
||||||
clock,
|
clock,
|
||||||
|
update_render_elements_time: Duration::ZERO,
|
||||||
options: opts,
|
options: opts,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2194,22 +2198,22 @@ impl<W: LayoutElement> Layout<W> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn advance_animations(&mut self, current_time: Duration) {
|
pub fn advance_animations(&mut self) {
|
||||||
let _span = tracy_client::span!("Layout::advance_animations");
|
let _span = tracy_client::span!("Layout::advance_animations");
|
||||||
|
|
||||||
if let Some(InteractiveMoveState::Moving(move_)) = &mut self.interactive_move {
|
if let Some(InteractiveMoveState::Moving(move_)) = &mut self.interactive_move {
|
||||||
move_.tile.advance_animations(current_time);
|
move_.tile.advance_animations();
|
||||||
}
|
}
|
||||||
|
|
||||||
match &mut self.monitor_set {
|
match &mut self.monitor_set {
|
||||||
MonitorSet::Normal { monitors, .. } => {
|
MonitorSet::Normal { monitors, .. } => {
|
||||||
for mon in monitors {
|
for mon in monitors {
|
||||||
mon.advance_animations(current_time);
|
mon.advance_animations();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MonitorSet::NoOutputs { workspaces, .. } => {
|
MonitorSet::NoOutputs { workspaces, .. } => {
|
||||||
for ws in workspaces {
|
for ws in workspaces {
|
||||||
ws.advance_animations(current_time);
|
ws.advance_animations();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2242,6 +2246,8 @@ impl<W: LayoutElement> Layout<W> {
|
|||||||
pub fn update_render_elements(&mut self, output: Option<&Output>) {
|
pub fn update_render_elements(&mut self, output: Option<&Output>) {
|
||||||
let _span = tracy_client::span!("Layout::update_render_elements");
|
let _span = tracy_client::span!("Layout::update_render_elements");
|
||||||
|
|
||||||
|
self.update_render_elements_time = self.clock.now();
|
||||||
|
|
||||||
if let Some(InteractiveMoveState::Moving(move_)) = &mut self.interactive_move {
|
if let Some(InteractiveMoveState::Moving(move_)) = &mut self.interactive_move {
|
||||||
if output.map_or(true, |output| move_.output == *output) {
|
if output.map_or(true, |output| move_.output == *output) {
|
||||||
let pos_within_output = move_.tile_render_location();
|
let pos_within_output = move_.tile_render_location();
|
||||||
@@ -3475,6 +3481,10 @@ impl<W: LayoutElement> Layout<W> {
|
|||||||
output: &Output,
|
output: &Output,
|
||||||
target: RenderTarget,
|
target: RenderTarget,
|
||||||
) -> impl Iterator<Item = TileRenderElement<R>> {
|
) -> impl Iterator<Item = TileRenderElement<R>> {
|
||||||
|
if self.update_render_elements_time != self.clock.now() {
|
||||||
|
error!("clock moved between updating render elements and rendering");
|
||||||
|
}
|
||||||
|
|
||||||
let mut rv = None;
|
let mut rv = None;
|
||||||
|
|
||||||
if let Some(InteractiveMoveState::Moving(move_)) = &self.interactive_move {
|
if let Some(InteractiveMoveState::Moving(move_)) = &self.interactive_move {
|
||||||
@@ -3653,7 +3663,7 @@ mod tests {
|
|||||||
|
|
||||||
impl<W: LayoutElement> Default for Layout<W> {
|
impl<W: LayoutElement> Default for Layout<W> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::with_options(Clock::with_override(Duration::ZERO), Default::default())
|
Self::with_options(Clock::with_time(Duration::ZERO), Default::default())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4553,14 +4563,14 @@ mod tests {
|
|||||||
layout.refresh(is_active);
|
layout.refresh(is_active);
|
||||||
}
|
}
|
||||||
Op::AdvanceAnimations { msec_delta } => {
|
Op::AdvanceAnimations { msec_delta } => {
|
||||||
let mut now = layout.clock.now();
|
let mut now = layout.clock.now_unadjusted();
|
||||||
if msec_delta >= 0 {
|
if msec_delta >= 0 {
|
||||||
now = now.saturating_add(Duration::from_millis(msec_delta as u64));
|
now = now.saturating_add(Duration::from_millis(msec_delta as u64));
|
||||||
} else {
|
} else {
|
||||||
now = now.saturating_sub(Duration::from_millis(-msec_delta as u64));
|
now = now.saturating_sub(Duration::from_millis(-msec_delta as u64));
|
||||||
}
|
}
|
||||||
layout.clock.set_time_override(Some(now));
|
layout.clock.set_unadjusted(now);
|
||||||
layout.advance_animations(now);
|
layout.advance_animations();
|
||||||
}
|
}
|
||||||
Op::MoveWorkspaceToOutput(id) => {
|
Op::MoveWorkspaceToOutput(id) => {
|
||||||
let name = format!("output{id}");
|
let name = format!("output{id}");
|
||||||
@@ -4674,7 +4684,7 @@ mod tests {
|
|||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn check_ops_with_options(options: Options, ops: &[Op]) {
|
fn check_ops_with_options(options: Options, ops: &[Op]) {
|
||||||
let mut layout = Layout::with_options(Clock::with_override(Duration::ZERO), options);
|
let mut layout = Layout::with_options(Clock::with_time(Duration::ZERO), options);
|
||||||
|
|
||||||
for op in ops {
|
for op in ops {
|
||||||
op.apply(&mut layout);
|
op.apply(&mut layout);
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ impl<W: LayoutElement> Monitor<W> {
|
|||||||
self.active_workspace_idx = idx;
|
self.active_workspace_idx = idx;
|
||||||
|
|
||||||
self.workspace_switch = Some(WorkspaceSwitch::Animation(Animation::new(
|
self.workspace_switch = Some(WorkspaceSwitch::Animation(Animation::new(
|
||||||
self.clock.now(),
|
self.clock.clone(),
|
||||||
current_idx,
|
current_idx,
|
||||||
idx as f64,
|
idx as f64,
|
||||||
0.,
|
0.,
|
||||||
@@ -734,9 +734,8 @@ impl<W: LayoutElement> Monitor<W> {
|
|||||||
Some(column.tiles[column.active_tile_idx].window())
|
Some(column.tiles[column.active_tile_idx].window())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn advance_animations(&mut self, current_time: Duration) {
|
pub fn advance_animations(&mut self) {
|
||||||
if let Some(WorkspaceSwitch::Animation(anim)) = &mut self.workspace_switch {
|
if let Some(WorkspaceSwitch::Animation(anim)) = &mut self.workspace_switch {
|
||||||
anim.set_current_time(current_time);
|
|
||||||
if anim.is_done() {
|
if anim.is_done() {
|
||||||
self.workspace_switch = None;
|
self.workspace_switch = None;
|
||||||
self.clean_up_workspaces();
|
self.clean_up_workspaces();
|
||||||
@@ -744,7 +743,7 @@ impl<W: LayoutElement> Monitor<W> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for ws in &mut self.workspaces {
|
for ws in &mut self.workspaces {
|
||||||
ws.advance_animations(current_time);
|
ws.advance_animations();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1112,7 +1111,7 @@ impl<W: LayoutElement> Monitor<W> {
|
|||||||
|
|
||||||
self.active_workspace_idx = new_idx;
|
self.active_workspace_idx = new_idx;
|
||||||
self.workspace_switch = Some(WorkspaceSwitch::Animation(Animation::new(
|
self.workspace_switch = Some(WorkspaceSwitch::Animation(Animation::new(
|
||||||
self.clock.now(),
|
self.clock.clone(),
|
||||||
gesture.current_idx,
|
gesture.current_idx,
|
||||||
new_idx as f64,
|
new_idx as f64,
|
||||||
velocity,
|
velocity,
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use anyhow::Context as _;
|
use anyhow::Context as _;
|
||||||
use glam::{Mat3, Vec2};
|
use glam::{Mat3, Vec2};
|
||||||
@@ -41,9 +40,7 @@ impl OpenAnimation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn advance_animations(&mut self, current_time: Duration) {
|
pub fn advance_animations(&mut self) {}
|
||||||
self.anim.set_current_time(current_time);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_done(&self) -> bool {
|
pub fn is_done(&self) -> bool {
|
||||||
self.anim.is_done()
|
self.anim.is_done()
|
||||||
|
|||||||
+7
-12
@@ -1,5 +1,4 @@
|
|||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use niri_config::{Color, CornerRadius, GradientInterpolation};
|
use niri_config::{Color, CornerRadius, GradientInterpolation};
|
||||||
use smithay::backend::allocator::Fourcc;
|
use smithay::backend::allocator::Fourcc;
|
||||||
@@ -185,7 +184,7 @@ impl<W: LayoutElement> Tile<W> {
|
|||||||
let change = f64::max(change.x.abs(), change.y.abs());
|
let change = f64::max(change.x.abs(), change.y.abs());
|
||||||
if change > RESIZE_ANIMATION_THRESHOLD {
|
if change > RESIZE_ANIMATION_THRESHOLD {
|
||||||
let anim = Animation::new(
|
let anim = Animation::new(
|
||||||
self.clock.now(),
|
self.clock.clone(),
|
||||||
0.,
|
0.,
|
||||||
1.,
|
1.,
|
||||||
0.,
|
0.,
|
||||||
@@ -218,29 +217,25 @@ impl<W: LayoutElement> Tile<W> {
|
|||||||
self.rounded_corner_damage.set_size(window_size);
|
self.rounded_corner_damage.set_size(window_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn advance_animations(&mut self, current_time: Duration) {
|
pub fn advance_animations(&mut self) {
|
||||||
if let Some(open) = &mut self.open_animation {
|
if let Some(open) = &mut self.open_animation {
|
||||||
open.advance_animations(current_time);
|
|
||||||
if open.is_done() {
|
if open.is_done() {
|
||||||
self.open_animation = None;
|
self.open_animation = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(resize) = &mut self.resize_animation {
|
if let Some(resize) = &mut self.resize_animation {
|
||||||
resize.anim.set_current_time(current_time);
|
|
||||||
if resize.anim.is_done() {
|
if resize.anim.is_done() {
|
||||||
self.resize_animation = None;
|
self.resize_animation = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(move_) = &mut self.move_x_animation {
|
if let Some(move_) = &mut self.move_x_animation {
|
||||||
move_.anim.set_current_time(current_time);
|
|
||||||
if move_.anim.is_done() {
|
if move_.anim.is_done() {
|
||||||
self.move_x_animation = None;
|
self.move_x_animation = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(move_) = &mut self.move_y_animation {
|
if let Some(move_) = &mut self.move_y_animation {
|
||||||
move_.anim.set_current_time(current_time);
|
|
||||||
if move_.anim.is_done() {
|
if move_.anim.is_done() {
|
||||||
self.move_y_animation = None;
|
self.move_y_animation = None;
|
||||||
}
|
}
|
||||||
@@ -326,7 +321,7 @@ impl<W: LayoutElement> Tile<W> {
|
|||||||
|
|
||||||
pub fn start_open_animation(&mut self) {
|
pub fn start_open_animation(&mut self) {
|
||||||
self.open_animation = Some(OpenAnimation::new(Animation::new(
|
self.open_animation = Some(OpenAnimation::new(Animation::new(
|
||||||
self.clock.now(),
|
self.clock.clone(),
|
||||||
0.,
|
0.,
|
||||||
1.,
|
1.,
|
||||||
0.,
|
0.,
|
||||||
@@ -353,8 +348,8 @@ impl<W: LayoutElement> Tile<W> {
|
|||||||
// Preserve the previous config if ongoing.
|
// Preserve the previous config if ongoing.
|
||||||
let anim = self.move_x_animation.take().map(|move_| move_.anim);
|
let anim = self.move_x_animation.take().map(|move_| move_.anim);
|
||||||
let anim = anim
|
let anim = anim
|
||||||
.map(|anim| anim.restarted(self.clock.now(), 1., 0., 0.))
|
.map(|anim| anim.restarted(1., 0., 0.))
|
||||||
.unwrap_or_else(|| Animation::new(self.clock.now(), 1., 0., 0., config));
|
.unwrap_or_else(|| Animation::new(self.clock.clone(), 1., 0., 0., config));
|
||||||
|
|
||||||
self.move_x_animation = Some(MoveAnimation {
|
self.move_x_animation = Some(MoveAnimation {
|
||||||
anim,
|
anim,
|
||||||
@@ -372,8 +367,8 @@ impl<W: LayoutElement> Tile<W> {
|
|||||||
// Preserve the previous config if ongoing.
|
// Preserve the previous config if ongoing.
|
||||||
let anim = self.move_y_animation.take().map(|move_| move_.anim);
|
let anim = self.move_y_animation.take().map(|move_| move_.anim);
|
||||||
let anim = anim
|
let anim = anim
|
||||||
.map(|anim| anim.restarted(self.clock.now(), 1., 0., 0.))
|
.map(|anim| anim.restarted(1., 0., 0.))
|
||||||
.unwrap_or_else(|| Animation::new(self.clock.now(), 1., 0., 0., config));
|
.unwrap_or_else(|| Animation::new(self.clock.clone(), 1., 0., 0., config));
|
||||||
|
|
||||||
self.move_y_animation = Some(MoveAnimation {
|
self.move_y_animation = Some(MoveAnimation {
|
||||||
anim,
|
anim,
|
||||||
|
|||||||
+170
-169
@@ -84,10 +84,7 @@ pub struct Workspace<W: LayoutElement> {
|
|||||||
/// Any gaps, including left padding from work area left exclusive zone, is handled
|
/// Any gaps, including left padding from work area left exclusive zone, is handled
|
||||||
/// with this view offset (rather than added as a constant elsewhere in the code). This allows
|
/// with this view offset (rather than added as a constant elsewhere in the code). This allows
|
||||||
/// for natural handling of fullscreen windows, which must ignore work area padding.
|
/// for natural handling of fullscreen windows, which must ignore work area padding.
|
||||||
view_offset: f64,
|
view_offset: ViewOffset,
|
||||||
|
|
||||||
/// Adjustment of the view offset, if one is currently ongoing.
|
|
||||||
view_offset_adj: Option<ViewOffsetAdjustment>,
|
|
||||||
|
|
||||||
/// Whether to activate the previous, rather than the next, column upon column removal.
|
/// Whether to activate the previous, rather than the next, column upon column removal.
|
||||||
///
|
///
|
||||||
@@ -188,8 +185,12 @@ struct ColumnData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum ViewOffsetAdjustment {
|
enum ViewOffset {
|
||||||
|
/// The view offset is static.
|
||||||
|
Static(f64),
|
||||||
|
/// The view offset is animating.
|
||||||
Animation(Animation),
|
Animation(Animation),
|
||||||
|
/// The view offset is controlled by the ongoing gesture.
|
||||||
Gesture(ViewGesture),
|
Gesture(ViewGesture),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,7 +200,7 @@ struct ViewGesture {
|
|||||||
tracker: SwipeTracker,
|
tracker: SwipeTracker,
|
||||||
delta_from_tracker: f64,
|
delta_from_tracker: f64,
|
||||||
// The view offset we'll use if needed for activate_prev_column_on_removal.
|
// The view offset we'll use if needed for activate_prev_column_on_removal.
|
||||||
static_view_offset: f64,
|
stationary_view_offset: f64,
|
||||||
/// Whether the gesture is controlled by the touchpad.
|
/// Whether the gesture is controlled by the touchpad.
|
||||||
is_touchpad: bool,
|
is_touchpad: bool,
|
||||||
}
|
}
|
||||||
@@ -325,17 +326,70 @@ impl OutputId {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ViewOffsetAdjustment {
|
impl ViewOffset {
|
||||||
|
/// Returns the current view offset.
|
||||||
|
pub fn current(&self) -> f64 {
|
||||||
|
match self {
|
||||||
|
ViewOffset::Static(offset) => *offset,
|
||||||
|
ViewOffset::Animation(anim) => anim.value(),
|
||||||
|
ViewOffset::Gesture(gesture) => gesture.current_view_offset,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the target view offset suitable for computing the new view offset.
|
||||||
|
pub fn target(&self) -> f64 {
|
||||||
|
match self {
|
||||||
|
ViewOffset::Static(offset) => *offset,
|
||||||
|
ViewOffset::Animation(anim) => anim.to(),
|
||||||
|
// This can be used for example if a gesture is interrupted.
|
||||||
|
ViewOffset::Gesture(gesture) => gesture.current_view_offset,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a view offset value suitable for saving and later restoration.
|
||||||
|
///
|
||||||
|
/// This means that it shouldn't return an in-progress animation or gesture value.
|
||||||
|
fn stationary(&self) -> f64 {
|
||||||
|
match self {
|
||||||
|
ViewOffset::Static(offset) => *offset,
|
||||||
|
// For animations we can return the final value.
|
||||||
|
ViewOffset::Animation(anim) => anim.to(),
|
||||||
|
ViewOffset::Gesture(gesture) => gesture.stationary_view_offset,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_static(&self) -> bool {
|
||||||
|
matches!(self, Self::Static(_))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_animation(&self) -> bool {
|
pub fn is_animation(&self) -> bool {
|
||||||
matches!(self, Self::Animation(_))
|
matches!(self, Self::Animation(_))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn target_view_offset(&self) -> f64 {
|
pub fn is_gesture(&self) -> bool {
|
||||||
|
matches!(self, Self::Gesture(_))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn offset(&mut self, delta: f64) {
|
||||||
match self {
|
match self {
|
||||||
ViewOffsetAdjustment::Animation(anim) => anim.to(),
|
ViewOffset::Static(offset) => *offset += delta,
|
||||||
ViewOffsetAdjustment::Gesture(gesture) => gesture.current_view_offset,
|
ViewOffset::Animation(anim) => anim.offset(delta),
|
||||||
|
ViewOffset::Gesture(_gesture) => {
|
||||||
|
// Is this needed?
|
||||||
|
error!("cancel gesture before offsetting");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn cancel_gesture(&mut self) {
|
||||||
|
if let ViewOffset::Gesture(gesture) = self {
|
||||||
|
*self = ViewOffset::Static(gesture.current_view_offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stop_anim_and_gesture(&mut self) {
|
||||||
|
*self = ViewOffset::Static(self.current());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ColumnData {
|
impl ColumnData {
|
||||||
@@ -442,8 +496,7 @@ impl<W: LayoutElement> Workspace<W> {
|
|||||||
data: vec![],
|
data: vec![],
|
||||||
active_column_idx: 0,
|
active_column_idx: 0,
|
||||||
interactive_resize: None,
|
interactive_resize: None,
|
||||||
view_offset: 0.,
|
view_offset: ViewOffset::Static(0.),
|
||||||
view_offset_adj: None,
|
|
||||||
activate_prev_column_on_removal: None,
|
activate_prev_column_on_removal: None,
|
||||||
view_offset_before_fullscreen: None,
|
view_offset_before_fullscreen: None,
|
||||||
closing_windows: vec![],
|
closing_windows: vec![],
|
||||||
@@ -484,8 +537,7 @@ impl<W: LayoutElement> Workspace<W> {
|
|||||||
data: vec![],
|
data: vec![],
|
||||||
active_column_idx: 0,
|
active_column_idx: 0,
|
||||||
interactive_resize: None,
|
interactive_resize: None,
|
||||||
view_offset: 0.,
|
view_offset: ViewOffset::Static(0.),
|
||||||
view_offset_adj: None,
|
|
||||||
activate_prev_column_on_removal: None,
|
activate_prev_column_on_removal: None,
|
||||||
view_offset_before_fullscreen: None,
|
view_offset_before_fullscreen: None,
|
||||||
closing_windows: vec![],
|
closing_windows: vec![],
|
||||||
@@ -524,37 +576,31 @@ impl<W: LayoutElement> Workspace<W> {
|
|||||||
|| (self.options.always_center_single_column && self.columns.len() <= 1)
|
|| (self.options.always_center_single_column && self.columns.len() <= 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn advance_animations(&mut self, current_time: Duration) {
|
pub fn advance_animations(&mut self) {
|
||||||
if let Some(ViewOffsetAdjustment::Animation(anim)) = &mut self.view_offset_adj {
|
if let ViewOffset::Animation(anim) = &self.view_offset {
|
||||||
anim.set_current_time(current_time);
|
|
||||||
self.view_offset = anim.value();
|
|
||||||
if anim.is_done() {
|
if anim.is_done() {
|
||||||
self.view_offset_adj = None;
|
self.view_offset = ViewOffset::Static(anim.to());
|
||||||
}
|
}
|
||||||
} else if let Some(ViewOffsetAdjustment::Gesture(gesture)) = &self.view_offset_adj {
|
|
||||||
self.view_offset = gesture.current_view_offset;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for col in &mut self.columns {
|
for col in &mut self.columns {
|
||||||
col.advance_animations(current_time);
|
col.advance_animations();
|
||||||
}
|
}
|
||||||
|
|
||||||
self.closing_windows.retain_mut(|closing| {
|
self.closing_windows.retain_mut(|closing| {
|
||||||
closing.advance_animations(current_time);
|
closing.advance_animations();
|
||||||
closing.are_animations_ongoing()
|
closing.are_animations_ongoing()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn are_animations_ongoing(&self) -> bool {
|
pub fn are_animations_ongoing(&self) -> bool {
|
||||||
self.view_offset_adj
|
self.view_offset.is_animation()
|
||||||
.as_ref()
|
|
||||||
.is_some_and(|s| s.is_animation())
|
|
||||||
|| self.columns.iter().any(Column::are_animations_ongoing)
|
|| self.columns.iter().any(Column::are_animations_ongoing)
|
||||||
|| !self.closing_windows.is_empty()
|
|| !self.closing_windows.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn are_transitions_ongoing(&self) -> bool {
|
pub fn are_transitions_ongoing(&self) -> bool {
|
||||||
self.view_offset_adj.is_some()
|
!self.view_offset.is_static()
|
||||||
|| self.columns.iter().any(Column::are_animations_ongoing)
|
|| self.columns.iter().any(Column::are_animations_ongoing)
|
||||||
|| !self.closing_windows.is_empty()
|
|| !self.closing_windows.is_empty()
|
||||||
}
|
}
|
||||||
@@ -786,7 +832,7 @@ impl<W: LayoutElement> Workspace<W> {
|
|||||||
|
|
||||||
fn compute_new_view_offset_fit(
|
fn compute_new_view_offset_fit(
|
||||||
&self,
|
&self,
|
||||||
current_x: f64,
|
target_x: Option<f64>,
|
||||||
col_x: f64,
|
col_x: f64,
|
||||||
width: f64,
|
width: f64,
|
||||||
is_fullscreen: bool,
|
is_fullscreen: bool,
|
||||||
@@ -795,14 +841,10 @@ impl<W: LayoutElement> Workspace<W> {
|
|||||||
return 0.;
|
return 0.;
|
||||||
}
|
}
|
||||||
|
|
||||||
let final_x = if let Some(ViewOffsetAdjustment::Animation(anim)) = &self.view_offset_adj {
|
let target_x = target_x.unwrap_or_else(|| self.target_view_pos());
|
||||||
current_x - self.view_offset + anim.to()
|
|
||||||
} else {
|
|
||||||
current_x
|
|
||||||
};
|
|
||||||
|
|
||||||
let new_offset = compute_new_view_offset(
|
let new_offset = compute_new_view_offset(
|
||||||
final_x + self.working_area.loc.x,
|
target_x + self.working_area.loc.x,
|
||||||
self.working_area.size.w,
|
self.working_area.size.w,
|
||||||
col_x,
|
col_x,
|
||||||
width,
|
width,
|
||||||
@@ -815,37 +857,41 @@ impl<W: LayoutElement> Workspace<W> {
|
|||||||
|
|
||||||
fn compute_new_view_offset_centered(
|
fn compute_new_view_offset_centered(
|
||||||
&self,
|
&self,
|
||||||
current_x: f64,
|
target_x: Option<f64>,
|
||||||
col_x: f64,
|
col_x: f64,
|
||||||
width: f64,
|
width: f64,
|
||||||
is_fullscreen: bool,
|
is_fullscreen: bool,
|
||||||
) -> f64 {
|
) -> f64 {
|
||||||
if is_fullscreen {
|
if is_fullscreen {
|
||||||
return self.compute_new_view_offset_fit(current_x, col_x, width, is_fullscreen);
|
return self.compute_new_view_offset_fit(target_x, col_x, width, is_fullscreen);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Columns wider than the view are left-aligned (the fit code can deal with that).
|
// Columns wider than the view are left-aligned (the fit code can deal with that).
|
||||||
if self.working_area.size.w <= width {
|
if self.working_area.size.w <= width {
|
||||||
return self.compute_new_view_offset_fit(current_x, col_x, width, is_fullscreen);
|
return self.compute_new_view_offset_fit(target_x, col_x, width, is_fullscreen);
|
||||||
}
|
}
|
||||||
|
|
||||||
-(self.working_area.size.w - width) / 2. - self.working_area.loc.x
|
-(self.working_area.size.w - width) / 2. - self.working_area.loc.x
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compute_new_view_offset_for_column_fit(&self, current_x: f64, idx: usize) -> f64 {
|
fn compute_new_view_offset_for_column_fit(&self, target_x: Option<f64>, idx: usize) -> f64 {
|
||||||
let col = &self.columns[idx];
|
let col = &self.columns[idx];
|
||||||
self.compute_new_view_offset_fit(
|
self.compute_new_view_offset_fit(
|
||||||
current_x,
|
target_x,
|
||||||
self.column_x(idx),
|
self.column_x(idx),
|
||||||
col.width(),
|
col.width(),
|
||||||
col.is_fullscreen,
|
col.is_fullscreen,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compute_new_view_offset_for_column_centered(&self, current_x: f64, idx: usize) -> f64 {
|
fn compute_new_view_offset_for_column_centered(
|
||||||
|
&self,
|
||||||
|
target_x: Option<f64>,
|
||||||
|
idx: usize,
|
||||||
|
) -> f64 {
|
||||||
let col = &self.columns[idx];
|
let col = &self.columns[idx];
|
||||||
self.compute_new_view_offset_centered(
|
self.compute_new_view_offset_centered(
|
||||||
current_x,
|
target_x,
|
||||||
self.column_x(idx),
|
self.column_x(idx),
|
||||||
col.width(),
|
col.width(),
|
||||||
col.is_fullscreen,
|
col.is_fullscreen,
|
||||||
@@ -854,21 +900,21 @@ impl<W: LayoutElement> Workspace<W> {
|
|||||||
|
|
||||||
fn compute_new_view_offset_for_column(
|
fn compute_new_view_offset_for_column(
|
||||||
&self,
|
&self,
|
||||||
current_x: f64,
|
target_x: Option<f64>,
|
||||||
idx: usize,
|
idx: usize,
|
||||||
prev_idx: Option<usize>,
|
prev_idx: Option<usize>,
|
||||||
) -> f64 {
|
) -> f64 {
|
||||||
if self.is_centering_focused_column() {
|
if self.is_centering_focused_column() {
|
||||||
return self.compute_new_view_offset_for_column_centered(current_x, idx);
|
return self.compute_new_view_offset_for_column_centered(target_x, idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
match self.options.center_focused_column {
|
match self.options.center_focused_column {
|
||||||
CenterFocusedColumn::Always => {
|
CenterFocusedColumn::Always => {
|
||||||
self.compute_new_view_offset_for_column_centered(current_x, idx)
|
self.compute_new_view_offset_for_column_centered(target_x, idx)
|
||||||
}
|
}
|
||||||
CenterFocusedColumn::OnOverflow => {
|
CenterFocusedColumn::OnOverflow => {
|
||||||
let Some(prev_idx) = prev_idx else {
|
let Some(prev_idx) = prev_idx else {
|
||||||
return self.compute_new_view_offset_for_column_fit(current_x, idx);
|
return self.compute_new_view_offset_for_column_fit(target_x, idx);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Always take the left or right neighbor of the target as the source.
|
// Always take the left or right neighbor of the target as the source.
|
||||||
@@ -878,36 +924,35 @@ impl<W: LayoutElement> Workspace<W> {
|
|||||||
idx.saturating_sub(1)
|
idx.saturating_sub(1)
|
||||||
};
|
};
|
||||||
|
|
||||||
let source_x = self.column_x(source_idx);
|
let source_col_x = self.column_x(source_idx);
|
||||||
let source_width = self.columns[source_idx].width();
|
let source_col_width = self.columns[source_idx].width();
|
||||||
|
|
||||||
let target_x = self.column_x(idx);
|
let target_col_x = self.column_x(idx);
|
||||||
let target_width = self.columns[idx].width();
|
let target_col_width = self.columns[idx].width();
|
||||||
|
|
||||||
let total_width = if source_x < target_x {
|
let total_width = if source_col_x < target_col_x {
|
||||||
// Source is left from target.
|
// Source is left from target.
|
||||||
target_x - source_x + target_width
|
target_col_x - source_col_x + target_col_width
|
||||||
} else {
|
} else {
|
||||||
// Source is right from target.
|
// Source is right from target.
|
||||||
source_x - target_x + source_width
|
source_col_x - target_col_x + source_col_width
|
||||||
} + self.options.gaps * 2.;
|
} + self.options.gaps * 2.;
|
||||||
|
|
||||||
// If it fits together, do a normal animation, otherwise center the new column.
|
// If it fits together, do a normal animation, otherwise center the new column.
|
||||||
if total_width <= self.working_area.size.w {
|
if total_width <= self.working_area.size.w {
|
||||||
self.compute_new_view_offset_for_column_fit(current_x, idx)
|
self.compute_new_view_offset_for_column_fit(target_x, idx)
|
||||||
} else {
|
} else {
|
||||||
self.compute_new_view_offset_for_column_centered(current_x, idx)
|
self.compute_new_view_offset_for_column_centered(target_x, idx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CenterFocusedColumn::Never => {
|
CenterFocusedColumn::Never => {
|
||||||
self.compute_new_view_offset_for_column_fit(current_x, idx)
|
self.compute_new_view_offset_for_column_fit(target_x, idx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn animate_view_offset(&mut self, current_x: f64, idx: usize, new_view_offset: f64) {
|
fn animate_view_offset(&mut self, idx: usize, new_view_offset: f64) {
|
||||||
self.animate_view_offset_with_config(
|
self.animate_view_offset_with_config(
|
||||||
current_x,
|
|
||||||
idx,
|
idx,
|
||||||
new_view_offset,
|
new_view_offset,
|
||||||
self.options.animations.horizontal_view_movement.0,
|
self.options.animations.horizontal_view_movement.0,
|
||||||
@@ -916,78 +961,67 @@ impl<W: LayoutElement> Workspace<W> {
|
|||||||
|
|
||||||
fn animate_view_offset_with_config(
|
fn animate_view_offset_with_config(
|
||||||
&mut self,
|
&mut self,
|
||||||
current_x: f64,
|
|
||||||
idx: usize,
|
idx: usize,
|
||||||
new_view_offset: f64,
|
new_view_offset: f64,
|
||||||
config: niri_config::Animation,
|
config: niri_config::Animation,
|
||||||
) {
|
) {
|
||||||
|
self.view_offset.cancel_gesture();
|
||||||
|
|
||||||
let new_col_x = self.column_x(idx);
|
let new_col_x = self.column_x(idx);
|
||||||
let old_col_x = current_x - self.view_offset;
|
let old_col_x = self.column_x(self.active_column_idx);
|
||||||
let offset_delta = old_col_x - new_col_x;
|
let offset_delta = old_col_x - new_col_x;
|
||||||
self.view_offset += offset_delta;
|
self.view_offset.offset(offset_delta);
|
||||||
|
|
||||||
let pixel = 1. / self.scale.fractional_scale();
|
let pixel = 1. / self.scale.fractional_scale();
|
||||||
|
|
||||||
// If we're already animating towards that, don't restart it.
|
// If our view offset is already this or animating towards this, we don't need to do
|
||||||
if let Some(ViewOffsetAdjustment::Animation(anim)) = &mut self.view_offset_adj {
|
// anything.
|
||||||
// Offset the animation for the active column change.
|
let to_diff = new_view_offset - self.view_offset.target();
|
||||||
anim.offset(offset_delta);
|
if to_diff.abs() < pixel {
|
||||||
|
|
||||||
let to_diff = new_view_offset - anim.to();
|
|
||||||
if (anim.value() - self.view_offset).abs() < pixel && to_diff.abs() < pixel {
|
|
||||||
// Correct for any inaccuracy.
|
|
||||||
anim.offset(to_diff);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If our view offset is already this, we don't need to do anything.
|
|
||||||
if (self.view_offset - new_view_offset).abs() < pixel {
|
|
||||||
// Correct for any inaccuracy.
|
// Correct for any inaccuracy.
|
||||||
self.view_offset = new_view_offset;
|
self.view_offset.offset(to_diff);
|
||||||
self.view_offset_adj = None;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: also compute and use current velocity.
|
// FIXME: also compute and use current velocity.
|
||||||
self.view_offset_adj = Some(ViewOffsetAdjustment::Animation(Animation::new(
|
self.view_offset = ViewOffset::Animation(Animation::new(
|
||||||
self.clock.now(),
|
self.clock.clone(),
|
||||||
self.view_offset,
|
self.view_offset.current(),
|
||||||
new_view_offset,
|
new_view_offset,
|
||||||
0.,
|
0.,
|
||||||
config,
|
config,
|
||||||
)));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn animate_view_offset_to_column_centered(
|
fn animate_view_offset_to_column_centered(
|
||||||
&mut self,
|
&mut self,
|
||||||
current_x: f64,
|
target_x: Option<f64>,
|
||||||
idx: usize,
|
idx: usize,
|
||||||
config: niri_config::Animation,
|
config: niri_config::Animation,
|
||||||
) {
|
) {
|
||||||
let new_view_offset = self.compute_new_view_offset_for_column_centered(current_x, idx);
|
let new_view_offset = self.compute_new_view_offset_for_column_centered(target_x, idx);
|
||||||
self.animate_view_offset_with_config(current_x, idx, new_view_offset, config);
|
self.animate_view_offset_with_config(idx, new_view_offset, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn animate_view_offset_to_column_with_config(
|
fn animate_view_offset_to_column_with_config(
|
||||||
&mut self,
|
&mut self,
|
||||||
current_x: f64,
|
target_x: Option<f64>,
|
||||||
idx: usize,
|
idx: usize,
|
||||||
prev_idx: Option<usize>,
|
prev_idx: Option<usize>,
|
||||||
config: niri_config::Animation,
|
config: niri_config::Animation,
|
||||||
) {
|
) {
|
||||||
let new_view_offset = self.compute_new_view_offset_for_column(current_x, idx, prev_idx);
|
let new_view_offset = self.compute_new_view_offset_for_column(target_x, idx, prev_idx);
|
||||||
self.animate_view_offset_with_config(current_x, idx, new_view_offset, config);
|
self.animate_view_offset_with_config(idx, new_view_offset, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn animate_view_offset_to_column(
|
fn animate_view_offset_to_column(
|
||||||
&mut self,
|
&mut self,
|
||||||
current_x: f64,
|
target_x: Option<f64>,
|
||||||
idx: usize,
|
idx: usize,
|
||||||
prev_idx: Option<usize>,
|
prev_idx: Option<usize>,
|
||||||
) {
|
) {
|
||||||
self.animate_view_offset_to_column_with_config(
|
self.animate_view_offset_to_column_with_config(
|
||||||
current_x,
|
target_x,
|
||||||
idx,
|
idx,
|
||||||
prev_idx,
|
prev_idx,
|
||||||
self.options.animations.horizontal_view_movement.0,
|
self.options.animations.horizontal_view_movement.0,
|
||||||
@@ -1006,9 +1040,8 @@ impl<W: LayoutElement> Workspace<W> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let current_x = self.view_pos();
|
|
||||||
self.animate_view_offset_to_column_with_config(
|
self.animate_view_offset_to_column_with_config(
|
||||||
current_x,
|
None,
|
||||||
idx,
|
idx,
|
||||||
Some(self.active_column_idx),
|
Some(self.active_column_idx),
|
||||||
config,
|
config,
|
||||||
@@ -1240,14 +1273,13 @@ impl<W: LayoutElement> Workspace<W> {
|
|||||||
// If this is the first window on an empty workspace, remove the effect of whatever
|
// If this is the first window on an empty workspace, remove the effect of whatever
|
||||||
// view_offset was left over and skip the animation.
|
// view_offset was left over and skip the animation.
|
||||||
if was_empty {
|
if was_empty {
|
||||||
self.view_offset = 0.;
|
self.view_offset = ViewOffset::Static(0.);
|
||||||
self.view_offset_adj = None;
|
|
||||||
self.view_offset =
|
self.view_offset =
|
||||||
self.compute_new_view_offset_for_column(self.view_pos(), idx, None);
|
ViewOffset::Static(self.compute_new_view_offset_for_column(None, idx, None));
|
||||||
}
|
}
|
||||||
|
|
||||||
let prev_offset = (!was_empty && idx == self.active_column_idx + 1)
|
let prev_offset = (!was_empty && idx == self.active_column_idx + 1)
|
||||||
.then(|| self.static_view_offset());
|
.then(|| self.view_offset.stationary());
|
||||||
|
|
||||||
let anim_config =
|
let anim_config =
|
||||||
anim_config.unwrap_or(self.options.animations.horizontal_view_movement.0);
|
anim_config.unwrap_or(self.options.animations.horizontal_view_movement.0);
|
||||||
@@ -1416,15 +1448,13 @@ impl<W: LayoutElement> Workspace<W> {
|
|||||||
|
|
||||||
// Restore the view offset but make sure to scroll the view in case the
|
// Restore the view offset but make sure to scroll the view in case the
|
||||||
// previous window had resized.
|
// previous window had resized.
|
||||||
let current_x = self.view_pos();
|
|
||||||
self.animate_view_offset_with_config(
|
self.animate_view_offset_with_config(
|
||||||
current_x,
|
|
||||||
self.active_column_idx,
|
self.active_column_idx,
|
||||||
prev_offset,
|
prev_offset,
|
||||||
view_config,
|
view_config,
|
||||||
);
|
);
|
||||||
self.animate_view_offset_to_column_with_config(
|
self.animate_view_offset_to_column_with_config(
|
||||||
current_x,
|
None,
|
||||||
self.active_column_idx,
|
self.active_column_idx,
|
||||||
None,
|
None,
|
||||||
view_config,
|
view_config,
|
||||||
@@ -1509,6 +1539,9 @@ impl<W: LayoutElement> Workspace<W> {
|
|||||||
// the Resizing state, which can trigger this code path for a while.
|
// the Resizing state, which can trigger this code path for a while.
|
||||||
let resize = if offset != 0. { resize } else { None };
|
let resize = if offset != 0. { resize } else { None };
|
||||||
if let Some(resize) = resize {
|
if let Some(resize) = resize {
|
||||||
|
// Don't bother with the gesture.
|
||||||
|
self.view_offset.cancel_gesture();
|
||||||
|
|
||||||
// If this is an interactive resize commit of an active window, then we need to
|
// If this is an interactive resize commit of an active window, then we need to
|
||||||
// either preserve the view offset or adjust it accordingly.
|
// either preserve the view offset or adjust it accordingly.
|
||||||
let centered = self.is_centering_focused_column();
|
let centered = self.is_centering_focused_column();
|
||||||
@@ -1518,33 +1551,24 @@ impl<W: LayoutElement> Workspace<W> {
|
|||||||
// FIXME: when view_offset becomes fractional, this can be made additive too.
|
// FIXME: when view_offset becomes fractional, this can be made additive too.
|
||||||
let new_offset =
|
let new_offset =
|
||||||
-(self.working_area.size.w - width) / 2. - self.working_area.loc.x;
|
-(self.working_area.size.w - width) / 2. - self.working_area.loc.x;
|
||||||
new_offset - self.view_offset
|
new_offset - self.view_offset.target()
|
||||||
} else if resize.edges.contains(ResizeEdge::LEFT) {
|
} else if resize.edges.contains(ResizeEdge::LEFT) {
|
||||||
-offset
|
-offset
|
||||||
} else {
|
} else {
|
||||||
0.
|
0.
|
||||||
};
|
};
|
||||||
|
|
||||||
self.view_offset += offset;
|
self.view_offset.offset(offset);
|
||||||
if let Some(ViewOffsetAdjustment::Animation(anim)) = &mut self.view_offset_adj {
|
|
||||||
anim.offset(offset);
|
|
||||||
} else {
|
|
||||||
// Don't bother with the gesture.
|
|
||||||
self.view_offset_adj = None;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.interactive_resize.is_none()
|
if self.interactive_resize.is_none() && !self.view_offset.is_gesture() {
|
||||||
&& !matches!(self.view_offset_adj, Some(ViewOffsetAdjustment::Gesture(_)))
|
|
||||||
{
|
|
||||||
// We might need to move the view to ensure the resized window is still visible.
|
// We might need to move the view to ensure the resized window is still visible.
|
||||||
let current_x = self.view_pos();
|
|
||||||
|
|
||||||
// Upon unfullscreening, restore the view offset.
|
// Upon unfullscreening, restore the view offset.
|
||||||
let is_fullscreen = self.columns[col_idx].tiles[tile_idx].is_fullscreen();
|
let is_fullscreen = self.columns[col_idx].tiles[tile_idx].is_fullscreen();
|
||||||
if was_fullscreen && !is_fullscreen {
|
if was_fullscreen && !is_fullscreen {
|
||||||
if let Some(prev_offset) = self.view_offset_before_fullscreen.take() {
|
if let Some(prev_offset) = self.view_offset_before_fullscreen.take() {
|
||||||
self.animate_view_offset(current_x, col_idx, prev_offset);
|
self.animate_view_offset(col_idx, prev_offset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1558,7 +1582,7 @@ impl<W: LayoutElement> Workspace<W> {
|
|||||||
|
|
||||||
// FIXME: we will want to skip the animation in some cases here to make continuously
|
// FIXME: we will want to skip the animation in some cases here to make continuously
|
||||||
// resizing windows not look janky.
|
// resizing windows not look janky.
|
||||||
self.animate_view_offset_to_column_with_config(current_x, col_idx, None, config);
|
self.animate_view_offset_to_column_with_config(None, col_idx, None, config);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1574,22 +1598,16 @@ impl<W: LayoutElement> Workspace<W> {
|
|||||||
return 0.;
|
return 0.;
|
||||||
}
|
}
|
||||||
|
|
||||||
let current_x = self.view_pos();
|
// Consider the end of an ongoing animation because that's what compute to fit does too.
|
||||||
|
let target_x = self.target_view_pos();
|
||||||
let new_view_offset = self.compute_new_view_offset_for_column(
|
let new_view_offset = self.compute_new_view_offset_for_column(
|
||||||
current_x,
|
Some(target_x),
|
||||||
column_idx,
|
column_idx,
|
||||||
Some(self.active_column_idx),
|
Some(self.active_column_idx),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Consider the end of an ongoing animation because that's what compute to fit does too.
|
|
||||||
let final_x = if let Some(ViewOffsetAdjustment::Animation(anim)) = &self.view_offset_adj {
|
|
||||||
current_x - self.view_offset + anim.to()
|
|
||||||
} else {
|
|
||||||
current_x
|
|
||||||
};
|
|
||||||
|
|
||||||
let new_col_x = self.column_x(column_idx);
|
let new_col_x = self.column_x(column_idx);
|
||||||
let from_view_offset = final_x - new_col_x;
|
let from_view_offset = target_x - new_col_x;
|
||||||
|
|
||||||
(from_view_offset - new_view_offset).abs() / self.working_area.size.w
|
(from_view_offset - new_view_offset).abs() / self.working_area.size.w
|
||||||
}
|
}
|
||||||
@@ -1698,7 +1716,7 @@ impl<W: LayoutElement> Workspace<W> {
|
|||||||
let output_scale = Scale::from(self.scale.fractional_scale());
|
let output_scale = Scale::from(self.scale.fractional_scale());
|
||||||
|
|
||||||
let anim = Animation::new(
|
let anim = Animation::new(
|
||||||
self.clock.now(),
|
self.clock.clone(),
|
||||||
0.,
|
0.,
|
||||||
1.,
|
1.,
|
||||||
0.,
|
0.,
|
||||||
@@ -1870,10 +1888,8 @@ impl<W: LayoutElement> Workspace<W> {
|
|||||||
|
|
||||||
// Preserve the camera position when moving to the left.
|
// Preserve the camera position when moving to the left.
|
||||||
let view_offset_delta = -self.column_x(self.active_column_idx) + current_col_x;
|
let view_offset_delta = -self.column_x(self.active_column_idx) + current_col_x;
|
||||||
self.view_offset += view_offset_delta;
|
self.view_offset.cancel_gesture();
|
||||||
if let Some(ViewOffsetAdjustment::Animation(anim)) = &mut self.view_offset_adj {
|
self.view_offset.offset(view_offset_delta);
|
||||||
anim.offset(view_offset_delta);
|
|
||||||
}
|
|
||||||
|
|
||||||
// The column we just moved is offset by the difference between its new and old position.
|
// The column we just moved is offset by the difference between its new and old position.
|
||||||
let new_col_x = self.column_x(new_idx);
|
let new_col_x = self.column_x(new_idx);
|
||||||
@@ -1987,7 +2003,8 @@ impl<W: LayoutElement> Workspace<W> {
|
|||||||
|
|
||||||
if source_tile_was_active {
|
if source_tile_was_active {
|
||||||
// Make sure the previous (target) column is activated so the animation looks right.
|
// Make sure the previous (target) column is activated so the animation looks right.
|
||||||
self.activate_prev_column_on_removal = Some(self.static_view_offset() + offset.x);
|
self.activate_prev_column_on_removal =
|
||||||
|
Some(self.view_offset.stationary() + offset.x);
|
||||||
}
|
}
|
||||||
|
|
||||||
offset.x += self.columns[source_col_idx].render_offset().x;
|
offset.x += self.columns[source_col_idx].render_offset().x;
|
||||||
@@ -2204,9 +2221,8 @@ impl<W: LayoutElement> Workspace<W> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let center_x = self.view_pos();
|
|
||||||
self.animate_view_offset_to_column_centered(
|
self.animate_view_offset_to_column_centered(
|
||||||
center_x,
|
None,
|
||||||
self.active_column_idx,
|
self.active_column_idx,
|
||||||
self.options.animations.horizontal_view_movement.0,
|
self.options.animations.horizontal_view_movement.0,
|
||||||
);
|
);
|
||||||
@@ -2216,19 +2232,11 @@ impl<W: LayoutElement> Workspace<W> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn view_pos(&self) -> f64 {
|
pub fn view_pos(&self) -> f64 {
|
||||||
self.column_x(self.active_column_idx) + self.view_offset
|
self.column_x(self.active_column_idx) + self.view_offset.current()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a view offset value suitable for saving and later restoration.
|
pub fn target_view_pos(&self) -> f64 {
|
||||||
///
|
self.column_x(self.active_column_idx) + self.view_offset.target()
|
||||||
/// This means that it shouldn't return an in-progress animation or gesture value.
|
|
||||||
fn static_view_offset(&self) -> f64 {
|
|
||||||
match &self.view_offset_adj {
|
|
||||||
// For animations we can return the final value.
|
|
||||||
Some(ViewOffsetAdjustment::Animation(anim)) => anim.to(),
|
|
||||||
Some(ViewOffsetAdjustment::Gesture(gesture)) => gesture.static_view_offset,
|
|
||||||
_ => self.view_offset,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HACK: pass a self.data iterator in manually as a workaround for the lack of method partial
|
// HACK: pass a self.data iterator in manually as a workaround for the lack of method partial
|
||||||
@@ -2398,9 +2406,9 @@ impl<W: LayoutElement> Workspace<W> {
|
|||||||
// effect here.
|
// effect here.
|
||||||
if self.columns.is_empty() {
|
if self.columns.is_empty() {
|
||||||
let view_offset = if self.is_centering_focused_column() {
|
let view_offset = if self.is_centering_focused_column() {
|
||||||
self.compute_new_view_offset_centered(0., 0., hint_area.size.w, false)
|
self.compute_new_view_offset_centered(Some(0.), 0., hint_area.size.w, false)
|
||||||
} else {
|
} else {
|
||||||
self.compute_new_view_offset_fit(0., 0., hint_area.size.w, false)
|
self.compute_new_view_offset_fit(Some(0.), 0., hint_area.size.w, false)
|
||||||
};
|
};
|
||||||
hint_area.loc.x -= view_offset;
|
hint_area.loc.x -= view_offset;
|
||||||
} else {
|
} else {
|
||||||
@@ -2429,10 +2437,7 @@ impl<W: LayoutElement> Workspace<W> {
|
|||||||
pub fn active_tile_visual_rectangle(&self) -> Option<Rectangle<f64, Logical>> {
|
pub fn active_tile_visual_rectangle(&self) -> Option<Rectangle<f64, Logical>> {
|
||||||
let col = self.columns.get(self.active_column_idx)?;
|
let col = self.columns.get(self.active_column_idx)?;
|
||||||
|
|
||||||
let final_view_offset = self
|
let final_view_offset = self.view_offset.target();
|
||||||
.view_offset_adj
|
|
||||||
.as_ref()
|
|
||||||
.map_or(self.view_offset, |adj| adj.target_view_offset());
|
|
||||||
let view_off = Point::from((-final_view_offset, 0.));
|
let view_off = Point::from((-final_view_offset, 0.));
|
||||||
|
|
||||||
let (tile, tile_off) = col.tiles().nth(col.active_tile_idx).unwrap();
|
let (tile, tile_off) = col.tiles().nth(col.active_tile_idx).unwrap();
|
||||||
@@ -2623,7 +2628,7 @@ impl<W: LayoutElement> Workspace<W> {
|
|||||||
&& col_idx == self.active_column_idx
|
&& col_idx == self.active_column_idx
|
||||||
&& self.columns[col_idx].tiles.len() == 1
|
&& self.columns[col_idx].tiles.len() == 1
|
||||||
{
|
{
|
||||||
self.view_offset_before_fullscreen = Some(self.static_view_offset());
|
self.view_offset_before_fullscreen = Some(self.view_offset.stationary());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut col = &mut self.columns[col_idx];
|
let mut col = &mut self.columns[col_idx];
|
||||||
@@ -2682,7 +2687,7 @@ impl<W: LayoutElement> Workspace<W> {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.view_offset_adj.is_some() {
|
if !self.view_offset.is_static() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2745,13 +2750,13 @@ impl<W: LayoutElement> Workspace<W> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let gesture = ViewGesture {
|
let gesture = ViewGesture {
|
||||||
current_view_offset: self.view_offset,
|
current_view_offset: self.view_offset.current(),
|
||||||
tracker: SwipeTracker::new(),
|
tracker: SwipeTracker::new(),
|
||||||
delta_from_tracker: self.view_offset,
|
delta_from_tracker: self.view_offset.current(),
|
||||||
static_view_offset: self.static_view_offset(),
|
stationary_view_offset: self.view_offset.stationary(),
|
||||||
is_touchpad,
|
is_touchpad,
|
||||||
};
|
};
|
||||||
self.view_offset_adj = Some(ViewOffsetAdjustment::Gesture(gesture));
|
self.view_offset = ViewOffset::Gesture(gesture);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn view_offset_gesture_update(
|
pub fn view_offset_gesture_update(
|
||||||
@@ -2760,7 +2765,7 @@ impl<W: LayoutElement> Workspace<W> {
|
|||||||
timestamp: Duration,
|
timestamp: Duration,
|
||||||
is_touchpad: bool,
|
is_touchpad: bool,
|
||||||
) -> Option<bool> {
|
) -> Option<bool> {
|
||||||
let Some(ViewOffsetAdjustment::Gesture(gesture)) = &mut self.view_offset_adj else {
|
let ViewOffset::Gesture(gesture) = &mut self.view_offset else {
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -2783,7 +2788,7 @@ impl<W: LayoutElement> Workspace<W> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn view_offset_gesture_end(&mut self, _cancelled: bool, is_touchpad: Option<bool>) -> bool {
|
pub fn view_offset_gesture_end(&mut self, _cancelled: bool, is_touchpad: Option<bool>) -> bool {
|
||||||
let Some(ViewOffsetAdjustment::Gesture(gesture)) = &self.view_offset_adj else {
|
let ViewOffset::Gesture(gesture) = &mut self.view_offset else {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -2806,8 +2811,7 @@ impl<W: LayoutElement> Workspace<W> {
|
|||||||
let current_view_offset = pos + gesture.delta_from_tracker;
|
let current_view_offset = pos + gesture.delta_from_tracker;
|
||||||
|
|
||||||
if self.columns.is_empty() {
|
if self.columns.is_empty() {
|
||||||
self.view_offset = current_view_offset;
|
self.view_offset = ViewOffset::Static(current_view_offset);
|
||||||
self.view_offset_adj = None;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2979,7 +2983,6 @@ impl<W: LayoutElement> Workspace<W> {
|
|||||||
|
|
||||||
let new_col_x = self.column_x(new_col_idx);
|
let new_col_x = self.column_x(new_col_idx);
|
||||||
let delta = active_col_x - new_col_x;
|
let delta = active_col_x - new_col_x;
|
||||||
self.view_offset = current_view_offset + delta;
|
|
||||||
|
|
||||||
if self.active_column_idx != new_col_idx {
|
if self.active_column_idx != new_col_idx {
|
||||||
self.view_offset_before_fullscreen = None;
|
self.view_offset_before_fullscreen = None;
|
||||||
@@ -2989,16 +2992,16 @@ impl<W: LayoutElement> Workspace<W> {
|
|||||||
|
|
||||||
let target_view_offset = target_snap.view_pos - new_col_x;
|
let target_view_offset = target_snap.view_pos - new_col_x;
|
||||||
|
|
||||||
self.view_offset_adj = Some(ViewOffsetAdjustment::Animation(Animation::new(
|
self.view_offset = ViewOffset::Animation(Animation::new(
|
||||||
self.clock.now(),
|
self.clock.clone(),
|
||||||
current_view_offset + delta,
|
current_view_offset + delta,
|
||||||
target_view_offset,
|
target_view_offset,
|
||||||
velocity,
|
velocity,
|
||||||
self.options.animations.horizontal_view_movement.0,
|
self.options.animations.horizontal_view_movement.0,
|
||||||
)));
|
));
|
||||||
|
|
||||||
// HACK: deal with things like snapping to the right edge of a larger-than-view window.
|
// HACK: deal with things like snapping to the right edge of a larger-than-view window.
|
||||||
self.animate_view_offset_to_column(self.view_pos(), new_col_idx, None);
|
self.animate_view_offset_to_column(None, new_col_idx, None);
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
@@ -3033,8 +3036,7 @@ impl<W: LayoutElement> Workspace<W> {
|
|||||||
};
|
};
|
||||||
self.interactive_resize = Some(resize);
|
self.interactive_resize = Some(resize);
|
||||||
|
|
||||||
// Stop ongoing animation.
|
self.view_offset.stop_anim_and_gesture();
|
||||||
self.view_offset_adj = None;
|
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
@@ -3112,7 +3114,7 @@ impl<W: LayoutElement> Workspace<W> {
|
|||||||
|
|
||||||
// Animate the active window into view right away.
|
// Animate the active window into view right away.
|
||||||
if self.columns[self.active_column_idx].contains(window) {
|
if self.columns[self.active_column_idx].contains(window) {
|
||||||
self.animate_view_offset_to_column(self.view_pos(), self.active_column_idx, None);
|
self.animate_view_offset_to_column(None, self.active_column_idx, None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3282,16 +3284,15 @@ impl<W: LayoutElement> Column<W> {
|
|||||||
self.update_tile_sizes(animate);
|
self.update_tile_sizes(animate);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn advance_animations(&mut self, current_time: Duration) {
|
pub fn advance_animations(&mut self) {
|
||||||
if let Some(anim) = &mut self.move_animation {
|
if let Some(anim) = &mut self.move_animation {
|
||||||
anim.set_current_time(current_time);
|
|
||||||
if anim.is_done() {
|
if anim.is_done() {
|
||||||
self.move_animation = None;
|
self.move_animation = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for tile in &mut self.tiles {
|
for tile in &mut self.tiles {
|
||||||
tile.advance_animations(current_time);
|
tile.advance_animations();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3335,7 +3336,7 @@ impl<W: LayoutElement> Column<W> {
|
|||||||
let current_offset = self.move_animation.as_ref().map_or(0., Animation::value);
|
let current_offset = self.move_animation.as_ref().map_or(0., Animation::value);
|
||||||
|
|
||||||
self.move_animation = Some(Animation::new(
|
self.move_animation = Some(Animation::new(
|
||||||
self.clock.now(),
|
self.clock.clone(),
|
||||||
from_x_offset + current_offset,
|
from_x_offset + current_offset,
|
||||||
0.,
|
0.,
|
||||||
0.,
|
0.,
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ use std::{env, mem};
|
|||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use directories::ProjectDirs;
|
use directories::ProjectDirs;
|
||||||
use niri::animation;
|
|
||||||
use niri::cli::{Cli, Sub};
|
use niri::cli::{Cli, Sub};
|
||||||
#[cfg(feature = "dbus")]
|
#[cfg(feature = "dbus")]
|
||||||
use niri::dbus;
|
use niri::dbus;
|
||||||
@@ -164,13 +163,6 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
})
|
})
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
let slowdown = if config.animations.off {
|
|
||||||
0.
|
|
||||||
} else {
|
|
||||||
config.animations.slowdown.clamp(0., 100.)
|
|
||||||
};
|
|
||||||
animation::ANIMATION_SLOWDOWN.store(slowdown, Ordering::Relaxed);
|
|
||||||
|
|
||||||
let spawn_at_startup = mem::take(&mut config.spawn_at_startup);
|
let spawn_at_startup = mem::take(&mut config.spawn_at_startup);
|
||||||
*CHILD_ENV.write().unwrap() = mem::take(&mut config.environment);
|
*CHILD_ENV.write().unwrap() = mem::take(&mut config.environment);
|
||||||
|
|
||||||
|
|||||||
+42
-27
@@ -122,6 +122,7 @@ use crate::layer::MappedLayer;
|
|||||||
use crate::layout::tile::TileRenderElement;
|
use crate::layout::tile::TileRenderElement;
|
||||||
use crate::layout::workspace::WorkspaceId;
|
use crate::layout::workspace::WorkspaceId;
|
||||||
use crate::layout::{Layout, LayoutElement as _, MonitorRenderElement};
|
use crate::layout::{Layout, LayoutElement as _, MonitorRenderElement};
|
||||||
|
use crate::niri_render_elements;
|
||||||
use crate::protocols::foreign_toplevel::{self, ForeignToplevelManagerState};
|
use crate::protocols::foreign_toplevel::{self, ForeignToplevelManagerState};
|
||||||
use crate::protocols::gamma_control::GammaControlManagerState;
|
use crate::protocols::gamma_control::GammaControlManagerState;
|
||||||
use crate::protocols::mutter_x11_interop::MutterX11InteropManagerState;
|
use crate::protocols::mutter_x11_interop::MutterX11InteropManagerState;
|
||||||
@@ -150,7 +151,6 @@ use crate::utils::{
|
|||||||
make_screenshot_path, output_matches_name, output_size, send_scale_transform, write_png_rgba8,
|
make_screenshot_path, output_matches_name, output_size, send_scale_transform, write_png_rgba8,
|
||||||
};
|
};
|
||||||
use crate::window::{InitialConfigureState, Mapped, ResolvedWindowRules, Unmapped, WindowRef};
|
use crate::window::{InitialConfigureState, Mapped, ResolvedWindowRules, Unmapped, WindowRef};
|
||||||
use crate::{animation, niri_render_elements};
|
|
||||||
|
|
||||||
const CLEAR_COLOR_LOCKED: [f32; 4] = [0.3, 0.1, 0.1, 1.];
|
const CLEAR_COLOR_LOCKED: [f32; 4] = [0.3, 0.1, 0.1, 1.];
|
||||||
|
|
||||||
@@ -553,12 +553,21 @@ impl State {
|
|||||||
|
|
||||||
self.refresh();
|
self.refresh();
|
||||||
|
|
||||||
|
// Advance animations to the current time (not target render time) before rendering outputs
|
||||||
|
// in order to clear completed animations and render elements. Even if we're not rendering,
|
||||||
|
// it's good to advance every now and then so the workspace clean-up and animations don't
|
||||||
|
// build up (the 1 second frame callback timer will call this line).
|
||||||
|
self.niri.advance_animations();
|
||||||
|
|
||||||
self.niri.redraw_queued_outputs(&mut self.backend);
|
self.niri.redraw_queued_outputs(&mut self.backend);
|
||||||
|
|
||||||
{
|
{
|
||||||
let _span = tracy_client::span!("flush_clients");
|
let _span = tracy_client::span!("flush_clients");
|
||||||
self.niri.display_handle.flush_clients().unwrap();
|
self.niri.display_handle.flush_clients().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clear the time so it's fetched afresh next iteration.
|
||||||
|
self.niri.clock.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn refresh(&mut self) {
|
fn refresh(&mut self) {
|
||||||
@@ -1054,12 +1063,11 @@ impl State {
|
|||||||
self.niri.layout.ensure_named_workspace(ws_config);
|
self.niri.layout.ensure_named_workspace(ws_config);
|
||||||
}
|
}
|
||||||
|
|
||||||
let slowdown = if config.animations.off {
|
let rate = 1.0 / config.animations.slowdown.max(0.001);
|
||||||
0.
|
self.niri.clock.set_rate(rate);
|
||||||
} else {
|
self.niri
|
||||||
config.animations.slowdown.clamp(0., 100.)
|
.clock
|
||||||
};
|
.set_complete_instantly(config.animations.off);
|
||||||
animation::ANIMATION_SLOWDOWN.store(slowdown, Ordering::Relaxed);
|
|
||||||
|
|
||||||
*CHILD_ENV.write().unwrap() = mem::take(&mut config.environment);
|
*CHILD_ENV.write().unwrap() = mem::take(&mut config.environment);
|
||||||
|
|
||||||
@@ -1675,8 +1683,13 @@ impl Niri {
|
|||||||
let config_ = config.borrow();
|
let config_ = config.borrow();
|
||||||
let config_file_output_config = config_.outputs.clone();
|
let config_file_output_config = config_.outputs.clone();
|
||||||
|
|
||||||
let clock = Clock::default();
|
let mut animation_clock = Clock::default();
|
||||||
let layout = Layout::new(clock.clone(), &config_);
|
|
||||||
|
let rate = 1.0 / config_.animations.slowdown.max(0.001);
|
||||||
|
animation_clock.set_rate(rate);
|
||||||
|
animation_clock.set_complete_instantly(config_.animations.off);
|
||||||
|
|
||||||
|
let layout = Layout::new(animation_clock.clone(), &config_);
|
||||||
|
|
||||||
let (blocker_cleared_tx, blocker_cleared_rx) = mpsc::channel();
|
let (blocker_cleared_tx, blocker_cleared_rx) = mpsc::channel();
|
||||||
|
|
||||||
@@ -1804,8 +1817,9 @@ impl Niri {
|
|||||||
let mods_with_finger_scroll_binds =
|
let mods_with_finger_scroll_binds =
|
||||||
mods_with_finger_scroll_binds(backend.mod_key(), &config_.binds);
|
mods_with_finger_scroll_binds(backend.mod_key(), &config_.binds);
|
||||||
|
|
||||||
let screenshot_ui = ScreenshotUi::new(clock.clone(), config.clone());
|
let screenshot_ui = ScreenshotUi::new(animation_clock.clone(), config.clone());
|
||||||
let config_error_notification = ConfigErrorNotification::new(clock.clone(), config.clone());
|
let config_error_notification =
|
||||||
|
ConfigErrorNotification::new(animation_clock.clone(), config.clone());
|
||||||
|
|
||||||
let mut hotkey_overlay = HotkeyOverlay::new(config.clone(), backend.mod_key());
|
let mut hotkey_overlay = HotkeyOverlay::new(config.clone(), backend.mod_key());
|
||||||
if !config_.hotkey_overlay.skip_at_startup {
|
if !config_.hotkey_overlay.skip_at_startup {
|
||||||
@@ -1900,7 +1914,7 @@ impl Niri {
|
|||||||
display_handle,
|
display_handle,
|
||||||
start_time: Instant::now(),
|
start_time: Instant::now(),
|
||||||
is_at_startup: true,
|
is_at_startup: true,
|
||||||
clock,
|
clock: animation_clock,
|
||||||
|
|
||||||
layout,
|
layout,
|
||||||
global_space: Space::default(),
|
global_space: Space::default(),
|
||||||
@@ -3042,16 +3056,15 @@ impl Niri {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn advance_animations(&mut self, target_time: Duration) {
|
pub fn advance_animations(&mut self) {
|
||||||
self.layout.advance_animations(target_time);
|
let _span = tracy_client::span!("Niri::advance_animations");
|
||||||
self.config_error_notification
|
|
||||||
.advance_animations(target_time);
|
self.layout.advance_animations();
|
||||||
self.screenshot_ui.advance_animations(target_time);
|
self.config_error_notification.advance_animations();
|
||||||
|
self.screenshot_ui.advance_animations();
|
||||||
|
|
||||||
for state in self.output_state.values_mut() {
|
for state in self.output_state.values_mut() {
|
||||||
if let Some(transition) = &mut state.screen_transition {
|
if let Some(transition) = &mut state.screen_transition {
|
||||||
// Screen transition uses real time so that it's not affected by animation slowdown.
|
|
||||||
transition.advance_animations(target_time);
|
|
||||||
if transition.is_done() {
|
if transition.is_done() {
|
||||||
state.screen_transition = None;
|
state.screen_transition = None;
|
||||||
}
|
}
|
||||||
@@ -3263,11 +3276,13 @@ impl Niri {
|
|||||||
|
|
||||||
let target_presentation_time = state.frame_clock.next_presentation_time();
|
let target_presentation_time = state.frame_clock.next_presentation_time();
|
||||||
|
|
||||||
|
// Freeze the clock at the target time.
|
||||||
|
self.clock.set_unadjusted(target_presentation_time);
|
||||||
|
|
||||||
|
self.update_render_elements(Some(output));
|
||||||
|
|
||||||
let mut res = RenderResult::Skipped;
|
let mut res = RenderResult::Skipped;
|
||||||
if self.monitors_active {
|
if self.monitors_active {
|
||||||
// Update from the config and advance the animations.
|
|
||||||
self.advance_animations(target_presentation_time);
|
|
||||||
|
|
||||||
let state = self.output_state.get_mut(output).unwrap();
|
let state = self.output_state.get_mut(output).unwrap();
|
||||||
state.unfinished_animations_remain = self.layout.are_animations_ongoing(Some(output));
|
state.unfinished_animations_remain = self.layout.are_animations_ongoing(Some(output));
|
||||||
state.unfinished_animations_remain |=
|
state.unfinished_animations_remain |=
|
||||||
@@ -3280,8 +3295,6 @@ impl Niri {
|
|||||||
.cursor_manager
|
.cursor_manager
|
||||||
.is_current_cursor_animated(output.current_scale().integer_scale());
|
.is_current_cursor_animated(output.current_scale().integer_scale());
|
||||||
|
|
||||||
self.update_render_elements(Some(output));
|
|
||||||
|
|
||||||
// Render.
|
// Render.
|
||||||
res = backend.render(self, output, target_presentation_time);
|
res = backend.render(self, output, target_presentation_time);
|
||||||
}
|
}
|
||||||
@@ -4827,11 +4840,13 @@ impl Niri {
|
|||||||
Duration::from_millis(u64::from(d))
|
Duration::from_millis(u64::from(d))
|
||||||
});
|
});
|
||||||
|
|
||||||
// Screen transition uses real time so that it's not affected by animation slowdown.
|
|
||||||
let start_at = get_monotonic_time() + delay;
|
|
||||||
for (output, from_texture) in textures {
|
for (output, from_texture) in textures {
|
||||||
let state = self.output_state.get_mut(&output).unwrap();
|
let state = self.output_state.get_mut(&output).unwrap();
|
||||||
state.screen_transition = Some(ScreenTransition::new(from_texture, start_at));
|
state.screen_transition = Some(ScreenTransition::new(
|
||||||
|
from_texture,
|
||||||
|
delay,
|
||||||
|
self.clock.clone(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// We don't actually need to queue a redraw because the point is to freeze the screen for a
|
// We don't actually need to queue a redraw because the point is to freeze the screen for a
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ impl ConfigErrorNotification {
|
|||||||
fn animation(&self, from: f64, to: f64) -> Animation {
|
fn animation(&self, from: f64, to: f64) -> Animation {
|
||||||
let c = self.config.borrow();
|
let c = self.config.borrow();
|
||||||
Animation::new(
|
Animation::new(
|
||||||
self.clock.now(),
|
self.clock.clone(),
|
||||||
from,
|
from,
|
||||||
to,
|
to,
|
||||||
0.,
|
0.,
|
||||||
@@ -96,11 +96,10 @@ impl ConfigErrorNotification {
|
|||||||
self.state = State::Hiding(self.animation(1., 0.));
|
self.state = State::Hiding(self.animation(1., 0.));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn advance_animations(&mut self, target_presentation_time: Duration) {
|
pub fn advance_animations(&mut self) {
|
||||||
match &mut self.state {
|
match &mut self.state {
|
||||||
State::Hidden => (),
|
State::Hidden => (),
|
||||||
State::Showing(anim) => {
|
State::Showing(anim) => {
|
||||||
anim.set_current_time(target_presentation_time);
|
|
||||||
if anim.is_done() {
|
if anim.is_done() {
|
||||||
let duration = if self.created_path.is_some() {
|
let duration = if self.created_path.is_some() {
|
||||||
// Make this quite a bit longer because it comes with a monitor modeset
|
// Make this quite a bit longer because it comes with a monitor modeset
|
||||||
@@ -110,16 +109,15 @@ impl ConfigErrorNotification {
|
|||||||
} else {
|
} else {
|
||||||
Duration::from_secs(4)
|
Duration::from_secs(4)
|
||||||
};
|
};
|
||||||
self.state = State::Shown(target_presentation_time + duration);
|
self.state = State::Shown(self.clock.now() + duration);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
State::Shown(deadline) => {
|
State::Shown(deadline) => {
|
||||||
if target_presentation_time >= *deadline {
|
if self.clock.now() >= *deadline {
|
||||||
self.hide();
|
self.hide();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
State::Hiding(anim) => {
|
State::Hiding(anim) => {
|
||||||
anim.set_current_time(target_presentation_time);
|
|
||||||
if anim.is_clamped_done() {
|
if anim.is_clamped_done() {
|
||||||
self.state = State::Hidden;
|
self.state = State::Hidden;
|
||||||
}
|
}
|
||||||
|
|||||||
+23
-17
@@ -4,6 +4,7 @@ use smithay::backend::renderer::element::Kind;
|
|||||||
use smithay::backend::renderer::gles::GlesTexture;
|
use smithay::backend::renderer::gles::GlesTexture;
|
||||||
use smithay::utils::{Scale, Transform};
|
use smithay::utils::{Scale, Transform};
|
||||||
|
|
||||||
|
use crate::animation::Clock;
|
||||||
use crate::render_helpers::primary_gpu_texture::PrimaryGpuTextureRenderElement;
|
use crate::render_helpers::primary_gpu_texture::PrimaryGpuTextureRenderElement;
|
||||||
use crate::render_helpers::texture::{TextureBuffer, TextureRenderElement};
|
use crate::render_helpers::texture::{TextureBuffer, TextureRenderElement};
|
||||||
use crate::render_helpers::RenderTarget;
|
use crate::render_helpers::RenderTarget;
|
||||||
@@ -17,31 +18,25 @@ pub struct ScreenTransition {
|
|||||||
from_texture: [TextureBuffer<GlesTexture>; 3],
|
from_texture: [TextureBuffer<GlesTexture>; 3],
|
||||||
/// Monotonic time when to start the crossfade.
|
/// Monotonic time when to start the crossfade.
|
||||||
start_at: Duration,
|
start_at: Duration,
|
||||||
/// Current crossfade alpha.
|
/// Clock to drive animations.
|
||||||
alpha: f32,
|
clock: Clock,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ScreenTransition {
|
impl ScreenTransition {
|
||||||
pub fn new(from_texture: [TextureBuffer<GlesTexture>; 3], start_at: Duration) -> Self {
|
pub fn new(
|
||||||
|
from_texture: [TextureBuffer<GlesTexture>; 3],
|
||||||
|
delay: Duration,
|
||||||
|
clock: Clock,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
from_texture,
|
from_texture,
|
||||||
start_at,
|
start_at: clock.now_unadjusted() + delay,
|
||||||
alpha: 1.,
|
clock,
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn advance_animations(&mut self, current_time: Duration) {
|
|
||||||
if self.start_at + DURATION <= current_time {
|
|
||||||
self.alpha = 0.;
|
|
||||||
} else if self.start_at <= current_time {
|
|
||||||
self.alpha = 1. - (current_time - self.start_at).as_secs_f32() / DURATION.as_secs_f32();
|
|
||||||
} else {
|
|
||||||
self.alpha = 1.;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_done(&self) -> bool {
|
pub fn is_done(&self) -> bool {
|
||||||
self.alpha == 0.
|
self.start_at + DURATION <= self.clock.now_unadjusted()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_render_elements(&mut self, scale: Scale<f64>, transform: Transform) {
|
pub fn update_render_elements(&mut self, scale: Scale<f64>, transform: Transform) {
|
||||||
@@ -53,6 +48,17 @@ impl ScreenTransition {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn render(&self, target: RenderTarget) -> PrimaryGpuTextureRenderElement {
|
pub fn render(&self, target: RenderTarget) -> PrimaryGpuTextureRenderElement {
|
||||||
|
// Screen transition ignores animation slowdown.
|
||||||
|
let now = self.clock.now_unadjusted();
|
||||||
|
|
||||||
|
let alpha = if self.start_at + DURATION <= now {
|
||||||
|
0.
|
||||||
|
} else if self.start_at <= now {
|
||||||
|
1. - (now - self.start_at).as_secs_f32() / DURATION.as_secs_f32()
|
||||||
|
} else {
|
||||||
|
1.
|
||||||
|
};
|
||||||
|
|
||||||
let idx = match target {
|
let idx = match target {
|
||||||
RenderTarget::Output => 0,
|
RenderTarget::Output => 0,
|
||||||
RenderTarget::Screencast => 1,
|
RenderTarget::Screencast => 1,
|
||||||
@@ -62,7 +68,7 @@ impl ScreenTransition {
|
|||||||
PrimaryGpuTextureRenderElement(TextureRenderElement::from_texture_buffer(
|
PrimaryGpuTextureRenderElement(TextureRenderElement::from_texture_buffer(
|
||||||
self.from_texture[idx].clone(),
|
self.from_texture[idx].clone(),
|
||||||
(0., 0.),
|
(0., 0.),
|
||||||
self.alpha,
|
alpha,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
Kind::Unspecified,
|
Kind::Unspecified,
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ use std::cmp::{max, min};
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::iter::zip;
|
use std::iter::zip;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use arrayvec::ArrayVec;
|
use arrayvec::ArrayVec;
|
||||||
@@ -185,7 +184,7 @@ impl ScreenshotUi {
|
|||||||
|
|
||||||
let open_anim = {
|
let open_anim = {
|
||||||
let c = config.borrow();
|
let c = config.borrow();
|
||||||
Animation::new(clock.now(), 0., 1., 0., c.animations.screenshot_ui_open.0)
|
Animation::new(clock.clone(), 0., 1., 0., c.animations.screenshot_ui_open.0)
|
||||||
};
|
};
|
||||||
|
|
||||||
*self = Self::Open {
|
*self = Self::Open {
|
||||||
@@ -238,13 +237,7 @@ impl ScreenshotUi {
|
|||||||
matches!(self, ScreenshotUi::Open { .. })
|
matches!(self, ScreenshotUi::Open { .. })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn advance_animations(&mut self, current_time: Duration) {
|
pub fn advance_animations(&mut self) {}
|
||||||
let Self::Open { open_anim, .. } = self else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
open_anim.set_current_time(current_time);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn are_animations_ongoing(&self) -> bool {
|
pub fn are_animations_ongoing(&self) -> bool {
|
||||||
let Self::Open { open_anim, .. } = self else {
|
let Self::Open { open_anim, .. } = self else {
|
||||||
|
|||||||
Reference in New Issue
Block a user