IS-Matrix-Forge 1.0.0.dev27__tar.gz → 1.0.0.dev28__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (208) hide show
  1. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/PKG-INFO +3 -3
  2. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/assets/font_map/base.py +2 -2
  3. is_matrix_forge-1.0.0.dev28/is_matrix_forge/led_matrix/Scripts/led_matrix/__init__.py +531 -0
  4. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/Scripts/led_matrix/arguments/commands/display_text.py +7 -0
  5. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/Scripts/led_matrix/arguments/commands/scroll_text.py +14 -0
  6. is_matrix_forge-1.0.0.dev28/is_matrix_forge/led_matrix/__init__.py +41 -0
  7. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/controller/base.py +30 -0
  8. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/controller/components/drawing.py +1 -1
  9. is_matrix_forge-1.0.0.dev28/is_matrix_forge/led_matrix/controller/components/history/__init__.py +1 -0
  10. is_matrix_forge-1.0.0.dev28/is_matrix_forge/led_matrix/controller/components/history/event.py +24 -0
  11. is_matrix_forge-1.0.0.dev28/is_matrix_forge/led_matrix/controller/components/history/manager.py +185 -0
  12. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/controller/controller.py +79 -71
  13. is_matrix_forge-1.0.0.dev28/is_matrix_forge/led_matrix/controller/helpers/__init__.py +131 -0
  14. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/animations/audio_visualizer.py +6 -3
  15. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/animations/text_scroller.py +6 -4
  16. is_matrix_forge-1.0.0.dev28/is_matrix_forge/led_matrix/display/grid/composite/utils.py +258 -0
  17. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/hardware.py +1 -1
  18. is_matrix_forge-1.0.0.dev28/is_matrix_forge/led_matrix/utils/__init__.py +107 -0
  19. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/pyproject.toml +3 -3
  20. is_matrix_forge-1.0.0.dev27/is_matrix_forge/led_matrix/Scripts/led_matrix/__init__.py +0 -210
  21. is_matrix_forge-1.0.0.dev27/is_matrix_forge/led_matrix/__init__.py +0 -96
  22. is_matrix_forge-1.0.0.dev27/is_matrix_forge/led_matrix/controller/helpers/__init__.py +0 -67
  23. is_matrix_forge-1.0.0.dev27/is_matrix_forge/led_matrix/utils/__init__.py +0 -41
  24. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/LICENSE.md +0 -0
  25. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/README.md +0 -0
  26. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/Untitled.ipynb +0 -0
  27. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/__init__.py +0 -0
  28. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/assets/__init__.py +0 -0
  29. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/assets/audio/__init__.py +0 -0
  30. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/assets/audio/files/__init__.py +0 -0
  31. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/assets/audio/files/plugged.wav +0 -0
  32. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/assets/audio/files/unplugged.wav +0 -0
  33. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/assets/char_map.json +0 -0
  34. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/assets/digit_map.py +0 -0
  35. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/assets/font_map/__init__.py +0 -0
  36. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/assets/font_map/builders.py +0 -0
  37. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/assets/font_map/helpers.py +0 -0
  38. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/assets/font_map/models/__init__.py +0 -0
  39. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/assets/font_map/models/glyph.py +0 -0
  40. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/common/__init__.py +0 -0
  41. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/common/decorators/__init__.py +0 -0
  42. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/common/decorators/freeze_setter.py +0 -0
  43. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/common/dirs.py +0 -0
  44. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/common/helpers/__init__.py +0 -0
  45. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/common/helpers/errors.py +0 -0
  46. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/common/helpers/github_api.py +0 -0
  47. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/common/logging/__init__.py +0 -0
  48. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/common/logging/log_exceptions.py +0 -0
  49. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/designer_gui/__init__.py +0 -0
  50. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/designer_gui/main_window/__init__.py +0 -0
  51. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/designer_gui/main_window/layout/__init__.py +0 -0
  52. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/designer_gui/main_window/layout/button_grid/__init__.py +0 -0
  53. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/designer_gui/main_window/layout/button_grid/column.py +0 -0
  54. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/designer_gui/main_window/layout/button_grid/grid.py +0 -0
  55. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/designer_gui/main_window/layout/menu_bar.py +0 -0
  56. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/designer_gui/main_window/streamlit_app.py +0 -0
  57. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/dev_tools/__init__.py +0 -0
  58. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/dev_tools/debug.py +0 -0
  59. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/dev_tools/presets.py +0 -0
  60. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/inputmodule/__init__.py +0 -0
  61. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/inputmodule/b1display.py +0 -0
  62. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/inputmodule/c1minimal.py +0 -0
  63. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/inputmodule/font.py +0 -0
  64. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/inputmodule/helpers/__init__.py +0 -0
  65. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/inputmodule/ledmatrix.py +0 -0
  66. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/Scripts/__init__.py +0 -0
  67. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/Scripts/battery_monitor.py +0 -0
  68. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/Scripts/identify_matrices.py +0 -0
  69. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/Scripts/install_presets/__init__.py +0 -0
  70. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/Scripts/install_presets/arguments.py +0 -0
  71. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/Scripts/install_presets/main.py +0 -0
  72. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/Scripts/led_matrix/arguments/__init__.py +0 -0
  73. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/Scripts/led_matrix/arguments/commands/__init__.py +0 -0
  74. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/Scripts/led_matrix/arguments/commands/identify_matrices.py +0 -0
  75. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/Scripts/led_matrix/guards.py +0 -0
  76. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/Scripts/pixel_grid.py +0 -0
  77. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/commands/__init__.py +0 -0
  78. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/commands/map.py +0 -0
  79. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/constants.py +0 -0
  80. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/controller/__init__.py +0 -0
  81. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/controller/components/__init__.py +0 -0
  82. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/controller/components/animation.py +0 -0
  83. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/controller/components/breather.py +0 -0
  84. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/controller/components/brightness/__init__.py +0 -0
  85. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/controller/components/brightness/helpers/__init__.py +0 -0
  86. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/controller/components/brightness/helpers/breather_killer.py +0 -0
  87. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/controller/components/brightness/helpers/easing.py +0 -0
  88. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/controller/components/brightness/helpers/fade_planner.py +0 -0
  89. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/controller/components/brightness/helpers/fade_spec.py +0 -0
  90. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/controller/components/brightness/helpers/levels.py +0 -0
  91. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/controller/components/brightness/helpers/percent.py +0 -0
  92. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/controller/components/brightness/helpers/safe_ops.py +0 -0
  93. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/controller/components/brightness/manager.py +0 -0
  94. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/controller/components/example_template.py +0 -0
  95. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/controller/components/identify.py +0 -0
  96. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/controller/components/keep_alive.py +0 -0
  97. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/controller/helpers/threading.py +0 -0
  98. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/controller/legacy.py +0 -0
  99. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/controller/multiton.py +0 -0
  100. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/controller/threaded.py +0 -0
  101. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/__init__.py +0 -0
  102. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/animations/__init__.py +0 -0
  103. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/animations/animation.py +0 -0
  104. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/animations/effects/__init__.py +0 -0
  105. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/animations/errors.py +0 -0
  106. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/animations/font/__init__.py +0 -0
  107. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/animations/font/font.py +0 -0
  108. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/animations/font/map_builder.py +0 -0
  109. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/animations/frame/__init__.py +0 -0
  110. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/animations/frame/base.py +0 -0
  111. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/animations/frame/helpers.py +0 -0
  112. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/animations/keep_awake.py +0 -0
  113. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/animations/plugged.py +0 -0
  114. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/animations/text/__init__.py +0 -0
  115. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/animations/text/glyph_normalizer.py +0 -0
  116. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/animations.zip +0 -0
  117. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/assets/__init__.py +0 -0
  118. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/assets/fonts.py +0 -0
  119. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/effects/__init__.py +0 -0
  120. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/effects/breather.py +0 -0
  121. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/effects/fade.py +0 -0
  122. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/effects/scroller/__init__.py +0 -0
  123. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/effects/scroller/base.py +0 -0
  124. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/effects/scroller/helpers.py +0 -0
  125. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/grid/__init__.py +0 -0
  126. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/grid/base.py +0 -0
  127. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/grid/composite/__init__.py +0 -0
  128. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/grid/composite/background.py +0 -0
  129. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/grid/composite/composite.py +0 -0
  130. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/grid/composite/foreground.py +0 -0
  131. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/grid/helpers.py +0 -0
  132. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/grid/presets/__init__.py +0 -0
  133. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/grid/presets/manifest.py +0 -0
  134. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/helpers/__init__.py +0 -0
  135. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/helpers/columns.py +0 -0
  136. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/media.py +0 -0
  137. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/patterns/built_in/__init__.py +0 -0
  138. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/patterns/built_in/commands.py +0 -0
  139. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/patterns/built_in/constants.py +0 -0
  140. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/patterns/built_in/stencils/__init__.py +0 -0
  141. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/patterns/built_in/stencils/res.py +0 -0
  142. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/scene/__init__.py +0 -0
  143. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/scene/errors.py +0 -0
  144. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/scene/scene.py +0 -0
  145. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/text/__init__.py +0 -0
  146. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/text/scroller.py +0 -0
  147. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/display/text/text.py +0 -0
  148. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/errors/__init__.py +0 -0
  149. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/errors/base.py +0 -0
  150. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/errors/composite_grid.py +0 -0
  151. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/errors/grid.py +0 -0
  152. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/errors/matrix.py +0 -0
  153. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/errors/misc.py +0 -0
  154. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/helpers/__init__.py +0 -0
  155. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/helpers/device/__init__.py +0 -0
  156. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/helpers/device/bootloader/__init__.py +0 -0
  157. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/helpers/device/bootloader/constants.py +0 -0
  158. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/helpers/device/bootloader/unix.py +0 -0
  159. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/helpers/device/bootloader/win.py +0 -0
  160. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/helpers/status_handler.py +0 -0
  161. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/is_matrix_forge.led_matrix.display.zip +0 -0
  162. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/status.py +0 -0
  163. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/tools/__init__.py +0 -0
  164. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/led_matrix/tools/identify.py +0 -0
  165. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/log_engine.py +0 -0
  166. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/monitor/__init__.py +0 -0
  167. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/monitor/errors.py +0 -0
  168. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/monitor/events.py +0 -0
  169. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/monitor/gui/__init__.py +0 -0
  170. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/monitor/gui/errors.py +0 -0
  171. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/monitor/gui/event/__init__.py +0 -0
  172. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/monitor/gui/event/collection.py +0 -0
  173. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/monitor/gui/event/errors.py +0 -0
  174. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/monitor/gui/event/event.py +0 -0
  175. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/monitor/gui/layout/__init__.py +0 -0
  176. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/monitor/gui/layout/base.py +0 -0
  177. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/monitor/gui/metaclasses/__init__.py +0 -0
  178. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/monitor/gui/windows/__init__.py +0 -0
  179. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/monitor/gui/windows/base.py +0 -0
  180. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/monitor/gui/windows/main/__init__.py +0 -0
  181. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/monitor/gui/windows/main/layout.py +0 -0
  182. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/monitor/helpers.py +0 -0
  183. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/monitor/monitor.py +0 -0
  184. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/notify/__init__.py +0 -0
  185. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/notify/sounds/__init__.py +0 -0
  186. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/notify/sounds/base.py +0 -0
  187. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/progress.py +0 -0
  188. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/scripts/__init__.py +0 -0
  189. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/scripts/led-matrix.py +0 -0
  190. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/scripts/preset_installer.py +0 -0
  191. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/serial_com/__init__.py +0 -0
  192. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/serial_com/base/__init__.py +0 -0
  193. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/serial_com/base/answer.py +0 -0
  194. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/serial_com/base/message.py +0 -0
  195. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/serial_com/base/request.py +0 -0
  196. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/is_matrix_forge/serial_com/exchange.py +0 -0
  197. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/presets/blank.json +0 -0
  198. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/presets/manifest.json +0 -0
  199. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/presets/shock_face.json +0 -0
  200. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/presets/test1.json +0 -0
  201. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/presets/test2.json +0 -0
  202. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/presets/test3.json +0 -0
  203. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/presets/test4.json +0 -0
  204. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/presets/test5.json +0 -0
  205. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/presets/test6.json +0 -0
  206. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/presets/test7.json +0 -0
  207. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/presets/x_animation II.json +0 -0
  208. {is_matrix_forge-1.0.0.dev27 → is_matrix_forge-1.0.0.dev28}/presets/x_animation.json +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: IS-Matrix-Forge
