Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions data/gala.gresource.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
<gresource prefix="/io/elementary/desktop/gala">
<file compressed="true">shaders/colorblindness-correction.vert</file>
<file compressed="true">shaders/monochrome.vert</file>
<file compressed="true">shaders/rounded-corners.vert</file>
</gresource>
<gresource prefix="/io/elementary/desktop/gala-daemon">
<file compressed="true">gala-daemon.css</file>
Expand Down
59 changes: 59 additions & 0 deletions data/shaders/rounded-corners.vert
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Copyright 2025 elementary, Inc. <https://elementary.io>
* SPDX-License-Identifier: LGPL-3.0-or-later
*/

// based on shader from mutter

uniform sampler2D tex;
uniform float clip_radius;
uniform vec2 actor_size;

float rounded_rect_coverage (vec2 p) {
float center_left = clip_radius + 1.5;
float center_right = actor_size.x - clip_radius - 0.55;
float center_x;

if (p.x < center_left)
center_x = center_left;
else if (p.x >= center_right)
center_x = center_right;
else
return 1.0;

float center_top = clip_radius + 1.5;
float center_bottom = actor_size.y - clip_radius - 0.55;
float center_y;

if (p.y < center_top)
center_y = center_top;
else if (p.y > center_bottom)
center_y = center_bottom;
else
return 1.0;

vec2 delta = p - vec2 (center_x, center_y);
float dist_squared = dot (delta, delta);

// Fully outside the circle
float outer_radius = clip_radius + 0.5;
if (dist_squared > (outer_radius * outer_radius))
return 0.0;

// Fully inside the circle
float inner_radius = clip_radius - 0.5;
if (dist_squared <= (inner_radius * inner_radius))
return 1.0;

// Only pixels on the edge of the curve need expensive antialiasing
return smoothstep (outer_radius, inner_radius, sqrt (dist_squared));
}

void main () {
vec4 sample = texture2D (tex, cogl_tex_coord0_in.xy);

vec2 texture_coord = cogl_tex_coord0_in.xy * actor_size;
float res = rounded_rect_coverage (texture_coord);

cogl_color_out = sample * cogl_color_in * res;
}
73 changes: 73 additions & 0 deletions lib/RoundedCornersEffect.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* Copyright 2025 elementary, Inc. <https://elementary.io>
* SPDX-License-Identifier: LGPL-3.0-or-later
*/

