diff --git a/data/gala.gresource.xml b/data/gala.gresource.xml
index 7713468a1..424b4f10c 100644
--- a/data/gala.gresource.xml
+++ b/data/gala.gresource.xml
@@ -21,6 +21,7 @@
resize.svg
+ shaders/box-blur.frag
shaders/colorblindness-correction.frag
shaders/monochrome.frag
shaders/rounded-corners.frag
diff --git a/data/shaders/box-blur.frag b/data/shaders/box-blur.frag
new file mode 100644
index 000000000..474759d13
--- /dev/null
+++ b/data/shaders/box-blur.frag
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2026 elementary, Inc.
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+uniform sampler2D tex;
+uniform int RADIUS;
+uniform vec2 PIXEL_STEP;
+uniform vec2 DIRECTION;
+
+void main() {
+ if (RADIUS == 0) {
+ cogl_color_out = texture2D(tex, cogl_tex_coord0_in.xy);
+ return;
+ }
+
+ vec4 sum = vec4(0, 0, 0, 0);
+ int count = 0;
+
+ for (int i = -RADIUS; i <= RADIUS; i++) {
+ vec2 offset = DIRECTION * PIXEL_STEP * i;
+
+ sum += texture2D(tex, cogl_tex_coord0_in.xy + offset);
+ count += 1;
+ }
+
+ cogl_color_out = sum / count;
+}
diff --git a/lib/Drawing/Color.vala b/lib/Drawing/Color.vala
index 1733c6d13..ad8b2ba70 100644
--- a/lib/Drawing/Color.vala
+++ b/lib/Drawing/Color.vala
@@ -1,5 +1,5 @@
/*
- * Copyright 2019-2025 elementary, Inc. (https://elementary.io)
+ * Copyright 2019-2026 elementary, Inc. (https://elementary.io)
* Copyright 2011–2013 Robert Dyer
* SPDX-License-Identifier: LGPL-3.0-or-later
*/
@@ -16,8 +16,9 @@ namespace Gala.Drawing {
public const Clutter.Color DARK_BORDER = { 0, 0, 0, 191};
public const Clutter.Color LIGHT_HIGHLIGHT = { 255, 255, 255, 255};
public const Clutter.Color DARK_HIGHLIGHT = { 255, 255, 255, 13};
- public const Clutter.Color TOOLTIP_BACKGROUND = { 0, 0, 0, 255};
+ public const Clutter.Color TOOLTIP_BACKGROUND = { 26, 26, 26, 230};
public const Clutter.Color TOOLTIP_TEXT_COLOR = { 255, 255, 255, 255};
+ public const Clutter.Color TOOLTIP_TEXT_SHADOW_COLOR = { 0, 0, 0, 153};
#else
public const Cogl.Color LIGHT_BACKGROUND = { 250, 250, 250, 255};
public const Cogl.Color DARK_BACKGROUND = { 51, 51, 51, 255};
@@ -25,8 +26,9 @@ namespace Gala.Drawing {
public const Cogl.Color DARK_BORDER = { 0, 0, 0, 191};
public const Cogl.Color LIGHT_HIGHLIGHT = { 255, 255, 255, 255};
public const Cogl.Color DARK_HIGHLIGHT = { 255, 255, 255, 13};
- public const Cogl.Color TOOLTIP_BACKGROUND = { 0, 0, 0, 255};
+ public const Cogl.Color TOOLTIP_BACKGROUND = { 26, 26, 26, 230};
public const Cogl.Color TOOLTIP_TEXT_COLOR = { 255, 255, 255, 255};
+ public const Cogl.Color TOOLTIP_TEXT_SHADOW_COLOR = { 0, 0, 0, 153};
#endif
/**
diff --git a/lib/Effects/BoxBlurManager.vala b/lib/Effects/BoxBlurManager.vala
new file mode 100644
index 000000000..a8e1e65ec
--- /dev/null
+++ b/lib/Effects/BoxBlurManager.vala
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2026 elementary, Inc.
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+public class Gala.BoxBlurManager : Object {
+ private int _radius = 0;
+ public int radius {
+ get {
+ return _radius;
+ }
+ set {
+ _radius = value;
+ horizontal_effect.radius = value;
+ vertical_effect.radius = value;
+ }
+ }
+
+ private BoxBlurEffect horizontal_effect;
+ private BoxBlurEffect vertical_effect;
+
+ public BoxBlurManager (Clutter.Actor actor) {
+ horizontal_effect = new BoxBlurEffect (HORIZONTAL);
+ vertical_effect = new BoxBlurEffect (VERTICAL);
+
+ actor.add_effect (horizontal_effect);
+ actor.add_effect (vertical_effect);
+ }
+
+ private class BoxBlurEffect : Clutter.ShaderEffect {
+ public enum PassDirection {
+ HORIZONTAL,
+ VERTICAL;
+ }
+
+ private const float[] HORIZONTAL_PASS_DATA = { 1.0f, 0.0f };
+ private const float[] VERTICAL_PASS_DATA = { 0.0f, 1.0f };
+
+ public int radius { set { set_uniform_value ("RADIUS", value); } }
+
+ public BoxBlurEffect (PassDirection direction) {
+ Object (
+ #if HAS_MUTTER48
+ shader_type: Cogl.ShaderType.FRAGMENT
+ #else
+ shader_type: Clutter.ShaderType.FRAGMENT_SHADER
+ #endif
+ );
+
+ try {
+ var bytes = GLib.resources_lookup_data ("/io/elementary/desktop/gala/shaders/box-blur.frag", NONE);
+ set_shader_source ((string) bytes.get_data ());
+ } catch (Error e) {
+ critical ("Unable to load box-blur.frag: %s", e.message);
+ }
+
+ radius = 0;
+
+ var direction_value = GLib.Value (typeof (Clutter.ShaderFloat));
+ var direction_data = direction == HORIZONTAL ? HORIZONTAL_PASS_DATA : VERTICAL_PASS_DATA;
+ Clutter.Value.set_shader_float (direction_value, direction_data);
+
+ set_uniform_value ("DIRECTION", direction_value);
+ }
+
+ public override void set_actor (Clutter.Actor? new_actor) {
+ if (actor != null) {
+ actor.notify["width"].disconnect (update_pixel_step);
+ actor.notify["height"].disconnect (update_pixel_step);
+ }
+
+ base.set_actor (new_actor);
+
+ if (actor != null) {
+ actor.notify["width"].connect (update_pixel_step);
+ actor.notify["height"].connect (update_pixel_step);
+ update_pixel_step ();
+ }
+ }
+
+ private void update_pixel_step () {
+ var pixel_step_value = GLib.Value (typeof (Clutter.ShaderFloat));
+ Clutter.Value.set_shader_float (pixel_step_value, { 1 / actor.width, 1 / actor.height });
+
+ set_uniform_value ("PIXEL_STEP", pixel_step_value);
+ }
+ }
+}
diff --git a/lib/Widgets/Text.vala b/lib/Widgets/Text.vala
index 7a5bd37de..492fdc19e 100644
--- a/lib/Widgets/Text.vala
+++ b/lib/Widgets/Text.vala
@@ -1,13 +1,13 @@
/*
* SPDX-License-Identifier: LGPL-3.0-or-later
- * SPDX-FileCopyrightText: 2025 elementary, Inc. (https://elementary.io)
+ * SPDX-FileCopyrightText: 2025-2026 elementary, Inc. (https://elementary.io)
*/
/*
- * Clutter.Text that automatically changes font-name to the system one
+ * Clutter.Text that automatically changes font-name to the system one and supports text shadow
*/
public class Gala.Text : Clutter.Actor {
- private static GLib.Settings gnome_interface_settings;
+ private static GLib.Settings gnome_interface_settings = new GLib.Settings ("org.gnome.desktop.interface");
#if HAS_MUTTER47
public Cogl.Color color { get { return text_actor.color; } set { text_actor.color = value; } }
@@ -15,17 +15,34 @@ public class Gala.Text : Clutter.Actor {
public Clutter.Color color { get { return text_actor.color; } set { text_actor.color = value; } }
#endif
public Pango.EllipsizeMode ellipsize { get { return text_actor.ellipsize; } set { text_actor.ellipsize = value; } }
- public Pango.Alignment line_alignment {
- get { return text_actor.line_alignment; } set { text_actor.line_alignment = value; }
- }
+ public Pango.Alignment line_alignment { get { return text_actor.line_alignment; } set { text_actor.line_alignment = value; }}
public string text { get { return text_actor.text; } set { text_actor.text = value; } }
- private Clutter.Text text_actor;
+#if HAS_MUTTER47
+ public Cogl.Color shadow_color {
+#else
+ public Clutter.Color shadow_color {
+#endif
+ get { return shadow_actor.color; }
+ set {
+ shadow_actor.color = value;
- static construct {
- gnome_interface_settings = new GLib.Settings ("org.gnome.desktop.interface");
+ if (shadow_actor.color.alpha != 0 && shadow_actor.get_parent () == null) {
+ insert_child_below (shadow_actor, null);
+ } else if (shadow_actor.color.alpha == 0 && shadow_actor.get_parent () == this) {
+ remove_child (shadow_actor);
+ }
+ }
}
+ public float shadow_offset_x { get { return shadow_actor.translation_x; } set { shadow_actor.translation_x = value; } }
+ public float shadow_offset_y { get { return shadow_actor.translation_y; } set { shadow_actor.translation_y = value; } }
+ public int shadow_blur_radius { get { return box_blur_manager.radius; } set { box_blur_manager.radius = value; } }
+
+ private Clutter.Text text_actor;
+ private Clutter.Text shadow_actor;
+ private BoxBlurManager box_blur_manager;
+
class construct {
set_layout_manager_type (typeof (Clutter.BinLayout));
}
@@ -34,6 +51,14 @@ public class Gala.Text : Clutter.Actor {
text_actor = new Clutter.Text ();
add_child (text_actor);
+ shadow_actor = new Clutter.Text ();
+ box_blur_manager = new BoxBlurManager (shadow_actor);
+
+ text_actor.bind_property ("ellipsize", shadow_actor, "ellipsize");
+ text_actor.bind_property ("line-alignment", shadow_actor, "line-alignment");
+ text_actor.bind_property ("text", shadow_actor, "text");
+ text_actor.bind_property ("font-name", shadow_actor, "font-name");
+
set_system_font_name ();
gnome_interface_settings.changed["font-name"].connect (set_system_font_name);
}
diff --git a/lib/meson.build b/lib/meson.build
index ee073287e..b6a34f261 100644
--- a/lib/meson.build
+++ b/lib/meson.build
@@ -19,6 +19,7 @@ gala_lib_sources = files(
'Drawing/StyleManager.vala',
'Drawing/Utilities.vala',
'Effects/BackgroundBlurEffect.vala',
+ 'Effects/BoxBlurManager.vala',
'Effects/RoundedCornersEffect.vala',
'Effects/ShadowEffect.vala',
'Gestures/Backends/GestureBackend.vala',
diff --git a/src/Widgets/MultitaskingView/Tooltip.vala b/src/Widgets/MultitaskingView/Tooltip.vala
index 2dec8ed57..bd13fcd94 100644
--- a/src/Widgets/MultitaskingView/Tooltip.vala
+++ b/src/Widgets/MultitaskingView/Tooltip.vala
@@ -10,6 +10,8 @@
public class Gala.Tooltip : Clutter.Actor {
private const int TEXT_MARGIN = 6;
private const int CORNER_RADIUS = 3;
+ private const int TEXT_SHADOW_Y_OFFSET = 1;
+ private const int TEXT_SHADOW_BLUR_RADIUS = 2;
public float monitor_scale { get; construct set; }
@@ -22,7 +24,10 @@ public class Gala.Tooltip : Clutter.Actor {
construct {
text_actor = new Gala.Text () {
ellipsize = Pango.EllipsizeMode.MIDDLE,
- color = Drawing.Color.TOOLTIP_TEXT_COLOR
+ color = Drawing.Color.TOOLTIP_TEXT_COLOR,
+ shadow_color = Drawing.Color.TOOLTIP_TEXT_SHADOW_COLOR,
+ shadow_offset_y = TEXT_SHADOW_Y_OFFSET,
+ shadow_blur_radius = TEXT_SHADOW_BLUR_RADIUS
};
bind_property ("monitor-scale", text_actor, "margin-left", SYNC_CREATE, transform_monitor_scale_to_margin);
bind_property ("monitor-scale", text_actor, "margin-top", SYNC_CREATE, transform_monitor_scale_to_margin);