3
- Version: 1.0.0.dev27
3
+ Version: 1.0.0.dev28
4
4
  Summary:
5
5
  License: MIT
6
6
  Keywords: led,matrix,framework,hardware
@@ -22,8 +22,8 @@ Requires-Dist: inspyre-toolbox (>=1.6.0.dev23)
22
22
  Requires-Dist: ipywidgets (==8.1.7)
23
23
  Requires-Dist: notebook (>=7.4.4,<8.0.0)
24
24
  Requires-Dist: opencv-python (>=4.11.0.86,<5.0.0.0)
25
- Requires-Dist: pillow (>=11.2.1,<12.0.0)
26
- Requires-Dist: psutil (==6.1.1)
25
+ Requires-Dist: pillow (>=12.0.0,<13.0.0)
26
+ Requires-Dist: platformdirs (>=4.5.0,<5.0.0)
27
27
  Requires-Dist: pyserial (>=3.5,<4.0)
28
28
  Requires-Dist: pysimplegui-4-foss (==4.60.4.1)
29
29
  Requires-Dist: pysimpleguiweb (==0.39.0)
@@ -20,8 +20,8 @@ from dataclasses import dataclass
20
20
  from typing import Any, Dict, Iterable, Optional, Tuple, Callable
21
21
 
22
22
  from inspyre_toolbox.syntactic_sweets.classes import validate_type
