tilemap-editor 3.2.8__tar.gz → 3.2.9__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 (199) hide show
  1. {tilemap_editor-3.2.8/src/tilemap_editor.egg-info → tilemap_editor-3.2.9}/PKG-INFO +1 -1
  2. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/pyproject.toml +1 -1
  3. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/tilemap.py +3 -0
  4. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9/src/tilemap_editor.egg-info}/PKG-INFO +1 -1
  5. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/ttypes/tilemap.py +1 -0
  6. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/widgets/tile_grid.py +9 -0
  7. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/widgets/tile_selector.py +21 -0
  8. tilemap_editor-3.2.9/src/widgets/ui/tileset_type_dialog.py +451 -0
  9. tilemap_editor-3.2.8/src/widgets/ui/tileset_type_dialog.py +0 -214
  10. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/LICENSE +0 -0
  11. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/MANIFEST.in +0 -0
  12. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/README.md +0 -0
  13. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/assets/fonts/jetbrain-fonts/JetBrainsMono-Bold.ttf +0 -0
  14. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/assets/fonts/jetbrain-fonts/JetBrainsMono-BoldItalic.ttf +0 -0
  15. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/assets/fonts/jetbrain-fonts/JetBrainsMono-Italic.ttf +0 -0
  16. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/assets/fonts/jetbrain-fonts/JetBrainsMono-Regular.ttf +0 -0
  17. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/assets/fonts/noto/NotoSans-Bold.ttf +0 -0
  18. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/assets/fonts/noto/NotoSans-BoldItalic.ttf +0 -0
  19. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/assets/fonts/noto/NotoSans-Italic.ttf +0 -0
  20. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/assets/fonts/noto/NotoSans-Regular.ttf +0 -0
  21. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/assets/icons/arrow-down.svg +0 -0
  22. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/assets/icons/check.svg +0 -0
  23. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/assets/icons/checked.svg +0 -0
  24. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/assets/icons/close.svg +0 -0
  25. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/assets/icons/duplicate.svg +0 -0
  26. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/assets/icons/error.svg +0 -0
  27. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/assets/icons/file.svg +0 -0
  28. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/assets/icons/filedead.svg +0 -0
  29. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/assets/icons/fit.svg +0 -0
  30. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/assets/icons/folder.svg +0 -0
  31. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/assets/icons/grid.svg +0 -0
  32. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/assets/icons/image.svg +0 -0
  33. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/assets/icons/info.svg +0 -0
  34. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/assets/icons/load.svg +0 -0
  35. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/assets/icons/loop.svg +0 -0
  36. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/assets/icons/pan.svg +0 -0
  37. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/assets/icons/pause.svg +0 -0
  38. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/assets/icons/pencil.svg +0 -0
  39. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/assets/icons/play.svg +0 -0
  40. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/assets/icons/plus.svg +0 -0
  41. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/assets/icons/radio.svg +0 -0
  42. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/assets/icons/reset.svg +0 -0
  43. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/assets/icons/save.svg +0 -0
  44. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/assets/icons/stop.svg +0 -0
  45. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/assets/icons/tilemap.svg +0 -0
  46. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/assets/icons/tileset.svg +0 -0
  47. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/assets/icons/unchecked.svg +0 -0
  48. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/assets/icons/warning.svg +0 -0
  49. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/assets/icons/zoomin.svg +0 -0
  50. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/assets/icons/zoomout.svg +0 -0
  51. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/setup.cfg +0 -0
  52. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/configs/themes.py +0 -0
  53. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/constants.py +0 -0
  54. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/editor.py +0 -0
  55. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/event_map.py +0 -0
  56. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/layers.py +0 -0
  57. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/main.py +0 -0
  58. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/node_manager.py +0 -0
  59. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/nodes.py +0 -0
  60. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/plugins/__init__.py +0 -0
  61. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/plugins/character_collision/__init__.py +0 -0
  62. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/plugins/character_collision/editor.py +0 -0
  63. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/plugins/character_collision/models.py +0 -0
  64. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/plugins/character_collision/protocols.py +0 -0
  65. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/plugins/character_collision/shape_editor.py +0 -0
  66. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/plugins/character_collision/standalone.py +0 -0
  67. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/plugins/object_tileset_collision/__init__.py +0 -0
  68. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/plugins/object_tileset_collision/editor.py +0 -0
  69. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/plugins/object_tileset_collision/models.py +0 -0
  70. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/plugins/object_tileset_collision/protocols.py +0 -0
  71. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/plugins/object_tileset_collision/standalone.py +0 -0
  72. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/plugins/sprite_animation/__init__.py +0 -0
  73. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/plugins/sprite_animation/__main__.py +0 -0
  74. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/plugins/sprite_animation/clipboard_util.py +0 -0
  75. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/plugins/sprite_animation/editor.py +0 -0
  76. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/plugins/sprite_animation/frame_picker.py +0 -0
  77. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/plugins/sprite_animation/models.py +0 -0
  78. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/plugins/sprite_animation/preview.py +0 -0
  79. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/plugins/sprite_animation/protocols.py +0 -0
  80. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/plugins/sprite_animation/runtime_load.py +0 -0
  81. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/plugins/sprite_animation/standalone.py +0 -0
  82. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/plugins/sprite_animation/timeline.py +0 -0
  83. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/plugins/sprite_animation/validation.py +0 -0
  84. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/plugins/sprite_editor/__init__.py +0 -0
  85. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/plugins/sprite_editor/editor.py +0 -0
  86. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/plugins/sprite_editor/standalone.py +0 -0
  87. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/plugins/tileset_collision/__init__.py +0 -0
  88. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/plugins/tileset_collision/collision_painter.py +0 -0
  89. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/plugins/tileset_collision/editor.py +0 -0
  90. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/plugins/tileset_collision/models.py +0 -0
  91. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/plugins/tileset_collision/protocols.py +0 -0
  92. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/plugins/tileset_collision/standalone.py +0 -0
  93. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/standalone_automap.py +0 -0
  94. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/standalone_error_console.py +0 -0
  95. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/standalone_filemanager.py +0 -0
  96. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/standalone_image_viewer.py +0 -0
  97. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/tilemap_editor/__init__.py +0 -0
  98. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/tilemap_editor/__main__.py +0 -0
  99. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/tilemap_editor/assets/__init__.py +0 -0
  100. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/tilemap_editor/assets/fonts/jetbrain-fonts/JetBrainsMono-Bold.ttf +0 -0
  101. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/tilemap_editor/assets/fonts/jetbrain-fonts/JetBrainsMono-BoldItalic.ttf +0 -0
  102. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/tilemap_editor/assets/fonts/jetbrain-fonts/JetBrainsMono-Italic.ttf +0 -0
  103. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/tilemap_editor/assets/fonts/jetbrain-fonts/JetBrainsMono-Regular.ttf +0 -0
  104. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/tilemap_editor/assets/fonts/noto/NotoSans-Bold.ttf +0 -0
  105. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/tilemap_editor/assets/fonts/noto/NotoSans-BoldItalic.ttf +0 -0
  106. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/tilemap_editor/assets/fonts/noto/NotoSans-Italic.ttf +0 -0
  107. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/tilemap_editor/assets/fonts/noto/NotoSans-Regular.ttf +0 -0
  108. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/tilemap_editor/assets/icons/ToolMove.svg +0 -0
  109. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/tilemap_editor/assets/icons/arrow-down.svg +0 -0
  110. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/tilemap_editor/assets/icons/check.svg +0 -0
  111. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/tilemap_editor/assets/icons/checked.svg +0 -0
  112. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/tilemap_editor/assets/icons/close.svg +0 -0
  113. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/tilemap_editor/assets/icons/duplicate.svg +0 -0
  114. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/tilemap_editor/assets/icons/error.svg +0 -0
  115. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/tilemap_editor/assets/icons/file.svg +0 -0
  116. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/tilemap_editor/assets/icons/filedead.svg +0 -0
  117. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/tilemap_editor/assets/icons/fit.svg +0 -0
  118. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/tilemap_editor/assets/icons/folder.svg +0 -0
  119. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/tilemap_editor/assets/icons/grid.svg +0 -0
  120. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/tilemap_editor/assets/icons/image.svg +0 -0
  121. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/tilemap_editor/assets/icons/info.svg +0 -0
  122. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/tilemap_editor/assets/icons/load.svg +0 -0
  123. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/tilemap_editor/assets/icons/loop.svg +0 -0
  124. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/tilemap_editor/assets/icons/pan.svg +0 -0
  125. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/tilemap_editor/assets/icons/pause.svg +0 -0
  126. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/tilemap_editor/assets/icons/pencil.svg +0 -0
  127. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/tilemap_editor/assets/icons/play.svg +0 -0
  128. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/tilemap_editor/assets/icons/plus.svg +0 -0
  129. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/tilemap_editor/assets/icons/radio.svg +0 -0
  130. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/tilemap_editor/assets/icons/reset.svg +0 -0
  131. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/tilemap_editor/assets/icons/save.svg +0 -0
  132. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/tilemap_editor/assets/icons/stop.svg +0 -0
  133. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/tilemap_editor/assets/icons/tilemap.svg +0 -0
  134. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/tilemap_editor/assets/icons/tileset.svg +0 -0
  135. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/tilemap_editor/assets/icons/unchecked.svg +0 -0
  136. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/tilemap_editor/assets/icons/warning.svg +0 -0
  137. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/tilemap_editor/assets/icons/zoomin.svg +0 -0
  138. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/tilemap_editor/assets/icons/zoomout.svg +0 -0
  139. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/tilemap_editor/cli.py +0 -0
  140. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/tilemap_editor/main.py +0 -0
  141. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/tilemap_editor/settings.py +0 -0
  142. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/tilemap_editor.egg-info/SOURCES.txt +0 -0
  143. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/tilemap_editor.egg-info/dependency_links.txt +0 -0
  144. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/tilemap_editor.egg-info/entry_points.txt +0 -0
  145. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/tilemap_editor.egg-info/requires.txt +0 -0
  146. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/tilemap_editor.egg-info/top_level.txt +0 -0
  147. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/ttypes/__init__.py +0 -0
  148. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/utils/__init__.py +0 -0
  149. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/utils/editor_preference.py +0 -0
  150. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/utils/error_handler.py +0 -0
  151. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/utils/font_manager.py +0 -0
  152. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/utils/history.py +0 -0
  153. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/utils/icon_manager.py +0 -0
  154. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/utils/icons_cache.py +0 -0
  155. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/utils/log_capture.py +0 -0
  156. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/utils/project_paths.py +0 -0
  157. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/utils/serialization.py +0 -0
  158. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/utils/standalone.py +0 -0
  159. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/utils/validation.py +0 -0
  160. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/widgets/__init__.py +0 -0
  161. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/widgets/automap_models.py +0 -0
  162. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/widgets/autotile_template.py +0 -0
  163. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/widgets/autotiler.py +0 -0
  164. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/widgets/filemanager.py +0 -0
  165. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/widgets/input.py +0 -0
  166. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/widgets/layer_selector.py +0 -0
  167. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/widgets/map_properties.py +0 -0
  168. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/widgets/mapsetup.py +0 -0
  169. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/widgets/regex_automap_designer.py +0 -0
  170. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/widgets/spritesheet_grid.py +0 -0
  171. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/widgets/ui/collision_layer_mask.py +0 -0
  172. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/widgets/ui/collision_layer_sidebar.py +0 -0
  173. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/widgets/ui/confirm_dialog.py +0 -0
  174. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/widgets/ui/draw_utils.py +0 -0
  175. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/widgets/ui/fileinput.py +0 -0
  176. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/widgets/ui/layer_type_dialog.py +0 -0
  177. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/widgets/ui/menubar.py +0 -0
  178. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/widgets/ui/mode_indicator.py +0 -0
  179. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/widgets/ui/node_editor.py +0 -0
  180. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/widgets/ui/node_selector.py +0 -0
  181. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/widgets/ui/notification.py +0 -0
  182. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/widgets/ui/property_editor.py +0 -0
  183. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/widgets/ui/region_selector.py +0 -0
  184. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/widgets/ui/status_bar.py +0 -0
  185. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/widgets/ui/theme.py +0 -0
  186. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/widgets/ui/toolbar.py +0 -0
  187. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/src/widgets/ui/tooltip.py +0 -0
  188. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/tests/test_animation_library_grid_offset.py +0 -0
  189. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/tests/test_collision_layer_mask.py +0 -0
  190. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/tests/test_collision_layer_sidebar.py +0 -0
  191. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/tests/test_editor_pan_mode.py +0 -0
  192. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/tests/test_project_paths.py +0 -0
  193. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/tests/test_render_scale.py +0 -0
  194. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/tests/test_sprite_animation_editor_grid.py +0 -0
  195. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/tests/test_tile_grid_selection.py +0 -0
  196. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/tests/test_tile_selector_pick.py +0 -0
  197. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/tests/test_tilemap_save.py +0 -0
  198. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/tests/test_timeline_invalidate_cache.py +0 -0
  199. {tilemap_editor-3.2.8 → tilemap_editor-3.2.9}/tests/test_toolbar_tools.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tilemap-editor
