bootstack 0.1.0__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 +157 -0
- bootstack/__main__.py +5 -0
- bootstack/_core/__init__.py +21 -0
- bootstack/_core/capabilities/__init__.py +45 -0
- bootstack/_core/capabilities/after.py +103 -0
- bootstack/_core/capabilities/bind.py +192 -0
- bootstack/_core/capabilities/bindtags.py +112 -0
- bootstack/_core/capabilities/busy.py +72 -0
- bootstack/_core/capabilities/clipboard.py +89 -0
- bootstack/_core/capabilities/focus.py +118 -0
- bootstack/_core/capabilities/grab.py +65 -0
- bootstack/_core/capabilities/grid.py +211 -0
- bootstack/_core/capabilities/localization.py +231 -0
- bootstack/_core/capabilities/pack.py +140 -0
- bootstack/_core/capabilities/place.py +113 -0
- bootstack/_core/capabilities/selection.py +136 -0
- bootstack/_core/capabilities/signals.py +244 -0
- bootstack/_core/capabilities/winfo.py +315 -0
- bootstack/_core/colorutils.py +234 -0
- bootstack/_core/exceptions.py +25 -0
- bootstack/_core/images.py +463 -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/paths.py +49 -0
- bootstack/_core/publisher.py +149 -0
- bootstack/_core/variables.py +62 -0
- bootstack/_runtime/__init__.py +3 -0
- bootstack/_runtime/app.py +930 -0
- bootstack/_runtime/base_window.py +945 -0
- bootstack/_runtime/events.py +399 -0
- bootstack/_runtime/shortcuts.py +496 -0
- bootstack/_runtime/tk_patch.py +43 -0
- bootstack/_runtime/toplevel.py +276 -0
- bootstack/_runtime/utility.py +457 -0
- bootstack/_runtime/visual_focus.py +240 -0
- bootstack/_runtime/window_utilities.py +1123 -0
- bootstack/assets/__init__.py +21 -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/button-compact.png +0 -0
- bootstack/assets/elements/button-default.png +0 -0
- bootstack/assets/elements/buttongroup-after-h-compact.png +0 -0
- bootstack/assets/elements/buttongroup-after-h-default.png +0 -0
- bootstack/assets/elements/buttongroup-after-v-compact.png +0 -0
- bootstack/assets/elements/buttongroup-after-v-default.png +0 -0
- bootstack/assets/elements/buttongroup-before-h-compact.png +0 -0
- bootstack/assets/elements/buttongroup-before-h-default.png +0 -0
- bootstack/assets/elements/buttongroup-before-v-compact.png +0 -0
- bootstack/assets/elements/buttongroup-before-v-default.png +0 -0
- bootstack/assets/elements/buttongroup-center-h-compact.png +0 -0
- bootstack/assets/elements/buttongroup-center-h-default.png +0 -0
- bootstack/assets/elements/buttongroup-center-v-compact.png +0 -0
- bootstack/assets/elements/buttongroup-center-v-default.png +0 -0
- bootstack/assets/elements/card.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-addon-compact.png +0 -0
- bootstack/assets/elements/input-addon-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/listrow-compact.png +0 -0
- bootstack/assets/elements/listrow-default.png +0 -0
- bootstack/assets/elements/manifest.toml +361 -0
- bootstack/assets/elements/menu-item.png +0 -0
- bootstack/assets/elements/navitem-compact.png +0 -0
- bootstack/assets/elements/navitem-default.png +0 -0
- bootstack/assets/elements/progressbar-h-compact.png +0 -0
- bootstack/assets/elements/progressbar-h-default.png +0 -0
- bootstack/assets/elements/progressbar-v-compact.png +0 -0
- bootstack/assets/elements/progressbar-v-default.png +0 -0
- bootstack/assets/elements/radiobutton.png +0 -0
- bootstack/assets/elements/scrollbar-horizontal.png +0 -0
- bootstack/assets/elements/scrollbar-vertical.png +0 -0
- bootstack/assets/elements/slider-handle.png +0 -0
- bootstack/assets/elements/slider-track-h.png +0 -0
- bootstack/assets/elements/slider-track-v.png +0 -0
- bootstack/assets/elements/switch-off.png +0 -0
- bootstack/assets/elements/switch-on.png +0 -0
- bootstack/assets/elements/tab-h.png +0 -0
- bootstack/assets/elements/tab-v.png +0 -0
- bootstack/assets/icons/bootstrap.ttf +0 -0
- bootstack/assets/icons/glyphmap.json +2080 -0
- bootstack/assets/icons/icon_metrics.json +12470 -0
- bootstack/assets/locales/ar/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/ar/LC_MESSAGES/bootstack.po +856 -0
- bootstack/assets/locales/bg/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/bg/LC_MESSAGES/bootstack.po +878 -0
- bootstack/assets/locales/cs/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/cs/LC_MESSAGES/bootstack.po +856 -0
- bootstack/assets/locales/da/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/da/LC_MESSAGES/bootstack.po +856 -0
- bootstack/assets/locales/de/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/de/LC_MESSAGES/bootstack.po +856 -0
- bootstack/assets/locales/en/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/en/LC_MESSAGES/bootstack.po +878 -0
- bootstack/assets/locales/es/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/es/LC_MESSAGES/bootstack.po +856 -0
- bootstack/assets/locales/fr/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/fr/LC_MESSAGES/bootstack.po +856 -0
- bootstack/assets/locales/he/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/he/LC_MESSAGES/bootstack.po +854 -0
- bootstack/assets/locales/hi/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/hi/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/it/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/it/LC_MESSAGES/bootstack.po +844 -0
- bootstack/assets/locales/ja/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/ja/LC_MESSAGES/bootstack.po +917 -0
- bootstack/assets/locales/ko/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/ko/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/nb/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/nb/LC_MESSAGES/bootstack.po +844 -0
- bootstack/assets/locales/nl/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/nl/LC_MESSAGES/bootstack.po +844 -0
- bootstack/assets/locales/pl/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/pl/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/pt/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/pt/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/pt_BR/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/pt_BR/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/sl/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/sl/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/sv/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/sv/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/tr/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/tr/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/zh_CN/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/zh_CN/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/zh_TW/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/zh_TW/LC_MESSAGES/bootstack.po +845 -0
- bootstack/cli/__init__.py +133 -0
- bootstack/cli/__main__.py +6 -0
- bootstack/cli/add.py +395 -0
- bootstack/cli/appicon.py +285 -0
- bootstack/cli/build.py +115 -0
- bootstack/cli/config.py +313 -0
- bootstack/cli/demo.py +564 -0
- bootstack/cli/dev.py +153 -0
- bootstack/cli/doctor.py +195 -0
- bootstack/cli/icons.py +98 -0
- bootstack/cli/promote.py +120 -0
- bootstack/cli/pyinstaller.py +268 -0
- bootstack/cli/run.py +95 -0
- bootstack/cli/start.py +117 -0
- bootstack/cli/templates/__init__.py +931 -0
- bootstack/clipboard.py +48 -0
- bootstack/constants.py +318 -0
- bootstack/data/README.md +615 -0
- bootstack/data/__init__.py +78 -0
- bootstack/data/_observable.py +276 -0
- bootstack/data/base.py +780 -0
- bootstack/data/file_source.py +367 -0
- bootstack/data/memory_source.py +450 -0
- bootstack/data/query.py +367 -0
- bootstack/data/readers.py +289 -0
- bootstack/data/sqlite_source.py +869 -0
- bootstack/data/types.py +354 -0
- bootstack/data/writers.py +232 -0
- bootstack/dev/__init__.py +36 -0
- bootstack/dev/_capture.py +141 -0
- bootstack/dev/_env.py +43 -0
- bootstack/dev/_registry.py +140 -0
- bootstack/dev/_reloader.py +351 -0
- bootstack/dev/_reset.py +50 -0
- bootstack/dev/_watcher.py +91 -0
- bootstack/dialogs/__init__.py +948 -0
- bootstack/dialogs/_impl/__init__.py +47 -0
- bootstack/dialogs/_impl/colorchooser.py +588 -0
- bootstack/dialogs/_impl/datedialog.py +450 -0
- bootstack/dialogs/_impl/dialog.py +594 -0
- bootstack/dialogs/_impl/filterdialog.py +358 -0
- bootstack/dialogs/_impl/fontdialog.py +364 -0
- bootstack/dialogs/_impl/formdialog.py +564 -0
- bootstack/dialogs/_impl/message.py +486 -0
- bootstack/dialogs/_impl/query.py +570 -0
- bootstack/errors.py +67 -0
- bootstack/events/__init__.py +111 -0
- bootstack/events/_event.py +135 -0
- bootstack/events/_payloads.py +539 -0
- bootstack/events/_subscription.py +38 -0
- bootstack/i18n/README.md +77 -0
- bootstack/i18n/__init__.py +23 -0
- bootstack/i18n/catalog.py +121 -0
- bootstack/i18n/intl_format.py +584 -0
- bootstack/i18n/msgcat.py +425 -0
- bootstack/i18n/specs.py +156 -0
- bootstack/images.py +563 -0
- bootstack/py.typed +1 -0
- bootstack/scheduling/__init__.py +11 -0
- bootstack/scheduling/_schedule.py +218 -0
- bootstack/shortcuts.py +21 -0
- bootstack/signals/README.md +98 -0
- bootstack/signals/__init__.py +10 -0
- bootstack/signals/integration.py +100 -0
- bootstack/signals/signal.py +353 -0
- bootstack/signals/types.py +6 -0
- bootstack/store.py +286 -0
- bootstack/streams/__init__.py +12 -0
- bootstack/streams/_stream.py +321 -0
- bootstack/style/__init__.py +38 -0
- bootstack/style/builders/__init__.py +51 -0
- bootstack/style/builders/badge.py +46 -0
- bootstack/style/builders/button.py +339 -0
- bootstack/style/builders/buttongroup.py +311 -0
- bootstack/style/builders/calendar.py +271 -0
- bootstack/style/builders/checkbutton.py +110 -0
- bootstack/style/builders/combobox.py +113 -0
- bootstack/style/builders/contextmenu.py +268 -0
- bootstack/style/builders/entry.py +82 -0
- bootstack/style/builders/expander.py +148 -0
- bootstack/style/builders/field.py +335 -0
- bootstack/style/builders/frame.py +50 -0
- bootstack/style/builders/label.py +28 -0
- bootstack/style/builders/labelframe.py +34 -0
- bootstack/style/builders/listview.py +369 -0
- bootstack/style/builders/menubar.py +91 -0
- bootstack/style/builders/menubutton.py +359 -0
- bootstack/style/builders/panedwindow.py +25 -0
- bootstack/style/builders/progressbar.py +67 -0
- bootstack/style/builders/radiobutton.py +99 -0
- bootstack/style/builders/scale.py +64 -0
- bootstack/style/builders/scrollbar.py +225 -0
- bootstack/style/builders/separator.py +49 -0
- bootstack/style/builders/sidenav.py +643 -0
- bootstack/style/builders/sizegrip.py +15 -0
- bootstack/style/builders/spinbox.py +119 -0
- bootstack/style/builders/switch.py +70 -0
- bootstack/style/builders/tabitem.py +204 -0
- bootstack/style/builders/togglegroup.py +294 -0
- bootstack/style/builders/toolbutton.py +275 -0
- bootstack/style/builders/tooltip.py +26 -0
- bootstack/style/builders/treeview.py +193 -0
- bootstack/style/builders/utils.py +455 -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/fonts.py +123 -0
- bootstack/style/style.py +609 -0
- bootstack/style/style_builder_base.py +716 -0
- bootstack/style/style_builder_mixed.py +93 -0
- bootstack/style/style_builder_tk.py +109 -0
- bootstack/style/style_builder_ttk.py +353 -0
- bootstack/style/style_resolver.py +447 -0
- bootstack/style/theme.py +245 -0
- bootstack/style/theme_provider.py +471 -0
- bootstack/style/themes/__init__.py +128 -0
- bootstack/style/tk_patch.py +5 -0
- bootstack/style/token_maps.py +41 -0
- bootstack/style/types.py +32 -0
- bootstack/style/typography.py +523 -0
- bootstack/style/utility.py +746 -0
- bootstack/types.py +39 -0
- bootstack/validation/__init__.py +6 -0
- bootstack/validation/types.py +15 -0
- bootstack/validation/validation_result.py +17 -0
- bootstack/validation/validation_rules.py +205 -0
- bootstack/widgets/__init__.py +74 -0
- bootstack/widgets/_core/__init__.py +31 -0
- bootstack/widgets/_core/app_config.py +266 -0
- bootstack/widgets/_core/base.py +461 -0
- bootstack/widgets/_core/container.py +334 -0
- bootstack/widgets/_core/context.py +35 -0
- bootstack/widgets/_core/events.py +130 -0
- bootstack/widgets/_core/field_mixin.py +353 -0
- bootstack/widgets/_core/icon_image_props.py +72 -0
- bootstack/widgets/_core/image_binding.py +93 -0
- bootstack/widgets/_core/navmodel.py +345 -0
- bootstack/widgets/_core/options.py +210 -0
- bootstack/widgets/_core/selection_group.py +130 -0
- bootstack/widgets/_core/window_controls.py +96 -0
- bootstack/widgets/_core/window_menu.py +246 -0
- bootstack/widgets/_impl/__init__.py +1 -0
- bootstack/widgets/_impl/_internal/__init__.py +0 -0
- bootstack/widgets/_impl/_internal/wrapper_base.py +307 -0
- bootstack/widgets/_impl/_parts/__init__.py +11 -0
- bootstack/widgets/_impl/_parts/numberentry_part.py +385 -0
- bootstack/widgets/_impl/_parts/spinnerentry_part.py +434 -0
- bootstack/widgets/_impl/_parts/textentry_part.py +406 -0
- bootstack/widgets/_impl/composites/__init__.py +33 -0
- bootstack/widgets/_impl/composites/_dateutils.py +33 -0
- bootstack/widgets/_impl/composites/_image_fit.py +105 -0
- bootstack/widgets/_impl/composites/accordion.py +381 -0
- bootstack/widgets/_impl/composites/avatar.py +191 -0
- bootstack/widgets/_impl/composites/buttongroup.py +371 -0
- bootstack/widgets/_impl/composites/calendar.py +972 -0
- bootstack/widgets/_impl/composites/carousel.py +567 -0
- bootstack/widgets/_impl/composites/chart.py +882 -0
- bootstack/widgets/_impl/composites/compositeframe.py +298 -0
- bootstack/widgets/_impl/composites/contextmenu.py +1951 -0
- bootstack/widgets/_impl/composites/dateentry.py +404 -0
- bootstack/widgets/_impl/composites/dropdownbutton.py +325 -0
- bootstack/widgets/_impl/composites/expander.py +515 -0
- bootstack/widgets/_impl/composites/field.py +670 -0
- bootstack/widgets/_impl/composites/form.py +1066 -0
- bootstack/widgets/_impl/composites/gallery.py +551 -0
- bootstack/widgets/_impl/composites/list/__init__.py +15 -0
- bootstack/widgets/_impl/composites/list/listitem.py +802 -0
- bootstack/widgets/_impl/composites/list/listview.py +1433 -0
- bootstack/widgets/_impl/composites/menu/__init__.py +18 -0
- bootstack/widgets/_impl/composites/menu/model.py +358 -0
- bootstack/widgets/_impl/composites/menu/render_native.py +134 -0
- bootstack/widgets/_impl/composites/menu/render_themed.py +134 -0
- bootstack/widgets/_impl/composites/meter.py +860 -0
- bootstack/widgets/_impl/composites/numericentry.py +201 -0
- bootstack/widgets/_impl/composites/pagestack.py +395 -0
- bootstack/widgets/_impl/composites/passwordentry.py +142 -0
- bootstack/widgets/_impl/composites/pathentry.py +168 -0
- bootstack/widgets/_impl/composites/picture.py +289 -0
- bootstack/widgets/_impl/composites/radiogroup.py +511 -0
- bootstack/widgets/_impl/composites/scrolledtext.py +375 -0
- bootstack/widgets/_impl/composites/scrolledtext.pyi +186 -0
- bootstack/widgets/_impl/composites/scrollview.py +764 -0
- bootstack/widgets/_impl/composites/selectbox.py +1026 -0
- bootstack/widgets/_impl/composites/shell/__init__.py +40 -0
- bootstack/widgets/_impl/composites/shell/content_host.py +52 -0
- bootstack/widgets/_impl/composites/shell/layout.py +335 -0
- bootstack/widgets/_impl/composites/shell/nav_panel.py +345 -0
- bootstack/widgets/_impl/composites/shell/providers.py +558 -0
- bootstack/widgets/_impl/composites/shell/rail.py +117 -0
- bootstack/widgets/_impl/composites/shell/shell.py +581 -0
- bootstack/widgets/_impl/composites/shell/workspace.py +273 -0
- bootstack/widgets/_impl/composites/sidenav/__init__.py +16 -0
- bootstack/widgets/_impl/composites/sidenav/header.py +81 -0
- bootstack/widgets/_impl/composites/sidenav/separator.py +44 -0
- bootstack/widgets/_impl/composites/slider/__init__.py +7 -0
- bootstack/widgets/_impl/composites/slider/_shared.py +195 -0
- bootstack/widgets/_impl/composites/slider/rangeslider.py +982 -0
- bootstack/widgets/_impl/composites/slider/slider.py +851 -0
- bootstack/widgets/_impl/composites/spinnerentry.py +185 -0
- bootstack/widgets/_impl/composites/tableview/__init__.py +5 -0
- bootstack/widgets/_impl/composites/tableview/tableview.py +3388 -0
- bootstack/widgets/_impl/composites/tableview/types.py +169 -0
- bootstack/widgets/_impl/composites/tabs/__init__.py +23 -0
- bootstack/widgets/_impl/composites/tabs/tabitem.py +389 -0
- bootstack/widgets/_impl/composites/tabs/tabs.py +974 -0
- bootstack/widgets/_impl/composites/tabs/tabview.py +650 -0
- bootstack/widgets/_impl/composites/textarea/__init__.py +26 -0
- bootstack/widgets/_impl/composites/textarea/change.py +46 -0
- bootstack/widgets/_impl/composites/textarea/codeeditor.py +526 -0
- bootstack/widgets/_impl/composites/textarea/core.py +495 -0
- bootstack/widgets/_impl/composites/textarea/decoration.py +42 -0
- bootstack/widgets/_impl/composites/textarea/diff.py +127 -0
- bootstack/widgets/_impl/composites/textarea/extensions/__init__.py +1 -0
- bootstack/widgets/_impl/composites/textarea/extensions/bracket_matcher.py +138 -0
- bootstack/widgets/_impl/composites/textarea/extensions/indent_guides.py +158 -0
- bootstack/widgets/_impl/composites/textarea/extensions/line_numbers.py +138 -0
- bootstack/widgets/_impl/composites/textarea/extensions/pygments_highlighter.py +312 -0
- bootstack/widgets/_impl/composites/textarea/extensions/smart_indent.py +177 -0
- bootstack/widgets/_impl/composites/textarea/filter.py +171 -0
- bootstack/widgets/_impl/composites/textarea/search_overlay.py +459 -0
- bootstack/widgets/_impl/composites/textarea/sidebar.py +88 -0
- bootstack/widgets/_impl/composites/textarea/style_registry.py +178 -0
- bootstack/widgets/_impl/composites/textarea/textarea.py +606 -0
- bootstack/widgets/_impl/composites/textarea/undo.py +217 -0
- bootstack/widgets/_impl/composites/textentry.py +57 -0
- bootstack/widgets/_impl/composites/timeentry.py +176 -0
- bootstack/widgets/_impl/composites/toast.py +390 -0
- bootstack/widgets/_impl/composites/toast_stack.py +156 -0
- bootstack/widgets/_impl/composites/togglegroup.py +418 -0
- bootstack/widgets/_impl/composites/toolbar.py +608 -0
- bootstack/widgets/_impl/composites/tooltip.py +491 -0
- bootstack/widgets/_impl/composites/tree/__init__.py +7 -0
- bootstack/widgets/_impl/composites/tree/source_binding.py +136 -0
- bootstack/widgets/_impl/composites/tree/treeitem.py +393 -0
- bootstack/widgets/_impl/composites/tree/treenode.py +174 -0
- bootstack/widgets/_impl/composites/tree/treeview.py +841 -0
- bootstack/widgets/_impl/mixins/__init__.py +24 -0
- bootstack/widgets/_impl/mixins/configure_mixin.py +216 -0
- bootstack/widgets/_impl/mixins/entry_mixin.py +134 -0
- bootstack/widgets/_impl/mixins/font_mixin.py +368 -0
- bootstack/widgets/_impl/mixins/icon_mixin.py +61 -0
- bootstack/widgets/_impl/mixins/localization_mixin.py +253 -0
- bootstack/widgets/_impl/mixins/signal_mixin.py +268 -0
- bootstack/widgets/_impl/mixins/validation_mixin.py +226 -0
- bootstack/widgets/_impl/primitives/__init__.py +49 -0
- bootstack/widgets/_impl/primitives/_menubutton.py +107 -0
- bootstack/widgets/_impl/primitives/badge.py +45 -0
- bootstack/widgets/_impl/primitives/button.py +76 -0
- bootstack/widgets/_impl/primitives/card.py +45 -0
- bootstack/widgets/_impl/primitives/checkbutton.py +124 -0
- bootstack/widgets/_impl/primitives/checktoggle.py +62 -0
- bootstack/widgets/_impl/primitives/combobox.py +156 -0
- bootstack/widgets/_impl/primitives/entry.py +87 -0
- bootstack/widgets/_impl/primitives/flexframe.py +448 -0
- bootstack/widgets/_impl/primitives/frame.py +185 -0
- bootstack/widgets/_impl/primitives/gridframe.py +546 -0
- bootstack/widgets/_impl/primitives/label.py +84 -0
- bootstack/widgets/_impl/primitives/labelframe.py +54 -0
- bootstack/widgets/_impl/primitives/optionmenu.py +387 -0
- bootstack/widgets/_impl/primitives/packframe.py +227 -0
- bootstack/widgets/_impl/primitives/panedwindow.py +45 -0
- bootstack/widgets/_impl/primitives/progressbar.py +83 -0
- bootstack/widgets/_impl/primitives/radiobutton.py +115 -0
- bootstack/widgets/_impl/primitives/radiotoggle.py +54 -0
- bootstack/widgets/_impl/primitives/scrollbar.py +42 -0
- bootstack/widgets/_impl/primitives/separator.py +43 -0
- bootstack/widgets/_impl/primitives/sizegrip.py +33 -0
- bootstack/widgets/_impl/primitives/spinbox.py +95 -0
- bootstack/widgets/_impl/primitives/switch.py +44 -0
- bootstack/widgets/_impl/primitives/treeview.py +69 -0
- bootstack/widgets/app.py +371 -0
- bootstack/widgets/appshell.py +1179 -0
- bootstack/widgets/avatar.py +140 -0
- bootstack/widgets/boolean_controls.py +455 -0
- bootstack/widgets/button.py +224 -0
- bootstack/widgets/buttongroup.py +239 -0
- bootstack/widgets/calendar.py +195 -0
- bootstack/widgets/card.py +159 -0
- bootstack/widgets/carousel.py +241 -0
- bootstack/widgets/chart.py +302 -0
- bootstack/widgets/codeeditor.py +675 -0
- bootstack/widgets/contextmenu.py +371 -0
- bootstack/widgets/datatable.py +688 -0
- bootstack/widgets/datefield.py +395 -0
- bootstack/widgets/divider.py +60 -0
- bootstack/widgets/expander.py +579 -0
- bootstack/widgets/form.py +200 -0
- bootstack/widgets/gallery.py +250 -0
- bootstack/widgets/gauge.py +168 -0
- bootstack/widgets/grid.py +121 -0
- bootstack/widgets/groupbox.py +162 -0
- bootstack/widgets/label.py +229 -0
- bootstack/widgets/listview.py +354 -0
- bootstack/widgets/menubutton.py +419 -0
- bootstack/widgets/numberfield.py +431 -0
- bootstack/widgets/pagestack.py +345 -0
- bootstack/widgets/passwordfield.py +383 -0
- bootstack/widgets/pathfield.py +454 -0
- bootstack/widgets/picture.py +251 -0
- bootstack/widgets/progressbar.py +106 -0
- bootstack/widgets/radio_variants.py +271 -0
- bootstack/widgets/radiogroup.py +228 -0
- bootstack/widgets/scrollbar.py +64 -0
- bootstack/widgets/scrollview.py +186 -0
- bootstack/widgets/select.py +302 -0
- bootstack/widgets/selectbutton.py +182 -0
- bootstack/widgets/sidebar_toggle.py +130 -0
- bootstack/widgets/sizegrip.py +42 -0
- bootstack/widgets/slider.py +408 -0
- bootstack/widgets/spinbox.py +147 -0
- bootstack/widgets/spinnerfield.py +413 -0
- bootstack/widgets/splash.py +367 -0
- bootstack/widgets/splitview.py +482 -0
- bootstack/widgets/stacks.py +249 -0
- bootstack/widgets/statusbar.py +189 -0
- bootstack/widgets/tabs.py +425 -0
- bootstack/widgets/textarea.py +459 -0
- bootstack/widgets/textfield.py +410 -0
- bootstack/widgets/theme_toggle.py +92 -0
- bootstack/widgets/timefield.py +340 -0
- bootstack/widgets/toast.py +314 -0
- bootstack/widgets/togglegroup.py +231 -0
- bootstack/widgets/toolbar.py +347 -0
- bootstack/widgets/tooltip.py +94 -0
- bootstack/widgets/tree.py +648 -0
- bootstack/widgets/types.py +305 -0
- bootstack/widgets/window.py +307 -0
- bootstack-0.1.0.dist-info/METADATA +301 -0
- bootstack-0.1.0.dist-info/RECORD +471 -0
- bootstack-0.1.0.dist-info/WHEEL +5 -0
- bootstack-0.1.0.dist-info/entry_points.txt +2 -0
- bootstack-0.1.0.dist-info/licenses/LICENSE +21 -0
- bootstack-0.1.0.dist-info/licenses/NOTICE +10 -0
- bootstack-0.1.0.dist-info/top_level.txt +1 -0
bootstack/style/style.py
ADDED
|
@@ -0,0 +1,609 @@
|
|
|
1
|
+
"""Enhanced TTK Style class with builder registry and theme management."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import tkinter
|
|
6
|
+
from tkinter.ttk import Style as ttkStyle
|
|
7
|
+
from typing import Dict, Optional, Set
|
|
8
|
+
|
|
9
|
+
from bootstack._runtime.app import get_app_settings
|
|
10
|
+
from bootstack.style.style_builder_ttk import StyleBuilderTtk
|
|
11
|
+
from bootstack.style.theme_provider import ThemeProvider, use_theme
|
|
12
|
+
from bootstack.widgets.types import Master
|
|
13
|
+
|
|
14
|
+
_style_instance: Style | None = None
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Style(ttkStyle):
|
|
18
|
+
"""Enhanced TTK Style with builder registry and theme management.
|
|
19
|
+
|
|
20
|
+
This class extends ttk.Style to provide:
|
|
21
|
+
|
|
22
|
+
- Singleton pattern (one instance per Tkinter master)
|
|
23
|
+
- Integration with StyleBuilder registry
|
|
24
|
+
- Theme management via ThemeProvider
|
|
25
|
+
- Automatic style rebuilding on theme changes
|
|
26
|
+
- Custom style options support
|
|
27
|
+
|
|
28
|
+
The Style class maintains registries to track:
|
|
29
|
+
|
|
30
|
+
- All created styles (for theme change rebuilds)
|
|
31
|
+
- Which styles exist in each theme (for caching)
|
|
32
|
+
- Custom options per style (for recreating with same options)
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __new__(cls, *args, **kwargs):
|
|
36
|
+
"""Ensure Style() always returns the global singleton instance.
|
|
37
|
+
|
|
38
|
+
If an instance already exists, return it. Otherwise, create it.
|
|
39
|
+
"""
|
|
40
|
+
global _style_instance
|
|
41
|
+
if _style_instance is None:
|
|
42
|
+
_style_instance = super().__new__(cls)
|
|
43
|
+
return _style_instance
|
|
44
|
+
|
|
45
|
+
def __init__(self, master: Master = None, theme: str = "light"):
|
|
46
|
+
"""Initialize the Style instance.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
master: Tkinter master widget (None for default)
|
|
50
|
+
theme: Initial theme name (default: "light")
|
|
51
|
+
"""
|
|
52
|
+
# Prevent reinitialization on subsequent Style() calls
|
|
53
|
+
if getattr(self, "_initialized", False):
|
|
54
|
+
# Optionally honor a theme argument after initialization
|
|
55
|
+
try:
|
|
56
|
+
if theme and theme != self._current_theme:
|
|
57
|
+
self.theme_use(theme)
|
|
58
|
+
except Exception:
|
|
59
|
+
pass
|
|
60
|
+
return
|
|
61
|
+
|
|
62
|
+
super().__init__(master)
|
|
63
|
+
|
|
64
|
+
self._theme_provider = use_theme(theme)
|
|
65
|
+
self._style_builder = StyleBuilderTtk(theme_provider=self._theme_provider, style_instance=None)
|
|
66
|
+
self._style_builder.set_style_instance(self)
|
|
67
|
+
|
|
68
|
+
# Style registries
|
|
69
|
+
self._style_registry: Set[str] = set()
|
|
70
|
+
self._style_accents: Dict[str, Optional[str]] = {}
|
|
71
|
+
self._style_variants: Dict[str, str] = {}
|
|
72
|
+
self._style_options: Dict[str, dict] = {}
|
|
73
|
+
self._style_widget_classes: Dict[str, str] = {}
|
|
74
|
+
|
|
75
|
+
# Current theme tracking
|
|
76
|
+
self._current_theme: Optional[str] = theme
|
|
77
|
+
|
|
78
|
+
# Incremented on every theme change so a widget can detect a restyle it
|
|
79
|
+
# missed while off-screen (stamped on `_bs_theme_version`; see
|
|
80
|
+
# apply_theme_walk).
|
|
81
|
+
self._theme_version: int = 0
|
|
82
|
+
|
|
83
|
+
# Cached Tk builder — lazily initialized, reused across all widget creations
|
|
84
|
+
self._cached_tk_builder = None
|
|
85
|
+
|
|
86
|
+
self.theme_use(theme)
|
|
87
|
+
self._initialized = True
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def style_builder(self) -> StyleBuilderTtk:
|
|
91
|
+
"""Get the builder manager instance.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
StyleBuilder instance
|
|
95
|
+
"""
|
|
96
|
+
return self._style_builder
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def theme_provider(self) -> ThemeProvider:
|
|
100
|
+
"""Get the theme provider instance.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
ThemeProvider instance
|
|
104
|
+
"""
|
|
105
|
+
return self._theme_provider
|
|
106
|
+
|
|
107
|
+
# ----- Theme metadata helpers -------------------------------------------------
|
|
108
|
+
|
|
109
|
+
def list_themes(self) -> list[dict[str, str]]:
|
|
110
|
+
"""Return available themes as [{'name', 'display_name'}, ...].
|
|
111
|
+
|
|
112
|
+
This delegates to the underlying ThemeProvider. Themes are always
|
|
113
|
+
loaded from both the v2 and legacy theme packages.
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
List of theme dictionaries with 'name' and 'display_name' keys.
|
|
117
|
+
"""
|
|
118
|
+
return self._theme_provider.list_themes()
|
|
119
|
+
|
|
120
|
+
@property
|
|
121
|
+
def current_theme(self) -> Optional[str]:
|
|
122
|
+
"""Get the current theme name.
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
Current theme name or None
|
|
126
|
+
"""
|
|
127
|
+
return self._current_theme
|
|
128
|
+
|
|
129
|
+
@property
|
|
130
|
+
def colors(self) -> dict[str, str]:
|
|
131
|
+
"""Get colors for the current theme.
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
Colors dictionary from ThemeProvider
|
|
135
|
+
"""
|
|
136
|
+
return self._theme_provider.colors
|
|
137
|
+
|
|
138
|
+
def style_exists(self, style: str) -> bool:
|
|
139
|
+
"""Check if a style exists (basic check).
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
style: TTK style name
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
True if style has any configuration
|
|
146
|
+
"""
|
|
147
|
+
return bool(self.configure(style))
|
|
148
|
+
|
|
149
|
+
def register_style(self, ttk_style: str, options: Optional[dict] = None):
|
|
150
|
+
"""Register a style in the current theme.
|
|
151
|
+
|
|
152
|
+
This adds the style to registries so it can be:
|
|
153
|
+
|
|
154
|
+
- Cached (not recreated if already exists)
|
|
155
|
+
- Rebuilt when theme changes
|
|
156
|
+
- Recreated with same options
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
ttk_style: Full TTK style name
|
|
160
|
+
options: Optional custom style options
|
|
161
|
+
"""
|
|
162
|
+
# Add to global registry
|
|
163
|
+
self._style_registry.add(ttk_style)
|
|
164
|
+
|
|
165
|
+
# Store a copy of custom options if provided (to prevent mutation issues)
|
|
166
|
+
if options:
|
|
167
|
+
self._style_options[ttk_style] = options.copy()
|
|
168
|
+
|
|
169
|
+
def create_style(
|
|
170
|
+
self,
|
|
171
|
+
widget_class: str,
|
|
172
|
+
variant: str,
|
|
173
|
+
ttk_style: str,
|
|
174
|
+
accent: Optional[str] = None,
|
|
175
|
+
options: Optional[dict] = None) -> None:
|
|
176
|
+
"""Create a new style if it doesn't exist in current theme.
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
widget_class: TTK widget class (e.g., "TButton")
|
|
180
|
+
variant: Variant name (e.g., "outline")
|
|
181
|
+
ttk_style: Full TTK style name (e.g., "success.Outline.TButton")
|
|
182
|
+
accent: Optional accent token (e.g., "success", "blue[100]")
|
|
183
|
+
options: Optional custom style options
|
|
184
|
+
"""
|
|
185
|
+
# Fast path: Python-side set lookup before crossing into Tcl
|
|
186
|
+
if ttk_style in self._style_registry:
|
|
187
|
+
return
|
|
188
|
+
# Fallback for styles known to Tcl but not our registry (e.g. external theme changes)
|
|
189
|
+
if self.style_exists(ttk_style):
|
|
190
|
+
self.register_style(ttk_style, options)
|
|
191
|
+
return
|
|
192
|
+
|
|
193
|
+
# Call builder with widget class, variant, and parsed accent
|
|
194
|
+
self._style_builder.call_builder(
|
|
195
|
+
widget_class=widget_class,
|
|
196
|
+
variant=variant,
|
|
197
|
+
ttk_style=ttk_style,
|
|
198
|
+
accent=accent,
|
|
199
|
+
**(options or {})
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
# Store the accent, variant, and widget_class so we can rebuild with the same values
|
|
203
|
+
self._style_accents[ttk_style] = accent
|
|
204
|
+
self._style_variants[ttk_style] = variant
|
|
205
|
+
self._style_widget_classes[ttk_style] = widget_class
|
|
206
|
+
|
|
207
|
+
# Register it
|
|
208
|
+
self.register_style(ttk_style, options)
|
|
209
|
+
|
|
210
|
+
def theme_use(self, name: str = None) -> str | None:
|
|
211
|
+
"""Switch to a different theme and rebuild all styles.
|
|
212
|
+
|
|
213
|
+
Applies theme change to the global Style instance, rebuilds all
|
|
214
|
+
TTK styles and registered Tk widgets, and publishes a legacy
|
|
215
|
+
theme-change event for subscribers.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
name: Theme name to switch to. If None, returns the current theme.
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
The current theme name, or None if no theme is set.
|
|
222
|
+
"""
|
|
223
|
+
if name is None:
|
|
224
|
+
return super().theme_use()
|
|
225
|
+
|
|
226
|
+
# Only call use() if theme is changing to avoid redundant build_theme_colors()
|
|
227
|
+
if name != self._theme_provider.name:
|
|
228
|
+
self._theme_provider.use(name)
|
|
229
|
+
self._current_theme = name
|
|
230
|
+
|
|
231
|
+
if name not in self.theme_names():
|
|
232
|
+
self.theme_create(name, 'clam', {})
|
|
233
|
+
|
|
234
|
+
super().theme_use(name)
|
|
235
|
+
|
|
236
|
+
# Initialize all default widget styles before rebuilding custom styles
|
|
237
|
+
# self._style_builder.initialize_all_default_styles()
|
|
238
|
+
|
|
239
|
+
self._rebuild_all_styles()
|
|
240
|
+
|
|
241
|
+
# Bump version so off-screen widgets know they missed this theme change
|
|
242
|
+
# (they recolor when their container next shows them — see apply_theme_walk).
|
|
243
|
+
self._theme_version += 1
|
|
244
|
+
|
|
245
|
+
# Fire the bootstack theme-change event after full rebuild so handlers
|
|
246
|
+
# can safely read new colors. Uses get_default_root lazily to avoid
|
|
247
|
+
# importing app at module level.
|
|
248
|
+
try:
|
|
249
|
+
from bootstack._runtime.app import get_default_root
|
|
250
|
+
root = get_default_root()
|
|
251
|
+
# Unified theme walk: recolor every visible tree widget now; off-screen
|
|
252
|
+
# ones are left stale and recovered when their page/tab/section is next
|
|
253
|
+
# shown. Non-tree reactors (the Image handle, OS window chrome, the
|
|
254
|
+
# app-level on_theme_change callback) listen on <<BsThemeChanged>> below.
|
|
255
|
+
if root is not None:
|
|
256
|
+
self.apply_theme_walk(root, only_stale=False)
|
|
257
|
+
root.event_generate(
|
|
258
|
+
"<<BsThemeChanged>>",
|
|
259
|
+
data={"theme": name, "mode": self._theme_provider.mode},
|
|
260
|
+
when="tail",
|
|
261
|
+
)
|
|
262
|
+
except Exception:
|
|
263
|
+
pass
|
|
264
|
+
|
|
265
|
+
return self._current_theme
|
|
266
|
+
|
|
267
|
+
def _rebuild_all_styles(self):
|
|
268
|
+
"""Recreate all registered styles when theme changes.
|
|
269
|
+
|
|
270
|
+
This iterates through all styles that have been created and
|
|
271
|
+
rebuilds them using the new theme's colors, preserving any
|
|
272
|
+
custom options.
|
|
273
|
+
"""
|
|
274
|
+
for style in self._style_registry:
|
|
275
|
+
# Get stored options, accent, and variant
|
|
276
|
+
options = self._style_options.get(style, {})
|
|
277
|
+
accent = self._style_accents.get(style)
|
|
278
|
+
variant = self._style_variants.get(style)
|
|
279
|
+
|
|
280
|
+
# Use stored widget_class if available (preserves composite classes like 'Menubar.TField')
|
|
281
|
+
# Fall back to parsing from style name for backwards compatibility
|
|
282
|
+
widget_class = self._style_widget_classes.get(style)
|
|
283
|
+
if not widget_class:
|
|
284
|
+
parsed = self._parse_style_name(style)
|
|
285
|
+
if not parsed:
|
|
286
|
+
continue
|
|
287
|
+
widget_class = parsed['widget_class']
|
|
288
|
+
# Only use parsed variant if we don't have a stored one
|
|
289
|
+
if variant is None:
|
|
290
|
+
variant = parsed['variant']
|
|
291
|
+
|
|
292
|
+
if variant is None:
|
|
293
|
+
from bootstack.style.style_builder_ttk import StyleBuilderTtk
|
|
294
|
+
variant = StyleBuilderTtk.get_default_variant(widget_class)
|
|
295
|
+
|
|
296
|
+
self._style_builder.call_builder(
|
|
297
|
+
widget_class=widget_class,
|
|
298
|
+
variant=variant,
|
|
299
|
+
ttk_style=style,
|
|
300
|
+
accent=accent,
|
|
301
|
+
**options
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
def _get_tk_builder(self):
|
|
305
|
+
"""Return the cached Tk builder, creating it on first call."""
|
|
306
|
+
if self._cached_tk_builder is None:
|
|
307
|
+
from bootstack.style.style_builder_tk import StyleBuilderTk
|
|
308
|
+
self._cached_tk_builder = StyleBuilderTk(
|
|
309
|
+
theme_provider=self._theme_provider,
|
|
310
|
+
style_instance=self,
|
|
311
|
+
)
|
|
312
|
+
return self._cached_tk_builder
|
|
313
|
+
|
|
314
|
+
# ------------------------------------------------------------------ Unified theme walk
|
|
315
|
+
#
|
|
316
|
+
# The single mechanism for recoloring *tree widgets* on a theme change. A
|
|
317
|
+
# widget that needs theming exposes one method, ``_bs_apply_theme()``, doing
|
|
318
|
+
# all of its work (background + any canvas repaint). Two triggers drive it,
|
|
319
|
+
# and neither relies on ``<Map>``:
|
|
320
|
+
# * a theme change walks from the root and applies every *visible* widget;
|
|
321
|
+
# * a container showing a page/tab walks that subtree and applies the
|
|
322
|
+
# *stale* ones (those that missed the change while hidden).
|
|
323
|
+
# Visibility is resolved at apply time, so nothing is deferred to a hoped-for
|
|
324
|
+
# event. Non-tree reactors (the Image handle, OS window chrome, the app-level
|
|
325
|
+
# on_theme_change callback) stay on the root ``<<BsThemeChanged>>`` binding.
|
|
326
|
+
|
|
327
|
+
def _apply_theme_to_widget(self, widget) -> None:
|
|
328
|
+
"""Apply the current theme to one widget and stamp its version.
|
|
329
|
+
|
|
330
|
+
Re-applies the surface background for an autostyled Tk widget, then runs
|
|
331
|
+
the widget's ``_bs_apply_theme`` hook if it defines one (canvas painters
|
|
332
|
+
redraw there). Both are best-effort; a failure on one widget never aborts
|
|
333
|
+
the walk.
|
|
334
|
+
"""
|
|
335
|
+
surface = getattr(widget, '_surface', None)
|
|
336
|
+
if surface is not None:
|
|
337
|
+
try:
|
|
338
|
+
self._get_tk_builder().call_builder(widget, surface=surface)
|
|
339
|
+
except Exception:
|
|
340
|
+
pass
|
|
341
|
+
hook = getattr(widget, '_bs_apply_theme', None)
|
|
342
|
+
if hook is not None:
|
|
343
|
+
try:
|
|
344
|
+
hook()
|
|
345
|
+
except Exception:
|
|
346
|
+
pass
|
|
347
|
+
widget._bs_theme_version = self._theme_version
|
|
348
|
+
|
|
349
|
+
def apply_theme_walk(self, root_widget, *, only_stale: bool) -> None:
|
|
350
|
+
"""Walk ``root_widget``'s subtree and theme each on-screen widget.
|
|
351
|
+
|
|
352
|
+
Args:
|
|
353
|
+
root_widget: Subtree root — the application root on a theme change, or
|
|
354
|
+
a freshly-shown page/tab on a container-show.
|
|
355
|
+
only_stale: When True (container-show), apply every widget in the
|
|
356
|
+
subtree that missed the current theme version — regardless of
|
|
357
|
+
``winfo_viewable()``, which is unreliable for a widget embedded in
|
|
358
|
+
a scrollable page's canvas; the container is showing it now. When
|
|
359
|
+
False (theme change), apply every *visible* widget and leave the
|
|
360
|
+
off-screen ones stale for their next container-show.
|
|
361
|
+
"""
|
|
362
|
+
version = self._theme_version
|
|
363
|
+
stack = [root_widget]
|
|
364
|
+
while stack:
|
|
365
|
+
w = stack.pop()
|
|
366
|
+
# A PageStack keeps inactive pages mapped (so re-showing one doesn't
|
|
367
|
+
# blank-flash on macOS) but flags them hidden; prune the whole subtree
|
|
368
|
+
# so a theme change repaints only the visible page. A hidden page's
|
|
369
|
+
# widgets stay stale and are repainted on its next show (only_stale).
|
|
370
|
+
if getattr(w, '_bs_nav_hidden', False):
|
|
371
|
+
continue
|
|
372
|
+
try:
|
|
373
|
+
stack.extend(w.winfo_children())
|
|
374
|
+
except Exception:
|
|
375
|
+
pass
|
|
376
|
+
themed = hasattr(w, '_bs_apply_theme') or getattr(w, '_surface', None) is not None
|
|
377
|
+
if not themed:
|
|
378
|
+
continue
|
|
379
|
+
if only_stale:
|
|
380
|
+
if getattr(w, '_bs_theme_version', -1) == version:
|
|
381
|
+
continue
|
|
382
|
+
else:
|
|
383
|
+
try:
|
|
384
|
+
if not w.winfo_viewable():
|
|
385
|
+
continue
|
|
386
|
+
except Exception:
|
|
387
|
+
continue
|
|
388
|
+
self._apply_theme_to_widget(w)
|
|
389
|
+
|
|
390
|
+
@staticmethod
|
|
391
|
+
def _parse_style_name(ttk_style: str) -> Optional[dict]:
|
|
392
|
+
"""Parse TTK style name to extract widget class and variant.
|
|
393
|
+
|
|
394
|
+
Args:
|
|
395
|
+
ttk_style: Full TTK style name
|
|
396
|
+
|
|
397
|
+
Returns:
|
|
398
|
+
Dict with 'widget_class' and 'variant', or None
|
|
399
|
+
|
|
400
|
+
Examples:
|
|
401
|
+
>>> Style._parse_style_name("success.TButton")
|
|
402
|
+
{'widget_class': 'TButton', 'variant': 'solid'}
|
|
403
|
+
>>> Style._parse_style_name("info.Thin.TProgressbar")
|
|
404
|
+
{'widget_class': 'TProgressbar', 'variant': 'thin'}
|
|
405
|
+
"""
|
|
406
|
+
# Split into parts
|
|
407
|
+
parts = ttk_style.split('.')
|
|
408
|
+
|
|
409
|
+
if not parts:
|
|
410
|
+
return None
|
|
411
|
+
|
|
412
|
+
# Extract widget class (e.g., "TButton")
|
|
413
|
+
# Widget class is always the LAST part starting with 'T'
|
|
414
|
+
widget_class = None
|
|
415
|
+
for part in parts:
|
|
416
|
+
if part.startswith('T'):
|
|
417
|
+
widget_class = part
|
|
418
|
+
|
|
419
|
+
if not widget_class:
|
|
420
|
+
return None
|
|
421
|
+
|
|
422
|
+
# Determine variant using registered builders for this widget
|
|
423
|
+
from bootstack.style.style_builder_ttk import StyleBuilderTtk
|
|
424
|
+
builder_variants = set(v.lower() for v in StyleBuilderTtk.get_registered_builders(widget_class))
|
|
425
|
+
|
|
426
|
+
variant = None
|
|
427
|
+
accent = None
|
|
428
|
+
for part in parts:
|
|
429
|
+
# Skip the widget class itself (e.g., 'TCheckbutton')
|
|
430
|
+
if part == widget_class:
|
|
431
|
+
continue
|
|
432
|
+
token = part.lower()
|
|
433
|
+
# Identify variant by registry match
|
|
434
|
+
if token in builder_variants and variant is None:
|
|
435
|
+
variant = token
|
|
436
|
+
continue
|
|
437
|
+
# First non-variant token becomes accent
|
|
438
|
+
if accent is None:
|
|
439
|
+
accent = part
|
|
440
|
+
|
|
441
|
+
if variant is None:
|
|
442
|
+
variant = StyleBuilderTtk.get_default_variant(widget_class)
|
|
443
|
+
|
|
444
|
+
return {
|
|
445
|
+
'widget_class': widget_class,
|
|
446
|
+
'variant': variant
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
def get_style_builder(self) -> StyleBuilderTtk:
|
|
450
|
+
"""Get the style builder instance.
|
|
451
|
+
|
|
452
|
+
Returns:
|
|
453
|
+
StyleBuilder instance
|
|
454
|
+
"""
|
|
455
|
+
return self._style_builder
|
|
456
|
+
|
|
457
|
+
def __repr__(self) -> str:
|
|
458
|
+
"""String representation of Style instance."""
|
|
459
|
+
return f"<Style theme={self._current_theme} styles={len(self._style_registry)}>"
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
def get_style(master: Master = None) -> Style:
|
|
463
|
+
"""Return the global Style singleton instance.
|
|
464
|
+
|
|
465
|
+
Convenience helper function that ensures a single Style instance
|
|
466
|
+
is used across the application.
|
|
467
|
+
|
|
468
|
+
Args:
|
|
469
|
+
master: Optional master for initial construction; ignored thereafter.
|
|
470
|
+
|
|
471
|
+
Returns:
|
|
472
|
+
Global Style instance.
|
|
473
|
+
|
|
474
|
+
Examples:
|
|
475
|
+
>>> style = get_style()
|
|
476
|
+
>>> style.theme_use("nord-dark")
|
|
477
|
+
"""
|
|
478
|
+
if _style_instance:
|
|
479
|
+
return _style_instance
|
|
480
|
+
else:
|
|
481
|
+
return Style(master)
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
def reset_style() -> None:
|
|
485
|
+
"""Drop the process-wide Style singleton.
|
|
486
|
+
|
|
487
|
+
The Style singleton is a `ttk.Style` bound to the Tk root that created it.
|
|
488
|
+
Once that root is destroyed its interpreter is gone, so the singleton can no
|
|
489
|
+
longer be used — a fresh App must rebuild Style against the new root. Call
|
|
490
|
+
this after destroying the root (App.destroy does) so the next
|
|
491
|
+
`get_style()`/`Style()` constructs cleanly instead of reusing a dead
|
|
492
|
+
instance (whose `theme_use` would fail, e.g. "named font body does not
|
|
493
|
+
already exist").
|
|
494
|
+
"""
|
|
495
|
+
global _style_instance
|
|
496
|
+
_style_instance = None
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
def get_style_builder() -> StyleBuilderTtk:
|
|
500
|
+
"""Return the style builder for the currently active theme.
|
|
501
|
+
|
|
502
|
+
Returns:
|
|
503
|
+
The StyleBuilderTtk instance for the active theme.
|
|
504
|
+
|
|
505
|
+
Examples:
|
|
506
|
+
>>> builder = get_style_builder()
|
|
507
|
+
>>> primary_color = builder.color("primary")
|
|
508
|
+
"""
|
|
509
|
+
style = get_style()
|
|
510
|
+
return style.style_builder
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
# --- Global Utilities ---
|
|
514
|
+
|
|
515
|
+
def set_theme(name: str) -> None:
|
|
516
|
+
"""Set the active application theme.
|
|
517
|
+
|
|
518
|
+
Args:
|
|
519
|
+
name: Theme name to activate (e.g., "bootstrap-light", "nord-dark").
|
|
520
|
+
|
|
521
|
+
Examples:
|
|
522
|
+
>>> set_theme("nord-dark")
|
|
523
|
+
"""
|
|
524
|
+
style = get_style()
|
|
525
|
+
style.theme_use(name)
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
def toggle_theme() -> None:
|
|
529
|
+
"""Toggle the active application theme between light and dark mode.
|
|
530
|
+
|
|
531
|
+
Uses the light and dark themes specified in app settings, or defaults
|
|
532
|
+
to bootstrap-light and bootstrap-dark.
|
|
533
|
+
|
|
534
|
+
Examples:
|
|
535
|
+
>>> toggle_theme()
|
|
536
|
+
"""
|
|
537
|
+
settings = get_app_settings()
|
|
538
|
+
light = settings.light_theme
|
|
539
|
+
dark = settings.dark_theme
|
|
540
|
+
theme = get_theme()
|
|
541
|
+
if theme == light or theme == "light":
|
|
542
|
+
set_theme(dark)
|
|
543
|
+
else:
|
|
544
|
+
set_theme(light)
|
|
545
|
+
|
|
546
|
+
|
|
547
|
+
def get_theme() -> str:
|
|
548
|
+
"""Return the name of the currently active theme.
|
|
549
|
+
|
|
550
|
+
Returns:
|
|
551
|
+
Name of the active theme.
|
|
552
|
+
|
|
553
|
+
Examples:
|
|
554
|
+
>>> theme = get_theme()
|
|
555
|
+
>>> print(theme) # "bootstrap-dark"
|
|
556
|
+
"""
|
|
557
|
+
style = get_style()
|
|
558
|
+
return style.theme_use()
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
def get_themes() -> list[dict[str, str]]:
|
|
562
|
+
"""Return the list of all registered themes.
|
|
563
|
+
|
|
564
|
+
Returns:
|
|
565
|
+
List of dictionaries containing `name` and `display_name` for each theme.
|
|
566
|
+
|
|
567
|
+
Examples:
|
|
568
|
+
>>> themes = get_themes()
|
|
569
|
+
>>> print([theme["name"] for theme in themes])
|
|
570
|
+
"""
|
|
571
|
+
style = get_style()
|
|
572
|
+
return style.list_themes()
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
def get_theme_provider() -> ThemeProvider:
|
|
576
|
+
"""Get the theme provider instance for the active theme.
|
|
577
|
+
|
|
578
|
+
Returns:
|
|
579
|
+
ThemeProvider instance.
|
|
580
|
+
|
|
581
|
+
Examples:
|
|
582
|
+
>>> provider = get_theme_provider()
|
|
583
|
+
>>> colors = provider.get_colors()
|
|
584
|
+
"""
|
|
585
|
+
style = get_style()
|
|
586
|
+
return style.theme_provider
|
|
587
|
+
|
|
588
|
+
|
|
589
|
+
def get_theme_color(token: str) -> str:
|
|
590
|
+
"""Get a hex color value from a color token based on the active theme.
|
|
591
|
+
|
|
592
|
+
Args:
|
|
593
|
+
token: Color token name (e.g., "primary", "background").
|
|
594
|
+
|
|
595
|
+
Returns:
|
|
596
|
+
Hex color string (e.g., "#007bff").
|
|
597
|
+
|
|
598
|
+
Raises:
|
|
599
|
+
ValueError: If the color token is invalid.
|
|
600
|
+
|
|
601
|
+
Examples:
|
|
602
|
+
>>> primary = get_theme_color("primary")
|
|
603
|
+
>>> print(primary) # "#007bff"
|
|
604
|
+
"""
|
|
605
|
+
builder = get_style_builder()
|
|
606
|
+
try:
|
|
607
|
+
return builder.color(token)
|
|
608
|
+
except Exception:
|
|
609
|
+
raise ValueError(f"Invalid color token: {token}")
|