diff --git a/data/darktableconfig.xml.in b/data/darktableconfig.xml.in index d4bf3bb7cc87..12952516b6df 100644 --- a/data/darktableconfig.xml.in +++ b/data/darktableconfig.xml.in @@ -2398,6 +2398,13 @@ enabled: the mouse wheel scrolls the modules panel and with Ctrl+Alt adjusts a control's value\n\ndisabled: the mouse wheel adjusts the control under the pointer and with Ctrl+Alt scrolls the panel.]]> + + darkroom/ui/touchpad_gestures + bool + true + enable touchpad gestures in darkroom + enabled: touchpad pinch gestures zoom the image and two-finger touchpad scrolling pans it; Ctrl+scroll still uses the legacy zoom behavior. disabled: touchpad gestures are ignored and darkroom falls back to the legacy scroll behavior, including Ctrl+scroll for zooming in and out.]]> + plugins/darkroom/ui/border_size int diff --git a/src/gui/gtk.c b/src/gui/gtk.c index e498c0c9488a..5c1c7a9dfd35 100644 --- a/src/gui/gtk.c +++ b/src/gui/gtk.c @@ -726,6 +726,21 @@ static gboolean _draw(GtkWidget *da, } static GdkDevice *_touchpad = NULL; +static gboolean _touchpad_gestures_enabled(void) +{ + // If conf_gen.h was built before darktableconfig.xml.in gained this key + // (incremental build without cmake reconfigure), dt_confgen_value_exists + // returns FALSE and dt_conf_get_bool gets an empty string โ†’ FALSE. + // Default to enabled in that case so a stale build doesn't silently break gestures. + if(!dt_confgen_value_exists("darkroom/ui/touchpad_gestures", DT_DEFAULT)) + { + dt_print(DT_DEBUG_INPUT, + "[touchpad] 'darkroom/ui/touchpad_gestures' missing from confgen" + " (stale conf_gen.h โ€” run cmake reconfigure), defaulting to enabled"); + return TRUE; + } + return dt_conf_get_bool("darkroom/ui/touchpad_gestures"); +} static gboolean _input_event(GtkWidget *widget, GdkEvent *event, @@ -738,20 +753,45 @@ static gboolean _input_event(GtkWidget *widget, case GDK_TOUCHPAD_PINCH: case GDK_TOUCHPAD_SWIPE: _touchpad = gdk_event_get_source_device(event); + if(_touchpad) + { + dt_print(DT_DEBUG_INPUT, + "[touchpad] gesture event type=%d source='%s' source_type=%d", + event->type, + gdk_device_get_name(_touchpad), + gdk_device_get_source(_touchpad)); + } + else + { + dt_print(DT_DEBUG_INPUT, + "[touchpad] gesture event type=%d without source device", + event->type); + } break; default: break; } - if(event->type == GDK_TOUCHPAD_PINCH) + if(event->type == GDK_TOUCHPAD_PINCH && _touchpad_gestures_enabled()) { const GdkEventTouchpadPinch *pinch = &event->touchpad_pinch; + dt_print(DT_DEBUG_INPUT, + "[touchpad] pinch x=%.2f y=%.2f phase=%d scale=%.6f state=0x%x", + pinch->x, pinch->y, pinch->phase, pinch->scale, pinch->state); if(dt_view_manager_gesture_pinch(darktable.view_manager, pinch->x, pinch->y, pinch->phase, pinch->scale, pinch->state & 0xf)) { gtk_widget_queue_draw(widget); return TRUE; } + + dt_print(DT_DEBUG_INPUT, + "[touchpad] pinch ignored by current view"); + } + else if(event->type == GDK_TOUCHPAD_PINCH) + { + dt_print(DT_DEBUG_INPUT, + "[touchpad] pinch received but disabled by preference darkroom/ui/touchpad_gestures"); } return FALSE; @@ -763,14 +803,25 @@ static gboolean _scrolled(GtkWidget *widget, { (void)user_data; GdkDevice *device = gdk_event_get_source_device((GdkEvent *)event); - - if(((device && gdk_device_get_source(device) == GDK_SOURCE_TOUCHPAD) - || device == _touchpad) + const gboolean touchpad_enabled = _touchpad_gestures_enabled(); + const gboolean ctrl_pressed = dt_modifier_is(event->state, GDK_CONTROL_MASK); + const gboolean is_touchpad_source = device && gdk_device_get_source(device) == GDK_SOURCE_TOUCHPAD; + const gboolean matches_last_gesture_device = (device == _touchpad); + + if(touchpad_enabled + && !ctrl_pressed + && (is_touchpad_source || matches_last_gesture_device) && event->direction == GDK_SCROLL_SMOOTH && !event->is_stop) { gdouble delta_x = 0.0, delta_y = 0.0; if(!dt_gui_get_scroll_deltas(event, &delta_x, &delta_y)) + { + dt_print(DT_DEBUG_INPUT, + "[touchpad] smooth scroll ignored (likely pointer emulated), source='%s' source_type=%d", + device ? gdk_device_get_name(device) : "", + device ? gdk_device_get_source(device) : -1); return TRUE; + } delta_x *= DT_UI_SCROLL_SMOOTH_DELTA_SCALE; delta_y *= DT_UI_SCROLL_SMOOTH_DELTA_SCALE; @@ -778,14 +829,41 @@ static gboolean _scrolled(GtkWidget *widget, && dt_view_manager_gesture_pan(darktable.view_manager, event->x, event->y, delta_x, delta_y, event->state & 0xf)) { + dt_print(DT_DEBUG_INPUT, + "[touchpad] pan x=%.2f y=%.2f dx=%.3f dy=%.3f source='%s'", + event->x, event->y, delta_x, delta_y, + device ? gdk_device_get_name(device) : ""); gtk_widget_queue_draw(widget); return TRUE; } + else if(delta_x != 0.0 || delta_y != 0.0) + { + dt_print(DT_DEBUG_INPUT, + "[touchpad] pan not handled by current view (no gesture_pan handler?)" + " dx=%.3f dy=%.3f", + delta_x, delta_y); + } + } + else if(event->direction == GDK_SCROLL_SMOOTH && !event->is_stop) + { + dt_print(DT_DEBUG_INPUT, + "[touchpad] smooth scroll not treated as pan: enabled=%d ctrl=%d touchpad_source=%d matches_last_gesture=%d source='%s' source_type=%d", + touchpad_enabled, + ctrl_pressed, + is_touchpad_source, + matches_last_gesture_device, + device ? gdk_device_get_name(device) : "", + device ? gdk_device_get_source(device) : -1); } int delta_y; if(dt_gui_get_scroll_unit_delta(event, &delta_y)) { + dt_print(DT_DEBUG_INPUT, + "[scroll] discrete fallback x=%.2f y=%.2f up=%d state=0x%x source='%s' source_type=%d", + event->x, event->y, delta_y < 0, event->state, + device ? gdk_device_get_name(device) : "", + device ? gdk_device_get_source(device) : -1); dt_view_manager_scrolled(darktable.view_manager, event->x, event->y, delta_y < 0, event->state & 0xf); diff --git a/src/views/darkroom.c b/src/views/darkroom.c index 98bfb8fc9be6..205852be4293 100644 --- a/src/views/darkroom.c +++ b/src/views/darkroom.c @@ -4192,7 +4192,7 @@ gboolean gesture_pinch(dt_view_t *self, { dt_develop_t *dev = self->data; if(!dev) return FALSE; - const gboolean constrained = !dt_modifier_is(state, GDK_CONTROL_MASK); + const double pinch_step_ratio = 1.1; static double pinch_last_scale = 0.0; @@ -4200,17 +4200,35 @@ gboolean gesture_pinch(dt_view_t *self, if(phase == GDK_TOUCHPAD_GESTURE_PHASE_BEGIN) { pinch_last_scale = scale > 0.0 ? scale : 1.0; + dt_print(DT_DEBUG_INPUT, + "[darkroom pinch] begin x=%.2f y=%.2f scale=%.6f state=0x%x last=%.6f", + x, y, scale, state, pinch_last_scale); return TRUE; } else if(phase == GDK_TOUCHPAD_GESTURE_PHASE_END || phase == GDK_TOUCHPAD_GESTURE_PHASE_CANCEL) { + dt_print(DT_DEBUG_INPUT, + "[darkroom pinch] end/cancel phase=%d x=%.2f y=%.2f scale=%.6f state=0x%x", + phase, x, y, scale, state); pinch_last_scale = 0.0; return TRUE; } - if(phase != GDK_TOUCHPAD_GESTURE_PHASE_UPDATE) return FALSE; - if(pinch_last_scale <= 0.0 || scale <= 0.0) return FALSE; + if(phase != GDK_TOUCHPAD_GESTURE_PHASE_UPDATE) + { + dt_print(DT_DEBUG_INPUT, + "[darkroom pinch] ignored phase=%d x=%.2f y=%.2f scale=%.6f state=0x%x", + phase, x, y, scale, state); + return FALSE; + } + if(pinch_last_scale <= 0.0 || scale <= 0.0) + { + dt_print(DT_DEBUG_INPUT, + "[darkroom pinch] invalid scale update last=%.6f scale=%.6f", + pinch_last_scale, scale); + return FALSE; + } const double ratio = scale / pinch_last_scale; int zoom_step = -1; @@ -4221,9 +4239,18 @@ gboolean gesture_pinch(dt_view_t *self, if(zoom_step >= 0) { - dt_dev_zoom_move(&dev->full, DT_ZOOM_SCROLL, 0.0f, zoom_step, x, y, constrained); + dt_print(DT_DEBUG_INPUT, + "[darkroom pinch] zoom step=%d x=%.2f y=%.2f ratio=%.6f scale=%.6f last=%.6f", + zoom_step, x, y, ratio, scale, pinch_last_scale); + dt_dev_zoom_move(&dev->full, DT_ZOOM_SCROLL, 0.0f, zoom_step, x, y, FALSE); pinch_last_scale = scale; } + else + { + dt_print(DT_DEBUG_INPUT, + "[darkroom pinch] below threshold ratio=%.6f scale=%.6f last=%.6f", + ratio, scale, pinch_last_scale); + } return TRUE; }