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.
- {ttkbootstrap-2.1.1/src/ttkbootstrap.egg-info → ttkbootstrap-2.2.0}/PKG-INFO +36 -3
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/README.md +35 -2
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/pyproject.toml +8 -1
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/__init__.py +19 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/__init__.pyi +4 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/__main__.py +7 -1
- ttkbootstrap-2.2.0/src/ttkbootstrap/cli.py +124 -0
- ttkbootstrap-2.2.0/src/ttkbootstrap/convert_theme.py +323 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/engine.py +13 -1
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/theme.py +38 -12
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/themes/legacy.py +22 -10
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0/src/ttkbootstrap.egg-info}/PKG-INFO +36 -3
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap.egg-info/SOURCES.txt +5 -0
- ttkbootstrap-2.2.0/src/ttkbootstrap.egg-info/entry_points.txt +3 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkcreator/__main__.py +7 -2
- ttkbootstrap-2.2.0/tests/test_cli_api.py +182 -0
- ttkbootstrap-2.2.0/tests/test_convert_theme.py +317 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_scaling.py +61 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/LICENSE +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/MANIFEST.in +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/setup.cfg +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/assets/__init__.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/assets/app_icons/ttkbootstrap.ico +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/assets/app_icons/ttkbootstrap.png +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/assets/elements/README.md +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/assets/elements/__init__.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/assets/elements/checkbox-checked.png +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/assets/elements/checkbox-indeterminate.png +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/assets/elements/checkbox-unchecked.png +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/assets/elements/manifest.json +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/assets/elements/progressbar-default.png +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/assets/elements/progressbar-thin.png +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/assets/elements/radio.png +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/assets/elements/sizegrip.png +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/assets/elements/slider-handle.png +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/assets/elements/slider-track.png +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/assets/elements/switch-round.png +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/assets/elements/switch-square.png +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/assets/icons/LICENSE +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/assets/icons/README.md +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/assets/icons/bootstrap.ttf +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/assets/icons/glyphmap.json +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/assets/icons/icon_metrics.json +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/colorutils.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/constants.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/dialogs/__init__.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/dialogs/base.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/dialogs/colorchooser.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/dialogs/colordropper.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/dialogs/datepicker.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/dialogs/filedialog.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/dialogs/fontdialog.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/dialogs/message.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/dialogs/query.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/internal/__init__.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/internal/busy.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/internal/configure_delegation.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/internal/positioning.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/internal/publisher.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/internal/utility.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/internal/wheel.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/localization/README.md +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/localization/__init__.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/localization/api.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/localization/msgcat.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/localization/msgs.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/menu.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/publisher.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/py.typed +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/__init__.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/_compat.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/assets.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/bootstyle.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/builders/__init__.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/builders/button.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/builders/calendar.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/builders/checkbutton.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/builders/combobox.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/builders/entry.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/builders/floodgauge.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/builders/frame.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/builders/label.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/builders/labelframe.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/builders/menubutton.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/builders/notebook.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/builders/panedwindow.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/builders/progressbar.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/builders/radiobutton.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/builders/registry.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/builders/scale.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/builders/scrollbar.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/builders/separator.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/builders/sizegrip.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/builders/spinbox.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/builders/toggle.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/builders/toolbutton.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/builders/treeview.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/builders/utils.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/builders_tk.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/builders_ttk.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/elements.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/icons.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/layout.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/style/scaling.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/themes/__init__.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/themes/builtin.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/themes/standard.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/utility.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/utils/__init__.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/utils/color.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/utils/config.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/utils/fonts.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/utils/platform.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/utils/scaling.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/validation.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/widgets/__init__.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/widgets/dateentry.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/widgets/floodgauge.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/widgets/labeledscale.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/widgets/meter.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/widgets/scrolled.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/widgets/tableview.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/widgets/toast.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/widgets/tooltip.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap/window.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap.egg-info/dependency_links.txt +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap.egg-info/requires.txt +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkbootstrap.egg-info/top_level.txt +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/src/ttkcreator/__init__.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_bootstyle_grammar.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_bootstyle_value_tokens.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_busy_api.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_config_api.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_configure_delegation.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_dateentry_api.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_dialogs_api.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_durable_style_options.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_filedialog_api.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_floodgauge_api.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_fonts_api.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_icon_drop.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_icon_theme.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_labeledscale_tooltip_api.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_menu_api.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_menu_border.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_meter_api.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_mixin_api.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_scroll_events.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_scrolled_api.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_style_package.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_style_reference.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_surface_color.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_surface_families.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_surface_grammar.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_tableview_api.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_toast_api.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_toolkit.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_treeview_rowheight.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_utility_api.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_widget_catalog_coverage.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_widget_reexports.py +0 -0
- {ttkbootstrap-2.1.1 → ttkbootstrap-2.2.0}/tests/test_widget_stubs.py +0 -0
- {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.
|
|
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
|
-
[
|
|
146
|
-
|
|
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
|
-
[
|
|
119
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
"""
|
|
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']
|