Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 62 additions & 4 deletions src/gui/gtk.c
Original file line number Diff line number Diff line change
Expand Up @@ -738,6 +738,11 @@ static gboolean _input_event(GtkWidget *widget,
case GDK_TOUCHPAD_PINCH:
case GDK_TOUCHPAD_SWIPE:
_touchpad = gdk_event_get_source_device(event);
dt_print(DT_DEBUG_INPUT,
"[touchpad] gesture-type event: type=%d device='%s' source-type=%d",
event->type,
_touchpad ? gdk_device_get_name(_touchpad) : "<none>",
_touchpad ? (int)gdk_device_get_source(_touchpad) : -1);
break;
default:
break;
Expand All @@ -746,12 +751,18 @@ static gboolean _input_event(GtkWidget *widget,
if(event->type == GDK_TOUCHPAD_PINCH)
{
const GdkEventTouchpadPinch *pinch = &event->touchpad_pinch;
dt_print(DT_DEBUG_INPUT,
"[touchpad] pinch phase=%d x=%.1f y=%.1f dx=%.3f dy=%.3f scale=%.6f state=0x%x",
pinch->phase, pinch->x, pinch->y, pinch->dx, pinch->dy,
pinch->scale, pinch->state);
if(dt_view_manager_gesture_pinch(darktable.view_manager, pinch->x, pinch->y,
pinch->phase, pinch->scale, pinch->state & 0xf))
pinch->dx, pinch->dy, pinch->phase,
pinch->scale, pinch->state & 0xf))
{
gtk_widget_queue_draw(widget);
return TRUE;
}
dt_print(DT_DEBUG_INPUT, "[touchpad] pinch not handled by current view");
}

return FALSE;
Expand All @@ -764,24 +775,71 @@ 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)
&& event->direction == GDK_SCROLL_SMOOTH && !event->is_stop)
dt_print(DT_DEBUG_INPUT,
"[scroll] direction=%d smooth=%s stop=%s ctrl=%s"
" x=%.1f y=%.1f dx=%.3f dy=%.3f state=0x%x"
" device='%s' source-type=%d",
event->direction,
event->direction == GDK_SCROLL_SMOOTH ? "yes" : "no",
event->is_stop ? "yes" : "no",
dt_modifier_is(event->state, GDK_CONTROL_MASK) ? "yes" : "no",
event->x, event->y, event->delta_x, event->delta_y, event->state,
device ? gdk_device_get_name(device) : "<none>",
device ? (int)gdk_device_get_source(device) : -1);

const gboolean ctrl_held = dt_modifier_is(event->state, GDK_CONTROL_MASK);
const gboolean is_touchpad_source = device && gdk_device_get_source(device) == GDK_SOURCE_TOUCHPAD;
const gboolean is_known_gesture_device = (device == _touchpad);
const gboolean is_smooth = event->direction == GDK_SCROLL_SMOOTH && !event->is_stop;

#ifdef GDK_WINDOWING_QUARTZ
// On macOS/Quartz, the built-in trackpad reports as GDK_SOURCE_MOUSE, not
// GDK_SOURCE_TOUCHPAD. Route every non-ctrl smooth scroll to gesture_pan so
// that two-finger panning works in views like darkroom (both standalone and
// interleaved with a pinch-zoom gesture whose translational component macOS
// delivers as a separate scroll stream).
const gboolean route_as_pan = !ctrl_held && is_smooth;
#else
const gboolean route_as_pan = !ctrl_held
&& (is_touchpad_source || is_known_gesture_device)
&& is_smooth;
#endif
if(route_as_pan)
{
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 skipped (pointer-emulated) device='%s' source-type=%d",
device ? gdk_device_get_name(device) : "<none>",
device ? (int)gdk_device_get_source(device) : -1);
return TRUE;
}

delta_x *= DT_UI_SCROLL_SMOOTH_DELTA_SCALE;
delta_y *= DT_UI_SCROLL_SMOOTH_DELTA_SCALE;
if((delta_x != 0.0 || delta_y != 0.0)
&& 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=%.1f y=%.1f dx=%.3f dy=%.3f state=0x%x device='%s'",
event->x, event->y, delta_x, delta_y, event->state,
device ? gdk_device_get_name(device) : "<none>");
gtk_widget_queue_draw(widget);
return TRUE;
}
}
else if(is_smooth)
{
dt_print(DT_DEBUG_INPUT,
"[touchpad] smooth scroll not routed as pan:"
" ctrl=%d touchpad_source=%d known_gesture_dev=%d route_as_pan=%d"
" device='%s' source-type=%d",
ctrl_held, is_touchpad_source, is_known_gesture_device, route_as_pan,
device ? gdk_device_get_name(device) : "<none>",
device ? (int)gdk_device_get_source(device) : -1);
}

