1

How is it possible to render a rectangle with the background color of selections in GTK+3. I cannot find any API to do that:

static gboolean draw_callback (GtkWidget *widget, cairo_t *cr, gpointer data)
    {
    auto state=reinterpret_cast<State*>(data);
    auto width = gtk_widget_get_allocated_width (widget);
    auto height = gtk_widget_get_allocated_height (widget);

    auto context = gtk_widget_get_style_context (widget);
    gtk_render_background(context,cr,0,0,width,height);

    cairo_rectangle(cr,0,height*(1.0 - state->max),width,height*(state->max - state->min));
    cairo_set_source_rgb(cr, 0.05,0.6,0.15); //What color should be used here?
    cairo_fill (cr);

    cairo_set_source_rgb(cr,0.01,0.3,0.07); //And here
    auto mid=height*(1.0 - 0.5*(state->min + state->max));
    cairo_move_to(cr,0, mid);
    cairo_line_to(cr,width,mid);
    cairo_stroke(cr);

    return FALSE;
    }
user877329
  • 6,717
  • 8
  • 46
  • 88

2 Answers2

1

Use gtk_render_frame() and gtk_render_background(), and set up the GtkStyleContext you obtain from the GtkWidget instance with the CSS state you want to replicate.

If you want to adhere to the theme, then you cannot draw yourself; and CSS does not have "colors": each CSS state can have multiple layers that include images, gradients, and complex blend modes.

ebassi
  • 8,648
  • 27
  • 29
  • `gtk_style_context_set_state(context,GTK_STATE_FLAG_SELECTED);` before `gtk_render_background` does not work. Do I have to add a stylesheet manually? But then, how can I retrive the proper settings for that stylesheet => Custom widgets cannot be themed. – user877329 Apr 13 '17 at 12:27
  • You may explain more in more details how to develop a custom *themable* widget using GTK+3. Or is that impossible with the new architecture? The obvious hack is to check whether or not we are using dark or light mode through `g_object_get(gtk_settings_get_default() ,"gtk-application-prefer-dark-theme",&dark_is,NULL);` and adapt to that. – user877329 Apr 18 '17 at 07:57
  • If by "custom themable" widget you mean "a widget that will adapt to any theme the user will load" then the only way to do that is to hijack some CSS class and render your widget like another. Otherwise, no: you cannot do what you used to do with GTK+ 2, and inspect the current state of the theme and re-use it. The state is defined by CSS selectors, which means classes, element names, and widget state flags. If the theme has a set of properties matching that selector, you'll get those properties. – ebassi May 04 '17 at 15:40
  • One way to ensure that your widget will adapt to different themes is to have a custom CSS provider that will load custom CSS definitions for your widget — with the option of using the theme name as a variable to determine which CSS fragment to load. This, of course, will work only for the themes you decide to handle, not any random theme. – ebassi May 04 '17 at 15:42
  • Ok, this means that they decided that the system didn't need to be extensible in that way (bad decision, I hope that Gtk4 will fix that issue in some way). The old Windows API model was really fine, for both developers and end users. Developer could choose the appropriate color element from a finite list (but do never assume light background), and the end user could apply "Hot Dog" or easily come up with a custom theme. Extensibility and simplicity should be considered more important the extreme theming possibilities. – user877329 May 05 '17 at 06:34
  • There is no plan to remove CSS support from GTK+ 4.x. The changes to the style system were implemented at the behest of the theme developers, as well as a way to avoid theme writers to inject random modules into applications in order to implement what CSS provides. You simply have to give up the idea that you can inspect a theme at run time, and instead ship your own theme with your application — or write your widgets to render with the GTK+ API instead of querying states from the theme. – ebassi May 06 '17 at 16:32
  • If the GTK+ API had sufficient methods like render_path_like_this, render_circle, render_my_custom_shape, etc it would have been possible. `gtk_render_background` is too limited for this. I guess this boils down to problems with the visitor pattern. It restricts the developer to a finite set of primitives, when there really are an infinite set. The set of color to use is normally finite, but the set of all shapes is infinite, therefore, provide an API for colors, and not shapes. – user877329 May 06 '17 at 17:45
0

Well, here is my hack:

