Reactive logo/image/SVG shapes/paths to libadwaita named colors GTK4/Adw in Python

I have a logo I created in Inkscape, which works well as a scalable icon in an Adw.Application using:

logo = Gtk.Image.new_from_icon_name("logo-symbolic")

I can apply CSS to this icon this fine and set this as @theme_selected_bg_color using logo.header_logo.set_css_classes(["logo-style"]).

What I’m wanting to do is display the SVG logo in an About content pane with different colors for the shape objects in the SVG depending on the user’s theme detected in libadwaita. For instance, if the theme is dark, then some paths would adjust their fill property based on @theme_selected_fg_color with different alpha attributes. One other part of the SVG would have a path fill of @theme_selected_bg_color as an accent. I’ve tried a few different approaches:

  • Tried to reference CSS classes in the SVG in various Gtk.Image input methods, but from what I’m understanding is that this method only works when parsing in the SVG as an icon using the alpha values of the shapes to shade a simple icon which eventually is coloured using the inherited CSS.
  • Tried to resolve the libadwaita CSS color for theme_selected_bg_color to then edit the XML attributes in a Python string. I was never able to figure out in my program how to return the value from the user’s libadwaita environment for this approach in GTK4 - and this seems like an anti-pattern in the new version of the API.
  • Tried to render the different paths from the SVG in cairo using Gtk.DrawingArea and stack them on top of each other to get a final image. When I did this, I was not able to change the color of each shape/path from the SVG, only the background-color was changed. I would still run into the same problem of not being able to resolve the RGBA attributes of the libadwaita named colors even were this possible - which as I understand are only to be used when rendering the UI with the CssProvider resolving those theme colors, and not in the context of cairo.

I’ve spent considerable time on this, and I of course can simply use the SVG with a set colors in the file as a Gtk.Image and not worry about this, but I was curious to see if this was possible and perhaps I was close to a solution that I’ve yet to find.

I’ve search far and wide for similar issues in this discourse, in turtorials and on Google, and GTK4 being “relatively” fresh, it seems like there were ways of getting the color of a widget previously styled with CSS in GTK3, but those methods have now been deprecated. I want to be clear I’m not complaining, and I’m not trying to challenge the justifications for this at all. I am not a framework developer, and I’m sure there are great reasons for these deprecations.

I’m also not trying to get into an argument over how CSS works in GTK4, or how I’m thinking about things the wrong way with CSS hierarchy or inheritance, or what it means to the “get the color” of a widget. I’m just asking if anyone knows how I might accomplish this using the libadwaita named colors that I love, I enjoy the app UI being responsive to the users’ styling preferences, and it would be nice if I could extend this to an SVG in a Gtk.Image or some other approach that I’m yet to learn.

I’m brand new to GTK, so please be nice, and I’m sorry if I am talking about a touchy subject which seems to be the case in some of the topics I’ve read :slight_smile:

Edit: This some of my early code, but it doesn’t contain anything I’ve tried above, but there is a screenshot of my layout so far: GitHub - berglh/zen-focus: Python3 GTK4 Libadwaita application for monitoring AMD Ryzen CPUs via ryzen_smu, k10temp kernel modules

So, this is what I have so far as I’m continuing on. I discovered I can adjust the SVG attributes using CSS on a simple SVG using Rsvg. This is great, however, it seems like the CSS parsers for GTK and Rsvg are different. I feel like I’m back to the problem of being able to resolve libadwaita named colours and return the value to set for the CSS for Rsvg. I’ve had a look through the Rsvg repository issues and I can’t see any support for named colours from libadwaita. Similarly, CssProvider doesn’t know about SVG attributes, which makes sense because Rsvg is returning a pixel buffer to render with Gtk.Image widget.

import gi
gi.require_version('Gtk', '4.0')
from gi.repository import Gtk, Gio, Rsvg

class IconApp(Gtk.Application):
    def __init__(self):
        super().__init__(application_id="SVG Example",
                         flags=Gio.ApplicationFlags.FLAGS_NONE)

    def do_activate(self):

        self.window = Gtk.ApplicationWindow(application=self)
        self.window.set_default_size(200, 200)

        # Load a simple SVG
        svg_data = Rsvg.Handle.new_from_data("""
        <svg width="100" height="100" version="1.1" xmlns="http://www.w3.org/2000/svg">
            <rect id="rect1" x="10" y="10" width="30" height="30" fill="#ff0000" />
            <rect id="rect2" x="50" y="10" width="30" height="30" fill="#00ff00" />
        </svg>                       
        """.encode())

        stylesheet = """
            #rect1, #rect2 {
                fill: #0000ff ;
            }
        """

        # The style provider does know about the @accent_fg_color
        # However, it's unaware of properties for Rsvg and is
        # not responsible for applying the CSS to the SVG data
        style_provider = Gtk.CssProvider()
        style_provider.load_from_data("""
                #rect1, #rect2 {
                color: @accent_fg_color;
            }
        """)

        # I can apply the CSS to the SVG data
        svg_data.set_stylesheet(stylesheet.encode())        

        # Render the updated SVG
        image = Gtk.Image()
        # This is deprecated, haven't figured out how to use new_from_paintable and Gdk.Texture.new_for_pixbuf yet
        image.set_from_pixbuf(svg_data.get_pixbuf())

        self.window.set_child(image)
        self.window.present()

if __name__ == "__main__":
    app = IconApp()
    app.run([])

Ok, I think this does it. Creating a label, setting the CSS property and then getting the color from the widget. This does not feel great and a bit of an anti-pattern. I’d be open to any suggestions.

import gi
gi.require_version('Gtk', '4.0')
gi.require_version('Rsvg', '2.0')

from gi.repository import Gtk, Gio, Rsvg, Gdk

class IconApp(Gtk.Application):
    def __init__(self):
        super().__init__(application_id="SVG Example",
                         flags=Gio.ApplicationFlags.FLAGS_NONE)

    def do_activate(self):

        self.window = Gtk.ApplicationWindow(application=self)
        self.window.set_default_size(200, 200)

        style_provider = Gtk.CssProvider()
        style_provider.load_from_data("""
                .myrectcolor {
                color: @theme_selected_bg_color;
            }
        """)
        Gtk.StyleContext.add_provider_for_display(
            Gdk.Display.get_default(),
            style_provider,
            Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
        )

        label = Gtk.Label(label="Some Text")
        label.set_css_classes(["myrectcolor"])
       
        #self.window.set_child(label)
        print(label.get_color())
        rgba_color = label.get_color()
        color_hex = '#{:02x}{:02x}{:02x}'.format(int(rgba_color.red*255), int(rgba_color.green*255), int(rgba_color.blue*255))
        print(color_hex)

        # Load a simple SVG
        svg_data = Rsvg.Handle.new_from_data("""
        <svg width="100" height="100" version="1.1" xmlns="http://www.w3.org/2000/svg">
            <rect id="rect1" x="10" y="10" width="30" height="30" fill="#ff0000" />
            <rect id="rect2" x="50" y="10" width="30" height="30" fill="#00ff00" />
        </svg>                       
        """.encode())

        # Paint in the resolved hex color
        stylesheet = f"#rect1, #rect2 {{ fill: {color_hex} ; }}"

        # Apply the CSS to the SVG data
        svg_data.set_stylesheet(stylesheet.encode())        

        # Render the updated SVG
        image = Gtk.Image()

        # image = Gtk.Image()
        image.set_from_pixbuf(svg_data.get_pixbuf())

        self.window.set_child(image)
        self.window.present()

if __name__ == "__main__":
    app = IconApp()
    app.run([])

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.