~exec64/imv-devel

Enabling touch navigation v1 PROPOSED

I have been using the touch navigation PR for about 2 years now 
and it works flawlessly (with the fix posted later in the comments)
So I rebased it on master's HEAD from the new sourcehut repo.

I am not really the one who wrote the code and I tried to make that 
clear in the commits messages. Though I did not use the name and email 
address from the original author for privacy reasons, only their github 
use names.

If you prefer I could try to reach out to the two authors to get there 
aproval before this get merged. (if it does gets marged, in the ~2 years
I have been using it I can't seem to recal ever encountering a bug 🙃)

Nicolai Dagestad (2):
  Enabling touch navigation
  Fix the jump when switching from zoom to drag with touch

 src/.editorconfig |   3 +
 src/imv.c         |  64 +++++++++-
 src/viewport.c    |  85 ++++++++-----
 src/viewport.h    |  15 ++-
 src/window.h      |  17 +++
 src/wl_window.c   | 297 ++++++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 449 insertions(+), 32 deletions(-)
 create mode 100644 src/.editorconfig

-- 
2.35.1
I just tried out the patch and there's two things I noticed
immeditately.

1. Dragging the image with one finger (like scrolling) moves it slower
than I move my finger over the screen. Almost as if the speed was
reduced by some coefficient. I wonder if this is the effect of DPI
scaling on Wayland (my screen is scaled by 1.4, since it's high DPI),
I'm running Sway.
Could out please test it with

  output <your output> scale 1.4

in ~/.config/sway/config?

2. When you zoom on image on Android or iOS with two fingers it
usually allows you to move it at the same time. Feels a bit unnatural
when it doesn't happen here, imv seems to either zoom or move the
image but not simultaneously. How expensive would it be to allow both
simultaneously? (This one is more of a wish, I don't think this should
block the patches from being merged.)
Ivan
Hi

On Mon May 30, 2022 at 2:16 PM CEST, Ivan Oleynikov wrote:
Next
Export patchset (mbox)
How do I use this?

Copy & paste the following snippet into your terminal to import this patchset into git:

curl -s https://lists.sr.ht/~exec64/imv-devel/patches/29247/mbox | git am -3
Learn more about email & git

[PATCH 1/2] Enabling touch navigation Export this patch

* tap to left/right side to navigate to previous/next image
* tap into the middle to show overlay
* move single touch to pan the image
* pinch in/out to zoom

This is the PR[1] by gg-rewrite[2] for adding touch navigation from
github rebased on HEAD from master.

1: https://github.com/eXeC64/imv/pull/245
2: https://github.com/gg-rewrite
---
 src/imv.c       |  60 +++++++++++
 src/viewport.c  |  51 ++++++---
 src/viewport.h  |  11 +-
 src/window.h    |  17 +++
 src/wl_window.c | 278 ++++++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 404 insertions(+), 13 deletions(-)

diff --git a/src/imv.c b/src/imv.c
index 8380a12..4ca12a7 100644
--- a/src/imv.c
+++ b/src/imv.c
@@ -192,6 +192,12 @@ struct imv {
  /* if reading an image from stdin, this is the buffer for it */
  void *stdin_image_data;
  size_t stdin_image_data_len;

  /* initial scale for correct pinch zoom*/
  double initial_zoom;

  /* initial view offset for correct touch pan*/
  int initial_view_x, initial_view_y;
};

static void command_quit(struct list *args, const char *argstr, void *data);
@@ -472,6 +478,60 @@ static void event_handler(void *data, const struct imv_event *e)
    case IMV_EVENT_CUSTOM:
      consume_internal_event(imv, e->data.custom);
      break;
    case IMV_EVENT_TOUCH_TAP:
      {
        double current_scale;
        imv_viewport_get_scale(imv->view, &current_scale);
        double default_scale =
          imv_viewport_get_scale_to_window(imv->view, imv->current_image);
        double x = e->data.touch_tap.x;
        double width = e->data.touch_tap.width;
        if (current_scale <= default_scale) {
          if (x < width / 4) {
            imv_navigator_select_rel(imv->navigator, -1);
          } else if (x > width * 3  / 4) {
            imv_navigator_select_rel(imv->navigator, 1);
          } 
        } 
        if (x > width / 4 && x < width * 3 / 4) {
          imv->overlay.enabled = !imv->overlay.enabled;
          imv->need_redraw = true;
        }
      }
      break;
    case IMV_EVENT_TOUCH_ZOOM_START:
      {
        double current_scale;
        imv_viewport_get_scale(imv->view, &current_scale);
        imv->initial_zoom = current_scale;
      }
      break;
    case IMV_EVENT_TOUCH_ZOOM_CHANGE:
      {
        double new_zoom = imv->initial_zoom * e->data.touch_zoom.zoom;
        int new_zoom_int = (int) (new_zoom * 100);
        imv_viewport_zoom(imv->view, imv->current_image, IMV_ZOOM_TOUCH,
            e->data.touch_zoom.x, e->data.touch_zoom.y,
            new_zoom_int);
      }
      break;
    case IMV_EVENT_TOUCH_PAN_START:
      {
        int x,y;
        imv_viewport_get_offset(imv->view, &x, &y);
        imv->initial_view_x = x;
        imv->initial_view_y = y;
      }
      break;
    case IMV_EVENT_TOUCH_PAN_CHANGE:
      {
        imv_viewport_move_relative(imv->view,
            imv->initial_view_x,
            imv->initial_view_y,
            (int) (e->data.touch_pan.current_x - e->data.touch_pan.initial_x),
            (int) (e->data.touch_pan.current_y - e->data.touch_pan.initial_y),
            imv->current_image);
      }
    default:
      break;
  }
diff --git a/src/viewport.c b/src/viewport.c
index 095bf71..e226615 100644
--- a/src/viewport.c
+++ b/src/viewport.c
@@ -110,14 +110,9 @@ void imv_viewport_set_default_pan_factor(struct imv_viewport *view, double pan_f
  view->pan_factor_y = pan_factor_y;
}

void imv_viewport_move(struct imv_viewport *view, int x, int y,
void imv_viewport_keep_onscreen(struct imv_viewport *view,
    const struct imv_image *image)
{
  input_xy_to_render_xy(view, &x, &y);
  view->x += x;
  view->y += y;
  view->redraw = 1;
  view->locked = 1;
  int w = (int)(imv_image_width(image) * view->scale);
  int h = (int)(imv_image_height(image) * view->scale);
  if (view->x < -w) {
@@ -134,6 +129,29 @@ void imv_viewport_move(struct imv_viewport *view, int x, int y,
  }
}

void imv_viewport_move(struct imv_viewport *view, int x, int y,
    const struct imv_image *image)
{
  input_xy_to_render_xy(view, &x, &y);
  view->x += x;
  view->y += y;
  view->redraw = 1;
  view->locked = 1;
  imv_viewport_keep_onscreen(view, image);
}

void imv_viewport_move_relative(struct imv_viewport *view,
				int initial_x, int initial_y,
				int delta_x, int delta_y,
				struct imv_image *image)
{
  view->x = initial_x + delta_x;
  view->y = initial_y + delta_y;
  view->redraw = 1;
  view->locked = 1;
  imv_viewport_keep_onscreen(view, image);
}

void imv_viewport_zoom(struct imv_viewport *view, const struct imv_image *image,
                       enum imv_zoom_source src, int mouse_x, int mouse_y, int amount)
{
@@ -144,7 +162,7 @@ void imv_viewport_zoom(struct imv_viewport *view, const struct imv_image *image,
  const int image_height = imv_image_height(image);

  /* x and y coordinates are relative to the image */
  if(src == IMV_ZOOM_MOUSE) {
  if(src == IMV_ZOOM_MOUSE || src == IMV_ZOOM_TOUCH) {
    input_xy_to_render_xy(view, &mouse_x, &mouse_y);
    x = mouse_x - view->x;
    y = mouse_y - view->y;
@@ -160,8 +178,12 @@ void imv_viewport_zoom(struct imv_viewport *view, const struct imv_image *image,
  const int wc_x = view->buffer.width/2;
  const int wc_y = view->buffer.height/2;

  const double scale_factor = pow(1.04, amount);
  view->scale *= scale_factor;
  if (src == IMV_ZOOM_TOUCH) {
    view->scale = amount / 100.0f;
  } else {
    double delta_scale = 0.04 * view->buffer.width * amount / image_width;
    view->scale += delta_scale;
  }

  const double min_scale = 0.1;
  const double max_scale = 100;
@@ -245,7 +267,8 @@ void imv_viewport_center(struct imv_viewport *view, const struct imv_image *imag
  view->redraw = 1;
}

void imv_viewport_scale_to_window(struct imv_viewport *view, const struct imv_image *image)
double imv_viewport_get_scale_to_window(struct imv_viewport *view,
					const struct imv_image *image)
{
  const int image_width = imv_image_width(image);
  const int image_height = imv_image_height(image);
@@ -254,12 +277,16 @@ void imv_viewport_scale_to_window(struct imv_viewport *view, const struct imv_im

  if(window_aspect > image_aspect) {
    /* Image will become too tall before it becomes too wide */
    view->scale = (double)view->buffer.height / (double)image_height;
    return (double)view->buffer.height / (double)image_height;
  } else {
    /* Image will become too wide before it becomes too tall */
    view->scale = (double)view->buffer.width / (double)image_width;
    return (double)view->buffer.width / (double)image_width;
  }
}

void imv_viewport_scale_to_window(struct imv_viewport *view, const struct imv_image *image)
{
  view->scale = imv_viewport_get_scale_to_window(view, image);
  imv_viewport_center(view, image);
  view->locked = 0;
}
diff --git a/src/viewport.h b/src/viewport.h
index a24113c..b54cf06 100644
--- a/src/viewport.h
+++ b/src/viewport.h
@@ -17,7 +17,8 @@ enum scaling_mode {
/* Used to signify how a a user requested a zoom */
enum imv_zoom_source {
  IMV_ZOOM_MOUSE,
  IMV_ZOOM_KEYBOARD
  IMV_ZOOM_KEYBOARD,
  IMV_ZOOM_TOUCH
};

/* Creates an instance of imv_viewport */
@@ -56,6 +57,10 @@ void imv_viewport_set_default_pan_factor(struct imv_viewport *view, double pan_f
void imv_viewport_move(struct imv_viewport *view, int x, int y,
    const struct imv_image *image);

/* Pan the view relatively by coordinates */
void imv_viewport_move_relative(struct imv_viewport *view, int initial_x,
    int initial_y, int delta_x, int delta_y, struct imv_image *image);

/* Zoom the view by the given amount. imv_image* is used to get the image
 * dimensions */
void imv_viewport_zoom(struct imv_viewport *view, const struct imv_image *image,
@@ -84,6 +89,10 @@ void imv_viewport_center(struct imv_viewport *view,
void imv_viewport_scale_to_actual(struct imv_viewport *view,
                                  const struct imv_image *image);

/*get scale when scaled to window*/
double imv_viewport_get_scale_to_window(struct imv_viewport *view,
					const struct imv_image *image);

/* Scale the view so that the image fits in the window */
void imv_viewport_scale_to_window(struct imv_viewport *view,
                                  const struct imv_image *image);
diff --git a/src/window.h b/src/window.h
index a4786bb..40d5b74 100644
--- a/src/window.h
+++ b/src/window.h
@@ -13,6 +13,11 @@ enum imv_event_type {
  IMV_EVENT_MOUSE_MOTION,
  IMV_EVENT_MOUSE_BUTTON,
  IMV_EVENT_MOUSE_SCROLL,
  IMV_EVENT_TOUCH_TAP,
  IMV_EVENT_TOUCH_ZOOM_START,
  IMV_EVENT_TOUCH_ZOOM_CHANGE,
  IMV_EVENT_TOUCH_PAN_START,
  IMV_EVENT_TOUCH_PAN_CHANGE,
  IMV_EVENT_CUSTOM
};

@@ -42,6 +47,18 @@ struct imv_event {
    struct {
      double dx, dy;
    } mouse_scroll;
    struct {
      double x,y;
      int height, width;
    } touch_tap;
    struct {
      double x,y;
      double zoom;
    } touch_zoom;
    struct {
      double initial_x, initial_y;
      double current_x, current_y;
    } touch_pan;
    void *custom;
  } data;
};
diff --git a/src/wl_window.c b/src/wl_window.c
index 5efa42f..49bf35d 100644
--- a/src/wl_window.c
+++ b/src/wl_window.c
@@ -20,6 +20,27 @@

#define imv_min(a,b) ((a) > (b) ? (b) : (a))

struct imv_point {
  double x;
  double y;
};

struct imv_touch_point {
  struct wl_list link;
  struct imv_point initial, current;
  int32_t id;
};


enum imv_touch_state {
	TOUCH_STATE_IDLE,
	TOUCH_STATE_TAP,
	TOUCH_STATE_PAN_START,
	TOUCH_STATE_PAN_CHANGE,
	TOUCH_STATE_ZOOM_START,
	TOUCH_STATE_ZOOM_CHANGE
};

struct imv_window {
  struct wl_display    *wl_display;
  struct wl_registry   *wl_registry;
@@ -31,6 +52,7 @@ struct imv_window {
  struct wl_seat       *wl_seat;
  struct wl_keyboard   *wl_keyboard;
  struct wl_pointer    *wl_pointer;
  struct wl_touch      *wl_touch;
  EGLDisplay           egl_display;
  EGLContext           egl_context;
  EGLSurface           egl_surface;
@@ -72,6 +94,242 @@ struct imv_window {
      double dy;
    } scroll;
  } pointer;

  struct {
	  struct wl_list points;
	  enum imv_touch_state state;
  } touch;

};

static void touch_down(void *data, struct wl_touch *touch, uint32_t serial,
    uint32_t time, struct wl_surface *surface, int32_t id,
    wl_fixed_t surface_x, wl_fixed_t surface_y)
{
  (void)touch;
  (void)serial;
  (void)surface;

  struct imv_window *window = data;

  uint32_t num_points = wl_list_length(&window->touch.points);

  if (num_points == 2) {
	  (void)id;
	  (void)time;
	  (void)surface_x;
	  (void)surface_y;
	  return;
  }
  if (num_points == 0) {
	  window->touch.state = TOUCH_STATE_TAP;
  } 

  double x = wl_fixed_to_double(surface_x);
  double y = wl_fixed_to_double(surface_y);

  struct imv_touch_point *point = calloc(1, sizeof(struct imv_touch_point));

  point->initial.x = x;
  point->initial.y = y;
  point->current.x = x;
  point->current.y = y;
  point->id = id;

  wl_list_insert(&window->touch.points, &point->link);
}

static void touch_up(void *data, struct wl_touch *touch, uint32_t serial,
    uint32_t time, int32_t id)
{
  (void)touch;
  (void)serial;

  struct imv_window *window = data;

  struct imv_touch_point *point, *tmp;
  wl_list_for_each_safe(point, tmp, &window->touch.points, link) {
    if (point->id == id) {

      uint32_t num_points = wl_list_length(&window->touch.points);
      if (num_points == 1
	  && window->touch.state == TOUCH_STATE_TAP) {

	struct imv_event e = {
          .type = IMV_EVENT_TOUCH_TAP,
	  .data = {
	    .touch_tap = {
	      .x = point->current.x,
	      .y = point->current.y,
	      .height = window->height,
	      .width = window->width
	    }
	  }
	};
	imv_window_push_event(window, &e);

	window->touch.state = TOUCH_STATE_IDLE;
      } else if (num_points == 2) {
	window->touch.state = TOUCH_STATE_PAN_START;
      }

      wl_list_remove(&point->link);
    }
  }
}

static inline double measure_distance(double x1, double y1,
			       double x2, double y2)
{
  return sqrt((x2-x1) * (x2-x1) + (y2 - y1) * (y2-y1));
}

static void touch_motion(void *data, struct wl_touch *touch, uint32_t time,
  int32_t id, wl_fixed_t x, wl_fixed_t y)
{
  (void)touch;

  struct imv_window *window = data;
  struct imv_touch_point *point;
  wl_list_for_each(point, &window->touch.points, link) {
    if (point->id == id) {
      point->current.x = wl_fixed_to_double(x);
      point->current.y = wl_fixed_to_double(y);

      uint32_t num_points = wl_list_length(&window->touch.points);
      if (num_points == 1) {
	double distance =
	  measure_distance(point->current.x,
	    point->current.y,
	    point->initial.x,
	    point->initial.y);

	if (distance > 10.0) {
	  enum imv_event_type event_type;
	  switch (window->touch.state) {
	    case TOUCH_STATE_PAN_START:
	    case TOUCH_STATE_PAN_CHANGE:
	      {
		window->touch.state = TOUCH_STATE_PAN_CHANGE;
		event_type = IMV_EVENT_TOUCH_PAN_CHANGE;
	      }
	      break;
	    default:
	      {
		window->touch.state = TOUCH_STATE_PAN_START;
		event_type = IMV_EVENT_TOUCH_PAN_START;
	      }
	      break;
	  }

	  struct imv_event e = {
	    .type = event_type,
	    .data = {
	      .touch_pan = {
		.initial_x = (int) point->initial.x,
	        .initial_y = (int) point->initial.y,
		.current_x = (int) point->current.x,
		.current_y = (int) point->current.y
	      }
	    }
	  };
	  imv_window_push_event(window, &e);
	}
      } else {
        struct imv_touch_point  *point2;
	wl_list_for_each(point2, &window->touch.points, link) {
	  if (point2->id != point->id) {
	    double initial_distance =
	      measure_distance(point->initial.x,
		point->initial.y,
	        point2->initial.x,
	        point2->initial.y);
	    double current_distance =
	      measure_distance(point->current.x,
	        point->current.y,
	        point2->current.x,
	        point2->current.y);

	    double zoom_ratio = current_distance / initial_distance;
	    double cx = fabs(point->initial.x + point2->initial.x)/2;
	    double cy = fabs(point->initial.y + point2->initial.y)/2;

	    enum imv_event_type event_type;

	    switch (window->touch.state) {
	      case TOUCH_STATE_ZOOM_CHANGE:
	      case TOUCH_STATE_ZOOM_START:
		event_type = IMV_EVENT_TOUCH_ZOOM_CHANGE;
		window->touch.state = TOUCH_STATE_ZOOM_CHANGE;
		break;
	      default:
		event_type = IMV_EVENT_TOUCH_ZOOM_START;
		window->touch.state = TOUCH_STATE_ZOOM_START;
		break;
		};

	    struct imv_event e = {
	      .type = event_type,
	      .data = {
		.touch_zoom = {
		  .x = cx,
		  .y = cy,
		  .zoom = zoom_ratio
		}
	      }
	    };
	    imv_window_push_event(window, &e);

	    break;
	  }
	}
      }
      break;
    }
  }

}

static void touch_frame(void *data, struct wl_touch *touch)
{
  (void)data;
  (void)touch;
}

static void touch_orientation(void *data, struct wl_touch *touch, int32_t id,
                                 wl_fixed_t orientation)
{
  (void)data;
  (void)touch;
  (void)id;
  (void)orientation;
}

static void touch_cancel(void *data, struct wl_touch *touch)
{
  (void)data;
  (void)touch;
}

static void touch_shape(void *data, struct wl_touch *touch, int32_t id,
                           wl_fixed_t major, wl_fixed_t minor)
{
  (void)data;
  (void)touch;
  (void)id;
  (void)major;
  (void)minor;
}

static const struct wl_touch_listener touch_listener =
{
 .down = touch_down,
 .up = touch_up,
 .motion = touch_motion,
 .frame = touch_frame,
 .orientation = touch_orientation,
 .shape = touch_shape,
 .cancel = touch_cancel,
};

struct output_data {
@@ -436,6 +694,18 @@ static void seat_capabilities(void *data, struct wl_seat *seat, uint32_t capabil
      window->wl_keyboard = NULL;
    }
  }

  if (capabilities & WL_SEAT_CAPABILITY_TOUCH) {
    if (!window->wl_touch) {
      window->wl_touch = wl_seat_get_touch(window->wl_seat);
      wl_touch_add_listener(window->wl_touch, &touch_listener, window);
    } else {
      if (window->wl_touch) {
	wl_touch_release(window->wl_touch);
	window->wl_touch = NULL;
      }
    }
  }
}

static void seat_name(void *data, struct wl_seat *seat, const char *name)
@@ -738,6 +1008,9 @@ static void shutdown_wayland(struct imv_window *window)
{
  close(window->pipe_fds[0]);
  close(window->pipe_fds[1]);
  if (window->wl_touch) {
    wl_touch_destroy(window->wl_touch);
  }
  if (window->wl_pointer) {
    wl_pointer_destroy(window->wl_pointer);
  }
@@ -790,6 +1063,10 @@ struct imv_window *imv_window_create(int width, int height, const char *title)

  window->keyboard = imv_keyboard_create();
  assert(window->keyboard);

  wl_list_init(&window->touch.points);
  window->touch.state = TOUCH_STATE_IDLE;

  window->wl_outputs = list_create();
  connect_to_wayland(window);
  create_window(window, width, height, title);
@@ -808,6 +1085,7 @@ void imv_window_free(struct imv_window *window)
{
  timer_delete(window->timer_id);
  imv_keyboard_free(window->keyboard);
  wl_list_remove(&window->touch.points);
  shutdown_wayland(window);
  list_deep_free(window->wl_outputs);
  free(window);
-- 
2.35.1

[PATCH 2/2] Fix the jump when switching from zoom to drag with touch Export this patch

When using touch, if you the zoom image the image, then lift one finger
and start draging the remaining finger, the image will jump mostly out
of the viewport before starting the zoom.

This is the patch proposed by mlngh[1] on the github PR
regarding enabling touch navigation[2]

1: https://github.com/mlngh
2: https://github.com/eXeC64/imv/pull/245
---
 src/.editorconfig |   3 +
 src/imv.c         |   6 +-
 src/viewport.c    |  42 ++++----
 src/viewport.h    |   8 +-
 src/wl_window.c   | 249 +++++++++++++++++++++++++---------------------
 5 files changed, 167 insertions(+), 141 deletions(-)
 create mode 100644 src/.editorconfig

diff --git a/src/.editorconfig b/src/.editorconfig
new file mode 100644
index 0000000..7f9845b
--- /dev/null
+++ b/src/.editorconfig
@@ -0,0 +1,3 @@
[*.{c,h}]
indent_style = space
indent_size = 2
diff --git a/src/imv.c b/src/imv.c
index 4ca12a7..066a062 100644
--- a/src/imv.c
+++ b/src/imv.c
@@ -472,7 +472,7 @@ static void event_handler(void *data, const struct imv_event *e)
        double x, y;
        imv_window_get_mouse_position(imv->window, &x, &y);
        imv_viewport_zoom(imv->view, imv->current_image, IMV_ZOOM_MOUSE,
            x, y, -e->data.mouse_scroll.dy);
            x, y, -e->data.mouse_scroll.dy, false);
      }
      break;
    case IMV_EVENT_CUSTOM:
@@ -512,7 +512,7 @@ static void event_handler(void *data, const struct imv_event *e)
        int new_zoom_int = (int) (new_zoom * 100);
        imv_viewport_zoom(imv->view, imv->current_image, IMV_ZOOM_TOUCH,
            e->data.touch_zoom.x, e->data.touch_zoom.y,
            new_zoom_int);
            new_zoom_int, true);
      }
      break;
    case IMV_EVENT_TOUCH_PAN_START:
@@ -1772,7 +1772,7 @@ static void command_zoom(struct list *args, const char *argstr, void *data)
      imv_viewport_scale_to_actual(imv->view, imv->current_image);
    } else {
      long int amount = strtol(args->items[1], NULL, 10);
      imv_viewport_zoom(imv->view, imv->current_image, IMV_ZOOM_KEYBOARD, 0, 0, amount);
      imv_viewport_zoom(imv->view, imv->current_image, IMV_ZOOM_KEYBOARD, 0, 0, amount, false);
    }
  }
}
diff --git a/src/viewport.c b/src/viewport.c
index e226615..f7230ab 100644
--- a/src/viewport.c
+++ b/src/viewport.c
@@ -14,7 +14,7 @@ struct imv_viewport {
  struct {
    int width, height;
  } buffer; /* rendering buffer dimensions */
  int x, y;
  double x, y;
  double pan_factor_x, pan_factor_y;
  int redraw;
  int playing;
@@ -141,9 +141,9 @@ void imv_viewport_move(struct imv_viewport *view, int x, int y,
}

void imv_viewport_move_relative(struct imv_viewport *view,
				int initial_x, int initial_y,
				int delta_x, int delta_y,
				struct imv_image *image)
        int initial_x, int initial_y,
        int delta_x, int delta_y,
        struct imv_image *image)
{
  view->x = initial_x + delta_x;
  view->y = initial_y + delta_y;
@@ -153,7 +153,8 @@ void imv_viewport_move_relative(struct imv_viewport *view,
}

void imv_viewport_zoom(struct imv_viewport *view, const struct imv_image *image,
                       enum imv_zoom_source src, int mouse_x, int mouse_y, int amount)
                       enum imv_zoom_source src, int mouse_x, int mouse_y, int amount,
                       bool ignore_edges)
{
  double prev_scale = view->scale;
  int x, y;
@@ -192,20 +193,21 @@ void imv_viewport_zoom(struct imv_viewport *view, const struct imv_image *image,
  } else if (view->scale < min_scale) {
    view->scale = min_scale;
  }

  if(view->scale < prev_scale) {
    if(scaled_width < view->buffer.width) {
      x = scaled_width/2 - (ic_x - wc_x)*2;
    }
    if(scaled_height < view->buffer.height) {
      y = scaled_height/2 - (ic_y - wc_y)*2;
    }
  } else {
    if(scaled_width < view->buffer.width) {
      x = scaled_width/2;
    }
    if(scaled_height < view->buffer.height) {
      y = scaled_height/2;
  if(!ignore_edges) {
    if(view->scale < prev_scale) {
      if(scaled_width < view->buffer.width) {
        x = scaled_width/2 - (ic_x - wc_x)*2;
      }
      if(scaled_height < view->buffer.height) {
        y = scaled_height/2 - (ic_y - wc_y)*2;
      }
    } else {
      if(scaled_width < view->buffer.width) {
        x = scaled_width/2;
      }
      if(scaled_height < view->buffer.height) {
        y = scaled_height/2;
      }
    }
  }

@@ -268,7 +270,7 @@ void imv_viewport_center(struct imv_viewport *view, const struct imv_image *imag
}

double imv_viewport_get_scale_to_window(struct imv_viewport *view,
					const struct imv_image *image)
          const struct imv_image *image)
{
  const int image_width = imv_image_width(image);
  const int image_height = imv_image_height(image);
diff --git a/src/viewport.h b/src/viewport.h
index b54cf06..08c6299 100644
--- a/src/viewport.h
+++ b/src/viewport.h
@@ -59,12 +59,14 @@ void imv_viewport_move(struct imv_viewport *view, int x, int y,

/* Pan the view relatively by coordinates */
void imv_viewport_move_relative(struct imv_viewport *view, int initial_x,
    int initial_y, int delta_x, int delta_y, struct imv_image *image);
                                int initial_y, int delta_x, int delta_y, 
                                struct imv_image *image);

/* Zoom the view by the given amount. imv_image* is used to get the image
 * dimensions */
void imv_viewport_zoom(struct imv_viewport *view, const struct imv_image *image,
                       enum imv_zoom_source, int mouse_x, int mouse_y, int amount);
                       enum imv_zoom_source, int mouse_x, int mouse_y, int amount,
                       bool ignore_edges);

/* Rotate the view by the given number of degrees */
void imv_viewport_rotate_by(struct imv_viewport *view, double degrees);
@@ -91,7 +93,7 @@ void imv_viewport_scale_to_actual(struct imv_viewport *view,

/*get scale when scaled to window*/
double imv_viewport_get_scale_to_window(struct imv_viewport *view,
					const struct imv_image *image);
                                        const struct imv_image *image);

/* Scale the view so that the image fits in the window */
void imv_viewport_scale_to_window(struct imv_viewport *view,
diff --git a/src/wl_window.c b/src/wl_window.c
index 49bf35d..d97906b 100644
--- a/src/wl_window.c
+++ b/src/wl_window.c
@@ -33,12 +33,12 @@ struct imv_touch_point {


enum imv_touch_state {
	TOUCH_STATE_IDLE,
	TOUCH_STATE_TAP,
	TOUCH_STATE_PAN_START,
	TOUCH_STATE_PAN_CHANGE,
	TOUCH_STATE_ZOOM_START,
	TOUCH_STATE_ZOOM_CHANGE
  TOUCH_STATE_IDLE,
  TOUCH_STATE_TAP,
  TOUCH_STATE_PAN_START,
  TOUCH_STATE_PAN_CHANGE,
  TOUCH_STATE_ZOOM_START,
  TOUCH_STATE_ZOOM_CHANGE
};

struct imv_window {
@@ -96,8 +96,8 @@ struct imv_window {
  } pointer;

  struct {
	  struct wl_list points;
	  enum imv_touch_state state;
    struct wl_list points;
    enum imv_touch_state state;
  } touch;

};
@@ -115,14 +115,14 @@ static void touch_down(void *data, struct wl_touch *touch, uint32_t serial,
  uint32_t num_points = wl_list_length(&window->touch.points);

  if (num_points == 2) {
	  (void)id;
	  (void)time;
	  (void)surface_x;
	  (void)surface_y;
	  return;
    (void)id;
    (void)time;
    (void)surface_x;
    (void)surface_y;
    return;
  }
  if (num_points == 0) {
	  window->touch.state = TOUCH_STATE_TAP;
    window->touch.state = TOUCH_STATE_TAP;
  } 

  double x = wl_fixed_to_double(surface_x);
@@ -153,33 +153,52 @@ static void touch_up(void *data, struct wl_touch *touch, uint32_t serial,

      uint32_t num_points = wl_list_length(&window->touch.points);
      if (num_points == 1
	  && window->touch.state == TOUCH_STATE_TAP) {
    && window->touch.state == TOUCH_STATE_TAP) {

	struct imv_event e = {
  struct imv_event e = {
          .type = IMV_EVENT_TOUCH_TAP,
	  .data = {
	    .touch_tap = {
	      .x = point->current.x,
	      .y = point->current.y,
	      .height = window->height,
	      .width = window->width
	    }
	  }
	};
	imv_window_push_event(window, &e);

	window->touch.state = TOUCH_STATE_IDLE;
    .data = {
      .touch_tap = {
        .x = point->current.x,
        .y = point->current.y,
        .height = window->height,
        .width = window->width
      }
    }
  };
  imv_window_push_event(window, &e);

  window->touch.state = TOUCH_STATE_IDLE;
      } else if (num_points == 2) {
	window->touch.state = TOUCH_STATE_PAN_START;
  window->touch.state = TOUCH_STATE_PAN_START;
      }

      wl_list_remove(&point->link);
    }
  }
  /* if after touch up we have one point, reinitiate pan */
  if (wl_list_length(&window->touch.points) == 1) {
    wl_list_for_each_safe(point, tmp, &window->touch.points, link) {
        point->initial.x = point->current.x;
        point->initial.y = point->current.y;
       struct imv_event e = {
         .type = IMV_EVENT_TOUCH_PAN_START,
         .data = {
           .touch_pan = {
             .initial_x = (int) point->initial.x * window->scale,
             .initial_y = (int) point->initial.y * window->scale,
             .current_x = (int) point->current.x * window->scale,
             .current_y = (int) point->current.y * window->scale
           }
         }
       };
       imv_window_push_event(window, &e);
    }
  }
}

static inline double measure_distance(double x1, double y1,
			       double x2, double y2)
             double x2, double y2)
{
  return sqrt((x2-x1) * (x2-x1) + (y2 - y1) * (y2-y1));
}
@@ -198,91 +217,91 @@ static void touch_motion(void *data, struct wl_touch *touch, uint32_t time,

      uint32_t num_points = wl_list_length(&window->touch.points);
      if (num_points == 1) {
	double distance =
	  measure_distance(point->current.x,
	    point->current.y,
	    point->initial.x,
	    point->initial.y);

	if (distance > 10.0) {
	  enum imv_event_type event_type;
	  switch (window->touch.state) {
	    case TOUCH_STATE_PAN_START:
	    case TOUCH_STATE_PAN_CHANGE:
	      {
		window->touch.state = TOUCH_STATE_PAN_CHANGE;
		event_type = IMV_EVENT_TOUCH_PAN_CHANGE;
	      }
	      break;
	    default:
	      {
		window->touch.state = TOUCH_STATE_PAN_START;
		event_type = IMV_EVENT_TOUCH_PAN_START;
	      }
	      break;
	  }

	  struct imv_event e = {
	    .type = event_type,
	    .data = {
	      .touch_pan = {
		.initial_x = (int) point->initial.x,
	        .initial_y = (int) point->initial.y,
		.current_x = (int) point->current.x,
		.current_y = (int) point->current.y
	      }
	    }
	  };
	  imv_window_push_event(window, &e);
	}
  double distance =
    measure_distance(point->current.x,
      point->current.y,
      point->initial.x,
      point->initial.y);

  if (distance > 10.0) {
    enum imv_event_type event_type;
    switch (window->touch.state) {
      case TOUCH_STATE_PAN_START:
      case TOUCH_STATE_PAN_CHANGE:
        {
    window->touch.state = TOUCH_STATE_PAN_CHANGE;
    event_type = IMV_EVENT_TOUCH_PAN_CHANGE;
        }
        break;
      default:
        {
    window->touch.state = TOUCH_STATE_PAN_START;
    event_type = IMV_EVENT_TOUCH_PAN_START;
        }
        break;
    }

    struct imv_event e = {
      .type = event_type,
      .data = {
        .touch_pan = {
    .initial_x = (int) point->initial.x,
          .initial_y = (int) point->initial.y,
    .current_x = (int) point->current.x,
    .current_y = (int) point->current.y
        }
      }
    };
    imv_window_push_event(window, &e);
  }
      } else {
        struct imv_touch_point  *point2;
	wl_list_for_each(point2, &window->touch.points, link) {
	  if (point2->id != point->id) {
	    double initial_distance =
	      measure_distance(point->initial.x,
		point->initial.y,
	        point2->initial.x,
	        point2->initial.y);
	    double current_distance =
	      measure_distance(point->current.x,
	        point->current.y,
	        point2->current.x,
	        point2->current.y);

	    double zoom_ratio = current_distance / initial_distance;
	    double cx = fabs(point->initial.x + point2->initial.x)/2;
	    double cy = fabs(point->initial.y + point2->initial.y)/2;

	    enum imv_event_type event_type;

	    switch (window->touch.state) {
	      case TOUCH_STATE_ZOOM_CHANGE:
	      case TOUCH_STATE_ZOOM_START:
		event_type = IMV_EVENT_TOUCH_ZOOM_CHANGE;
		window->touch.state = TOUCH_STATE_ZOOM_CHANGE;
		break;
	      default:
		event_type = IMV_EVENT_TOUCH_ZOOM_START;
		window->touch.state = TOUCH_STATE_ZOOM_START;
		break;
		};

	    struct imv_event e = {
	      .type = event_type,
	      .data = {
		.touch_zoom = {
		  .x = cx,
		  .y = cy,
		  .zoom = zoom_ratio
		}
	      }
	    };
	    imv_window_push_event(window, &e);

	    break;
	  }
	}
  wl_list_for_each(point2, &window->touch.points, link) {
    if (point2->id != point->id) {
      double initial_distance =
        measure_distance(point->initial.x,
    point->initial.y,
          point2->initial.x,
          point2->initial.y);
      double current_distance =
        measure_distance(point->current.x,
          point->current.y,
          point2->current.x,
          point2->current.y);

      double zoom_ratio = current_distance / initial_distance;
      double cx = fabs(point->initial.x + point2->initial.x)/2;
      double cy = fabs(point->initial.y + point2->initial.y)/2;

      enum imv_event_type event_type;

      switch (window->touch.state) {
        case TOUCH_STATE_ZOOM_CHANGE:
        case TOUCH_STATE_ZOOM_START:
    event_type = IMV_EVENT_TOUCH_ZOOM_CHANGE;
    window->touch.state = TOUCH_STATE_ZOOM_CHANGE;
    break;
        default:
    event_type = IMV_EVENT_TOUCH_ZOOM_START;
    window->touch.state = TOUCH_STATE_ZOOM_START;
    break;
    };

      struct imv_event e = {
        .type = event_type,
        .data = {
    .touch_zoom = {
      .x = cx,
      .y = cy,
      .zoom = zoom_ratio
    }
        }
      };
      imv_window_push_event(window, &e);

      break;
    }
  }
      }
      break;
    }
@@ -701,8 +720,8 @@ static void seat_capabilities(void *data, struct wl_seat *seat, uint32_t capabil
      wl_touch_add_listener(window->wl_touch, &touch_listener, window);
    } else {
      if (window->wl_touch) {
	wl_touch_release(window->wl_touch);
	window->wl_touch = NULL;
  wl_touch_release(window->wl_touch);
  window->wl_touch = NULL;
      }
    }
  }
-- 
2.35.1