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,231 @@
|
|
|
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.togglegroup import ToggleGroup as _InternalToggleGroup
|
|
7
|
+
from bootstack.widgets._core.base import PublicWidgetBase
|
|
8
|
+
from bootstack.widgets._core.selection_group import SelectionGroupMixin
|
|
9
|
+
from bootstack.widgets._core.options import normalize_options, option_display
|
|
10
|
+
from bootstack.widgets._core.events import register_widget_events
|
|
11
|
+
from bootstack.events import Subscription, ChangeEvent
|
|
12
|
+
from bootstack.streams import Stream
|
|
13
|
+
from bootstack.widgets.types import AccentToken, Event, Option, Orient, ButtonVariant
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from bootstack.signals import Signal
|
|
17
|
+
|
|
18
|
+
_TOGGLEGROUP_EVENTS: dict[str, str] = {
|
|
19
|
+
"change": "<<Change>>",
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ToggleGroup(SelectionGroupMixin, PublicWidgetBase):
|
|
24
|
+
"""A group of toggle buttons — single-select or multi-select.
|
|
25
|
+
|
|
26
|
+
Options are supplied at construction via `options=` and can be added
|
|
27
|
+
at runtime with `add()`. In `'single'` mode exactly one button is
|
|
28
|
+
active at a time; in `'multi'` mode any combination can be active.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
options: Choices for the group. Each item is a plain string (text and
|
|
32
|
+
value are the same), a `(text, value)` tuple, or a
|
|
33
|
+
`{'text': ..., 'value': ...}` dict — e.g. `["Grid", "List"]` or
|
|
34
|
+
`[("Grid view", "grid"), ("List view", "list")]`. A dict option may
|
|
35
|
+
also carry `'icon'` (a glyph rendered beside the label) and
|
|
36
|
+
`'disabled'` (when `True` the option is dimmed and cannot be
|
|
37
|
+
selected); any other keys ride along as carried data on `selection`.
|
|
38
|
+
An option with an `'icon'` and no text — e.g.
|
|
39
|
+
`{'icon': 'grid', 'value': 'grid'}` — renders as an icon-only button.
|
|
40
|
+
mode: Selection behavior. `'single'` (default) enforces mutual
|
|
41
|
+
exclusivity like a radio group; `'multi'` allows any number
|
|
42
|
+
of buttons to be active simultaneously.
|
|
43
|
+
signal: Reactive `Signal` holding the selected value(s). In
|
|
44
|
+
`'single'` mode the signal value is a `str`; in `'multi'`
|
|
45
|
+
mode it is a `set[str]`. When provided, `value=` is ignored
|
|
46
|
+
— seed the Signal directly.
|
|
47
|
+
value: Initial selection. A `str` for `'single'` mode or a
|
|
48
|
+
`set[str]` for `'multi'` mode. Ignored when `signal=` is
|
|
49
|
+
passed.
|
|
50
|
+
orient: Layout direction. Default `'horizontal'`.
|
|
51
|
+
accent: Accent token applied to all buttons.
|
|
52
|
+
variant: Button style variant. Default `'solid'`.
|
|
53
|
+
disabled: If `True`, all buttons are non-interactive and dimmed.
|
|
54
|
+
Defaults to `False`.
|
|
55
|
+
localize: Whether option labels are translated through the catalog —
|
|
56
|
+
`True`, `False`, or `'auto'` (translate when a translation is
|
|
57
|
+
registered, otherwise show the literal). Defaults to the app's
|
|
58
|
+
`localize_mode`. Set `False` to keep proper nouns untranslated;
|
|
59
|
+
override a single option with its `localize` key.
|
|
60
|
+
parent: Explicit parent widget. If omitted, the current
|
|
61
|
+
context-stack container is used.
|
|
62
|
+
**kwargs: Layout placement options applied by the parent container —
|
|
63
|
+
`fill`, `expand`, `anchor`, `margin`, `row`, `column`, `sticky`.
|
|
64
|
+
See :doc:`/tasks/layout`.
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
def __init__(
|
|
68
|
+
self,
|
|
69
|
+
options: list[Option] | None = None,
|
|
70
|
+
*,
|
|
71
|
+
mode: Literal["single", "multi"] = "single",
|
|
72
|
+
signal: "Signal | None" = None,
|
|
73
|
+
value: Any = None,
|
|
74
|
+
orient: Orient = "horizontal",
|
|
75
|
+
accent: AccentToken | str | None = None,
|
|
76
|
+
variant: ButtonVariant = "default",
|
|
77
|
+
disabled: bool = False,
|
|
78
|
+
localize: bool | Literal['auto'] | None = None,
|
|
79
|
+
parent: Any = None,
|
|
80
|
+
**kwargs: Any,
|
|
81
|
+
) -> None:
|
|
82
|
+
self._localize = localize
|
|
83
|
+
self._parent = self._resolve_parent(parent)
|
|
84
|
+
layout_kw = self._split_layout_kwargs(kwargs)
|
|
85
|
+
|
|
86
|
+
tk_master = self._parent._child_master() if self._parent else None
|
|
87
|
+
|
|
88
|
+
internal_kwargs: dict[str, Any] = {
|
|
89
|
+
"mode": mode,
|
|
90
|
+
"orient": orient,
|
|
91
|
+
}
|
|
92
|
+
if signal is not None:
|
|
93
|
+
internal_kwargs["signal"] = signal
|
|
94
|
+
if value is not None:
|
|
95
|
+
internal_kwargs["value"] = value
|
|
96
|
+
if accent is not None:
|
|
97
|
+
internal_kwargs["accent"] = accent
|
|
98
|
+
if variant is not None:
|
|
99
|
+
internal_kwargs["variant"] = variant
|
|
100
|
+
if disabled:
|
|
101
|
+
internal_kwargs["state"] = "disabled"
|
|
102
|
+
|
|
103
|
+
self._internal = _InternalToggleGroup(tk_master, **internal_kwargs)
|
|
104
|
+
|
|
105
|
+
# Populate options passed at construction; keep the records (the data
|
|
106
|
+
# bag) so `selection` can return each option's full dict.
|
|
107
|
+
records = normalize_options(options)
|
|
108
|
+
self._set_option_records(records)
|
|
109
|
+
for record in records:
|
|
110
|
+
icon, disabled = option_display(record)
|
|
111
|
+
add_kwargs, _ = self._option_render(icon, disabled, record.text)
|
|
112
|
+
item_localize = self._resolve_localize(record)
|
|
113
|
+
if item_localize is not None:
|
|
114
|
+
add_kwargs["localize"] = item_localize
|
|
115
|
+
self._internal.add(text=record.text, value=record.value, **add_kwargs)
|
|
116
|
+
|
|
117
|
+
# Trace signal → <<Change>> for consistent Subscription-based on_change().
|
|
118
|
+
self._prev_value = self._internal.signal()
|
|
119
|
+
|
|
120
|
+
def _on_value_change(new_value: Any) -> None:
|
|
121
|
+
try:
|
|
122
|
+
prev, self._prev_value = self._prev_value, new_value
|
|
123
|
+
self._internal.event_generate(
|
|
124
|
+
"<<Change>>", data=ChangeEvent(value=new_value, prev_value=prev)
|
|
125
|
+
)
|
|
126
|
+
except tkinter.TclError:
|
|
127
|
+
pass
|
|
128
|
+
|
|
129
|
+
self._trace_id = self._internal.signal.subscribe(_on_value_change)
|
|
130
|
+
self._attach_to_parent(layout_kw)
|
|
131
|
+
|
|
132
|
+
# ----- Properties -----
|
|
133
|
+
|
|
134
|
+
@property
|
|
135
|
+
def value(self) -> Any:
|
|
136
|
+
"""The current selection. A `str` in single mode, `set[str]` in multi mode."""
|
|
137
|
+
return self._internal.get()
|
|
138
|
+
|
|
139
|
+
@value.setter
|
|
140
|
+
def value(self, v: Any) -> None:
|
|
141
|
+
self._internal.set(v)
|
|
142
|
+
|
|
143
|
+
@property
|
|
144
|
+
def text(self) -> str | set[str] | None:
|
|
145
|
+
"""The label(s) of the selected option(s) — the display complement of `value`.
|
|
146
|
+
|
|
147
|
+
In single mode, the selected option's label (or `None` if nothing is
|
|
148
|
+
selected). In multi mode, a `set` of the selected options' labels.
|
|
149
|
+
Read-only.
|
|
150
|
+
"""
|
|
151
|
+
value = self._internal.get()
|
|
152
|
+
if isinstance(value, set):
|
|
153
|
+
return {t for v in value if (t := self._internal.text_for(v)) is not None}
|
|
154
|
+
return self._internal.text_for(value)
|
|
155
|
+
|
|
156
|
+
@property
|
|
157
|
+
def selection(self) -> dict | list[dict] | None:
|
|
158
|
+
"""The selected option(s) as full record dicts — the data bag.
|
|
159
|
+
|
|
160
|
+
In single mode, the selected option's `{text, value, ...extras}` dict
|
|
161
|
+
(or `None`). In multi mode, a `list` of those dicts. Read-only.
|
|
162
|
+
"""
|
|
163
|
+
value = self._internal.get()
|
|
164
|
+
if isinstance(value, set):
|
|
165
|
+
return [d for v in value if (d := self._record_dict_for(v)) is not None]
|
|
166
|
+
return self._record_dict_for(value)
|
|
167
|
+
|
|
168
|
+
@property
|
|
169
|
+
def disabled(self) -> bool:
|
|
170
|
+
"""Whether all buttons in the group are non-interactive."""
|
|
171
|
+
return self._internal._state == "disabled"
|
|
172
|
+
|
|
173
|
+
@disabled.setter
|
|
174
|
+
def disabled(self, v: bool) -> None:
|
|
175
|
+
self._internal.configure(state="disabled" if v else "normal")
|
|
176
|
+
|
|
177
|
+
# ----- Methods -----
|
|
178
|
+
|
|
179
|
+
def add(
|
|
180
|
+
self,
|
|
181
|
+
label: str,
|
|
182
|
+
value: Any | None = None,
|
|
183
|
+
*,
|
|
184
|
+
icon: Any = None,
|
|
185
|
+
disabled: bool = False,
|
|
186
|
+
localize: bool | Literal['auto'] | None = None,
|
|
187
|
+
**kwargs: Any,
|
|
188
|
+
) -> None:
|
|
189
|
+
"""Add a toggle button at runtime.
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
label: Display text. Pass `''` with an `icon` for an icon-only button.
|
|
193
|
+
value: Value this button represents. Defaults to `label`.
|
|
194
|
+
icon: Optional icon spec rendered beside the label (alone when
|
|
195
|
+
`label` is blank).
|
|
196
|
+
disabled: If `True`, the option is dimmed and cannot be selected.
|
|
197
|
+
localize: Translation mode for this option's label, overriding the
|
|
198
|
+
group's `localize=`. Defaults to the group setting.
|
|
199
|
+
"""
|
|
200
|
+
resolved = value if value is not None else label
|
|
201
|
+
add_kwargs, extras = self._option_render(icon, disabled, label)
|
|
202
|
+
item_localize = self._resolve_localize(override=localize)
|
|
203
|
+
if item_localize is not None:
|
|
204
|
+
add_kwargs["localize"] = item_localize
|
|
205
|
+
if localize is not None:
|
|
206
|
+
extras["localize"] = localize
|
|
207
|
+
self._internal.add(text=label, value=resolved, **add_kwargs, **kwargs)
|
|
208
|
+
self._add_option_record(label, resolved, extras)
|
|
209
|
+
|
|
210
|
+
# ----- Event shorthands -----
|
|
211
|
+
|
|
212
|
+
@overload
|
|
213
|
+
def on_change(self) -> Stream: ...
|
|
214
|
+
@overload
|
|
215
|
+
def on_change(self, handler: Callable[[ChangeEvent], Any]) -> Subscription: ...
|
|
216
|
+
def on_change(self, handler: Callable[[ChangeEvent], Any] | None = None) -> Stream | Subscription:
|
|
217
|
+
"""Register a callback fired whenever the selection changes.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
handler: Called with a :class:`~bootstack.events.ChangeEvent`; the
|
|
221
|
+
current value is also available via `group.value`. Omit to get
|
|
222
|
+
a composable :class:`~bootstack.streams.Stream` instead.
|
|
223
|
+
|
|
224
|
+
Returns:
|
|
225
|
+
A cancellable :class:`~bootstack.events.Subscription` when a
|
|
226
|
+
handler is given, otherwise a :class:`~bootstack.streams.Stream`.
|
|
227
|
+
"""
|
|
228
|
+
return self.on("change", handler)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
register_widget_events(ToggleGroup, _TOGGLEGROUP_EVENTS)
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any, Callable
|
|
4
|
+
|
|
5
|
+
from bootstack.errors import BootstackError
|
|
6
|
+
from bootstack.widgets._impl.composites.toolbar import Toolbar as _InternalToolbar
|
|
7
|
+
from bootstack.widgets._core.base import PublicWidgetBase
|
|
8
|
+
from bootstack.widgets.types import AccentToken, WidgetDensity, SurfaceToken, ButtonVariant
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from bootstack.widgets._impl.composites.menu.model import MenuGroup
|
|
12
|
+
from bootstack.widgets.button import Button
|
|
13
|
+
from bootstack.widgets.label import Label
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Toolbar(PublicWidgetBase):
|
|
17
|
+
"""A horizontal strip of buttons, labels, and other widgets.
|
|
18
|
+
|
|
19
|
+
The toolbar holds an app's commands — buttons, a search box, a theme toggle.
|
|
20
|
+
Items are added left-to-right via `add_button()`, `add_label()`,
|
|
21
|
+
`add_divider()`, `add_spacer()`, and `add_widget()`. Call `add_spacer()` to
|
|
22
|
+
push subsequent items to the right side.
|
|
23
|
+
|
|
24
|
+
Use this widget directly to build a standalone toolbar or a custom titlebar.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
button_variant: Default variant applied to every button added via
|
|
28
|
+
`add_button()`. Default `'ghost'`.
|
|
29
|
+
density: Size of toolbar items. Default `'default'`.
|
|
30
|
+
surface: Background surface token. Defaults to `None` — the toolbar
|
|
31
|
+
inherits the base content surface (it blends into its parent). Pass
|
|
32
|
+
`'chrome'` (or another token) to tint the bar explicitly.
|
|
33
|
+
padding: Inner padding in pixels — an int (all sides) or a
|
|
34
|
+
`(horizontal, vertical)` tuple. Defaults to `3` for default density
|
|
35
|
+
and `(3, 1)` for compact.
|
|
36
|
+
show_border: If `True`, draws a border around the toolbar frame.
|
|
37
|
+
Default `False`.
|
|
38
|
+
show_window_controls: If `True`, adds minimize, maximize, and close
|
|
39
|
+
buttons to the right side. Default `False`.
|
|
40
|
+
draggable: If `True`, clicking and dragging the toolbar repositions
|
|
41
|
+
the window. Automatically enabled when `show_window_controls=True`.
|
|
42
|
+
Default `False`.
|
|
43
|
+
parent: Override the context-stack parent.
|
|
44
|
+
**kwargs: Layout placement options applied by the parent container —
|
|
45
|
+
`fill`, `expand`, `anchor`, `margin`, `row`, `column`, `sticky`.
|
|
46
|
+
See :doc:`/tasks/layout`.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def __init__(
|
|
50
|
+
self,
|
|
51
|
+
*,
|
|
52
|
+
button_variant: ButtonVariant = "ghost",
|
|
53
|
+
density: WidgetDensity = "default",
|
|
54
|
+
surface: SurfaceToken | str | None = None,
|
|
55
|
+
padding: int | tuple[int, int] | None = None,
|
|
56
|
+
show_border: bool = False,
|
|
57
|
+
show_window_controls: bool = False,
|
|
58
|
+
draggable: bool = False,
|
|
59
|
+
parent: Any = None,
|
|
60
|
+
_toolbar: Any = None,
|
|
61
|
+
_host: Any = None,
|
|
62
|
+
**kwargs: Any,
|
|
63
|
+
) -> None:
|
|
64
|
+
# The chrome host (App/Window/AppShell) that owns this bar, when built via
|
|
65
|
+
# `host.add_toolbar()`. Lets shell-aware conveniences (add_sidebar_toggle)
|
|
66
|
+
# reach the sidebar; None for a standalone bar.
|
|
67
|
+
self._host = _host
|
|
68
|
+
if _toolbar is not None:
|
|
69
|
+
# Adoption: wrap a pre-built internal toolbar (e.g. an AppShell's
|
|
70
|
+
# titlebar band, which already carries its window controls + drag).
|
|
71
|
+
self._internal = _toolbar
|
|
72
|
+
self._parent = None
|
|
73
|
+
return
|
|
74
|
+
|
|
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
|
+
"button_variant": button_variant,
|
|
81
|
+
"density": density,
|
|
82
|
+
"show_window_controls": show_window_controls,
|
|
83
|
+
"draggable": draggable,
|
|
84
|
+
"show_border": show_border,
|
|
85
|
+
}
|
|
86
|
+
if surface is not None:
|
|
87
|
+
internal_kwargs["surface"] = surface
|
|
88
|
+
if padding is not None:
|
|
89
|
+
internal_kwargs["padding"] = padding
|
|
90
|
+
internal_kwargs.update(kwargs)
|
|
91
|
+
|
|
92
|
+
self._internal = _InternalToolbar(tk_master, **internal_kwargs)
|
|
93
|
+
self._attach_to_parent(layout_kw)
|
|
94
|
+
|
|
95
|
+
# ----- Properties -----
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def density(self) -> WidgetDensity:
|
|
99
|
+
"""Current density setting for toolbar items."""
|
|
100
|
+
return self._internal.density
|
|
101
|
+
|
|
102
|
+
@property
|
|
103
|
+
def button_variant(self) -> str:
|
|
104
|
+
"""Default variant applied to buttons added via `add_button()`."""
|
|
105
|
+
return self._internal._button_variant
|
|
106
|
+
|
|
107
|
+
# ----- Content -----
|
|
108
|
+
|
|
109
|
+
def add_button(
|
|
110
|
+
self,
|
|
111
|
+
label: str | None = None,
|
|
112
|
+
*,
|
|
113
|
+
icon: str | None = None,
|
|
114
|
+
on_click: Callable[[], Any] | None = None,
|
|
115
|
+
accent: AccentToken | str | None = None,
|
|
116
|
+
variant: ButtonVariant | None = None,
|
|
117
|
+
**kwargs: Any,
|
|
118
|
+
) -> "Button":
|
|
119
|
+
"""Add a button to the toolbar and return it for later control.
|
|
120
|
+
|
|
121
|
+
When both `label` and `icon` are given, the button shows text and icon
|
|
122
|
+
side by side. When only `icon` is given, the button is icon-only.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
label: Button label text.
|
|
126
|
+
icon: Icon name. When provided without `label`, renders icon-only.
|
|
127
|
+
on_click: Callback fired when the button is clicked.
|
|
128
|
+
accent: Color intent override.
|
|
129
|
+
variant: Variant override. When omitted, falls back to the toolbar's
|
|
130
|
+
`button_variant` (default `'ghost'`).
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
The :class:`~bootstack.Button` — use its live properties to control
|
|
134
|
+
it later, e.g. ``btn.disabled = True`` or ``btn.text = 'Saving…'``.
|
|
135
|
+
"""
|
|
136
|
+
from bootstack.widgets.button import Button
|
|
137
|
+
|
|
138
|
+
kw: dict[str, Any] = {
|
|
139
|
+
"text": label or "",
|
|
140
|
+
"variant": variant or self.button_variant,
|
|
141
|
+
"density": self.density,
|
|
142
|
+
"surface": self._internal.surface,
|
|
143
|
+
}
|
|
144
|
+
if icon is not None:
|
|
145
|
+
kw["icon"] = icon
|
|
146
|
+
if on_click is not None:
|
|
147
|
+
kw["on_click"] = on_click
|
|
148
|
+
if accent is not None:
|
|
149
|
+
kw["accent"] = accent
|
|
150
|
+
kw.update(kwargs) # explicit kwargs (incl. text=) win over the defaults
|
|
151
|
+
return Button(parent=self, **kw)
|
|
152
|
+
|
|
153
|
+
def add_menu(self, text: str, *, key: str | None = None) -> "MenuGroup":
|
|
154
|
+
"""Add a dropdown menu (File / Edit / …) as a toolbar item.
|
|
155
|
+
|
|
156
|
+
A menu is just another toolbar item. The returned builder is a context
|
|
157
|
+
manager, so the natural form reads::
|
|
158
|
+
|
|
159
|
+
with toolbar.add_menu("File") as file:
|
|
160
|
+
file.add_action("Open", shortcut="Mod+O", on_click=open_file)
|
|
161
|
+
file.add_divider()
|
|
162
|
+
file.add_action("Quit", shortcut="Mod+Q", on_click=app.close)
|
|
163
|
+
|
|
164
|
+
On Windows/Linux the menu renders as an in-window dropdown; on macOS it
|
|
165
|
+
is bridged to the native global menu bar (when the toolbar is part of a
|
|
166
|
+
window's chrome).
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
text: The menu's label (e.g. `'File'`).
|
|
170
|
+
key: Optional stable identifier; defaults to `text`.
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
The menu's `MenuGroup` builder (`add_action` / `add_check` /
|
|
174
|
+
`add_radio` / `add_divider`; usable as a context manager).
|
|
175
|
+
"""
|
|
176
|
+
return self._internal.add_menu(text, key=key)
|
|
177
|
+
|
|
178
|
+
def add_label(
|
|
179
|
+
self,
|
|
180
|
+
text: str | None = None,
|
|
181
|
+
*,
|
|
182
|
+
icon: str | None = None,
|
|
183
|
+
font: str | None = None,
|
|
184
|
+
**kwargs: Any,
|
|
185
|
+
) -> "Label":
|
|
186
|
+
"""Add a non-interactive label to the toolbar and return it.
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
text: Label text.
|
|
190
|
+
icon: Icon name displayed alongside the text.
|
|
191
|
+
font: Font token, e.g. `'heading-md'` or `'caption'`.
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
The :class:`~bootstack.Label` — use its live properties to update it
|
|
195
|
+
later, e.g. ``lbl.text = 'Updated'``.
|
|
196
|
+
"""
|
|
197
|
+
from bootstack.widgets.label import Label
|
|
198
|
+
|
|
199
|
+
kw: dict[str, Any] = {"text": text or "", "surface": self._internal.surface}
|
|
200
|
+
if icon is not None:
|
|
201
|
+
kw["icon"] = icon
|
|
202
|
+
if font is not None:
|
|
203
|
+
kw["font"] = font
|
|
204
|
+
kw.update(kwargs)
|
|
205
|
+
lbl = Label(parent=self, **kw)
|
|
206
|
+
# On a draggable bar (e.g. a titlebar) the label doubles as a drag handle.
|
|
207
|
+
self._internal._attach_drag(lbl._internal)
|
|
208
|
+
return lbl
|
|
209
|
+
|
|
210
|
+
def add_divider(self, length: int = 16) -> None:
|
|
211
|
+
"""Add a vertical divider.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
length: Divider height in pixels. Defaults to `16`.
|
|
215
|
+
"""
|
|
216
|
+
self._internal.add_separator(length=length)
|
|
217
|
+
|
|
218
|
+
def add_spacer(self) -> None:
|
|
219
|
+
"""Add a flexible spacer that pushes subsequent items to the right."""
|
|
220
|
+
self._internal.add_spacer()
|
|
221
|
+
|
|
222
|
+
def add_widget(self, widget_cls: Any, **kwargs: Any) -> Any:
|
|
223
|
+
"""Build a widget on the toolbar from its class.
|
|
224
|
+
|
|
225
|
+
Pass a widget **class** — the bar builds it, applying its own `density`
|
|
226
|
+
and `surface` (for any the class accepts) so the widget matches the rest
|
|
227
|
+
of the bar, and forwarding `kwargs` to the constructor (overriding those
|
|
228
|
+
defaults):
|
|
229
|
+
|
|
230
|
+
bar.add_widget(bs.ThemeToggle)
|
|
231
|
+
bar.add_widget(bs.TextField, placeholder="Search", width=24)
|
|
232
|
+
|
|
233
|
+
To add a widget you have already built yourself, parent it onto the bar
|
|
234
|
+
directly — it attaches automatically and keeps whatever you configured:
|
|
235
|
+
|
|
236
|
+
bs.MyCustomWidget(parent=bar)
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
widget_cls: The widget class to build on the bar.
|
|
240
|
+
**kwargs: Constructor arguments for the widget.
|
|
241
|
+
|
|
242
|
+
Returns:
|
|
243
|
+
The new widget instance.
|
|
244
|
+
"""
|
|
245
|
+
self._apply_bar_defaults(widget_cls, kwargs)
|
|
246
|
+
return widget_cls(parent=self, **kwargs)
|
|
247
|
+
|
|
248
|
+
def _apply_bar_defaults(self, widget_cls: type, kwargs: dict[str, Any]) -> None:
|
|
249
|
+
"""Default `density`/`surface` from the bar onto a class being built, but
|
|
250
|
+
only for parameters the widget actually accepts (and not if the caller
|
|
251
|
+
already passed them).
|
|
252
|
+
|
|
253
|
+
A widget is treated as accepting `density`/`surface` only when it declares
|
|
254
|
+
that parameter by name. A bare `**kwargs` catch-all does NOT count: in the
|
|
255
|
+
public layer `**kwargs` is the layout-placement passthrough, and several
|
|
256
|
+
widgets use it to *reject* unknown keys — injecting into those raises a
|
|
257
|
+
`TypeError`, and injecting into ones that ignore it is a silent no-op.
|
|
258
|
+
"""
|
|
259
|
+
import inspect
|
|
260
|
+
|
|
261
|
+
try:
|
|
262
|
+
params = inspect.signature(widget_cls).parameters
|
|
263
|
+
except (TypeError, ValueError):
|
|
264
|
+
return
|
|
265
|
+
if "density" in params:
|
|
266
|
+
kwargs.setdefault("density", self._internal.density)
|
|
267
|
+
if "surface" in params:
|
|
268
|
+
kwargs.setdefault("surface", self._internal.surface)
|
|
269
|
+
|
|
270
|
+
# ----- Container protocol (so `parent=toolbar` works) -----
|
|
271
|
+
|
|
272
|
+
def _child_master(self) -> Any:
|
|
273
|
+
return self._internal.content
|
|
274
|
+
|
|
275
|
+
def guide_layout(self, child: Any, **layout_kw: Any) -> None:
|
|
276
|
+
# A widget created with `parent=toolbar` is packed into the bar.
|
|
277
|
+
self._internal.add_widget(child._internal)
|
|
278
|
+
|
|
279
|
+
def __enter__(self) -> "Toolbar":
|
|
280
|
+
# A *scoping* context manager (like `menubar.add_menu`): `with
|
|
281
|
+
# window.add_toolbar() as tb:` reads naturally and hands back the handle,
|
|
282
|
+
# but it does NOT capture bare widgets — fill the bar with `add_button` /
|
|
283
|
+
# `add_menu` / `add_widget(Class, ...)` so each item inherits the bar's
|
|
284
|
+
# density and surface (a constructed-then-attached widget cannot).
|
|
285
|
+
return self
|
|
286
|
+
|
|
287
|
+
def __exit__(self, exc_type: Any, exc: Any, tb: Any) -> None:
|
|
288
|
+
return None
|
|
289
|
+
|
|
290
|
+
@property
|
|
291
|
+
def content(self) -> Any:
|
|
292
|
+
"""The bar's content frame — parent custom widgets here."""
|
|
293
|
+
return self._internal.content
|
|
294
|
+
|
|
295
|
+
def add_theme_toggle(self, **kwargs: Any) -> Any:
|
|
296
|
+
"""Add a `ThemeToggle` — a sun/moon button that flips the theme.
|
|
297
|
+
|
|
298
|
+
Args:
|
|
299
|
+
**kwargs: Forwarded to `ThemeToggle` — e.g. `variant`, `density`,
|
|
300
|
+
`accent`, `light_icon`, `dark_icon`.
|
|
301
|
+
|
|
302
|
+
Returns:
|
|
303
|
+
The created `ThemeToggle`.
|
|
304
|
+
"""
|
|
305
|
+
from bootstack.widgets.theme_toggle import ThemeToggle
|
|
306
|
+
|
|
307
|
+
self._apply_bar_defaults(ThemeToggle, kwargs)
|
|
308
|
+
return ThemeToggle(parent=self, **kwargs)
|
|
309
|
+
|
|
310
|
+
def add_sidebar_toggle(self, **kwargs: Any) -> Any:
|
|
311
|
+
"""Add a hamburger button that collapses/expands the AppShell sidebar.
|
|
312
|
+
|
|
313
|
+
Only meaningful on a toolbar built into an `AppShell` (via
|
|
314
|
+
`shell.add_toolbar()`); raises on an `App`/`Window` toolbar, which has no
|
|
315
|
+
sidebar. Place it wherever you like in the bar.
|
|
316
|
+
|
|
317
|
+
Args:
|
|
318
|
+
**kwargs: Forwarded to the toggle button — e.g. `collapse`
|
|
319
|
+
(`'hidden'`/`'compact'`), `icon`, `expand_icon`, `collapse_icon`,
|
|
320
|
+
`variant`, `density`, `accent`.
|
|
321
|
+
|
|
322
|
+
Returns:
|
|
323
|
+
The created sidebar-toggle button.
|
|
324
|
+
"""
|
|
325
|
+
from bootstack.widgets.sidebar_toggle import SidebarToggle
|
|
326
|
+
|
|
327
|
+
shell = self._sidebar_host()
|
|
328
|
+
self._apply_bar_defaults(SidebarToggle, kwargs)
|
|
329
|
+
return SidebarToggle(shell=shell, parent=self, **kwargs)
|
|
330
|
+
|
|
331
|
+
def _sidebar_host(self) -> Any:
|
|
332
|
+
"""Return the owning `AppShell`, or raise if this bar has no sidebar.
|
|
333
|
+
|
|
334
|
+
Restricted to `AppShell` — the single-tier sidebar host. A `Workbench`
|
|
335
|
+
(two-tier, rail + per-workspace sidebars) inherits the shell sidebar
|
|
336
|
+
methods but is intentionally not supported here.
|
|
337
|
+
"""
|
|
338
|
+
from bootstack.widgets.appshell import AppShell
|
|
339
|
+
|
|
340
|
+
host = getattr(self, "_host", None)
|
|
341
|
+
if not isinstance(host, AppShell):
|
|
342
|
+
raise BootstackError(
|
|
343
|
+
"add_sidebar_toggle() requires a toolbar built on an AppShell "
|
|
344
|
+
"(via shell.add_toolbar()); it is not supported on a Workbench or "
|
|
345
|
+
"a plain App/Window."
|
|
346
|
+
)
|
|
347
|
+
return host
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Literal
|
|
4
|
+
|
|
5
|
+
from bootstack.widgets._impl.composites.tooltip import ToolTip as _InternalToolTip
|
|
6
|
+
from bootstack.widgets._core.base import PublicWidgetBase
|
|
7
|
+
from bootstack.widgets.types import AccentToken, Anchor, Justify
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Tooltip:
|
|
11
|
+
"""A hover tooltip attached to a target widget.
|
|
12
|
+
|
|
13
|
+
The tooltip appears after `delay` milliseconds when the mouse enters the
|
|
14
|
+
target and disappears when the mouse leaves or clicks. Positioning follows
|
|
15
|
+
the mouse cursor by default; pass `anchor_point` and `window_point` to
|
|
16
|
+
pin the tooltip to a specific edge of the widget instead.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
target: Widget to attach the tooltip to. Accepts any bootstack widget.
|
|
20
|
+
When the target is a container, the tooltip shows while hovering
|
|
21
|
+
anywhere inside it, including over its children present at attach
|
|
22
|
+
time. For children added later, call `refresh_bindings()`.
|
|
23
|
+
text: Tooltip text content. Defaults to `''`.
|
|
24
|
+
delay: Milliseconds before the tooltip appears on mouse enter. Defaults
|
|
25
|
+
to `250`.
|
|
26
|
+
accent: Semantic color accent. Defaults to the theme's elevated
|
|
27
|
+
background color.
|
|
28
|
+
wrap_width: Maximum text width in pixels before wrapping. Defaults to
|
|
29
|
+
`None` (auto-scaled to approximately 300 px).
|
|
30
|
+
justify: Text alignment inside the tooltip. Defaults to `'left'`.
|
|
31
|
+
anchor_point: Anchor on the *target widget* the tooltip attaches to.
|
|
32
|
+
Defaults to `None` (tooltip follows the mouse).
|
|
33
|
+
window_point: Anchor on the *tooltip window* aligned to `anchor_point`.
|
|
34
|
+
Defaults to the opposite of `anchor_point` when it is set.
|
|
35
|
+
auto_flip: Keep the tooltip fully on screen. `True` flips both axes,
|
|
36
|
+
`False` disables flipping, `'vertical'` or `'horizontal'` restricts
|
|
37
|
+
flipping to one axis. Defaults to `True`.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def __init__(
|
|
41
|
+
self,
|
|
42
|
+
target: PublicWidgetBase | Any,
|
|
43
|
+
text: str = "",
|
|
44
|
+
*,
|
|
45
|
+
delay: int = 250,
|
|
46
|
+
accent: AccentToken | str | None = None,
|
|
47
|
+
wrap_width: int | None = None,
|
|
48
|
+
justify: Justify = "left",
|
|
49
|
+
anchor_point: Anchor | None = None,
|
|
50
|
+
window_point: Anchor | None = None,
|
|
51
|
+
auto_flip: bool | Literal["vertical", "horizontal"] = True,
|
|
52
|
+
) -> None:
|
|
53
|
+
tk_widget = target.tk if isinstance(target, PublicWidgetBase) else target
|
|
54
|
+
|
|
55
|
+
internal_kwargs: dict[str, Any] = {
|
|
56
|
+
"text": text,
|
|
57
|
+
"delay": delay,
|
|
58
|
+
"justify": justify,
|
|
59
|
+
"auto_flip": auto_flip,
|
|
60
|
+
}
|
|
61
|
+
if accent is not None:
|
|
62
|
+
internal_kwargs["accent"] = accent
|
|
63
|
+
if wrap_width is not None:
|
|
64
|
+
internal_kwargs["wraplength"] = wrap_width
|
|
65
|
+
if anchor_point is not None:
|
|
66
|
+
internal_kwargs["anchor_point"] = anchor_point
|
|
67
|
+
if window_point is not None:
|
|
68
|
+
internal_kwargs["window_point"] = window_point
|
|
69
|
+
|
|
70
|
+
self._internal = _InternalToolTip(tk_widget, **internal_kwargs)
|
|
71
|
+
|
|
72
|
+
def refresh_bindings(self) -> None:
|
|
73
|
+
"""Re-cover a container target's subtree after adding children to it.
|
|
74
|
+
|
|
75
|
+
A tooltip on a container covers the children present when it was
|
|
76
|
+
created. Add more children later and call this so hovering them shows
|
|
77
|
+
the tip too. Safe to call repeatedly — already-covered children are
|
|
78
|
+
left untouched.
|
|
79
|
+
"""
|
|
80
|
+
self._internal.refresh_bindings()
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def text(self) -> str:
|
|
84
|
+
"""The tooltip text. Assign to change it — a visible tooltip updates
|
|
85
|
+
immediately, and the next hover shows the new text."""
|
|
86
|
+
return self._internal._text
|
|
87
|
+
|
|
88
|
+
@text.setter
|
|
89
|
+
def text(self, value: str) -> None:
|
|
90
|
+
self._internal.text = value
|
|
91
|
+
|
|
92
|
+
def destroy(self) -> None:
|
|
93
|
+
"""Remove the tooltip and unbind all event handlers."""
|
|
94
|
+
self._internal.destroy()
|