public class Gala.RoundedCornersEffect : Clutter.ShaderEffect {
private const int CLIP_RADIUS_OFFSET = 3;

public float clip_radius {
construct set {
set_uniform_value ("clip_radius", value + CLIP_RADIUS_OFFSET);
}
}

private float _monitor_scale = 1.0f;
public float monitor_scale {
get {
return _monitor_scale;
}
construct set {
_monitor_scale = value;

if (actor != null) {
update_actor_size ();
}
}
}

public RoundedCornersEffect (float clip_radius, float monitor_scale) {
Object (
#if HAS_MUTTER48
shader_type: Cogl.ShaderType.FRAGMENT,
#else
shader_type: Clutter.ShaderType.FRAGMENT_SHADER,
#endif
clip_radius: clip_radius,
monitor_scale: monitor_scale
);
}

construct {
try {
var bytes = GLib.resources_lookup_data ("/io/elementary/desktop/gala/shaders/rounded-corners.vert", GLib.ResourceLookupFlags.NONE);
set_shader_source ((string) bytes.get_data ());
} catch (Error e) {
critical ("Unable to load rounded-corners.vert: %s", e.message);
}
}

public override void set_actor (Clutter.Actor? actor) {
base.set_actor (actor);

if (actor == null) {
return;
}

actor.notify["width"].connect (update_actor_size);
actor.notify["height"].connect (update_actor_size);

update_actor_size ();
}

private void update_actor_size () requires (actor != null) {
float[] actor_size = {
actor.width * monitor_scale,
actor.height * monitor_scale
};

var actor_size_value = GLib.Value (typeof (Clutter.ShaderFloat));
Clutter.Value.set_shader_float (actor_size_value, actor_size);
set_uniform_value ("actor_size", actor_size_value);
}
}
1 change: 1 addition & 0 deletions lib/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ gala_lib_sources = files(
'Drawing/Utilities.vala',
'Image.vala',
'Plugin.vala',
'RoundedCornersEffect.vala',
'ShadowEffect.vala',
'Utils.vala',
'WindowIcon.vala',
Expand Down
32 changes: 9 additions & 23 deletions src/Widgets/MultitaskingView/Tooltip.vala
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
/**
* Clutter actor to display text in a tooltip-like component.
*/
public class Gala.Tooltip : CanvasActor {
public class Gala.Tooltip : Clutter.Actor {
/**
* Actor to display the Tooltip text.
*/
Expand Down Expand Up @@ -37,31 +37,17 @@ public class Gala.Tooltip : CanvasActor {
add_child (text_actor);

layout_manager = new Clutter.BinLayout ();
background_color = {
(uint8) (Drawing.Color.TOOLTIP_BACKGROUND.red * uint8.MAX),
(uint8) (Drawing.Color.TOOLTIP_BACKGROUND.green * uint8.MAX),
(uint8) (Drawing.Color.TOOLTIP_BACKGROUND.blue * uint8.MAX),
(uint8) (Drawing.Color.TOOLTIP_BACKGROUND.alpha * uint8.MAX)
};

add_effect (new RoundedCornersEffect (3, 1.0f));
}

public void set_text (string new_text) {
text_actor.text = new_text;
}

protected override void draw (Cairo.Context ctx, int width, int height) {
ctx.save ();
ctx.set_operator (Cairo.Operator.CLEAR);
ctx.paint ();
ctx.clip ();
ctx.reset_clip ();
ctx.set_operator (Cairo.Operator.OVER);

var background_color = Drawing.Color.TOOLTIP_BACKGROUND;
ctx.set_source_rgba (
background_color.red,
background_color.green,
background_color.blue,
background_color.alpha
);

Drawing.Utilities.cairo_rounded_rectangle (ctx, 0, 0, width, height, 4);
ctx.fill ();

ctx.restore ();
}
}
33 changes: 12 additions & 21 deletions src/Widgets/MultitaskingView/WindowClone.vala
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ public class Gala.WindowClone : ActorTarget, RootTarget {
*/
public bool active {
set {
active_shape.update_color ();

active_shape.save_easing_state ();
active_shape.set_easing_duration (Utils.get_animation_duration (FADE_ANIMATION_DURATION));
active_shape.opacity = value ? 255 : 0;
Expand Down Expand Up @@ -687,33 +689,22 @@ public class Gala.WindowClone : ActorTarget, RootTarget {
/**
* Border to show around the selected window when using keyboard navigation.
*/
private class ActiveShape : CanvasActor {
private class ActiveShape : Clutter.Actor {
private const int BORDER_RADIUS = 16;
private const double COLOR_OPACITY = 0.8;

construct {
notify["opacity"].connect (invalidate);
}

public void invalidate () {
content.invalidate ();
add_effect (new RoundedCornersEffect (BORDER_RADIUS, 1.0f));
}

protected override void draw (Cairo.Context cr, int width, int height) {
if (!visible || opacity == 0) {
return;
}

var color = Drawing.StyleManager.get_instance ().theme_accent_color;

cr.save ();
cr.set_operator (Cairo.Operator.CLEAR);
cr.paint ();
cr.restore ();

Drawing.Utilities.cairo_rounded_rectangle (cr, 0, 0, width, height, BORDER_RADIUS);
cr.set_source_rgba (color.red, color.green, color.blue, COLOR_OPACITY);
cr.fill ();
public void update_color () {
var accent_color = Drawing.StyleManager.get_instance ().theme_accent_color;
background_color = {
(uint8) (accent_color.red * uint8.MAX),
(uint8) (accent_color.green * uint8.MAX),
(uint8) (accent_color.blue * uint8.MAX),
(uint8) (COLOR_OPACITY * uint8.MAX)
};
}
}
}
78 changes: 29 additions & 49 deletions src/Widgets/WindowSwitcher/WindowSwitcherIcon.vala
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,46 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/

public class Gala.WindowSwitcherIcon : CanvasActor {
public class Gala.WindowSwitcherIcon : Clutter.Actor {
private const int WRAPPER_BORDER_RADIUS = 3;

public Meta.Window window { get; construct; }

private WindowIcon icon;
private RoundedCornersEffect rounded_corners_effect;

private bool _selected = false;
public bool selected {
get {
return _selected;
}
set {
_selected = value;
content.invalidate ();
if (value) {
var accent_color = Drawing.StyleManager.get_instance ().theme_accent_color;
background_color = {
(uint8) (accent_color.red * uint8.MAX),
(uint8) (accent_color.green * uint8.MAX),
(uint8) (accent_color.blue * uint8.MAX),
(uint8) (accent_color.alpha * uint8.MAX)
};
} else {
#if HAS_MUTTER47
background_color = Cogl.Color.from_4f (0, 0, 0, 0);
#else
background_color = Clutter.Color.alloc ();
#endif
}

get_accessible ().notify_state_change (Atk.StateType.SELECTED, value);
get_accessible ().notify_state_change (Atk.StateType.FOCUSED, value);
}
}

private float _scale_factor = 1.0f;
public float scale_factor {
get {
return _scale_factor;
}
set {
_scale_factor = value;
var indicator_size = Utils.scale_to_int (
(WindowSwitcher.ICON_SIZE + WindowSwitcher.WRAPPER_PADDING * 2),
value
);
set_size (indicator_size, indicator_size);

update_size ();
rounded_corners_effect.monitor_scale = value;
}
}

Expand All @@ -40,6 +53,9 @@ public class Gala.WindowSwitcherIcon : CanvasActor {
icon.add_constraint (new Clutter.AlignConstraint (this, Clutter.AlignAxis.BOTH, 0.5f));
add_child (icon);

rounded_corners_effect = new RoundedCornersEffect (WRAPPER_BORDER_RADIUS, scale_factor);
add_effect (rounded_corners_effect);

get_accessible ().accessible_name = window.title;
get_accessible ().accessible_role = LIST_ITEM;
get_accessible ().notify_state_change (Atk.StateType.FOCUSABLE, true);
Expand All @@ -48,40 +64,4 @@ public class Gala.WindowSwitcherIcon : CanvasActor {

this.scale_factor = scale_factor;
}

private void update_size () {
var indicator_size = Utils.scale_to_int (
(WindowSwitcher.ICON_SIZE + WindowSwitcher.WRAPPER_PADDING * 2),
scale_factor
);
set_size (indicator_size, indicator_size);
}

protected override void draw (Cairo.Context ctx, int width, int height) {
ctx.save ();
ctx.set_operator (Cairo.Operator.CLEAR);
ctx.paint ();
ctx.clip ();
ctx.reset_clip ();

if (selected) {
// draw rect
var rgba = Drawing.StyleManager.get_instance ().theme_accent_color;
ctx.set_source_rgba (
rgba.red,
rgba.green,
rgba.blue,
rgba.alpha
);
var rect_radius = Utils.scale_to_int (WRAPPER_BORDER_RADIUS, scale_factor);
Drawing.Utilities.cairo_rounded_rectangle (ctx, 0, 0, width, height, rect_radius);
ctx.set_operator (Cairo.Operator.SOURCE);
ctx.fill ();

ctx.restore ();
}

get_accessible ().notify_state_change (Atk.StateType.SELECTED, selected);
get_accessible ().notify_state_change (Atk.StateType.FOCUSED, selected);
}
}