int delta_y;
if(dt_gui_get_scroll_unit_delta(event, &delta_y))
Expand Down
91 changes: 72 additions & 19 deletions src/views/darkroom.c
Original file line number Diff line number Diff line change
Expand Up @@ -4168,62 +4168,115 @@ gboolean gesture_pan(dt_view_t *self,
// Mask editing (brush etc.) uses scroll for tool parameters.
if(dev->form_visible
&& !darktable.develop->darkroom_skip_mouse_events)
{
dt_print(DT_DEBUG_INPUT,
"[darkroom pan] ignored: mask form active");
return FALSE;
}

// Let active modules consume scroll for their own interactions (e.g. brush size).
if(dev->gui_module && dev->gui_module->scrolled
&& !darktable.develop->darkroom_skip_mouse_events
&& !dt_iop_color_picker_is_visible(dev)
&& dt_dev_modulegroups_test_activated(darktable.develop))
{
dt_print(DT_DEBUG_INPUT,
"[darkroom pan] ignored: active module '%s' consumes scroll",
dev->gui_module->name());
return FALSE;
}

if(dx == 0.0 && dy == 0.0) return FALSE;

dt_print(DT_DEBUG_INPUT,
"[darkroom pan] x=%.1f y=%.1f dx=%.3f dy=%.3f state=0x%x",
x, y, dx, dy, state);
dt_dev_zoom_move(&dev->full, DT_ZOOM_MOVE, 1.0f, 0, dx, dy, TRUE);
return TRUE;
}

