diff --git a/boot.py b/boot.py index b4ba3d3..c9134f4 100644 --- a/boot.py +++ b/boot.py @@ -1,16 +1,32 @@ import storage -from macropad_os.config import Config, ConfigVars +from macropad_os import Config -config = Config("config.json") -print("test") -print(config.data) +default_config = Config("default_config.json").load() +config = Config("config.json").load(default_config) +dev_mode = config.get_item_by_name("dev_mode") -if not config.data.get(ConfigVars.DEV_MODE.name): - storage.disable_usb_drive() - print("file system should be writable") - storage.remount("/", False) - config.data[ConfigVars.DEV_MODE.name] = True - config.save() - print("success") +if dev_mode : + if not dev_mode.value: + print("file system should not be writable - dev mode not active") + storage.disable_usb_drive() + storage.remount("/", False) + dev_mode_active = config.get_item_by_name("dev_mode_active") + dev_mode_active.value = False + config.set_item(dev_mode_active) + config.save() + else: + # dev mode is on and needs to be disabled + print("Disabling dev mode before boot so that settings will work") + storage.disable_usb_drive() + storage.remount("/", False) + dev_mode = config.get_item_by_name("dev_mode") + dev_mode.value = False + config.set_item(dev_mode) + dev_mode_active = config.get_item_by_name("dev_mode_active") + dev_mode_active.value = True + config.set_item(dev_mode_active) + config.save() + storage.enable_usb_drive() + storage.remount("/", True) \ No newline at end of file diff --git a/code.py b/code.py index 01f2ec9..27fed70 100644 --- a/code.py +++ b/code.py @@ -1,16 +1,19 @@ -from macropad_os import AppRouter, DebugApp, SerialComms -from macropad_os.config import Config +from macropad_os import AppRouter, SerialComms, Config +from macropad_os.system_apps import DebugApp from adafruit_macropad import MacroPad +from python_apps import NumpadApp macropad = MacroPad() -config = Config("config.json") +default_config = Config("default_config.json").load() +config = Config("config.json").load(default_config) ar = AppRouter(macropad, config, [ - DebugApp(macropad, config, "DEBUG 1"), - DebugApp(macropad, config, "DEBUG 2") + NumpadApp(macropad, config), +# Arrow Keys +# Script Runner ]) sc = SerialComms(config) diff --git a/config.json b/config.json index ef73a2c..71582cc 100644 --- a/config.json +++ b/config.json @@ -1,6 +1,87 @@ { - "dev_mode": true, - "debug_app_enabled": true, - "brightness": 100, - "default_tone": 523 + "rgb_mode": { + "current_value": "rainbow", + "available_values": [ + "rainbow", + "static" + ] + }, + "rgb_green": { + "current_value": 100, + "value_range": { + "end": 1500, + "start": 0 + }, + "dependency": { + "rgb_mode": "static" + } + }, + "rgb_red": { + "current_value": 100, + "value_range": { + "end": 1500, + "start": 0 + }, + "dependency": { + "rgb_mode": "static" + } + }, + "key_tone_hz": { + "current_value": 690, + "value_range": { + "end": 1500, + "start": 0 + }, + "dependency": { + "key_tone": true + } + }, + "brightness": { + "current_value": 75, + "value_range": { + "end": 100, + "start": 1 + } + }, + "rgb_blue": { + "current_value": 100, + "value_range": { + "end": 1500, + "start": 0 + }, + "dependency": { + "rgb_mode": "static" + } + }, + "key_tone": { + "current_value": false, + "available_values": [ + true, + false + ] + }, + "dev_mode": { + "current_value": false, + "available_values": [ + true, + false + ] + }, + "debug_app": { + "current_value": true, + "available_values": [ + true, + false + ] + }, + "dev_mode_active": { + "current_value": false, + "available_values": [ + true, + false + ], + "dependency": { + "never resolving dependency": "asdf" + } + } } \ No newline at end of file diff --git a/default_config.json b/default_config.json new file mode 100644 index 0000000..fa8f1c3 --- /dev/null +++ b/default_config.json @@ -0,0 +1,80 @@ +{ + "rgb_mode": { + "current_value": "rainbow", + "available_values": [ + "rainbow", + "static" + ] + }, + "rgb_green": { + "current_value": 100, + "value_range": { + "end": 1500, + "start": 0 + }, + "dependency": { + "rgb_mode": "static" + } + }, + "rgb_red": { + "current_value": 100, + "value_range": { + "end": 1500, + "start": 0 + }, + "dependency": { + "rgb_mode": "static" + } + }, + "key_tone_hz": { + "current_value": 523, + "value_range": { + "end": 1500, + "start": 0 + }, + "dependency": { + "key_tone": true + } + }, + "brightness": { + "current_value": 100, + "value_range": { + "end": 100, + "start": 0 + } + }, + "rgb_blue": { + "current_value": 100, + "value_range": { + "end": 1500, + "start": 0 + }, + "dependency": { + "rgb_mode": "static" + } + }, + "key_tone": { + "current_value": true, + "available_values": [ + true, + false + ] + }, + "dev_mode": { + "current_value": false, + "available_values": [ + true, + false + ] + }, + "dev_mode_active": { + "current_value": false, + "available_values": [ + true, + false + ], + "dependency": { + "never resolving dependency": "asdf" + } + } +} \ No newline at end of file diff --git a/macropad_os/__init__.py b/macropad_os/__init__.py index 5cfa543..2afbcca 100644 --- a/macropad_os/__init__.py +++ b/macropad_os/__init__.py @@ -1,5 +1,5 @@ -from .abstract_app import App +from .serial_communications import SerialComms +from .configuration import Config, ConfigItem from .app_state import AppState, InvalidStateUpdateError -from .app_router import AppRouter -from macropad_os.default_apps.debug_app import DebugApp -from .serial_communications import SerialComms \ No newline at end of file +from .abstract_app import App +from .app_router import AppRouter \ No newline at end of file diff --git a/macropad_os/abstract_app.py b/macropad_os/abstract_app.py index af6137c..b584bd9 100644 --- a/macropad_os/abstract_app.py +++ b/macropad_os/abstract_app.py @@ -1,8 +1,10 @@ +import time + import displayio import terminalio from adafruit_display_text import label -from .app_state import AppState, InvalidStateUpdateError +from macropad_os import AppState, InvalidStateUpdateError, Config DISPLAY = displayio.Group() @@ -13,7 +15,7 @@ def convert_to_keynum(x, y): class App(object): - def __init__(self, macropad, config): + def __init__(self, macropad, config: Config): """ :param macropad: @@ -31,28 +33,29 @@ class App(object): text=f"", background_color=0xFFFFFF, ) - self._key_tones = [config.data["default_tone"] for _ in range(12)] - self._enabled_key_sounds = True self._pressed_keys = [] self._key_pressed_callbacks = [] self._key_released_callbacks = [] self._encoder_changed_callbacks = [] self._encoder_state = 0 self._labels = [] + self._state = AppState.STOPPED + self._name = "app" + self._key_tones = {} + + self._current_brightness = config.brightness() self.macropad = macropad self.config = config - self.name = "app" - self.state = AppState.STOPPED def start(self) -> None: print("Start from base class ") - if self.state is not AppState.STOPPED: - raise InvalidStateUpdateError(f"Start called but the current app state is {self.state}") - self.state = AppState.STARTING + if self._state is not AppState.STOPPED: + raise InvalidStateUpdateError(f"Start called but the current app state is {self._state}") + self._state = AppState.STARTING self._on_start() self.on_start() - self.state = AppState.PAUSED + self._state = AppState.PAUSED def _on_start(self) -> None: pass @@ -61,12 +64,12 @@ class App(object): raise NotImplementedError("on_start not implemented") def resume(self) -> None: - if self.state is not AppState.PAUSED: - raise InvalidStateUpdateError(f"Resume called but the current app state is {self.state}") - self.state = AppState.RESUMING + if self._state is not AppState.PAUSED: + raise InvalidStateUpdateError(f"Resume called but the current app state is {self._state}") + self._state = AppState.RESUMING self._on_resume() self.on_resume() - self.state = AppState.RUNNING + self._state = AppState.RUNNING def _on_resume(self) -> None: self.add_displays_to_group() @@ -75,12 +78,12 @@ class App(object): raise NotImplementedError("on_resume not implemented") def pause(self) -> None: - if self.state is not AppState.RUNNING: - raise InvalidStateUpdateError(f"Pause called but the current app state is {self.state}") - self.state = AppState.PAUSING + if self._state is not AppState.RUNNING: + raise InvalidStateUpdateError(f"Pause called but the current app state is {self._state}") + self._state = AppState.PAUSING self._on_pause() self.on_pause() - self.state = AppState.PAUSED + self._state = AppState.PAUSED def _on_pause(self) -> None: self.macropad.keyboard.release_all() @@ -113,20 +116,34 @@ class App(object): if key_event.key_number < 12: if key_event.pressed: self.macropad.stop_tone() - self.macropad.start_tone(self._key_tones[key_event.key_number]) - self._pressed_keys.append(key_event.key_number) + print(self.config.get_items()) + self._play_tone_for_key(key_event.key_number) if self._key_pressed_callbacks: for callback in self._key_pressed_callbacks: callback(key_event.key_number) else: - self.macropad.stop_tone() - self._pressed_keys.remove(key_event.key_number) - if self._pressed_keys: - self.macropad.start_tone(self._key_tones[self._pressed_keys[0]]) + self._stop_tone_for_key(key_event.key_number) if self._key_released_callbacks: for callback in self._key_pressed_callbacks: callback(key_event.key_number) + def _play_tone_for_key(self, key_number): + if self.config.key_tone_enabled(): + if key_number in self._key_tones: + self.macropad.start_tone(self._key_tones[key_number]) + else: + self.macropad.start_tone(self.config.key_tone_hz()) + self._pressed_keys.append(key_number) + + def _stop_tone_for_key(self, key_number): + self.macropad.stop_tone() + self._pressed_keys.remove(key_number) + if self._pressed_keys and self.config.key_tone_enabled(): + if key_number in self._key_tones: + self.macropad.start_tone(self._key_tones[self._pressed_keys[0]]) + else: + self.macropad.start_tone(self.config.key_tone_hz()) + def _process_wheel_changes(self) -> None: encoder = self.macropad.encoder if self._encoder_state != encoder: @@ -138,12 +155,12 @@ class App(object): self._encoder_state = encoder def stop(self) -> None: - if self.state is not AppState.PAUSED: - raise InvalidStateUpdateError(f"Stop called but the current app state is {self.state}") - self.state = AppState.STOPPING + if self._state is not AppState.PAUSED: + raise InvalidStateUpdateError(f"Stop called but the current app state is {self._state}") + self._state = AppState.STOPPING self._on_stop() self.on_stop() - self.state = AppState.STOPPED + self._state = AppState.STOPPED def _on_stop(self) -> None: pass @@ -152,6 +169,14 @@ class App(object): raise NotImplementedError("on_stop not implemented.") def _update_lighting(self) -> None: + new_brightness = self.config.brightness() + if self._current_brightness != new_brightness: + print("SETTING BRIGHTNESS!!!!") + print(self._key_lights) + self._key_lights = [tuple(rgb_val * new_brightness / self._current_brightness for rgb_val in color) for + color in self._key_lights] + print(self._key_lights) + self._current_brightness = self.config.brightness() for index, color in enumerate(self._key_lights): self.macropad.pixels[index] = color @@ -165,6 +190,8 @@ class App(object): self._display_group.remove(self._layout) def set_color(self, x, y, color) -> None: + print("setting color") + color = tuple(rgb_val * self._current_brightness for rgb_val in color) key_value = convert_to_keynum(x, y) if key_value >= 12: raise ValueError("color index out of range") @@ -172,12 +199,14 @@ class App(object): self._key_lights[key_value] = color def set_colors(self, colors) -> None: + print(time.time()) if len(colors) != 12: raise ValueError("Colors must be passed in as a 12 len array") for color in colors: if len(color) != 3: raise ValueError("Color format error - color must be length 3") - self._key_lights = colors + self._key_lights = [tuple(round(rgb_val * self._current_brightness / 100) for rgb_val in color) for color in + colors] def get_colors(self) -> [(int, int, int)]: return self._key_lights @@ -195,12 +224,10 @@ class App(object): for tone in tones: if tone < 20 or tone > 20000: raise ValueError("Tone format error - tone out of human hearing range (20 - 20000)") - self._key_tones = tones + for index, hz in enumerate(tones): + self._key_tones[index] = hz - def set_tone_status(self, enable) -> None: - self._enabled_key_sounds = enable - - def get_tones(self) -> [int]: + def get_tones(self) -> {int: int}: return self._key_tones def set_title(self, title) -> None: diff --git a/macropad_os/app_router.py b/macropad_os/app_router.py index 990dd83..84cb052 100644 --- a/macropad_os/app_router.py +++ b/macropad_os/app_router.py @@ -1,7 +1,8 @@ import time - from .app_state import AppState -from .default_apps.options_app import OptionsApp + +from macropad_os.system_apps import OptionsApp, DebugApp + class AppRouter(object): def __init__(self, macropad, config, apps): @@ -15,6 +16,10 @@ class AppRouter(object): self.encoder_state = False self.options_time = 500000000 # .5 seconds in nanoseconds self.click_time = 0 + self.debug_app = DebugApp(macropad, config, "DEBUG APP") + self.debug_app_active = self.config.debug_app_enabled() + if self.debug_app_active: + self.apps.append(self.debug_app) def swap_to_app(self, app): """ @@ -26,10 +31,10 @@ class AppRouter(object): self.current_app.pause() print("Selecting new app") self.current_app = app - if self.current_app.state is AppState.STOPPED: + if self.current_app._state is AppState.STOPPED: print("Starting new app") self.current_app.start() - if self.current_app.state is AppState.PAUSED: + if self.current_app._state is AppState.PAUSED: print("Starting new app") self.current_app.resume() @@ -51,6 +56,12 @@ class AppRouter(object): self.encoder_state = False if self.current_app is self.options: print("Moving from options to the last opened app.") + if self.debug_app_active != self.config.debug_app_enabled(): + if self.debug_app_active: + self.apps.append(self.debug_app) + else: + self.apps.remove(self.debug_app) + self.debug_app_active = self.config.debug_app_enabled() else: self.app_index += 1 print("Moving to the next app on the list. ") diff --git a/macropad_os/default_apps/common/__init__.py b/macropad_os/app_utils/__init__.py similarity index 100% rename from macropad_os/default_apps/common/__init__.py rename to macropad_os/app_utils/__init__.py diff --git a/macropad_os/app_utils/helper_funcs.py b/macropad_os/app_utils/helper_funcs.py new file mode 100644 index 0000000..2364c6f --- /dev/null +++ b/macropad_os/app_utils/helper_funcs.py @@ -0,0 +1,10 @@ +def clamp(num, min_value, max_value): + return max(min(num, max_value), min_value) + + + +def rgb_from_int(rgb): + blue = rgb & 255 + green = (rgb >> 8) & 255 + red = (rgb >> 16) & 255 + return red, green, blue \ No newline at end of file diff --git a/macropad_os/app_utils/light_patterns.py b/macropad_os/app_utils/light_patterns.py new file mode 100644 index 0000000..7fdb714 --- /dev/null +++ b/macropad_os/app_utils/light_patterns.py @@ -0,0 +1,20 @@ +arrows_with_enter = [ + (0, 0, 0), (0, 0, 0), (0, 0, 0), + (0, 0, 0), (0, 0, 100), (0, 0, 0), + (0, 0, 100), (100, 0, 0), (0, 0, 100), + (0, 0, 0), (0, 0, 100), (0, 0, 0) +] + +up_down_enter = [ + (0, 0, 0), (0, 0, 0), (0, 0, 0), + (0, 0, 0), (0, 0, 100), (0, 0, 0), + (0, 0, 0), (100, 0, 0), (0, 0, 0), + (0, 0, 0), (0, 0, 100), (0, 0, 0) +] + +arrows_yes_no = [ + (0, 0, 0), (0, 0, 100), (0, 0, 0), + (100, 0, 0), (0, 0, 100), (0, 100, 0), + (0, 0, 0), (0, 0, 0), (0, 0, 0), + (0, 0, 0), (0, 0, 0), (0, 0, 0), +] diff --git a/macropad_os/default_apps/common/navigation_patterns.py b/macropad_os/app_utils/navigation_patterns.py similarity index 100% rename from macropad_os/default_apps/common/navigation_patterns.py rename to macropad_os/app_utils/navigation_patterns.py diff --git a/macropad_os/default_apps/common/sound_patterns.py b/macropad_os/app_utils/sound_patterns.py similarity index 100% rename from macropad_os/default_apps/common/sound_patterns.py rename to macropad_os/app_utils/sound_patterns.py diff --git a/macropad_os/config/__init__.py b/macropad_os/config/__init__.py deleted file mode 100644 index 2313cb2..0000000 --- a/macropad_os/config/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .config_vars import ConfigVars, ConVar -from .config import Config diff --git a/macropad_os/config/config.py b/macropad_os/config/config.py deleted file mode 100644 index a407cec..0000000 --- a/macropad_os/config/config.py +++ /dev/null @@ -1,21 +0,0 @@ -import json - -from .config_vars import ConfigVars - - -class Config(object): - def __init__(self, save_file): - self.data = {} - self.save_file = save_file - self.load() - - def load(self): - with open(self.save_file, "r") as f: - self.data = json.load(f) - for convar in ConfigVars.__dict__.keys(): - print(convar) - - def save(self): - with open(self.save_file, "w") as f: - json.dump(self.data, f) - diff --git a/macropad_os/config/config_vars.py b/macropad_os/config/config_vars.py deleted file mode 100644 index 9bd9e37..0000000 --- a/macropad_os/config/config_vars.py +++ /dev/null @@ -1,9 +0,0 @@ -class ConVar(object): - def __init__(self, name, default): - self.name = name - self.default = default - - -class ConfigVars(object): - DEV_MODE = ConVar("dev_mode", True) - TEST_VAR = ConVar("test_var", False) diff --git a/macropad_os/configuration/__init__.py b/macropad_os/configuration/__init__.py new file mode 100644 index 0000000..2e85486 --- /dev/null +++ b/macropad_os/configuration/__init__.py @@ -0,0 +1,2 @@ +from .config import Config +from .config_item import ConfigItem diff --git a/macropad_os/configuration/config.py b/macropad_os/configuration/config.py new file mode 100644 index 0000000..b166809 --- /dev/null +++ b/macropad_os/configuration/config.py @@ -0,0 +1,61 @@ +import json + +from .config_item import ConfigItem + + +class Config(object): + def __init__(self, save_file): + self._data = {} + self._items = {} + self._save_file = save_file + + def load(self, default_config=None): + try: + f = open(self._save_file, "r") + self._data = json.load(f) + self._items = {} + for key, value in self._data.items(): + self._items[key] = ConfigItem.from_json(key, value) + except Exception as e: + print(e) + self._items = {} + self._data = {} + if default_config: + for item in default_config._items: + if not (item in self._items): + self._items[item] = default_config._items[item] + return self + + def get_items(self): + return self._items + + def get_item_by_name(self, key): + if key in self._items: + return self._items[key] + else: + return None + + def set_item(self, config_item: ConfigItem): + self._items[config_item.name] = config_item + return self + + def save(self): + save_format = {} + for name, item in self._items.items(): + save_format[name] = item.to_json() + with open(self._save_file, "w") as f: + json.dump(save_format, f) + self._data = save_format + return self + + def key_tone_enabled(self): + return self.get_item_by_name("key_tone").value + + def key_tone_hz(self): + return self.get_item_by_name("key_tone_hz").value if self.key_tone_enabled() else 0 + + def brightness(self): + return self.get_item_by_name("brightness").value + + def debug_app_enabled(self): + return self.get_item_by_name("debug_app").value \ No newline at end of file diff --git a/macropad_os/configuration/config_item.py b/macropad_os/configuration/config_item.py new file mode 100644 index 0000000..4f3db61 --- /dev/null +++ b/macropad_os/configuration/config_item.py @@ -0,0 +1,50 @@ +class ValueRange(object): + def __init__(self, start, end): + self.start = start + self.end = end + + @staticmethod + def from_json(json_dict: dict): + if not ("start" in json_dict and "end" in json_dict): + raise ValueError("Value Range must be provided a start and end value in the provided json dictionary") + return ValueRange(json_dict["start"], json_dict["end"]) + + def to_json(self): + return { + "start": self.start, + "end": self.end + } + + def __str__(self): + return f"{self.to_json()}" + + +class ConfigItem(object): + def __init__(self, name: str, value, available_values: list = None, value_range: ValueRange = None, + dependency: dict = None): + self.name = name + self.value = value + self.available_values = available_values + self.value_range = value_range + self.dependency = dependency + + @staticmethod + def from_json(key: str, json_dict: dict): + return ConfigItem( + key, + json_dict["current_value"], + available_values=json_dict["available_values"] if "available_values" in json_dict else None, + value_range=ValueRange.from_json(json_dict["value_range"]) if "value_range" in json_dict else None, + dependency=json_dict["dependency"] if "dependency" in json_dict else None, + ) + + def to_json(self): + json_dict = {"current_value": self.value} + if self.value_range: + json_dict["value_range"] = self.value_range.to_json() + if self.available_values: + json_dict["available_values"] = self.available_values + if self.dependency: + json_dict["dependency"] = self.dependency + + return json_dict diff --git a/macropad_os/default_apps/__init__.py b/macropad_os/default_apps/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/macropad_os/default_apps/common/helper_funcs.py b/macropad_os/default_apps/common/helper_funcs.py deleted file mode 100644 index e69de29..0000000 diff --git a/macropad_os/default_apps/common/light_patterns.py b/macropad_os/default_apps/common/light_patterns.py deleted file mode 100644 index 3df7315..0000000 --- a/macropad_os/default_apps/common/light_patterns.py +++ /dev/null @@ -1,20 +0,0 @@ -arrows_with_enter = [ - [(0, 0, 0), (0, 0, 0), (0, 0, 0)], - [(0, 0, 0), (0, 0, 100), (0, 0, 0)], - [(0, 0, 100), (100, 0, 0), (0, 0, 100)], - [(0, 0, 0), (0, 0, 100), (0, 0, 0)] -] - -up_down_enter = [ - [(0, 0, 0), (0, 0, 0), (0, 0, 0)], - [(0, 0, 0), (0, 0, 100), (0, 0, 0)], - [(0, 0, 0), (100, 0, 0), (0, 0, 0)], - [(0, 0, 0), (0, 0, 100), (0, 0, 0)] -] - -arrows_yes_no = [ - [(0, 0, 0), (0, 0, 0), (0, 0, 0)], - [(0, 0, 0), (0, 0, 100), (0, 0, 0)], - [(0, 0, 0), (100, 0, 0), (0, 0, 0)], - [(0, 100, 0), (0, 0, 100), (100, 0, 0)] -] diff --git a/macropad_os/default_apps/options_app.py b/macropad_os/default_apps/options_app.py deleted file mode 100644 index 399dd5d..0000000 --- a/macropad_os/default_apps/options_app.py +++ /dev/null @@ -1,49 +0,0 @@ -from adafruit_displayio_layout.layouts.grid_layout import GridLayout - -from .common import arrows_yes_no, arrows_with_enter, up_down_enter -from ..abstract_app import App - - -class OptionsApp(App): - - def __init__(self, macropad, config): - super().__init__(macropad, config) - self.send_keyboard_inputs = 0 - self.labels = [] - self.key_colors = arrows_yes_no - self.possible_key_colors = [arrows_yes_no, arrows_with_enter, up_down_enter] - self.counter = 0 - self.title = "Options" - - def on_start(self): - print("on start from the options app!") - self.set_title(self.title) - self.set_layout(GridLayout(x=0, y=9, width=128, height=54, grid_size=(4, 4), cell_padding=1)) - - def on_resume(self): - print("resume from the options app!") - - def on_pause(self): - print("Pausing") - - - def on_stop(self): - print("Stopping") - - def on_loop(self): - pass - - def on_key_pressed(self, keyevent): - print("ON KEY PRESSED CALLBACK FROM OPTIONS") - print(keyevent) - - def on_key_released(self, keyevent): - print("ON KEY RELEASED CALLBACK FROM OPTIONS") - print(keyevent) - - def on_wheel_change(self, event): - print("ON WHEEL CHANGED CALLBACK") - print(event) - - - diff --git a/macropad_os/system_apps/__init__.py b/macropad_os/system_apps/__init__.py new file mode 100644 index 0000000..e7da3a5 --- /dev/null +++ b/macropad_os/system_apps/__init__.py @@ -0,0 +1,2 @@ +from .debug_app import DebugApp +from .options_app import OptionsApp \ No newline at end of file diff --git a/macropad_os/default_apps/debug_app.py b/macropad_os/system_apps/debug_app.py similarity index 79% rename from macropad_os/default_apps/debug_app.py rename to macropad_os/system_apps/debug_app.py index 6993829..8a71d1c 100644 --- a/macropad_os/default_apps/debug_app.py +++ b/macropad_os/system_apps/debug_app.py @@ -1,17 +1,11 @@ import terminalio -from adafruit_display_text import bitmap_label as label +from adafruit_display_text.scrolling_label import ScrollingLabel from adafruit_displayio_layout.layouts.grid_layout import GridLayout from rainbowio import colorwheel -from macropad_os.abstract_app import App - - -def rgb_from_int(rgb): - blue = rgb & 255 - green = (rgb >> 8) & 255 - red = (rgb >> 16) & 255 - return red, green, blue +from macropad_os import App +from macropad_os.app_utils import rgb_from_int class DebugApp(App): @@ -26,17 +20,15 @@ class DebugApp(App): def on_start(self): print("on start from the app!") - self.set_layout(GridLayout(x=0, y=9, width=128, height=54, grid_size=(4, 4), cell_padding=1)) + self.set_layout(GridLayout(x=0, y=9, width=128, height=54, grid_size=(3, 4), cell_padding=1)) self.set_title(self.title) self.lit_keys = [True] * 12 - - for _ in range(12): - self.labels.append(label.Label(terminalio.FONT, text="")) + for i in range(12): + self.labels.append(ScrollingLabel(terminalio.FONT, text=f"{i}{i}{i}{i}{i}", max_characters=6, animate_time=0.5)) for index in range(12): x = index % 3 y = index // 3 self._layout.add_content(self.labels[index], grid_position=(x, y), cell_size=(1, 1)) - self.set_tone_status(True) self.set_tones([196, 220, 246, 262, 294, 330, 349, 392, 440, 494, 523, 587]) self.register_on_key_pressed(self.process_keys_pressed_callback) self.register_on_key_released(self.process_keys_released_callback) @@ -53,16 +45,18 @@ class DebugApp(App): def on_loop(self): self.update_key_colors() + for label in self.labels: + label.update() def update_key_colors(self): self.wheel_offset += 1 - colors = self.get_colors() + colors = [] for pixel in range(12): if self.lit_keys[pixel]: (r, g, b) = rgb_from_int(colorwheel((pixel / 12 * 256) + self.wheel_offset)) - colors[pixel] = (r, g, b) + colors.append((r, g, b)) else: - colors[pixel] = 0 + colors.append((0, 0, 0)) self.set_colors(colors) def process_keys_pressed_callback(self, keyevent): diff --git a/macropad_os/system_apps/options_app.py b/macropad_os/system_apps/options_app.py new file mode 100644 index 0000000..fcc119a --- /dev/null +++ b/macropad_os/system_apps/options_app.py @@ -0,0 +1,210 @@ +import terminalio + +from adafruit_display_text import label +from adafruit_display_text.scrolling_label import ScrollingLabel +from adafruit_displayio_layout.layouts.grid_layout import GridLayout + +from macropad_os import App, Config +from macropad_os.app_utils import arrows_yes_no, arrows_with_enter, up_down_enter, clamp + + +class OptionsState(object): + UNINIT = "uninit" + VIEWING = "viewing" + SELECTING = "selecting" + + +down_arrow_text = "vvvvvvvvvvvvvvvvvvvv" +down_arrow_not_shown = "" + + +class OptionsApp(App): + def __init__(self, macropad, config: Config): + super().__init__(macropad, config) + self.send_keyboard_inputs = 0 + self.key_colors = arrows_yes_no + self.possible_key_colors = [arrows_yes_no, arrows_with_enter, up_down_enter] + self.counter = 0 + self.title = "Options" + self.cursor_index = -1 + self.next_cursor_index = 0 + self.bottom_nav_label = label.Label( + terminalio.FONT, + text=down_arrow_text, + background_tight=False, + padding_left=100, + padding_right=100 + ) + self.sorted_settings_keys = [] + self.state = OptionsState.UNINIT + self.display_labels = [ + [ScrollingLabel(terminalio.FONT, text=" ", max_characters=11, animate_time=0.5) for _ in range(3)], + [label.Label(terminalio.FONT, text="") for _ in range(3)] + ] + self.selected_item = None + self.selected_item_new_value = None + self.is_dev_mode_active = self.config.get_item_by_name("dev_mode_active").value + + def on_start(self): + self.set_title(self.title) + if not self.is_dev_mode_active: + self.set_layout(GridLayout(x=0, y=9, width=128, height=54, grid_size=(3, 4), cell_padding=1)) + self._update_settings_items() + self.update_settings_menu() + self.set_colors(arrows_yes_no) + self.register_on_key_pressed(self.on_key_pressed) + self.register_on_encoder_changed(self.on_wheel_change) + else: + self.set_layout(GridLayout(x=0, y=9, width=128, height=54, grid_size=(1, 4), cell_padding=1)) + self._layout.add_content(label.Label(terminalio.FONT, text="Disabled in dev-mode"), grid_position=(0, 0), + cell_size=(1, 1)) + self._layout.add_content(label.Label(terminalio.FONT, text="reboot device to exit"), grid_position=(0, 1), + cell_size=(1, 1)) + + def _update_settings_items(self): + self.sorted_settings_keys = [] + for config_item_name in self.config.get_items().keys(): + dependency = self.config.get_item_by_name(config_item_name).dependency + dep_check = True + if dependency is not None: + for k, v in dependency.items(): + config_item = self.config.get_item_by_name(k) + if not config_item or config_item.value != v: + dep_check = False + if dep_check: + self.sorted_settings_keys.append(config_item_name) + else: + self.sorted_settings_keys.append(config_item_name) + print("SORTED KEYS") + self.sorted_settings_keys = sorted(self.sorted_settings_keys) + print(self.sorted_settings_keys) + self._scroll() + + def _initialize_menu(self): + for y in range(3): + self._layout.add_content(self.display_labels[0][y], grid_position=(0, y), cell_size=(2, 1)) + self._layout.add_content(self.display_labels[1][y], grid_position=(2, y), cell_size=(1, 1)) + self._layout.add_content(self.bottom_nav_label, grid_position=(0, 3), cell_size=(3, 1)) + self.state = OptionsState.VIEWING + + def update_settings_menu(self): + self.next_cursor_index = clamp(self.next_cursor_index, 0, len(self.sorted_settings_keys) - 1) + if self.state == OptionsState.UNINIT: + self._initialize_menu() + if self.state == OptionsState.VIEWING and self.next_cursor_index != self.cursor_index: + self._scroll() + if self.state == OptionsState.SELECTING: + self._update_selected_item() + + def _update_selected_item(self): + if self.state == OptionsState.VIEWING: + self.display_labels[1][0].background_color = None + self.display_labels[1][0].color = 0xffffff + if self.state == OptionsState.SELECTING: + self.display_labels[1][0].background_color = 0xffffff + self.display_labels[1][0].color = 0x000000 + + def _up(self): + if self.state == OptionsState.VIEWING: + self.next_cursor_index -= 1 + elif self.state == OptionsState.SELECTING: + if self.selected_item.available_values != None: + avail_values = self.selected_item.available_values + item_index = avail_values.index(self.selected_item_new_value) - 1 + item_index = clamp(item_index, 0, len(avail_values) - 1) + self.selected_item_new_value = avail_values[item_index] + elif self.selected_item.value_range != None: + self.selected_item_new_value += 1 + self.selected_item_new_value = clamp( + self.selected_item_new_value, + self.selected_item.value_range.start, + self.selected_item.value_range.end + ) + self.display_labels[1][0].text = f"{self.selected_item_new_value}" + + def _down(self): + if self.state == OptionsState.VIEWING: + self.next_cursor_index += 1 + elif self.state == OptionsState.SELECTING: + if self.selected_item.available_values != None: + avail_values = self.selected_item.available_values + item_index = avail_values.index(self.selected_item_new_value) + 1 + item_index = clamp(item_index, 0, len(avail_values) - 1) + self.selected_item_new_value = avail_values[item_index] + elif self.selected_item.value_range != None: + self.selected_item_new_value -= 1 + self.selected_item_new_value = clamp( + self.selected_item_new_value, + self.selected_item.value_range.start, + self.selected_item.value_range.end + ) + self.display_labels[1][0].text = f"{self.selected_item_new_value}" + + def _select(self): + if self.state == OptionsState.VIEWING: + self.state = OptionsState.SELECTING + self.selected_item = self.config.get_item_by_name(self.sorted_settings_keys[self.cursor_index]) + self.selected_item_new_value = self.selected_item.value + elif self.state == OptionsState.SELECTING: + self.state = OptionsState.VIEWING + self.selected_item.value = self.selected_item_new_value + self.config.set_item(self.selected_item) + if not self.is_dev_mode_active: + self.config.save() + self._update_settings_items() + self._update_selected_item() + + def _cancel(self): + if self.state == OptionsState.VIEWING: + print("Cancel called during viewing") + elif self.state == OptionsState.SELECTING: + self.state = OptionsState.VIEWING + self.selected_item_new_value = self.selected_item.value + self._update_selected_item() + + def _scroll(self): + self.cursor_index = self.next_cursor_index + keys = self.sorted_settings_keys[self.cursor_index:self.cursor_index + 3] + print(keys) + for y in range(3): + if y < len(keys): + self.display_labels[0][y].text = f">{keys[y]}" if y == 0 else keys[y] + self.display_labels[1][y].text = f"{self.config.get_item_by_name(keys[y]).value}" + else: + self.display_labels[0][y].text = " " + self.display_labels[1][y].text = " " + if self.display_labels[0][2].text == " ": + self.bottom_nav_label.text = down_arrow_not_shown + else: + self.bottom_nav_label.text = down_arrow_text + + def on_resume(self): + print("resume from the options app!") + + def on_pause(self): + print("Pausing") + + def on_stop(self): + print("Stopping") + + def on_loop(self): + pass + + def on_key_pressed(self, keyevent): + print(keyevent) + if keyevent == 4: + self._down() + elif keyevent == 1: + self._up() + elif keyevent == 5: + self._select() + elif keyevent == 3: + self._cancel() + self.update_settings_menu() + + def on_wheel_change(self, event): + if event > 0: + self._up() + else: + self._down() + self.update_settings_menu() diff --git a/python_apps/__init__.py b/python_apps/__init__.py new file mode 100644 index 0000000..e37fb02 --- /dev/null +++ b/python_apps/__init__.py @@ -0,0 +1 @@ +from .numpad import NumpadApp \ No newline at end of file diff --git a/python_apps/numpad.py b/python_apps/numpad.py new file mode 100644 index 0000000..f321e6e --- /dev/null +++ b/python_apps/numpad.py @@ -0,0 +1,85 @@ +import terminalio +from adafruit_display_text.bitmap_label import Label + +from adafruit_display_text.scrolling_label import ScrollingLabel +from adafruit_displayio_layout.layouts.grid_layout import GridLayout +from rainbowio import colorwheel + +from macropad_os import App +from macropad_os.app_utils import rgb_from_int + + +labels = [ + "7", "8", "9", + "4", "5", "6", + "1", "2", "3", + "0", ".", "SHIFT" +] + +modified_labels = [ + "<", ">", "&", + "(", ")", "%", + "/", "+", "-", + "*", "=", "SHIFT" +] + + +class NumpadApp(App): + + def __init__(self, macropad, config): + super().__init__(macropad, config) + self.name = "Numpad" + self.wheel_offset = 0 + self.lit_keys = [False] * 12 + self.labels = [] + self.title = "Numpad" + self.modifier_pressed = False + + def on_start(self): + print("on start from the app!") + self.set_layout(GridLayout(x=0, y=9, width=128, height=54, grid_size=(3, 4), cell_padding=1)) + self.set_title(self.title) + self.lit_keys = [True] * 12 + for i in range(12): + self.labels.append(Label(terminalio.FONT, text=labels[i])) + for index in range(12): + x = index % 3 + y = index // 3 + self._layout.add_content(self.labels[index], grid_position=(x, y), cell_size=(1, 1)) + self.register_on_key_pressed(self.process_keys_pressed_callback) + self.register_on_key_released(self.process_keys_released_callback) + self.register_on_encoder_changed(self.process_enbcoder_changed) + + def on_resume(self): + print("resume from the debug app!") + + def on_pause(self): + print("Pausing") + + def on_stop(self): + print("Stopping") + + def on_loop(self): + self.update_key_colors() + + def update_key_colors(self): + self.wheel_offset += 1 + colors = [] + for pixel in range(12): + if self.lit_keys[pixel]: + (r, g, b) = rgb_from_int(colorwheel((pixel / 12 * 256) + self.wheel_offset)) + colors.append((r, g, b)) + else: + colors.append((0, 0, 0)) + self.set_colors(colors) + + def process_keys_pressed_callback(self, keyevent): + print("PROCESS KEYS CALLBACK FROM DEBUG") + print(keyevent) + + def process_keys_released_callback(self, keyevent): + print("PROCESS KEYS RELEASED CALLBACK FROM DEBUG") + print(keyevent) + def process_enbcoder_changed(self, keyevent): + print("PROCESS Encoder Changed Callback FROM DEBUG") + print(keyevent)