ttkbootstrap 2.1.1__tar.gz → 2.2.0__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 (163) hide show
  1. {ttkbootstrap-2.1.1/src/ttkbootstrap.egg-info → ttkbootstrap-2.2.0}/PKG-INFO +36 -3
  2. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/README.md +35 -2
  3. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/pyproject.toml +8 -1
  4. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/__init__.py +19 -0
  5. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/__init__.pyi +4 -0
  6. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/__main__.py +7 -1
  7. ttkbootstrap-2.2.0/src/ttkbootstrap/cli.py +124 -0
  8. ttkbootstrap-2.2.0/src/ttkbootstrap/convert_theme.py +323 -0
  9. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/engine.py +13 -1
  10. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/theme.py +38 -12
  11. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/themes/legacy.py +22 -10
  12. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0/src/ttkbootstrap.egg-info}/PKG-INFO +36 -3
  13. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap.egg-info/SOURCES.txt +5 -0
  14. ttkbootstrap-2.2.0/src/ttkbootstrap.egg-info/entry_points.txt +3 -0
  15. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkcreator/__main__.py +7 -2
  16. ttkbootstrap-2.2.0/tests/test_cli_api.py +182 -0
  17. ttkbootstrap-2.2.0/tests/test_convert_theme.py +317 -0
  18. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_scaling.py +61 -0
  19. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/LICENSE +0 -0
  20. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/MANIFEST.in +0 -0
  21. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/setup.cfg +0 -0
  22. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/assets/__init__.py +0 -0
  23. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/assets/app_icons/ttkbootstrap.ico +0 -0
  24. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/assets/app_icons/ttkbootstrap.png +0 -0
  25. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/assets/elements/README.md +0 -0
  26. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/assets/elements/__init__.py +0 -0
  27. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/assets/elements/checkbox-checked.png +0 -0
  28. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/assets/elements/checkbox-indeterminate.png +0 -0
  29. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/assets/elements/checkbox-unchecked.png +0 -0
  30. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/assets/elements/manifest.json +0 -0
  31. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/assets/elements/progressbar-default.png +0 -0
  32. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/assets/elements/progressbar-thin.png +0 -0
  33. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/assets/elements/radio.png +0 -0
  34. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/assets/elements/sizegrip.png +0 -0
  35. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/assets/elements/slider-handle.png +0 -0
  36. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/assets/elements/slider-track.png +0 -0
  37. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/assets/elements/switch-round.png +0 -0
  38. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/assets/elements/switch-square.png +0 -0
  39. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/assets/icons/LICENSE +0 -0
  40. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/assets/icons/README.md +0 -0
  41. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/assets/icons/bootstrap.ttf +0 -0
  42. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/assets/icons/glyphmap.json +0 -0
  43. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/assets/icons/icon_metrics.json +0 -0
  44. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/colorutils.py +0 -0
  45. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/constants.py +0 -0
  46. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/dialogs/__init__.py +0 -0
  47. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/dialogs/base.py +0 -0
  48. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/dialogs/colorchooser.py +0 -0
  49. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/dialogs/colordropper.py +0 -0
  50. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/dialogs/datepicker.py +0 -0
  51. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/dialogs/filedialog.py +0 -0
  52. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/dialogs/fontdialog.py +0 -0
  53. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/dialogs/message.py +0 -0
  54. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/dialogs/query.py +0 -0
  55. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/internal/__init__.py +0 -0
  56. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/internal/busy.py +0 -0
  57. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/internal/configure_delegation.py +0 -0
  58. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/internal/positioning.py +0 -0
  59. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/internal/publisher.py +0 -0
  60. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/internal/utility.py +0 -0
  61. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/internal/wheel.py +0 -0
  62. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/localization/README.md +0 -0
  63. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/localization/__init__.py +0 -0
  64. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/localization/api.py +0 -0
  65. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/localization/msgcat.py +0 -0
  66. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/localization/msgs.py +0 -0
  67. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/menu.py +0 -0
  68. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/publisher.py +0 -0
  69. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/py.typed +0 -0
  70. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/__init__.py +0 -0
  71. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/_compat.py +0 -0
  72. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/assets.py +0 -0
  73. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/bootstyle.py +0 -0
  74. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/builders/__init__.py +0 -0
  75. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/builders/button.py +0 -0
  76. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/builders/calendar.py +0 -0
  77. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/builders/checkbutton.py +0 -0
  78. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/builders/combobox.py +0 -0
  79. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/builders/entry.py +0 -0
  80. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/builders/floodgauge.py +0 -0
  81. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/builders/frame.py +0 -0
  82. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/builders/label.py +0 -0
  83. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/builders/labelframe.py +0 -0
  84. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/builders/menubutton.py +0 -0
  85. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/builders/notebook.py +0 -0
  86. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/builders/panedwindow.py +0 -0
  87. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/builders/progressbar.py +0 -0
  88. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/builders/radiobutton.py +0 -0
  89. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/builders/registry.py +0 -0
  90. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/builders/scale.py +0 -0
  91. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/builders/scrollbar.py +0 -0
  92. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/builders/separator.py +0 -0
  93. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/builders/sizegrip.py +0 -0
  94. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/builders/spinbox.py +0 -0
  95. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/builders/toggle.py +0 -0
  96. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/builders/toolbutton.py +0 -0
  97. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/builders/treeview.py +0 -0
  98. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/builders/utils.py +0 -0
  99. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/builders_tk.py +0 -0
  100. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/builders_ttk.py +0 -0
  101. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/elements.py +0 -0
  102. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/icons.py +0 -0
  103. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/layout.py +0 -0
  104. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/scaling.py +0 -0
  105. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/themes/__init__.py +0 -0
  106. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/themes/builtin.py +0 -0
  107. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/themes/standard.py +0 -0
  108. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/utility.py +0 -0
  109. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/utils/__init__.py +0 -0
  110. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/utils/color.py +0 -0
  111. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/utils/config.py +0 -0
  112. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/utils/fonts.py +0 -0
  113. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/utils/platform.py +0 -0
  114. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/utils/scaling.py +0 -0
  115. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/validation.py +0 -0
  116. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/widgets/__init__.py +0 -0
  117. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/widgets/dateentry.py +0 -0
  118. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/widgets/floodgauge.py +0 -0
  119. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/widgets/labeledscale.py +0 -0
  120. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/widgets/meter.py +0 -0
  121. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/widgets/scrolled.py +0 -0
  122. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/widgets/tableview.py +0 -0
  123. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/widgets/toast.py +0 -0
  124. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/widgets/tooltip.py +0 -0
  125. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/window.py +0 -0
  126. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap.egg-info/dependency_links.txt +0 -0
  127. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap.egg-info/requires.txt +0 -0
  128. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap.egg-info/top_level.txt +0 -0
  129. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkcreator/__init__.py +0 -0
  130. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_bootstyle_grammar.py +0 -0
  131. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_bootstyle_value_tokens.py +0 -0
  132. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_busy_api.py +0 -0
  133. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_config_api.py +0 -0
  134. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_configure_delegation.py +0 -0
  135. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_dateentry_api.py +0 -0
  136. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_dialogs_api.py +0 -0
  137. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_durable_style_options.py +0 -0
  138. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_filedialog_api.py +0 -0
  139. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_floodgauge_api.py +0 -0
  140. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_fonts_api.py +0 -0
  141. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_icon_drop.py +0 -0
  142. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_icon_theme.py +0 -0
  143. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_labeledscale_tooltip_api.py +0 -0
  144. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_menu_api.py +0 -0
  145. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_menu_border.py +0 -0
  146. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_meter_api.py +0 -0
  147. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_mixin_api.py +0 -0
  148. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_scroll_events.py +0 -0
  149. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_scrolled_api.py +0 -0
  150. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_style_package.py +0 -0
  151. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_style_reference.py +0 -0
  152. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_surface_color.py +0 -0
  153. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_surface_families.py +0 -0
  154. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_surface_grammar.py +0 -0
  155. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_tableview_api.py +0 -0
  156. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_toast_api.py +0 -0
  157. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_toolkit.py +0 -0
  158. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_treeview_rowheight.py +0 -0
  159. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_utility_api.py +0 -0
  160. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_widget_catalog_coverage.py +0 -0
  161. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_widget_reexports.py +0 -0
  162. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_widget_stubs.py +0 -0
  163. {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_window_api.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ttkbootstrap
3
- Version: 2.1.1
3
+ Version: 2.2.0
4
4
  Summary: A supercharged theme extension for tkinter that enables on-demand modern flat style themes inspired by Bootstrap.
5
5
  Author-email: Israel Dryer <israel.dryer@gmail.com>
6
6
  License-Expression: MIT AND (Apache-2.0 OR BSD-2-Clause)
@@ -66,6 +66,12 @@ tkinter's, and everything you already know about tkinter still applies.
66
66
  python -m pip install ttkbootstrap
67
67
  ```
68
68
 
69
+ Then see it running — this opens a window with every widget and a theme picker:
70
+
71
+ ```bash
72
+ ttkb demo
73
+ ```
74
+
69
75
  ## Quickstart
70
76
 
71
77
  ```python
@@ -115,6 +121,26 @@ app.toggle_theme() # flip light <-> dark
115
121
  Browse them in the
116
122
  [themes gallery](https://ttkbootstrap.readthedocs.io/en/latest/themes.html).
117
123
 
124
+ Design your own with the bundled editor — pick colors, preview them on live
125
+ widgets, and export a `Theme(...)` snippet for your app:
126
+
127
+ ```bash
128
+ ttkb creator
129
+ ```
130
+
131
+ ## Command line
132
+
133
+ Installing ttkbootstrap installs `ttkb`:
134
+
135
+ | Command | What it does |
136
+ | --- | --- |
137
+ | `ttkb version` | Print the installed version |
138
+ | `ttkb demo` | Open the widget demo |
139
+ | `ttkb convert-theme <file>` | Convert a 1.x theme file to the 2.x `Theme(...)` form |
140
+ | `ttkb creator` | Open ttkcreator, the theme designer |
141
+
142
+ See the [command-line reference](https://ttkbootstrap.readthedocs.io/en/latest/reference/cli.html).
143
+
118
144
  ## Documentation
119
145
 
120
146
  - **[Documentation](https://ttkbootstrap.readthedocs.io/en/latest/)** — guides, the widget catalog, and the API reference.
@@ -131,6 +157,13 @@ deprecation warning. The
131
157
  [Migrating to 2.0](https://ttkbootstrap.readthedocs.io/en/latest/user-guide/getting-started/migrating.html)
132
158
  guide sorts every change into breaking / deprecated / notable / new.
133
159
 
160
+ Custom themes moved out of the package and into your own code. To bring one
161
+ forward, convert the file 1.x saved:
162
+
163
+ ```bash
164
+ ttkb convert-theme user.py -o brand.py
165
+ ```
166
+
134
167
  ## Icons
135
168
 
136
169
  ttkbootstrap ships with the full [Bootstrap Icons](https://icons.getbootstrap.com/)
@@ -142,8 +175,8 @@ ttk.Button(app, text="Settings", icon="gear-fill", bootstyle="primary")
142
175
  ```
143
176
 
144
177
  Need other icon families — Font Awesome, Material, Lucide, and more? The
145
- [ttkbootstrap-icons](https://github.com/israel-dryer/ttkbootstrap-icons) extension
146
- is still available and adds installable icon providers alongside the built-in set.
178
+ [tkinter-icons](https://github.com/israel-dryer/tkinter-icons) extension adds
179
+ sixteen icon sets (61,000+ icons) alongside the built-in set.
147
180
 
148
181
  ## Contributing
149
182
 
@@ -39,6 +39,12 @@ tkinter's, and everything you already know about tkinter still applies.
39
39
  python -m pip install ttkbootstrap
40
40
  ```
41
41
 
42
+ Then see it running — this opens a window with every widget and a theme picker:
43
+
44
+ ```bash
45
+ ttkb demo
46
+ ```
47
+
42
48
  ## Quickstart
43
49
 
44
50
  ```python
@@ -88,6 +94,26 @@ app.toggle_theme() # flip light <-> dark
88
94
  Browse them in the
89
95
  [themes gallery](https://ttkbootstrap.readthedocs.io/en/latest/themes.html).
90
96
 
97
+ Design your own with the bundled editor — pick colors, preview them on live
98
+ widgets, and export a `Theme(...)` snippet for your app:
99
+
100
+ ```bash
101
+ ttkb creator
102
+ ```
103
+
104
+ ## Command line
105
+
106
+ Installing ttkbootstrap installs `ttkb`:
107
+
108
+ | Command | What it does |
109
+ | --- | --- |
110
+ | `ttkb version` | Print the installed version |
111
+ | `ttkb demo` | Open the widget demo |
112
+ | `ttkb convert-theme <file>` | Convert a 1.x theme file to the 2.x `Theme(...)` form |
113
+ | `ttkb creator` | Open ttkcreator, the theme designer |
114
+
115
+ See the [command-line reference](https://ttkbootstrap.readthedocs.io/en/latest/reference/cli.html).
116
+
91
117
  ## Documentation
92
118
 
93
119
  - **[Documentation](https://ttkbootstrap.readthedocs.io/en/latest/)** — guides, the widget catalog, and the API reference.
@@ -104,6 +130,13 @@ deprecation warning. The
104
130
  [Migrating to 2.0](https://ttkbootstrap.readthedocs.io/en/latest/user-guide/getting-started/migrating.html)
105
131
  guide sorts every change into breaking / deprecated / notable / new.
106
132
 
133
+ Custom themes moved out of the package and into your own code. To bring one
134
+ forward, convert the file 1.x saved:
135
+
136
+ ```bash
137
+ ttkb convert-theme user.py -o brand.py
138
+ ```
139
+
107
140
  ## Icons
108
141
 
109
142
  ttkbootstrap ships with the full [Bootstrap Icons](https://icons.getbootstrap.com/)
@@ -115,8 +148,8 @@ ttk.Button(app, text="Settings", icon="gear-fill", bootstyle="primary")
115
148
  ```
116
149
 
117
150
  Need other icon families — Font Awesome, Material, Lucide, and more? The
118
- [ttkbootstrap-icons](https://github.com/israel-dryer/ttkbootstrap-icons) extension
119
- is still available and adds installable icon providers alongside the built-in set.
151
+ [tkinter-icons](https://github.com/israel-dryer/tkinter-icons) extension adds
152
+ sixteen icon sets (61,000+ icons) alongside the built-in set.
120
153
 
121
154
  ## Contributing
122
155
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ttkbootstrap"
7
- version = "2.1.1"
7
+ version = "2.2.0"
8
8
  description = "A supercharged theme extension for tkinter that enables on-demand modern flat style themes inspired by Bootstrap."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -49,6 +49,13 @@ dependencies = ["pillow>=10,<13"]
49
49
  Homepage = "https://github.com/israel-dryer/ttkbootstrap"
50
50
  Documentation = "https://ttkbootstrap.readthedocs.io/en/latest/"
51
51
 
52
+ [project.scripts]
53
+ # Two names for one command: `ttkbootstrap` is guessable from the package name,
54
+ # `ttkb` is what anyone types twice. When the scripts directory is not on PATH,
55
+ # `python -m ttkbootstrap.cli <command>` runs the same thing.
56
+ ttkbootstrap = "ttkbootstrap.cli:main"
57
+ ttkb = "ttkbootstrap.cli:main"
58
+
52
59
  [tool.setuptools.packages.find]
53
60
  where = ["src"]
54
61
 
@@ -37,6 +37,11 @@ bootstyle)`.
37
37
 
38
38
  For more information, see: https://ttkbootstrap.readthedocs.io/
39
39
  """
40
+ from importlib.metadata import (
41
+ PackageNotFoundError as _PackageNotFoundError,
42
+ version as _distribution_version,
43
+ )
44
+
40
45
  from tkinter import (
41
46
  Menu as _tkMenu, Text as _tkText, Canvas as _tkCanvas, Tk as _tkTk,
42
47
  Frame as _tkFrame, Label as _tkLabel,
@@ -100,6 +105,20 @@ from ttkbootstrap.utils import (
100
105
  set_global_family,
101
106
  )
102
107
 
108
+ try:
109
+ #: The installed distribution's version, e.g. `"2.2.0"`.
110
+ #
111
+ # Read from the installed metadata rather than written here, so
112
+ # `pyproject.toml` stays the one place the version literal lives. The
113
+ # consequence is that this reports what was *installed*: an editable install
114
+ # keeps whatever metadata it was built with until it is reinstalled.
115
+ __version__ = _distribution_version("ttkbootstrap")
116
+ except _PackageNotFoundError:
117
+ # Running from a source tree that was never installed (e.g. PYTHONPATH=src)
118
+ # -- there is no metadata to read, and guessing one would be worse than
119
+ # saying so.
120
+ __version__ = "unknown"
121
+
103
122
 
104
123
  # --------------------------------------------------------------------------- #
105
124
  # Concrete ttk widget classes — the blessed `bootstyle` set.
@@ -21,6 +21,10 @@ from typing import Any, Callable
21
21
 
22
22
  from ttkbootstrap.style import AutoStyleMixin as AutoStyleMixin, BootMixin as BootMixin
23
23
 
24
+ # Assigned in `__init__.py` rather than imported, so `_reexports` -- which
25
+ # sources every name from an import statement -- cannot reach it.
26
+ __version__: str
27
+
24
28
  from tkinter import Canvas as _tkinterCanvas
25
29
  from tkinter import Frame as _tkinterFrame
26
30
  from tkinter import Label as _tkinterLabel
@@ -139,7 +139,8 @@ def setup_demo(app: ttk.App, theme: str):
139
139
  return bag, gc
140
140
 
141
141
 
142
- if __name__ == "__main__":
142
+ def main():
143
+ """Open the widget demo. Also what `ttkb demo` runs."""
143
144
  app = ttk.App("ttkbootstrap widget demo", minsize=(600, 0))
144
145
  # Hide it before building the UI, not after: a window is placed by its
145
146
  # window manager, and once it has been mapped, re-showing it is a fresh
@@ -159,3 +160,8 @@ if __name__ == "__main__":
159
160
  app.deiconify()
160
161
 
161
162
  app.mainloop()
163
+ return 0
164
+
165
+
166
+ if __name__ == "__main__":
167
+ main()
@@ -0,0 +1,124 @@
1
+ """The `ttkb` command line.
2
+
3
+ One entry point for the things ttkbootstrap ships that are not library calls:
4
+ the version, the widget demo, the 1.x theme converter, and ttkcreator. Each was
5
+ reachable before only as its own `python -m ...` invocation, which nothing
6
+ surfaced.
7
+
8
+ The command installs under two names -- `ttkb` and `ttkbootstrap` -- that run
9
+ the same thing, and `python -m ttkbootstrap.cli <command>` runs it too, which is
10
+ what to reach for when the scripts directory is not on PATH::
11
+
12
+ ttkb version
13
+ ttkb demo
14
+ ttkb convert-theme user.py -o brand.py
15
+ ttkb creator
16
+ """
17
+ import argparse
18
+ import sys
19
+
20
+ from ttkbootstrap import __version__
21
+
22
+
23
+ def _run_version(args, parser):
24
+ """Print the installed version."""
25
+ print(__version__)
26
+ return 0
27
+
28
+
29
+ def _run_demo(args, parser):
30
+ """Open the widget demo."""
31
+ from ttkbootstrap.__main__ import main
32
+
33
+ return main()
34
+
35
+
36
+ def _run_creator(args, parser):
37
+ """Open ttkcreator."""
38
+ try:
39
+ from ttkcreator.__main__ import main
40
+ except ModuleNotFoundError as error:
41
+ # ttkcreator ships alongside ttkbootstrap but is a separate top-level
42
+ # package, so it can be absent from a trimmed install. Only report that
43
+ # when ttkcreator itself is what is missing -- an import failing *inside*
44
+ # it is a different problem, and saying "not installed" would send the
45
+ # user after a package they already have.
46
+ if error.name not in ("ttkcreator", "ttkcreator.__main__"):
47
+ raise
48
+ parser.exit(2, f"{parser.prog}: ttkcreator is not installed ({error}).\n")
49
+ return main()
50
+
51
+
52
+ def _run_convert_theme(args, parser):
53
+ """Convert a 1.x theme file."""
54
+ from ttkbootstrap import convert_theme
55
+
56
+ return convert_theme.run(args, parser)
57
+
58
+
59
+ def build_parser():
60
+ """Return the `ttkb` argument parser."""
61
+ parser = argparse.ArgumentParser(
62
+ prog="ttkb",
63
+ description="Command-line utilities for ttkbootstrap.",
64
+ )
65
+ parser.add_argument(
66
+ "-V", "--version", action="version", version=__version__
67
+ )
68
+ commands = parser.add_subparsers(dest="command", metavar="<command>")
69
+
70
+ version = commands.add_parser(
71
+ "version",
72
+ help="print the installed ttkbootstrap version",
73
+ description="Print the installed ttkbootstrap version.",
74
+ )
75
+ version.set_defaults(func=_run_version, parser=version)
76
+
77
+ demo = commands.add_parser(
78
+ "demo",
79
+ help="open the widget demo",
80
+ description="Open the widget demo -- every themed widget on one "
81
+ "window, with a theme picker.",
82
+ )
83
+ demo.set_defaults(func=_run_demo, parser=demo)
84
+
85
+ # Imported lazily elsewhere, but the description and arguments are shared
86
+ # here so the two spellings of the converter cannot drift apart.
87
+ from ttkbootstrap import convert_theme
88
+
89
+ convert = commands.add_parser(
90
+ "convert-theme",
91
+ help="convert a 1.x theme file to the 2.x Theme(...) form",
92
+ description=convert_theme.DESCRIPTION,
93
+ )
94
+ convert_theme.add_arguments(convert)
95
+ convert.set_defaults(func=_run_convert_theme, parser=convert)
96
+
97
+ creator = commands.add_parser(
98
+ "creator",
99
+ help="open ttkcreator, the theme designer",
100
+ description="Open ttkcreator: edit a theme's colors, preview the "
101
+ "result live, and export a Theme(...).register() snippet.",
102
+ )
103
+ creator.set_defaults(func=_run_creator, parser=creator)
104
+
105
+ return parser
106
+
107
+
108
+ def main(argv=None):
109
+ """Run the `ttkb` command line."""
110
+ parser = build_parser()
111
+ args = parser.parse_args(argv)
112
+ if getattr(args, "func", None) is None:
113
+ # No subcommand. argparse can require one, but the error it prints on
114
+ # its own names the missing argument rather than showing what is
115
+ # available -- and a bare `ttkb` is someone asking exactly that.
116
+ parser.print_help()
117
+ return 2
118
+ # Each subparser records itself, so a failing command names itself in its
119
+ # usage line rather than the bare `ttkb`.
120
+ return args.func(args, args.parser)
121
+
122
+
123
+ if __name__ == "__main__":
124
+ sys.exit(main())
@@ -0,0 +1,323 @@
1
+ """Convert a ttkbootstrap 1.x theme file into a 2.x ``Theme(...).register()`` snippet.
2
+
3
+ Run it on a theme file saved by 1.x -- a ``user.py`` holding a ``USER_THEMES``
4
+ dict, a ``.py`` holding the ``ThemeDefinition(...)`` call ttkcreator's *Export
5
+ theme definition* wrote, or a JSON file in the ``Style.load_user_themes``
6
+ format -- and it prints Python you paste into your own app::
7
+
8
+ ttkb convert-theme mythemes.json
9
+ ttkb convert-theme user.py -o brand.py
10
+
11
+ The module stays runnable as ``python -m ttkbootstrap.convert_theme``, which is
12
+ what to use when the scripts directory is not on PATH.
13
+
14
+ The five accent anchors, the optional secondary accent, and the theme's
15
+ background/foreground carry over. `border`, `inputbg`, `inputfg`, `selectbg`,
16
+ `selectfg`, and `active` are dropped: 2.x derives them from the anchors. The
17
+ `light` and `dark` accents are dropped too: 2.x derives that pair from the
18
+ `neutral` ramp, which you set with `Theme(neutral=...)`. A 1.x theme declares
19
+ one mode, so the generated family declares that mode and leaves the opposite
20
+ one commented out.
21
+ """
22
+ import argparse
23
+ import ast
24
+ import json
25
+ import sys
26
+ import textwrap
27
+ from pathlib import Path
28
+
29
+ #: 1.x color keys that carry over to a `Theme` anchor of the same name.
30
+ _ACCENT_KEYS = ("primary", "success", "info", "warning", "danger")
31
+
32
+ #: 1.x color keys the 2.x engine derives rather than reads.
33
+ _DERIVED_KEYS = ("border", "inputbg", "inputfg", "selectbg", "selectfg", "active")
34
+
35
+ #: 1.x accent keys 2.x derives from the `neutral` ramp instead.
36
+ _NEUTRAL_KEYS = ("light", "dark")
37
+
38
+
39
+ def _literal(value):
40
+ """Render a value as a Python string literal, escaping what it contains.
41
+
42
+ JSON string escapes are a subset of Python's, so `json.dumps` gives a
43
+ double-quoted literal that survives a quote or a backslash in a theme name
44
+ or a color -- the generated file is source we tell people to run.
45
+ """
46
+ return json.dumps(str(value))
47
+
48
+
49
+ def load_legacy_themes(path):
50
+ """Read a 1.x theme file and return its ``{name: spec}`` mapping.
51
+
52
+ Parameters:
53
+
54
+ path (str or Path):
55
+ A ``.json`` file in the ``Style.load_user_themes`` format, or a
56
+ ``.py`` file defining a ``USER_THEMES`` dict or a
57
+ ``ThemeDefinition(...)`` call.
58
+
59
+ Returns:
60
+
61
+ dict:
62
+ Each 1.x spec, keyed by theme name.
63
+ """
64
+ path = Path(path)
65
+ text = path.read_text(encoding="utf-8")
66
+ if path.suffix == ".py":
67
+ themes = _themes_from_python(text, path)
68
+ else:
69
+ themes = _themes_from_json(text, path)
70
+ if not themes:
71
+ raise ValueError(f"{path}: no themes found.")
72
+ return themes
73
+
74
+
75
+ def _themes_from_json(text, path):
76
+ """Return ``{name: spec}`` from a ``load_user_themes``-format JSON file."""
77
+ data = json.loads(text)
78
+ entries = data.get("themes", data) if isinstance(data, dict) else data
79
+ if isinstance(entries, dict):
80
+ return dict(entries)
81
+ if not isinstance(entries, list):
82
+ raise ValueError(
83
+ f"{path}: expected a theme mapping or a list of them, got "
84
+ f"{type(entries).__name__}."
85
+ )
86
+ themes = {}
87
+ for entry in entries:
88
+ if not isinstance(entry, dict):
89
+ raise ValueError(
90
+ f"{path}: expected each entry to be a theme mapping, got "
91
+ f"{type(entry).__name__}."
92
+ )
93
+ themes.update(entry)
94
+ return themes
95
+
96
+
97
+ def _themes_from_python(text, path):
98
+ """Return ``{name: spec}`` from a 1.x ``user.py`` store or a ttkcreator
99
+ ``ThemeDefinition`` export."""
100
+ tree = ast.parse(text, filename=str(path))
101
+ themes = {}
102
+ for node in tree.body:
103
+ if not isinstance(node, ast.Assign):
104
+ continue
105
+ names = [t.id for t in node.targets if isinstance(t, ast.Name)]
106
+ if "USER_THEMES" in names or "STANDARD_THEMES" in names:
107
+ store = ast.literal_eval(node.value)
108
+ if not isinstance(store, dict):
109
+ raise ValueError(
110
+ f"{path}: expected USER_THEMES to be a mapping of themes, "
111
+ f"got {type(store).__name__}."
112
+ )
113
+ return dict(store)
114
+ call = _theme_definition_call(node.value)
115
+ if call is not None:
116
+ themes.update(_spec_from_definition(call, path))
117
+ if themes:
118
+ return themes
119
+ raise ValueError(
120
+ f"{path}: found neither a USER_THEMES assignment nor a "
121
+ "ThemeDefinition(...) call."
122
+ )
123
+
124
+
125
+ def _theme_definition_call(node):
126
+ """Return the node when it is a ``ThemeDefinition(...)`` call, else None."""
127
+ if not isinstance(node, ast.Call):
128
+ return None
129
+ func = node.func
130
+ name = (
131
+ func.attr if isinstance(func, ast.Attribute)
132
+ else getattr(func, "id", None)
133
+ )
134
+ return node if name == "ThemeDefinition" else None
135
+
136
+
137
+ def _spec_from_definition(call, path):
138
+ """Return ``{name: spec}`` from ttkcreator's *Export theme definition* file.
139
+
140
+ That export writes ``ThemeDefinition(name=..., themetype=...,
141
+ colors={...})``. 1.x took those three positionally as ``(name, colors,
142
+ themetype)``, with `themetype` defaulting to light, so both spellings are
143
+ read.
144
+ """
145
+ args = dict(zip(("name", "colors", "themetype"), call.args))
146
+ args.update({kw.arg: kw.value for kw in call.keywords if kw.arg})
147
+ try:
148
+ name = ast.literal_eval(args["name"])
149
+ colors = ast.literal_eval(args["colors"])
150
+ except KeyError as error:
151
+ raise ValueError(
152
+ f"{path}: ThemeDefinition(...) is missing {error.args[0]}."
153
+ ) from None
154
+ mode = "light"
155
+ if "themetype" in args:
156
+ mode = ast.literal_eval(args["themetype"])
157
+ return {name: {"type": mode, "colors": colors}}
158
+
159
+
160
+ def _mode(name, spec):
161
+ """Return the theme's ``light``/``dark`` mode."""
162
+ if not isinstance(spec, dict):
163
+ raise ValueError(
164
+ f"theme {name!r}: expected a spec mapping, got "
165
+ f"{type(spec).__name__}."
166
+ )
167
+ mode = spec.get("mode") or spec.get("type")
168
+ if mode not in ("light", "dark"):
169
+ raise ValueError(
170
+ f"theme {name!r}: expected a 'mode' (or 1.x 'type') of 'light' or "
171
+ f"'dark', got {mode!r}."
172
+ )
173
+ return mode
174
+
175
+
176
+ def _colors(name, spec):
177
+ """Return the theme's color mapping, checking the keys conversion needs."""
178
+ colors = spec.get("colors")
179
+ if not isinstance(colors, dict):
180
+ raise ValueError(f"theme {name!r}: missing a 'colors' mapping.")
181
+ missing = [k for k in _ACCENT_KEYS + ("bg", "fg") if not colors.get(k)]
182
+ if missing:
183
+ raise ValueError(
184
+ f"theme {name!r}: missing color(s) {', '.join(missing)}."
185
+ )
186
+ return colors
187
+
188
+
189
+ def theme_snippet(name, spec):
190
+ """Render one 1.x theme spec as a ``ttk.Theme(...).register()`` call.
191
+
192
+ Parameters:
193
+
194
+ name (str):
195
+ The theme name; becomes the family name, so the call registers
196
+ ``<name>-light`` or ``<name>-dark``.
197
+
198
+ spec (dict):
199
+ A 1.x spec — ``{'type': 'light'|'dark', 'colors': {...}}``.
200
+
201
+ Returns:
202
+
203
+ str:
204
+ The rendered call, without a trailing newline.
205
+ """
206
+ mode = _mode(name, spec)
207
+ colors = _colors(name, spec)
208
+ other = "dark" if mode == "light" else "light"
209
+
210
+ primary = f"primary={_literal(colors['primary'])}"
211
+ if colors.get("secondary"):
212
+ primary += f", secondary={_literal(colors['secondary'])}"
213
+ lines = [
214
+ "ttk.Theme(",
215
+ f" name={_literal(name)},",
216
+ f" {primary},",
217
+ f" success={_literal(colors['success'])}, "
218
+ f"info={_literal(colors['info'])},",
219
+ f" warning={_literal(colors['warning'])}, "
220
+ f"danger={_literal(colors['danger'])},",
221
+ f" {mode}=dict(background={_literal(colors['bg'])}, "
222
+ f"foreground={_literal(colors['fg'])}),",
223
+ f" # {other}=dict(background=..., foreground=...),"
224
+ f" # 1.x theme was {mode}-only",
225
+ ").register()",
226
+ ]
227
+ return "\n".join(lines)
228
+
229
+
230
+ def convert(path):
231
+ """Convert a 1.x theme file into a runnable Python module.
232
+
233
+ Parameters:
234
+
235
+ path (str or Path):
236
+ The 1.x ``.json`` or ``.py`` theme file.
237
+
238
+ Returns:
239
+
240
+ str:
241
+ Python source registering every theme in the file.
242
+ """
243
+ themes = load_legacy_themes(path)
244
+ dropped = ", ".join(_DERIVED_KEYS[:-1]) + f", and {_DERIVED_KEYS[-1]}"
245
+ note = textwrap.wrap(
246
+ f"The 1.x {dropped} values are dropped; 2.x derives them from the "
247
+ f"anchors below. So are the {' and '.join(_NEUTRAL_KEYS)} accents; 2.x "
248
+ f"derives that pair from the neutral ramp, which Theme(neutral=...) "
249
+ f"sets.",
250
+ width=75,
251
+ )
252
+ # A registered theme is selectable by name, and the name is `<family>-<mode>`
253
+ # rather than the 1.x name -- so show it. Naming the first theme names the
254
+ # one they just converted.
255
+ first, first_spec = next(iter(themes.items()))
256
+ registered = f"{first}-{_mode(first, first_spec)}"
257
+ header = [
258
+ f"# Converted from {Path(path).name} by ttkb convert-theme.",
259
+ "#",
260
+ *(f"# {line}" for line in note),
261
+ "#",
262
+ "# Import this module, then select the theme by name:",
263
+ f"# app = ttk.App(theme={_literal(registered)})",
264
+ "",
265
+ "import ttkbootstrap as ttk",
266
+ "",
267
+ "",
268
+ ]
269
+ blocks = [theme_snippet(name, spec) for name, spec in themes.items()]
270
+ return "\n".join(header) + "\n\n".join(blocks) + "\n"
271
+
272
+
273
+ #: One-line summary, shared by this module's parser and the `ttkb` subcommand.
274
+ DESCRIPTION = (
275
+ "Convert a ttkbootstrap 1.x theme file into a 2.x Theme(...).register() "
276
+ "snippet."
277
+ )
278
+
279
+
280
+ def add_arguments(parser):
281
+ """Add the converter's arguments to `parser`.
282
+
283
+ Shared with the `ttkb convert-theme` subcommand so the two spellings cannot
284
+ drift apart.
285
+ """
286
+ parser.add_argument(
287
+ "file",
288
+ help="a 1.x theme file (.json, or a .py USER_THEMES / ThemeDefinition "
289
+ "store)",
290
+ )
291
+ parser.add_argument(
292
+ "-o", "--output", help="write to this file instead of standard output"
293
+ )
294
+ return parser
295
+
296
+
297
+ def run(args, parser):
298
+ """Convert the file named by `args`, reporting failures through `parser`."""
299
+ try:
300
+ source = convert(args.file)
301
+ if args.output:
302
+ Path(args.output).write_text(source, encoding="utf-8")
303
+ except (OSError, ValueError, SyntaxError) as error:
304
+ parser.exit(2, f"{parser.prog}: {error}\n")
305
+
306
+ if args.output:
307
+ print(f"Wrote {args.output}", file=sys.stderr)
308
+ else:
309
+ sys.stdout.write(source)
310
+ return 0
311
+
312
+
313
+ def main(argv=None):
314
+ """Run the command-line converter."""
315
+ parser = argparse.ArgumentParser(
316
+ prog="python -m ttkbootstrap.convert_theme", description=DESCRIPTION
317
+ )
318
+ add_arguments(parser)
319
+ return run(parser.parse_args(argv), parser)
320
+
321
+
322
+ if __name__ == "__main__":
323
+ sys.exit(main())
@@ -951,7 +951,19 @@ class Style(ttk.Style):
951
951
  self.register_theme(theme)
952
952
 
953
953
  def load_user_themes(self, file):
954
- """Load user themes saved in json format"""
954
+ """Register themes from a 1.x JSON theme file.
955
+
956
+ The file holds ``{"themes": [{"<name>": {"type": "light"|"dark",
957
+ "colors": {...}}}]}`` with the sixteen 1.x color keys, taken verbatim.
958
+ Each theme registers under its own name as a single mode. To convert a
959
+ 1.x theme into a light/dark `Theme` family whose plumbing colors are
960
+ regenerated, run ``ttkb convert-theme`` instead.
961
+
962
+ Parameters:
963
+
964
+ file (str):
965
+ Path to the JSON theme file.
966
+ """
955
967
  with open(file, encoding='utf-8') as f:
956
968
  data = json.load(f)
957
969
  themes = data['themes']