Align corner clipping, antialiasing, and fractional material regions
This commit is contained in:
1 parent
f110ec80e1
commit
9666e4ff6c
16 files changed
+235
-66
No files matched your search
+24
-11
@@ -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,8 +204,11 @@ 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))
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -20,23 +20,12 @@ pub(super) fn append_material_surface(
|
||||
};
|
||||
let regions = crate::effects::surface_regions(surface);
|
||||
let targets: Vec<_> = match ®ions {
|
||||
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());
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -228,7 +228,7 @@ pub(super) fn overview_strip_elements(
|
||||
alpha,
|
||||
true,
|
||||
output,
|
||||
programs.texture.clone(),
|
||||
programs.clone(),
|
||||
ConstrainScaleBehavior::Stretch,
|
||||
));
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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; }} }}"
|
||||
))
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -222,6 +222,7 @@ impl FereseShell {
|
||||
width: 1.0,
|
||||
radius: theme.material_radius.into(),
|
||||
},
|
||||
snap: true,
|
||||
..Default::default()
|
||||
}));
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
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(®ions);
|
||||
let decoded: Vec<_> = bytes
|
||||
.chunks_exact(4)
|
||||
.map(|v| f32::from_ne_bytes(v.try_into().unwrap()))
|
||||
.collect();
|
||||
assert_eq!(decoded, regions[0]);
|
||||
}
|
||||
}
|
||||
@@ -541,6 +541,7 @@ impl FereseShell {
|
||||
width: 1.0,
|
||||
radius: theme.material_radius.into(),
|
||||
},
|
||||
snap: true,
|
||||
..Default::default()
|
||||
}))
|
||||
.into()
|
||||
|
||||
Reference in new issue
Block a user