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,273 @@
|
|
|
1
|
+
"""A workspace — one sidebar panel + content, filled by one provider.
|
|
2
|
+
|
|
3
|
+
A `Workspace` owns a single navigation provider (chosen lazily and mutually
|
|
4
|
+
exclusive) mounted into its own sidebar-panel frame and content frame. The shell
|
|
5
|
+
holds one workspace per rail icon (and an implicit default workspace for the
|
|
6
|
+
single-tier case). Selection truth lives in the shell's `NavModel`; the workspace
|
|
7
|
+
reports clicks/refreshes back to the shell, which drives the model.
|
|
8
|
+
|
|
9
|
+
The workspace exposes the same content API the shell does — `add_page` /
|
|
10
|
+
`list_nav` / `tree_nav` / `@detail` / headers — so a single-tier app and a
|
|
11
|
+
two-tier workspace are authored identically (the spec's degenerate-case
|
|
12
|
+
unification).
|
|
13
|
+
|
|
14
|
+
The sidebar has three authored shapes: a flat nav list (`add_page`), the same
|
|
15
|
+
list chunked by static `add_header` labels (grouped-static), or a data-bound
|
|
16
|
+
`list_nav` / `tree_nav`. There is no built-in collapsible accordion — a section
|
|
17
|
+
that hides/reveals a sub-list is a content concern; compose `bs.Accordion`
|
|
18
|
+
inside a custom `panel()` if you need one.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
from typing import Any, Callable
|
|
24
|
+
|
|
25
|
+
from bootstack.widgets._impl.composites.shell.providers import (
|
|
26
|
+
CustomProvider,
|
|
27
|
+
ListNavProvider,
|
|
28
|
+
StaticProvider,
|
|
29
|
+
TreeNavProvider,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Workspace:
|
|
34
|
+
"""One workspace's provider + sidebar-panel/content frames.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
key: Workspace identifier.
|
|
38
|
+
sidebar_panel: The frame this workspace's sidebar provider mounts into.
|
|
39
|
+
content_frame: The frame this workspace's content renders into.
|
|
40
|
+
nav_accent: Accent token for the active nav item.
|
|
41
|
+
on_select: Called `(workspace_key, page_key)` when an item is clicked.
|
|
42
|
+
on_refresh: Called `(workspace_key)` after a data-bound provider rebuilds.
|
|
43
|
+
on_first_page: Called `(workspace_key, page_key)` when the workspace's
|
|
44
|
+
first page is added (so the shell can auto-select it).
|
|
45
|
+
nav_variant: Static-item style variant for the tier (`'nav-pill'`
|
|
46
|
+
standalone, `'nav-quiet'` under a rail).
|
|
47
|
+
nav_surface: Surface token the sidebar items sit on.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def __init__(
|
|
51
|
+
self,
|
|
52
|
+
key: str,
|
|
53
|
+
sidebar_panel: Any,
|
|
54
|
+
content_frame: Any,
|
|
55
|
+
*,
|
|
56
|
+
nav_accent: str,
|
|
57
|
+
on_select: Callable[[str, str], None],
|
|
58
|
+
on_refresh: Callable[[str], None],
|
|
59
|
+
on_first_page: Callable[[str, str], None],
|
|
60
|
+
nav_variant: str = "nav-quiet",
|
|
61
|
+
nav_surface: str = "card",
|
|
62
|
+
nav_selection: str = "ghost",
|
|
63
|
+
) -> None:
|
|
64
|
+
self._key = key
|
|
65
|
+
self._sidebar = sidebar_panel
|
|
66
|
+
self._content = content_frame
|
|
67
|
+
self._nav_accent = nav_accent
|
|
68
|
+
self._nav_variant = nav_variant
|
|
69
|
+
self._nav_surface = nav_surface
|
|
70
|
+
self._nav_selection = nav_selection
|
|
71
|
+
self._on_select = on_select
|
|
72
|
+
self._on_refresh = on_refresh
|
|
73
|
+
self._on_first_page = on_first_page
|
|
74
|
+
self._provider: Any = None
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def key(self) -> str:
|
|
78
|
+
"""The workspace identifier."""
|
|
79
|
+
return self._key
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def provider(self) -> Any:
|
|
83
|
+
"""The workspace's navigation provider (or `None`)."""
|
|
84
|
+
return self._provider
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def supports_compact(self) -> bool:
|
|
88
|
+
"""Whether this workspace's provider can render icon-only (compact)."""
|
|
89
|
+
return bool(getattr(self._provider, "supports_compact", False))
|
|
90
|
+
|
|
91
|
+
def set_compact(self, compact: bool) -> None:
|
|
92
|
+
"""Render the sidebar icon-only (`compact`) or with labels."""
|
|
93
|
+
if self._provider is not None and hasattr(self._provider, "set_compact"):
|
|
94
|
+
self._provider.set_compact(compact)
|
|
95
|
+
|
|
96
|
+
# ----- Provider selection (lazy + mutually exclusive) -----
|
|
97
|
+
|
|
98
|
+
def _ensure_static(self) -> StaticProvider:
|
|
99
|
+
if self._provider is None:
|
|
100
|
+
self._provider = StaticProvider(
|
|
101
|
+
accent=self._nav_accent, variant=self._nav_variant,
|
|
102
|
+
surface=self._nav_surface, selection=self._nav_selection,
|
|
103
|
+
)
|
|
104
|
+
self._mount_provider()
|
|
105
|
+
elif not isinstance(self._provider, StaticProvider):
|
|
106
|
+
raise RuntimeError(
|
|
107
|
+
"cannot mix add_page() with a data-bound provider in one workspace"
|
|
108
|
+
)
|
|
109
|
+
return self._provider
|
|
110
|
+
|
|
111
|
+
def _claim_provider(self) -> None:
|
|
112
|
+
if self._provider is not None:
|
|
113
|
+
raise RuntimeError("this workspace already has a navigation provider")
|
|
114
|
+
|
|
115
|
+
def _mount_provider(self) -> None:
|
|
116
|
+
self._provider.mount(
|
|
117
|
+
self._sidebar,
|
|
118
|
+
self._content,
|
|
119
|
+
on_select=lambda page_key: self._on_select(self._key, page_key),
|
|
120
|
+
on_refresh=lambda: self._on_refresh(self._key),
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
# ----- Static content API -----
|
|
124
|
+
|
|
125
|
+
def page_nav(self, *, selection: str | None = None) -> StaticProvider:
|
|
126
|
+
"""Establish the static page-list provider; return it for authoring.
|
|
127
|
+
|
|
128
|
+
Called when the page nav is declared explicitly (so its options have a
|
|
129
|
+
home), before any `add_page`. `selection` overrides the selected-item
|
|
130
|
+
treatment (`'ghost'`/`'solid'`). `'solid'` is the standalone (`nav-pill`)
|
|
131
|
+
emphasis only — under a rail (`nav-quiet`) a solid fill competes with the
|
|
132
|
+
rail, so it is forced to the wash.
|
|
133
|
+
"""
|
|
134
|
+
if selection is not None:
|
|
135
|
+
self._nav_selection = selection if self._nav_variant == "nav-pill" else "ghost"
|
|
136
|
+
return self._ensure_static()
|
|
137
|
+
|
|
138
|
+
def add_page(self, key: str, *, text: str = "", icon=None, footer: bool = False) -> Any:
|
|
139
|
+
"""Add a nav item and its page; return the page frame."""
|
|
140
|
+
if not key:
|
|
141
|
+
raise ValueError("page key must be non-empty")
|
|
142
|
+
provider = self._ensure_static()
|
|
143
|
+
if key in provider.keys():
|
|
144
|
+
raise ValueError(f"duplicate page key: {key!r}")
|
|
145
|
+
first = not provider.keys()
|
|
146
|
+
page = provider.add_page(key, text=text, icon=icon, footer=footer)
|
|
147
|
+
if first:
|
|
148
|
+
self._on_first_page(self._key, key)
|
|
149
|
+
return page
|
|
150
|
+
|
|
151
|
+
def add_footer_page(self, key: str, *, text: str = "", icon=None) -> Any:
|
|
152
|
+
"""Add a nav item pinned to the sidebar footer and its page."""
|
|
153
|
+
return self.add_page(key, text=text, icon=icon, footer=True)
|
|
154
|
+
|
|
155
|
+
def add_header(self, text: str) -> Any:
|
|
156
|
+
"""Add a plain section-label header (grouped-static)."""
|
|
157
|
+
return self._ensure_static().add_header(text)
|
|
158
|
+
|
|
159
|
+
def add_divider(self) -> Any:
|
|
160
|
+
"""Add a divider to the sidebar."""
|
|
161
|
+
return self._ensure_static().add_divider()
|
|
162
|
+
|
|
163
|
+
# ----- Custom mode -----
|
|
164
|
+
|
|
165
|
+
def panel(self) -> Any:
|
|
166
|
+
"""Claim the workspace as a custom panel; return its sidebar container.
|
|
167
|
+
|
|
168
|
+
The workspace enters custom mode: no provider cascade, no compaction.
|
|
169
|
+
Fill the returned container with your own sidebar UI (e.g. a
|
|
170
|
+
`bs.Accordion`) and drive the content region yourself via `content`.
|
|
171
|
+
"""
|
|
172
|
+
self._claim_provider()
|
|
173
|
+
self._provider = CustomProvider()
|
|
174
|
+
self._mount_provider()
|
|
175
|
+
return self._provider.container
|
|
176
|
+
|
|
177
|
+
@property
|
|
178
|
+
def content(self) -> Any:
|
|
179
|
+
"""The workspace's content region frame (for hand-driven swapping)."""
|
|
180
|
+
return self._content
|
|
181
|
+
|
|
182
|
+
# ----- Data-bound content API -----
|
|
183
|
+
|
|
184
|
+
def list_nav(
|
|
185
|
+
self,
|
|
186
|
+
source: Any,
|
|
187
|
+
*,
|
|
188
|
+
separator: bool = False,
|
|
189
|
+
density: str = "default",
|
|
190
|
+
placeholder: str = "Select an item to view",
|
|
191
|
+
chevron: bool = False,
|
|
192
|
+
) -> Any:
|
|
193
|
+
"""Fill the workspace from a `DataSource` (flat master-detail).
|
|
194
|
+
|
|
195
|
+
Records render their `icon`/`title`/`text` in a recycling `ListView`
|
|
196
|
+
(which inherits the sidebar surface). `separator` draws divider lines
|
|
197
|
+
between rows (default flush); `density` is `'default'`/`'compact'`
|
|
198
|
+
(compact suits one-line items, default suits richer two-line items).
|
|
199
|
+
`placeholder` is the quiet empty-state shown in the content area when the
|
|
200
|
+
source has no records. `chevron` adds a per-row disclosure chevron (off by
|
|
201
|
+
default — use it only when a row visibly drills into something).
|
|
202
|
+
"""
|
|
203
|
+
self._claim_provider()
|
|
204
|
+
self._provider = ListNavProvider(
|
|
205
|
+
source, accent=self._nav_accent, separator=separator,
|
|
206
|
+
density=density, placeholder=placeholder, chevron=chevron,
|
|
207
|
+
)
|
|
208
|
+
self._mount_provider()
|
|
209
|
+
return self._provider
|
|
210
|
+
|
|
211
|
+
def tree_nav(
|
|
212
|
+
self,
|
|
213
|
+
*,
|
|
214
|
+
nodes: list | None = None,
|
|
215
|
+
source: Any = None,
|
|
216
|
+
parent_field: str = "parent_id",
|
|
217
|
+
label_field: str = "name",
|
|
218
|
+
icon_field: str = "icon",
|
|
219
|
+
density: str = "default",
|
|
220
|
+
placeholder: str = "Select an item to view",
|
|
221
|
+
) -> Any:
|
|
222
|
+
"""Fill the workspace from a hierarchy (tree master-detail).
|
|
223
|
+
|
|
224
|
+
`density` is `'default'`/`'compact'`. `placeholder` is the quiet
|
|
225
|
+
empty-state shown in the content area until a node is picked (a tree
|
|
226
|
+
opens unselected).
|
|
227
|
+
"""
|
|
228
|
+
self._claim_provider()
|
|
229
|
+
self._provider = TreeNavProvider(
|
|
230
|
+
nodes=nodes,
|
|
231
|
+
source=source,
|
|
232
|
+
parent_field=parent_field,
|
|
233
|
+
label_field=label_field,
|
|
234
|
+
icon_field=icon_field,
|
|
235
|
+
accent=self._nav_accent,
|
|
236
|
+
density=density,
|
|
237
|
+
placeholder=placeholder,
|
|
238
|
+
)
|
|
239
|
+
self._mount_provider()
|
|
240
|
+
return self._provider
|
|
241
|
+
|
|
242
|
+
def detail(self, fn: Callable[[dict], Any]) -> Callable[[dict], Any]:
|
|
243
|
+
"""Register the detail body for a data-bound provider (decorator)."""
|
|
244
|
+
if self._provider is None or not hasattr(self._provider, "set_detail"):
|
|
245
|
+
raise RuntimeError("detail() requires a data-bound provider (list_nav/tree_nav)")
|
|
246
|
+
self._provider.set_detail(fn)
|
|
247
|
+
if self._provider.keys():
|
|
248
|
+
self._on_first_page(self._key, self._provider.keys()[0])
|
|
249
|
+
return fn
|
|
250
|
+
|
|
251
|
+
# ----- Provider plumbing -----
|
|
252
|
+
|
|
253
|
+
def keys(self) -> tuple[str, ...]:
|
|
254
|
+
"""All selectable page/record keys this workspace exposes."""
|
|
255
|
+
return self._provider.keys() if self._provider is not None else ()
|
|
256
|
+
|
|
257
|
+
def show(self, key: str, data: dict | None = None) -> None:
|
|
258
|
+
"""Render content for `key` (delegates to the provider)."""
|
|
259
|
+
if self._provider is not None:
|
|
260
|
+
self._provider.show(key, data=data)
|
|
261
|
+
|
|
262
|
+
def select_visual(self, key: str | None) -> None:
|
|
263
|
+
"""Reflect the active `key` in the sidebar selection (delegates)."""
|
|
264
|
+
if self._provider is not None:
|
|
265
|
+
self._provider.select_visual(key)
|
|
266
|
+
|
|
267
|
+
def close(self) -> None:
|
|
268
|
+
"""Release provider resources (e.g. data-source subscriptions)."""
|
|
269
|
+
if self._provider is not None and hasattr(self._provider, "close"):
|
|
270
|
+
try:
|
|
271
|
+
self._provider.close()
|
|
272
|
+
except Exception:
|
|
273
|
+
pass
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Static navigation building blocks shared by the shell's nav panel.
|
|
2
|
+
|
|
3
|
+
The standalone `SideNav` widget was retired with the AppShell rewrite; what
|
|
4
|
+
remains are the two non-interactive primitives the shell's `NavPanel` composes:
|
|
5
|
+
|
|
6
|
+
- SideNavHeader: a non-selectable section label
|
|
7
|
+
- SideNavSeparator: a visual divider between groups
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from bootstack.widgets._impl.composites.sidenav.header import SideNavHeader
|
|
11
|
+
from bootstack.widgets._impl.composites.sidenav.separator import SideNavSeparator
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
'SideNavHeader',
|
|
15
|
+
'SideNavSeparator',
|
|
16
|
+
]
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""SideNavHeader widget for section labels in navigation menus."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from typing_extensions import TypedDict, Unpack
|
|
6
|
+
|
|
7
|
+
from bootstack.widgets._impl.primitives.frame import Frame
|
|
8
|
+
from bootstack.widgets._impl.primitives.label import Label
|
|
9
|
+
from bootstack.widgets._impl.mixins import configure_delegate
|
|
10
|
+
from bootstack.widgets.types import Master
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SideNavHeaderKwargs(TypedDict, total=False):
|
|
14
|
+
text: str
|
|
15
|
+
# Frame options
|
|
16
|
+
padding: Any
|
|
17
|
+
width: int
|
|
18
|
+
height: int
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class SideNavHeader(Frame):
|
|
22
|
+
"""A section header for grouping navigation items.
|
|
23
|
+
|
|
24
|
+
SideNavHeader provides a text label to identify groups of related
|
|
25
|
+
navigation items. Unlike SideNavItem, headers are not selectable
|
|
26
|
+
and serve only as visual labels — a quiet, non-interactive section
|
|
27
|
+
marker. Uses the 'label' font token for styling.
|
|
28
|
+
|
|
29
|
+
A header is always a plain label; it does not collapse its items (a
|
|
30
|
+
collapsible sub-list is a content concern — use a `bs.Accordion`).
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
DEFAULT_PADDING = (8, 20, 8, 4)
|
|
34
|
+
DEFAULT_FONT = 'label'
|
|
35
|
+
# Muted, not full-strength: a section header is quiet chrome that names a
|
|
36
|
+
# group — it should recede so the clickable items carry the contrast.
|
|
37
|
+
DEFAULT_ACCENT = 'muted'
|
|
38
|
+
|
|
39
|
+
def __init__(
|
|
40
|
+
self,
|
|
41
|
+
master: Master = None,
|
|
42
|
+
text: str = '',
|
|
43
|
+
**kwargs: Unpack[SideNavHeaderKwargs]
|
|
44
|
+
):
|
|
45
|
+
"""Initialize a SideNavHeader.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
master: Parent widget.
|
|
49
|
+
text: The header text to display.
|
|
50
|
+
**kwargs: Additional arguments passed to Frame.
|
|
51
|
+
"""
|
|
52
|
+
self._text = text
|
|
53
|
+
|
|
54
|
+
# Default padding: more top margin for visual separation between sections
|
|
55
|
+
kwargs.setdefault('padding', self.DEFAULT_PADDING)
|
|
56
|
+
|
|
57
|
+
super().__init__(master, **kwargs)
|
|
58
|
+
|
|
59
|
+
# Header label with 'label' font (smaller, bold) and secondary accent.
|
|
60
|
+
self._text_label = Label(
|
|
61
|
+
self,
|
|
62
|
+
text=text,
|
|
63
|
+
font=self.DEFAULT_FONT,
|
|
64
|
+
accent=self.DEFAULT_ACCENT,
|
|
65
|
+
anchor='w',
|
|
66
|
+
)
|
|
67
|
+
self._text_label.pack(fill='x')
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def text(self) -> str:
|
|
71
|
+
"""Get the header text."""
|
|
72
|
+
return self._text
|
|
73
|
+
|
|
74
|
+
@configure_delegate('text')
|
|
75
|
+
def _delegate_text(self, value: str = None):
|
|
76
|
+
"""Configure the header text."""
|
|
77
|
+
if value is None:
|
|
78
|
+
return self._text
|
|
79
|
+
self._text = value
|
|
80
|
+
self._text_label.configure(text=value)
|
|
81
|
+
return None
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""SideNavSeparator widget for visual grouping in navigation menus."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from typing_extensions import TypedDict, Unpack
|
|
6
|
+
|
|
7
|
+
from bootstack.widgets._impl.primitives.frame import Frame
|
|
8
|
+
from bootstack.widgets._impl.primitives.separator import Separator
|
|
9
|
+
from bootstack.widgets.types import Master
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SideNavSeparatorKwargs(TypedDict, total=False):
|
|
13
|
+
# Frame options
|
|
14
|
+
padding: Any
|
|
15
|
+
width: int
|
|
16
|
+
height: int
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class SideNavSeparator(Frame):
|
|
20
|
+
"""A visual separator for grouping items in a SideNav.
|
|
21
|
+
|
|
22
|
+
SideNavSeparator provides a horizontal line to visually separate
|
|
23
|
+
groups of navigation items. It is a thin wrapper around the Separator
|
|
24
|
+
primitive with appropriate padding for navigation contexts.
|
|
25
|
+
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
DEFAULT_PADDING = (0, 4, 0, 4)
|
|
29
|
+
|
|
30
|
+
def __init__(self, master: Master = None, **kwargs: Unpack[SideNavSeparatorKwargs]):
|
|
31
|
+
"""Initialize a SideNavSeparator.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
master: Parent widget.
|
|
35
|
+
**kwargs: Additional arguments passed to Frame.
|
|
36
|
+
"""
|
|
37
|
+
# Default padding for visual separation
|
|
38
|
+
kwargs.setdefault('padding', self.DEFAULT_PADDING)
|
|
39
|
+
|
|
40
|
+
super().__init__(master, **kwargs)
|
|
41
|
+
|
|
42
|
+
# Create horizontal separator
|
|
43
|
+
self._separator = Separator(self, orient='horizontal')
|
|
44
|
+
self._separator.pack(fill='x')
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"""Shared constants and PIL helpers for Slider and RangeSlider."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from PIL import Image, ImageDraw
|
|
5
|
+
from PIL.ImageTk import PhotoImage
|
|
6
|
+
|
|
7
|
+
# Focus-ring border thickness. Retired (0): the ring was never wired to an
|
|
8
|
+
# actual keyboard-focus style and its frozen color did not track theme changes,
|
|
9
|
+
# so it only ever showed as a stale 1-2px border. Kept as a named constant so
|
|
10
|
+
# the geometry math (`cross_size + HL * 2`) and the highlight kwargs stay valid.
|
|
11
|
+
HL = 0
|
|
12
|
+
|
|
13
|
+
HANDLE_SIZE = 22 # handle diameter (px) — matches slider-handle.png at standard DPI
|
|
14
|
+
TRACK_H = 6 # track height (px)
|
|
15
|
+
|
|
16
|
+
# Value badge geometry
|
|
17
|
+
BADGE_H = 22
|
|
18
|
+
BADGE_PAD_X = 8
|
|
19
|
+
BADGE_GAP = 5 # gap between badge bottom and handle top
|
|
20
|
+
BADGE_FONT_SIZE = 9
|
|
21
|
+
|
|
22
|
+
# Tick geometry
|
|
23
|
+
TICK_GAP = 4
|
|
24
|
+
MAJOR_TICK_H = 8
|
|
25
|
+
MINOR_TICK_H = 4
|
|
26
|
+
LABEL_GAP = 3
|
|
27
|
+
LABEL_FONT_SIZE = 9
|
|
28
|
+
|
|
29
|
+
# Keyboard step sizes
|
|
30
|
+
STEP = 1
|
|
31
|
+
STEP_LARGE = 10
|
|
32
|
+
|
|
33
|
+
# Keyboard focus ring (RangeSlider: a halo around the active handle)
|
|
34
|
+
FOCUS_RING_GAP = 2 # gap between the handle edge and the ring
|
|
35
|
+
FOCUS_RING_W = 2 # ring stroke width
|
|
36
|
+
HALO_PAD = FOCUS_RING_GAP + FOCUS_RING_W # ring overhang beyond the handle,
|
|
37
|
+
# reserved on every side so it can't clip
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _make_focus_ring(diameter: int, color: str, width: int = FOCUS_RING_W) -> PhotoImage:
|
|
41
|
+
"""Render a smooth anti-aliased ring (transparent center) as a PhotoImage —
|
|
42
|
+
the keyboard focus halo drawn around the active handle.
|
|
43
|
+
|
|
44
|
+
Drawn as a filled outer disc with the center punched out (an annulus) at 4x
|
|
45
|
+
and LANCZOS-downsampled, which anti-aliases far better than a thin outline
|
|
46
|
+
stroke at the target size.
|
|
47
|
+
"""
|
|
48
|
+
scale = 4
|
|
49
|
+
s = diameter * scale
|
|
50
|
+
margin = scale # 1px transparent margin so the outer edge
|
|
51
|
+
# isn't clipped at the image boundary
|
|
52
|
+
img = Image.new("RGBA", (s, s), (0, 0, 0, 0))
|
|
53
|
+
d = ImageDraw.Draw(img)
|
|
54
|
+
d.ellipse([margin, margin, s - 1 - margin, s - 1 - margin], fill=color)
|
|
55
|
+
inset = margin + width * scale # ring thickness = width (post-downsample)
|
|
56
|
+
d.ellipse([inset, inset, s - 1 - inset, s - 1 - inset], fill=(0, 0, 0, 0))
|
|
57
|
+
return PhotoImage(image=img.resize((diameter, diameter), Image.Resampling.LANCZOS))
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _make_badge(width: int, height: int, bg_color: str) -> PhotoImage:
|
|
61
|
+
"""Render an anti-aliased pill badge background as a PhotoImage."""
|
|
62
|
+
sw, sh = width * 2, height * 2
|
|
63
|
+
r = sh // 2
|
|
64
|
+
img = Image.new("RGBA", (sw, sh), (0, 0, 0, 0))
|
|
65
|
+
d = ImageDraw.Draw(img)
|
|
66
|
+
d.rounded_rectangle([0, 0, sw - 1, sh - 1], radius=r, fill=bg_color)
|
|
67
|
+
return PhotoImage(image=img.resize((width, height), Image.Resampling.LANCZOS))
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _make_handle(fill_color: str, ring_color: str, border_color: str) -> PhotoImage:
|
|
71
|
+
"""Recolor the slider-handle asset and return a PhotoImage.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
fill_color: Accent color for the handle center (magenta channel).
|
|
75
|
+
ring_color: Surface color for the outer ring (white channel).
|
|
76
|
+
border_color: Neutral stroke around the outer edge (black channel).
|
|
77
|
+
"""
|
|
78
|
+
from bootstack.style.utility import recolor_element_image
|
|
79
|
+
result = recolor_element_image(
|
|
80
|
+
'slider_handle',
|
|
81
|
+
white_color=ring_color,
|
|
82
|
+
black_color=border_color,
|
|
83
|
+
magenta_color=fill_color,
|
|
84
|
+
)
|
|
85
|
+
return result.image
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _make_track(
|
|
89
|
+
track_w: int,
|
|
90
|
+
track_h: int,
|
|
91
|
+
fill_start: int,
|
|
92
|
+
fill_end: int,
|
|
93
|
+
trough_color: str,
|
|
94
|
+
fill_color: str,
|
|
95
|
+
*,
|
|
96
|
+
vertical: bool = False,
|
|
97
|
+
) -> PhotoImage | None:
|
|
98
|
+
"""Render a slider track (trough + fill region) as a PhotoImage.
|
|
99
|
+
|
|
100
|
+
When `vertical=True` the image is rotated 90° CCW so the fill region
|
|
101
|
+
appears at the bottom of the returned (tall) image.
|
|
102
|
+
"""
|
|
103
|
+
if track_w < 1:
|
|
104
|
+
return None
|
|
105
|
+
# Render at 2× for crisp anti-aliased edges, then LANCZOS-downsample
|
|
106
|
+
scale = 2
|
|
107
|
+
sw, sh = track_w * scale, track_h * scale
|
|
108
|
+
r = sh // 2
|
|
109
|
+
img = Image.new("RGBA", (sw, sh), (0, 0, 0, 0))
|
|
110
|
+
d = ImageDraw.Draw(img)
|
|
111
|
+
d.rounded_rectangle([0, 0, sw - 1, sh - 1], radius=r, fill=trough_color)
|
|
112
|
+
fw = (fill_end - fill_start) * scale
|
|
113
|
+
if fw >= 1:
|
|
114
|
+
d.rounded_rectangle(
|
|
115
|
+
[fill_start * scale, 0, min(fill_end * scale, sw) - 1, sh - 1],
|
|
116
|
+
radius=r, fill=fill_color,
|
|
117
|
+
)
|
|
118
|
+
img = img.resize((track_w, track_h), Image.Resampling.LANCZOS)
|
|
119
|
+
if vertical:
|
|
120
|
+
img = img.rotate(90, expand=True)
|
|
121
|
+
return PhotoImage(image=img)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def get_widget_bg(widget: object) -> str | None:
|
|
125
|
+
"""Return a widget's rendered background as a `#rrggbb` hex string.
|
|
126
|
+
|
|
127
|
+
Tries `cget('background')` first (tk widgets), then falls back to the
|
|
128
|
+
ttk Style lookup (ttk widgets). Returns `None` when neither works.
|
|
129
|
+
"""
|
|
130
|
+
if widget is None:
|
|
131
|
+
return None
|
|
132
|
+
# tk.Frame, tk.Canvas, etc. — direct option
|
|
133
|
+
try:
|
|
134
|
+
raw = widget.cget('background') # type: ignore[union-attr]
|
|
135
|
+
if raw:
|
|
136
|
+
r, g, b = widget.winfo_rgb(raw) # type: ignore[union-attr]
|
|
137
|
+
return f"#{r // 256:02x}{g // 256:02x}{b // 256:02x}"
|
|
138
|
+
except Exception:
|
|
139
|
+
pass
|
|
140
|
+
# ttk.Frame and bootstack Frame — resolve via style system
|
|
141
|
+
try:
|
|
142
|
+
from tkinter import ttk
|
|
143
|
+
sname = widget.cget('style') or '' # type: ignore[union-attr]
|
|
144
|
+
style = ttk.Style(widget) # type: ignore[arg-type]
|
|
145
|
+
bg = style.lookup(sname, 'background') if sname else None
|
|
146
|
+
if not bg:
|
|
147
|
+
wclass = widget.winfo_class() # type: ignore[union-attr]
|
|
148
|
+
bg = style.lookup(wclass, 'background')
|
|
149
|
+
if bg:
|
|
150
|
+
r, g, b = widget.winfo_rgb(bg) # type: ignore[union-attr]
|
|
151
|
+
return f"#{r // 256:02x}{g // 256:02x}{b // 256:02x}"
|
|
152
|
+
except Exception:
|
|
153
|
+
pass
|
|
154
|
+
return None
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def resolve_colors(accent: str, surface: str, bg_override: str | None = None) -> dict[str, str]:
|
|
158
|
+
"""Resolve all slider colors from theme tokens.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
accent: Accent token (e.g., `'primary'`, `'success'`).
|
|
162
|
+
surface: Surface token (e.g., `'content'`, `'card'`).
|
|
163
|
+
bg_override: Explicit hex background color (`'#rrggbb'`) to use
|
|
164
|
+
instead of the theme default. Pass the result of
|
|
165
|
+
`get_widget_bg(master)` to make the slider blend with its
|
|
166
|
+
container surface.
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
Dict of resolved hex color strings.
|
|
170
|
+
"""
|
|
171
|
+
from bootstack.style.style import get_style
|
|
172
|
+
b = get_style().style_builder
|
|
173
|
+
bg = bg_override or b.color('background')
|
|
174
|
+
accent_hex = b.color(accent)
|
|
175
|
+
border = b.border(bg)
|
|
176
|
+
# A muted tone with guaranteed ≥4.5 contrast against the slider's own surface.
|
|
177
|
+
# (NOT `foreground[muted]`, which computes muted *relative to the foreground*
|
|
178
|
+
# and so lands near the background — faint everywhere, invisible in e.g.
|
|
179
|
+
# solarized-light. `muted_foreground(bg)` is the same value the public
|
|
180
|
+
# `'muted'` token resolves to.)
|
|
181
|
+
muted = b.muted_foreground(bg)
|
|
182
|
+
return {
|
|
183
|
+
'bg': bg,
|
|
184
|
+
'trough': border,
|
|
185
|
+
'fill': accent_hex,
|
|
186
|
+
'fill_disabled': muted,
|
|
187
|
+
'handle': accent_hex,
|
|
188
|
+
'handle_disabled': muted,
|
|
189
|
+
'ring': bg,
|
|
190
|
+
'handle_border': border,
|
|
191
|
+
'badge_bg': b.color('foreground'),
|
|
192
|
+
'badge_text': b.on_color(b.color('foreground')),
|
|
193
|
+
'tick': muted,
|
|
194
|
+
'focus': b.color('foreground'),
|
|
195
|
+
}
|