/!\ This page describes best practices for dialogs in GTK3. With GTK4, some details might be different. /!\

Create Symbolic Icons (that are theme-color aware)

Problem / Background

You want to use custom icons in your application, but they look stupid on different themes. You want them to adapt to the current theme coloring (like native icons)

Solution and Keywords

You need to:

  • use svg images.

  • make the icons part of the theme. (Keyword: themed icons)

  • bind the icons into resources. (Keyword: GResource)

  • load the icon with the right function.

Step-by-Step-guide

Prepare the file structure

For this example we use the following file structure:

[PROJECT_ROOT]
   └ main.c
   └ data
       └ icons
           └ scalable
                 └ actions

Compilation is done from [PROJECT_ROOT] (henceforth ./).

Create an icon

  1. Create an svg image.

  2. Store it as ./data/icons/scalable/action/aaaa-symbolic.svg

    • (The path is complex, but it's a valid "icon theme path".
      • You always want to use a valid icon theme path.)

Be aware of the following:

  • Only fill colors can become theme-color-aware. Stroke colors will *not* become theme-color-aware.

  • Use color #00000000. For grey-scaling, use the alpha channel only (don't make the color "grey").

  • Text has to be converted to a path. If you use Inkscape, do: select text, Menu → PathObject to Path.

Example file

Here's an example image, that you can use.

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
   xmlns:dc="http://purl.org/dc/elements/1.1/"
   xmlns:cc="http://creativecommons.org/ns#"
   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
   xmlns:svg="http://www.w3.org/2000/svg"
   xmlns="http://www.w3.org/2000/svg"
   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
   width="32"
   height="32"
   viewBox="0 0 209.89055 226.02279"
   version="1.1"
   id="svg8"
   inkscape:version="1.0.2 (e86c870879, 2021-01-15)"
   sodipodi:docname="aaaa-symbolic.svg"
   style="enable-background:new">
  <defs
     id="defs2" />
  <sodipodi:namedview
     id="base"
     borderopacity="0.50196078"
     inkscape:pageopacity="0"
     inkscape:pageshadow="2"
     inkscape:zoom="4.0495384"
     inkscape:cx="16.568684"
     inkscape:cy="36.498024"
     inkscape:document-units="mm"
     inkscape:current-layer="layer1"
     inkscape:document-rotation="0"
     showgrid="false"
     inkscape:window-width="1307"
     inkscape:window-height="977"
     inkscape:window-x="520"
     inkscape:window-y="16"
     inkscape:window-maximized="0"
     inkscape:showpageshadow="false"
     showborder="true"
     borderlayer="false"
     bordercolor="#000000"
     pagecolor="#ffffff" />
  <metadata
     id="metadata5">
    <rdf:RDF>
      <cc:Work
         rdf:about="">
        <dc:format>image/svg+xml</dc:format>
        <dc:type
           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
        <dc:title></dc:title>
      </cc:Work>
    </rdf:RDF>
  </metadata>
  <g
     inkscape:label="Layer 1"
     inkscape:groupmode="layer"
     id="layer1"
     transform="translate(0,0)">
    <path
       style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:19.365;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
       d="m 194.43781,13.561946 0.11168,204.523594"
       id="path842" />
    <g
       aria-label="W"
       transform="scale(1.0182881,0.98204034)"
       id="text838"
       style="font-style:normal;font-weight:normal;font-size:120.222px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264585px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1">
      <path
         d="M 21.335921,124.20114 H 33.31116 l 18.432474,74.08211 18.373773,-74.08211 h 13.325388 l 18.432475,74.08211 18.37377,-74.08211 h 12.03394 l -22.0133,87.6423 H 95.359331 L 76.868154,135.76546 58.200871,211.84344 H 43.290525 Z"
         style="stroke-width:0.264585px"
         id="path18" />
    </g>
    <ellipse
       style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:17.8;stroke-miterlimit:4;stroke-dasharray:none;paint-order:markers stroke fill;enable-background:new"
       id="path834-3-3"
       cx="80.893105"
       cy="56.29356"
       rx="41.264389"
       ry="37.936203" />
  </g>
</svg>

Prepare Resources

Details on Resources here...

Create resources

Create a new file called ./resources.xml with the following content:

<?xml version="1.0" encoding="UTF-8"?>
<gresources>
  <gresource prefix="/org/myapp/">
    <file>data/icons/scalable/actions/aaaa-symbolic.svg</file>
  </gresource>
</gresources>

Compile Resources

In your [PROJECT_ROOT], run

  • glib-compile-resources --generate-header ./resources.xml

  • glib-compile-resources --generate-source ./resources.xml

This will generate a resource.h and resource.c file.

**Warning** You cannot generate the header and source file together with one command.

Write a Program

