commit b3a32dd52672a8a385308afb73198d129294c7c3
parent 91a730677ac95f922d6e5bca531f9cf0811e0fd6
Author: lumidify <nobody@lumidify.org>
Date:   Thu, 11 Nov 2021 13:41:07 +0100
Make selecting and scrolling work somewhat better
Diffstat:
7 files changed, 106 insertions(+), 31 deletions(-)
diff --git a/Makefile b/Makefile
@@ -47,6 +47,7 @@ LDFLAGS_LEDIT = ${LDFLAGS} `pkg-config --libs x11 xkbfile pangoxft xext` -lm
 
 all: ${BIN}
 
+ledit.o : config.h
 theme.o : theme_config.h
 keys_basic.o : keys_basic_config.h
 keys_command.o : keys_command_config.h
diff --git a/buffer.c b/buffer.c
@@ -1976,18 +1976,22 @@ ledit_buffer_set_selection(ledit_buffer *buffer, int line1, int byte1, int line2
 	    byte1 == buffer->sel.byte1 && byte2 == buffer->sel.byte2) {
 		return;
 	}
-	if (buffer->sel.line1 >= 0) {
+	/* FIXME: maybe check both lines and bytes? */
+	if (buffer->sel.line1 >= 0 || line1 >= 0) {
 		int l1_new = line1, l2_new = line2;
 		int b1_new = byte1, b2_new = byte2;
-		ledit_buffer_sort_selection(&buffer->sel.line1, &buffer->sel.byte1, &buffer->sel.line2, &buffer->sel.byte2);
 		ledit_buffer_sort_selection(&l1_new, &b1_new, &l2_new, &b2_new);
+		ledit_buffer_sort_selection(&buffer->sel.line1, &buffer->sel.byte1, &buffer->sel.line2, &buffer->sel.byte2);
+		/* FIXME: make this a bit nicer and optimize it */
 		if (buffer->sel.line1 > l2_new || buffer->sel.line2 < l1_new) {
 			for (int i = buffer->sel.line1; i <= buffer->sel.line2; i++) {
-				ledit_buffer_wipe_line_cursor_attrs(buffer, i);
+				if (i >= 0)
+					ledit_buffer_wipe_line_cursor_attrs(buffer, i);
 			}
 		} else {
 			for (int i = buffer->sel.line1; i < l1_new; i++) {
-				ledit_buffer_wipe_line_cursor_attrs(buffer, i);
+				if (i >= 0)
+					ledit_buffer_wipe_line_cursor_attrs(buffer, i);
 			}
 			for (int i = buffer->sel.line2; i > l2_new; i--) {
 				ledit_buffer_wipe_line_cursor_attrs(buffer, i);
@@ -2029,12 +2033,20 @@ ledit_buffer_button_handler(void *data, XEvent *event) {
 	ledit_buffer *buffer = (ledit_buffer *)data;
 	int x = event->xbutton.x;
 	int y = event->xbutton.y;
+	int snap;
 	switch (event->type) {
 	case ButtonPress:
-		ledit_xy_to_line_byte(buffer, x, y, 0,  &l, &b);
+		snap = buffer->common->mode == NORMAL ? 0 : 1;
+		ledit_xy_to_line_byte(buffer, x, y, snap,  &l, &b);
 		buffer->selecting = 1;
+		if (buffer->common->mode == NORMAL)
+			ledit_buffer_wipe_line_cursor_attrs(buffer, buffer->cur_line);
 		buffer->cur_line = l;
 		buffer->cur_index = b;
+		/* don't set selection yet because the mouse may not be
+		   dragged, so we don't want to switch to visual (this
+		   allows setting just the cursor position in normal mode
+		   without always switching to visual) */
 		ledit_buffer_set_selection(buffer, -1, -1, -1, -1);
 		if (buffer->common->mode == NORMAL)
 			ledit_buffer_set_line_cursor_attrs(buffer, l, b);
@@ -2046,17 +2058,26 @@ ledit_buffer_button_handler(void *data, XEvent *event) {
 		if (buffer->selecting) {
 			y = y >= 0 ? y : 0;
 			ledit_xy_to_line_byte(buffer, x, y, 1, &l, &b);
-			if (buffer->sel.line1 < 0 || buffer->sel.byte1 < 0) {
-				ledit_buffer_set_selection(buffer, l, b, l, b);
-			} else {
-				ledit_buffer_set_selection(buffer, buffer->sel.line1, buffer->sel.byte1, l, b);
-			}
 			if (buffer->common->mode == NORMAL) {
 				ledit_buffer_wipe_line_cursor_attrs(buffer, buffer->cur_line);
 				/* FIXME: return to old mode afterwards? */
 				/* should change_mode_group even be called here? */
 				ledit_buffer_set_mode(buffer, VISUAL);
 			}
+			if (buffer->sel.line1 < 0 || buffer->sel.byte1 < 0) {
+				/* the selection has just started, so the current
+				   position is already set to the beginning of the
+				   selection (see case ButtonPress above) */
+				ledit_buffer_set_selection(
+				    buffer,
+				    buffer->cur_line, buffer->cur_index, l, b
+				);
+			} else {
+				ledit_buffer_set_selection(
+				    buffer,
+				    buffer->sel.line1, buffer->sel.byte1, l, b
+				);
+			}
 			buffer->cur_line = l;
 			buffer->cur_index = b;
 		}
diff --git a/config.h b/config.h
@@ -0,0 +1,5 @@
+/* minimum time between redraws (nanoseconds) */
+#define TICK (long long)20000000
+/* minimum time between processing of mouse events -
+   events inbetween are discarded (nanoseconds) */
+#define MOUSE_TICK (long long)100000000
diff --git a/ledit.c b/ledit.c
@@ -1,3 +1,4 @@
+/* FIXME: Make scrolling more smooth */
 /* FIXME: Document that everything is assumed to be utf8 */
 /* FIXME: Only redraw part of screen if needed */
 /* FIXME: overflow in repeated commands */
@@ -9,6 +10,7 @@
 /* FIXME: sort out types for indices (currently just int, but that might overflow) */
 /* TODO: allow extending selection with shift+mouse like in e.g. gtk */
 #include <math.h>
+#include <time.h>
 #include <stdio.h>
 #include <errno.h>
 #include <assert.h>
@@ -29,6 +31,7 @@
 #include <X11/extensions/XKBrules.h>
 #include <X11/extensions/Xdbe.h>
 
+#include "config.h"
 #include "memory.h"
 #include "common.h"
 #include "txtbuf.h"
@@ -48,7 +51,7 @@ static void mainloop(void);
 static void setup(int argc, char *argv[]);
 static void cleanup(void);
 static void redraw(void);
-static int button_press(XEvent *event);
+static int button_press(XEvent *event, int scroll_num);
 static int button_release(XEvent *event);
 static int drag_motion(XEvent *event);
 
@@ -91,9 +94,19 @@ mainloop(void) {
 
 	int need_redraw = 0;
 	redraw();
-
+	/* store last time that a mouse event was processed in order to
+	   avoid sending too many mouse events to be processed */
+	/* also store last draw time so framerate can be limited */
+	struct timespec now, elapsed, last, last_scroll, last_motion, sleep_time;
+	clock_gettime(CLOCK_MONOTONIC, &last);
+	last_scroll = last_motion = last;
+	sleep_time.tv_sec = 0;
+	XEvent last_scroll_event, last_motion_event;
+	int last_scroll_valid = 0, last_motion_valid = 0;
+	int scroll_num = 0;
+	int scroll_delta = 0;
 	while (running) {
-		do {
+		while (XPending(common.dpy)) {
 			XNextEvent(common.dpy, &event);
 			if (event.type == xkb_event_type) {
 				change_kbd = 1;
@@ -115,13 +128,28 @@ mainloop(void) {
 				need_redraw = 1;
 				break;
 			case ButtonPress:
-				need_redraw |= button_press(&event);
+				/* FIXME: this is all a bit hacky */
+				if (event.xbutton.button == Button4 ||
+				    event.xbutton.button == Button5) {
+					scroll_delta = event.xbutton.button == Button4 ? -1 : 1;
+					if (last_scroll_valid) {
+						scroll_num += scroll_delta;
+					} else {
+						last_scroll_event = event;
+						last_scroll_valid = 1;
+						scroll_num = scroll_delta;
+					}
+				} else {
+					need_redraw |= button_press(&event, 0);
+				}
 				break;
 			case ButtonRelease:
 				need_redraw |= button_release(&event);
 				break;
 			case MotionNotify:
-				need_redraw |= drag_motion(&event);
+				/* FIXME: is it legal to just copy event like this? */
+				last_motion_event = event;
+				last_motion_valid = 1;
 				break;
 			case KeyPress:
 				need_redraw = 1;
@@ -143,7 +171,25 @@ mainloop(void) {
 			default:
 				break;
 			}
-		} while (XPending(common.dpy));
+		};
+		if (last_motion_valid) {
+			clock_gettime(CLOCK_MONOTONIC, &now);
+			timespecsub(&now, &last_motion, &elapsed);
+			if (elapsed.tv_sec > 0 || elapsed.tv_nsec >= MOUSE_TICK) {
+				need_redraw |= drag_motion(&last_motion_event);
+				last_motion = now;
+				last_motion_valid = 0;
+			}
+		}
+		if (last_scroll_valid) {
+			clock_gettime(CLOCK_MONOTONIC, &now);
+			timespecsub(&now, &last_scroll, &elapsed);
+			if (elapsed.tv_sec > 0 || elapsed.tv_nsec >= MOUSE_TICK) {
+				need_redraw |= button_press(&last_scroll_event, scroll_num);
+				last_scroll = now;
+				last_scroll_valid = 0;
+			}
+		}
 
 		if (change_kbd) {
 			change_kbd = 0;
@@ -163,6 +209,14 @@ mainloop(void) {
 			redraw();
 			need_redraw = 0;
 		}
+
+		clock_gettime(CLOCK_MONOTONIC, &now);
+		timespecsub(&now, &last, &elapsed);
+		if (elapsed.tv_sec == 0 && elapsed.tv_nsec < TICK) {
+			sleep_time.tv_nsec = TICK - elapsed.tv_nsec;
+			nanosleep(&sleep_time, NULL);
+		}
+		last = now;
 	}
 }
 
@@ -251,8 +305,8 @@ redraw(void) {
 }
 
 static int
-button_press(XEvent *event) {
-	return ledit_window_button_press(window, event);
+button_press(XEvent *event, int scroll_num) {
+	return ledit_window_button_press(window, event, scroll_num);
 }
 
 static int
diff --git a/theme_config.h b/theme_config.h
@@ -4,6 +4,6 @@ static const char *TEXT_BG = "#FFFFFF";
 
 /* FIXME: give in units other than pixels */
 static const int SCROLLBAR_WIDTH = 10;
-static const int SCROLLBAR_STEP = 10;
+static const int SCROLLBAR_STEP = 20;
 static const char *SCROLLBAR_BG = "#CCCCCC";
 static const char *SCROLLBAR_FG = "#000000";
diff --git a/window.c b/window.c
@@ -700,7 +700,7 @@ clipboard_selrequest(ledit_window *window, XEvent *e)
 
 /* FIXME: improve set_scroll_pos; make it a bit clearer */
 int
-ledit_window_button_press(ledit_window *window, XEvent *event) {
+ledit_window_button_press(ledit_window *window, XEvent *event, int scroll_num) {
 	int x, y;
 	double scroll_h, scroll_y;
 	switch (event->xbutton.button) {
@@ -723,18 +723,12 @@ ledit_window_button_press(ledit_window *window, XEvent *event) {
 			}
 			break;
 		case Button4:
-			window->scroll_offset -= window->theme->scrollbar_step;
+		case Button5:
+			window->scroll_offset += scroll_num * window->theme->scrollbar_step;
 			if (window->scroll_offset < 0)
 				window->scroll_offset = 0;
-			if (window->scroll_callback)
-				window->scroll_callback(window->scroll_cb_data, (long)window->scroll_offset);
-			return 1;
-		case Button5:
-			if (window->scroll_offset + window->text_h < window->scroll_max) {
-				window->scroll_offset += window->theme->scrollbar_step;
-				if (window->scroll_offset + window->text_h > window->scroll_max) {
-					window->scroll_offset = window->scroll_max - window->text_h;
-				}
+			if (window->scroll_offset + window->text_h > window->scroll_max) {
+				window->scroll_offset = window->scroll_max - window->text_h;
 			}
 			if (window->scroll_callback)
 				window->scroll_callback(window->scroll_cb_data, (long)window->scroll_offset);
diff --git a/window.h b/window.h
@@ -74,7 +74,7 @@ void clipboard_paste_clipboard(ledit_window *window);
 void clipboard_paste_primary(ledit_window *window);
 txtbuf *ledit_window_get_primary_clipboard_buffer(void);
 
-int ledit_window_button_press(ledit_window *window, XEvent *event);
+int ledit_window_button_press(ledit_window *window, XEvent *event, int scroll_num);
 int ledit_window_button_release(ledit_window *window, XEvent *event);
 int ledit_window_drag_motion(ledit_window *window, XEvent *event);
 int ledit_window_clipboard_event(ledit_window *window, XEvent *event);