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,448 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import tkinter as tk
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from typing_extensions import Unpack
|
|
7
|
+
|
|
8
|
+
from bootstack.widgets._impl.primitives.frame import Frame, FrameKwargs
|
|
9
|
+
from bootstack.widgets._impl.mixins.configure_mixin import configure_delegate
|
|
10
|
+
from bootstack.widgets.types import Master
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _alive(widget: tk.Widget) -> bool:
|
|
14
|
+
"""True if `widget` still exists (Tk `destroy()` leaves dead references)."""
|
|
15
|
+
try:
|
|
16
|
+
return bool(widget.winfo_exists())
|
|
17
|
+
except tk.TclError:
|
|
18
|
+
return False
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class FlexFrame(Frame):
|
|
22
|
+
"""A grid-backed 1-D flex container (the engine behind Row and Column).
|
|
23
|
+
|
|
24
|
+
Lays children along a single main axis using the Tk *grid* geometry
|
|
25
|
+
manager. Unlike pack, grid can express whole-group main-axis distribution
|
|
26
|
+
(`justify`) without phantom spacer widgets — an empty track with a weight
|
|
27
|
+
claims space on its own. That makes true weighted growth, correct
|
|
28
|
+
`space-around`, and a `Spacer` element all expressible on one engine.
|
|
29
|
+
|
|
30
|
+
Children are added through `add_child`, which appends to an ordered managed
|
|
31
|
+
list and re-plans the whole layout (`_relayout`). Re-planning on every add
|
|
32
|
+
is required because `justify`'s phantom tracks and the content-track indices
|
|
33
|
+
shift as children come and go — the same approach `GridFrame` uses.
|
|
34
|
+
|
|
35
|
+
Layout model (HStack == one row, N columns shown; Column transposes):
|
|
36
|
+
|
|
37
|
+
- The single cross track always carries `weight=1` so `align`
|
|
38
|
+
(start/center/end/stretch) has room to position a child even when the
|
|
39
|
+
container is larger than its content.
|
|
40
|
+
- `justify` injects phantom weighted tracks *between/around* the content
|
|
41
|
+
cells (no widget) — only when nothing grows (grow defeats justify).
|
|
42
|
+
- `grow` / `grow_items` / `weights=` put weight on the *content* tracks.
|
|
43
|
+
- `Spacer` children occupy a real content track whose weight (flexible) or
|
|
44
|
+
`minsize` (fixed) is taken from the spacer.
|
|
45
|
+
- Gap is synthesized with edge-aware leading `padx`/`pady`, skipping the
|
|
46
|
+
phantom and spacer breaks so it never doubles up around them.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def __init__(
|
|
50
|
+
self,
|
|
51
|
+
master: Master = None,
|
|
52
|
+
*,
|
|
53
|
+
direction: str = "horizontal",
|
|
54
|
+
horizontal_items: str = "left",
|
|
55
|
+
vertical_items: str = "top",
|
|
56
|
+
grow_items: bool = False,
|
|
57
|
+
weights: list[int] | None = None,
|
|
58
|
+
gap: int = 0,
|
|
59
|
+
propagate: bool | None = None,
|
|
60
|
+
**kwargs: Unpack[FrameKwargs],
|
|
61
|
+
) -> None:
|
|
62
|
+
"""Create a FlexFrame.
|
|
63
|
+
|
|
64
|
+
The public layout vocabulary is screen-axis based: `horizontal_items` and
|
|
65
|
+
`vertical_items` set how children sit on the x and y axes (values are edge
|
|
66
|
+
names — `left`/`right`/`top`/`bottom`/`center`/`stretch`, plus `space-*`
|
|
67
|
+
for the stacking axis). Internally the frame resolves which screen axis is
|
|
68
|
+
the *main* (stacking) axis and which is the *cross* axis from `direction`.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
master: Parent widget. If None, uses the default root window.
|
|
72
|
+
direction: Main axis — `'horizontal'` (a Row) or `'vertical'`
|
|
73
|
+
(a Column). Defaults to `'horizontal'`.
|
|
74
|
+
horizontal_items: How children are positioned on the x axis. For a
|
|
75
|
+
Row (horizontal stacks) this arranges the group
|
|
76
|
+
(`left`/`center`/`right`/`space-*`); for a Column it aligns each
|
|
77
|
+
child (`left`/`center`/`right`/`stretch`). Defaults to `'left'`.
|
|
78
|
+
vertical_items: How children are positioned on the y axis. For a
|
|
79
|
+
Column (vertical stacks) this arranges the group
|
|
80
|
+
(`top`/`center`/`bottom`/`space-*`); for a Row it aligns each
|
|
81
|
+
child (`top`/`center`/`bottom`/`stretch`). Defaults to `'top'`.
|
|
82
|
+
grow_items: When True, every content child grows equally (uniform)
|
|
83
|
+
to share the main axis. Defaults to False.
|
|
84
|
+
weights: Positional shorthand for per-child `grow` —
|
|
85
|
+
`weights[i]` is the flex-grow weight of content child `i`
|
|
86
|
+
(share of leftover main-axis space, not a fixed size). Overrides
|
|
87
|
+
`grow_items` and per-child `grow`; missing trailing entries are
|
|
88
|
+
0. Defaults to None.
|
|
89
|
+
gap: Spacing in pixels between adjacent content children. Defaults
|
|
90
|
+
to 0.
|
|
91
|
+
propagate: When False, the frame does not resize to fit its
|
|
92
|
+
children. Defaults to None (Tk default).
|
|
93
|
+
|
|
94
|
+
Other Parameters:
|
|
95
|
+
**kwargs: Additional keyword arguments forwarded to `Frame`.
|
|
96
|
+
"""
|
|
97
|
+
super().__init__(master, **kwargs)
|
|
98
|
+
self._direction = direction
|
|
99
|
+
self._horizontal_items = horizontal_items
|
|
100
|
+
self._vertical_items = vertical_items
|
|
101
|
+
self._grow_items = grow_items
|
|
102
|
+
self._weights = weights
|
|
103
|
+
self._gap = gap
|
|
104
|
+
# Ordered (widget, opts) entries. opts may carry: grow, horizontal,
|
|
105
|
+
# vertical, padx, pady (resolved margins), and spacer metadata
|
|
106
|
+
# (_spacer, _spacer_size, _spacer_weight).
|
|
107
|
+
self._managed: list[tuple[tk.Widget, dict[str, Any]]] = []
|
|
108
|
+
# Highest main-axis track index configured on the previous relayout,
|
|
109
|
+
# so we can zero stale tracks when the plan shrinks.
|
|
110
|
+
self._used_main_tracks = 0
|
|
111
|
+
# Count of managed children carrying `grow` or spacer metadata. Lets the
|
|
112
|
+
# append fast-path decide in O(1) whether the stack is "pure" (see
|
|
113
|
+
# `_can_append_in_place`) without rescanning every add.
|
|
114
|
+
self._flex_child_count = 0
|
|
115
|
+
if propagate is not None:
|
|
116
|
+
self.grid_propagate(propagate)
|
|
117
|
+
|
|
118
|
+
# ----- direction helpers -------------------------------------------------
|
|
119
|
+
|
|
120
|
+
@property
|
|
121
|
+
def _row_dir(self) -> bool:
|
|
122
|
+
"""True when the main axis is horizontal (columns vary)."""
|
|
123
|
+
return self._direction in ("horizontal", "row")
|
|
124
|
+
|
|
125
|
+
def _main_distribution(self) -> str:
|
|
126
|
+
"""Normalize the stacking-axis value to start/center/end/space-* tokens."""
|
|
127
|
+
if self._row_dir:
|
|
128
|
+
value = self._horizontal_items
|
|
129
|
+
start, end = "left", "right"
|
|
130
|
+
else:
|
|
131
|
+
value = self._vertical_items
|
|
132
|
+
start, end = "top", "bottom"
|
|
133
|
+
if value == start:
|
|
134
|
+
return "start"
|
|
135
|
+
if value == end:
|
|
136
|
+
return "end"
|
|
137
|
+
return value # center, space-between, space-around, space-evenly
|
|
138
|
+
|
|
139
|
+
def _cross_default(self) -> str:
|
|
140
|
+
"""The cross-axis (non-stacking) container alignment value."""
|
|
141
|
+
return self._vertical_items if self._row_dir else self._horizontal_items
|
|
142
|
+
|
|
143
|
+
def _cross_sticky(self, value: str) -> str:
|
|
144
|
+
"""Map a cross-axis edge value to Tk sticky chars for this direction."""
|
|
145
|
+
if value == "stretch":
|
|
146
|
+
return "ns" if self._row_dir else "ew"
|
|
147
|
+
if self._row_dir: # cross axis is vertical
|
|
148
|
+
if value == "top":
|
|
149
|
+
return "n"
|
|
150
|
+
if value == "bottom":
|
|
151
|
+
return "s"
|
|
152
|
+
else: # cross axis is horizontal
|
|
153
|
+
if value == "left":
|
|
154
|
+
return "w"
|
|
155
|
+
if value == "right":
|
|
156
|
+
return "e"
|
|
157
|
+
return "" # center
|
|
158
|
+
|
|
159
|
+
def index_of(self, widget: tk.Widget) -> int:
|
|
160
|
+
"""Return the managed index of `widget`, or -1 if not managed."""
|
|
161
|
+
for i, (w, _) in enumerate(self._managed):
|
|
162
|
+
if w is widget:
|
|
163
|
+
return i
|
|
164
|
+
return -1
|
|
165
|
+
|
|
166
|
+
# ----- child management --------------------------------------------------
|
|
167
|
+
|
|
168
|
+
def add_child(
|
|
169
|
+
self, widget: tk.Widget, opts: dict[str, Any], index: int | None = None
|
|
170
|
+
) -> None:
|
|
171
|
+
"""Add `widget` to the flow (at `index`, else appended) and re-plan.
|
|
172
|
+
|
|
173
|
+
A plain append onto a "pure" start-aligned stack grids only the new
|
|
174
|
+
child (the fast path) instead of re-planning every sibling, so building
|
|
175
|
+
an N-child stack is O(N) rather than O(N^2). Anything that can shift a
|
|
176
|
+
sibling's track — a mid-list insert, a distribution mode, or a
|
|
177
|
+
grow/spacer child — takes the full `_relayout`.
|
|
178
|
+
"""
|
|
179
|
+
entry = (widget, dict(opts))
|
|
180
|
+
appending = index is None or index >= len(self._managed)
|
|
181
|
+
new_is_flex = bool(opts.get("grow")) or bool(opts.get("_spacer"))
|
|
182
|
+
# Decide the fast path BEFORE the count reflects this child (the check
|
|
183
|
+
# asks whether the EXISTING stack is pure).
|
|
184
|
+
fast = appending and not new_is_flex and self._can_append_in_place()
|
|
185
|
+
if appending:
|
|
186
|
+
self._managed.append(entry)
|
|
187
|
+
else:
|
|
188
|
+
self._managed.insert(max(index, 0), entry)
|
|
189
|
+
if new_is_flex:
|
|
190
|
+
self._flex_child_count += 1
|
|
191
|
+
if fast:
|
|
192
|
+
self._grid_appended_plain(widget, opts)
|
|
193
|
+
else:
|
|
194
|
+
self._relayout()
|
|
195
|
+
|
|
196
|
+
def remove_child(self, widget: tk.Widget) -> None:
|
|
197
|
+
"""Remove `widget` from the flow (grid-forget it) and re-plan."""
|
|
198
|
+
idx = self.index_of(widget)
|
|
199
|
+
if idx < 0:
|
|
200
|
+
return
|
|
201
|
+
_, removed_opts = self._managed[idx]
|
|
202
|
+
if removed_opts.get("grow") or removed_opts.get("_spacer"):
|
|
203
|
+
self._flex_child_count -= 1
|
|
204
|
+
try:
|
|
205
|
+
tk.Grid.forget(widget)
|
|
206
|
+
except tk.TclError:
|
|
207
|
+
pass
|
|
208
|
+
self._managed.pop(idx)
|
|
209
|
+
self._relayout()
|
|
210
|
+
|
|
211
|
+
def _can_append_in_place(self) -> bool:
|
|
212
|
+
"""True when appending a plain child cannot move any existing sibling.
|
|
213
|
+
|
|
214
|
+
Holds only for a start-aligned stack with no container-level growth and
|
|
215
|
+
no grow/spacer children: every child then occupies one consecutive
|
|
216
|
+
track, so a new child simply takes the next one. Any distribution mode
|
|
217
|
+
(`grow`, `weights`, `grow_items`, a `Spacer`, or a `space-*`/`center`/
|
|
218
|
+
`end` justify) interleaves phantom tracks or shifts weights and needs a
|
|
219
|
+
full re-plan. O(1) — `_flex_child_count` avoids a per-add scan.
|
|
220
|
+
"""
|
|
221
|
+
return (
|
|
222
|
+
self._flex_child_count == 0
|
|
223
|
+
and not self._grow_items
|
|
224
|
+
and self._weights is None
|
|
225
|
+
and self._main_distribution() == "start"
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
def _grid_appended_plain(self, widget: tk.Widget, opts: dict[str, Any]) -> None:
|
|
229
|
+
"""Grid a single just-appended child, leaving its siblings in place.
|
|
230
|
+
|
|
231
|
+
Valid only under `_can_append_in_place`. Mirrors the content-child arm
|
|
232
|
+
of `_relayout` for the pure case: one track per child, no growth, cross
|
|
233
|
+
sticky from the child's alignment, and a leading gap after the first.
|
|
234
|
+
"""
|
|
235
|
+
row_dir = self._row_dir
|
|
236
|
+
idx = len(self._managed) - 1 # one track per child in the pure case
|
|
237
|
+
cross_cfg = self.grid_rowconfigure if row_dir else self.grid_columnconfigure
|
|
238
|
+
main_cfg = self.grid_columnconfigure if row_dir else self.grid_rowconfigure
|
|
239
|
+
cross_cfg(0, weight=1) # cross track fills so alignment has room
|
|
240
|
+
main_cfg(idx, weight=0)
|
|
241
|
+
|
|
242
|
+
cross_key = "vertical" if row_dir else "horizontal"
|
|
243
|
+
cross_val = opts.get(cross_key) or self._cross_default()
|
|
244
|
+
sticky = self._cross_sticky(cross_val)
|
|
245
|
+
|
|
246
|
+
main_axis = "padx" if row_dir else "pady"
|
|
247
|
+
cross_axis = "pady" if row_dir else "padx"
|
|
248
|
+
lead = self._gap if idx > 0 else 0
|
|
249
|
+
trail = 0
|
|
250
|
+
margin_main = opts.get(main_axis)
|
|
251
|
+
if isinstance(margin_main, tuple):
|
|
252
|
+
lead += margin_main[0]
|
|
253
|
+
trail = margin_main[1]
|
|
254
|
+
elif margin_main is not None:
|
|
255
|
+
lead += margin_main
|
|
256
|
+
pad: dict[str, Any] = {}
|
|
257
|
+
if lead or trail:
|
|
258
|
+
pad[main_axis] = (lead, trail)
|
|
259
|
+
margin_cross = opts.get(cross_axis)
|
|
260
|
+
if margin_cross is not None:
|
|
261
|
+
pad[cross_axis] = margin_cross
|
|
262
|
+
|
|
263
|
+
self._grid_cell(widget, idx, sticky, pad)
|
|
264
|
+
self._used_main_tracks = max(self._used_main_tracks, idx)
|
|
265
|
+
|
|
266
|
+
# ----- configuration delegates -------------------------------------------
|
|
267
|
+
|
|
268
|
+
@configure_delegate('gap')
|
|
269
|
+
def _delegate_gap(self, value=None) -> int:
|
|
270
|
+
"""Get or set the gap between children."""
|
|
271
|
+
if value is None:
|
|
272
|
+
return self._gap
|
|
273
|
+
self._gap = value
|
|
274
|
+
self._relayout()
|
|
275
|
+
|
|
276
|
+
@configure_delegate('horizontal_items')
|
|
277
|
+
def _delegate_horizontal_items(self, value=None) -> str:
|
|
278
|
+
"""Get or set how children are positioned on the x axis."""
|
|
279
|
+
if value is None:
|
|
280
|
+
return self._horizontal_items
|
|
281
|
+
self._horizontal_items = value
|
|
282
|
+
self._relayout()
|
|
283
|
+
|
|
284
|
+
@configure_delegate('vertical_items')
|
|
285
|
+
def _delegate_vertical_items(self, value=None) -> str:
|
|
286
|
+
"""Get or set how children are positioned on the y axis."""
|
|
287
|
+
if value is None:
|
|
288
|
+
return self._vertical_items
|
|
289
|
+
self._vertical_items = value
|
|
290
|
+
self._relayout()
|
|
291
|
+
|
|
292
|
+
# ----- the planner -------------------------------------------------------
|
|
293
|
+
|
|
294
|
+
def _grid_cell(
|
|
295
|
+
self, widget: tk.Widget, idx: int, sticky: str, pad: dict[str, Any]
|
|
296
|
+
) -> None:
|
|
297
|
+
"""Place `widget` into main-track `idx` on the single cross track."""
|
|
298
|
+
opts: dict[str, Any] = {"in_": self, "sticky": sticky, **pad}
|
|
299
|
+
if self._row_dir:
|
|
300
|
+
opts.update(row=0, column=idx)
|
|
301
|
+
else:
|
|
302
|
+
opts.update(column=0, row=idx)
|
|
303
|
+
tk.Grid.configure(widget, **opts)
|
|
304
|
+
|
|
305
|
+
def _relayout(self) -> None:
|
|
306
|
+
"""Re-plan and re-grid every managed child from scratch."""
|
|
307
|
+
row_dir = self._row_dir
|
|
308
|
+
main_cfg = self.grid_columnconfigure if row_dir else self.grid_rowconfigure
|
|
309
|
+
cross_cfg = self.grid_rowconfigure if row_dir else self.grid_columnconfigure
|
|
310
|
+
|
|
311
|
+
# Reposition surviving children IN PLACE — re-gridding an already-gridded
|
|
312
|
+
# widget (grid_configure) moves it without an unmap/remap, so we must NOT
|
|
313
|
+
# grid_forget them here: that would fire spurious <Unmap>/<Map> (and thus
|
|
314
|
+
# on_detach/on_attach) on every sibling whenever one child is added or
|
|
315
|
+
# removed. A genuinely removed child is grid-forgotten by remove_child
|
|
316
|
+
# before this runs, so it is already out of self._managed.
|
|
317
|
+
#
|
|
318
|
+
# Zero the tracks the previous plan used so a now-shorter plan leaves no
|
|
319
|
+
# stale weights/minsizes/uniform groups (configuring a track does not
|
|
320
|
+
# touch the widgets in it).
|
|
321
|
+
for i in range(self._used_main_tracks + 1):
|
|
322
|
+
main_cfg(i, weight=0, minsize=0, uniform="")
|
|
323
|
+
self._used_main_tracks = 0
|
|
324
|
+
|
|
325
|
+
# Drop children destroyed out from under us: Tk's destroy() does not route
|
|
326
|
+
# through remove_child, so a dead child lingers in _managed and re-gridding
|
|
327
|
+
# it below would raise "bad window path name". (GridFrame guards the same.)
|
|
328
|
+
if any(not _alive(w) for w, _ in self._managed):
|
|
329
|
+
self._managed = [e for e in self._managed if _alive(e[0])]
|
|
330
|
+
self._flex_child_count = sum(
|
|
331
|
+
1 for _, o in self._managed if o.get("grow") or o.get("_spacer")
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
items = self._managed
|
|
335
|
+
if not items:
|
|
336
|
+
return
|
|
337
|
+
|
|
338
|
+
# The cross track always fills so align has room to work.
|
|
339
|
+
cross_cfg(0, weight=1)
|
|
340
|
+
|
|
341
|
+
j = self._main_distribution()
|
|
342
|
+
per_child_grow = any(
|
|
343
|
+
o.get("grow") for _, o in items if not o.get("_spacer")
|
|
344
|
+
)
|
|
345
|
+
has_spacer = any(o.get("_spacer") for _, o in items)
|
|
346
|
+
any_grow = (
|
|
347
|
+
self._grow_items
|
|
348
|
+
or per_child_grow
|
|
349
|
+
or self._weights is not None
|
|
350
|
+
or has_spacer
|
|
351
|
+
)
|
|
352
|
+
# justify only engages when nothing competes for the main axis.
|
|
353
|
+
use_just = (not any_grow) and j != "start"
|
|
354
|
+
uniform_grow = self._grow_items and not per_child_grow and self._weights is None
|
|
355
|
+
|
|
356
|
+
# Build a plan of phantom breaks ('p', weight) and content ('c', w, o).
|
|
357
|
+
plan: list[tuple] = []
|
|
358
|
+
if use_just and j in ("end", "center", "space-evenly", "space-around"):
|
|
359
|
+
plan.append(("p", 1))
|
|
360
|
+
for i, (w, o) in enumerate(items):
|
|
361
|
+
if i > 0 and use_just:
|
|
362
|
+
if j in ("space-between", "space-evenly"):
|
|
363
|
+
plan.append(("p", 1))
|
|
364
|
+
elif j == "space-around":
|
|
365
|
+
plan.append(("p", 2))
|
|
366
|
+
plan.append(("c", w, o))
|
|
367
|
+
if use_just and j in ("center", "space-evenly", "space-around"):
|
|
368
|
+
plan.append(("p", 1))
|
|
369
|
+
|
|
370
|
+
idx = 0
|
|
371
|
+
content_seen = 0
|
|
372
|
+
prev_content = False
|
|
373
|
+
for entry in plan:
|
|
374
|
+
if entry[0] == "p":
|
|
375
|
+
main_cfg(idx, weight=entry[1])
|
|
376
|
+
idx += 1
|
|
377
|
+
prev_content = False
|
|
378
|
+
continue
|
|
379
|
+
|
|
380
|
+
_, widget, o = entry
|
|
381
|
+
|
|
382
|
+
# Spacer: a real child whose track carries the slack/fixed size.
|
|
383
|
+
if o.get("_spacer"):
|
|
384
|
+
size = o.get("_spacer_size")
|
|
385
|
+
if size is not None:
|
|
386
|
+
main_cfg(idx, weight=0, minsize=size)
|
|
387
|
+
else:
|
|
388
|
+
main_cfg(idx, weight=o.get("_spacer_weight", 1))
|
|
389
|
+
self._grid_cell(widget, idx, "", {})
|
|
390
|
+
idx += 1
|
|
391
|
+
prev_content = False
|
|
392
|
+
continue
|
|
393
|
+
|
|
394
|
+
# Content child: resolve its main-axis weight. `grow` is bool | int —
|
|
395
|
+
# grow=True means weight 1, grow=N sets the relative weight. Coerce
|
|
396
|
+
# bool to int so Tk's columnconfigure never sees a bool.
|
|
397
|
+
weight = o.get("grow")
|
|
398
|
+
if isinstance(weight, bool):
|
|
399
|
+
weight = 1 if weight else 0
|
|
400
|
+
if weight is None and self._grow_items:
|
|
401
|
+
weight = 1
|
|
402
|
+
if self._weights is not None:
|
|
403
|
+
weight = (
|
|
404
|
+
int(self._weights[content_seen])
|
|
405
|
+
if content_seen < len(self._weights)
|
|
406
|
+
else 0
|
|
407
|
+
)
|
|
408
|
+
weight = int(weight or 0)
|
|
409
|
+
grows = weight > 0
|
|
410
|
+
if uniform_grow:
|
|
411
|
+
main_cfg(idx, weight=weight, uniform="flex")
|
|
412
|
+
else:
|
|
413
|
+
main_cfg(idx, weight=weight)
|
|
414
|
+
|
|
415
|
+
# Cross-axis sticky from the child's cross alignment (+ main-axis
|
|
416
|
+
# stretch when growing). The cross axis is vertical for a Row and
|
|
417
|
+
# horizontal for a Column, so the relevant per-child key flips.
|
|
418
|
+
cross_key = "vertical" if row_dir else "horizontal"
|
|
419
|
+
cross_val = o.get(cross_key) or self._cross_default()
|
|
420
|
+
sticky = ""
|
|
421
|
+
if grows:
|
|
422
|
+
sticky += "ew" if row_dir else "ns"
|
|
423
|
+
sticky += self._cross_sticky(cross_val)
|
|
424
|
+
|
|
425
|
+
# Gap (leading, between content only) + per-child margins.
|
|
426
|
+
main_axis = "padx" if row_dir else "pady"
|
|
427
|
+
cross_axis = "pady" if row_dir else "padx"
|
|
428
|
+
lead = self._gap if prev_content else 0
|
|
429
|
+
trail = 0
|
|
430
|
+
margin_main = o.get(main_axis)
|
|
431
|
+
if isinstance(margin_main, tuple):
|
|
432
|
+
lead += margin_main[0]
|
|
433
|
+
trail = margin_main[1]
|
|
434
|
+
elif margin_main is not None:
|
|
435
|
+
lead += margin_main
|
|
436
|
+
pad: dict[str, Any] = {}
|
|
437
|
+
if lead or trail:
|
|
438
|
+
pad[main_axis] = (lead, trail)
|
|
439
|
+
margin_cross = o.get(cross_axis)
|
|
440
|
+
if margin_cross is not None:
|
|
441
|
+
pad[cross_axis] = margin_cross
|
|
442
|
+
|
|
443
|
+
self._grid_cell(widget, idx, sticky, pad)
|
|
444
|
+
idx += 1
|
|
445
|
+
content_seen += 1
|
|
446
|
+
prev_content = True
|
|
447
|
+
|
|
448
|
+
self._used_main_tracks = max(idx - 1, 0)
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from tkinter import ttk
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from typing_extensions import Unpack
|
|
7
|
+
|
|
8
|
+
from bootstack._core.mixins.ttk_state import TtkStateMixin
|
|
9
|
+
from bootstack._core.mixins.widget import WidgetCapabilitiesMixin
|
|
10
|
+
from bootstack.widgets._impl._internal.wrapper_base import TTKWrapperBase
|
|
11
|
+
from bootstack.widgets.types import Master, StyledKwargs
|
|
12
|
+
from bootstack.style.style import get_style
|
|
13
|
+
from ..mixins import configure_delegate
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class FrameKwargs(StyledKwargs, total=False):
|
|
17
|
+
# Standard ttk.Frame options
|
|
18
|
+
padding: Any
|
|
19
|
+
width: int
|
|
20
|
+
height: int
|
|
21
|
+
|
|
22
|
+
# bootstack-specific extensions
|
|
23
|
+
input_background: str
|
|
24
|
+
show_border: bool
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Frame(TTKWrapperBase, WidgetCapabilitiesMixin, TtkStateMixin, ttk.Frame):
|
|
28
|
+
"""bootstack wrapper for `ttk.Frame` with themed styling support."""
|
|
29
|
+
|
|
30
|
+
_ttk_base = ttk.Frame
|
|
31
|
+
|
|
32
|
+
def __init__(self, master: Master = None, **kwargs: Unpack[FrameKwargs]) -> None:
|
|
33
|
+
"""Create a themed bootstack Frame.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
master: Parent widget. If None, uses the default root window.
|
|
37
|
+
|
|
38
|
+
Other Parameters:
|
|
39
|
+
padding: Extra padding inside the frame.
|
|
40
|
+
relief: Border style.
|
|
41
|
+
borderwidth: Border width.
|
|
42
|
+
width: Requested width in pixels.
|
|
43
|
+
height: Requested height in pixels.
|
|
44
|
+
takefocus: Widget accepts focus during keyboard traversal.
|
|
45
|
+
style: Explicit ttk style name (overrides accent/variant).
|
|
46
|
+
accent: Accent token for styling, e.g. 'primary', 'secondary', 'success'.
|
|
47
|
+
variant: Style variant (if applicable).
|
|
48
|
+
surface: Optional surface token; otherwise inherited.
|
|
49
|
+
input_background: Surface token used as the fill color for all input
|
|
50
|
+
widgets (Entry, Combobox, Spinbox, Field) inside this container. Cascades
|
|
51
|
+
to descendants the same way `surface` does. Input foreground, border,
|
|
52
|
+
and focus-ring colors are all derived from this fill so contrast is always
|
|
53
|
+
correct. Defaults to `'content'` (the app background), which keeps
|
|
54
|
+
inputs visually distinct regardless of the container surface. Override
|
|
55
|
+
with any surface token (e.g. `'card'`) to match the container.
|
|
56
|
+
show_border: Draw a border around the frame.
|
|
57
|
+
style_options: Optional dict forwarded to the style builder.
|
|
58
|
+
"""
|
|
59
|
+
existing = kwargs.pop('style_options', {})
|
|
60
|
+
captured = self._capture_style_options(['show_border'], kwargs)
|
|
61
|
+
kwargs['style_options'] = {**existing, **captured}
|
|
62
|
+
super().__init__(master, **kwargs)
|
|
63
|
+
|
|
64
|
+
# A canvas-painting Frame subclass (Meter, Slider, …) recolors on a theme
|
|
65
|
+
# change by defining `_bs_apply_theme(self)`; the unified theme walk in
|
|
66
|
+
# `bootstack.style.style` calls it. There is no per-widget subscription or
|
|
67
|
+
# `<Map>` deferral — visibility is resolved by the walk at apply time.
|
|
68
|
+
|
|
69
|
+
def configure_style_options(self, value=None, **kwargs):
|
|
70
|
+
"""Set style options and refresh descendant surfaces if needed."""
|
|
71
|
+
old_surface = getattr(self, "_surface", "background")
|
|
72
|
+
old_input_bg = getattr(self, "_input_background", None)
|
|
73
|
+
result = super().configure_style_options(value, **kwargs)
|
|
74
|
+
if value is None:
|
|
75
|
+
if "surface" in kwargs:
|
|
76
|
+
new_surface = getattr(self, "_surface", "background")
|
|
77
|
+
if old_surface != new_surface:
|
|
78
|
+
self.rebuild_style()
|
|
79
|
+
self._refresh_descendant_surfaces(old_surface, new_surface)
|
|
80
|
+
if "input_background" in kwargs:
|
|
81
|
+
new_input_bg = getattr(self, "_input_background", None)
|
|
82
|
+
if old_input_bg != new_input_bg:
|
|
83
|
+
self._refresh_descendant_input_backgrounds(old_input_bg, new_input_bg)
|
|
84
|
+
return result
|
|
85
|
+
|
|
86
|
+
def _refresh_descendant_surfaces(self, old_surface: str, new_surface: str) -> None:
|
|
87
|
+
if old_surface == new_surface:
|
|
88
|
+
return
|
|
89
|
+
|
|
90
|
+
style = get_style()
|
|
91
|
+
builder_tk = style._get_tk_builder()
|
|
92
|
+
|
|
93
|
+
for child in self._iter_descendants():
|
|
94
|
+
try:
|
|
95
|
+
child_surface = getattr(child, "_surface", None)
|
|
96
|
+
except Exception:
|
|
97
|
+
child_surface = None
|
|
98
|
+
|
|
99
|
+
explicit_surface = None
|
|
100
|
+
try:
|
|
101
|
+
explicit_surface = getattr(child, "_style_options", {}).get("surface")
|
|
102
|
+
except Exception:
|
|
103
|
+
explicit_surface = None
|
|
104
|
+
|
|
105
|
+
if explicit_surface and explicit_surface != old_surface:
|
|
106
|
+
continue
|
|
107
|
+
|
|
108
|
+
if child_surface != old_surface:
|
|
109
|
+
continue
|
|
110
|
+
|
|
111
|
+
try:
|
|
112
|
+
setattr(child, "_surface", new_surface)
|
|
113
|
+
except Exception:
|
|
114
|
+
continue
|
|
115
|
+
|
|
116
|
+
if hasattr(child, "rebuild_style"):
|
|
117
|
+
try:
|
|
118
|
+
child.rebuild_style()
|
|
119
|
+
except Exception:
|
|
120
|
+
pass
|
|
121
|
+
else:
|
|
122
|
+
try:
|
|
123
|
+
builder_tk.call_builder(child, surface=new_surface)
|
|
124
|
+
except Exception:
|
|
125
|
+
pass
|
|
126
|
+
|
|
127
|
+
def _refresh_descendant_input_backgrounds(self, old_bg: str | None, new_bg: str | None) -> None:
|
|
128
|
+
if old_bg == new_bg:
|
|
129
|
+
return
|
|
130
|
+
|
|
131
|
+
style = get_style()
|
|
132
|
+
builder_tk = style._get_tk_builder()
|
|
133
|
+
|
|
134
|
+
for child in self._iter_descendants():
|
|
135
|
+
try:
|
|
136
|
+
child_input_bg = getattr(child, "_input_background", None)
|
|
137
|
+
except Exception:
|
|
138
|
+
child_input_bg = None
|
|
139
|
+
|
|
140
|
+
explicit_input_bg = None
|
|
141
|
+
try:
|
|
142
|
+
explicit_input_bg = getattr(child, "_style_options", {}).get("input_background")
|
|
143
|
+
except Exception:
|
|
144
|
+
explicit_input_bg = None
|
|
145
|
+
|
|
146
|
+
if explicit_input_bg and explicit_input_bg != old_bg:
|
|
147
|
+
continue
|
|
148
|
+
|
|
149
|
+
if child_input_bg != old_bg:
|
|
150
|
+
continue
|
|
151
|
+
|
|
152
|
+
try:
|
|
153
|
+
setattr(child, "_input_background", new_bg)
|
|
154
|
+
except Exception:
|
|
155
|
+
continue
|
|
156
|
+
|
|
157
|
+
if hasattr(child, "rebuild_style"):
|
|
158
|
+
try:
|
|
159
|
+
child.rebuild_style()
|
|
160
|
+
except Exception:
|
|
161
|
+
pass
|
|
162
|
+
else:
|
|
163
|
+
try:
|
|
164
|
+
builder_tk.call_builder(child, input_background=new_bg)
|
|
165
|
+
except Exception:
|
|
166
|
+
pass
|
|
167
|
+
|
|
168
|
+
def _iter_descendants(self):
|
|
169
|
+
stack = list(self.winfo_children())
|
|
170
|
+
while stack:
|
|
171
|
+
widget = stack.pop()
|
|
172
|
+
yield widget
|
|
173
|
+
try:
|
|
174
|
+
stack.extend(widget.winfo_children())
|
|
175
|
+
except Exception:
|
|
176
|
+
pass
|
|
177
|
+
|
|
178
|
+
@configure_delegate('show_border')
|
|
179
|
+
def _delegate_show_border(self, value=None):
|
|
180
|
+
"""Get or set whether to draw a border."""
|
|
181
|
+
if value is not None:
|
|
182
|
+
return self.configure_style_options('show_border')
|
|
183
|
+
else:
|
|
184
|
+
self.configure_style_options(show_border=True)
|
|
185
|
+
return self.rebuild_style()
|