The following code is a fully functional program loading the image we just created.

#include <gtk/gtk.h>
#include "resources.h"


GtkWidget *
create_gui()
{
  // resources_get_resource is defined in resource.c
  g_resources_register (resources_get_resource ());

  gtk_icon_theme_add_resource_path (gtk_icon_theme_get_default (),
                                    "/org/myapp/data/icons/scalable/actions");

  GtkWidget * icon = gtk_image_new_from_icon_name ("aaaa-symbolic", GTK_ICON_SIZE_INVALID /*size is ignored by gtk*/);

  // make icon bigger for this example
  gtk_image_set_pixel_size (GTK_IMAGE (icon),256);

  // add icon to a button
  GtkToolButton *button;
  button = GTK_TOOL_BUTTON (gtk_tool_button_new (icon, "my tooltext"));

  return GTK_WIDGET (button);
}



/**
 *  standard stuff. See `create_gui ()`
 **/
int main(int argc, char *argv[])
{
  gtk_init (&argc, &argv);

  GtkWidget *window;
  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);

  GtkWidget * box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 5);
  gtk_container_add (GTK_CONTAINER (window), box);

  GtkWidget * gui = create_gui ();
  gtk_container_add (GTK_CONTAINER (box), gui);

  g_signal_connect (G_OBJECT (window), "destroy",
                    G_CALLBACK (gtk_main_quit), G_OBJECT (window));

  gtk_widget_show_all(window);
  gtk_main();

  return 0;
}

Fallacies

Below are some approaches you might run into, that won't work:

// WRONG: Your icon wouldn't be shown
// GtkIconTheme * theme = gtk_icon_theme_new ();


/*/ WRONG: This will load the icon, but it won't be color-aware
// icon = gtk_image_new_from_resource ("/org/myapp/data/icons/scalable/actions/aaaa-symbolic.svg");
//*/


/* WRONG: loads the icon, but it won't be color-aware
{
  GdkPixbuf * pixbuf = gtk_icon_info_load_icon (icon_info, NULL);
  icon = gtk_image_new_from_pixbuf (pixbuf);
}
//*/


/* PROBLEMATIC: The icon will be color-aware, but only
//              at initializing time. I.e. if the theme
//              changes later, the icon won't be updated
{
  GtkIconInfo * icon_info = gtk_icon_theme_lookup_icon (theme,"aaaa-symbolic", 64 ,GTK_ICON_LOOKUP_FORCE_SYMBOLIC);
  GdkRGBA fg = {0.0,0.0,0.0,1.0};
  GtkWidget * tmp = gtk_button_new ();
  GtkStyleContext * context = gtk_widget_get_style_context (tmp);
  gtk_style_context_get_color (context,GTK_STATE_FLAG_NORMAL,&fg);
  gtk_widget_destroy (tmp);
  GdkPixbuf * pixbuf = gtk_icon_info_load_symbolic (icon_info, &fg, NULL, NULL, NULL, NULL, NULL);
  icon = gtk_image_new_from_pixbuf (pixbuf);
}
//*/


/* WRONG, but nice to know:
//        This way you can freely colorize
//        your foreground color.
{
  GtkIconInfo * icon_info = gtk_icon_theme_lookup_icon (theme,"aaaa-symbolic", 64 ,GTK_ICON_LOOKUP_FORCE_SYMBOLIC);
  GdkRGBA fg = {1.0,0.0,0.0,1.0};
  GdkPixbuf * pixbuf = gtk_icon_info_load_symbolic (icon_info, &fg, NULL, NULL, NULL, NULL, NULL);
  icon = gtk_image_new_from_pixbuf (pixbuf);
}
//*/


// Nice to know:
// `gtk_icon_theme_has_icon` doesn't work as expected:
//
// gtk_icon_theme_has_icon (theme, "aaaa"             ); // => FALSE
// gtk_icon_theme_has_icon (theme, "aaaa-symbolic.svg"); // => FALSE
// gtk_icon_theme_has_icon (theme, "aaaa-symbolic"    ); // => FALSE
// gtk_icon_theme_has_icon (theme, "/org/myapp/data/icons/scalable/actions/aaaa-symbolic.svg"); // => FALSE
// gtk_icon_theme_has_icon (theme, "data/aaaa"        ); // => FALSE

Compile

You compile file main.c and resources.c.

Overview of the final directory structure

Before compiling you should have a directory structure, that looks like this:

[PROJECT_ROOT]
   └ main.c
   └ resources.xml
   └ resources.c
   └ resources.h
   └ data
       └ icons
           └ scalable
                 └ actions
                     └ aaaa-symbolic.svg

References

HowDoI/CreateSymbolicIconsThatChangeColorAccordingToTheme (last edited 2021-09-07 04:35:45 by DarkTrick)