#include "aspect_container.h" struct _AspectContainer { GtkWidget parent_instance; float aspect_ratio; int max_width; }; enum { PROP_0, PROP_ASPECT_RATIO, PROP_MAX_WIDTH, LAST_PROP }; static GParamSpec *props[LAST_PROP]; G_DEFINE_TYPE(AspectContainer, aspect_container, GTK_TYPE_WIDGET) static void aspect_container_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { AspectContainer *self = ASPECT_CONTAINER(object); switch (prop_id) { case PROP_ASPECT_RATIO: aspect_container_set_aspect_ratio(self, g_value_get_float(value)); break; case PROP_MAX_WIDTH: aspect_container_set_max_width(self, g_value_get_int(value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void aspect_container_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { AspectContainer *self = ASPECT_CONTAINER(object); switch (prop_id) { case PROP_ASPECT_RATIO: g_value_set_float(value, self->aspect_ratio); break; case PROP_MAX_WIDTH: g_value_set_int(value, self->max_width); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void aspect_container_dispose(GObject *object) { GtkWidget *widget = GTK_WIDGET(object); GtkWidget *child; while ((child = gtk_widget_get_first_child(widget))) gtk_widget_unparent(child); G_OBJECT_CLASS(aspect_container_parent_class)->dispose(object); } static GtkSizeRequestMode aspect_container_get_request_mode(GtkWidget *widget) { (void)widget; return GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH; } static void aspect_container_size_allocate(GtkWidget *widget, int width, int height, int baseline) { GtkWidget *child = gtk_widget_get_first_child(widget); if (!child) return; GtkAllocation alloc = { .x = 0, .y = 0, .width = width, .height = height, }; gtk_widget_size_allocate(child, &alloc, baseline); } static void aspect_container_measure(GtkWidget *widget, GtkOrientation orientation, int for_size, int *minimum, int *natural, int *minimum_baseline, int *natural_baseline) { AspectContainer *self = ASPECT_CONTAINER(widget); if (minimum_baseline) *minimum_baseline = -1; if (natural_baseline) *natural_baseline = -1; GtkWidget *child = gtk_widget_get_first_child(widget); gboolean has_child = child && gtk_widget_get_visible(child); if (orientation == GTK_ORIENTATION_HORIZONTAL) { if (has_child) gtk_widget_measure(child, GTK_ORIENTATION_HORIZONTAL, -1, NULL, NULL, NULL, NULL); int w = self->max_width > 0 ? self->max_width : 0; if (minimum) *minimum = w; if (natural) *natural = w; } else { if (for_size >= 0) { int w = self->max_width > 0 && for_size > self->max_width ? self->max_width : for_size; int h_min = (int)(w * self->aspect_ratio); int h_nat = (int)(w * self->aspect_ratio); if (minimum) *minimum = h_min; if (natural) *natural = h_nat; } else if (has_child) { int child_min_w = 0, child_nat_w = 0; gtk_widget_measure(child, GTK_ORIENTATION_HORIZONTAL, -1, &child_min_w, &child_nat_w, NULL, NULL); int min_w = child_min_w; int nat_w = child_nat_w >= child_min_w ? child_nat_w : child_min_w; int req_w = 0, req_h = 0; gtk_widget_get_size_request(widget, &req_w, &req_h); if (req_w > min_w) min_w = req_w; if (req_w > nat_w) nat_w = req_w; if (self->max_width > 0) { if (min_w > self->max_width) min_w = self->max_width; if (nat_w > self->max_width) nat_w = self->max_width; } int h_min = (int)(min_w * self->aspect_ratio); int h_nat = (int)(nat_w * self->aspect_ratio); if (minimum) *minimum = h_min; if (natural) *natural = h_nat; } else { if (minimum) *minimum = 0; if (natural) *natural = 0; } } } static void aspect_container_compute_expand(GtkWidget *widget, gboolean *hexpand_p, gboolean *vexpand_p) { GtkWidget *child = gtk_widget_get_first_child(widget); if (child) { *hexpand_p = gtk_widget_compute_expand(child, GTK_ORIENTATION_HORIZONTAL); *vexpand_p = gtk_widget_compute_expand(child, GTK_ORIENTATION_VERTICAL); } else { *hexpand_p = FALSE; *vexpand_p = FALSE; } } static void aspect_container_class_init(AspectContainerClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS(klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass); gobject_class->set_property = aspect_container_set_property; gobject_class->get_property = aspect_container_get_property; gobject_class->dispose = aspect_container_dispose; widget_class->get_request_mode = aspect_container_get_request_mode; widget_class->size_allocate = aspect_container_size_allocate; widget_class->measure = aspect_container_measure; widget_class->compute_expand = aspect_container_compute_expand; props[PROP_ASPECT_RATIO] = g_param_spec_float( "aspect-ratio", NULL, NULL, 0.0, G_MAXFLOAT, 1.0, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); props[PROP_MAX_WIDTH] = g_param_spec_int( "max-width", NULL, NULL, 0, G_MAXINT, 0, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); g_object_class_install_properties(gobject_class, LAST_PROP, props); gtk_widget_class_set_css_name(widget_class, "aspectcontainer"); gtk_widget_class_set_accessible_role(widget_class, GTK_ACCESSIBLE_ROLE_GENERIC); } static void aspect_container_init(AspectContainer *self) { self->aspect_ratio = 1.0; self->max_width = 0; } GtkWidget * aspect_container_new(float aspect_ratio) { return g_object_new(ASPECT_TYPE_CONTAINER, "aspect-ratio", aspect_ratio, NULL); } void aspect_container_set_aspect_ratio(AspectContainer *self, float ratio) { g_return_if_fail(ASPECT_IS_CONTAINER(self)); if (self->aspect_ratio == ratio) return; self->aspect_ratio = ratio; g_object_notify_by_pspec(G_OBJECT(self), props[PROP_ASPECT_RATIO]); gtk_widget_queue_resize(GTK_WIDGET(self)); } float aspect_container_get_aspect_ratio(AspectContainer *self) { g_return_val_if_fail(ASPECT_IS_CONTAINER(self), 1.0); return self->aspect_ratio; } void aspect_container_set_max_width(AspectContainer *self, int max_width) { g_return_if_fail(ASPECT_IS_CONTAINER(self)); if (self->max_width == max_width) return; self->max_width = max_width; g_object_notify_by_pspec(G_OBJECT(self), props[PROP_MAX_WIDTH]); gtk_widget_queue_resize(GTK_WIDGET(self)); } int aspect_container_get_max_width(AspectContainer *self) { g_return_val_if_fail(ASPECT_IS_CONTAINER(self), 0); return self->max_width; }