Add open and close animations, xdg_popup blur pre-pass,
damage viz config option.
This commit is contained in:
parent
12adb678bb
commit
e35dce433a
24 changed files with 1056 additions and 67 deletions
|
|
@ -169,3 +169,55 @@ impl Default for BlurConfigIpc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq)]
|
||||||
|
pub enum AnimationCurveIpc {
|
||||||
|
Linear,
|
||||||
|
EaseOut,
|
||||||
|
EaseInOut,
|
||||||
|
/// Standard CSS cubic-bezier(x1, y1, x2, y2). P0=(0,0), P3=(1,1) are fixed.
|
||||||
|
Bezier {
|
||||||
|
x1: f32,
|
||||||
|
y1: f32,
|
||||||
|
x2: f32,
|
||||||
|
y2: f32,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Copy, Clone, Debug)]
|
||||||
|
pub struct AnimationsConfigIpc {
|
||||||
|
pub enabled: bool,
|
||||||
|
pub open_duration_ms: u32,
|
||||||
|
pub open_curve: AnimationCurveIpc,
|
||||||
|
pub close_duration_ms: u32,
|
||||||
|
pub close_curve: AnimationCurveIpc,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for AnimationsConfigIpc {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
enabled: false,
|
||||||
|
open_duration_ms: 200,
|
||||||
|
open_curve: AnimationCurveIpc::EaseOut,
|
||||||
|
close_duration_ms: 200,
|
||||||
|
close_curve: AnimationCurveIpc::EaseOut,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Copy, Clone, Debug)]
|
||||||
|
pub struct DamageVisualizationIpc {
|
||||||
|
pub enabled: bool,
|
||||||
|
pub color: crate::theme::Color,
|
||||||
|
pub decay_millis: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for DamageVisualizationIpc {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
enabled: false,
|
||||||
|
color: crate::theme::Color::new_straight(255, 0, 0, 128),
|
||||||
|
decay_millis: 2000,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -880,6 +880,14 @@ impl ConfigClient {
|
||||||
self.send(&ClientMessage::SetBlurConfig { config })
|
self.send(&ClientMessage::SetBlurConfig { config })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_damage_visualization(&self, config: crate::_private::DamageVisualizationIpc) {
|
||||||
|
self.send(&ClientMessage::SetDamageVisualization { config })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_animations_config(&self, config: crate::_private::AnimationsConfigIpc) {
|
||||||
|
self.send(&ClientMessage::SetAnimationsConfig { config })
|
||||||
|
}
|
||||||
|
|
||||||
pub fn switch_to_vt(&self, vtnr: u32) {
|
pub fn switch_to_vt(&self, vtnr: u32) {
|
||||||
self.send(&ClientMessage::SwitchTo { vtnr })
|
self.send(&ClientMessage::SwitchTo { vtnr })
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
_private::{
|
_private::{
|
||||||
BlurConfigIpc, ClientCriterionIpc, LayerRuleIpc, PollableId, WindowCriterionIpc,
|
BlurConfigIpc, ClientCriterionIpc, DamageVisualizationIpc, LayerRuleIpc, PollableId,
|
||||||
WireMode,
|
WindowCriterionIpc, WireMode,
|
||||||
},
|
},
|
||||||
Axis, Direction, PciId, Workspace,
|
Axis, Direction, PciId, Workspace,
|
||||||
client::{Client, ClientCapabilities, ClientMatcher},
|
client::{Client, ClientCapabilities, ClientMatcher},
|
||||||
|
|
@ -925,6 +925,12 @@ pub enum ClientMessage<'a> {
|
||||||
SetBlurConfig {
|
SetBlurConfig {
|
||||||
config: BlurConfigIpc,
|
config: BlurConfigIpc,
|
||||||
},
|
},
|
||||||
|
SetDamageVisualization {
|
||||||
|
config: DamageVisualizationIpc,
|
||||||
|
},
|
||||||
|
SetAnimationsConfig {
|
||||||
|
config: crate::_private::AnimationsConfigIpc,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
|
|
||||||
|
|
@ -407,6 +407,34 @@ pub fn _set_blur_config(config: crate::_private::BlurConfigIpc) {
|
||||||
get!().set_blur_config(config)
|
get!().set_blur_config(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn _set_damage_visualization(config: crate::_private::DamageVisualizationIpc) {
|
||||||
|
get!().set_damage_visualization(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn _set_animations_config(config: crate::_private::AnimationsConfigIpc) {
|
||||||
|
get!().set_animations_config(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configures the damage region visualizer.
|
||||||
|
///
|
||||||
|
/// When enabled, every damaged screen region is overlaid with `color` and fades
|
||||||
|
/// out over `decay` (producing a "blink" effect as new damage accumulates).
|
||||||
|
/// Useful for debugging damage-tracked rendering paths.
|
||||||
|
pub fn set_damage_visualization(
|
||||||
|
enabled: bool,
|
||||||
|
color: crate::theme::Color,
|
||||||
|
decay: std::time::Duration,
|
||||||
|
) {
|
||||||
|
let decay_millis = decay.as_millis().min(u64::MAX as u128) as u64;
|
||||||
|
_set_damage_visualization(crate::_private::DamageVisualizationIpc {
|
||||||
|
enabled,
|
||||||
|
color,
|
||||||
|
decay_millis,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the current corner radius for window borders.
|
/// Returns the current corner radius for window borders.
|
||||||
pub fn get_corner_radius() -> f32 {
|
pub fn get_corner_radius() -> f32 {
|
||||||
get!(0.0).get_corner_radius()
|
get!(0.0).get_corner_radius()
|
||||||
|
|
|
||||||
193
src/animation.rs
Normal file
193
src/animation.rs
Normal file
|
|
@ -0,0 +1,193 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
allocator::{BO_USE_RENDERING, BufferUsage},
|
||||||
|
format::ARGB8888,
|
||||||
|
gfx_api::{AcquireSync, GfxTexture, ReleaseSync, needs_render_usage},
|
||||||
|
rect::Rect,
|
||||||
|
renderer::Renderer,
|
||||||
|
state::State,
|
||||||
|
theme::Color,
|
||||||
|
tree::{OutputNode, ToplevelNode, Transform},
|
||||||
|
video::Modifier,
|
||||||
|
},
|
||||||
|
std::{cell::Cell, rc::Weak},
|
||||||
|
std::rc::Rc,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A captured snapshot of a toplevel's last rendered state, used to drive the
|
||||||
|
/// close animation after the toplevel itself has been torn down. Owns its own
|
||||||
|
/// GPU texture so the source client buffers can be released immediately.
|
||||||
|
pub struct Snapshot {
|
||||||
|
pub texture: Rc<dyn GfxTexture>,
|
||||||
|
/// The output the toplevel was on, used to schedule per-frame damage.
|
||||||
|
pub output: Weak<OutputNode>,
|
||||||
|
/// Logical absolute position the toplevel occupied, used to draw the
|
||||||
|
/// snapshot into the correct screen region during the close animation.
|
||||||
|
pub rect: Rect,
|
||||||
|
/// Slide-out direction in logical pixels. The snapshot moves from (0, 0)
|
||||||
|
/// at start to (slide_dx, slide_dy) at end.
|
||||||
|
pub slide_dx: f32,
|
||||||
|
pub slide_dy: f32,
|
||||||
|
pub start_nsec: Cell<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Snapshot {
|
||||||
|
/// Returns the eased close-animation progress in [0, 1], or None if the
|
||||||
|
/// animation has finished.
|
||||||
|
pub fn close_progress(&self, state: &State) -> Option<f32> {
|
||||||
|
let cfg = state.animations_config.get();
|
||||||
|
if !cfg.enabled || cfg.close_duration_ms == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let now = state.now_nsec();
|
||||||
|
let elapsed = now.saturating_sub(self.start_nsec.get());
|
||||||
|
let dur = (cfg.close_duration_ms as u64).saturating_mul(1_000_000);
|
||||||
|
if elapsed >= dur {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let t = (elapsed as f32) / (dur as f32);
|
||||||
|
let eased = match cfg.close_curve {
|
||||||
|
jay_config::_private::AnimationCurveIpc::Linear => t,
|
||||||
|
jay_config::_private::AnimationCurveIpc::EaseOut => {
|
||||||
|
let inv = 1.0 - t;
|
||||||
|
1.0 - inv * inv * inv
|
||||||
|
}
|
||||||
|
jay_config::_private::AnimationCurveIpc::EaseInOut => {
|
||||||
|
if t < 0.5 {
|
||||||
|
4.0 * t * t * t
|
||||||
|
} else {
|
||||||
|
let f = -2.0 * t + 2.0;
|
||||||
|
1.0 - f * f * f / 2.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jay_config::_private::AnimationCurveIpc::Bezier { x1, y1, x2, y2 } => {
|
||||||
|
cubic_bezier_y_at_x(t, x1, y1, x2, y2)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Some(eased.clamp(0.0, 1.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cubic_bezier_y_at_x(x: f32, x1: f32, y1: f32, x2: f32, y2: f32) -> f32 {
|
||||||
|
fn bx(t: f32, x1: f32, x2: f32) -> f32 {
|
||||||
|
let it = 1.0 - t;
|
||||||
|
3.0 * it * it * t * x1 + 3.0 * it * t * t * x2 + t * t * t
|
||||||
|
}
|
||||||
|
fn dbx(t: f32, x1: f32, x2: f32) -> f32 {
|
||||||
|
let it = 1.0 - t;
|
||||||
|
3.0 * it * it * x1 + 6.0 * it * t * (x2 - x1) + 3.0 * t * t * (1.0 - x2)
|
||||||
|
}
|
||||||
|
let mut t = x;
|
||||||
|
for _ in 0..8 {
|
||||||
|
let err = bx(t, x1, x2) - x;
|
||||||
|
if err.abs() < 1e-4 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let d = dbx(t, x1, x2);
|
||||||
|
if d.abs() < 1e-6 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
t = (t - err / d).clamp(0.0, 1.0);
|
||||||
|
}
|
||||||
|
let it = 1.0 - t;
|
||||||
|
3.0 * it * it * t * y1 + 3.0 * it * t * t * y2 + t * t * t
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Renders the toplevel into a private GPU texture and returns the texture.
|
||||||
|
/// Used at unmap time to capture the last-rendered state, so a close animation
|
||||||
|
/// can run after the toplevel itself has been destroyed. Returns None if the
|
||||||
|
/// render context is unavailable, the toplevel has no workspace, or
|
||||||
|
/// allocation/rendering fails.
|
||||||
|
///
|
||||||
|
/// Any open animation in flight on the toplevel is cleared before rendering so
|
||||||
|
/// the snapshot is at full opacity / final position.
|
||||||
|
pub fn capture_snapshot(state: &State, tl: &Rc<dyn ToplevelNode>) -> Option<Snapshot> {
|
||||||
|
let ctx = state.render_ctx.get()?;
|
||||||
|
let formats = ctx.formats();
|
||||||
|
let format_info = formats.get(&ARGB8888.drm)?;
|
||||||
|
let modifiers: Vec<Modifier> = format_info
|
||||||
|
.write_modifiers
|
||||||
|
.iter()
|
||||||
|
.filter(|(m, _)| format_info.read_modifiers.contains(*m))
|
||||||
|
.map(|(m, _)| *m)
|
||||||
|
.collect();
|
||||||
|
if modifiers.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let data = tl.tl_data();
|
||||||
|
data.anim_open_start_nsec.set(None);
|
||||||
|
let workspace = data.workspace.get()?;
|
||||||
|
let output = workspace.output.get();
|
||||||
|
let scale = output.global.persistent.scale.get();
|
||||||
|
let scalef = scale.to_f64();
|
||||||
|
let tl_rect = tl.node_absolute_position();
|
||||||
|
let pw = (tl_rect.width() as f64 * scalef).round() as i32;
|
||||||
|
let ph = (tl_rect.height() as f64 * scalef).round() as i32;
|
||||||
|
if pw <= 0 || ph <= 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let allocator = ctx.allocator();
|
||||||
|
let mut usage = BO_USE_RENDERING;
|
||||||
|
if !needs_render_usage(format_info.write_modifiers.values()) {
|
||||||
|
usage = BufferUsage::none();
|
||||||
|
}
|
||||||
|
let bo = allocator
|
||||||
|
.create_bo(&state.dma_buf_ids, pw, ph, ARGB8888, &modifiers, usage)
|
||||||
|
.ok()?;
|
||||||
|
let img = ctx.clone().dmabuf_img(bo.dmabuf()).ok()?;
|
||||||
|
let fb = img.clone().to_framebuffer().ok()?;
|
||||||
|
let mut ops = vec![];
|
||||||
|
{
|
||||||
|
let mut renderer = Renderer {
|
||||||
|
base: fb.renderer_base(&mut ops, scale, Transform::None),
|
||||||
|
state,
|
||||||
|
logical_extents: tl_rect.at_point(0, 0),
|
||||||
|
pixel_extents: Rect::new_saturating(0, 0, pw, ph),
|
||||||
|
stretch: None,
|
||||||
|
corner_radius: None,
|
||||||
|
current_anim_node: None,
|
||||||
|
};
|
||||||
|
tl.clone().node_render(&mut renderer, 0, 0, None);
|
||||||
|
}
|
||||||
|
let cd = state.color_manager.srgb_gamma22();
|
||||||
|
fb.render(
|
||||||
|
AcquireSync::Unnecessary,
|
||||||
|
ReleaseSync::Implicit,
|
||||||
|
cd,
|
||||||
|
&ops,
|
||||||
|
Some(&Color::TRANSPARENT),
|
||||||
|
&cd.linear,
|
||||||
|
None,
|
||||||
|
cd,
|
||||||
|
)
|
||||||
|
.ok()?;
|
||||||
|
let texture = img.to_texture().ok()?;
|
||||||
|
|
||||||
|
// Slide-out direction: same closest-edge logic as the open animation, but
|
||||||
|
// the snapshot moves AWAY from its rect during close. Computed once here so
|
||||||
|
// we don't need the toplevel's tile lookup anymore once it's torn down.
|
||||||
|
let output_rect = output.global.pos.get();
|
||||||
|
let dl = (tl_rect.x1() - output_rect.x1()).max(0) as f32;
|
||||||
|
let dr = (output_rect.x2() - tl_rect.x2()).max(0) as f32;
|
||||||
|
let dt = (tl_rect.y1() - output_rect.y1()).max(0) as f32;
|
||||||
|
let db = (output_rect.y2() - tl_rect.y2()).max(0) as f32;
|
||||||
|
let mind = dl.min(dr).min(dt).min(db);
|
||||||
|
let (slide_dx, slide_dy) = if mind == dl {
|
||||||
|
(-(tl_rect.width() as f32), 0.0)
|
||||||
|
} else if mind == dr {
|
||||||
|
(tl_rect.width() as f32, 0.0)
|
||||||
|
} else if mind == dt {
|
||||||
|
(0.0, -(tl_rect.height() as f32))
|
||||||
|
} else {
|
||||||
|
(0.0, tl_rect.height() as f32)
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(Snapshot {
|
||||||
|
texture,
|
||||||
|
output: Rc::downgrade(&output),
|
||||||
|
rect: tl_rect,
|
||||||
|
slide_dx,
|
||||||
|
slide_dy,
|
||||||
|
start_nsec: Cell::new(state.now_nsec()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -399,6 +399,10 @@ fn start_compositor2(
|
||||||
hyprland_global_shortcuts: Default::default(),
|
hyprland_global_shortcuts: Default::default(),
|
||||||
layer_rules: Default::default(),
|
layer_rules: Default::default(),
|
||||||
blur_config: Default::default(),
|
blur_config: Default::default(),
|
||||||
|
blur_cache_epoch: Default::default(),
|
||||||
|
animations_config: Default::default(),
|
||||||
|
active_animations: Default::default(),
|
||||||
|
close_snapshots: Default::default(),
|
||||||
});
|
});
|
||||||
state.tracker.register(ClientId::from_raw(0));
|
state.tracker.register(ClientId::from_raw(0));
|
||||||
create_dummy_output(&state);
|
create_dummy_output(&state);
|
||||||
|
|
|
||||||
|
|
@ -3537,6 +3537,20 @@ impl ConfigProxyHandler {
|
||||||
ClientMessage::SetBlurConfig { config } => {
|
ClientMessage::SetBlurConfig { config } => {
|
||||||
self.state.blur_config.set(config);
|
self.state.blur_config.set(config);
|
||||||
}
|
}
|
||||||
|
ClientMessage::SetAnimationsConfig { config } => {
|
||||||
|
self.state.animations_config.set(config);
|
||||||
|
}
|
||||||
|
ClientMessage::SetDamageVisualization { config } => {
|
||||||
|
let [r, g, b, a] = config.color.to_u8_straight();
|
||||||
|
let color = crate::theme::Color::from_srgba_straight(r, g, b, a);
|
||||||
|
self.state.damage_visualizer.set_color(color);
|
||||||
|
self.state
|
||||||
|
.damage_visualizer
|
||||||
|
.set_decay(std::time::Duration::from_millis(config.decay_millis));
|
||||||
|
self.state
|
||||||
|
.damage_visualizer
|
||||||
|
.set_enabled(&self.state, config.enabled);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -107,12 +107,36 @@ pub enum GfxApiOpt {
|
||||||
BlurBackdrop(BlurBackdrop),
|
BlurBackdrop(BlurBackdrop),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Clone)]
|
||||||
pub struct BlurBackdrop {
|
pub struct BlurBackdrop {
|
||||||
pub rect: FramebufferRect,
|
pub rect: FramebufferRect,
|
||||||
pub passes: u8,
|
pub passes: u8,
|
||||||
pub offset: f32,
|
pub offset: f32,
|
||||||
pub mask: Option<BlurMask>,
|
pub mask: Option<BlurMask>,
|
||||||
|
pub cache: Option<Rc<std::cell::RefCell<Option<BlurCacheEntry>>>>,
|
||||||
|
pub cache_epoch: u64,
|
||||||
|
pub cache_pixel_rect: [i32; 4],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for BlurBackdrop {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("BlurBackdrop")
|
||||||
|
.field("rect", &self.rect)
|
||||||
|
.field("passes", &self.passes)
|
||||||
|
.field("offset", &self.offset)
|
||||||
|
.field("mask", &self.mask)
|
||||||
|
.field("cache_epoch", &self.cache_epoch)
|
||||||
|
.field("cache_pixel_rect", &self.cache_pixel_rect)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct BlurCacheEntry {
|
||||||
|
pub pixel_rect: [i32; 4],
|
||||||
|
pub passes: u8,
|
||||||
|
pub offset: f32,
|
||||||
|
pub epoch: u64,
|
||||||
|
pub image: Rc<dyn GfxTexture>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
|
@ -120,6 +144,9 @@ pub struct BlurMask {
|
||||||
pub texture: Rc<dyn GfxTexture>,
|
pub texture: Rc<dyn GfxTexture>,
|
||||||
pub source: SampleRect,
|
pub source: SampleRect,
|
||||||
pub threshold: f32,
|
pub threshold: f32,
|
||||||
|
pub buffer_resv: Option<Rc<dyn BufferResv>>,
|
||||||
|
pub acquire_sync: AcquireSync,
|
||||||
|
pub release_sync: ReleaseSync,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for BlurMask {
|
impl std::fmt::Debug for BlurMask {
|
||||||
|
|
@ -785,6 +812,7 @@ impl dyn GfxFramebuffer {
|
||||||
},
|
},
|
||||||
stretch: None,
|
stretch: None,
|
||||||
corner_radius: None,
|
corner_radius: None,
|
||||||
|
current_anim_node: None,
|
||||||
};
|
};
|
||||||
cursor.render_hardware_cursor(&mut renderer);
|
cursor.render_hardware_cursor(&mut renderer);
|
||||||
self.render(
|
self.render(
|
||||||
|
|
@ -1119,6 +1147,7 @@ pub fn create_render_pass(
|
||||||
},
|
},
|
||||||
stretch: None,
|
stretch: None,
|
||||||
corner_radius: None,
|
corner_radius: None,
|
||||||
|
current_anim_node: None,
|
||||||
};
|
};
|
||||||
node.node_render(&mut renderer, 0, 0, None);
|
node.node_render(&mut renderer, 0, 0, None);
|
||||||
if let Some(rect) = cursor_rect {
|
if let Some(rect) = cursor_rect {
|
||||||
|
|
@ -1193,6 +1222,9 @@ pub fn renderer_base<'a>(
|
||||||
fb_width: width as _,
|
fb_width: width as _,
|
||||||
fb_height: height as _,
|
fb_height: height as _,
|
||||||
discard_alpha: None,
|
discard_alpha: None,
|
||||||
|
alpha_mul: 1.0,
|
||||||
|
translate_x: 0.0,
|
||||||
|
translate_y: 0.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1582,7 +1582,9 @@ impl WlSeatGlobal {
|
||||||
{
|
{
|
||||||
con.disconnect(TextDisconnectReason::FocusLost);
|
con.disconnect(TextDisconnectReason::FocusLost);
|
||||||
}
|
}
|
||||||
if let Some(tis) = self.text_inputs.borrow().get(&surface.client.id) {
|
if !surface.destroyed.get()
|
||||||
|
&& let Some(tis) = self.text_inputs.borrow().get(&surface.client.id)
|
||||||
|
{
|
||||||
for ti in tis.lock().values() {
|
for ti in tis.lock().values() {
|
||||||
ti.send_leave(surface);
|
ti.send_leave(surface);
|
||||||
ti.send_done();
|
ti.send_done();
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ use {
|
||||||
Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, NodeLayerLink,
|
Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, NodeLayerLink,
|
||||||
NodeLocation, NodeVisitor, OutputNode, StackedNode,
|
NodeLocation, NodeVisitor, OutputNode, StackedNode,
|
||||||
},
|
},
|
||||||
utils::{clonecell::CloneCell, smallmap::SmallMap},
|
utils::{clonecell::CloneCell, numcell::NumCell, smallmap::SmallMap},
|
||||||
wire::{XdgPopupId, xdg_popup::*},
|
wire::{XdgPopupId, xdg_popup::*},
|
||||||
},
|
},
|
||||||
std::{
|
std::{
|
||||||
|
|
@ -72,6 +72,7 @@ pub struct LayerPopupBlur {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct XdgPopup {
|
pub struct XdgPopup {
|
||||||
|
pub blur_pre_rendered: Cell<bool>,
|
||||||
pub id: XdgPopupId,
|
pub id: XdgPopupId,
|
||||||
node_id: PopupId,
|
node_id: PopupId,
|
||||||
pub xdg: Rc<XdgSurface>,
|
pub xdg: Rc<XdgSurface>,
|
||||||
|
|
@ -79,6 +80,8 @@ pub struct XdgPopup {
|
||||||
relative_position: Cell<Rect>,
|
relative_position: Cell<Rect>,
|
||||||
pos: RefCell<XdgPositioned>,
|
pos: RefCell<XdgPositioned>,
|
||||||
pub tracker: Tracker<Self>,
|
pub tracker: Tracker<Self>,
|
||||||
|
pub blur_cache: Rc<RefCell<Option<crate::gfx_api::BlurCacheEntry>>>,
|
||||||
|
pub blur_cache_epoch: NumCell<u64>,
|
||||||
seat_state: NodeSeatState,
|
seat_state: NodeSeatState,
|
||||||
set_visible_prepared: Cell<bool>,
|
set_visible_prepared: Cell<bool>,
|
||||||
jay_popup_ext: CloneCell<Option<Rc<JayPopupExtV1>>>,
|
jay_popup_ext: CloneCell<Option<Rc<JayPopupExtV1>>>,
|
||||||
|
|
@ -104,8 +107,11 @@ impl XdgPopup {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
id,
|
id,
|
||||||
node_id: xdg.surface.client.state.node_ids.next(),
|
node_id: xdg.surface.client.state.node_ids.next(),
|
||||||
|
blur_pre_rendered: Cell::new(false),
|
||||||
xdg: xdg.clone(),
|
xdg: xdg.clone(),
|
||||||
parent: Default::default(),
|
parent: Default::default(),
|
||||||
|
blur_cache: Default::default(),
|
||||||
|
blur_cache_epoch: Default::default(),
|
||||||
relative_position: Cell::new(Default::default()),
|
relative_position: Cell::new(Default::default()),
|
||||||
pos: RefCell::new(pos),
|
pos: RefCell::new(pos),
|
||||||
tracker: Default::default(),
|
tracker: Default::default(),
|
||||||
|
|
@ -314,6 +320,9 @@ impl XdgPopupRequestHandler for XdgPopup {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl XdgPopup {
|
impl XdgPopup {
|
||||||
|
pub fn layer_blur_settings(&self) -> Option<LayerPopupBlur> {
|
||||||
|
self.parent.get()?.layer_blur_settings()
|
||||||
|
}
|
||||||
pub fn set_visible(&self, visible: bool) {
|
pub fn set_visible(&self, visible: bool) {
|
||||||
let surface = &self.xdg.surface;
|
let surface = &self.xdg.surface;
|
||||||
let extents = surface.extents.get();
|
let extents = surface.extents.get();
|
||||||
|
|
@ -418,33 +427,56 @@ impl Node for XdgPopup {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn node_render(&self, renderer: &mut Renderer, x: i32, y: i32, bounds: Option<&Rect>) {
|
fn node_render(&self, renderer: &mut Renderer, x: i32, y: i32, bounds: Option<&Rect>) {
|
||||||
let settings = self.parent.get().and_then(|p| p.layer_blur_settings());
|
let settings = self.layer_blur_settings();
|
||||||
if let Some(s) = settings {
|
if let Some(s) = settings {
|
||||||
if s.blur {
|
if s.blur && !self.blur_pre_rendered.get() {
|
||||||
|
// Only push blur if it wasn't already pushed in the pre-pass
|
||||||
let extents = self.xdg.surface.extents.get();
|
let extents = self.xdg.surface.extents.get();
|
||||||
let geo = self.xdg.geometry();
|
let geo = self.xdg.geometry();
|
||||||
let (gx, gy) = geo.translate(x, y);
|
let (gx, gy) = geo.translate(x, y);
|
||||||
let rect = extents.move_(gx, gy);
|
let rect = extents.move_(gx, gy);
|
||||||
let scaled = renderer.base.scale_rect(rect);
|
let popup_blur_rect = if let Some(parent) = self.parent.get() {
|
||||||
let cfg = renderer.state.blur_config.get();
|
let parent_rect = parent.position();
|
||||||
let mask = s.ignore_alpha.and_then(|threshold| {
|
if parent_rect.contains_rect(&rect) {
|
||||||
let buffer = self.xdg.surface.buffer.get()?;
|
None
|
||||||
let texture = buffer.buffer.buf.get_texture(&self.xdg.surface)?;
|
} else {
|
||||||
let source = *self.xdg.surface.buffer_points_norm.borrow();
|
Some(rect)
|
||||||
Some(crate::gfx_api::BlurMask {
|
}
|
||||||
texture,
|
} else {
|
||||||
source,
|
Some(rect)
|
||||||
threshold,
|
};
|
||||||
})
|
if let Some(blur_rect) = popup_blur_rect {
|
||||||
});
|
let scaled = renderer.base.scale_rect(blur_rect);
|
||||||
renderer
|
let cfg = renderer.state.blur_config.get();
|
||||||
.base
|
let mask = s.ignore_alpha.and_then(|threshold| {
|
||||||
.push_blur_backdrop(scaled, cfg.passes, cfg.size, mask);
|
let buffer = self.xdg.surface.buffer.get()?;
|
||||||
|
let texture = buffer.buffer.buf.get_texture(&self.xdg.surface)?;
|
||||||
|
let source = *self.xdg.surface.buffer_points_norm.borrow();
|
||||||
|
let release_sync = buffer.release_sync;
|
||||||
|
Some(crate::gfx_api::BlurMask {
|
||||||
|
texture,
|
||||||
|
source,
|
||||||
|
threshold,
|
||||||
|
buffer_resv: Some(buffer),
|
||||||
|
acquire_sync: crate::gfx_api::AcquireSync::Unnecessary,
|
||||||
|
release_sync,
|
||||||
|
})
|
||||||
|
});
|
||||||
|
renderer.base.push_blur_backdrop(
|
||||||
|
scaled,
|
||||||
|
cfg.passes,
|
||||||
|
cfg.size,
|
||||||
|
mask,
|
||||||
|
Some(self.blur_cache.clone()),
|
||||||
|
self.blur_cache_epoch.get(),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
renderer.base.discard_alpha = s.ignore_alpha;
|
// Always clear the flag after node_render regardless of path
|
||||||
|
self.blur_pre_rendered.set(false);
|
||||||
renderer.render_xdg_surface(&self.xdg, x, y, bounds);
|
renderer.render_xdg_surface(&self.xdg, x, y, bounds);
|
||||||
renderer.base.discard_alpha = None;
|
|
||||||
} else {
|
} else {
|
||||||
|
self.blur_pre_rendered.set(false);
|
||||||
renderer.render_xdg_surface(&self.xdg, x, y, bounds);
|
renderer.render_xdg_surface(&self.xdg, x, y, bounds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -555,6 +555,7 @@ impl XdgToplevel {
|
||||||
self.state.tree_changed();
|
self.state.tree_changed();
|
||||||
self.toplevel_data.mapped_source.trigger();
|
self.toplevel_data.mapped_source.trigger();
|
||||||
self.toplevel_data.broadcast(self.clone());
|
self.toplevel_data.broadcast(self.clone());
|
||||||
|
self.toplevel_data.start_open_animation();
|
||||||
}
|
}
|
||||||
self.toplevel_data
|
self.toplevel_data
|
||||||
.set_content_type(self.xdg.surface.content_type.get());
|
.set_content_type(self.xdg.surface.content_type.get());
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,7 @@ pub struct ZwlrLayerSurfaceV1 {
|
||||||
pub client: Rc<Client>,
|
pub client: Rc<Client>,
|
||||||
pub surface: Rc<WlSurface>,
|
pub surface: Rc<WlSurface>,
|
||||||
pub output: Rc<OutputGlobalOpt>,
|
pub output: Rc<OutputGlobalOpt>,
|
||||||
|
pub blur_cache_epoch: NumCell<u64>,
|
||||||
pub namespace: String,
|
pub namespace: String,
|
||||||
pub tracker: Tracker<Self>,
|
pub tracker: Tracker<Self>,
|
||||||
output_extents: Cell<Rect>,
|
output_extents: Cell<Rect>,
|
||||||
|
|
@ -62,6 +63,7 @@ pub struct ZwlrLayerSurfaceV1 {
|
||||||
pub blur: Cell<bool>,
|
pub blur: Cell<bool>,
|
||||||
pub blur_popups: Cell<bool>,
|
pub blur_popups: Cell<bool>,
|
||||||
pub ignore_alpha: Cell<Option<f32>>,
|
pub ignore_alpha: Cell<Option<f32>>,
|
||||||
|
pub blur_cache: Rc<RefCell<Option<crate::gfx_api::BlurCacheEntry>>>,
|
||||||
requested_serial: NumCell<u32>,
|
requested_serial: NumCell<u32>,
|
||||||
size: Cell<(i32, i32)>,
|
size: Cell<(i32, i32)>,
|
||||||
anchor: Cell<u32>,
|
anchor: Cell<u32>,
|
||||||
|
|
@ -158,6 +160,7 @@ impl ZwlrLayerSurfaceV1 {
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
id,
|
id,
|
||||||
|
blur_cache_epoch: Default::default(),
|
||||||
node_id: shell.client.state.node_ids.next(),
|
node_id: shell.client.state.node_ids.next(),
|
||||||
shell: shell.clone(),
|
shell: shell.clone(),
|
||||||
client: shell.client.clone(),
|
client: shell.client.clone(),
|
||||||
|
|
@ -172,6 +175,7 @@ impl ZwlrLayerSurfaceV1 {
|
||||||
blur: Cell::new(false),
|
blur: Cell::new(false),
|
||||||
blur_popups: Cell::new(false),
|
blur_popups: Cell::new(false),
|
||||||
ignore_alpha: Cell::new(None),
|
ignore_alpha: Cell::new(None),
|
||||||
|
blur_cache: Default::default(),
|
||||||
requested_serial: Default::default(),
|
requested_serial: Default::default(),
|
||||||
size: Cell::new((0, 0)),
|
size: Cell::new((0, 0)),
|
||||||
anchor: Cell::new(0),
|
anchor: Cell::new(0),
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ mod leaks;
|
||||||
mod tracy;
|
mod tracy;
|
||||||
mod acceptor;
|
mod acceptor;
|
||||||
mod allocator;
|
mod allocator;
|
||||||
|
mod animation;
|
||||||
mod async_engine;
|
mod async_engine;
|
||||||
mod backend;
|
mod backend;
|
||||||
mod backends;
|
mod backends;
|
||||||
|
|
|
||||||
215
src/renderer.rs
215
src/renderer.rs
|
|
@ -5,7 +5,7 @@ use {
|
||||||
ifs::wl_surface::{
|
ifs::wl_surface::{
|
||||||
SurfaceBuffer, WlSurface,
|
SurfaceBuffer, WlSurface,
|
||||||
x_surface::xwindow::Xwindow,
|
x_surface::xwindow::Xwindow,
|
||||||
xdg_surface::{XdgSurface, xdg_toplevel::XdgToplevel},
|
xdg_surface::{XdgSurface, xdg_popup::XdgPopup, xdg_toplevel::XdgToplevel},
|
||||||
zwlr_layer_surface_v1::ZwlrLayerSurfaceV1,
|
zwlr_layer_surface_v1::ZwlrLayerSurfaceV1,
|
||||||
},
|
},
|
||||||
rect::Rect,
|
rect::Rect,
|
||||||
|
|
@ -14,8 +14,9 @@ use {
|
||||||
state::State,
|
state::State,
|
||||||
theme::{Color, CornerRadius},
|
theme::{Color, CornerRadius},
|
||||||
tree::{
|
tree::{
|
||||||
ContainerNode, DisplayNode, FloatNode, OutputNode, PlaceholderNode, ToplevelData,
|
ContainerNode, DisplayNode, FloatNode, NodeId, OutputNode, PlaceholderNode,
|
||||||
ToplevelNodeBase, WorkspaceNode, tab_bar::TabBar,
|
StackedNode, ToplevelData, ToplevelNode, ToplevelNodeBase, WorkspaceNode,
|
||||||
|
tab_bar::TabBar,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
std::{ops::Deref, rc::Rc, slice},
|
std::{ops::Deref, rc::Rc, slice},
|
||||||
|
|
@ -30,9 +31,60 @@ pub struct Renderer<'a> {
|
||||||
pub pixel_extents: Rect,
|
pub pixel_extents: Rect,
|
||||||
pub stretch: Option<(i32, i32)>,
|
pub stretch: Option<(i32, i32)>,
|
||||||
pub corner_radius: Option<CornerRadius>,
|
pub corner_radius: Option<CornerRadius>,
|
||||||
|
/// The toplevel whose open-animation transform is currently applied to the
|
||||||
|
/// renderer base. Used to prevent double-applying when a parent (container,
|
||||||
|
/// float) has already entered the animation scope before drawing its own
|
||||||
|
/// per-child decorations.
|
||||||
|
pub current_anim_node: Option<NodeId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub struct OpenAnimSaved {
|
||||||
|
alpha_mul: f32,
|
||||||
|
translate_x: f32,
|
||||||
|
translate_y: f32,
|
||||||
|
prev_node: Option<NodeId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Renderer<'_> {
|
impl Renderer<'_> {
|
||||||
|
pub fn render_layer_popup_blur_only(&mut self, popup: &XdgPopup, x: i32, y: i32) {
|
||||||
|
let Some(settings) = popup.layer_blur_settings() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if !settings.blur {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let extents = popup.xdg.surface.extents.get();
|
||||||
|
let geo = popup.xdg.geometry();
|
||||||
|
let (gx, gy) = geo.translate(x, y);
|
||||||
|
let rect = extents.move_(gx, gy);
|
||||||
|
let scaled = self.base.scale_rect(rect);
|
||||||
|
let cfg = self.state.blur_config.get();
|
||||||
|
let mask = settings.ignore_alpha.and_then(|threshold| {
|
||||||
|
let buffer = popup.xdg.surface.buffer.get()?;
|
||||||
|
let texture = buffer.buffer.buf.get_texture(&popup.xdg.surface)?;
|
||||||
|
let source = *popup.xdg.surface.buffer_points_norm.borrow();
|
||||||
|
let release_sync = buffer.release_sync;
|
||||||
|
Some(crate::gfx_api::BlurMask {
|
||||||
|
texture,
|
||||||
|
source,
|
||||||
|
threshold,
|
||||||
|
buffer_resv: Some(buffer),
|
||||||
|
acquire_sync: AcquireSync::Unnecessary,
|
||||||
|
release_sync,
|
||||||
|
})
|
||||||
|
});
|
||||||
|
popup.blur_pre_rendered.set(true);
|
||||||
|
self.base.push_blur_backdrop(
|
||||||
|
scaled,
|
||||||
|
cfg.passes,
|
||||||
|
cfg.size,
|
||||||
|
mask,
|
||||||
|
Some(popup.blur_cache.clone()),
|
||||||
|
popup.blur_cache_epoch.get(),
|
||||||
|
);
|
||||||
|
self.base.sync();
|
||||||
|
}
|
||||||
pub fn scale(&self) -> Scale {
|
pub fn scale(&self) -> Scale {
|
||||||
self.base.scale
|
self.base.scale
|
||||||
}
|
}
|
||||||
|
|
@ -215,14 +267,28 @@ impl Renderer<'_> {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
render_stacked!(self.state.root.stacked);
|
render_stacked!(self.state.root.stacked);
|
||||||
// Flush RoundedFillRect ops from container/float borders so they don't
|
|
||||||
// sort after (and render on top of) layer-shell CopyTexture ops.
|
|
||||||
self.base.sync();
|
self.base.sync();
|
||||||
if fullscreen.is_none() {
|
if fullscreen.is_none() {
|
||||||
|
// Pre-pass: push blur backdrops for layer-shell popups before
|
||||||
|
// the bar renders, so they sample the raw background rather than
|
||||||
|
// the already-composited bar content.
|
||||||
|
for stacked in self.state.root.stacked_above_layers.iter() {
|
||||||
|
if stacked.node_visible() {
|
||||||
|
let pos = stacked.node_absolute_position();
|
||||||
|
if pos.intersects(&opos) {
|
||||||
|
let (sx, sy) = opos.translate(pos.x1(), pos.y1());
|
||||||
|
let stacked_rc: Rc<dyn StackedNode> = stacked.deref().clone();
|
||||||
|
if let Some(popup) = stacked_rc.node_into_popup() {
|
||||||
|
self.render_layer_popup_blur_only(&popup, sx, sy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
render_layer!(output.layers[2]);
|
render_layer!(output.layers[2]);
|
||||||
}
|
}
|
||||||
render_layer!(output.layers[3]);
|
render_layer!(output.layers[3]);
|
||||||
render_stacked!(self.state.root.stacked_above_layers);
|
render_stacked!(self.state.root.stacked_above_layers);
|
||||||
|
self.render_close_snapshots(output, x, y);
|
||||||
if let Some(ws) = output.workspace.get()
|
if let Some(ws) = output.workspace.get()
|
||||||
&& ws.render_highlight.get() > 0
|
&& ws.render_highlight.get() > 0
|
||||||
{
|
{
|
||||||
|
|
@ -407,6 +473,7 @@ impl Renderer<'_> {
|
||||||
self.render_tab_bar(tb, x, y, container.width.get());
|
self.render_tab_bar(tb, x, y, container.width.get());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let saved_anim = self.enter_open_anim(&*child.node);
|
||||||
let mb = container.mono_body.get();
|
let mb = container.mono_body.get();
|
||||||
if self.state.theme.sizes.gap.get() != 0 {
|
if self.state.theme.sizes.gap.get() != 0 {
|
||||||
let srgb_srgb = self.state.color_manager.srgb_gamma22();
|
let srgb_srgb = self.state.color_manager.srgb_gamma22();
|
||||||
|
|
@ -487,6 +554,7 @@ impl Renderer<'_> {
|
||||||
.node_render(self, x + content.x1(), y + content.y1(), Some(&body));
|
.node_render(self, x + content.x1(), y + content.y1(), Some(&body));
|
||||||
self.stretch = None;
|
self.stretch = None;
|
||||||
self.corner_radius = None;
|
self.corner_radius = None;
|
||||||
|
self.exit_open_anim(saved_anim);
|
||||||
} else {
|
} else {
|
||||||
let gap = self.state.theme.sizes.gap.get();
|
let gap = self.state.theme.sizes.gap.get();
|
||||||
let (srgb_srgb, bw, border_color, focused_border_color) = if gap != 0 {
|
let (srgb_srgb, bw, border_color, focused_border_color) = if gap != 0 {
|
||||||
|
|
@ -504,6 +572,7 @@ impl Renderer<'_> {
|
||||||
if body.x1() >= container.width.get() || body.y1() >= container.height.get() {
|
if body.x1() >= container.width.get() || body.y1() >= container.height.get() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
let saved_anim = self.enter_open_anim(&*child.node);
|
||||||
if let Some(srgb_srgb) = srgb_srgb {
|
if let Some(srgb_srgb) = srgb_srgb {
|
||||||
let srgb = &srgb_srgb.linear;
|
let srgb = &srgb_srgb.linear;
|
||||||
let c = if child.border_color_is_focused.get() {
|
let c = if child.border_color_is_focused.get() {
|
||||||
|
|
@ -574,6 +643,7 @@ impl Renderer<'_> {
|
||||||
.node_render(self, x + content.x1(), y + content.y1(), Some(&body));
|
.node_render(self, x + content.x1(), y + content.y1(), Some(&body));
|
||||||
self.stretch = None;
|
self.stretch = None;
|
||||||
self.corner_radius = None;
|
self.corner_radius = None;
|
||||||
|
self.exit_open_anim(saved_anim);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -581,13 +651,135 @@ impl Renderer<'_> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_xwindow(&mut self, tl: &Xwindow, x: i32, y: i32, bounds: Option<&Rect>) {
|
pub fn render_xwindow(&mut self, tl: &Xwindow, x: i32, y: i32, bounds: Option<&Rect>) {
|
||||||
|
let saved = self.enter_open_anim(tl);
|
||||||
|
let bounds = if tl.tl_data().anim_open_alpha().is_some() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
bounds
|
||||||
|
};
|
||||||
self.render_surface(&tl.x.surface, x, y, bounds);
|
self.render_surface(&tl.x.surface, x, y, bounds);
|
||||||
self.render_tl_aux(tl.tl_data(), bounds, true);
|
self.render_tl_aux(tl.tl_data(), bounds, true);
|
||||||
|
self.exit_open_anim(saved);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_xdg_toplevel(&mut self, tl: &XdgToplevel, x: i32, y: i32, bounds: Option<&Rect>) {
|
pub fn render_xdg_toplevel(&mut self, tl: &XdgToplevel, x: i32, y: i32, bounds: Option<&Rect>) {
|
||||||
|
let saved = self.enter_open_anim(tl);
|
||||||
|
let bounds = if tl.tl_data().anim_open_alpha().is_some() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
bounds
|
||||||
|
};
|
||||||
self.render_xdg_surface(&tl.xdg, x, y, bounds);
|
self.render_xdg_surface(&tl.xdg, x, y, bounds);
|
||||||
self.render_tl_aux(tl.tl_data(), bounds, true);
|
self.render_tl_aux(tl.tl_data(), bounds, true);
|
||||||
|
self.exit_open_anim(saved);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enters open-animation scope for `tl`: applies its eased alpha + slide
|
||||||
|
/// translate to the renderer base. If a parent has already entered scope
|
||||||
|
/// for the same toplevel (so its borders/decorations slide too), this is a
|
||||||
|
/// no-op and returns `None`. Pair every `Some` return with `exit_open_anim`.
|
||||||
|
pub fn enter_open_anim(&mut self, tl: &dyn ToplevelNode) -> Option<OpenAnimSaved> {
|
||||||
|
let data = tl.tl_data();
|
||||||
|
let eased = data.anim_open_alpha()?;
|
||||||
|
if self.current_anim_node == Some(data.node_id) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let saved = OpenAnimSaved {
|
||||||
|
alpha_mul: self.base.alpha_mul,
|
||||||
|
translate_x: self.base.translate_x,
|
||||||
|
translate_y: self.base.translate_y,
|
||||||
|
prev_node: self.current_anim_node,
|
||||||
|
};
|
||||||
|
self.current_anim_node = Some(data.node_id);
|
||||||
|
self.base.alpha_mul *= eased;
|
||||||
|
if let Some(ws) = data.workspace.get() {
|
||||||
|
let tl_rect = tl.node_absolute_position();
|
||||||
|
let output_rect = ws.output.get().global.pos.get();
|
||||||
|
let dl = (tl_rect.x1() - output_rect.x1()).max(0) as f32;
|
||||||
|
let dr = (output_rect.x2() - tl_rect.x2()).max(0) as f32;
|
||||||
|
let dt = (tl_rect.y1() - output_rect.y1()).max(0) as f32;
|
||||||
|
let db = (output_rect.y2() - tl_rect.y2()).max(0) as f32;
|
||||||
|
let mind = dl.min(dr).min(dt).min(db);
|
||||||
|
let (sx, sy) = if mind == dl {
|
||||||
|
(-(tl_rect.width() as f32), 0.0)
|
||||||
|
} else if mind == dr {
|
||||||
|
(tl_rect.width() as f32, 0.0)
|
||||||
|
} else if mind == dt {
|
||||||
|
(0.0, -(tl_rect.height() as f32))
|
||||||
|
} else {
|
||||||
|
(0.0, tl_rect.height() as f32)
|
||||||
|
};
|
||||||
|
let factor = (1.0 - eased) * self.base.scalef as f32;
|
||||||
|
self.base.translate_x += sx * factor;
|
||||||
|
self.base.translate_y += sy * factor;
|
||||||
|
}
|
||||||
|
Some(saved)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn exit_open_anim(&mut self, saved: Option<OpenAnimSaved>) {
|
||||||
|
if let Some(s) = saved {
|
||||||
|
self.base.alpha_mul = s.alpha_mul;
|
||||||
|
self.base.translate_x = s.translate_x;
|
||||||
|
self.base.translate_y = s.translate_y;
|
||||||
|
self.current_anim_node = s.prev_node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Renders any active close-animation snapshots that belong to this output.
|
||||||
|
/// Each snapshot fades out and slides toward its closest output edge —
|
||||||
|
/// mirroring the open animation in reverse. Finished snapshots stay in the
|
||||||
|
/// list until `tick_animations` cleans them up.
|
||||||
|
fn render_close_snapshots(&mut self, output: &OutputNode, x: i32, y: i32) {
|
||||||
|
let snaps = self.state.close_snapshots.borrow();
|
||||||
|
if snaps.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let output_pos = output.global.pos.get();
|
||||||
|
for snap in snaps.iter() {
|
||||||
|
let Some(snap_output) = snap.output.upgrade() else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if !std::ptr::eq(&*snap_output, output) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let Some(progress) = snap.close_progress(self.state) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let alpha = (1.0 - progress).clamp(0.0, 1.0);
|
||||||
|
let prev_alpha = self.base.alpha_mul;
|
||||||
|
let prev_tx = self.base.translate_x;
|
||||||
|
let prev_ty = self.base.translate_y;
|
||||||
|
self.base.alpha_mul *= alpha;
|
||||||
|
self.base.translate_x += snap.slide_dx * progress * self.base.scalef as f32;
|
||||||
|
self.base.translate_y += snap.slide_dy * progress * self.base.scalef as f32;
|
||||||
|
let local_x = x + snap.rect.x1() - output_pos.x1();
|
||||||
|
let local_y = y + snap.rect.y1() - output_pos.y1();
|
||||||
|
let (sx, sy) = self.base.scale_point(local_x, local_y);
|
||||||
|
let scalef = self.base.scalef;
|
||||||
|
let tw = (snap.rect.width() as f64 * scalef).round() as i32;
|
||||||
|
let th = (snap.rect.height() as f64 * scalef).round() as i32;
|
||||||
|
let cd = self.state.color_manager.srgb_gamma22();
|
||||||
|
self.base.render_texture(
|
||||||
|
&snap.texture,
|
||||||
|
None,
|
||||||
|
sx,
|
||||||
|
sy,
|
||||||
|
None,
|
||||||
|
Some((tw, th)),
|
||||||
|
self.base.scale,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
AcquireSync::Unnecessary,
|
||||||
|
ReleaseSync::Implicit,
|
||||||
|
false,
|
||||||
|
cd,
|
||||||
|
RenderIntent::Perceptual,
|
||||||
|
AlphaMode::PremultipliedElectrical,
|
||||||
|
);
|
||||||
|
self.base.alpha_mul = prev_alpha;
|
||||||
|
self.base.translate_x = prev_tx;
|
||||||
|
self.base.translate_y = prev_ty;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_xdg_surface(
|
pub fn render_xdg_surface(
|
||||||
|
|
@ -804,6 +996,7 @@ impl Renderer<'_> {
|
||||||
Some(c) => c,
|
Some(c) => c,
|
||||||
_ => return,
|
_ => return,
|
||||||
};
|
};
|
||||||
|
let saved_anim = self.enter_open_anim(&*child);
|
||||||
let pos = floating.position.get();
|
let pos = floating.position.get();
|
||||||
let theme = &self.state.theme;
|
let theme = &self.state.theme;
|
||||||
let bw = theme.sizes.border_width.get();
|
let bw = theme.sizes.border_width.get();
|
||||||
|
|
@ -848,11 +1041,13 @@ impl Renderer<'_> {
|
||||||
}
|
}
|
||||||
child.node_render(self, body.x1(), body.y1(), Some(&scissor_body));
|
child.node_render(self, body.x1(), body.y1(), Some(&scissor_body));
|
||||||
self.corner_radius = None;
|
self.corner_radius = None;
|
||||||
|
self.exit_open_anim(saved_anim);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_layer_surface(&mut self, surface: &ZwlrLayerSurfaceV1, x: i32, y: i32) {
|
pub fn render_layer_surface(&mut self, surface: &ZwlrLayerSurfaceV1, x: i32, y: i32) {
|
||||||
let (dx, dy) = surface.surface.extents.get().position();
|
let (dx, dy) = surface.surface.extents.get().position();
|
||||||
let blur = surface.blur.get();
|
let blur = surface.blur.get();
|
||||||
|
|
||||||
let ignore_alpha = surface.ignore_alpha.get();
|
let ignore_alpha = surface.ignore_alpha.get();
|
||||||
if blur {
|
if blur {
|
||||||
let extents = surface.surface.extents.get();
|
let extents = surface.surface.extents.get();
|
||||||
|
|
@ -863,18 +1058,22 @@ impl Renderer<'_> {
|
||||||
let buffer = surface.surface.buffer.get()?;
|
let buffer = surface.surface.buffer.get()?;
|
||||||
let texture = buffer.buffer.buf.get_texture(&surface.surface)?;
|
let texture = buffer.buffer.buf.get_texture(&surface.surface)?;
|
||||||
let source = *surface.surface.buffer_points_norm.borrow();
|
let source = *surface.surface.buffer_points_norm.borrow();
|
||||||
|
let release_sync = buffer.release_sync;
|
||||||
Some(crate::gfx_api::BlurMask {
|
Some(crate::gfx_api::BlurMask {
|
||||||
texture,
|
texture,
|
||||||
source,
|
source,
|
||||||
threshold,
|
threshold,
|
||||||
|
buffer_resv: Some(buffer),
|
||||||
|
acquire_sync: AcquireSync::Unnecessary,
|
||||||
|
release_sync,
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
let cache_epoch = surface.blur_cache_epoch.get();
|
||||||
|
let cache = Some(surface.blur_cache.clone());
|
||||||
self.base
|
self.base
|
||||||
.push_blur_backdrop(scaled, cfg.passes, cfg.size, mask);
|
.push_blur_backdrop(scaled, cfg.passes, cfg.size, mask, cache, cache_epoch);
|
||||||
}
|
}
|
||||||
self.base.discard_alpha = ignore_alpha;
|
|
||||||
self.render_surface(&surface.surface, x - dx, y - dy, None);
|
self.render_surface(&surface.surface, x - dx, y - dy, None);
|
||||||
self.base.discard_alpha = None;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bounds_are_opaque(
|
fn bounds_are_opaque(
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,9 @@ pub struct RendererBase<'a> {
|
||||||
pub fb_width: f32,
|
pub fb_width: f32,
|
||||||
pub fb_height: f32,
|
pub fb_height: f32,
|
||||||
pub discard_alpha: Option<f32>,
|
pub discard_alpha: Option<f32>,
|
||||||
|
pub alpha_mul: f32,
|
||||||
|
pub translate_x: f32,
|
||||||
|
pub translate_y: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RendererBase<'_> {
|
impl RendererBase<'_> {
|
||||||
|
|
@ -33,6 +36,26 @@ impl RendererBase<'_> {
|
||||||
self.scale
|
self.scale
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn apply_alpha_mul(&self, alpha: Option<f32>) -> Option<f32> {
|
||||||
|
if self.alpha_mul >= 1.0 {
|
||||||
|
alpha
|
||||||
|
} else {
|
||||||
|
Some(alpha.unwrap_or(1.0) * self.alpha_mul)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fb_rect(&self, x1: f32, y1: f32, x2: f32, y2: f32) -> FramebufferRect {
|
||||||
|
FramebufferRect::new(
|
||||||
|
x1 + self.translate_x,
|
||||||
|
y1 + self.translate_y,
|
||||||
|
x2 + self.translate_x,
|
||||||
|
y2 + self.translate_y,
|
||||||
|
self.transform,
|
||||||
|
self.fb_width,
|
||||||
|
self.fb_height,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn scale_point(&self, mut x: i32, mut y: i32) -> (i32, i32) {
|
pub fn scale_point(&self, mut x: i32, mut y: i32) -> (i32, i32) {
|
||||||
if self.scaled {
|
if self.scaled {
|
||||||
[x, y] = self.scale.pixel_size([x, y]);
|
[x, y] = self.scale.pixel_size([x, y]);
|
||||||
|
|
@ -123,17 +146,14 @@ impl RendererBase<'_> {
|
||||||
true => bx,
|
true => bx,
|
||||||
};
|
};
|
||||||
self.ops.push(GfxApiOpt::FillRect(FillRect {
|
self.ops.push(GfxApiOpt::FillRect(FillRect {
|
||||||
rect: FramebufferRect::new(
|
rect: self.fb_rect(
|
||||||
bx.x1() as f32,
|
bx.x1() as f32,
|
||||||
bx.y1() as f32,
|
bx.y1() as f32,
|
||||||
bx.x2() as f32,
|
bx.x2() as f32,
|
||||||
bx.y2() as f32,
|
bx.y2() as f32,
|
||||||
self.transform,
|
|
||||||
self.fb_width,
|
|
||||||
self.fb_height,
|
|
||||||
),
|
),
|
||||||
color: *color,
|
color: *color,
|
||||||
alpha,
|
alpha: self.apply_alpha_mul(alpha),
|
||||||
render_intent,
|
render_intent,
|
||||||
cd: cd.clone(),
|
cd: cd.clone(),
|
||||||
}));
|
}));
|
||||||
|
|
@ -166,17 +186,9 @@ impl RendererBase<'_> {
|
||||||
for bx in boxes {
|
for bx in boxes {
|
||||||
let (x1, y1, x2, y2) = self.scale_rect_f(*bx);
|
let (x1, y1, x2, y2) = self.scale_rect_f(*bx);
|
||||||
self.ops.push(GfxApiOpt::FillRect(FillRect {
|
self.ops.push(GfxApiOpt::FillRect(FillRect {
|
||||||
rect: FramebufferRect::new(
|
rect: self.fb_rect(x1 + dx, y1 + dy, x2 + dx, y2 + dy),
|
||||||
x1 + dx,
|
|
||||||
y1 + dy,
|
|
||||||
x2 + dx,
|
|
||||||
y2 + dy,
|
|
||||||
self.transform,
|
|
||||||
self.fb_width,
|
|
||||||
self.fb_height,
|
|
||||||
),
|
|
||||||
color: *color,
|
color: *color,
|
||||||
alpha: None,
|
alpha: self.apply_alpha_mul(None),
|
||||||
render_intent,
|
render_intent,
|
||||||
cd: cd.clone(),
|
cd: cd.clone(),
|
||||||
}));
|
}));
|
||||||
|
|
@ -227,21 +239,20 @@ impl RendererBase<'_> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let target = FramebufferRect::new(
|
let target = self.fb_rect(
|
||||||
target_x[0] as f32,
|
target_x[0] as f32,
|
||||||
target_y[0] as f32,
|
target_y[0] as f32,
|
||||||
target_x[1] as f32,
|
target_x[1] as f32,
|
||||||
target_y[1] as f32,
|
target_y[1] as f32,
|
||||||
self.transform,
|
|
||||||
self.fb_width,
|
|
||||||
self.fb_height,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let new_alpha = self.apply_alpha_mul(alpha);
|
||||||
|
let opaque = opaque && new_alpha == alpha;
|
||||||
self.ops.push(GfxApiOpt::CopyTexture(CopyTexture {
|
self.ops.push(GfxApiOpt::CopyTexture(CopyTexture {
|
||||||
tex: texture.clone(),
|
tex: texture.clone(),
|
||||||
source: texcoord,
|
source: texcoord,
|
||||||
target,
|
target,
|
||||||
alpha,
|
alpha: new_alpha,
|
||||||
buffer_resv,
|
buffer_resv,
|
||||||
acquire_sync,
|
acquire_sync,
|
||||||
release_sync,
|
release_sync,
|
||||||
|
|
@ -296,17 +307,14 @@ impl RendererBase<'_> {
|
||||||
let fitted = corner_radius.fit_to(width, height);
|
let fitted = corner_radius.fit_to(width, height);
|
||||||
let cr: [f32; 4] = fitted.into();
|
let cr: [f32; 4] = fitted.into();
|
||||||
self.ops.push(GfxApiOpt::RoundedFillRect(RoundedFillRect {
|
self.ops.push(GfxApiOpt::RoundedFillRect(RoundedFillRect {
|
||||||
rect: FramebufferRect::new(
|
rect: self.fb_rect(
|
||||||
rect.x1() as f32,
|
rect.x1() as f32,
|
||||||
rect.y1() as f32,
|
rect.y1() as f32,
|
||||||
rect.x2() as f32,
|
rect.x2() as f32,
|
||||||
rect.y2() as f32,
|
rect.y2() as f32,
|
||||||
self.transform,
|
|
||||||
self.fb_width,
|
|
||||||
self.fb_height,
|
|
||||||
),
|
),
|
||||||
color: *color,
|
color: *color,
|
||||||
alpha,
|
alpha: self.apply_alpha_mul(alpha),
|
||||||
render_intent,
|
render_intent,
|
||||||
cd: cd.clone(),
|
cd: cd.clone(),
|
||||||
size: [width, height],
|
size: [width, height],
|
||||||
|
|
@ -358,14 +366,11 @@ impl RendererBase<'_> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let target = FramebufferRect::new(
|
let target = self.fb_rect(
|
||||||
target_x[0] as f32,
|
target_x[0] as f32,
|
||||||
target_y[0] as f32,
|
target_y[0] as f32,
|
||||||
target_x[1] as f32,
|
target_x[1] as f32,
|
||||||
target_y[1] as f32,
|
target_y[1] as f32,
|
||||||
self.transform,
|
|
||||||
self.fb_width,
|
|
||||||
self.fb_height,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let width = (target_x[1] - target_x[0]) as f32;
|
let width = (target_x[1] - target_x[0]) as f32;
|
||||||
|
|
@ -379,7 +384,7 @@ impl RendererBase<'_> {
|
||||||
tex: texture.clone(),
|
tex: texture.clone(),
|
||||||
source: texcoord,
|
source: texcoord,
|
||||||
target,
|
target,
|
||||||
alpha,
|
alpha: self.apply_alpha_mul(alpha),
|
||||||
buffer_resv,
|
buffer_resv,
|
||||||
acquire_sync,
|
acquire_sync,
|
||||||
release_sync,
|
release_sync,
|
||||||
|
|
@ -404,6 +409,8 @@ impl RendererBase<'_> {
|
||||||
passes: u8,
|
passes: u8,
|
||||||
offset: f32,
|
offset: f32,
|
||||||
mask: Option<BlurMask>,
|
mask: Option<BlurMask>,
|
||||||
|
cache: Option<Rc<std::cell::RefCell<Option<crate::gfx_api::BlurCacheEntry>>>>,
|
||||||
|
cache_epoch: u64,
|
||||||
) {
|
) {
|
||||||
let target = FramebufferRect::new(
|
let target = FramebufferRect::new(
|
||||||
rect.x1() as f32,
|
rect.x1() as f32,
|
||||||
|
|
@ -414,11 +421,15 @@ impl RendererBase<'_> {
|
||||||
self.fb_width,
|
self.fb_width,
|
||||||
self.fb_height,
|
self.fb_height,
|
||||||
);
|
);
|
||||||
|
let cache_pixel_rect = [rect.x1(), rect.y1(), rect.x2(), rect.y2()];
|
||||||
self.ops.push(GfxApiOpt::BlurBackdrop(BlurBackdrop {
|
self.ops.push(GfxApiOpt::BlurBackdrop(BlurBackdrop {
|
||||||
rect: target,
|
rect: target,
|
||||||
passes,
|
passes,
|
||||||
offset,
|
offset,
|
||||||
mask,
|
mask,
|
||||||
|
cache,
|
||||||
|
cache_epoch,
|
||||||
|
cache_pixel_rect,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
73
src/state.rs
73
src/state.rs
|
|
@ -306,6 +306,10 @@ pub struct State {
|
||||||
pub hyprland_global_shortcuts: CopyHashMap<(String, String), Rc<HyprlandGlobalShortcutV1>>,
|
pub hyprland_global_shortcuts: CopyHashMap<(String, String), Rc<HyprlandGlobalShortcutV1>>,
|
||||||
pub layer_rules: RefCell<Vec<jay_config::_private::LayerRuleIpc>>,
|
pub layer_rules: RefCell<Vec<jay_config::_private::LayerRuleIpc>>,
|
||||||
pub blur_config: Cell<jay_config::_private::BlurConfigIpc>,
|
pub blur_config: Cell<jay_config::_private::BlurConfigIpc>,
|
||||||
|
pub blur_cache_epoch: NumCell<u64>,
|
||||||
|
pub animations_config: Cell<jay_config::_private::AnimationsConfigIpc>,
|
||||||
|
pub active_animations: RefCell<Vec<std::rc::Weak<dyn ToplevelNode>>>,
|
||||||
|
pub close_snapshots: RefCell<Vec<Rc<crate::animation::Snapshot>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// impl Drop for State {
|
// impl Drop for State {
|
||||||
|
|
@ -1049,6 +1053,25 @@ impl State {
|
||||||
if rect.is_empty() {
|
if rect.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if !cursor {
|
||||||
|
for output in self.root.outputs.lock().values() {
|
||||||
|
for layer in &output.layers {
|
||||||
|
for surface in layer.iter() {
|
||||||
|
if surface.blur.get() && surface.node_absolute_position().intersects(&rect)
|
||||||
|
{
|
||||||
|
surface.blur_cache_epoch.fetch_add(1);
|
||||||
|
}
|
||||||
|
if surface.blur.get() && surface.blur_popups.get() {
|
||||||
|
surface.for_each_popup(|popup| {
|
||||||
|
if popup.node_absolute_position().intersects(&rect) {
|
||||||
|
popup.blur_cache_epoch.fetch_add(1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
self.damage_visualizer.add(rect);
|
self.damage_visualizer.add(rect);
|
||||||
for output in self.root.outputs.lock().values() {
|
for output in self.root.outputs.lock().values() {
|
||||||
if output.global.pos.get().intersects(&rect) {
|
if output.global.pos.get().intersects(&rect) {
|
||||||
|
|
@ -1290,6 +1313,7 @@ impl State {
|
||||||
},
|
},
|
||||||
stretch: None,
|
stretch: None,
|
||||||
corner_radius: None,
|
corner_radius: None,
|
||||||
|
current_anim_node: None,
|
||||||
};
|
};
|
||||||
let mut sample_rect = SampleRect::identity();
|
let mut sample_rect = SampleRect::identity();
|
||||||
sample_rect.buffer_transform = transform;
|
sample_rect.buffer_transform = transform;
|
||||||
|
|
@ -1464,6 +1488,55 @@ impl State {
|
||||||
self.eng.now().msec()
|
self.eng.now().msec()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Walks the active-animations list, damages each toplevel's slide region
|
||||||
|
/// (so the next frame re-renders it), and removes any whose animation is
|
||||||
|
/// done. Also ticks close-animation snapshots: damages their output and
|
||||||
|
/// drops finished ones. Intended to be called once per output present cycle.
|
||||||
|
pub fn tick_animations(&self) {
|
||||||
|
{
|
||||||
|
let mut animations = self.active_animations.borrow_mut();
|
||||||
|
if !animations.is_empty() {
|
||||||
|
animations.retain(|weak| {
|
||||||
|
let Some(tl) = weak.upgrade() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let data = tl.tl_data();
|
||||||
|
if data.anim_open_alpha().is_none() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Damage the entire output the toplevel is on: the slide
|
||||||
|
// can render outside the toplevel's nominal rect, so the
|
||||||
|
// narrow rect alone would leave the slid-out portion
|
||||||
|
// unredrawn.
|
||||||
|
if let Some(ws) = data.workspace.get() {
|
||||||
|
self.damage(ws.output.get().global.pos.get());
|
||||||
|
} else {
|
||||||
|
self.damage(tl.node_absolute_position());
|
||||||
|
}
|
||||||
|
true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut snapshots = self.close_snapshots.borrow_mut();
|
||||||
|
if snapshots.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
snapshots.retain(|snap| {
|
||||||
|
if snap.close_progress(self).is_none() {
|
||||||
|
// Final damage so the snapshot's last-rendered position gets
|
||||||
|
// repainted (clearing any leftover pixels).
|
||||||
|
if let Some(output) = snap.output.upgrade() {
|
||||||
|
self.damage(output.global.pos.get());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if let Some(output) = snap.output.upgrade() {
|
||||||
|
self.damage(output.global.pos.get());
|
||||||
|
}
|
||||||
|
true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
pub fn output_extents_changed(&self) {
|
pub fn output_extents_changed(&self) {
|
||||||
self.root.update_extents();
|
self.root.update_extents();
|
||||||
for seat in self.globals.seats.lock().values() {
|
for seat in self.globals.seats.lock().values() {
|
||||||
|
|
|
||||||
|
|
@ -236,6 +236,7 @@ impl OutputNode {
|
||||||
for listener in self.presentation_event.iter() {
|
for listener in self.presentation_event.iter() {
|
||||||
listener.presented(self, tv_sec, tv_nsec, refresh, seq, flags, vrr);
|
listener.presented(self, tv_sec, tv_nsec, refresh, seq, flags, vrr);
|
||||||
}
|
}
|
||||||
|
self.state.tick_animations();
|
||||||
if locked && let Some(lock) = self.state.lock.lock.get() {
|
if locked && let Some(lock) = self.state.lock.lock.get() {
|
||||||
lock.check_locked()
|
lock.check_locked()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -428,6 +428,7 @@ pub struct ToplevelData {
|
||||||
pub property_changed_source: OnceCell<Rc<LazyEventSource>>,
|
pub property_changed_source: OnceCell<Rc<LazyEventSource>>,
|
||||||
pub mapped_source: Rc<LazyEventSource>,
|
pub mapped_source: Rc<LazyEventSource>,
|
||||||
pub unmapped_source: Rc<LazyEventSource>,
|
pub unmapped_source: Rc<LazyEventSource>,
|
||||||
|
pub anim_open_start_nsec: Cell<Option<u64>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToplevelData {
|
impl ToplevelData {
|
||||||
|
|
@ -485,6 +486,7 @@ impl ToplevelData {
|
||||||
property_changed_source: Default::default(),
|
property_changed_source: Default::default(),
|
||||||
mapped_source: state.lazy_event_sources.create_source(),
|
mapped_source: state.lazy_event_sources.create_source(),
|
||||||
unmapped_source: state.lazy_event_sources.create_source(),
|
unmapped_source: state.lazy_event_sources.create_source(),
|
||||||
|
anim_open_start_nsec: Cell::new(None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -531,6 +533,63 @@ impl ToplevelData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the eased alpha multiplier for the open animation, or None if no
|
||||||
|
/// animation is active. When the animation has finished, clears the start
|
||||||
|
/// time and returns None.
|
||||||
|
pub fn anim_open_alpha(&self) -> Option<f32> {
|
||||||
|
let start = self.anim_open_start_nsec.get()?;
|
||||||
|
let cfg = self.state.animations_config.get();
|
||||||
|
if !cfg.enabled || cfg.open_duration_ms == 0 {
|
||||||
|
self.anim_open_start_nsec.set(None);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let now = self.state.now_nsec();
|
||||||
|
let elapsed_ns = now.saturating_sub(start);
|
||||||
|
let dur_ns = (cfg.open_duration_ms as u64).saturating_mul(1_000_000);
|
||||||
|
if elapsed_ns >= dur_ns {
|
||||||
|
self.anim_open_start_nsec.set(None);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let t = (elapsed_ns as f32) / (dur_ns as f32);
|
||||||
|
let eased = match cfg.open_curve {
|
||||||
|
jay_config::_private::AnimationCurveIpc::Linear => t,
|
||||||
|
jay_config::_private::AnimationCurveIpc::EaseOut => {
|
||||||
|
let inv = 1.0 - t;
|
||||||
|
1.0 - inv * inv * inv
|
||||||
|
}
|
||||||
|
jay_config::_private::AnimationCurveIpc::EaseInOut => {
|
||||||
|
if t < 0.5 {
|
||||||
|
4.0 * t * t * t
|
||||||
|
} else {
|
||||||
|
let f = -2.0 * t + 2.0;
|
||||||
|
1.0 - f * f * f / 2.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jay_config::_private::AnimationCurveIpc::Bezier { x1, y1, x2, y2 } => {
|
||||||
|
cubic_bezier_y_at_x(t, x1, y1, x2, y2)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Some(eased.clamp(0.0, 1.0))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Starts the open animation if animations are enabled. Inserts the
|
||||||
|
/// toplevel into the state's active-animations list so the present loop
|
||||||
|
/// can drive redraws.
|
||||||
|
pub fn start_open_animation(&self) {
|
||||||
|
let cfg = self.state.animations_config.get();
|
||||||
|
if !cfg.enabled || cfg.open_duration_ms == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if self.anim_open_start_nsec.get().is_some() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.anim_open_start_nsec.set(Some(self.state.now_nsec()));
|
||||||
|
self.state
|
||||||
|
.active_animations
|
||||||
|
.borrow_mut()
|
||||||
|
.push(self.slf.clone());
|
||||||
|
}
|
||||||
|
|
||||||
pub fn property_changed(&self, change: TlMatcherChange) {
|
pub fn property_changed(&self, change: TlMatcherChange) {
|
||||||
self.trigger_property_source();
|
self.trigger_property_source();
|
||||||
let mgr = &self.state.tl_matcher_manager;
|
let mgr = &self.state.tl_matcher_manager;
|
||||||
|
|
@ -1108,3 +1167,31 @@ pub fn toplevel_set_workspace(state: &Rc<State>, tl: Rc<dyn ToplevelNode>, ws: &
|
||||||
tl.tl_set_fullscreen(true, Some(ws.clone()));
|
tl.tl_set_fullscreen(true, Some(ws.clone()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Evaluates a cubic Bezier easing curve `cubic-bezier(x1, y1, x2, y2)` at the
|
||||||
|
/// given input time `x`. P0=(0,0) and P3=(1,1) are fixed. Uses Newton-Raphson
|
||||||
|
/// to invert the x(t) parametric form, then evaluates y(t).
|
||||||
|
fn cubic_bezier_y_at_x(x: f32, x1: f32, y1: f32, x2: f32, y2: f32) -> f32 {
|
||||||
|
fn bx(t: f32, x1: f32, x2: f32) -> f32 {
|
||||||
|
let it = 1.0 - t;
|
||||||
|
3.0 * it * it * t * x1 + 3.0 * it * t * t * x2 + t * t * t
|
||||||
|
}
|
||||||
|
fn dbx(t: f32, x1: f32, x2: f32) -> f32 {
|
||||||
|
let it = 1.0 - t;
|
||||||
|
3.0 * it * it * x1 + 6.0 * it * t * (x2 - x1) + 3.0 * t * t * (1.0 - x2)
|
||||||
|
}
|
||||||
|
let mut t = x;
|
||||||
|
for _ in 0..8 {
|
||||||
|
let err = bx(t, x1, x2) - x;
|
||||||
|
if err.abs() < 1e-4 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let d = dbx(t, x1, x2);
|
||||||
|
if d.abs() < 1e-6 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
t = (t - err / d).clamp(0.0, 1.0);
|
||||||
|
}
|
||||||
|
let it = 1.0 - t;
|
||||||
|
3.0 * it * it * t * y1 + 3.0 * it * t * t * y2 + t * t * t
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -393,6 +393,30 @@ pub struct BlurConfig {
|
||||||
pub size: Option<f32>,
|
pub size: Option<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
pub enum AnimationCurve {
|
||||||
|
Linear,
|
||||||
|
EaseOut,
|
||||||
|
EaseInOut,
|
||||||
|
Bezier { x1: f32, y1: f32, x2: f32, y2: f32 },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct AnimationsConfig {
|
||||||
|
pub enabled: Option<bool>,
|
||||||
|
pub open_duration_ms: Option<u32>,
|
||||||
|
pub open_curve: Option<AnimationCurve>,
|
||||||
|
pub close_duration_ms: Option<u32>,
|
||||||
|
pub close_curve: Option<AnimationCurve>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct DamageVisualization {
|
||||||
|
pub enabled: Option<bool>,
|
||||||
|
pub color: Option<jay_config::theme::Color>,
|
||||||
|
pub decay_ms: Option<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum DrmDeviceMatch {
|
pub enum DrmDeviceMatch {
|
||||||
Any(Vec<DrmDeviceMatch>),
|
Any(Vec<DrmDeviceMatch>),
|
||||||
|
|
@ -607,6 +631,8 @@ pub struct Config {
|
||||||
pub window_rules: Vec<WindowRule>,
|
pub window_rules: Vec<WindowRule>,
|
||||||
pub layer_rules: Vec<LayerRule>,
|
pub layer_rules: Vec<LayerRule>,
|
||||||
pub blur: Option<BlurConfig>,
|
pub blur: Option<BlurConfig>,
|
||||||
|
pub damage_visualization: Option<DamageVisualization>,
|
||||||
|
pub animations: Option<AnimationsConfig>,
|
||||||
pub pointer_revert_key: Option<KeySym>,
|
pub pointer_revert_key: Option<KeySym>,
|
||||||
pub use_hardware_cursor: Option<bool>,
|
pub use_hardware_cursor: Option<bool>,
|
||||||
pub show_bar: Option<bool>,
|
pub show_bar: Option<bool>,
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ use {
|
||||||
|
|
||||||
pub mod action;
|
pub mod action;
|
||||||
mod actions;
|
mod actions;
|
||||||
|
mod animations;
|
||||||
mod blur;
|
mod blur;
|
||||||
mod capabilities;
|
mod capabilities;
|
||||||
mod clean_logs_older_than;
|
mod clean_logs_older_than;
|
||||||
|
|
@ -19,6 +20,7 @@ pub mod config;
|
||||||
mod connector;
|
mod connector;
|
||||||
mod connector_match;
|
mod connector_match;
|
||||||
mod content_type;
|
mod content_type;
|
||||||
|
mod damage_visualization;
|
||||||
mod drm_device;
|
mod drm_device;
|
||||||
mod drm_device_match;
|
mod drm_device_match;
|
||||||
mod env;
|
mod env;
|
||||||
|
|
|
||||||
73
toml-config/src/config/parsers/animations.rs
Normal file
73
toml-config/src/config/parsers/animations.rs
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
config::{
|
||||||
|
AnimationCurve, AnimationsConfig,
|
||||||
|
context::Context,
|
||||||
|
extractor::{Extractor, ExtractorError, bol, int, opt, recover, str},
|
||||||
|
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||||
|
},
|
||||||
|
toml::{
|
||||||
|
toml_span::{DespanExt, Span, Spanned, SpannedExt},
|
||||||
|
toml_value::Value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
indexmap::IndexMap,
|
||||||
|
thiserror::Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum AnimationsConfigParserError {
|
||||||
|
#[error(transparent)]
|
||||||
|
Expected(#[from] UnexpectedDataType),
|
||||||
|
#[error(transparent)]
|
||||||
|
Extract(#[from] ExtractorError),
|
||||||
|
#[error("unknown animation curve `{0}`; expected one of: linear, ease-out, ease-in-out")]
|
||||||
|
UnknownCurve(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AnimationsConfigParser<'a>(pub &'a Context<'a>);
|
||||||
|
|
||||||
|
impl Parser for AnimationsConfigParser<'_> {
|
||||||
|
type Value = AnimationsConfig;
|
||||||
|
type Error = AnimationsConfigParserError;
|
||||||
|
const EXPECTED: &'static [DataType] = &[DataType::Table];
|
||||||
|
|
||||||
|
fn parse_table(
|
||||||
|
&mut self,
|
||||||
|
span: Span,
|
||||||
|
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||||
|
) -> ParseResult<Self> {
|
||||||
|
let mut ext = Extractor::new(self.0, span, table);
|
||||||
|
let (enabled_val, open_duration_val, open_curve_val, close_duration_val, close_curve_val) =
|
||||||
|
ext.extract((
|
||||||
|
recover(opt(bol("enabled"))),
|
||||||
|
recover(opt(int("open-duration-ms"))),
|
||||||
|
opt(str("open-curve")),
|
||||||
|
recover(opt(int("close-duration-ms"))),
|
||||||
|
opt(str("close-curve")),
|
||||||
|
))?;
|
||||||
|
let enabled = enabled_val.despan();
|
||||||
|
let open_duration_ms = open_duration_val.despan().and_then(|v| u32::try_from(v).ok());
|
||||||
|
let close_duration_ms = close_duration_val.despan().and_then(|v| u32::try_from(v).ok());
|
||||||
|
let parse_curve = |val: Option<Spanned<&str>>| match val {
|
||||||
|
Some(s) => match s.value {
|
||||||
|
"linear" => Ok(Some(AnimationCurve::Linear)),
|
||||||
|
"ease-out" => Ok(Some(AnimationCurve::EaseOut)),
|
||||||
|
"ease-in-out" => Ok(Some(AnimationCurve::EaseInOut)),
|
||||||
|
other => {
|
||||||
|
Err(AnimationsConfigParserError::UnknownCurve(other.to_string()).spanned(s.span))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => Ok(None),
|
||||||
|
};
|
||||||
|
let open_curve = parse_curve(open_curve_val)?;
|
||||||
|
let close_curve = parse_curve(close_curve_val)?;
|
||||||
|
Ok(AnimationsConfig {
|
||||||
|
enabled,
|
||||||
|
open_duration_ms,
|
||||||
|
open_curve,
|
||||||
|
close_duration_ms,
|
||||||
|
close_curve,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,8 +8,10 @@ use {
|
||||||
parsers::{
|
parsers::{
|
||||||
action::ActionParser,
|
action::ActionParser,
|
||||||
actions::ActionsParser,
|
actions::ActionsParser,
|
||||||
|
animations::AnimationsConfigParser,
|
||||||
blur::BlurConfigParser,
|
blur::BlurConfigParser,
|
||||||
clean_logs_older_than::CleanLogsOlderThanParser,
|
clean_logs_older_than::CleanLogsOlderThanParser,
|
||||||
|
damage_visualization::DamageVisualizationParser,
|
||||||
client_rule::ClientRulesParser,
|
client_rule::ClientRulesParser,
|
||||||
color_management::ColorManagementParser,
|
color_management::ColorManagementParser,
|
||||||
connector::ConnectorsParser,
|
connector::ConnectorsParser,
|
||||||
|
|
@ -157,7 +159,7 @@ impl Parser for ConfigParser<'_> {
|
||||||
mouse_follows_focus,
|
mouse_follows_focus,
|
||||||
layer_rules_val,
|
layer_rules_val,
|
||||||
),
|
),
|
||||||
(blur_val,),
|
(blur_val, damage_visualization_val, animations_val),
|
||||||
) = ext.extract((
|
) = ext.extract((
|
||||||
(
|
(
|
||||||
opt(val("keymap")),
|
opt(val("keymap")),
|
||||||
|
|
@ -219,7 +221,11 @@ impl Parser for ConfigParser<'_> {
|
||||||
recover(opt(bol("unstable-mouse-follows-focus"))),
|
recover(opt(bol("unstable-mouse-follows-focus"))),
|
||||||
opt(val("layers")),
|
opt(val("layers")),
|
||||||
),
|
),
|
||||||
(opt(val("blur")),),
|
(
|
||||||
|
opt(val("blur")),
|
||||||
|
opt(val("damage-visualization")),
|
||||||
|
opt(val("animations")),
|
||||||
|
),
|
||||||
))?;
|
))?;
|
||||||
let mut keymap = None;
|
let mut keymap = None;
|
||||||
if let Some(value) = keymap_val {
|
if let Some(value) = keymap_val {
|
||||||
|
|
@ -515,6 +521,26 @@ impl Parser for ConfigParser<'_> {
|
||||||
Err(e) => log::warn!("Could not parse the blur config: {}", self.0.error(e)),
|
Err(e) => log::warn!("Could not parse the blur config: {}", self.0.error(e)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let mut damage_visualization = None;
|
||||||
|
if let Some(value) = damage_visualization_val {
|
||||||
|
match value.parse(&mut DamageVisualizationParser(self.0)) {
|
||||||
|
Ok(v) => damage_visualization = Some(v),
|
||||||
|
Err(e) => log::warn!(
|
||||||
|
"Could not parse the damage-visualization config: {}",
|
||||||
|
self.0.error(e)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut animations = None;
|
||||||
|
if let Some(value) = animations_val {
|
||||||
|
match value.parse(&mut AnimationsConfigParser(self.0)) {
|
||||||
|
Ok(v) => animations = Some(v),
|
||||||
|
Err(e) => log::warn!(
|
||||||
|
"Could not parse the animations config: {}",
|
||||||
|
self.0.error(e)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
let mut pointer_revert_key = None;
|
let mut pointer_revert_key = None;
|
||||||
if let Some(value) = pointer_revert_key_str {
|
if let Some(value) = pointer_revert_key_str {
|
||||||
match Keysym::from_str(value.value) {
|
match Keysym::from_str(value.value) {
|
||||||
|
|
@ -616,6 +642,8 @@ impl Parser for ConfigParser<'_> {
|
||||||
window_rules,
|
window_rules,
|
||||||
layer_rules,
|
layer_rules,
|
||||||
blur,
|
blur,
|
||||||
|
damage_visualization,
|
||||||
|
animations,
|
||||||
pointer_revert_key,
|
pointer_revert_key,
|
||||||
use_hardware_cursor: use_hardware_cursor.despan(),
|
use_hardware_cursor: use_hardware_cursor.despan(),
|
||||||
show_bar: show_bar.despan(),
|
show_bar: show_bar.despan(),
|
||||||
|
|
|
||||||
65
toml-config/src/config/parsers/damage_visualization.rs
Normal file
65
toml-config/src/config/parsers/damage_visualization.rs
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
config::{
|
||||||
|
DamageVisualization,
|
||||||
|
context::Context,
|
||||||
|
extractor::{Extractor, ExtractorError, bol, int, opt, recover, str},
|
||||||
|
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||||
|
parsers::color::{ColorParser, ColorParserError},
|
||||||
|
},
|
||||||
|
toml::{
|
||||||
|
toml_span::{DespanExt, Span, Spanned, SpannedExt},
|
||||||
|
toml_value::Value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
indexmap::IndexMap,
|
||||||
|
thiserror::Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum DamageVisualizationParserError {
|
||||||
|
#[error(transparent)]
|
||||||
|
Expected(#[from] UnexpectedDataType),
|
||||||
|
#[error(transparent)]
|
||||||
|
Extract(#[from] ExtractorError),
|
||||||
|
#[error(transparent)]
|
||||||
|
Color(#[from] ColorParserError),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DamageVisualizationParser<'a>(pub &'a Context<'a>);
|
||||||
|
|
||||||
|
impl Parser for DamageVisualizationParser<'_> {
|
||||||
|
type Value = DamageVisualization;
|
||||||
|
type Error = DamageVisualizationParserError;
|
||||||
|
const EXPECTED: &'static [DataType] = &[DataType::Table];
|
||||||
|
|
||||||
|
fn parse_table(
|
||||||
|
&mut self,
|
||||||
|
span: Span,
|
||||||
|
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||||
|
) -> ParseResult<Self> {
|
||||||
|
let mut ext = Extractor::new(self.0, span, table);
|
||||||
|
let (enabled_val, color_val, decay_val) = ext.extract((
|
||||||
|
recover(opt(bol("enabled"))),
|
||||||
|
opt(str("color")),
|
||||||
|
recover(opt(int("decay-ms"))),
|
||||||
|
))?;
|
||||||
|
let enabled = enabled_val.despan();
|
||||||
|
let color = match color_val {
|
||||||
|
Some(s) => match ColorParser.parse_string(s.span, s.value) {
|
||||||
|
Ok(c) => Some(c),
|
||||||
|
Err(e) => {
|
||||||
|
return Err(DamageVisualizationParserError::Color(e.value)
|
||||||
|
.spanned(s.span));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
let decay_ms = decay_val.despan().and_then(|v| u64::try_from(v).ok());
|
||||||
|
Ok(DamageVisualization {
|
||||||
|
enabled,
|
||||||
|
color,
|
||||||
|
decay_ms,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -13,7 +13,8 @@ mod toml;
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
config::{
|
config::{
|
||||||
Action, BlurConfig, ClientRule, Config, ConfigConnector, ConfigDrmDevice, ConfigKeymap,
|
Action, AnimationCurve, AnimationsConfig, BlurConfig, ClientRule, Config,
|
||||||
|
ConfigConnector, ConfigDrmDevice, ConfigKeymap,
|
||||||
ConnectorMatch, DrmDeviceMatch, Exec, Input, InputMatch, LayerKind, LayerRule, Output,
|
ConnectorMatch, DrmDeviceMatch, Exec, Input, InputMatch, LayerKind, LayerRule, Output,
|
||||||
OutputMatch, SimpleCommand, Status, Theme, WindowRule, parse_config,
|
OutputMatch, SimpleCommand, Status, Theme, WindowRule, parse_config,
|
||||||
},
|
},
|
||||||
|
|
@ -23,8 +24,12 @@ use {
|
||||||
ahash::{AHashMap, AHashSet},
|
ahash::{AHashMap, AHashSet},
|
||||||
error_reporter::Report,
|
error_reporter::Report,
|
||||||
jay_config::{
|
jay_config::{
|
||||||
_private::{BlurConfigIpc, LayerKindIpc, LayerMatchIpc, LayerRuleIpc},
|
_private::{
|
||||||
_set_blur_config, _set_layer_rules, Axis,
|
AnimationCurveIpc, AnimationsConfigIpc, BlurConfigIpc, DamageVisualizationIpc,
|
||||||
|
LayerKindIpc, LayerMatchIpc, LayerRuleIpc,
|
||||||
|
},
|
||||||
|
_set_animations_config, _set_blur_config, _set_damage_visualization, _set_layer_rules,
|
||||||
|
Axis,
|
||||||
client::Client,
|
client::Client,
|
||||||
config, config_dir,
|
config, config_dir,
|
||||||
exec::{Command, set_env, unset_env},
|
exec::{Command, set_env, unset_env},
|
||||||
|
|
@ -1471,6 +1476,8 @@ fn load_config(initial_load: bool, auto_reload: bool, persistent: &Rc<Persistent
|
||||||
persistent.window_rules.set(window_rules);
|
persistent.window_rules.set(window_rules);
|
||||||
push_layer_rules(&config.layer_rules);
|
push_layer_rules(&config.layer_rules);
|
||||||
push_blur_config(config.blur);
|
push_blur_config(config.blur);
|
||||||
|
push_damage_visualization(config.damage_visualization);
|
||||||
|
push_animations_config(config.animations);
|
||||||
state.set_status(&config.status);
|
state.set_status(&config.status);
|
||||||
persistent.actions.borrow_mut().clear();
|
persistent.actions.borrow_mut().clear();
|
||||||
for a in config.named_actions {
|
for a in config.named_actions {
|
||||||
|
|
@ -1720,6 +1727,33 @@ fn load_config(initial_load: bool, auto_reload: bool, persistent: &Rc<Persistent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn push_animations_config(anim: Option<AnimationsConfig>) {
|
||||||
|
let default = AnimationsConfigIpc::default();
|
||||||
|
let to_ipc = |c: AnimationCurve| match c {
|
||||||
|
AnimationCurve::Linear => AnimationCurveIpc::Linear,
|
||||||
|
AnimationCurve::EaseOut => AnimationCurveIpc::EaseOut,
|
||||||
|
AnimationCurve::EaseInOut => AnimationCurveIpc::EaseInOut,
|
||||||
|
AnimationCurve::Bezier { x1, y1, x2, y2 } => AnimationCurveIpc::Bezier { x1, y1, x2, y2 },
|
||||||
|
};
|
||||||
|
let cfg = match anim {
|
||||||
|
Some(a) => AnimationsConfigIpc {
|
||||||
|
enabled: a.enabled.unwrap_or(default.enabled),
|
||||||
|
open_duration_ms: a
|
||||||
|
.open_duration_ms
|
||||||
|
.unwrap_or(default.open_duration_ms)
|
||||||
|
.clamp(0, 10_000),
|
||||||
|
open_curve: to_ipc(a.open_curve.unwrap_or(AnimationCurve::EaseOut)),
|
||||||
|
close_duration_ms: a
|
||||||
|
.close_duration_ms
|
||||||
|
.unwrap_or(default.close_duration_ms)
|
||||||
|
.clamp(0, 10_000),
|
||||||
|
close_curve: to_ipc(a.close_curve.unwrap_or(AnimationCurve::EaseOut)),
|
||||||
|
},
|
||||||
|
None => default,
|
||||||
|
};
|
||||||
|
_set_animations_config(cfg);
|
||||||
|
}
|
||||||
|
|
||||||
fn push_blur_config(blur: Option<BlurConfig>) {
|
fn push_blur_config(blur: Option<BlurConfig>) {
|
||||||
let default = BlurConfigIpc::default();
|
let default = BlurConfigIpc::default();
|
||||||
let cfg = match blur {
|
let cfg = match blur {
|
||||||
|
|
@ -1732,6 +1766,19 @@ fn push_blur_config(blur: Option<BlurConfig>) {
|
||||||
_set_blur_config(cfg);
|
_set_blur_config(cfg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn push_damage_visualization(dv: Option<crate::config::DamageVisualization>) {
|
||||||
|
let default = DamageVisualizationIpc::default();
|
||||||
|
let cfg = match dv {
|
||||||
|
Some(d) => DamageVisualizationIpc {
|
||||||
|
enabled: d.enabled.unwrap_or(default.enabled),
|
||||||
|
color: d.color.unwrap_or(default.color),
|
||||||
|
decay_millis: d.decay_ms.unwrap_or(default.decay_millis),
|
||||||
|
},
|
||||||
|
None => default,
|
||||||
|
};
|
||||||
|
_set_damage_visualization(cfg);
|
||||||
|
}
|
||||||
|
|
||||||
fn push_layer_rules(rules: &[LayerRule]) {
|
fn push_layer_rules(rules: &[LayerRule]) {
|
||||||
let ipc: Vec<LayerRuleIpc> = rules
|
let ipc: Vec<LayerRuleIpc> = rules
|
||||||
.iter()
|
.iter()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue