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,371 @@
|
|
|
1
|
+
"""Utility functions for bootstack.
|
|
2
|
+
|
|
3
|
+
This module provides various utility functions for common tasks in
|
|
4
|
+
ttksbootstrap applications, including high-DPI support, screen geometry
|
|
5
|
+
calculations, and color manipulations.
|
|
6
|
+
|
|
7
|
+
Functions:
|
|
8
|
+
enable_high_dpi_awareness: Enable high-DPI scaling on Windows/Linux
|
|
9
|
+
detect_scale_factor: Detect the appropriate scale factor for the display
|
|
10
|
+
scale_size: Scale a size value for high-DPI displays
|
|
11
|
+
get_desktop_geometry: Get the screen dimensions
|
|
12
|
+
get_asset_path: Get the path to an asset file
|
|
13
|
+
|
|
14
|
+
Example:
|
|
15
|
+
```python
|
|
16
|
+
from bootstack.utility import enable_high_dpi_awareness
|
|
17
|
+
import bootstack as bs
|
|
18
|
+
|
|
19
|
+
# Enable high-DPI before creating window
|
|
20
|
+
enable_high_dpi_awareness()
|
|
21
|
+
|
|
22
|
+
root = bs.Window()
|
|
23
|
+
root.mainloop()
|
|
24
|
+
```
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _platform_baseline() -> float:
|
|
29
|
+
"""Return the default Tk scaling baseline for the current platform.
|
|
30
|
+
|
|
31
|
+
Tk scaling is measured in pixels-per-point (72 points per inch).
|
|
32
|
+
- Windows/Linux default DPI is 96 → baseline = 96/72 ≈ 1.334
|
|
33
|
+
- macOS default DPI is 72 → baseline = 72/72 = 1.0
|
|
34
|
+
(Retina pixel-doubling is handled by the OS, not Tk scaling)
|
|
35
|
+
"""
|
|
36
|
+
import platform
|
|
37
|
+
if platform.system() == 'Darwin':
|
|
38
|
+
return 1.0
|
|
39
|
+
return 1.33398982438864281 # 96 DPI / 72
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class _ScalingState:
|
|
43
|
+
"""Internal class to store global scaling state."""
|
|
44
|
+
_scale_factor: float = 1.0
|
|
45
|
+
_baseline: float = _platform_baseline()
|
|
46
|
+
|
|
47
|
+
@classmethod
|
|
48
|
+
def set_scale_factor(cls, factor: float):
|
|
49
|
+
"""Set the global scale factor."""
|
|
50
|
+
cls._scale_factor = factor
|
|
51
|
+
|
|
52
|
+
@classmethod
|
|
53
|
+
def get_scale_factor(cls) -> float:
|
|
54
|
+
"""Get the current global scale factor."""
|
|
55
|
+
return cls._scale_factor
|
|
56
|
+
|
|
57
|
+
@classmethod
|
|
58
|
+
def get_ui_scale(cls) -> float:
|
|
59
|
+
"""Get the UI scale factor (relative to baseline)."""
|
|
60
|
+
return cls._scale_factor / cls._baseline
|
|
61
|
+
|
|
62
|
+
@classmethod
|
|
63
|
+
def get_image_scale(cls, source_resolution: float = 2.0) -> float:
|
|
64
|
+
"""Get the scale factor for images.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
source_resolution: The resolution multiplier of source images.
|
|
68
|
+
For example, 2.0 means images are 2x resolution.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
The scale factor to apply to images.
|
|
72
|
+
"""
|
|
73
|
+
return cls.get_ui_scale() / source_resolution
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def enable_high_dpi_awareness(root=None, scaling=None):
|
|
77
|
+
"""Enable high dpi awareness.
|
|
78
|
+
|
|
79
|
+
**Windows OS**
|
|
80
|
+
Call the method BEFORE creating the `Tk` object. No parameters
|
|
81
|
+
required. After the root is created, call again with the root
|
|
82
|
+
parameter to apply detected scaling.
|
|
83
|
+
|
|
84
|
+
**Linux OS**
|
|
85
|
+
Must provided the `root` and `scaling` parameters. Call the method
|
|
86
|
+
AFTER creating the `Tk` object. A number between 1.6 and 2.0 is
|
|
87
|
+
usually suffient to scale for high-dpi screen.
|
|
88
|
+
|
|
89
|
+
!!! warning
|
|
90
|
+
If the `root` argument is provided, then `scaling` must also
|
|
91
|
+
be provided. Otherwise, there is no effect.
|
|
92
|
+
|
|
93
|
+
Parameters:
|
|
94
|
+
|
|
95
|
+
root (tk.Tk):
|
|
96
|
+
The root widget
|
|
97
|
+
|
|
98
|
+
scaling (float or 'auto'):
|
|
99
|
+
Sets and queries the current scaling factor used by Tk to
|
|
100
|
+
convert between physical units (for example, points,
|
|
101
|
+
inches, or millimeters) and pixels. The number argument is
|
|
102
|
+
a floating point number that specifies the number of pixels
|
|
103
|
+
per point on window's display. If the window argument is
|
|
104
|
+
omitted, it defaults to the main window. If the number
|
|
105
|
+
argument is omitted, the current value of the scaling
|
|
106
|
+
factor is returned.
|
|
107
|
+
|
|
108
|
+
If set to 'auto', the scale factor will be detected
|
|
109
|
+
automatically from the system DPI settings.
|
|
110
|
+
|
|
111
|
+
A "point" is a unit of measurement equal to 1/72 inch. A
|
|
112
|
+
scaling factor of 1.0 corresponds to 1 pixel per point,
|
|
113
|
+
which is equivalent to a standard 72 dpi monitor. A scaling
|
|
114
|
+
factor of 1.25 would mean 1.25 pixels per point, which is
|
|
115
|
+
the setting for a 90 dpi monitor; setting the scaling factor
|
|
116
|
+
to 1.25 on a 72 dpi monitor would cause everything in the
|
|
117
|
+
application to be displayed 1.25 times as large as normal.
|
|
118
|
+
The initial value for the scaling factor is set when the
|
|
119
|
+
application starts, based on properties of the installed
|
|
120
|
+
monitor, but it can be changed at any time. Measurements
|
|
121
|
+
made after the scaling factor is changed will use the new
|
|
122
|
+
scaling factor, but it is undefined whether existing
|
|
123
|
+
widgets will resize themselves dynamically to accommodate
|
|
124
|
+
the new scaling factor.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
|
|
128
|
+
float:
|
|
129
|
+
The scaling factor that was applied, or None if no scaling
|
|
130
|
+
was applied.
|
|
131
|
+
"""
|
|
132
|
+
import platform
|
|
133
|
+
|
|
134
|
+
# Enable DPI awareness on Windows
|
|
135
|
+
try:
|
|
136
|
+
from ctypes import windll
|
|
137
|
+
# Use shcore for better DPI awareness (Windows 8.1+)
|
|
138
|
+
try:
|
|
139
|
+
windll.shcore.SetProcessDpiAwareness(1)
|
|
140
|
+
except:
|
|
141
|
+
# Fallback to older API
|
|
142
|
+
windll.user32.SetProcessDPIAware()
|
|
143
|
+
except:
|
|
144
|
+
pass
|
|
145
|
+
|
|
146
|
+
# Apply scaling if root is provided
|
|
147
|
+
if root:
|
|
148
|
+
# Auto-detect scaling if requested
|
|
149
|
+
if scaling == 'auto':
|
|
150
|
+
scaling = detect_scale_factor(root)
|
|
151
|
+
|
|
152
|
+
if scaling:
|
|
153
|
+
try:
|
|
154
|
+
root.tk.call('tk', 'scaling', scaling)
|
|
155
|
+
# Store the scale factor globally
|
|
156
|
+
_ScalingState.set_scale_factor(scaling)
|
|
157
|
+
return scaling
|
|
158
|
+
except:
|
|
159
|
+
pass
|
|
160
|
+
|
|
161
|
+
return None
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def detect_scale_factor(root):
|
|
165
|
+
"""Detect the appropriate scale factor for the display.
|
|
166
|
+
|
|
167
|
+
This function attempts to detect the system DPI scaling and return
|
|
168
|
+
an appropriate scale factor for Tk. The scale factor is measured in
|
|
169
|
+
"pixels per point" (72 points per inch).
|
|
170
|
+
|
|
171
|
+
Parameters:
|
|
172
|
+
|
|
173
|
+
root (tk.Tk):
|
|
174
|
+
The root window (must be created before calling)
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
|
|
178
|
+
float:
|
|
179
|
+
The detected scale factor in pixels per point
|
|
180
|
+
(e.g., 1.333 for 96 DPI, 2.0 for 144 DPI)
|
|
181
|
+
"""
|
|
182
|
+
import platform
|
|
183
|
+
|
|
184
|
+
system = platform.system()
|
|
185
|
+
|
|
186
|
+
try:
|
|
187
|
+
if system == 'Windows':
|
|
188
|
+
# Try to get scale factor from Windows (Windows 8.1+)
|
|
189
|
+
try:
|
|
190
|
+
from ctypes import windll
|
|
191
|
+
# GetScaleFactorForDevice returns percentage (100, 125, 150, 200, etc.)
|
|
192
|
+
scale_percent = windll.shcore.GetScaleFactorForDevice(0)
|
|
193
|
+
# Convert to DPI: 100% = 96 DPI, 125% = 120 DPI, etc.
|
|
194
|
+
dpi = (96 * scale_percent) / 100
|
|
195
|
+
# Convert to tk scaling (pixels per point, 72 points per inch)
|
|
196
|
+
scale_factor = dpi / 72
|
|
197
|
+
return scale_factor
|
|
198
|
+
except:
|
|
199
|
+
pass
|
|
200
|
+
|
|
201
|
+
# Fallback: detect from current DPI
|
|
202
|
+
# This works on Linux, Mac, and older Windows
|
|
203
|
+
current_dpi = root.winfo_fpixels('1i')
|
|
204
|
+
# Tk scaling is pixels per point (72 points per inch)
|
|
205
|
+
scale_factor = current_dpi / 72
|
|
206
|
+
return scale_factor
|
|
207
|
+
|
|
208
|
+
except:
|
|
209
|
+
# If all else fails, return default for 96 DPI
|
|
210
|
+
return 96 / 72 # 1.333...
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def get_image_name(image):
|
|
214
|
+
"""Extract and return the tcl/tk image name from a PhotoImage
|
|
215
|
+
object.
|
|
216
|
+
|
|
217
|
+
Parameters:
|
|
218
|
+
|
|
219
|
+
image (ImageTk.PhotoImage):
|
|
220
|
+
A photoimage object.
|
|
221
|
+
|
|
222
|
+
Returns:
|
|
223
|
+
|
|
224
|
+
str:
|
|
225
|
+
The tcl/tk name of the photoimage object.
|
|
226
|
+
"""
|
|
227
|
+
return image._PhotoImage__photo.name
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def scale_size(widget=None, size=None):
|
|
231
|
+
"""Scale the size based on the scaling factor of tkinter.
|
|
232
|
+
This is used most frequently to adjust the assets for
|
|
233
|
+
image-based widget layouts and padding values.
|
|
234
|
+
|
|
235
|
+
Can be called in two ways:
|
|
236
|
+
1. scale_size(widget, size) - Legacy mode, calculates from widget
|
|
237
|
+
2. scale_size(size) - Uses global scaling state
|
|
238
|
+
|
|
239
|
+
Parameters:
|
|
240
|
+
|
|
241
|
+
widget (Widget, optional):
|
|
242
|
+
The widget object. If provided, scaling is calculated from
|
|
243
|
+
the widget's tk instance. If None, uses global scaling state.
|
|
244
|
+
|
|
245
|
+
size (Union[int, List, Tuple]):
|
|
246
|
+
A single integer or an iterable of integers
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
|
|
250
|
+
Union[int, List]:
|
|
251
|
+
An integer or list of integers representing the new size.
|
|
252
|
+
|
|
253
|
+
Examples:
|
|
254
|
+
|
|
255
|
+
>>> # Using widget (legacy mode)
|
|
256
|
+
>>> scaled = scale_size(my_widget, 10)
|
|
257
|
+
|
|
258
|
+
>>> # Using global state (new mode)
|
|
259
|
+
>>> scaled = scale_size(10)
|
|
260
|
+
"""
|
|
261
|
+
# Handle both calling conventions
|
|
262
|
+
if widget is not None and size is None:
|
|
263
|
+
# Called as scale_size(size) - widget is actually the size
|
|
264
|
+
size = widget
|
|
265
|
+
factor = _ScalingState.get_ui_scale()
|
|
266
|
+
elif widget is not None and size is not None:
|
|
267
|
+
# Called as scale_size(widget, size) - legacy mode
|
|
268
|
+
BASELINE = _ScalingState._baseline
|
|
269
|
+
scaling = widget.tk.call('tk', 'scaling')
|
|
270
|
+
factor = scaling / BASELINE
|
|
271
|
+
else:
|
|
272
|
+
# Called as scale_size(size=size) - use global state
|
|
273
|
+
factor = _ScalingState.get_ui_scale()
|
|
274
|
+
|
|
275
|
+
if isinstance(size, int):
|
|
276
|
+
return int(size * factor)
|
|
277
|
+
elif isinstance(size, tuple) or isinstance(size, list):
|
|
278
|
+
return [int(x * factor) for x in size]
|
|
279
|
+
else:
|
|
280
|
+
return size
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
# --- Debug helpers ---------------------------------------------------------
|
|
284
|
+
def _debug_enabled() -> bool:
|
|
285
|
+
"""Return True if debug logging is enabled.
|
|
286
|
+
|
|
287
|
+
Controlled via the environment variable `TTKBOOTSTRAP_DEBUG`.
|
|
288
|
+
Accepts: "1", "true", "yes" (case-insensitive) as truthy values.
|
|
289
|
+
"""
|
|
290
|
+
import os
|
|
291
|
+
return str(os.environ.get("TTKBOOTSTRAP_DEBUG", "")).lower() in {"1", "true", "yes"}
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def debug_log_exception(message: str = "") -> None:
|
|
295
|
+
"""Print the current exception traceback if debug is enabled.
|
|
296
|
+
|
|
297
|
+
Args:
|
|
298
|
+
message: Optional context message to print before the traceback.
|
|
299
|
+
"""
|
|
300
|
+
if not _debug_enabled():
|
|
301
|
+
return
|
|
302
|
+
try:
|
|
303
|
+
import traceback
|
|
304
|
+
if message:
|
|
305
|
+
print(f"TTKBootstrap DEBUG: {message}")
|
|
306
|
+
traceback.print_exc()
|
|
307
|
+
except Exception:
|
|
308
|
+
# Never raise from debug logging
|
|
309
|
+
pass
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def center_on_parent(win, parent=None):
|
|
313
|
+
"""Center `win` on parent or over its master if not given"""
|
|
314
|
+
win.update_idletasks() # ensure geometry
|
|
315
|
+
if parent is None:
|
|
316
|
+
parent = getattr(win, 'master', None) or win # root if no parent
|
|
317
|
+
|
|
318
|
+
# parent geometry
|
|
319
|
+
parent.update_idletasks()
|
|
320
|
+
px, py = parent.winfo_rootx(), parent.winfo_rooty()
|
|
321
|
+
pw, ph = parent.winfo_width(), parent.winfo_height()
|
|
322
|
+
if pw <= 1 or ph <= 1:
|
|
323
|
+
# not yet realized, fallback to requested size
|
|
324
|
+
pw, ph = parent.winfo_reqwidth(), parent.winfo_reqheight()
|
|
325
|
+
|
|
326
|
+
# window geometry
|
|
327
|
+
ww = win.winfo_width() or win.winfo_reqwidth()
|
|
328
|
+
wh = win.winfo_height() or win.winfo_reqheight()
|
|
329
|
+
|
|
330
|
+
x = px + (pw - ww) // 2
|
|
331
|
+
y = py + (ph - wh) // 2
|
|
332
|
+
win.geometry(f"{ww}x{wh}+{x}+{y}")
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def bind_right_click(widget, handler, add: str | bool = '+'):
|
|
336
|
+
"""Bind a right-click handler portably across Tk windowing systems.
|
|
337
|
+
|
|
338
|
+
On Win/Linux right-click maps to `<Button-3>`. On macOS Tk maps the
|
|
339
|
+
right mouse button (and two-finger trackpad click) to `<Button-2>`,
|
|
340
|
+
and Mac users also expect Ctrl+click as a context-menu trigger. This
|
|
341
|
+
helper binds the appropriate event(s) for the current platform so
|
|
342
|
+
callers don't have to repeat the platform check.
|
|
343
|
+
|
|
344
|
+
Args:
|
|
345
|
+
widget: Any Tk widget with a `.bind` method and a `.tk` attribute.
|
|
346
|
+
handler: Event handler callable (or Tcl command string).
|
|
347
|
+
add: Passed through to `bind`. Defaults to `'+'` so the helper
|
|
348
|
+
never silently replaces an existing binding.
|
|
349
|
+
"""
|
|
350
|
+
widget.bind('<Button-3>', handler, add=add)
|
|
351
|
+
try:
|
|
352
|
+
winsys = widget.tk.call('tk', 'windowingsystem')
|
|
353
|
+
except Exception:
|
|
354
|
+
winsys = None
|
|
355
|
+
if winsys == 'aqua':
|
|
356
|
+
widget.bind('<Button-2>', handler, add=add)
|
|
357
|
+
widget.bind('<Control-Button-1>', handler, add=add)
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def clamp(value, min_val, max_val):
|
|
361
|
+
"""Return a value that is bounded by a minimum and maximum.
|
|
362
|
+
|
|
363
|
+
Args:
|
|
364
|
+
value: The value to evaluate.
|
|
365
|
+
min_val: The minimum allowed value.
|
|
366
|
+
max_val: The maximum allowed value.
|
|
367
|
+
|
|
368
|
+
Returns:
|
|
369
|
+
The value, constrained between `min_val` and `max_val`.
|
|
370
|
+
"""
|
|
371
|
+
return min(max(value, min_val), max_val)
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
"""Visual focus management for bootstack.
|
|
2
|
+
|
|
3
|
+
This module provides keyboard vs mouse focus distinction by leveraging
|
|
4
|
+
the TTK 'background' state as a "keyboard focus" indicator. This enables
|
|
5
|
+
style maps to show focus rings only for keyboard navigation (Tab), not
|
|
6
|
+
for mouse clicks.
|
|
7
|
+
|
|
8
|
+
The approach:
|
|
9
|
+
- Tab key press → sets 'background' state on newly focused widget
|
|
10
|
+
- FocusOut → removes 'background' state
|
|
11
|
+
- Mouse clicks never add 'background', so click focus is distinguishable
|
|
12
|
+
|
|
13
|
+
Style maps can then use:
|
|
14
|
+
('background focus', ring_color) # Keyboard focus - show ring
|
|
15
|
+
('focus', '') # Mouse focus - no ring
|
|
16
|
+
|
|
17
|
+
This matches the CSS :focus-visible behavior that modern browsers implement.
|
|
18
|
+
|
|
19
|
+
Programmatic focus:
|
|
20
|
+
The focus_set() and focus_force() methods accept a `visual_focus` parameter.
|
|
21
|
+
When True, the focus ring is shown as if the widget was focused via keyboard:
|
|
22
|
+
|
|
23
|
+
widget.focus_set(visual_focus=True) # Shows focus ring
|
|
24
|
+
|
|
25
|
+
Note:
|
|
26
|
+
The 'background' TTK state is normally used to indicate an inactive
|
|
27
|
+
window. Since this is rarely styled in practice, it's repurposed here
|
|
28
|
+
for keyboard focus tracking.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
import tkinter as tk
|
|
32
|
+
from tkinter import TclError
|
|
33
|
+
from typing import Optional
|
|
34
|
+
|
|
35
|
+
_installed = False
|
|
36
|
+
_root_ref: Optional[tk.Misc] = None
|
|
37
|
+
|
|
38
|
+
# Store original focus methods
|
|
39
|
+
_original_focus_set = tk.Misc.focus_set
|
|
40
|
+
_original_focus_force = tk.Misc.focus_force
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _patched_focus_set(self, *, visual_focus: bool = False) -> None:
|
|
44
|
+
"""Enhanced focus_set that optionally shows visual focus ring.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
visual_focus: If True, show focus as if focused via keyboard. Default is False.
|
|
48
|
+
|
|
49
|
+
Examples:
|
|
50
|
+
```python
|
|
51
|
+
# Normal programmatic focus (no ring)
|
|
52
|
+
entry.focus_set()
|
|
53
|
+
|
|
54
|
+
# Focus with visible ring (e.g., after validation error)
|
|
55
|
+
entry.focus_set(visual_focus=True)
|
|
56
|
+
```
|
|
57
|
+
"""
|
|
58
|
+
_original_focus_set(self)
|
|
59
|
+
if visual_focus:
|
|
60
|
+
try:
|
|
61
|
+
self.state(['background'])
|
|
62
|
+
except (TclError, AttributeError):
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _patched_focus_force(self, *, visual_focus: bool = False) -> None:
|
|
67
|
+
"""Enhanced focus_force that optionally shows visual focus ring.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
visual_focus: If True, show focus as if focused via keyboard. Default is False.
|
|
71
|
+
|
|
72
|
+
Examples:
|
|
73
|
+
```python
|
|
74
|
+
# Normal forced focus (no ring)
|
|
75
|
+
entry.focus_force()
|
|
76
|
+
|
|
77
|
+
# Forced focus with visible ring
|
|
78
|
+
entry.focus_force(visual_focus=True)
|
|
79
|
+
```
|
|
80
|
+
"""
|
|
81
|
+
_original_focus_force(self)
|
|
82
|
+
if visual_focus:
|
|
83
|
+
try:
|
|
84
|
+
self.state(['background'])
|
|
85
|
+
except (TclError, AttributeError):
|
|
86
|
+
pass
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _on_tab_focus(event: tk.Event) -> None:
|
|
90
|
+
"""Handle Tab key press by marking the newly focused widget.
|
|
91
|
+
|
|
92
|
+
Uses after_idle to wait for focus to actually move before
|
|
93
|
+
querying focus_get() and setting the background state.
|
|
94
|
+
"""
|
|
95
|
+
root = event.widget.winfo_toplevel()
|
|
96
|
+
|
|
97
|
+
def set_keyboard_focus_state():
|
|
98
|
+
widget = root.focus_get()
|
|
99
|
+
if widget is None:
|
|
100
|
+
return
|
|
101
|
+
try:
|
|
102
|
+
widget.state(['background'])
|
|
103
|
+
except (TclError, AttributeError):
|
|
104
|
+
pass # Widget doesn't support state (non-TTK)
|
|
105
|
+
|
|
106
|
+
root.after_idle(set_keyboard_focus_state)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _on_focus_out(event: tk.Event) -> None:
|
|
110
|
+
"""Clear the keyboard focus indicator when widget loses focus."""
|
|
111
|
+
try:
|
|
112
|
+
event.widget.state(['!background'])
|
|
113
|
+
except (TclError, AttributeError):
|
|
114
|
+
pass # Widget doesn't support state (non-TTK)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def install_visual_focus(root: tk.Misc = None) -> None:
|
|
118
|
+
"""Install keyboard focus tracking for the application.
|
|
119
|
+
|
|
120
|
+
This function sets up global event bindings that track whether
|
|
121
|
+
focus was acquired via keyboard (Tab) or mouse click, enabling
|
|
122
|
+
style maps to show focus rings only for keyboard navigation.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
root: Optional root widget. If not provided, bindings are
|
|
126
|
+
set up to work with any root via bind_class on Tk.
|
|
127
|
+
|
|
128
|
+
Note:
|
|
129
|
+
This is called automatically when bootstack is imported.
|
|
130
|
+
You typically don't need to call this manually.
|
|
131
|
+
|
|
132
|
+
Examples:
|
|
133
|
+
Style builders can use the 'background' state to distinguish:
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
b.map_style(ttk_style,
|
|
137
|
+
focuscolor=[
|
|
138
|
+
('background focus', ring_color), # Keyboard focus
|
|
139
|
+
('focus', ''), # Mouse focus
|
|
140
|
+
('', ''),
|
|
141
|
+
]
|
|
142
|
+
)
|
|
143
|
+
```
|
|
144
|
+
"""
|
|
145
|
+
global _installed, _root_ref
|
|
146
|
+
|
|
147
|
+
if _installed:
|
|
148
|
+
return
|
|
149
|
+
|
|
150
|
+
# Patch focus_set and focus_force to support visual_focus parameter
|
|
151
|
+
tk.Misc.focus_set = _patched_focus_set
|
|
152
|
+
tk.Misc.focus_force = _patched_focus_force
|
|
153
|
+
|
|
154
|
+
# Bind Tab key globally - works even before any root exists
|
|
155
|
+
# by using bind_class on the base Tk class
|
|
156
|
+
tk.Tk.bind_all = _bind_all_with_focus
|
|
157
|
+
|
|
158
|
+
_installed = True
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _bind_all_with_focus(self, sequence=None, func=None, add=None):
|
|
162
|
+
"""Wrapper for bind_all that installs focus tracking on first call."""
|
|
163
|
+
global _root_ref
|
|
164
|
+
|
|
165
|
+
# Install our bindings on first bind_all call (when root exists)
|
|
166
|
+
if _root_ref is None:
|
|
167
|
+
_root_ref = self
|
|
168
|
+
# Bind Tab on root window - the after_idle callback will set state
|
|
169
|
+
# on whatever widget receives focus (forward or backward)
|
|
170
|
+
self.bind('<Tab>', _on_tab_focus, '+')
|
|
171
|
+
# Clear background state on focus out (needs bind_all for all widgets)
|
|
172
|
+
self.bind_all('<FocusOut>', _on_focus_out, '+')
|
|
173
|
+
|
|
174
|
+
# Call original bind_all
|
|
175
|
+
return tk.Misc.bind_all(self, sequence, func, add)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def uninstall_visual_focus() -> None:
|
|
179
|
+
"""Remove keyboard focus tracking bindings.
|
|
180
|
+
|
|
181
|
+
This restores the original behavior where focus state doesn't
|
|
182
|
+
distinguish between keyboard and mouse focus.
|
|
183
|
+
|
|
184
|
+
Note:
|
|
185
|
+
After calling this, style maps using 'background focus' will
|
|
186
|
+
no longer show focus rings for keyboard navigation.
|
|
187
|
+
"""
|
|
188
|
+
global _installed, _root_ref
|
|
189
|
+
|
|
190
|
+
if not _installed:
|
|
191
|
+
return
|
|
192
|
+
|
|
193
|
+
# Restore original focus methods
|
|
194
|
+
tk.Misc.focus_set = _original_focus_set
|
|
195
|
+
tk.Misc.focus_force = _original_focus_force
|
|
196
|
+
|
|
197
|
+
if _root_ref is not None:
|
|
198
|
+
try:
|
|
199
|
+
_root_ref.unbind('<Tab>')
|
|
200
|
+
_root_ref.unbind_all('<FocusOut>')
|
|
201
|
+
except TclError:
|
|
202
|
+
pass # Root may have been destroyed
|
|
203
|
+
|
|
204
|
+
_root_ref = None
|
|
205
|
+
_installed = False
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def is_keyboard_focus(widget: tk.Misc) -> bool:
|
|
209
|
+
"""Check if a widget currently has keyboard-initiated focus.
|
|
210
|
+
|
|
211
|
+
Args:
|
|
212
|
+
widget: The widget to check.
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
True if the widget has focus AND was focused via keyboard.
|
|
216
|
+
"""
|
|
217
|
+
try:
|
|
218
|
+
state = widget.state()
|
|
219
|
+
return 'focus' in state and 'background' in state
|
|
220
|
+
except (TclError, AttributeError):
|
|
221
|
+
return False
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
__all__ = [
|
|
225
|
+
'install_visual_focus',
|
|
226
|
+
'uninstall_visual_focus',
|
|
227
|
+
'is_keyboard_focus',
|
|
228
|
+
]
|