Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 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
54 changes: 54 additions & 0 deletions data/shaders/rounded-corners.vert
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// 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, length (delta));
}

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: GPL-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 ();
}
}
38 changes: 15 additions & 23 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 @@ -122,8 +124,9 @@ public class Gala.WindowClone : ActorTarget, RootTarget {
add_action (drag_action);
}

active_shape = new ActiveShape ();
active_shape.opacity = 0;
active_shape = new ActiveShape () {
opacity = 0
};

clone_container = new Clutter.Actor () {
pivot_point = { 0.5f, 0.5f }
Expand Down Expand Up @@ -687,33 +690,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)
};
}
}
}
81 changes: 31 additions & 50 deletions src/Widgets/WindowSwitcher/WindowSwitcherIcon.vala
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,48 @@
* 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);

queue_redraw ();
}
}

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,48 +55,14 @@ 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);

reactive = true;

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