gboolean gesture_pinch(dt_view_t *self,
const double x,
const double y,
const double dx,
const double dy,
const int phase,
const double scale,
const int state)
{
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;
(void)state;
static float pinch_begin_tscale = 0.0f;

if(phase == GDK_TOUCHPAD_GESTURE_PHASE_BEGIN)
{
pinch_last_scale = scale > 0.0 ? scale : 1.0;
pinch_begin_tscale =
dt_dev_get_zoom_scale(&dev->full, dev->full.zoom, 1 << dev->full.closeup, FALSE)
* dev->full.ppd;
dt_print(DT_DEBUG_INPUT,
"[darkroom pinch] begin x=%.1f y=%.1f scale=%.6f state=0x%x"
" -> begin_tscale=%.6f ppd=%.2f",
x, y, scale, state, pinch_begin_tscale, dev->full.ppd);
return TRUE;
}
else if(phase == GDK_TOUCHPAD_GESTURE_PHASE_END
|| phase == GDK_TOUCHPAD_GESTURE_PHASE_CANCEL)
{
pinch_last_scale = 0.0;
dt_print(DT_DEBUG_INPUT,
"[darkroom pinch] %s x=%.1f y=%.1f scale=%.6f state=0x%x",
phase == GDK_TOUCHPAD_GESTURE_PHASE_END ? "end" : "cancel",
x, y, scale, state);
pinch_begin_tscale = 0.0f;
return TRUE;
}

if(phase != GDK_TOUCHPAD_GESTURE_PHASE_UPDATE) return FALSE;
if(pinch_last_scale <= 0.0 || scale <= 0.0) return FALSE;

const double ratio = scale / pinch_last_scale;
int zoom_step = -1;
if(ratio > pinch_step_ratio)
zoom_step = 1;
else if(ratio < 1.0 / pinch_step_ratio)
zoom_step = 0;

if(zoom_step >= 0)
if(phase != GDK_TOUCHPAD_GESTURE_PHASE_UPDATE)
{
dt_dev_zoom_move(&dev->full, DT_ZOOM_SCROLL, 0.0f, zoom_step, x, y, constrained);
pinch_last_scale = scale;
dt_print(DT_DEBUG_INPUT,
"[darkroom pinch] unknown phase=%d ignored", phase);
return FALSE;
}
if(pinch_begin_tscale <= 0.0f || scale <= 0.0)
{
dt_print(DT_DEBUG_INPUT,
"[darkroom pinch] update skipped: begin_tscale=%.6f scale=%.6f",
pinch_begin_tscale, scale);
return FALSE;
}

// On macOS (GDK Quartz), NSEventTypeMagnify never populates dx/dy and the
// gesture focal-point x/y is set once at phase=BEGIN and does not update
// during the gesture — so both approaches to infer translation are zero.
// Pan on macOS therefore arrives as a separate smooth-scroll stream which is
// routed to gesture_pan by _scrolled() in gtk.c.
// On other platforms (Wayland/X11), dx/dy carry the actual translational delta.
const double eff_dx = dx;
const double eff_dy = dy;

if(eff_dx != 0.0 || eff_dy != 0.0)
{
dt_print(DT_DEBUG_INPUT,
"[darkroom pinch] pan component eff_dx=%.3f eff_dy=%.3f (combined with scale)",
eff_dx, eff_dy);
dt_dev_zoom_move(&dev->full, DT_ZOOM_MOVE, 1.0f, 0, eff_dx, eff_dy, TRUE);
}

const float ppd = dev->full.ppd;
const float fitscale = dt_dev_get_zoom_scale(&dev->full, DT_ZOOM_FIT, 1.0f, FALSE);
const float tscalefloor = MIN(0.5f * fitscale * ppd, 1.0f);
const float tscaletop = 16.0f;
const float tscale = CLAMP(pinch_begin_tscale * scale, tscalefloor, tscaletop);

// Keep pinch fully continuous for a smartphone-like feeling, including at high zoom.
const float zoom_scale = tscale / ppd;
dt_print(DT_DEBUG_INPUT,
"[darkroom pinch] update x=%.1f y=%.1f raw_dx=%.3f raw_dy=%.3f"
" eff_dx=%.3f eff_dy=%.3f scale=%.6f state=0x%x"
" -> tscale=%.6f (floor=%.6f top=%.1f) zoom_scale=%.6f",
x, y, dx, dy, eff_dx, eff_dy, scale, state,
tscale, tscalefloor, tscaletop, zoom_scale);
dt_dev_zoom_move(&dev->full, DT_ZOOM_FREE, zoom_scale, 0, x, y, TRUE);

return TRUE;
}
Expand Down
4 changes: 3 additions & 1 deletion src/views/view.c
Original file line number Diff line number Diff line change
Expand Up @@ -715,6 +715,8 @@ gboolean dt_view_manager_gesture_pan(dt_view_manager_t *vm,
gboolean dt_view_manager_gesture_pinch(dt_view_manager_t *vm,
const double x,
const double y,
const double dx,
const double dy,
const int phase,
const double scale,
const int state)
Expand All @@ -725,7 +727,7 @@ gboolean dt_view_manager_gesture_pinch(dt_view_manager_t *vm,
}
else if(vm->current_view->gesture_pinch)
{
return vm->current_view->gesture_pinch(vm->current_view, x, y, phase, scale, state);
return vm->current_view->gesture_pinch(vm->current_view, x, y, dx, dy, phase, scale, state);
}
else
{
Expand Down
2 changes: 2 additions & 0 deletions src/views/view.h
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,8 @@ gboolean dt_view_manager_gesture_pan(dt_view_manager_t *vm,
gboolean dt_view_manager_gesture_pinch(dt_view_manager_t *vm,
const double x,
const double y,
const double dx,
const double dy,
const int phase,
const double scale,
const int state);
Expand Down
4 changes: 2 additions & 2 deletions src/views/view_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ OPTIONAL(void, configure, struct dt_view_t *self, int width, int height);
OPTIONAL(void, scrolled, struct dt_view_t *self, double x, double y, int up, int state); // mouse scrolled in view
OPTIONAL(void, scrollbar_changed, struct dt_view_t *self, double x, double y); // scrollbars changed in view
OPTIONAL(gboolean, gesture_pan, struct dt_view_t *self, double x, double y, double dx, double dy, int state);
OPTIONAL(gboolean, gesture_pinch, struct dt_view_t *self, double x, double y, int phase, double scale, int state);
OPTIONAL(gboolean, gesture_pinch, struct dt_view_t *self, double x, double y, double dx, double dy,
int phase, double scale, int state);

// list of mouse actions
OPTIONAL(GSList *, mouse_actions, const struct dt_view_t *self);
Expand All @@ -77,4 +78,3 @@ G_END_DECLS
// vim: shiftwidth=2 expandtab tabstop=2 cindent
// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
// clang-format on