Skip to content
Snippets Groups Projects
Select Git revision
  • 2d0e309dbbf8a5a4365777b6c22b83afcc35b487
  • main default protected
  • phhw
  • captouch-threshold
  • t
  • dos
  • test2
  • test
  • slewtest
  • simtest
  • view-think
  • vm-pending
  • media-buf
  • scope
  • passthrough
  • wave
  • vsync
  • dos-main-patch-50543
  • json-error
  • rahix/big-flow3r
  • pippin/media_framework
  • v1.3.0
  • v1.2.0
  • v1.2.0+rc1
  • v1.1.1
  • v1.1.0
  • v1.1.0+rc1
  • v1.0.0
  • v1.0.0+rc6
  • v1.0.0+rc5
  • v1.0.0+rc4
  • v1.0.0+rc3
  • v1.0.0+rc2
  • v1.0.0+rc1
34 results

interactions.py

Blame
  • Forked from flow3r / flow3r firmware
    Source project has a limited visibility.
    interactions.py 5.21 KiB
    import st4m
    
    from st4m.input import InputState
    from st4m.ui.ctx import Ctx
    from st4m import Responder
    
    
    class ScrollController(st4m.Responder):
        """
        ScrolLController is a controller for physically scrollable one-dimensional
        lists.
    
        It allows navigating with explicit 'left'/'right' commands (triggered by eg.
        buttons). In the future it will also allow navigating by captouch gestures.
    
        Its output is the information about a 'target position' (integer from 0 to
        Nitems-1) a 'current position' (float from -inf to +inf).
    
        The target position is the user intent, ie. what the user likely selected.
        This value should be used to interpret what the user has currently selected.
    
        The current position is the animated 'current' position, which includes
        effects like acceleration and past-end-of-list bounce-back. This value
        should be used to render the current state of the scrolling list.
        """
    
        __slots__ = (
            "_nitems",
            "_target_position",
            "_current_position",
            "_velocity",
        )
    
        def __init__(self, wrap=False) -> None:
            self._nitems = 0
            self._target_position = 0
            self._current_position = 0.0
            self._velocity: float = 0.0
            self.wrap = wrap
    
        def set_item_count(self, count: int) -> None:
            """
            Set how many items this scrollable list contains. Currently, updating
            the item count does not gracefully handle current/target position
            switching - it will be clamped to the new length. A different API should
            provide the ability to remove a [start,end) range of items from the list
            and update positions gracefully according to that.
            """
            if count < 0:
                count = 0
            self._nitems = count
    
        def scroll_left(self) -> None:
            """
            Call when the user wants to scroll left by discrete action (eg. button
            press).
            """
            self._target_position -= 1
            self._velocity = -10
    
        def scroll_right(self) -> None:
            """
            Call when the user wants to scroll right by discrete action (eg. button
            press).
            """
            self._target_position += 1
            self._velocity = 10
    
        def think(self, ins: InputState, delta_ms: int) -> None:
            if self._nitems == 0:
                self._target_position = 0
                self._current_position = 0
                self._velocity = 0
                return
    
            if self.wrap:
                self._target_position = self._target_position % self._nitems
            else:
                if self._target_position < 0:
                    self._target_position = 0
                if self._target_position >= self._nitems:
                    self._target_position = self._nitems - 1
    
            self._physics_step(delta_ms / 1000.0)
    
        def draw(self, ctx: Ctx) -> None:
            pass
    
        def current_position(self) -> float:
            """
            Return current position, a float from -inf to +inf.
    
            In general, this value will be within [O, Nitems), but might go out of
            bounds when rendering the bounceback effect.
    
            With every tick, this value moves closer and closer to target_position().
    
            Use this value to animate the scroll list.
            """
            return self._current_position
    
        def target_position(self) -> int:
            """
            Return user-selected 'target' position, an integer in [0, Nitems).
    
            Use this value to interpret the user selection when eg. a 'select'
            button is pressed.
            """
            return self._target_position
    
        def at_left_limit(self) -> bool:
            """
            Returns true if the scrollable list is at its leftmost (0) position.
            """
            return self._target_position <= 0
    
        def at_right_limit(self) -> bool:
            """
            Returns true if the scrollable list is as its rightmost (Nitems-1)
            position.
            """
            return self._target_position >= self._nitems - 1
    
        def _physics_step(self, delta: float) -> None:
            diff = self._nitems
            for i in [0, 1, -1]:
                d = (
                    float(self._target_position) + self._nitems * i
                ) - self._current_position
                if abs(d) < abs(diff):
                    diff = d
            # diff =
            # print(diff)
            max_velocity = 500
            velocity = self._velocity
    
            if abs(diff) > 0.2:
                # Apply force to reach target position.
                if diff > 0:
                    velocity += 80 * delta
                else:
                    velocity -= 80 * delta
    
                # Clamp velocity.
                if velocity > max_velocity:
                    velocity = max_velocity
                if velocity < -max_velocity:
                    velocity = -max_velocity
                self._velocity = velocity
            else:
                # Try to snap to target position.
                pos = self._velocity > 0 and diff > 0
                neg = self._velocity < 0 and diff < 0
                if self.wrap or pos or neg:
                    self._current_position = self._target_position
                    self._velocity = 0
    
            self._physics_integrate(delta)
    
        def _physics_integrate(self, delta: float) -> None:
            self._velocity -= self._velocity * delta * 10
            self._current_position = (
                self._current_position + self._velocity * delta
            ) % self._nitems