bootstack 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bootstack/__init__.py +157 -0
- bootstack/__main__.py +5 -0
- bootstack/_core/__init__.py +21 -0
- bootstack/_core/capabilities/__init__.py +45 -0
- bootstack/_core/capabilities/after.py +103 -0
- bootstack/_core/capabilities/bind.py +192 -0
- bootstack/_core/capabilities/bindtags.py +112 -0
- bootstack/_core/capabilities/busy.py +72 -0
- bootstack/_core/capabilities/clipboard.py +89 -0
- bootstack/_core/capabilities/focus.py +118 -0
- bootstack/_core/capabilities/grab.py +65 -0
- bootstack/_core/capabilities/grid.py +211 -0
- bootstack/_core/capabilities/localization.py +231 -0
- bootstack/_core/capabilities/pack.py +140 -0
- bootstack/_core/capabilities/place.py +113 -0
- bootstack/_core/capabilities/selection.py +136 -0
- bootstack/_core/capabilities/signals.py +244 -0
- bootstack/_core/capabilities/winfo.py +315 -0
- bootstack/_core/colorutils.py +234 -0
- bootstack/_core/exceptions.py +25 -0
- bootstack/_core/images.py +463 -0
- bootstack/_core/mixins/__init__.py +1 -0
- bootstack/_core/mixins/ttk_state.py +35 -0
- bootstack/_core/mixins/widget.py +132 -0
- bootstack/_core/paths.py +49 -0
- bootstack/_core/publisher.py +149 -0
- bootstack/_core/variables.py +62 -0
- bootstack/_runtime/__init__.py +3 -0
- bootstack/_runtime/app.py +930 -0
- bootstack/_runtime/base_window.py +945 -0
- bootstack/_runtime/events.py +399 -0
- bootstack/_runtime/shortcuts.py +496 -0
- bootstack/_runtime/tk_patch.py +43 -0
- bootstack/_runtime/toplevel.py +276 -0
- bootstack/_runtime/utility.py +457 -0
- bootstack/_runtime/visual_focus.py +240 -0
- bootstack/_runtime/window_utilities.py +1123 -0
- bootstack/assets/__init__.py +21 -0
- bootstack/assets/bootstack.ico +0 -0
- bootstack/assets/bootstack.png +0 -0
- bootstack/assets/elements/__init__.py +0 -0
- bootstack/assets/elements/badge-pill.png +0 -0
- bootstack/assets/elements/badge-square.png +0 -0
- bootstack/assets/elements/button-compact.png +0 -0
- bootstack/assets/elements/button-default.png +0 -0
- bootstack/assets/elements/buttongroup-after-h-compact.png +0 -0
- bootstack/assets/elements/buttongroup-after-h-default.png +0 -0
- bootstack/assets/elements/buttongroup-after-v-compact.png +0 -0
- bootstack/assets/elements/buttongroup-after-v-default.png +0 -0
- bootstack/assets/elements/buttongroup-before-h-compact.png +0 -0
- bootstack/assets/elements/buttongroup-before-h-default.png +0 -0
- bootstack/assets/elements/buttongroup-before-v-compact.png +0 -0
- bootstack/assets/elements/buttongroup-before-v-default.png +0 -0
- bootstack/assets/elements/buttongroup-center-h-compact.png +0 -0
- bootstack/assets/elements/buttongroup-center-h-default.png +0 -0
- bootstack/assets/elements/buttongroup-center-v-compact.png +0 -0
- bootstack/assets/elements/buttongroup-center-v-default.png +0 -0
- bootstack/assets/elements/card.png +0 -0
- bootstack/assets/elements/checkbox-checked.png +0 -0
- bootstack/assets/elements/checkbox-indeterminate.png +0 -0
- bootstack/assets/elements/checkbox-unchecked.png +0 -0
- bootstack/assets/elements/field.png +0 -0
- bootstack/assets/elements/input-addon-compact.png +0 -0
- bootstack/assets/elements/input-addon-default.png +0 -0
- bootstack/assets/elements/input-compact.png +0 -0
- bootstack/assets/elements/input-default.png +0 -0
- bootstack/assets/elements/list-item-separated.png +0 -0
- bootstack/assets/elements/list-item.png +0 -0
- bootstack/assets/elements/listrow-compact.png +0 -0
- bootstack/assets/elements/listrow-default.png +0 -0
- bootstack/assets/elements/manifest.toml +361 -0
- bootstack/assets/elements/menu-item.png +0 -0
- bootstack/assets/elements/navitem-compact.png +0 -0
- bootstack/assets/elements/navitem-default.png +0 -0
- bootstack/assets/elements/progressbar-h-compact.png +0 -0
- bootstack/assets/elements/progressbar-h-default.png +0 -0
- bootstack/assets/elements/progressbar-v-compact.png +0 -0
- bootstack/assets/elements/progressbar-v-default.png +0 -0
- bootstack/assets/elements/radiobutton.png +0 -0
- bootstack/assets/elements/scrollbar-horizontal.png +0 -0
- bootstack/assets/elements/scrollbar-vertical.png +0 -0
- bootstack/assets/elements/slider-handle.png +0 -0
- bootstack/assets/elements/slider-track-h.png +0 -0
- bootstack/assets/elements/slider-track-v.png +0 -0
- bootstack/assets/elements/switch-off.png +0 -0
- bootstack/assets/elements/switch-on.png +0 -0
- bootstack/assets/elements/tab-h.png +0 -0
- bootstack/assets/elements/tab-v.png +0 -0
- bootstack/assets/icons/bootstrap.ttf +0 -0
- bootstack/assets/icons/glyphmap.json +2080 -0
- bootstack/assets/icons/icon_metrics.json +12470 -0
- bootstack/assets/locales/ar/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/ar/LC_MESSAGES/bootstack.po +856 -0
- bootstack/assets/locales/bg/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/bg/LC_MESSAGES/bootstack.po +878 -0
- bootstack/assets/locales/cs/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/cs/LC_MESSAGES/bootstack.po +856 -0
- bootstack/assets/locales/da/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/da/LC_MESSAGES/bootstack.po +856 -0
- bootstack/assets/locales/de/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/de/LC_MESSAGES/bootstack.po +856 -0
- bootstack/assets/locales/en/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/en/LC_MESSAGES/bootstack.po +878 -0
- bootstack/assets/locales/es/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/es/LC_MESSAGES/bootstack.po +856 -0
- bootstack/assets/locales/fr/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/fr/LC_MESSAGES/bootstack.po +856 -0
- bootstack/assets/locales/he/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/he/LC_MESSAGES/bootstack.po +854 -0
- bootstack/assets/locales/hi/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/hi/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/it/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/it/LC_MESSAGES/bootstack.po +844 -0
- bootstack/assets/locales/ja/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/ja/LC_MESSAGES/bootstack.po +917 -0
- bootstack/assets/locales/ko/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/ko/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/nb/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/nb/LC_MESSAGES/bootstack.po +844 -0
- bootstack/assets/locales/nl/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/nl/LC_MESSAGES/bootstack.po +844 -0
- bootstack/assets/locales/pl/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/pl/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/pt/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/pt/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/pt_BR/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/pt_BR/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/sl/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/sl/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/sv/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/sv/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/tr/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/tr/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/zh_CN/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/zh_CN/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/zh_TW/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/zh_TW/LC_MESSAGES/bootstack.po +845 -0
- bootstack/cli/__init__.py +133 -0
- bootstack/cli/__main__.py +6 -0
- bootstack/cli/add.py +395 -0
- bootstack/cli/appicon.py +285 -0
- bootstack/cli/build.py +115 -0
- bootstack/cli/config.py +313 -0
- bootstack/cli/demo.py +564 -0
- bootstack/cli/dev.py +153 -0
- bootstack/cli/doctor.py +195 -0
- bootstack/cli/icons.py +98 -0
- bootstack/cli/promote.py +120 -0
- bootstack/cli/pyinstaller.py +268 -0
- bootstack/cli/run.py +95 -0
- bootstack/cli/start.py +117 -0
- bootstack/cli/templates/__init__.py +931 -0
- bootstack/clipboard.py +48 -0
- bootstack/constants.py +318 -0
- bootstack/data/README.md +615 -0
- bootstack/data/__init__.py +78 -0
- bootstack/data/_observable.py +276 -0
- bootstack/data/base.py +780 -0
- bootstack/data/file_source.py +367 -0
- bootstack/data/memory_source.py +450 -0
- bootstack/data/query.py +367 -0
- bootstack/data/readers.py +289 -0
- bootstack/data/sqlite_source.py +869 -0
- bootstack/data/types.py +354 -0
- bootstack/data/writers.py +232 -0
- bootstack/dev/__init__.py +36 -0
- bootstack/dev/_capture.py +141 -0
- bootstack/dev/_env.py +43 -0
- bootstack/dev/_registry.py +140 -0
- bootstack/dev/_reloader.py +351 -0
- bootstack/dev/_reset.py +50 -0
- bootstack/dev/_watcher.py +91 -0
- bootstack/dialogs/__init__.py +948 -0
- bootstack/dialogs/_impl/__init__.py +47 -0
- bootstack/dialogs/_impl/colorchooser.py +588 -0
- bootstack/dialogs/_impl/datedialog.py +450 -0
- bootstack/dialogs/_impl/dialog.py +594 -0
- bootstack/dialogs/_impl/filterdialog.py +358 -0
- bootstack/dialogs/_impl/fontdialog.py +364 -0
- bootstack/dialogs/_impl/formdialog.py +564 -0
- bootstack/dialogs/_impl/message.py +486 -0
- bootstack/dialogs/_impl/query.py +570 -0
- bootstack/errors.py +67 -0
- bootstack/events/__init__.py +111 -0
- bootstack/events/_event.py +135 -0
- bootstack/events/_payloads.py +539 -0
- bootstack/events/_subscription.py +38 -0
- bootstack/i18n/README.md +77 -0
- bootstack/i18n/__init__.py +23 -0
- bootstack/i18n/catalog.py +121 -0
- bootstack/i18n/intl_format.py +584 -0
- bootstack/i18n/msgcat.py +425 -0
- bootstack/i18n/specs.py +156 -0
- bootstack/images.py +563 -0
- bootstack/py.typed +1 -0
- bootstack/scheduling/__init__.py +11 -0
- bootstack/scheduling/_schedule.py +218 -0
- bootstack/shortcuts.py +21 -0
- bootstack/signals/README.md +98 -0
- bootstack/signals/__init__.py +10 -0
- bootstack/signals/integration.py +100 -0
- bootstack/signals/signal.py +353 -0
- bootstack/signals/types.py +6 -0
- bootstack/store.py +286 -0
- bootstack/streams/__init__.py +12 -0
- bootstack/streams/_stream.py +321 -0
- bootstack/style/__init__.py +38 -0
- bootstack/style/builders/__init__.py +51 -0
- bootstack/style/builders/badge.py +46 -0
- bootstack/style/builders/button.py +339 -0
- bootstack/style/builders/buttongroup.py +311 -0
- bootstack/style/builders/calendar.py +271 -0
- bootstack/style/builders/checkbutton.py +110 -0
- bootstack/style/builders/combobox.py +113 -0
- bootstack/style/builders/contextmenu.py +268 -0
- bootstack/style/builders/entry.py +82 -0
- bootstack/style/builders/expander.py +148 -0
- bootstack/style/builders/field.py +335 -0
- bootstack/style/builders/frame.py +50 -0
- bootstack/style/builders/label.py +28 -0
- bootstack/style/builders/labelframe.py +34 -0
- bootstack/style/builders/listview.py +369 -0
- bootstack/style/builders/menubar.py +91 -0
- bootstack/style/builders/menubutton.py +359 -0
- bootstack/style/builders/panedwindow.py +25 -0
- bootstack/style/builders/progressbar.py +67 -0
- bootstack/style/builders/radiobutton.py +99 -0
- bootstack/style/builders/scale.py +64 -0
- bootstack/style/builders/scrollbar.py +225 -0
- bootstack/style/builders/separator.py +49 -0
- bootstack/style/builders/sidenav.py +643 -0
- bootstack/style/builders/sizegrip.py +15 -0
- bootstack/style/builders/spinbox.py +119 -0
- bootstack/style/builders/switch.py +70 -0
- bootstack/style/builders/tabitem.py +204 -0
- bootstack/style/builders/togglegroup.py +294 -0
- bootstack/style/builders/toolbutton.py +275 -0
- bootstack/style/builders/tooltip.py +26 -0
- bootstack/style/builders/treeview.py +193 -0
- bootstack/style/builders/utils.py +455 -0
- bootstack/style/builders_tk/__init__.py +16 -0
- bootstack/style/builders_tk/defaults.py +229 -0
- bootstack/style/element.py +173 -0
- bootstack/style/fonts.py +123 -0
- bootstack/style/style.py +609 -0
- bootstack/style/style_builder_base.py +716 -0
- bootstack/style/style_builder_mixed.py +93 -0
- bootstack/style/style_builder_tk.py +109 -0
- bootstack/style/style_builder_ttk.py +353 -0
- bootstack/style/style_resolver.py +447 -0
- bootstack/style/theme.py +245 -0
- bootstack/style/theme_provider.py +471 -0
- bootstack/style/themes/__init__.py +128 -0
- bootstack/style/tk_patch.py +5 -0
- bootstack/style/token_maps.py +41 -0
- bootstack/style/types.py +32 -0
- bootstack/style/typography.py +523 -0
- bootstack/style/utility.py +746 -0
- bootstack/types.py +39 -0
- bootstack/validation/__init__.py +6 -0
- bootstack/validation/types.py +15 -0
- bootstack/validation/validation_result.py +17 -0
- bootstack/validation/validation_rules.py +205 -0
- bootstack/widgets/__init__.py +74 -0
- bootstack/widgets/_core/__init__.py +31 -0
- bootstack/widgets/_core/app_config.py +266 -0
- bootstack/widgets/_core/base.py +461 -0
- bootstack/widgets/_core/container.py +334 -0
- bootstack/widgets/_core/context.py +35 -0
- bootstack/widgets/_core/events.py +130 -0
- bootstack/widgets/_core/field_mixin.py +353 -0
- bootstack/widgets/_core/icon_image_props.py +72 -0
- bootstack/widgets/_core/image_binding.py +93 -0
- bootstack/widgets/_core/navmodel.py +345 -0
- bootstack/widgets/_core/options.py +210 -0
- bootstack/widgets/_core/selection_group.py +130 -0
- bootstack/widgets/_core/window_controls.py +96 -0
- bootstack/widgets/_core/window_menu.py +246 -0
- bootstack/widgets/_impl/__init__.py +1 -0
- bootstack/widgets/_impl/_internal/__init__.py +0 -0
- bootstack/widgets/_impl/_internal/wrapper_base.py +307 -0
- bootstack/widgets/_impl/_parts/__init__.py +11 -0
- bootstack/widgets/_impl/_parts/numberentry_part.py +385 -0
- bootstack/widgets/_impl/_parts/spinnerentry_part.py +434 -0
- bootstack/widgets/_impl/_parts/textentry_part.py +406 -0
- bootstack/widgets/_impl/composites/__init__.py +33 -0
- bootstack/widgets/_impl/composites/_dateutils.py +33 -0
- bootstack/widgets/_impl/composites/_image_fit.py +105 -0
- bootstack/widgets/_impl/composites/accordion.py +381 -0
- bootstack/widgets/_impl/composites/avatar.py +191 -0
- bootstack/widgets/_impl/composites/buttongroup.py +371 -0
- bootstack/widgets/_impl/composites/calendar.py +972 -0
- bootstack/widgets/_impl/composites/carousel.py +567 -0
- bootstack/widgets/_impl/composites/chart.py +882 -0
- bootstack/widgets/_impl/composites/compositeframe.py +298 -0
- bootstack/widgets/_impl/composites/contextmenu.py +1951 -0
- bootstack/widgets/_impl/composites/dateentry.py +404 -0
- bootstack/widgets/_impl/composites/dropdownbutton.py +325 -0
- bootstack/widgets/_impl/composites/expander.py +515 -0
- bootstack/widgets/_impl/composites/field.py +670 -0
- bootstack/widgets/_impl/composites/form.py +1066 -0
- bootstack/widgets/_impl/composites/gallery.py +551 -0
- bootstack/widgets/_impl/composites/list/__init__.py +15 -0
- bootstack/widgets/_impl/composites/list/listitem.py +802 -0
- bootstack/widgets/_impl/composites/list/listview.py +1433 -0
- bootstack/widgets/_impl/composites/menu/__init__.py +18 -0
- bootstack/widgets/_impl/composites/menu/model.py +358 -0
- bootstack/widgets/_impl/composites/menu/render_native.py +134 -0
- bootstack/widgets/_impl/composites/menu/render_themed.py +134 -0
- bootstack/widgets/_impl/composites/meter.py +860 -0
- bootstack/widgets/_impl/composites/numericentry.py +201 -0
- bootstack/widgets/_impl/composites/pagestack.py +395 -0
- bootstack/widgets/_impl/composites/passwordentry.py +142 -0
- bootstack/widgets/_impl/composites/pathentry.py +168 -0
- bootstack/widgets/_impl/composites/picture.py +289 -0
- bootstack/widgets/_impl/composites/radiogroup.py +511 -0
- bootstack/widgets/_impl/composites/scrolledtext.py +375 -0
- bootstack/widgets/_impl/composites/scrolledtext.pyi +186 -0
- bootstack/widgets/_impl/composites/scrollview.py +764 -0
- bootstack/widgets/_impl/composites/selectbox.py +1026 -0
- bootstack/widgets/_impl/composites/shell/__init__.py +40 -0
- bootstack/widgets/_impl/composites/shell/content_host.py +52 -0
- bootstack/widgets/_impl/composites/shell/layout.py +335 -0
- bootstack/widgets/_impl/composites/shell/nav_panel.py +345 -0
- bootstack/widgets/_impl/composites/shell/providers.py +558 -0
- bootstack/widgets/_impl/composites/shell/rail.py +117 -0
- bootstack/widgets/_impl/composites/shell/shell.py +581 -0
- bootstack/widgets/_impl/composites/shell/workspace.py +273 -0
- bootstack/widgets/_impl/composites/sidenav/__init__.py +16 -0
- bootstack/widgets/_impl/composites/sidenav/header.py +81 -0
- bootstack/widgets/_impl/composites/sidenav/separator.py +44 -0
- bootstack/widgets/_impl/composites/slider/__init__.py +7 -0
- bootstack/widgets/_impl/composites/slider/_shared.py +195 -0
- bootstack/widgets/_impl/composites/slider/rangeslider.py +982 -0
- bootstack/widgets/_impl/composites/slider/slider.py +851 -0
- bootstack/widgets/_impl/composites/spinnerentry.py +185 -0
- bootstack/widgets/_impl/composites/tableview/__init__.py +5 -0
- bootstack/widgets/_impl/composites/tableview/tableview.py +3388 -0
- bootstack/widgets/_impl/composites/tableview/types.py +169 -0
- bootstack/widgets/_impl/composites/tabs/__init__.py +23 -0
- bootstack/widgets/_impl/composites/tabs/tabitem.py +389 -0
- bootstack/widgets/_impl/composites/tabs/tabs.py +974 -0
- bootstack/widgets/_impl/composites/tabs/tabview.py +650 -0
- bootstack/widgets/_impl/composites/textarea/__init__.py +26 -0
- bootstack/widgets/_impl/composites/textarea/change.py +46 -0
- bootstack/widgets/_impl/composites/textarea/codeeditor.py +526 -0
- bootstack/widgets/_impl/composites/textarea/core.py +495 -0
- bootstack/widgets/_impl/composites/textarea/decoration.py +42 -0
- bootstack/widgets/_impl/composites/textarea/diff.py +127 -0
- bootstack/widgets/_impl/composites/textarea/extensions/__init__.py +1 -0
- bootstack/widgets/_impl/composites/textarea/extensions/bracket_matcher.py +138 -0
- bootstack/widgets/_impl/composites/textarea/extensions/indent_guides.py +158 -0
- bootstack/widgets/_impl/composites/textarea/extensions/line_numbers.py +138 -0
- bootstack/widgets/_impl/composites/textarea/extensions/pygments_highlighter.py +312 -0
- bootstack/widgets/_impl/composites/textarea/extensions/smart_indent.py +177 -0
- bootstack/widgets/_impl/composites/textarea/filter.py +171 -0
- bootstack/widgets/_impl/composites/textarea/search_overlay.py +459 -0
- bootstack/widgets/_impl/composites/textarea/sidebar.py +88 -0
- bootstack/widgets/_impl/composites/textarea/style_registry.py +178 -0
- bootstack/widgets/_impl/composites/textarea/textarea.py +606 -0
- bootstack/widgets/_impl/composites/textarea/undo.py +217 -0
- bootstack/widgets/_impl/composites/textentry.py +57 -0
- bootstack/widgets/_impl/composites/timeentry.py +176 -0
- bootstack/widgets/_impl/composites/toast.py +390 -0
- bootstack/widgets/_impl/composites/toast_stack.py +156 -0
- bootstack/widgets/_impl/composites/togglegroup.py +418 -0
- bootstack/widgets/_impl/composites/toolbar.py +608 -0
- bootstack/widgets/_impl/composites/tooltip.py +491 -0
- bootstack/widgets/_impl/composites/tree/__init__.py +7 -0
- bootstack/widgets/_impl/composites/tree/source_binding.py +136 -0
- bootstack/widgets/_impl/composites/tree/treeitem.py +393 -0
- bootstack/widgets/_impl/composites/tree/treenode.py +174 -0
- bootstack/widgets/_impl/composites/tree/treeview.py +841 -0
- bootstack/widgets/_impl/mixins/__init__.py +24 -0
- bootstack/widgets/_impl/mixins/configure_mixin.py +216 -0
- bootstack/widgets/_impl/mixins/entry_mixin.py +134 -0
- bootstack/widgets/_impl/mixins/font_mixin.py +368 -0
- bootstack/widgets/_impl/mixins/icon_mixin.py +61 -0
- bootstack/widgets/_impl/mixins/localization_mixin.py +253 -0
- bootstack/widgets/_impl/mixins/signal_mixin.py +268 -0
- bootstack/widgets/_impl/mixins/validation_mixin.py +226 -0
- bootstack/widgets/_impl/primitives/__init__.py +49 -0
- bootstack/widgets/_impl/primitives/_menubutton.py +107 -0
- bootstack/widgets/_impl/primitives/badge.py +45 -0
- bootstack/widgets/_impl/primitives/button.py +76 -0
- bootstack/widgets/_impl/primitives/card.py +45 -0
- bootstack/widgets/_impl/primitives/checkbutton.py +124 -0
- bootstack/widgets/_impl/primitives/checktoggle.py +62 -0
- bootstack/widgets/_impl/primitives/combobox.py +156 -0
- bootstack/widgets/_impl/primitives/entry.py +87 -0
- bootstack/widgets/_impl/primitives/flexframe.py +448 -0
- bootstack/widgets/_impl/primitives/frame.py +185 -0
- bootstack/widgets/_impl/primitives/gridframe.py +546 -0
- bootstack/widgets/_impl/primitives/label.py +84 -0
- bootstack/widgets/_impl/primitives/labelframe.py +54 -0
- bootstack/widgets/_impl/primitives/optionmenu.py +387 -0
- bootstack/widgets/_impl/primitives/packframe.py +227 -0
- bootstack/widgets/_impl/primitives/panedwindow.py +45 -0
- bootstack/widgets/_impl/primitives/progressbar.py +83 -0
- bootstack/widgets/_impl/primitives/radiobutton.py +115 -0
- bootstack/widgets/_impl/primitives/radiotoggle.py +54 -0
- bootstack/widgets/_impl/primitives/scrollbar.py +42 -0
- bootstack/widgets/_impl/primitives/separator.py +43 -0
- bootstack/widgets/_impl/primitives/sizegrip.py +33 -0
- bootstack/widgets/_impl/primitives/spinbox.py +95 -0
- bootstack/widgets/_impl/primitives/switch.py +44 -0
- bootstack/widgets/_impl/primitives/treeview.py +69 -0
- bootstack/widgets/app.py +371 -0
- bootstack/widgets/appshell.py +1179 -0
- bootstack/widgets/avatar.py +140 -0
- bootstack/widgets/boolean_controls.py +455 -0
- bootstack/widgets/button.py +224 -0
- bootstack/widgets/buttongroup.py +239 -0
- bootstack/widgets/calendar.py +195 -0
- bootstack/widgets/card.py +159 -0
- bootstack/widgets/carousel.py +241 -0
- bootstack/widgets/chart.py +302 -0
- bootstack/widgets/codeeditor.py +675 -0
- bootstack/widgets/contextmenu.py +371 -0
- bootstack/widgets/datatable.py +688 -0
- bootstack/widgets/datefield.py +395 -0
- bootstack/widgets/divider.py +60 -0
- bootstack/widgets/expander.py +579 -0
- bootstack/widgets/form.py +200 -0
- bootstack/widgets/gallery.py +250 -0
- bootstack/widgets/gauge.py +168 -0
- bootstack/widgets/grid.py +121 -0
- bootstack/widgets/groupbox.py +162 -0
- bootstack/widgets/label.py +229 -0
- bootstack/widgets/listview.py +354 -0
- bootstack/widgets/menubutton.py +419 -0
- bootstack/widgets/numberfield.py +431 -0
- bootstack/widgets/pagestack.py +345 -0
- bootstack/widgets/passwordfield.py +383 -0
- bootstack/widgets/pathfield.py +454 -0
- bootstack/widgets/picture.py +251 -0
- bootstack/widgets/progressbar.py +106 -0
- bootstack/widgets/radio_variants.py +271 -0
- bootstack/widgets/radiogroup.py +228 -0
- bootstack/widgets/scrollbar.py +64 -0
- bootstack/widgets/scrollview.py +186 -0
- bootstack/widgets/select.py +302 -0
- bootstack/widgets/selectbutton.py +182 -0
- bootstack/widgets/sidebar_toggle.py +130 -0
- bootstack/widgets/sizegrip.py +42 -0
- bootstack/widgets/slider.py +408 -0
- bootstack/widgets/spinbox.py +147 -0
- bootstack/widgets/spinnerfield.py +413 -0
- bootstack/widgets/splash.py +367 -0
- bootstack/widgets/splitview.py +482 -0
- bootstack/widgets/stacks.py +249 -0
- bootstack/widgets/statusbar.py +189 -0
- bootstack/widgets/tabs.py +425 -0
- bootstack/widgets/textarea.py +459 -0
- bootstack/widgets/textfield.py +410 -0
- bootstack/widgets/theme_toggle.py +92 -0
- bootstack/widgets/timefield.py +340 -0
- bootstack/widgets/toast.py +314 -0
- bootstack/widgets/togglegroup.py +231 -0
- bootstack/widgets/toolbar.py +347 -0
- bootstack/widgets/tooltip.py +94 -0
- bootstack/widgets/tree.py +648 -0
- bootstack/widgets/types.py +305 -0
- bootstack/widgets/window.py +307 -0
- bootstack-0.1.0.dist-info/METADATA +301 -0
- bootstack-0.1.0.dist-info/RECORD +471 -0
- bootstack-0.1.0.dist-info/WHEEL +5 -0
- bootstack-0.1.0.dist-info/entry_points.txt +2 -0
- bootstack-0.1.0.dist-info/licenses/LICENSE +21 -0
- bootstack-0.1.0.dist-info/licenses/NOTICE +10 -0
- bootstack-0.1.0.dist-info/top_level.txt +1 -0
bootstack/types.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Public type aliases and token Literals for annotating bootstack code.
|
|
2
|
+
|
|
3
|
+
These are the names used in widget signatures — accent/variant/surface tokens,
|
|
4
|
+
layout Literals, density, and the keyword TypedDicts. Import them when you
|
|
5
|
+
annotate your own helpers or widget subclasses::
|
|
6
|
+
|
|
7
|
+
from bootstack.types import AccentToken, WidgetDensity
|
|
8
|
+
|
|
9
|
+
They are re-exported from :mod:`bootstack.widgets.types`.
|
|
10
|
+
"""
|
|
11
|
+
from bootstack.widgets.types import (
|
|
12
|
+
BaseWidgetKwargs, StyledKwargs,
|
|
13
|
+
Anchor, BorderMode, CompoundMode, Direction,
|
|
14
|
+
Fill, Justify, Orient, Padding, Relief, Side, Sticky,
|
|
15
|
+
HAlign, VAlign, HArrange, VArrange,
|
|
16
|
+
LayoutKind, AutoFlow, AccordionVariant, Region, ButtonVariant,
|
|
17
|
+
IconPosition, SelectionMode, ExportScope, ExportFormat,
|
|
18
|
+
ToastCorner, SnackbarAlign,
|
|
19
|
+
WidgetState, WidgetDensity, ScrollbarVariant,
|
|
20
|
+
AccentToken, SurfaceToken, WindowStyle,
|
|
21
|
+
ColumnSpec, EditorType, FormOptions,
|
|
22
|
+
Option, OptionDict,
|
|
23
|
+
Icon, IconSpec,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
"BaseWidgetKwargs", "StyledKwargs",
|
|
28
|
+
"Anchor", "BorderMode", "CompoundMode", "Direction",
|
|
29
|
+
"Fill", "Justify", "Orient", "Padding", "Relief", "Side", "Sticky",
|
|
30
|
+
"HAlign", "VAlign", "HArrange", "VArrange",
|
|
31
|
+
"LayoutKind", "AutoFlow", "AccordionVariant", "Region", "ButtonVariant",
|
|
32
|
+
"IconPosition", "SelectionMode", "ExportScope", "ExportFormat",
|
|
33
|
+
"ToastCorner", "SnackbarAlign",
|
|
34
|
+
"WidgetState", "WidgetDensity", "ScrollbarVariant",
|
|
35
|
+
"AccentToken", "SurfaceToken", "WindowStyle",
|
|
36
|
+
"ColumnSpec", "EditorType", "FormOptions",
|
|
37
|
+
"Option", "OptionDict",
|
|
38
|
+
"Icon", "IconSpec",
|
|
39
|
+
]
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from typing import Any, Callable, Literal, Optional, TypedDict
|
|
2
|
+
|
|
3
|
+
RuleType = Literal["required", "email", "pattern", "stringLength", "range", "custom", "compare"]
|
|
4
|
+
"""A built-in validation rule name."""
|
|
5
|
+
|
|
6
|
+
RuleTriggerType = Literal['key', 'blur', 'always', 'manual']
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ValidationOptions(TypedDict, total=False):
|
|
10
|
+
pattern: str
|
|
11
|
+
message: str
|
|
12
|
+
min: Any
|
|
13
|
+
max: Any
|
|
14
|
+
trigger: Optional[Literal["key", "blur", "always", "manual"]]
|
|
15
|
+
func: Callable[[str], bool]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
class ValidationResult:
|
|
2
|
+
"""The outcome of a single validation check.
|
|
3
|
+
|
|
4
|
+
Attributes:
|
|
5
|
+
is_valid (bool): True if validation passed, False otherwise.
|
|
6
|
+
message (str): Error message when `is_valid` is False; empty string otherwise.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
def __init__(self, is_valid: bool, message: str = ""):
|
|
10
|
+
"""Create a validation result.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
is_valid: True if validation passed.
|
|
14
|
+
message: Error message. Defaults to an empty string.
|
|
15
|
+
"""
|
|
16
|
+
self.is_valid = is_valid
|
|
17
|
+
self.message = message
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from typing import Callable
|
|
3
|
+
|
|
4
|
+
from bootstack.validation.types import RuleTriggerType, RuleType
|
|
5
|
+
from bootstack.validation.validation_result import ValidationResult
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# Rules that operate on a text value; meaningless on a typed (number/date/time)
|
|
9
|
+
# value. The field rejects these at attach time when it does not hold text.
|
|
10
|
+
TEXT_RULES = frozenset({"stringLength", "pattern", "email"})
|
|
11
|
+
|
|
12
|
+
# Rules that operate on an orderable typed value (number, date, or time).
|
|
13
|
+
ORDERED_RULES = frozenset({"range"})
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def rule_applies_to_kind(rule_type: str, kind: str) -> bool:
|
|
17
|
+
"""Whether a rule type can validate a field holding the given value kind.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
rule_type: The rule name (e.g. `'stringLength'`, `'range'`).
|
|
21
|
+
kind: The field's value kind — `'text'`, `'number'`, `'date'`, or
|
|
22
|
+
`'time'`.
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
`True` if the rule applies. Text rules apply only to text fields;
|
|
26
|
+
`'range'` applies only to ordered (number/date/time) fields; the
|
|
27
|
+
remaining rules (`'required'`, `'compare'`, `'custom'`) apply to any.
|
|
28
|
+
"""
|
|
29
|
+
if rule_type in TEXT_RULES:
|
|
30
|
+
return kind == "text"
|
|
31
|
+
if rule_type in ORDERED_RULES:
|
|
32
|
+
return kind in ("number", "date", "time")
|
|
33
|
+
return True
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _as_text(value: object) -> str:
|
|
37
|
+
"""Coerce a value to text for the string rules.
|
|
38
|
+
|
|
39
|
+
The string rules (`stringLength`, `pattern`, `email`) operate on text. When
|
|
40
|
+
one is applied to a typed value (a number or date), coerce rather than crash;
|
|
41
|
+
the rule taxonomy rejects that misuse at attach time, this is the last-ditch
|
|
42
|
+
guard. `None` (an empty field) becomes the empty string.
|
|
43
|
+
"""
|
|
44
|
+
if isinstance(value, str):
|
|
45
|
+
return value
|
|
46
|
+
return "" if value is None else str(value)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class ValidationRule:
|
|
50
|
+
"""A single validation rule that can be applied to a string value.
|
|
51
|
+
|
|
52
|
+
Supports the built-in rule types `'required'`, `'email'`,
|
|
53
|
+
`'stringLength'`, `'pattern'`, `'compare'`, and `'custom'`, and carries a
|
|
54
|
+
trigger policy that controls when the rule is evaluated.
|
|
55
|
+
|
|
56
|
+
Attributes:
|
|
57
|
+
type (RuleType): The validation rule type.
|
|
58
|
+
message (str): Custom error message; if empty a default is generated.
|
|
59
|
+
trigger (RuleTriggerType): When the rule fires — `'always'`, `'key'`,
|
|
60
|
+
`'blur'`, or `'manual'`.
|
|
61
|
+
params (dict): Additional parameters specific to the rule type
|
|
62
|
+
(e.g., `min`/`max` for `'stringLength'`, `pattern` for `'pattern'`,
|
|
63
|
+
`other_field` for `'compare'`, `func` for `'custom'`).
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
def __init__(
|
|
67
|
+
self,
|
|
68
|
+
rule_type: RuleType,
|
|
69
|
+
message: str = "",
|
|
70
|
+
**kwargs
|
|
71
|
+
):
|
|
72
|
+
"""Create a validation rule.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
rule_type: The type of validation to apply.
|
|
76
|
+
message: Custom error message. If empty, a sensible default is used.
|
|
77
|
+
**kwargs: Rule-specific parameters. Pass `trigger` to override the
|
|
78
|
+
default trigger policy; all other keys are stored in `params`
|
|
79
|
+
(e.g., `min=3, max=20` for `'stringLength'`, `pattern=r'\\d+'`
|
|
80
|
+
for `'pattern'`, `func=callable` for `'custom'`).
|
|
81
|
+
"""
|
|
82
|
+
self.type = rule_type
|
|
83
|
+
self.message = message
|
|
84
|
+
self.trigger = kwargs.pop('trigger', self._default_trigger())
|
|
85
|
+
self.params = kwargs
|
|
86
|
+
|
|
87
|
+
def validate(self, value: str) -> ValidationResult:
|
|
88
|
+
"""Apply this rule to a value and return the result.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
value: The string value to validate.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
A ValidationResult with `is_valid=True` on success or `is_valid=False`
|
|
95
|
+
with an error message on failure.
|
|
96
|
+
"""
|
|
97
|
+
msg = self.message or self._default_message()
|
|
98
|
+
|
|
99
|
+
if self.type == "required":
|
|
100
|
+
if value is None:
|
|
101
|
+
return ValidationResult(False, msg)
|
|
102
|
+
if isinstance(value, str) and not value.strip():
|
|
103
|
+
return ValidationResult(False, msg)
|
|
104
|
+
# Everything else is valid (non-empty string, number, date, etc.)
|
|
105
|
+
return ValidationResult(True, "")
|
|
106
|
+
|
|
107
|
+
elif self.type == "email":
|
|
108
|
+
if not re.match(r"[^@]+@[^@]+\.[^@]+", _as_text(value)):
|
|
109
|
+
return ValidationResult(False, msg)
|
|
110
|
+
elif self.type == "stringLength":
|
|
111
|
+
min_len = self.params.get("min", 0)
|
|
112
|
+
max_len = self.params.get("max", float("inf"))
|
|
113
|
+
if not (min_len <= len(_as_text(value)) <= max_len):
|
|
114
|
+
return ValidationResult(False, msg)
|
|
115
|
+
elif self.type == "pattern":
|
|
116
|
+
pattern = self.params.get("pattern", "")
|
|
117
|
+
if not re.match(pattern, _as_text(value)):
|
|
118
|
+
return ValidationResult(False, msg)
|
|
119
|
+
elif self.type == "range":
|
|
120
|
+
# Bounds on an ordered value (number/date/time). An empty field is
|
|
121
|
+
# not out of range — use 'required' for presence.
|
|
122
|
+
if value is None or value == "":
|
|
123
|
+
return ValidationResult(True)
|
|
124
|
+
lo = self.params.get("min")
|
|
125
|
+
hi = self.params.get("max")
|
|
126
|
+
try:
|
|
127
|
+
if lo is not None and value < lo:
|
|
128
|
+
return ValidationResult(False, msg)
|
|
129
|
+
if hi is not None and value > hi:
|
|
130
|
+
return ValidationResult(False, msg)
|
|
131
|
+
except TypeError:
|
|
132
|
+
# Incomparable types (e.g. a string bound against a number).
|
|
133
|
+
return ValidationResult(False, msg)
|
|
134
|
+
elif self.type == "compare":
|
|
135
|
+
if value != self._read_other(self.params.get("other_field")):
|
|
136
|
+
return ValidationResult(False, msg)
|
|
137
|
+
elif self.type == "custom":
|
|
138
|
+
func: Callable[[str], bool] = self.params.get("func")
|
|
139
|
+
if func and not func(value):
|
|
140
|
+
return ValidationResult(False, msg)
|
|
141
|
+
|
|
142
|
+
return ValidationResult(True)
|
|
143
|
+
|
|
144
|
+
@staticmethod
|
|
145
|
+
def _read_other(other: object) -> object:
|
|
146
|
+
"""Resolve the current value of a `'compare'` rule's `other_field`.
|
|
147
|
+
|
|
148
|
+
Accepts a `Signal` or any zero-argument callable (called to read), a
|
|
149
|
+
field wrapper exposing a `value` property, or a plain literal value.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
other: The `other_field` parameter passed to the rule.
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
The other field's current value, or `None` if `other` is `None`.
|
|
156
|
+
"""
|
|
157
|
+
if other is None:
|
|
158
|
+
return None
|
|
159
|
+
if callable(other):
|
|
160
|
+
return other()
|
|
161
|
+
if hasattr(other, "value"):
|
|
162
|
+
return other.value
|
|
163
|
+
return other
|
|
164
|
+
|
|
165
|
+
def _default_message(self) -> str:
|
|
166
|
+
"""Return a sensible default error message for this rule type."""
|
|
167
|
+
if self.type == "required":
|
|
168
|
+
return "This field is required."
|
|
169
|
+
elif self.type == "email":
|
|
170
|
+
return "Enter a valid email address."
|
|
171
|
+
elif self.type == "stringLength":
|
|
172
|
+
min_len = self.params.get("min", 0)
|
|
173
|
+
max_len = self.params.get("max", None)
|
|
174
|
+
if max_len is None or max_len == float("inf"):
|
|
175
|
+
return f"Enter at least {min_len} characters."
|
|
176
|
+
return f"Enter between {min_len} and {max_len} characters."
|
|
177
|
+
elif self.type == "pattern":
|
|
178
|
+
return "Value does not match the required pattern."
|
|
179
|
+
elif self.type == "range":
|
|
180
|
+
lo = self.params.get("min")
|
|
181
|
+
hi = self.params.get("max")
|
|
182
|
+
if lo is not None and hi is not None:
|
|
183
|
+
return f"Enter a value between {lo} and {hi}."
|
|
184
|
+
if lo is not None:
|
|
185
|
+
return f"Enter a value of at least {lo}."
|
|
186
|
+
if hi is not None:
|
|
187
|
+
return f"Enter a value of at most {hi}."
|
|
188
|
+
return "Value is out of range."
|
|
189
|
+
elif self.type == "compare":
|
|
190
|
+
return "Values do not match."
|
|
191
|
+
elif self.type == "custom":
|
|
192
|
+
return "Invalid value."
|
|
193
|
+
return "Invalid input."
|
|
194
|
+
|
|
195
|
+
def _default_trigger(self) -> RuleTriggerType:
|
|
196
|
+
"""Return the default trigger policy for this rule type."""
|
|
197
|
+
if self.type == "required":
|
|
198
|
+
return "always"
|
|
199
|
+
elif self.type in {"stringLength", "compare", "range"}:
|
|
200
|
+
return "blur"
|
|
201
|
+
elif self.type in {"email", "pattern"}:
|
|
202
|
+
return "always"
|
|
203
|
+
elif self.type in {"custom"}:
|
|
204
|
+
return "manual"
|
|
205
|
+
return "blur"
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""Widget package for bootstack.
|
|
2
|
+
|
|
3
|
+
Public widgets are lazily importable from this package — e.g.
|
|
4
|
+
`from bootstack.widgets import Button` — mirroring the canonical
|
|
5
|
+
`import bootstack as bs; bs.Button` access. Resolution is lazy (PEP 562)
|
|
6
|
+
so importing the package does not eagerly pull in every widget module.
|
|
7
|
+
"""
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
# Public widget-area name -> defining submodule (relative to this package).
|
|
11
|
+
# Single source of truth for the runtime namespace below.
|
|
12
|
+
_EXPORTS: dict[str, str] = {
|
|
13
|
+
# Application & windows
|
|
14
|
+
"App": "app", "AppShell": "appshell", "Workbench": "appshell",
|
|
15
|
+
"Window": "window", "Splash": "splash",
|
|
16
|
+
# Layout
|
|
17
|
+
"Row": "stacks", "Column": "stacks", "Spacer": "stacks",
|
|
18
|
+
"Grid": "grid", "Card": "card",
|
|
19
|
+
"GroupBox": "groupbox", "Divider": "divider", "ScrollView": "scrollview",
|
|
20
|
+
"SplitView": "splitview", "SplitPane": "splitview",
|
|
21
|
+
"Accordion": "expander", "AccordionSection": "expander",
|
|
22
|
+
# Actions
|
|
23
|
+
"Button": "button", "ButtonGroup": "buttongroup", "MenuButton": "menubutton",
|
|
24
|
+
"Toolbar": "toolbar", "StatusBar": "statusbar",
|
|
25
|
+
"ContextMenu": "contextmenu", "ContextMenuItem": "contextmenu",
|
|
26
|
+
# Inputs
|
|
27
|
+
"TextField": "textfield", "PasswordField": "passwordfield",
|
|
28
|
+
"NumberField": "numberfield", "PathField": "pathfield",
|
|
29
|
+
"SpinnerField": "spinnerfield", "TextArea": "textarea", "CodeEditor": "codeeditor",
|
|
30
|
+
"DateField": "datefield", "TimeField": "timefield",
|
|
31
|
+
"Slider": "slider", "RangeSlider": "slider",
|
|
32
|
+
"EditFilter": "_impl.composites.textarea.filter",
|
|
33
|
+
# Selection
|
|
34
|
+
"Checkbox": "boolean_controls", "Switch": "boolean_controls",
|
|
35
|
+
"ToggleButton": "boolean_controls", "ThemeToggle": "theme_toggle",
|
|
36
|
+
"ToggleGroup": "togglegroup",
|
|
37
|
+
"Radio": "radio_variants", "RadioToggleButton": "radio_variants",
|
|
38
|
+
"RadioGroup": "radiogroup", "Select": "select", "SelectButton": "selectbutton",
|
|
39
|
+
"Calendar": "calendar",
|
|
40
|
+
# Data display
|
|
41
|
+
"Label": "label", "Badge": "label", "Picture": "picture", "Gallery": "gallery",
|
|
42
|
+
"Carousel": "carousel", "Avatar": "avatar", "ProgressBar": "progressbar",
|
|
43
|
+
"Gauge": "gauge", "ListView": "listview", "DataTable": "datatable",
|
|
44
|
+
"Tree": "tree", "TreeNode": "tree", "Chart": "chart",
|
|
45
|
+
# Navigation
|
|
46
|
+
"PageStack": "pagestack", "StackPage": "pagestack",
|
|
47
|
+
"Tabs": "tabs", "TabPage": "tabs",
|
|
48
|
+
# Overlays
|
|
49
|
+
"Tooltip": "tooltip", "toast": "toast", "Notification": "toast",
|
|
50
|
+
"Snackbar": "toast", "snackbar": "toast",
|
|
51
|
+
# Forms
|
|
52
|
+
"Form": "form", "FormItem": "form", "FieldItem": "form", "GroupItem": "form",
|
|
53
|
+
"TabsItem": "form", "TabItem": "form",
|
|
54
|
+
# Extension base classes (for building custom public widgets)
|
|
55
|
+
"PublicWidgetBase": "_core.base", "PublicContainer": "_core.container",
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
__all__ = sorted(_EXPORTS)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def __getattr__(name: str) -> Any:
|
|
62
|
+
"""Lazily import and cache a public widget by name (PEP 562)."""
|
|
63
|
+
module = _EXPORTS.get(name)
|
|
64
|
+
if module is None:
|
|
65
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
66
|
+
import importlib
|
|
67
|
+
|
|
68
|
+
obj = getattr(importlib.import_module(f"bootstack.widgets.{module}"), name)
|
|
69
|
+
globals()[name] = obj # cache so subsequent lookups skip __getattr__
|
|
70
|
+
return obj
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def __dir__() -> list[str]:
|
|
74
|
+
return sorted(set(globals()) | set(_EXPORTS))
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Public framework internals — extend here, do not import directly from user code."""
|
|
2
|
+
from bootstack.widgets._core.base import PublicWidgetBase
|
|
3
|
+
from bootstack.widgets._core.container import PublicContainer
|
|
4
|
+
from bootstack.widgets._core.context import push_container, pop_container, current_container
|
|
5
|
+
from bootstack.widgets._core.events import register_widget_events, resolve_event
|
|
6
|
+
from bootstack.widgets.types import Event
|
|
7
|
+
from bootstack.errors import BootstackError, UnknownEventError, ParentResolutionError
|
|
8
|
+
from bootstack.widgets._core.field_mixin import FieldAddonMixin
|
|
9
|
+
from bootstack.scheduling import Schedule, Job
|
|
10
|
+
from bootstack.streams import Stream, Handle
|
|
11
|
+
from bootstack.events import Subscription
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"BootstackError",
|
|
15
|
+
"Event",
|
|
16
|
+
"FieldAddonMixin",
|
|
17
|
+
"Handle",
|
|
18
|
+
"Job",
|
|
19
|
+
"ParentResolutionError",
|
|
20
|
+
"PublicContainer",
|
|
21
|
+
"PublicWidgetBase",
|
|
22
|
+
"Schedule",
|
|
23
|
+
"Stream",
|
|
24
|
+
"Subscription",
|
|
25
|
+
"UnknownEventError",
|
|
26
|
+
"current_container",
|
|
27
|
+
"pop_container",
|
|
28
|
+
"push_container",
|
|
29
|
+
"register_widget_events",
|
|
30
|
+
"resolve_event",
|
|
31
|
+
]
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
"""Flat configuration properties shared by `App` and `AppShell`.
|
|
2
|
+
|
|
3
|
+
The public app surface exposes configuration as flat `app.*` properties, so
|
|
4
|
+
that what goes in through the constructor comes back out symmetrically:
|
|
5
|
+
`App(theme=...)` in, `app.theme` out. Settable knobs reflect live where the
|
|
6
|
+
machinery supports a runtime change (theme, locale, title); locale-derived
|
|
7
|
+
formats are read-only.
|
|
8
|
+
|
|
9
|
+
`AppConfigMixin` delegates to the internal runtime `App` returned by
|
|
10
|
+
`_config_app()`, which each public class implements.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from typing import TYPE_CHECKING, Any, Callable, Sequence, overload
|
|
16
|
+
|
|
17
|
+
from bootstack.events import Subscription
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from bootstack.streams import Stream
|
|
21
|
+
from bootstack.widgets.types import WindowStyle
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class AppConfigMixin:
|
|
25
|
+
"""Mixin providing flat configuration properties for the public app.
|
|
26
|
+
|
|
27
|
+
Subclasses must implement `_config_app()` to return the internal runtime
|
|
28
|
+
`App` (a `tkinter.Tk` subclass) whose `settings` holds the resolved
|
|
29
|
+
configuration.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def _config_app(self) -> Any:
|
|
33
|
+
"""Return the internal runtime `App` backing this public app."""
|
|
34
|
+
raise NotImplementedError
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def _settings(self) -> Any:
|
|
38
|
+
return self._config_app().settings
|
|
39
|
+
|
|
40
|
+
# ----- identity -----
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def title(self) -> str:
|
|
44
|
+
"""The window title bar text (also the app's display name)."""
|
|
45
|
+
return self._config_app().title()
|
|
46
|
+
|
|
47
|
+
@title.setter
|
|
48
|
+
def title(self, value: str) -> None:
|
|
49
|
+
app = self._config_app()
|
|
50
|
+
app.title(value)
|
|
51
|
+
app.settings.app_name = value
|
|
52
|
+
|
|
53
|
+
# The app name (config-directory / taskbar identity) is set via `title=`
|
|
54
|
+
# (which writes both the title bar and `app_name`); there is no separate
|
|
55
|
+
# public `name` property — it was redundant with `title`.
|
|
56
|
+
|
|
57
|
+
# ----- theme -----
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def theme(self) -> str:
|
|
61
|
+
"""The active theme name. Setting it switches the theme live."""
|
|
62
|
+
from bootstack.style.style import get_theme
|
|
63
|
+
return get_theme()
|
|
64
|
+
|
|
65
|
+
@theme.setter
|
|
66
|
+
def theme(self, value: str) -> None:
|
|
67
|
+
from bootstack.style.style import set_theme
|
|
68
|
+
set_theme(value)
|
|
69
|
+
self._settings.theme = value
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def light_theme(self) -> str:
|
|
73
|
+
"""Theme used for the light end of system-appearance / `toggle_theme`."""
|
|
74
|
+
return self._settings.light_theme
|
|
75
|
+
|
|
76
|
+
@light_theme.setter
|
|
77
|
+
def light_theme(self, value: str) -> None:
|
|
78
|
+
self._settings.light_theme = value
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def dark_theme(self) -> str:
|
|
82
|
+
"""Theme used for the dark end of system-appearance / `toggle_theme`."""
|
|
83
|
+
return self._settings.dark_theme
|
|
84
|
+
|
|
85
|
+
@dark_theme.setter
|
|
86
|
+
def dark_theme(self, value: str) -> None:
|
|
87
|
+
self._settings.dark_theme = value
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def follow_system_appearance(self) -> bool:
|
|
91
|
+
"""Whether the app tracks the OS light/dark appearance (macOS)."""
|
|
92
|
+
return self._settings.follow_system_appearance
|
|
93
|
+
|
|
94
|
+
@follow_system_appearance.setter
|
|
95
|
+
def follow_system_appearance(self, value: bool) -> None:
|
|
96
|
+
self._settings.follow_system_appearance = value
|
|
97
|
+
|
|
98
|
+
# `available_themes` is intentionally construction-only (no runtime property):
|
|
99
|
+
# `App(available_themes=...)` reads as an input ("offer these themes"), but a
|
|
100
|
+
# getter would collide with `get_themes()` (the actual theme-list query) and
|
|
101
|
+
# return `()` by default. Read the exposed list via `get_themes()`.
|
|
102
|
+
|
|
103
|
+
# ----- localization -----
|
|
104
|
+
|
|
105
|
+
@property
|
|
106
|
+
def locale(self) -> str | None:
|
|
107
|
+
"""The active locale (e.g. `'en_US'`). Setting it switches locale live."""
|
|
108
|
+
return self._settings.locale
|
|
109
|
+
|
|
110
|
+
@locale.setter
|
|
111
|
+
def locale(self, value: str) -> None:
|
|
112
|
+
from bootstack.i18n.msgcat import MessageCatalog
|
|
113
|
+
MessageCatalog.locale(value)
|
|
114
|
+
self._settings.locale = value
|
|
115
|
+
|
|
116
|
+
# `localize_mode` is construction-only (no runtime property): changing it
|
|
117
|
+
# would only affect widgets created afterward, not the existing UI, so a
|
|
118
|
+
# live setter would be misleading. Pass it to the constructor.
|
|
119
|
+
|
|
120
|
+
@property
|
|
121
|
+
def locale_language(self) -> str | None:
|
|
122
|
+
"""Base language code derived from the locale (e.g. `'en'`). Read-only."""
|
|
123
|
+
from bootstack._runtime.app import _language_from_locale
|
|
124
|
+
loc = self._settings.locale
|
|
125
|
+
return _language_from_locale(loc) if loc else None
|
|
126
|
+
|
|
127
|
+
@property
|
|
128
|
+
def locale_date_format(self) -> str | None:
|
|
129
|
+
"""Short date pattern for the active locale (e.g. `'M/d/yy'`). Read-only."""
|
|
130
|
+
from bootstack._runtime.app import _safe_date_format
|
|
131
|
+
loc = self._settings.locale
|
|
132
|
+
return _safe_date_format(loc) if loc else None
|
|
133
|
+
|
|
134
|
+
@property
|
|
135
|
+
def locale_time_format(self) -> str | None:
|
|
136
|
+
"""Short time pattern for the active locale, e.g. h:mm a. Read-only."""
|
|
137
|
+
from bootstack._runtime.app import _safe_time_format
|
|
138
|
+
loc = self._settings.locale
|
|
139
|
+
return _safe_time_format(loc) if loc else None
|
|
140
|
+
|
|
141
|
+
@property
|
|
142
|
+
def locale_decimal(self) -> str | None:
|
|
143
|
+
"""Decimal separator for the active locale (e.g. `'.'`). Read-only."""
|
|
144
|
+
from bootstack._runtime.app import _safe_decimal_symbol
|
|
145
|
+
loc = self._settings.locale
|
|
146
|
+
return _safe_decimal_symbol(loc) if loc else None
|
|
147
|
+
|
|
148
|
+
@property
|
|
149
|
+
def locale_thousands(self) -> str | None:
|
|
150
|
+
"""Thousands separator for the active locale (e.g. `','`). Read-only."""
|
|
151
|
+
from bootstack._runtime.app import _safe_group_symbol
|
|
152
|
+
loc = self._settings.locale
|
|
153
|
+
return _safe_group_symbol(loc) if loc else None
|
|
154
|
+
|
|
155
|
+
# ----- platform / window state -----
|
|
156
|
+
|
|
157
|
+
@property
|
|
158
|
+
def window_style(self) -> WindowStyle | str | None:
|
|
159
|
+
"""Windows-only window effect, or None to disable."""
|
|
160
|
+
return self._settings.window_style
|
|
161
|
+
|
|
162
|
+
@window_style.setter
|
|
163
|
+
def window_style(self, value: WindowStyle | str | None) -> None:
|
|
164
|
+
app = self._config_app()
|
|
165
|
+
app.settings.window_style = value
|
|
166
|
+
# Best-effort live re-apply (Windows / pywinstyles only).
|
|
167
|
+
try:
|
|
168
|
+
app._window_style = value
|
|
169
|
+
app._window_style_applied = False
|
|
170
|
+
app._apply_window_style()
|
|
171
|
+
except Exception:
|
|
172
|
+
pass
|
|
173
|
+
|
|
174
|
+
# `macos_quit_behavior` and `state_path` are construction-only (no runtime
|
|
175
|
+
# properties): both are set-once startup configuration that a user would not
|
|
176
|
+
# change while the app runs. Pass them to the constructor.
|
|
177
|
+
|
|
178
|
+
@property
|
|
179
|
+
def remember_window_state(self) -> bool:
|
|
180
|
+
"""Whether window geometry is saved on close and restored on launch."""
|
|
181
|
+
return self._settings.remember_window_state
|
|
182
|
+
|
|
183
|
+
@remember_window_state.setter
|
|
184
|
+
def remember_window_state(self, value: bool) -> None:
|
|
185
|
+
self._settings.remember_window_state = value
|
|
186
|
+
|
|
187
|
+
# ----- configuration-change events -----
|
|
188
|
+
|
|
189
|
+
def _bind_value_event(
|
|
190
|
+
self,
|
|
191
|
+
sequence: str,
|
|
192
|
+
key: str,
|
|
193
|
+
handler: Callable[[Any], Any] | None,
|
|
194
|
+
) -> "Stream | Subscription":
|
|
195
|
+
app = self._config_app()
|
|
196
|
+
|
|
197
|
+
def _decode(h: Callable[[Any], Any]) -> Callable[[Any], Any]:
|
|
198
|
+
def _wrapped(raw: Any) -> Any:
|
|
199
|
+
data = getattr(raw, "data", None)
|
|
200
|
+
value = data.get(key) if isinstance(data, dict) else data
|
|
201
|
+
return h(value)
|
|
202
|
+
return _wrapped
|
|
203
|
+
|
|
204
|
+
if handler is None:
|
|
205
|
+
from bootstack.streams import Stream
|
|
206
|
+
|
|
207
|
+
def _source(h: Callable[[Any], Any]) -> Subscription:
|
|
208
|
+
bid = app.bind(sequence, _decode(h), add="+")
|
|
209
|
+
return Subscription(app, sequence, bid)
|
|
210
|
+
|
|
211
|
+
return Stream(app, _source=_source)
|
|
212
|
+
bid = app.bind(sequence, _decode(handler), add="+")
|
|
213
|
+
return Subscription(app, sequence, bid)
|
|
214
|
+
|
|
215
|
+
@overload
|
|
216
|
+
def on_theme_change(self) -> "Stream": ...
|
|
217
|
+
@overload
|
|
218
|
+
def on_theme_change(self, handler: Callable[[str], Any]) -> Subscription: ...
|
|
219
|
+
def on_theme_change(
|
|
220
|
+
self, handler: Callable[[str], Any] | None = None
|
|
221
|
+
) -> "Stream | Subscription":
|
|
222
|
+
"""React to theme changes; the handler receives the new theme name.
|
|
223
|
+
|
|
224
|
+
Fired after the theme is fully rebuilt, so handlers can safely read new
|
|
225
|
+
colors. Useful for persisting the choice, e.g.
|
|
226
|
+
`app.on_theme_change(lambda t: store.update(theme=t))`.
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
`Subscription` (with handler) or `Stream` (without handler).
|
|
230
|
+
"""
|
|
231
|
+
return self._bind_value_event("<<BsThemeChanged>>", "theme", handler)
|
|
232
|
+
|
|
233
|
+
@overload
|
|
234
|
+
def on_locale_change(self) -> "Stream": ...
|
|
235
|
+
@overload
|
|
236
|
+
def on_locale_change(self, handler: Callable[[str], Any]) -> Subscription: ...
|
|
237
|
+
def on_locale_change(
|
|
238
|
+
self, handler: Callable[[str], Any] | None = None
|
|
239
|
+
) -> "Stream | Subscription":
|
|
240
|
+
"""React to locale changes; the handler receives the new locale code.
|
|
241
|
+
|
|
242
|
+
Returns:
|
|
243
|
+
`Subscription` (with handler) or `Stream` (without handler).
|
|
244
|
+
"""
|
|
245
|
+
return self._bind_value_event("<<LocaleChanged>>", "locale", handler)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
# The set of App() constructor kwargs that map to configuration. Used by
|
|
249
|
+
# App.from_store() to tolerantly filter a persisted dict (version skew).
|
|
250
|
+
APP_CONFIG_KWARGS: frozenset[str] = frozenset({
|
|
251
|
+
"title",
|
|
252
|
+
"theme",
|
|
253
|
+
"light_theme",
|
|
254
|
+
"dark_theme",
|
|
255
|
+
"follow_system_appearance",
|
|
256
|
+
"available_themes",
|
|
257
|
+
"locale",
|
|
258
|
+
"localize_mode",
|
|
259
|
+
"window_style",
|
|
260
|
+
"macos_quit_behavior",
|
|
261
|
+
"remember_window_state",
|
|
262
|
+
"state_path",
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
__all__ = ["AppConfigMixin", "APP_CONFIG_KWARGS"]
|