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,96 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Callable
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class WindowControlsMixin:
|
|
7
|
+
"""Window lifecycle and show-state controls shared by top-level windows.
|
|
8
|
+
|
|
9
|
+
Mixed into `App`, `AppShell`, and `Window` so every top-level window exposes
|
|
10
|
+
the same close and show-state surface.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
_internal: Any
|
|
14
|
+
|
|
15
|
+
def close(self) -> None:
|
|
16
|
+
"""Close the window and exit its event loop.
|
|
17
|
+
|
|
18
|
+
For the application window this quits the loop started by `run()` — the
|
|
19
|
+
natural action for a "Quit" command. It does not run the `on_close` veto
|
|
20
|
+
handlers (those guard the user clicking the window's close button), so a
|
|
21
|
+
programmatic `close()` always proceeds.
|
|
22
|
+
"""
|
|
23
|
+
self._internal.close()
|
|
24
|
+
|
|
25
|
+
def show(self) -> None:
|
|
26
|
+
"""Show the window — typically to reveal it again after `hide()`.
|
|
27
|
+
|
|
28
|
+
For the application window, `run()` already shows it and starts the
|
|
29
|
+
event loop; reach for `show()` to bring a hidden window back.
|
|
30
|
+
"""
|
|
31
|
+
self._internal.show()
|
|
32
|
+
|
|
33
|
+
def hide(self) -> None:
|
|
34
|
+
"""Hide the window without destroying it.
|
|
35
|
+
|
|
36
|
+
The window is unmapped (and usually removed from the taskbar). Bring it
|
|
37
|
+
back with `show()`.
|
|
38
|
+
"""
|
|
39
|
+
self._internal.hide()
|
|
40
|
+
|
|
41
|
+
def minimize(self) -> None:
|
|
42
|
+
"""Minimize the window to the taskbar or dock."""
|
|
43
|
+
self._internal.minimize()
|
|
44
|
+
|
|
45
|
+
def maximize(self) -> None:
|
|
46
|
+
"""Maximize the window where the platform supports it."""
|
|
47
|
+
self._internal.maximize()
|
|
48
|
+
|
|
49
|
+
def set_fullscreen(self, value: bool = True) -> None:
|
|
50
|
+
"""Enter or leave fullscreen mode where supported.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
value: `True` to enter fullscreen, `False` to exit. Default `True`.
|
|
54
|
+
"""
|
|
55
|
+
self._internal.set_fullscreen(value)
|
|
56
|
+
|
|
57
|
+
def set_topmost(self, value: bool = True) -> None:
|
|
58
|
+
"""Pin the window above all others, or release it.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
value: `True` to keep the window above others, `False` to release.
|
|
62
|
+
Default `True`.
|
|
63
|
+
"""
|
|
64
|
+
self._internal.set_topmost(value)
|
|
65
|
+
|
|
66
|
+
def on_close(self, handler: Callable[[], bool | None]) -> None:
|
|
67
|
+
"""Register a handler invoked when the user clicks the window's close button.
|
|
68
|
+
|
|
69
|
+
Handlers run in registration order; return `False` from one to veto the
|
|
70
|
+
close (the window stays open), or `None`/`True` to allow it. This guards
|
|
71
|
+
the close-button gesture only — it is not run by the programmatic
|
|
72
|
+
`close()`.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
handler: Called with no arguments on a close request. Return `False`
|
|
76
|
+
to cancel the close.
|
|
77
|
+
"""
|
|
78
|
+
self._internal.add_close_handler(handler)
|
|
79
|
+
|
|
80
|
+
def add_close_handler(self, handler: Callable[[], bool | None]) -> None:
|
|
81
|
+
"""Register a veto-able close handler — an alias for `on_close()`.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
handler: Called with no arguments on a close request. Return `False`
|
|
85
|
+
to cancel the close.
|
|
86
|
+
"""
|
|
87
|
+
self._internal.add_close_handler(handler)
|
|
88
|
+
|
|
89
|
+
def remove_close_handler(self, handler: Callable[[], bool | None]) -> None:
|
|
90
|
+
"""Remove a close handler previously registered with `on_close()`.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
handler: The same callable passed to `on_close()` or
|
|
94
|
+
`add_close_handler()`. No-op if it was not registered.
|
|
95
|
+
"""
|
|
96
|
+
self._internal.remove_close_handler(handler)
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
"""The stacked-toolbar chrome host for top-level windows.
|
|
2
|
+
|
|
3
|
+
`ChromeHostMixin` gives `App` / `Window` / `AppShell` an `add_toolbar()` factory:
|
|
4
|
+
the window's top region is a vertical stack of `Toolbar`s, each holding buttons,
|
|
5
|
+
labels, widgets, and menus (`toolbar.add_menu(...)`). A menu is just a toolbar
|
|
6
|
+
item — it renders as an in-window dropdown on Windows/Linux and bridges to the
|
|
7
|
+
native global menu bar on macOS (aggregated across every bridged chrome toolbar).
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
# Softness for a chrome divider, matching the shell's region dividers: the stroke
|
|
14
|
+
# blends close to its surface for a quiet hairline rather than the default
|
|
15
|
+
# separator border (which blends harder toward the semantic border color).
|
|
16
|
+
_CHROME_DIVIDER_BORDER_STRENGTH = 0.90
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class _ToolbarStackContainer:
|
|
20
|
+
"""Public-container shim so an `add_toolbar()` toolbar packs into the host's
|
|
21
|
+
vertical chrome stack (full-width, top-to-bottom in creation order)."""
|
|
22
|
+
|
|
23
|
+
_auto_place = True
|
|
24
|
+
|
|
25
|
+
def __init__(self, frame: Any) -> None:
|
|
26
|
+
self._frame = frame
|
|
27
|
+
|
|
28
|
+
def _child_master(self) -> Any:
|
|
29
|
+
return self._frame
|
|
30
|
+
|
|
31
|
+
def guide_layout(self, child: Any, **layout_kw: Any) -> None:
|
|
32
|
+
# Toolbars stack full-width in creation order; the stack owns the layout.
|
|
33
|
+
child._internal.pack(side="top", fill="x")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class ChromeHostMixin:
|
|
37
|
+
"""`add_toolbar()` — the window's top region is a stack of toolbars.
|
|
38
|
+
|
|
39
|
+
Each `add_toolbar()` appends a full-width `Toolbar` band; menus live on the
|
|
40
|
+
toolbars (`toolbar.add_menu(...)`). On macOS the bridged toolbars' menus are
|
|
41
|
+
aggregated into the native global menu bar; on Windows/Linux they render as
|
|
42
|
+
in-window dropdowns. `AppShell` overrides `_ensure_toolbar_stack()` to mount
|
|
43
|
+
the stack into its own region layout.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
def _menu_root(self) -> Any:
|
|
47
|
+
"""Tk root/toplevel for the native menubar (`['menu']`) + shortcuts."""
|
|
48
|
+
return getattr(self, "_internal", None)
|
|
49
|
+
|
|
50
|
+
# ----- toolbar stack -----
|
|
51
|
+
|
|
52
|
+
def add_toolbar(
|
|
53
|
+
self, *, divider: bool = False, use_macos_menus: bool = True, **toolbar_kwargs: Any
|
|
54
|
+
) -> Any:
|
|
55
|
+
"""Append a `Toolbar` to the window's top chrome stack and return it.
|
|
56
|
+
|
|
57
|
+
Toolbars stack full-width, top-to-bottom, in the order added — fill each
|
|
58
|
+
with buttons, labels, widgets, and menus (`toolbar.add_menu(...)`). The
|
|
59
|
+
returned `Toolbar` is a context manager, so the natural form reads::
|
|
60
|
+
|
|
61
|
+
with window.add_toolbar() as tb:
|
|
62
|
+
tb.add_menu("File")
|
|
63
|
+
tb.add_spacer()
|
|
64
|
+
tb.add_theme_toggle()
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
divider: Draw a hairline beneath this toolbar (default `False`).
|
|
68
|
+
use_macos_menus: On macOS, bridge this toolbar's menus to the native
|
|
69
|
+
global menu bar (in-window dropdowns hide). Default `True`; set
|
|
70
|
+
`False` to keep its menus in-window even on macOS. No effect on
|
|
71
|
+
Windows/Linux.
|
|
72
|
+
**toolbar_kwargs: Forwarded to `Toolbar` — e.g. `surface`, `density`,
|
|
73
|
+
`button_variant`, `show_window_controls`, `draggable`. Window
|
|
74
|
+
chrome defaults `surface='chrome'` and `density='compact'` (a tight
|
|
75
|
+
strip); pass `density='default'` for a roomier bar.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
The `Toolbar` for this layer.
|
|
79
|
+
"""
|
|
80
|
+
from bootstack.widgets._impl.primitives import Separator
|
|
81
|
+
from bootstack.widgets.toolbar import Toolbar
|
|
82
|
+
|
|
83
|
+
stack = self._ensure_toolbar_stack()
|
|
84
|
+
toolbar_kwargs.setdefault("surface", "chrome")
|
|
85
|
+
# Window chrome reads as a tight strip (menu bars, title bars, command
|
|
86
|
+
# bars are all compact by convention) — default to compact, overridable.
|
|
87
|
+
toolbar_kwargs.setdefault("density", "compact")
|
|
88
|
+
tb = Toolbar(parent=_ToolbarStackContainer(stack), _host=self, **toolbar_kwargs)
|
|
89
|
+
if divider:
|
|
90
|
+
# A bare full-width hairline — no margin. The bar above provides any
|
|
91
|
+
# spacing through its own (chrome) padding, so nothing of the stack
|
|
92
|
+
# surface leaks below the line when this is the last band before the
|
|
93
|
+
# content (the window's content surface starts right after the line).
|
|
94
|
+
Separator(
|
|
95
|
+
stack, orient="horizontal", surface=toolbar_kwargs["surface"],
|
|
96
|
+
border_strength=_CHROME_DIVIDER_BORDER_STRENGTH,
|
|
97
|
+
).pack(side="top", fill="x")
|
|
98
|
+
self._register_chrome_toolbar(tb, use_macos_menus)
|
|
99
|
+
return tb
|
|
100
|
+
|
|
101
|
+
def _ensure_default_titlebar(self) -> None:
|
|
102
|
+
"""Give a borderless window a movable, closeable title bar.
|
|
103
|
+
|
|
104
|
+
Batteries-included chrome for an `undecorated` window: when the OS title
|
|
105
|
+
bar is gone and the author added no chrome toolbar of their own, inject a
|
|
106
|
+
single `add_toolbar(show_window_controls=True)` band (min/max/close on the
|
|
107
|
+
right, draggable, double-click to maximize) with the window title as a
|
|
108
|
+
left label — so a borderless window is never stranded.
|
|
109
|
+
|
|
110
|
+
No-op when window controls are turned off (`window_controls=False`), when
|
|
111
|
+
the window is decorated, on macOS (where `overrideredirect` is
|
|
112
|
+
force-disabled), or when the author already built chrome with
|
|
113
|
+
`add_toolbar()`. Idempotent — the author-chrome guard makes a second call
|
|
114
|
+
a no-op. Called from the window's show/run path, after authoring.
|
|
115
|
+
"""
|
|
116
|
+
import sys
|
|
117
|
+
|
|
118
|
+
if not getattr(self, "_window_controls", True):
|
|
119
|
+
return
|
|
120
|
+
if not getattr(self, "_undecorated", False):
|
|
121
|
+
return
|
|
122
|
+
if sys.platform == "darwin":
|
|
123
|
+
return
|
|
124
|
+
if getattr(self, "_chrome_toolbars", None):
|
|
125
|
+
return # the author owns the chrome
|
|
126
|
+
tb = self.add_toolbar(show_window_controls=True)
|
|
127
|
+
root = self._menu_root()
|
|
128
|
+
title = ""
|
|
129
|
+
if root is not None:
|
|
130
|
+
try:
|
|
131
|
+
title = root.title()
|
|
132
|
+
except Exception:
|
|
133
|
+
title = ""
|
|
134
|
+
if title:
|
|
135
|
+
tb.add_label(title)
|
|
136
|
+
|
|
137
|
+
def _ensure_toolbar_stack(self) -> Any:
|
|
138
|
+
stack = getattr(self, "_toolbar_stack", None)
|
|
139
|
+
if stack is not None:
|
|
140
|
+
return stack
|
|
141
|
+
from bootstack.widgets._impl.primitives.packframe import PackFrame
|
|
142
|
+
|
|
143
|
+
# The stack carries the chrome surface so any gap between stacked bars
|
|
144
|
+
# (e.g. a divider's margin) reads as chrome rather than the window's
|
|
145
|
+
# content surface. Chrome-surfaced toolbars sit seamlessly on it.
|
|
146
|
+
stack = PackFrame(self._toolbar_stack_parent(), direction="vertical", surface="chrome")
|
|
147
|
+
self._mount_toolbar_stack(stack)
|
|
148
|
+
self._toolbar_stack = stack
|
|
149
|
+
return stack
|
|
150
|
+
|
|
151
|
+
def _toolbar_stack_parent(self) -> Any:
|
|
152
|
+
"""Widget the chrome stack is built under (overridable by the host)."""
|
|
153
|
+
return self._menu_root()
|
|
154
|
+
|
|
155
|
+
def _mount_toolbar_stack(self, stack: Any) -> None:
|
|
156
|
+
"""Pack the chrome stack at the top of the window, above the content."""
|
|
157
|
+
content = getattr(self, "_content_frame", None)
|
|
158
|
+
pack_kw: dict[str, Any] = {"side": "top", "fill": "x"}
|
|
159
|
+
if content is not None:
|
|
160
|
+
pack_kw["before"] = content
|
|
161
|
+
stack.pack(**pack_kw)
|
|
162
|
+
|
|
163
|
+
# ----- macOS native menu bridge -----
|
|
164
|
+
|
|
165
|
+
def _register_chrome_toolbar(self, tb: Any, use_macos_menus: bool) -> None:
|
|
166
|
+
"""Track a chrome toolbar and, on macOS, wire its menus into the native
|
|
167
|
+
global menu bar (aggregated across all bridged toolbars)."""
|
|
168
|
+
toolbars = getattr(self, "_chrome_toolbars", None)
|
|
169
|
+
if toolbars is None:
|
|
170
|
+
toolbars = self._chrome_toolbars = []
|
|
171
|
+
toolbars.append((tb, use_macos_menus))
|
|
172
|
+
if use_macos_menus and self._menus_are_native():
|
|
173
|
+
tb._internal._on_menu_change = self._schedule_native_menu_rebuild
|
|
174
|
+
self._schedule_native_menu_rebuild()
|
|
175
|
+
|
|
176
|
+
def _menus_are_native(self) -> bool:
|
|
177
|
+
"""Whether menus render in the native global bar (macOS / aqua)."""
|
|
178
|
+
root = self._menu_root()
|
|
179
|
+
try:
|
|
180
|
+
return root.tk.call("tk", "windowingsystem") == "aqua"
|
|
181
|
+
except Exception:
|
|
182
|
+
return False
|
|
183
|
+
|
|
184
|
+
def _aggregate_native_menu_model(self) -> Any:
|
|
185
|
+
"""Build one `MenuModel` from every bridged chrome toolbar's menus, in
|
|
186
|
+
stack order then left-to-right within each toolbar. Also hides the
|
|
187
|
+
in-window triggers for bridged toolbars (the native bar shows them)."""
|
|
188
|
+
from bootstack.widgets._impl.composites.menu.model import MenuModel
|
|
189
|
+
|
|
190
|
+
agg = MenuModel()
|
|
191
|
+
for tb, bridge in getattr(self, "_chrome_toolbars", []):
|
|
192
|
+
if not bridge:
|
|
193
|
+
continue
|
|
194
|
+
model = tb._internal.menu_model
|
|
195
|
+
if model is None:
|
|
196
|
+
continue
|
|
197
|
+
agg.groups.extend(model.groups)
|
|
198
|
+
for trigger in tb._internal._menu_triggers.values():
|
|
199
|
+
try:
|
|
200
|
+
trigger.pack_forget()
|
|
201
|
+
except Exception:
|
|
202
|
+
pass
|
|
203
|
+
return agg
|
|
204
|
+
|
|
205
|
+
def _schedule_native_menu_rebuild(self) -> None:
|
|
206
|
+
root = self._menu_root()
|
|
207
|
+
pending = getattr(self, "_native_menu_pending", None)
|
|
208
|
+
if root is None:
|
|
209
|
+
self._rebuild_native_menu()
|
|
210
|
+
return
|
|
211
|
+
if pending is not None:
|
|
212
|
+
try:
|
|
213
|
+
root.after_cancel(pending)
|
|
214
|
+
except Exception:
|
|
215
|
+
pass
|
|
216
|
+
try:
|
|
217
|
+
self._native_menu_pending = root.after_idle(self._rebuild_native_menu)
|
|
218
|
+
except Exception:
|
|
219
|
+
self._rebuild_native_menu()
|
|
220
|
+
|
|
221
|
+
def _cancel_native_menu_pending(self) -> None:
|
|
222
|
+
"""Cancel a scheduled native-menu rebuild and clear the handle.
|
|
223
|
+
|
|
224
|
+
Used on teardown / dev-reload reset: an `after_idle` rebuild scheduled
|
|
225
|
+
just before a reset would otherwise fire against freshly-rebuilt chrome.
|
|
226
|
+
"""
|
|
227
|
+
pending = getattr(self, "_native_menu_pending", None)
|
|
228
|
+
if pending is not None:
|
|
229
|
+
root = self._menu_root()
|
|
230
|
+
if root is not None:
|
|
231
|
+
try:
|
|
232
|
+
root.after_cancel(pending)
|
|
233
|
+
except Exception:
|
|
234
|
+
pass
|
|
235
|
+
self._native_menu_pending = None
|
|
236
|
+
|
|
237
|
+
def _rebuild_native_menu(self) -> None:
|
|
238
|
+
from bootstack.widgets._impl.composites.menu.render_native import NativeMenuBar
|
|
239
|
+
|
|
240
|
+
self._native_menu_pending = None
|
|
241
|
+
model = self._aggregate_native_menu_model()
|
|
242
|
+
renderer = getattr(self, "_native_menu_renderer", None)
|
|
243
|
+
if renderer is None:
|
|
244
|
+
self._native_menu_renderer = NativeMenuBar(self._menu_root(), model)
|
|
245
|
+
else:
|
|
246
|
+
renderer.set_model(model)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Internal widget implementation — do not import directly from user code."""
|
|
File without changes
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
"""Shared base for ttk wrapper widgets with style and config delegation.
|
|
2
|
+
|
|
3
|
+
This base class wires up:
|
|
4
|
+
- Constructor: delegates to StyleResolver's constructor wrapper for init-time style
|
|
5
|
+
setup, including surface color inheritance and default variants.
|
|
6
|
+
- Configure: routes keys annotated via @configure_delegate to their handlers,
|
|
7
|
+
then forwards remaining options to the underlying ttk widget.
|
|
8
|
+
- Index access: `w['key']` and `w['key'] = value` work for delegated keys and
|
|
9
|
+
for `style` queries.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
from bootstack.style.style_resolver import (
|
|
17
|
+
StyleResolver,
|
|
18
|
+
extract_accent_from_style,
|
|
19
|
+
extract_variant_from_style,
|
|
20
|
+
)
|
|
21
|
+
from bootstack.style.token_maps import CONTAINER_CLASSES, ORIENT_CLASSES
|
|
22
|
+
from bootstack.widgets._impl.mixins.configure_mixin import (
|
|
23
|
+
ConfigureDelegationMixin,
|
|
24
|
+
configure_delegate,
|
|
25
|
+
)
|
|
26
|
+
from bootstack.widgets._impl.mixins.font_mixin import FontMixin
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class TTKWrapperBase(FontMixin, ConfigureDelegationMixin):
|
|
30
|
+
"""Base class for all ttk wrapper widgets.
|
|
31
|
+
|
|
32
|
+
Subclasses must set `_ttk_base` to the underlying ttk class and inherit
|
|
33
|
+
from that ttk class as well (MRO: WrapperBase, ttk.Class).
|
|
34
|
+
|
|
35
|
+
Includes FontMixin for font modifier syntax support.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
_ttk_base: type
|
|
39
|
+
|
|
40
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None: # type: ignore[override]
|
|
41
|
+
"""Initialize the wrapper, applying font modifier syntax and StyleResolver setup."""
|
|
42
|
+
font_value = self._init_font_mixin(kwargs)
|
|
43
|
+
init_wrapper = StyleResolver.override_ttk_widget_constructor(self._ttk_base.__init__) # type: ignore[attr-defined]
|
|
44
|
+
init_wrapper(self, *args, **kwargs)
|
|
45
|
+
if font_value is not None:
|
|
46
|
+
self._delegate_font(font_value)
|
|
47
|
+
|
|
48
|
+
def configure(self, cnf: Any | None = None, **kwargs: Any): # type: ignore[override]
|
|
49
|
+
"""Configure widget options, routing delegated keys before forwarding to ttk."""
|
|
50
|
+
# First, route custom keys via delegation
|
|
51
|
+
if kwargs:
|
|
52
|
+
for _k in list(kwargs.keys()):
|
|
53
|
+
if _k in self._configure_delegate_map:
|
|
54
|
+
if self._config_delegate_set(_k, kwargs[_k]):
|
|
55
|
+
kwargs.pop(_k, None)
|
|
56
|
+
|
|
57
|
+
# Getter path for delegated keys
|
|
58
|
+
if isinstance(cnf, str) and cnf in self._configure_delegate_map:
|
|
59
|
+
handled, value = self._config_delegate_get(cnf)
|
|
60
|
+
if handled:
|
|
61
|
+
return value
|
|
62
|
+
|
|
63
|
+
# Forward remaining options directly to ttk
|
|
64
|
+
return self._ttk_base.configure(self, cnf, **kwargs) # type: ignore[misc]
|
|
65
|
+
|
|
66
|
+
# tk alias
|
|
67
|
+
config = configure
|
|
68
|
+
|
|
69
|
+
# Indexing support: delegate custom keys; handle style queries
|
|
70
|
+
def __setitem__(self, key: str, value: Any) -> None: # noqa: D401
|
|
71
|
+
if key in getattr(self, "_configure_delegate_map", {}):
|
|
72
|
+
self._config_delegate_set(key, value)
|
|
73
|
+
return None
|
|
74
|
+
if key == "style" and getattr(self, "__class__", None).__name__ != "OptionMenu":
|
|
75
|
+
return self.configure(**{key: value})
|
|
76
|
+
return self._ttk_base.__setitem__(self, key, value) # type: ignore[misc]
|
|
77
|
+
|
|
78
|
+
def __getitem__(self, key: str) -> Any: # noqa: D401
|
|
79
|
+
if key in getattr(self, "_configure_delegate_map", {}):
|
|
80
|
+
handled, value = self._config_delegate_get(key)
|
|
81
|
+
if handled:
|
|
82
|
+
return value
|
|
83
|
+
if key == "style" and getattr(self, "__class__", None).__name__ != "OptionMenu":
|
|
84
|
+
return self.configure(cnf=key)
|
|
85
|
+
return self._ttk_base.__getitem__(self, key) # type: ignore[misc]
|
|
86
|
+
|
|
87
|
+
def rebuild_style(self):
|
|
88
|
+
"""Recreate the widget's current style with updated style options.
|
|
89
|
+
|
|
90
|
+
This is useful when runtime style_options change (e.g., showing/hiding
|
|
91
|
+
dropdown chevrons) and you need the builder to regenerate assets.
|
|
92
|
+
|
|
93
|
+
Generates a new style name with the correct hash based on the new
|
|
94
|
+
style_options, then applies it to the widget.
|
|
95
|
+
|
|
96
|
+
NOTE: Style options are updated via the `style_options()` method.
|
|
97
|
+
"""
|
|
98
|
+
# Use stored _ttk_class if available (for custom style class like ButtonGroup)
|
|
99
|
+
# Otherwise fall back to actual widget class
|
|
100
|
+
widget_class = getattr(self, '_ttk_class', None) or self.winfo_class()
|
|
101
|
+
|
|
102
|
+
style_options = getattr(self, '_style_options', {})
|
|
103
|
+
|
|
104
|
+
# Use stored accent/variant if available
|
|
105
|
+
accent = getattr(self, '_accent', None)
|
|
106
|
+
variant = getattr(self, '_variant', None)
|
|
107
|
+
|
|
108
|
+
# Fall back to extraction from style if not stored
|
|
109
|
+
if accent is None or variant is None:
|
|
110
|
+
current_style = self._ttk_base.cget(self, "style") # type: ignore[misc]
|
|
111
|
+
if current_style:
|
|
112
|
+
if accent is None:
|
|
113
|
+
accent = extract_accent_from_style(current_style, default=None)
|
|
114
|
+
if variant is None:
|
|
115
|
+
variant = extract_variant_from_style(current_style, widget_class)
|
|
116
|
+
|
|
117
|
+
# Preserve surface and orientation in style_options
|
|
118
|
+
if not style_options:
|
|
119
|
+
style_options = {}
|
|
120
|
+
surface = getattr(self, "_surface", None)
|
|
121
|
+
if surface and surface != "content":
|
|
122
|
+
style_options["surface"] = surface
|
|
123
|
+
if widget_class in ORIENT_CLASSES:
|
|
124
|
+
try:
|
|
125
|
+
style_options["orient"] = str(self.cget("orient"))
|
|
126
|
+
except Exception:
|
|
127
|
+
pass
|
|
128
|
+
|
|
129
|
+
# Generate NEW style name with NEW hash based on new options
|
|
130
|
+
ttk_style = StyleResolver.create_ttk_style(
|
|
131
|
+
widget_class=widget_class,
|
|
132
|
+
style_options=style_options or None,
|
|
133
|
+
accent=accent,
|
|
134
|
+
variant=variant,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
# Apply the new style
|
|
138
|
+
return self._ttk_base.configure(self, style=ttk_style) # type: ignore[misc]
|
|
139
|
+
|
|
140
|
+
# ----- Built-in delegated handlers -----
|
|
141
|
+
@configure_delegate("accent")
|
|
142
|
+
def _delegate_accent(self, value: Any = None):
|
|
143
|
+
"""Get or set the accent token for this widget.
|
|
144
|
+
|
|
145
|
+
- Query: returns the current accent token or None
|
|
146
|
+
- Set: updates the widget style with the new accent, preserving variant
|
|
147
|
+
"""
|
|
148
|
+
# Query path
|
|
149
|
+
if value is None:
|
|
150
|
+
# Try stored value first
|
|
151
|
+
stored = getattr(self, '_accent', None)
|
|
152
|
+
if stored:
|
|
153
|
+
return stored
|
|
154
|
+
# Fall back to extracting from current style
|
|
155
|
+
current_style = self._ttk_base.cget(self, "style") # type: ignore[misc]
|
|
156
|
+
if not current_style:
|
|
157
|
+
return None
|
|
158
|
+
return extract_accent_from_style(current_style, default=None)
|
|
159
|
+
|
|
160
|
+
# Set path - use stored _ttk_class if available
|
|
161
|
+
widget_class = getattr(self, '_ttk_class', None) or self.winfo_class()
|
|
162
|
+
current_variant = getattr(self, '_variant', None)
|
|
163
|
+
if current_variant is None:
|
|
164
|
+
current_style = self._ttk_base.cget(self, "style") # type: ignore[misc]
|
|
165
|
+
if current_style:
|
|
166
|
+
current_variant = extract_variant_from_style(current_style, widget_class)
|
|
167
|
+
|
|
168
|
+
# Coerce 'default' to None — means "no accent, use widget's neutral style"
|
|
169
|
+
if value == 'default':
|
|
170
|
+
value = None
|
|
171
|
+
|
|
172
|
+
# Store the new accent
|
|
173
|
+
setattr(self, '_accent', value)
|
|
174
|
+
|
|
175
|
+
# Use stored style_options
|
|
176
|
+
style_options = getattr(self, '_style_options', {}).copy()
|
|
177
|
+
|
|
178
|
+
surface = getattr(self, "_surface", None)
|
|
179
|
+
if surface and surface != "content":
|
|
180
|
+
style_options["surface"] = surface
|
|
181
|
+
|
|
182
|
+
if widget_class in ORIENT_CLASSES:
|
|
183
|
+
try:
|
|
184
|
+
style_options["orient"] = str(self.cget("orient"))
|
|
185
|
+
except Exception:
|
|
186
|
+
pass
|
|
187
|
+
|
|
188
|
+
if widget_class in CONTAINER_CLASSES:
|
|
189
|
+
style_options["surface"] = value
|
|
190
|
+
setattr(self, "_surface", value)
|
|
191
|
+
|
|
192
|
+
ttk_style = StyleResolver.create_ttk_style(
|
|
193
|
+
widget_class=widget_class,
|
|
194
|
+
accent=value,
|
|
195
|
+
variant=current_variant,
|
|
196
|
+
style_options=style_options or None,
|
|
197
|
+
)
|
|
198
|
+
return self._ttk_base.configure(self, style=ttk_style) # type: ignore[misc]
|
|
199
|
+
|
|
200
|
+
@configure_delegate("variant")
|
|
201
|
+
def _delegate_variant(self, value: Any = None):
|
|
202
|
+
"""Get or set the variant for this widget.
|
|
203
|
+
|
|
204
|
+
- Query: returns the current variant name or None
|
|
205
|
+
- Set: updates the widget style with the new variant, preserving accent
|
|
206
|
+
|
|
207
|
+
Note: If the variant is not valid for this widget type, a StyleBuilderError
|
|
208
|
+
will be raised.
|
|
209
|
+
"""
|
|
210
|
+
# Query path
|
|
211
|
+
if value is None:
|
|
212
|
+
# Try stored value first
|
|
213
|
+
stored = getattr(self, '_variant', None)
|
|
214
|
+
if stored:
|
|
215
|
+
return stored
|
|
216
|
+
# Fall back to extracting from current style
|
|
217
|
+
current_style = self._ttk_base.cget(self, "style") # type: ignore[misc]
|
|
218
|
+
if not current_style:
|
|
219
|
+
return None
|
|
220
|
+
widget_class = getattr(self, '_ttk_class', None) or self.winfo_class()
|
|
221
|
+
return extract_variant_from_style(current_style, widget_class)
|
|
222
|
+
|
|
223
|
+
# Set path - use stored _ttk_class if available
|
|
224
|
+
widget_class = getattr(self, '_ttk_class', None) or self.winfo_class()
|
|
225
|
+
current_accent = getattr(self, '_accent', None)
|
|
226
|
+
if current_accent is None:
|
|
227
|
+
current_style = self._ttk_base.cget(self, "style") # type: ignore[misc]
|
|
228
|
+
if current_style:
|
|
229
|
+
current_accent = extract_accent_from_style(current_style, default=None)
|
|
230
|
+
|
|
231
|
+
# Store the new variant
|
|
232
|
+
setattr(self, '_variant', value)
|
|
233
|
+
|
|
234
|
+
# Use stored style_options
|
|
235
|
+
style_options = getattr(self, '_style_options', {}).copy()
|
|
236
|
+
|
|
237
|
+
surface = getattr(self, "_surface", None)
|
|
238
|
+
if surface and surface != "content":
|
|
239
|
+
style_options["surface"] = surface
|
|
240
|
+
|
|
241
|
+
if widget_class in ORIENT_CLASSES:
|
|
242
|
+
try:
|
|
243
|
+
style_options["orient"] = str(self.cget("orient"))
|
|
244
|
+
except Exception:
|
|
245
|
+
pass
|
|
246
|
+
|
|
247
|
+
ttk_style = StyleResolver.create_ttk_style(
|
|
248
|
+
widget_class=widget_class,
|
|
249
|
+
accent=current_accent,
|
|
250
|
+
variant=value,
|
|
251
|
+
style_options=style_options or None,
|
|
252
|
+
)
|
|
253
|
+
return self._ttk_base.configure(self, style=ttk_style) # type: ignore[misc]
|
|
254
|
+
|
|
255
|
+
def configure_style_options(self, value=None, **kwargs):
|
|
256
|
+
"""Get or set the widget style options if handled by the widget's style builder.
|
|
257
|
+
|
|
258
|
+
Special cascading keys:
|
|
259
|
+
surface: Updates `_surface` on this widget and (via Frame subclass) triggers
|
|
260
|
+
`_refresh_descendant_surfaces` to propagate the change to children.
|
|
261
|
+
input_background: Updates `_input_background` on this widget and (via Frame
|
|
262
|
+
subclass) triggers `_refresh_descendant_input_backgrounds` to propagate
|
|
263
|
+
the change to all descendant input widgets.
|
|
264
|
+
"""
|
|
265
|
+
options = getattr(self, "_style_options", {})
|
|
266
|
+
if value is None:
|
|
267
|
+
options.update(**kwargs)
|
|
268
|
+
setattr(self, "_style_options", options)
|
|
269
|
+
if "surface" in kwargs:
|
|
270
|
+
surface = kwargs.get("surface")
|
|
271
|
+
setattr(self, "_surface", surface or "content")
|
|
272
|
+
if "input_background" in kwargs:
|
|
273
|
+
setattr(self, "_input_background", kwargs.get("input_background"))
|
|
274
|
+
return None
|
|
275
|
+
else:
|
|
276
|
+
return options.get(value, None)
|
|
277
|
+
|
|
278
|
+
def _capture_style_options(self, options: list[str] = None, source: Any = None):
|
|
279
|
+
"""Extract named kwargs into a style-options dict before passing to super.
|
|
280
|
+
|
|
281
|
+
Call this before `super().__init__` to capture style-related kwargs that
|
|
282
|
+
are exposed as explicit parameters rather than via `style_options=`. Also
|
|
283
|
+
merges any `style_options` dict already in `source`.
|
|
284
|
+
|
|
285
|
+
Args:
|
|
286
|
+
options: Names of kwargs to extract from `source`.
|
|
287
|
+
source: The kwargs dict to extract from (mutated in-place).
|
|
288
|
+
|
|
289
|
+
Returns:
|
|
290
|
+
A dict of captured style options, e.g. `{'icon_only': True, 'icon': 'house'}`.
|
|
291
|
+
"""
|
|
292
|
+
if source is None:
|
|
293
|
+
return {}
|
|
294
|
+
|
|
295
|
+
style_options = source.pop("style_options", {})
|
|
296
|
+
|
|
297
|
+
captured = {}
|
|
298
|
+
for option in options:
|
|
299
|
+
if option in source:
|
|
300
|
+
captured[option] = source.pop(option)
|
|
301
|
+
if captured:
|
|
302
|
+
style_options.update(**captured)
|
|
303
|
+
|
|
304
|
+
return style_options
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
__all__ = ["TTKWrapperBase"]
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Widget parts module for composite widgets."""
|
|
2
|
+
|
|
3
|
+
from bootstack.widgets._impl._parts.textentry_part import TextEntryPart
|
|
4
|
+
from bootstack.widgets._impl._parts.numberentry_part import NumberEntryPart
|
|
5
|
+
from bootstack.widgets._impl._parts.spinnerentry_part import SpinnerEntryPart
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
'TextEntryPart',
|
|
9
|
+
'NumberEntryPart',
|
|
10
|
+
'SpinnerEntryPart',
|
|
11
|
+
]
|