Wrap already attached Widget into a GtkFrame

I’m trying to create a way in my application for users to style GTK widgets through CSS. I’ve reached the border property, and was wondering how I can create one in GTK.

Specifically, I was looking at how I can achieve this through GTK with Rust bindings.

GtkFrame seems to be what I’m looking for. (although I haven’t seen any mention of setting the border radius except through CSS, I believe it’s only possible to do so at compile time, which is a problem for me)

Since the GtkLabel I’m trying to apply a border to is already set as the child of a GtkBox, I can’t simply append() the GtkFrame instead of the GtkLabel - I have to “replace” it.

This is my current code:

if border_style != "none" {
    let frame = gtk::Frame::new(None);
    frame.set_label_widget(Some(self));

    // how do I replace self with frame in self's parent? 
}

Maybe a GtkFrame isn’t the proper way of adding a border to a widget? I’ve also seen GtkBorder but the properties looked identical to a GtkFrame.

Summary: I’m trying to apply a (styled) border to GTK Widgets, can’t figure out how to properly do it. The widgets are already attached to a parent.

Hi,

No need to add a GtkFrame for that.
GtkFrame is mostly relevant for its optional label, useful to group widgets together with a title.

If you just need to draw a border around a widget, just use CSS styles:

  • create a CSS with border and border-radius properties
  • load the CSS in a Gtk.CssProvider() and load it with Gtk.StyleContext.add_provider_for_display()
  • apply the style to your GtkLabel with label.add_css_class()

Here an example in Python:

#!/usr/bin/env python3

import sys
import gi
gi.require_version('Gdk', '4.0')
gi.require_version('Gtk', '4.0')
from gi.repository import Gdk, Gtk

THEME = """
label.red-frame {
    padding: 9px;
    border: 3px solid red;
    border-radius: 9px;
}
"""

class MyAppWindow(Gtk.ApplicationWindow):
    __gtype_name__ = __qualname__
    def __init__(self, **kwargs):
        super().__init__(default_width=200, default_height=200, show_menubar=False, **kwargs)
        label = Gtk.Label.new("Hello World!")
        label.add_css_class('red-frame')
        label.set_halign(Gtk.Align.CENTER)
        label.set_valign(Gtk.Align.CENTER)
        self.set_child(label)
        self.present()

class MyApp(Gtk.Application):
    __gtype_name__ = __qualname__
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.connect('activate', self.on_activate)
    def on_activate(self, app):
        cssp = Gtk.CssProvider()
        cssp.load_from_string(THEME)
        d = Gdk.Display.get_default()
        Gtk.StyleContext.add_provider_for_display(d, cssp, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
        self.add_window(MyAppWindow())

if __name__ == '__main__':
    sys.exit(MyApp().run(sys.argv))

Hey!

If you just need to draw a border around a widget, just use CSS styles

I don’t think there’s a way to load CSS into the application after it has been activated, which is the behavior I need in this case. Unless I’m mistaken and add_provider_for_display can be called anytime, or more than once, or if there’s another way of loading CSS at runtime :thinking:

I’m already loading some CSS styles at the start of the app:

fn load_css() {
    let provider = CssProvider::new();
    provider.load_from_string(include_str!("style.css"));

    gtk::style_context_add_provider_for_display(
        &Display::default().unwrap(),
        &provider,
        gtk::STYLE_PROVIDER_PRIORITY_APPLICATION,
    );
}

Quoting from the original post:

[…] except through CSS, I believe it’s only possible to do so at compile time, which is a problem for me

To better explain my problem, I’m trying to load user-generated content that is fetched and then renderred into the GTK application. The user-generated content includes an HTML and CSS file, which I’m translating into GTK components.

For Label components I’m using the pango markup utility:

                let markup = &format!(
                    "<span foreground=\"{color}\" size=\"{font_size}\" line_height=\"{line_height}\" font_family=\"{font_family}\" font_weight=\"{font_weight}\" underline=\"{underline}\" underline_color=\"{underline_color}\" overline=\"{overline}\" overline_color=\"{overline_color}\" strikethrough=\"{strikethrough}\" strikethrough_color=\"{strikethrough_color}\">{}</span>",
                    self.label()
                );

                self.set_markup(markup);

However it doesn’t include border and padding.

If there was a way CSS could be loaded at runtime, that would most likely solve all my problems - but after searching I can’t seem to find it

Of course it’s possible to call add_provider_for_display multiple times, also at runtime :slight_smile:

What’s not allowed is to reuse the last CssProvider to load new CSS data. You must instantiate a new one.

You may have to call Gtk.StyleContext.remove_provider_for_display() with the previous CssProvider, to avoid collisions or leftovers.

That’s wonderful! I will try it out when I have time, thank you so much for the help

1 Like