Thayne
Thayne

Reputation: 7012

How to rotate a widget in Gtk4

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.

Upvotes: 0

Views: 53

Answers (1)

Holger
Holger

Reputation: 1020

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.

Upvotes: 0

Related Questions