23
- from .models.glyph import Glyph
24
- from .helpers import load_builtin_char_map, get_ignore_case, has_key_ignore_case
23
+ from is_matrix_forge.assets.font_map.models.glyph import Glyph
24
+ from is_matrix_forge.assets.font_map.helpers import load_builtin_char_map, get_ignore_case, has_key_ignore_case
25
25
 
26
26
 
27
27
  @dataclass(init=False, slots=True)
@@ -0,0 +1,531 @@
1
+
2
+ from typing import Iterable, Optional, Tuple
3
+
4
+ import threading
5
+ from collections.abc import Callable
6
+
7
+ from is_matrix_forge.led_matrix.Scripts.led_matrix.arguments import Arguments
8
+ from is_matrix_forge.led_matrix.Scripts.led_matrix.guards import run_with_guard
9
+ # from is_matrix_forge.led_matrix.Scripts.identify_matrices import
10
+
11
+ ARGUMENTS = Arguments()
12
+
13
+
14
+ def _normalize_side(value):
15
+ if isinstance(value, str):
16
+ return value.strip().lower()
17
+ return None
18
+
19
+ def _desired_side(cli_args):
20
+ if cli_args is None:
21
+ return None
22
+ if getattr(cli_args, 'only_left', False):
23
+ return 'left'
24
+ if getattr(cli_args, 'only_right', False):
25
+ return 'right'
26
+ return None
27
+
28
+ def _resolve_location(controller) -> Tuple[Optional[str], Optional[int]]:
29
+ """
30
+ Return a normalized (side, slot) tuple from a controller.
31
+
32
+ Looks in this order:
33
+ 1) Explicit attributes: controller.side_of_keyboard, controller.slot
34
+ 2) controller.location if it's a dict with 'side'/'slot'
35
+ 3) controller.location if it's a dict with 'abbrev' (e.g. 'L0', 'R2')
36
+ 4) controller.location if it's a str, resolved via SLOT_MAP
37
+ """
38
+ # 1) explicit attributes
39
+ side = _normalize_side(getattr(controller, 'side_of_keyboard', None))
40
+ slot = getattr(controller, 'slot', None)
41
+
42
+ location = getattr(controller, 'location', None)
43
+
44
+ # 2) dict with 'side'/'slot'
45
+ if isinstance(location, dict):
46
+ if side is None:
47
+ side = _normalize_side(location.get('side'))
48
+ if slot is None:
49
+ slot = location.get('slot')
50
+
51
+ # 3) dict with 'abbrev' like 'L0', 'R1'
52
+ if (side is None or slot is None) and 'abbrev' in location and isinstance(location['abbrev'], str):
53
+ abbrev = location['abbrev'].strip().upper()
54
+ # Try SLOT_MAP first, then parse fallback
55
+ try:
56
+ from is_matrix_forge.led_matrix.constants import SLOT_MAP
57
+ except Exception:
58
+ SLOT_MAP = {}
59
+
60
+ entry = SLOT_MAP.get(abbrev) or {}
61
+ side = _normalize_side(side or entry.get('side'))
62
+ slot = slot if slot is not None else entry.get('slot')
63
+
64
+ if (side is None or slot is None):
65
+ # Parse e.g. 'L0'/'R12' -> ('left'/'right', 0/12)
66
+ if len(abbrev) >= 2 and abbrev[0] in ('L', 'R') and abbrev[1:].isdigit():
67
+ side = _normalize_side(side or ('left' if abbrev[0] == 'L' else 'right'))
68
+ slot = slot if slot is not None else int(abbrev[1:])
69
+
70
+ # 4) location is a string -> SLOT_MAP lookup
71
+ if (side is None or slot is None) and isinstance(location, str):
72
+ try:
73
+ from is_matrix_forge.led_matrix.constants import SLOT_MAP
74
+ except Exception:
75
+ SLOT_MAP = {}
76
+ entry = SLOT_MAP.get(location, {})
77
+ side = _normalize_side(side or entry.get('side'))
78
+ slot = slot if slot is not None else entry.get('slot')
79
+
80
+ # normalize slot
81
+ try:
82
+ slot = int(slot) if slot is not None else None
83
+ except (TypeError, ValueError):
84
+ slot = None
85
+
86
+ return side, slot
87
+
88
+
89
+ def _controller_side(controller) -> Optional[str]:
90
+ side, _ = _resolve_location(controller)
91
+ return side
92
+
93
+ def _slot_rank(controller) -> int:
94
+ """
95
+ Returns an integer for sorting. Unknown becomes 0 (neutral).
96
+ """
97
+ _, slot = _resolve_location(controller)
98
+ try:
99
+ return int(slot)
100
+ except (TypeError, ValueError):
101
+ return 0
102
+
103
+ def find_leftmost_matrix(controllers: Iterable):
104
+ """Return the controller that is physically positioned furthest left.
105
+
106
+ Ordering:
107
+ 1) left side first, unknown middle, right side last
108
+ 2) within same side, lower slot = further left
109
+ 3) stable tiebreaker by original index
110
+ """
111
+ ranked = []
112
+ for index, controller in enumerate(controllers):
113
+ side = _controller_side(controller)
114
+ if side == 'left':
115
+ side_rank = 0
116
+ elif side == 'right':
117
+ side_rank = 2
118
+ else:
119
+ side_rank = 1 # unknowns in the middle
120
+
121
+ rank_tuple = (side_rank, _slot_rank(controller), index)
122
+ print(f'Ranks: {rank_tuple[0]}, {rank_tuple[1]}, {rank_tuple[2]}')
123
+ ranked.append((rank_tuple, controller))
124
+ print(ranked)
125
+
126
+ if not ranked:
127
+ return None
128
+
129
+ return min(ranked, key=lambda item: item[0])[1]
130
+
131
+ def find_rightmost_matrix(controllers: Iterable):
132
+ """Return the controller that is physically positioned furthest right.
133
+
134
+ Ordering:
135
+ 1) right side first, unknown middle, left side last
136
+ 2) within same side, higher slot = further right
137
+ 3) stable tiebreaker by original index
138
+ """
139
+ ranked = []
140
+ for index, controller in enumerate(controllers):
141
+ side = _controller_side(controller)
142
+ if side == 'right':
143
+ side_rank = 0
144
+ elif side == 'left':
145
+ side_rank = 2
146
+ else:
147
+ side_rank = 1 # unknowns in the middle
148
+
149
+ # negate slot so larger slot numbers sort earlier for "rightmost"
150
+ rank_tuple = (side_rank, -_slot_rank(controller), index)
151
+ print(f'Ranks: {rank_tuple[0]}, {-rank_tuple[1]}, {rank_tuple[2]}')
152
+ ranked.append((rank_tuple, controller))
153
+ print(ranked)
154
+
155
+ if not ranked:
156
+ return None
157
+
158
+ return min(ranked, key=lambda item: item[0])[1]
159
+
160
+
161
+ def _filter_controllers_by_side(controllers, cli_args):
162
+ """Return the controllers that match the requested keyboard side."""
163
+
164
+ desired = _desired_side(cli_args)
165
+
166
+ if desired is None:
167
+ return list(controllers)
168
+
169
+ return [
170
+ controller for controller in controllers
171
+ if _controller_side(controller) == desired
172
+ ]
173
+
174
+
175
+ def _describe_selection(cli_args):
176
+ desired = _desired_side(cli_args)
177
+
178
+ if desired == 'left':
179
+ return 'the leftmost LED matrix'
180
+
181
+ if desired == 'right':
182
+ return 'the rightmost LED matrix'
183
+
184
+ return 'any LED matrix'
185
+
186
+
187
+ def _order_controllers_for_span(controllers: Iterable):
188
+ controllers = list(controllers)
189
+
190
+ if len(controllers) <= 1:
191
+ return controllers
192
+
193
+ origin = find_rightmost_matrix(controllers)
194
+
195
+ if origin is None:
196
+ return controllers
197
+
198
+ ordered = [origin]
199
+ remaining = [controller for controller in controllers if controller is not origin]
200
+
201
+ while remaining:
202
+ next_controller = find_leftmost_matrix(remaining)
203
+
204
+ if next_controller is None:
205
+ ordered.extend(remaining)
206
+ break
207
+
208
+ ordered.append(next_controller)
209
+ remaining = [controller for controller in remaining if controller is not next_controller]
210
+
211
+ return ordered
212
+
213
+
214
+ def _build_horizontal_span_animations(text: str, controllers: Iterable):
215
+ controllers = list(controllers)
216
+
217
+ if not controllers:
218
+ return {}
219
+
220
+ from is_matrix_forge.assets.font_map.base import FontMap
221
+ from is_matrix_forge.led_matrix.display.animations.animation import Animation, Frame
222
+ from is_matrix_forge.led_matrix.display.animations.text.glyph_normalizer import GlyphNormalizer
223
+ from is_matrix_forge.led_matrix.display.grid.base import MATRIX_HEIGHT, MATRIX_WIDTH, Grid
224
+
225
+ font_map = FontMap(case_sensitive=False)
226
+ case_sensitive = font_map.is_case_sensitive
227
+ normalized_text = text if case_sensitive else text.upper()
228
+
229
+ glyphs = []
230
+ normalizer = GlyphNormalizer(MATRIX_WIDTH, MATRIX_HEIGHT)
231
+ for character in normalized_text:
232
+ glyph_rows = font_map.lookup(character)
233
+ normalized_rows = normalizer.normalize(glyph_rows)
234
+ glyphs.append([row[:] for row in normalized_rows])
235
+
236
+ spacing = 1
237
+ glyph_widths = [len(glyph[0]) if glyph and glyph[0] else 0 for glyph in glyphs]
238
+ total_glyph_width = sum(glyph_widths) + spacing * (len(glyph_widths) - 1 if glyph_widths else 0)
239
+
240
+ segment_width = MATRIX_WIDTH
241
+ total_width = segment_width * len(controllers)
242
+ frame_duration = 0.05
243
+
244
+ if total_glyph_width == 0:
245
+ animations = {}
246
+
247
+ for controller in controllers:
248
+ blank_columns = [[0] * MATRIX_HEIGHT for _ in range(segment_width)]
249
+ grid = Grid(
250
+ width=segment_width,
251
+ height=MATRIX_HEIGHT,
252
+ init_grid=[column[:] for column in blank_columns],
253
+ align_x='left',
254
+ )
255
+ frame_one = Frame(grid=grid)
256
+ frame_two = Frame(grid=Grid(
257
+ width=segment_width,
258
+ height=MATRIX_HEIGHT,
259
+ init_grid=[column[:] for column in blank_columns],
260
+ align_x='left',
261
+ ))
262
+ animation = Animation(frame_data=[frame_one, frame_two])
263
+ animation.set_all_frame_durations(frame_duration)
264
+ animations[controller] = animation
265
+
266
+ return animations
267
+
268
+ canvas = [[0] * max(total_glyph_width, 1) for _ in range(MATRIX_HEIGHT)]
269
+ x_cursor = 0
270
+
271
+ for glyph, width in zip(glyphs, glyph_widths):
272
+ glyph_height = len(glyph)
273
+ vertical_padding = max((MATRIX_HEIGHT - glyph_height) // 2, 0)
274
+
275
+ for row_index in range(glyph_height):
276
+ if width:
277
+ canvas_row = canvas[vertical_padding + row_index]
278
+ canvas_row[x_cursor:x_cursor + width] = glyph[row_index]
279
+
280
+ x_cursor += width + spacing
281
+
282
+ offsets = range(-total_width, total_glyph_width)
283
+ frame_columns_by_controller = {controller: [] for controller in controllers}
284
+
285
+ for offset in offsets:
286
+ window_rows = [
287
+ [canvas[row][offset + column] if 0 <= offset + column < total_glyph_width else 0 for column in range(total_width)]
288
+ for row in range(MATRIX_HEIGHT)
289
+ ]
290
+
291
+ window_columns = [
292
+ [window_rows[row][column] for row in range(MATRIX_HEIGHT)]
293
+ for column in range(total_width)
294
+ ]
295
+
296
+ for index, controller in enumerate(controllers):
297
+ start = index * segment_width
298
+ end = start + segment_width
299
+ segment = [column[:] for column in window_columns[start:end]]
300
+
301
+ if len(segment) < segment_width:
302
+ segment.extend([[0] * MATRIX_HEIGHT for _ in range(segment_width - len(segment))])
303
+
304
+ grid = Grid(
305
+ width=segment_width,
306
+ height=MATRIX_HEIGHT,
307
+ init_grid=segment,
308
+ align_x='left',
309
+ )
310
+ frame_columns_by_controller[controller].append(Frame(grid=grid))
311
+
312
+ animations = {}
313
+
314
+ for controller, frames in frame_columns_by_controller.items():
315
+ animation = Animation(frame_data=frames or [Frame(width=segment_width, height=MATRIX_HEIGHT)])
316
+ animation.set_all_frame_durations(frame_duration)
317
+ animations[controller] = animation
318
+
319
+ return animations
320
+
321
+
322
+ def _run_operation(controllers: Iterable, operation: Callable, *, concurrent: bool) -> None:
323
+ """Run an operation against one or more controllers, optionally in parallel."""
324
+
325
+ if concurrent and len(controllers) > 1:
326
+ threads = []
327
+
328
+ for controller in controllers:
329
+ thread = threading.Thread(target=operation, args=(controller,), daemon=True)
330
+ thread.start()
331
+ threads.append(thread)
332
+
333
+ for thread in threads:
334
+ thread.join()
335
+ return
336
+
337
+ for controller in controllers:
338
+ operation(controller)
339
+
340
+
341
+ def execute_get_controllers(cli_args=None):
342
+ """Return the available controllers honoring any CLI matrix selection.
343
+
344
+ Parameters:
345
+ cli_args (Optional[argparse.Namespace]):
346
+ The parsed command-line arguments. When provided, any matrix
347
+ selection flags (``--only-left`` / ``--only-right``) are applied to
348
+ the available controllers.
349
+
350
+ Returns:
351
+ List[LEDMatrixController]:
352
+ A list of controller objects, each representing an available LED matrix.
353
+ """
354
+ from is_matrix_forge.led_matrix.controller import get_controllers
355
+ controllers = get_controllers(
356
+ threaded = True,
357
+ skip_all_init_animations = True,
358
+ clear_on_init = True
359
+ )
360
+
361
+ if not controllers:
362
+ raise SystemExit('No LED matrices are available.')
363
+
364
+ if filtered := _filter_controllers_by_side(controllers, cli_args):
365
+ return filtered
366
+
367
+ raise SystemExit(f'No LED matrices matched the requested selection ({_describe_selection(cli_args)}).')
368
+
369
+
370
+ def scroll_text_command(cli_args=ARGUMENTS):
371
+ """
372
+ Invokes the command to scroll text on the LED matrix.
373
+
374
+ Parameters:
375
+ cli_args (Optional[Arguments]):
376
+ The object that holds the command-line arguments. This is a subclass of `argparse.ArgumentParser`.
377
+ (Defaults to `ARGUMENTS`)
378
+
379
+ Returns:
380
+ None
381
+ """
382
+ from .arguments.commands.scroll_text import DIRECTION_MAP
383
+ controllers = execute_get_controllers(cli_args)
384
+
385
+ direction = DIRECTION_MAP[cli_args.direction.strip().lower()]
386
+ text = cli_args.input
387
+
388
+ sequential_requested = getattr(cli_args, 'sequential', False) and len(controllers) > 1
389
+ span_requested = getattr(cli_args, 'span_matrices', False) and len(controllers) > 1
390
+
391
+ span_animations = None
392
+
393
+ if span_requested:
394
+ if cli_args.direction.strip().lower() != 'h':
395
+ raise SystemExit('--span-matrices requires --direction h.')
396
+
397
+ if sequential_requested:
398
+ raise SystemExit('--span-matrices cannot be combined with --sequential.')
399
+
400
+ controllers = _order_controllers_for_span(controllers)
401
+ span_animations = _build_horizontal_span_animations(text, controllers)
402
+ sequential = False
403
+ concurrent = True
404
+ else:
405
+ sequential = sequential_requested
406
+ concurrent = not sequential
407
+
408
+ def activator(devices, _stop_event):
409
+ def operation(controller):
410
+ controller.keep_alive = True
411
+ if span_animations is not None:
412
+ animation = span_animations.get(controller)
413
+ if animation is None:
414
+ return
415
+ controller.play_animation(animation)
416
+ else:
417
+ controller.scroll_text(text, direction=direction)
418
+
419
+ _run_operation(devices, operation, concurrent=concurrent)
420
+
421
+ def invoke(targets, index=None):
422
+ run_with_guard(
423
+ targets,
424
+ run_for=None,
425
+ clear_after=False,
426
+ activator=activator,
427
+ thread_name='scroll-text-guard' if index is None else f'scroll-text-guard-{index}',
428
+ )
429
+
430
+ if sequential:
431
+ for index, controller in enumerate(controllers, start=1):
432
+ invoke([controller], index)
433
+ else:
434
+ invoke(controllers)
435
+
436
+
437
+ def display_text_command(cli_args):
438
+ """Display static text on the selected LED matrices until interrupted."""
439
+ controllers = execute_get_controllers(cli_args)
440
+
441
+ clear_after = not cli_args.skip_clear
442
+ sequential = getattr(cli_args, 'sequential', False) and len(controllers) > 1
443
+
444
+ if sequential and cli_args.run_for is None:
445
+ raise SystemExit('--sequential requires --run-for when multiple matrices are targeted.')
446
+
447
+ wait_for_interrupt = cli_args.run_for is None
448
+ text = cli_args.text
449
+
450
+ concurrent = not sequential
451
+
452
+ def activator(devices, _stop_event):
453
+ def operation(controller):
454
+ controller.keep_alive = True
455
+ controller.show_text(text)
456
+
457
+ _run_operation(devices, operation, concurrent=concurrent)
458
+
459
+ def invoke(targets, index=None):
460
+ run_with_guard(
461
+ targets,
462
+ run_for=cli_args.run_for,
463
+ clear_after=clear_after,
464
+ activator=activator,
465
+ thread_name='display-text-guard' if index is None else f'display-text-guard-{index}',
466
+ wait_for_interrupt=wait_for_interrupt,
467
+ )
468
+
469
+ if sequential:
470
+ for index, controller in enumerate(controllers, start=1):
471
+ invoke([controller], index)
472
+ else:
473
+ invoke(controllers)
474
+
475
+
476
+ def identify_matrices_command(cli_args):
477
+ """Run the identification routine on the selected LED matrices.
478
+
479
+ Parameters:
480
+ cli_args: argparse.Namespace
481
+ The parsed arguments for the ``identify-matrices`` sub-command.
482
+ """
483
+ controllers = execute_get_controllers(cli_args)
484
+
485
+ for controller in controllers:
486
+ controller.identify(
487
+ skip_clear=cli_args.skip_clear,
488
+ duration=float(cli_args.runtime),
489
+ cycles=int(cli_args.cycle_count),
490
+ )
491
+
492
+
493
+ def main(cli_args=ARGUMENTS):
494
+ """
495
+ Parses and handles command-line arguments, registering specific subcommands
496
+ and executing associated functions.
497
+
498
+ This function sets up a command-line interface (CLI) parser, registers a
499
+ specific subcommand (`scroll-text`), and associates a function (`scroll_text_command`)
500
+ with this subcommand. After parsing the arguments, it executes the appropriate
501
+ function based on the parsed subcommand.
502
+
503
+ Parameters:
504
+ cli_args (Optional[Arguments]):
505
+ An object containing the command-line parser and associated
506
+ subcommand configurations. It must have attributes `scroll_parser`
507
+ and a callable `parse` function. (Defaults to `ARGUMENTS`)
508
+ """
509
+ parser_bindings = (
510
+ ('scroll_parser', 'Scroll text command parser was not initialized.', scroll_text_command),
511
+ ('identify_parser', 'Identify matrices command parser was not initialized.', identify_matrices_command),
512
+ ('display_parser', 'Display text command parser was not initialized.', display_text_command),
513
+ )
514
+
515
+ for attr_name, error_message, handler in parser_bindings:
516
+ parser = getattr(cli_args, attr_name)
517
+ if parser is None:
518
+ raise RuntimeError(error_message)
519
+ parser.set_defaults(func=handler)
520
+
521
+ # Parse command-line arguments;
522
+ parsed = cli_args.parse()
523
+
524
+ # Run the function associated with the parsed command;
525
+ parsed.func(parsed)
526
+
527
+
528
+ if __name__ == '__main__':
529
+ main()
530
+
531
+
@@ -34,4 +34,11 @@ def register_command(parser: ArgumentParser):
34
34
  help='Do not clear the matrix after the display stops.',
35
35
  )
