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,302 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import tkinter
|
|
4
|
+
from typing import overload, Any, Callable, Literal, TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from bootstack.widgets._impl.composites.selectbox import SelectBox as _InternalSelectBox
|
|
7
|
+
from bootstack.widgets._core.base import PublicWidgetBase, adapt_handler
|
|
8
|
+
from bootstack.widgets._core.events import resolve_event, register_widget_events
|
|
9
|
+
from bootstack.widgets._core.options import record_to_dict
|
|
10
|
+
from bootstack.events import ChangeEvent, Subscription
|
|
11
|
+
from bootstack.streams import Stream
|
|
12
|
+
from bootstack.widgets.textfield import _INNER_ENTRY_SEQUENCES
|
|
13
|
+
from bootstack.widgets.types import AccentToken, Event, Option, OptionDict, WidgetDensity
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from bootstack.signals import Signal
|
|
17
|
+
|
|
18
|
+
_SELECT_EVENTS: dict[str, str] = {
|
|
19
|
+
"change": "<<Change>>",
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Select(PublicWidgetBase):
|
|
24
|
+
"""A single-selection dropdown field.
|
|
25
|
+
|
|
26
|
+
The options list is the first positional argument. All options are
|
|
27
|
+
keyword-only.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
options: Choices presented in the popup. Each item is a plain string,
|
|
31
|
+
a `(text, value)` tuple, or a `{'text': ..., 'value': ...}` dict —
|
|
32
|
+
so an option's displayed label can differ from its stored value.
|
|
33
|
+
A dict option may also carry `'icon'` (a glyph rendered beside the
|
|
34
|
+
row label) and `'disabled'` (when `True` the row is dimmed and
|
|
35
|
+
cannot be chosen); any other keys ride along as carried data on
|
|
36
|
+
`selection`. Defaults to an empty list.
|
|
37
|
+
value: Initially selected value (value-space — matches an option's
|
|
38
|
+
value, not its label).
|
|
39
|
+
signal: Reactive `Signal` two-way bound to the field's displayed text.
|
|
40
|
+
With decoupled options (text differs from value), bind to the value
|
|
41
|
+
via `on_change`/`value`; the signal carries the display text.
|
|
42
|
+
label: Label displayed above the field.
|
|
43
|
+
message: Hint or helper text displayed below the field.
|
|
44
|
+
required: If `True`, marks the field as required and prevents
|
|
45
|
+
empty submission.
|
|
46
|
+
searchable: If `True`, typing in the field filters the option
|
|
47
|
+
list. Defaults to `False`.
|
|
48
|
+
allow_custom_values: If `True`, users may type values not in
|
|
49
|
+
`options`. Defaults to `False`.
|
|
50
|
+
group_by: Name of an option field to cluster the popup rows under
|
|
51
|
+
non-selectable group headers (e.g. `'category'`). The field is read
|
|
52
|
+
from each option's flat record, so it may be any carried bag key (or
|
|
53
|
+
`'text'`/`'value'`). Groups appear in first-appearance order; an
|
|
54
|
+
option missing the field renders headerless. Grouping is
|
|
55
|
+
presentational only — `value`/`selection` are unaffected. `None`
|
|
56
|
+
(default) renders a flat list.
|
|
57
|
+
max_visible_items: Approximate number of option rows the popup shows
|
|
58
|
+
before it scrolls (height is `max_visible_items * row_height`).
|
|
59
|
+
Group headers and separators consume some of that budget, so the
|
|
60
|
+
count is approximate. `None` (default) uses the built-in cap.
|
|
61
|
+
read_only: If `True`, value is visible but the popup cannot be
|
|
62
|
+
opened.
|
|
63
|
+
disabled: If `True`, field is fully non-interactive and dimmed.
|
|
64
|
+
width: Width in character units.
|
|
65
|
+
accent: Accent token applied to the focus ring.
|
|
66
|
+
density: Padding density.
|
|
67
|
+
localize: Whether option labels (in the popup and the selected label
|
|
68
|
+
shown in the field) are translated through the catalog — `True`,
|
|
69
|
+
`False`, or `'auto'` (translate when a translation is registered,
|
|
70
|
+
otherwise show the literal). Defaults to the app's `localize_mode`.
|
|
71
|
+
Set `False` to keep proper nouns untranslated; override a single
|
|
72
|
+
option with its `localize` key. When searching is enabled, search
|
|
73
|
+
matches the displayed (translated) labels.
|
|
74
|
+
parent: Explicit parent widget. If omitted, the current
|
|
75
|
+
context-stack container is used.
|
|
76
|
+
**kwargs: Layout placement options applied by the parent container —
|
|
77
|
+
`fill`, `expand`, `anchor`, `margin`, `row`, `column`, `sticky`.
|
|
78
|
+
See :doc:`/tasks/layout`.
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
def __init__(
|
|
82
|
+
self,
|
|
83
|
+
options: list[Option] | None = None,
|
|
84
|
+
*,
|
|
85
|
+
value: Any = None,
|
|
86
|
+
signal: "Signal[str] | None" = None,
|
|
87
|
+
label: str | None = None,
|
|
88
|
+
message: str | None = None,
|
|
89
|
+
required: bool = False,
|
|
90
|
+
searchable: bool = False,
|
|
91
|
+
allow_custom_values: bool = False,
|
|
92
|
+
group_by: str | None = None,
|
|
93
|
+
max_visible_items: int | None = None,
|
|
94
|
+
read_only: bool = False,
|
|
95
|
+
disabled: bool = False,
|
|
96
|
+
width: int | None = None,
|
|
97
|
+
accent: AccentToken | str | None = None,
|
|
98
|
+
density: WidgetDensity | None = None,
|
|
99
|
+
localize: bool | Literal['auto'] | None = None,
|
|
100
|
+
parent: Any = None,
|
|
101
|
+
**kwargs: Any,
|
|
102
|
+
) -> None:
|
|
103
|
+
self._parent = self._resolve_parent(parent)
|
|
104
|
+
layout_kw = self._split_layout_kwargs(kwargs)
|
|
105
|
+
tk_master = self._parent._child_master() if self._parent else None
|
|
106
|
+
|
|
107
|
+
internal_kwargs: dict[str, Any] = {
|
|
108
|
+
"items": options or [],
|
|
109
|
+
"enable_search": searchable,
|
|
110
|
+
"allow_custom_values": allow_custom_values,
|
|
111
|
+
"group_by": group_by,
|
|
112
|
+
"max_visible_items": max_visible_items,
|
|
113
|
+
# Public Select rejects an unknown value (unless custom values are
|
|
114
|
+
# allowed); internal/embedded SelectBoxes stay lenient.
|
|
115
|
+
"strict_value": not allow_custom_values,
|
|
116
|
+
}
|
|
117
|
+
if value is not None:
|
|
118
|
+
internal_kwargs["value"] = value
|
|
119
|
+
if signal is not None:
|
|
120
|
+
internal_kwargs["textsignal"] = signal
|
|
121
|
+
if label is not None:
|
|
122
|
+
internal_kwargs["label"] = label
|
|
123
|
+
if message is not None:
|
|
124
|
+
internal_kwargs["message"] = message
|
|
125
|
+
if required:
|
|
126
|
+
internal_kwargs["required"] = True
|
|
127
|
+
if disabled:
|
|
128
|
+
internal_kwargs["state"] = "disabled"
|
|
129
|
+
elif read_only:
|
|
130
|
+
internal_kwargs["state"] = "readonly"
|
|
131
|
+
if width is not None:
|
|
132
|
+
internal_kwargs["width"] = width
|
|
133
|
+
if accent is not None:
|
|
134
|
+
internal_kwargs["accent"] = accent
|
|
135
|
+
if density is not None:
|
|
136
|
+
internal_kwargs["density"] = density
|
|
137
|
+
if localize is not None:
|
|
138
|
+
internal_kwargs["localize"] = localize
|
|
139
|
+
|
|
140
|
+
self._internal = _InternalSelectBox(tk_master, **internal_kwargs)
|
|
141
|
+
self._attach_to_parent(layout_kw)
|
|
142
|
+
|
|
143
|
+
# ----- Event routing -----
|
|
144
|
+
|
|
145
|
+
def _entry_widget(self) -> tkinter.Misc:
|
|
146
|
+
return self._internal._entry
|
|
147
|
+
|
|
148
|
+
@overload
|
|
149
|
+
def on(self, event: str) -> Stream: ...
|
|
150
|
+
@overload
|
|
151
|
+
def on(self, event: str, handler: Callable[[Event], Any]) -> Subscription: ...
|
|
152
|
+
def on(self, event: str, handler: Callable[[Event], Any] | None = None) -> Stream | Subscription:
|
|
153
|
+
"""Register a callback for an event by name.
|
|
154
|
+
|
|
155
|
+
A generic, string-keyed escape hatch — prefer the typed `on_*`
|
|
156
|
+
shorthands (e.g. `on_change`), which carry the precise payload type.
|
|
157
|
+
Called with no handler, returns a composable `Stream`; with a handler,
|
|
158
|
+
binds it and returns a `Subscription`.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
event: Event name (for example `'change'` or `'focus'`).
|
|
162
|
+
handler: Called with the event payload. Omit to get a composable
|
|
163
|
+
:class:`~bootstack.streams.Stream` instead.
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
A cancellable :class:`~bootstack.events.Subscription` when a handler
|
|
167
|
+
is given, otherwise a :class:`~bootstack.streams.Stream`.
|
|
168
|
+
"""
|
|
169
|
+
sequence = resolve_event(self, str(event))
|
|
170
|
+
target = self._entry_widget() if sequence in _INNER_ENTRY_SEQUENCES else self._internal
|
|
171
|
+
if handler is None:
|
|
172
|
+
def _source(h):
|
|
173
|
+
t = self._entry_widget() if sequence in _INNER_ENTRY_SEQUENCES else self._internal
|
|
174
|
+
bid = t.bind(sequence, adapt_handler(h), add="+")
|
|
175
|
+
return Subscription(t, sequence, bid)
|
|
176
|
+
return Stream(self._internal, _source=_source)
|
|
177
|
+
bid = target.bind(sequence, adapt_handler(handler), add="+")
|
|
178
|
+
return Subscription(target, sequence, bid)
|
|
179
|
+
|
|
180
|
+
# ----- Properties -----
|
|
181
|
+
|
|
182
|
+
@property
|
|
183
|
+
def value(self) -> Any:
|
|
184
|
+
"""The currently selected value, or `None` if unselected.
|
|
185
|
+
|
|
186
|
+
This is the option's *value* (value-space). For the displayed label, see
|
|
187
|
+
`text`.
|
|
188
|
+
"""
|
|
189
|
+
return self._internal.value
|
|
190
|
+
|
|
191
|
+
@value.setter
|
|
192
|
+
def value(self, v: Any) -> None:
|
|
193
|
+
self._internal.value = v
|
|
194
|
+
|
|
195
|
+
@property
|
|
196
|
+
def text(self) -> str:
|
|
197
|
+
"""The label currently shown in the field — the selected option's text.
|
|
198
|
+
|
|
199
|
+
Read-only; the complement of `value` (the selection's value). Assign to
|
|
200
|
+
`value` to change the selection.
|
|
201
|
+
"""
|
|
202
|
+
return self._internal.text
|
|
203
|
+
|
|
204
|
+
@property
|
|
205
|
+
def selection(self) -> dict | None:
|
|
206
|
+
"""The selected option as a full record dict — the data bag — or `None`.
|
|
207
|
+
|
|
208
|
+
`{'text': ..., 'value': ..., ...any extra keys}`, indexed by key like
|
|
209
|
+
any record. `None` when nothing is selected or the value is a custom
|
|
210
|
+
off-list one. Read-only.
|
|
211
|
+
"""
|
|
212
|
+
return self._internal.selection
|
|
213
|
+
|
|
214
|
+
@property
|
|
215
|
+
def options(self) -> list[OptionDict]:
|
|
216
|
+
"""The available options as normalized `{'text', 'value'}` records.
|
|
217
|
+
|
|
218
|
+
Assigning a new list rebuilds the popup. Accepts the same `Option`
|
|
219
|
+
forms as the constructor (strings, `(text, value)` tuples, or dicts).
|
|
220
|
+
"""
|
|
221
|
+
return [record_to_dict(r) for r in self._internal.cget("items")]
|
|
222
|
+
|
|
223
|
+
@options.setter
|
|
224
|
+
def options(self, items: list[Option]) -> None:
|
|
225
|
+
self._internal.configure(items=list(items))
|
|
226
|
+
|
|
227
|
+
@property
|
|
228
|
+
def group_by(self) -> str | None:
|
|
229
|
+
"""Name of the option field the popup clusters rows under, or `None`.
|
|
230
|
+
|
|
231
|
+
Assigning a field name groups the popup the next time it opens; assign
|
|
232
|
+
`None` to render a flat list. Grouping is presentational only — it does
|
|
233
|
+
not affect `value`, `selection`, or `options`.
|
|
234
|
+
"""
|
|
235
|
+
return self._internal.cget("group_by")
|
|
236
|
+
|
|
237
|
+
@group_by.setter
|
|
238
|
+
def group_by(self, field: str | None) -> None:
|
|
239
|
+
self._internal.configure(group_by=field or "")
|
|
240
|
+
|
|
241
|
+
@property
|
|
242
|
+
def max_visible_items(self) -> int | None:
|
|
243
|
+
"""Approximate option-row cap before the popup scrolls, or `None`.
|
|
244
|
+
|
|
245
|
+
Assigning takes effect the next time the popup opens; assign `None` to
|
|
246
|
+
restore the built-in cap. Height is `max_visible_items * row_height`,
|
|
247
|
+
so group headers and separators make the count approximate.
|
|
248
|
+
"""
|
|
249
|
+
return self._internal.cget("max_visible_items")
|
|
250
|
+
|
|
251
|
+
@max_visible_items.setter
|
|
252
|
+
def max_visible_items(self, n: int | None) -> None:
|
|
253
|
+
self._internal.configure(max_visible_items=n or 0)
|
|
254
|
+
|
|
255
|
+
@property
|
|
256
|
+
def selected_index(self) -> int:
|
|
257
|
+
"""Zero-based index of the selected option, or `-1` if none selected."""
|
|
258
|
+
return self._internal.selected_index
|
|
259
|
+
|
|
260
|
+
@selected_index.setter
|
|
261
|
+
def selected_index(self, v: int) -> None:
|
|
262
|
+
self._internal.selected_index = v
|
|
263
|
+
|
|
264
|
+
@property
|
|
265
|
+
def read_only(self) -> bool:
|
|
266
|
+
"""Whether the field is visible but the popup cannot be opened."""
|
|
267
|
+
return str(self._entry_widget().cget("state")) == "readonly"
|
|
268
|
+
|
|
269
|
+
@read_only.setter
|
|
270
|
+
def read_only(self, v: bool) -> None:
|
|
271
|
+
self._internal.configure(state="readonly" if v else "normal")
|
|
272
|
+
|
|
273
|
+
@property
|
|
274
|
+
def disabled(self) -> bool:
|
|
275
|
+
"""Whether the field is fully non-interactive."""
|
|
276
|
+
return str(self._entry_widget().cget("state")) == "disabled"
|
|
277
|
+
|
|
278
|
+
@disabled.setter
|
|
279
|
+
def disabled(self, v: bool) -> None:
|
|
280
|
+
self._internal.configure(state="disabled" if v else "normal")
|
|
281
|
+
|
|
282
|
+
# ----- Event shorthands -----
|
|
283
|
+
|
|
284
|
+
@overload
|
|
285
|
+
def on_change(self) -> Stream: ...
|
|
286
|
+
@overload
|
|
287
|
+
def on_change(self, handler: Callable[[ChangeEvent], Any]) -> Subscription: ...
|
|
288
|
+
def on_change(self, handler: Callable[[ChangeEvent], Any] | None = None) -> Stream | Subscription:
|
|
289
|
+
"""Register a callback fired when the selection changes.
|
|
290
|
+
|
|
291
|
+
Args:
|
|
292
|
+
handler: Called with a :class:`~bootstack.events.ChangeEvent`. Omit
|
|
293
|
+
to get a composable :class:`~bootstack.streams.Stream` instead.
|
|
294
|
+
|
|
295
|
+
Returns:
|
|
296
|
+
A cancellable :class:`~bootstack.events.Subscription` when a
|
|
297
|
+
handler is given, otherwise a :class:`~bootstack.streams.Stream`.
|
|
298
|
+
"""
|
|
299
|
+
return self.on("change", handler)
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
register_widget_events(Select, _SELECT_EVENTS)
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import overload, Any, Callable, Literal, TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from bootstack.widgets._impl.primitives.optionmenu import OptionMenu as _InternalOptionMenu
|
|
6
|
+
from bootstack.widgets._core.base import PublicWidgetBase
|
|
7
|
+
from bootstack.widgets._core.events import register_widget_events
|
|
8
|
+
from bootstack.widgets._core.icon_image_props import IconProperty
|
|
9
|
+
from bootstack.widgets._core.options import record_to_dict
|
|
10
|
+
from bootstack.events import ChangeEvent, Subscription
|
|
11
|
+
from bootstack.streams import Stream
|
|
12
|
+
from bootstack.widgets.types import AccentToken, Event, Option, OptionDict, WidgetDensity, ButtonVariant, IconSpec
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from bootstack.signals import Signal
|
|
16
|
+
|
|
17
|
+
_SELECTBUTTON_EVENTS: dict[str, str] = {
|
|
18
|
+
"change": "<<Change>>",
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class SelectButton(IconProperty, PublicWidgetBase):
|
|
23
|
+
"""A button that opens a dropdown value list — a button-styled alternative to `Select`.
|
|
24
|
+
|
|
25
|
+
Clicking the button opens a popup list of options. The selected value is
|
|
26
|
+
displayed on the button face. Unlike `Select`, there is no text field —
|
|
27
|
+
the button is the only interaction point.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
options: Choices shown in the popup. Each item is a plain string, a
|
|
31
|
+
`(text, value)` tuple, or a `{'text': ..., 'value': ...}` dict — so
|
|
32
|
+
an option's displayed label can differ from its stored value, e.g.
|
|
33
|
+
`["Light", "Dark"]` or `[("Light theme", "light")]`. A dict option
|
|
34
|
+
may also carry `'icon'` (a glyph rendered beside the menu label) and
|
|
35
|
+
`'disabled'` (when `True` the item is dimmed and cannot be chosen);
|
|
36
|
+
any other keys ride along as carried data on `selection`.
|
|
37
|
+
value: Initially selected value (value-space — matches an option's
|
|
38
|
+
value, not its label). Must match one of the options.
|
|
39
|
+
signal: Reactive `Signal[str]` controlling the selected value. When
|
|
40
|
+
provided, `value=` is ignored — seed the Signal directly.
|
|
41
|
+
disabled: If `True`, the button is non-interactive and dimmed.
|
|
42
|
+
Defaults to `False`.
|
|
43
|
+
accent: Accent token applied to the button.
|
|
44
|
+
variant: Button style variant. Default `'solid'`.
|
|
45
|
+
density: Padding density.
|
|
46
|
+
icon: Bootstrap Icons name shown on the button beside the value.
|
|
47
|
+
localize: Whether option labels (and the selected label on the button
|
|
48
|
+
face) are translated through the catalog — `True`, `False`, or
|
|
49
|
+
`'auto'` (translate when a translation is registered, otherwise show
|
|
50
|
+
the literal). Defaults to the app's `localize_mode`. Set `False` to
|
|
51
|
+
keep proper nouns untranslated; override a single option with its
|
|
52
|
+
`localize` key.
|
|
53
|
+
parent: Explicit parent widget. If omitted, the current
|
|
54
|
+
context-stack container is used.
|
|
55
|
+
**kwargs: Layout placement options applied by the parent container —
|
|
56
|
+
`fill`, `expand`, `anchor`, `margin`, `row`, `column`, `sticky`.
|
|
57
|
+
See :doc:`/tasks/layout`.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
def __init__(
|
|
61
|
+
self,
|
|
62
|
+
options: list[Option] | None = None,
|
|
63
|
+
*,
|
|
64
|
+
value: Any = None,
|
|
65
|
+
signal: "Signal[str] | None" = None,
|
|
66
|
+
disabled: bool = False,
|
|
67
|
+
accent: AccentToken | str | None = None,
|
|
68
|
+
variant: ButtonVariant = "default",
|
|
69
|
+
density: WidgetDensity | None = None,
|
|
70
|
+
icon: str | IconSpec | None = None,
|
|
71
|
+
localize: bool | Literal['auto'] | None = None,
|
|
72
|
+
parent: Any = None,
|
|
73
|
+
**kwargs: Any,
|
|
74
|
+
) -> None:
|
|
75
|
+
self._parent = self._resolve_parent(parent)
|
|
76
|
+
layout_kw = self._split_layout_kwargs(kwargs)
|
|
77
|
+
tk_master = self._parent._child_master() if self._parent else None
|
|
78
|
+
|
|
79
|
+
internal_kwargs: dict[str, Any] = {
|
|
80
|
+
"options": options or [],
|
|
81
|
+
}
|
|
82
|
+
if value is not None:
|
|
83
|
+
internal_kwargs["value"] = value
|
|
84
|
+
if signal is not None:
|
|
85
|
+
internal_kwargs["textsignal"] = signal
|
|
86
|
+
if disabled:
|
|
87
|
+
internal_kwargs["state"] = "disabled"
|
|
88
|
+
if accent is not None:
|
|
89
|
+
internal_kwargs["accent"] = accent
|
|
90
|
+
if variant is not None:
|
|
91
|
+
internal_kwargs["variant"] = variant
|
|
92
|
+
if density is not None:
|
|
93
|
+
internal_kwargs["density"] = density
|
|
94
|
+
if icon is not None:
|
|
95
|
+
internal_kwargs["icon"] = icon
|
|
96
|
+
if localize is not None:
|
|
97
|
+
internal_kwargs["localize"] = localize
|
|
98
|
+
|
|
99
|
+
self._internal = _InternalOptionMenu(tk_master, **internal_kwargs)
|
|
100
|
+
self._attach_to_parent(layout_kw)
|
|
101
|
+
|
|
102
|
+
# ----- Properties -----
|
|
103
|
+
|
|
104
|
+
@property
|
|
105
|
+
def value(self) -> Any:
|
|
106
|
+
"""The currently selected value, or `None` if unselected.
|
|
107
|
+
|
|
108
|
+
This is the option's *value* (value-space). For the displayed label, see
|
|
109
|
+
`text`.
|
|
110
|
+
"""
|
|
111
|
+
return self._internal.get()
|
|
112
|
+
|
|
113
|
+
@value.setter
|
|
114
|
+
def value(self, v: Any) -> None:
|
|
115
|
+
self._internal.set(v)
|
|
116
|
+
|
|
117
|
+
@property
|
|
118
|
+
def text(self) -> str:
|
|
119
|
+
"""The label currently shown on the button — the selected option's text.
|
|
120
|
+
|
|
121
|
+
Read-only; the complement of `value`. Assign to `value` to change the
|
|
122
|
+
selection.
|
|
123
|
+
"""
|
|
124
|
+
return self._internal.text
|
|
125
|
+
|
|
126
|
+
@property
|
|
127
|
+
def selection(self) -> dict | None:
|
|
128
|
+
"""The selected option as a full record dict — the data bag — or `None`.
|
|
129
|
+
|
|
130
|
+
`{'text': ..., 'value': ..., ...any extra keys}`, indexed by key like
|
|
131
|
+
any record. Read-only.
|
|
132
|
+
"""
|
|
133
|
+
return self._internal.selection
|
|
134
|
+
|
|
135
|
+
@property
|
|
136
|
+
def options(self) -> list[OptionDict]:
|
|
137
|
+
"""The available options as normalized `{'text', 'value'}` records.
|
|
138
|
+
|
|
139
|
+
Assigning a new list rebuilds the dropdown. Accepts the same `Option`
|
|
140
|
+
forms as the constructor (strings, `(text, value)` tuples, or dicts).
|
|
141
|
+
"""
|
|
142
|
+
return [record_to_dict(r) for r in self._internal.cget("options")]
|
|
143
|
+
|
|
144
|
+
@options.setter
|
|
145
|
+
def options(self, items: list[Option]) -> None:
|
|
146
|
+
self._internal.configure(options=list(items))
|
|
147
|
+
|
|
148
|
+
@property
|
|
149
|
+
def signal(self) -> "Signal[str] | None":
|
|
150
|
+
"""The reactive `Signal` linked to this button, or `None`."""
|
|
151
|
+
return getattr(self._internal, 'textsignal', None)
|
|
152
|
+
|
|
153
|
+
@property
|
|
154
|
+
def disabled(self) -> bool:
|
|
155
|
+
"""Whether the button is non-interactive."""
|
|
156
|
+
return str(self._internal.cget("state")) == "disabled"
|
|
157
|
+
|
|
158
|
+
@disabled.setter
|
|
159
|
+
def disabled(self, v: bool) -> None:
|
|
160
|
+
self._internal.configure(state="disabled" if v else "normal")
|
|
161
|
+
|
|
162
|
+
# ----- Event shorthands -----
|
|
163
|
+
|
|
164
|
+
@overload
|
|
165
|
+
def on_change(self) -> Stream: ...
|
|
166
|
+
@overload
|
|
167
|
+
def on_change(self, handler: Callable[[ChangeEvent], Any]) -> Subscription: ...
|
|
168
|
+
def on_change(self, handler: Callable[[ChangeEvent], Any] | None = None) -> Stream | Subscription:
|
|
169
|
+
"""Register a callback fired whenever the selected value changes.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
handler: Called with a :class:`~bootstack.events.ChangeEvent`. Omit
|
|
173
|
+
to get a composable :class:`~bootstack.streams.Stream` instead.
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
A cancellable :class:`~bootstack.events.Subscription` when a
|
|
177
|
+
handler is given, otherwise a :class:`~bootstack.streams.Stream`.
|
|
178
|
+
"""
|
|
179
|
+
return self.on("change", handler)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
register_widget_events(SelectButton, _SELECTBUTTON_EVENTS)
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""SidebarToggle — a hamburger button that collapses/expands an AppShell sidebar.
|
|
2
|
+
|
|
3
|
+
This is not a standalone, place-anywhere widget like `ThemeToggle`: a sidebar
|
|
4
|
+
toggle only means anything in the context of one shell's sidebar. It is built by
|
|
5
|
+
`Toolbar.add_sidebar_toggle()` / `StatusBar.add_sidebar_toggle()`, which wire it
|
|
6
|
+
to the owning `AppShell`; it is not exported at the `bootstack` top level.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Any, Literal
|
|
11
|
+
|
|
12
|
+
from bootstack.widgets.button import Button
|
|
13
|
+
from bootstack.widgets.types import AccentToken, ButtonVariant, WidgetDensity
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SidebarToggle(Button):
|
|
17
|
+
"""A hamburger button that collapses and expands an AppShell's sidebar.
|
|
18
|
+
|
|
19
|
+
Clicking it toggles the shell's sidebar. With `collapse='compact'` (the
|
|
20
|
+
default — the desktop convention) it shrinks the sidebar to an icon rail when
|
|
21
|
+
that sidebar can show icons (a static `page_nav`), and falls back to fully
|
|
22
|
+
hiding it when it can't (a data-bound `list_nav`/`tree_nav`). With
|
|
23
|
+
`collapse='hidden'` it always toggles between hidden and expanded. Its icon
|
|
24
|
+
can reflect the current state — `expand_icon` is shown while the sidebar is
|
|
25
|
+
collapsed (clicking expands it) and `collapse_icon` while it is expanded
|
|
26
|
+
(clicking collapses it); each falls back to `icon` when not given.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
shell: The `AppShell` whose sidebar this controls.
|
|
30
|
+
collapse: What "collapse" means — `'compact'` (shrink to the icon rail
|
|
31
|
+
where possible, else hide; the default) or `'hidden'` (always fully
|
|
32
|
+
hide).
|
|
33
|
+
icon: Base icon, used for both states unless a state-specific icon is
|
|
34
|
+
given. Defaults by mode — `'layout-sidebar'` (a panel glyph) for
|
|
35
|
+
`collapse='compact'`, `'list'` (a hamburger) for `collapse='hidden'`.
|
|
36
|
+
expand_icon: Icon shown while the sidebar is collapsed (the affordance to
|
|
37
|
+
expand it). Falls back to `icon`.
|
|
38
|
+
collapse_icon: Icon shown while the sidebar is expanded (the affordance to
|
|
39
|
+
collapse it). Falls back to `icon`.
|
|
40
|
+
variant: Button style variant. Default `'ghost'`.
|
|
41
|
+
density: Padding density — `'default'` or `'compact'`.
|
|
42
|
+
accent: Accent token applied to the icon. Defaults to the foreground.
|
|
43
|
+
parent: Explicit parent widget. If omitted, the current context-stack
|
|
44
|
+
container is used.
|
|
45
|
+
**kwargs: Layout placement options applied by the parent container.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
def __init__(
|
|
49
|
+
self,
|
|
50
|
+
*,
|
|
51
|
+
shell: Any,
|
|
52
|
+
collapse: Literal["compact", "hidden"] = "compact",
|
|
53
|
+
icon: str | None = None,
|
|
54
|
+
expand_icon: str | None = None,
|
|
55
|
+
collapse_icon: str | None = None,
|
|
56
|
+
variant: ButtonVariant = "ghost",
|
|
57
|
+
density: WidgetDensity = "default",
|
|
58
|
+
accent: AccentToken | str | None = None,
|
|
59
|
+
parent: Any = None,
|
|
60
|
+
**kwargs: Any,
|
|
61
|
+
) -> None:
|
|
62
|
+
self._shell = shell
|
|
63
|
+
self._collapse = collapse
|
|
64
|
+
# Default glyph follows the collapse mode: a panel for the icon rail, a
|
|
65
|
+
# hamburger for full hide.
|
|
66
|
+
if icon is None:
|
|
67
|
+
icon = "layout-sidebar" if collapse == "compact" else "list"
|
|
68
|
+
self._base_icon = icon
|
|
69
|
+
self._expand_icon = expand_icon
|
|
70
|
+
self._collapse_icon = collapse_icon
|
|
71
|
+
super().__init__(
|
|
72
|
+
on_click=self._toggle,
|
|
73
|
+
icon=self._state_icon(),
|
|
74
|
+
variant=variant,
|
|
75
|
+
density=density,
|
|
76
|
+
accent=accent,
|
|
77
|
+
parent=parent,
|
|
78
|
+
**kwargs,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
# Keep the icon matching the live sidebar state — toggling here or
|
|
82
|
+
# elsewhere (Ctrl-B, another control) refreshes it. Setting the icon never
|
|
83
|
+
# fires a click, so there is no cycle.
|
|
84
|
+
self._subs = [
|
|
85
|
+
shell.on_sidebar_toggle(self._sync_icon),
|
|
86
|
+
shell.on_sidebar_mode_change(self._sync_icon),
|
|
87
|
+
]
|
|
88
|
+
self.on_destroy(self._cleanup_sidebar_toggle)
|
|
89
|
+
|
|
90
|
+
def _is_expanded(self) -> bool:
|
|
91
|
+
return self._shell.sidebar_mode == "expanded"
|
|
92
|
+
|
|
93
|
+
def _state_icon(self) -> str:
|
|
94
|
+
if self._is_expanded():
|
|
95
|
+
return self._collapse_icon or self._base_icon
|
|
96
|
+
return self._expand_icon or self._base_icon
|
|
97
|
+
|
|
98
|
+
def _toggle(self) -> None:
|
|
99
|
+
if self._collapse == "hidden":
|
|
100
|
+
# Always fully hide (VS Code-style focus collapse).
|
|
101
|
+
self._shell.toggle_sidebar()
|
|
102
|
+
return
|
|
103
|
+
# "compact": collapse as far as is useful — to the icon rail when the
|
|
104
|
+
# active sidebar can show icons, otherwise hide it (a data list can't
|
|
105
|
+
# compact). Mirrors the shell's own Ctrl-B behavior.
|
|
106
|
+
if self._can_compact():
|
|
107
|
+
mode = self._shell.sidebar_mode
|
|
108
|
+
self._shell.sidebar_mode = "expanded" if mode == "compact" else "compact"
|
|
109
|
+
else:
|
|
110
|
+
self._shell.toggle_sidebar()
|
|
111
|
+
|
|
112
|
+
def _can_compact(self) -> bool:
|
|
113
|
+
"""Whether the active sidebar can shrink to icons (vs. only hide)."""
|
|
114
|
+
try:
|
|
115
|
+
return bool(self._shell._internal._can_compact_active())
|
|
116
|
+
except Exception:
|
|
117
|
+
return False
|
|
118
|
+
|
|
119
|
+
def _sync_icon(self, _event: Any = None) -> None:
|
|
120
|
+
self.icon = self._state_icon()
|
|
121
|
+
|
|
122
|
+
def _cleanup_sidebar_toggle(self, _event: Any = None) -> None:
|
|
123
|
+
for sub in self._subs:
|
|
124
|
+
try:
|
|
125
|
+
sub.cancel()
|
|
126
|
+
except Exception:
|
|
127
|
+
pass
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
__all__ = ["SidebarToggle"]
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from bootstack.widgets._impl.primitives.sizegrip import SizeGrip as _InternalSizeGrip
|
|
6
|
+
from bootstack.widgets._core.base import PublicWidgetBase
|
|
7
|
+
from bootstack.widgets.types import AccentToken
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class SizeGrip(PublicWidgetBase):
|
|
11
|
+
"""A resize handle placed in the corner of a window or panel.
|
|
12
|
+
|
|
13
|
+
Typically anchored to the bottom-right corner so users can drag to resize
|
|
14
|
+
the parent window.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
accent: Accent token for styling.
|
|
18
|
+
parent: Override the context-stack parent.
|
|
19
|
+
**kwargs: Layout placement options applied by the parent container —
|
|
20
|
+
`fill`, `expand`, `anchor`, `margin`, `row`, `column`, `sticky`.
|
|
21
|
+
See :doc:`/tasks/layout`.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(
|
|
25
|
+
self,
|
|
26
|
+
*,
|
|
27
|
+
accent: AccentToken | str | None = None,
|
|
28
|
+
parent: Any = None,
|
|
29
|
+
**kwargs: Any,
|
|
30
|
+
) -> None:
|
|
31
|
+
self._parent = self._resolve_parent(parent)
|
|
32
|
+
layout_kw = self._split_layout_kwargs(kwargs)
|
|
33
|
+
|
|
34
|
+
tk_master = self._parent._child_master() if self._parent else None
|
|
35
|
+
|
|
36
|
+
internal_kwargs: dict[str, Any] = {}
|
|
37
|
+
if accent is not None:
|
|
38
|
+
internal_kwargs["accent"] = accent
|
|
39
|
+
# **kwargs is layout-only (split out above) — not forwarded to the internal.
|
|
40
|
+
|
|
41
|
+
self._internal = _InternalSizeGrip(tk_master, **internal_kwargs)
|
|
42
|
+
self._attach_to_parent(layout_kw)
|