How to reuse an existing Gtk widget's style class in a custom widget?

,

Specifically, I wanted to make the custom widget (a Gtk.Box) have the background of Gtk.Entry so that it will look interactive or harmony no matter in which theme.
In Gtk3, I got that with the following method:

imports.gi.versions.Gtk = "3.0";
const { Gtk, GObject } = imports.gi;
Gtk.init(null);

var ExampleApp = GObject.registerClass(
class ExampleApp extends Gtk.Application {
    vfunc_activate() {
        let appWindow = new Gtk.ApplicationWindow({ application: this, });

        let add = new Gtk.Box({ halign: Gtk.Align.CENTER });
        add.pack_start(new Gtk.Image({ icon_name: 'list-add-symbolic' }), false, false, 0);
        let bgcolor = new Gtk.Entry().get_style_context().get_background_color(Gtk.STATE_FLAG_NORMAL).to_string();
        let context = add.get_style_context();
        let provider = new Gtk.CssProvider();
        provider.load_from_data(`.abox { background-color: ${bgcolor}; min-height: 2em; } `);
        context.add_class('abox');
        context.add_provider(provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
        appWindow.add(add);

        appWindow.set_default_size(200, 200);
        appWindow.show_all();
    }
});
new ExampleApp().run([imports.system.programInvocationName].concat(ARGV));

But there is no Gtk.StyleContext.get_background_color() in Gtk4, also, I tried adding the style class directly context.add_class('entry') but it’s not working.
So, is it possible? If yes, How should I do it?

Have you tried Gtk.render_background?

Thanks, I’ve considered Gtk.render_background, which seems to render a Cairo.Context as background, however, I don’t know how I can retrieve a Cairo.Context or the background from a Gtk widget. The problem is actually how to render a ‘native’ color not any other colors.

In GTK3, boxes (like most of the GTK containers) do not have a background.

If you want to draw a background you’ll have to either:

  1. subclass GtkBox, override the do_draw() virtual function, call Gtk.render_background() with the given Cairo context, and then call the parent class implementation of do_draw()
  2. pack the box widget inside a Gtk.EventBox, and set the background on the event box widget

I think you mean override snapshot instead of draw, for Gtk4? For that there is also Gtk.Snapshot.render_background, if no additional cairo drawing is required.

Thanks, I see that there might be more than one method to change a widget’s background. But I’m more interested in knowing how to reuse the background of the other widget in Gtk4, which lacks of Gtk.StyleContext.get_background_color() .


Gtk.StyleContext.lookup_color('theme_base_color') did the trick at least in Adwaita theme.

That will work but it will only get you the background color, it will be ineffective if there is a background image or if there are other effects on the background such as border radius. To re-use those you will have to use one of the render_background functions with the style context from the entry.

Could you show me a demo to achieve that? Thanks.

You can try this snippet using libadwaita to create a basic container, the margin should demonstrate the effect:

imports.gi.versions.Gtk = '4.0';
const { GObject, Gio, Gtk, Adw } = imports.gi;

const EntryBin = GObject.registerClass(
class EntryBin extends Adw.Bin {
  _init() {
    super._init();
    var entry = new Gtk.Entry();
    this.entry_ctx = entry.get_style_context();
  }
  vfunc_snapshot(snapshot) {
    snapshot.render_background(this.entry_ctx, 0, 0,
                               this.get_width(), this.get_height());
    super.vfunc_snapshot(snapshot);
  }
});
const app = new Gtk.Application({ application_id: 'org.example.entrybin' });
app.connect('activate', () => {
  const window = new Gtk.ApplicationWindow({
    application: app, title: 'Entry Bin',
    default_width: 300, default_height: 200,
  });
  const entry_bin = new EntryBin();
  entry_bin.set_child(Gtk.Label.new('Hello World!'));
  entry_bin.margin_top = entry_bin.margin_bottom =
    entry_bin.margin_start = entry_bin.margin_end = 16;
  window.set_child(entry_bin);
  window.present();
});
app.run(ARGV);

That should be usable from .ui files too because it inherits the Gtk.Buildable implementation from Adw.Bin.

If you want the full styles from the entry and not just the background, you can try this code instead to set the CSS name to that of the entry:

const EntryBin = GObject.registerClass(
class EntryBin extends Adw.Bin {
  static _classInit(klass) {
    klass = Gtk.Widget._classInit(klass);
    Gtk.Widget.set_css_name.call(klass, 'entry');
    return klass;
  }
});
1 Like

To render a widget with the style of another widget, there is also the possibility of changing the css name of the widget.

box.cssName = 'entry';

You can for example use a multiline text view as an entry.
However, I don’t think it is adapted to your case.

I couldn’t get that way to work, it appears css-name is a construct only property that is only expected to be set through gtk_widget_class_set_css_name.

Sorry, it is a construct-only property:

let box = new Gtk.Box({ cssName: 'entry' });
1 Like