1

I have a Gtk.Label in an app I am working on migrating from Gtk3 to Gtk4. In Gtk3, Label had a set_angle method that allowed rotating a label about its center.

That was removed in Gtk4, but there isn't any clear guidance on how to accomplish the same thing.

Following a few comments on various forum threads, I've tried using a CSS transform like

#my-widget {
  transform: rotate(90deg);
}

this does rotate the content of the label, but it doens't seem to rotate the size of the label, so the text ends up overlapping other widgets and/or getting truncated if it is near the edge of the window.

I've also tried implementing a custom measure method that swaps the orientation, and a custom size_allocate that passes in a rotated transform to gtk_widget_allocate on the child widget. But then the text doesn't show up at all. I think because it is rotating around the origin instead of the center. Rotating around the center would require knowing the actual size of the widget.

I've also seen suggestions to use pango directly, but that adds significant complexity, and requires calculating the size of the rotated text myself.

Surely there is a simpler way to accomplish this?

I'd really love to see a working example of how to do this.

2 Answers 2

2

There are certainly several variants to rotate a widget. In my variant, I created my own class "rotated_label", which I can then use flexibly.

class_rotated_label.h

// class-rotated-label.h
 
#pragma once
#include<gtk/gtk.h>

#define  ROTATED_TYPE_LABEL (rotated_label_get_type())

G_DECLARE_FINAL_TYPE (RotatedLabel, rotated_label,  ROTATED, LABEL, GtkWidget)

GtkWidget *rotated_label_new(int angle, const char *text);

class_rotated_label.c

 
 //  class-rotated-label.c

#include"class-rotated-label.h"
#include<math.h>

struct _RotatedLabel
{
  GtkWidget parent_instance;
  GtkWidget *label;
  int angle;
  int flag;
};
 
struct _RotatedLabelClass
{
  GtkWidgetClass parent_class;
};
 
G_DEFINE_TYPE (RotatedLabel, rotated_label, GTK_TYPE_WIDGET)
 
static void
rotated_label_init(RotatedLabel *self)
{
    self->label = gtk_label_new("");
            
    self->flag = 0;

    gtk_widget_set_parent(GTK_WIDGET(self->label),GTK_WIDGET(self));

    // LayoutManager
    GtkLayoutManager *center_layout;
    center_layout = gtk_widget_get_layout_manager (GTK_WIDGET(self));
    gtk_center_layout_set_start_widget (GTK_CENTER_LAYOUT (center_layout),self->label);
}
 
static void
rotated_label_snapshot (GtkWidget   *widget,
                   GtkSnapshot *snapshot)
{
  RotatedLabel *self = ROTATED_LABEL (widget);
  int angle = self->angle;

  GtkWidget *child;
  child = gtk_widget_get_first_child(widget);
  int child_width,  child_height;

  child_width = gtk_widget_get_width (child);
  child_height = gtk_widget_get_height(child);

  gtk_snapshot_translate(snapshot, &(graphene_point_t){child_width / 2,child_height /2});
  gtk_snapshot_rotate (snapshot,angle);
  gtk_snapshot_translate(snapshot, &(graphene_point_t){- child_width / 2,- child_height /2.});
       
  gtk_widget_snapshot_child (widget, child, snapshot);

  // Calculate the necessary height of the widget
  double radiant = angle * (M_PI / 180 );
  double sinus = sin(radiant);
  double cosinus = cos(radiant);
  double result = child_width * ABS(sinus) + child_height * ABS(cosinus);

  // only apply once during initialization
  if (self->flag == 0)
  { 
     gtk_widget_set_size_request(GTK_WIDGET(self),-1,result);
     self->flag = 1;
  }
}

static void rotated_label_dispose(GObject *gobject)
{
    RotatedLabel *self = ROTATED_LABEL(gobject);
    g_clear_pointer(&self->label,gtk_widget_unparent);
    G_OBJECT_CLASS (rotated_label_parent_class)->dispose;
}

static void
rotated_label_class_init (RotatedLabelClass *class)
{
  G_OBJECT_CLASS(class)->dispose = rotated_label_dispose;   
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
 
  widget_class->snapshot = rotated_label_snapshot;

  gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_CENTER_LAYOUT);
}
 
GtkWidget *
rotated_label_new (int angle, const char* text)
{
RotatedLabel *self;
 
  self = g_object_new (ROTATED_TYPE_LABEL,NULL);
  self->angle = angle;  
  gtk_label_set_text(GTK_LABEL(self->label),text);

  return GTK_WIDGET (self);
 }
 

One possible application could then be as follows:

 
//   rotated-label.c
  
#include"rotated-label.h"
#include"class-rotated-label.h"

