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 })
|
||||
}
|
||||
|
||||
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) {
|
||||
self.send(&ClientMessage::SwitchTo { vtnr })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
use {
|
||||
crate::{
|
||||
_private::{
|
||||
BlurConfigIpc, ClientCriterionIpc, LayerRuleIpc, PollableId, WindowCriterionIpc,
|
||||
WireMode,
|
||||
BlurConfigIpc, ClientCriterionIpc, DamageVisualizationIpc, LayerRuleIpc, PollableId,
|
||||
WindowCriterionIpc, WireMode,
|
||||
},
|
||||
Axis, Direction, PciId, Workspace,
|
||||
client::{Client, ClientCapabilities, ClientMatcher},
|
||||
|
|
@ -925,6 +925,12 @@ pub enum ClientMessage<'a> {
|
|||
SetBlurConfig {
|
||||
config: BlurConfigIpc,
|
||||
},
|
||||
SetDamageVisualization {
|
||||
config: DamageVisualizationIpc,
|
||||
},
|
||||
SetAnimationsConfig {
|
||||
config: crate::_private::AnimationsConfigIpc,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
|
|
|
|||
|
|
@ -407,6 +407,34 @@ pub fn _set_blur_config(config: crate::_private::BlurConfigIpc) {
|
|||
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.
|
||||
pub fn get_corner_radius() -> f32 {
|
||||
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(),
|
||||
layer_rules: 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));
|
||||
create_dummy_output(&state);
|
||||
|
|
|
|||
|
|
@ -3537,6 +3537,20 @@ impl ConfigProxyHandler {
|
|||
ClientMessage::SetBlurConfig { 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(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -107,12 +107,36 @@ pub enum GfxApiOpt {
|
|||
BlurBackdrop(BlurBackdrop),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Clone)]
|
||||
pub struct BlurBackdrop {
|
||||
pub rect: FramebufferRect,
|
||||
pub passes: u8,
|
||||
pub offset: f32,
|
||||
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)]
|
||||
|
|
@ -120,6 +144,9 @@ pub struct BlurMask {
|
|||
pub texture: Rc<dyn GfxTexture>,
|
||||
pub source: SampleRect,
|
||||
pub threshold: f32,
|
||||
pub buffer_resv: Option<Rc<dyn BufferResv>>,
|
||||
pub acquire_sync: AcquireSync,
|
||||
pub release_sync: ReleaseSync,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for BlurMask {
|
||||
|
|
@ -785,6 +812,7 @@ impl dyn GfxFramebuffer {
|
|||
},
|
||||
stretch: None,
|
||||
corner_radius: None,
|
||||
current_anim_node: None,
|
||||
};
|
||||
cursor.render_hardware_cursor(&mut renderer);
|
||||
self.render(
|
||||
|
|
@ -1119,6 +1147,7 @@ pub fn create_render_pass(
|
|||
},
|
||||
stretch: None,
|
||||
corner_radius: None,
|
||||
current_anim_node: None,
|
||||
};
|
||||
node.node_render(&mut renderer, 0, 0, None);
|
||||
if let Some(rect) = cursor_rect {
|
||||
|
|
@ -1193,6 +1222,9 @@ pub fn renderer_base<'a>(
|
|||
fb_width: width as _,
|
||||
fb_height: height as _,
|
||||
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);
|
||||
}
|
||||
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() {
|
||||
ti.send_leave(surface);
|
||||
ti.send_done();
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ use {
|
|||
Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, NodeLayerLink,
|
||||
NodeLocation, NodeVisitor, OutputNode, StackedNode,
|
||||
},
|
||||
utils::{clonecell::CloneCell, smallmap::SmallMap},
|
||||
utils::{clonecell::CloneCell, numcell::NumCell, smallmap::SmallMap},
|
||||
wire::{XdgPopupId, xdg_popup::*},
|
||||
},
|
||||
std::{
|
||||
|
|
@ -72,6 +72,7 @@ pub struct LayerPopupBlur {
|
|||
}
|
||||
|
||||
pub struct XdgPopup {
|
||||
pub blur_pre_rendered: Cell<bool>,
|
||||
pub id: XdgPopupId,
|
||||
node_id: PopupId,
|
||||
pub xdg: Rc<XdgSurface>,
|
||||
|
|
@ -79,6 +80,8 @@ pub struct XdgPopup {
|
|||
relative_position: Cell<Rect>,
|
||||
pos: RefCell<XdgPositioned>,
|
||||
pub tracker: Tracker<Self>,
|
||||
pub blur_cache: Rc<RefCell<Option<crate::gfx_api::BlurCacheEntry>>>,
|
||||
pub blur_cache_epoch: NumCell<u64>,
|
||||
seat_state: NodeSeatState,
|
||||
set_visible_prepared: Cell<bool>,
|
||||
jay_popup_ext: CloneCell<Option<Rc<JayPopupExtV1>>>,
|
||||
|
|
@ -104,8 +107,11 @@ impl XdgPopup {
|
|||
Ok(Self {
|
||||
id,
|
||||
node_id: xdg.surface.client.state.node_ids.next(),
|
||||
blur_pre_rendered: Cell::new(false),
|
||||
xdg: xdg.clone(),
|
||||
parent: Default::default(),
|
||||
blur_cache: Default::default(),
|
||||
blur_cache_epoch: Default::default(),
|
||||
relative_position: Cell::new(Default::default()),
|
||||
pos: RefCell::new(pos),
|
||||
tracker: Default::default(),
|
||||
|
|
@ -314,6 +320,9 @@ impl XdgPopupRequestHandler for XdgPopup {
|
|||
}
|
||||
|
||||
impl XdgPopup {
|
||||
pub fn layer_blur_settings(&self) -> Option<LayerPopupBlur> {
|
||||
self.parent.get()?.layer_blur_settings()
|
||||
}
|
||||
pub fn set_visible(&self, visible: bool) {
|
||||
let surface = &self.xdg.surface;
|
||||
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>) {
|
||||
let settings = self.parent.get().and_then(|p| p.layer_blur_settings());
|
||||
let settings = self.layer_blur_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 geo = self.xdg.geometry();
|
||||
let (gx, gy) = geo.translate(x, y);
|
||||
let rect = extents.move_(gx, gy);
|
||||
let scaled = renderer.base.scale_rect(rect);
|
||||
let cfg = renderer.state.blur_config.get();
|
||||
let mask = s.ignore_alpha.and_then(|threshold| {
|
||||
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();
|
||||
Some(crate::gfx_api::BlurMask {
|
||||
texture,
|
||||
source,
|
||||
threshold,
|
||||
})
|
||||
});
|
||||
renderer
|
||||
.base
|
||||
.push_blur_backdrop(scaled, cfg.passes, cfg.size, mask);
|
||||
let popup_blur_rect = if let Some(parent) = self.parent.get() {
|
||||
let parent_rect = parent.position();
|
||||
if parent_rect.contains_rect(&rect) {
|
||||
None
|
||||
} else {
|
||||
Some(rect)
|
||||
}
|
||||
} else {
|
||||
Some(rect)
|
||||
};
|
||||
if let Some(blur_rect) = popup_blur_rect {
|
||||
let scaled = renderer.base.scale_rect(blur_rect);
|
||||
let cfg = renderer.state.blur_config.get();
|
||||
let mask = s.ignore_alpha.and_then(|threshold| {
|
||||
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.base.discard_alpha = None;
|
||||
} else {
|
||||
self.blur_pre_rendered.set(false);
|
||||
renderer.render_xdg_surface(&self.xdg, x, y, bounds);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -555,6 +555,7 @@ impl XdgToplevel {
|
|||
self.state.tree_changed();
|
||||
self.toplevel_data.mapped_source.trigger();
|
||||
self.toplevel_data.broadcast(self.clone());
|
||||
self.toplevel_data.start_open_animation();
|
||||
}
|
||||
self.toplevel_data
|
||||
.set_content_type(self.xdg.surface.content_type.get());
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ pub struct ZwlrLayerSurfaceV1 {
|
|||
pub client: Rc<Client>,
|
||||
pub surface: Rc<WlSurface>,
|
||||
pub output: Rc<OutputGlobalOpt>,
|
||||
pub blur_cache_epoch: NumCell<u64>,
|
||||
pub namespace: String,
|
||||
pub tracker: Tracker<Self>,
|
||||
output_extents: Cell<Rect>,
|
||||
|
|
@ -62,6 +63,7 @@ pub struct ZwlrLayerSurfaceV1 {
|
|||
pub blur: Cell<bool>,
|
||||
pub blur_popups: Cell<bool>,
|
||||
pub ignore_alpha: Cell<Option<f32>>,
|
||||
pub blur_cache: Rc<RefCell<Option<crate::gfx_api::BlurCacheEntry>>>,
|
||||
requested_serial: NumCell<u32>,
|
||||
size: Cell<(i32, i32)>,
|
||||
anchor: Cell<u32>,
|
||||
|
|
@ -158,6 +160,7 @@ impl ZwlrLayerSurfaceV1 {
|
|||
) -> Self {
|
||||
Self {
|
||||
id,
|
||||
blur_cache_epoch: Default::default(),
|
||||
node_id: shell.client.state.node_ids.next(),
|
||||
shell: shell.clone(),
|
||||
client: shell.client.clone(),
|
||||
|
|
@ -172,6 +175,7 @@ impl ZwlrLayerSurfaceV1 {
|
|||
blur: Cell::new(false),
|
||||
blur_popups: Cell::new(false),
|
||||
ignore_alpha: Cell::new(None),
|
||||
blur_cache: Default::default(),
|
||||
requested_serial: Default::default(),
|
||||
size: Cell::new((0, 0)),
|
||||
anchor: Cell::new(0),
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ mod leaks;
|
|||
mod tracy;
|
||||
mod acceptor;
|
||||
mod allocator;
|
||||
mod animation;
|
||||
mod async_engine;
|
||||
mod backend;
|
||||
mod backends;
|
||||
|
|
|
|||
215
src/renderer.rs
215
src/renderer.rs
|
|
@ -5,7 +5,7 @@ use {
|
|||
ifs::wl_surface::{
|
||||
SurfaceBuffer, WlSurface,
|
||||
x_surface::xwindow::Xwindow,
|
||||
xdg_surface::{XdgSurface, xdg_toplevel::XdgToplevel},
|
||||
xdg_surface::{XdgSurface, xdg_popup::XdgPopup, xdg_toplevel::XdgToplevel},
|
||||
zwlr_layer_surface_v1::ZwlrLayerSurfaceV1,
|
||||
},
|
||||
rect::Rect,
|
||||
|
|
@ -14,8 +14,9 @@ use {
|
|||
state::State,
|
||||
theme::{Color, CornerRadius},
|
||||
tree::{
|
||||
ContainerNode, DisplayNode, FloatNode, OutputNode, PlaceholderNode, ToplevelData,
|
||||
ToplevelNodeBase, WorkspaceNode, tab_bar::TabBar,
|
||||
ContainerNode, DisplayNode, FloatNode, NodeId, OutputNode, PlaceholderNode,
|
||||
StackedNode, ToplevelData, ToplevelNode, ToplevelNodeBase, WorkspaceNode,
|
||||
tab_bar::TabBar,
|
||||
},
|
||||
},
|
||||
std::{ops::Deref, rc::Rc, slice},
|
||||
|
|
@ -30,9 +31,60 @@ pub struct Renderer<'a> {
|
|||
pub pixel_extents: Rect,
|
||||
pub stretch: Option<(i32, i32)>,
|
||||
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<'_> {
|
||||
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 {
|
||||
self.base.scale
|
||||
}
|
||||
|
|
@ -215,14 +267,28 @@ impl Renderer<'_> {
|
|||
};
|
||||
}
|
||||
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();
|
||||
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[3]);
|
||||
render_stacked!(self.state.root.stacked_above_layers);
|
||||
self.render_close_snapshots(output, x, y);
|
||||
if let Some(ws) = output.workspace.get()
|
||||
&& ws.render_highlight.get() > 0
|
||||
{
|
||||
|
|
@ -407,6 +473,7 @@ impl Renderer<'_> {
|
|||
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();
|
||||
if self.state.theme.sizes.gap.get() != 0 {
|
||||
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));
|
||||
self.stretch = None;
|
||||
self.corner_radius = None;
|
||||
self.exit_open_anim(saved_anim);
|
||||
} else {
|
||||
let gap = self.state.theme.sizes.gap.get();
|
||||
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() {
|
||||
break;
|
||||
}
|
||||
let saved_anim = self.enter_open_anim(&*child.node);
|
||||
if let Some(srgb_srgb) = srgb_srgb {
|
||||
let srgb = &srgb_srgb.linear;
|
||||
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));
|
||||
self.stretch = 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>) {
|
||||
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_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>) {
|
||||
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_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(
|
||||
|
|
@ -804,6 +996,7 @@ impl Renderer<'_> {
|
|||
Some(c) => c,
|
||||
_ => return,
|
||||
};
|
||||
let saved_anim = self.enter_open_anim(&*child);
|
||||
let pos = floating.position.get();
|
||||
let theme = &self.state.theme;
|
||||
let bw = theme.sizes.border_width.get();
|
||||
|
|
@ -848,11 +1041,13 @@ impl Renderer<'_> {
|
|||
}
|
||||
child.node_render(self, body.x1(), body.y1(), Some(&scissor_body));
|
||||
self.corner_radius = None;
|
||||
self.exit_open_anim(saved_anim);
|
||||
}
|
||||
|
||||
pub fn render_layer_surface(&mut self, surface: &ZwlrLayerSurfaceV1, x: i32, y: i32) {
|
||||
let (dx, dy) = surface.surface.extents.get().position();
|
||||
let blur = surface.blur.get();
|
||||
|
||||
let ignore_alpha = surface.ignore_alpha.get();
|
||||
if blur {
|
||||
let extents = surface.surface.extents.get();
|
||||
|
|
@ -863,18 +1058,22 @@ impl Renderer<'_> {
|
|||
let buffer = surface.surface.buffer.get()?;
|
||||
let texture = buffer.buffer.buf.get_texture(&surface.surface)?;
|
||||
let source = *surface.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,
|
||||
})
|
||||
});
|
||||
let cache_epoch = surface.blur_cache_epoch.get();
|
||||
let cache = Some(surface.blur_cache.clone());
|
||||
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.base.discard_alpha = None;
|
||||
}
|
||||
|
||||
fn bounds_are_opaque(
|
||||
|
|
|
|||
|
|
@ -26,6 +26,9 @@ pub struct RendererBase<'a> {
|
|||
pub fb_width: f32,
|
||||
pub fb_height: f32,
|
||||
pub discard_alpha: Option<f32>,
|
||||
pub alpha_mul: f32,
|
||||
pub translate_x: f32,
|
||||
pub translate_y: f32,
|
||||
}
|
||||
|
||||
impl RendererBase<'_> {
|
||||
|
|
@ -33,6 +36,26 @@ impl RendererBase<'_> {
|
|||
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) {
|
||||
if self.scaled {
|
||||
[x, y] = self.scale.pixel_size([x, y]);
|
||||
|
|
@ -123,17 +146,14 @@ impl RendererBase<'_> {
|
|||
true => bx,
|
||||
};
|
||||
self.ops.push(GfxApiOpt::FillRect(FillRect {
|
||||
rect: FramebufferRect::new(
|
||||
rect: self.fb_rect(
|
||||
bx.x1() as f32,
|
||||
bx.y1() as f32,
|
||||
bx.x2() as f32,
|
||||
bx.y2() as f32,
|
||||
self.transform,
|
||||
self.fb_width,
|
||||
self.fb_height,
|
||||
),
|
||||
color: *color,
|
||||
alpha,
|
||||
alpha: self.apply_alpha_mul(alpha),
|
||||
render_intent,
|
||||
cd: cd.clone(),
|
||||
}));
|
||||
|
|
@ -166,17 +186,9 @@ impl RendererBase<'_> {
|
|||
for bx in boxes {
|
||||
let (x1, y1, x2, y2) = self.scale_rect_f(*bx);
|
||||
self.ops.push(GfxApiOpt::FillRect(FillRect {
|
||||
rect: FramebufferRect::new(
|
||||
x1 + dx,
|
||||
y1 + dy,
|
||||
x2 + dx,
|
||||
y2 + dy,
|
||||
self.transform,
|
||||
self.fb_width,
|
||||
self.fb_height,
|
||||
),
|
||||
rect: self.fb_rect(x1 + dx, y1 + dy, x2 + dx, y2 + dy),
|
||||
color: *color,
|
||||
alpha: None,
|
||||
alpha: self.apply_alpha_mul(None),
|
||||
render_intent,
|
||||
cd: cd.clone(),
|
||||
}));
|
||||
|
|
@ -227,21 +239,20 @@ impl RendererBase<'_> {
|
|||
return;
|
||||
}
|
||||
|
||||
let target = FramebufferRect::new(
|
||||
let target = self.fb_rect(
|
||||
target_x[0] as f32,
|
||||
target_y[0] as f32,
|
||||
target_x[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 {
|
||||
tex: texture.clone(),
|
||||
source: texcoord,
|
||||
target,
|
||||
alpha,
|
||||
alpha: new_alpha,
|
||||
buffer_resv,
|
||||
acquire_sync,
|
||||
release_sync,
|
||||
|
|
@ -296,17 +307,14 @@ impl RendererBase<'_> {
|
|||
let fitted = corner_radius.fit_to(width, height);
|
||||
let cr: [f32; 4] = fitted.into();
|
||||
self.ops.push(GfxApiOpt::RoundedFillRect(RoundedFillRect {
|
||||
rect: FramebufferRect::new(
|
||||
rect: self.fb_rect(
|
||||
rect.x1() as f32,
|
||||
rect.y1() as f32,
|
||||
rect.x2() as f32,
|
||||
rect.y2() as f32,
|
||||
self.transform,
|
||||
self.fb_width,
|
||||
self.fb_height,
|
||||
),
|
||||
color: *color,
|
||||
alpha,
|
||||
alpha: self.apply_alpha_mul(alpha),
|
||||
render_intent,
|
||||
cd: cd.clone(),
|
||||
size: [width, height],
|
||||
|
|
@ -358,14 +366,11 @@ impl RendererBase<'_> {
|
|||
return;
|
||||
}
|
||||
|
||||
let target = FramebufferRect::new(
|
||||
let target = self.fb_rect(
|
||||
target_x[0] as f32,
|
||||
target_y[0] as f32,
|
||||
target_x[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;
|
||||
|
|
@ -379,7 +384,7 @@ impl RendererBase<'_> {
|
|||
tex: texture.clone(),
|
||||
source: texcoord,
|
||||
target,
|
||||
alpha,
|
||||
alpha: self.apply_alpha_mul(alpha),
|
||||
buffer_resv,
|
||||
acquire_sync,
|
||||
release_sync,
|
||||
|
|
@ -404,6 +409,8 @@ impl RendererBase<'_> {
|
|||
passes: u8,
|
||||
offset: f32,
|
||||
mask: Option<BlurMask>,
|
||||
cache: Option<Rc<std::cell::RefCell<Option<crate::gfx_api::BlurCacheEntry>>>>,
|
||||
cache_epoch: u64,
|
||||
) {
|
||||
let target = FramebufferRect::new(
|
||||
rect.x1() as f32,
|
||||
|
|
@ -414,11 +421,15 @@ impl RendererBase<'_> {
|
|||
self.fb_width,
|
||||
self.fb_height,
|
||||
);
|
||||
let cache_pixel_rect = [rect.x1(), rect.y1(), rect.x2(), rect.y2()];
|
||||
self.ops.push(GfxApiOpt::BlurBackdrop(BlurBackdrop {
|
||||
rect: target,
|
||||
passes,
|
||||
offset,
|
||||
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 layer_rules: RefCell<Vec<jay_config::_private::LayerRuleIpc>>,
|
||||
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 {
|
||||
|
|
@ -1049,6 +1053,25 @@ impl State {
|
|||
if rect.is_empty() {
|
||||
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);
|
||||
for output in self.root.outputs.lock().values() {
|
||||
if output.global.pos.get().intersects(&rect) {
|
||||
|
|
@ -1290,6 +1313,7 @@ impl State {
|
|||
},
|
||||
stretch: None,
|
||||
corner_radius: None,
|
||||
current_anim_node: None,
|
||||
};
|
||||
let mut sample_rect = SampleRect::identity();
|
||||
sample_rect.buffer_transform = transform;
|
||||
|
|
@ -1464,6 +1488,55 @@ impl State {
|
|||
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) {
|
||||
self.root.update_extents();
|
||||
for seat in self.globals.seats.lock().values() {
|
||||
|
|
|
|||
|
|
@ -236,6 +236,7 @@ impl OutputNode {
|
|||
for listener in self.presentation_event.iter() {
|
||||
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() {
|
||||
lock.check_locked()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -428,6 +428,7 @@ pub struct ToplevelData {
|
|||
pub property_changed_source: OnceCell<Rc<LazyEventSource>>,
|
||||
pub mapped_source: Rc<LazyEventSource>,
|
||||
pub unmapped_source: Rc<LazyEventSource>,
|
||||
pub anim_open_start_nsec: Cell<Option<u64>>,
|
||||
}
|
||||
|
||||
impl ToplevelData {
|
||||
|
|
@ -485,6 +486,7 @@ impl ToplevelData {
|
|||
property_changed_source: Default::default(),
|
||||
mapped_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) {
|
||||
self.trigger_property_source();
|
||||
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()));
|
||||
}
|
||||
}
|
||||
|
||||
/// 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>,
|
||||
}
|
||||
|
||||
#[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)]
|
||||
pub enum DrmDeviceMatch {
|
||||
Any(Vec<DrmDeviceMatch>),
|
||||
|
|
@ -607,6 +631,8 @@ pub struct Config {
|
|||
pub window_rules: Vec<WindowRule>,
|
||||
pub layer_rules: Vec<LayerRule>,
|
||||
pub blur: Option<BlurConfig>,
|
||||
pub damage_visualization: Option<DamageVisualization>,
|
||||
pub animations: Option<AnimationsConfig>,
|
||||
pub pointer_revert_key: Option<KeySym>,
|
||||
pub use_hardware_cursor: Option<bool>,
|
||||
pub show_bar: Option<bool>,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ use {
|
|||
|
||||
pub mod action;
|
||||
mod actions;
|
||||
mod animations;
|
||||
mod blur;
|
||||
mod capabilities;
|
||||
mod clean_logs_older_than;
|
||||
|
|
@ -19,6 +20,7 @@ pub mod config;
|
|||
mod connector;
|
||||
mod connector_match;
|
||||
mod content_type;
|
||||
mod damage_visualization;
|
||||
mod drm_device;
|
||||
mod drm_device_match;
|
||||
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::{
|
||||
action::ActionParser,
|
||||
actions::ActionsParser,
|
||||
animations::AnimationsConfigParser,
|
||||
blur::BlurConfigParser,
|
||||
clean_logs_older_than::CleanLogsOlderThanParser,
|
||||
damage_visualization::DamageVisualizationParser,
|
||||
client_rule::ClientRulesParser,
|
||||
color_management::ColorManagementParser,
|
||||
connector::ConnectorsParser,
|
||||
|
|
@ -157,7 +159,7 @@ impl Parser for ConfigParser<'_> {
|
|||
mouse_follows_focus,
|
||||
layer_rules_val,
|
||||
),
|
||||
(blur_val,),
|
||||
(blur_val, damage_visualization_val, animations_val),
|
||||
) = ext.extract((
|
||||
(
|
||||
opt(val("keymap")),
|
||||
|
|
@ -219,7 +221,11 @@ impl Parser for ConfigParser<'_> {
|
|||
recover(opt(bol("unstable-mouse-follows-focus"))),
|
||||
opt(val("layers")),
|
||||
),
|
||||
(opt(val("blur")),),
|
||||
(
|
||||
opt(val("blur")),
|
||||
opt(val("damage-visualization")),
|
||||
opt(val("animations")),
|
||||
),
|
||||
))?;
|
||||
let mut keymap = None;
|
||||
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)),
|
||||
}
|
||||
}
|
||||
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;
|
||||
if let Some(value) = pointer_revert_key_str {
|
||||
match Keysym::from_str(value.value) {
|
||||
|
|
@ -616,6 +642,8 @@ impl Parser for ConfigParser<'_> {
|
|||
window_rules,
|
||||
layer_rules,
|
||||
blur,
|
||||
damage_visualization,
|
||||
animations,
|
||||
pointer_revert_key,
|
||||
use_hardware_cursor: use_hardware_cursor.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 {
|
||||
crate::{
|
||||
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,
|
||||
OutputMatch, SimpleCommand, Status, Theme, WindowRule, parse_config,
|
||||
},
|
||||
|
|
@ -23,8 +24,12 @@ use {
|
|||
ahash::{AHashMap, AHashSet},
|
||||
error_reporter::Report,
|
||||
jay_config::{
|
||||
_private::{BlurConfigIpc, LayerKindIpc, LayerMatchIpc, LayerRuleIpc},
|
||||
_set_blur_config, _set_layer_rules, Axis,
|
||||
_private::{
|
||||
AnimationCurveIpc, AnimationsConfigIpc, BlurConfigIpc, DamageVisualizationIpc,
|
||||
LayerKindIpc, LayerMatchIpc, LayerRuleIpc,
|
||||
},
|
||||
_set_animations_config, _set_blur_config, _set_damage_visualization, _set_layer_rules,
|
||||
Axis,
|
||||
client::Client,
|
||||
config, config_dir,
|
||||
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);
|
||||
push_layer_rules(&config.layer_rules);
|
||||
push_blur_config(config.blur);
|
||||
push_damage_visualization(config.damage_visualization);
|
||||
push_animations_config(config.animations);
|
||||
state.set_status(&config.status);
|
||||
persistent.actions.borrow_mut().clear();
|
||||
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>) {
|
||||
let default = BlurConfigIpc::default();
|
||||
let cfg = match blur {
|
||||
|
|
@ -1732,6 +1766,19 @@ fn push_blur_config(blur: Option<BlurConfig>) {
|
|||
_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]) {
|
||||
let ipc: Vec<LayerRuleIpc> = rules
|
||||
.iter()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue