Let example/imageviewer scroll
diff --git a/example/imageviewer/imageviewer.cc b/example/imageviewer/imageviewer.cc
index 9e33474..26a5cf9 100644
--- a/example/imageviewer/imageviewer.cc
+++ b/example/imageviewer/imageviewer.cc
@@ -35,6 +35,8 @@
The '1' to '8' keys change the magnification zoom (or minification zoom with
the shift key). The '0' key toggles nearest neighbor and bilinear filtering.
+The arrow keys (or hjkl keys) scroll the image. The '`' key resets.
+
The Escape key quits.
*/
@@ -108,6 +110,8 @@
wuffs_base__pixel_buffer g_pixbuf = {0};
uint32_t g_background_color_index = 0;
int32_t g_zoom = 0;
+int32_t g_pos_x = 0;
+int32_t g_pos_y = 0;
bool g_filter = false;
struct {
@@ -160,6 +164,16 @@
return NULL;
}
+static int32_t //
+i32_min(int32_t a, int32_t b) {
+ return (a < b) ? a : b;
+}
+
+static int32_t //
+i32_max(int32_t a, int32_t b) {
+ return (a > b) ? a : b;
+}
+
class MyDecodeImageCallbacks : public wuffs_aux::DecodeImageCallbacks {
public:
MyDecodeImageCallbacks() : m_combined_gamma(1.0) {}
@@ -308,6 +322,10 @@
#define XK_BackSpace 0xFF08
#define XK_Escape 0xFF1B
#define XK_Return 0xFF0D
+#define XK_Left 0xFF51
+#define XK_Up 0xFF52
+#define XK_Right 0xFF53
+#define XK_Down 0xFF54
uint32_t g_maximum_request_length = 0; // Measured in 4-byte units.
xcb_atom_t g_atom_net_wm_name = XCB_NONE;
@@ -335,7 +353,10 @@
uint32_t value_mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
uint32_t value_list[2];
value_list[0] = s->black_pixel;
- value_list[1] = XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_KEY_PRESS;
+ value_list[1] = XCB_EVENT_MASK_KEY_PRESS | //
+ XCB_EVENT_MASK_BUTTON_PRESS | //
+ XCB_EVENT_MASK_BUTTON_1_MOTION | //
+ XCB_EVENT_MASK_EXPOSURE;
xcb_create_window(c, 0, w, s->root, 0, 0, 1024, 768, 0,
XCB_WINDOW_CLASS_INPUT_OUTPUT, s->root_visual, value_mask,
value_list);
@@ -407,6 +428,16 @@
return (b < M) ? b : M;
}
+int32_t //
+calculate_delta(uint16_t state) {
+ if (state & XCB_MOD_MASK_SHIFT) {
+ return 256;
+ } else if (state & XCB_MOD_MASK_CONTROL) {
+ return 1;
+ }
+ return 16;
+}
+
bool //
load(xcb_connection_t* c, xcb_window_t w, const char* filename) {
if (g_pixmap != XCB_NONE) {
@@ -469,6 +500,44 @@
return true;
}
+// clear_area clears the L-shaped difference between old and new rectangles (of
+// equal width and height). It does this in up to two xcb_clear_area calls,
+// labeled A and B in the example below (with old_x=0, old_y=0, width=5,
+// height=4, new_x=2, new_y=2).
+//
+// AAAAA
+// AAAAA
+// BB+---+
+// BB| |
+// | |
+// +---+
+void //
+clear_area(xcb_connection_t* c,
+ xcb_window_t w,
+ int32_t old_x,
+ int32_t old_y,
+ int32_t width,
+ int32_t height,
+ int32_t new_x,
+ int32_t new_y) {
+ int32_t dy = new_y - old_y;
+ if (dy < 0) {
+ xcb_clear_area(c, 1, w, old_x, old_y + height + dy, width, -dy);
+ } else if (dy > 0) {
+ xcb_clear_area(c, 1, w, old_x, old_y, width, dy);
+ }
+
+ int32_t y0 = i32_max(old_y, new_y);
+ int32_t y1 = i32_min(old_y + height, new_y + height);
+
+ int32_t dx = new_x - old_x;
+ if (dx < 0) {
+ xcb_clear_area(c, 1, w, old_x + width + dx, y0, -dx, y1 - y0);
+ } else if (dx > 0) {
+ xcb_clear_area(c, 1, w, old_x, y0, dx, y1 - y0);
+ }
+}
+
int //
main(int argc, char** argv) {
const char* status = parse_flags(argc, argv);
@@ -532,6 +601,9 @@
g_pixmap_gc = xcb_generate_id(c);
g_pixmap_picture = xcb_generate_id(c);
+ int32_t button_x = 0;
+ int32_t button_y = 0;
+
bool loaded = load(
c, w, (g_flags.remaining_argc > 0) ? g_flags.remaining_argv[0] : NULL);
int arg = 0;
@@ -544,12 +616,14 @@
}
bool reload = false;
+ int32_t old_pos_x = g_pos_x;
+ int32_t old_pos_y = g_pos_y;
switch (event->response_type & 0x7F) {
case XCB_EXPOSE: {
xcb_expose_event_t* e = (xcb_expose_event_t*)event;
if (loaded && (e->count == 0)) {
xcb_render_composite(c, XCB_RENDER_PICT_OP_SRC, g_pixmap_picture,
- XCB_NONE, p, 0, 0, 0, 0, 0, 0,
+ XCB_NONE, p, 0, 0, 0, 0, g_pos_x, g_pos_y,
zoom_shift(g_width), zoom_shift(g_height));
xcb_flush(c);
}
@@ -589,6 +663,31 @@
reload = true;
break;
+ case '`':
+ g_pos_x = 0;
+ g_pos_y = 0;
+ break;
+
+ case XK_Left:
+ case 'h':
+ g_pos_x += calculate_delta(e->state);
+ break;
+
+ case XK_Down:
+ case 'j':
+ g_pos_y -= calculate_delta(e->state);
+ break;
+
+ case XK_Up:
+ case 'k':
+ g_pos_y += calculate_delta(e->state);
+ break;
+
+ case XK_Right:
+ case 'l':
+ g_pos_x -= calculate_delta(e->state);
+ break;
+
case '0':
case '1':
case '2':
@@ -619,6 +718,38 @@
break;
}
+ case XCB_BUTTON_PRESS: {
+ xcb_button_press_event_t* e = (xcb_button_press_event_t*)event;
+ switch (e->detail) {
+ case 1:
+ button_x = e->event_x;
+ button_y = e->event_y;
+ break;
+ case 4:
+ g_pos_y += calculate_delta(e->state);
+ break;
+ case 5:
+ g_pos_y -= calculate_delta(e->state);
+ break;
+ case 6:
+ g_pos_x += calculate_delta(e->state);
+ break;
+ case 7:
+ g_pos_x -= calculate_delta(e->state);
+ break;
+ }
+ break;
+ }
+
+ case XCB_MOTION_NOTIFY: {
+ xcb_motion_notify_event_t* e = (xcb_motion_notify_event_t*)event;
+ g_pos_x += e->event_x - button_x;
+ g_pos_y += e->event_y - button_y;
+ button_x = e->event_x;
+ button_y = e->event_y;
+ break;
+ }
+
case XCB_CLIENT_MESSAGE: {
xcb_client_message_event_t* e = (xcb_client_message_event_t*)event;
if (e->data.data32[0] == g_atom_wm_delete_window) {
@@ -634,6 +765,14 @@
loaded = load(c, w, g_flags.remaining_argv[arg]);
xcb_clear_area(c, 1, w, 0, 0, 0xFFFF, 0xFFFF);
xcb_flush(c);
+ } else if (loaded && //
+ ((old_pos_x != g_pos_x) || (old_pos_y != g_pos_y))) {
+ clear_area(c, w, old_pos_x, old_pos_y, zoom_shift(g_width),
+ zoom_shift(g_height), g_pos_x, g_pos_y);
+ xcb_render_composite(c, XCB_RENDER_PICT_OP_SRC, g_pixmap_picture,
+ XCB_NONE, p, 0, 0, 0, 0, g_pos_x, g_pos_y,
+ zoom_shift(g_width), zoom_shift(g_height));
+ xcb_flush(c);
}
}
return 0;