diff -r 64ded9f6a6c6 -r 6606616eca9f ui/gtk/image.c --- a/ui/gtk/image.c Tue Feb 25 21:11:00 2025 +0100 +++ b/ui/gtk/image.c Sat Apr 05 16:46:11 2025 +0200 @@ -33,59 +33,265 @@ #include "../common/context.h" #include "../common/object.h" +static void imageviewer_destroy(UiImageViewer *iv) { + if(iv->pixbuf) { + g_object_unref(iv->pixbuf); + } + free(iv); +} + +#if GTK_MAJOR_VERSION >= 4 + +static void imageviewer_draw( + GtkDrawingArea *drawingarea, + cairo_t *cr, + int width, + int height, + gpointer userdata) +{ + ui_cairo_draw_image(userdata, cr, width, height); +} + +#else + +static gboolean imageviewer_draw(GtkWidget *widget, cairo_t *cr, gpointer userdata) { + int width = gtk_widget_get_allocated_width(widget); + int height = gtk_widget_get_allocated_height(widget); + ui_cairo_draw_image(userdata, cr, width, height); + return FALSE; +} + +#endif UIWIDGET ui_imageviewer_create(UiObject *obj, UiImageViewerArgs args) { UiObject *current = uic_current_obj(obj); - GtkWidget *scrolledwindow = SCROLLEDWINDOW_NEW(); -#if GTK_CHECK_VERSION(4, 0, 0) - GtkWidget *image = gtk_picture_new(); -#else - GtkWidget *image = gtk_image_new(); -#endif - - ui_set_name_and_style(image, args.name, args.style_class); + GtkWidget *drawingarea = gtk_drawing_area_new(); + GtkWidget *toplevel; + GtkWidget *widget = drawingarea; + + gtk_widget_set_size_request(drawingarea, 100, 100); #if GTK_MAJOR_VERSION < 4 GtkWidget *eventbox = gtk_event_box_new(); - SCROLLEDWINDOW_SET_CHILD(scrolledwindow, eventbox); - gtk_container_add(GTK_CONTAINER(eventbox), image); -#else - SCROLLEDWINDOW_SET_CHILD(scrolledwindow, image); - GtkWidget *eventbox = image; + gtk_container_add(GTK_CONTAINER(eventbox), drawingarea); + widget = eventbox; #endif - UI_APPLY_LAYOUT1(current, args); - current->container->add(current->container, scrolledwindow, TRUE); + if(args.scrollarea) { + toplevel = SCROLLEDWINDOW_NEW(); + SCROLLEDWINDOW_SET_CHILD(toplevel, widget); + args.adjustwidgetsize = TRUE; + } else { + toplevel = widget; + } + + UiImageViewer *imgviewer = malloc(sizeof(UiImageViewer)); + memset(imgviewer, 0, sizeof(UiImageViewer)); + imgviewer->obj = obj; + imgviewer->onbuttonpress = args.onbuttonpress; + imgviewer->onbuttonpressdata = args.onbuttonpressdata; + imgviewer->onbuttonrelease = args.onbuttonrelease; + imgviewer->onbuttonreleasedata = args.onbuttonreleasedata; + if(args.image_padding > 0) { + imgviewer->padding_left = args.image_padding; + imgviewer->padding_right = args.image_padding; + imgviewer->padding_top = args.image_padding; + imgviewer->padding_bottom = args.image_padding; + } else { + imgviewer->padding_left = args.image_padding_left; + imgviewer->padding_right = args.image_padding_right; + imgviewer->padding_top = args.image_padding_top; + imgviewer->padding_bottom = args.image_padding_bottom; + } + imgviewer->adjustwidgetsize = args.adjustwidgetsize; + imgviewer->autoscale = args.autoscale; + imgviewer->useradjustable = args.useradjustable; + imgviewer->zoom_scale = 20; + + g_object_set_data_full(G_OBJECT(drawingarea), "uiimageviewer", imgviewer, (GDestroyNotify)imageviewer_destroy); UiVar *var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_GENERIC); + imgviewer->var = var; + imgviewer->widget = drawingarea; + if(var) { UiGeneric *value = var->value; value->get = ui_imageviewer_get; value->get_type = ui_imageviewer_get_type; value->set = ui_imageviewer_set; - value->obj = image; + value->obj = imgviewer; if(value->value && value->type && !strcmp(value->type, UI_IMAGE_OBJECT_TYPE)) { GdkPixbuf *pixbuf = value->value; value->value = NULL; ui_imageviewer_set(value, pixbuf, UI_IMAGE_OBJECT_TYPE); + g_object_unref(pixbuf); } } - if(args.contextmenu) { - UIMENU menu = ui_contextmenu_create(args.contextmenu, obj, eventbox); - ui_widget_set_contextmenu(eventbox, menu); +#if GTK_MAJOR_VERSION >= 4 + gtk_drawing_area_set_draw_func( + GTK_DRAWING_AREA(drawingarea), + imageviewer_draw, + imgviewer, + NULL); + + if(args.useradjustable) { + gtk_widget_set_focusable(drawingarea, TRUE); } - return scrolledwindow; + GtkEventController *scrollcontroller = gtk_event_controller_scroll_new(GTK_EVENT_CONTROLLER_SCROLL_VERTICAL); + g_signal_connect(scrollcontroller, "scroll", G_CALLBACK(ui_imageviewer_scroll), imgviewer); + gtk_widget_add_controller(GTK_WIDGET(drawingarea), GTK_EVENT_CONTROLLER(scrollcontroller)); + + GtkGesture *drag = gtk_gesture_drag_new(); + g_signal_connect(drag, "drag-begin", G_CALLBACK(ui_imageviewer_drag_begin_cb), imgviewer); + g_signal_connect(drag, "drag-end", G_CALLBACK(ui_imageviewer_drag_end_cb), imgviewer); + g_signal_connect(drag, "drag-update", G_CALLBACK(ui_imageviewer_drag_update_cb), imgviewer); + gtk_widget_add_controller(GTK_WIDGET(drawingarea), GTK_EVENT_CONTROLLER(drag)); + + GtkGesture *click = gtk_gesture_click_new(); + g_signal_connect(click, "pressed", G_CALLBACK(ui_imageviewer_pressed_cb), imgviewer); + g_signal_connect(click, "released", G_CALLBACK(ui_imageviewer_released_cb), imgviewer); + gtk_widget_add_controller(GTK_WIDGET(drawingarea), GTK_EVENT_CONTROLLER(click)); + +#elif GTK_MAJOR_VERSION == 3 + g_signal_connect( + drawingarea, + "draw", + G_CALLBACK(imageviewer_draw), + imgviewer); + + gtk_widget_add_events(eventbox, GDK_SCROLL_MASK); + + g_signal_connect( + eventbox, + "scroll-event", + G_CALLBACK(ui_imageviewer_scroll_event), + imgviewer); + g_signal_connect( + eventbox, + "button-press-event", + G_CALLBACK(ui_imageviewer_button_press_event), + imgviewer); + g_signal_connect( + eventbox, + "button-release-event", + G_CALLBACK(ui_imageviewer_button_release_event), + imgviewer); + +#endif + + if(args.contextmenu) { + UIMENU menu = ui_contextmenu_create(args.contextmenu, obj, widget); + ui_widget_set_contextmenu(widget, menu); + } + + UI_APPLY_LAYOUT1(current, args); + current->container->add(current->container, toplevel, TRUE); + + return toplevel; +} + +static void imageviewer_reset(UiImageViewer *imgviewer) { + imgviewer->isautoscaled = FALSE; + imgviewer->transx = 0; + imgviewer->transy; + imgviewer->begin_transx = 0; + imgviewer->begin_transy = 0; + imgviewer->scale = 1; + imgviewer->user_scale = 1; +} + +UIWIDGET ui_imageviewer_reset(UIWIDGET w) { + UiImageViewer *imgviewer = g_object_get_data(G_OBJECT(w), "uiimageviewer"); + if(imgviewer) { + imageviewer_reset(imgviewer); + gtk_widget_queue_draw(w); + } +} + +UIWIDGET ui_imageviewer_set_autoscale(UIWIDGET w, UiBool set) { + UiImageViewer *imgviewer = g_object_get_data(G_OBJECT(w), "uiimageviewer"); + if(imgviewer) { + imgviewer->autoscale = set; + } +} + +UIWIDGET ui_imageviewer_set_adjustwidgetsize(UIWIDGET w, UiBool set) { + UiImageViewer *imgviewer = g_object_get_data(G_OBJECT(w), "uiimageviewer"); + if(imgviewer) { + imgviewer->adjustwidgetsize = set; + } +} + +UIWIDGET ui_imageviewer_set_useradjustable(UIWIDGET w, UiBool set) { + UiImageViewer *imgviewer = g_object_get_data(G_OBJECT(w), "uiimageviewer"); + if(imgviewer) { + imgviewer->useradjustable = set; + } +} + +void ui_cairo_draw_image(UiImageViewer *imgviewer, cairo_t *cr, int width, int height) { + if(!imgviewer->pixbuf) { + return; + } + + GdkPixbuf *pixbuf = imgviewer->pixbuf; + double dpixwidth = (double)gdk_pixbuf_get_width(pixbuf); + double dpixheight = (double)gdk_pixbuf_get_height(pixbuf); + + double dwidth = width; + double dheight = height; + double scale = 1; + // if autoscale is enabled, scale the image to fill available space + // if useradjustable is also enabled, the autoscaling is only done once + if(imgviewer->autoscale && imgviewer->scale != 0) { + if(!imgviewer->isautoscaled) { + scale = dwidth / dpixwidth; + if(dpixheight * scale > dheight) { + scale = dheight / dpixheight; + } + + if(imgviewer->useradjustable) { + imgviewer->isautoscaled = TRUE; + } + + imgviewer->scale = scale; + } else { + scale = imgviewer->scale; + } + + imgviewer->user_scale = scale; + } else { + // user-adjusted scaling + //scale = 1 + ((double)imgviewer->zoom / (double)imgviewer->zoom_scale); + scale = imgviewer->user_scale; + } + + dpixwidth *= scale; + dpixheight *= scale; + double x = (dwidth - dpixwidth) / 2; + double y = (dheight - dpixheight) / 2; + + x += imgviewer->transx; + y += imgviewer->transy; + + cairo_translate(cr, x, y); + cairo_scale(cr, scale, scale); + + gdk_cairo_set_source_pixbuf(cr, pixbuf, 0, 0); + cairo_paint(cr); } void* ui_imageviewer_get(UiGeneric *g) { + UiImageViewer *imgviewer = g->obj; + g->value = imgviewer->pixbuf; return g->value; } const char* ui_imageviewer_get_type(UiGeneric *g) { - + return UI_IMAGE_OBJECT_TYPE; } int ui_imageviewer_set(UiGeneric *g, void *value, const char *type) { @@ -93,25 +299,25 @@ return 1; } - // TODO: do we need to free the previous value here? + GdkPixbuf *pixbuf = value; + g_object_ref(pixbuf); + + UiImageViewer *imgviewer = g->obj; + g->value = pixbuf; - g->value = value; - g->type = type; - GdkPixbuf *pixbuf = value; + imageviewer_reset(imgviewer); - if(pixbuf) { + if(imgviewer->pixbuf) { + g_object_unref(imgviewer->pixbuf); + } + imgviewer->pixbuf = pixbuf; + + if(imgviewer->adjustwidgetsize && !imgviewer->autoscale) { int width = gdk_pixbuf_get_width(pixbuf); int height = gdk_pixbuf_get_height(pixbuf); - -#if GTK_CHECK_VERSION(4, 0, 0) - GdkTexture *texture = gdk_texture_new_for_pixbuf(pixbuf); - gtk_picture_set_paintable(GTK_PICTURE(g->obj), GDK_PAINTABLE(texture)); -#else - gtk_image_set_from_pixbuf(GTK_IMAGE(g->obj), pixbuf); -#endif - gtk_widget_set_size_request(g->obj, width, height); + gtk_widget_set_size_request(imgviewer->widget, width, height); } - + gtk_widget_queue_draw(imgviewer->widget); return 0; } @@ -127,9 +333,147 @@ if(obj->set) { obj->set(obj, pixbuf, UI_IMAGE_OBJECT_TYPE); + g_object_unref(pixbuf); } else { obj->value = pixbuf; } - + return 0; } + +void ui_image_ref(UIIMAGE img) { + g_object_ref(img); +} + +void ui_image_unref(UIIMAGE img) { + g_object_unref(img); +} + +#if GTK_MAJOR_VERSION >= 4 + +gboolean ui_imageviewer_scroll( + GtkEventControllerScroll *widget, + gdouble dx, + gdouble dy, + gpointer userdata) +{ + UiImageViewer *imgviewer = userdata; + if(imgviewer->useradjustable) { + double step = dy / imgviewer->zoom_scale; + if(imgviewer->user_scale - step > 0) { + imgviewer->user_scale -= step; + } + + imgviewer->scale = 0; // disable autoscale + gtk_widget_queue_draw(imgviewer->widget); + return TRUE; + } + return FALSE; +} + +void ui_imageviewer_drag_begin_cb( + GtkGestureDrag *self, + gdouble start_x, + gdouble start_y, + gpointer userdata) +{ + UiImageViewer *imgviewer = userdata; + imgviewer->begin_transx = imgviewer->transx; + imgviewer->begin_transy = imgviewer->transy; +} + +void ui_imageviewer_drag_end_cb( + GtkGestureDrag* self, + gdouble x, + gdouble y, + gpointer userdata) +{ + +} + +void ui_imageviewer_drag_update_cb( + GtkGestureDrag *self, + gdouble x, + gdouble y, + gpointer userdata) +{ + UiImageViewer *imgviewer = userdata; + if(imgviewer->useradjustable) { + imgviewer->transx = imgviewer->begin_transx + x; + imgviewer->transy = imgviewer->begin_transy + y; + gtk_widget_queue_draw(imgviewer->widget); + } +} + +static void imgviewer_button_event( + GtkGestureClick *gesture, + UiImageViewer *imgviewer, + ui_callback callback, + void *userdata) +{ + UiEvent event; + event.obj = imgviewer->obj; + event.window = event.obj->window; + event.document = event.obj->ctx->document; + event.eventdata = NULL; + event.intval = gtk_gesture_single_get_current_button(GTK_GESTURE_SINGLE(gesture)); + event.set = 0; + callback(&event, userdata); +} + +void ui_imageviewer_pressed_cb( + GtkGestureClick *self, + gint n_press, + gdouble x, + gdouble y, + gpointer userdata) +{ + UiImageViewer *imgviewer = userdata; + if(imgviewer->onbuttonpress) { + imgviewer_button_event(self, imgviewer, imgviewer->onbuttonpress, imgviewer->onbuttonpressdata); + } +} + +void ui_imageviewer_released_cb( + GtkGestureClick *self, + gint n_press, + gdouble x, + gdouble y, + gpointer userdata) +{ + UiImageViewer *imgviewer = userdata; + if(imgviewer->onbuttonrelease) { + imgviewer_button_event(self, imgviewer, imgviewer->onbuttonrelease, imgviewer->onbuttonreleasedata); + } +} + +#else + +gboolean ui_imageviewer_scroll_event( + GtkWidget *widget, + GdkEventScroll event, + gpointer userdata) +{ + // TODO + return FALSE; +} + +gboolean ui_imageviewer_button_press_event( + GtkWidget *widget, + GdkEventButton event, + gpointer userdata) +{ + // TODO + return FALSE; +} + +gboolean ui_imageviewer_button_release_event( + GtkWidget *widget, + GdkEventButton event, + gpointer userdata) +{ + // TODO + return FALSE; +} + +#endif