diff --git a/python_payload/apps/demo_sparabo.py b/python_payload/apps/demo_sparabo.py index fcf6648bb4f96e4c4ef12d33d34c689e079275ba..739826cb134b91a51ecce76d29d1c6fa8315ae5c 100644 --- a/python_payload/apps/demo_sparabo.py +++ b/python_payload/apps/demo_sparabo.py @@ -21,7 +21,9 @@ class AppSparabo(application.Application): self.synth.decay_ms(250) print("here") - self.sequencer = event.Sequence(bpm=160, steps=8, action=self.on_step, loop=True) + self.sequencer = event.Sequence( + bpm=160, steps=8, action=self.on_step, loop=True + ) self.sequencer.start() if self.sequencer.repeat_event: self.add_event(self.sequencer.repeat_event) @@ -34,7 +36,7 @@ class AppSparabo(application.Application): synth.start() self.x, self.y = ui.xy_from_polar(90, -2 * math.pi / 8 * data["step"] + math.pi) - self.step = data['step'] + self.step = data["step"] def on_draw(self, ctx): x, y = self.x, self.y diff --git a/python_payload/apps/flow3r/menu_main.py b/python_payload/apps/flow3r/menu_main.py index ac2a47aacfc62a93e8ebf1a9b44fb649f450fdbd..308c92645c8da9d9f9ec07a2d1175b26ddeadc73 100644 --- a/python_payload/apps/flow3r/menu_main.py +++ b/python_payload/apps/flow3r/menu_main.py @@ -8,7 +8,6 @@ from apps.flow3r import menu_settings, menu_tinysynth, menu_crazysynth import time - def get_menu(app): menu_main = menu.Menu("flow3r", has_back=False) menu_badge = menu.Menu("badge") diff --git a/python_payload/apps/flow3r/menu_tinysynth.py b/python_payload/apps/flow3r/menu_tinysynth.py index 1e725d2e76df558cdb27c6a61d36a3e06afcbc24..b534a941e023062a530bbc922525a0c6a764aa01 100644 --- a/python_payload/apps/flow3r/menu_tinysynth.py +++ b/python_payload/apps/flow3r/menu_tinysynth.py @@ -6,6 +6,7 @@ from st3m.system import hardware, audio synth = tinysynth(440) synth.sustain(1) + def set_play(value): print("set_controls_overlay") if value: diff --git a/python_payload/apps/harmonic_demo.py b/python_payload/apps/harmonic_demo.py index d8c361a2fe99e2d6d70a343b374949b4cf412e84..9301bea890c28e7aa919d0011f826947c3a11676 100644 --- a/python_payload/apps/harmonic_demo.py +++ b/python_payload/apps/harmonic_demo.py @@ -20,17 +20,15 @@ class HarmonicApp(Application): self.color_intensity = 0 self.chord_index = None self.chord = None - self.synths = [ - tinysynth(440) for i in range(15) - ] + self.synths = [tinysynth(440) for i in range(15)] for i, synth in enumerate(self.synths): synth.decay_ms(100) synth.sustain(0.5) - if(i<5): + if i < 5: synth.waveform(1) synth.volume(0.5) synth.release_ms(1200) - elif(i<10): + elif i < 10: synth.waveform(1) synth.attack_ms(300) synth.volume(0.1) @@ -75,18 +73,18 @@ class HarmonicApp(Application): else: k = int(i / 2) self.synths[k].tone(self.chord[k]) - self.synths[k+5].tone(12+self.chord[k]) - self.synths[k+10].tone(7+self.chord[k]) + self.synths[k + 5].tone(12 + self.chord[k]) + self.synths[k + 10].tone(7 + self.chord[k]) self.synths[k].start() - self.synths[k+5].start() - self.synths[k+10].start() + self.synths[k + 5].start() + self.synths[k + 10].start() self.color_intensity = 1.0 else: - if (1+i) % 2: + if (1 + i) % 2: k = int(i / 2) self.synths[k].stop() - self.synths[k+5].stop() - self.synths[k+10].stop() + self.synths[k + 5].stop() + self.synths[k + 10].stop() app = HarmonicApp("harmonic") diff --git a/python_payload/main_st4m.py b/python_payload/main_st4m.py index 9aaaddfa9d943e7210dc1534515a54ff14999969..4e2bd30ed4d34a9002a544553e0d60a4e64ccce7 100644 --- a/python_payload/main_st4m.py +++ b/python_payload/main_st4m.py @@ -11,7 +11,13 @@ import st4m from st4m.goose import Optional, List, ABCBase, abstractmethod from st4m.ui.view import View, ViewManager, ViewTransitionBlend -from st4m.ui.menu import MenuItem, MenuController, MenuItemBack, MenuItemForeground, MenuItemNoop +from st4m.ui.menu import ( + MenuItem, + MenuController, + MenuItemBack, + MenuItemForeground, + MenuItemNoop, +) from st4m import Responder, InputState, Ctx import math, hardware @@ -32,6 +38,7 @@ class USBIcon(Responder): """ Found in the bargain bin at an Aldi Süd. """ + def draw(self, ctx: Ctx) -> None: ctx.gray(1.0) ctx.arc(-90, 0, 20, 0, 6.28, 0).fill() @@ -41,7 +48,7 @@ class USBIcon(Responder): ctx.move_to(-50, 0).line_to(-10, -40).line_to(20, -40).stroke() ctx.arc(20, -40, 15, 0, 6.28, 0).fill() ctx.move_to(-30, 0).line_to(10, 40).line_to(40, 40).stroke() - ctx.rectangle(40-15, 40-15, 30, 30).fill() + ctx.rectangle(40 - 15, 40 - 15, 30, 30).fill() def think(self, ins: InputState, delta_ms: int) -> None: pass @@ -63,6 +70,7 @@ class Sun(Responder): """ A rotating sun widget. """ + def __init__(self) -> None: self.x = 0.0 self.y = 0.0 @@ -74,12 +82,11 @@ class Sun(Responder): pass def draw(self, ctx: Ctx) -> None: - nrays = 10 angle_per_ray = 6.28 / nrays for i in range(nrays): angle = i * angle_per_ray + self.ts / 4000 - angle %= 3.14159*2 + angle %= 3.14159 * 2 if angle > 2 and angle < 4: continue @@ -108,8 +115,8 @@ class MainMenu(MenuController): """ __slots__ = ( - '_ts', - '_sun', + "_ts", + "_sun", ) def __init__(self, items: List[MenuItem], vm: ViewManager) -> None: @@ -122,7 +129,9 @@ class MainMenu(MenuController): self._sun.think(ins, delta_ms) self._ts += delta_ms - def _draw_text_angled(self, ctx: Ctx, text: str, angle: float, activity: float) -> None: + def _draw_text_angled( + self, ctx: Ctx, text: str, angle: float, activity: float + ) -> None: size = lerp(20, 40, activity) color = lerp(0, 1, activity) if color < 0.01: @@ -137,7 +146,7 @@ class MainMenu(MenuController): def draw(self, ctx: Ctx) -> None: ctx.gray(0) ctx.rectangle(-120, -120, 240, 240).fill() - + self._sun.draw(ctx) ctx.font_size = 40 @@ -150,13 +159,14 @@ class MainMenu(MenuController): for ix, item in enumerate(self._items): rot = (ix - current) * angle_per_item - self._draw_text_angled(ctx, item.label(), rot, 1-abs(rot)) - + self._draw_text_angled(ctx, item.label(), rot, 1 - abs(rot)) + class SimpleMenu(MenuController): """ A simple line-by-line menu. """ + def draw(self, ctx: Ctx) -> None: ctx.gray(0) ctx.rectangle(-120, -120, 240, 240).fill() @@ -174,26 +184,35 @@ class SimpleMenu(MenuController): ctx.move_to(0, offs).text(item.label()) -menu_music = SimpleMenu([ - MenuItemBack(), - MenuItemNoop("Harmonic"), - MenuItemNoop("Melodic"), - MenuItemNoop("TinySynth"), - MenuItemNoop("CrazySynth"), - MenuItemNoop("Sequencer"), -], vm) - -menu_apps = SimpleMenu([ - MenuItemBack(), - MenuItemNoop("captouch"), - MenuItemNoop("worms"), -], vm) - -menu_main = MainMenu([ - MenuItemForeground("Music", menu_music), - MenuItemForeground("Apps", menu_apps), - MenuItemNoop("Settings"), -], vm) +menu_music = SimpleMenu( + [ + MenuItemBack(), + MenuItemNoop("Harmonic"), + MenuItemNoop("Melodic"), + MenuItemNoop("TinySynth"), + MenuItemNoop("CrazySynth"), + MenuItemNoop("Sequencer"), + ], + vm, +) + +menu_apps = SimpleMenu( + [ + MenuItemBack(), + MenuItemNoop("captouch"), + MenuItemNoop("worms"), + ], + vm, +) + +menu_main = MainMenu( + [ + MenuItemForeground("Music", menu_music), + MenuItemForeground("Apps", menu_apps), + MenuItemNoop("Settings"), + ], + vm, +) vm.push(menu_main) @@ -207,7 +226,7 @@ class Viewport(Responder): def think(self, ins: InputState, delta_ms: int) -> None: self.vm.think(ins, delta_ms) - + self.visible = [] usb = hardware.usb_connected() console = hardware.usb_console_active() @@ -215,7 +234,7 @@ class Viewport(Responder): console = False if usb: self.visible.append(self.usb) - #if console: + # if console: # self.visible.append(self.repl) for v in self.visible: diff --git a/python_payload/mypystubs/badge_link.pyi b/python_payload/mypystubs/badge_link.pyi index cab12382688bbb3707c2c7149ee8e5c6b21da9df..7e31d61d64d615ee3204a51b82779da99753deea 100644 --- a/python_payload/mypystubs/badge_link.pyi +++ b/python_payload/mypystubs/badge_link.pyi @@ -38,8 +38,8 @@ class Pin(Protocol): """ TODO(q3k): properly define in machine.pyi """ - pass + pass class JackPin(Protocol): @property @@ -51,7 +51,6 @@ class JackPin(Protocol): badgelink mode. """ ... - def enable(self) -> bool: """ Enable badgelink on this jack, on both ring and tip connectors. @@ -67,13 +66,11 @@ class JackPin(Protocol): currently plugged in (as a safety measure). """ ... - def disable(self) -> None: """ Disable badgelink on this jack, returning it into audio mode. """ ... - def active(self) -> bool: """ Returns true if this pin is currently enabled for badgelink, false if @@ -100,7 +97,6 @@ class Jack(Protocol): individually enabled using the .pin.enable/.pin.disable methods. """ ... - def disable(self) -> None: """ Disable badgelink on this jack, returning it into audio mode. @@ -109,7 +105,6 @@ class Jack(Protocol): individually disabled using the .pin.enable/.pin.disable methods. """ ... - def active(self) -> bool: """ Returns true if this jack is currently fully enabled for badgelink, @@ -119,7 +114,6 @@ class Jack(Protocol): false. """ ... - @property def tip(self) -> JackPin: """ @@ -130,7 +124,6 @@ class Jack(Protocol): badge link on this pin only. """ ... - @property def ring(self) -> JackPin: """ @@ -143,4 +136,4 @@ class Jack(Protocol): ... left: Jack -right: JackKhizqrU9ANxlu5sP \ No newline at end of file +right: JackKhizqrU9ANxlu5sP diff --git a/python_payload/mypystubs/captouch.pyi b/python_payload/mypystubs/captouch.pyi index 6a4d4c07b95c07d0ffa6cea0331d0bdecd1da304..1b8822d50dfc0ac389b898d2a048bc5d09fa3f9c 100644 --- a/python_payload/mypystubs/captouch.pyi +++ b/python_payload/mypystubs/captouch.pyi @@ -1,6 +1,5 @@ from typing import Protocol, List - class CaptouchPetalPadsState(Protocol): """ Current state of pads on a captouch petal. @@ -15,21 +14,18 @@ class CaptouchPetalPadsState(Protocol): True if the petals's tip is currently touched. """ ... - @property def base(self) -> bool: """ True if the petal's base is currently touched. """ ... - @property def cw(self) -> bool: """ True if the petal's clockwise pad is currently touched. """ ... - @property def ccw(self) -> bool: """ @@ -37,7 +33,6 @@ class CaptouchPetalPadsState(Protocol): """ ... - class CaptouchPetalState(Protocol): @property def pressed(self) -> bool: @@ -45,21 +40,18 @@ class CaptouchPetalState(Protocol): True if any of the petal's pads is currently touched. """ ... - @property def top(self) -> bool: """ True if this is a top petal. """ ... - @property def bottom(self) -> bool: """ True if this is a bottom petal. """ ... - @property def pads(self) -> CaptouchPetalPadsState: """ @@ -67,11 +59,11 @@ class CaptouchPetalState(Protocol): """ ... - class CaptouchState(Protocol): """ State of captouch sensors, captured at some time. """ + @property def petals(self) -> List[CaptouchPetalState]: """ @@ -86,7 +78,6 @@ class CaptouchState(Protocol): """ ... - def read() -> CaptouchState: """ Reads current captouch state from hardware and returns a snapshot in time. @@ -97,4 +88,4 @@ def calibration_active() -> bool: """ Returns true if the captouch system is current recalibrating. """ - ... \ No newline at end of file + ... diff --git a/python_payload/mypystubs/hardware.pyi b/python_payload/mypystubs/hardware.pyi index 14d91ba3539ba4c5b7120be8c52e98d3c973db10..0b39553bd58ac6e7d83fbf080cc4765b7f27b677 100644 --- a/python_payload/mypystubs/hardware.pyi +++ b/python_payload/mypystubs/hardware.pyi @@ -2,21 +2,12 @@ import time from st4m.ui.ctx import Ctx - def freertos_sleep(ms: int) -> None: ... - def get_ctx() -> Ctx: ... - def display_pipe_full() -> bool: ... - def display_update(c: Ctx) -> None: ... - def get_captouch(ix: int) -> bool: ... - def left_button_get() -> int: ... - def right_button_get() -> int: ... - def usb_connected() -> bool: ... - def usb_console_active() -> bool: ... diff --git a/python_payload/mypystubs/time.pyi b/python_payload/mypystubs/time.pyi index 2a276106b5b96623eaca8a784f2df1184c13e462..2a4d09189575bd5c025026fe3be8e2c2184ea217 100644 --- a/python_payload/mypystubs/time.pyi +++ b/python_payload/mypystubs/time.pyi @@ -1 +1,2 @@ -def ticks_ms() -> int: pass \ No newline at end of file +def ticks_ms() -> int: + pass diff --git a/python_payload/st4m/__init__.py b/python_payload/st4m/__init__.py index 033876cbeb5b3f2b0756b1d02df4342ebf8ba835..2aaabd2b504060d133ef481606cb34633af7feff 100644 --- a/python_payload/st4m/__init__.py +++ b/python_payload/st4m/__init__.py @@ -3,9 +3,9 @@ from st4m.ui.ctx import Ctx from st4m.input import InputState, InputController __all__ = [ - 'Reactor', 'Responder', - - 'InputState', 'InputController', - - 'Ctx', -] \ No newline at end of file + "Reactor", + "Responder", + "InputState", + "InputController", + "Ctx", +] diff --git a/python_payload/st4m/goose.py b/python_payload/st4m/goose.py index 77b9ecaa797300e2dac5a5c7268095c5536694af..c4380609ffd25c551305bbf10bafb59f5391869f 100644 --- a/python_payload/st4m/goose.py +++ b/python_payload/st4m/goose.py @@ -13,6 +13,7 @@ except: if TYPE_CHECKING: # We're in MyPy. from abc import ABCMeta, abstractmethod + class ABCBase(metaclass=ABCMeta): pass @@ -26,6 +27,7 @@ else: def abstractmethod(f): def _fail(): raise Exception("abstract method call") + return _fail try: @@ -39,11 +41,12 @@ else: class Enum: pass -__all__ = [ - 'TYPE_CHECKING', - 'ABCBase', 'abstractmethod', - 'List', 'Optional', - - 'Enum', -] \ No newline at end of file +__all__ = [ + "TYPE_CHECKING", + "ABCBase", + "abstractmethod", + "List", + "Optional", + "Enum", +] diff --git a/python_payload/st4m/input.py b/python_payload/st4m/input.py index 576bb906cbd084f911d5e46c1361169c05b51262..cdeacc05ba85e4e9efed3729426449ab96af8ecf 100644 --- a/python_payload/st4m/input.py +++ b/python_payload/st4m/input.py @@ -8,24 +8,24 @@ class InputState: """ Current state of inputs from badge user. Passed via think() to every Responder. - + If you want to detect edges, use the stateful InputController. """ - def __init__(self, petal_pressed: List[bool], left_button: int, right_button: int) -> None: + + def __init__( + self, petal_pressed: List[bool], left_button: int, right_button: int + ) -> None: self.petal_pressed = petal_pressed self.left_button = left_button self.right_button = right_button - + @classmethod - def gather(cls) -> 'InputState': + def gather(cls) -> "InputState": """ Build InputState from current hardware state. Should only be used by the Reactor. """ - petal_pressed = [ - hardware.get_captouch(i) - for i in range(10) - ] + petal_pressed = [hardware.get_captouch(i) for i in range(10)] left_button = hardware.left_button_get() right_button = hardware.right_button_get() @@ -48,11 +48,11 @@ class Pressable: button repeating. """ - PRESSED = 'pressed' - REPEATED = 'repeated' - RELEASED = 'released' - DOWN = 'down' - UP = 'up' + PRESSED = "pressed" + REPEATED = "repeated" + RELEASED = "released" + DOWN = "down" + UP = "up" def __init__(self, state: bool) -> None: self._state = state @@ -93,7 +93,6 @@ class Pressable: self._prev_state = False self._pressed_at = ts self._repeated = True - @property def state(self) -> str: @@ -162,10 +161,10 @@ class Pressable: self._ignoring = 2 self._repeating = False self._repeated = False - + def __repr__(self) -> str: - return '<Pressable: ' + self.state + '>' - + return "<Pressable: " + self.state + ">" + class PetalState(Pressable): def __init__(self, ix: int) -> None: @@ -180,18 +179,16 @@ class CaptouchState: The petals are indexed from 0 to 9 (inclusive). Petal 0 is above the USB-C socket, then the numbering continues counter-clockwise. """ - __slots__ = ('petals') + + __slots__ = "petals" def __init__(self) -> None: - self.petals = [ - PetalState(i) - for i in range(10) - ] - + self.petals = [PetalState(i) for i in range(10)] + def _update(self, ts: int, hr: InputState) -> None: for i, petal in enumerate(self.petals): petal._update(ts, hr.petal_pressed[i]) - + def _ignore_pressed(self) -> None: for petal in self.petals: petal._ignore_pressed() @@ -201,15 +198,17 @@ class TriSwitchHandedness(Enum): """ Left or right shoulder button. """ - left = 'left' - right = 'right' + + left = "left" + right = "right" class TriSwitchState: """ State of a tri-stat shoulder button """ - __slots__ = ('left', 'middle', 'right', 'handedness') + + __slots__ = ("left", "middle", "right", "handedness") def __init__(self, h: TriSwitchHandedness) -> None: self.handedness = h @@ -219,7 +218,11 @@ class TriSwitchState: self.right = Pressable(False) def _update(self, ts: int, hr: InputState) -> None: - st = hr.left_button if self.handedness == TriSwitchHandedness.left else hr.right_button + st = ( + hr.left_button + if self.handedness == TriSwitchHandedness.left + else hr.right_button + ) self.left._update(ts, st == -1) self.middle._update(ts, st == 2) self.right._update(ts, st == 1) @@ -228,7 +231,7 @@ class TriSwitchState: self.left._ignore_pressed() self.middle._ignore_pressed() self.right._ignore_pressed() - + class InputController: """ @@ -241,12 +244,14 @@ class InputController: Then, access the captouch/left_shoulder/right_shoulder fields. """ + __slots__ = ( - 'captouch', - 'left_shoulder', - 'right_shoulder', - '_ts', + "captouch", + "left_shoulder", + "right_shoulder", + "_ts", ) + def __init__(self) -> None: self.captouch = CaptouchState() self.left_shoulder = TriSwitchState(TriSwitchHandedness.left) @@ -268,4 +273,3 @@ class InputController: self.captouch._ignore_pressed() self.left_shoulder._ignore_pressed() self.right_shoulder._ignore_pressed() - diff --git a/python_payload/st4m/reactor.py b/python_payload/st4m/reactor.py index 558186f254396ff104bbecca74d9fb5f869f6206..30f1e22436571b98b4c33b9762ed2bdc92b246b3 100644 --- a/python_payload/st4m/reactor.py +++ b/python_payload/st4m/reactor.py @@ -4,6 +4,7 @@ from st4m.ui.ctx import Ctx import time, hardware + class Responder(ABCBase): """ Responder is an interface from the Reactor to any running Micropython code @@ -14,6 +15,7 @@ class Responder(ABCBase): The Reactor will call think and draw methods at a somewhat-constant pace in order to maintain a smooth system-wide update rate and framerate. """ + @abstractmethod def think(self, ins: InputState, delta_ms: int) -> None: """ @@ -37,7 +39,7 @@ class Responder(ABCBase): left). Unless specified otherwise by the compositing stack, the screen coordinates are +/- 120 in both X and Y (positive numbers towards up and right), with 0,0 being the middle of the screen. - + The Reactor will then rasterize and blit the result. The code must not sleep or block during this callback, as that will @@ -54,7 +56,9 @@ class Reactor: It will attempt to run a top Responder with a fixed tickrate a framerate that saturates the display rasterization/blitting pipeline. """ - __slots__ = ('_top', '_tickrate_ms', '_last_tick', '_ctx', '_ts') + + __slots__ = ("_top", "_tickrate_ms", "_last_tick", "_ctx", "_ts") + def __init__(self) -> None: self._top: Optional[Responder] = None self._tickrate_ms: int = 20 @@ -89,14 +93,14 @@ class Reactor: if wait > 0: hardware.freertos_sleep(wait) else: - print('too long', wait) + print("too long", wait) def _run_top(self, start: int) -> None: # Skip if we have no top Responder. if self._top is None: return - # Calculate delta (default to tickrate if running first iteration). + # Calculate delta (default to tickrate if running first iteration). delta = self._tickrate_ms if self._last_tick is not None: delta = start - self._last_tick @@ -106,7 +110,7 @@ class Reactor: hr = InputState.gather() - # Think! + # Think! self._top.think(hr, delta) # Draw! @@ -119,4 +123,3 @@ class Reactor: if self._ctx is not None and not hardware.display_pipe_full(): hardware.display_update(self._ctx) self._ctx = None - diff --git a/python_payload/st4m/ui/ctx.py b/python_payload/st4m/ui/ctx.py index 7a442cfaec90ed3b51cfc22072e4d15547cf2f8d..ddd00768fcd721e8f92291ca1cb8e9f84e45dc15 100644 --- a/python_payload/st4m/ui/ctx.py +++ b/python_payload/st4m/ui/ctx.py @@ -1,5 +1,6 @@ from st4m.goose import ABCBase, abstractmethod, List + class Ctx(ABCBase): """ Ctx is the rendering/rasterization API used by st4m. @@ -12,34 +13,41 @@ class Ctx(ABCBase): in-memory draw list. Then, when the rasterizer is ready, it will rasterize said drawlist to pixels in a separate thread. - A Ctx object is passed to all draw() calls to Responder. This object should - only be used within the lifecycle of the draw method and must not be - persisted. + A Ctx object is passed to all draw() calls to Responder. This object should + only be used within the lifecycle of the draw method and must not be + persisted. For more information, see: https://ctx.graphics/ """ - __slots__ = ('font_size', 'text_align', 'text_baseline', 'line_width', 'global_alpha') - CENTER = 'center' - MIDDLE = 'middle' + __slots__ = ( + "font_size", + "text_align", + "text_baseline", + "line_width", + "global_alpha", + ) + + CENTER = "center" + MIDDLE = "middle" @abstractmethod def __init__(self) -> None: self.font_size: float = 10 - self.text_align: str = 'start' - self.text_baseline: str = 'alphabetic' + self.text_align: str = "start" + self.text_baseline: str = "alphabetic" self.line_width: float = 1.0 self.global_alpha: float = 1.0 @abstractmethod - def begin_path(self) -> 'Ctx': + def begin_path(self) -> "Ctx": """ Clears the current path if any. """ pass @abstractmethod - def save(self) -> 'Ctx': + def save(self) -> "Ctx": """ Stores the transform, clipping state, fill and stroke sources, font size, stroking and dashing options. @@ -47,7 +55,7 @@ class Ctx(ABCBase): pass @abstractmethod - def restore(self) -> 'Ctx': + def restore(self) -> "Ctx": """ Restores the state previously saved with save, calls to save/restore should be balanced. @@ -55,7 +63,7 @@ class Ctx(ABCBase): pass @abstractmethod - def start_frame(self) -> 'Ctx': + def start_frame(self) -> "Ctx": """ Prepare for rendering a new frame, clears internal drawlist and initializes the state. @@ -65,7 +73,7 @@ class Ctx(ABCBase): pass @abstractmethod - def end_frame(self) -> 'Ctx': + def end_frame(self) -> "Ctx": """ We're done rendering a frame, this does nothing on a context created for a framebuffer, where drawing commands are immediate. @@ -75,14 +83,14 @@ class Ctx(ABCBase): pass @abstractmethod - def start_group(self) -> 'Ctx': + def start_group(self) -> "Ctx": """ Start a compositing group. """ pass @abstractmethod - def end_group(self) -> 'Ctx': + def end_group(self) -> "Ctx": """ End a compositing group, the global alpha, compositing mode and blend mode set before this call is used to apply the group. @@ -90,7 +98,7 @@ class Ctx(ABCBase): pass @abstractmethod - def clip(self) -> 'Ctx': + def clip(self) -> "Ctx": """ Use the current path as a clipping mask, subsequent draw calls are limited by the path. The only way to increase the visible area is to @@ -99,28 +107,39 @@ class Ctx(ABCBase): pass @abstractmethod - def rotate(self, x: float) -> 'Ctx': + def rotate(self, x: float) -> "Ctx": """ Add rotation to the user to device space transform. """ pass @abstractmethod - def scale(self, x: float, y: float) -> 'Ctx': + def scale(self, x: float, y: float) -> "Ctx": """ Scales the user to device transform. """ pass @abstractmethod - def translate(self, x: float, y: float) -> 'Ctx': + def translate(self, x: float, y: float) -> "Ctx": """ Adds translation to the user to device transform. """ pass @abstractmethod - def apply_transform(self, a: float, b: float, c: float, d: float, e: float, f: float, g: float, h: float, i: float) -> 'Ctx': + def apply_transform( + self, + a: float, + b: float, + c: float, + d: float, + e: float, + f: float, + g: float, + h: float, + i: float, + ) -> "Ctx": """ Adds a 3x3 matrix on top of the existing user to device space transform. @@ -129,7 +148,7 @@ class Ctx(ABCBase): pass @abstractmethod - def line_to(self, x: float, y: float) -> 'Ctx': + def line_to(self, x: float, y: float) -> "Ctx": """ Draws a line segment from the position of the last {line,move,curve,quad}_to) to the given coordinates. @@ -137,35 +156,37 @@ class Ctx(ABCBase): pass @abstractmethod - def move_to(self, x: float, y: float) -> 'Ctx': + def move_to(self, x: float, y: float) -> "Ctx": """ Moves the virtual pen to the given coordinates without drawing anything. """ pass @abstractmethod - def curve_to(self, cx0: float, cy0: float, cx1: float, cy1: float, x: float, y: float) -> 'Ctx': + def curve_to( + self, cx0: float, cy0: float, cx1: float, cy1: float, x: float, y: float + ) -> "Ctx": """ TOD(q3k): document """ pass @abstractmethod - def quad_to(self, cx: float, cy: float, x: float, y: float) -> 'Ctx': + def quad_to(self, cx: float, cy: float, x: float, y: float) -> "Ctx": """ TOD(q3k): document """ pass @abstractmethod - def gray(self, a: float) -> 'Ctx': + def gray(self, a: float) -> "Ctx": """ Set current draw color to a floating point grayscale value from 0 to 1. """ pass @abstractmethod - def rgb(self, r: float, g: float, b: float) -> 'Ctx': + def rgb(self, r: float, g: float, b: float) -> "Ctx": """ Set current draw color to an RGB color defined by component values from 0 to 1. @@ -173,7 +194,7 @@ class Ctx(ABCBase): pass @abstractmethod - def rgba(self, r: float, g: float, b: float, a: float) -> 'Ctx': + def rgba(self, r: float, g: float, b: float, a: float) -> "Ctx": """ Set current draw color to an RGBA color defined by component values from 0 to 1. @@ -181,105 +202,121 @@ class Ctx(ABCBase): pass @abstractmethod - def arc_to(self, x1: float, y1: float, x2: float, y2: float, radius: float) -> 'Ctx': + def arc_to( + self, x1: float, y1: float, x2: float, y2: float, radius: float + ) -> "Ctx": """ TOD(q3k): document """ pass @abstractmethod - def rel_line_to(self, x: float, y: float) -> 'Ctx': + def rel_line_to(self, x: float, y: float) -> "Ctx": """ TOD(q3k): document """ pass @abstractmethod - def rel_move_to(self, x: float, y: float) -> 'Ctx': + def rel_move_to(self, x: float, y: float) -> "Ctx": """ TOD(q3k): document """ pass @abstractmethod - def rel_curve_to(self, cx0: float, cy0: float, cx1: float, cy1: float, x: float, y: float) -> 'Ctx': + def rel_curve_to( + self, cx0: float, cy0: float, cx1: float, cy1: float, x: float, y: float + ) -> "Ctx": """ TOD(q3k): document """ pass @abstractmethod - def rel_quad_to(self, cx: float, cy: float, x: float, y: float) -> 'Ctx': + def rel_quad_to(self, cx: float, cy: float, x: float, y: float) -> "Ctx": """ TOD(q3k): document """ pass @abstractmethod - def rel_arc_to(self, x1: float, y1: float, x2: float, y2: float, radius: float) -> 'Ctx': + def rel_arc_to( + self, x1: float, y1: float, x2: float, y2: float, radius: float + ) -> "Ctx": """ TOD(q3k): document """ pass @abstractmethod - def rectangle(self, x: float, y: float, w: float, h: float) -> 'Ctx': + def rectangle(self, x: float, y: float, w: float, h: float) -> "Ctx": """ TOD(q3k): document """ pass @abstractmethod - def round_rectangle(self, x: float, y: float, w: float, h: float, r: float) -> 'Ctx': + def round_rectangle( + self, x: float, y: float, w: float, h: float, r: float + ) -> "Ctx": """ TOD(q3k): document """ pass @abstractmethod - def arc(self, x: float, y: float, radius: float, angle1: float, angle2: float, direction: float) -> 'Ctx': + def arc( + self, + x: float, + y: float, + radius: float, + angle1: float, + angle2: float, + direction: float, + ) -> "Ctx": """ TOD(q3k): document """ pass @abstractmethod - def close_path(self) -> 'Ctx': + def close_path(self) -> "Ctx": """ TOD(q3k): document """ pass @abstractmethod - def preserve(self) -> 'Ctx': + def preserve(self) -> "Ctx": """ TOD(q3k): document """ pass @abstractmethod - def fill(self) -> 'Ctx': + def fill(self) -> "Ctx": """ TOD(q3k): document """ pass @abstractmethod - def stroke(self) -> 'Ctx': + def stroke(self) -> "Ctx": """ TOD(q3k): document """ pass @abstractmethod - def paint(self) -> 'Ctx': + def paint(self) -> "Ctx": """ TOD(q3k): document """ pass @abstractmethod - def linear_gradient(self, x0: float, y0: float, x1: float, y1: float) -> 'Ctx': + def linear_gradient(self, x0: float, y0: float, x1: float, y1: float) -> "Ctx": """ Change the source to a linear gradient from x0,y0 to x1,y1, by default an empty gradient from black to white exists, add stops with @@ -290,7 +327,9 @@ class Ctx(ABCBase): pass @abstractmethod - def radial_gradient(self, x0: float, y0: float, r0: float, x1: float, y1: float, r1: float) -> 'Ctx': + def radial_gradient( + self, x0: float, y0: float, r0: float, x1: float, y1: float, r1: float + ) -> "Ctx": """ Change the source to a radial gradient from a circle x0,y0 with radius0 to an outer circle x1,y1 with radidus r1. @@ -301,15 +340,15 @@ class Ctx(ABCBase): pass @abstractmethod - def logo(self, x: float, y: float, dim: float) -> 'Ctx': + def logo(self, x: float, y: float, dim: float) -> "Ctx": """ TOD(q3k): document """ pass @abstractmethod - def text(self, text: str) -> 'Ctx': + def text(self, text: str) -> "Ctx": """ TOD(q3k): document """ - pass \ No newline at end of file + pass diff --git a/python_payload/st4m/ui/interactions.py b/python_payload/st4m/ui/interactions.py index a665d17df56f38db5030b8067ca686b0e4c769cb..f8eb2f17a863eb437813ab224a25afb25cc47aab 100644 --- a/python_payload/st4m/ui/interactions.py +++ b/python_payload/st4m/ui/interactions.py @@ -23,11 +23,12 @@ class ScrollController(st4m.Responder): 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', + "_nitems", + "_target_position", + "_current_position", + "_velocity", ) def __init__(self) -> None: @@ -35,7 +36,7 @@ class ScrollController(st4m.Responder): self._target_position = 0 self._current_position = 0.0 self._velocity: float = 0.0 - + def set_item_count(self, count: int) -> None: """ Set how many items this scrollable list contains. Currently, updating @@ -108,7 +109,7 @@ class ScrollController(st4m.Responder): 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) @@ -147,5 +148,3 @@ class ScrollController(st4m.Responder): def _physics_integrate(self, delta: float) -> None: self._velocity -= self._velocity * delta * 10 self._current_position += self._velocity * delta - - diff --git a/python_payload/st4m/ui/menu.py b/python_payload/st4m/ui/menu.py index a2d85b4fbe58c9a9b41eced349d5ac227fd0ecad..e076dedd52dce11e28f6bb4ca76de0c89384d636 100644 --- a/python_payload/st4m/ui/menu.py +++ b/python_payload/st4m/ui/menu.py @@ -3,7 +3,13 @@ import st4m from st4m import Responder from st4m.goose import ABCBase, abstractmethod, List, Optional from st4m.input import InputState, InputController -from st4m.ui.view import ViewWithInputState, View, ViewManager, ViewTransitionSwipeLeft, ViewTransitionSwipeRight +from st4m.ui.view import ( + ViewWithInputState, + View, + ViewManager, + ViewTransitionSwipeLeft, + ViewTransitionSwipeRight, +) from st4m.ui.interactions import ScrollController from st4m.ui.ctx import Ctx @@ -12,7 +18,7 @@ class MenuItem(ABCBase): """ An abstract MenuItem to be implemented by concrete impementations. - A MenuItem implementation can be added to a MenuController + A MenuItem implementation can be added to a MenuController """ @abstractmethod @@ -37,6 +43,7 @@ class MenuItemForeground(MenuItem): """ A MenuItem which, when activated, navigates to the given View. """ + def __init__(self, label: str, r: View) -> None: self._r = r self._label = label @@ -53,6 +60,7 @@ class MenuItemNoop(MenuItem): """ A MenuItem which does nothing. """ + def __init__(self, label: str) -> None: self._label = label @@ -67,6 +75,7 @@ class MenuItemBack(MenuItem): """ A MenuItem which, when activatd, navigates back in history. """ + def press(self, vm: Optional[ViewManager]) -> None: if vm is not None: vm.pop(ViewTransitionSwipeRight()) @@ -83,10 +92,11 @@ class MenuController(ViewWithInputState): Implementers must implement draw() and use self._items and self._scroll_controller to display menu items accordingly. """ + __slots__ = ( - '_items', - '_scroll_controller', - '_view_manager', + "_items", + "_scroll_controller", + "_view_manager", ) def __init__(self, items: List[MenuItem], vm: Optional[ViewManager]) -> None: @@ -100,7 +110,7 @@ class MenuController(ViewWithInputState): def _parse_state(self) -> None: left = self.input.left_shoulder.left right = self.input.left_shoulder.right - + if left.pressed: self._scroll_controller.scroll_left() return @@ -135,4 +145,3 @@ class MenuController(ViewWithInputState): Automatically called on canonical user input. """ self._items[self._scroll_controller.target_position()].press(self._view_manager) - diff --git a/python_payload/st4m/ui/view.py b/python_payload/st4m/ui/view.py index a66d9194f357d2854ae3333791ac216e2477dbfe..da1948874465ab3cded8816ebe7f9582b0cd50d6 100644 --- a/python_payload/st4m/ui/view.py +++ b/python_payload/st4m/ui/view.py @@ -30,9 +30,8 @@ class ViewWithInputState(View): Remember to call super().think() in think()! """ - __slots__ = ( - 'input', - ) + + __slots__ = ("input",) def __init__(self) -> None: self.input = InputController() @@ -50,8 +49,11 @@ class ViewTransition(ABCBase): Can be implemented by the user to provide transition animations. """ + @abstractmethod - def draw(self, ctx: Ctx, transition: float, incoming: Responder, outgoing: Responder) -> None: + def draw( + self, ctx: Ctx, transition: float, incoming: Responder, outgoing: Responder + ) -> None: """ Called when the ViewManager performs a transition from the outgoing responder to the incoming responder. The implementer should draw both @@ -67,7 +69,10 @@ class ViewTransitionBlend(ViewTransition): """ Transition from one view to another by opacity blending. """ - def draw(self, ctx: Ctx, transition: float, incoming: Responder, outgoing: Responder) -> None: + + def draw( + self, ctx: Ctx, transition: float, incoming: Responder, outgoing: Responder + ) -> None: ctx.start_group() outgoing.draw(ctx) ctx.end_group() @@ -82,7 +87,10 @@ class ViewTransitionSwipeLeft(ViewTransition): """ Swipe the outoing view to the left and replace it with the incoming view. """ - def draw(self, ctx: Ctx, transition: float, incoming: Responder, outgoing: Responder) -> None: + + def draw( + self, ctx: Ctx, transition: float, incoming: Responder, outgoing: Responder + ) -> None: ctx.save() ctx.translate(transition * -240, 0) outgoing.draw(ctx) @@ -98,7 +106,10 @@ class ViewTransitionSwipeRight(ViewTransition): """ Swipe the outoing view to the right and replace it with the incoming view. """ - def draw(self, ctx: Ctx, transition: float, incoming: Responder, outgoing: Responder) -> None: + + def draw( + self, ctx: Ctx, transition: float, incoming: Responder, outgoing: Responder + ) -> None: ctx.save() ctx.translate(transition * 240, 0) outgoing.draw(ctx) @@ -117,6 +128,7 @@ class ViewManager(Responder): It manages a history of Views, to which new Views can be pushed and then popped. """ + def __init__(self, vt: ViewTransition) -> None: """ Create a new ViewManager with a default ViewTransition. @@ -124,7 +136,7 @@ class ViewManager(Responder): self._incoming: Optional[View] = None self._outgoing: Optional[View] = None - # Transition time. + # Transition time. self._time_ms = 150 self._default_vt = vt @@ -140,9 +152,9 @@ class ViewManager(Responder): if self._transition >= 1.0: self._transition = 0 self._transitioning = False - + self._outgoing = None - + if self._outgoing is not None: self._outgoing.think(ins, delta_ms) if self._incoming is not None: @@ -195,4 +207,4 @@ class ViewManager(Responder): animation. """ r = self._history.pop() - self.replace(r, override_vt) \ No newline at end of file + self.replace(r, override_vt) diff --git a/sim/fakes/captouch.py b/sim/fakes/captouch.py index 4706637827149178f303530a0547daddb8cf69ed..a7c4f3d46366968ad22ebc45edaccf87905e172a 100644 --- a/sim/fakes/captouch.py +++ b/sim/fakes/captouch.py @@ -11,10 +11,10 @@ class CaptouchPetalPadsState: @property def tip(self) -> bool: return self._tip - + @property def base(self) -> bool: - return self._base + return self._base @property def cw(self) -> bool: @@ -61,6 +61,7 @@ class CaptouchState: def read() -> CaptouchState: import hardware + hardware._sim.process_events() hardware._sim.render_gui_lazy() petals = hardware._sim.petals @@ -83,4 +84,4 @@ def read() -> CaptouchState: def calibration_active() -> bool: - return False \ No newline at end of file + return False diff --git a/sim/fakes/ctx.py b/sim/fakes/ctx.py index 72a91d2ac23cab13819e84a318fe65138faed1a9..bade7298b5290b82bfe469d6339f6faa23d395fa 100644 --- a/sim/fakes/ctx.py +++ b/sim/fakes/ctx.py @@ -16,13 +16,14 @@ class Wasm: Wasm wraps access to WebAssembly functions, converting to/from Python types as needed. It's intended to be used as a singleton. """ + def __init__(self): store = wasmer.Store(wasmer.engine.JIT(wasmer_compiler_cranelift.Compiler)) simpath = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) - wasmpath = os.path.join(simpath, 'wasm', 'ctx.wasm') - module = wasmer.Module(store, open(wasmpath, 'rb').read()) + wasmpath = os.path.join(simpath, "wasm", "ctx.wasm") + module = wasmer.Module(store, open(wasmpath, "rb").read()) wasi_version = wasmer.wasi.get_version(module, strict=False) - wasi_env = wasmer.wasi.StateBuilder('badge23sim').finalize() + wasi_env = wasmer.wasi.StateBuilder("badge23sim").finalize() import_object = wasi_env.generate_import_object(store, wasi_version) instance = wasmer.Instance(module, import_object) self._i = instance @@ -34,12 +35,12 @@ class Wasm: self._i.exports.free(p) def ctx_parse(self, ctx, s): - s = s.encode('utf-8') + s = s.encode("utf-8") slen = len(s) + 1 p = self.malloc(slen) mem = self._i.exports.memory.uint8_view(p) - mem[0:slen-1] = s - mem[slen-1] = 0 + mem[0 : slen - 1] = s + mem[slen - 1] = 0 self._i.exports.ctx_parse(ctx, p) self.free(p) @@ -55,7 +56,9 @@ class Wasm: # framebuffer into pygame's surfaces, which is a _huge_ speed benefit # (difference between ~10FPS and 500+FPS!). BRGA8 = 5 - return fb, self._i.exports.ctx_new_for_framebuffer(fb, width, height, width * 4, BRGA8) + return fb, self._i.exports.ctx_new_for_framebuffer( + fb, width, height, width * 4, BRGA8 + ) def ctx_new_drawlist(self, width, height): return self._i.exports.ctx_new_drawlist(width, height) @@ -65,12 +68,12 @@ class Wasm: return self._i.exports.ctx_apply_transform(ctx, *args) def ctx_text_width(self, ctx, text): - s = text.encode('utf-8') + s = text.encode("utf-8") slen = len(s) + 1 p = self.malloc(slen) mem = self._i.exports.memory.uint8_view(p) - mem[0:slen-1] = s - mem[slen-1] = 0 + mem[0 : slen - 1] = s + mem[slen - 1] = 0 res = self._i.exports.ctx_text_width(ctx, p) self.free(p) return res @@ -81,8 +84,10 @@ class Wasm: def ctx_render_ctx(self, ctx, dctx): return self._i.exports.ctx_render_ctx(ctx, dctx) + _wasm = Wasm() + class Ctx: """ Ctx implements a subset of uctx [1]. It should be extended as needed as we @@ -90,12 +95,13 @@ class Ctx: [1] - https://ctx.graphics/uctx/ """ - LEFT = 'left' - RIGHT = 'right' - CENTER = 'center' - END = 'end' - MIDDLE = 'middle' - BEVEL = 'bevel' + + LEFT = "left" + RIGHT = "right" + CENTER = "center" + END = "end" + MIDDLE = "middle" + BEVEL = "bevel" def __init__(self, _ctx): self._ctx = _ctx @@ -107,7 +113,7 @@ class Ctx: @text_align.setter def text_align(self, v): self._emit(f"textAlign {v}") - + @property def text_baseline(self): return None @@ -127,7 +133,7 @@ class Ctx: @property def font_size(self): return None - + @font_size.setter def font_size(self, v): self._emit(f"fontSize {v:.3f}") @@ -135,7 +141,7 @@ class Ctx: @property def global_alpha(self): return None - + @global_alpha.setter def global_alpha(self, v): self._emit(f"globalAlpha {v:.3f}") @@ -167,7 +173,6 @@ class Ctx: self._emit(f"gray {v:.3f}") return self - def rgba(self, r, g, b, a): # TODO(q3k): dispatch by type instead of value, warn on # ambiguous/unexpected values for type. @@ -190,11 +195,13 @@ class Ctx: return self def text(self, s): - self._emit(f"text \"{s}\"") + self._emit(f'text "{s}"') return self def round_rectangle(self, x, y, width, height, radius): - self._emit(f"roundRectangle {int(x)} {int(y)} {int(width)} {int(height)} {radius}") + self._emit( + f"roundRectangle {int(x)} {int(y)} {int(width)} {int(height)} {radius}" + ) return self def rectangle(self, x, y, width, height): @@ -225,8 +232,10 @@ class Ctx: self._emit(f"fill") return self - def arc(self, x, y , radius, arc_from, arc_to, direction): - self._emit(f"arc {int(x)} {int(y)} {int(radius)} {arc_from:.4f} {arc_to:.4f} {1 if direction else 0}") + def arc(self, x, y, radius, arc_from, arc_to, direction): + self._emit( + f"arc {int(x)} {int(y)} {int(radius)} {arc_from:.4f} {arc_to:.4f} {1 if direction else 0}" + ) return self def text_width(self, text): diff --git a/sim/fakes/hardware.py b/sim/fakes/hardware.py index 3f6ef563b41b8dc2c00ad2cfd565b5b4e60a0493..4afe93921058c0facc39d19027ae112637f58780 100644 --- a/sim/fakes/hardware.py +++ b/sim/fakes/hardware.py @@ -11,7 +11,7 @@ screen_w = 814 screen_h = 854 screen = pygame.display.set_mode(size=(screen_w, screen_h)) simpath = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) -bgpath = os.path.join(simpath, 'background.png') +bgpath = os.path.join(simpath, "background.png") background = pygame.image.load(bgpath) @@ -20,15 +20,16 @@ class Input: Input implements an input overlay (for petals or buttons) that can be mouse-picked by the user, and in the future also keyboard-controlled. """ + # Pixels positions of each marker. POSITIONS = [] # Pixel size (diameter) of each marker. MARKER_SIZE = 100 # Colors for various states (RGBA). - COLOR_HELD = (0x5b, 0x5b, 0x5b, 0xa0) - COLOR_HOVER = (0x6b, 0x6b, 0x6b, 0xa0) - COLOR_IDLE = (0x8b, 0x8b, 0x8b, 0x80) + COLOR_HELD = (0x5B, 0x5B, 0x5B, 0xA0) + COLOR_HOVER = (0x6B, 0x6B, 0x6B, 0xA0) + COLOR_IDLE = (0x8B, 0x8B, 0x8B, 0x80) def __init__(self): self._state = [False for _ in self.POSITIONS] @@ -71,40 +72,74 @@ class Input: s = self.state() for i, (x, y) in enumerate(self.POSITIONS): if s[i]: - pygame.draw.circle(surface, self.COLOR_HELD, (x, y), self.MARKER_SIZE//2) + pygame.draw.circle( + surface, self.COLOR_HELD, (x, y), self.MARKER_SIZE // 2 + ) elif i == self._mouse_hover: - pygame.draw.circle(surface, self.COLOR_HOVER, (x, y), self.MARKER_SIZE//2) + pygame.draw.circle( + surface, self.COLOR_HOVER, (x, y), self.MARKER_SIZE // 2 + ) else: - pygame.draw.circle(surface, self.COLOR_IDLE, (x, y), self.MARKER_SIZE//2) + pygame.draw.circle( + surface, self.COLOR_IDLE, (x, y), self.MARKER_SIZE // 2 + ) class PetalsInput(Input): _petal_positions_top = [ - (406, 172), (164, 352), (254, 637), (554, 637), (652, 348), + (406, 172), + (164, 352), + (254, 637), + (554, 637), + (652, 348), ] _petal_positions_bottom = [ - (213, 162), (99, 527), (402, 746), (710, 527), (597, 167) + (213, 162), + (99, 527), + (402, 746), + (710, 527), + (597, 167), ] - POSITIONS = list(itertools.chain(*[ - [ - (x + math.cos(i * -1.256 + 1.57) * 40, y + math.sin(i * -1.256 + 1.57) * 40), # base - (x + math.cos(i * -1.256 + 5.75) * 40, y + math.sin(i * -1.256 + 5.75) * 40), # cw - (x + math.cos(i * -1.256 + 3.66) * 40, y + math.sin(i * -1.256 + 3.66) * 40), # ccw - ] - for i, (x, y) in enumerate(_petal_positions_top) - ] + [ - [ - (x + math.cos(i * -1.256 - 2.20) * 40, y + math.sin(i * -1.256 - 2.20) * 40), # tip - (x + math.cos(i * -1.256 - 5.34) * 40, y + math.sin(i * -1.256 - 5.34) * 40), # base - ] - for i, (x, y) in enumerate(_petal_positions_bottom) - ])) + POSITIONS = list( + itertools.chain( + *[ + [ + ( + x + math.cos(i * -1.256 + 1.57) * 40, + y + math.sin(i * -1.256 + 1.57) * 40, + ), # base + ( + x + math.cos(i * -1.256 + 5.75) * 40, + y + math.sin(i * -1.256 + 5.75) * 40, + ), # cw + ( + x + math.cos(i * -1.256 + 3.66) * 40, + y + math.sin(i * -1.256 + 3.66) * 40, + ), # ccw + ] + for i, (x, y) in enumerate(_petal_positions_top) + ] + + [ + [ + ( + x + math.cos(i * -1.256 - 2.20) * 40, + y + math.sin(i * -1.256 - 2.20) * 40, + ), # tip + ( + x + math.cos(i * -1.256 - 5.34) * 40, + y + math.sin(i * -1.256 - 5.34) * 40, + ), # base + ] + for i, (x, y) in enumerate(_petal_positions_bottom) + ] + ) + ) MARKER_SIZE = 40 def _index_for_petal_pad(self, petal, pad): if petal >= 10: raise ValueError("petal cannot be > 10") - + # convert from st3m/bsp index into input state index top = False if petal % 2 == 0: @@ -117,18 +152,18 @@ class PetalsInput(Input): res += 3 * 5 if top: - if pad == 1: # ccw + if pad == 1: # ccw res += 2 - elif pad == 2: # cw + elif pad == 2: # cw res += 1 - elif pad == 3: # base + elif pad == 3: # base res += 0 else: raise ValueError("invalid pad number") else: - if pad == 0: # tip + if pad == 0: # tip res += 0 - elif pad == 3: # base + elif pad == 3: # base res += 1 else: raise ValueError("invalid pad number") @@ -154,13 +189,17 @@ class PetalsInput(Input): class ButtonsInput(Input): POSITIONS = [ - ( 24, 240), ( 56, 240), ( 88, 240), - (724, 240), (756, 240), (788, 240), + (24, 240), + (56, 240), + (88, 240), + (724, 240), + (756, 240), + (788, 240), ] MARKER_SIZE = 20 - COLOR_HELD = (0x80, 0x80, 0x80, 0xff) - COLOR_HOVER = (0x40, 0x40, 0x40, 0xff) - COLOR_IDLE = (0x20, 0x20, 0x20, 0xff) + COLOR_HELD = (0x80, 0x80, 0x80, 0xFF) + COLOR_HOVER = (0x40, 0x40, 0x40, 0xFF) + COLOR_IDLE = (0x20, 0x20, 0x20, 0xFF) class Simulation: @@ -172,11 +211,46 @@ class Simulation: # Pixel coordinates of each LED. The order is the same as the hardware # WS2812 chain, not the order as expected by the micropython API! LED_POSITIONS = [ - (660, 455), (608, 490), (631, 554), (648, 618), (659, 690), (655, 770), (571, 746), (502, 711), - (452, 677), (401, 639), (352, 680), (299, 713), (241, 745), (151, 771), (147, 682), (160, 607), - (176, 549), (197, 491), (147, 453), ( 98, 416), ( 43, 360), ( 0, 292), ( 64, 267), (144, 252), - (210, 248), (276, 249), (295, 190), (318, 129), (351, 65), (404, 0), (456, 64), (490, 131), - (511, 186), (529, 250), (595, 247), (663, 250), (738, 264), (810, 292), (755, 371), (705, 419), + (660, 455), + (608, 490), + (631, 554), + (648, 618), + (659, 690), + (655, 770), + (571, 746), + (502, 711), + (452, 677), + (401, 639), + (352, 680), + (299, 713), + (241, 745), + (151, 771), + (147, 682), + (160, 607), + (176, 549), + (197, 491), + (147, 453), + (98, 416), + (43, 360), + (0, 292), + (64, 267), + (144, 252), + (210, 248), + (276, 249), + (295, 190), + (318, 129), + (351, 65), + (404, 0), + (456, 64), + (490, 131), + (511, 186), + (529, 250), + (595, 247), + (663, 250), + (738, 264), + (810, 292), + (755, 371), + (705, 419), ] def __init__(self): @@ -196,7 +270,9 @@ class Simulation: # corresponding surface when there was no change to its render data. self._led_surface = pygame.Surface((screen_w, screen_h), flags=pygame.SRCALPHA) self._led_surface_dirty = True - self._petal_surface = pygame.Surface((screen_w, screen_h), flags=pygame.SRCALPHA) + self._petal_surface = pygame.Surface( + (screen_w, screen_h), flags=pygame.SRCALPHA + ) self._petal_surface_dirty = True self._full_surface = pygame.Surface((screen_w, screen_h), flags=pygame.SRCALPHA) self._oled_surface = pygame.Surface((240, 240), flags=pygame.SRCALPHA) @@ -215,19 +291,13 @@ class Simulation: # that is True if the pixel corresponding to this mask's bit is part of # the OLED disc, and false otherwise. mask = [ - [ - math.sqrt((x - 120)**2 + (y - 120)**2) <= 120 - for x in range(240) - ] + [math.sqrt((x - 120) ** 2 + (y - 120) ** 2) <= 120 for x in range(240)] for y in range(240) ] # Now, we iterate the mask row-by-row and find the first True bit in # it. The offset within that row is our per-row offset for the # rendering routine. - self._oled_offset = [ - m.index(True) - for m in mask - ] + self._oled_offset = [m.index(True) for m in mask] def process_events(self): """ @@ -262,14 +332,14 @@ class Simulation: surface.fill((0, 0, 0, 0)) buf = surface.get_buffer() - fb = fb[:240*240*4] + fb = fb[: 240 * 240 * 4] for y in range(240): # Use precalculated row offset to turn OLED disc into square # bounded plane. offset = self._oled_offset[y] start_offs_bytes = y * 240 * 4 start_offs_bytes += offset * 4 - end_offs_bytes = (y+1) * 240 * 4 + end_offs_bytes = (y + 1) * 240 * 4 end_offs_bytes -= offset * 4 buf.write(bytes(fb[start_offs_bytes:end_offs_bytes]), start_offs_bytes) @@ -309,7 +379,7 @@ class Simulation: off_y = center_y - (240 // 2) full.blit(self._oled_surface, (off_x, off_y)) - screen.blit(full, (0,0)) + screen.blit(full, (0, 0)) pygame.display.flip() def render_gui_lazy(self): @@ -319,7 +389,7 @@ class Simulation: this call. """ target_fps = 60.0 - d = 1/target_fps + d = 1 / target_fps if self.last_gui_render is None: self.render_gui_now() @@ -362,6 +432,7 @@ def captouch_calibration_active(): import ctx + class FramebufferManager: def __init__(self): self._free = [] @@ -369,7 +440,7 @@ class FramebufferManager: fb, c = ctx._wasm.ctx_new_for_framebuffer(240, 240) ctx._wasm.ctx_apply_transform(c, 1, 0, 120, 0, 1, 120, 0, 0, 1) self._free.append((fb, c)) - + def get(self): if len(self._free) == 0: return None, None @@ -381,8 +452,10 @@ class FramebufferManager: def put(self, fb, ctx): self._free.append((fb, ctx)) + fbm = FramebufferManager() + def get_ctx(): dctx = ctx._wasm.ctx_new_drawlist(240, 240) return ctx.Ctx(dctx) @@ -435,22 +508,28 @@ def get_button_state(left): menu_button_left = 0 + def menu_button_get(): return get_button_state(menu_button_left) + def application_button_get(): return get_button_state(1 - menu_button_left) + def left_button_get(): return get_button_state(1) + def right_button_get(): return get_button_state(0) + def menu_button_set_left(_broken): global menu_button_left menu_button_left = 1 + def menu_button_get_left(): return menu_button_left @@ -460,18 +539,23 @@ def get_captouch(a): _sim.render_gui_lazy() return _sim.petals.state_for_petal(a) -#TODO(iggy/q3k do proper positional captouch) + +# TODO(iggy/q3k do proper positional captouch) def captouch_get_petal_rad(a): return 0 + def captouch_get_petal_phi(a): return 0 + def captouch_get_petal_pad(i, x): return 0 + def freertos_sleep(ms): import _time + _time.sleep(ms / 1000.0) @@ -482,18 +566,21 @@ def scope_draw(ctx): ctx.move_to(x, 0) for i in range(240): x2 = x + i - y2 = math.sin(i/10) * 80 + y2 = math.sin(i / 10) * 80 ctx.line_to(x2, y2) ctx.line_to(130, 0) ctx.line_to(130, 130) ctx.line_to(-130, 130) ctx.line_to(-130, 0) + def usb_connected(): return True + def usb_console_active(): return True + def i2c_scan(): return [16, 44, 45, 85, 109, 110] diff --git a/sim/fakes/kernel.py b/sim/fakes/kernel.py index 62893ff8dfeadad3f7bbacb0670143303997d69a..25fe328f2ac239c9d0bfc6a54ebc17dad6f6c5ce 100644 --- a/sim/fakes/kernel.py +++ b/sim/fakes/kernel.py @@ -5,9 +5,11 @@ class FakeHeapKindStats: self.total_allocated_bytes = 1337 self.largest_free_block = 1337 + class FakeHeapStats: - general = FakeHeapKindStats('general') - dma = FakeHeapKindStats('dma') + general = FakeHeapKindStats("general") + dma = FakeHeapKindStats("dma") + def heap_stats(): return FakeHeapStats() diff --git a/sim/fakes/leds.py b/sim/fakes/leds.py index c40b76681dc77f8702ef7e0e55f6a355851b84da..3656f5334b03254eb8e9b989794370d0282572d1 100644 --- a/sim/fakes/leds.py +++ b/sim/fakes/leds.py @@ -4,7 +4,7 @@ import pygame def set_rgb(ix, r, g, b): - ix = ((39-ix) - 2 + 32)%40; + ix = ((39 - ix) - 2 + 32) % 40 r = r << 3 g = g << 2 @@ -17,6 +17,7 @@ def set_rgb(ix, r, g, b): b = 255 _sim.set_led_rgb(ix, r, g, b) + def set_all_rgb(r, g, b): for i in range(40): set_rgb(i, r, g, b) diff --git a/sim/fakes/time.py b/sim/fakes/time.py index a710a2fcf62c00a8fc26617bf25c700578c98847..e87af66b1f8f9340ac2ed5cb8b72aa58ca01cabe 100644 --- a/sim/fakes/time.py +++ b/sim/fakes/time.py @@ -1,14 +1,19 @@ import importlib -_time = importlib.import_module('_time') + +_time = importlib.import_module("_time") + def sleep_ms(ms): _time.sleep(ms * 0.001) + def ticks_ms(): return int(_time.time() * 1000) + def ticks_diff(a, b): return a - b + def ticks_add(a, b): return a + b diff --git a/sim/run.py b/sim/run.py index 31fb796fe76d10d22afba44253246436db0aeae1..b031d107f0594b542c83df0304ed60be13c71f05 100644 --- a/sim/run.py +++ b/sim/run.py @@ -24,22 +24,22 @@ class UnderscoreFinder(importlib.abc.MetaPathFinder): self.pathfinder = pathfinder def find_spec(self, fullname, path, target=None): - if fullname == '_time': - return self.builtin.find_spec('time', path, target) - if fullname in ['random', 'math']: + if fullname == "_time": + return self.builtin.find_spec("time", path, target) + if fullname in ["random", "math"]: return self.builtin.find_spec(fullname, path, target) - if fullname in ['json']: + if fullname in ["json"]: sys_path_saved = sys.path sys.path = sys_path_orig return self.pathfinder.find_spec(fullname, path, target) sys.path = sys_path_saved -#sys.meta_path.insert(0, Hook()) +# sys.meta_path.insert(0, Hook()) sys.path = [ - os.path.join(projectpath, 'python_payload'), - os.path.join(projectpath, 'sim', 'fakes'), + os.path.join(projectpath, "python_payload"), + os.path.join(projectpath, "sim", "fakes"), ] builtin = BuiltinImporter() @@ -49,13 +49,14 @@ sys.meta_path = [pathfinder, underscore] # Clean up whatever might have already been imported as `time`. import time -print('aaaa', time) + +print("aaaa", time) importlib.reload(time) -print('bbbb', time) +print("bbbb", time) sys.path_importer_cache.clear() importlib.invalidate_caches() -sys.modules['time'] = time +sys.modules["time"] = time -import main \ No newline at end of file +import main diff --git a/tools/codequal-report.py b/tools/codequal-report.py index ecab2c534230839c225b8632415310d10a0d5033..7bdefd038377b48da4db9dc9d7308af46da0357b 100644 --- a/tools/codequal-report.py +++ b/tools/codequal-report.py @@ -50,13 +50,13 @@ class Issue: fingerprint: str -def parse_line(s: str, nocol: bool=False) -> Union[Issue, None]: +def parse_line(s: str, nocol: bool = False) -> Union[Issue, None]: nparts = 4 if nocol: nparts = 3 - parts = s.split(':', nparts) + parts = s.split(":", nparts) path = parts[0] - path = path.removeprefix(os.getcwd() + '/') + path = path.removeprefix(os.getcwd() + "/") line = int(parts[1]) if nocol: col = None @@ -67,17 +67,25 @@ def parse_line(s: str, nocol: bool=False) -> Union[Issue, None]: col = int(parts[2]) rest = parts[4].strip() severity: Severity = { - 'warning': Severity.major, - 'error': Severity.blocker, + "warning": Severity.major, + "error": Severity.blocker, }.get(level, Severity.info) - check = '' - if rest.endswith(']'): - rest, check = rest.split('[') + check = "" + if rest.endswith("]"): + rest, check = rest.split("[") rest = rest.strip() - check = check.rstrip(']') - location = Location(path=path, positions=Positions(begin=Position(line=line, column=col))) - fp = hashlib.md5(f'{check} {path} {line}'.encode()).hexdigest() - return Issue(description=rest, check_name=check, severity=severity, location=location, fingerprint=fp) + check = check.rstrip("]") + location = Location( + path=path, positions=Positions(begin=Position(line=line, column=col)) + ) + fp = hashlib.md5(f"{check} {path} {line}".encode()).hexdigest() + return Issue( + description=rest, + check_name=check, + severity=severity, + location=location, + fingerprint=fp, + ) def mypy(p: str) -> List[Issue]: @@ -85,9 +93,9 @@ def mypy(p: str) -> List[Issue]: with open(p) as f: for line in f: line = line.strip() - if line.startswith('Found '): + if line.startswith("Found "): continue - if line.startswith('Success: '): + if line.startswith("Success: "): continue v = parse_line(line, nocol=True) if v is not None: @@ -103,7 +111,7 @@ def clang_tidy(p: str) -> List[Issue]: in_context = False for line in f: line = line.strip() - if line == 'Enabled checks:': + if line == "Enabled checks:": in_checks = True continue @@ -113,7 +121,7 @@ def clang_tidy(p: str) -> List[Issue]: continue if in_list: - if line.split()[0].endswith('clang-tidy'): + if line.split()[0].endswith("clang-tidy"): in_context = False continue if not in_context: @@ -132,9 +140,9 @@ def main() -> None: kind = sys.argv[1] path = sys.argv[2] - if kind == 'clang-tidy': + if kind == "clang-tidy": res = clang_tidy(path) - elif kind == 'mypy': + elif kind == "mypy": res = mypy(path) else: sys.stderr.write(f"Unknown kind {kind}\n") @@ -145,5 +153,6 @@ def main() -> None: if res != []: sys.exit(1) -if __name__ == '__main__': + +if __name__ == "__main__": main()