3
- Version: 3.2.8
3
+ Version: 3.2.9
4
4
  Summary: Pygame tilemap editor with SVG icons, professional UI, and sprite animation tools
5
5
  Author: tilemap editor contributors
6
6
  License: Proprietary
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "tilemap-editor"
7
- version = "3.2.8"
7
+ version = "3.2.9"
8
8
  description = "Pygame tilemap editor with SVG icons, professional UI, and sprite animation tools"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -298,6 +298,8 @@ class Tilemap:
298
298
  ts_data["tile_properties"] = {
299
299
  str(k): v for k, v in ts.tile_properties.items()
300
300
  }
301
+ if ts.animation:
302
+ ts_data["animation"] = ts.animation
301
303
 
302
304
  save_data["resources"]["tilesets"].append(ts_data)
303
305
 
@@ -547,6 +549,7 @@ class Tilemap:
547
549
  tileset_type,
548
550
  properties=ts_entry.get("properties", {}),
549
551
  tile_properties=ts_entry.get("tile_properties", {}),
552
+ animation=ts_entry.get("animation"),
550
553
  )
551
554
  except Exception as e:
552
555
  error_msg = f"Error loading tileset {path_str}: {e}"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tilemap-editor
3
- Version: 3.2.8
3
+ Version: 3.2.9
4
4
  Summary: Pygame tilemap editor with SVG icons, professional UI, and sprite animation tools
