Implement baba-is-float for layers

This commit is contained in:
Ivan Molodetskikh
2025-05-12 08:16:01 +03:00
parent 9c103f1f1d
commit c98537a2b0
9 changed files with 122 additions and 25 deletions
+2
View File
@@ -17,6 +17,8 @@ pub struct LayerRule {
pub geometry_corner_radius: Option<CornerRadius>,
#[knuffel(child, unwrap(argument))]
pub place_within_backdrop: Option<bool>,
#[knuffel(child, unwrap(argument))]
pub baba_is_float: Option<bool>,
}
#[derive(knuffel::Decode, Debug, Default, Clone, PartialEq)]
+1
View File
@@ -4985,6 +4985,7 @@ mod tests {
},
geometry_corner_radius: None,
place_within_backdrop: None,
baba_is_float: None,
},
],
binds: Binds(
+15 -2
View File
@@ -12,7 +12,7 @@ use smithay::wayland::shell::xdg::PopupSurface;
use crate::layer::{MappedLayer, ResolvedLayerRules};
use crate::niri::State;
use crate::utils::{is_mapped, send_scale_transform};
use crate::utils::{is_mapped, output_size, send_scale_transform};
impl WlrLayerShellHandler for State {
fn shell_state(&mut self) -> &mut WlrLayerShellState {
@@ -125,10 +125,23 @@ impl State {
// Resolve rules for newly mapped layer surfaces.
if was_unmapped {
let config = self.niri.config.borrow();
let rules = &config.layer_rules;
let rules =
ResolvedLayerRules::compute(rules, layer, self.niri.is_at_startup);
let mapped = MappedLayer::new(layer.clone(), rules, &config);
let output_size = output_size(&output);
let scale = output.current_scale().fractional_scale();
let mapped = MappedLayer::new(
layer.clone(),
rules,
output_size,
scale,
self.niri.clock.clone(),
&config,
);
let prev = self
.niri
.mapped_layer_surfaces
+48 -5
View File
@@ -9,12 +9,14 @@ use smithay::utils::{Logical, Point, Scale, Size};
use smithay::wayland::shell::wlr_layer::{ExclusiveZone, Layer};
use super::ResolvedLayerRules;
use crate::animation::Clock;
use crate::layout::shadow::Shadow;
use crate::niri_render_elements;
use crate::render_helpers::renderer::NiriRenderer;
use crate::render_helpers::shadow::ShadowRenderElement;
use crate::render_helpers::solid_color::{SolidColorBuffer, SolidColorRenderElement};
use crate::render_helpers::{RenderTarget, SplitElements};
use crate::utils::{baba_is_float_offset, round_logical_in_physical};
#[derive(Debug)]
pub struct MappedLayer {
@@ -29,6 +31,15 @@ pub struct MappedLayer {
/// The shadow around the surface.
shadow: Shadow,
/// The view size for the layer surface's output.
view_size: Size<f64, Logical>,
/// Scale of the output the layer surface is on (and rounds its sizes to).
scale: f64,
/// Clock for driving animations.
clock: Clock,
}
niri_render_elements! {
@@ -40,7 +51,14 @@ niri_render_elements! {
}
impl MappedLayer {
pub fn new(surface: LayerSurface, rules: ResolvedLayerRules, config: &Config) -> Self {
pub fn new(
surface: LayerSurface,
rules: ResolvedLayerRules,
view_size: Size<f64, Logical>,
scale: f64,
clock: Clock,
config: &Config,
) -> Self {
let mut shadow_config = config.layout.shadow;
// Shadows for layer surfaces need to be explicitly enabled.
shadow_config.on = false;
@@ -50,7 +68,10 @@ impl MappedLayer {
surface,
rules,
block_out_buffer: SolidColorBuffer::new((0., 0.), [0., 0., 0., 1.]),
view_size,
scale,
shadow: Shadow::new(shadow_config),
clock,
}
}
@@ -66,16 +87,27 @@ impl MappedLayer {
self.shadow.update_shaders();
}
pub fn update_render_elements(&mut self, size: Size<f64, Logical>, scale: Scale<f64>) {
pub fn update_sizes(&mut self, view_size: Size<f64, Logical>, scale: f64) {
self.view_size = view_size;
self.scale = scale;
}
pub fn update_render_elements(&mut self, size: Size<f64, Logical>) {
// Round to physical pixels.
let size = size.to_physical_precise_round(scale).to_logical(scale);
let size = size
.to_physical_precise_round(self.scale)
.to_logical(self.scale);
self.block_out_buffer.resize(size);
let radius = self.rules.geometry_corner_radius.unwrap_or_default();
// FIXME: is_active based on keyboard focus?
self.shadow
.update_render_elements(size, true, radius, scale.x, 1.);
.update_render_elements(size, true, radius, self.scale, 1.);
}
pub fn are_animations_ongoing(&self) -> bool {
self.rules.baba_is_float
}
pub fn surface(&self) -> &LayerSurface {
@@ -114,16 +146,27 @@ impl MappedLayer {
true
}
pub fn bob_offset(&self) -> Point<f64, Logical> {
if !self.rules.baba_is_float {
return Point::from((0., 0.));
}
let y = baba_is_float_offset(self.clock.now(), self.view_size.h);
let y = round_logical_in_physical(self.scale, y);
Point::from((0., y))
}
pub fn render<R: NiriRenderer>(
&self,
renderer: &mut R,
location: Point<f64, Logical>,
scale: Scale<f64>,
target: RenderTarget,
) -> SplitElements<LayerSurfaceRenderElement<R>> {
let mut rv = SplitElements::default();
let scale = Scale::from(self.scale);
let alpha = self.rules.opacity.unwrap_or(1.).clamp(0., 1.);
let location = location + self.bob_offset();
if target.should_block_out(self.rules.block_out_from) {
// Round to physical pixels.
+7
View File
@@ -22,6 +22,9 @@ pub struct ResolvedLayerRules {
/// Whether to place this layer surface within the overview backdrop.
pub place_within_backdrop: bool,
/// Whether to bob this window up and down.
pub baba_is_float: bool,
}
impl ResolvedLayerRules {
@@ -41,6 +44,7 @@ impl ResolvedLayerRules {
},
geometry_corner_radius: None,
place_within_backdrop: false,
baba_is_float: false,
}
}
@@ -80,6 +84,9 @@ impl ResolvedLayerRules {
if let Some(x) = rule.place_within_backdrop {
resolved.place_within_backdrop = x;
}
if let Some(x) = rule.baba_is_float {
resolved.baba_is_float = x;
}
resolved.shadow.merge_with(&rule.shadow);
}
+2 -4
View File
@@ -25,8 +25,8 @@ use crate::render_helpers::shadow::ShadowRenderElement;
use crate::render_helpers::snapshot::RenderSnapshot;
use crate::render_helpers::solid_color::{SolidColorBuffer, SolidColorRenderElement};
use crate::render_helpers::RenderTarget;
use crate::utils::round_logical_in_physical;
use crate::utils::transaction::Transaction;
use crate::utils::{baba_is_float_offset, round_logical_in_physical};
/// Toplevel window with decorations.
#[derive(Debug)]
@@ -800,9 +800,7 @@ impl<W: LayoutElement> Tile<W> {
return Point::from((0., 0.));
}
let now = self.clock.now().as_secs_f64();
let amplitude = self.view_size.h / 96.;
let y = amplitude * ((f64::consts::TAU * now / 3.6).sin() - 1.);
let y = baba_is_float_offset(self.clock.now(), self.view_size.h);
let y = round_logical_in_physical(self.scale, y);
Point::from((0., y))
}
+22 -13
View File
@@ -2896,6 +2896,10 @@ impl Niri {
layer.with_surfaces(|surface, data| {
send_scale_transform(surface, data, scale, transform);
});
if let Some(mapped) = self.mapped_layer_surfaces.get_mut(layer) {
mapped.update_sizes(output_size, scale.fractional_scale());
}
}
layer_map.arrange();
}
@@ -2980,8 +2984,12 @@ impl Niri {
.layers_on(layer)
.rev()
.find_map(|layer| {
let layer_pos_within_output =
let mapped = self.mapped_layer_surfaces.get(layer)?;
let mut layer_pos_within_output =
layers.layer_geometry(layer).unwrap().loc.to_f64();
layer_pos_within_output += mapped.bob_offset();
let surface_type = if popup {
WindowSurfaceType::POPUP
} else {
@@ -3042,6 +3050,7 @@ impl Niri {
let mut layer_pos_within_output =
layers.layer_geometry(layer_surface).unwrap().loc.to_f64();
layer_pos_within_output += mapped.bob_offset();
// Background and bottom layers move together with the workspaces.
let mon = self.layout.monitor_for_output(output)?;
@@ -3197,6 +3206,7 @@ impl Niri {
let mut layer_pos_within_output =
layers.layer_geometry(layer_surface).unwrap().loc.to_f64();
layer_pos_within_output += mapped.bob_offset();
// Background and bottom layers move together with the workspaces.
if matches!(layer, Layer::Background | Layer::Bottom) {
@@ -3924,7 +3934,7 @@ impl Niri {
continue;
};
mapped.update_render_elements(geo.size.to_f64(), scale);
mapped.update_render_elements(geo.size.to_f64());
}
}
}
@@ -4077,15 +4087,7 @@ impl Niri {
let layer_map = layer_map_for_output(output);
let mut extend_from_layer =
|elements: &mut SplitElements<LayerSurfaceRenderElement<R>>, layer, for_backdrop| {
self.render_layer(
renderer,
target,
output_scale,
&layer_map,
layer,
elements,
for_backdrop,
);
self.render_layer(renderer, target, &layer_map, layer, elements, for_backdrop);
};
// The overlay layer elements go next.
@@ -4205,7 +4207,6 @@ impl Niri {
&self,
renderer: &mut R,
target: RenderTarget,
scale: Scale<f64>,
layer_map: &LayerMap,
layer: Layer,
elements: &mut SplitElements<LayerSurfaceRenderElement<R>>,
@@ -4223,7 +4224,7 @@ impl Niri {
Some((mapped, geo))
});
for (mapped, geo) in iter {
elements.extend(mapped.render(renderer, geo.loc.to_f64(), scale, target));
elements.extend(mapped.render(renderer, geo.loc.to_f64(), target));
}
}
@@ -4258,6 +4259,14 @@ impl Niri {
.cursor_manager
.is_current_cursor_animated(output.current_scale().integer_scale());
// Also check layer surfaces.
if !state.unfinished_animations_remain {
state.unfinished_animations_remain |= layer_map_for_output(output)
.layers()
.filter_map(|surface| self.mapped_layer_surfaces.get(surface))
.any(|mapped| mapped.are_animations_ongoing());
}
// Render.
res = backend.render(self, output, target_presentation_time);
}
+7
View File
@@ -1,4 +1,5 @@
use std::cmp::{max, min};
use std::f64;
use std::ffi::{CString, OsStr};
use std::io::Write;
use std::os::unix::prelude::OsStrExt;
@@ -396,6 +397,12 @@ pub fn center_preferring_top_left_in_area(
area.loc + offset
}
pub fn baba_is_float_offset(now: Duration, view_height: f64) -> f64 {
let now = now.as_secs_f64();
let amplitude = view_height / 96.;
amplitude * ((f64::consts::TAU * now / 3.6).sin() - 1.)
}
#[cfg(feature = "dbus")]
pub fn show_screenshot_notification(image_path: Option<PathBuf>) -> anyhow::Result<()> {
use std::collections::HashMap;
+18 -1
View File
@@ -32,8 +32,8 @@ layer-rule {
}
geometry-corner-radius 12
place-within-backdrop true
baba-is-float true
}
```
@@ -174,3 +174,20 @@ layer-rule {
place-within-backdrop true
}
```
#### `baba-is-float`
<sup>Since: next release</sup>
Make your layer surfaces FLOAT up and down.
This is a natural extension of the [April Fools' 2025 feature](./Configuration:-Window-Rules.md#baba-is-float).
```kdl
// Make fuzzel FLOAT.
layer-rule {
match namespace="^launcher$"
baba-is-float true
}
```