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);