mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-24 02:01:18 +07:00
exit_confirm_dialog: Add open/close animation
This commit is contained in:
@@ -46,6 +46,10 @@ animations {
|
||||
spring damping-ratio=0.6 stiffness=1000 epsilon=0.001
|
||||
}
|
||||
|
||||
exit-confirmation-open-close {
|
||||
spring damping-ratio=0.6 stiffness=500 epsilon=0.01
|
||||
}
|
||||
|
||||
screenshot-ui-open {
|
||||
duration-ms 200
|
||||
curve "ease-out-quad"
|
||||
@@ -363,6 +367,22 @@ animations {
|
||||
}
|
||||
```
|
||||
|
||||
#### `exit-confirmation-open-close`
|
||||
|
||||
<sup>Since: next release</sup>
|
||||
|
||||
The open/close animation of the exit confirmation dialog.
|
||||
|
||||
This one uses an underdamped spring by default (`damping-ratio=0.6`) which causes a slight oscillation in the end.
|
||||
|
||||
```kdl
|
||||
animations {
|
||||
exit-confirmation-open-close {
|
||||
spring damping-ratio=0.6 stiffness=500 epsilon=0.01
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### `screenshot-ui-open`
|
||||
|
||||
<sup>Since: 0.1.8</sup>
|
||||
|
||||
@@ -1109,6 +1109,8 @@ pub struct Animations {
|
||||
#[knuffel(child, default)]
|
||||
pub config_notification_open_close: ConfigNotificationOpenCloseAnim,
|
||||
#[knuffel(child, default)]
|
||||
pub exit_confirmation_open_close: ExitConfirmationOpenCloseAnim,
|
||||
#[knuffel(child, default)]
|
||||
pub screenshot_ui_open: ScreenshotUiOpenAnim,
|
||||
#[knuffel(child, default)]
|
||||
pub overview_open_close: OverviewOpenCloseAnim,
|
||||
@@ -1126,6 +1128,7 @@ impl Default for Animations {
|
||||
window_close: Default::default(),
|
||||
window_resize: Default::default(),
|
||||
config_notification_open_close: Default::default(),
|
||||
exit_confirmation_open_close: Default::default(),
|
||||
screenshot_ui_open: Default::default(),
|
||||
overview_open_close: Default::default(),
|
||||
}
|
||||
@@ -1260,6 +1263,22 @@ impl Default for ConfigNotificationOpenCloseAnim {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct ExitConfirmationOpenCloseAnim(pub Animation);
|
||||
|
||||
impl Default for ExitConfirmationOpenCloseAnim {
|
||||
fn default() -> Self {
|
||||
Self(Animation {
|
||||
off: false,
|
||||
kind: AnimationKind::Spring(SpringParams {
|
||||
damping_ratio: 0.6,
|
||||
stiffness: 500,
|
||||
epsilon: 0.01,
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct ScreenshotUiOpenAnim(pub Animation);
|
||||
|
||||
@@ -3338,6 +3357,21 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> knuffel::Decode<S> for ExitConfirmationOpenCloseAnim
|
||||
where
|
||||
S: knuffel::traits::ErrorSpan,
|
||||
{
|
||||
fn decode_node(
|
||||
node: &knuffel::ast::SpannedNode<S>,
|
||||
ctx: &mut knuffel::decode::Context<S>,
|
||||
) -> Result<Self, DecodeError<S>> {
|
||||
let default = Self::default().0;
|
||||
Ok(Self(Animation::decode_node(node, ctx, default, |_, _| {
|
||||
Ok(false)
|
||||
})?))
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> knuffel::Decode<S> for ScreenshotUiOpenAnim
|
||||
where
|
||||
S: knuffel::traits::ErrorSpan,
|
||||
@@ -5148,6 +5182,18 @@ mod tests {
|
||||
),
|
||||
},
|
||||
),
|
||||
exit_confirmation_open_close: ExitConfirmationOpenCloseAnim(
|
||||
Animation {
|
||||
off: false,
|
||||
kind: Spring(
|
||||
SpringParams {
|
||||
damping_ratio: 0.6,
|
||||
stiffness: 500,
|
||||
epsilon: 0.01,
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
screenshot_ui_open: ScreenshotUiOpenAnim(
|
||||
Animation {
|
||||
off: false,
|
||||
|
||||
+3
-1
@@ -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();
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
Reference in New Issue
Block a user