36
36
 
37
+ display_parser.add_argument(
38
+ '--sequential',
39
+ action='store_true',
40
+ default=False,
41
+ help='When multiple matrices are active, present the text on one matrix at a time.',
42
+ )
43
+
37
44
  return display_parser
@@ -32,4 +32,18 @@ def register_command(parser: ArgumentParser):
32
32
  help='The direction to scroll the text in. Default is up.'
33
33
  )
34
34
 
35
+ scroll_parser.add_argument(
36
+ '--sequential',
37
+ action='store_true',
38
+ default=False,
39
+ help='Scroll across one matrix at a time when multiple are selected. Cannot be used with --span-matrices.',
40
+ )
41
+
42
+ scroll_parser.add_argument(
43
+ '--span-matrices',
44
+ action='store_true',
45
+ default=False,
46
+ help='Treat multiple matrices as a single wide canvas when scrolling horizontally. Cannot be used with --sequential.',
47
+ )
48
+
35
49
  return scroll_parser
@@ -0,0 +1,41 @@
1
+ """
2
+ LED Matrix package initialization.
3
+
4
+ Keep this module light: no hardware access, no file IO, no heavy imports.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from .utils import (
10
+ get_first_run,
11
+ set_first_run,
12
+ process_first_run,
13
+ initialize,
14
+ )
15
+
16
+ __all__ = [
17
+ 'get_first_run',
18
+ 'set_first_run',
19
+ 'process_first_run',
20
+ 'initialize',
21
+ 'LEDMatrixController',
22
+ 'get_controllers',
23
+ 'scroll_text_on_multiple_matrices',
24
+ ]
25
+
26
+
27
+ def __getattr__(name: str):
28
+ if name in ('LEDMatrixController', 'get_controllers'):
29
+ from .controller import LEDMatrixController, get_controllers
30
+ globals().update({
31
+ 'LEDMatrixController': LEDMatrixController,
32
+ 'get_controllers': get_controllers,
33
+ })
34
+ return globals()[name]
35
+
36
+ if name == 'scroll_text_on_multiple_matrices':
37
+ from .display.text.scroller import scroll_text_on_multiple_matrices
38
+ globals()['scroll_text_on_multiple_matrices'] = scroll_text_on_multiple_matrices
39
+ return scroll_text_on_multiple_matrices
40
+
41
+ raise AttributeError(f'module {__name__!r} has no attribute {name!r}')
@@ -22,6 +22,14 @@ class DeviceBase(Aliases):
22
22
  device (ListPortInfo): The serial device to control.
23
23
  thread_safe (bool): Enables use of an internal RLock for synchronized ops.
24
24
  cmd_lock (Optional[threading.RLock]): Lazily created lock when thread_safe.
25
+
26
+ Properties:
27
+ location (Dict[str, Any]): Physical location information for the device.
28
+ location_abbrev (str): Abbreviated location identifier (e.g., 'R1', 'L2').
29
+ name (str): Device name.
30
+ serial_number (str): Device serial number.
31
+ side_of_keyboard (str): Which side of the keyboard the device is on ('left' or 'right').
32
+ slot (int): The slot number of the device (1 or 2).
25
33
  """
