diff --git a/python_payload/st3m/input.py b/python_payload/st3m/input.py index 8d199b6395adddb3a64d5673988872a2fae43cf1..00aebf7966ca7e035964e6b4acd1464361200c51 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 8e2e4506e0880e3956849ec8a07bfa9e5e2f01bc..f5bbae1c39d798cd883e881b23d4c753fd488ecc 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 6dc1ec230b84582bdb3942357b415a1c981ab847..c0fb5fd66e912f5ab1ca55adf4f928f8498ba0d7 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 500cea8153c74268adf907dd48c4e34a6c3b0e95..e72e3bfdd43a9e280fc770c8852e098635f7c0d2 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: