feat: clip windows with rounded corners
This commit is contained in:
1 parent
5bdb7bdf24
commit
9abdaee01e
2 files changed
+500
-118
No files matched your search
@@ -20,7 +20,7 @@ use smithay::{
|
|||||||
backend::{
|
backend::{
|
||||||
allocator::dmabuf::Dmabuf,
|
allocator::dmabuf::Dmabuf,
|
||||||
drm::{DrmEventTime, DrmNode},
|
drm::{DrmEventTime, DrmNode},
|
||||||
renderer::ImportDma,
|
renderer::{ErasedContextId, ImportDma},
|
||||||
},
|
},
|
||||||
desktop::{
|
desktop::{
|
||||||
LayerSurface, PopupManager, Space, Window, WindowSurfaceType, layer_map_for_output,
|
LayerSurface, PopupManager, Space, Window, WindowSurfaceType, layer_map_for_output,
|
||||||
@@ -93,6 +93,7 @@ pub struct Ferese {
|
|||||||
pub window_ids: HashMap<Window, WindowId>,
|
pub window_ids: HashMap<Window, WindowId>,
|
||||||
pub window_geometry: HashMap<WindowId, WindowGeometry>,
|
pub window_geometry: HashMap<WindowId, WindowGeometry>,
|
||||||
pub(crate) window_borders: HashMap<WindowId, crate::winit::WindowBorderBuffers>,
|
pub(crate) window_borders: HashMap<WindowId, crate::winit::WindowBorderBuffers>,
|
||||||
|
pub(crate) rounded_clip_programs: HashMap<ErasedContextId, crate::winit::RoundedClipPrograms>,
|
||||||
viewport_animations: HashMap<WorkspaceId, AnimatedValue>,
|
viewport_animations: HashMap<WorkspaceId, AnimatedValue>,
|
||||||
scrolling_world_x: HashMap<WindowId, (WorkspaceId, AnimatedValue)>,
|
scrolling_world_x: HashMap<WindowId, (WorkspaceId, AnimatedValue)>,
|
||||||
pub focused_window: Option<WindowId>,
|
pub focused_window: Option<WindowId>,
|
||||||
@@ -394,6 +395,7 @@ impl Ferese {
|
|||||||
window_ids: HashMap::new(),
|
window_ids: HashMap::new(),
|
||||||
window_geometry: HashMap::new(),
|
window_geometry: HashMap::new(),
|
||||||
window_borders: HashMap::new(),
|
window_borders: HashMap::new(),
|
||||||
|
rounded_clip_programs: HashMap::new(),
|
||||||
viewport_animations: HashMap::new(),
|
viewport_animations: HashMap::new(),
|
||||||
scrolling_world_x: HashMap::new(),
|
scrolling_world_x: HashMap::new(),
|
||||||
focused_window: None,
|
focused_window: None,
|
||||||
|
|||||||
+497
-117
@@ -1,4 +1,5 @@
|
|||||||
use std::{
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
error::Error,
|
error::Error,
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
@@ -6,26 +7,32 @@ use std::{
|
|||||||
use smithay::{
|
use smithay::{
|
||||||
backend::{
|
backend::{
|
||||||
renderer::{
|
renderer::{
|
||||||
Color32F, Frame, ImportDma, Renderer,
|
Color32F, ErasedContextId, Frame, ImportDma, Renderer,
|
||||||
damage::OutputDamageTracker,
|
damage::OutputDamageTracker,
|
||||||
element::{
|
element::{
|
||||||
AsRenderElements, Kind as RenderElementKind, RenderElement,
|
AsRenderElements, Element, Id, Kind as RenderElementKind, RenderElement,
|
||||||
|
UnderlyingStorage,
|
||||||
memory::MemoryRenderBufferRenderElement,
|
memory::MemoryRenderBufferRenderElement,
|
||||||
render_elements,
|
render_elements,
|
||||||
solid::{SolidColorBuffer, SolidColorRenderElement},
|
surface::{
|
||||||
surface::{WaylandSurfaceRenderElement, render_elements_from_surface_tree},
|
WaylandSurfaceRenderElement, WaylandSurfaceTexture,
|
||||||
|
render_elements_from_surface_tree,
|
||||||
|
},
|
||||||
utils::{
|
utils::{
|
||||||
ConstrainAlign, ConstrainScaleBehavior, CropRenderElement, Relocate,
|
ConstrainAlign, ConstrainScaleBehavior, CropRenderElement, Relocate,
|
||||||
RelocateRenderElement, RescaleRenderElement,
|
RelocateRenderElement, RescaleRenderElement, constrain_render_elements,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
gles::GlesRenderer,
|
gles::{
|
||||||
utils::draw_render_elements,
|
GlesError, GlesFrame, GlesPixelProgram, GlesRenderer, GlesTexProgram, Uniform,
|
||||||
|
UniformName, UniformType, element::PixelShaderElement,
|
||||||
|
},
|
||||||
|
utils::{CommitCounter, DamageSet, OpaqueRegions, draw_render_elements},
|
||||||
},
|
},
|
||||||
winit::{self, WinitEvent},
|
winit::{self, WinitEvent},
|
||||||
},
|
},
|
||||||
desktop::{
|
desktop::{
|
||||||
LayerSurface, layer_map_for_output,
|
LayerSurface, PopupManager, layer_map_for_output,
|
||||||
space::{ConstrainBehavior, ConstrainReference, constrain_space_element},
|
space::{ConstrainBehavior, ConstrainReference, constrain_space_element},
|
||||||
utils::OutputPresentationFeedback,
|
utils::OutputPresentationFeedback,
|
||||||
},
|
},
|
||||||
@@ -33,7 +40,10 @@ use smithay::{
|
|||||||
output::{Mode, Output, PhysicalProperties, Scale, Subpixel},
|
output::{Mode, Output, PhysicalProperties, Scale, Subpixel},
|
||||||
reexports::calloop::EventLoop,
|
reexports::calloop::EventLoop,
|
||||||
reexports::wayland_protocols::wp::presentation_time::server::wp_presentation_feedback::Kind as PresentationKind,
|
reexports::wayland_protocols::wp::presentation_time::server::wp_presentation_feedback::Kind as PresentationKind,
|
||||||
utils::{Clock, Logical, Monotonic, Physical, Point, Rectangle, Transform},
|
utils::{
|
||||||
|
Buffer, Clock, Logical, Monotonic, Physical, Point, Rectangle, Scale as RenderScale,
|
||||||
|
Transform,
|
||||||
|
},
|
||||||
wayland::shell::wlr_layer::Layer,
|
wayland::shell::wlr_layer::Layer,
|
||||||
wayland::{compositor::with_states, presentation::Refresh},
|
wayland::{compositor::with_states, presentation::Refresh},
|
||||||
};
|
};
|
||||||
@@ -44,28 +54,231 @@ type SurfaceRenderElement = CropRenderElement<
|
|||||||
RelocateRenderElement<RescaleRenderElement<WaylandSurfaceRenderElement<GlesRenderer>>>,
|
RelocateRenderElement<RescaleRenderElement<WaylandSurfaceRenderElement<GlesRenderer>>>,
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
type WindowRenderElement =
|
||||||
|
CropRenderElement<RelocateRenderElement<RescaleRenderElement<WindowContentRenderElement>>>;
|
||||||
|
|
||||||
type MemoryRenderElement = CropRenderElement<
|
type MemoryRenderElement = CropRenderElement<
|
||||||
RelocateRenderElement<RescaleRenderElement<MemoryRenderBufferRenderElement<GlesRenderer>>>,
|
RelocateRenderElement<RescaleRenderElement<MemoryRenderBufferRenderElement<GlesRenderer>>>,
|
||||||
>;
|
>;
|
||||||
|
|
||||||
render_elements! {
|
render_elements! {
|
||||||
pub(crate) AnimatedWindowRenderElement<=GlesRenderer>;
|
pub(crate) AnimatedWindowRenderElement<=GlesRenderer>;
|
||||||
|
Window=WindowRenderElement,
|
||||||
Surface=SurfaceRenderElement,
|
Surface=SurfaceRenderElement,
|
||||||
Memory=MemoryRenderElement,
|
Memory=MemoryRenderElement,
|
||||||
Solid=SolidColorRenderElement,
|
Border=PixelShaderElement,
|
||||||
|
}
|
||||||
|
|
||||||
|
render_elements! {
|
||||||
|
WindowContentRenderElement<=GlesRenderer>;
|
||||||
|
Rounded=RoundedSurfaceRenderElement,
|
||||||
|
Popup=WaylandSurfaceRenderElement<GlesRenderer>,
|
||||||
|
}
|
||||||
|
|
||||||
|
const ROUNDED_TEXTURE_SHADER: &str = r#"#version 100
|
||||||
|
|
||||||
|
//_DEFINES_
|
||||||
|
|
||||||
|
#if defined(EXTERNAL)
|
||||||
|
#extension GL_OES_EGL_image_external : require
|
||||||
|
#endif
|
||||||
|
|
||||||
|
precision mediump float;
|
||||||
|
#if defined(EXTERNAL)
|
||||||
|
uniform samplerExternalOES tex;
|
||||||
|
#else
|
||||||
|
uniform sampler2D tex;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
uniform float alpha;
|
||||||
|
uniform vec4 clip_rect;
|
||||||
|
uniform float radius;
|
||||||
|
varying vec2 v_coords;
|
||||||
|
|
||||||
|
#if defined(DEBUG_FLAGS)
|
||||||
|
uniform float tint;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec4 color = texture2D(tex, v_coords);
|
||||||
|
|
||||||
|
#if defined(NO_ALPHA)
|
||||||
|
color = vec4(color.rgb, 1.0) * alpha;
|
||||||
|
#else
|
||||||
|
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);
|
||||||
|
color *= coverage;
|
||||||
|
|
||||||
|
#if defined(DEBUG_FLAGS)
|
||||||
|
if (tint == 1.0)
|
||||||
|
color = vec4(0.0, 0.2, 0.0, 0.2) + color * 0.8;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
gl_FragColor = color;
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
const ROUNDED_BORDER_SHADER: &str = r#"
|
||||||
|
precision mediump float;
|
||||||
|
|
||||||
|
uniform float alpha;
|
||||||
|
uniform vec4 clip_rect;
|
||||||
|
uniform float radius;
|
||||||
|
uniform float border_width;
|
||||||
|
uniform vec4 border_color;
|
||||||
|
varying vec2 v_coords;
|
||||||
|
|
||||||
|
#if defined(DEBUG_FLAGS)
|
||||||
|
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);
|
||||||
|
|
||||||
|
vec2 inner_half_size = max(half_size - vec2(border_width), vec2(0.0));
|
||||||
|
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 coverage = max(outer_coverage - inner_coverage, 0.0);
|
||||||
|
vec4 color = vec4(border_color.rgb * border_color.a, border_color.a)
|
||||||
|
* coverage * alpha;
|
||||||
|
|
||||||
|
#if defined(DEBUG_FLAGS)
|
||||||
|
if (tint == 1.0)
|
||||||
|
color = vec4(0.0, 0.2, 0.0, 0.2) + color * 0.8;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
gl_FragColor = color;
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub(crate) struct RoundedClipPrograms {
|
||||||
|
texture: GlesTexProgram,
|
||||||
|
border: GlesPixelProgram,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
struct BorderParameters {
|
||||||
|
geometry: Rectangle<i32, Logical>,
|
||||||
|
clip_rect: [f32; 4],
|
||||||
|
radius: f32,
|
||||||
|
width: f32,
|
||||||
|
color: [f32; 4],
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct WindowBorderBuffers {
|
struct CachedBorder {
|
||||||
edges: [SolidColorBuffer; 4],
|
element: PixelShaderElement,
|
||||||
|
parameters: BorderParameters,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for WindowBorderBuffers {
|
#[derive(Debug, Default)]
|
||||||
fn default() -> Self {
|
pub(crate) struct WindowBorderBuffers {
|
||||||
Self {
|
contexts: HashMap<ErasedContextId, CachedBorder>,
|
||||||
edges: std::array::from_fn(|_| SolidColorBuffer::default()),
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct RoundedSurfaceRenderElement {
|
||||||
|
inner: WaylandSurfaceRenderElement<GlesRenderer>,
|
||||||
|
program: GlesTexProgram,
|
||||||
|
clip_rect: [f32; 4],
|
||||||
|
radius: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Element for RoundedSurfaceRenderElement {
|
||||||
|
fn id(&self) -> &Id {
|
||||||
|
self.inner.id()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn current_commit(&self) -> CommitCounter {
|
||||||
|
self.inner.current_commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn geometry(&self, scale: RenderScale<f64>) -> Rectangle<i32, Physical> {
|
||||||
|
self.inner.geometry(scale)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transform(&self) -> Transform {
|
||||||
|
self.inner.transform()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn src(&self) -> Rectangle<f64, Buffer> {
|
||||||
|
self.inner.src()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn damage_since(
|
||||||
|
&self,
|
||||||
|
scale: RenderScale<f64>,
|
||||||
|
commit: Option<CommitCounter>,
|
||||||
|
) -> DamageSet<i32, Physical> {
|
||||||
|
self.inner.damage_since(scale, commit)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn opaque_regions(&self, _scale: RenderScale<f64>) -> OpaqueRegions<i32, Physical> {
|
||||||
|
OpaqueRegions::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn alpha(&self) -> f32 {
|
||||||
|
self.inner.alpha()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn kind(&self) -> RenderElementKind {
|
||||||
|
self.inner.kind()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderElement<GlesRenderer> for RoundedSurfaceRenderElement {
|
||||||
|
fn draw(
|
||||||
|
&self,
|
||||||
|
frame: &mut GlesFrame<'_, '_>,
|
||||||
|
src: Rectangle<f64, Buffer>,
|
||||||
|
dst: Rectangle<i32, Physical>,
|
||||||
|
damage: &[Rectangle<i32, Physical>],
|
||||||
|
opaque_regions: &[Rectangle<i32, Physical>],
|
||||||
|
) -> Result<(), GlesError> {
|
||||||
|
match self.inner.texture() {
|
||||||
|
WaylandSurfaceTexture::Texture(texture) => frame.render_texture_from_to(
|
||||||
|
texture,
|
||||||
|
src,
|
||||||
|
dst,
|
||||||
|
damage,
|
||||||
|
opaque_regions,
|
||||||
|
self.transform(),
|
||||||
|
self.alpha(),
|
||||||
|
Some(&self.program),
|
||||||
|
&[
|
||||||
|
Uniform::new("clip_rect", self.clip_rect),
|
||||||
|
Uniform::new("radius", self.radius),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
WaylandSurfaceTexture::SolidColor(_) => {
|
||||||
|
self.inner.draw(frame, src, dst, damage, opaque_regions)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn underlying_storage(&self, renderer: &mut GlesRenderer) -> Option<UnderlyingStorage<'_>> {
|
||||||
|
self.inner.underlying_storage(renderer)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(event_loop: &mut EventLoop<Ferese>, state: &mut Ferese) -> Result<(), Box<dyn Error>> {
|
pub fn init(event_loop: &mut EventLoop<Ferese>, state: &mut Ferese) -> Result<(), Box<dyn Error>> {
|
||||||
@@ -256,6 +469,7 @@ fn output_elements(
|
|||||||
return Vec::new();
|
return Vec::new();
|
||||||
};
|
};
|
||||||
let scale = output.current_scale().fractional_scale();
|
let scale = output.current_scale().fractional_scale();
|
||||||
|
let rounded_clip_program = rounded_clip_program(state, renderer);
|
||||||
|
|
||||||
let mut elements = if include_cursor {
|
let mut elements = if include_cursor {
|
||||||
cursor_elements(state, renderer, output_geometry, scale)
|
cursor_elements(state, renderer, output_geometry, scale)
|
||||||
@@ -298,26 +512,61 @@ fn output_elements(
|
|||||||
.into(),
|
.into(),
|
||||||
);
|
);
|
||||||
|
|
||||||
if !fullscreen {
|
if !fullscreen && let Some(programs) = rounded_clip_program.clone() {
|
||||||
elements.extend(window_border_elements(state, id, constrain, scale));
|
let focused = state.focused_window == Some(id);
|
||||||
|
let border_width = if focused {
|
||||||
|
state.theme_settings.focus_ring_width
|
||||||
|
} else {
|
||||||
|
state.theme_settings.border_width
|
||||||
|
};
|
||||||
|
let border_color = if focused {
|
||||||
|
state.theme_settings.accent_color.0
|
||||||
|
} else {
|
||||||
|
state.theme_settings.border_color.0
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(border) = window_border_element(
|
||||||
|
state,
|
||||||
|
renderer,
|
||||||
|
id,
|
||||||
|
constrain,
|
||||||
|
scale,
|
||||||
|
state.theme_settings.window_radius,
|
||||||
|
border_width,
|
||||||
|
border_color,
|
||||||
|
output,
|
||||||
|
&programs,
|
||||||
|
) {
|
||||||
|
elements.push(border.into());
|
||||||
|
}
|
||||||
|
elements.extend(rounded_window_elements(
|
||||||
|
renderer,
|
||||||
|
&window,
|
||||||
|
constrain,
|
||||||
|
scale,
|
||||||
|
state.theme_settings.window_radius,
|
||||||
|
output,
|
||||||
|
programs.texture,
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
elements.extend(constrain_space_element::<
|
||||||
|
GlesRenderer,
|
||||||
|
_,
|
||||||
|
AnimatedWindowRenderElement,
|
||||||
|
>(
|
||||||
|
renderer,
|
||||||
|
&window,
|
||||||
|
constrain.loc,
|
||||||
|
1.0,
|
||||||
|
scale,
|
||||||
|
constrain,
|
||||||
|
ConstrainBehavior {
|
||||||
|
reference: ConstrainReference::Geometry,
|
||||||
|
behavior: ConstrainScaleBehavior::Stretch,
|
||||||
|
align: ConstrainAlign::TOP | ConstrainAlign::LEFT,
|
||||||
|
},
|
||||||
|
));
|
||||||
}
|
}
|
||||||
elements.extend(constrain_space_element::<
|
|
||||||
GlesRenderer,
|
|
||||||
_,
|
|
||||||
AnimatedWindowRenderElement,
|
|
||||||
>(
|
|
||||||
renderer,
|
|
||||||
&window,
|
|
||||||
constrain.loc,
|
|
||||||
1.0,
|
|
||||||
scale,
|
|
||||||
constrain,
|
|
||||||
ConstrainBehavior {
|
|
||||||
reference: ConstrainReference::Geometry,
|
|
||||||
behavior: ConstrainScaleBehavior::Stretch,
|
|
||||||
align: ConstrainAlign::TOP | ConstrainAlign::LEFT,
|
|
||||||
},
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
elements.extend(layer_elements(
|
elements.extend(layer_elements(
|
||||||
renderer,
|
renderer,
|
||||||
@@ -327,86 +576,221 @@ fn output_elements(
|
|||||||
elements
|
elements
|
||||||
}
|
}
|
||||||
|
|
||||||
fn window_border_elements(
|
fn rounded_clip_program(
|
||||||
state: &mut Ferese,
|
state: &mut Ferese,
|
||||||
|
renderer: &mut GlesRenderer,
|
||||||
|
) -> Option<RoundedClipPrograms> {
|
||||||
|
let context = renderer.context_id().erased();
|
||||||
|
if let Some(program) = state.rounded_clip_programs.get(&context) {
|
||||||
|
return Some(program.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
let texture_uniforms = [
|
||||||
|
UniformName::new("clip_rect", UniformType::_4f),
|
||||||
|
UniformName::new("radius", UniformType::_1f),
|
||||||
|
];
|
||||||
|
let border_uniforms = [
|
||||||
|
UniformName::new("clip_rect", UniformType::_4f),
|
||||||
|
UniformName::new("radius", UniformType::_1f),
|
||||||
|
UniformName::new("border_width", UniformType::_1f),
|
||||||
|
UniformName::new("border_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);
|
||||||
|
match texture.and_then(|texture| border.map(|border| (texture, border))) {
|
||||||
|
Ok((texture, border)) => {
|
||||||
|
let programs = RoundedClipPrograms { texture, border };
|
||||||
|
state
|
||||||
|
.rounded_clip_programs
|
||||||
|
.insert(context, programs.clone());
|
||||||
|
Some(programs)
|
||||||
|
}
|
||||||
|
Err(error) => {
|
||||||
|
tracing::error!(%error, "failed to compile rounded-window shader");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
fn window_border_element(
|
||||||
|
state: &mut Ferese,
|
||||||
|
renderer: &GlesRenderer,
|
||||||
id: ferese_layout::WindowId,
|
id: ferese_layout::WindowId,
|
||||||
geometry: Rectangle<i32, Logical>,
|
geometry: Rectangle<i32, Logical>,
|
||||||
scale: f64,
|
scale: f64,
|
||||||
) -> Vec<AnimatedWindowRenderElement> {
|
requested_radius: f64,
|
||||||
let focused = state.focused_window == Some(id);
|
requested_width: f64,
|
||||||
let width = if focused {
|
color: [f32; 4],
|
||||||
state.theme_settings.focus_ring_width
|
output: &Output,
|
||||||
} else {
|
programs: &RoundedClipPrograms,
|
||||||
state.theme_settings.border_width
|
) -> Option<PixelShaderElement> {
|
||||||
};
|
let mode = output.current_mode()?;
|
||||||
let rectangles = border_rectangles(geometry, width);
|
let width = scaled_effect_value(requested_width, geometry, scale);
|
||||||
if rectangles.is_empty() {
|
if width == 0.0 {
|
||||||
return Vec::new();
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let color = if focused {
|
let parameters = BorderParameters {
|
||||||
state.theme_settings.accent_color.0
|
geometry,
|
||||||
} else {
|
clip_rect: framebuffer_clip_rect(
|
||||||
state.theme_settings.border_color.0
|
geometry.to_physical_precise_round(scale),
|
||||||
|
mode.size,
|
||||||
|
output.current_transform().invert(),
|
||||||
|
),
|
||||||
|
radius: scaled_effect_value(requested_radius, geometry, scale),
|
||||||
|
width,
|
||||||
|
color,
|
||||||
};
|
};
|
||||||
|
let context = renderer.context_id().erased();
|
||||||
let buffers = state.window_borders.entry(id).or_default();
|
let buffers = state.window_borders.entry(id).or_default();
|
||||||
|
if !buffers.contexts.contains_key(&context) {
|
||||||
|
let element = PixelShaderElement::new(
|
||||||
|
programs.border.clone(),
|
||||||
|
geometry,
|
||||||
|
None,
|
||||||
|
1.0,
|
||||||
|
border_uniforms(¶meters),
|
||||||
|
RenderElementKind::Unspecified,
|
||||||
|
);
|
||||||
|
buffers.contexts.insert(
|
||||||
|
context.clone(),
|
||||||
|
CachedBorder {
|
||||||
|
element,
|
||||||
|
parameters: parameters.clone(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
buffers
|
let cached = buffers.contexts.get_mut(&context)?;
|
||||||
.edges
|
if cached.parameters != parameters {
|
||||||
.iter_mut()
|
if cached.parameters.geometry != parameters.geometry {
|
||||||
.zip(rectangles)
|
cached.element.resize(parameters.geometry, None);
|
||||||
.map(|(buffer, rectangle)| {
|
}
|
||||||
buffer.update(rectangle.size, color);
|
cached.element.update_uniforms(border_uniforms(¶meters));
|
||||||
SolidColorRenderElement::from_buffer(
|
cached.parameters = parameters;
|
||||||
buffer,
|
}
|
||||||
rectangle.loc.to_physical_precise_round(scale),
|
|
||||||
|
Some(cached.element.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn border_uniforms(parameters: &BorderParameters) -> Vec<Uniform<'static>> {
|
||||||
|
vec![
|
||||||
|
Uniform::new("clip_rect", parameters.clip_rect).into_owned(),
|
||||||
|
Uniform::new("radius", parameters.radius).into_owned(),
|
||||||
|
Uniform::new("border_width", parameters.width).into_owned(),
|
||||||
|
Uniform::new("border_color", parameters.color).into_owned(),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
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)]
|
||||||
|
fn rounded_window_elements(
|
||||||
|
renderer: &mut GlesRenderer,
|
||||||
|
window: &smithay::desktop::Window,
|
||||||
|
constrain: Rectangle<i32, Logical>,
|
||||||
|
scale: f64,
|
||||||
|
requested_radius: f64,
|
||||||
|
output: &Output,
|
||||||
|
program: GlesTexProgram,
|
||||||
|
) -> Vec<AnimatedWindowRenderElement> {
|
||||||
|
let Some(toplevel) = window.toplevel() else {
|
||||||
|
return Vec::new();
|
||||||
|
};
|
||||||
|
let Some(mode) = output.current_mode() else {
|
||||||
|
return Vec::new();
|
||||||
|
};
|
||||||
|
|
||||||
|
let geometry = window.geometry();
|
||||||
|
let reference = geometry.to_physical_precise_round(scale);
|
||||||
|
let physical_constrain = constrain.to_physical_precise_round(scale);
|
||||||
|
let location = (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 surface = toplevel.wl_surface();
|
||||||
|
|
||||||
|
let mut content = PopupManager::popups_for_surface(surface)
|
||||||
|
.flat_map(|(popup, popup_offset)| {
|
||||||
|
let offset = (geometry.loc + popup_offset - popup.geometry().loc)
|
||||||
|
.to_physical_precise_round(scale);
|
||||||
|
|
||||||
|
render_elements_from_surface_tree::<
|
||||||
|
GlesRenderer,
|
||||||
|
WaylandSurfaceRenderElement<GlesRenderer>,
|
||||||
|
>(
|
||||||
|
renderer,
|
||||||
|
popup.wl_surface(),
|
||||||
|
location + offset,
|
||||||
scale,
|
scale,
|
||||||
1.0,
|
1.0,
|
||||||
RenderElementKind::Unspecified,
|
RenderElementKind::Unspecified,
|
||||||
)
|
)
|
||||||
.into()
|
.into_iter()
|
||||||
|
.map(WindowContentRenderElement::from)
|
||||||
})
|
})
|
||||||
.collect()
|
.collect::<Vec<_>>();
|
||||||
|
content.extend(
|
||||||
|
render_elements_from_surface_tree::<
|
||||||
|
GlesRenderer,
|
||||||
|
WaylandSurfaceRenderElement<GlesRenderer>,
|
||||||
|
>(
|
||||||
|
renderer,
|
||||||
|
surface,
|
||||||
|
location,
|
||||||
|
scale,
|
||||||
|
1.0,
|
||||||
|
RenderElementKind::Unspecified,
|
||||||
|
)
|
||||||
|
.into_iter()
|
||||||
|
.map(|inner| {
|
||||||
|
RoundedSurfaceRenderElement {
|
||||||
|
inner,
|
||||||
|
program: program.clone(),
|
||||||
|
clip_rect: clip,
|
||||||
|
radius,
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
constrain_render_elements(
|
||||||
|
content,
|
||||||
|
location,
|
||||||
|
physical_constrain,
|
||||||
|
reference,
|
||||||
|
ConstrainScaleBehavior::Stretch,
|
||||||
|
ConstrainAlign::TOP | ConstrainAlign::LEFT,
|
||||||
|
scale,
|
||||||
|
)
|
||||||
|
.map(Into::into)
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn border_rectangles(
|
fn framebuffer_clip_rect(
|
||||||
geometry: Rectangle<i32, Logical>,
|
geometry: Rectangle<i32, Physical>,
|
||||||
requested_width: f64,
|
output_size: smithay::utils::Size<i32, Physical>,
|
||||||
) -> Vec<Rectangle<i32, Logical>> {
|
transform: Transform,
|
||||||
let width = requested_width
|
) -> [f32; 4] {
|
||||||
.ceil()
|
let transformed = transform.transform_rect_in(geometry, &output_size);
|
||||||
.max(0.0)
|
let transformed_output_size = transform.transform_size(output_size);
|
||||||
.min(f64::from(geometry.size.w.min(geometry.size.h)) / 2.0) as i32;
|
let bottom = transformed_output_size.h - transformed.loc.y - transformed.size.h;
|
||||||
if width == 0 {
|
|
||||||
return Vec::new();
|
|
||||||
}
|
|
||||||
|
|
||||||
let inner_height = geometry.size.h - width * 2;
|
[
|
||||||
let mut rectangles = vec![
|
transformed.loc.x as f32,
|
||||||
Rectangle::new(geometry.loc, (geometry.size.w, width).into()),
|
bottom as f32,
|
||||||
Rectangle::new(
|
transformed.size.w as f32,
|
||||||
(geometry.loc.x, geometry.loc.y + geometry.size.h - width).into(),
|
transformed.size.h as f32,
|
||||||
(geometry.size.w, width).into(),
|
]
|
||||||
),
|
|
||||||
];
|
|
||||||
if inner_height > 0 {
|
|
||||||
rectangles.extend([
|
|
||||||
Rectangle::new(
|
|
||||||
(geometry.loc.x, geometry.loc.y + width).into(),
|
|
||||||
(width, inner_height).into(),
|
|
||||||
),
|
|
||||||
Rectangle::new(
|
|
||||||
(
|
|
||||||
geometry.loc.x + geometry.size.w - width,
|
|
||||||
geometry.loc.y + width,
|
|
||||||
)
|
|
||||||
.into(),
|
|
||||||
(width, inner_height).into(),
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
rectangles
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn redraw_output<R, E>(
|
pub(crate) fn redraw_output<R, E>(
|
||||||
@@ -573,9 +957,9 @@ fn normalized_scale(scale: f64) -> f64 {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use smithay::utils::{Logical, Rectangle};
|
use smithay::utils::{Physical, Rectangle, Transform};
|
||||||
|
|
||||||
use super::{border_rectangles, normalized_scale};
|
use super::{framebuffer_clip_rect, normalized_scale};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn accepts_positive_finite_scale() {
|
fn accepts_positive_finite_scale() {
|
||||||
@@ -590,30 +974,26 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn border_rectangles_stay_inside_window_geometry() {
|
fn clip_rect_uses_opengl_bottom_left_origin() {
|
||||||
let geometry = Rectangle::<i32, Logical>::new((10, 20).into(), (100, 80).into());
|
let geometry = Rectangle::<i32, Physical>::new((10, 5).into(), (30, 40).into());
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
border_rectangles(geometry, 2.0),
|
framebuffer_clip_rect(geometry, (100, 80).into(), Transform::Normal),
|
||||||
vec![
|
[10.0, 35.0, 30.0, 40.0]
|
||||||
Rectangle::new((10, 20).into(), (100, 2).into()),
|
|
||||||
Rectangle::new((10, 98).into(), (100, 2).into()),
|
|
||||||
Rectangle::new((10, 22).into(), (2, 76).into()),
|
|
||||||
Rectangle::new((108, 22).into(), (2, 76).into()),
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn border_width_is_clamped_for_tiny_windows() {
|
fn clip_rect_follows_output_transform() {
|
||||||
let geometry = Rectangle::<i32, Logical>::new((0, 0).into(), (3, 2).into());
|
let geometry = Rectangle::<i32, Physical>::new((10, 5).into(), (30, 40).into());
|
||||||
let rectangles = border_rectangles(geometry, 20.0);
|
|
||||||
|
|
||||||
assert_eq!(rectangles.len(), 2);
|
assert_eq!(
|
||||||
assert!(
|
framebuffer_clip_rect(geometry, (100, 80).into(), Transform::Flipped180),
|
||||||
rectangles
|
[10.0, 5.0, 30.0, 40.0]
|
||||||
.iter()
|
);
|
||||||
.all(|rectangle| geometry.contains_rect(*rectangle))
|
assert_eq!(
|
||||||
|
framebuffer_clip_rect(geometry, (100, 80).into(), Transform::_180),
|
||||||
|
[60.0, 5.0, 30.0, 40.0]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user