bootstack 0.1.0a1__py3-none-any.whl
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.
- bootstack/__init__.py +249 -0
- bootstack/__main__.py +5 -0
- bootstack/api/__init__.py +127 -0
- bootstack/api/app.py +30 -0
- bootstack/api/constants.py +3 -0
- bootstack/api/data.py +23 -0
- bootstack/api/dialogs.py +44 -0
- bootstack/api/i18n.py +17 -0
- bootstack/api/localization.py +16 -0
- bootstack/api/menu.py +7 -0
- bootstack/api/style.py +23 -0
- bootstack/api/utils.py +24 -0
- bootstack/api/widgets.py +137 -0
- bootstack/assets/__init__.py +24 -0
- bootstack/assets/bootstack-transparent.png +0 -0
- bootstack/assets/bootstack.ico +0 -0
- bootstack/assets/bootstack.png +0 -0
- bootstack/assets/elements/__init__.py +0 -0
- bootstack/assets/elements/badge-pill.png +0 -0
- bootstack/assets/elements/badge-square.png +0 -0
- bootstack/assets/elements/border.png +0 -0
- bootstack/assets/elements/button-compact.png +0 -0
- bootstack/assets/elements/button-default.png +0 -0
- bootstack/assets/elements/button-group-horizontal-after-compact.png +0 -0
- bootstack/assets/elements/button-group-horizontal-after-default.png +0 -0
- bootstack/assets/elements/button-group-horizontal-before-compact.png +0 -0
- bootstack/assets/elements/button-group-horizontal-before-default.png +0 -0
- bootstack/assets/elements/button-group-horizontal-center-compact.png +0 -0
- bootstack/assets/elements/button-group-horizontal-center-default.png +0 -0
- bootstack/assets/elements/button-group-vertical-after-compact.png +0 -0
- bootstack/assets/elements/button-group-vertical-after-default.png +0 -0
- bootstack/assets/elements/button-group-vertical-before-compact.png +0 -0
- bootstack/assets/elements/button-group-vertical-before-default.png +0 -0
- bootstack/assets/elements/button-group-vertical-center-compact.png +0 -0
- bootstack/assets/elements/button-group-vertical-center-default.png +0 -0
- bootstack/assets/elements/checkbox-checked.png +0 -0
- bootstack/assets/elements/checkbox-indeterminate.png +0 -0
- bootstack/assets/elements/checkbox-unchecked.png +0 -0
- bootstack/assets/elements/field.png +0 -0
- bootstack/assets/elements/input-after-compact.png +0 -0
- bootstack/assets/elements/input-after-default.png +0 -0
- bootstack/assets/elements/input-before-compact.png +0 -0
- bootstack/assets/elements/input-before-default.png +0 -0
- bootstack/assets/elements/input-compact.png +0 -0
- bootstack/assets/elements/input-default.png +0 -0
- bootstack/assets/elements/list-item-separated.png +0 -0
- bootstack/assets/elements/list-item.png +0 -0
- bootstack/assets/elements/manifest.toml +480 -0
- bootstack/assets/elements/menu-item.png +0 -0
- bootstack/assets/elements/nav-button-compact.png +0 -0
- bootstack/assets/elements/nav-button-default.png +0 -0
- bootstack/assets/elements/nav-icon-button-compact.png +0 -0
- bootstack/assets/elements/nav-icon-button-default.png +0 -0
- bootstack/assets/elements/notebook-client-border.png +0 -0
- bootstack/assets/elements/notebook-tab-active.png +0 -0
- bootstack/assets/elements/notebook-tab-bar.png +0 -0
- bootstack/assets/elements/notebook-tab-normal.png +0 -0
- bootstack/assets/elements/notebook-tab-pill.png +0 -0
- bootstack/assets/elements/progress-bar-horizontal-striped.png +0 -0
- bootstack/assets/elements/progress-bar-solid.png +0 -0
- bootstack/assets/elements/progress-bar-thin.png +0 -0
- bootstack/assets/elements/progress-bar-vertical-striped.png +0 -0
- bootstack/assets/elements/radio-selected.png +0 -0
- bootstack/assets/elements/radio-unselected.png +0 -0
- bootstack/assets/elements/scrollbar-horizontal.png +0 -0
- bootstack/assets/elements/scrollbar-vertical.png +0 -0
- bootstack/assets/elements/slider-handle-focus.png +0 -0
- bootstack/assets/elements/slider-handle.png +0 -0
- bootstack/assets/elements/slider-track-horizontal.png +0 -0
- bootstack/assets/elements/slider-track-vertical.png +0 -0
- bootstack/assets/elements/switch-off.png +0 -0
- bootstack/assets/elements/switch-on.png +0 -0
- bootstack/assets/elements/tabs-bar-horizontal.png +0 -0
- bootstack/assets/elements/tabs-bar-vertical.png +0 -0
- bootstack/assets/elements/tabs-pill.png +0 -0
- bootstack/assets/locales/ar/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/ar/LC_MESSAGES/bootstack.po +853 -0
- bootstack/assets/locales/bg/LC_MESSAGES/bootstack.po +875 -0
- bootstack/assets/locales/cs/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/cs/LC_MESSAGES/bootstack.po +853 -0
- bootstack/assets/locales/da/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/da/LC_MESSAGES/bootstack.po +853 -0
- bootstack/assets/locales/de/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/de/LC_MESSAGES/bootstack.po +853 -0
- bootstack/assets/locales/en/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/en/LC_MESSAGES/bootstack.po +875 -0
- bootstack/assets/locales/es/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/es/LC_MESSAGES/bootstack.po +853 -0
- bootstack/assets/locales/fr/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/fr/LC_MESSAGES/bootstack.po +853 -0
- bootstack/assets/locales/he/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/he/LC_MESSAGES/bootstack.po +851 -0
- bootstack/assets/locales/hi/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/hi/LC_MESSAGES/bootstack.po +842 -0
- bootstack/assets/locales/it/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/it/LC_MESSAGES/bootstack.po +841 -0
- bootstack/assets/locales/ja/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/ja/LC_MESSAGES/bootstack.po +914 -0
- bootstack/assets/locales/ko/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/ko/LC_MESSAGES/bootstack.po +842 -0
- bootstack/assets/locales/nb/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/nb/LC_MESSAGES/bootstack.po +841 -0
- bootstack/assets/locales/nl/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/nl/LC_MESSAGES/bootstack.po +841 -0
- bootstack/assets/locales/pl/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/pl/LC_MESSAGES/bootstack.po +842 -0
- bootstack/assets/locales/pt/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/pt/LC_MESSAGES/bootstack.po +842 -0
- bootstack/assets/locales/pt_BR/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/pt_BR/LC_MESSAGES/bootstack.po +842 -0
- bootstack/assets/locales/sl/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/sl/LC_MESSAGES/bootstack.po +842 -0
- bootstack/assets/locales/sv/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/sv/LC_MESSAGES/bootstack.po +842 -0
- bootstack/assets/locales/tr/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/tr/LC_MESSAGES/bootstack.po +842 -0
- bootstack/assets/locales/zh_CN/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/zh_CN/LC_MESSAGES/bootstack.po +842 -0
- bootstack/assets/locales/zh_TW/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/zh_TW/LC_MESSAGES/bootstack.po +842 -0
- bootstack/assets/themes/__init__.py +0 -0
- bootstack/assets/themes/amber-dark.json +32 -0
- bootstack/assets/themes/amber-light.json +32 -0
- bootstack/assets/themes/aurora-dark.json +32 -0
- bootstack/assets/themes/aurora-light.json +32 -0
- bootstack/assets/themes/bootstrap-dark.json +32 -0
- bootstack/assets/themes/bootstrap-light.json +32 -0
- bootstack/assets/themes/classic-dark.json +32 -0
- bootstack/assets/themes/classic-light.json +32 -0
- bootstack/assets/themes/docs-dark.json +32 -0
- bootstack/assets/themes/docs-light.json +32 -0
- bootstack/assets/themes/forest-dark.json +32 -0
- bootstack/assets/themes/forest-light.json +32 -0
- bootstack/assets/themes/ocean-dark.json +32 -0
- bootstack/assets/themes/ocean-light.json +32 -0
- bootstack/assets/themes/rose-dark.json +32 -0
- bootstack/assets/themes/rose-light.json +32 -0
- bootstack/assets/widgets/__init__.py +0 -0
- bootstack/assets/widgets/badge-default.png +0 -0
- bootstack/assets/widgets/badge-pill.png +0 -0
- bootstack/assets/widgets/border.png +0 -0
- bootstack/assets/widgets/button-group-horizontal-after.png +0 -0
- bootstack/assets/widgets/button-group-horizontal-before.png +0 -0
- bootstack/assets/widgets/button-group-horizontal-center.png +0 -0
- bootstack/assets/widgets/button-group-vertical-after.png +0 -0
- bootstack/assets/widgets/button-group-vertical-before.png +0 -0
- bootstack/assets/widgets/button-group-vertical-center.png +0 -0
- bootstack/assets/widgets/button.png +0 -0
- bootstack/assets/widgets/checkbox-checked.png +0 -0
- bootstack/assets/widgets/checkbox-indeterminate.png +0 -0
- bootstack/assets/widgets/checkbox-unchecked.png +0 -0
- bootstack/assets/widgets/field.png +0 -0
- bootstack/assets/widgets/icon-button.png +0 -0
- bootstack/assets/widgets/input-inner.png +0 -0
- bootstack/assets/widgets/input-prefix.png +0 -0
- bootstack/assets/widgets/input-suffix.png +0 -0
- bootstack/assets/widgets/input.png +0 -0
- bootstack/assets/widgets/list-item-focus.png +0 -0
- bootstack/assets/widgets/list-item-separated.png +0 -0
- bootstack/assets/widgets/menu-item-separated.png +0 -0
- bootstack/assets/widgets/notebook-client-border.png +0 -0
- bootstack/assets/widgets/notebook-pill-active.png +0 -0
- bootstack/assets/widgets/notebook-pill-inactive.png +0 -0
- bootstack/assets/widgets/notebook-tab-active.png +0 -0
- bootstack/assets/widgets/notebook-tab-border.png +0 -0
- bootstack/assets/widgets/notebook-tab-normal.png +0 -0
- bootstack/assets/widgets/notebook-underline.png +0 -0
- bootstack/assets/widgets/progress-bar-horizontal-default.png +0 -0
- bootstack/assets/widgets/progress-bar-horizontal-striped.png +0 -0
- bootstack/assets/widgets/progress-bar-vertical-default.png +0 -0
- bootstack/assets/widgets/progress-bar-vertical-striped.png +0 -0
- bootstack/assets/widgets/progress-trough-horizontal.png +0 -0
- bootstack/assets/widgets/progress-trough-vertical.png +0 -0
- bootstack/assets/widgets/radio-selected.png +0 -0
- bootstack/assets/widgets/radio-unselected.png +0 -0
- bootstack/assets/widgets/scrollbar-horizontal-rounded.png +0 -0
- bootstack/assets/widgets/scrollbar-vertical-rounded.png +0 -0
- bootstack/assets/widgets/separator-horizontal.png +0 -0
- bootstack/assets/widgets/separator-vertical.png +0 -0
- bootstack/assets/widgets/slider-handle-focus.png +0 -0
- bootstack/assets/widgets/slider-handle.png +0 -0
- bootstack/assets/widgets/slider-track-horizontal.png +0 -0
- bootstack/assets/widgets/slider-track-vertical.png +0 -0
- bootstack/assets/widgets/switch-off.png +0 -0
- bootstack/assets/widgets/switch-on.png +0 -0
- bootstack/assets/widgets/tabs-bar-horizontal.png +0 -0
- bootstack/assets/widgets/tabs-bar-vertical.png +0 -0
- bootstack/assets/widgets/tabs-pill.png +0 -0
- bootstack/cli/__init__.py +124 -0
- bootstack/cli/__main__.py +6 -0
- bootstack/cli/add.py +439 -0
- bootstack/cli/build.py +115 -0
- bootstack/cli/config.py +287 -0
- bootstack/cli/demo.py +1267 -0
- bootstack/cli/doctor.py +195 -0
- bootstack/cli/list_cmd.py +71 -0
- bootstack/cli/promote.py +120 -0
- bootstack/cli/pyinstaller.py +246 -0
- bootstack/cli/run.py +99 -0
- bootstack/cli/start.py +105 -0
- bootstack/cli/templates/__init__.py +861 -0
- bootstack/constants.py +325 -0
- bootstack/core/__init__.py +34 -0
- bootstack/core/capabilities/__init__.py +45 -0
- bootstack/core/capabilities/after.py +103 -0
- bootstack/core/capabilities/bind.py +154 -0
- bootstack/core/capabilities/bindtags.py +112 -0
- bootstack/core/capabilities/busy.py +61 -0
- bootstack/core/capabilities/clipboard.py +88 -0
- bootstack/core/capabilities/focus.py +118 -0
- bootstack/core/capabilities/grab.py +65 -0
- bootstack/core/capabilities/grid.py +188 -0
- bootstack/core/capabilities/localization.py +231 -0
- bootstack/core/capabilities/pack.py +119 -0
- bootstack/core/capabilities/place.py +92 -0
- bootstack/core/capabilities/selection.py +136 -0
- bootstack/core/capabilities/signals.py +242 -0
- bootstack/core/capabilities/winfo.py +315 -0
- bootstack/core/colorutils.py +234 -0
- bootstack/core/exceptions.py +95 -0
- bootstack/core/images.py +283 -0
- bootstack/core/localization/README.md +90 -0
- bootstack/core/localization/__init__.py +13 -0
- bootstack/core/localization/intl_format.py +580 -0
- bootstack/core/localization/msgcat.py +425 -0
- bootstack/core/localization/specs.py +143 -0
- bootstack/core/mixins/__init__.py +1 -0
- bootstack/core/mixins/ttk_state.py +35 -0
- bootstack/core/mixins/widget.py +132 -0
- bootstack/core/publisher.py +147 -0
- bootstack/core/signals/README.md +112 -0
- bootstack/core/signals/__init__.py +8 -0
- bootstack/core/signals/integration.py +100 -0
- bootstack/core/signals/signal.py +317 -0
- bootstack/core/signals/types.py +4 -0
- bootstack/core/validation/__init__.py +5 -0
- bootstack/core/validation/types.py +13 -0
- bootstack/core/validation/validation_result.py +17 -0
- bootstack/core/validation/validation_rules.py +112 -0
- bootstack/core/variables.py +62 -0
- bootstack/datasource/README.md +607 -0
- bootstack/datasource/__init__.py +51 -0
- bootstack/datasource/base.py +474 -0
- bootstack/datasource/file_source.py +541 -0
- bootstack/datasource/memory_source.py +482 -0
- bootstack/datasource/sqlite_source.py +453 -0
- bootstack/datasource/types.py +259 -0
- bootstack/dialogs/__init__.py +56 -0
- bootstack/dialogs/colorchooser.py +674 -0
- bootstack/dialogs/colordropper.py +257 -0
- bootstack/dialogs/datedialog.py +404 -0
- bootstack/dialogs/dialog.py +514 -0
- bootstack/dialogs/filterdialog.py +358 -0
- bootstack/dialogs/fontdialog.py +339 -0
- bootstack/dialogs/formdialog.py +541 -0
- bootstack/dialogs/message.py +489 -0
- bootstack/dialogs/query.py +561 -0
- bootstack/py.typed +1 -0
- bootstack/runtime/__init__.py +3 -0
- bootstack/runtime/app.py +879 -0
- bootstack/runtime/base_window.py +786 -0
- bootstack/runtime/events.py +399 -0
- bootstack/runtime/menu.py +510 -0
- bootstack/runtime/shortcuts.py +423 -0
- bootstack/runtime/tk_patch.py +31 -0
- bootstack/runtime/toplevel.py +131 -0
- bootstack/runtime/utility.py +371 -0
- bootstack/runtime/visual_focus.py +228 -0
- bootstack/runtime/window_utilities.py +1043 -0
- bootstack/style/__init__.py +5498 -0
- bootstack/style/bootstyle.py +507 -0
- bootstack/style/bootstyle_builder_base.py +752 -0
- bootstack/style/bootstyle_builder_mixed.py +93 -0
- bootstack/style/bootstyle_builder_tk.py +109 -0
- bootstack/style/bootstyle_builder_ttk.py +354 -0
- bootstack/style/builders/__init__.py +51 -0
- bootstack/style/builders/badge.py +44 -0
- bootstack/style/builders/button.py +453 -0
- bootstack/style/builders/buttongroup.py +344 -0
- bootstack/style/builders/calendar.py +271 -0
- bootstack/style/builders/checkbutton.py +95 -0
- bootstack/style/builders/combobox.py +112 -0
- bootstack/style/builders/contextmenu.py +268 -0
- bootstack/style/builders/entry.py +83 -0
- bootstack/style/builders/expander.py +171 -0
- bootstack/style/builders/field.py +312 -0
- bootstack/style/builders/frame.py +27 -0
- bootstack/style/builders/label.py +28 -0
- bootstack/style/builders/labelframe.py +41 -0
- bootstack/style/builders/listview.py +267 -0
- bootstack/style/builders/menubar.py +74 -0
- bootstack/style/builders/menubutton.py +408 -0
- bootstack/style/builders/notebook.py +316 -0
- bootstack/style/builders/panedwindow.py +25 -0
- bootstack/style/builders/progressbar.py +71 -0
- bootstack/style/builders/radiobutton.py +68 -0
- bootstack/style/builders/scale.py +66 -0
- bootstack/style/builders/scrollbar.py +360 -0
- bootstack/style/builders/separator.py +45 -0
- bootstack/style/builders/sidenav.py +313 -0
- bootstack/style/builders/sizegrip.py +15 -0
- bootstack/style/builders/spinbox.py +119 -0
- bootstack/style/builders/switch.py +67 -0
- bootstack/style/builders/tabitem.py +205 -0
- bootstack/style/builders/toolbutton.py +260 -0
- bootstack/style/builders/tooltip.py +26 -0
- bootstack/style/builders/treeview.py +269 -0
- bootstack/style/builders/utils.py +404 -0
- bootstack/style/builders_tk/__init__.py +16 -0
- bootstack/style/builders_tk/defaults.py +229 -0
- bootstack/style/element.py +173 -0
- bootstack/style/style.py +499 -0
- bootstack/style/theme_provider.py +449 -0
- bootstack/style/tk_patch.py +5 -0
- bootstack/style/token_maps.py +42 -0
- bootstack/style/types.py +32 -0
- bootstack/style/typography.py +527 -0
- bootstack/style/utility.py +696 -0
- bootstack/themes/__init__.py +12 -0
- bootstack/themes/standard.py +415 -0
- bootstack/themes/user.py +45 -0
- bootstack/widgets/__init__.py +53 -0
- bootstack/widgets/composites/__init__.py +38 -0
- bootstack/widgets/composites/accordion.py +385 -0
- bootstack/widgets/composites/appshell.py +445 -0
- bootstack/widgets/composites/buttongroup.py +391 -0
- bootstack/widgets/composites/calendar.py +914 -0
- bootstack/widgets/composites/compositeframe.py +282 -0
- bootstack/widgets/composites/contextmenu.py +1754 -0
- bootstack/widgets/composites/dateentry.py +261 -0
- bootstack/widgets/composites/dropdownbutton.py +190 -0
- bootstack/widgets/composites/expander.py +508 -0
- bootstack/widgets/composites/field.py +448 -0
- bootstack/widgets/composites/floodgauge.py +434 -0
- bootstack/widgets/composites/form.py +983 -0
- bootstack/widgets/composites/labeledscale.py +209 -0
- bootstack/widgets/composites/list/__init__.py +15 -0
- bootstack/widgets/composites/list/listitem.py +733 -0
- bootstack/widgets/composites/list/listview.py +1507 -0
- bootstack/widgets/composites/menubar.py +303 -0
- bootstack/widgets/composites/meter.py +882 -0
- bootstack/widgets/composites/numericentry.py +183 -0
- bootstack/widgets/composites/pagestack.py +330 -0
- bootstack/widgets/composites/passwordentry.py +149 -0
- bootstack/widgets/composites/pathentry.py +223 -0
- bootstack/widgets/composites/radiogroup.py +466 -0
- bootstack/widgets/composites/scrolledtext.py +388 -0
- bootstack/widgets/composites/scrolledtext.pyi +186 -0
- bootstack/widgets/composites/scrollview.py +675 -0
- bootstack/widgets/composites/selectbox.py +544 -0
- bootstack/widgets/composites/sidenav/__init__.py +24 -0
- bootstack/widgets/composites/sidenav/group.py +485 -0
- bootstack/widgets/composites/sidenav/header.py +83 -0
- bootstack/widgets/composites/sidenav/item.py +413 -0
- bootstack/widgets/composites/sidenav/separator.py +51 -0
- bootstack/widgets/composites/sidenav/view.py +919 -0
- bootstack/widgets/composites/spinnerentry.py +232 -0
- bootstack/widgets/composites/tableview/__init__.py +5 -0
- bootstack/widgets/composites/tableview/tableview.py +2254 -0
- bootstack/widgets/composites/tableview/types.py +169 -0
- bootstack/widgets/composites/tabs/__init__.py +6 -0
- bootstack/widgets/composites/tabs/tabitem.py +372 -0
- bootstack/widgets/composites/tabs/tabs.py +478 -0
- bootstack/widgets/composites/tabs/tabview.py +352 -0
- bootstack/widgets/composites/textentry.py +90 -0
- bootstack/widgets/composites/timeentry.py +189 -0
- bootstack/widgets/composites/toast.py +364 -0
- bootstack/widgets/composites/togglegroup.py +382 -0
- bootstack/widgets/composites/toolbar.py +393 -0
- bootstack/widgets/composites/tooltip.py +404 -0
- bootstack/widgets/internal/__init__.py +0 -0
- bootstack/widgets/internal/wrapper_base.py +304 -0
- bootstack/widgets/mixins/__init__.py +25 -0
- bootstack/widgets/mixins/configure_mixin.py +186 -0
- bootstack/widgets/mixins/entry_mixin.py +70 -0
- bootstack/widgets/mixins/font_mixin.py +346 -0
- bootstack/widgets/mixins/icon_mixin.py +38 -0
- bootstack/widgets/mixins/localization_mixin.py +255 -0
- bootstack/widgets/mixins/signal_mixin.py +272 -0
- bootstack/widgets/mixins/validation_mixin.py +204 -0
- bootstack/widgets/parts/__init__.py +11 -0
- bootstack/widgets/parts/numberentry_part.py +345 -0
- bootstack/widgets/parts/spinnerentry_part.py +394 -0
- bootstack/widgets/parts/textentry_part.py +344 -0
- bootstack/widgets/primitives/__init__.py +55 -0
- bootstack/widgets/primitives/badge.py +44 -0
- bootstack/widgets/primitives/button.py +89 -0
- bootstack/widgets/primitives/card.py +66 -0
- bootstack/widgets/primitives/checkbutton.py +124 -0
- bootstack/widgets/primitives/checktoggle.py +53 -0
- bootstack/widgets/primitives/combobox.py +165 -0
- bootstack/widgets/primitives/entry.py +98 -0
- bootstack/widgets/primitives/frame.py +206 -0
- bootstack/widgets/primitives/gridframe.py +479 -0
- bootstack/widgets/primitives/label.py +95 -0
- bootstack/widgets/primitives/labelframe.py +63 -0
- bootstack/widgets/primitives/menubutton.py +118 -0
- bootstack/widgets/primitives/notebook.py +551 -0
- bootstack/widgets/primitives/optionmenu.py +248 -0
- bootstack/widgets/primitives/packframe.py +228 -0
- bootstack/widgets/primitives/panedwindow.py +58 -0
- bootstack/widgets/primitives/progressbar.py +95 -0
- bootstack/widgets/primitives/radiobutton.py +115 -0
- bootstack/widgets/primitives/radiotoggle.py +50 -0
- bootstack/widgets/primitives/scale.py +85 -0
- bootstack/widgets/primitives/scrollbar.py +56 -0
- bootstack/widgets/primitives/separator.py +56 -0
- bootstack/widgets/primitives/sizegrip.py +47 -0
- bootstack/widgets/primitives/spinbox.py +91 -0
- bootstack/widgets/primitives/switch.py +41 -0
- bootstack/widgets/primitives/treeview.py +77 -0
- bootstack/widgets/types.py +20 -0
- bootstack-0.1.0a1.dist-info/METADATA +196 -0
- bootstack-0.1.0a1.dist-info/RECORD +419 -0
- bootstack-0.1.0a1.dist-info/WHEEL +5 -0
- bootstack-0.1.0a1.dist-info/entry_points.txt +2 -0
- bootstack-0.1.0a1.dist-info/licenses/LICENSE +22 -0
- bootstack-0.1.0a1.dist-info/licenses/NOTICE +10 -0
- bootstack-0.1.0a1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,882 @@
|
|
|
1
|
+
import math
|
|
2
|
+
from tkinter import Canvas, DoubleVar, IntVar, StringVar, font as tkfont
|
|
3
|
+
from typing import Any, Callable, Literal
|
|
4
|
+
from warnings import warn
|
|
5
|
+
|
|
6
|
+
from PIL import Image, ImageDraw, ImageTk
|
|
7
|
+
from PIL.Image import Resampling
|
|
8
|
+
|
|
9
|
+
from bootstack.core.exceptions import ConfigurationWarning
|
|
10
|
+
from bootstack.widgets.primitives.frame import Frame
|
|
11
|
+
from bootstack.widgets.mixins.configure_mixin import configure_delegate
|
|
12
|
+
from bootstack.widgets.types import Master
|
|
13
|
+
|
|
14
|
+
DEFAULT_IMAGE_SCALE = 6
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Meter(Frame):
|
|
18
|
+
"""A circular progress meter widget with customizable appearance and optional text display.
|
|
19
|
+
|
|
20
|
+
The Meter widget displays a value as a circular arc indicator with optional value text,
|
|
21
|
+
prefix/suffix labels, and subtitle. Supports both full circle and semi-circle styles,
|
|
22
|
+
segmented or solid indicators, and interactive mode for user input.
|
|
23
|
+
|
|
24
|
+
The meter value can be accessed and modified using:
|
|
25
|
+
|
|
26
|
+
- `get()` / `set(value)` - Standard value-widget API methods
|
|
27
|
+
- `.value` property - Direct property access
|
|
28
|
+
- `configure(value=x)` - Via the configure interface
|
|
29
|
+
|
|
30
|
+
!!! note "Events"
|
|
31
|
+
|
|
32
|
+
`<<Change>>`: Fired whenever the meter value changes.
|
|
33
|
+
Provides `event.data` with keys: `value`, `prev_value`.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
master: Master = None,
|
|
39
|
+
accent: str = None,
|
|
40
|
+
|
|
41
|
+
# value parameters
|
|
42
|
+
value: int | float = 0,
|
|
43
|
+
minvalue: int | float = 0,
|
|
44
|
+
maxvalue: int | float = 100,
|
|
45
|
+
value_format: str = "{:.0f}",
|
|
46
|
+
value_prefix: str = None,
|
|
47
|
+
value_suffix: str = None,
|
|
48
|
+
value_font: str = None,
|
|
49
|
+
dtype: type[int] | type[float] = int,
|
|
50
|
+
|
|
51
|
+
# secondary options
|
|
52
|
+
secondary_font: str = None,
|
|
53
|
+
secondary_style: str = None,
|
|
54
|
+
subtitle: str = None,
|
|
55
|
+
|
|
56
|
+
# appearance
|
|
57
|
+
size: int = 200,
|
|
58
|
+
thickness: int = 10,
|
|
59
|
+
indicator_width: int = 0,
|
|
60
|
+
segment_width: int = 0,
|
|
61
|
+
arc_range: int = None,
|
|
62
|
+
arc_offset: int = None,
|
|
63
|
+
|
|
64
|
+
# other
|
|
65
|
+
meter_type: Literal['semi', 'full'] = 'full',
|
|
66
|
+
show_text: bool = True,
|
|
67
|
+
interactive: bool = False,
|
|
68
|
+
step_size: int | float = 1,
|
|
69
|
+
**kwargs: Any
|
|
70
|
+
):
|
|
71
|
+
"""Initialize a Meter widget.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
master: The parent widget.
|
|
75
|
+
accent: Accent token for the meter indicator (e.g., 'primary', 'success').
|
|
76
|
+
bootstyle: DEPRECATED - Use `accent` instead.
|
|
77
|
+
|
|
78
|
+
value: Current meter value.
|
|
79
|
+
minvalue: Minimum value for the meter range.
|
|
80
|
+
maxvalue: Maximum value for the meter range.
|
|
81
|
+
value_format: Format string for displaying the value (e.g., "{:.0f}", "{:.2f}").
|
|
82
|
+
value_prefix: Text to display before the value (e.g., "$", "@").
|
|
83
|
+
value_suffix: Text to display after the value (e.g., "%", "mph").
|
|
84
|
+
value_font: Font specification for the value text (e.g., "-size 36 -weight bold").
|
|
85
|
+
dtype: Data type for the value variable (int or float).
|
|
86
|
+
|
|
87
|
+
secondary_font: Font specification for prefix, suffix, and subtitle text.
|
|
88
|
+
secondary_style: Style name for prefix, suffix, and subtitle color.
|
|
89
|
+
subtitle: Optional subtitle text displayed below the value.
|
|
90
|
+
|
|
91
|
+
size: Width and height of the meter in pixels.
|
|
92
|
+
thickness: Width of the meter arc in pixels.
|
|
93
|
+
indicator_width: Width of the indicator segment when using a wedge-style indicator.
|
|
94
|
+
0 means fill from start to current value.
|
|
95
|
+
segment_width: Width of each segment for a segmented meter style. 0 means solid.
|
|
96
|
+
arc_range: Total arc range in degrees. None uses defaults (360 for full, 270 for semi).
|
|
97
|
+
arc_offset: Starting angle offset in degrees. None uses defaults (-90 for full, 135 for semi).
|
|
98
|
+
|
|
99
|
+
meter_type: Meter style - 'full' for full circle or 'semi' for semicircle.
|
|
100
|
+
show_text: Whether to display the value text and labels.
|
|
101
|
+
interactive: Whether the meter responds to mouse clicks/drags to change value.
|
|
102
|
+
step_size: Increment step when in interactive mode.
|
|
103
|
+
**kwargs: Additional keyword arguments passed to the Frame parent class.
|
|
104
|
+
|
|
105
|
+
!!! note "Events"
|
|
106
|
+
- `<<Change>>`: Emitted when the value changes (see on_changed()).
|
|
107
|
+
"""
|
|
108
|
+
legacy = Meter._coerce_legacy_params(kwargs)
|
|
109
|
+
super().__init__(master, **kwargs)
|
|
110
|
+
|
|
111
|
+
# configuration
|
|
112
|
+
self._dtype = dtype
|
|
113
|
+
self._size = legacy.get('size', size)
|
|
114
|
+
self._thickness = legacy.get('thickness', thickness)
|
|
115
|
+
self._indicator_width = legacy.get('indicator_width', indicator_width)
|
|
116
|
+
self._segment_width = legacy.get('segment_width', segment_width)
|
|
117
|
+
self._arc_range = legacy.get('arc_range', arc_range)
|
|
118
|
+
self._arc_offset = legacy.get('arc_offset', arc_offset)
|
|
119
|
+
self._minvalue = legacy.get('minvalue', minvalue)
|
|
120
|
+
self._maxvalue = legacy.get('maxvalue', maxvalue)
|
|
121
|
+
|
|
122
|
+
self._meter_type = legacy.get('meter_type', meter_type)
|
|
123
|
+
self._show_text = legacy.get('show_text', show_text)
|
|
124
|
+
self._interactive = interactive
|
|
125
|
+
self._step_size = legacy.get('step_size', step_size)
|
|
126
|
+
|
|
127
|
+
self._value_format = legacy.get('value_format', value_format)
|
|
128
|
+
self._value_font = legacy.get('value_font', value_font or '-size 36 -weight bold')
|
|
129
|
+
self._value_prefix = legacy.get('value_prefix', value_prefix)
|
|
130
|
+
self._value_suffix = legacy.get('value_suffix', value_suffix)
|
|
131
|
+
self._accent = accent or 'primary'
|
|
132
|
+
|
|
133
|
+
self._subtitle = legacy.get('subtitle', subtitle)
|
|
134
|
+
self._secondary_font = legacy.get('secondary_font', secondary_font or '-size 9')
|
|
135
|
+
self._secondary_style = legacy.get('secondary_style', secondary_style or 'background[muted]')
|
|
136
|
+
|
|
137
|
+
# color tokens (separate from resolved colors)
|
|
138
|
+
self._surface_token = 'background' # Token name for surface color
|
|
139
|
+
|
|
140
|
+
# state tracking
|
|
141
|
+
self._towards_maximum = True
|
|
142
|
+
self._resolve_arc_range_offset(meter_type, arc_offset, arc_range)
|
|
143
|
+
self._binding = {}
|
|
144
|
+
|
|
145
|
+
# widget variables
|
|
146
|
+
value = legacy.get('value', value)
|
|
147
|
+
self._last_changed_value = value
|
|
148
|
+
self._value_var = self._variable(value)
|
|
149
|
+
self._value_var.trace_add('write', self._update_meter) # Update meter when value changes
|
|
150
|
+
self._value_display_var = StringVar(value=value_format.format(value))
|
|
151
|
+
self._subtitle_var = StringVar(value=self._subtitle)
|
|
152
|
+
|
|
153
|
+
# Resolve styles first to get colors
|
|
154
|
+
self._resolve_meter_styles()
|
|
155
|
+
|
|
156
|
+
# layout - use canvas for both meter and text
|
|
157
|
+
self._canvas = Canvas(
|
|
158
|
+
master=self,
|
|
159
|
+
width=self._size,
|
|
160
|
+
height=self._size,
|
|
161
|
+
highlightthickness=0,
|
|
162
|
+
background=self._surface
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
# Canvas text items (will be created/updated in _draw_meter)
|
|
166
|
+
self._meter_image_id = None
|
|
167
|
+
self._value_text_id = None
|
|
168
|
+
self._prefix_text_id = None
|
|
169
|
+
self._suffix_text_id = None
|
|
170
|
+
self._subtitle_text_id = None
|
|
171
|
+
|
|
172
|
+
# update bindings
|
|
173
|
+
self._binding['<<ThemeChanged>>'] = self.bind('<<ThemeChanged>>', self._handle_theme_changed)
|
|
174
|
+
self._binding['<<Configure>>'] = self.bind('<<Configure>>', self._handle_theme_changed)
|
|
175
|
+
self._bind_interactive_events()
|
|
176
|
+
self._draw_base_meter_images()
|
|
177
|
+
self._draw_meter()
|
|
178
|
+
|
|
179
|
+
# set widget geometry
|
|
180
|
+
self._canvas.pack()
|
|
181
|
+
|
|
182
|
+
# ----- Configuration Delegates -----
|
|
183
|
+
|
|
184
|
+
@staticmethod
|
|
185
|
+
def _coerce_legacy_params(options):
|
|
186
|
+
param_map = dict(
|
|
187
|
+
amountused="value",
|
|
188
|
+
amountmin="minvalue",
|
|
189
|
+
amounttotal="maxvalue",
|
|
190
|
+
amountformat="value_format",
|
|
191
|
+
textleft="value_prefix",
|
|
192
|
+
textright="value_suffix",
|
|
193
|
+
textfont="value_font",
|
|
194
|
+
subtextfont="secondary_font",
|
|
195
|
+
subtextstyle="secondary_style",
|
|
196
|
+
subtext="subtitle",
|
|
197
|
+
metersize="size",
|
|
198
|
+
meterthickness="thickness",
|
|
199
|
+
wedgesize="indicator_width",
|
|
200
|
+
stripethickness="segment_width",
|
|
201
|
+
arcrange="arc_range",
|
|
202
|
+
arcoffset="arc_offset",
|
|
203
|
+
metertype="meter_type",
|
|
204
|
+
showtext="show_text",
|
|
205
|
+
stepsize="step_size"
|
|
206
|
+
)
|
|
207
|
+
legacy_params = dict()
|
|
208
|
+
legacy_keys = set()
|
|
209
|
+
|
|
210
|
+
for k, v in options.items():
|
|
211
|
+
if k in param_map:
|
|
212
|
+
legacy_params[param_map[k]] = v
|
|
213
|
+
legacy_keys.add(k)
|
|
214
|
+
|
|
215
|
+
if legacy_params:
|
|
216
|
+
for k in legacy_keys:
|
|
217
|
+
del options[k]
|
|
218
|
+
warn(
|
|
219
|
+
f'You are using a param signature for Meter which is deprecated. {legacy_keys}. See reference map: {param_map}',
|
|
220
|
+
DeprecationWarning)
|
|
221
|
+
return legacy_params
|
|
222
|
+
|
|
223
|
+
@property
|
|
224
|
+
def value(self):
|
|
225
|
+
return self._value_var.get()
|
|
226
|
+
|
|
227
|
+
@value.setter
|
|
228
|
+
def value(self, value: int | float):
|
|
229
|
+
self._value_var.set(value)
|
|
230
|
+
|
|
231
|
+
@property
|
|
232
|
+
def subtitle(self):
|
|
233
|
+
return self._subtitle_var.get()
|
|
234
|
+
|
|
235
|
+
@subtitle.setter
|
|
236
|
+
def subtitle(self, value: str):
|
|
237
|
+
self._subtitle = value
|
|
238
|
+
self._subtitle_var.set(value)
|
|
239
|
+
|
|
240
|
+
# ------ Value API Methods ------
|
|
241
|
+
|
|
242
|
+
def get(self):
|
|
243
|
+
"""Return the current meter value.
|
|
244
|
+
|
|
245
|
+
This is part of the standard value-widget API. It is equivalent
|
|
246
|
+
to accessing the `.value` property.
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
The current meter value (int or float depending on dtype).
|
|
250
|
+
"""
|
|
251
|
+
return self.value
|
|
252
|
+
|
|
253
|
+
def set(self, value):
|
|
254
|
+
"""Set the meter value.
|
|
255
|
+
|
|
256
|
+
This is part of the standard value-widget API. It is equivalent
|
|
257
|
+
to setting the `.value` property.
|
|
258
|
+
|
|
259
|
+
Args:
|
|
260
|
+
value: The new meter value.
|
|
261
|
+
"""
|
|
262
|
+
self.value = value
|
|
263
|
+
|
|
264
|
+
# ------ Configuration Delegates ------
|
|
265
|
+
|
|
266
|
+
@configure_delegate('accent')
|
|
267
|
+
def _delegate_accent(self, value=None):
|
|
268
|
+
if value is None:
|
|
269
|
+
return self._accent
|
|
270
|
+
else:
|
|
271
|
+
self._accent = value
|
|
272
|
+
self._resolve_meter_styles()
|
|
273
|
+
self._draw_meter()
|
|
274
|
+
return None
|
|
275
|
+
|
|
276
|
+
@configure_delegate('value')
|
|
277
|
+
def _delegate_value(self, value=None):
|
|
278
|
+
if value is None:
|
|
279
|
+
return self.value
|
|
280
|
+
else:
|
|
281
|
+
self.value = value
|
|
282
|
+
return None
|
|
283
|
+
|
|
284
|
+
@configure_delegate('minvalue')
|
|
285
|
+
def _delegate_minvalue(self, value=None):
|
|
286
|
+
if value is None:
|
|
287
|
+
return self._minvalue
|
|
288
|
+
else:
|
|
289
|
+
self._minvalue = value
|
|
290
|
+
self._draw_meter()
|
|
291
|
+
return None
|
|
292
|
+
|
|
293
|
+
@configure_delegate('maxvalue')
|
|
294
|
+
def _delegate_maxvalue(self, value=None):
|
|
295
|
+
if value is None:
|
|
296
|
+
return self._maxvalue
|
|
297
|
+
else:
|
|
298
|
+
self._maxvalue = value
|
|
299
|
+
self._draw_meter()
|
|
300
|
+
return None
|
|
301
|
+
|
|
302
|
+
@configure_delegate('value_format')
|
|
303
|
+
def _delegate_value_format(self, value=None):
|
|
304
|
+
if value is None:
|
|
305
|
+
return self._value_format
|
|
306
|
+
else:
|
|
307
|
+
self._value_format = value
|
|
308
|
+
self._draw_meter()
|
|
309
|
+
return None
|
|
310
|
+
|
|
311
|
+
@configure_delegate('value_prefix')
|
|
312
|
+
def _delegate_value_prefix(self, value=None):
|
|
313
|
+
if value is None:
|
|
314
|
+
return self._value_prefix
|
|
315
|
+
else:
|
|
316
|
+
self._value_prefix = value
|
|
317
|
+
self._draw_meter()
|
|
318
|
+
return None
|
|
319
|
+
|
|
320
|
+
@configure_delegate('value_suffix')
|
|
321
|
+
def _delegate_value_suffix(self, value=None):
|
|
322
|
+
if value is None:
|
|
323
|
+
return self._value_suffix
|
|
324
|
+
else:
|
|
325
|
+
self._value_suffix = value
|
|
326
|
+
self._draw_meter()
|
|
327
|
+
return None
|
|
328
|
+
|
|
329
|
+
@configure_delegate('value_font')
|
|
330
|
+
def _delegate_value_font(self, value=None):
|
|
331
|
+
if value is None:
|
|
332
|
+
return self._value_font
|
|
333
|
+
else:
|
|
334
|
+
self._value_font = value
|
|
335
|
+
self._draw_meter()
|
|
336
|
+
return None
|
|
337
|
+
|
|
338
|
+
@configure_delegate('dtype')
|
|
339
|
+
def _delegate_dtype(self, value=None):
|
|
340
|
+
if value is None:
|
|
341
|
+
return self._dtype
|
|
342
|
+
else:
|
|
343
|
+
warn('dtype is only configurable in the widget constructor', ConfigurationWarning)
|
|
344
|
+
return None
|
|
345
|
+
|
|
346
|
+
@configure_delegate('secondary_font')
|
|
347
|
+
def _delegate_secondary_font(self, value=None):
|
|
348
|
+
if value is None:
|
|
349
|
+
return self._secondary_font
|
|
350
|
+
else:
|
|
351
|
+
self._secondary_font = value
|
|
352
|
+
self._draw_meter()
|
|
353
|
+
return None
|
|
354
|
+
|
|
355
|
+
@configure_delegate('secondary_style')
|
|
356
|
+
def _delegate_secondary_style(self, value=None):
|
|
357
|
+
if value is None:
|
|
358
|
+
return self._secondary_style
|
|
359
|
+
else:
|
|
360
|
+
self._secondary_style = value
|
|
361
|
+
self._resolve_meter_styles()
|
|
362
|
+
self._draw_meter()
|
|
363
|
+
return None
|
|
364
|
+
|
|
365
|
+
@configure_delegate('subtitle')
|
|
366
|
+
def _delegate_subtitle(self, value=None):
|
|
367
|
+
if value is None:
|
|
368
|
+
return self.subtitle
|
|
369
|
+
else:
|
|
370
|
+
self.subtitle = value
|
|
371
|
+
self._draw_meter()
|
|
372
|
+
return None
|
|
373
|
+
|
|
374
|
+
@configure_delegate('size')
|
|
375
|
+
def _delegate_size(self, value=None):
|
|
376
|
+
if value is None:
|
|
377
|
+
return self._size
|
|
378
|
+
else:
|
|
379
|
+
self._size = value
|
|
380
|
+
self._canvas.configure(width=value, height=value)
|
|
381
|
+
self._draw_base_meter_images()
|
|
382
|
+
self._draw_meter()
|
|
383
|
+
return None
|
|
384
|
+
|
|
385
|
+
@configure_delegate('thickness')
|
|
386
|
+
def _delegate_thickness(self, value=None):
|
|
387
|
+
if value is None:
|
|
388
|
+
return self._thickness
|
|
389
|
+
else:
|
|
390
|
+
self._thickness = value
|
|
391
|
+
self._draw_base_meter_images()
|
|
392
|
+
self._draw_meter()
|
|
393
|
+
return None
|
|
394
|
+
|
|
395
|
+
@configure_delegate('indicator_width')
|
|
396
|
+
def _delegate_indicator_width(self, value=None):
|
|
397
|
+
if value is None:
|
|
398
|
+
return self._indicator_width
|
|
399
|
+
else:
|
|
400
|
+
self._indicator_width = value
|
|
401
|
+
self._draw_meter()
|
|
402
|
+
return None
|
|
403
|
+
|
|
404
|
+
@configure_delegate('segment_width')
|
|
405
|
+
def _delegate_segment_width(self, value=None):
|
|
406
|
+
if value is None:
|
|
407
|
+
return self._segment_width
|
|
408
|
+
else:
|
|
409
|
+
self._segment_width = value
|
|
410
|
+
self._draw_base_meter_images()
|
|
411
|
+
self._draw_meter()
|
|
412
|
+
return None
|
|
413
|
+
|
|
414
|
+
@configure_delegate('arc_range')
|
|
415
|
+
def _delegate_arc_range(self, value=None):
|
|
416
|
+
if value is None:
|
|
417
|
+
return self._arc_range
|
|
418
|
+
else:
|
|
419
|
+
self._arc_range = value
|
|
420
|
+
self._draw_base_meter_images()
|
|
421
|
+
self._draw_meter()
|
|
422
|
+
return None
|
|
423
|
+
|
|
424
|
+
@configure_delegate('arc_offset')
|
|
425
|
+
def _delegate_arc_offset(self, value=None):
|
|
426
|
+
if value is None:
|
|
427
|
+
return self._arc_offset
|
|
428
|
+
else:
|
|
429
|
+
self._arc_offset = value
|
|
430
|
+
self._draw_base_meter_images()
|
|
431
|
+
self._draw_meter()
|
|
432
|
+
return None
|
|
433
|
+
|
|
434
|
+
@configure_delegate('meter_type')
|
|
435
|
+
def _delegate_meter_type(self, value=None):
|
|
436
|
+
if value is None:
|
|
437
|
+
return self._meter_type
|
|
438
|
+
else:
|
|
439
|
+
self._resolve_arc_range_offset(value, self._arc_offset, self._arc_range)
|
|
440
|
+
self._draw_base_meter_images()
|
|
441
|
+
self._draw_meter()
|
|
442
|
+
return None
|
|
443
|
+
|
|
444
|
+
@configure_delegate('show_text')
|
|
445
|
+
def _delegate_show_text(self, value=None):
|
|
446
|
+
if value is None:
|
|
447
|
+
return self._show_text
|
|
448
|
+
else:
|
|
449
|
+
self._show_text = value
|
|
450
|
+
self._draw_meter()
|
|
451
|
+
return None
|
|
452
|
+
|
|
453
|
+
@configure_delegate('interactive')
|
|
454
|
+
def _delegate_interactive(self, value=None):
|
|
455
|
+
if value is None:
|
|
456
|
+
return self._interactive
|
|
457
|
+
else:
|
|
458
|
+
self._interactive = value
|
|
459
|
+
self._bind_interactive_events()
|
|
460
|
+
return None
|
|
461
|
+
|
|
462
|
+
@configure_delegate('step_size')
|
|
463
|
+
def _delegate_step_size(self, value=None):
|
|
464
|
+
if value is None:
|
|
465
|
+
return self._step_size
|
|
466
|
+
else:
|
|
467
|
+
self._step_size = value
|
|
468
|
+
return None
|
|
469
|
+
|
|
470
|
+
def _variable(self, value: int | float):
|
|
471
|
+
return IntVar(value=value) if self._dtype is int else DoubleVar(value=value)
|
|
472
|
+
|
|
473
|
+
def _update_meter(self, *_: Any):
|
|
474
|
+
"""Update meter display when value changes."""
|
|
475
|
+
value = self._value_var.get()
|
|
476
|
+
self._value_display_var.set(self._value_format.format(value))
|
|
477
|
+
self._draw_meter()
|
|
478
|
+
if value != self._last_changed_value:
|
|
479
|
+
prev_value = self._last_changed_value
|
|
480
|
+
self._last_changed_value = value
|
|
481
|
+
self.event_generate('<<Change>>', data={"value": value, "prev_value": prev_value})
|
|
482
|
+
|
|
483
|
+
def _resolve_meter_styles(self):
|
|
484
|
+
"""Resolve theme colors for meter indicator, trough, and text."""
|
|
485
|
+
from bootstack.style.style import get_style
|
|
486
|
+
style = get_style()
|
|
487
|
+
b = style.style_builder
|
|
488
|
+
|
|
489
|
+
# Resolve colors from tokens
|
|
490
|
+
accent_token = self._accent or 'primary'
|
|
491
|
+
accent_color = b.color(accent_token)
|
|
492
|
+
|
|
493
|
+
# Use _surface_token to get the token name, resolve to actual color
|
|
494
|
+
surface_token = getattr(self, '_surface_token', 'background')
|
|
495
|
+
surface = b.color(surface_token)
|
|
496
|
+
trough_color = b.border(surface)
|
|
497
|
+
|
|
498
|
+
# Get text colors
|
|
499
|
+
value_text_color = b.color(accent_token)
|
|
500
|
+
secondary_text_color = b.color(self._secondary_style or 'background[muted]')
|
|
501
|
+
|
|
502
|
+
self._image_scale = b.scale(DEFAULT_IMAGE_SCALE)
|
|
503
|
+
self._accent_color = accent_color
|
|
504
|
+
self._surface = surface # Store resolved color
|
|
505
|
+
self._trough_color = trough_color
|
|
506
|
+
self._value_text_color = value_text_color
|
|
507
|
+
self._secondary_text_color = secondary_text_color
|
|
508
|
+
|
|
509
|
+
def _bind_interactive_events(self):
|
|
510
|
+
seq1 = '<B1-Motion>'
|
|
511
|
+
seq2 = '<Button-1>'
|
|
512
|
+
|
|
513
|
+
if self._interactive:
|
|
514
|
+
self._binding[seq1] = self._canvas.bind(seq1, self._handle_interaction)
|
|
515
|
+
self._binding[seq2] = self._canvas.bind(seq2, self._handle_interaction)
|
|
516
|
+
return
|
|
517
|
+
|
|
518
|
+
if seq1 in self._binding:
|
|
519
|
+
self._canvas.unbind(seq1, self._binding[seq1])
|
|
520
|
+
self._canvas.unbind(seq2, self._binding[seq2])
|
|
521
|
+
self._binding.clear()
|
|
522
|
+
|
|
523
|
+
def _resolve_arc_range_offset(self, meter_type: str, arc_offset: int | None, arc_range: int | None):
|
|
524
|
+
"""Set default arc parameters based on meter type (full or semi)."""
|
|
525
|
+
if meter_type == 'semi':
|
|
526
|
+
self._arc_offset = 135 if arc_offset is None else arc_offset
|
|
527
|
+
self._arc_range = 270 if arc_range is None else arc_range
|
|
528
|
+
else:
|
|
529
|
+
self._arc_offset = -90 if arc_offset is None else arc_offset
|
|
530
|
+
self._arc_range = 360 if arc_range is None else arc_range
|
|
531
|
+
self._meter_type = meter_type
|
|
532
|
+
|
|
533
|
+
def _draw_meter(self):
|
|
534
|
+
"""Draw meter indicator and text on canvas."""
|
|
535
|
+
# Draw meter indicator
|
|
536
|
+
img = self._base_image.copy()
|
|
537
|
+
draw = ImageDraw.Draw(img)
|
|
538
|
+
if self._segment_width > 0:
|
|
539
|
+
self._draw_segment_indicator(draw)
|
|
540
|
+
else:
|
|
541
|
+
self._draw_solid_indicator(draw)
|
|
542
|
+
|
|
543
|
+
self._meter_image = ImageTk.PhotoImage(
|
|
544
|
+
img.resize(
|
|
545
|
+
(self._size, self._size),
|
|
546
|
+
Resampling.BILINEAR
|
|
547
|
+
)
|
|
548
|
+
)
|
|
549
|
+
|
|
550
|
+
# Update or create image on canvas
|
|
551
|
+
if self._meter_image_id is None:
|
|
552
|
+
self._meter_image_id = self._canvas.create_image(
|
|
553
|
+
0, 0,
|
|
554
|
+
image=self._meter_image,
|
|
555
|
+
anchor='nw'
|
|
556
|
+
)
|
|
557
|
+
else:
|
|
558
|
+
self._canvas.itemconfig(self._meter_image_id, image=self._meter_image)
|
|
559
|
+
|
|
560
|
+
# Draw text if enabled
|
|
561
|
+
if self._show_text:
|
|
562
|
+
self._draw_text_on_canvas()
|
|
563
|
+
else:
|
|
564
|
+
# Hide text elements
|
|
565
|
+
self._hide_text_items()
|
|
566
|
+
# Show subtitle if it exists
|
|
567
|
+
if self._subtitle:
|
|
568
|
+
self._draw_subtitle_centered()
|
|
569
|
+
|
|
570
|
+
def _draw_text_on_canvas(self):
|
|
571
|
+
"""Draw value text with optional prefix, suffix, and subtitle on canvas."""
|
|
572
|
+
value_text = self._value_display_var.get()
|
|
573
|
+
center_x = self._size / 2
|
|
574
|
+
center_y = self._size / 2
|
|
575
|
+
|
|
576
|
+
# Create font objects once
|
|
577
|
+
value_font = tkfont.Font(font=self._value_font)
|
|
578
|
+
secondary_font = tkfont.Font(font=self._secondary_font)
|
|
579
|
+
|
|
580
|
+
# Get font metrics
|
|
581
|
+
value_metrics = value_font.metrics()
|
|
582
|
+
secondary_metrics = secondary_font.metrics()
|
|
583
|
+
|
|
584
|
+
value_height = value_metrics['ascent'] + value_metrics['descent']
|
|
585
|
+
secondary_height = secondary_metrics['ascent'] + secondary_metrics['descent']
|
|
586
|
+
|
|
587
|
+
# Calculate max height of value line (value + prefix/suffix)
|
|
588
|
+
max_text_height = max(value_height, secondary_height) if (
|
|
589
|
+
self._value_prefix or self._value_suffix) else value_height
|
|
590
|
+
|
|
591
|
+
# Calculate total block height including subtitle
|
|
592
|
+
subtitle_height = secondary_height if self._subtitle else 0
|
|
593
|
+
subtitle_gap = -4 if self._subtitle else 0
|
|
594
|
+
total_height = max_text_height + subtitle_gap + subtitle_height
|
|
595
|
+
|
|
596
|
+
# Position value text to center the entire block
|
|
597
|
+
block_top = center_y - (total_height / 2)
|
|
598
|
+
value_y = block_top + (max_text_height / 2)
|
|
599
|
+
|
|
600
|
+
# Calculate value text baseline for prefix/suffix alignment
|
|
601
|
+
value_baseline_y = value_y + (value_metrics['ascent'] - value_metrics['descent']) / 2
|
|
602
|
+
|
|
603
|
+
# Draw value text
|
|
604
|
+
self._value_text_id = self._update_or_create_text(
|
|
605
|
+
self._value_text_id, center_x, value_y,
|
|
606
|
+
value_text, self._value_font, self._value_text_color, 'center'
|
|
607
|
+
)
|
|
608
|
+
|
|
609
|
+
# Position and draw prefix/suffix
|
|
610
|
+
if self._value_prefix or self._value_suffix:
|
|
611
|
+
value_width = value_font.measure(value_text)
|
|
612
|
+
horizontal_gap = 4
|
|
613
|
+
value_left_x = center_x - (value_width / 2) - horizontal_gap
|
|
614
|
+
value_right_x = center_x + (value_width / 2) + horizontal_gap - 1
|
|
615
|
+
|
|
616
|
+
# Calculate y position for prefix/suffix (baseline aligned, slightly raised)
|
|
617
|
+
secondary_y = value_baseline_y - (secondary_metrics['ascent'] - secondary_metrics['descent']) / 2 - 4
|
|
618
|
+
|
|
619
|
+
if self._value_prefix:
|
|
620
|
+
self._prefix_text_id = self._update_or_create_text(
|
|
621
|
+
self._prefix_text_id, value_left_x, secondary_y,
|
|
622
|
+
self._value_prefix, self._secondary_font, self._secondary_text_color, 'e'
|
|
623
|
+
)
|
|
624
|
+
elif self._prefix_text_id:
|
|
625
|
+
self._canvas.itemconfig(self._prefix_text_id, state='hidden')
|
|
626
|
+
|
|
627
|
+
if self._value_suffix:
|
|
628
|
+
self._suffix_text_id = self._update_or_create_text(
|
|
629
|
+
self._suffix_text_id, value_right_x, secondary_y,
|
|
630
|
+
self._value_suffix, self._secondary_font, self._secondary_text_color, 'w'
|
|
631
|
+
)
|
|
632
|
+
elif self._suffix_text_id:
|
|
633
|
+
self._canvas.itemconfig(self._suffix_text_id, state='hidden')
|
|
634
|
+
|
|
635
|
+
# Draw subtitle
|
|
636
|
+
if self._subtitle:
|
|
637
|
+
subtitle_y = value_y + (max_text_height / 2) - 4
|
|
638
|
+
self._subtitle_text_id = self._update_or_create_text(
|
|
639
|
+
self._subtitle_text_id, center_x, subtitle_y,
|
|
640
|
+
self._subtitle_var.get(), self._secondary_font, self._secondary_text_color, 'n'
|
|
641
|
+
)
|
|
642
|
+
elif self._subtitle_text_id:
|
|
643
|
+
self._canvas.itemconfig(self._subtitle_text_id, state='hidden')
|
|
644
|
+
|
|
645
|
+
def _update_or_create_text(self, item_id, x, y, text, font, fill, anchor):
|
|
646
|
+
"""Update existing canvas text item or create new one.
|
|
647
|
+
|
|
648
|
+
Args:
|
|
649
|
+
item_id: Existing canvas item ID or None to create new.
|
|
650
|
+
x: X coordinate for text position.
|
|
651
|
+
y: Y coordinate for text position.
|
|
652
|
+
text: Text string to display.
|
|
653
|
+
font: Font specification.
|
|
654
|
+
fill: Text color.
|
|
655
|
+
anchor: Text anchor position (e.g., 'center', 'e', 'w').
|
|
656
|
+
|
|
657
|
+
Returns:
|
|
658
|
+
Canvas item ID.
|
|
659
|
+
"""
|
|
660
|
+
if item_id is None:
|
|
661
|
+
return self._canvas.create_text(x, y, text=text, font=font, fill=fill, anchor=anchor)
|
|
662
|
+
else:
|
|
663
|
+
self._canvas.itemconfig(item_id, text=text, font=font, fill=fill, state='normal')
|
|
664
|
+
self._canvas.coords(item_id, x, y)
|
|
665
|
+
return item_id
|
|
666
|
+
|
|
667
|
+
def _hide_text_items(self):
|
|
668
|
+
"""Hide all text items on canvas."""
|
|
669
|
+
if self._value_text_id:
|
|
670
|
+
self._canvas.itemconfig(self._value_text_id, state='hidden')
|
|
671
|
+
if self._prefix_text_id:
|
|
672
|
+
self._canvas.itemconfig(self._prefix_text_id, state='hidden')
|
|
673
|
+
if self._suffix_text_id:
|
|
674
|
+
self._canvas.itemconfig(self._suffix_text_id, state='hidden')
|
|
675
|
+
if self._subtitle_text_id:
|
|
676
|
+
self._canvas.itemconfig(self._subtitle_text_id, state='hidden')
|
|
677
|
+
|
|
678
|
+
def _draw_subtitle_centered(self):
|
|
679
|
+
"""Draw subtitle centered when show_text is False."""
|
|
680
|
+
center_x = self._size // 2
|
|
681
|
+
center_y = self._size // 2
|
|
682
|
+
|
|
683
|
+
if self._subtitle_text_id is None:
|
|
684
|
+
self._subtitle_text_id = self._canvas.create_text(
|
|
685
|
+
center_x, center_y,
|
|
686
|
+
text=self._subtitle_var.get(),
|
|
687
|
+
font=self._secondary_font,
|
|
688
|
+
fill=self._secondary_text_color,
|
|
689
|
+
anchor='center'
|
|
690
|
+
)
|
|
691
|
+
else:
|
|
692
|
+
self._canvas.itemconfig(
|
|
693
|
+
self._subtitle_text_id,
|
|
694
|
+
text=self._subtitle_var.get(),
|
|
695
|
+
font=self._secondary_font,
|
|
696
|
+
fill=self._secondary_text_color,
|
|
697
|
+
state='normal'
|
|
698
|
+
)
|
|
699
|
+
self._canvas.coords(self._subtitle_text_id, center_x, center_y)
|
|
700
|
+
|
|
701
|
+
def _draw_base_meter_images(self):
|
|
702
|
+
"""Draw meter background trough at high resolution."""
|
|
703
|
+
self._resolve_meter_styles()
|
|
704
|
+
self._base_image = Image.new(
|
|
705
|
+
mode='RGBA',
|
|
706
|
+
size=(self._size * self._image_scale, self._size * self._image_scale),
|
|
707
|
+
)
|
|
708
|
+
draw = ImageDraw.Draw(self._base_image)
|
|
709
|
+
|
|
710
|
+
# Center the arc with equal margins on all sides
|
|
711
|
+
margin = 10
|
|
712
|
+
x1 = y1 = self._size * self._image_scale - margin
|
|
713
|
+
width = self._thickness * self._image_scale
|
|
714
|
+
|
|
715
|
+
if self._segment_width > 0:
|
|
716
|
+
# segmented meter
|
|
717
|
+
minvalue = self._arc_offset
|
|
718
|
+
maxvalue = self._arc_range + self._arc_offset
|
|
719
|
+
step = 2 if self._segment_width == 1 else self._segment_width
|
|
720
|
+
|
|
721
|
+
for x in range(minvalue, maxvalue, step):
|
|
722
|
+
draw.arc(
|
|
723
|
+
xy=(margin, margin, x1, y1),
|
|
724
|
+
start=x,
|
|
725
|
+
end=x + self._segment_width - 1,
|
|
726
|
+
fill=self._trough_color,
|
|
727
|
+
width=width
|
|
728
|
+
)
|
|
729
|
+
else:
|
|
730
|
+
# default meter
|
|
731
|
+
draw.arc(
|
|
732
|
+
xy=(margin, margin, x1, y1),
|
|
733
|
+
start=self._arc_offset,
|
|
734
|
+
end=self._arc_range + self._arc_offset,
|
|
735
|
+
fill=self._trough_color,
|
|
736
|
+
width=width
|
|
737
|
+
)
|
|
738
|
+
|
|
739
|
+
def _draw_solid_indicator(self, draw):
|
|
740
|
+
"""Draw solid arc indicator from start to current value."""
|
|
741
|
+
margin = 10
|
|
742
|
+
x1 = y1 = self._size * self._image_scale - margin
|
|
743
|
+
width = self._thickness * self._image_scale
|
|
744
|
+
value_degrees = self._meter_value_as_degrees()
|
|
745
|
+
|
|
746
|
+
if self._indicator_width > 0:
|
|
747
|
+
draw.arc(
|
|
748
|
+
xy=(margin, margin, x1, y1),
|
|
749
|
+
start=value_degrees - self._indicator_width,
|
|
750
|
+
end=value_degrees + self._indicator_width,
|
|
751
|
+
fill=self._accent_color,
|
|
752
|
+
width=width
|
|
753
|
+
)
|
|
754
|
+
else:
|
|
755
|
+
draw.arc(
|
|
756
|
+
xy=(margin, margin, x1, y1),
|
|
757
|
+
start=self._arc_offset,
|
|
758
|
+
end=value_degrees,
|
|
759
|
+
fill=self._accent_color,
|
|
760
|
+
width=width
|
|
761
|
+
)
|
|
762
|
+
|
|
763
|
+
def _draw_segment_indicator(self, draw):
|
|
764
|
+
"""Draw segmented arc indicator from start to current value."""
|
|
765
|
+
value_degrees = self._meter_value_as_degrees()
|
|
766
|
+
margin = 10
|
|
767
|
+
x1 = y1 = self._size * self._image_scale - margin
|
|
768
|
+
width = self._thickness * self._image_scale
|
|
769
|
+
|
|
770
|
+
if self._indicator_width > 0:
|
|
771
|
+
draw.arc(
|
|
772
|
+
xy=(margin, margin, x1, y1),
|
|
773
|
+
start=value_degrees - self._indicator_width,
|
|
774
|
+
end=value_degrees + self._indicator_width,
|
|
775
|
+
fill=self._accent_color,
|
|
776
|
+
width=width
|
|
777
|
+
)
|
|
778
|
+
else:
|
|
779
|
+
# Draw segments from arc start to current value in degrees
|
|
780
|
+
minvalue = self._arc_offset
|
|
781
|
+
maxvalue = value_degrees - 1
|
|
782
|
+
step = self._segment_width
|
|
783
|
+
|
|
784
|
+
for x in range(minvalue, maxvalue, step):
|
|
785
|
+
draw.arc(
|
|
786
|
+
xy=(margin, margin, x1, y1),
|
|
787
|
+
start=x,
|
|
788
|
+
end=x + self._segment_width - 1,
|
|
789
|
+
fill=self._accent_color,
|
|
790
|
+
width=width
|
|
791
|
+
)
|
|
792
|
+
|
|
793
|
+
def _meter_value_as_degrees(self):
|
|
794
|
+
"""Convert current meter value to arc degrees.
|
|
795
|
+
|
|
796
|
+
Returns:
|
|
797
|
+
Degree value for meter indicator position.
|
|
798
|
+
"""
|
|
799
|
+
minvalue = self._minvalue
|
|
800
|
+
maxvalue = self._maxvalue
|
|
801
|
+
value = self._value_var.get()
|
|
802
|
+
|
|
803
|
+
# normalize to 0-1 range to handle negative values
|
|
804
|
+
range_size = maxvalue - minvalue
|
|
805
|
+
if range_size == 0:
|
|
806
|
+
normalized = 0
|
|
807
|
+
else:
|
|
808
|
+
normalized = (value - minvalue) / range_size
|
|
809
|
+
|
|
810
|
+
return int(normalized * self._arc_range + self._arc_offset)
|
|
811
|
+
|
|
812
|
+
def _handle_theme_changed(self, *_):
|
|
813
|
+
self._resolve_meter_styles()
|
|
814
|
+
self._canvas.configure(background=self._surface)
|
|
815
|
+
self._draw_base_meter_images()
|
|
816
|
+
self._draw_meter()
|
|
817
|
+
|
|
818
|
+
def _handle_interaction(self, e):
|
|
819
|
+
"""Handle mouse clicks/drags to update meter value in interactive mode."""
|
|
820
|
+
dx = e.x - self._size // 2
|
|
821
|
+
dy = e.y - self._size // 2
|
|
822
|
+
rads = math.atan2(dy, dx)
|
|
823
|
+
degrees = math.degrees(rads)
|
|
824
|
+
|
|
825
|
+
if degrees > self._arc_offset:
|
|
826
|
+
factor = degrees - self._arc_offset
|
|
827
|
+
else:
|
|
828
|
+
factor = 360 + degrees - self._arc_offset
|
|
829
|
+
|
|
830
|
+
# clamp the value between `minvalue` and `maxvalue`
|
|
831
|
+
minvalue = self._minvalue
|
|
832
|
+
maxvalue = self._maxvalue
|
|
833
|
+
last_value = self._value_var.get()
|
|
834
|
+
|
|
835
|
+
# calculate the value based on the range
|
|
836
|
+
range_size = maxvalue - minvalue
|
|
837
|
+
value = (range_size / self._arc_range * factor) + minvalue
|
|
838
|
+
|
|
839
|
+
# calculate the value given the stepsize.
|
|
840
|
+
if self._step_size > 0:
|
|
841
|
+
# round to the nearest stepsize
|
|
842
|
+
value = round(value / self._step_size) * self._step_size
|
|
843
|
+
|
|
844
|
+
# if the number is the same, then do not redraw
|
|
845
|
+
if last_value == value:
|
|
846
|
+
return
|
|
847
|
+
|
|
848
|
+
# update the value variable
|
|
849
|
+
self._value_var.set(max(min(value, maxvalue), minvalue))
|
|
850
|
+
|
|
851
|
+
def step(self, delta: int | float = 1):
|
|
852
|
+
"""Increment or decrement meter value with automatic bounce at limits.
|
|
853
|
+
|
|
854
|
+
Args:
|
|
855
|
+
delta: Amount to step by (default 1).
|
|
856
|
+
"""
|
|
857
|
+
value = self._value_var.get()
|
|
858
|
+
minvalue = self._minvalue
|
|
859
|
+
maxvalue = self._maxvalue
|
|
860
|
+
|
|
861
|
+
if self._towards_maximum:
|
|
862
|
+
value_updated = value + delta
|
|
863
|
+
else:
|
|
864
|
+
value_updated = value - delta
|
|
865
|
+
|
|
866
|
+
if value_updated >= maxvalue:
|
|
867
|
+
self._towards_maximum = False
|
|
868
|
+
self._value_var.set(maxvalue - (value_updated - maxvalue))
|
|
869
|
+
elif value_updated < minvalue:
|
|
870
|
+
self._towards_maximum = True
|
|
871
|
+
self._value_var.set(minvalue + (minvalue - value_updated))
|
|
872
|
+
else:
|
|
873
|
+
self._value_var.set(value_updated)
|
|
874
|
+
|
|
875
|
+
def on_changed(self, callback: Callable[[Any], Any]) -> str:
|
|
876
|
+
"""Bind a callback to the `<<Change>>` virtual event."""
|
|
877
|
+
return self.bind('<<Change>>', callback, add="+")
|
|
878
|
+
|
|
879
|
+
def off_changed(self, bind_id: str):
|
|
880
|
+
"""Remove a previously registered `<<Change>>` callback."""
|
|
881
|
+
self.unbind('<<Change>>', bind_id)
|
|
882
|
+
|