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
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import tkinter
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
PACK_KEYS = frozenset({
|
|
7
|
+
"side", "fill", "expand", "anchor",
|
|
8
|
+
"padx", "pady", "ipadx", "ipady",
|
|
9
|
+
"margin", "margin_x", "margin_y",
|
|
10
|
+
"index", "before", "after", "in_",
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
GRID_KEYS = frozenset({
|
|
14
|
+
"row", "column", "rowspan", "columnspan",
|
|
15
|
+
"sticky", "padx", "pady", "ipadx", "ipady",
|
|
16
|
+
"margin", "margin_x", "margin_y",
|
|
17
|
+
"in_",
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
PLACE_KEYS = frozenset({
|
|
21
|
+
"x", "y", "relx", "rely",
|
|
22
|
+
"width", "height", "relwidth", "relheight",
|
|
23
|
+
"anchor", "bordermode", "in_",
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
# Per-child placement keys for the flex/grid layout engine (Row/Column/Grid).
|
|
27
|
+
# Collision-free by design — never bare `align`/`justify`, which would shadow
|
|
28
|
+
# Label/TextField text `justify` and Snackbar `align` on every widget.
|
|
29
|
+
FLEX_CHILD_KEYS = frozenset({
|
|
30
|
+
"grow", "horizontal", "vertical",
|
|
31
|
+
"margin", "margin_x", "margin_y", "index",
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
# Legacy pack/grid placement kwargs the flex engine deliberately rejects, so a
|
|
35
|
+
# stale `fill=`/`expand=`/`anchor=`/`sticky=` raises a clear migration error
|
|
36
|
+
# instead of silently collapsing a child (data/canvas widgets especially).
|
|
37
|
+
_LEGACY_CHILD_KEYS = frozenset({"fill", "expand", "anchor", "sticky", "side"})
|
|
38
|
+
|
|
39
|
+
# Presence of any of these signals "use place() instead of pack()/grid()"
|
|
40
|
+
PLACE_TRIGGER_KEYS = frozenset({"x", "y", "relx", "rely", "relwidth", "relheight"})
|
|
41
|
+
|
|
42
|
+
# Human-readable aliases for Tk's single-character fill values.
|
|
43
|
+
# Accepted everywhere fill= or fill_items= appears in the public API.
|
|
44
|
+
_FILL_ALIASES: dict[str, str] = {
|
|
45
|
+
"horizontal": "x",
|
|
46
|
+
"vertical": "y",
|
|
47
|
+
"all": "both",
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def normalize_fill(value: str | None) -> str | None:
|
|
52
|
+
"""Resolve a fill alias to its Tk value, passing through unknowns unchanged."""
|
|
53
|
+
if value is None:
|
|
54
|
+
return None
|
|
55
|
+
return _FILL_ALIASES.get(value, value)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _expand_margin(layout_kw: dict) -> None:
|
|
59
|
+
"""Convert margin=/marginx=/marginy= to padx=/pady= in-place.
|
|
60
|
+
|
|
61
|
+
Processing order: margin sets both axes as a baseline; marginx and
|
|
62
|
+
marginy override their respective axis. Explicit padx/pady always win.
|
|
63
|
+
"""
|
|
64
|
+
margin = layout_kw.pop("margin", None)
|
|
65
|
+
marginx = layout_kw.pop("margin_x", None)
|
|
66
|
+
marginy = layout_kw.pop("margin_y", None)
|
|
67
|
+
|
|
68
|
+
padx = pady = None
|
|
69
|
+
|
|
70
|
+
if margin is not None:
|
|
71
|
+
if isinstance(margin, (int, float)):
|
|
72
|
+
padx = pady = margin
|
|
73
|
+
elif len(margin) == 2:
|
|
74
|
+
padx, pady = margin # (horizontal, vertical)
|
|
75
|
+
else:
|
|
76
|
+
left, top, right, bottom = margin
|
|
77
|
+
padx = (left, right)
|
|
78
|
+
pady = (top, bottom)
|
|
79
|
+
|
|
80
|
+
if marginx is not None:
|
|
81
|
+
padx = marginx
|
|
82
|
+
if marginy is not None:
|
|
83
|
+
pady = marginy
|
|
84
|
+
|
|
85
|
+
if padx is not None:
|
|
86
|
+
layout_kw.setdefault("padx", padx)
|
|
87
|
+
if pady is not None:
|
|
88
|
+
layout_kw.setdefault("pady", pady)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# --- PublicContainer -------------------------------------------------------
|
|
92
|
+
# Imported here (after constants) to avoid circular imports in base.py.
|
|
93
|
+
|
|
94
|
+
from bootstack.widgets._core.base import PublicWidgetBase # noqa: E402
|
|
95
|
+
from bootstack.widgets._core.context import push_container, pop_container # noqa: E402
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class Placement:
|
|
99
|
+
"""Snapshot of how a widget was placed, enabling `detach`/`attach`.
|
|
100
|
+
|
|
101
|
+
Recorded on the child at layout time by `guide_layout`; consumed by
|
|
102
|
+
`PublicWidgetBase.detach`/`attach` to remove and re-insert the widget
|
|
103
|
+
without re-running its constructor. `options` holds the resolved Tk
|
|
104
|
+
geometry-manager options (no ordering refs); `index` is the pack position
|
|
105
|
+
among siblings, snapshotted afresh on each `detach`.
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
__slots__ = ("method", "master", "options", "index")
|
|
109
|
+
|
|
110
|
+
def __init__(
|
|
111
|
+
self,
|
|
112
|
+
method: str,
|
|
113
|
+
master: tkinter.Misc,
|
|
114
|
+
options: dict,
|
|
115
|
+
index: int | None = None,
|
|
116
|
+
) -> None:
|
|
117
|
+
self.method = method # "pack" | "grid" | "place"
|
|
118
|
+
self.master = master # the Tk master the child is placed in
|
|
119
|
+
self.options = options # resolved Tk options (no before/after/index)
|
|
120
|
+
self.index = index # pack order among siblings (snapshot at detach)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def resolve_pack_order(order: dict, master: tkinter.Misc) -> dict:
|
|
124
|
+
"""Translate public `index`/`before`/`after` into a Tk pack-order kwarg.
|
|
125
|
+
|
|
126
|
+
Pops the ordering keys from `order` and returns the corresponding Tk
|
|
127
|
+
`pack()` kwarg dict. `index=` is the position among the master's currently
|
|
128
|
+
packed children, translated to `before=<that child>` (appended when the
|
|
129
|
+
index is past the end). Explicit `before=`/`after=` accept either a public
|
|
130
|
+
widget or a raw Tk widget. `before` wins over `after` wins over `index`.
|
|
131
|
+
"""
|
|
132
|
+
index = order.pop("index", None)
|
|
133
|
+
before = order.pop("before", None)
|
|
134
|
+
after = order.pop("after", None)
|
|
135
|
+
if before is not None:
|
|
136
|
+
return {"before": getattr(before, "_internal", before)}
|
|
137
|
+
if after is not None:
|
|
138
|
+
return {"after": getattr(after, "_internal", after)}
|
|
139
|
+
if index is not None:
|
|
140
|
+
slaves = master.pack_slaves()
|
|
141
|
+
if 0 <= index < len(slaves):
|
|
142
|
+
return {"before": slaves[index]}
|
|
143
|
+
return {}
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class PublicContainer(PublicWidgetBase):
|
|
147
|
+
"""Base for public containers (Row, Column, Grid, App).
|
|
148
|
+
|
|
149
|
+
Subclasses must implement `_child_master`, `_default_layout_method`,
|
|
150
|
+
and `_merge_layout_options`.
|
|
151
|
+
"""
|
|
152
|
+
|
|
153
|
+
def _child_master(self) -> tkinter.Misc:
|
|
154
|
+
"""Default: children parent directly to `self._internal`."""
|
|
155
|
+
return self._internal
|
|
156
|
+
|
|
157
|
+
def _default_layout_method(self) -> str:
|
|
158
|
+
raise NotImplementedError
|
|
159
|
+
|
|
160
|
+
def _merge_layout_options(
|
|
161
|
+
self, child: PublicWidgetBase, layout_kw: dict
|
|
162
|
+
) -> tuple[str, dict]:
|
|
163
|
+
raise NotImplementedError
|
|
164
|
+
|
|
165
|
+
def guide_layout(self, child: PublicWidgetBase, **layout_kw: Any) -> None:
|
|
166
|
+
"""Place `child._internal` under this container.
|
|
167
|
+
|
|
168
|
+
Records a `Placement` snapshot on the child so it can later be
|
|
169
|
+
`detach`-ed and `attach`-ed. When `attached=False` is passed, the
|
|
170
|
+
snapshot is recorded but the widget is NOT mapped — it starts hidden,
|
|
171
|
+
ready to be shown with `attach`.
|
|
172
|
+
"""
|
|
173
|
+
attached = layout_kw.pop("attached", True)
|
|
174
|
+
if "fill" in layout_kw:
|
|
175
|
+
layout_kw["fill"] = normalize_fill(layout_kw["fill"])
|
|
176
|
+
_expand_margin(layout_kw)
|
|
177
|
+
place_mode = any(k in layout_kw for k in PLACE_TRIGGER_KEYS)
|
|
178
|
+
tk_widget = child._internal
|
|
179
|
+
master = self._child_master()
|
|
180
|
+
|
|
181
|
+
if place_mode:
|
|
182
|
+
options = {k: v for k, v in layout_kw.items() if k in PLACE_KEYS}
|
|
183
|
+
if attached:
|
|
184
|
+
tk_widget.place(in_=master, **options)
|
|
185
|
+
child._placement = Placement("place", master, options)
|
|
186
|
+
return
|
|
187
|
+
|
|
188
|
+
method, options = self._merge_layout_options(child, layout_kw)
|
|
189
|
+
if method == "pack":
|
|
190
|
+
# Capture an explicit index before resolve_pack_order strips it.
|
|
191
|
+
explicit_index = options.get("index")
|
|
192
|
+
order_kw = resolve_pack_order(options, master) # mutates options
|
|
193
|
+
if attached:
|
|
194
|
+
tk_widget.pack(in_=master, **options, **order_kw)
|
|
195
|
+
child._placement = Placement("pack", master, dict(options))
|
|
196
|
+
else:
|
|
197
|
+
# Remember the slot a later attach() should restore: an
|
|
198
|
+
# explicit index if given, else the natural append position
|
|
199
|
+
# (the count of siblings already packed at this point).
|
|
200
|
+
index = explicit_index if explicit_index is not None else len(master.pack_slaves())
|
|
201
|
+
child._placement = Placement("pack", master, dict(options), index=index)
|
|
202
|
+
elif method == "grid":
|
|
203
|
+
if attached:
|
|
204
|
+
tk_widget.grid(in_=master, **options)
|
|
205
|
+
child._placement = Placement("grid", master, dict(options))
|
|
206
|
+
else:
|
|
207
|
+
raise ValueError(f"Unknown layout method: {method!r}")
|
|
208
|
+
|
|
209
|
+
def __enter__(self) -> "PublicContainer":
|
|
210
|
+
push_container(self)
|
|
211
|
+
return self
|
|
212
|
+
|
|
213
|
+
def __exit__(self, exc_type, exc, tb) -> None:
|
|
214
|
+
pop_container(self)
|
|
215
|
+
return None
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
# --- Flex engine (Row / Column) --------------------------------------------
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def _reject_legacy_child_kwargs(layout_kw: dict, where: str) -> None:
|
|
222
|
+
"""Raise on legacy pack/grid placement kwargs passed to a flex child."""
|
|
223
|
+
bad = _LEGACY_CHILD_KEYS & layout_kw.keys()
|
|
224
|
+
if bad:
|
|
225
|
+
from bootstack.errors import BootstackError
|
|
226
|
+
|
|
227
|
+
raise BootstackError(
|
|
228
|
+
f"{where}: {', '.join(sorted(bad))} is not a valid layout option for "
|
|
229
|
+
f"a Row/Column/Grid child. Use grow= / align_self= (and justify_self= "
|
|
230
|
+
f"in a Grid) instead — see the layout guide."
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def grid_sticky(horizontal: str | None, vertical: str | None) -> str:
|
|
235
|
+
"""Derive a Tk `sticky` string from in-cell alignment edge values.
|
|
236
|
+
|
|
237
|
+
`horizontal` is `left`/`center`/`right`/`stretch`, `vertical` is
|
|
238
|
+
`top`/`center`/`bottom`/`stretch`. `stretch` fills the axis, the edge values
|
|
239
|
+
pin to that edge, and `center` (or None) leaves the axis unpinned so the
|
|
240
|
+
child sits centered in the cell.
|
|
241
|
+
"""
|
|
242
|
+
sticky = ""
|
|
243
|
+
if horizontal == "stretch":
|
|
244
|
+
sticky += "ew"
|
|
245
|
+
elif horizontal == "left":
|
|
246
|
+
sticky += "w"
|
|
247
|
+
elif horizontal == "right":
|
|
248
|
+
sticky += "e"
|
|
249
|
+
if vertical == "stretch":
|
|
250
|
+
sticky += "ns"
|
|
251
|
+
elif vertical == "top":
|
|
252
|
+
sticky += "n"
|
|
253
|
+
elif vertical == "bottom":
|
|
254
|
+
sticky += "s"
|
|
255
|
+
return sticky
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def _flex_child_opts(child: PublicWidgetBase, layout_kw: dict) -> dict:
|
|
259
|
+
"""Build the FlexFrame per-child opts dict from resolved layout kwargs.
|
|
260
|
+
|
|
261
|
+
Expects `_expand_margin` to have already converted margins to padx/pady.
|
|
262
|
+
Carries spacer metadata when `child` is a `Spacer` (duck-typed to avoid a
|
|
263
|
+
circular import).
|
|
264
|
+
"""
|
|
265
|
+
opts: dict[str, Any] = {}
|
|
266
|
+
for key in ("grow", "horizontal", "vertical", "padx", "pady"):
|
|
267
|
+
if key in layout_kw:
|
|
268
|
+
opts[key] = layout_kw[key]
|
|
269
|
+
if getattr(child, "_is_spacer", False):
|
|
270
|
+
opts["_spacer"] = True
|
|
271
|
+
opts["_spacer_size"] = getattr(child, "_spacer_size", None)
|
|
272
|
+
opts["_spacer_weight"] = getattr(child, "_spacer_weight", 1)
|
|
273
|
+
return opts
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
class FlexContainer(PublicContainer):
|
|
277
|
+
"""Base for containers whose children flow through a `FlexFrame`.
|
|
278
|
+
|
|
279
|
+
Subclasses build a `FlexFrame` and expose it via `_flex_frame`; children
|
|
280
|
+
are added to it (re-planning the layout) and a `Placement(method="flex")`
|
|
281
|
+
is recorded for `detach`/`attach`. Defaults `_flex_frame` to `_internal`,
|
|
282
|
+
which suits the stack widgets; containers with a nested layout frame (App's
|
|
283
|
+
content frame, Card's body) override the property.
|
|
284
|
+
"""
|
|
285
|
+
|
|
286
|
+
@property
|
|
287
|
+
def _flex_frame(self) -> Any:
|
|
288
|
+
return self._internal
|
|
289
|
+
|
|
290
|
+
def _child_master(self) -> tkinter.Misc:
|
|
291
|
+
return self._flex_frame
|
|
292
|
+
|
|
293
|
+
def _default_layout_method(self) -> str:
|
|
294
|
+
return "flex"
|
|
295
|
+
|
|
296
|
+
def guide_layout(self, child: PublicWidgetBase, **layout_kw: Any) -> None:
|
|
297
|
+
"""Place `child` into this container's flow and snapshot its placement."""
|
|
298
|
+
place_flex_child(self._flex_frame, child, layout_kw, type(self).__name__)
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def place_flex_child(
|
|
302
|
+
frame: Any, child: PublicWidgetBase, layout_kw: dict, where: str
|
|
303
|
+
) -> None:
|
|
304
|
+
"""Add `child` to a `FlexFrame` and record a `Placement` for detach/attach.
|
|
305
|
+
|
|
306
|
+
Shared by `FlexContainer` and the dual-mode containers (Card/GroupBox in
|
|
307
|
+
their column/row mode) so the flex placement contract lives in one place.
|
|
308
|
+
Absolute placement (`x=`/`y=`) is honored as an overlay escape hatch — the
|
|
309
|
+
child is `place`-d over the flow rather than entering it.
|
|
310
|
+
"""
|
|
311
|
+
attached = layout_kw.pop("attached", True)
|
|
312
|
+
|
|
313
|
+
# Place-mode escape hatch: a child given x/y/relx/rely is positioned
|
|
314
|
+
# absolutely over the flow, not gridded into it.
|
|
315
|
+
if any(k in layout_kw for k in PLACE_TRIGGER_KEYS):
|
|
316
|
+
options = {k: v for k, v in layout_kw.items() if k in PLACE_KEYS}
|
|
317
|
+
if attached:
|
|
318
|
+
child._internal.place(in_=frame, **options)
|
|
319
|
+
child._placement = Placement("place", frame, options)
|
|
320
|
+
return
|
|
321
|
+
|
|
322
|
+
_reject_legacy_child_kwargs(layout_kw, where)
|
|
323
|
+
_expand_margin(layout_kw)
|
|
324
|
+
index = layout_kw.get("index")
|
|
325
|
+
opts = _flex_child_opts(child, layout_kw)
|
|
326
|
+
if attached:
|
|
327
|
+
frame.add_child(child._internal, opts, index=index)
|
|
328
|
+
child._placement = Placement("flex", frame, opts)
|
|
329
|
+
else:
|
|
330
|
+
# Record the slot a later attach() restores: an explicit index, else the
|
|
331
|
+
# natural append position (current child count).
|
|
332
|
+
if index is None:
|
|
333
|
+
index = len(frame._managed)
|
|
334
|
+
child._placement = Placement("flex", frame, opts, index=index)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import threading
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from bootstack.widgets._core.container import PublicContainer
|
|
8
|
+
|
|
9
|
+
_local = threading.local()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _stack() -> list:
|
|
13
|
+
s = getattr(_local, "stack", None)
|
|
14
|
+
if s is None:
|
|
15
|
+
s = []
|
|
16
|
+
_local.stack = s
|
|
17
|
+
return s
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def push_container(container: "PublicContainer") -> None:
|
|
21
|
+
_stack().append(container)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def pop_container(expected: "PublicContainer") -> None:
|
|
25
|
+
s = _stack()
|
|
26
|
+
if not s or s[-1] is not expected:
|
|
27
|
+
if expected in s:
|
|
28
|
+
s.remove(expected)
|
|
29
|
+
return
|
|
30
|
+
s.pop()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def current_container() -> "PublicContainer | None":
|
|
34
|
+
s = _stack()
|
|
35
|
+
return s[-1] if s else None
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from enum import Enum
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class _EventCategory(str, Enum):
|
|
7
|
+
"""String-valued enum so members compare equal to plain strings."""
|
|
8
|
+
pass
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class _Widget(_EventCategory):
|
|
12
|
+
CLICK = "click"
|
|
13
|
+
DOUBLE_CLICK = "double_click"
|
|
14
|
+
RIGHT_CLICK = "right_click"
|
|
15
|
+
HOVER = "hover"
|
|
16
|
+
LEAVE = "leave"
|
|
17
|
+
FOCUS = "focus"
|
|
18
|
+
BLUR = "blur"
|
|
19
|
+
RESIZE = "resize"
|
|
20
|
+
DESTROY = "destroy"
|
|
21
|
+
ATTACH = "attach"
|
|
22
|
+
DETACH = "detach"
|
|
23
|
+
EXPAND = "expand"
|
|
24
|
+
COLLAPSE = "collapse"
|
|
25
|
+
ACTIVATE = "activate"
|
|
26
|
+
DEACTIVATE = "deactivate"
|
|
27
|
+
DISMISS = "dismiss"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class _Input(_EventCategory):
|
|
31
|
+
CHANGE = "change"
|
|
32
|
+
INPUT = "input"
|
|
33
|
+
SUBMIT = "submit"
|
|
34
|
+
VALIDATE = "validate"
|
|
35
|
+
VALID = "valid"
|
|
36
|
+
INVALID = "invalid"
|
|
37
|
+
COMMIT = "commit"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class _Selection(_EventCategory):
|
|
41
|
+
SELECT = "select"
|
|
42
|
+
DESELECT = "deselect"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class _App(_EventCategory):
|
|
46
|
+
THEME_CHANGE = "theme_change"
|
|
47
|
+
PAGE_MOUNT = "page_mount"
|
|
48
|
+
PAGE_UNMOUNT = "page_unmount"
|
|
49
|
+
PAGE_WILL_MOUNT = "page_will_mount"
|
|
50
|
+
PAGE_CHANGE = "page_change"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class EventName:
|
|
54
|
+
"""Namespace of canonical event-name constants.
|
|
55
|
+
|
|
56
|
+
Internal reference for the string names accepted by `widget.on(...)`.
|
|
57
|
+
Members compare equal to their string values, so `widget.on("click", h)`
|
|
58
|
+
and `widget.on(EventName.Widget.CLICK, h)` are interchangeable. Public code
|
|
59
|
+
uses the plain string names; the handler argument type is
|
|
60
|
+
`bootstack.widgets.types.Event`.
|
|
61
|
+
"""
|
|
62
|
+
Widget = _Widget
|
|
63
|
+
Input = _Input
|
|
64
|
+
Selection = _Selection
|
|
65
|
+
App = _App
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
GLOBAL_EVENT_MAP: dict[str, str] = {
|
|
69
|
+
# Widget
|
|
70
|
+
"click": "<Button-1>",
|
|
71
|
+
"double_click": "<Double-Button-1>",
|
|
72
|
+
"right_click": "<Button-3>",
|
|
73
|
+
"hover": "<Enter>",
|
|
74
|
+
"leave": "<Leave>",
|
|
75
|
+
"focus": "<FocusIn>",
|
|
76
|
+
"blur": "<FocusOut>",
|
|
77
|
+
"resize": "<Configure>",
|
|
78
|
+
"destroy": "<Destroy>",
|
|
79
|
+
"attach": "<Map>",
|
|
80
|
+
"detach": "<Unmap>",
|
|
81
|
+
# Input
|
|
82
|
+
"input": "<KeyRelease>",
|
|
83
|
+
"submit": "<Return>",
|
|
84
|
+
# App-level
|
|
85
|
+
"theme_change": "<<ThemeChanged>>",
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
# Per-class override map. Populated via `register_widget_events`.
|
|
89
|
+
_CLASS_EVENT_MAPS: dict[type, dict[str, str]] = {}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def register_widget_events(cls: type, mapping: dict[str, str]) -> None:
|
|
93
|
+
"""Attach a class-specific event map to a public widget class.
|
|
94
|
+
|
|
95
|
+
Entries map the public name (e.g. "select") to the Tk sequence
|
|
96
|
+
(e.g. "<<TreeviewSelect>>"). Class maps take precedence over the global map.
|
|
97
|
+
"""
|
|
98
|
+
_CLASS_EVENT_MAPS[cls] = mapping
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def resolve_event(public_widget: object, name: str) -> str:
|
|
102
|
+
"""Resolve a public event name to a Tk sequence.
|
|
103
|
+
|
|
104
|
+
Lookup order:
|
|
105
|
+
1. Walk the public widget's MRO; first class-specific match wins.
|
|
106
|
+
2. Global map.
|
|
107
|
+
3. Pass-through literal `<...>` or `<<...>>` strings unchanged.
|
|
108
|
+
4. Raise `UnknownEventError`.
|
|
109
|
+
"""
|
|
110
|
+
from bootstack.errors import UnknownEventError
|
|
111
|
+
|
|
112
|
+
if name.startswith("<") and name.endswith(">"):
|
|
113
|
+
return name
|
|
114
|
+
|
|
115
|
+
for klass in type(public_widget).__mro__:
|
|
116
|
+
cmap = _CLASS_EVENT_MAPS.get(klass)
|
|
117
|
+
if cmap and name in cmap:
|
|
118
|
+
return cmap[name]
|
|
119
|
+
|
|
120
|
+
if name in GLOBAL_EVENT_MAP:
|
|
121
|
+
return GLOBAL_EVENT_MAP[name]
|
|
122
|
+
|
|
123
|
+
all_known = sorted(
|
|
124
|
+
set(GLOBAL_EVENT_MAP)
|
|
125
|
+
| set().union(*(set(m) for m in _CLASS_EVENT_MAPS.values()))
|
|
126
|
+
)
|
|
127
|
+
raise UnknownEventError(
|
|
128
|
+
f"Unknown event {name!r} on {type(public_widget).__name__}. "
|
|
129
|
+
f"Known events: {all_known}"
|
|
130
|
+
)
|