URI: 
       tMake selecting and scrolling work somewhat better - ledit - Text editor (WIP)
  HTML git clone git://lumidify.org/ledit.git (fast, but not encrypted)
  HTML git clone https://lumidify.org/git/ledit.git (encrypted, but very slow)
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
   DIR commit b3a32dd52672a8a385308afb73198d129294c7c3
   DIR parent 91a730677ac95f922d6e5bca531f9cf0811e0fd6
  HTML Author: lumidify <nobody@lumidify.org>
       Date:   Thu, 11 Nov 2021 13:41:07 +0100
       
       Make selecting and scrolling work somewhat better
       
       Diffstat:
         M Makefile                            |       1 +
         M buffer.c                            |      41 +++++++++++++++++++++++--------
         A config.h                            |       5 +++++
         M ledit.c                             |      70 +++++++++++++++++++++++++++----
         M theme_config.h                      |       2 +-
         M window.c                            |      16 +++++-----------
         M window.h                            |       2 +-
       
       7 files changed, 106 insertions(+), 31 deletions(-)
       ---
   DIR diff --git a/Makefile b/Makefile
       t@@ -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
   DIR diff --git a/buffer.c b/buffer.c
       t@@ -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);
       t@@ -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);
       t@@ -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;
                        }
   DIR diff --git a/config.h b/config.h
       t@@ -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
   DIR diff --git a/ledit.c b/ledit.c
       t@@ -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 */
       t@@ -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>
       t@@ -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"
       t@@ -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);
        
       t@@ -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;
       t@@ -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;
       t@@ -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;
       t@@ -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;
                }
        }
        
       t@@ -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
   DIR diff --git a/theme_config.h b/theme_config.h
       t@@ -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";
   DIR diff --git a/window.c b/window.c
       t@@ -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) {
       t@@ -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);
   DIR diff --git a/window.h b/window.h
       t@@ -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);