Skip to content

Commit eab3cb7

Browse files
authored
Merge branch 'main' into release-8.3.1
2 parents 3d6151b + 0e9dce3 commit eab3cb7

28 files changed

+269
-246
lines changed

.github/workflows/gettext.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
apt-get install git -y
1818
1919
- name: Clone repository
20-
uses: actions/checkout@v5
20+
uses: actions/checkout@v6
2121
with:
2222
token: ${{ secrets.GIT_USER_TOKEN }}
2323

.github/workflows/main.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ jobs:
2727
image: ghcr.io/elementary/docker:${{ matrix.version }}
2828

2929
steps:
30-
- uses: actions/checkout@v5
30+
- uses: actions/checkout@v6
3131
- name: Install Dependencies
3232
run: |
3333
apt update
@@ -50,7 +50,7 @@ jobs:
5050
image: fedora:latest
5151

5252
steps:
53-
- uses: actions/checkout@v5
53+
- uses: actions/checkout@v6
5454
- name: Install Dependencies
5555
run: |
5656
dnf install -y desktop-file-utils gettext gsettings-desktop-schemas-devel atk-devel clutter-devel libgee-devel glib2-devel gnome-desktop3-devel granite-devel granite-7-devel gtk3-devel gtk4-devel libhandy-devel mutter-devel sqlite-devel meson valac valadoc
@@ -73,7 +73,7 @@ jobs:
7373
zypper addrepo https://download.opensuse.org/repositories/X11:Pantheon/15.6/X11:Pantheon.repo
7474
zypper --gpg-auto-import-keys refresh
7575
zypper --non-interactive install tar git desktop-file-utils gsettings-desktop-schemas-devel libatk-1_0-0 clutter-devel libgee-devel glib2-devel libgnome-desktop-4-devel granite6-devel granite-devel gtk3-devel gtk4-devel libhandy-devel mutter-devel sqlite3-devel meson vala valadoc gcc
76-
- uses: actions/checkout@v5
76+
- uses: actions/checkout@v6
7777
- name: Build
7878
env:
7979
DESTDIR: out
@@ -88,7 +88,7 @@ jobs:
8888
image: valalang/lint
8989

9090
steps:
91-
- uses: actions/checkout@v5
91+
- uses: actions/checkout@v6
9292
- name: Lint
9393
run: |
9494
io.elementary.vala-lint -d daemon

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ jobs:
1010
runs-on: ubuntu-22.04
1111
if: github.event.pull_request.merged == true && true == contains(join(github.event.pull_request.labels.*.name), 'Release')
1212
steps:
13-
- uses: actions/checkout@v5
13+
- uses: actions/checkout@v6
1414
- uses: elementary/actions/release@main
1515
env:
1616
GIT_USER_TOKEN: "${{ secrets.GIT_USER_TOKEN }}"

lib/CloseButton.vala