26
34
  def __init__(self, *, device: ListPortInfo, thread_safe: bool = False, **kwargs: Any) -> None:
27
35
  # Initialize core device/threading state BEFORE forwarding to super(), so
@@ -73,6 +81,28 @@ class DeviceBase(Aliases):
73
81
  def serial_number(self) -> str:
74
82
  return self.device.serial_number
75
83
 
84
+ @property
85
+ def side_of_keyboard(self) -> str:
86
+ """
87
+ Get which side of the keyboard the device is on.
88
+
89
+ Returns:
90
+ str:
91
+ The side of the keyboard ('left' or 'right').
92
+ """
93
+ return self.location['side']
94
+
95
+ @property
96
+ def slot(self) -> int:
97
+ """
98
+ Get the slot number of the device.
99
+
100
+ Returns:
101
+ int:
102
+ The slot number (1 or 2).
103
+ """
104
+ return self.location['slot']
105
+
76
106
  def _ping(self) -> None:
77
107
  try:
78
108
  _ = self.animating
@@ -1,5 +1,5 @@
1
1
  from __future__ import annotations
2
- from typing import Optional
2
+ from typing import Optional, Literal
3
3
  from is_matrix_forge.led_matrix.controller.helpers.threading import synchronized
4
4
  from aliaser import alias, Aliases
5
5
 
@@ -0,0 +1 @@
1
+ from .manager import DisplayHistoryManager