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
7 changes: 7 additions & 0 deletions data/darktableconfig.xml.in
Original file line number Diff line number Diff line change
Expand Up @@ -2398,6 +2398,13 @@
<longdescription><![CDATA[in darktable's darkroom, where you do your edits, all controls are listed on a panel on the right; you can choose whether you want to use the scroll wheel or scroll gesture on your touchpad to scroll the panel, or to change the values of the control under the pointer; this latter modality enables faster editing, but this can be dangerous because you can change the values ​​of the module controls without noticing if you are not used to it\n\n<b>enabled:</b> the mouse wheel scrolls the modules panel and with <i>Ctrl+Alt</i> adjusts a control's value\n\n<b>disabled:</b> the mouse wheel adjusts the control under the pointer and with <i>Ctrl+Alt</i> scrolls the panel.]]></longdescription>
<welcomescreen pagenum="5" questionnum="1"/>
</dtconfig>
<dtconfig prefs="misc" section="interface">
<name>darkroom/ui/touchpad_gestures</name>
<type>bool</type>
<default>true</default>
<shortdescription>enable touchpad gestures in darkroom</shortdescription>
<longdescription><![CDATA[use two-finger touchpad gestures in darkroom for panning and pinch-to-zoom. <b>enabled:</b> touchpad pinch gestures zoom the image and two-finger touchpad scrolling pans it; <i>Ctrl+scroll</i> still uses the legacy zoom behavior. <b>disabled:</b> touchpad gestures are ignored and darkroom falls back to the legacy scroll behavior, including <i>Ctrl+scroll</i> for zooming in and out.]]></longdescription>
</dtconfig>
<dtconfig prefs="darkroom" section="general">
<name>plugins/darkroom/ui/border_size</name>
<type>int</type>
Expand Down
86 changes: 82 additions & 4 deletions src/gui/gtk.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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;
Expand All @@ -763,29 +803,67 @@ 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) : "<none>",
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;
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=%.2f y=%.2f dx=%.3f dy=%.3f source='%s'",
event->x, event->y, delta_x, delta_y,
device ? gdk_device_get_name(device) : "<none>");
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) : "<none>",
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) : "<none>",
device ? gdk_device_get_source(device) : -1);
dt_view_manager_scrolled(darktable.view_manager, event->x, event->y,
delta_y < 0,
event->state & 0xf);
Expand Down
35 changes: 31 additions & 4 deletions src/views/darkroom.c
Original file line number Diff line number Diff line change
Expand Up @@ -4192,25 +4192,43 @@ 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;

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;
Expand All @@ -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;
}
Expand Down