Lines changed: 11 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@ public class Gala.CloseButton : Clutter.Actor {
1111

1212
public float monitor_scale { get; construct set; }
1313

14-
// used to avoid changing hitbox of the button
15-
private Clutter.Actor pixbuf_actor;
14+
private Icon icon;
1615
private Clutter.ClickAction click_action;
1716

1817
static construct {
@@ -26,18 +25,18 @@ public class Gala.CloseButton : Clutter.Actor {
2625
construct {
2726
reactive = true;
2827

29-
pixbuf_actor = new Clutter.Actor () {
28+
icon = new Icon.from_resource (
29+
Utils.BUTTON_SIZE, monitor_scale,
30+
"/org/pantheon/desktop/gala/buttons/close.svg"
31+
) {
3032
pivot_point = { 0.5f, 0.5f }
3133
};
32-
add_child (pixbuf_actor);
34+
add_child (icon);
3335

3436
click_action = new Clutter.ClickAction ();
3537
add_action (click_action);
3638
click_action.clicked.connect (on_clicked);
3739
click_action.notify["pressed"].connect (on_pressed_changed);
38-
39-
load_pixbuf ();
40-
notify["monitor-scale"].connect (load_pixbuf);
4140
}
4241

4342
private void on_clicked () {
@@ -48,53 +47,10 @@ public class Gala.CloseButton : Clutter.Actor {
4847
var estimated_duration = Utils.get_animation_duration ((uint) (ANIMATION_DURATION * (scale_x - 0.8) / 0.2));
4948
var scale = click_action.pressed ? 0.8 : 1.0;
5049

51-
pixbuf_actor.save_easing_state ();
52-
pixbuf_actor.set_easing_duration (estimated_duration);
53-
pixbuf_actor.set_easing_mode (Clutter.AnimationMode.EASE_IN_OUT);
54-
pixbuf_actor.set_scale (scale, scale);
55-
pixbuf_actor.restore_easing_state ();
56-
}
57-
58-
private void load_pixbuf () {
59-
var pixbuf = get_close_button_pixbuf (monitor_scale);
60-
if (pixbuf != null) {
61-
var image = new Gala.Image.from_pixbuf (pixbuf);
62-
pixbuf_actor.set_content (image);
63-
pixbuf_actor.set_size (pixbuf.width, pixbuf.height);
64-
set_size (pixbuf.width, pixbuf.height);
65-
} else {
66-
create_error_texture ();
67-
}
68-
}
69-
70-
private static Gdk.Pixbuf? get_close_button_pixbuf (float monitor_scale) {
71-
var height = Utils.calculate_button_size (monitor_scale);
72-
73-
if (close_pixbufs[height] == null) {
74-
try {
75-
close_pixbufs[height] = new Gdk.Pixbuf.from_resource_at_scale (
76-
"/org/pantheon/desktop/gala/buttons/close.svg",
77-
-1,
78-
height,
79-
true
80-
);
81-
} catch (Error e) {
82-
critical (e.message);
83-
return null;
84-
}
85-
}
86-
87-
return close_pixbufs[height];
88-
}
89-
90-
private void create_error_texture () {
91-
// we'll just make this red so there's at least something as an
92-
// indicator that loading failed. Should never happen and this
93-
// works as good as some weird fallback-image-failed-to-load pixbuf
94-
critical ("Could not create close button");
95-
96-
var size = Utils.calculate_button_size (monitor_scale);
97-
pixbuf_actor.set_size (size, size);
98-
pixbuf_actor.background_color = { 255, 0, 0, 255 };
50+
icon.save_easing_state ();
51+
icon.set_easing_duration (estimated_duration);
52+
icon.set_easing_mode (Clutter.AnimationMode.EASE_IN_OUT);
53+
icon.set_scale (scale, scale);
54+
icon.restore_easing_state ();
9955
}
10056
}

lib/Icon.vala

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
/*
2+
* Copyright 2025 elementary, Inc. (https://elementary.io)
3+
* SPDX-License-Identifier: GPL-3.0-or-later
4+
*
5+
* Authored by: Leonhard Kargl <[email protected]>
6+
*/
7+
8+
public class Gala.Icon : Clutter.Actor {
9+
private class GIconSource : Object, IconSource {
10+
private static Gtk.IconTheme icon_theme = new Gtk.IconTheme () {
11+
theme_name = "elementary"
12+
};
13+
14+
public GLib.Icon gicon { get; construct; }
15+
16+
public GIconSource (GLib.Icon gicon) {
17+
Object (gicon: gicon);
18+
}
19+
20+
public string? get_cache_key (int size, float scale) {
21+
var gicon_str = gicon.to_string ();
22+
return gicon_str != null ? "gicon-%s-%d-%f".printf (gicon_str, size, scale) : null;
23+
}
24+
25+
public Gdk.Pixbuf create_pixbuf (int size, float scale) throws Error {
26+
var icon_paintable = icon_theme.lookup_by_gicon (gicon, size, (int) Math.ceilf (scale), NONE, 0);
27+
28+
var file = icon_paintable?.file;
29+
if (file == null) {
30+
throw new IOError.FAILED ("Icon paintable has no file");
31+
}
32+
33+
return new Gdk.Pixbuf.from_stream_at_scale (file.read (), -1, get_texture_size (size, scale), true);
34+
}
35+
}
36+
37+
private class ResourceIconSource : Object, IconSource {
38+
public string path { get; construct; }
39+
40+
public ResourceIconSource (string path) {
41+
Object (path: path);
42+
}
43+
44+
public string? get_cache_key (int size, float scale) {
45+
return "%s-%d".printf (path, get_texture_size (size, scale));
46+
}
47+
48+
public Gdk.Pixbuf create_pixbuf (int size, float scale) throws Error {
49+
return new Gdk.Pixbuf.from_resource_at_scale (path, -1, get_texture_size (size, scale), true);
50+
}
51+
}
52+
53+
private interface IconSource : Object {
54+
public abstract string? get_cache_key (int size, float scale);
55+
/**
56+
* Should look up the icon for the given size (e.g. if there are more detailed icons
57+
* for larger sizes) and return a texture of size * scale.
58+
*/
59+
public abstract Gdk.Pixbuf create_pixbuf (int size, float scale) throws Error;
60+
61+
protected static int get_texture_size (int size, float scale) {
62+
return (int) Math.ceilf (size * scale);
63+
}
64+
}
65+
66+
private static HashTable<string, Gdk.Pixbuf> icon_pixbufs = new HashTable<string, Gdk.Pixbuf> (str_hash, str_equal);
67+
68+
public int icon_size { get; construct set; }
69+
public float monitor_scale { get; construct set; }
70+
71+
public string resource_path { set { source = new ResourceIconSource (value); } }
72+
public GLib.Icon gicon { set { source = new GIconSource (value); } }
73+
74+
private IconSource _source;
75+
private IconSource source {
76+
get { return _source; }
77+
set {
78+
_source = value;
79+
load_pixbuf ();
80+
}
81+
}
82+
83+
public Icon (int icon_size, float monitor_scale) {
84+
Object (icon_size: icon_size, monitor_scale: monitor_scale);
85+
}
86+
87+
public Icon.from_resource (int icon_size, float monitor_scale, string resource_path) {
88+
Object (icon_size: icon_size, monitor_scale: monitor_scale, resource_path: resource_path);
89+
}
90+
91+
construct {
92+
notify["icon-size"].connect (load_pixbuf);
93+
notify["monitor-scale"].connect (load_pixbuf);
94+
resource_scale_changed.connect (load_pixbuf);
95+
}
96+
97+
private void load_pixbuf () {
98+
var actor_size = Utils.scale_to_int (icon_size, monitor_scale);
99+
set_size (actor_size, actor_size);
100+
101+
var scale = monitor_scale * get_resource_scale ();
102+
103+
try {
104+
var pixbuf = get_pixbuf (icon_size, scale);
105+
content = new Gala.Image.from_pixbuf_with_size (actor_size, actor_size, pixbuf);
106+
107+
set_background_color (null);
108+
} catch (Error e) {
109+
critical ("Could not load icon pixbuf: %s", e.message);
110+
background_color = { 255, 0, 0, 255 };
111+
}
112+
}
113+
114+
private Gdk.Pixbuf get_pixbuf (int size, float scale) throws Error {
115+
var cache_key = source.get_cache_key (size, scale);
116+
117+
if (cache_key == null) {
118+
return source.create_pixbuf (size, scale);
119+
}
120+
121+
if (!(cache_key in icon_pixbufs)) {
122+
icon_pixbufs[cache_key] = source.create_pixbuf (size, scale);
123+
}
124+
125+
return icon_pixbufs[cache_key];
126+
}
127+
}

lib/Image.vala

Lines changed: 43 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,23 @@
33
* Copyright 2025 elementary, Inc. <https://elementary.io>
44
* SPDX-License-Identifier: GPL-3.0-or-later
55
*/
6+
#if !HAS_MUTTER46
7+
public class Gala.Image : Clutter.Image, Clutter.Content {
8+
private int width;
9+
private int height;
10+
11+
public Image.from_pixbuf_with_size (int width, int height, Gdk.Pixbuf pixbuf) {
12+
Object ();
13+
14+
this.width = width;
15+
this.height = height;
16+
17+
Cogl.PixelFormat pixel_format = (pixbuf.get_has_alpha () ? Cogl.PixelFormat.RGBA_8888 : Cogl.PixelFormat.RGB_888);
18+
try {
19+
set_data (pixbuf.get_pixels (), pixel_format, pixbuf.width, pixbuf.height, pixbuf.rowstride);
20+
} catch (Error e) {}
21+
}
622

7-
#if !HAS_MUTTER48
8-
public class Gala.Image : Clutter.Image {
923
public Image.from_pixbuf (Gdk.Pixbuf pixbuf) {
1024
Object ();
1125

@@ -14,19 +28,27 @@ public class Gala.Image : Clutter.Image {
1428
set_data (pixbuf.get_pixels (), pixel_format, pixbuf.width, pixbuf.height, pixbuf.rowstride);
1529
} catch (Error e) {}
1630
}
31+
32+
public override bool get_preferred_size (out float width, out float height) {
33+
width = this.width;
34+
height = this.height;
35+
return true;
36+
}
1737
}
1838
#else
1939
public class Gala.Image : GLib.Object, Clutter.Content {
20-
Gdk.Pixbuf? pixbuf;
21-
Cogl.Texture? texture;
22-
uint width;
23-
uint height;
40+
public int width { get; construct; }
41+
public int height { get; construct; }
42+
public Gdk.Pixbuf pixbuf { get; construct; }
43+
44+
private Cogl.Texture? texture;
45+
46+
public Image.from_pixbuf_with_size (int width, int height, Gdk.Pixbuf pixbuf) {
47+
Object (width: width, height: height, pixbuf: pixbuf);
48+
}
2449

2550
public Image.from_pixbuf (Gdk.Pixbuf pixbuf) {
26-
this.pixbuf = pixbuf;
27-
width = pixbuf.width;
28-
height = pixbuf.height;
29-
invalidate ();
51+
Object (width: pixbuf.width, height: pixbuf.height, pixbuf: pixbuf);
3052
}
3153

3254
public bool get_preferred_size (out float width, out float height) {
@@ -36,22 +58,21 @@ public class Gala.Image : GLib.Object, Clutter.Content {
3658
return false;
3759
}
3860

39-
width = texture.get_width ();
40-
height = texture.get_height ();
61+
width = this.width;
62+
height = this.height;
4163
return true;
4264
}
4365

4466
public void paint_content (Clutter.Actor actor, Clutter.PaintNode node, Clutter.PaintContext paint_context) {
4567
if (pixbuf != null && texture == null) {
68+
#if HAS_MUTTER48
4669
var cogl_context = actor.context.get_backend ().get_cogl_context ();
70+
#else
71+
var cogl_context = Clutter.get_default_backend ().get_cogl_context ();
72+
#endif
4773
Cogl.PixelFormat pixel_format = (pixbuf.get_has_alpha () ? Cogl.PixelFormat.RGBA_8888 : Cogl.PixelFormat.RGB_888);
4874
try {
4975
texture = new Cogl.Texture2D.from_data (cogl_context, pixbuf.width, pixbuf.height, pixel_format, pixbuf.rowstride, pixbuf.get_pixels ());
50-
if (width != texture.get_width () || height != texture.get_height ()) {
51-
width = texture.get_width ();
52-
height = texture.get_height ();
53-
invalidate_size ();
54-
}
5576
} catch (Error e) {
5677
critical (e.message);
5778
}
@@ -61,15 +82,15 @@ public class Gala.Image : GLib.Object, Clutter.Content {
6182
return;
6283

6384
var content_node = actor.create_texture_paint_node (texture);
85+
86+
#if HAS_MUTTER48
6487
content_node.set_static_name ("Image Content");
88+
#endif
6589
node.add_child (content_node);
6690
}
6791

68-
public void invalidate () {
69-
texture = null;
70-
}
92+
public void invalidate () { }
7193

72-
public void invalidate_size () {
73-
}
94+
public void invalidate_size () { }
7495
}
7596
#endif

0 commit comments

Comments
 (0)