From e8c8d3de681da8623fb76b69fbf6c3e699ab0882 Mon Sep 17 00:00:00 2001 From: Some Chinese Guy Date: Tue, 5 May 2020 18:33:31 +0300 Subject: [PATCH] Enabling touch navigation * 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 --- .gitignore | 3 + src/imv.c | 60 +++++++++++ src/viewport.c | 51 ++++++--- src/viewport.h | 11 +- src/window.h | 17 +++ src/wl_window.c | 282 ++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 411 insertions(+), 13 deletions(-) diff --git a/.gitignore b/.gitignore index 5008593..689b810 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,6 @@ build/ imv.1 imv.5 doc/imv-msg.1 +*.png +*.jpg +src/G* diff --git a/src/imv.c b/src/imv.c index 4231fd4..d605cec 100644 --- a/src/imv.c +++ b/src/imv.c @@ -170,6 +170,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); @@ -442,6 +448,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, ¤t_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, ¤t_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 c894f26..a4b92d6 100644 --- a/src/viewport.c +++ b/src/viewport.c @@ -90,14 +90,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) { @@ -114,6 +109,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) { @@ -124,7 +142,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 cordinates 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; @@ -140,8 +158,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; - double delta_scale = 0.04 * view->buffer.width * amount / image_width; - view->scale += delta_scale; + 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; @@ -203,7 +225,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); @@ -212,12 +235,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 5e5bc24..a2ebf8a 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 */ @@ -50,6 +51,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, @@ -63,6 +68,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 f84cbaf..970a5fa 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 }; @@ -41,6 +46,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 3a1caed..c4a52d0 100644 --- a/src/wl_window.c +++ b/src/wl_window.c @@ -3,6 +3,8 @@ #include "keyboard.h" #include "list.h" +#include + #include #include #include @@ -18,6 +20,26 @@ #include #include "xdg-shell-client-protocol.h" +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; @@ -29,6 +51,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; @@ -50,6 +73,7 @@ struct imv_window { bool fullscreen; int scale; + struct { struct { double last; @@ -68,6 +92,13 @@ struct imv_window { double dy; } scroll; } pointer; + + struct { + struct wl_list points; + enum imv_touch_state state; + } touch; + + }; struct output_data { @@ -398,6 +429,237 @@ static const struct wl_pointer_listener pointer_listener = { .axis_discrete = pointer_axis_discrete }; +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, +}; + static void seat_capabilities(void *data, struct wl_seat *seat, uint32_t capabilities) { (void)seat; @@ -426,6 +688,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) @@ -710,6 +984,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); } @@ -762,6 +1039,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); @@ -780,6 +1061,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);