Align corner clipping, antialiasing, and fractional material regions

This commit is contained in:
Samuel Onoja committed 2026-10-01 16:27:31 +01:00
1 parent f110ec80e1
commit 9666e4ff6c
16 files changed
+237 -68

No files matched your search

+25 -12
View File
@@ -56,7 +56,7 @@ struct SurfaceEffectsState {
attached: AtomicBool,
generation: AtomicU64,
role: Mutex<Option<SemanticRole>>,
regions: Mutex<Option<Vec<[i32; 5]>>>,
regions: Mutex<Option<Vec<[f64; 5]>>>,
opacity: Mutex<u32>,
presentation_supported: AtomicBool,
dismissing: AtomicBool,
@@ -183,7 +183,7 @@ impl Dispatch<FereseSurfaceMaterialV1, SurfaceEffectsUserData> for Ferese {
}
}
pub(crate) fn surface_regions(surface: &WlSurface) -> Option<Vec<[i32; 5]>> {
pub(crate) fn surface_regions(surface: &WlSurface) -> Option<Vec<[f64; 5]>> {
with_states(surface, |states| {
states
.data_map
@@ -195,7 +195,7 @@ pub(crate) fn surface_regions(surface: &WlSurface) -> Option<Vec<[i32; 5]>> {
})
}
fn decode_regions(bytes: &[u8]) -> Option<Vec<[i32; 5]>> {
fn decode_regions(bytes: &[u8]) -> Option<Vec<[f64; 5]>> {
if !bytes.len().is_multiple_of(20) || bytes.len() > 32 * 20 {
return None;
}
@@ -204,9 +204,12 @@ fn decode_regions(bytes: &[u8]) -> Option<Vec<[i32; 5]>> {
.0
.iter()
.map(|tuple| {
let values: [i32; 5] = std::array::from_fn(|index| i32::from_ne_bytes(tuple.as_chunks::<4>().0[index]));
(values[2] > 0 && values[3] > 0 && values[4] >= 0 && values.iter().all(|v| v.abs_diff(0) <= 32768))
.then_some(values)
let values: [f64; 5] = std::array::from_fn(|i| f64::from(f32::from_ne_bytes(tuple.as_chunks::<4>().0[i])));
(values[2] > 0.0
&& values[3] > 0.0
&& values[4] >= 0.0
&& values.iter().all(|v| v.is_finite() && v.abs() <= 32768.0))
.then_some(values)
})
.collect()
}
@@ -439,15 +442,25 @@ mod tests {
use super::*;
#[test]
fn rounded_regions_are_bounded_and_validated() {
let region = [12_i32, 8, 120, 48, 11];
let bytes: Vec<_> = region.into_iter().flat_map(i32::to_ne_bytes).collect();
assert_eq!(decode_regions(&bytes), Some(vec![region]));
fn fractional_regions_validate_all_wire_values() {
let region = [0.25f32, -0.75, 100.5, 40.25, 13.5];
let encode = |r: [f32; 5]| r.into_iter().flat_map(f32::to_ne_bytes).collect::<Vec<_>>();
let bytes = encode(region);
assert_eq!(decode_regions(&bytes), Some(vec![region.map(f64::from)]));
assert_eq!(decode_regions(&[]), Some(vec![]));
assert!(decode_regions(&bytes[..19]).is_none());
assert!(decode_regions(&bytes.repeat(33)).is_none());
for invalid in [[0_i32, 0, 0, 48, 11], [0, 0, 120, 48, -1], [i32::MIN, 0, 120, 48, 11]] {
assert!(decode_regions(&invalid.into_iter().flat_map(i32::to_ne_bytes).collect::<Vec<_>>()).is_none());
for index in 0..5 {
for invalid in [f32::NAN, f32::INFINITY, f32::NEG_INFINITY, 32769.0] {
let mut bad = region;
bad[index] = invalid;
assert!(decode_regions(&encode(bad)).is_none());
}
}
for (index, value) in [(2, 0.0), (3, -1.0), (4, -0.25)] {
let mut bad = region;
bad[index] = value;
assert!(decode_regions(&encode(bad)).is_none());
}
}
+15 -4
View File
@@ -32,11 +32,22 @@ pub(super) fn rounded_clip_program(state: &mut Ferese, renderer: &mut GlesRender
let texture = renderer.compile_custom_texture_shader(&corner_shader(ROUNDED_TEXTURE_SHADER), &texture_uniforms);
let border = renderer.compile_custom_pixel_shader(&corner_shader(ROUNDED_BORDER_SHADER), &border_uniforms);
let shadow = renderer.compile_custom_pixel_shader(&corner_shader(WINDOW_SHADOW_SHADER), &shadow_uniforms);
let compiled = texture.and_then(|texture| border.and_then(|border| shadow.map(|shadow| (texture, border, shadow))));
let solid = renderer.compile_custom_pixel_shader(
&corner_shader(ROUNDED_SOLID_SHADER),
&[
UniformName::new("clip_rect", UniformType::_4f),
UniformName::new("radius", UniformType::_1f),
UniformName::new("color", UniformType::_4f),
],
);
let compiled = texture.and_then(|texture| {
border.and_then(|border| shadow.and_then(|shadow| solid.map(|solid| (texture, border, shadow, solid))))
});
match compiled {
Ok((texture, border, shadow)) => {
Ok((texture, border, shadow, solid)) => {
let programs = RoundedClipPrograms {
texture,
solid,
border,
shadow,
};
@@ -343,7 +354,7 @@ pub(super) fn rounded_window_elements(
alpha: f32,
clip_changed: bool,
output: &Output,
program: GlesTexProgram,
programs: RoundedClipPrograms,
behavior: ConstrainScaleBehavior,
) -> Vec<AnimatedWindowRenderElement> {
let Some(toplevel) = window.toplevel() else {
@@ -390,7 +401,7 @@ pub(super) fn rounded_window_elements(
.map(|inner| {
RoundedSurfaceRenderElement {
inner,
program: program.clone(),
programs: programs.clone(),
clip_rect: clip,
radius,
clip_changed,
+44 -15
View File
@@ -20,23 +20,12 @@ pub(super) fn append_material_surface(
};
let regions = crate::effects::surface_regions(surface);
let targets: Vec<_> = match &regions {
None => vec![(geometry, radius as f32)],
None => vec![(geometry, RoundedRect::from_logical(geometry, scale, radius))],
Some(regions) => regions
.iter()
.filter_map(|r| {
Rectangle::new(
(content_origin.x + r[0], content_origin.y + r[1]).into(),
(r[2], r[3]).into(),
)
.intersection(geometry)
.map(|rect| (rect, r[4] as f32))
})
.filter_map(|r| material_region_geometry(*r, geometry, content_origin, scale))
.collect(),
};
let targets: Vec<_> = targets
.into_iter()
.map(|(rect, radius)| (rect, RoundedRect::from_logical(rect, scale, f64::from(radius))))
.collect();
let materials: Vec<_> = targets
.iter()
.enumerate()
@@ -65,7 +54,7 @@ pub(super) fn append_material_surface(
let mode = output.current_mode()?;
let program = rounded_clip_program(state, renderer)?;
Some((
program.texture,
program,
framebuffer_clip_rect(corners.rect, mode.size, output.current_transform().invert()),
corners.radius,
))
@@ -84,7 +73,7 @@ pub(super) fn append_material_surface(
let element = if let Some((program, clip_rect, radius)) = &clip {
WindowContentRenderElement::Rounded(RoundedSurfaceRenderElement {
inner: element,
program: program.clone(),
programs: program.clone(),
clip_rect: *clip_rect,
radius: *radius,
clip_changed: false,
@@ -102,6 +91,27 @@ pub(super) fn append_material_surface(
}
}
/// Preserve the surface-local fractional edges until the shared physical snap.
fn material_region_geometry(
region: [f64; 5],
geometry: Rectangle<i32, Logical>,
origin: Point<i32, Logical>,
scale: f64,
) -> Option<(Rectangle<i32, Logical>, RoundedRect)> {
let rect = Rectangle::<f64, Logical>::new(
(f64::from(origin.x) + region[0], f64::from(origin.y) + region[1]).into(),
(region[2], region[3]).into(),
)
.intersection(geometry.to_f64())?;
let corners = RoundedRect::new(
ferese_layout::Rect::new(rect.loc.x, rect.loc.y, rect.size.w, rect.size.h),
(0, 0).into(),
scale,
region[4],
);
Some((rect.to_i32_up(), corners))
}
pub(super) struct MaterialSurface {
pub geometry: Rectangle<i32, Logical>,
pub corners: RoundedRect,
@@ -424,3 +434,22 @@ pub(super) fn blur_damage(
DamageSet::from_slice(&[Rectangle::from_size((size.w, size.h).into())])
}
}
#[cfg(test)]
mod region_geometry_tests {
use super::*;
#[test]
fn fractional_regions_snap_once_in_output_pixels() {
let bounds = Rectangle::from_size((200, 100).into());
let (_, corners) = material_region_geometry([0.4, 0.4, 100.4, 40.4, 14.0], bounds, (0, 0).into(), 1.5).unwrap();
assert_eq!(corners.rect, Rectangle::new((1, 1).into(), (150, 60).into()));
assert_eq!(corners.radius, 21.0);
// The old logical rounding would put this at physical (0, 0).
let (_, clipped) =
material_region_geometry([-0.4, -0.4, 100.4, 40.4, 100.0], bounds, (0, 0).into(), 1.5).unwrap();
assert_eq!(clipped.rect, Rectangle::from_size((150, 60).into()));
assert_eq!(clipped.radius, 30.0);
assert!(material_region_geometry([300.0, 0.0, 10.0, 10.0, 2.0], bounds, (0, 0).into(), 1.5).is_none());
}
}
+78 -3
View File
@@ -96,6 +96,8 @@ render_elements! {
const ROUNDED_TEXTURE_SHADER: &str = include_str!("shaders/rounded_texture_shader.frag");
const ROUNDED_SOLID_SHADER: &str = include_str!("shaders/rounded_solid_shader.frag");
const ROUNDED_BORDER_SHADER: &str = include_str!("shaders/rounded_border_shader.frag");
const WINDOW_SHADOW_SHADER: &str = include_str!("shaders/window_shadow_shader.frag");
@@ -280,6 +282,7 @@ impl RenderElement<GlesRenderer> for BlurRenderElement {
#[derive(Clone, Debug)]
pub(crate) struct RoundedClipPrograms {
solid: GlesPixelProgram,
texture: GlesTexProgram,
border: GlesPixelProgram,
shadow: GlesPixelProgram,
@@ -390,7 +393,7 @@ pub(crate) struct WindowShadowBuffers {
#[derive(Debug)]
struct RoundedSurfaceRenderElement {
inner: WaylandSurfaceRenderElement<GlesRenderer>,
program: GlesTexProgram,
programs: RoundedClipPrograms,
clip_rect: [f32; 4],
radius: f32,
clip_changed: bool,
@@ -459,13 +462,25 @@ impl RenderElement<GlesRenderer> for RoundedSurfaceRenderElement {
opaque_regions,
self.transform(),
self.alpha(),
Some(&self.program),
Some(&self.programs.texture),
&[
Uniform::new("clip_rect", self.clip_rect),
Uniform::new("radius", self.radius),
],
),
WaylandSurfaceTexture::SolidColor(_) => self.inner.draw(frame, src, dst, damage, opaque_regions),
WaylandSurfaceTexture::SolidColor(color) => frame.render_pixel_shader_to(
&self.programs.solid,
src,
dst,
(dst.size.w, dst.size.h).into(),
Some(damage),
self.alpha(),
&[
Uniform::new("clip_rect", self.clip_rect),
Uniform::new("radius", self.radius),
Uniform::new("color", color.components()),
],
),
}
}
@@ -514,6 +529,65 @@ mod tests {
scaled_visual_rect, shadow_bounds,
};
#[test]
#[ignore = "requires an EGL rendering device"]
fn solid_corner_mask_preserves_premultiplied_color() {
use smithay::backend::allocator::Fourcc;
use smithay::backend::egl::{EGLContext, EGLDevice, EGLDisplay};
use smithay::backend::renderer::gles::{GlesRenderer, GlesTexture, Uniform, UniformName, UniformType};
use smithay::backend::renderer::{Bind, Color32F, ExportMem, Frame, Offscreen, Renderer};
let device = EGLDevice::enumerate().unwrap().last().expect("an EGL device");
let display = unsafe { EGLDisplay::new(device).unwrap() };
let context = EGLContext::new(&display).unwrap();
let mut renderer = unsafe { GlesRenderer::new(context).unwrap() };
let program = renderer
.compile_custom_pixel_shader(
&super::corner_shader(super::ROUNDED_SOLID_SHADER),
&[
UniformName::new("clip_rect", UniformType::_4f),
UniformName::new("radius", UniformType::_1f),
UniformName::new("color", UniformType::_4f),
],
)
.unwrap();
let size = (32, 32).into();
let mut texture: GlesTexture = renderer.create_buffer(Fourcc::Abgr8888, size).unwrap();
let rect = Rectangle::<i32, Physical>::from_size((32, 32).into());
for (radius, corner_alpha, inset) in [(12.0f32, 0u8, 0.0f32), (0.0, 64, 0.0), (0.0, 48, 0.25)] {
let mut target = renderer.bind(&mut texture).unwrap();
{
let mut frame = renderer
.render(&mut target, (32, 32).into(), Transform::Normal)
.unwrap();
frame.clear(Color32F::TRANSPARENT, &[rect]).unwrap();
frame
.render_pixel_shader_to(
&program,
Rectangle::from_size(size.to_f64()),
rect,
size,
Some(&[rect]),
0.5,
&[
Uniform::new("clip_rect", [inset, inset, 32.0 - 2.0 * inset, 32.0 - 2.0 * inset]),
Uniform::new("radius", radius),
Uniform::new("color", [0.5f32, 0.0, 0.0, 0.5]),
],
)
.unwrap();
let _ = frame.finish().unwrap();
}
let mapping = renderer
.copy_framebuffer(&target, Rectangle::from_size(size), Fourcc::Abgr8888)
.unwrap();
let pixels = renderer.map_texture(&mapping).unwrap();
let center = (16 * 32 + 16) * 4;
assert!(pixels[center].abs_diff(64) <= 1);
assert!(pixels[center + 3].abs_diff(64) <= 1);
assert!(pixels[3].abs_diff(corner_alpha) <= 1);
}
}
#[test]
#[ignore = "requires an EGL rendering device"]
fn shared_corner_shaders_compile() {
@@ -529,6 +603,7 @@ mod tests {
.unwrap();
}
for source in [
super::ROUNDED_SOLID_SHADER,
super::ROUNDED_BORDER_SHADER,
super::WINDOW_SHADOW_SHADER,
super::MATERIAL_SHADER,
+1 -1
View File
@@ -228,7 +228,7 @@ pub(super) fn overview_strip_elements(
alpha,
true,
output,
programs.texture.clone(),
programs.clone(),
ConstrainScaleBehavior::Stretch,
));
}
+1 -1
View File
@@ -265,7 +265,7 @@ pub(crate) fn output_elements(
.get(&id)
.is_some_and(|geometry| geometry.presentation_changed),
output,
programs.texture.clone(),
programs.clone(),
behavior,
));
let source = window.geometry().size;
+2 -1
View File
@@ -6,5 +6,6 @@ float rounded_rect_distance(vec2 point, vec4 rect, float radius) {
}
float edge_coverage(float distance) {
return 1.0 - smoothstep(-0.5, 0.5, distance);
// Match Iced/WGPU quad edges: a one-physical-pixel linear ramp.
return clamp(0.5 - distance, 0.0, 1.0);
}
@@ -0,0 +1,13 @@
precision highp float;
uniform float alpha;
uniform vec4 clip_rect;
uniform float radius;
uniform vec4 color;
//_CORNERS_
void main() {
float distance = rounded_rect_distance(gl_FragCoord.xy, clip_rect, radius);
// Single-pixel buffer colors are already premultiplied.
gl_FragColor = color * alpha * edge_coverage(distance);
}
+2 -5
View File
@@ -604,9 +604,6 @@ pub fn resolve_with_context(
*path = theme_path(directory, path);
}
validate(&tokens)?;
// Effects regions encode integer logical radii; publish the same value
// to the shell and compositor, without rewriting the authored config.
tokens.geometry.shell_radius = tokens.geometry.shell_radius.round();
let requested_accent = tokens.colors.accent.clone();
transform(&mut tokens, &policy.accessibility, &mut warnings);
let theme = ResolvedTheme {
@@ -1301,8 +1298,8 @@ pub fn import_family(id: &str, source: &str) -> Result<ImportedFamily, String> {
#[cfg(test)]
mod tests {
#[test]
fn resolved_shell_radius_matches_integer_effect_regions() {
for (requested, expected) in [(0.0, 0.0), (0.4, 0.0), (13.4, 13.0), (13.5, 14.0)] {
fn resolved_shell_radius_preserves_fractional_values() {
for (requested, expected) in [(0.0, 0.0), (0.4, 0.4), (13.4, 13.4), (13.5, 13.5)] {
let document = crate::Document::parse(&format!(
"theme {{ geometry {{ shell-radius {requested}; window-radius 7.25; }} }}"
))
+1 -1
View File
@@ -183,7 +183,7 @@ Filled controls choose a contrasting text color automatically.
| `border-width` | number ≥ 0 | `1` | Window border thickness |
| `focus-ring-width` | number ≥ 0 | `2` | Focused border thickness |
| `window-radius` | number ≥ 0 | `14` | Managed-window corners, independent of the shell |
| `shell-radius` | number ≥ 0 | `14` | All shell surfaces, cards, widgets and interaction backgrounds; rounded to the nearest logical pixel to match blur regions; 0 makes them square |
| `shell-radius` | number ≥ 0 | `14` | All shell surfaces, cards, widgets and interaction backgrounds; fractional radii are preserved; 0 makes them square |
| `top-bar-height` | number > 0 | `28` | Menu-bar height |
| `top-bar-margin-top` | integer ≥ 0 | `0` | Space above bar |
| `top-bar-window-gap` | integer ≥ 0 | `0` | Clearance below bar |
+5 -2
View File
@@ -81,8 +81,11 @@
<request name="set_regions" since="2">
<description summary="restrict material to rounded surface-local rectangles">
An array of native-endian signed 32-bit tuples (x, y, width, height,
radius), measured in logical pixels. At most 32 rectangles are allowed.
An array of native-endian IEEE 754 binary32 tuples (x, y, width, height,
radius) in logical pixels. All values must be finite and have magnitude
at most 32768; width and height must be positive and radius nonnegative.
At most 32 rectangles are allowed. Invalid arrays are ignored.
Coordinates are rounded only after output scaling.
An empty array disables material painting without clearing the role.
Surfaces which never send this request retain full-surface materials.
</description>
+17 -8
View File
@@ -3,7 +3,7 @@ use cosmic::iced::advanced::{Clipboard, Layout, Renderer as _, Shell, Widget, la
use cosmic::iced::{Event, Length, Rectangle, Size, Transformation, Vector};
use cosmic::{Element, Theme};
pub type Regions = std::sync::Arc<std::sync::Mutex<Vec<[i32; 5]>>>;
pub type Regions = std::sync::Arc<std::sync::Mutex<Vec<[f32; 5]>>>;
#[derive(Clone, Copy, Debug, PartialEq, serde::Deserialize)]
#[serde(default)]
@@ -463,18 +463,18 @@ impl<M> Widget<M, Theme, cosmic::Renderer> for Motion<'_, M> {
struct CollectRegions(Vec<Rectangle>);
impl CollectRegions {
fn into_regions(self, translation: Vector, radius: f32) -> Vec<[i32; 5]> {
fn into_regions(self, translation: Vector, radius: f32) -> Vec<[f32; 5]> {
self.0
.into_iter()
.take(32)
.filter(|r| r.width > 0.0 && r.height > 0.0)
.map(|r| {
[
(r.x + translation.x).round() as i32,
(r.y + translation.y).round() as i32,
r.width.round() as i32,
r.height.round() as i32,
radius.round().max(0.0) as i32,
r.x + translation.x,
r.y + translation.y,
r.width,
r.height,
radius.max(0.0),
]
})
.collect()
@@ -508,6 +508,15 @@ impl widget::Operation for CollectRegions {
mod tests {
use super::*;
#[test]
fn material_regions_preserve_fractional_layout_and_translation() {
let collector = CollectRegions(vec![Rectangle::new((0.25, 2.5).into(), (100.25, 40.5).into())]);
assert_eq!(
collector.into_regions(Vector::new(0.125, -0.25), 13.5),
vec![[0.375, 2.25, 100.25, 40.5, 13.5]]
);
}
#[test]
fn centered_modal_material_tracks_surface_position_and_animation() {
let mut collector = CollectRegions(Vec::new());
@@ -515,7 +524,7 @@ mod tests {
widget::Operation::container(&mut collector, Some(&widget::Id::new("ferese-blur-card")), card);
assert_eq!(
collector.into_regions(Vector::new(0.0, -2.0), 14.0),
vec![[380, 418, 440, 220, 14]]
vec![[380., 418., 440., 220., 14.]]
);
}
@@ -467,6 +467,7 @@ impl FereseShell {
width: if history { 0.0 } else { 1.0 },
radius: palette.material_radius.into(),
},
snap: true,
..Default::default()
}));
let card: Element<'_, cosmic::Action<Message>> = card.into();
@@ -738,6 +739,7 @@ impl FereseShell {
width: 1.0,
radius: palette.material_radius.into(),
},
snap: true,
..Default::default()
}))
.into();
+1
View File
@@ -222,6 +222,7 @@ impl FereseShell {
width: 1.0,
radius: theme.material_radius.into(),
},
snap: true,
..Default::default()
}));
+29 -15
View File
@@ -433,12 +433,19 @@ pub(super) fn wallpaper_fallback_style(_theme: &cosmic::Theme) -> container::Sty
}
}
fn encode_regions(regions: &[[f32; 5]]) -> Vec<u8> {
regions
.iter()
.flat_map(|region| region.iter().flat_map(|value| value.to_ne_bytes()))
.collect()
}
pub(super) struct EffectsBinding {
connection: Connection,
_manager: FereseEffectsManagerV1,
pub(super) surface: FereseSurfaceEffectsV1,
_queue: EventQueue<EffectsState>,
regions: std::cell::RefCell<Option<Vec<[i32; 5]>>>,
regions: std::cell::RefCell<Option<Vec<[f32; 5]>>>,
opacity: std::cell::Cell<Option<u32>>,
}
@@ -463,13 +470,11 @@ impl EffectsBinding {
let connection = Connection::from_backend(backend);
let (globals, queue) = registry_queue_init::<EffectsState>(&connection)?;
let qh = queue.handle();
let manager = globals.bind::<FereseEffectsManagerV1, _, _>(&qh, 1..=3, ())?;
let manager = globals.bind::<FereseEffectsManagerV1, _, _>(&qh, 3..=3, ())?;
let effects = manager.get_surface_effects(surface, &qh, ());
let opacity = (opacity.clamp(0.0, 1.0) * 1000.0).round() as u32;
if effects.version() >= 3 {
effects.set_opacity(opacity);
}
effects.set_opacity(opacity);
if let Some(role) = role {
effects.set_role(role);
} else {
@@ -487,24 +492,19 @@ impl EffectsBinding {
})
}
pub(super) fn set_regions(&self, regions: &[[i32; 5]]) -> Result<(), Box<dyn std::error::Error>> {
pub(super) fn set_regions(&self, regions: &[[f32; 5]]) -> Result<(), Box<dyn std::error::Error>> {
self.set_material_regions(regions, ferese_surface_effects_v1::Role::Popover)
}
pub(super) fn set_material_regions(
&self,
regions: &[[i32; 5]],
regions: &[[f32; 5]],
role: ferese_surface_effects_v1::Role,
) -> Result<(), Box<dyn std::error::Error>> {
if self.surface.version() < 2 || self.regions.borrow().as_deref() == Some(regions) {
if self.regions.borrow().as_deref() == Some(regions) {
return Ok(());
}
self.surface.set_regions(
regions
.iter()
.flat_map(|r| r.iter().flat_map(|v| v.to_ne_bytes()))
.collect(),
);
self.surface.set_regions(encode_regions(regions));
if regions.is_empty() {
self.surface.clear_role();
} else {
@@ -529,7 +529,7 @@ impl EffectsBinding {
pub(super) fn set_opacity(&self, opacity: f32) -> Result<(), Box<dyn std::error::Error>> {
let opacity = (opacity.clamp(0.0, 1.0) * 1000.0).round() as u32;
if self.surface.version() >= 3 && self.opacity.get() != Some(opacity) {
if self.opacity.get() != Some(opacity) {
self.surface.set_opacity(opacity);
self.connection.flush()?;
self.opacity.set(Some(opacity));
@@ -554,3 +554,17 @@ impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for EffectsState {
delegate_noop!(EffectsState: ignore FereseEffectsManagerV1);
delegate_noop!(EffectsState: ignore FereseSurfaceEffectsV1);
#[cfg(test)]
mod region_encoding_tests {
#[test]
fn fractional_region_encoding_preserves_values() {
let regions = [[0.25, -0.75, 100.5, 40.25, 14.0]];
let bytes = super::encode_regions(&regions);
let decoded: Vec<_> = bytes
.chunks_exact(4)
.map(|v| f32::from_ne_bytes(v.try_into().unwrap()))
.collect();
assert_eq!(decoded, regions[0]);
}
}
+1
View File
@@ -541,6 +541,7 @@ impl FereseShell {
width: 1.0,
radius: theme.material_radius.into(),
},
snap: true,
..Default::default()
}))
.into()