33 * SPDX-License-Identifier: LGPL-3.0-or-later
44 */
55
6- public class Gala.RoundedCornersEffect : Clutter .ShaderEffect {
7- private const int CLIP_RADIUS_OFFSET = 3 ;
6+ public class Gala.RoundedCornersEffect : Clutter .Effect {
7+ public float clip_radius { get ; construct; }
8+ public float monitor_scale { get ; construct set ; }
89
9- public float clip_radius {
10- construct set {
11- set_uniform_value ("clip_radius ", value + CLIP_RADIUS_OFFSET );
12- }
13- }
14-
15- private float _monitor_scale = 1.0f ;
16- public float monitor_scale {
17- get {
18- return _monitor_scale;
19- }
20- construct set {
21- _monitor_scale = value ;
10+ private Cogl . Framebuffer round_framebuffer;
11+ private Cogl . Pipeline round_pipeline;
12+ private Cogl . Texture round_texture;
13+ private int round_clip_radius_location;
14+ private int round_actor_size_location;
2215
23- if (actor != null ) {
24- update_actor_size ();
25- }
26- }
16+ public RoundedCornersEffect (float clip_radius, float monitor_scale) {
17+ Object (clip_radius: clip_radius, monitor_scale: monitor_scale);
2718 }
2819
29- public RoundedCornersEffect (float clip_radius , float monitor_scale ) {
30- Object (
31- #if HAS_MUTTER48
32- shader_type: Cogl . ShaderType . FRAGMENT ,
20+ construct {
21+ #if HAS_MUTTER47
22+ unowned var ctx = actor. context. get_backend (). get_cogl_context ();
3323#else
34- shader_type : Clutter . ShaderType . FRAGMENT_SHADER ,
24+ unowned var ctx = Clutter . get_default_backend () . get_cogl_context ();
3525#endif
36- clip_radius: clip_radius,
37- monitor_scale: monitor_scale
26+
27+ round_pipeline = new Cogl .Pipeline (ctx);
28+ round_pipeline. set_layer_null_texture (0 );
29+ round_pipeline. set_layer_filters (0 , Cogl . PipelineFilter . LINEAR , Cogl . PipelineFilter . LINEAR );
30+ round_pipeline. set_layer_wrap_mode (0 , Cogl . PipelineWrapMode . CLAMP_TO_EDGE );
31+ round_pipeline. add_snippet (
32+ new Cogl .Snippet (
33+ Cogl . SnippetHook . FRAGMENT ,
34+ " " "
35+ uniform sampler2D tex;
36+ uniform vec2 actor_size;
37+ uniform float clip_radius;
38+
39+ float rounded_rect_coverage (vec2 p) {
40+ float center_left = clip_radius;
41+ float center_right = actor_size.x - clip_radius;
42+
43+ float center_x;
44+ if (p.x < center_left) {
45+ center_x = center_left;
46+ } else if (p.x > center_right) {
47+ center_x = center_right;
48+ } else {
49+ return 1.0;
50+ }
51+
52+ float center_top = clip_radius;
53+ float center_bottom = actor_size.y - clip_radius;
54+
55+ float center_y;
56+ if (p.y < center_top) {
57+ center_y = center_top;
58+ } else if (p.y > center_bottom) {
59+ center_y = center_bottom;
60+ } else {
61+ return 1.0;
62+ }
63+
64+ vec2 delta = p - vec2 (center_x, center_y);
65+ float dist_squared = dot (delta, delta);
66+
67+ // Fully outside the circle
68+ float outer_radius = clip_radius + 0.5;
69+ if (dist_squared >= (outer_radius * outer_radius)) {
70+ return 0.0;
71+ }
72+
73+ // Fully inside the circle
74+ float inner_radius = clip_radius - 0.5;
75+ if (dist_squared <= (inner_radius * inner_radius)) {
76+ return 1.0;
77+ }
78+ // Only pixels on the edge of the curve need expensive antialiasing
79+ return smoothstep (outer_radius, inner_radius, sqrt (dist_squared));
80+ }
81+ " " " ,
82+
83+ " " "
84+ vec4 sample = texture2D (tex, cogl_tex_coord0_in.xy);
85+ vec2 texture_coord = cogl_tex_coord0_in.xy * actor_size;
86+ float res = rounded_rect_coverage (texture_coord);
87+ cogl_color_out = sample * cogl_color_in * res;
88+ " " "
89+ )
3890 );
39- }
4091
41- construct {
42- try {
43- var bytes = GLib . resources_lookup_data ( " /io/elementary/desktop/gala/shaders/rounded-corners.vert " , GLib . ResourceLookupFlags . NONE );
44- set_shader_source (( string ) bytes . get_data () );
45- } catch ( Error e) {
46- critical ( " Unable to load rounded-corners.vert: %s " , e . message);
47- }
92+ round_clip_radius_location = round_pipeline . get_uniform_location ( " clip_radius " );
93+ round_actor_size_location = round_pipeline . get_uniform_location ( " actor_size " );
94+
95+ update_clip_radius ( );
96+ update_actor_size ();
97+
98+ notify[ " monitor-scale " ] . connect (update_clip_radius);
4899 }
49100
50101 public override void set_actor (Clutter . Actor ? new_actor) {
@@ -58,19 +109,102 @@ public class Gala.RoundedCornersEffect : Clutter.ShaderEffect {
58109 if (actor != null ) {
59110 actor. notify[" width" ]. connect (update_actor_size);
60111 actor. notify[" height" ]. connect (update_actor_size);
61-
62112 update_actor_size ();
63113 }
64114 }
65115
66- private void update_actor_size () requires (actor != null ) {
116+ private void update_clip_radius () {
117+ float [] _clip_radius = { clip_radius * monitor_scale };
118+ round_pipeline. set_uniform_float (round_clip_radius_location, 1 , 1 , _clip_radius);
119+ }
120+
121+ private void update_actor_size () {
122+ if (actor == null ) {
123+ return ;
124+ }
125+
67126 float [] actor_size = {
68- actor. width * monitor_scale ,
69- actor. height * monitor_scale
127+ actor. width,
128+ actor. height
70129 };
71130
72- var actor_size_value = GLib . Value (typeof (Clutter . ShaderFloat ));
73- Clutter . Value . set_shader_float (actor_size_value, actor_size);
74- set_uniform_value (" actor_size" , actor_size_value);
131+ round_pipeline. set_uniform_float (round_actor_size_location, 2 , 1 , actor_size);
132+ }
133+
134+ private void setup_projection_matrix (Cogl . Framebuffer framebuffer, float width, float height) {
135+ Graphene . Matrix projection = {};
136+ projection. init_translate ({ - width / 2.0f , - height / 2.0f , 0.0f });
137+ projection. scale (2.0f / width, - 2.0f / height, 1.0f );
138+
139+ framebuffer. set_projection_matrix (projection);
140+ }
141+
142+ private bool update_rounded_fbo (int width, int height) {
143+ #if HAS_MUTTER47
144+ unowned var ctx = actor. context. get_backend (). get_cogl_context ();
145+ #else
146+ unowned var ctx = Clutter . get_default_backend (). get_cogl_context ();
147+ #endif
148+
149+ #if HAS_MUTTER46
150+ round_texture = new Cogl .Texture2D .with_size (ctx, width, height);
151+ #else
152+ var surface = new Cairo .ImageSurface (Cairo . Format . ARGB32 , width, height);
153+ try {
154+ round_texture = new Cogl .Texture2D .from_data (ctx, width, height, Cogl . PixelFormat . BGRA_8888_PRE , surface. get_stride (), surface. get_data ());
155+ } catch (Error e) {
156+ critical (" BackgroundBlurEffect: Couldn't create round_texture: %s " , e. message);
157+ return false ;
158+ }
159+ #endif
160+
161+ round_pipeline. set_layer_texture (0 , round_texture);
162+ round_framebuffer = new Cogl .Offscreen .with_texture (round_texture);
163+
164+ setup_projection_matrix (round_framebuffer, width, height);
165+
166+ return true ;
167+ }
168+
169+ private bool update_framebuffers (Clutter . PaintContext paint_context) {
170+ if (actor == null ) {
171+ return false ;
172+ }
173+
174+ var width = (int ) actor. width;
175+ var height = (int ) actor. height;
176+
177+ if (width <= 0 || height <= 0 ) {
178+ warning (" RoundedCornersEffect: Couldn't update framebuffers, incorrect size" );
179+ return false ;
180+ }
181+
182+ return update_rounded_fbo (width, height);
183+ }
184+
185+ private Clutter . PaintNode create_round_nodes (Clutter . PaintNode node) {
186+ var round_node = new Clutter .LayerNode .to_framebuffer (round_framebuffer, round_pipeline);
187+ round_node. add_rectangle ({ 0.0f , 0.0f , actor. width, actor. height });
188+
189+ node. add_child (round_node);
190+
191+ return round_node;
192+ }
193+
194+ private void add_actor_node (Clutter . PaintNode node) {
195+ var actor_node = new Clutter .ActorNode (actor, - 1 );
196+ node. add_child (actor_node);
197+ }
198+
199+ public override void paint_node (Clutter . PaintNode node, Clutter . PaintContext paint_context, Clutter . EffectPaintFlags flags) {
200+ /* Failing to create or update the offscreen framebuffers prevents
201+ * the entire effect to be applied.
202+ */
203+ if (! update_framebuffers (paint_context)) {
204+ add_actor_node (node);
205+ return ;
206+ }
207+
208+ add_actor_node (create_round_nodes (node));
75209 }
76210}
0 commit comments