void activate (GtkApplication *app, gpointer data)
{
  GtkWidget *window;

  window =gtk_application_window_new(app);

 GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL,0);

 GtkWidget *label = rotated_label_new(0,"- first -"); 
 GtkWidget *label1 = rotated_label_new(45,"- second -");
 GtkWidget *label2 = rotated_label_new(90,"- third -");
 GtkWidget *label3 = rotated_label_new(135,"- fourth -");
 GtkWidget *label4 = rotated_label_new(180,"- fifth -");
 gtk_box_append(GTK_BOX(box),label);
 gtk_box_append(GTK_BOX(box),label1);
 gtk_box_append(GTK_BOX(box),label2);
 gtk_box_append(GTK_BOX(box),label3);
 gtk_box_append(GTK_BOX(box),label4);

 gtk_window_set_child(GTK_WINDOW(window),box);

 gtk_widget_set_visible(window,TRUE);
}

The result looks like this:

rotated label

I should perhaps add that I wanted to show just one of the principles. The example is of course still expandable.

Have fun programming.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks, this rotates the text nicely indeed. However, the size of the widget is not adjusted accordingly I believe, which messes up the layout if one uses this in a GUI.
1

I feel your pain! There is no easy solution for rotating a label in Gtk4, unfortunately. For me, the Pango solution was easiest to implement, because in Holger's solution one would need to subclass the GtkLayoutManager to size the widget correctly, if I'm not mistaken.

So if you don't need an actual GtkLabel, and a plain widget with some vertical text will do, then you could use Pango to draw some text in a GtkDrawingArea, like in my example below. I used two passes of some custom drawing function, in which the first pass merely calculates the extent of the text. This allows setting the exact size of the drawing area, before doing the actual drawing in a second pass.

enter image description here

This is the full code, as some kind of "hello world" example

#include <gtk/gtk.h>
#include <string>

struct vertical_text_t {
    std::string text;
    int w = -1;
    int h = -1;
};

static vertical_text_t vt{"Vertical text"};

void draw_vertical_text(GtkDrawingArea *drawing_area, cairo_t *cr, int w_widget, int /*h_widget*/, gpointer data) {
    PangoContext *context = gtk_widget_create_pango_context(GTK_WIDGET(drawing_area));
    PangoLayout *layout = pango_layout_new(context);

    vertical_text_t *vt = (vertical_text_t*)data;
    pango_layout_set_text(layout, vt->text.c_str(), -1);
    PangoFontDescription *desc = pango_font_description_from_string("Sans 18"); // Same font and size as in our CSS file
    pango_layout_set_font_description(layout, desc);

    int w_text, h_text;
    pango_layout_get_pixel_size(layout, &w_text, &h_text);

    if (vt->w == -1) { // On a first pass, we'll only calculate the dimensions of the text (before rotation) and return
        vt->w = w_text;
        vt->h = h_text;
    } else { // On a second pass, we'll actually do some drawing
        cairo_rotate(cr, -G_PI/2); // Rotate the text 90 degrees counter clockwise

        // Align the text to the upper-left corner
        cairo_move_to(cr, -w_text, 0); // Vertical, horizontal (because the axes are swapped due to the rotation);

        cairo_set_source_rgba(cr, 0, 0, 0, 1.0); // Black
        pango_cairo_show_layout(cr, layout);
    }

    pango_font_description_free(desc);
    g_object_unref(layout);
    g_object_unref(context);
}

static void activate(GtkApplication *app, gpointer user_data) {
    GtkWidget *window = gtk_application_window_new(app);
    gtk_window_set_title(GTK_WINDOW(window), "Vertical text demo");
    gtk_window_set_default_size(GTK_WINDOW(window), 300, 300);

    GtkWidget *vertical_label = gtk_drawing_area_new();
    draw_vertical_text(GTK_DRAWING_AREA(vertical_label), nullptr, 0, 0, &vt); // First pass; to calculate the size of the text; we only need to pass the drawing area and the vertical text struct
    gtk_widget_set_size_request(vertical_label, vt.h, vt.w); // Adjust the size request based upon the results from the first pass
    gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(vertical_label), draw_vertical_text, &vt, nullptr); // Second pass will be triggered when the widget is drawn

    gtk_widget_set_halign(vertical_label, GTK_ALIGN_START);
    gtk_widget_set_valign(vertical_label, GTK_ALIGN_START);

    gtk_window_set_child(GTK_WINDOW(window), vertical_label);
    gtk_window_present(GTK_WINDOW(window));
}

int main(int argc, char** argv, char** env) {
    GtkApplication *app;

    app = gtk_application_new("org.gtk.example", G_APPLICATION_DEFAULT_FLAGS);
    g_signal_connect(app, "activate", G_CALLBACK(activate), nullptr);
    int status = g_application_run(G_APPLICATION(app), argc, argv);
    g_object_unref(app);

    return status;
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.