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,251 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any, Callable, Literal, overload, TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from bootstack.events import ImageErrorEvent, ImageLoadEvent, Event, Subscription
|
|
7
|
+
from bootstack.streams import Stream
|
|
8
|
+
from bootstack.widgets._core.base import PublicWidgetBase
|
|
9
|
+
from bootstack.widgets._core.events import register_widget_events
|
|
10
|
+
from bootstack.widgets._impl.composites.picture import Picture as _InternalPicture
|
|
11
|
+
from bootstack.widgets.types import Anchor, Padding, SurfaceToken
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from bootstack.images import Image
|
|
15
|
+
|
|
16
|
+
ImageFit = Literal["contain", "cover", "fill", "none", "scale-down"]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Picture(PublicWidgetBase):
|
|
20
|
+
"""Displays an image, scaled to fit, with optional animation.
|
|
21
|
+
|
|
22
|
+
`Picture` is the display widget for pictures and media — the counterpart to
|
|
23
|
+
an `Image`, which is only a source handle. Hand it an `Image` (or a file
|
|
24
|
+
path) and it renders the picture into a resizable area, scaling it to fit
|
|
25
|
+
with a chosen policy. Animated GIF and WebP sources play automatically.
|
|
26
|
+
|
|
27
|
+
Unlike showing an image on a `Label`, a `Picture` is sizing-aware: it fills
|
|
28
|
+
the space it is given and re-fits the picture as that space changes, so it
|
|
29
|
+
works in responsive layouts.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
image: The picture to show — an `Image` handle (from `bootstack.images`),
|
|
33
|
+
a file path (`str` or `Path`, opened for you), or `None` for an empty
|
|
34
|
+
frame. Assign the `image` property later to change it.
|
|
35
|
+
fit: How the picture is scaled into the display area. Default
|
|
36
|
+
`'contain'`.
|
|
37
|
+
|
|
38
|
+
- `'contain'` — scale to fit inside, preserving aspect ratio
|
|
39
|
+
(letterboxing any remainder).
|
|
40
|
+
- `'cover'` — scale to fill, preserving aspect ratio (cropping the
|
|
41
|
+
overflow).
|
|
42
|
+
- `'fill'` — stretch to fill, ignoring aspect ratio.
|
|
43
|
+
- `'none'` — show at natural size, no scaling.
|
|
44
|
+
- `'scale-down'` — like `'contain'`, but never enlarge past natural
|
|
45
|
+
size.
|
|
46
|
+
width: Fixed display width in pixels. Omit to size from the container.
|
|
47
|
+
height: Fixed display height in pixels. Omit to size from the container.
|
|
48
|
+
anchor: Where the picture sits within the area when it does not fill it
|
|
49
|
+
(with `'contain'`, `'scale-down'`, or `'none'`). Default `'center'`.
|
|
50
|
+
corner_radius: Rounded-corner radius in pixels. `0` (default) is square.
|
|
51
|
+
autoplay: Start playing an animated source as soon as it is shown.
|
|
52
|
+
Default `True`.
|
|
53
|
+
loop: Loop an animated source. Default `True`.
|
|
54
|
+
surface: Background surface token used to letterbox behind the picture.
|
|
55
|
+
parent: Explicit parent widget. If omitted, the current context-stack
|
|
56
|
+
container is used.
|
|
57
|
+
**kwargs: Layout placement options applied by the parent container
|
|
58
|
+
(e.g. `fill`, `expand`, `row`, `column`). See :doc:`/tasks/layout`.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
_internal_class = _InternalPicture
|
|
62
|
+
|
|
63
|
+
def __init__(
|
|
64
|
+
self,
|
|
65
|
+
image: "Image | str | Path | None" = None,
|
|
66
|
+
*,
|
|
67
|
+
fit: ImageFit = "contain",
|
|
68
|
+
width: int | None = None,
|
|
69
|
+
height: int | None = None,
|
|
70
|
+
anchor: Anchor = "center",
|
|
71
|
+
corner_radius: int = 0,
|
|
72
|
+
autoplay: bool = True,
|
|
73
|
+
loop: bool = True,
|
|
74
|
+
surface: SurfaceToken | str | None = None,
|
|
75
|
+
padding: Padding | None = None,
|
|
76
|
+
parent: Any = None,
|
|
77
|
+
**kwargs: Any,
|
|
78
|
+
) -> None:
|
|
79
|
+
self._parent = self._resolve_parent(parent)
|
|
80
|
+
layout_kw = self._split_layout_kwargs(kwargs)
|
|
81
|
+
tk_master = self._parent._child_master() if self._parent else None
|
|
82
|
+
|
|
83
|
+
internal_kwargs: dict[str, Any] = {
|
|
84
|
+
"fit": fit,
|
|
85
|
+
"anchor": anchor,
|
|
86
|
+
"corner_radius": corner_radius,
|
|
87
|
+
"autoplay": autoplay,
|
|
88
|
+
"loop": loop,
|
|
89
|
+
"img_width": width,
|
|
90
|
+
"img_height": height,
|
|
91
|
+
}
|
|
92
|
+
if surface is not None:
|
|
93
|
+
internal_kwargs["surface"] = surface
|
|
94
|
+
if padding is not None:
|
|
95
|
+
internal_kwargs["padding"] = padding
|
|
96
|
+
internal_kwargs.update(kwargs)
|
|
97
|
+
|
|
98
|
+
self._source = self._coerce_image(image)
|
|
99
|
+
self._internal = self._internal_class(
|
|
100
|
+
tk_master, source=self._source, **internal_kwargs
|
|
101
|
+
)
|
|
102
|
+
self._attach_to_parent(layout_kw)
|
|
103
|
+
|
|
104
|
+
@staticmethod
|
|
105
|
+
def _coerce_image(image: "Image | str | Path | None") -> Any:
|
|
106
|
+
"""Normalize an `image=` argument to an `Image` handle (or None)."""
|
|
107
|
+
if image is None:
|
|
108
|
+
return None
|
|
109
|
+
from bootstack.images import Image as _ImageHandle
|
|
110
|
+
|
|
111
|
+
if isinstance(image, _ImageHandle):
|
|
112
|
+
return image
|
|
113
|
+
if isinstance(image, (str, Path)):
|
|
114
|
+
return _ImageHandle.open(image)
|
|
115
|
+
try:
|
|
116
|
+
from PIL.Image import Image as _PILImage
|
|
117
|
+
|
|
118
|
+
if isinstance(image, _PILImage):
|
|
119
|
+
return _ImageHandle.from_pil(image)
|
|
120
|
+
except Exception:
|
|
121
|
+
pass
|
|
122
|
+
raise TypeError(
|
|
123
|
+
"Picture image must be an Image handle, a file path, or a PIL image; "
|
|
124
|
+
f"got {type(image).__name__}"
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
# ----- Properties -----
|
|
128
|
+
|
|
129
|
+
@property
|
|
130
|
+
def image(self) -> "Image | None":
|
|
131
|
+
"""The displayed image handle, or `None` when empty.
|
|
132
|
+
|
|
133
|
+
Assign an `Image`, a file path, or `None` to change what is shown.
|
|
134
|
+
"""
|
|
135
|
+
return self._source
|
|
136
|
+
|
|
137
|
+
@image.setter
|
|
138
|
+
def image(self, value: "Image | str | Path | None") -> None:
|
|
139
|
+
self._source = self._coerce_image(value)
|
|
140
|
+
self._internal.set_source(self._source)
|
|
141
|
+
|
|
142
|
+
@property
|
|
143
|
+
def fit(self) -> ImageFit:
|
|
144
|
+
"""The fit policy controlling how the picture is scaled into its area."""
|
|
145
|
+
return self._internal._fit
|
|
146
|
+
|
|
147
|
+
@fit.setter
|
|
148
|
+
def fit(self, value: ImageFit) -> None:
|
|
149
|
+
self._internal.set_fit(value)
|
|
150
|
+
|
|
151
|
+
@property
|
|
152
|
+
def is_playing(self) -> bool:
|
|
153
|
+
"""Whether an animated source is currently playing."""
|
|
154
|
+
return self._internal.is_playing
|
|
155
|
+
|
|
156
|
+
# ----- Playback -----
|
|
157
|
+
|
|
158
|
+
def play(self) -> None:
|
|
159
|
+
"""Start or resume playback of an animated source.
|
|
160
|
+
|
|
161
|
+
Has no effect on a still image or when already playing.
|
|
162
|
+
"""
|
|
163
|
+
self._internal.play()
|
|
164
|
+
|
|
165
|
+
def pause(self) -> None:
|
|
166
|
+
"""Pause playback on the current frame.
|
|
167
|
+
|
|
168
|
+
Resume with `play`; the frame stays on screen meanwhile.
|
|
169
|
+
"""
|
|
170
|
+
self._internal.pause()
|
|
171
|
+
|
|
172
|
+
def stop(self) -> None:
|
|
173
|
+
"""Stop playback and reset to the first frame."""
|
|
174
|
+
self._internal.stop()
|
|
175
|
+
|
|
176
|
+
# ----- Events -----
|
|
177
|
+
|
|
178
|
+
@overload
|
|
179
|
+
def on_load(self) -> Stream: ...
|
|
180
|
+
@overload
|
|
181
|
+
def on_load(self, handler: Callable[[ImageLoadEvent], Any]) -> Subscription: ...
|
|
182
|
+
def on_load(
|
|
183
|
+
self, handler: Callable[[ImageLoadEvent], Any] | None = None
|
|
184
|
+
) -> Stream | Subscription:
|
|
185
|
+
"""Register a callback fired when an image is decoded and displayed.
|
|
186
|
+
|
|
187
|
+
The handler receives an :class:`~bootstack.events.ImageLoadEvent` with
|
|
188
|
+
the picture's natural `width`, `height`, and frame count.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
handler: Called with the load event. Omit to get a composable
|
|
192
|
+
:class:`~bootstack.streams.Stream`.
|
|
193
|
+
|
|
194
|
+
Returns:
|
|
195
|
+
A :class:`~bootstack.events.Subscription` when a handler is given,
|
|
196
|
+
otherwise a :class:`~bootstack.streams.Stream`.
|
|
197
|
+
"""
|
|
198
|
+
return self.on("load", handler)
|
|
199
|
+
|
|
200
|
+
@overload
|
|
201
|
+
def on_error(self) -> Stream: ...
|
|
202
|
+
@overload
|
|
203
|
+
def on_error(self, handler: Callable[[ImageErrorEvent], Any]) -> Subscription: ...
|
|
204
|
+
def on_error(
|
|
205
|
+
self, handler: Callable[[ImageErrorEvent], Any] | None = None
|
|
206
|
+
) -> Stream | Subscription:
|
|
207
|
+
"""Register a callback fired when an image fails to load or decode.
|
|
208
|
+
|
|
209
|
+
The handler receives an :class:`~bootstack.events.ImageErrorEvent`
|
|
210
|
+
carrying a human-readable `message`.
|
|
211
|
+
|
|
212
|
+
Args:
|
|
213
|
+
handler: Called with the error event. Omit to get a composable
|
|
214
|
+
:class:`~bootstack.streams.Stream`.
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
A :class:`~bootstack.events.Subscription` when a handler is given,
|
|
218
|
+
otherwise a :class:`~bootstack.streams.Stream`.
|
|
219
|
+
"""
|
|
220
|
+
return self.on("error", handler)
|
|
221
|
+
|
|
222
|
+
@overload
|
|
223
|
+
def on_click(self) -> Stream: ...
|
|
224
|
+
@overload
|
|
225
|
+
def on_click(self, handler: Callable[[Event], Any]) -> Subscription: ...
|
|
226
|
+
def on_click(
|
|
227
|
+
self, handler: Callable[[Event], Any] | None = None
|
|
228
|
+
) -> Stream | Subscription:
|
|
229
|
+
"""Register a callback for clicks on the picture.
|
|
230
|
+
|
|
231
|
+
The handler receives a curated :class:`~bootstack.events.Event`.
|
|
232
|
+
|
|
233
|
+
Args:
|
|
234
|
+
handler: Called with the click `Event`. Omit to get a composable
|
|
235
|
+
:class:`~bootstack.streams.Stream`.
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
A :class:`~bootstack.events.Subscription` when a handler is given,
|
|
239
|
+
otherwise a :class:`~bootstack.streams.Stream`.
|
|
240
|
+
"""
|
|
241
|
+
return self.on("click", handler)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
register_widget_events(
|
|
245
|
+
Picture,
|
|
246
|
+
{
|
|
247
|
+
"load": "<<PictureLoad>>",
|
|
248
|
+
"error": "<<PictureError>>",
|
|
249
|
+
"click": "<<PictureClick>>",
|
|
250
|
+
},
|
|
251
|
+
)
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Literal, TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from bootstack.widgets._impl.primitives.progressbar import Progressbar as _InternalProgressbar
|
|
6
|
+
from bootstack.widgets._core.base import PublicWidgetBase
|
|
7
|
+
from bootstack.widgets._core.events import register_widget_events
|
|
8
|
+
from bootstack.widgets.types import AccentToken, Orient
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from bootstack.signals import Signal
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ProgressBar(PublicWidgetBase):
|
|
15
|
+
"""A progress indicator bar.
|
|
16
|
+
|
|
17
|
+
Displays determinate progress (a fixed percentage fill) or indeterminate
|
|
18
|
+
progress (a looping animation) for operations whose duration is unknown.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
value: Initial progress value. Defaults to `0`.
|
|
22
|
+
max_value: Value that represents 100% progress. Defaults to `100`.
|
|
23
|
+
mode: `'determinate'` shows a fixed fill proportional to
|
|
24
|
+
`value / max_value`; `'indeterminate'` runs a looping animation.
|
|
25
|
+
Default `'determinate'`.
|
|
26
|
+
orient: Track orientation. Default `'horizontal'`.
|
|
27
|
+
signal: Reactive `Signal` two-way bound to `value`.
|
|
28
|
+
accent: Color intent token for the bar. Defaults to the theme's default
|
|
29
|
+
color.
|
|
30
|
+
variant: Style variant. `'thin'` reduces the bar height.
|
|
31
|
+
parent: Override the context-stack parent.
|
|
32
|
+
**kwargs: Layout placement options applied by the parent container —
|
|
33
|
+
`fill`, `expand`, `anchor`, `margin`, `row`, `column`, `sticky`.
|
|
34
|
+
See :doc:`/tasks/layout`.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
value: float = 0,
|
|
40
|
+
*,
|
|
41
|
+
max_value: float = 100,
|
|
42
|
+
mode: Literal["determinate", "indeterminate"] = "determinate",
|
|
43
|
+
orient: Orient = "horizontal",
|
|
44
|
+
signal: Signal | None = None,
|
|
45
|
+
accent: AccentToken | str | None = None,
|
|
46
|
+
variant: Literal["thin"] | None = None,
|
|
47
|
+
parent: Any = None,
|
|
48
|
+
**kwargs: Any,
|
|
49
|
+
) -> None:
|
|
50
|
+
self._parent = self._resolve_parent(parent)
|
|
51
|
+
layout_kw = self._split_layout_kwargs(kwargs)
|
|
52
|
+
|
|
53
|
+
tk_master = self._parent._child_master() if self._parent else None
|
|
54
|
+
|
|
55
|
+
internal_kwargs: dict[str, Any] = {
|
|
56
|
+
"value": value,
|
|
57
|
+
"maximum": max_value,
|
|
58
|
+
"mode": mode,
|
|
59
|
+
"orient": orient,
|
|
60
|
+
}
|
|
61
|
+
if signal is not None:
|
|
62
|
+
internal_kwargs["signal"] = signal
|
|
63
|
+
if accent is not None:
|
|
64
|
+
internal_kwargs["accent"] = accent
|
|
65
|
+
if variant is not None:
|
|
66
|
+
internal_kwargs["variant"] = variant
|
|
67
|
+
|
|
68
|
+
self._internal = _InternalProgressbar(tk_master, **internal_kwargs)
|
|
69
|
+
self._attach_to_parent(layout_kw)
|
|
70
|
+
|
|
71
|
+
# ----- Properties -----
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def value(self) -> float:
|
|
75
|
+
"""The current progress value."""
|
|
76
|
+
return self._internal.get()
|
|
77
|
+
|
|
78
|
+
@value.setter
|
|
79
|
+
def value(self, v: float) -> None:
|
|
80
|
+
self._internal.set(v)
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def max_value(self) -> float:
|
|
84
|
+
"""The value that represents 100% progress."""
|
|
85
|
+
return float(self._internal.cget("maximum"))
|
|
86
|
+
|
|
87
|
+
@max_value.setter
|
|
88
|
+
def max_value(self, v: float) -> None:
|
|
89
|
+
self._internal.configure(maximum=float(v))
|
|
90
|
+
|
|
91
|
+
# ----- Methods -----
|
|
92
|
+
|
|
93
|
+
def start(self, interval: int = 50) -> None:
|
|
94
|
+
"""Start indeterminate animation. `interval` is the step delay in ms."""
|
|
95
|
+
self._internal.start(interval)
|
|
96
|
+
|
|
97
|
+
def stop(self) -> None:
|
|
98
|
+
"""Stop indeterminate animation."""
|
|
99
|
+
self._internal.stop()
|
|
100
|
+
|
|
101
|
+
def step(self, amount: float = 1.0) -> None:
|
|
102
|
+
"""Advance the value by `amount`."""
|
|
103
|
+
self._internal.step(amount)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
register_widget_events(ProgressBar, {})
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import overload, Any, Callable, Literal, TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from bootstack.widgets._impl.primitives.radiobutton import RadioButton as _InternalRadioButton
|
|
6
|
+
from bootstack.widgets._impl.primitives.radiotoggle import RadioToggle as _InternalRadioToggle
|
|
7
|
+
from bootstack.widgets._core.base import PublicWidgetBase
|
|
8
|
+
from bootstack.widgets._core.events import register_widget_events
|
|
9
|
+
from bootstack.events import Subscription
|
|
10
|
+
from bootstack.streams import Stream
|
|
11
|
+
from bootstack.widgets.types import AccentToken, Event, WidgetDensity, IconPosition
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from bootstack.signals import Signal
|
|
15
|
+
|
|
16
|
+
_RADIO_EVENTS: dict[str, str] = {
|
|
17
|
+
"change": "<<Change>>",
|
|
18
|
+
"select": "<<Select>>",
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class _RadioBase(PublicWidgetBase):
|
|
23
|
+
"""Shared base for Radio and RadioToggleButton."""
|
|
24
|
+
|
|
25
|
+
_internal_class: type
|
|
26
|
+
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
label: str = "",
|
|
30
|
+
value: Any = None,
|
|
31
|
+
*,
|
|
32
|
+
signal: "Signal | None" = None,
|
|
33
|
+
text_signal: "Signal | None" = None,
|
|
34
|
+
on_change: Callable[[], Any] | None = None,
|
|
35
|
+
icon: str | None = None,
|
|
36
|
+
selected_icon: str | None = None,
|
|
37
|
+
unselected_icon: str | None = None,
|
|
38
|
+
icon_only: bool = False,
|
|
39
|
+
show_indicator: bool = True,
|
|
40
|
+
icon_position: IconPosition | None = None,
|
|
41
|
+
disabled: bool = False,
|
|
42
|
+
accent: AccentToken | str | None = None,
|
|
43
|
+
localize: bool | Literal['auto'] | None = None,
|
|
44
|
+
_internal_options: dict[str, Any] | None = None,
|
|
45
|
+
parent: Any = None,
|
|
46
|
+
**kwargs: Any,
|
|
47
|
+
) -> None:
|
|
48
|
+
self._parent = self._resolve_parent(parent)
|
|
49
|
+
layout_kw = self._split_layout_kwargs(kwargs)
|
|
50
|
+
if kwargs:
|
|
51
|
+
raise TypeError(
|
|
52
|
+
f"{type(self).__name__}() got unexpected keyword argument(s): "
|
|
53
|
+
f"{', '.join(sorted(kwargs))}"
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
tk_master = self._parent._child_master() if self._parent else None
|
|
57
|
+
|
|
58
|
+
_ctor_callback = on_change
|
|
59
|
+
|
|
60
|
+
if icon is not None and not label:
|
|
61
|
+
icon_only = True
|
|
62
|
+
|
|
63
|
+
internal_kwargs: dict[str, Any] = {}
|
|
64
|
+
if label:
|
|
65
|
+
internal_kwargs["text"] = label
|
|
66
|
+
if value is not None:
|
|
67
|
+
internal_kwargs["value"] = value
|
|
68
|
+
if signal is not None:
|
|
69
|
+
internal_kwargs["signal"] = signal
|
|
70
|
+
if text_signal is not None:
|
|
71
|
+
internal_kwargs["textsignal"] = text_signal
|
|
72
|
+
if icon is not None:
|
|
73
|
+
internal_kwargs["icon"] = icon
|
|
74
|
+
if selected_icon is not None:
|
|
75
|
+
internal_kwargs["on_icon"] = selected_icon
|
|
76
|
+
if unselected_icon is not None:
|
|
77
|
+
internal_kwargs["off_icon"] = unselected_icon
|
|
78
|
+
if icon_only:
|
|
79
|
+
internal_kwargs["icon_only"] = True
|
|
80
|
+
if not show_indicator:
|
|
81
|
+
internal_kwargs["show_indicator"] = False
|
|
82
|
+
if icon_position is not None:
|
|
83
|
+
internal_kwargs["compound"] = icon_position
|
|
84
|
+
if disabled:
|
|
85
|
+
internal_kwargs["state"] = "disabled"
|
|
86
|
+
if accent is not None:
|
|
87
|
+
internal_kwargs["accent"] = accent
|
|
88
|
+
if localize is not None:
|
|
89
|
+
internal_kwargs["localize"] = localize
|
|
90
|
+
# Per-subclass styling options (e.g. density — RadioToggleButton only).
|
|
91
|
+
if _internal_options:
|
|
92
|
+
internal_kwargs.update(_internal_options)
|
|
93
|
+
|
|
94
|
+
self._internal = self._internal_class(tk_master, **internal_kwargs)
|
|
95
|
+
|
|
96
|
+
def _command():
|
|
97
|
+
self._internal.event_generate("<<Change>>")
|
|
98
|
+
self._internal.event_generate("<<Select>>")
|
|
99
|
+
if _ctor_callback is not None:
|
|
100
|
+
_ctor_callback()
|
|
101
|
+
|
|
102
|
+
self._internal.configure(command=_command)
|
|
103
|
+
self._attach_to_parent(layout_kw)
|
|
104
|
+
|
|
105
|
+
# ----- Properties -----
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def selected(self) -> bool:
|
|
109
|
+
"""True if this radio is the currently selected option in its group."""
|
|
110
|
+
return self._internal.get() == self._internal.cget("value")
|
|
111
|
+
|
|
112
|
+
@property
|
|
113
|
+
def disabled(self) -> bool:
|
|
114
|
+
"""Whether the control is disabled (non-interactive and greyed out)."""
|
|
115
|
+
return str(self._internal.cget("state")) == "disabled"
|
|
116
|
+
|
|
117
|
+
@disabled.setter
|
|
118
|
+
def disabled(self, v: bool) -> None:
|
|
119
|
+
self._internal.configure(state="disabled" if v else "normal")
|
|
120
|
+
|
|
121
|
+
# ----- Methods -----
|
|
122
|
+
|
|
123
|
+
def select(self) -> None:
|
|
124
|
+
"""Programmatically select this radio button."""
|
|
125
|
+
self._internal.invoke()
|
|
126
|
+
|
|
127
|
+
# ----- Event shorthands -----
|
|
128
|
+
|
|
129
|
+
@overload
|
|
130
|
+
def on_change(self) -> Stream: ...
|
|
131
|
+
@overload
|
|
132
|
+
def on_change(self, handler: Callable[[Event], Any]) -> Subscription: ...
|
|
133
|
+
def on_change(self, handler: Callable[[Event], Any] | None = None) -> Stream | Subscription:
|
|
134
|
+
"""Register a callback fired when this radio is selected.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
handler: Called with the :class:`~bootstack.events.Event`. Omit to
|
|
138
|
+
get a composable :class:`~bootstack.streams.Stream` instead.
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
A cancellable :class:`~bootstack.events.Subscription` when a
|
|
142
|
+
handler is given, otherwise a :class:`~bootstack.streams.Stream`.
|
|
143
|
+
"""
|
|
144
|
+
return self.on("change", handler)
|
|
145
|
+
|
|
146
|
+
@overload
|
|
147
|
+
def on_select(self) -> Stream: ...
|
|
148
|
+
@overload
|
|
149
|
+
def on_select(self, handler: Callable[[Event], Any]) -> Subscription: ...
|
|
150
|
+
def on_select(self, handler: Callable[[Event], Any] | None = None) -> Stream | Subscription:
|
|
151
|
+
"""Register a callback fired when this radio is selected.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
handler: Called with the :class:`~bootstack.events.Event`. Omit to
|
|
155
|
+
get a composable :class:`~bootstack.streams.Stream` instead.
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
A cancellable :class:`~bootstack.events.Subscription` when a
|
|
159
|
+
handler is given, otherwise a :class:`~bootstack.streams.Stream`.
|
|
160
|
+
"""
|
|
161
|
+
return self.on("select", handler)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class Radio(_RadioBase):
|
|
165
|
+
"""A single radio button — one selectable option in a mutually-exclusive group.
|
|
166
|
+
|
|
167
|
+
Radio buttons in a group share a `signal`; clicking one sets the signal
|
|
168
|
+
to the radio's `value`.
|
|
169
|
+
|
|
170
|
+
Usage::
|
|
171
|
+
|
|
172
|
+
sig = bs.Signal("a")
|
|
173
|
+
bs.Radio("Option A", "a", signal=sig)
|
|
174
|
+
bs.Radio("Option B", "b", signal=sig)
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
label: Text displayed beside the radio indicator.
|
|
178
|
+
value: The value written to `signal` when this radio is selected.
|
|
179
|
+
signal: Shared `Signal` that holds the group's current value.
|
|
180
|
+
text_signal: Reactive `Signal` linked to the label text.
|
|
181
|
+
on_change: Callback fired when this radio is selected.
|
|
182
|
+
icon: Icon shown for all states.
|
|
183
|
+
selected_icon: Icon shown when this radio is selected.
|
|
184
|
+
unselected_icon: Icon shown when this radio is not selected.
|
|
185
|
+
icon_only: If True, removes extra padding reserved for the label.
|
|
186
|
+
Inferred automatically when `icon=` is set and no label is provided.
|
|
187
|
+
show_indicator: If False, hides the circular indicator.
|
|
188
|
+
icon_position: Position of the icon relative to the text.
|
|
189
|
+
disabled: If True, widget is non-interactive.
|
|
190
|
+
accent: Accent token applied to the indicator.
|
|
191
|
+
localize: Whether the label is translated through the catalog — `True`,
|
|
192
|
+
`False`, or `'auto'`. Defaults to the app's `localize_mode`. Set
|
|
193
|
+
`False` to keep a proper noun untranslated.
|
|
194
|
+
parent: Override the context-stack parent.
|
|
195
|
+
**kwargs: Layout placement options applied by the parent container —
|
|
196
|
+
`fill`, `expand`, `anchor`, `margin`, `row`, `column`, `sticky`.
|
|
197
|
+
See :doc:`/tasks/layout`.
|
|
198
|
+
"""
|
|
199
|
+
_internal_class = _InternalRadioButton
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
class RadioToggleButton(_RadioBase):
|
|
203
|
+
"""A radio button that renders as a toggle button — toolbar/segmented-control style.
|
|
204
|
+
|
|
205
|
+
Behaves identically to `Radio` but renders as a depressed button when
|
|
206
|
+
selected rather than showing the circular indicator.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
label: Text displayed on the button.
|
|
210
|
+
value: The value written to `signal` when this button is selected.
|
|
211
|
+
signal: Shared `Signal` that holds the group's current value.
|
|
212
|
+
text_signal: Reactive `Signal` linked to the label text.
|
|
213
|
+
on_change: Callback fired when this button is selected.
|
|
214
|
+
icon: Icon shown for all states.
|
|
215
|
+
selected_icon: Icon shown when selected.
|
|
216
|
+
unselected_icon: Icon shown when not selected.
|
|
217
|
+
icon_only: If True, removes extra label padding. Inferred
|
|
218
|
+
automatically when `icon=` is set and no label is provided.
|
|
219
|
+
disabled: If True, widget is non-interactive.
|
|
220
|
+
accent: Accent token applied to the button when selected.
|
|
221
|
+
density: Widget density.
|
|
222
|
+
localize: Whether the label is translated through the catalog — `True`,
|
|
223
|
+
`False`, or `'auto'`. Defaults to the app's `localize_mode`. Set
|
|
224
|
+
`False` to keep a proper noun untranslated.
|
|
225
|
+
parent: Override the context-stack parent.
|
|
226
|
+
**kwargs: Layout placement options applied by the parent container —
|
|
227
|
+
`fill`, `expand`, `anchor`, `margin`, `row`, `column`, `sticky`.
|
|
228
|
+
See :doc:`/tasks/layout`.
|
|
229
|
+
"""
|
|
230
|
+
_internal_class = _InternalRadioToggle
|
|
231
|
+
|
|
232
|
+
def __init__(
|
|
233
|
+
self,
|
|
234
|
+
label: str = "",
|
|
235
|
+
value: Any = None,
|
|
236
|
+
*,
|
|
237
|
+
signal: "Signal | None" = None,
|
|
238
|
+
text_signal: "Signal | None" = None,
|
|
239
|
+
on_change: Callable[[], Any] | None = None,
|
|
240
|
+
icon: str | None = None,
|
|
241
|
+
selected_icon: str | None = None,
|
|
242
|
+
unselected_icon: str | None = None,
|
|
243
|
+
icon_only: bool = False,
|
|
244
|
+
disabled: bool = False,
|
|
245
|
+
accent: AccentToken | str | None = None,
|
|
246
|
+
density: WidgetDensity | None = None,
|
|
247
|
+
localize: bool | Literal['auto'] | None = None,
|
|
248
|
+
parent: Any = None,
|
|
249
|
+
**kwargs: Any,
|
|
250
|
+
) -> None:
|
|
251
|
+
super().__init__(
|
|
252
|
+
label,
|
|
253
|
+
value,
|
|
254
|
+
signal=signal,
|
|
255
|
+
text_signal=text_signal,
|
|
256
|
+
on_change=on_change,
|
|
257
|
+
icon=icon,
|
|
258
|
+
selected_icon=selected_icon,
|
|
259
|
+
unselected_icon=unselected_icon,
|
|
260
|
+
icon_only=icon_only,
|
|
261
|
+
disabled=disabled,
|
|
262
|
+
accent=accent,
|
|
263
|
+
localize=localize,
|
|
264
|
+
_internal_options=({"density": density} if density is not None else None),
|
|
265
|
+
parent=parent,
|
|
266
|
+
**kwargs,
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
register_widget_events(Radio, _RADIO_EVENTS)
|
|
271
|
+
register_widget_events(RadioToggleButton, _RADIO_EVENTS)
|