Replacement for StyleContext lookup_color

I’m making a custom widget, which is a switch that can display icons:
custom-switch

I’m drawing it using the snapshot() of the widget. Since it’s all custom painting, I need foreground and background colors to paint it with. I’m currently using StyleContext lookup_color, which is deprecated. I’m aware of GtkWidget.color and it would suit my needs perfectly, however it only covers the foreground color. I’d still need a way to get a background color.
There used to be render_background for that purpose, but it was also deprecated.

I saw this paragraph in the gtk source:

The best way to render parts of your widget using CSS styling
is to use subwidgets. For example, to show a piece of text with
fonts, effects and shadows according to the current CSS style,
use a GtkLabel.

But I’m not sure how could I do that in my current context. I need a background color, even if I use a subwidget, I only know how to get its foreground color?

PS: you may have noticed the icon is not antialiased in my screenshot, I posted another question for that… Draw symbolic icon on snapshot: antialias?

Hi,

You should not draw the background by yourself in Gtk4, it’s automatically done in the generic GtkWidget code, based on the CSS background property.

If you need a specific background, just give your widget a proper CSS ID or class, then include a CSS file that defines the background’s style.

When snapshot() is called, the background should already have been drawn, just use GtkWidget.color to paint on top.

Hello,

thank you for the answer! However it’s clear that I didn’t properly formulate my question and so this doesn’t help me. I included a screenshot of the widget I’ve build in my original post, and you can see it has four “layers”:

  1. the background
  2. the blue background of the slider
  3. the white knob of the slider
  4. the black icon on top of the knob

When I mentioned the background I was actually aiming at #2. Or maybe #3… Yeah, my question was not formulated in a great manner.

So what my code is currently doing is… I’m not drawing #1, as you say. For #2, I’m using “accent_bg_color”, which I’m fetching through the style context (deprecated API!). For #3 I’m using “accent_fg_color”, which I’m again fetching through the deprecated style context API. And for #4 I currently hardcoded black. Maybe I should change #4 to the widget foreground color…

But without the style context API I don’t see any other way than to hardcode colors in the widget, because it seems to me I can only get one color from the CSS, the foreground color. I could also get the background color as you point out, but I need more colors than that…

So I’m not sure how to do that maybe even now, but it’s even worse when style context is deprecated :frowning:

PS: I now also renamed my question.

Thanks for the clarification!

Then the best way is to use a composite with 2 widgets: one for the slider and one for the indicator/handle.

Then you can use a CSS like this one:

iconswitch {
	/* Slider color when inactive */
	background: @theme_bg_color;
}

iconswitch:active {
	/* Slider color when active */
	background: @theme_selected_bg_color;
}

iconswitch indicator {
	/* Handle color */
	background: @theme_base_color;
	/* Icon color */
	color: @theme_fg_color;
}

Thank you!

I had actually seen mention of that pattern in the documentation, but I don’t exactly picture how could it work. Is there an example of the use of this pattern (including in the gtk source code) anywhere so that I could use that as inspiration?

Emmanuel

You can look at how GtkSwitch is implemented, and use the Inspector to see the layout and properties.

1 Like

alright thank you. I had already peeked but I’m often afraid that such builtin widgets use APIs that are not exported outside of gtk. I’ll review this and thanks to @gwillems infos I’m much more optimistic now!

Here a small example in Python.

It’s also possible to write custom widgets to manage measure() and snapshot() by yourself, if you need more control.

#!/usr/bin/env python3
# SPDX-License-Identifier: CC0-1.0

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


THEME = """
iconswitch {
    min-width: 48px;
    min-height: 24px;
    border-radius: 12px;
    background: @theme_base_color;   /* Slider color when inactive */
}

iconswitch:checked {
    background: @theme_selected_bg_color;   /* Slider color when active */
}

iconswitch indicator {
    -gtk-icon-size: 16px;
    margin: 0 0 0 24px;
    min-width: 24px;
    border-radius: 12px;
    border: 1px solid @borders;
    background: @theme_bg_color;   /* Handle color */
    color: @theme_fg_color;   /* Icon color */
}

iconswitch:checked indicator {
    margin: 0 24px 0 0;
}
"""


class MySwitchIndicator(Gtk.Image):
    __gtype_name__ = __qualname__

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.set_from_icon_name('view-reveal-symbolic')

MySwitchIndicator.set_css_name('indicator')


class MySwitch(Gtk.Overlay):
    __gtype_name__ = __qualname__

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.indicator = MySwitchIndicator()
        self.add_overlay(self.indicator)
        ev_click = Gtk.GestureClick(name='editide-pclick', button=Gdk.BUTTON_PRIMARY)
        ev_click.connect('pressed', self.on_click)
        self.add_controller(ev_click)

    def on_click(self, event, n, x, y):
        if self.get_state_flags() & Gtk.StateFlags.CHECKED:
            self.unset_state_flags(Gtk.StateFlags.CHECKED)
        else:
            self.set_state_flags(Gtk.StateFlags.CHECKED, False)

MySwitch.set_css_name('iconswitch')


class MyAppWindow(Gtk.ApplicationWindow):
    __gtype_name__ = __qualname__

    def __init__(self, **kwargs):
        super().__init__(default_width=200, default_height=200, show_menubar=False, **kwargs)
        switch = MySwitch(valign=Gtk.Align.CENTER, halign=Gtk.Align.CENTER)
        self.set_child(switch)
        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))
1 Like

The only private widget used by GtkSwitch is GtkGizmo, which is a plain GtkWidget subclass that doesn’t do anything except provide an empty area that gets styled by CSS. You can achieve the same result with an empty GtkLabel or a GtkImage.

2 Likes