From 328777361fefdac7ba912329160451fd498b8f7e Mon Sep 17 00:00:00 2001
From: moon2 <moon2protonmail@protonmail.com>
Date: Mon, 23 Dec 2024 13:26:51 +0100
Subject: [PATCH 1/5] indent settings files

---
 python_payload/st3m/application_settings.py | 2 +-
 python_payload/st3m/settings.py             | 4 +++-
 2 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/python_payload/st3m/application_settings.py b/python_payload/st3m/application_settings.py
index 027b7818b9..f35fb93b15 100644
--- a/python_payload/st3m/application_settings.py
+++ b/python_payload/st3m/application_settings.py
@@ -120,7 +120,7 @@ def _save_application_data():
             log.info("Could not save application settings: " + str(e))
     with open(_application_settings_path, "w") as f:
         try:
-            f.write(json.dumps(_application_data))
+            f.write(json.dumps(_application_data, indent=4))
         except Exception as e:
             log.info("Could not save application settings: " + str(e))
 
diff --git a/python_payload/st3m/settings.py b/python_payload/st3m/settings.py
index b35d077701..7f125da2f2 100644
--- a/python_payload/st3m/settings.py
+++ b/python_payload/st3m/settings.py
@@ -377,7 +377,9 @@ def save_all() -> None:
     for setting in load_save_settings:
         res = _update(res, setting.save())
     try:
-        saved_settings = save_file_if_changed(SETTINGS_JSON_FILE, json.dumps(res))
+        saved_settings = save_file_if_changed(
+            SETTINGS_JSON_FILE, json.dumps(res, indent=4)
+        )
     except Exception as e:
         log.warning("Could not save settings: " + str(e))
         return
-- 
GitLab


From 05ef2fdcffceaf69fb287c7c03d68d1acf8a53fd Mon Sep 17 00:00:00 2001
From: moon2 <moon2protonmail@protonmail.com>
Date: Mon, 23 Dec 2024 14:22:06 +0100
Subject: [PATCH 2/5] widgets: various minor fixes

---
 python_payload/st3m/input.py               | 29 ++++----
 python_payload/st3m/ui/elements/menus.py   | 10 ++-
 python_payload/st3m/ui/interactions.py     | 32 ++++-----
 python_payload/st3m/ui/widgets/__init__.py | 78 ++++++++++++++++------
 4 files changed, 94 insertions(+), 55 deletions(-)

diff --git a/python_payload/st3m/input.py b/python_payload/st3m/input.py
index 8d199b6395..00aebf7966 100644
--- a/python_payload/st3m/input.py
+++ b/python_payload/st3m/input.py
@@ -279,7 +279,6 @@ class Pressable:
 class TouchableState(Enum):
     UP = "up"
     BEGIN = "begin"
-    RESTING = "resting"
     MOVED = "moved"
     ENDED = "ended"
 
@@ -345,16 +344,17 @@ class Touchable:
 
     def __init__(self, ix) -> None:
         self._state = self.UP
-        self._dis = None
-        self._vel = 0j
         self._ts_prev = 0
 
         conf = captouch.Config.empty()
         self._scroller = widgets.Scroller(
             conf, ix, constraint=widgets.constraints.Ellipse()
         )
+        # somewhat illegal, just for the backwards hack...
         self._scroller.constraint = None
+
         self._active_prev = False
