aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorIvan Molodetskikh <yalterz@gmail.com>2025-08-22 08:50:52 +0300
committerIvan Molodetskikh <yalterz@gmail.com>2025-08-22 08:57:08 +0300
commit210d5e90fe00ae9add5d841e1752b7f8c4a639a7 (patch)
treeef1431ecb1744984105e7b30520581cb5a1c5219 /src
parent9d3beb49315ae4cde3d8edfb979fccda52af3fae (diff)
downloadniri-210d5e90fe00ae9add5d841e1752b7f8c4a639a7.tar.gz
niri-210d5e90fe00ae9add5d841e1752b7f8c4a639a7.tar.bz2
niri-210d5e90fe00ae9add5d841e1752b7f8c4a639a7.zip
exit_confirm_dialog: Add open/close animation
Diffstat (limited to 'src')
-rw-r--r--src/niri.rs4
-rw-r--r--src/ui/exit_confirm_dialog.rs110
2 files changed, 93 insertions, 21 deletions
diff --git a/src/niri.rs b/src/niri.rs
index b4b4a665..c97153f1 100644
--- a/src/niri.rs
+++ b/src/niri.rs
@@ -2460,7 +2460,7 @@ impl Niri {
hotkey_overlay.show();
}
- let exit_confirm_dialog = ExitConfirmDialog::new();
+ let exit_confirm_dialog = ExitConfirmDialog::new(animation_clock.clone(), config.clone());
event_loop
.insert_source(
@@ -4050,6 +4050,7 @@ impl Niri {
self.layout.advance_animations();
self.config_error_notification.advance_animations();
+ self.exit_confirm_dialog.advance_animations();
self.screenshot_ui.advance_animations();
for state in self.output_state.values_mut() {
@@ -4400,6 +4401,7 @@ impl Niri {
state.unfinished_animations_remain = self.layout.are_animations_ongoing(Some(output));
state.unfinished_animations_remain |=
self.config_error_notification.are_animations_ongoing();
+ state.unfinished_animations_remain |= self.exit_confirm_dialog.are_animations_ongoing();
state.unfinished_animations_remain |= self.screenshot_ui.are_animations_ongoing();
state.unfinished_animations_remain |= state.screen_transition.is_some();
diff --git a/src/ui/exit_confirm_dialog.rs b/src/ui/exit_confirm_dialog.rs
index 9162118b..e0cc9398 100644
--- a/src/ui/exit_confirm_dialog.rs
+++ b/src/ui/exit_confirm_dialog.rs
@@ -1,16 +1,20 @@
use std::cell::RefCell;
use std::collections::HashMap;
+use std::rc::Rc;
use std::sync::Mutex;
use arrayvec::ArrayVec;
+use niri_config::Config;
use ordered_float::NotNan;
use pangocairo::cairo::{self, ImageSurface};
use pangocairo::pango::{Alignment, FontDescription};
+use smithay::backend::renderer::element::utils::RescaleRenderElement;
use smithay::backend::renderer::element::Kind;
use smithay::output::Output;
use smithay::reexports::gbm::Format as Fourcc;
use smithay::utils::{Point, Transform};
+use crate::animation::{Animation, Clock};
use crate::niri_render_elements;
use crate::render_helpers::memory::MemoryBuffer;
use crate::render_helpers::primary_gpu_texture::PrimaryGpuTextureRenderElement;
@@ -27,13 +31,16 @@ const BORDER: i32 = 8;
const BACKDROP_COLOR: [f32; 4] = [0., 0., 0., 0.4];
pub struct ExitConfirmDialog {
- is_open: bool,
+ state: State,
buffers: RefCell<HashMap<NotNan<f64>, Option<MemoryBuffer>>>,
+
+ clock: Clock,
+ config: Rc<RefCell<Config>>,
}
niri_render_elements! {
ExitConfirmDialogRenderElement => {
- Texture = PrimaryGpuTextureRenderElement,
+ Texture = RescaleRenderElement<PrimaryGpuTextureRenderElement>,
SolidColor = SolidColorRenderElement,
}
}
@@ -42,8 +49,15 @@ struct OutputData {
backdrop: SolidColorBuffer,
}
+enum State {
+ Hidden,
+ Showing(Animation),
+ Visible,
+ Hiding(Animation),
+}
+
impl ExitConfirmDialog {
- pub fn new() -> Self {
+ pub fn new(clock: Clock, config: Rc<RefCell<Config>>) -> Self {
let buffer = match render(1.) {
Ok(x) => Some(x),
Err(err) => {
@@ -53,8 +67,10 @@ impl ExitConfirmDialog {
};
Self {
- is_open: false,
+ state: State::Hidden,
buffers: RefCell::new(HashMap::from([(NotNan::new(1.).unwrap(), buffer)])),
+ clock,
+ config,
}
}
@@ -64,26 +80,72 @@ impl ExitConfirmDialog {
fallback.is_some()
}
+ fn animation(&self, from: f64, to: f64) -> Animation {
+ let c = self.config.borrow();
+ Animation::new(
+ self.clock.clone(),
+ from,
+ to,
+ 0.,
+ c.animations.exit_confirmation_open_close.0,
+ )
+ }
+
+ fn value(&self) -> f64 {
+ match &self.state {
+ State::Hidden => 0.,
+ State::Showing(anim) | State::Hiding(anim) => anim.value(),
+ State::Visible => 1.,
+ }
+ }
+
+ /// Returns true if the dialog will be shown (even if it is already shown).
pub fn show(&mut self) -> bool {
if !self.can_show() {
return false;
}
- self.is_open = true;
+ if self.is_open() {
+ return true;
+ }
+
+ self.state = State::Showing(self.animation(self.value(), 1.));
true
}
+ /// Returns true if started the hide animation.
pub fn hide(&mut self) -> bool {
- if self.is_open {
- self.is_open = false;
- true
- } else {
- false
+ if !self.is_open() {
+ return false;
}
+
+ self.state = State::Hiding(self.animation(self.value(), 0.));
+ true
}
pub fn is_open(&self) -> bool {
- self.is_open
+ matches!(self.state, State::Showing(_) | State::Visible)
+ }
+
+ pub fn advance_animations(&mut self) {
+ match &mut self.state {
+ State::Hidden => (),
+ State::Showing(anim) => {
+ if anim.is_done() {
+ self.state = State::Visible;
+ }
+ }
+ State::Visible => (),
+ State::Hiding(anim) => {
+ if anim.is_clamped_done() {
+ self.state = State::Hidden;
+ }
+ }
+ }
+ }
+
+ pub fn are_animations_ongoing(&self) -> bool {
+ matches!(self.state, State::Showing(_) | State::Hiding(_))
}
pub fn render<R: NiriRenderer>(
@@ -93,9 +155,13 @@ impl ExitConfirmDialog {
) -> ArrayVec<ExitConfirmDialogRenderElement, 2> {
let mut rv = ArrayVec::new();
- if !self.is_open {
- return rv;
- }
+ let (value, clamped_value) = match &self.state {
+ State::Hidden => return rv,
+ State::Showing(anim) | State::Hiding(anim) => (anim.value(), anim.clamped_value()),
+ State::Visible => (1., 1.),
+ };
+ // Can be out of range when starting from past 0. or 1. from a spring bounce.
+ let clamped_value = clamped_value.clamp(0., 1.);
let scale = output.current_scale().fractional_scale();
let output_size = output_size(output);
@@ -117,7 +183,7 @@ impl ExitConfirmDialog {
return rv;
};
- let location = (output_size.to_f64().to_point() - size.to_point()).downscale(2.);
+ let location = (output_size.to_point() - size.to_point()).downscale(2.);
let mut location = location.to_physical_precise_round(scale).to_logical(scale);
location.x = f64::max(0., location.x);
location.y = f64::max(0., location.y);
@@ -125,14 +191,18 @@ impl ExitConfirmDialog {
let elem = TextureRenderElement::from_texture_buffer(
buffer,
location,
- 1.,
+ clamped_value as f32,
None,
None,
Kind::Unspecified,
);
- rv.push(ExitConfirmDialogRenderElement::Texture(
- PrimaryGpuTextureRenderElement(elem),
- ));
+ let elem = PrimaryGpuTextureRenderElement(elem);
+ let elem = RescaleRenderElement::from_element(
+ elem,
+ (location + size.downscale(2.)).to_physical_precise_round(scale),
+ value.max(0.) * 0.2 + 0.8,
+ );
+ rv.push(ExitConfirmDialogRenderElement::Texture(elem));
// Backdrop.
let data = output.user_data().get_or_insert(|| {
@@ -146,7 +216,7 @@ impl ExitConfirmDialog {
let elem = SolidColorRenderElement::from_buffer(
&data.backdrop,
Point::new(0., 0.),
- 1.,
+ clamped_value as f32,
Kind::Unspecified,
);
rv.push(ExitConfirmDialogRenderElement::SolidColor(elem));