5
5
  Author: tilemap editor contributors
6
6
  License: Proprietary
@@ -64,6 +64,7 @@ class TypeTilesetSerialized(TypedDict, total=True):
64
64
  type: str
65
65
  properties: NotRequired[Dict[str, Any]]
66
66
  tile_properties: NotRequired[Dict[str, Dict[str, Any]]] # variant_id string -> properties
67
+ animation: NotRequired[dict]
67
68
 
68
69
 
69
70
  TTile = Dict[Tuple[int, int], TypeTile]
@@ -1290,6 +1290,15 @@ class TileGrid:
1290
1290
  base_surf = tileset_data.surface
1291
1291
 
1292
1292
  variant_id = tile["variant"]
1293
+ if tileset_data.animation:
1294
+ anim = tileset_data.animation
1295
+ frame_ms = pygame.time.get_ticks()
1296
+ frame_idx = int(frame_ms / anim["frame_duration_ms"]) % anim["frame_count"]
1297
+ if anim.get("animation_mode") == "random_start_times":
1298
+ phase = hash((x, y, ttype)) % anim["frame_count"]
1299
+ frame_idx = (frame_idx + phase) % anim["frame_count"]
1300
+ variant_id += frame_idx * anim["frame_stride"]
1301
+
1293
1302
  sheet_w = base_surf.get_width()