ColorRGBA get_ambient_color(GtkWidget* widget)
    {
    auto surface=cairo_image_surface_create(CAIRO_FORMAT_ARGB32,4,4);
    auto cr=cairo_create(surface);
    while(widge!=NULL)
        {
        auto context=gtk_widget_get_style_context(widget));
        gtk_render_background(context,cr,0,0,1,1);
        cairo_surface_flush(surface);
        auto content=cairo_image_surface_get_data(surface);
        if(content[3]==255)
            {
            auto ret=ColorRGBA{content[2]/255.0f,content[1]/255.0f,content[0]/255.0f,content[3]/255.0f};
            cairo_destroy(cr);
            cairo_surface_destroy(surface);
            return ret;
            }
    //  Surface is not opaque yet. Continue to parent container.
        widget_handle=gtk_widget_get_parent(GTK_WIDGET(widget_handle));
        }
    cairo_destroy(cr);
    cairo_surface_destroy(surface);
    return ColorRGBA{1.0f,1.0f,1.0f,1.0f};
    }

It seams that I have failed to convince people, why you need the ambient colour, so here are two use-cases:

  1. Determine if we are using a dark/light theme. For some applications, this is sufficient. Querying the state only works if the theme supports dark/light modes. This proves the actual result.

  2. Use as input colour for simulating global illumination. The shading of widgets should be affected by the ambient, hence the name. Another good name would be get_average_background. Themers: please don't use gradients with high contrast.

Case 1: A plot

Dark plot

Light plot

Now you say that the colour of cursors and function graphs should be themable. That is simply not possible: The user of this plot widget can add as many curves and cursors as he wishes, and the easiest way to differentiate them is to use distinct colours.

What about curve and cursor lightness? If the background is dark, then the curve should be light and vice versa. And what background should be chosen? Ideally, something close the the background of the parent widget, but if the theme is regular, white for light, and black for dark would work. Do you notice that the curves are darker in the second figure?

Case 2: A checkbox that looks like a metallic toggle switch button

Switch

With the following technique, I have created a switch that looks exactly as if it were rendered through the Cycles path tracer. This is implemented in Gtk+2, but the algorithm is the same.

The two input images

Light Background

The code

GtkAllocation alloc;
gtk_widget_get_allocation(widget,&alloc);

auto width=alloc.width;
auto context=CairoContext( gdk_cairo_create(gtk_widget_get_window(widget)) );

auto w_in=cairo_image_surface_get_width(light);
auto h_in=cairo_image_surface_get_height(light);

// Render direct lighting
auto surf_temp=CairoSurface( cairo_image_surface_create(CAIRO_FORMAT_ARGB32,w_in,h_in) );
auto context_temp=CairoContext( cairo_create(surf_temp) );
cairo_set_source_surface(context_temp,light,0,0);
cairo_set_operator(context_temp,CAIRO_OPERATOR_OVER);
cairo_paint(context_temp);

//Render ambient reflections
auto surf_temp_2=CairoSurface( cairo_image_surface_create(CAIRO_FORMAT_ARGB32,w_in,h_in) );
auto context_temp_2=CairoContext( cairo_create(surf_temp_2) );
cairo_set_source_surface(context_temp_2,background,0,0);
cairo_set_operator(context_temp_2,CAIRO_OPERATOR_OVER);
cairo_paint(context_temp_2);

cairo_set_operator(context_temp_2,CAIRO_OPERATOR_MULTIPLY);
//Multiply reflections with the background color
cairo_set_source_rgb(context_temp_2, color_bg.r, color_bg.g, color_bg.b);
cairo_rectangle(context_temp_2, 0, 0, w_in, h_in);
cairo_mask_surface(context_temp_2,surf_temp,0,0);

//Add the results
cairo_set_source_surface(context_temp,surf_temp_2,0,0);
cairo_set_operator(context_temp,CAIRO_OPERATOR_ADD);
cairo_mask_surface(context_temp,surf_temp,0,0);

//Scale and move things into place
auto s=static_cast<double>(width)/static_cast<double>(w_in);
cairo_translate(context,alloc.x,alloc.y);
cairo_scale(context,s,s);
cairo_set_source_surface(context,surf_temp,0,0);
cairo_set_operator(context,CAIRO_OPERATOR_OVER);
cairo_paint(context);

Thoughts

The first example boils down to a light/dark query which is currently missing. Maybe querying colours is not required for this to work, but then there has to be an API controlling the shape and blending mode when rendering the background. For example, to render the ambient reflection, I use multiply rather than over. Also, gtk_render_background appears to be a no-op, since GtkDrawingArea has zero opacity (that's why I needed the loop). To be useful, it must use the background as it appears on screen, not the background of the current widget.

Community
  • 1
  • 1
user877329
  • 6,717
  • 8
  • 46
  • 88