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 {
|
pub(crate) fn handoff_alpha(elapsed: Duration) -> f32 {
|
||||||
let progress = (elapsed.as_secs_f64() / HANDOFF.as_secs_f64()).clamp(0.0, 1.0);
|
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
|
(1.0 - progress * progress * (3.0 - 2.0 * progress)) as f32
|
||||||
@@ -259,6 +294,51 @@ impl RenderElement<GlesRenderer> for NativeTextureElement {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
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::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -29,9 +29,9 @@ pub(super) fn rounded_clip_program(state: &mut Ferese, renderer: &mut GlesRender
|
|||||||
UniformName::new("opacity", UniformType::_1f),
|
UniformName::new("opacity", UniformType::_1f),
|
||||||
UniformName::new("shadow_color", UniformType::_4f),
|
UniformName::new("shadow_color", UniformType::_4f),
|
||||||
];
|
];
|
||||||
let texture = renderer.compile_custom_texture_shader(ROUNDED_TEXTURE_SHADER, &texture_uniforms);
|
let texture = renderer.compile_custom_texture_shader(&corner_shader(ROUNDED_TEXTURE_SHADER), &texture_uniforms);
|
||||||
let border = renderer.compile_custom_pixel_shader(ROUNDED_BORDER_SHADER, &border_uniforms);
|
let border = renderer.compile_custom_pixel_shader(&corner_shader(ROUNDED_BORDER_SHADER), &border_uniforms);
|
||||||
let shadow = renderer.compile_custom_pixel_shader(WINDOW_SHADOW_SHADER, &shadow_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 compiled = texture.and_then(|texture| border.and_then(|border| shadow.map(|shadow| (texture, border, shadow))));
|
||||||
match compiled {
|
match compiled {
|
||||||
Ok((texture, border, shadow)) => {
|
Ok((texture, border, shadow)) => {
|
||||||
@@ -44,7 +44,9 @@ pub(super) fn rounded_clip_program(state: &mut Ferese, renderer: &mut GlesRender
|
|||||||
Some(programs)
|
Some(programs)
|
||||||
}
|
}
|
||||||
Err(error) => {
|
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
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -56,9 +58,8 @@ pub(super) fn window_border_element(
|
|||||||
renderer: &GlesRenderer,
|
renderer: &GlesRenderer,
|
||||||
id: ferese_layout::WindowId,
|
id: ferese_layout::WindowId,
|
||||||
geometry: Rectangle<i32, Logical>,
|
geometry: Rectangle<i32, Logical>,
|
||||||
physical: Rectangle<i32, Physical>,
|
corners: RoundedRect,
|
||||||
scale: f64,
|
scale: f64,
|
||||||
requested_radius: f64,
|
|
||||||
requested_width: f64,
|
requested_width: f64,
|
||||||
color: [f32; 4],
|
color: [f32; 4],
|
||||||
gradient: Option<crate::config::BorderGradient>,
|
gradient: Option<crate::config::BorderGradient>,
|
||||||
@@ -68,7 +69,7 @@ pub(super) fn window_border_element(
|
|||||||
programs: &RoundedClipPrograms,
|
programs: &RoundedClipPrograms,
|
||||||
) -> Option<PhysicalShaderElement> {
|
) -> Option<PhysicalShaderElement> {
|
||||||
let mode = output.current_mode()?;
|
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 {
|
if width == 0.0 {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
@@ -77,7 +78,12 @@ pub(super) fn window_border_element(
|
|||||||
Some(gradient) => (
|
Some(gradient) => (
|
||||||
gradient.from.0,
|
gradient.from.0,
|
||||||
gradient.to.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]),
|
None => (color, color, [0.0; 4]),
|
||||||
};
|
};
|
||||||
@@ -85,7 +91,7 @@ pub(super) fn window_border_element(
|
|||||||
Some(g) => (
|
Some(g) => (
|
||||||
g.from.0,
|
g.from.0,
|
||||||
g.to.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 => (
|
None => (
|
||||||
state.theme_settings.accent_color.0,
|
state.theme_settings.accent_color.0,
|
||||||
@@ -95,8 +101,8 @@ pub(super) fn window_border_element(
|
|||||||
};
|
};
|
||||||
let parameters = BorderParameters {
|
let parameters = BorderParameters {
|
||||||
geometry,
|
geometry,
|
||||||
clip_rect: framebuffer_clip_rect(physical, mode.size, output.current_transform().invert()),
|
clip_rect: framebuffer_clip_rect(corners.rect, mode.size, output.current_transform().invert()),
|
||||||
radius: scaled_effect_value(requested_radius, geometry, scale),
|
radius: corners.radius,
|
||||||
width,
|
width,
|
||||||
color: color_with_alpha(from, opacity),
|
color: color_with_alpha(from, opacity),
|
||||||
color_to: color_with_alpha(to, opacity),
|
color_to: color_with_alpha(to, opacity),
|
||||||
@@ -137,7 +143,7 @@ pub(super) fn window_border_element(
|
|||||||
|
|
||||||
Some(PhysicalShaderElement {
|
Some(PhysicalShaderElement {
|
||||||
inner: cached.element.clone(),
|
inner: cached.element.clone(),
|
||||||
geometry: physical,
|
geometry: corners.rect,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,9 +168,7 @@ pub(super) fn window_tint_element(
|
|||||||
renderer: &mut GlesRenderer,
|
renderer: &mut GlesRenderer,
|
||||||
id: ferese_layout::WindowId,
|
id: ferese_layout::WindowId,
|
||||||
geometry: Rectangle<i32, Logical>,
|
geometry: Rectangle<i32, Logical>,
|
||||||
physical: Rectangle<i32, Physical>,
|
corners: RoundedRect,
|
||||||
scale: f64,
|
|
||||||
radius: f64,
|
|
||||||
color: [f32; 4],
|
color: [f32; 4],
|
||||||
resize_fill: bool,
|
resize_fill: bool,
|
||||||
output: &Output,
|
output: &Output,
|
||||||
@@ -181,8 +185,8 @@ pub(super) fn window_tint_element(
|
|||||||
let program = material_program(state, renderer)?;
|
let program = material_program(state, renderer)?;
|
||||||
let parameters = BorderParameters {
|
let parameters = BorderParameters {
|
||||||
geometry,
|
geometry,
|
||||||
clip_rect: framebuffer_clip_rect(physical, mode.size, output.current_transform().invert()),
|
clip_rect: framebuffer_clip_rect(corners.rect, mode.size, output.current_transform().invert()),
|
||||||
radius: scaled_effect_value(radius, geometry, scale),
|
radius: corners.radius,
|
||||||
width: 0.0,
|
width: 0.0,
|
||||||
color,
|
color,
|
||||||
color_to: color,
|
color_to: color,
|
||||||
@@ -230,7 +234,7 @@ pub(super) fn window_tint_element(
|
|||||||
}
|
}
|
||||||
Some(PhysicalShaderElement {
|
Some(PhysicalShaderElement {
|
||||||
inner: cached.element.clone(),
|
inner: cached.element.clone(),
|
||||||
geometry: physical,
|
geometry: corners.rect,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,9 +244,8 @@ pub(super) fn window_shadow_element(
|
|||||||
renderer: &GlesRenderer,
|
renderer: &GlesRenderer,
|
||||||
id: ferese_layout::WindowId,
|
id: ferese_layout::WindowId,
|
||||||
geometry: Rectangle<i32, Logical>,
|
geometry: Rectangle<i32, Logical>,
|
||||||
physical: Rectangle<i32, Physical>,
|
corners: RoundedRect,
|
||||||
scale: f64,
|
scale: f64,
|
||||||
requested_radius: f64,
|
|
||||||
offset_y: f64,
|
offset_y: f64,
|
||||||
blur: f64,
|
blur: f64,
|
||||||
opacity: f64,
|
opacity: f64,
|
||||||
@@ -256,15 +259,19 @@ pub(super) fn window_shadow_element(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let shadow_geometry = Rectangle::new(
|
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 bounds = shadow_bounds(geometry, offset_y, blur);
|
||||||
let parameters = ShadowParameters {
|
let parameters = ShadowParameters {
|
||||||
blur: (blur * scale) as f32,
|
blur: (blur * scale) as f32,
|
||||||
bounds,
|
bounds,
|
||||||
shadow_rect: framebuffer_clip_rect(shadow_geometry, mode.size, output.current_transform().invert()),
|
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,
|
opacity: opacity as f32,
|
||||||
color,
|
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)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub(super) fn rounded_window_elements(
|
pub(super) fn rounded_window_elements(
|
||||||
renderer: &mut GlesRenderer,
|
renderer: &mut GlesRenderer,
|
||||||
window: &smithay::desktop::Window,
|
window: &smithay::desktop::Window,
|
||||||
constrain: Rectangle<i32, Logical>,
|
corners: RoundedRect,
|
||||||
physical_constrain: Rectangle<i32, Physical>,
|
|
||||||
scale: f64,
|
scale: f64,
|
||||||
requested_radius: f64,
|
|
||||||
alpha: f32,
|
alpha: f32,
|
||||||
clip_changed: bool,
|
clip_changed: bool,
|
||||||
output: &Output,
|
output: &Output,
|
||||||
@@ -355,11 +353,12 @@ pub(super) fn rounded_window_elements(
|
|||||||
return Vec::new();
|
return Vec::new();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let physical_constrain = corners.rect;
|
||||||
let geometry = window.geometry();
|
let geometry = window.geometry();
|
||||||
let reference = geometry.to_physical_precise_round(scale);
|
let reference = geometry.to_physical_precise_round(scale);
|
||||||
let location = physical_constrain.loc - geometry.loc.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 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 surface = toplevel.wl_surface();
|
||||||
|
|
||||||
let mut content = PopupManager::popups_for_surface(surface)
|
let mut content = PopupManager::popups_for_surface(surface)
|
||||||
|
|||||||
@@ -33,10 +33,14 @@ pub(super) fn append_material_surface(
|
|||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
};
|
};
|
||||||
|
let targets: Vec<_> = targets
|
||||||
|
.into_iter()
|
||||||
|
.map(|(rect, radius)| (rect, RoundedRect::from_logical(rect, scale, f64::from(radius))))
|
||||||
|
.collect();
|
||||||
let materials: Vec<_> = targets
|
let materials: Vec<_> = targets
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.filter_map(|(index, (rect, radius))| {
|
.filter_map(|(index, (rect, corners))| {
|
||||||
material_element(
|
material_element(
|
||||||
state,
|
state,
|
||||||
renderer,
|
renderer,
|
||||||
@@ -44,7 +48,7 @@ pub(super) fn append_material_surface(
|
|||||||
surface,
|
surface,
|
||||||
MaterialSurface {
|
MaterialSurface {
|
||||||
geometry: *rect,
|
geometry: *rect,
|
||||||
radius: *radius,
|
corners: *corners,
|
||||||
index,
|
index,
|
||||||
capture_geometry: geometry,
|
capture_geometry: geometry,
|
||||||
alpha: 1.0,
|
alpha: 1.0,
|
||||||
@@ -57,16 +61,13 @@ pub(super) fn append_material_surface(
|
|||||||
}
|
}
|
||||||
let material = (!materials.is_empty() && regions.is_none()).then_some(());
|
let material = (!materials.is_empty() && regions.is_none()).then_some(());
|
||||||
let clip = material.as_ref().and_then(|_| {
|
let clip = material.as_ref().and_then(|_| {
|
||||||
|
let corners = targets.first()?.1;
|
||||||
let mode = output.current_mode()?;
|
let mode = output.current_mode()?;
|
||||||
let program = rounded_clip_program(state, renderer)?;
|
let program = rounded_clip_program(state, renderer)?;
|
||||||
Some((
|
Some((
|
||||||
program.texture,
|
program.texture,
|
||||||
framebuffer_clip_rect(
|
framebuffer_clip_rect(corners.rect, mode.size, output.current_transform().invert()),
|
||||||
geometry.to_physical_precise_round(scale),
|
corners.radius,
|
||||||
mode.size,
|
|
||||||
output.current_transform().invert(),
|
|
||||||
),
|
|
||||||
scaled_effect_value(radius, geometry, scale),
|
|
||||||
))
|
))
|
||||||
});
|
});
|
||||||
// Smithay element lists are front to back: content, material, shadow.
|
// 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(super) struct MaterialSurface {
|
||||||
pub geometry: Rectangle<i32, Logical>,
|
pub geometry: Rectangle<i32, Logical>,
|
||||||
pub radius: f32,
|
pub corners: RoundedRect,
|
||||||
pub index: usize,
|
pub index: usize,
|
||||||
pub capture_geometry: Rectangle<i32, Logical>,
|
pub capture_geometry: Rectangle<i32, Logical>,
|
||||||
pub alpha: f32,
|
pub alpha: f32,
|
||||||
@@ -118,7 +119,7 @@ pub(super) fn material_element(
|
|||||||
) -> Option<(AnimatedWindowRenderElement, AnimatedWindowRenderElement)> {
|
) -> Option<(AnimatedWindowRenderElement, AnimatedWindowRenderElement)> {
|
||||||
let MaterialSurface {
|
let MaterialSurface {
|
||||||
geometry,
|
geometry,
|
||||||
radius,
|
corners,
|
||||||
index,
|
index,
|
||||||
capture_geometry,
|
capture_geometry,
|
||||||
alpha,
|
alpha,
|
||||||
@@ -139,7 +140,7 @@ pub(super) fn material_element(
|
|||||||
state.theme_settings.surface_base_color
|
state.theme_settings.surface_base_color
|
||||||
};
|
};
|
||||||
let [red, green, blue, _] = background.0;
|
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 [offset_y, shadow_blur, shadow_opacity] = material.shadow;
|
||||||
let edge_bar = role == crate::effects::SemanticRole::Panel && radius == 0.0 && geometry.loc.y == 0;
|
let edge_bar = role == crate::effects::SemanticRole::Panel && radius == 0.0 && geometry.loc.y == 0;
|
||||||
let shadow_opacity = if edge_bar {
|
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)
|
shadow_opacity * (state.theme_settings.shadow_opacity / 0.2) * f64::from(presentation_alpha)
|
||||||
};
|
};
|
||||||
let shadow_geometry = Rectangle::new(
|
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 background_opacity = material.opacity;
|
||||||
let blur = material_blur_radius(material.style, material.opacity, state.theme_settings.backdrop_blur);
|
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_geometry,
|
||||||
sample_physical,
|
sample_physical,
|
||||||
sample_framebuffer: framebuffer_clip_rect(sample_physical, mode.size, transform),
|
sample_framebuffer: framebuffer_clip_rect(sample_physical, mode.size, transform),
|
||||||
radius: radius * scale as f32,
|
radius,
|
||||||
shadow_rect: framebuffer_clip_rect(shadow_geometry.to_physical_precise_round(scale), mode.size, transform),
|
shadow_rect: framebuffer_clip_rect(shadow_geometry, mode.size, transform),
|
||||||
shadow_values: [(shadow_blur * scale) as f32, shadow_opacity as f32],
|
shadow_values: [(shadow_blur * scale) as f32, shadow_opacity as f32],
|
||||||
shadow_bounds: shadow_bounds(geometry, offset_y, shadow_blur),
|
shadow_bounds: shadow_bounds(geometry, offset_y, shadow_blur),
|
||||||
geometry,
|
geometry,
|
||||||
tint: [red, green, blue, material.opacity * presentation_alpha],
|
tint: [red, green, blue, material.opacity * presentation_alpha],
|
||||||
generation,
|
generation,
|
||||||
opaque: material.opacity == 1.0 && presentation_alpha == 1.0 && radius == 0.0,
|
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 context = renderer.context_id().erased();
|
||||||
let program = material_program(state, renderer)?;
|
let program = material_program(state, renderer)?;
|
||||||
@@ -281,7 +286,14 @@ pub(super) fn material_element(
|
|||||||
cached.parameters = parameters;
|
cached.parameters = parameters;
|
||||||
}
|
}
|
||||||
let background = match &cached.element {
|
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(),
|
MaterialElement::Blur(element) => element.clone().into(),
|
||||||
};
|
};
|
||||||
Some((background, cached.shadow.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) {
|
if let Some(program) = state.blur_programs.get(&context) {
|
||||||
return Some(program.clone());
|
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) => {
|
Ok(program) => {
|
||||||
let program = BlurProgram(program);
|
let program = BlurProgram(program);
|
||||||
state.blur_programs.insert(context, program.clone());
|
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_rect", UniformType::_4f),
|
||||||
UniformName::new("shadow_values", UniformType::_2f),
|
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) => {
|
Ok(program) => {
|
||||||
let program = MaterialProgram(program);
|
let program = MaterialProgram(program);
|
||||||
state.material_programs.insert(context, program.clone());
|
state.material_programs.insert(context, program.clone());
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ use smithay::wayland::shell::wlr_layer::Layer;
|
|||||||
|
|
||||||
use crate::Ferese;
|
use crate::Ferese;
|
||||||
use crate::metrics::FrameEffectMetrics;
|
use crate::metrics::FrameEffectMetrics;
|
||||||
use crate::presentation::{NativeTextureElement, PhysicalShaderElement, physical_rect};
|
use crate::presentation::{NativeTextureElement, PhysicalShaderElement, RoundedRect, clamp_radius, physical_rect};
|
||||||
|
|
||||||
type SurfaceRenderElement =
|
type SurfaceRenderElement =
|
||||||
CropRenderElement<RelocateRenderElement<RescaleRenderElement<WaylandSurfaceRenderElement<GlesRenderer>>>>;
|
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");
|
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)]
|
#[derive(Clone, Debug)]
|
||||||
pub(crate) struct BlurProgram(GlesTexProgram);
|
pub(crate) struct BlurProgram(GlesTexProgram);
|
||||||
|
|
||||||
@@ -510,6 +514,31 @@ mod tests {
|
|||||||
scaled_visual_rect, shadow_bounds,
|
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]
|
#[test]
|
||||||
#[ignore = "requires an EGL rendering device"]
|
#[ignore = "requires an EGL rendering device"]
|
||||||
fn translucent_blur_does_not_leak_the_sharp_backdrop() {
|
fn translucent_blur_does_not_leak_the_sharp_backdrop() {
|
||||||
@@ -523,7 +552,7 @@ mod tests {
|
|||||||
let context = EGLContext::new(&display).unwrap();
|
let context = EGLContext::new(&display).unwrap();
|
||||||
let mut renderer = unsafe { GlesRenderer::new(context).unwrap() };
|
let mut renderer = unsafe { GlesRenderer::new(context).unwrap() };
|
||||||
let program = renderer
|
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();
|
.unwrap();
|
||||||
let size = (32, 32).into();
|
let size = (32, 32).into();
|
||||||
let mut pixels = vec![128u8; 32 * 32 * 4];
|
let mut pixels = vec![128u8; 32 * 32 * 4];
|
||||||
|
|||||||
@@ -15,12 +15,12 @@ pub(super) fn overview_chrome_element(
|
|||||||
) -> Option<PhysicalShaderElement> {
|
) -> Option<PhysicalShaderElement> {
|
||||||
let output_id = state.output_id(output)?;
|
let output_id = state.output_id(output)?;
|
||||||
let geometry = rounded_visual_rect(rect, output_geometry.loc);
|
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 mode = output.current_mode()?;
|
||||||
let parameters = BorderParameters {
|
let parameters = BorderParameters {
|
||||||
geometry,
|
geometry,
|
||||||
clip_rect: framebuffer_clip_rect(physical, mode.size, output.current_transform().invert()),
|
clip_rect: framebuffer_clip_rect(corners.rect, mode.size, output.current_transform().invert()),
|
||||||
radius: (radius * scale) as f32,
|
radius: corners.radius,
|
||||||
width: (width * scale) as f32,
|
width: (width * scale) as f32,
|
||||||
color,
|
color,
|
||||||
color_to: color,
|
color_to: color,
|
||||||
@@ -79,7 +79,7 @@ pub(super) fn overview_chrome_element(
|
|||||||
}
|
}
|
||||||
Some(PhysicalShaderElement {
|
Some(PhysicalShaderElement {
|
||||||
inner: cached.element.clone(),
|
inner: cached.element.clone(),
|
||||||
geometry: physical,
|
geometry: corners.rect,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,10 +223,8 @@ pub(super) fn overview_strip_elements(
|
|||||||
elements.extend(rounded_window_elements(
|
elements.extend(rounded_window_elements(
|
||||||
renderer,
|
renderer,
|
||||||
window,
|
window,
|
||||||
rounded_visual_rect(*rect, output_geometry.loc),
|
RoundedRect::new(*rect, output_geometry.loc, scale, 4.0),
|
||||||
physical_rect(*rect, output_geometry.loc, scale),
|
|
||||||
scale,
|
scale,
|
||||||
4.0,
|
|
||||||
alpha,
|
alpha,
|
||||||
true,
|
true,
|
||||||
output,
|
output,
|
||||||
|
|||||||
@@ -136,7 +136,6 @@ pub(crate) fn output_elements(
|
|||||||
|
|
||||||
for (window, id, visual, decoration_progress, close_alpha) in windows {
|
for (window, id, visual, decoration_progress, close_alpha) in windows {
|
||||||
let constrain = rounded_visual_rect(visual, output_geometry.loc);
|
let constrain = rounded_visual_rect(visual, output_geometry.loc);
|
||||||
let pixels = physical_rect(visual, output_geometry.loc, scale);
|
|
||||||
let material_surface = window
|
let material_surface = window
|
||||||
.toplevel()
|
.toplevel()
|
||||||
.map(|toplevel| toplevel.wl_surface())
|
.map(|toplevel| toplevel.wl_surface())
|
||||||
@@ -149,6 +148,8 @@ pub(crate) fn output_elements(
|
|||||||
} else {
|
} else {
|
||||||
state.theme_settings.window_radius
|
state.theme_settings.window_radius
|
||||||
} * decoration_progress;
|
} * 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.
|
// Only overview/close intentionally scale the complete application.
|
||||||
let scale_content = state.overview.is_presenting() || state.closing_visual(id).0 != 1.0;
|
let scale_content = state.overview.is_presenting() || state.closing_visual(id).0 != 1.0;
|
||||||
let behavior = resize_content_behavior(scale_content);
|
let behavior = resize_content_behavior(scale_content);
|
||||||
@@ -159,9 +160,7 @@ pub(crate) fn output_elements(
|
|||||||
renderer,
|
renderer,
|
||||||
id,
|
id,
|
||||||
constrain,
|
constrain,
|
||||||
pixels,
|
corners,
|
||||||
scale,
|
|
||||||
window_radius,
|
|
||||||
[0.0, 0.0, 0.0, dim as f32 * close_alpha],
|
[0.0, 0.0, 0.0, dim as f32 * close_alpha],
|
||||||
false,
|
false,
|
||||||
output,
|
output,
|
||||||
@@ -195,9 +194,8 @@ pub(crate) fn output_elements(
|
|||||||
renderer,
|
renderer,
|
||||||
id,
|
id,
|
||||||
constrain,
|
constrain,
|
||||||
pixels,
|
corners,
|
||||||
scale,
|
scale,
|
||||||
window_radius,
|
|
||||||
border_width,
|
border_width,
|
||||||
border_color,
|
border_color,
|
||||||
gradient,
|
gradient,
|
||||||
@@ -213,9 +211,8 @@ pub(crate) fn output_elements(
|
|||||||
renderer,
|
renderer,
|
||||||
id,
|
id,
|
||||||
constrain,
|
constrain,
|
||||||
pixels,
|
corners,
|
||||||
scale,
|
scale,
|
||||||
window_radius,
|
|
||||||
shadow_offset_y,
|
shadow_offset_y,
|
||||||
shadow_blur,
|
shadow_blur,
|
||||||
shadow_opacity * f64::from(close_alpha) * decoration_progress,
|
shadow_opacity * f64::from(close_alpha) * decoration_progress,
|
||||||
@@ -250,8 +247,7 @@ pub(crate) fn output_elements(
|
|||||||
program: Some(programs.texture.clone()),
|
program: Some(programs.texture.clone()),
|
||||||
uniforms: vec![
|
uniforms: vec![
|
||||||
Uniform::new("clip_rect", clip).into_owned(),
|
Uniform::new("clip_rect", clip).into_owned(),
|
||||||
Uniform::new("radius", scaled_effect_value(window_radius, constrain, scale))
|
Uniform::new("radius", corners.radius).into_owned(),
|
||||||
.into_owned(),
|
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
.into(),
|
.into(),
|
||||||
@@ -261,10 +257,8 @@ pub(crate) fn output_elements(
|
|||||||
elements.extend(rounded_window_elements(
|
elements.extend(rounded_window_elements(
|
||||||
renderer,
|
renderer,
|
||||||
&window,
|
&window,
|
||||||
constrain,
|
corners,
|
||||||
pixels,
|
|
||||||
scale,
|
scale,
|
||||||
window_radius,
|
|
||||||
close_alpha,
|
close_alpha,
|
||||||
state
|
state
|
||||||
.window_geometry
|
.window_geometry
|
||||||
@@ -281,18 +275,7 @@ pub(crate) fn output_elements(
|
|||||||
{
|
{
|
||||||
let mut color = state.theme_settings.surface_base_color.0;
|
let mut color = state.theme_settings.surface_base_color.0;
|
||||||
color[3] = close_alpha;
|
color[3] = close_alpha;
|
||||||
if let Some(fill) = window_tint_element(
|
if let Some(fill) = window_tint_element(state, renderer, id, constrain, corners, color, true, output) {
|
||||||
state,
|
|
||||||
renderer,
|
|
||||||
id,
|
|
||||||
constrain,
|
|
||||||
pixels,
|
|
||||||
scale,
|
|
||||||
window_radius,
|
|
||||||
color,
|
|
||||||
true,
|
|
||||||
output,
|
|
||||||
) {
|
|
||||||
// Front-to-back: fill uncovered strips behind the native
|
// Front-to-back: fill uncovered strips behind the native
|
||||||
// content instead of stretching it or exposing wallpaper.
|
// content instead of stretching it or exposing wallpaper.
|
||||||
elements.push(fill.into());
|
elements.push(fill.into());
|
||||||
@@ -308,7 +291,7 @@ pub(crate) fn output_elements(
|
|||||||
surface,
|
surface,
|
||||||
MaterialSurface {
|
MaterialSurface {
|
||||||
geometry: constrain,
|
geometry: constrain,
|
||||||
radius: window_radius as f32,
|
corners,
|
||||||
index: 0,
|
index: 0,
|
||||||
capture_geometry: constrain,
|
capture_geometry: constrain,
|
||||||
alpha: close_alpha,
|
alpha: close_alpha,
|
||||||
|
|||||||
@@ -19,11 +19,11 @@ uniform float presentation_alpha;
|
|||||||
uniform float background_opacity;
|
uniform float background_opacity;
|
||||||
uniform vec4 tint;
|
uniform vec4 tint;
|
||||||
varying vec2 v_coords;
|
varying vec2 v_coords;
|
||||||
|
//_CORNERS_
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
vec2 half_size = visible_rect.zw * 0.5;
|
float sdf = rounded_rect_distance(gl_FragCoord.xy, visible_rect, material_radius);
|
||||||
vec2 d = abs(gl_FragCoord.xy - visible_rect.xy - half_size) - (half_size - vec2(material_radius));
|
float coverage = alpha * presentation_alpha * edge_coverage(sdf);
|
||||||
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));
|
|
||||||
if (coverage <= 0.0) { gl_FragColor = vec4(0.0); return; }
|
if (coverage <= 0.0) { gl_FragColor = vec4(0.0); return; }
|
||||||
vec2 coords = (gl_FragCoord.xy - capture_origin) / texture_size;
|
vec2 coords = (gl_FragCoord.xy - capture_origin) / texture_size;
|
||||||
vec4 color = vec4(0.0);
|
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;
|
uniform vec2 shadow_values;
|
||||||
varying vec2 v_coords;
|
varying vec2 v_coords;
|
||||||
|
|
||||||
float rounded_distance(vec2 point, vec4 rect) {
|
//_CORNERS_
|
||||||
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;
|
|
||||||
}
|
|
||||||
void main() {
|
void main() {
|
||||||
float sdf = rounded_distance(gl_FragCoord.xy, visible_rect);
|
float sdf = rounded_rect_distance(gl_FragCoord.xy, visible_rect, material_radius);
|
||||||
float coverage = 1.0 - smoothstep(-0.5, 0.5, sdf);
|
float coverage = edge_coverage(sdf);
|
||||||
if (paint_mode > 0.5) {
|
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 sigma = max(shadow_values.x * 0.5, 0.5);
|
||||||
float opacity = exp(-0.5 * distance * distance / (sigma * sigma))
|
float opacity = exp(-0.5 * distance * distance / (sigma * sigma))
|
||||||
* shadow_values.y * (1.0 - coverage) * alpha;
|
* shadow_values.y * (1.0 - coverage) * alpha;
|
||||||
|
|||||||
@@ -18,23 +18,16 @@ varying vec2 v_coords;
|
|||||||
uniform float tint;
|
uniform float tint;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void main() {
|
//_CORNERS_
|
||||||
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);
|
|
||||||
|
|
||||||
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);
|
float inner_radius = max(radius - border_width, 0.0);
|
||||||
vec2 inner_distance = abs(point - half_size)
|
float inner_coverage = edge_coverage(rounded_rect_distance(gl_FragCoord.xy, inner_rect, inner_radius));
|
||||||
- (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 coverage = max(outer_coverage - inner_coverage, 0.0);
|
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);
|
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
|
// Interpolate premultiplied endpoints: a transparent endpoint must not
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ varying vec2 v_coords;
|
|||||||
uniform float tint;
|
uniform float tint;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
//_CORNERS_
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
vec4 color = texture2D(tex, v_coords);
|
vec4 color = texture2D(tex, v_coords);
|
||||||
|
|
||||||
@@ -31,13 +33,8 @@ void main() {
|
|||||||
color = color * alpha;
|
color = color * alpha;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
vec2 point = gl_FragCoord.xy - clip_rect.xy;
|
float signed_distance = rounded_rect_distance(gl_FragCoord.xy, clip_rect, radius);
|
||||||
vec2 half_size = clip_rect.zw * 0.5;
|
float coverage = edge_coverage(signed_distance);
|
||||||
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);
|
|
||||||
color *= coverage;
|
color *= coverage;
|
||||||
|
|
||||||
#if defined(DEBUG_FLAGS)
|
#if defined(DEBUG_FLAGS)
|
||||||
|
|||||||
@@ -13,13 +13,10 @@ varying vec2 v_coords;
|
|||||||
uniform float tint;
|
uniform float tint;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
//_CORNERS_
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
vec2 point = gl_FragCoord.xy - shadow_rect.xy;
|
float signed_distance = rounded_rect_distance(gl_FragCoord.xy, shadow_rect, radius);
|
||||||
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 sigma = max(blur * 0.5, 0.5);
|
float sigma = max(blur * 0.5, 0.5);
|
||||||
float normalized_distance = max(signed_distance, 0.0) / sigma;
|
float normalized_distance = max(signed_distance, 0.0) / sigma;
|
||||||
float coverage = exp(-0.5 * normalized_distance * normalized_distance);
|
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_dimming: HashMap<WindowId, DimAnimation>,
|
||||||
pub(crate) window_focus: HashMap<WindowId, DimAnimation>,
|
pub(crate) window_focus: HashMap<WindowId, DimAnimation>,
|
||||||
pub(crate) window_shadows: HashMap<WindowId, WindowShadowBuffers>,
|
pub(crate) window_shadows: HashMap<WindowId, WindowShadowBuffers>,
|
||||||
|
pub(crate) rounded_clip_warnings: HashSet<ErasedContextId>,
|
||||||
pub(crate) rounded_clip_programs: HashMap<ErasedContextId, RoundedClipPrograms>,
|
pub(crate) rounded_clip_programs: HashMap<ErasedContextId, RoundedClipPrograms>,
|
||||||
pub(crate) overview_scrims: HashMap<OutputId, OverviewScrim>,
|
pub(crate) overview_scrims: HashMap<OutputId, OverviewScrim>,
|
||||||
pub(crate) material_programs: HashMap<ErasedContextId, MaterialProgram>,
|
pub(crate) material_programs: HashMap<ErasedContextId, MaterialProgram>,
|
||||||
@@ -581,6 +582,7 @@ impl Ferese {
|
|||||||
window_focus: HashMap::new(),
|
window_focus: HashMap::new(),
|
||||||
window_shadows: HashMap::new(),
|
window_shadows: HashMap::new(),
|
||||||
rounded_clip_programs: HashMap::new(),
|
rounded_clip_programs: HashMap::new(),
|
||||||
|
rounded_clip_warnings: HashSet::new(),
|
||||||
overview_scrims: HashMap::new(),
|
overview_scrims: HashMap::new(),
|
||||||
material_programs: HashMap::new(),
|
material_programs: HashMap::new(),
|
||||||
material_buffers: HashMap::new(),
|
material_buffers: HashMap::new(),
|
||||||
|
|||||||
@@ -604,6 +604,9 @@ pub fn resolve_with_context(
|
|||||||
*path = theme_path(directory, path);
|
*path = theme_path(directory, path);
|
||||||
}
|
}
|
||||||
validate(&tokens)?;
|
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();
|
let requested_accent = tokens.colors.accent.clone();
|
||||||
transform(&mut tokens, &policy.accessibility, &mut warnings);
|
transform(&mut tokens, &policy.accessibility, &mut warnings);
|
||||||
let theme = ResolvedTheme {
|
let theme = ResolvedTheme {
|
||||||
@@ -1297,6 +1300,22 @@ pub fn import_family(id: &str, source: &str) -> Result<ImportedFamily, String> {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
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::*;
|
use super::*;
|
||||||
|
|
||||||
fn candidate(source: &str) -> Result<Candidate, String> {
|
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;
|
mod contrast;
|
||||||
pub mod controls;
|
pub mod controls;
|
||||||
pub mod gallery;
|
pub mod gallery;
|
||||||
|
mod geometry;
|
||||||
pub mod icons;
|
pub mod icons;
|
||||||
pub mod material;
|
pub mod material;
|
||||||
pub mod menus;
|
pub mod menus;
|
||||||
@@ -14,6 +15,7 @@ mod typography;
|
|||||||
|
|
||||||
pub use button::accent_button;
|
pub use button::accent_button;
|
||||||
pub use contrast::{accent_color, accent_pair, apply, composite, contrast, foreground, luminance};
|
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 palette::{Palette, mix, parse_color, surface_shade};
|
||||||
pub use presets::{PRESETS, Preset};
|
pub use presets::{PRESETS, Preset};
|
||||||
pub use typography::{font, text};
|
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> {
|
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, 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))
|
let knob = container(cosmic::widget::Space::new().width(16).height(16))
|
||||||
.width(16)
|
.width(16)
|
||||||
.height(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)
|
container(knob)
|
||||||
.width(36)
|
.width(36)
|
||||||
.height(20)
|
.height(20)
|
||||||
.padding(2)
|
.padding(padding)
|
||||||
.align_x(if enabled {
|
.align_x(if enabled {
|
||||||
cosmic::iced::alignment::Horizontal::Right
|
cosmic::iced::alignment::Horizontal::Right
|
||||||
} else {
|
} else {
|
||||||
cosmic::iced::alignment::Horizontal::Left
|
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()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ Filled controls choose a contrasting text color automatically.
|
|||||||
| `border-width` | number ≥ 0 | `1` | Window border thickness |
|
| `border-width` | number ≥ 0 | `1` | Window border thickness |
|
||||||
| `focus-ring-width` | number ≥ 0 | `2` | Focused border thickness |
|
| `focus-ring-width` | number ≥ 0 | `2` | Focused border thickness |
|
||||||
| `window-radius` | number ≥ 0 | `14` | Managed-window corners, independent of the shell |
|
| `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-height` | number > 0 | `28` | Menu-bar height |
|
||||||
| `top-bar-margin-top` | integer ≥ 0 | `0` | Space above bar |
|
| `top-bar-margin-top` | integer ≥ 0 | `0` | Space above bar |
|
||||||
| `top-bar-window-gap` | integer ≥ 0 | `0` | Clearance below bar |
|
| `top-bar-window-gap` | integer ≥ 0 | `0` | Clearance below bar |
|
||||||
|
|||||||
@@ -361,12 +361,13 @@ impl FereseShell {
|
|||||||
&& editor.id == id
|
&& editor.id == id
|
||||||
{
|
{
|
||||||
let id = id.to_owned();
|
let id = id.to_owned();
|
||||||
|
let editor_radius = ferese_theme::inner_radius(self.config.theme.material_radius, note.padding);
|
||||||
body = body.push(
|
body = body.push(
|
||||||
cosmic::widget::TextEditor::new(&editor.content)
|
cosmic::widget::TextEditor::new(&editor.content)
|
||||||
.style(|theme, status| {
|
.style(move |theme, status| {
|
||||||
use cosmic::iced::widget::text_editor::Catalog;
|
use cosmic::iced::widget::text_editor::Catalog;
|
||||||
let mut style = theme.style(&<cosmic::Theme as Catalog>::default(), status);
|
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
|
style
|
||||||
})
|
})
|
||||||
.height(Length::Fill)
|
.height(Length::Fill)
|
||||||
|
|||||||
Reference in new issue
Block a user