1294
1303
 
1295
1304
  # Source rect always uses logical tile size
@@ -28,6 +28,7 @@ class TilesetData:
28
28
  self.tile_properties: Dict[int, Dict[str, Any]] = {}
29
29
  self.object_collision_path: Optional[Path] = None
30
30
  self.object_collision_data: Optional[Dict[str, Any]] = None
31
+ self.animation: Optional[dict] = None
31
32
 
32
33
 
33
34
  class TileSelector:
@@ -335,6 +336,10 @@ class TileSelector:
335
336
  self._pending_tileset_queue.pop(0)
336
337
  )
337
338
  print(f"DEBUG: Processing tileset: {self._pending_tileset_path}")
339
+ tw, th = self.editor.tilemap.tile_size
340
+ sheet_cols = self._pending_tileset_surf.get_width() // tw
341
+ sheet_rows = self._pending_tileset_surf.get_height() // th
342
+ self.editor.tileset_type_dialog.set_sheet_dimensions(sheet_cols, sheet_rows)
338
343
  self.editor.tileset_type_dialog.show(
339
344
  on_confirm=self._on_tileset_type_selected,
340
345
  on_cancel=self._on_tileset_type_cancel,
@@ -346,6 +351,7 @@ class TileSelector:
346
351
  tileset_type: str,
347
352
  properties: dict = {},
348
353
  tile_properties: dict = {},
354
+ animation: Optional[dict] = None,
349
355
  ):
350
356
  """Load tileset from path without showing dialog (used when loading maps).
351
357
 
@@ -354,6 +360,7 @@ class TileSelector:
354
360
  tileset_type: Type of tileset ("tile" or "object") - already known from saved map
355
361
  properties: Custom properties for the tileset
356
362
  tile_properties: Custom properties for individual tiles in the tileset (variant_id str -> dict)
363
+ animation: Animation config dict (frame_count, frame_duration_ms, frame_stride, loop, animation_mode)
357
364
  """
358
365
  if path.exists():
359
366
  try:
@@ -365,6 +372,7 @@ class TileSelector:
365
372
  tileset_data.tile_properties = {
366
373
  int(k): v for k, v in tile_properties.items()
367
374
  }
375
+ tileset_data.animation = animation
368
376
  self._load_collision_data_for_tileset(tileset_data)
369
377
 
370
378
  self.tilesets.append(tileset_data)
@@ -403,6 +411,19 @@ class TileSelector:
403
411
  surf = self._pending_tileset_surf
404
412
 
405
413
  tileset_data = TilesetData(path.name, path, surf, tileset_type=tileset_type)