+        self._cached_gesture = None
 
     def _update(self, ts: int, petal: captouch.CaptouchPetalState) -> None:
         """
@@ -363,23 +363,20 @@ class Touchable:
         self._scroller.think(None, ts - self._ts_prev, petal)
         self._ts_prev = ts
 
+        self._cached_gesture = None
         if self._scroller.active:
             if self._active_prev == self._scroller.active:
                 self._state = self.MOVED
-                self._dis = self._scroller.pos
             else:
                 self._state = self.BEGIN
-                self._vel = 0j
-                self._dis = 0j
         else:
-            if self._active_prev == self._scroller.active:
-                self._state = self.UP
-                self._dis = None
-            else:
-                self._state = self.ENDED
-                self._vel = self._scroller._vel
+            self._state = self.ENDED
+            if self._scroller.pos or self._scroller._vel:
+                self._cached_gesture = self.current_gesture()
                 self._scroller.pos = 0j
                 self._scroller._vel = 0j
+            elif self._active_prev == self._scroller.active:
+                self._state = self.UP
         self._active_prev = self._scroller.active
 
     def phase(self) -> TouchableState:
@@ -389,10 +386,12 @@ class Touchable:
         return self._state
 
     def current_gesture(self) -> Optional[Gesture]:
-        if self._dis is None:
+        if self._cached_gesture:
+            return self._cached_gesture
+        if self._state == self.UP:
             return None
-        dis = self._dis * 35000
-        vel = self._vel * 35000 * 1000
+        dis = self._scroller.pos * 35000
+        vel = self._scroller._vel * 35000 * 1000
         return self.Gesture(*[(p.real, p.imag) for p in [dis, vel]])
 
 
diff --git a/python_payload/st3m/ui/elements/menus.py b/python_payload/st3m/ui/elements/menus.py
index 8e2e4506e0..f5bbae1c39 100644
--- a/python_payload/st3m/ui/elements/menus.py
+++ b/python_payload/st3m/ui/elements/menus.py
@@ -97,8 +97,14 @@ class OSMenu(SimpleMenu):
 
     def __init__(self, items: List[MenuItem]) -> None:
         super().__init__(items)
-        self.captouch_config = captouch.Config.default()
-        self._scroller = widgets.MultiItemScroller(self.captouch_config, petals=[2, 8])
+        self.captouch_config = captouch.Config.empty()
+        for x in range(10):
+            self.captouch_config.petals[x].set_min_mode(1)
+        self.captouch_config.petals[5].mode = 0
+
+        self._scroller = widgets.MultiItemScroller(
+            self.captouch_config, petals=[2, 8], friction=1
+        )
         self._scroller.max_item = len(items) - 1
         self._scroll_controller = self._FakeScrollController(self._scroller)
         self.sensitivity = 1.5
diff --git a/python_payload/st3m/ui/interactions.py b/python_payload/st3m/ui/interactions.py
index 6dc1ec230b..c0fb5fd66e 100644
--- a/python_payload/st3m/ui/interactions.py
+++ b/python_payload/st3m/ui/interactions.py
@@ -207,14 +207,21 @@ class CapScrollController:
         """
         Call this in your think() method.
         """
-        if t.phase() == t.BEGIN:
-            self._grab_start = self.position
-            self.momentum = (0.0, 0.0)
-
-        if t.phase() == t.MOVED and self._grab_start is not None:
+        if t.phase() == t.UP:
+            rad_p, phi_p = self.position
+            rad_m, phi_m = self.momentum
+            rad_p += rad_m / (1000 / delta_ms)
+            phi_p += phi_m / (1000 / delta_ms)
+            rad_m *= self._damp
+            phi_m *= self._damp
+            self.momentum = (rad_m, phi_m)
+            self.position = (rad_p, phi_p)
+            self._grab_start = None
+        else:
+            if self._grab_start is None:
+                self._grab_start = self.position
             move = t.current_gesture()
             assert move is not None
-            assert self._grab_start is not None
             drad, dphi = move.distance
             drad /= 1000
             dphi /= 1000
@@ -222,20 +229,7 @@ class CapScrollController:
             sphi = self._grab_start[1]
             self.position = (srad + drad, sphi + dphi)
 
-        if t.phase() == t.ENDED:
-            move = t.current_gesture()
-            assert move is not None
             vrad, vphi = move.velocity
             vrad /= 1000
             vphi /= 1000
             self.momentum = (vrad, vphi)
-
-        if t.phase() == t.UP:
-            rad_p, phi_p = self.position
-            rad_m, phi_m = self.momentum
-            rad_p += rad_m / (1000 / delta_ms)
-            phi_p += phi_m / (1000 / delta_ms)
-            rad_m *= self._damp
-            phi_m *= self._damp
-            self.momentum = (rad_m, phi_m)
-            self.position = (rad_p, phi_p)
diff --git a/python_payload/st3m/ui/widgets/__init__.py b/python_payload/st3m/ui/widgets/__init__.py
index 500cea8153..e72e3bfdd4 100644
--- a/python_payload/st3m/ui/widgets/__init__.py
+++ b/python_payload/st3m/ui/widgets/__init__.py
@@ -241,6 +241,7 @@ class CaptouchWidget(Widget):
             )
 
     def _apply_velocity(self, delta_ms):
+        vel_prev = self._vel
         if (not self._vel) or abs(self._vel) < 0.0001:
             self._vel = 0j
             return False
@@ -437,7 +438,7 @@ class Scroller(PetalWidget):
     def _autoclear(self):
         self._update_pos()
         self._ref = None
-        if self._log.length_ms() > 40:
+        if self._log.length_ms() > 27:
             start = 0
             stop = self._log.length() - 1
             if stop > 2:
@@ -446,7 +447,12 @@ class Scroller(PetalWidget):
                 start = self._log.index_offset_ms(-1, -70)
             if self._log.index_offset_ms(start, 20) is not None:
                 vel = self._log.slope_per_ms(start, stop)
-                self._vel = vel * self.gain
+                # gate val experimentally determined on petal 2
+                # optimum might be different for each petal but good enough for now
+                if abs(vel) < 0.01:
+                    self._vel = 0
+                else:
+                    self._vel = vel * self.gain
                 self._pos_range = None
                 # trigger callbacks
                 self.active = True
@@ -662,30 +668,36 @@ class MultiSlider(CaptouchWidget):
 
 
 class MultiItemScroller(CaptouchWidget):
-    def __init__(self, config, *, petals):
-        self._widgets = []
-        super().__init__(config)
+    def __init__(self, config, *, petals, gain=1, friction=0.7):
+        super().__init__(config, gain=gain, friction=friction)
         self._item = 0
         self._max_item = None
+        self._vel = 0
         petals = set(petals)
         for petal in petals:
             if petal not in range(0, 10, 2):
                 raise ValueError("all petals must be top petals")
 
-        constraint = constraints.Rectangle(100)
+        constraint = constraints.Rectangle()
 
         self._widgets = [
             Scroller(
                 config,
                 x,
                 gain=self.gain * captouch.PETAL_ANGLES[x],
-                friction=1,
+                friction=self.friction,
                 constraint=constraint,
             )
             for x in petals
         ]
         for widget in self._widgets:
             widget.ref_precision = 3
+            widget.constraint = None
+
+    def add_to_config(self, config):
+        if hasattr(self, "_widgets"):
+            for widget in self._widgets:
+                widget.add_to_config(config)
 
     @property
     def gain(self):
@@ -694,8 +706,22 @@ class MultiItemScroller(CaptouchWidget):
     @gain.setter
     def gain(self, value):
         self._gain = value
-        for widget in self._widgets:
-            widget.gain = value * captouch.PETAL_ANGLES[widget.petal]
+        if hasattr(self, "_widgets"):
+            for widget in self._widgets:
+                widget.gain = value * captouch.PETAL_ANGLES[widget.petal]
+
+    @property
+    def friction(self):
+        return self._friction
+
+    @friction.setter
+    def friction(self, value):
+        if value != 1:
+            raise ValueError("unfinished widget, friction must be 1 for now, sorry!")
+        self._friction = value
+        if hasattr(self, "_widgets"):
+            for widget in self._widgets:
+                widget.friction = value
 
     @property
     def item(self):
@@ -725,10 +751,6 @@ class MultiItemScroller(CaptouchWidget):
         self._max_item = val
         self.item = self._item
 
-    def add_to_config(self, config):
-        for widget in self._widgets:
-            widget.add_to_config(config)
-
     def on_enter(self):
         for widget in self._widgets:
             widget.on_enter()
@@ -749,12 +771,30 @@ class MultiItemScroller(CaptouchWidget):
             else:
                 widget.pos = 0j
 
-        offset = self._item + delta - self.pos
-        if offset > 4:
-            offset = 4
-        elif offset < -4:
-            offset = -4
-        self.pos += offset * (1 - (0.002 ** (delta_ms / 1000)))
+        target = self._item + delta
+
+        delta_ms_phys = delta_ms
+        while delta_ms_phys:
+            if delta_ms_phys < 50:
+                delta_s = delta_ms_phys / 1000
+                delta_ms_phys = 0
+            else:
+                delta_s = 50 / 1000
+                delta_ms_phys -= 50
+            offset = target - self.pos
+            if offset > 4:
+                offset = 4
+            elif offset < -4:
+                offset = -4
+            acc = offset * 175
+            acc -= self._vel * 25
+            # unit: items per second
+            self._vel += acc * delta_s
+            # if self._vel > 8:
+            #    self._vel = 8
+            # elif self._vel < -8:
+            #    self._vel = -8
+            self.pos += self._vel * delta_s
 
         delta_hyst = int(delta * 1.5)
         if delta_hyst:
-- 
GitLab


From 897fd7a8740ff760026cf87ada20b8d9acbb0491 Mon Sep 17 00:00:00 2001
From: moon2 <moon2protonmail@protonmail.com>
Date: Mon, 23 Dec 2024 16:53:30 +0100
Subject: [PATCH 3/5] menu: fix delete target

This is due to a subtle change in behavior of our duck
_scroll_controller, but it's a good one so we'll keep it.
---
 python_payload/st3m/main_menu.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/python_payload/st3m/main_menu.py b/python_payload/st3m/main_menu.py
index 94f1e58e30..d24f918248 100644
--- a/python_payload/st3m/main_menu.py
+++ b/python_payload/st3m/main_menu.py
@@ -251,10 +251,10 @@ class ApplicationMenu(RestoreMenu):
         self.tags = []
 
     def _pop_target_item(self):
-        self._scroll_controller.set_item_count(len(self._items) - 1)
         pos = self._scroll_controller.target_position()
         self._items.pop(pos)
         self._scroll_controller.set_position(pos - 1)
+        self._scroll_controller.set_item_count(len(self._items))
 
     def _sort(self):
         def sort_key(item):
-- 
GitLab


From cde043461e00c90ac8514fb415615b1a8caaa917 Mon Sep 17 00:00:00 2001
From: moon2 <moon2protonmail@protonmail.com>
Date: Mon, 23 Dec 2024 17:26:25 +0100
Subject: [PATCH 4/5] menu: fix post sort scroll

---
 python_payload/st3m/ui/elements/menus.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/python_payload/st3m/ui/elements/menus.py b/python_payload/st3m/ui/elements/menus.py
index f5bbae1c39..902b57fa59 100644
--- a/python_payload/st3m/ui/elements/menus.py
+++ b/python_payload/st3m/ui/elements/menus.py
@@ -93,6 +93,7 @@ class OSMenu(SimpleMenu):
             self._scroller.item = int(position)
 
         def set_position(self, position):
+            self._scroller.item = int(position)
             self._scroller.pos = int(position)
 
     def __init__(self, items: List[MenuItem]) -> None:
-- 
GitLab


From e7f774c9872720241eed9f6f441effdc5fa73c14 Mon Sep 17 00:00:00 2001
From: moon2 <moon2protonmail@protonmail.com>
Date: Mon, 23 Dec 2024 17:57:57 +0100
Subject: [PATCH 5/5] captouch: linearized bottom petal pos a bit

---
 components/flow3r_bsp/flow3r_bsp_ad7147.c | 27 ++++++++++++++++++++---
 1 file changed, 24 insertions(+), 3 deletions(-)

diff --git a/components/flow3r_bsp/flow3r_bsp_ad7147.c b/components/flow3r_bsp/flow3r_bsp_ad7147.c
index 811450d072..0f41fc3a67 100644
--- a/components/flow3r_bsp/flow3r_bsp_ad7147.c
+++ b/components/flow3r_bsp/flow3r_bsp_ad7147.c
@@ -1401,6 +1401,16 @@ static inline void pos_calc_bot(int index, int mode, int *rad, int *raw_sum,
         int rad_calc = tip - base;
         rad_calc *= petal_pos_gain[index];
         rad_calc /= ((tip + base) >> 2) + 1;
+
+        rad_calc = 16384 - rad_calc;
+
+        int rad_calc_gain = rad_calc > 0 ? rad_calc : 0;
+        rad_calc_gain = (rad_calc_gain + 32768) >> 5;
+        rad_calc_gain = (rad_calc_gain * rad_calc_gain) >> 12;
+        rad_calc = (rad_calc * rad_calc_gain) >> 10;
+
+        rad_calc = 16384 - rad_calc;
+
         *rad = rad_calc;
     } else {
         *rad = 0;
@@ -1435,9 +1445,20 @@ static inline void pos_calc_top2(int index, int mode, int *rad, int *phi,
     }
 }
 
-static void pos_calc_bot2(int index, int mode, int *rad, int *raw_sum,
-                          uint16_t *raw_petal) {
-    pos_calc_bot(index, mode, rad, raw_sum, raw_petal);
+static inline void pos_calc_bot2(int index, int mode, int *rad, int *raw_sum,
+                                 uint16_t *raw_petal) {
+    int32_t tip = raw_petal[petal_pad_tip];
+    int32_t base = raw_petal[petal_pad_base];
+    *raw_sum = base + tip;
+    if (mode > 1) {
+        base += ((base * 3) >> 2);  // tiny gain correction
+        int rad_calc = tip - base;
+        rad_calc *= petal_pos_gain[index];
+        rad_calc /= ((tip + base) >> 2) + 1;
+        *rad = rad_calc;
+    } else {
+        *rad = 0;
+    }
 }
 #endif
 
-- 
GitLab