diff --git a/python_payload/apps/led_painter/__init__.py b/python_payload/apps/led_painter/__init__.py index 3c55c2b375d83a147406b5d9d46323d991401375..09ca2b03ab031adb960e2a4d1e1fc401616cae01 100644 --- a/python_payload/apps/led_painter/__init__.py +++ b/python_payload/apps/led_painter/__init__.py @@ -26,6 +26,18 @@ log.info("hello led painter") class LEDPainter(Application): + def get_help(self): + help_text = ( + "use petals 0 to 5 to set rgb values or petals " + "6 or 7 for shortcuts to black and white.\n\n" + "use the app button to move cw/ccw through " + "the LEDs by pressing left/right and enable " + "or disable drawing to LEDs by pressing down.\n\n" + "your pattern is saved when you exit and can be " + 'set as a "LED wallpaper" in settings->appearance.' + ) + return help_text + def __init__(self, app_ctx: ApplicationContext) -> None: super().__init__(app_ctx) self.input = InputController() diff --git a/python_payload/st3m/ui/elements/overlays.py b/python_payload/st3m/ui/elements/overlays.py index d9ce9960ae50724f9737acdb582a524a92d4a2e4..264a8a8784cb4643d8da3509cec4f31522f1833e 100644 --- a/python_payload/st3m/ui/elements/overlays.py +++ b/python_payload/st3m/ui/elements/overlays.py @@ -15,6 +15,7 @@ import st3m.power from ctx import Context import st3m.wifi from st3m.application import viewmanager_is_in_application +from st3m.ui.help import Help import math import audio @@ -147,6 +148,9 @@ class Compositor(Responder): self._volume = OverlayVolume(self.input) self.add_overlay(self._volume) + def get_help(self): + return self.main.get_help() + def _enabled_overlays(self) -> List[Responder]: res: List[Responder] = [] for kind in _all_kinds: @@ -380,18 +384,19 @@ class OverlaySystemMenu(Overlay): self.exit_app = methodprovider.exit_app self.close_menu = methodprovider.close_system_menu self.go_home = methodprovider.go_home + self.get_help = methodprovider.get_help self._menu_pos = 0 self._os_menu_entries = [ "resume", "go home", + "help", # "mixer", - # "help", ] self._app_menu_entries = [ "resume", "exit app", + "help", # "mixer", - # "help", ] self.active = False self.sub = None @@ -422,6 +427,8 @@ class OverlaySystemMenu(Overlay): if self.input.buttons.app.middle.released: if self._menu_pos == 0: self.close_menu() + elif self._menu_pos == 2: + self.sub = Help(self.input, self.get_help()) elif self.in_app: if self._menu_pos == 1: self.exit_app() diff --git a/python_payload/st3m/ui/help.py b/python_payload/st3m/ui/help.py new file mode 100644 index 0000000000000000000000000000000000000000..a0c882400924e1e2487896a6488945b61309fec7 --- /dev/null +++ b/python_payload/st3m/ui/help.py @@ -0,0 +1,55 @@ +from st3m import Responder +from st3m.utils import wrap_text + + +class Help(Responder): + def __init__(self, inputcontroller, help_text): + self.input = inputcontroller + self.help_text = help_text + self.override_os_button_back = False + self.line_height = 16 + self.line_width = 190 + self.line_height_steps = 3 + self.x = -self.line_width / 2 + self.y = -80 + self.lines = None + + def think(self, ins, delta_ms): + if self.lines is None: + return + ud_dir = self.input.buttons.app.right.pressed + ud_dir -= self.input.buttons.app.left.pressed + self.y += -ud_dir * self.line_height * self.line_height_steps + if self.y > -80: + self.y = -80 + min_y = -80 - self.line_height * max(len(self.lines) - 8, 0) + if self.y < min_y: + self.y = min_y + + def draw(self, ctx): + ctx.rgb(0, 0, 0) + ctx.rectangle(-120, -120, 240, 240).fill() + ctx.rgb(0x81 / 255, 0xCD / 255, 0xC6 / 255) + ctx.move_to(0, self.y) + ctx.text_align = ctx.CENTER + if not (self.y < -120): + ctx.font_size = 24 + ctx.text("~ help ~") + ctx.font_size = 16 + offset = self.line_height + if not isinstance(self.help_text, str): + offset += self.line_height + ctx.move_to(0, self.y + offset) + ctx.text("no help found :/") + return + ctx.text_align = ctx.LEFT + if self.lines is None: + self.lines = wrap_text(self.help_text, self.line_width, ctx) + for line in self.lines: + offset += self.line_height + if (self.y + offset) < (-120): + continue + elif (self.y + offset) > (120 + self.line_height): + break + ctx.move_to(self.x, self.y + offset) + ctx.text(line) diff --git a/python_payload/st3m/ui/view.py b/python_payload/st3m/ui/view.py index 0247cb8c58ae21bd68aeecbbdcfcdeb150c188e7..83c52afa920509a0e6c1270019ed319dffd867ce 100644 --- a/python_payload/st3m/ui/view.py +++ b/python_payload/st3m/ui/view.py @@ -51,6 +51,15 @@ class View(Responder): """ return False + def get_help(self) -> Optional[str]: + """ + Returns help string to be displayed by the system menu. May be + dynamic/context-dependent. May in the future be also used + to return more complex helpy objects, so ideally check the + output with isinstance(ret, str). + """ + return None + class BaseView(View): """ @@ -262,6 +271,11 @@ class ViewManager(Responder): return False return self._incoming.override_os_button_back + def get_help(self): + if self._incoming is None: + return None + return self._incoming.get_help() + def exit_view(self): if not self._history and self._debug: utime.sleep(0.5) diff --git a/python_payload/st3m/utils.py b/python_payload/st3m/utils.py index f16295eb23ccd960bfcf2d090b254f8b3dd3e090..4e350a546a49fe185aa0eee82a75f613b3e41aa3 100644 --- a/python_payload/st3m/utils.py +++ b/python_payload/st3m/utils.py @@ -152,3 +152,49 @@ def is_simulator() -> bool: tau = math.pi * 2 + + +def wrap_text(text, line_width, ctx=None): + """ + wraps text to stay below a certain line width and returns + the wrapped lines as a list without newline characters. + + if no ctx is passed the line width is interpreted as number + of characters, else it is the rasterized width in pixels. + the latter mode uses ctx.text_width() to determine width so + do set your ctx up in the way you'll render the result before + calling this. + + may infinite-loop if not a single character fits the line + or similar edge cases. we didn't spend a lot of time on + this. + """ + wrapped_lines = [] + if ctx is None: + len_fun = len + else: + len_fun = ctx.text_width + for line in text.split("\n"): + if len_fun(line) <= line_width: + wrapped_lines += [line] + else: + line_words = line.split(" ") + subline_words = [] + while line_words: + subline_words.append(line_words.pop(0)) + if len_fun(" ".join(subline_words)) > line_width: + if len(subline_words) == 1: + split_word = subline_words[0] + for x in range(len(split_word)): + if len_fun(split_word[:x]) > line_width: + break + x -= 1 + subline_words[0] = split_word[:x] + line_words.insert(0, split_word[x:]) + else: + line_words.insert(0, subline_words.pop()) + wrapped_lines.append(" ".join(subline_words)) + subline_words = [] + if subline_words: + wrapped_lines.append(" ".join(subline_words)) + return wrapped_lines