414
+ animation = self.editor.tileset_type_dialog.get_animation_config()
415
+ if animation:
416
+ tw, th = self.editor.tilemap.tile_size
417
+ sheet_cols = surf.get_width() // tw
418
+ sheet_rows = surf.get_height() // th
419
+ frame_count = animation["frame_count"]
420
+ if sheet_cols % frame_count == 0:
421
+ animation["frame_stride"] = sheet_cols // frame_count
422
+ elif sheet_rows % frame_count == 0:
423
+ animation["frame_stride"] = (sheet_rows // frame_count) * sheet_cols
424
+ else:
425
+ animation["frame_stride"] = (sheet_cols * sheet_rows) // frame_count
426
+ tileset_data.animation = animation
406
427
  self._load_collision_data_for_tileset(tileset_data)
407
428
  self.tilesets.append(tileset_data)
408
429
  self.active_idx = len(self.tilesets) - 1
@@ -0,0 +1,451 @@
1
+ """
2
+ Simple dialog for selecting tileset type (tile vs object) and configuring animation.
3
+ """
4
+
5
+ import pygame
6
+ from pygame import Rect, Surface
7
+ from typing import Callable, Optional
8
+
9
+
10
+ class TilesetTypeDialog:
11
+ """Dialog to select whether a tileset is tile-based or object-based."""
12
+
13
+ def __init__(self, editor_rect: Rect):
14
+ self.editor_rect = editor_rect
15
+ self.rect = Rect(0, 0, 400, 420)
16
+ self.rect.center = editor_rect.center
17
+
18
+ self.active = False
19
+ self.selected_type: Optional[str] = None
20
+ self.on_confirm: Optional[Callable[[str], None]] = None
21
+ self.on_cancel: Optional[Callable[[], None]] = None
22
+
23
+ self.bg_color = (40, 40, 40)
24
+ self.border_color = (100, 100, 100)
25
+ self.text_color = (255, 255, 255)
26
+ self.field_color = (55, 55, 55)
27
+ self.field_border = (80, 80, 80)
28
+ self.radio_color = (100, 150, 255)
29
+ self.button_color = (60, 100, 180)
30
+ self.button_hover_color = (80, 120, 200)
31
+
32
+ self.radio_tile_rect = Rect(0, 0, 20, 20)
33
+ self.radio_object_rect = Rect(0, 0, 20, 20)
34
+ self.radio_tile_label_rect = Rect(0, 0, 0, 0)
35
+ self.radio_object_label_rect = Rect(0, 0, 0, 0)
36
+ self.radio_tile_row_rect = Rect(0, 0, 0, 0)
37
+ self.radio_object_row_rect = Rect(0, 0, 0, 0)
38
+
39
+ self.btn_ok = Rect(0, 0, 80, 30)
40
+ self.btn_cancel = Rect(0, 0, 80, 30)
41
+
42
+ self.font_title = pygame.font.SysFont("Arial", 18, bold=True)
43
+ self.font_text = pygame.font.SysFont("Arial", 14)
44
+ self.font_field = pygame.font.SysFont("Arial", 13)
45
+
46
+ self.btn_ok_hover = False
47
+ self.btn_cancel_hover = False
48
+
49
+ # Animation state
50
+ self.animated = False
51
+ self.anim_frame_count = 4
52
+ self.anim_frame_duration_ms = 200
53
+ self.anim_loop = True
54
+ self.anim_mode_index = 0 # 0=default, 1=random_start_times
55
+ self.anim_modes = ["default", "random_start_times"]
56
+ self.anim_mode_labels = ["Sync", "Random"]
57
+
58
+ # Inline editing state
59
+ self._editing_field: Optional[str] = None
60
+ self._edit_buffer = ""
61
+
62
+ # Animation field rects (set by _layout)
63
+ self.anim_check_rect = Rect(0, 0, 16, 16)
64
+ self.anim_fields_rects: dict = {}
65
+ self.anim_loop_check_rect = Rect(0, 0, 16, 16)
66
+ self.anim_mode_rect = Rect(0, 0, 0, 0)
67
+
68
+ self._layout()
69
+
70
+ def _layout(self):
71
+ """Position child controls from the current dialog rect."""
72
+ self.rect.center = self.editor_rect.center
73
+
74
+ h = 220
75
+ if self.animated or self._editing_field is not None:
76
+ h = 420
77
+ self.rect.h = h
78
+
79
+ radio_x = self.rect.x + 42
80
+ row_w = self.rect.w - 84
81
+ row_h = 38
82
+ first_y = self.rect.y + 58
83
+ gap = 12
84
+
85
+ self.radio_tile_row_rect = Rect(radio_x - 10, first_y - 9, row_w, row_h)
86
+ self.radio_object_row_rect = Rect(
87
+ radio_x - 10, first_y + row_h + gap - 9, row_w, row_h
88
+ )
89
+
90
+ self.radio_tile_rect = Rect(radio_x, first_y, 20, 20)
91
+ self.radio_object_rect = Rect(radio_x, first_y + row_h + gap, 20, 20)
92
+
93
+ label_x = radio_x + 34
94
+ self.radio_tile_label_rect = Rect(label_x, first_y - 4, row_w - 44, 28)
95
+ self.radio_object_label_rect = Rect(
96
+ label_x, first_y + row_h + gap - 4, row_w - 44, 28
97
+ )
98
+
99
+ # Animated checkbox
100
+ anim_y = first_y + (row_h + gap) * 2 + 6
101
+ self.anim_check_rect = Rect(radio_x, anim_y, 16, 16)
102
+
103
+ # Animation fields (collapsible)
104
+ field_y = anim_y + 30
105
+ field_h = 22
106
+ field_gap = 4
107
+ field_label_w = 100
108
+ field_value_w = 80
109
+ field_x = self.rect.x + 42
110
+
111
+ self.anim_fields_rects = {}
112
+ anim_fields = [
113
+ ("frame_count", "Frame Count", str(self.anim_frame_count)),
114
+ ("frame_duration_ms", "Duration (ms)", str(self.anim_frame_duration_ms)),
115
+ ]
116
+ for key, label, _ in anim_fields:
117
+ label_rect = Rect(field_x, field_y, field_label_w, field_h)
118
+ value_rect = Rect(field_x + field_label_w + 8, field_y, field_value_w, field_h)
119
+ self.anim_fields_rects[key] = {
120
+ "label": label_rect,
121
+ "value": value_rect,
122
+ "label_text": label,
123
+ }
124
+ field_y += field_h + field_gap
125
+
126
+ # Loop checkbox
127
+ self.anim_loop_check_rect = Rect(field_x, field_y, 16, 16)
128
+ field_y += 26
129
+
130
+ # Animation mode
131
+ mode_label_rect = Rect(field_x, field_y, field_label_w, field_h)
132
+ mode_value_rect = Rect(field_x + field_label_w + 8, field_y, field_value_w, field_h)
133
+ self.anim_fields_rects["mode"] = {
134
+ "label": mode_label_rect,
135
+ "value": mode_value_rect,
136
+ "label_text": "Animation Mode",
137
+ }
138
+
139
+ # Stride hint (read-only, auto-computed)
140
+ self._stride_hint_rect = Rect(field_x, field_y + 36, field_label_w + field_value_w + 8, field_h)
141
+
142
+ btn_y = self.rect.y + h - 44
143
+ self.btn_ok = Rect(self.rect.centerx - 94, btn_y, 80, 30)
144
+ self.btn_cancel = Rect(self.rect.centerx + 14, btn_y, 80, 30)
145
+
146
+ def get_animation_config(self) -> Optional[dict]:
147
+ if not self.animated:
148
+ return None
149
+ return {
150
+ "frame_count": self.anim_frame_count,
151
+ "frame_duration_ms": self.anim_frame_duration_ms,
152
+ "loop": self.anim_loop,
153
+ "animation_mode": self.anim_modes[self.anim_mode_index],
154
+ }
155
+
156
+ def _get_display_value(self, key: str) -> str:
157
+ if self._editing_field == key:
158
+ return self._edit_buffer
159
+ map = {
160
+ "frame_count": str(self.anim_frame_count),
161
+ "frame_duration_ms": str(self.anim_frame_duration_ms),
162
+ "mode": self.anim_mode_labels[self.anim_mode_index],
163
+ }
164
+ return map.get(key, "")
165
+
166
+ def _commit_edit(self):
167
+ if self._editing_field is None:
168
+ return
169
+ try:
170
+ if self._editing_field == "frame_count":
171
+ self.anim_frame_count = max(1, int(self._edit_buffer))
172
+ self._update_computed_stride()
173
+ elif self._editing_field == "frame_duration_ms":
174
+ self.anim_frame_duration_ms = max(1, int(self._edit_buffer))
175
+ except ValueError:
176
+ pass
177
+ self._editing_field = None
178
+ self._edit_buffer = ""
179
+
180
+ def set_sheet_dimensions(self, sheet_cols: int, sheet_rows: int):
181
+ self._sheet_cols = sheet_cols
182
+ self._sheet_rows = sheet_rows
183
+ if self.anim_frame_count > 0:
184
+ self._update_computed_stride()
185
+
186
+ def _update_computed_stride(self):
187
+ fc = self.anim_frame_count
188
+ if fc < 1:
189
+ return
190
+ if hasattr(self, '_sheet_cols') and self._sheet_cols % fc == 0:
191
+ self._computed_stride = self._sheet_cols // fc
192
+ elif hasattr(self, '_sheet_rows') and self._sheet_rows % fc == 0:
193
+ self._computed_stride = (self._sheet_rows // fc) * getattr(self, '_sheet_cols', 1)
194
+ else:
195
+ self._computed_stride = 1
196
+
197
+ def show(self, on_confirm: Callable[[str], None], on_cancel: Callable[[], None]):
198
+ """Show the dialog."""
199
+ if self.active:
200
+ self.hide()
201
+ self.active = True
202
+ self.selected_type = "tile"
203
+ self.on_confirm = on_confirm
204
+ self.on_cancel = on_cancel
205
+ self.btn_ok_hover = False
206
+ self.btn_cancel_hover = False
207
+ self.animated = False
208
+ self.anim_frame_count = 4
209
+ self.anim_frame_duration_ms = 200
210
+ self.anim_loop = True
211
+ self.anim_mode_index = 0
212
+ self._computed_stride = 1
213
+ self._editing_field = None
214
+ self._edit_buffer = ""
215
+
216
+ def hide(self):
217
+ """Hide the dialog."""
218
+ self.active = False
219
+ self._editing_field = None
220
+ self._edit_buffer = ""
221
+
222
+ def handle_event(self, event: pygame.event.Event) -> bool:
223
+ """Handle events. Returns True if event was consumed."""
224
+ if not self.active:
225
+ return False
226
+
227
+ self._layout()
228
+ mouse_pos = pygame.mouse.get_pos()
229
+
230
+ if event.type == pygame.KEYDOWN:
231
+ if self._editing_field is not None:
232
+ if event.key == pygame.K_RETURN:
233
+ self._commit_edit()
234
+ return True
235
+ elif event.key == pygame.K_ESCAPE:
236
+ self._editing_field = None
237
+ self._edit_buffer = ""
238
+ return True
239
+ elif event.key == pygame.K_BACKSPACE:
240
+ self._edit_buffer = self._edit_buffer[:-1]
241
+ return True
242
+ else:
243
+ ch = event.unicode
244
+ if ch.isdigit() or ch == "-":
245
+ self._edit_buffer += ch
246
+ return True
247
+ else:
248
+ if event.key == pygame.K_ESCAPE:
249
+ if self.on_cancel:
250
+ self.on_cancel()
251
+ self.hide()
252
+ return True
253
+ elif event.key == pygame.K_RETURN:
254
+ if self.on_confirm and self.selected_type:
255
+ self.on_confirm(self.selected_type)
256
+ self.hide()
257
+ return True
258
+
259
+ elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
260
+ if self.radio_tile_row_rect.collidepoint(mouse_pos):
261
+ self.selected_type = "tile"
262
+ return True
263
+ if self.radio_object_row_rect.collidepoint(mouse_pos):
264
+ self.selected_type = "object"
265
+ return True
266
+
267
+ # Animated checkbox toggle
268
+ if self.anim_check_rect.collidepoint(mouse_pos):
269
+ self.animated = not self.animated
270
+ self._editing_field = None
271
+ self._edit_buffer = ""
272
+ return True
273
+
274
+ if self.animated:
275
+ # Animation field clicks
276
+ if self.anim_loop_check_rect.collidepoint(mouse_pos):
277
+ self.anim_loop = not self.anim_loop
278
+ return True
279
+
280
+ for key, rects in self.anim_fields_rects.items():
281
+ if key == "mode":
282
+ if rects["value"].collidepoint(mouse_pos):
283
+ self.anim_mode_index = (self.anim_mode_index + 1) % len(self.anim_modes)
284
+ return True
285
+ else:
286
+ if rects["value"].collidepoint(mouse_pos):
287
+ self._commit_edit()
288
+ self._editing_field = key
289
+ self._edit_buffer = self._get_display_value(key)
290
+ return True
291
+
292
+ if self.btn_ok.collidepoint(mouse_pos):
293
+ self._commit_edit()
294
+ if self.on_confirm and self.selected_type:
295
+ self.on_confirm(self.selected_type)
296
+ self.hide()
297
+ return True
298
+ if self.btn_cancel.collidepoint(mouse_pos):
299
+ self._editing_field = None
300
+ self._edit_buffer = ""
301
+ if self.on_cancel:
302
+ self.on_cancel()
303
+ self.hide()
304
+ return True
305
+
306
+ elif event.type == pygame.MOUSEMOTION:
307
+ self.btn_ok_hover = self.btn_ok.collidepoint(mouse_pos)
308
+ self.btn_cancel_hover = self.btn_cancel.collidepoint(mouse_pos)
309
+
310
+ return False
311
+
312
+ def draw(self, surface: Surface):
313
+ """Draw the dialog on the given surface."""
314
+ if not self.active:
315
+ return
316
+
317
+ self._layout()
318
+ pygame.draw.rect(surface, self.bg_color, self.rect)
319
+ pygame.draw.rect(surface, self.border_color, self.rect, 2)
320
+
321
+ title = self.font_title.render("Tileset Type", True, self.text_color)
322
+ title_rect = title.get_rect(topleft=(self.rect.x + 20, self.rect.y + 15))
323
+ surface.blit(title, title_rect)
324
+
325
+ self._draw_radio(
326
+ surface,
327
+ self.radio_tile_rect,
328
+ self.radio_tile_row_rect,
329
+ self.selected_type == "tile",
330
+ "Tile Tileset (grid-based)",
331
+ self.radio_tile_label_rect,
332
+ )
333
+
334
+ self._draw_radio(
335
+ surface,
336
+ self.radio_object_rect,
337
+ self.radio_object_row_rect,
338
+ self.selected_type == "object",
339
+ "Object Tileset (free-positioned)",
340
+ self.radio_object_label_rect,
341
+ )
342
+
343
+ self._draw_checkbox(
344
+ surface,
345
+ self.anim_check_rect,
346
+ self.animated,
347
+ "Animated Tileset",
348
+ self.rect.x + 62,
349
+ )
350
+
351
+ if self.animated:
352
+ self._draw_animation_fields(surface)
353
+
354
+ ok_color = self.button_hover_color if self.btn_ok_hover else self.button_color
355
+ cancel_color = (
356
+ self.button_hover_color if self.btn_cancel_hover else self.button_color
357
+ )
358
+
359
+ pygame.draw.rect(surface, ok_color, self.btn_ok)
360
+ pygame.draw.rect(surface, cancel_color, self.btn_cancel)
361
+
362
+ ok_text = self.font_text.render("OK", True, self.text_color)
363
+ cancel_text = self.font_text.render("Cancel", True, self.text_color)
364
+
365
+ ok_text_rect = ok_text.get_rect(center=self.btn_ok.center)
366
+ cancel_text_rect = cancel_text.get_rect(center=self.btn_cancel.center)
367
+
368
+ surface.blit(ok_text, ok_text_rect)
369
+ surface.blit(cancel_text, cancel_text_rect)
370
+
371
+ def _draw_animation_fields(self, surface: Surface):
372
+ """Draw the animation configuration fields."""
373
+ numeric_keys = ["frame_count", "frame_duration_ms"]
374
+ for key in numeric_keys:
375
+ rects = self.anim_fields_rects.get(key)
376
+ if rects is None:
377
+ continue
378
+ label_surf = self.font_text.render(rects["label_text"], True, self.text_color)
379
+ surface.blit(label_surf, rects["label"])
380
+
381
+ value_str = self._get_display_value(key)
382
+ value_color = self.radio_color if self._editing_field == key else self.text_color
383
+ border = self.field_border if self._editing_field != key else self.radio_color
384
+ pygame.draw.rect(surface, self.field_color, rects["value"])
385
+ pygame.draw.rect(surface, border, rects["value"], 1)
386
+ val_surf = self.font_field.render(value_str, True, value_color)
387
+ val_rect = val_surf.get_rect(midleft=(rects["value"].x + 4, rects["value"].centery))
388
+ surface.blit(val_surf, val_rect)
389
+
390
+ # Loop checkbox — label to the right of the box, not at field_x
391
+ loop_label_x = self.anim_loop_check_rect.right + 6
392
+ self._draw_checkbox(
393
+ surface,
394
+ self.anim_loop_check_rect,
395
+ self.anim_loop,
396
+ "Loop",
397
+ loop_label_x,
398
+ )
399
+
400
+ # Animation mode (below loop)
401
+ mode_rects = self.anim_fields_rects.get("mode")
402
+ if mode_rects:
403
+ label_surf = self.font_text.render(mode_rects["label_text"], True, self.text_color)
404
+ surface.blit(label_surf, mode_rects["label"])
405
+ mode_border = self.field_border
406
+ pygame.draw.rect(surface, self.field_color, mode_rects["value"])
407
+ pygame.draw.rect(surface, mode_border, mode_rects["value"], 1)
408
+ mode_text = self.anim_mode_labels[self.anim_mode_index]
409
+ val_surf = self.font_field.render(mode_text, True, self.text_color)
410
+ val_rect = val_surf.get_rect(midleft=(mode_rects["value"].x + 4, mode_rects["value"].centery))
411
+ surface.blit(val_surf, val_rect)
412
+
413
+ # Stride hint (auto-computed, below mode)
414
+ hint_color = (160, 160, 160)
415
+ hint_surf = self.font_field.render(f"Stride: {self._computed_stride} (auto)", True, hint_color)
416
+ surface.blit(hint_surf, self._stride_hint_rect)
417
+
418
+ def _draw_checkbox(self, surface: Surface, rect: Rect, checked: bool, label: str, label_x: int):
419
+ pygame.draw.rect(surface, self.field_color, rect)
420
+ pygame.draw.rect(surface, self.field_border, rect, 1)
421
+ if checked:
422
+ pygame.draw.line(surface, self.radio_color, (rect.x + 3, rect.centery), (rect.centerx - 2, rect.bottom - 3), 2)
423
+ pygame.draw.line(surface, self.radio_color, (rect.centerx - 2, rect.bottom - 3), (rect.right - 3, rect.y + 3), 2)
424
+ label_surf = self.font_text.render(label, True, self.text_color)
425
+ label_pos = label_surf.get_rect(midleft=(label_x, rect.centery))
426
+ surface.blit(label_surf, label_pos)
427
+
428
+ def _draw_radio(
429
+ self,
430
+ surface: Surface,
431
+ radio_rect: Rect,
432
+ row_rect: Rect,
433
+ is_selected: bool,
434
+ label: str,
435
+ label_rect: Rect,
436
+ ):
437
+ """Draw a radio button with label."""
438
+ pygame.draw.rect(surface, (48, 48, 48), row_rect, border_radius=6)
439
+ pygame.draw.rect(surface, self.border_color, row_rect, 1, border_radius=6)
440
+
441
+ center = (radio_rect.centerx, radio_rect.centery)
442
+ radius = radio_rect.width // 2
443
+
444
+ pygame.draw.circle(surface, self.border_color, center, radius, 2)
445
+
446
+ if is_selected:
447
+ pygame.draw.circle(surface, self.radio_color, center, radius - 4)
448
+
449
+ label_surf = self.font_text.render(label, True, self.text_color)
450
+ label_pos = label_surf.get_rect(midleft=(label_rect.x, radio_rect.centery))
451
+ surface.blit(label_surf, label_pos)