summary refs log tree commit diff
path: root/quantum
diff options
context:
space:
mode:
authorSelene ToyKeeper <ToyKeeper@users.noreply.github.com>2022-10-26 13:49:44 -0600
committerGitHub <noreply@github.com>2022-10-26 20:49:44 +0100
commit2a61bfc27d6915877d18a09bc440d462a2051857 (patch)
tree72070605dda5410359774a33f52f0a459b8b184e /quantum
parent4c0c491e31535292194cc812cde483d223d16ed7 (diff)
add "inertia" mode for mouse keys (#18774)
Co-authored-by: Selene ToyKeeper <git@toykeeper.net>
Diffstat (limited to 'quantum')
-rw-r--r--quantum/mousekey.c147
-rw-r--r--quantum/mousekey.h39
2 files changed, 175 insertions, 11 deletions
diff --git a/quantum/mousekey.c b/quantum/mousekey.c
index 25a89bdba7..b91db80de6 100644
--- a/quantum/mousekey.c
+++ b/quantum/mousekey.c
@@ -37,6 +37,13 @@ static void           mousekey_debug(void);
 static uint8_t        mousekey_accel        = 0;
 static uint8_t        mousekey_repeat       = 0;
 static uint8_t        mousekey_wheel_repeat = 0;
+#ifdef MOUSEKEY_INERTIA
+static uint8_t mousekey_frame     = 0; // track whether gesture is inactive, first frame, or repeating
+static int8_t  mousekey_x_dir     = 0; // -1 / 0 / 1 = left / neutral / right
+static int8_t  mousekey_y_dir     = 0; // -1 / 0 / 0 = up / neutral / down
+static int8_t  mousekey_x_inertia = 0; // current velocity, limit +/- MOUSEKEY_TIME_TO_MAX
+static int8_t  mousekey_y_inertia = 0; // ...
+#endif
 #ifdef MK_KINETIC_SPEED
 static uint16_t mouse_timer = 0;
 #endif
@@ -76,6 +83,7 @@ uint8_t mk_wheel_time_to_max = MOUSEKEY_WHEEL_TIME_TO_MAX;
 
 #    ifndef MK_COMBINED
 #        ifndef MK_KINETIC_SPEED
+#            ifndef MOUSEKEY_INERTIA
 
 /* Default accelerated mode */
 
@@ -97,6 +105,53 @@ static uint8_t move_unit(void) {
     return (unit > MOUSEKEY_MOVE_MAX ? MOUSEKEY_MOVE_MAX : (unit == 0 ? 1 : unit));
 }
 
+#            else // MOUSEKEY_INERTIA mode
+
+static int8_t move_unit(uint8_t axis) {
+    int16_t unit;
+
+    // handle X or Y axis
+    int8_t inertia, dir;
+    if (axis) {
+        inertia = mousekey_y_inertia;
+        dir     = mousekey_y_dir;
+    } else {
+        inertia = mousekey_x_inertia;
+        dir     = mousekey_x_dir;
+    }
+
+    if (mousekey_frame < 2) { // first frame(s): initial keypress moves one pixel
+        mousekey_frame = 1;
+        unit           = dir * MOUSEKEY_MOVE_DELTA;
+    } else { // acceleration
+        // linear acceleration (is here for reference, but doesn't feel as good during use)
+        // unit = (MOUSEKEY_MOVE_DELTA * mk_max_speed * inertia) / mk_time_to_max;
+
+        // x**2 acceleration (quadratic, more precise for short movements)
+        int16_t percent = (inertia << 8) / mk_time_to_max;
+        percent         = (percent * percent) >> 8;
+        if (inertia < 0) percent = -percent;
+
+        // unit = sign(inertia) + (percent of max speed)
+        if (inertia > 0)
+            unit = 1;
+        else if (inertia < 0)
+            unit = -1;
+        else
+            unit = 0;
+
+        unit = unit + ((mk_max_speed * percent) >> 8);
+    }
+
+    if (unit > MOUSEKEY_MOVE_MAX)
+        unit = MOUSEKEY_MOVE_MAX;
+    else if (unit < -MOUSEKEY_MOVE_MAX)
+        unit = -MOUSEKEY_MOVE_MAX;
+    return unit;
+}
+
+#            endif // end MOUSEKEY_INERTIA mode
+
 static uint8_t wheel_unit(void) {
     uint16_t unit;
     if (mousekey_accel & (1 << 0)) {
@@ -213,6 +268,28 @@ static uint8_t wheel_unit(void) {
 
 #    endif /* #ifndef MK_COMBINED */
 
+#    ifdef MOUSEKEY_INERTIA
+
+static int8_t calc_inertia(int8_t direction, int8_t velocity) {
+    // simulate acceleration and deceleration
+
+    // deceleration
+    if ((direction > -1) && (velocity < 0))
+        velocity = (velocity + 1) * (256 - MOUSEKEY_FRICTION) / 256;
+    else if ((direction < 1) && (velocity > 0))
+        velocity = velocity * (256 - MOUSEKEY_FRICTION) / 256;
+
+    // acceleration
+    if ((direction > 0) && (velocity < mk_time_to_max))
+        velocity++;
+    else if ((direction < 0) && (velocity > -mk_time_to_max))
+        velocity--;
+
+    return velocity;
+}
+
+#    endif
+
 void mousekey_task(void) {
     // report cursor and scroll movement independently
     report_mouse_t tmpmr = mouse_report;
@@ -222,6 +299,32 @@ void mousekey_task(void) {
     mouse_report.v = 0;
     mouse_report.h = 0;
 
+#    ifdef MOUSEKEY_INERTIA
+
+    // if an animation is in progress and it's time for the next frame
+    if ((mousekey_frame) && timer_elapsed(last_timer_c) > ((mousekey_frame > 1) ? mk_interval : mk_delay * 10)) {
+        mousekey_x_inertia = calc_inertia(mousekey_x_dir, mousekey_x_inertia);
+        mousekey_y_inertia = calc_inertia(mousekey_y_dir, mousekey_y_inertia);
+
+        mouse_report.x = move_unit(0);
+        mouse_report.y = move_unit(1);
+
+        // prevent sticky "drift"
+        if ((!mousekey_x_dir) && (!mousekey_x_inertia)) tmpmr.x = 0;
+        if ((!mousekey_y_dir) && (!mousekey_y_inertia)) tmpmr.y = 0;
+
+        if (mousekey_frame < 2) mousekey_frame++;
+    }
+
+    // reset if not moving and no movement keys are held
+    if ((!mousekey_x_dir) && (!mousekey_y_dir) && (!mousekey_x_inertia) && (!mousekey_y_inertia)) {
+        mousekey_frame = 0;
+        tmpmr.x        = 0;
+        tmpmr.y        = 0;
+    }
+
+#    else // default acceleration
+
     if ((tmpmr.x || tmpmr.y) && timer_elapsed(last_timer_c) > (mousekey_repeat ? mk_interval : mk_delay * 10)) {
         if (mousekey_repeat != UINT8_MAX) mousekey_repeat++;
         if (tmpmr.x != 0) mouse_report.x = move_unit() * ((tmpmr.x > 0) ? 1 : -1);
@@ -239,6 +342,9 @@ void mousekey_task(void) {
             }
         }
     }
+
+#    endif // MOUSEKEY_INERTIA or not
+
     if ((tmpmr.v || tmpmr.h) && timer_elapsed(last_timer_w) > (mousekey_wheel_repeat ? mk_wheel_interval : mk_wheel_delay * 10)) {
         if (mousekey_wheel_repeat != UINT8_MAX) mousekey_wheel_repeat++;
         if (tmpmr.v != 0) mouse_report.v = wheel_unit() * ((tmpmr.v > 0) ? 1 : -1);
@@ -260,6 +366,7 @@ void mousekey_task(void) {
     if (has_mouse_report_changed(&mouse_report, &tmpmr) || should_mousekey_report_send(&mouse_report)) {
         mousekey_send();
     }
+    // save the state for later
     memcpy(&mouse_report, &tmpmr, sizeof(tmpmr));
 }
 
@@ -270,6 +377,19 @@ void mousekey_on(uint8_t code) {
     }
 #    endif /* #ifdef MK_KINETIC_SPEED */
 
+#    ifdef MOUSEKEY_INERTIA
+
+    // initial keypress sets impulse and activates first frame of movement
+    if ((code == KC_MS_UP) || (code == KC_MS_DOWN)) {
+        mousekey_y_dir = (code == KC_MS_DOWN) ? 1 : -1;
+        if (mousekey_frame < 2) mouse_report.y = move_unit(1);
+    } else if ((code == KC_MS_LEFT) || (code == KC_MS_RIGHT)) {
+        mousekey_x_dir = (code == KC_MS_RIGHT) ? 1 : -1;
+        if (mousekey_frame < 2) mouse_report.x = move_unit(0);
+    }
+
+#    else // no inertia
+
     if (code == KC_MS_UP)
         mouse_report.y = move_unit() * -1;
     else if (code == KC_MS_DOWN)
@@ -278,6 +398,9 @@ void mousekey_on(uint8_t code) {
         mouse_report.x = move_unit() * -1;
     else if (code == KC_MS_RIGHT)
         mouse_report.x = move_unit();
+
+#    endif // inertia or not
+
     else if (code == KC_MS_WH_UP)
         mouse_report.v = wheel_unit();
     else if (code == KC_MS_WH_DOWN)
@@ -297,6 +420,20 @@ void mousekey_on(uint8_t code) {
 }
 
 void mousekey_off(uint8_t code) {
+#    ifdef MOUSEKEY_INERTIA
+
+    // key release clears impulse unless opposite direction is held
+    if ((code == KC_MS_UP) && (mousekey_y_dir < 1))
+        mousekey_y_dir = 0;
+    else if ((code == KC_MS_DOWN) && (mousekey_y_dir > -1))
+        mousekey_y_dir = 0;
+    else if ((code == KC_MS_LEFT) && (mousekey_x_dir < 1))
+        mousekey_x_dir = 0;
+    else if ((code == KC_MS_RIGHT) && (mousekey_x_dir > -1))
+        mousekey_x_dir = 0;
+
+#    else // no inertia
+
     if (code == KC_MS_UP && mouse_report.y < 0)
         mouse_report.y = 0;
     else if (code == KC_MS_DOWN && mouse_report.y > 0)
@@ -305,6 +442,9 @@ void mousekey_off(uint8_t code) {
         mouse_report.x = 0;
     else if (code == KC_MS_RIGHT && mouse_report.x > 0)
         mouse_report.x = 0;
+
+#    endif // inertia or not
+
     else if (code == KC_MS_WH_UP && mouse_report.v > 0)
         mouse_report.v = 0;
     else if (code == KC_MS_WH_DOWN && mouse_report.v < 0)
@@ -476,6 +616,13 @@ void mousekey_clear(void) {
     mousekey_repeat       = 0;
     mousekey_wheel_repeat = 0;
     mousekey_accel        = 0;
+#ifdef MOUSEKEY_INERTIA
+    mousekey_frame     = 0;
+    mousekey_x_inertia = 0;
+    mousekey_y_inertia = 0;
+    mousekey_x_dir     = 0;
+    mousekey_y_dir     = 0;
+#endif
 }
 
 static void mousekey_debug(void) {
diff --git a/quantum/mousekey.h b/quantum/mousekey.h
index da2edb481a..e968e000c0 100644
--- a/quantum/mousekey.h
+++ b/quantum/mousekey.h
@@ -36,34 +36,48 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #    endif
 
 #    ifndef MOUSEKEY_MOVE_DELTA
-#        ifndef MK_KINETIC_SPEED
-#            define MOUSEKEY_MOVE_DELTA 8
-#        else
+#        if defined(MK_KINETIC_SPEED)
 #            define MOUSEKEY_MOVE_DELTA 16
+#        elif defined(MOUSEKEY_INERTIA)
+#            define MOUSEKEY_MOVE_DELTA 1
+#        else
+#            define MOUSEKEY_MOVE_DELTA 8
 #        endif
 #    endif
 #    ifndef MOUSEKEY_WHEEL_DELTA
 #        define MOUSEKEY_WHEEL_DELTA 1
 #    endif
 #    ifndef MOUSEKEY_DELAY
-#        ifndef MK_KINETIC_SPEED
-#            define MOUSEKEY_DELAY 10
-#        else
+#        if defined(MK_KINETIC_SPEED)
 #            define MOUSEKEY_DELAY 5
+#        elif defined(MOUSEKEY_INERTIA)
+#            define MOUSEKEY_DELAY 150 // allow single-pixel movements before repeat activates
+#        else
+#            define MOUSEKEY_DELAY 10
 #        endif
 #    endif
 #    ifndef MOUSEKEY_INTERVAL
-#        ifndef MK_KINETIC_SPEED
-#            define MOUSEKEY_INTERVAL 20
-#        else
+#        if defined(MK_KINETIC_SPEED)
 #            define MOUSEKEY_INTERVAL 10
+#        elif defined(MOUSEKEY_INERTIA)
+#            define MOUSEKEY_INTERVAL 16 // 60 fps
+#        else
+#            define MOUSEKEY_INTERVAL 20
 #        endif
 #    endif
 #    ifndef MOUSEKEY_MAX_SPEED
-#        define MOUSEKEY_MAX_SPEED 10
+#        if defined(MOUSEKEY_INERTIA)
+#            define MOUSEKEY_MAX_SPEED 32
+#        else
+#            define MOUSEKEY_MAX_SPEED 10
+#        endif
 #    endif
 #    ifndef MOUSEKEY_TIME_TO_MAX
-#        define MOUSEKEY_TIME_TO_MAX 30
+#        if defined(MOUSEKEY_INERTIA)
+#            define MOUSEKEY_TIME_TO_MAX 32
+#        else
+#            define MOUSEKEY_TIME_TO_MAX 30
+#        endif
 #    endif
 #    ifndef MOUSEKEY_WHEEL_DELAY
 #        define MOUSEKEY_WHEEL_DELAY 10
@@ -78,6 +92,9 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #        define MOUSEKEY_WHEEL_TIME_TO_MAX 40
 #    endif
 
+#    ifndef MOUSEKEY_FRICTION
+#        define MOUSEKEY_FRICTION 24 // 0 to 255
+#    endif
 #    ifndef MOUSEKEY_INITIAL_SPEED
 #        define MOUSEKEY_INITIAL_SPEED 100
 #    endif