Unify circular corner geometry and shader masks
This commit is contained in:
1 parent
0162870cca
commit
f110ec80e1
19 files changed
+277
-135
No files matched your search
@@ -96,6 +96,41 @@ pub(crate) fn physical_rect(
|
||||
)
|
||||
}
|
||||
|
||||
/// A single physical outline shared by content and all of its decorations.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub(crate) struct RoundedRect {
|
||||
pub rect: Rectangle<i32, Physical>,
|
||||
pub radius: f32,
|
||||
}
|
||||
|
||||
impl RoundedRect {
|
||||
pub(crate) fn new(rect: ferese_layout::Rect, origin: Point<i32, Logical>, scale: f64, radius: f64) -> Self {
|
||||
let rect = physical_rect(rect, origin, scale);
|
||||
Self {
|
||||
radius: clamp_radius(radius * scale, rect.size),
|
||||
rect,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn from_logical(rect: Rectangle<i32, Logical>, scale: f64, radius: f64) -> Self {
|
||||
Self::new(
|
||||
ferese_layout::Rect::new(
|
||||
rect.loc.x as f64,
|
||||
rect.loc.y as f64,
|
||||
rect.size.w as f64,
|
||||
rect.size.h as f64,
|
||||
),
|
||||
(0, 0).into(),
|
||||
scale,
|
||||
radius,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn clamp_radius(requested: f64, size: smithay::utils::Size<i32, Physical>) -> f32 {
|
||||
requested.min(f64::from(size.w.min(size.h)) / 2.0).max(0.0) as f32
|
||||
}
|
||||
|
||||
pub(crate) fn handoff_alpha(elapsed: Duration) -> f32 {
|
||||
let progress = (elapsed.as_secs_f64() / HANDOFF.as_secs_f64()).clamp(0.0, 1.0);
|
||||
(1.0 - progress * progress * (3.0 - 2.0 * progress)) as f32
|
||||
@@ -259,6 +294,51 @@ impl RenderElement<GlesRenderer> for NativeTextureElement {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn rounded_outline_clamps_after_physical_snapping() {
|
||||
// Logical clamping would give 1.125; the snapped 3px rect allows radius 1.5.
|
||||
let outline = RoundedRect::new(ferese_layout::Rect::new(0.2, 0.2, 1.5, 4.0), (0, 0).into(), 1.5, 20.0);
|
||||
assert_eq!(outline.rect, Rectangle::new((0, 0).into(), (3, 6).into()));
|
||||
assert_eq!(outline.radius, 1.5);
|
||||
for scale in [1.0, 1.25, 1.5, 2.0] {
|
||||
let rect = ferese_layout::Rect::new(-20.4, -10.2, 8.8, 6.4);
|
||||
let outline = RoundedRect::new(rect, (-10, -5).into(), scale, 100.0);
|
||||
assert_eq!(outline.rect, physical_rect(rect, (-10, -5).into(), scale));
|
||||
assert_eq!(
|
||||
outline.radius,
|
||||
outline.rect.size.w.min(outline.rect.size.h) as f32 / 2.0
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn physical_radius_preserves_fractions_and_handles_zero() {
|
||||
let size = (11, 7).into();
|
||||
assert_eq!(clamp_radius(100.0, size), 3.5);
|
||||
assert_eq!(clamp_radius(2.25, size), 2.25);
|
||||
assert_eq!(clamp_radius(0.0, size), 0.0);
|
||||
assert_eq!(clamp_radius(-1.0, size), 0.0);
|
||||
assert_eq!(clamp_radius(2.0, (0, 10).into()), 0.0);
|
||||
let outline = RoundedRect::new(ferese_layout::Rect::new(0.0, 0.0, 10.0, 10.0), (0, 0).into(), 1.5, 1.5);
|
||||
assert_eq!(outline.radius, 2.25);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn material_and_window_outline_agree_for_the_same_rect() {
|
||||
let logical = Rectangle::new((-3, 7).into(), (11, 9).into());
|
||||
for scale in [1.0, 1.25, 1.5, 2.0] {
|
||||
assert_eq!(
|
||||
RoundedRect::from_logical(logical, scale, 100.0),
|
||||
RoundedRect::new(
|
||||
ferese_layout::Rect::new(-3.0, 7.0, 11.0, 9.0),
|
||||
(0, 0).into(),
|
||||
scale,
|
||||
100.0
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -29,9 +29,9 @@ pub(super) fn rounded_clip_program(state: &mut Ferese, renderer: &mut GlesRender
|
||||
UniformName::new("opacity", UniformType::_1f),
|
||||
UniformName::new("shadow_color", UniformType::_4f),
|
||||
];
|
||||
let texture = renderer.compile_custom_texture_shader(ROUNDED_TEXTURE_SHADER, &texture_uniforms);
|
||||
let border = renderer.compile_custom_pixel_shader(ROUNDED_BORDER_SHADER, &border_uniforms);
|
||||
let shadow = renderer.compile_custom_pixel_shader(WINDOW_SHADOW_SHADER, &shadow_uniforms);
|
||||
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))));
|
||||
match compiled {
|
||||
Ok((texture, border, shadow)) => {
|
||||
@@ -44,7 +44,9 @@ pub(super) fn rounded_clip_program(state: &mut Ferese, renderer: &mut GlesRender
|
||||
Some(programs)
|
||||
}
|
||||
Err(error) => {
|
||||
tracing::error!(%error, "failed to compile rounded-window shader");
|
||||
if state.rounded_clip_warnings.insert(context) {
|
||||
tracing::warn!(%error, "rounded-window shader unavailable; falling back to unrounded rendering");
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -56,9 +58,8 @@ pub(super) fn window_border_element(
|
||||
renderer: &GlesRenderer,
|
||||
id: ferese_layout::WindowId,
|
||||
geometry: Rectangle<i32, Logical>,
|
||||
physical: Rectangle<i32, Physical>,
|
||||
corners: RoundedRect,
|
||||
scale: f64,
|
||||
requested_radius: f64,
|
||||
requested_width: f64,
|
||||
color: [f32; 4],
|
||||
gradient: Option<crate::config::BorderGradient>,
|
||||
@@ -68,7 +69,7 @@ pub(super) fn window_border_element(
|
||||
programs: &RoundedClipPrograms,
|
||||
) -> Option<PhysicalShaderElement> {
|
||||
let mode = output.current_mode()?;
|
||||
let width = scaled_effect_value(requested_width, geometry, scale);
|
||||
let width = clamp_radius(requested_width * scale, corners.rect.size);
|
||||
if width == 0.0 {
|
||||
return None;
|
||||
}
|
||||
@@ -77,7 +78,12 @@ pub(super) fn window_border_element(
|
||||
Some(gradient) => (
|
||||
gradient.from.0,
|
||||
gradient.to.0,
|
||||
border_gradient_line(physical, mode.size, output.current_transform().invert(), gradient.angle),
|
||||
border_gradient_line(
|
||||
corners.rect,
|
||||
mode.size,
|
||||
output.current_transform().invert(),
|
||||
gradient.angle,
|
||||
),
|
||||
),
|
||||
None => (color, color, [0.0; 4]),
|
||||
};
|
||||
@@ -85,7 +91,7 @@ pub(super) fn window_border_element(
|
||||
Some(g) => (
|
||||
g.from.0,
|
||||
g.to.0,
|
||||
border_gradient_line(physical, mode.size, output.current_transform().invert(), g.angle),
|
||||
border_gradient_line(corners.rect, mode.size, output.current_transform().invert(), g.angle),
|
||||
),
|
||||
None => (
|
||||
state.theme_settings.accent_color.0,
|
||||
@@ -95,8 +101,8 @@ pub(super) fn window_border_element(
|
||||
};
|
||||
let parameters = BorderParameters {
|
||||
geometry,
|
||||
clip_rect: framebuffer_clip_rect(physical, mode.size, output.current_transform().invert()),
|
||||
radius: scaled_effect_value(requested_radius, geometry, scale),
|
||||
clip_rect: framebuffer_clip_rect(corners.rect, mode.size, output.current_transform().invert()),
|
||||
radius: corners.radius,
|
||||
width,
|
||||
color: color_with_alpha(from, opacity),
|
||||
color_to: color_with_alpha(to, opacity),
|
||||
@@ -137,7 +143,7 @@ pub(super) fn window_border_element(
|
||||
|
||||
Some(PhysicalShaderElement {
|
||||
inner: cached.element.clone(),
|
||||
geometry: physical,
|
||||
geometry: corners.rect,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -162,9 +168,7 @@ pub(super) fn window_tint_element(
|
||||
renderer: &mut GlesRenderer,
|
||||
id: ferese_layout::WindowId,
|
||||
geometry: Rectangle<i32, Logical>,
|
||||
physical: Rectangle<i32, Physical>,
|
||||
scale: f64,
|
||||
radius: f64,
|
||||
corners: RoundedRect,
|
||||
color: [f32; 4],
|
||||
resize_fill: bool,
|
||||
output: &Output,
|
||||
@@ -181,8 +185,8 @@ pub(super) fn window_tint_element(
|
||||
let program = material_program(state, renderer)?;
|
||||
let parameters = BorderParameters {
|
||||
geometry,
|
||||
clip_rect: framebuffer_clip_rect(physical, mode.size, output.current_transform().invert()),
|
||||
radius: scaled_effect_value(radius, geometry, scale),
|
||||
clip_rect: framebuffer_clip_rect(corners.rect, mode.size, output.current_transform().invert()),
|
||||
radius: corners.radius,
|
||||
width: 0.0,
|
||||
color,
|
||||
color_to: color,
|
||||
@@ -230,7 +234,7 @@ pub(super) fn window_tint_element(
|
||||
}
|
||||
Some(PhysicalShaderElement {
|
||||
inner: cached.element.clone(),
|
||||
geometry: physical,
|
||||
geometry: corners.rect,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -240,9 +244,8 @@ pub(super) fn window_shadow_element(
|
||||
renderer: &GlesRenderer,
|
||||
id: ferese_layout::WindowId,
|
||||
geometry: Rectangle<i32, Logical>,
|
||||
physical: Rectangle<i32, Physical>,
|
||||
corners: RoundedRect,
|
||||
scale: f64,
|
||||
requested_radius: f64,
|
||||
offset_y: f64,
|
||||
blur: f64,
|
||||
opacity: f64,
|
||||
@@ -256,15 +259,19 @@ pub(super) fn window_shadow_element(
|
||||
}
|
||||
|
||||
let shadow_geometry = Rectangle::new(
|
||||
(physical.loc.x, physical.loc.y + (offset_y * scale).round() as i32).into(),
|
||||
physical.size,
|
||||
(
|
||||
corners.rect.loc.x,
|
||||
corners.rect.loc.y + (offset_y * scale).round() as i32,
|
||||
)
|
||||
.into(),
|
||||
corners.rect.size,
|
||||
);
|
||||
let bounds = shadow_bounds(geometry, offset_y, blur);
|
||||
let parameters = ShadowParameters {
|
||||
blur: (blur * scale) as f32,
|
||||
bounds,
|
||||
shadow_rect: framebuffer_clip_rect(shadow_geometry, mode.size, output.current_transform().invert()),
|
||||
radius: scaled_effect_value(requested_radius, geometry, scale),
|
||||
radius: corners.radius,
|
||||
opacity: opacity as f32,
|
||||
color,
|
||||
};
|
||||
@@ -327,21 +334,12 @@ pub(super) fn shadow_bounds(geometry: Rectangle<i32, Logical>, offset_y: f64, bl
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) fn scaled_effect_value(requested: f64, geometry: Rectangle<i32, Logical>, scale: f64) -> f32 {
|
||||
requested
|
||||
.min(f64::from(geometry.size.w.min(geometry.size.h)) / 2.0)
|
||||
.max(0.0) as f32
|
||||
* scale as f32
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(super) fn rounded_window_elements(
|
||||
renderer: &mut GlesRenderer,
|
||||
window: &smithay::desktop::Window,
|
||||
constrain: Rectangle<i32, Logical>,
|
||||
physical_constrain: Rectangle<i32, Physical>,
|
||||
corners: RoundedRect,
|
||||
scale: f64,
|
||||
requested_radius: f64,
|
||||
alpha: f32,
|
||||
clip_changed: bool,
|
||||
output: &Output,
|
||||
@@ -355,11 +353,12 @@ pub(super) fn rounded_window_elements(
|
||||
return Vec::new();
|
||||
};
|
||||
|
||||
let physical_constrain = corners.rect;
|
||||
let geometry = window.geometry();
|
||||
let reference = geometry.to_physical_precise_round(scale);
|
||||
let location = physical_constrain.loc - geometry.loc.to_physical_precise_round(scale);
|
||||
let clip = framebuffer_clip_rect(physical_constrain, mode.size, output.current_transform().invert());
|
||||
let radius = scaled_effect_value(requested_radius, constrain, scale);
|
||||
let radius = corners.radius;
|
||||
let surface = toplevel.wl_surface();
|
||||
|
||||
let mut content = PopupManager::popups_for_surface(surface)
|
||||
|
||||
@@ -33,10 +33,14 @@ pub(super) fn append_material_surface(
|
||||
})
|
||||
.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()
|
||||
.filter_map(|(index, (rect, radius))| {
|
||||
.filter_map(|(index, (rect, corners))| {
|
||||
material_element(
|
||||
state,
|
||||
renderer,
|
||||
@@ -44,7 +48,7 @@ pub(super) fn append_material_surface(
|
||||
surface,
|
||||
MaterialSurface {
|
||||
geometry: *rect,
|
||||
radius: *radius,
|
||||
corners: *corners,
|
||||
index,
|
||||
capture_geometry: geometry,
|
||||
alpha: 1.0,
|
||||
@@ -57,16 +61,13 @@ pub(super) fn append_material_surface(
|
||||
}
|
||||
let material = (!materials.is_empty() && regions.is_none()).then_some(());
|
||||
let clip = material.as_ref().and_then(|_| {
|
||||
let corners = targets.first()?.1;
|
||||
let mode = output.current_mode()?;
|
||||
let program = rounded_clip_program(state, renderer)?;
|
||||
Some((
|
||||
program.texture,
|
||||
framebuffer_clip_rect(
|
||||
geometry.to_physical_precise_round(scale),
|
||||
mode.size,
|
||||
output.current_transform().invert(),
|
||||
),
|
||||
scaled_effect_value(radius, geometry, scale),
|
||||
framebuffer_clip_rect(corners.rect, mode.size, output.current_transform().invert()),
|
||||
corners.radius,
|
||||
))
|
||||
});
|
||||
// Smithay element lists are front to back: content, material, shadow.
|
||||
@@ -103,7 +104,7 @@ pub(super) fn append_material_surface(
|
||||
|
||||
pub(super) struct MaterialSurface {
|
||||
pub geometry: Rectangle<i32, Logical>,
|
||||
pub radius: f32,
|
||||
pub corners: RoundedRect,
|
||||
pub index: usize,
|
||||
pub capture_geometry: Rectangle<i32, Logical>,
|
||||
pub alpha: f32,
|
||||
@@ -118,7 +119,7 @@ pub(super) fn material_element(
|
||||
) -> Option<(AnimatedWindowRenderElement, AnimatedWindowRenderElement)> {
|
||||
let MaterialSurface {
|
||||
geometry,
|
||||
radius,
|
||||
corners,
|
||||
index,
|
||||
capture_geometry,
|
||||
alpha,
|
||||
@@ -139,7 +140,7 @@ pub(super) fn material_element(
|
||||
state.theme_settings.surface_base_color
|
||||
};
|
||||
let [red, green, blue, _] = background.0;
|
||||
let radius = radius.min(geometry.size.w.min(geometry.size.h) as f32 * 0.5);
|
||||
let radius = corners.radius;
|
||||
let [offset_y, shadow_blur, shadow_opacity] = material.shadow;
|
||||
let edge_bar = role == crate::effects::SemanticRole::Panel && radius == 0.0 && geometry.loc.y == 0;
|
||||
let shadow_opacity = if edge_bar {
|
||||
@@ -148,8 +149,12 @@ pub(super) fn material_element(
|
||||
shadow_opacity * (state.theme_settings.shadow_opacity / 0.2) * f64::from(presentation_alpha)
|
||||
};
|
||||
let shadow_geometry = Rectangle::new(
|
||||
(geometry.loc.x, geometry.loc.y + offset_y.round() as i32).into(),
|
||||
geometry.size,
|
||||
(
|
||||
corners.rect.loc.x,
|
||||
corners.rect.loc.y + (offset_y * scale).round() as i32,
|
||||
)
|
||||
.into(),
|
||||
corners.rect.size,
|
||||
);
|
||||
let background_opacity = material.opacity;
|
||||
let blur = material_blur_radius(material.style, material.opacity, state.theme_settings.backdrop_blur);
|
||||
@@ -169,15 +174,15 @@ pub(super) fn material_element(
|
||||
sample_geometry,
|
||||
sample_physical,
|
||||
sample_framebuffer: framebuffer_clip_rect(sample_physical, mode.size, transform),
|
||||
radius: radius * scale as f32,
|
||||
shadow_rect: framebuffer_clip_rect(shadow_geometry.to_physical_precise_round(scale), mode.size, transform),
|
||||
radius,
|
||||
shadow_rect: framebuffer_clip_rect(shadow_geometry, mode.size, transform),
|
||||
shadow_values: [(shadow_blur * scale) as f32, shadow_opacity as f32],
|
||||
shadow_bounds: shadow_bounds(geometry, offset_y, shadow_blur),
|
||||
geometry,
|
||||
tint: [red, green, blue, material.opacity * presentation_alpha],
|
||||
generation,
|
||||
opaque: material.opacity == 1.0 && presentation_alpha == 1.0 && radius == 0.0,
|
||||
visible_framebuffer: framebuffer_clip_rect(geometry.to_physical_precise_round(scale), mode.size, transform),
|
||||
visible_framebuffer: framebuffer_clip_rect(corners.rect, mode.size, transform),
|
||||
};
|
||||
let context = renderer.context_id().erased();
|
||||
let program = material_program(state, renderer)?;
|
||||
@@ -281,7 +286,14 @@ pub(super) fn material_element(
|
||||
cached.parameters = parameters;
|
||||
}
|
||||
let background = match &cached.element {
|
||||
MaterialElement::Fill(element) => element.clone().into(),
|
||||
// Keep the shader's opaque regions when its canvas already has the
|
||||
// shared edges. Animated modal geometry may need a physical override.
|
||||
MaterialElement::Fill(element) if element.geometry(scale.into()) == corners.rect => element.clone().into(),
|
||||
MaterialElement::Fill(element) => PhysicalShaderElement {
|
||||
inner: element.clone(),
|
||||
geometry: corners.rect,
|
||||
}
|
||||
.into(),
|
||||
MaterialElement::Blur(element) => element.clone().into(),
|
||||
};
|
||||
Some((background, cached.shadow.clone().into()))
|
||||
@@ -300,7 +312,7 @@ pub(super) fn blur_program(state: &mut Ferese, renderer: &mut GlesRenderer) -> O
|
||||
if let Some(program) = state.blur_programs.get(&context) {
|
||||
return Some(program.clone());
|
||||
}
|
||||
match renderer.compile_custom_texture_shader(BLUR_SHADER, &blur_uniform_names()) {
|
||||
match renderer.compile_custom_texture_shader(&corner_shader(BLUR_SHADER), &blur_uniform_names()) {
|
||||
Ok(program) => {
|
||||
let program = BlurProgram(program);
|
||||
state.blur_programs.insert(context, program.clone());
|
||||
@@ -372,7 +384,7 @@ pub(super) fn material_program(state: &mut Ferese, renderer: &mut GlesRenderer)
|
||||
UniformName::new("shadow_rect", UniformType::_4f),
|
||||
UniformName::new("shadow_values", UniformType::_2f),
|
||||
];
|
||||
match renderer.compile_custom_pixel_shader(MATERIAL_SHADER, &uniforms) {
|
||||
match renderer.compile_custom_pixel_shader(&corner_shader(MATERIAL_SHADER), &uniforms) {
|
||||
Ok(program) => {
|
||||
let program = MaterialProgram(program);
|
||||
state.material_programs.insert(context, program.clone());
|
||||
|
||||
@@ -48,7 +48,7 @@ use smithay::wayland::shell::wlr_layer::Layer;
|
||||
|
||||
use crate::Ferese;
|
||||
use crate::metrics::FrameEffectMetrics;
|
||||
use crate::presentation::{NativeTextureElement, PhysicalShaderElement, physical_rect};
|
||||
use crate::presentation::{NativeTextureElement, PhysicalShaderElement, RoundedRect, clamp_radius, physical_rect};
|
||||
|
||||
type SurfaceRenderElement =
|
||||
CropRenderElement<RelocateRenderElement<RescaleRenderElement<WaylandSurfaceRenderElement<GlesRenderer>>>>;
|
||||
@@ -106,6 +106,10 @@ const MATERIAL_SHADER: &str = include_str!("shaders/material_shader.frag");
|
||||
|
||||
const BLUR_SHADER: &str = include_str!("shaders/blur_shader.frag");
|
||||
|
||||
fn corner_shader(source: &str) -> String {
|
||||
source.replace("//_CORNERS_", include_str!("shaders/corners.glsl"))
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct BlurProgram(GlesTexProgram);
|
||||
|
||||
@@ -510,6 +514,31 @@ mod tests {
|
||||
scaled_visual_rect, shadow_bounds,
|
||||
};
|
||||
|
||||
#[test]
|
||||
#[ignore = "requires an EGL rendering device"]
|
||||
fn shared_corner_shaders_compile() {
|
||||
use smithay::backend::egl::{EGLContext, EGLDevice, EGLDisplay};
|
||||
use smithay::backend::renderer::gles::GlesRenderer;
|
||||
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() };
|
||||
for source in [super::ROUNDED_TEXTURE_SHADER, super::BLUR_SHADER] {
|
||||
renderer
|
||||
.compile_custom_texture_shader(&super::corner_shader(source), &[])
|
||||
.unwrap();
|
||||
}
|
||||
for source in [
|
||||
super::ROUNDED_BORDER_SHADER,
|
||||
super::WINDOW_SHADOW_SHADER,
|
||||
super::MATERIAL_SHADER,
|
||||
] {
|
||||
renderer
|
||||
.compile_custom_pixel_shader(&super::corner_shader(source), &[])
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "requires an EGL rendering device"]
|
||||
fn translucent_blur_does_not_leak_the_sharp_backdrop() {
|
||||
@@ -523,7 +552,7 @@ mod tests {
|
||||
let context = EGLContext::new(&display).unwrap();
|
||||
let mut renderer = unsafe { GlesRenderer::new(context).unwrap() };
|
||||
let program = renderer
|
||||
.compile_custom_texture_shader(super::BLUR_SHADER, &super::blur_uniform_names())
|
||||
.compile_custom_texture_shader(&super::corner_shader(super::BLUR_SHADER), &super::blur_uniform_names())
|
||||
.unwrap();
|
||||
let size = (32, 32).into();
|
||||
let mut pixels = vec![128u8; 32 * 32 * 4];
|
||||
|
||||
@@ -15,12 +15,12 @@ pub(super) fn overview_chrome_element(
|
||||
) -> Option<PhysicalShaderElement> {
|
||||
let output_id = state.output_id(output)?;
|
||||
let geometry = rounded_visual_rect(rect, output_geometry.loc);
|
||||
let physical = physical_rect(rect, output_geometry.loc, scale);
|
||||
let corners = RoundedRect::new(rect, output_geometry.loc, scale, radius);
|
||||
let mode = output.current_mode()?;
|
||||
let parameters = BorderParameters {
|
||||
geometry,
|
||||
clip_rect: framebuffer_clip_rect(physical, mode.size, output.current_transform().invert()),
|
||||
radius: (radius * scale) as f32,
|
||||
clip_rect: framebuffer_clip_rect(corners.rect, mode.size, output.current_transform().invert()),
|
||||
radius: corners.radius,
|
||||
width: (width * scale) as f32,
|
||||
color,
|
||||
color_to: color,
|
||||
@@ -79,7 +79,7 @@ pub(super) fn overview_chrome_element(
|
||||
}
|
||||
Some(PhysicalShaderElement {
|
||||
inner: cached.element.clone(),
|
||||
geometry: physical,
|
||||
geometry: corners.rect,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -223,10 +223,8 @@ pub(super) fn overview_strip_elements(
|
||||
elements.extend(rounded_window_elements(
|
||||
renderer,
|
||||
window,
|
||||
rounded_visual_rect(*rect, output_geometry.loc),
|
||||
physical_rect(*rect, output_geometry.loc, scale),
|
||||
RoundedRect::new(*rect, output_geometry.loc, scale, 4.0),
|
||||
scale,
|
||||
4.0,
|
||||
alpha,
|
||||
true,
|
||||
output,
|
||||
|
||||
@@ -136,7 +136,6 @@ pub(crate) fn output_elements(
|
||||
|
||||
for (window, id, visual, decoration_progress, close_alpha) in windows {
|
||||
let constrain = rounded_visual_rect(visual, output_geometry.loc);
|
||||
let pixels = physical_rect(visual, output_geometry.loc, scale);
|
||||
let material_surface = window
|
||||
.toplevel()
|
||||
.map(|toplevel| toplevel.wl_surface())
|
||||
@@ -149,6 +148,8 @@ pub(crate) fn output_elements(
|
||||
} else {
|
||||
state.theme_settings.window_radius
|
||||
} * decoration_progress;
|
||||
let corners = RoundedRect::new(visual, output_geometry.loc, scale, window_radius);
|
||||
let pixels = corners.rect;
|
||||
// Only overview/close intentionally scale the complete application.
|
||||
let scale_content = state.overview.is_presenting() || state.closing_visual(id).0 != 1.0;
|
||||
let behavior = resize_content_behavior(scale_content);
|
||||
@@ -159,9 +160,7 @@ pub(crate) fn output_elements(
|
||||
renderer,
|
||||
id,
|
||||
constrain,
|
||||
pixels,
|
||||
scale,
|
||||
window_radius,
|
||||
corners,
|
||||
[0.0, 0.0, 0.0, dim as f32 * close_alpha],
|
||||
false,
|
||||
output,
|
||||
@@ -195,9 +194,8 @@ pub(crate) fn output_elements(
|
||||
renderer,
|
||||
id,
|
||||
constrain,
|
||||
pixels,
|
||||
corners,
|
||||
scale,
|
||||
window_radius,
|
||||
border_width,
|
||||
border_color,
|
||||
gradient,
|
||||
@@ -213,9 +211,8 @@ pub(crate) fn output_elements(
|
||||
renderer,
|
||||
id,
|
||||
constrain,
|
||||
pixels,
|
||||
corners,
|
||||
scale,
|
||||
window_radius,
|
||||
shadow_offset_y,
|
||||
shadow_blur,
|
||||
shadow_opacity * f64::from(close_alpha) * decoration_progress,
|
||||
@@ -250,8 +247,7 @@ pub(crate) fn output_elements(
|
||||
program: Some(programs.texture.clone()),
|
||||
uniforms: vec![
|
||||
Uniform::new("clip_rect", clip).into_owned(),
|
||||
Uniform::new("radius", scaled_effect_value(window_radius, constrain, scale))
|
||||
.into_owned(),
|
||||
Uniform::new("radius", corners.radius).into_owned(),
|
||||
],
|
||||
}
|
||||
.into(),
|
||||
@@ -261,10 +257,8 @@ pub(crate) fn output_elements(
|
||||
elements.extend(rounded_window_elements(
|
||||
renderer,
|
||||
&window,
|
||||
constrain,
|
||||
pixels,
|
||||
corners,
|
||||
scale,
|
||||
window_radius,
|
||||
close_alpha,
|
||||
state
|
||||
.window_geometry
|
||||
@@ -281,18 +275,7 @@ pub(crate) fn output_elements(
|
||||
{
|
||||
let mut color = state.theme_settings.surface_base_color.0;
|
||||
color[3] = close_alpha;
|
||||
if let Some(fill) = window_tint_element(
|
||||
state,
|
||||
renderer,
|
||||
id,
|
||||
constrain,
|
||||
pixels,
|
||||
scale,
|
||||
window_radius,
|
||||
color,
|
||||
true,
|
||||
output,
|
||||
) {
|
||||
if let Some(fill) = window_tint_element(state, renderer, id, constrain, corners, color, true, output) {
|
||||
// Front-to-back: fill uncovered strips behind the native
|
||||
// content instead of stretching it or exposing wallpaper.
|
||||
elements.push(fill.into());
|
||||
@@ -308,7 +291,7 @@ pub(crate) fn output_elements(
|
||||
surface,
|
||||
MaterialSurface {
|
||||
geometry: constrain,
|
||||
radius: window_radius as f32,
|
||||
corners,
|
||||
index: 0,
|
||||
capture_geometry: constrain,
|
||||
alpha: close_alpha,
|
||||
|
||||
@@ -19,11 +19,11 @@ uniform float presentation_alpha;
|
||||
uniform float background_opacity;
|
||||
uniform vec4 tint;
|
||||
varying vec2 v_coords;
|
||||
//_CORNERS_
|
||||
|
||||
void main() {
|
||||
vec2 half_size = visible_rect.zw * 0.5;
|
||||
vec2 d = abs(gl_FragCoord.xy - visible_rect.xy - half_size) - (half_size - vec2(material_radius));
|
||||
float sdf = length(max(d, 0.0)) + min(max(d.x, d.y), 0.0) - material_radius;
|
||||
float coverage = alpha * presentation_alpha * (1.0 - smoothstep(-0.5, 0.5, sdf));
|
||||
float sdf = rounded_rect_distance(gl_FragCoord.xy, visible_rect, material_radius);
|
||||
float coverage = alpha * presentation_alpha * edge_coverage(sdf);
|
||||
if (coverage <= 0.0) { gl_FragColor = vec4(0.0); return; }
|
||||
vec2 coords = (gl_FragCoord.xy - capture_origin) / texture_size;
|
||||
vec4 color = vec4(0.0);
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
// Coordinates and radius are physical pixels. Rust clamps the outer radius.
|
||||
float rounded_rect_distance(vec2 point, vec4 rect, float radius) {
|
||||
vec2 half_size = rect.zw * 0.5;
|
||||
vec2 d = abs(point - rect.xy - half_size) - (half_size - vec2(radius));
|
||||
return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0) - radius;
|
||||
}
|
||||
|
||||
float edge_coverage(float distance) {
|
||||
return 1.0 - smoothstep(-0.5, 0.5, distance);
|
||||
}
|
||||
@@ -9,17 +9,13 @@ uniform vec4 shadow_rect;
|
||||
uniform vec2 shadow_values;
|
||||
varying vec2 v_coords;
|
||||
|
||||
float rounded_distance(vec2 point, vec4 rect) {
|
||||
vec2 half_size = rect.zw * 0.5;
|
||||
float radius = min(material_radius, min(half_size.x, half_size.y));
|
||||
vec2 d = abs(point - rect.xy - half_size) - (half_size - vec2(radius));
|
||||
return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0) - radius;
|
||||
}
|
||||
//_CORNERS_
|
||||
|
||||
void main() {
|
||||
float sdf = rounded_distance(gl_FragCoord.xy, visible_rect);
|
||||
float coverage = 1.0 - smoothstep(-0.5, 0.5, sdf);
|
||||
float sdf = rounded_rect_distance(gl_FragCoord.xy, visible_rect, material_radius);
|
||||
float coverage = edge_coverage(sdf);
|
||||
if (paint_mode > 0.5) {
|
||||
float distance = max(rounded_distance(gl_FragCoord.xy, shadow_rect), 0.0);
|
||||
float distance = max(rounded_rect_distance(gl_FragCoord.xy, shadow_rect, material_radius), 0.0);
|
||||
float sigma = max(shadow_values.x * 0.5, 0.5);
|
||||
float opacity = exp(-0.5 * distance * distance / (sigma * sigma))
|
||||
* shadow_values.y * (1.0 - coverage) * alpha;
|
||||
|
||||
@@ -18,23 +18,16 @@ varying vec2 v_coords;
|
||||
uniform float tint;
|
||||
#endif
|
||||
|
||||
void main() {
|
||||
vec2 point = gl_FragCoord.xy - clip_rect.xy;
|
||||
vec2 half_size = clip_rect.zw * 0.5;
|
||||
vec2 distance = abs(point - half_size) - (half_size - vec2(radius));
|
||||
float signed_distance = length(max(distance, 0.0))
|
||||
+ min(max(distance.x, distance.y), 0.0)
|
||||
- radius;
|
||||
float outer_coverage = 1.0 - smoothstep(-0.5, 0.5, signed_distance);
|
||||
//_CORNERS_
|
||||
|
||||
vec2 inner_half_size = max(half_size - vec2(border_width), vec2(0.0));
|
||||
void main() {
|
||||
float signed_distance = rounded_rect_distance(gl_FragCoord.xy, clip_rect, radius);
|
||||
float outer_coverage = edge_coverage(signed_distance);
|
||||
|
||||
vec2 inner_size = max(clip_rect.zw - vec2(2.0 * border_width), vec2(0.0));
|
||||
vec4 inner_rect = vec4(clip_rect.xy + (clip_rect.zw - inner_size) * 0.5, inner_size);
|
||||
float inner_radius = max(radius - border_width, 0.0);
|
||||
vec2 inner_distance = abs(point - half_size)
|
||||
- (inner_half_size - vec2(inner_radius));
|
||||
float inner_signed_distance = length(max(inner_distance, 0.0))
|
||||
+ min(max(inner_distance.x, inner_distance.y), 0.0)
|
||||
- inner_radius;
|
||||
float inner_coverage = 1.0 - smoothstep(-0.5, 0.5, inner_signed_distance);
|
||||
float inner_coverage = edge_coverage(rounded_rect_distance(gl_FragCoord.xy, inner_rect, inner_radius));
|
||||
float coverage = max(outer_coverage - inner_coverage, 0.0);
|
||||
float progress = clamp(dot(gl_FragCoord.xy - gradient_line.xy, gradient_line.zw), 0.0, 1.0);
|
||||
// Interpolate premultiplied endpoints: a transparent endpoint must not
|
||||
|
||||
@@ -22,6 +22,8 @@ varying vec2 v_coords;
|
||||
uniform float tint;
|
||||
#endif
|
||||
|
||||
//_CORNERS_
|
||||
|
||||
void main() {
|
||||
vec4 color = texture2D(tex, v_coords);
|
||||
|
||||
@@ -31,13 +33,8 @@ void main() {
|
||||
color = color * alpha;
|
||||
#endif
|
||||
|
||||
vec2 point = gl_FragCoord.xy - clip_rect.xy;
|
||||
vec2 half_size = clip_rect.zw * 0.5;
|
||||
vec2 distance = abs(point - half_size) - (half_size - vec2(radius));
|
||||
float signed_distance = length(max(distance, 0.0))
|
||||
+ min(max(distance.x, distance.y), 0.0)
|
||||
- radius;
|
||||
float coverage = 1.0 - smoothstep(-0.5, 0.5, signed_distance);
|
||||
float signed_distance = rounded_rect_distance(gl_FragCoord.xy, clip_rect, radius);
|
||||
float coverage = edge_coverage(signed_distance);
|
||||
color *= coverage;
|
||||
|
||||
#if defined(DEBUG_FLAGS)
|
||||
|
||||
@@ -13,13 +13,10 @@ varying vec2 v_coords;
|
||||
uniform float tint;
|
||||
#endif
|
||||
|
||||
//_CORNERS_
|
||||
|
||||
void main() {
|
||||
vec2 point = gl_FragCoord.xy - shadow_rect.xy;
|
||||
vec2 half_size = shadow_rect.zw * 0.5;
|
||||
vec2 distance = abs(point - half_size) - (half_size - vec2(radius));
|
||||
float signed_distance = length(max(distance, 0.0))
|
||||
+ min(max(distance.x, distance.y), 0.0)
|
||||
- radius;
|
||||
float signed_distance = rounded_rect_distance(gl_FragCoord.xy, shadow_rect, radius);
|
||||
float sigma = max(blur * 0.5, 0.5);
|
||||
float normalized_distance = max(signed_distance, 0.0) / sigma;
|
||||
float coverage = exp(-0.5 * normalized_distance * normalized_distance);
|
||||
|
||||
@@ -346,6 +346,7 @@ pub struct Ferese {
|
||||
pub(crate) window_dimming: HashMap<WindowId, DimAnimation>,
|
||||
pub(crate) window_focus: HashMap<WindowId, DimAnimation>,
|
||||
pub(crate) window_shadows: HashMap<WindowId, WindowShadowBuffers>,
|
||||
pub(crate) rounded_clip_warnings: HashSet<ErasedContextId>,
|
||||
pub(crate) rounded_clip_programs: HashMap<ErasedContextId, RoundedClipPrograms>,
|
||||
pub(crate) overview_scrims: HashMap<OutputId, OverviewScrim>,
|
||||
pub(crate) material_programs: HashMap<ErasedContextId, MaterialProgram>,
|
||||
@@ -581,6 +582,7 @@ impl Ferese {
|
||||
window_focus: HashMap::new(),
|
||||
window_shadows: HashMap::new(),
|
||||
rounded_clip_programs: HashMap::new(),
|
||||
rounded_clip_warnings: HashSet::new(),
|
||||
overview_scrims: HashMap::new(),
|
||||
material_programs: HashMap::new(),
|
||||
material_buffers: HashMap::new(),
|
||||
|
||||
@@ -604,6 +604,9 @@ 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 {
|
||||
@@ -1297,6 +1300,22 @@ 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)] {
|
||||
let document = crate::Document::parse(&format!(
|
||||
"theme {{ geometry {{ shell-radius {requested}; window-radius 7.25; }} }}"
|
||||
))
|
||||
.unwrap();
|
||||
let result = super::resolve(&document, std::path::Path::new("/tmp"), jiff::Timestamp::now(), |_| {
|
||||
Err("unexpected theme file".into())
|
||||
})
|
||||
.unwrap();
|
||||
assert_eq!(result.theme.tokens.geometry.shell_radius, expected);
|
||||
assert_eq!(result.theme.tokens.geometry.window_radius, 7.25);
|
||||
}
|
||||
}
|
||||
|
||||
use super::*;
|
||||
|
||||
fn candidate(source: &str) -> Result<Candidate, String> {
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
/// Radius of a concentric inset, in the same units as the outer radius.
|
||||
pub fn inner_radius(outer: f32, padding: f32) -> f32 {
|
||||
(outer - padding).max(0.0)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn inset_corners_preserve_the_center_and_saturate() {
|
||||
assert_eq!(inner_radius(14.0, 4.0), 10.0);
|
||||
assert_eq!(inner_radius(7.5, 2.25), 5.25);
|
||||
assert_eq!(inner_radius(14.0, 0.0), 14.0);
|
||||
assert_eq!(inner_radius(14.0, 14.0), 0.0);
|
||||
assert_eq!(inner_radius(14.0, 20.0), 0.0);
|
||||
assert_eq!(inner_radius(0.0, 4.0), 0.0);
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ pub mod calendar;
|
||||
mod contrast;
|
||||
pub mod controls;
|
||||
pub mod gallery;
|
||||
mod geometry;
|
||||
pub mod icons;
|
||||
pub mod material;
|
||||
pub mod menus;
|
||||
@@ -14,6 +15,7 @@ mod typography;
|
||||
|
||||
pub use button::accent_button;
|
||||
pub use contrast::{accent_color, accent_pair, apply, composite, contrast, foreground, luminance};
|
||||
pub use geometry::inner_radius;
|
||||
pub use palette::{Palette, mix, parse_color, surface_shade};
|
||||
pub use presets::{PRESETS, Preset};
|
||||
pub use typography::{font, text};
|
||||
@@ -78,20 +78,25 @@ pub fn separator<'a, M: 'a>(foreground: Color, opacity: f32) -> Element<'a, M> {
|
||||
|
||||
pub fn switch<'a, M: 'a>(enabled: bool, palette: crate::Palette, opacity: f32) -> Element<'a, M> {
|
||||
let (track, thumb) = crate::controls::switch_colors(palette, enabled, false);
|
||||
let track_radius = 10.;
|
||||
let padding = 2.;
|
||||
let knob = container(cosmic::widget::Space::new().width(16).height(16))
|
||||
.width(16)
|
||||
.height(16)
|
||||
.class(crate::controls::surface(Color { a: opacity, ..thumb }, 8.));
|
||||
.class(crate::controls::surface(
|
||||
Color { a: opacity, ..thumb },
|
||||
crate::inner_radius(track_radius, padding),
|
||||
));
|
||||
container(knob)
|
||||
.width(36)
|
||||
.height(20)
|
||||
.padding(2)
|
||||
.padding(padding)
|
||||
.align_x(if enabled {
|
||||
cosmic::iced::alignment::Horizontal::Right
|
||||
} else {
|
||||
cosmic::iced::alignment::Horizontal::Left
|
||||
})
|
||||
.class(crate::controls::surface(track.scale_alpha(opacity), 10.))
|
||||
.class(crate::controls::surface(track.scale_alpha(opacity), track_radius))
|
||||
.into()
|
||||
}
|
||||
|
||||
|
||||
@@ -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; 0 makes them square |
|
||||
| `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 |
|
||||
| `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 |
|
||||
|
||||
@@ -361,12 +361,13 @@ impl FereseShell {
|
||||
&& editor.id == id
|
||||
{
|
||||
let id = id.to_owned();
|
||||
let editor_radius = ferese_theme::inner_radius(self.config.theme.material_radius, note.padding);
|
||||
body = body.push(
|
||||
cosmic::widget::TextEditor::new(&editor.content)
|
||||
.style(|theme, status| {
|
||||
.style(move |theme, status| {
|
||||
use cosmic::iced::widget::text_editor::Catalog;
|
||||
let mut style = theme.style(&<cosmic::Theme as Catalog>::default(), status);
|
||||
style.border.radius = motion::radius(f32::MAX).into();
|
||||
style.border.radius = editor_radius.into();
|
||||
style
|
||||
})
|
||||
.height(Length::Fill)
|
||||
|
||||
Reference in new issue
Block a user